diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index bedb84b4be8..bd4ec34cde0 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -826,10 +826,14 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file,
#endif
#ifdef DEV_ENABLED
-#define DEV_CHECK_ONCE(m_cond) \
- if (unlikely(!(m_cond))) { \
- ERR_PRINT_ONCE("DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \
- } else \
+#define DEV_CHECK_ONCE(m_cond) \
+ if (true) { \
+ static bool first_print = true; \
+ if (first_print && unlikely(!(m_cond))) { \
+ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \
+ first_print = false; \
+ } \
+ } else \
((void)0)
#else
#define DEV_CHECK_ONCE(m_cond)
diff --git a/core/templates/interpolated_property.cpp b/core/templates/interpolated_property.cpp
new file mode 100644
index 00000000000..556ca0bacad
--- /dev/null
+++ b/core/templates/interpolated_property.cpp
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* interpolated_property.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "interpolated_property.h"
+
+#include "core/math/vector2.h"
+
+namespace InterpolatedPropertyFuncs {
+
+float lerp(float p_a, float p_b, float p_fraction) {
+ return Math::lerp(p_a, p_b, p_fraction);
+}
+
+double lerp(double p_a, double p_b, float p_fraction) {
+ return Math::lerp(p_a, p_b, (double)p_fraction);
+}
+
+Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction) {
+ return p_a.lerp(p_b, p_fraction);
+}
+
+} //namespace InterpolatedPropertyFuncs
diff --git a/core/templates/interpolated_property.h b/core/templates/interpolated_property.h
new file mode 100644
index 00000000000..d55c1f8d6a4
--- /dev/null
+++ b/core/templates/interpolated_property.h
@@ -0,0 +1,107 @@
+/**************************************************************************/
+/* interpolated_property.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+struct Vector2;
+
+namespace InterpolatedPropertyFuncs {
+float lerp(float p_a, float p_b, float p_fraction);
+double lerp(double p_a, double p_b, float p_fraction);
+Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction);
+} //namespace InterpolatedPropertyFuncs
+
+// This class is intended to reduce the boiler plate involved to
+// support custom properties to be physics interpolated.
+
+template
+class InterpolatedProperty {
+ // Only needs interpolating / updating the servers when
+ // curr and prev are different.
+ bool _needs_interpolating = false;
+ T _interpolated;
+ T curr;
+ T prev;
+
+public:
+ // FTI depends on the constant flow between current values
+ // (on the current tick) and stored previous values (on the previous tick).
+ // These should be updated both on each tick, and also on resets.
+ void pump() {
+ prev = curr;
+ _needs_interpolating = false;
+ }
+ void reset() { pump(); }
+
+ void set_interpolated_value(const T &p_val) {
+ _interpolated = p_val;
+ }
+ const T &interpolated() const { return _interpolated; }
+ bool needs_interpolating() const { return _needs_interpolating; }
+
+ bool interpolate(float p_interpolation_fraction) {
+ if (_needs_interpolating) {
+ _interpolated = InterpolatedPropertyFuncs::lerp(prev, curr, p_interpolation_fraction);
+ return true;
+ }
+ return false;
+ }
+
+ operator T() const {
+ return curr;
+ }
+
+ bool operator==(const T &p_o) const {
+ return p_o == curr;
+ }
+
+ bool operator!=(const T &p_o) const {
+ return p_o != curr;
+ }
+
+ InterpolatedProperty &operator=(T p_val) {
+ curr = p_val;
+ _interpolated = p_val;
+ _needs_interpolating = true;
+ return *this;
+ }
+ InterpolatedProperty(T p_val) {
+ curr = p_val;
+ _interpolated = p_val;
+ pump();
+ }
+ InterpolatedProperty() {
+ // Ensure either the constructor is run,
+ // or the memory is zeroed if using a fundamental type.
+ _interpolated = T{};
+ curr = T{};
+ prev = T{};
+ }
+};
diff --git a/doc/classes/BoneAttachment3D.xml b/doc/classes/BoneAttachment3D.xml
index 1e2f3089776..7ddded94102 100644
--- a/doc/classes/BoneAttachment3D.xml
+++ b/doc/classes/BoneAttachment3D.xml
@@ -59,5 +59,6 @@
Whether the BoneAttachment3D node will override the bone pose of the bone it is attached to. When set to [code]true[/code], the BoneAttachment3D node can change the pose of the bone. When set to [code]false[/code], the BoneAttachment3D will always be set to the bone's transform.
[b]Note:[/b] This override performs interruptively in the skeleton update process using signals due to the old design. It may cause unintended behavior when used at the same time with [SkeletonModifier3D].
+
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 820563af0e8..00ecdde81f3 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -1931,14 +1931,6 @@
Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties.
-
-
-
-
- Prevents physics interpolation for the current physics tick.
- This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location.
-
-
@@ -1980,14 +1972,6 @@
If [code]true[/code], ignores both frustum and occlusion culling on the specified 3D geometry instance. This is not the same as [member GeometryInstance3D.ignore_occlusion_culling], which only ignores occlusion culling and leaves frustum culling intact.
-
-
-
-
-
- Turns on and off physics interpolation for the instance.
-
-
diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml
index 621f32de6da..dee096a52e6 100644
--- a/doc/classes/VehicleWheel3D.xml
+++ b/doc/classes/VehicleWheel3D.xml
@@ -64,6 +64,7 @@
[b]Note:[/b] The simulation does not take the effect of gears into account, you will need to add logic for this if you wish to simulate gears.
A negative value will result in the wheel reversing.
+
The steering angle for the wheel, in radians. Setting this to a non-zero value will result in the vehicle turning when it's moving.
diff --git a/misc/extension_api_validation/4.4-stable.expected b/misc/extension_api_validation/4.4-stable.expected
index 0c0e90c53c5..55b6156a9c6 100644
--- a/misc/extension_api_validation/4.4-stable.expected
+++ b/misc/extension_api_validation/4.4-stable.expected
@@ -82,3 +82,11 @@ Validate extension JSON: Error: Field 'classes/TextServerExtension/methods/_shap
Validate extension JSON: Error: Field 'classes/TextServerExtension/methods/_shaped_text_draw_outline/arguments': size changed value in new API, from 7 to 8.
Optional "oversmpling" argument added. Compatibility methods registered.
+
+
+GH-104269
+---------
+Validate extension JSON: API was removed: classes/RenderingServer/methods/instance_set_interpolated
+Validate extension JSON: API was removed: classes/RenderingServer/methods/instance_reset_physics_interpolation
+
+Functionality moved out of server.
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 59647457d83..68aa8173ad3 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -366,6 +366,7 @@ void BoneAttachment3D::notify_rebind_required() {
#endif // TOOLS_ENABLED
BoneAttachment3D::BoneAttachment3D() {
+ set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF);
}
void BoneAttachment3D::_bind_methods() {
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 386711598a4..9e3c445c65d 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -41,6 +41,64 @@ void Camera3D::_request_camera_update() {
_update_camera();
}
+void Camera3D::fti_pump_property() {
+ switch (mode) {
+ default:
+ break;
+ case PROJECTION_PERSPECTIVE: {
+ fov.pump();
+ } break;
+ case PROJECTION_ORTHOGONAL: {
+ size.pump();
+ } break;
+ case PROJECTION_FRUSTUM: {
+ size.pump();
+ frustum_offset.pump();
+ } break;
+ }
+ _near.pump();
+ _far.pump();
+
+ Node3D::fti_pump_property();
+}
+
+void Camera3D::fti_update_servers_property() {
+ if (camera.is_valid()) {
+ float f = Engine::get_singleton()->get_physics_interpolation_fraction();
+
+ switch (mode) {
+ default:
+ break;
+ case PROJECTION_PERSPECTIVE: {
+ // If there have been changes due to interpolation, update the servers.
+ if (fov.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
+ RS::get_singleton()->camera_set_perspective(camera, fov.interpolated(), _near.interpolated(), _far.interpolated());
+ }
+ } break;
+ case PROJECTION_ORTHOGONAL: {
+ if (size.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
+ RS::get_singleton()->camera_set_orthogonal(camera, size.interpolated(), _near.interpolated(), _far.interpolated());
+ }
+ } break;
+ case PROJECTION_FRUSTUM: {
+ if (size.interpolate(f) || frustum_offset.interpolate(f) || _near.interpolate(f) || _far.interpolate(f)) {
+ RS::get_singleton()->camera_set_frustum(camera, size.interpolated(), frustum_offset.interpolated(), _near.interpolated(), _far.interpolated());
+ }
+ } break;
+ }
+ }
+
+ Node3D::fti_update_servers_property();
+}
+
+void Camera3D::fti_update_servers_xform() {
+ if (camera.is_valid()) {
+ Transform3D tr = _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
+ RS::get_singleton()->camera_set_transform(camera, tr);
+ }
+ Node3D::fti_update_servers_xform();
+}
+
void Camera3D::_update_camera_mode() {
force_change = true;
switch (mode) {
@@ -55,6 +113,7 @@ void Camera3D::_update_camera_mode() {
set_frustum(size, frustum_offset, _near, _far);
} break;
}
+ fti_notify_node_changed(false);
}
void Camera3D::_validate_property(PropertyInfo &p_property) const {
@@ -92,12 +151,8 @@ void Camera3D::_update_camera() {
if (!is_physics_interpolated_and_enabled()) {
RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
} else {
- // Ideally we shouldn't be moving a physics interpolated camera within a frame,
- // because it will break smooth interpolation, but it may occur on e.g. level load.
- if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) {
- _physics_interpolation_ensure_transform_calculated(true);
- RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
- }
+ // Force a refresh next frame.
+ fti_notify_node_changed();
}
if (is_part_of_edited_scene() || !is_current()) {
@@ -111,40 +166,6 @@ void Camera3D::_physics_interpolated_changed() {
_update_process_mode();
}
-void Camera3D::_physics_interpolation_ensure_data_flipped() {
- // The curr -> previous update can either occur
- // on the INTERNAL_PHYSICS_PROCESS OR
- // on NOTIFICATION_TRANSFORM_CHANGED,
- // if NOTIFICATION_TRANSFORM_CHANGED takes place
- // earlier than INTERNAL_PHYSICS_PROCESS on a tick.
- // This is to ensure that the data keeps flowing, but the new data
- // doesn't overwrite before prev has been set.
-
- // Keep the data flowing.
- uint64_t tick = Engine::get_singleton()->get_physics_frames();
- if (_interpolation_data.last_update_physics_tick != tick) {
- _interpolation_data.xform_prev = _interpolation_data.xform_curr;
- _interpolation_data.last_update_physics_tick = tick;
- physics_interpolation_flip_data();
- }
-}
-
-void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const {
- DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame());
-
- InterpolationData &id = _interpolation_data;
- uint64_t frame = Engine::get_singleton()->get_frames_drawn();
-
- if (id.last_update_frame != frame || p_force) {
- id.last_update_frame = frame;
-
- TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction());
-
- Transform3D &tr = id.camera_xform_interpolated;
- tr = _get_adjusted_camera_transform(id.xform_interpolated);
- }
-}
-
void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) {
_desired_process_internal = p_process_internal;
_desired_physics_process_internal = p_physics_process_internal;
@@ -152,17 +173,8 @@ void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics
}
void Camera3D::_update_process_mode() {
- bool process = _desired_process_internal;
- bool physics_process = _desired_physics_process_internal;
-
- if (is_physics_interpolated_and_enabled()) {
- if (is_current()) {
- process = true;
- physics_process = true;
- }
- }
- set_process_internal(process);
- set_physics_process_internal(physics_process);
+ set_process_internal(_desired_process_internal);
+ set_physics_process_internal(_desired_physics_process_internal);
}
void Camera3D::_notification(int p_what) {
@@ -186,60 +198,18 @@ void Camera3D::_notification(int p_what) {
#endif
} break;
- case NOTIFICATION_INTERNAL_PROCESS: {
- if (is_physics_interpolated_and_enabled() && camera.is_valid()) {
- _physics_interpolation_ensure_transform_calculated();
-
-#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
- print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
-#endif
-
- RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
- }
- } break;
-
- case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
- if (is_physics_interpolated_and_enabled()) {
- _physics_interpolation_ensure_data_flipped();
- _interpolation_data.xform_curr = get_global_transform();
- }
- } break;
-
case NOTIFICATION_TRANSFORM_CHANGED: {
- if (is_physics_interpolated_and_enabled()) {
- _physics_interpolation_ensure_data_flipped();
- _interpolation_data.xform_curr = get_global_transform();
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (is_physics_interpolated_and_enabled()) {
if (!Engine::get_singleton()->is_in_physics_frame()) {
PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process");
}
-#endif
}
+#endif
_request_camera_update();
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->update_position(get_global_transform().origin);
}
- // Allow auto-reset when first adding to the tree, as a convenience.
- if (_is_physics_interpolation_reset_requested() && is_inside_tree()) {
- _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
- _set_physics_interpolation_reset_requested(false);
- }
- } break;
-
- case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
- if (is_inside_tree()) {
- _interpolation_data.xform_curr = get_global_transform();
- _interpolation_data.xform_prev = _interpolation_data.xform_curr;
- _update_process_mode();
- }
- } break;
-
- case NOTIFICATION_SUSPENDED:
- case NOTIFICATION_PAUSED: {
- if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) {
- _physics_interpolation_ensure_transform_calculated(true);
- RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
- }
} break;
case NOTIFICATION_EXIT_WORLD: {
@@ -289,8 +259,7 @@ Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform)
Transform3D Camera3D::get_camera_transform() const {
if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) {
- _physics_interpolation_ensure_transform_calculated();
- return _interpolation_data.camera_xform_interpolated;
+ return _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
}
return _get_adjusted_camera_transform(get_global_transform());
diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h
index a015d413736..7fd88f412ed 100644
--- a/scene/3d/camera_3d.h
+++ b/scene/3d/camera_3d.h
@@ -30,6 +30,7 @@
#pragma once
+#include "core/templates/interpolated_property.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/velocity_tracker_3d.h"
#include "scene/resources/camera_attributes.h"
@@ -64,12 +65,12 @@ private:
ProjectionType mode = PROJECTION_PERSPECTIVE;
- real_t fov = 75.0;
- real_t size = 1.0;
- Vector2 frustum_offset;
+ InterpolatedProperty fov = 75.0;
+ InterpolatedProperty size = 1.0;
+ InterpolatedProperty frustum_offset;
// _ prefix to avoid conflict with Windows defines.
- real_t _near = 0.05;
- real_t _far = 4000.0;
+ InterpolatedProperty _near = 0.05;
+ InterpolatedProperty _far = 4000.0;
real_t v_offset = 0.0;
real_t h_offset = 0.0;
KeepAspect keep_aspect = KEEP_HEIGHT;
@@ -99,25 +100,10 @@ private:
Vector pyramid_shape_points;
#endif // PHYSICS_3D_DISABLED
- ///////////////////////////////////////////////////////
- // INTERPOLATION FUNCTIONS
- void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const;
- void _physics_interpolation_ensure_data_flipped();
-
- // These can be set by derived Camera3Ds, if they wish to do processing
- // (while still allowing physics interpolation to function).
+ // These can be set by derived Cameras.
bool _desired_process_internal = false;
bool _desired_physics_process_internal = false;
- mutable struct InterpolationData {
- Transform3D xform_curr;
- Transform3D xform_prev;
- Transform3D xform_interpolated;
- Transform3D camera_xform_interpolated; // After modification according to camera type.
- uint32_t last_update_physics_tick = 0;
- uint32_t last_update_frame = UINT32_MAX;
- } _interpolation_data;
-
void _update_process_mode();
protected:
@@ -125,9 +111,6 @@ protected:
// This is because physics interpolation may need to request process modes additionally.
void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal);
- // Opportunity for derived classes to interpolate extra attributes.
- virtual void physics_interpolation_flip_data() {}
-
virtual void _physics_interpolated_changed() override;
virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const;
///////////////////////////////////////////////////////
@@ -136,6 +119,10 @@ protected:
virtual void _request_camera_update();
void _update_camera_mode();
+ virtual void fti_pump_property() override;
+ virtual void fti_update_servers_property() override;
+ virtual void fti_update_servers_xform() override;
+
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 2d47c053fe7..6f941f42e96 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -738,7 +738,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
Transform3D emission_xform;
Basis velocity_xform;
if (!local_coords) {
- emission_xform = get_global_transform();
+ emission_xform = get_global_transform_interpolated();
velocity_xform = emission_xform.basis;
}
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 4d8037b1406..50537139031 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -126,7 +126,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
callable_mp(this, &Node3D::_propagate_transform_changed_deferred).call_deferred();
}
}
- _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM);
+ _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM | DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
}
void Node3D::_notification(int p_what) {
@@ -164,16 +164,40 @@ void Node3D::_notification(int p_what) {
}
}
- _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene.
+ _set_dirty_bits(DIRTY_GLOBAL_TRANSFORM | DIRTY_GLOBAL_INTERPOLATED_TRANSFORM); // Global is always dirty upon entering a scene.
_notify_dirty();
notification(NOTIFICATION_ENTER_WORLD);
_update_visibility_parent(true);
+
+ if (is_inside_tree() && get_tree()->is_physics_interpolation_enabled()) {
+ // Always reset FTI when entering tree and update the servers,
+ // both for interpolated and non-interpolated nodes,
+ // to ensure the server xforms are up to date.
+ fti_pump_xform();
+
+ // No need to interpolate as we are doing a reset.
+ data.global_transform_interpolated = get_global_transform();
+
+ // Make sure servers are up to date.
+ fti_update_servers_xform();
+
+ // As well as a reset based on the transform when adding, the user may change
+ // the transform during the first tick / frame, and we want to reset automatically
+ // at the end of the frame / tick (unless the user manually called `reset_physics_interpolation()`).
+ if (is_physics_interpolated()) {
+ get_tree()->get_scene_tree_fti().node_3d_request_reset(this);
+ }
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_MAIN_THREAD_GUARD;
+ if (is_inside_tree()) {
+ get_tree()->get_scene_tree_fti().node_3d_notify_delete(this);
+ }
+
notification(NOTIFICATION_EXIT_WORLD, true);
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
@@ -240,6 +264,18 @@ void Node3D::_notification(int p_what) {
if (data.client_physics_interpolation_data) {
data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
}
+
+ // In most cases, nodes derived from Node3D will have to already have reset code available for SceneTreeFTI,
+ // so it makes sense for them to reuse this method rather than respond individually to NOTIFICATION_RESET_PHYSICS_INTERPOLATION,
+ // unless they need to perform specific tasks (like changing process modes).
+ fti_pump_xform();
+ fti_pump_property();
+ } break;
+ case NOTIFICATION_SUSPENDED:
+ case NOTIFICATION_PAUSED: {
+ if (is_physics_interpolated_and_enabled()) {
+ data.local_transform_prev = get_transform();
+ }
} break;
}
}
@@ -251,6 +287,7 @@ void Node3D::set_basis(const Basis &p_basis) {
}
void Node3D::set_quaternion(const Quaternion &p_quaternion) {
ERR_THREAD_GUARD;
+ fti_notify_node_changed();
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// We need the scale part, so if these are dirty, update it
@@ -317,8 +354,19 @@ void Node3D::set_global_rotation_degrees(const Vector3 &p_euler_degrees) {
set_global_rotation(radians);
}
+void Node3D::fti_pump_xform() {
+ data.local_transform_prev = get_transform();
+}
+
+void Node3D::fti_notify_node_changed(bool p_transform_changed) {
+ if (is_inside_tree()) {
+ get_tree()->get_scene_tree_fti().node_3d_notify_changed(*this, p_transform_changed);
+ }
+}
+
void Node3D::set_transform(const Transform3D &p_transform) {
ERR_THREAD_GUARD;
+ fti_notify_node_changed();
data.local_transform = p_transform;
_replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty.
@@ -452,9 +500,19 @@ Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fr
}
Transform3D Node3D::get_global_transform_interpolated() {
+#if 1
// Pass through if physics interpolation is switched off.
// This is a convenience, as it allows you to easy turn off interpolation
// without changing any code.
+ if (data.fti_global_xform_interp_set && is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame() && is_visible_in_tree()) {
+ return data.global_transform_interpolated;
+ }
+
+ return get_global_transform();
+#else
+ // OLD METHOD - deprecated since moving to SceneTreeFTI,
+ // but leaving for reference and comparison for debugging.
+
if (!is_physics_interpolated_and_enabled()) {
return get_global_transform();
}
@@ -467,6 +525,7 @@ Transform3D Node3D::get_global_transform_interpolated() {
}
return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction());
+#endif
}
Transform3D Node3D::get_global_transform() const {
@@ -537,6 +596,7 @@ Transform3D Node3D::get_relative_transform(const Node *p_parent) const {
void Node3D::set_position(const Vector3 &p_position) {
ERR_THREAD_GUARD;
+ fti_notify_node_changed();
data.local_transform.origin = p_position;
_propagate_transform_changed(this);
if (data.notify_local_transform) {
@@ -618,6 +678,7 @@ EulerOrder Node3D::get_rotation_order() const {
void Node3D::set_rotation(const Vector3 &p_euler_rad) {
ERR_THREAD_GUARD;
+ fti_notify_node_changed();
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// Update scale only if rotation and scale are dirty, as rotation will be overridden.
data.scale = data.local_transform.basis.get_scale();
@@ -640,6 +701,7 @@ void Node3D::set_rotation_degrees(const Vector3 &p_euler_degrees) {
void Node3D::set_scale(const Vector3 &p_scale) {
ERR_THREAD_GUARD;
+ fti_notify_node_changed();
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// Update rotation only if rotation and scale are dirty, as scale will be overridden.
data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
@@ -1380,6 +1442,13 @@ Node3D::Node3D() :
data.disable_scale = false;
data.vi_visible = true;
+ data.fti_on_frame_xform_list = false;
+ data.fti_on_frame_property_list = false;
+ data.fti_on_tick_xform_list = false;
+ data.fti_on_tick_property_list = false;
+ data.fti_global_xform_interp_set = false;
+ data.fti_frame_xform_force_update = false;
+
#ifdef TOOLS_ENABLED
data.gizmos_disabled = false;
data.gizmos_dirty = false;
@@ -1389,4 +1458,8 @@ Node3D::Node3D() :
Node3D::~Node3D() {
_disable_client_physics_interpolation();
+
+ if (is_inside_tree()) {
+ get_tree()->get_scene_tree_fti().node_3d_notify_delete(this);
+ }
}
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index e641d7b8db0..87363059489 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -50,6 +50,8 @@ public:
class Node3D : public Node {
GDCLASS(Node3D, Node);
+ friend class SceneTreeFTI;
+
public:
// Edit mode for the rotation.
// THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED
@@ -81,7 +83,8 @@ private:
DIRTY_NONE = 0,
DIRTY_EULER_ROTATION_AND_SCALE = 1,
DIRTY_LOCAL_TRANSFORM = 2,
- DIRTY_GLOBAL_TRANSFORM = 4
+ DIRTY_GLOBAL_TRANSFORM = 4,
+ DIRTY_GLOBAL_INTERPOLATED_TRANSFORM = 8,
};
struct ClientPhysicsInterpolationData {
@@ -97,8 +100,19 @@ private:
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
+ // Interpolated global transform - correct on the frame only.
+ // Only used with FTI.
+ Transform3D global_transform_interpolated;
+
+ // Current xforms are either
+ // * Used for everything (when not using FTI)
+ // * Correct on the physics tick (when using FTI)
mutable Transform3D global_transform;
mutable Transform3D local_transform;
+
+ // Only used with FTI.
+ Transform3D local_transform_prev;
+
mutable EulerOrder euler_rotation_order = EulerOrder::YXZ;
mutable Vector3 euler_rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
@@ -122,6 +136,14 @@ private:
bool visible : 1;
bool disable_scale : 1;
+ // Scene tree interpolation.
+ bool fti_on_frame_xform_list : 1;
+ bool fti_on_frame_property_list : 1;
+ bool fti_on_tick_xform_list : 1;
+ bool fti_on_tick_property_list : 1;
+ bool fti_global_xform_interp_set : 1;
+ bool fti_frame_xform_force_update : 1;
+
RID visibility_parent;
Node3D *parent = nullptr;
@@ -166,8 +188,30 @@ protected:
void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; }
bool _is_vi_visible() const { return data.vi_visible; }
Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction);
+ const Transform3D &_get_cached_global_transform_interpolated() const { return data.global_transform_interpolated; }
void _disable_client_physics_interpolation();
+ // Calling this announces to the FTI system that a node has been moved,
+ // or requires an update in terms of interpolation
+ // (e.g. changing Camera zoom even if position hasn't changed).
+ void fti_notify_node_changed(bool p_transform_changed = true);
+
+ // Opportunity after FTI to update the servers
+ // with global_transform_interpolated,
+ // and any custom interpolated data in derived classes.
+ // Make sure to call the parent class fti_update_servers(),
+ // so all data is updated to the servers.
+ virtual void fti_update_servers_xform() {}
+ virtual void fti_update_servers_property() {}
+
+ // Pump the FTI data, also gives a chance for inherited classes
+ // to pump custom data, but they *must* call the base class here too.
+ // This is the opportunity for classes to move current values for
+ // transforms and properties to stored previous values,
+ // and this should take place both on ticks, and during resets.
+ virtual void fti_pump_xform();
+ virtual void fti_pump_property() {}
+
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index 5289d287b0c..f02dc489f2d 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -353,6 +353,7 @@ real_t VehicleWheel3D::get_rpm() const {
}
VehicleWheel3D::VehicleWheel3D() {
+ set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF);
}
void VehicleBody3D::_update_wheel_transform(VehicleWheel3D &wheel, PhysicsDirectBodyState3D *s) {
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 3c3c3990e04..e65fbd5755c 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -245,7 +245,7 @@ void FabrikInverseKinematic::solve(Task *p_task, bool override_tip_basis, bool p
Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin;
- make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse());
+ make_goal(p_task, p_task->skeleton->get_global_transform_interpolated().affine_inverse());
if (p_use_magnet && p_task->chain.middle_chain_item) {
p_task->chain.magnet_position = p_magnet_position;
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index a40d76dacc6..41903f208a1 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -58,10 +58,6 @@ void VisualInstance3D::_update_visibility() {
RS::get_singleton()->instance_set_visible(instance, visible);
}
-void VisualInstance3D::_physics_interpolated_changed() {
- RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated());
-}
-
void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) {
// Prevent sending instance transforms when using global coordinates.
_set_use_identity_transform(p_enable);
@@ -77,6 +73,12 @@ void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) {
}
}
+void VisualInstance3D::fti_update_servers_xform() {
+ if (!_is_using_identity_transform()) {
+ RS::get_singleton()->instance_set_transform(get_instance(), _get_cached_global_transform_interpolated());
+ }
+}
+
void VisualInstance3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
@@ -86,33 +88,19 @@ void VisualInstance3D::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
- if (_is_vi_visible() || is_physics_interpolated_and_enabled()) {
- if (!_is_using_identity_transform()) {
- RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
-
- // For instance when first adding to the tree, when the previous transform is
- // unset, to prevent streaking from the origin.
- if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) {
- if (_is_vi_visible()) {
- _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
- }
- _set_physics_interpolation_reset_requested(false);
- }
- }
+ // ToDo : Can we turn off notify transform for physics interpolated cases?
+ if (_is_vi_visible() && !(is_inside_tree() && get_tree()->is_physics_interpolation_enabled()) && !_is_using_identity_transform()) {
+ // Physics interpolation global off, always send.
+ RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
- if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) {
- // We must ensure the RenderingServer transform is up to date before resetting.
- // This is because NOTIFICATION_TRANSFORM_CHANGED is deferred,
- // and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION.
- if (!_is_using_identity_transform()) {
- RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
- }
+ if (_is_vi_visible() && is_inside_tree()) {
+ // Allow resetting motion vectors etc
+ // at the same time as resetting physics interpolation,
+ // giving users one common interface.
RenderingServer::get_singleton()->instance_teleport(instance);
-
- RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance);
}
} break;
diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h
index 608352a7f29..727066a754b 100644
--- a/scene/3d/visual_instance_3d.h
+++ b/scene/3d/visual_instance_3d.h
@@ -44,8 +44,8 @@ class VisualInstance3D : public Node3D {
protected:
void _update_visibility();
- virtual void _physics_interpolated_changed() override;
void set_instance_use_identity_transform(bool p_enable);
+ virtual void fti_update_servers_xform() override;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 6894158644b..ab098215aaa 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -2151,11 +2151,6 @@ void RuntimeNodeSelect::_update_selection() {
RS::get_singleton()->instance_set_transform(sb->instance_ofs, t_offset);
RS::get_singleton()->instance_set_transform(sb->instance_xray, t);
RS::get_singleton()->instance_set_transform(sb->instance_xray_ofs, t_offset);
-
- RS::get_singleton()->instance_reset_physics_interpolation(sb->instance);
- RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_ofs);
- RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_xray);
- RS::get_singleton()->instance_reset_physics_interpolation(sb->instance_xray_ofs);
}
#endif // _3D_DISABLED
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 59062228e70..19f6832bfe1 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -219,11 +219,6 @@ void Node::_notification(int p_notification) {
get_tree()->nodes_in_tree_count++;
orphan_node_count--;
- // Allow physics interpolated nodes to automatically reset when added to the tree
- // (this is to save the user from doing this manually each time).
- if (get_tree()->is_physics_interpolation_enabled()) {
- _set_physics_interpolation_reset_requested(true);
- }
} break;
case NOTIFICATION_POST_ENTER_TREE: {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 70757523e1c..916e163e9b8 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -588,6 +588,8 @@ void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
_physics_interpolation_enabled = p_enabled;
RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
+ get_scene_tree_fti().set_enabled(get_root(), p_enabled);
+
// Perform an auto reset on the root node for convenience for the user.
if (root) {
root->reset_physics_interpolation();
@@ -615,6 +617,7 @@ void SceneTree::iteration_prepare() {
// Make sure any pending transforms from the last tick / frame
// are flushed before pumping the interpolation prev and currents.
flush_transform_notifications();
+ get_scene_tree_fti().tick_update();
RenderingServer::get_singleton()->tick();
}
}
@@ -669,6 +672,17 @@ void SceneTree::iteration_end() {
}
bool SceneTree::process(double p_time) {
+ // First pass of scene tree fixed timestep interpolation.
+ if (get_scene_tree_fti().is_enabled()) {
+ // Special, we need to ensure RenderingServer is up to date
+ // with *all* the pending xforms *before* updating it during
+ // the FTI update.
+ // If this is not done, we can end up with a deferred `set_transform()`
+ // overwriting the interpolated xform in the server.
+ flush_transform_notifications();
+ get_scene_tree_fti().frame_update(get_root(), true);
+ }
+
if (MainLoop::process(p_time)) {
_quit = true;
}
@@ -750,6 +764,11 @@ bool SceneTree::process(double p_time) {
#endif // _3D_DISABLED
#endif // TOOLS_ENABLED
+ // Second pass of scene tree fixed timestep interpolation.
+ // ToDo: Possibly needs another flush_transform_notifications here
+ // depending on whether there are side effects to _call_idle_callbacks().
+ get_scene_tree_fti().frame_update(get_root(), false);
+
if (_physics_interpolation_enabled) {
RenderingServer::get_singleton()->pre_draw(true);
}
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 2695c1bbd83..dfff6551b4e 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -34,6 +34,7 @@
#include "core/os/thread_safe.h"
#include "core/templates/paged_allocator.h"
#include "core/templates/self_list.h"
+#include "scene/main/scene_tree_fti.h"
#include "scene/resources/mesh.h"
#undef Window
@@ -148,6 +149,7 @@ private:
bool _quit = false;
bool _physics_interpolation_enabled = false;
+ SceneTreeFTI scene_tree_fti;
StringName tree_changed_name = "tree_changed";
StringName node_added_name = "node_added";
@@ -454,6 +456,8 @@ public:
void client_physics_interpolation_remove_node_3d(SelfList *p_elem);
#endif
+ SceneTreeFTI &get_scene_tree_fti() { return scene_tree_fti; }
+
SceneTree();
~SceneTree();
};
diff --git a/scene/main/scene_tree_fti.cpp b/scene/main/scene_tree_fti.cpp
new file mode 100644
index 00000000000..28d4c5adf05
--- /dev/null
+++ b/scene/main/scene_tree_fti.cpp
@@ -0,0 +1,474 @@
+/**************************************************************************/
+/* scene_tree_fti.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef _3D_DISABLED
+
+#include "scene_tree_fti.h"
+
+#include "core/config/engine.h"
+#include "core/math/transform_interpolator.h"
+#include "core/os/os.h"
+#include "scene/3d/visual_instance_3d.h"
+
+// Uncomment this to enable some slow extra DEV_ENABLED
+// checks to ensure there aren't more than one object added to the lists.
+// #define GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+
+void SceneTreeFTI::_reset_flags(Node *p_node) {
+ Node3D *s = Object::cast_to(p_node);
+
+ if (s) {
+ s->data.fti_on_tick_xform_list = false;
+ s->data.fti_on_tick_property_list = false;
+ s->data.fti_on_frame_xform_list = false;
+ s->data.fti_on_frame_property_list = false;
+ s->data.fti_global_xform_interp_set = false;
+ s->data.fti_frame_xform_force_update = false;
+
+ // In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION
+ // will reset this, but this should help cover hidden nodes.
+ s->data.local_transform_prev = s->get_transform();
+ }
+
+ for (int n = 0; n < p_node->get_child_count(); n++) {
+ _reset_flags(p_node->get_child(n));
+ }
+}
+
+void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
+ if (data.enabled == p_enabled) {
+ return;
+ }
+ MutexLock(data.mutex);
+
+ data.tick_xform_list[0].clear();
+ data.tick_xform_list[1].clear();
+
+ // Node3D flags must be reset.
+ if (p_root) {
+ _reset_flags(p_root);
+ }
+
+ data.enabled = p_enabled;
+}
+
+void SceneTreeFTI::tick_update() {
+ if (!data.enabled) {
+ return;
+ }
+ MutexLock(data.mutex);
+
+ _update_request_resets();
+
+ uint32_t curr_mirror = data.mirror;
+ uint32_t prev_mirror = curr_mirror ? 0 : 1;
+
+ LocalVector &curr = data.tick_xform_list[curr_mirror];
+ LocalVector &prev = data.tick_xform_list[prev_mirror];
+
+ // First detect on the previous list but not on this tick list.
+ for (uint32_t n = 0; n < prev.size(); n++) {
+ Node3D *s = prev[n];
+ if (!s->data.fti_on_tick_xform_list) {
+ // Needs a reset so jittering will stop.
+ s->fti_pump_xform();
+
+ // This may not get updated so set it to the same as global xform.
+ // TODO: double check this is the best value.
+ s->data.global_transform_interpolated = s->get_global_transform();
+
+ // Remove from interpolation list.
+ if (s->data.fti_on_frame_xform_list) {
+ s->data.fti_on_frame_xform_list = false;
+ }
+
+ // Ensure that the spatial gets at least ONE further
+ // update in the resting position in the next frame update.
+ s->data.fti_frame_xform_force_update = true;
+ }
+ }
+
+ LocalVector &curr_prop = data.tick_property_list[curr_mirror];
+ LocalVector &prev_prop = data.tick_property_list[prev_mirror];
+
+ // Detect on the previous property list but not on this tick list.
+ for (uint32_t n = 0; n < prev_prop.size(); n++) {
+ Node3D *s = prev_prop[n];
+
+ if (!s->data.fti_on_tick_property_list) {
+ // Needs a reset so jittering will stop.
+ s->fti_pump_xform();
+
+ // Ensure the servers are up to date with the final resting value.
+ s->fti_update_servers_property();
+
+ // Remove from interpolation list.
+ if (s->data.fti_on_frame_property_list) {
+ s->data.fti_on_frame_property_list = false;
+ data.frame_property_list.erase_unordered(s);
+
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ DEV_CHECK_ONCE(data.frame_property_list.find(s) == -1);
+#endif
+ }
+ }
+ }
+
+ // Pump all on the property list that are NOT on the tick list.
+ for (uint32_t n = 0; n < curr_prop.size(); n++) {
+ Node3D *s = curr_prop[n];
+
+ // Reset, needs to be marked each tick.
+ s->data.fti_on_tick_property_list = false;
+ s->fti_pump_property();
+ }
+
+ // Now pump all on the current list.
+ for (uint32_t n = 0; n < curr.size(); n++) {
+ Node3D *s = curr[n];
+
+ // Reset, needs to be marked each tick.
+ s->data.fti_on_tick_xform_list = false;
+
+ // Pump.
+ s->fti_pump_xform();
+ }
+
+ // Clear previous list and flip.
+ prev.clear();
+ prev_prop.clear();
+ data.mirror = prev_mirror;
+}
+
+void SceneTreeFTI::_update_request_resets() {
+ // For instance when first adding to the tree, when the previous transform is
+ // unset, to prevent streaking from the origin.
+ for (uint32_t n = 0; n < data.request_reset_list.size(); n++) {
+ Node3D *s = data.request_reset_list[n];
+ if (s->_is_physics_interpolation_reset_requested()) {
+ if (s->_is_vi_visible() && !s->_is_using_identity_transform()) {
+ s->notification(Node3D::NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ }
+
+ s->_set_physics_interpolation_reset_requested(false);
+ }
+ }
+
+ data.request_reset_list.clear();
+}
+
+void SceneTreeFTI::node_3d_request_reset(Node3D *p_node) {
+ DEV_CHECK_ONCE(data.enabled);
+ DEV_ASSERT(p_node);
+
+ MutexLock(data.mutex);
+
+ if (!p_node->_is_physics_interpolation_reset_requested()) {
+ p_node->_set_physics_interpolation_reset_requested(true);
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
+#endif
+ data.request_reset_list.push_back(p_node);
+ }
+}
+
+void SceneTreeFTI::_node_3d_notify_set_property(Node3D &r_node) {
+ if (!r_node.is_physics_interpolated()) {
+ return;
+ }
+
+ DEV_CHECK_ONCE(data.enabled);
+
+ // Note that a Node3D can be on BOTH the transform list and the property list.
+ if (!r_node.data.fti_on_tick_property_list) {
+ r_node.data.fti_on_tick_property_list = true;
+
+ // Should only appear once in the property list.
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_node) == -1);
+#endif
+ data.tick_property_list[data.mirror].push_back(&r_node);
+ }
+
+ if (!r_node.data.fti_on_frame_property_list) {
+ r_node.data.fti_on_frame_property_list = true;
+
+ // Should only appear once in the property frame list.
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ DEV_CHECK_ONCE(data.frame_property_list.find(&r_node) == -1);
+#endif
+ data.frame_property_list.push_back(&r_node);
+ }
+}
+
+void SceneTreeFTI::_node_3d_notify_set_xform(Node3D &r_node) {
+ DEV_CHECK_ONCE(data.enabled);
+
+ if (!r_node.is_physics_interpolated()) {
+ // Force an update of non-interpolated to servers
+ // on the next traversal.
+ r_node.data.fti_frame_xform_force_update = true;
+ return;
+ }
+
+ if (!r_node.data.fti_on_tick_xform_list) {
+ r_node.data.fti_on_tick_xform_list = true;
+
+ // Should only appear once in the xform list.
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_node) == -1);
+#endif
+ data.tick_xform_list[data.mirror].push_back(&r_node);
+
+ // The following flag could have been previously set
+ // (for removal from the tick list).
+ // We no longer need this guarantee,
+ // however there is probably no downside to leaving it set
+ // as it will be cleared on the next frame anyway.
+ // This line is left for reference.
+ // r_spatial.data.fti_frame_xform_force_update = false;
+ }
+
+ if (!r_node.data.fti_on_frame_xform_list) {
+ r_node.data.fti_on_frame_xform_list = true;
+ }
+}
+
+void SceneTreeFTI::node_3d_notify_delete(Node3D *p_node) {
+ if (!data.enabled) {
+ return;
+ }
+
+ ERR_FAIL_NULL(p_node);
+
+ MutexLock(data.mutex);
+
+ p_node->data.fti_on_frame_xform_list = false;
+
+ // Ensure this is kept in sync with the lists, in case a node
+ // is removed and re-added to the scene tree multiple times
+ // on the same frame / tick.
+ p_node->_set_physics_interpolation_reset_requested(false);
+
+ // This can potentially be optimized for large scenes with large churn,
+ // as it will be doing a linear search through the lists.
+ data.tick_xform_list[0].erase_unordered(p_node);
+ data.tick_xform_list[1].erase_unordered(p_node);
+
+ data.tick_property_list[0].erase_unordered(p_node);
+ data.tick_property_list[1].erase_unordered(p_node);
+
+ data.frame_property_list.erase_unordered(p_node);
+ data.request_reset_list.erase_unordered(p_node);
+
+#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
+ // There should only be one occurrence on the lists.
+ // Check this in DEV_ENABLED builds.
+ DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_node) == -1);
+ DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_node) == -1);
+
+ DEV_CHECK_ONCE(data.tick_property_list[0].find(p_node) == -1);
+ DEV_CHECK_ONCE(data.tick_property_list[1].find(p_node) == -1);
+
+ DEV_CHECK_ONCE(data.frame_property_list.find(p_node) == -1);
+ DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
+#endif
+}
+
+void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) {
+ Node3D *s = Object::cast_to(p_node);
+
+ // Don't recurse into hidden branches.
+ if (s && !s->is_visible()) {
+ // NOTE : If we change from recursing entire tree, we should do an is_visible_in_tree()
+ // check for the first of the branch.
+ return;
+ }
+
+ // Not a Node3D.
+ // Could be e.g. a viewport or something
+ // so we should still recurse to children.
+ if (!s) {
+ for (int n = 0; n < p_node->get_child_count(); n++) {
+ _update_dirty_nodes(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
+ }
+ return;
+ }
+
+ // We are going to be using data.global_transform, so
+ // we need to ensure data.global_transform is not dirty!
+ if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) {
+ _ALLOW_DISCARD_ s->get_global_transform();
+ }
+
+ // Start the active interpolation chain from here onwards
+ // as we recurse further into the SceneTree.
+ // Once we hit an active (interpolated) node, we have to fully
+ // process all ancestors because their xform will also change.
+ // Anything not moving (inactive) higher in the tree need not be processed.
+ if (!p_active) {
+ if (data.frame_start) {
+ // On the frame start, activate whenever we hit something that requests interpolation.
+ if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) {
+ p_active = true;
+ }
+ } else {
+ // On the frame end, we want to re-interpolate *anything* that has moved
+ // since the frame start.
+
+ if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) {
+ p_active = true;
+ }
+ }
+ }
+
+ if (data.frame_start) {
+ // Mark on the Node3D whether we have set global_transform_interp.
+ // This can later be used when calling `get_global_transform_interpolated()`
+ // to know which xform to return.
+ s->data.fti_global_xform_interp_set = p_active;
+ }
+
+ if (p_active) {
+#if 0
+ bool dirty = s->data.dirty & Node3D::DIRTY_GLOBAL_INTERP;
+
+ if (data.debug) {
+ String sz;
+ for (int n = 0; n < p_depth; n++) {
+ sz += "\t";
+ }
+ print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : ""));
+ }
+#endif
+
+ // First calculate our local xform.
+ // This will either use interpolation, or just use the current local if not interpolated.
+ Transform3D local_interp;
+ if (s->is_physics_interpolated()) {
+ // Make sure to call `get_transform()` rather than using local_transform directly, because
+ // local_transform may be dirty and need updating from rotation / scale.
+ TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction);
+ } else {
+ local_interp = s->get_transform();
+ }
+
+ // Concatenate parent xform.
+ if (!s->is_set_as_top_level()) {
+ if (p_parent_global_xform) {
+ s->data.global_transform_interpolated = (*p_parent_global_xform) * local_interp;
+ } else {
+ const Node3D *parent = s->get_parent_node_3d();
+
+ if (parent) {
+ const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->data.global_transform;
+ s->data.global_transform_interpolated = parent_glob * local_interp;
+ } else {
+ s->data.global_transform_interpolated = local_interp;
+ }
+ }
+ } else {
+ s->data.global_transform_interpolated = local_interp;
+ }
+
+ // Watch for this, disable_scale can cause incredibly confusing bugs
+ // and must be checked for when calculating global xforms.
+ if (s->data.disable_scale) {
+ s->data.global_transform_interpolated.basis.orthonormalize();
+ }
+
+ // Upload to RenderingServer the interpolated global xform.
+ s->fti_update_servers_xform();
+
+ // Only do this at most for one frame,
+ // it is used to catch objects being removed from the tick lists
+ // that have a deferred frame update.
+ s->data.fti_frame_xform_force_update = false;
+
+ } // if active.
+
+ // Remove the dirty interp flag from EVERYTHING as we go.
+ s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
+
+ // Recurse to children.
+ for (int n = 0; n < p_node->get_child_count(); n++) {
+ _update_dirty_nodes(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
+ }
+}
+
+void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
+ if (!data.enabled || !p_root) {
+ return;
+ }
+ MutexLock(data.mutex);
+
+ _update_request_resets();
+
+ data.frame_start = p_frame_start;
+
+ float f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ uint32_t frame = Engine::get_singleton()->get_frames_drawn();
+
+// #define SCENE_TREE_FTI_TAKE_TIMINGS
+#ifdef SCENE_TREE_FTI_TAKE_TIMINGS
+ uint64_t before = OS::get_singleton()->get_ticks_usec();
+#endif
+
+ if (data.debug) {
+ print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n");
+ }
+
+ // Probably not the most optimal approach as we traverse the entire SceneTree
+ // but simple and foolproof.
+ // Can be optimized later.
+ _update_dirty_nodes(p_root, frame, f, false);
+
+ if (!p_frame_start && data.debug) {
+ data.debug = false;
+ }
+
+#ifdef SCENE_TREE_FTI_TAKE_TIMINGS
+ uint64_t after = OS::get_singleton()->get_ticks_usec();
+ if ((Engine::get_singleton()->get_frames_drawn() % 60) == 0) {
+ print_line("Took " + itos(after - before) + " usec " + (data.frame_start ? "start" : "end"));
+ }
+#endif
+
+ // Update the properties once off at the end of the frame.
+ // No need for two passes for properties.
+ if (!p_frame_start) {
+ for (uint32_t n = 0; n < data.frame_property_list.size(); n++) {
+ Node3D *s = data.frame_property_list[n];
+ s->fti_update_servers_property();
+ }
+ }
+}
+
+#endif // ndef _3D_DISABLED
diff --git a/scene/main/scene_tree_fti.h b/scene/main/scene_tree_fti.h
new file mode 100644
index 00000000000..33be68158d0
--- /dev/null
+++ b/scene/main/scene_tree_fti.h
@@ -0,0 +1,128 @@
+/**************************************************************************/
+/* scene_tree_fti.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "core/os/mutex.h"
+#include "core/templates/local_vector.h"
+
+class Node3D;
+class Node;
+struct Transform3D;
+
+#ifdef _3D_DISABLED
+// Stubs
+class SceneTreeFTI {
+public:
+ void frame_update(Node *p_root, bool p_frame_start) {}
+ void tick_update() {}
+ void set_enabled(Node *p_root, bool p_enabled) {}
+ bool is_enabled() const { return false; }
+
+ void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) {}
+ void node_3d_notify_delete(Node3D *p_node) {}
+ void node_3d_request_reset(Node3D *p_node) {}
+};
+#else
+
+// Important.
+// This class uses raw pointers, so it is essential that on deletion, this class is notified
+// so that any references can be cleared up to prevent dangling pointer access.
+
+// This class can be used from a custom SceneTree.
+
+// Note we could potentially make SceneTreeFTI static / global to avoid the lookup through scene tree,
+// but this covers the custom case of multiple scene trees.
+
+class SceneTreeFTI {
+ struct Data {
+ // Prev / Curr lists of Node3Ds having local xforms pumped.
+ LocalVector tick_xform_list[2];
+
+ // Prev / Curr lists of Node3Ds having actively interpolated properties.
+ LocalVector tick_property_list[2];
+
+ LocalVector frame_property_list;
+
+ LocalVector request_reset_list;
+
+ uint32_t mirror = 0;
+
+ bool enabled = false;
+
+ // Whether we are in physics ticks, or in a frame.
+ bool in_frame = false;
+
+ // Updating at the start of the frame, or the end on second pass.
+ bool frame_start = true;
+
+ Mutex mutex;
+
+ bool debug = false;
+ } data;
+
+ void _update_dirty_nodes(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform = nullptr, int p_depth = 0);
+ void _update_request_resets();
+
+ void _reset_flags(Node *p_node);
+ void _node_3d_notify_set_xform(Node3D &r_node);
+ void _node_3d_notify_set_property(Node3D &r_node);
+
+public:
+ // Hottest function, allow inlining the data.enabled check.
+ void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) {
+ if (!data.enabled) {
+ return;
+ }
+ MutexLock(data.mutex);
+
+ if (p_transform_changed) {
+ _node_3d_notify_set_xform(r_node);
+ } else {
+ _node_3d_notify_set_property(r_node);
+ }
+ }
+
+ void node_3d_request_reset(Node3D *p_node);
+ void node_3d_notify_delete(Node3D *p_node);
+
+ // Calculate interpolated xforms, send to visual server.
+ void frame_update(Node *p_root, bool p_frame_start);
+
+ // Update local xform pumps.
+ void tick_update();
+
+ void set_enabled(Node *p_root, bool p_enabled);
+ bool is_enabled() const { return data.enabled; }
+
+ void set_debug_next_frame() { data.debug = true; }
+};
+
+#endif // ndef _3D_DISABLED
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index a00b9bfa0a9..277ada1e4d0 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -941,45 +941,8 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
Instance *instance = instance_owner.get_or_null(p_instance);
ERR_FAIL_NULL(instance);
-#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
- print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
-#endif
-
- if (!_interpolation_data.interpolation_enabled || !instance->interpolated || !instance->scenario) {
- if (instance->transform == p_transform) {
- return; // Must be checked to avoid worst evil.
- }
-
-#ifdef DEBUG_ENABLED
-
- for (int i = 0; i < 4; i++) {
- const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin;
- ERR_FAIL_COND(!v.is_finite());
- }
-
-#endif
- instance->transform = p_transform;
- _instance_queue_update(instance, true);
-
-#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
- if (_interpolation_data.interpolation_enabled && !instance->interpolated && Engine::get_singleton()->is_in_physics_frame()) {
- PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Non-interpolated instance triggered from physics process");
- }
-#endif
-
- return;
- }
-
- float new_checksum = TransformInterpolator::checksum_transform_3d(p_transform);
- bool checksums_match = (instance->transform_checksum_curr == new_checksum) && (instance->transform_checksum_prev == new_checksum);
-
- // We can't entirely reject no changes because we need the interpolation
- // system to keep on stewing.
-
- // Optimized check. First checks the checksums. If they pass it does the slow check at the end.
- // Alternatively we can do this non-optimized and ignore the checksum... if no change.
- if (checksums_match && (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform)) {
- return;
+ if (instance->transform == p_transform) {
+ return; // Must be checked to avoid worst evil.
}
#ifdef DEBUG_ENABLED
@@ -990,71 +953,8 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
}
#endif
-
- instance->transform_curr = p_transform;
-
-#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
- print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
-#endif
-
- // Keep checksums up to date.
- instance->transform_checksum_curr = new_checksum;
-
- if (!instance->on_interpolate_transform_list) {
- _interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
- instance->on_interpolate_transform_list = true;
- } else {
- DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size());
- }
-
- // If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated
- // transform or anything else.
- // Ideally we would not even call the VisualServer::set_transform() when invisible but that would entail having logic
- // to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise.
- if (!instance->visible) {
- return;
- }
-
- // Decide on the interpolation method... slerp if possible.
- instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
-
- if (!instance->on_interpolate_list) {
- _interpolation_data.instance_interpolate_update_list.push_back(p_instance);
- instance->on_interpolate_list = true;
- } else {
- DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size());
- }
-
+ instance->transform = p_transform;
_instance_queue_update(instance, true);
-
-#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
- if (!Engine::get_singleton()->is_in_physics_frame()) {
- PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Interpolated instance triggered from outside physics process");
- }
-#endif
-}
-
-void RendererSceneCull::instance_set_interpolated(RID p_instance, bool p_interpolated) {
- Instance *instance = instance_owner.get_or_null(p_instance);
- ERR_FAIL_NULL(instance);
- instance->interpolated = p_interpolated;
-}
-
-void RendererSceneCull::instance_reset_physics_interpolation(RID p_instance) {
- Instance *instance = instance_owner.get_or_null(p_instance);
- ERR_FAIL_NULL(instance);
-
- instance->teleported = true;
-
- if (_interpolation_data.interpolation_enabled && instance->interpolated) {
- instance->transform_prev = instance->transform_curr;
- instance->transform_checksum_prev = instance->transform_checksum_curr;
-
-#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
- print_line("instance_reset_physics_interpolation .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
- print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
-#endif
- }
}
void RendererSceneCull::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
@@ -1107,23 +1007,6 @@ void RendererSceneCull::instance_set_visible(RID p_instance, bool p_visible) {
if (p_visible) {
if (instance->scenario != nullptr) {
- // Special case for physics interpolation, we want to ensure the interpolated data is up to date
- if (_interpolation_data.interpolation_enabled && instance->interpolated && !instance->on_interpolate_list) {
- // Do all the extra work we normally do on instance_set_transform(), because this is optimized out for hidden instances.
- // This prevents a glitch of stale interpolation transform data when unhiding before the next physics tick.
- instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
- _interpolation_data.instance_interpolate_update_list.push_back(p_instance);
- instance->on_interpolate_list = true;
-
- // We must also place on the transform update list for a tick, so the system
- // can auto-detect if the instance is no longer moving, and remove from the interpolate lists again.
- // If this step is ignored, an unmoving instance could remain on the interpolate lists indefinitely
- // (or rather until the object is deleted) and cause unnecessary updates and drawcalls.
- if (!instance->on_interpolate_transform_list) {
- _interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
- instance->on_interpolate_transform_list = true;
- }
- }
_instance_queue_update(instance, true, false);
}
} else if (instance->indexer_id.is_valid()) {
@@ -4278,8 +4161,6 @@ bool RendererSceneCull::free(RID p_rid) {
Instance *instance = instance_owner.get_or_null(p_rid);
- _interpolation_data.notify_free_instance(p_rid, *instance);
-
instance_geometry_set_lightmap(p_rid, RID(), Rect2(), 0);
instance_set_scenario(p_rid, RID());
instance_set_base(p_rid, RID());
@@ -4342,103 +4223,17 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) {
void RendererSceneCull::update_interpolation_tick(bool p_process) {
// MultiMesh: Update interpolation in storage.
RSG::mesh_storage->update_interpolation_tick(p_process);
-
- // INSTANCES
-
- // Detect any that were on the previous transform list that are no longer active;
- // we should remove them from the interpolate list.
-
- for (const RID &rid : *_interpolation_data.instance_transform_update_list_prev) {
- Instance *instance = instance_owner.get_or_null(rid);
-
- bool active = true;
-
- // No longer active? (Either the instance deleted or no longer being transformed.)
- if (instance && !instance->on_interpolate_transform_list) {
- active = false;
- instance->on_interpolate_list = false;
-
- // Make sure the most recent transform is set...
- instance->transform = instance->transform_curr;
-
- // ... and that both prev and current are the same, just in case of any interpolations.
- instance->transform_prev = instance->transform_curr;
-
- // Make sure instances are updated one more time to ensure the AABBs are correct.
- _instance_queue_update(instance, true);
- }
-
- if (!instance) {
- active = false;
- }
-
- if (!active) {
- _interpolation_data.instance_interpolate_update_list.erase(rid);
- }
- }
-
- // Now for any in the transform list (being actively interpolated), keep the previous transform
- // value up to date, ready for the next tick.
- if (p_process) {
- for (const RID &rid : *_interpolation_data.instance_transform_update_list_curr) {
- Instance *instance = instance_owner.get_or_null(rid);
- if (instance) {
- instance->transform_prev = instance->transform_curr;
- instance->transform_checksum_prev = instance->transform_checksum_curr;
- instance->on_interpolate_transform_list = false;
- }
- }
- }
-
- // We maintain a mirror list for the transform updates, so we can detect when an instance
- // is no longer being transformed, and remove it from the interpolate list.
- SWAP(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev);
-
- // Prepare for the next iteration.
- _interpolation_data.instance_transform_update_list_curr->clear();
}
void RendererSceneCull::update_interpolation_frame(bool p_process) {
// MultiMesh: Update interpolation in storage.
RSG::mesh_storage->update_interpolation_frame(p_process);
-
- if (p_process) {
- real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
-
- for (const RID &rid : _interpolation_data.instance_interpolate_update_list) {
- Instance *instance = instance_owner.get_or_null(rid);
- if (instance) {
- TransformInterpolator::interpolate_transform_3d_via_method(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method);
-
-#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
- print_line("\t\tinterpolated: " + rtos(instance->transform.origin.x) + "\t( prev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
-#endif
-
- // Make sure AABBs are constantly up to date through the interpolation.
- _instance_queue_update(instance, true);
- }
- }
- }
}
void RendererSceneCull::set_physics_interpolation_enabled(bool p_enabled) {
_interpolation_data.interpolation_enabled = p_enabled;
}
-void RendererSceneCull::InterpolationData::notify_free_instance(RID p_rid, Instance &r_instance) {
- r_instance.on_interpolate_list = false;
- r_instance.on_interpolate_transform_list = false;
-
- if (!interpolation_enabled) {
- return;
- }
-
- // If the instance was on any of the lists, remove.
- instance_interpolate_update_list.erase_multiple_unordered(p_rid);
- instance_transform_update_list_curr->erase_multiple_unordered(p_rid);
- instance_transform_update_list_prev->erase_multiple_unordered(p_rid);
-}
-
RendererSceneCull::RendererSceneCull() {
render_pass = 1;
singleton = this;
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 67f69dc06cc..42a75891966 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -408,17 +408,9 @@ public:
RID mesh_instance; //only used for meshes and when skeleton/blendshapes exist
- // This is the main transform to be drawn with ...
- // This will either be the interpolated transform (when using fixed timestep interpolation)
- // or the ONLY transform (when not using FTI).
Transform3D transform;
bool teleported = false;
- // For interpolation we store the current transform (this physics tick)
- // and the transform in the previous tick.
- Transform3D transform_curr;
- Transform3D transform_prev;
-
float lod_bias;
bool ignore_occlusion_culling;
@@ -437,16 +429,6 @@ public:
bool dynamic_gi : 1; // Same as above for dynamic objects.
bool redraw_if_visible : 1;
- bool on_interpolate_list : 1;
- bool on_interpolate_transform_list : 1;
- bool interpolated : 1;
- TransformInterpolator::Method interpolation_method : 3;
-
- // For fixed timestep interpolation.
- // Note 32 bits is plenty for checksum, no need for real_t
- float transform_checksum_curr;
- float transform_checksum_prev;
-
Instance *lightmap = nullptr;
Rect2 lightmap_uv_scale;
int lightmap_slice_index;
@@ -587,13 +569,6 @@ public:
dynamic_gi = false;
redraw_if_visible = false;
- on_interpolate_list = false;
- on_interpolate_transform_list = false;
- interpolated = true;
- interpolation_method = TransformInterpolator::INTERP_LERP;
- transform_checksum_curr = 0.0;
- transform_checksum_prev = 0.0;
-
lightmap_slice_index = 0;
lightmap = nullptr;
lightmap_cull_index = 0;
@@ -1047,8 +1022,6 @@ public:
virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask);
virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center);
virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform);
- virtual void instance_set_interpolated(RID p_instance, bool p_interpolated);
- virtual void instance_reset_physics_interpolation(RID p_instance);
virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id);
virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material);
@@ -1430,12 +1403,6 @@ public:
virtual void set_physics_interpolation_enabled(bool p_enabled);
struct InterpolationData {
- void notify_free_instance(RID p_rid, Instance &r_instance);
- LocalVector instance_interpolate_update_list;
- LocalVector instance_transform_update_lists[2];
- LocalVector *instance_transform_update_list_curr = &instance_transform_update_lists[0];
- LocalVector *instance_transform_update_list_prev = &instance_transform_update_lists[1];
-
bool interpolation_enabled = false;
} _interpolation_data;
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 98286045def..38e173bf48f 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -82,8 +82,6 @@ public:
virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
- virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0;
- virtual void instance_reset_physics_interpolation(RID p_instance) = 0;
virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 2ab5c24ae8f..f8321f0448d 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -882,8 +882,6 @@ public:
FUNC2(instance_set_layer_mask, RID, uint32_t)
FUNC3(instance_set_pivot_data, RID, float, bool)
FUNC2(instance_set_transform, RID, const Transform3D &)
- FUNC2(instance_set_interpolated, RID, bool)
- FUNC1(instance_reset_physics_interpolation, RID)
FUNC2(instance_attach_object_instance_id, RID, ObjectID)
FUNC3(instance_set_blend_shape_weight, RID, int, float)
FUNC3(instance_set_surface_override_material, RID, int, RID)
diff --git a/servers/rendering_server.compat.inc b/servers/rendering_server.compat.inc
index 2ae2f3c8844..b3ccaa2da70 100644
--- a/servers/rendering_server.compat.inc
+++ b/servers/rendering_server.compat.inc
@@ -50,12 +50,22 @@ void RenderingServer::_canvas_item_add_circle_bind_compat_84523(RID p_item, cons
canvas_item_add_circle(p_item, p_pos, p_radius, p_color, false);
}
+void RenderingServer::_instance_set_interpolated_bind_compat_104269(RID p_instance, bool p_interpolated) {
+ WARN_PRINT_ONCE("instance_set_interpolated() is deprecated.");
+}
+
+void RenderingServer::_instance_reset_physics_interpolation_bind_compat_104269(RID p_instance) {
+ WARN_PRINT_ONCE("instance_reset_physics_interpolation() is deprecated.");
+}
+
void RenderingServer::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::_multimesh_allocate_data_bind_compat_99455, DEFVAL(false), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::_environment_set_fog_bind_compat_84792);
ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_multiline", "item", "points", "colors", "width"), &RenderingServer::_canvas_item_add_multiline_bind_compat_84523, DEFVAL(-1.0));
ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_rect", "item", "rect", "color"), &RenderingServer::_canvas_item_add_rect_bind_compat_84523);
ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_circle", "item", "pos", "radius", "color"), &RenderingServer::_canvas_item_add_circle_bind_compat_84523);
+ ClassDB::bind_compatibility_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::_instance_set_interpolated_bind_compat_104269);
+ ClassDB::bind_compatibility_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::_instance_reset_physics_interpolation_bind_compat_104269);
}
#endif
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index d0cb25d3237..f0768c1ef8b 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3164,8 +3164,6 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask);
ClassDB::bind_method(D_METHOD("instance_set_pivot_data", "instance", "sorting_offset", "use_aabb_center"), &RenderingServer::instance_set_pivot_data);
ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform);
- ClassDB::bind_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::instance_set_interpolated);
- ClassDB::bind_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::instance_reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id);
ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight);
ClassDB::bind_method(D_METHOD("instance_set_surface_override_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_override_material);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 1b678e2038f..c83309275ae 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -87,6 +87,8 @@ protected:
void _canvas_item_add_multiline_bind_compat_84523(RID p_item, const Vector &p_points, const Vector &p_colors, float p_width = -1.0);
void _canvas_item_add_rect_bind_compat_84523(RID p_item, const Rect2 &p_rect, const Color &p_color);
void _canvas_item_add_circle_bind_compat_84523(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color);
+ void _instance_set_interpolated_bind_compat_104269(RID p_instance, bool p_interpolated);
+ void _instance_reset_physics_interpolation_bind_compat_104269(RID p_instance);
static void _bind_compatibility_methods();
#endif
@@ -1442,8 +1444,6 @@ public:
virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
- virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0;
- virtual void instance_reset_physics_interpolation(RID p_instance) = 0;
virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;