diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp
new file mode 100644
index 000000000000..7cfe880b5a59
--- /dev/null
+++ b/core/math/transform_interpolator.cpp
@@ -0,0 +1,76 @@
+/**************************************************************************/
+/* transform_interpolator.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 "transform_interpolator.h"
+
+#include "core/math/transform_2d.h"
+
+void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
+ // Extract parameters.
+ Vector2 p1 = p_prev.get_origin();
+ Vector2 p2 = p_curr.get_origin();
+
+ // Special case for physics interpolation, if flipping, don't interpolate basis.
+ // If the determinant polarity changes, the handedness of the coordinate system changes.
+ if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) {
+ r_result.columns[0] = p_curr.columns[0];
+ r_result.columns[1] = p_curr.columns[1];
+ r_result.set_origin(p1.lerp(p2, p_fraction));
+ return;
+ }
+
+ real_t r1 = p_prev.get_rotation();
+ real_t r2 = p_curr.get_rotation();
+
+ Size2 s1 = p_prev.get_scale();
+ Size2 s2 = p_curr.get_scale();
+
+ // Slerp rotation.
+ Vector2 v1(Math::cos(r1), Math::sin(r1));
+ Vector2 v2(Math::cos(r2), Math::sin(r2));
+
+ real_t dot = v1.dot(v2);
+
+ dot = CLAMP(dot, -1, 1);
+
+ Vector2 v;
+
+ if (dot > 0.9995f) {
+ v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues.
+ } else {
+ real_t angle = p_fraction * Math::acos(dot);
+ Vector2 v3 = (v2 - v1 * dot).normalized();
+ v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
+ }
+
+ // Construct matrix.
+ r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction));
+ r_result.scale_basis(s1.lerp(s2, p_fraction));
+}
diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h
new file mode 100644
index 000000000000..a9bce2bd7fea
--- /dev/null
+++ b/core/math/transform_interpolator.h
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* transform_interpolator.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. */
+/**************************************************************************/
+
+#ifndef TRANSFORM_INTERPOLATOR_H
+#define TRANSFORM_INTERPOLATOR_H
+
+#include "core/math/math_defs.h"
+
+struct Transform2D;
+
+class TransformInterpolator {
+private:
+ static bool _sign(real_t p_val) { return p_val >= 0; }
+
+public:
+ static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
+};
+
+#endif // TRANSFORM_INTERPOLATOR_H
diff --git a/core/os/main_loop.h b/core/os/main_loop.h
index b45eb38aebfe..e48541d07415 100644
--- a/core/os/main_loop.h
+++ b/core/os/main_loop.h
@@ -62,6 +62,7 @@ public:
};
virtual void initialize();
+ virtual void iteration_prepare() {}
virtual bool physics_process(double p_time);
virtual bool process(double p_time);
virtual void finalize();
diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h
index 6478297fd196..e0047e0782ad 100644
--- a/core/templates/local_vector.h
+++ b/core/templates/local_vector.h
@@ -104,6 +104,22 @@ public:
return false;
}
+ U erase_multiple_unordered(const T &p_val) {
+ U from = 0;
+ U occurrences = 0;
+ while (true) {
+ int64_t idx = find(p_val, from);
+
+ if (idx == -1) {
+ break;
+ }
+ remove_at_unordered(idx);
+ from = idx;
+ occurrences++;
+ }
+ return occurrences;
+ }
+
void invert() {
for (U i = 0; i < count / 2; i++) {
SWAP(data[i], data[count - i - 1]);
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 43c3f5c1be67..697afed6367f 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1012,6 +1012,7 @@
Distance between the node's top edge and its parent control, based on [member anchor_top].
Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node.
+
By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot. Set this property to [member size] / 2 to pivot around the Control's center.
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index b786b679331d..c69e5edf0c97 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -619,6 +619,21 @@
[method request_ready] resets it back to [code]false[/code].
+
+
+
+ Returns [code]true[/code] if physics interpolation is enabled for this node (see [member physics_interpolation_mode]).
+ [b]Note:[/b] Interpolation will only be active if both the flag is set [b]and[/b] physics interpolation is enabled within the [SceneTree]. This can be tested using [method is_physics_interpolated_and_enabled].
+
+
+
+
+
+ Returns [code]true[/code] if physics interpolation is enabled (see [member physics_interpolation_mode]) [b]and[/b] enabled in the [SceneTree].
+ This is a convenience version of [method is_physics_interpolated] that also checks whether physics interpolation is enabled globally.
+ See [member SceneTree.physics_interpolation] and [member ProjectSettings.physics/common/physics_interpolation].
+
+
@@ -793,6 +808,15 @@
[b]Note:[/b] This method only affects the current node. If the node's children also need to request ready, this method needs to be called for each one of them. When the node and its children enter the tree again, the order of [method _ready] callbacks will be the same as normal.
+
+
+
+ When physics interpolation is active, moving a node to a radically different transform (such as placement within a level) can result in a visible glitch as the object is rendered moving from the old to new position over the physics tick.
+ That glitch can be prevented by calling this method, which temporarily disables interpolation until the physics tick is complete.
+ The notification [constant NOTIFICATION_RESET_PHYSICS_INTERPOLATION] will be received by the node and all children recursively.
+ [b]Note:[/b] This function should be called [b]after[/b] moving the node, rather than before.
+
+
@@ -964,6 +988,10 @@
The owner of this node. The owner must be an ancestor of this node. When packing the owner node in a [PackedScene], all the nodes it owns are also saved with it.
[b]Note:[/b] In the editor, nodes not owned by the scene root are usually not displayed in the Scene dock, and will [b]not[/b] be saved. To prevent this, remember to set the owner after calling [method add_child]. See also (see [member unique_name_in_owner])
+
+ Allows enabling or disabling physics interpolation per node, offering a finer grain of control than turning physics interpolation on and off globally. See [member ProjectSettings.physics/common/physics_interpolation] and [member SceneTree.physics_interpolation] for the global setting.
+ [b]Note:[/b] When teleporting a node to a distant position you should temporarily disable interpolation with [method Node.reset_physics_interpolation].
+
The node's processing behavior (see [enum ProcessMode]). To check if the node can process in its current mode, use [method can_process].
@@ -1122,6 +1150,9 @@
Notification received when the node is enabled again after being disabled. See [constant PROCESS_MODE_DISABLED].
+
+ Notification received when [method reset_physics_interpolation] is called on the node or its ancestors.
+
Notification received right before the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
@@ -1237,6 +1268,15 @@
Allows this node to process threaded messages created with [method call_deferred_thread_group] right before either [method _process] or [method _physics_process] are called.
+
+ Inherits [member physics_interpolation_mode] from the node's parent. This is the default for any newly created node.
+
+
+ Enables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT]. This is the default for the root node.
+
+
+ Disables physics interpolation for this node and for children set to [constant PHYSICS_INTERPOLATION_MODE_INHERIT].
+
Duplicate the node's signal connections.
diff --git a/doc/classes/Parallax2D.xml b/doc/classes/Parallax2D.xml
index 6db29b7a3374..4e712a7e2aac 100644
--- a/doc/classes/Parallax2D.xml
+++ b/doc/classes/Parallax2D.xml
@@ -25,6 +25,7 @@
Bottom-right limits for scrolling to end. If the camera is outside of this limit, the [Parallax2D] will stop scrolling. Must be higher than [member limit_begin] and the viewport size combined to work.
+
Repeats the [Texture2D] of each of this node's children and offsets them by this value. When scrolling, the node's position loops, giving the illusion of an infinite scrolling background if the values are larger than the screen size. If an axis is set to [code]0[/code], the [Texture2D] will not be repeated.
diff --git a/doc/classes/ParallaxLayer.xml b/doc/classes/ParallaxLayer.xml
index fb92c9d85f97..12482d6f6612 100644
--- a/doc/classes/ParallaxLayer.xml
+++ b/doc/classes/ParallaxLayer.xml
@@ -23,5 +23,6 @@
Multiplies the ParallaxLayer's motion. If an axis is set to [code]0[/code], it will not scroll.
+
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 407041289c9c..98e8939c3f40 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2266,9 +2266,15 @@
Controls the maximum number of physics steps that can be simulated each rendered frame. The default value is tuned to avoid "spiral of death" situations where expensive physics simulations trigger more expensive simulations indefinitely. However, the game will appear to slow down if the rendering FPS is less than [code]1 / max_physics_steps_per_frame[/code] of [member physics/common/physics_ticks_per_second]. This occurs even if [code]delta[/code] is consistently used in physics calculations. To avoid this, increase [member physics/common/max_physics_steps_per_frame] if you have increased [member physics/common/physics_ticks_per_second] significantly above its default value.
[b]Note:[/b] This property is only read when the project starts. To change the maximum number of simulated physics steps per frame at runtime, set [member Engine.max_physics_steps_per_frame] instead.
+
+ If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames. See also [member Node.physics_interpolation_mode] and [method Node.reset_physics_interpolation].
+ [b]Note:[/b] If [code]true[/code], the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
+ [b]Note:[/b] This property is only read when the project starts. To toggle physics interpolation at runtime, set [member SceneTree.physics_interpolation] instead.
+ [b]Note:[/b] This feature is currently only implemented in the 2D renderer.
+
Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
- [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0[/code].
+ [b]Note:[/b] When using a physics interpolation solution (such as enabling [member physics/common/physics_interpolation] or using a custom solution), the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 324e6d50b6d9..5efda5f83f45 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -424,6 +424,14 @@
[b]Note:[/b] The equivalent node is [CanvasItem].
+
+
+
+
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
+
+
@@ -504,6 +512,14 @@
Sets the index for the [CanvasItem].
+
+
+
+
+
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas item.
+
+
@@ -612,6 +628,15 @@
Sets the [CanvasItem]'s Z index, i.e. its draw order (lower indexes are drawn first).
+
+
+
+
+
+ Transforms both the current and previous stored transform for a canvas item.
+ This allows transforming a canvas item without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
+
+
@@ -644,6 +669,14 @@
[b]Note:[/b] The equivalent node is [LightOccluder2D].
+
+
+
+
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving an occluder to a new location, to give an instantaneous change rather than interpolation from the previous location.
+
+
@@ -659,6 +692,14 @@
Enables or disables light occluder.
+
+
+
+
+
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the light occluder.
+
+
@@ -683,6 +724,23 @@
Sets a light occluder's [Transform2D].
+
+
+
+
+
+ Transforms both the current and previous stored transform for a light occluder.
+ This allows transforming an occluder without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilising a shifting origin.
+
+
+
+
+
+
+ Prevents physics interpolation for the current physics tick.
+ This is useful when moving a canvas item to a new location, to give an instantaneous change rather than interpolation from the previous location.
+
+
@@ -723,6 +781,14 @@
Sets a canvas light's height.
+
+
+
+
+
+ If [param interpolated] is [code]true[/code], turns on physics interpolation for the canvas light.
+
+
@@ -829,6 +895,15 @@
Sets the Z range of objects that will be affected by this light. Equivalent to [member Light2D.range_z_min] and [member Light2D.range_z_max].
+
+
+
+
+
+ Transforms both the current and previous stored transform for a canvas light.
+ This allows transforming a light without creating a "glitch" in the interpolation, which is is particularly useful for large worlds utilising a shifting origin.
+
+
diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml
index f1bb5a1cf6ce..bae5fe12054b 100644
--- a/doc/classes/SceneTree.xml
+++ b/doc/classes/SceneTree.xml
@@ -264,6 +264,10 @@
- 2D and 3D physics will be stopped, as well as collision detection and related signals.
- Depending on each node's [member Node.process_mode], their [method Node._process], [method Node._physics_process] and [method Node._input] callback methods may not called anymore.
+
+ If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, so that smooth motion is seen even when physics ticks do not coincide with rendered frames.
+ The default value of this property is controlled by [member ProjectSettings.physics/common/physics_interpolation].
+
If [code]true[/code], the application quits automatically when navigating back (e.g. using the system "Back" button on Android).
To handle 'Go Back' button when this option is disabled, use [constant DisplayServer.WINDOW_EVENT_GO_BACK_REQUEST].
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 304dc9e328cb..9fa95a93f83d 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -38,6 +38,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
+#include "core/math/transform_interpolator.h"
#include "servers/rendering/rendering_server_default.h"
#include "storage/config.h"
#include "storage/material_storage.h"
@@ -226,7 +227,15 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
ERR_CONTINUE(!clight);
}
- Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
+ Transform2D final_xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
+ final_xform = l->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
+ }
+ // Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
+ Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;
@@ -820,7 +829,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
+ base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform; // Used by transform command
diff --git a/main/main.cpp b/main/main.cpp
index 9215f2e848ea..f1ba25bf02bd 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -3967,6 +3967,11 @@ bool Main::iteration() {
PhysicsServer3D::get_singleton()->flush_queries();
#endif // _3D_DISABLED
+ // Prepare the fixed timestep interpolated nodes BEFORE they are updated
+ // by the physics server, otherwise the current and previous transforms
+ // may be the same, and no interpolation takes place.
+ OS::get_singleton()->get_main_loop()->iteration_prepare();
+
PhysicsServer2D::get_singleton()->sync();
PhysicsServer2D::get_singleton()->flush_queries();
diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp
index 569930d42777..d358d9fa933e 100644
--- a/main/main_timer_sync.cpp
+++ b/main/main_timer_sync.cpp
@@ -299,6 +299,17 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
// before advance_core considers changing the physics_steps return from
// the typical values as defined by typical_physics_steps
double MainTimerSync::get_physics_jitter_fix() {
+ // Turn off jitter fix when using fixed timestep interpolation.
+ // Note this shouldn't be on UNTIL 3d interpolation is implemented,
+ // otherwise we will get people making 3d games with the physics_interpolation
+ // set to on getting jitter fix disabled unexpectedly.
+#if 0
+ if (Engine::get_singleton()->is_physics_interpolation_enabled()) {
+ // Would be better to write a simple bypass for jitter fix but this will do to get started.
+ return 0.0;
+ }
+#endif
+
return Engine::get_singleton()->get_physics_jitter_fix();
}
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 722858b674af..e9bab274c649 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -54,7 +54,12 @@ void Camera2D::_update_scroll() {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
- Transform2D xform = get_camera_transform();
+ Transform2D xform;
+ if (is_physics_interpolated_and_enabled()) {
+ xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
+ } else {
+ xform = get_camera_transform();
+ }
viewport->set_canvas_transform(xform);
@@ -68,15 +73,26 @@ void Camera2D::_update_scroll() {
}
void Camera2D::_update_process_callback() {
- if (_is_editing_in_editor()) {
+ if (is_physics_interpolated_and_enabled()) {
+ set_process_internal(is_current());
+ set_physics_process_internal(is_current());
+
+#ifdef TOOLS_ENABLED
+ if (process_callback == CAMERA2D_PROCESS_IDLE) {
+ WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation.");
+ }
+#endif
+ } else if (_is_editing_in_editor()) {
set_process_internal(false);
set_physics_process_internal(false);
- } else if (process_callback == CAMERA2D_PROCESS_IDLE) {
- set_process_internal(true);
- set_physics_process_internal(false);
} else {
- set_process_internal(false);
- set_physics_process_internal(true);
+ if (process_callback == CAMERA2D_PROCESS_IDLE) {
+ set_process_internal(true);
+ set_physics_process_internal(false);
+ } else {
+ set_process_internal(false);
+ set_physics_process_internal(true);
+ }
}
}
@@ -161,8 +177,15 @@ Transform2D Camera2D::get_camera_transform() {
}
}
+ // FIXME: There is a bug here, introduced before physics interpolation.
+ // Smoothing occurs rather confusingly during the call to get_camera_transform().
+ // It may be called MULTIPLE TIMES on certain frames,
+ // therefore smoothing is not currently applied only once per frame / tick,
+ // which will result in some haphazard results.
if (position_smoothing_enabled && !_is_editing_in_editor()) {
- real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
+ bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled();
+ real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time();
+ real_t c = position_smoothing_speed * delta;
smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
ret_camera_pos = smoothed_camera_pos;
//camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed;
@@ -223,19 +246,54 @@ Transform2D Camera2D::get_camera_transform() {
return xform.affine_inverse();
}
+void Camera2D::_ensure_update_interpolation_data() {
+ // The "curr -> previous" update can either occur
+ // on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR
+ // on NOTIFICATION_TRANSFORM_CHANGED,
+ // if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than
+ // NOTIFICATION_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;
+ }
+}
+
void Camera2D::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_INTERNAL_PROCESS:
- case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ case NOTIFICATION_INTERNAL_PROCESS: {
_update_scroll();
} break;
- case NOTIFICATION_TRANSFORM_CHANGED: {
- if (!position_smoothing_enabled || _is_editing_in_editor()) {
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ if (is_physics_interpolated_and_enabled()) {
+ _ensure_update_interpolation_data();
+ _interpolation_data.xform_curr = get_camera_transform();
+ } else {
_update_scroll();
}
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ // Force the limits etc. to update.
+ _interpolation_data.xform_curr = get_camera_transform();
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ } break;
+
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+ if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
+ _update_scroll();
+ }
+ if (is_physics_interpolated_and_enabled()) {
+ _ensure_update_interpolation_data();
+ _interpolation_data.xform_curr = get_camera_transform();
+ }
+ } break;
+
case NOTIFICATION_ENTER_TREE: {
ERR_FAIL_COND(!is_inside_tree());
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
@@ -260,6 +318,15 @@ void Camera2D::_notification(int p_what) {
_update_process_callback();
first = true;
_update_scroll();
+
+ // Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION
+ // is automatically called before this because Camera2D is inherited
+ // from CanvasItem. However, the camera transform is not up to date
+ // until this point, so we do an extra manual reset.
+ if (is_physics_interpolated_and_enabled()) {
+ _interpolation_data.xform_curr = get_camera_transform();
+ _interpolation_data.xform_prev = _interpolation_data.xform_curr;
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -431,12 +498,17 @@ void Camera2D::_make_current(Object *p_which) {
queue_redraw();
- if (p_which == this) {
+ bool was_current = viewport->get_camera_2d() == this;
+ bool is_current = p_which == this;
+
+ if (is_current) {
viewport->_camera_2d_set(this);
- } else {
- if (viewport->get_camera_2d() == this) {
- viewport->_camera_2d_set(nullptr);
- }
+ } else if (was_current) {
+ viewport->_camera_2d_set(nullptr);
+ }
+
+ if (is_current != was_current) {
+ _update_process_callback();
}
}
@@ -456,6 +528,7 @@ void Camera2D::make_current() {
_make_current(this);
}
_update_scroll();
+ _update_process_callback();
}
void Camera2D::clear_current() {
@@ -468,6 +541,8 @@ void Camera2D::clear_current() {
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
viewport->assign_next_enabled_camera_2d(group_name);
}
+
+ _update_process_callback();
}
bool Camera2D::is_current() const {
diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h
index 5693d05ee58d..bf25267aa8fb 100644
--- a/scene/2d/camera_2d.h
+++ b/scene/2d/camera_2d.h
@@ -102,6 +102,14 @@ protected:
Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE;
+ struct InterpolationData {
+ Transform2D xform_curr;
+ Transform2D xform_prev;
+ uint32_t last_update_physics_tick = 0;
+ } _interpolation_data;
+
+ void _ensure_update_interpolation_data();
+
Size2 _get_camera_screen_size() const;
protected:
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index c03786caef31..79732d78618d 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -197,6 +197,10 @@ Light2D::BlendMode Light2D::get_blend_mode() const {
return blend_mode;
}
+void Light2D::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated());
+}
+
void Light2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -212,6 +216,17 @@ void Light2D::_notification(int p_what) {
_update_light_visibility();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ // Explicitly make sure the transform is up to date in RenderingServer before
+ // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
+ // is normally deferred, and a client change to transform will not always be sent
+ // before the reset, so we need to guarantee this.
+ RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform());
+ RS::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light);
+ }
+ } break;
+
case NOTIFICATION_EXIT_TREE: {
RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID());
_update_light_visibility();
diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h
index 3c1171deae29..8a0c2a2a926b 100644
--- a/scene/2d/light_2d.h
+++ b/scene/2d/light_2d.h
@@ -74,6 +74,7 @@ private:
void _update_light_visibility();
virtual void owner_changed_notify() override;
+ virtual void _physics_interpolated_changed() override;
protected:
_FORCE_INLINE_ RID _get_light() const { return canvas_light; }
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index 61f5d5cd46bd..092c987ac0ba 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -158,6 +158,10 @@ void LightOccluder2D::_poly_changed() {
#endif
}
+void LightOccluder2D::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated());
+}
+
void LightOccluder2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_CANVAS: {
@@ -199,6 +203,17 @@ void LightOccluder2D::_notification(int p_what) {
case NOTIFICATION_EXIT_CANVAS: {
RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID());
} break;
+
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ // Explicitly make sure the transform is up to date in RenderingServer before
+ // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED
+ // is normally deferred, and a client change to transform will not always be sent
+ // before the reset, so we need to guarantee this.
+ RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform());
+ RS::get_singleton()->canvas_light_occluder_reset_physics_interpolation(occluder);
+ }
+ } break;
}
}
diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h
index dd3130394e49..4c499d0465cf 100644
--- a/scene/2d/light_occluder_2d.h
+++ b/scene/2d/light_occluder_2d.h
@@ -86,6 +86,8 @@ class LightOccluder2D : public Node2D {
bool sdf_collision = false;
void _poly_changed();
+ virtual void _physics_interpolated_changed() override;
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index f516fd41ac8a..7e47e0f061e6 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -129,6 +129,7 @@ void Parallax2D::_update_repeat() {
Point2 repeat_scale = repeat_size * get_scale();
RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_scale, repeat_times);
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false);
}
void Parallax2D::set_scroll_scale(const Size2 &p_scale) {
@@ -287,4 +288,6 @@ void Parallax2D::_bind_methods() {
}
Parallax2D::Parallax2D() {
+ // Parallax2D is always updated every frame so there is no need to interpolate.
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index 3dd0d7b61cd2..e2a7e9e15408 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -73,6 +73,7 @@ void ParallaxLayer::_update_mirroring() {
RID ci = get_canvas_item();
Point2 mirrorScale = mirroring * get_scale();
RenderingServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirrorScale);
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(ci, false);
}
}
@@ -162,4 +163,6 @@ void ParallaxLayer::_bind_methods() {
}
ParallaxLayer::ParallaxLayer() {
+ // ParallaxLayer is always updated every frame so there is no need to interpolate.
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index c7aa4c2be440..84c4c43a5ad8 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -356,6 +356,14 @@ void TileMapLayer::_rendering_update() {
// Drawing the tile in the canvas item.
TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset);
}
+
+ // Reset physics interpolation for any recreated canvas items.
+ if (is_physics_interpolated_and_enabled() && is_visible_in_tree()) {
+ for (const RID &ci : rendering_quadrant->canvas_items) {
+ rs->canvas_item_reset_physics_interpolation(ci);
+ }
+ }
+
} else {
// Free the quadrant.
for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) {
@@ -453,6 +461,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
}
}
}
+ } else if (p_what == NOTIFICATION_RESET_PHYSICS_INTERPOLATION) {
+ for (const KeyValue> &kv : rendering_quadrant_map) {
+ for (const RID &ci : kv.value->canvas_items) {
+ if (ci.is_null()) {
+ continue;
+ }
+ rs->canvas_item_reset_physics_interpolation(ci);
+ }
+ }
}
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index d65399446ca1..08edc65d7fc9 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -3690,6 +3690,8 @@ void Control::_bind_methods() {
Control::Control() {
data.theme_owner = memnew(ThemeOwner(this));
+
+ set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
}
Control::~Control() {
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 2d0da075bae2..768c83954bc6 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -336,6 +336,19 @@ void CanvasItem::_notification(int p_what) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
}
+ // If using physics interpolation, reset for this node only,
+ // as a helper, as in most cases, users will want items reset when
+ // adding to the tree.
+ // In cases where they move immediately after adding,
+ // there will be little cost in having two resets as these are cheap,
+ // and it is worth it for convenience.
+ // Do not propagate to children, as each child of an added branch
+ // receives its own NOTIFICATION_ENTER_TREE, and this would
+ // cause unnecessary duplicate resets.
+ if (is_physics_interpolated_and_enabled()) {
+ notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+ }
+
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_MAIN_THREAD_GUARD;
@@ -360,6 +373,12 @@ void CanvasItem::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (is_visible_in_tree() && is_physics_interpolated()) {
+ RenderingServer::get_singleton()->canvas_item_reset_physics_interpolation(canvas_item);
+ }
+ } break;
+
case NOTIFICATION_VISIBILITY_CHANGED: {
ERR_MAIN_THREAD_GUARD;
@@ -921,6 +940,10 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
}
}
+void CanvasItem::_physics_interpolated_changed() {
+ RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated());
+}
+
Rect2 CanvasItem::get_viewport_rect() const {
ERR_READ_THREAD_GUARD_V(Rect2());
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 03b01f7ef73d..383edeec93a6 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -140,6 +140,8 @@ private:
void _notify_transform(CanvasItem *p_node);
+ virtual void _physics_interpolated_changed() override;
+
static CanvasItem *current_item_drawn;
friend class Viewport;
void _refresh_texture_repeat_cache() const;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 604a322a8b11..142736e388fd 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -99,6 +99,14 @@ void Node::_notification(int p_notification) {
}
}
+ if (data.physics_interpolation_mode == PHYSICS_INTERPOLATION_MODE_INHERIT) {
+ bool interpolate = true; // Root node default is for interpolation to be on.
+ if (data.parent) {
+ interpolate = data.parent->is_physics_interpolated();
+ }
+ _propagate_physics_interpolated(interpolate);
+ }
+
// Update auto translate mode.
if (data.auto_translate_mode == AUTO_TRANSLATE_MODE_INHERIT && !data.parent) {
ERR_PRINT("The root node can't be set to Inherit auto translate mode, reverting to Always instead.");
@@ -395,6 +403,36 @@ void Node::_propagate_exit_tree() {
data.depth = -1;
}
+void Node::_propagate_physics_interpolated(bool p_interpolated) {
+ switch (data.physics_interpolation_mode) {
+ case PHYSICS_INTERPOLATION_MODE_INHERIT:
+ // Keep the parent p_interpolated.
+ break;
+ case PHYSICS_INTERPOLATION_MODE_OFF: {
+ p_interpolated = false;
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_ON: {
+ p_interpolated = true;
+ } break;
+ }
+
+ // No change? No need to propagate further.
+ if (data.physics_interpolated == p_interpolated) {
+ return;
+ }
+
+ data.physics_interpolated = p_interpolated;
+
+ // Allow a call to the RenderingServer etc. in derived classes.
+ _physics_interpolated_changed();
+
+ data.blocked++;
+ for (KeyValue &K : data.children) {
+ K.value->_propagate_physics_interpolated(p_interpolated);
+ }
+ data.blocked--;
+}
+
void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
ERR_FAIL_NULL(p_child);
@@ -507,6 +545,8 @@ void Node::move_child_notify(Node *p_child) {
void Node::owner_changed_notify() {
}
+void Node::_physics_interpolated_changed() {}
+
void Node::set_physics_process(bool p_process) {
ERR_THREAD_GUARD
if (data.physics_process == p_process) {
@@ -821,6 +861,42 @@ bool Node::_can_process(bool p_paused) const {
}
}
+void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
+ ERR_THREAD_GUARD
+ if (data.physics_interpolation_mode == p_mode) {
+ return;
+ }
+
+ data.physics_interpolation_mode = p_mode;
+
+ bool interpolate = true; // Default for root node.
+
+ switch (p_mode) {
+ case PHYSICS_INTERPOLATION_MODE_INHERIT: {
+ if (is_inside_tree() && data.parent) {
+ interpolate = data.parent->is_physics_interpolated();
+ }
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_OFF: {
+ interpolate = false;
+ } break;
+ case PHYSICS_INTERPOLATION_MODE_ON: {
+ interpolate = true;
+ } break;
+ }
+
+ // If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
+ if (is_physics_interpolated() && !interpolate) {
+ reset_physics_interpolation();
+ }
+
+ _propagate_physics_interpolated(interpolate);
+}
+
+void Node::reset_physics_interpolation() {
+ propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+}
+
bool Node::_is_enabled() const {
ProcessMode process_mode;
@@ -3515,6 +3591,12 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal);
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_mode", "mode"), &Node::set_physics_interpolation_mode);
+ ClassDB::bind_method(D_METHOD("get_physics_interpolation_mode"), &Node::get_physics_interpolation_mode);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolated_and_enabled"), &Node::is_physics_interpolated_and_enabled);
+ ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation);
+
ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode);
@@ -3620,6 +3702,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_DISABLED);
BIND_CONSTANT(NOTIFICATION_ENABLED);
+ BIND_CONSTANT(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE);
BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE);
@@ -3659,6 +3742,10 @@ void Node::_bind_methods() {
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS);
BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_ALL);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_INHERIT);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_ON);
+ BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_OFF);
+
BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS);
BIND_ENUM_CONSTANT(DUPLICATE_GROUPS);
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
@@ -3700,6 +3787,9 @@ void Node::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages");
+ ADD_GROUP("Physics Interpolation", "physics_interpolation_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_mode", PROPERTY_HINT_ENUM, "Inherit,Off,On"), "set_physics_interpolation_mode", "get_physics_interpolation_mode");
+
ADD_GROUP("Auto Translate", "auto_translate_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "auto_translate_mode", PROPERTY_HINT_ENUM, "Inherit,Always,Disabled"), "set_auto_translate_mode", "get_auto_translate_mode");
@@ -3734,6 +3824,35 @@ String Node::_get_name_num_separator() {
Node::Node() {
orphan_node_count++;
+
+ // Default member initializer for bitfield is a C++20 extension, so:
+
+ data.process_mode = PROCESS_MODE_INHERIT;
+ data.physics_interpolation_mode = PHYSICS_INTERPOLATION_MODE_INHERIT;
+
+ data.physics_process = false;
+ data.process = false;
+
+ data.physics_process_internal = false;
+ data.process_internal = false;
+
+ data.input = false;
+ data.shortcut_input = false;
+ data.unhandled_input = false;
+ data.unhandled_key_input = false;
+
+ data.physics_interpolated = false;
+
+ data.parent_owned = false;
+ data.in_constructor = true;
+ data.use_placeholder = false;
+
+ data.display_folded = false;
+ data.editable_instance = false;
+
+ data.inside_tree = false;
+ data.ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
+ data.ready_first = true;
}
Node::~Node() {
diff --git a/scene/main/node.h b/scene/main/node.h
index 916d44c48cf3..f49eeec9cd79 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -66,7 +66,9 @@ protected:
};
public:
- enum ProcessMode {
+ // N.B. Any enum stored as a bitfield should be specified as UNSIGNED to work around
+ // some compilers trying to store it as signed, and requiring 1 more bit than necessary.
+ enum ProcessMode : unsigned int {
PROCESS_MODE_INHERIT, // same as parent node
PROCESS_MODE_PAUSABLE, // process only if not paused
PROCESS_MODE_WHEN_PAUSED, // process only if paused
@@ -86,6 +88,12 @@ public:
FLAG_PROCESS_THREAD_MESSAGES_ALL = 3,
};
+ enum PhysicsInterpolationMode : unsigned int {
+ PHYSICS_INTERPOLATION_MODE_INHERIT,
+ PHYSICS_INTERPOLATION_MODE_ON,
+ PHYSICS_INTERPOLATION_MODE_OFF,
+ };
+
enum DuplicateFlags {
DUPLICATE_SIGNALS = 1,
DUPLICATE_GROUPS = 2,
@@ -170,9 +178,7 @@ private:
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
SceneTree *tree = nullptr;
- bool inside_tree = false;
- bool ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification.
- bool ready_first = true;
+
#ifdef TOOLS_ENABLED
NodePath import_path; // Path used when imported, used by scene editors to keep tracking.
#endif
@@ -184,7 +190,6 @@ private:
List::Element *OW = nullptr; // Owned element.
List owned;
- ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT;
Node *process_thread_group_owner = nullptr;
@@ -196,26 +201,39 @@ private:
Variant rpc_config;
// Variables used to properly sort the node when processing, ignored otherwise.
- // TODO: Should move all the stuff below to bits.
- bool physics_process = false;
- bool process = false;
int process_priority = 0;
int physics_process_priority = 0;
- bool physics_process_internal = false;
- bool process_internal = false;
+ // Keep bitpacked values together to get better packing.
+ ProcessMode process_mode : 3;
+ PhysicsInterpolationMode physics_interpolation_mode : 2;
- bool input = false;
- bool shortcut_input = false;
- bool unhandled_input = false;
- bool unhandled_key_input = false;
+ bool physics_process : 1;
+ bool process : 1;
- bool parent_owned = false;
- bool in_constructor = true;
- bool use_placeholder = false;
+ bool physics_process_internal : 1;
+ bool process_internal : 1;
- bool display_folded = false;
- bool editable_instance = false;
+ bool input : 1;
+ bool shortcut_input : 1;
+ bool unhandled_input : 1;
+ bool unhandled_key_input : 1;
+
+ // Physics interpolation can be turned on and off on a per node basis.
+ // This only takes effect when the SceneTree (or project setting) physics interpolation
+ // is switched on.
+ bool physics_interpolated : 1;
+
+ bool parent_owned : 1;
+ bool in_constructor : 1;
+ bool use_placeholder : 1;
+
+ bool display_folded : 1;
+ bool editable_instance : 1;
+
+ bool inside_tree : 1;
+ bool ready_notified : 1;
+ bool ready_first : 1;
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
mutable bool is_auto_translating = true;
@@ -243,6 +261,7 @@ private:
void _propagate_ready();
void _propagate_exit_tree();
void _propagate_after_exit_tree();
+ void _propagate_physics_interpolated(bool p_interpolated);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
Array _get_node_and_resource(const NodePath &p_path);
@@ -296,6 +315,8 @@ protected:
void _notification(int p_notification);
+ virtual void _physics_interpolated_changed();
+
virtual void add_child_notify(Node *p_child);
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
@@ -340,7 +361,7 @@ protected:
public:
enum {
- // you can make your own, but don't use the same numbers as other notifications in other nodes
+ // You can make your own, but don't use the same numbers as other notifications in other nodes.
NOTIFICATION_ENTER_TREE = 10,
NOTIFICATION_EXIT_TREE = 11,
NOTIFICATION_MOVED_IN_PARENT = 12,
@@ -361,8 +382,8 @@ public:
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
- //keep these linked to node
-
+ NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 2001, // A GodotSpace Odyssey.
+ // Keep these linked to Node.
NOTIFICATION_WM_MOUSE_ENTER = 1002,
NOTIFICATION_WM_MOUSE_EXIT = 1003,
NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004,
@@ -614,6 +635,13 @@ public:
ProcessMode get_process_mode() const;
bool can_process() const;
bool can_process_notification(int p_what) const;
+
+ void set_physics_interpolation_mode(PhysicsInterpolationMode p_mode);
+ PhysicsInterpolationMode get_physics_interpolation_mode() const { return data.physics_interpolation_mode; }
+ _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; }
+ _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); }
+ void reset_physics_interpolation();
+
bool is_enabled() const;
bool is_ready() const;
@@ -743,6 +771,7 @@ VARIANT_ENUM_CAST(Node::ProcessMode);
VARIANT_ENUM_CAST(Node::ProcessThreadGroup);
VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages);
VARIANT_ENUM_CAST(Node::InternalMode);
+VARIANT_ENUM_CAST(Node::PhysicsInterpolationMode);
VARIANT_ENUM_CAST(Node::AutoTranslateMode);
typedef HashSet NodeSet;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index cb16f425b56b..0c77a60af682 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -451,6 +451,30 @@ void SceneTree::initialize() {
root->_set_tree(this);
}
+void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
+ // We never want interpolation in the editor.
+ if (Engine::get_singleton()->is_editor_hint()) {
+ p_enabled = false;
+ }
+
+ if (p_enabled == _physics_interpolation_enabled) {
+ return;
+ }
+
+ _physics_interpolation_enabled = p_enabled;
+ RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
+}
+
+bool SceneTree::is_physics_interpolation_enabled() const {
+ return _physics_interpolation_enabled;
+}
+
+void SceneTree::iteration_prepare() {
+ if (_physics_interpolation_enabled) {
+ RenderingServer::get_singleton()->tick();
+ }
+}
+
bool SceneTree::physics_process(double p_time) {
root_lock++;
@@ -1606,6 +1630,9 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_frame"), &SceneTree::get_frame);
ClassDB::bind_method(D_METHOD("quit", "exit_code"), &SceneTree::quit, DEFVAL(EXIT_SUCCESS));
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_enabled", "enabled"), &SceneTree::set_physics_interpolation_enabled);
+ ClassDB::bind_method(D_METHOD("is_physics_interpolation_enabled"), &SceneTree::is_physics_interpolation_enabled);
+
ClassDB::bind_method(D_METHOD("queue_delete", "obj"), &SceneTree::queue_delete);
MethodInfo mi;
@@ -1657,6 +1684,7 @@ void SceneTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
ADD_SIGNAL(MethodInfo("tree_changed"));
ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time
@@ -1748,6 +1776,8 @@ SceneTree::SceneTree() {
root->set_as_audio_listener_3d(true);
#endif // _3D_DISABLED
+ set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
+
// Initialize network state.
set_multiplayer(MultiplayerAPI::create_default_interface());
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index a7515f0243e8..603438cc5f34 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -139,6 +139,8 @@ private:
HashMap group_map;
bool _quit = false;
+ bool _physics_interpolation_enabled = false;
+
StringName tree_changed_name = "tree_changed";
StringName node_added_name = "node_added";
StringName node_removed_name = "node_removed";
@@ -313,6 +315,8 @@ public:
virtual void initialize() override;
+ virtual void iteration_prepare() override;
+
virtual bool physics_process(double p_time) override;
virtual bool process(double p_time) override;
@@ -425,6 +429,9 @@ public:
void set_disable_node_threading(bool p_disable);
//default texture settings
+ void set_physics_interpolation_enabled(bool p_enabled);
+ bool is_physics_interpolation_enabled() const;
+
SceneTree();
~SceneTree();
};
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 8e342375f86b..e32164ea981b 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
+#include "core/math/transform_interpolator.h"
#include "renderer_viewport.h"
#include "rendering_server_default.h"
#include "rendering_server_globals.h"
@@ -81,7 +82,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
if (r_items) {
r_items[r_index] = child_items[i];
child_items[i]->ysort_xform = p_transform;
- child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.columns[2]);
+ child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]);
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_index = r_index;
@@ -98,7 +99,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2
r_index++;
if (child_items[i]->sort_y) {
- _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
+ _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
@@ -244,7 +245,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
}
- Transform2D xform = ci->xform;
+ Transform2D final_xform;
+ if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
+ final_xform = ci->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
+ }
+
Transform2D parent_xform = p_parent_xform;
Point2 repeat_size = p_repeat_size;
@@ -258,19 +266,19 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
ci->repeat_times = repeat_times;
if (repeat_size.x || repeat_size.y) {
- rect.size += repeat_size * repeat_times / ci->xform.get_scale();
+ rect.size += repeat_size * repeat_times / final_xform.get_scale();
rect.position -= repeat_size * (repeat_times / 2);
}
}
if (snapping_2d_transforms_to_pixel) {
- xform.columns[2] = xform.columns[2].round();
+ final_xform.columns[2] = final_xform.columns[2].round();
parent_xform.columns[2] = parent_xform.columns[2].round();
}
- xform = parent_xform * xform;
+ final_xform = parent_xform * final_xform;
- Rect2 global_rect = xform.xform(rect);
+ Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) {
@@ -324,7 +332,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));
- ci->ysort_xform = ci->xform.affine_inverse();
+ ci->ysort_xform = final_xform.affine_inverse();
ci->ysort_pos = Vector2();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
@@ -337,7 +345,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
- _cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -347,7 +355,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
canvas_group_from = r_z_last_list[zidx];
}
- _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+ _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -361,14 +369,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
- _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+ _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
}
}
}
@@ -512,7 +520,16 @@ void RendererCanvasCull::canvas_item_set_transform(RID p_item, const Transform2D
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
- canvas_item->xform = p_transform;
+ if (_interpolation_data.interpolation_enabled && canvas_item->interpolated) {
+ if (!canvas_item->on_interpolate_transform_list) {
+ _interpolation_data.canvas_item_transform_update_list_curr->push_back(p_item);
+ canvas_item->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_item_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ canvas_item->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) {
@@ -1622,6 +1639,26 @@ bool RendererCanvasCull::canvas_item_get_debug_redraw() const {
return debug_redraw;
}
+void RendererCanvasCull::canvas_item_set_interpolated(RID p_item, bool p_interpolated) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_item_reset_physics_interpolation(RID p_item) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->xform_prev = canvas_item->xform_curr;
+}
+
+// Useful especially for origin shifting.
+void RendererCanvasCull::canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ canvas_item->xform_prev = p_transform * canvas_item->xform_prev;
+ canvas_item->xform_curr = p_transform * canvas_item->xform_curr;
+}
+
void RendererCanvasCull::canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin, bool p_fit_empty, float p_fit_margin, bool p_blur_mipmaps) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
@@ -1720,7 +1757,16 @@ void RendererCanvasCull::canvas_light_set_transform(RID p_light, const Transform
RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
ERR_FAIL_NULL(clight);
- clight->xform = p_transform;
+ if (_interpolation_data.interpolation_enabled && clight->interpolated) {
+ if (!clight->on_interpolate_transform_list) {
+ _interpolation_data.canvas_light_transform_update_list_curr->push_back(p_light);
+ clight->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_light_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ clight->xform_curr = p_transform;
}
void RendererCanvasCull::canvas_light_set_texture(RID p_light, RID p_texture) {
@@ -1839,6 +1885,25 @@ void RendererCanvasCull::canvas_light_set_shadow_smooth(RID p_light, float p_smo
clight->shadow_smooth = p_smooth;
}
+void RendererCanvasCull::canvas_light_set_interpolated(RID p_light, bool p_interpolated) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_light_reset_physics_interpolation(RID p_light) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->xform_prev = clight->xform_curr;
+}
+
+void RendererCanvasCull::canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) {
+ RendererCanvasRender::Light *clight = canvas_light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(clight);
+ clight->xform_prev = p_transform * clight->xform_prev;
+ clight->xform_curr = p_transform * clight->xform_curr;
+}
+
RID RendererCanvasCull::canvas_light_occluder_allocate() {
return canvas_light_occluder_owner.allocate_rid();
}
@@ -1911,7 +1976,16 @@ void RendererCanvasCull::canvas_light_occluder_set_transform(RID p_occluder, con
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
ERR_FAIL_NULL(occluder);
- occluder->xform = p_xform;
+ if (_interpolation_data.interpolation_enabled && occluder->interpolated) {
+ if (!occluder->on_interpolate_transform_list) {
+ _interpolation_data.canvas_light_occluder_transform_update_list_curr->push_back(p_occluder);
+ occluder->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(_interpolation_data.canvas_light_occluder_transform_update_list_curr->size() > 0);
+ }
+ }
+
+ occluder->xform_curr = p_xform;
}
void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) {
@@ -1921,6 +1995,25 @@ void RendererCanvasCull::canvas_light_occluder_set_light_mask(RID p_occluder, in
occluder->light_mask = p_mask;
}
+void RendererCanvasCull::canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->interpolated = p_interpolated;
+}
+
+void RendererCanvasCull::canvas_light_occluder_reset_physics_interpolation(RID p_occluder) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->xform_prev = occluder->xform_curr;
+}
+
+void RendererCanvasCull::canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) {
+ RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_occluder);
+ ERR_FAIL_NULL(occluder);
+ occluder->xform_prev = p_transform * occluder->xform_prev;
+ occluder->xform_curr = p_transform * occluder->xform_curr;
+}
+
RID RendererCanvasCull::canvas_occluder_polygon_allocate() {
return canvas_light_occluder_polygon_owner.allocate_rid();
}
@@ -2075,6 +2168,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_item_owner.owns(p_rid)) {
Item *canvas_item = canvas_item_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_item, true);
+ _interpolation_data.notify_free_canvas_item(p_rid, *canvas_item);
if (canvas_item->parent.is_valid()) {
if (canvas_owner.owns(canvas_item->parent)) {
@@ -2114,6 +2208,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_owner.owns(p_rid)) {
RendererCanvasRender::Light *canvas_light = canvas_light_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(canvas_light, true);
+ _interpolation_data.notify_free_canvas_light(p_rid, *canvas_light);
if (canvas_light->canvas.is_valid()) {
Canvas *canvas = canvas_owner.get_or_null(canvas_light->canvas);
@@ -2129,6 +2224,7 @@ bool RendererCanvasCull::free(RID p_rid) {
} else if (canvas_light_occluder_owner.owns(p_rid)) {
RendererCanvasRender::LightOccluderInstance *occluder = canvas_light_occluder_owner.get_or_null(p_rid);
ERR_FAIL_NULL_V(occluder, true);
+ _interpolation_data.notify_free_canvas_light_occluder(p_rid, *occluder);
if (occluder->polygon.is_valid()) {
LightOccluderPolygon *occluder_poly = canvas_light_occluder_polygon_owner.get_or_null(occluder->polygon);
@@ -2186,6 +2282,81 @@ void RendererCanvasCull::finalize() {
_free_rids(canvas_light_occluder_polygon_owner, "CanvasLightOccluderPolygon");
}
+void RendererCanvasCull::tick() {
+ if (_interpolation_data.interpolation_enabled) {
+ update_interpolation_tick(true);
+ }
+}
+
+void RendererCanvasCull::update_interpolation_tick(bool p_process) {
+#define GODOT_UPDATE_INTERPOLATION_TICK(m_list_prev, m_list_curr, m_type, m_owner_list) \
+ /* Detect any that were on the previous transform list that are no longer active. */ \
+ for (unsigned int n = 0; n < _interpolation_data.m_list_prev->size(); n++) { \
+ const RID &rid = (*_interpolation_data.m_list_prev)[n]; \
+ m_type *item = m_owner_list.get_or_null(rid); \
+ /* no longer active? (either the instance deleted or no longer being transformed) */ \
+ if (item && !item->on_interpolate_transform_list) { \
+ item->xform_prev = item->xform_curr; \
+ } \
+ } \
+ /* and now for any in the transform list (being actively interpolated), */ \
+ /* keep the previous transform value up to date and ready for next tick */ \
+ if (p_process) { \
+ for (unsigned int n = 0; n < _interpolation_data.m_list_curr->size(); n++) { \
+ const RID &rid = (*_interpolation_data.m_list_curr)[n]; \
+ m_type *item = m_owner_list.get_or_null(rid); \
+ if (item) { \
+ item->xform_prev = item->xform_curr; \
+ item->on_interpolate_transform_list = false; \
+ } \
+ } \
+ } \
+ SWAP(_interpolation_data.m_list_curr, _interpolation_data.m_list_prev); \
+ _interpolation_data.m_list_curr->clear();
+
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_item_transform_update_list_prev, canvas_item_transform_update_list_curr, Item, canvas_item_owner);
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_transform_update_list_prev, canvas_light_transform_update_list_curr, RendererCanvasRender::Light, canvas_light_owner);
+ GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_occluder_transform_update_list_prev, canvas_light_occluder_transform_update_list_curr, RendererCanvasRender::LightOccluderInstance, canvas_light_occluder_owner);
+
+#undef GODOT_UPDATE_INTERPOLATION_TICK
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item) {
+ r_canvas_item.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_item_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_item_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light) {
+ r_canvas_light.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_light_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_light_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
+void RendererCanvasCull::InterpolationData::notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder) {
+ r_canvas_light_occluder.on_interpolate_transform_list = false;
+
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // If the instance was on any of the lists, remove.
+ canvas_light_occluder_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ canvas_light_occluder_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
RendererCanvasCull::RendererCanvasCull() {
z_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
z_last_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index d733a7bed2c7..961506ca282d 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -271,6 +271,10 @@ public:
void canvas_item_set_debug_redraw(bool p_enabled);
bool canvas_item_get_debug_redraw() const;
+ void canvas_item_set_interpolated(RID p_item, bool p_interpolated);
+ void canvas_item_reset_physics_interpolation(RID p_item);
+ void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform);
+
RID canvas_light_allocate();
void canvas_light_initialize(RID p_rid);
@@ -297,6 +301,10 @@ public:
void canvas_light_set_shadow_color(RID p_light, const Color &p_color);
void canvas_light_set_shadow_smooth(RID p_light, float p_smooth);
+ void canvas_light_set_interpolated(RID p_light, bool p_interpolated);
+ void canvas_light_reset_physics_interpolation(RID p_light);
+ void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform);
+
RID canvas_light_occluder_allocate();
void canvas_light_occluder_initialize(RID p_rid);
@@ -307,6 +315,10 @@ public:
void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform);
void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask);
+ void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated);
+ void canvas_light_occluder_reset_physics_interpolation(RID p_occluder);
+ void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform);
+
RID canvas_occluder_polygon_allocate();
void canvas_occluder_polygon_initialize(RID p_rid);
@@ -336,6 +348,32 @@ public:
void finalize();
+ /* INTERPOLATION */
+
+ void tick();
+ void update_interpolation_tick(bool p_process = true);
+ void set_physics_interpolation_enabled(bool p_enabled) { _interpolation_data.interpolation_enabled = p_enabled; }
+
+ struct InterpolationData {
+ void notify_free_canvas_item(RID p_rid, RendererCanvasCull::Item &r_canvas_item);
+ void notify_free_canvas_light(RID p_rid, RendererCanvasRender::Light &r_canvas_light);
+ void notify_free_canvas_light_occluder(RID p_rid, RendererCanvasRender::LightOccluderInstance &r_canvas_light_occluder);
+
+ LocalVector canvas_item_transform_update_lists[2];
+ LocalVector *canvas_item_transform_update_list_curr = &canvas_item_transform_update_lists[0];
+ LocalVector *canvas_item_transform_update_list_prev = &canvas_item_transform_update_lists[1];
+
+ LocalVector canvas_light_transform_update_lists[2];
+ LocalVector *canvas_light_transform_update_list_curr = &canvas_light_transform_update_lists[0];
+ LocalVector *canvas_light_transform_update_list_prev = &canvas_light_transform_update_lists[1];
+
+ LocalVector canvas_light_occluder_transform_update_lists[2];
+ LocalVector *canvas_light_occluder_transform_update_list_curr = &canvas_light_occluder_transform_update_lists[0];
+ LocalVector *canvas_light_occluder_transform_update_list_prev = &canvas_light_occluder_transform_update_lists[1];
+
+ bool interpolation_enabled = false;
+ } _interpolation_data;
+
RendererCanvasCull();
~RendererCanvasCull();
};
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index cf8f6dcb2ee2..4a5654893293 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -51,9 +51,12 @@ public:
};
struct Light {
- bool enabled;
+ bool enabled : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
Color color;
- Transform2D xform;
+ Transform2D xform_curr;
+ Transform2D xform_prev;
float height;
float energy;
float scale;
@@ -97,6 +100,8 @@ public:
Light() {
version = 0;
enabled = true;
+ on_interpolate_transform_list = false;
+ interpolated = true;
color = Color(1, 1, 1);
shadow_color = Color(0, 0, 0, 0);
height = 0;
@@ -307,11 +312,17 @@ public:
Rect2 rect;
};
- Transform2D xform;
- bool clip;
- bool visible;
- bool behind;
- bool update_when_visible;
+ // For interpolation we store the current local xform,
+ // and the previous xform from the previous tick.
+ Transform2D xform_curr;
+ Transform2D xform_prev;
+
+ bool clip : 1;
+ bool visible : 1;
+ bool behind : 1;
+ bool update_when_visible : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
struct CanvasGroup {
RS::CanvasGroupMode mode;
@@ -472,6 +483,8 @@ public:
texture_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
repeat_source = false;
+ on_interpolate_transform_list = false;
+ interpolated = true;
}
virtual ~Item() {
clear();
@@ -487,12 +500,15 @@ public:
virtual void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) = 0;
struct LightOccluderInstance {
- bool enabled;
+ bool enabled : 1;
+ bool on_interpolate_transform_list : 1;
+ bool interpolated : 1;
RID canvas;
RID polygon;
RID occluder;
Rect2 aabb_cache;
- Transform2D xform;
+ Transform2D xform_curr;
+ Transform2D xform_prev;
Transform2D xform_cache;
int light_mask;
bool sdf_collision;
@@ -502,6 +518,8 @@ public:
LightOccluderInstance() {
enabled = true;
+ on_interpolate_transform_list = false;
+ interpolated = false;
sdf_collision = false;
next = nullptr;
light_mask = 1;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 673afc53e540..6f5671115164 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -34,6 +34,7 @@
#include "core/math/geometry_2d.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
+#include "core/math/transform_interpolator.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
#include "servers/rendering/renderer_rd/storage_rd/particles_storage.h"
@@ -427,7 +428,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform.get_scale());
+ base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
}
Transform2D draw_transform;
@@ -1366,7 +1367,15 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
ERR_CONTINUE(!clight);
}
- Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss
+ Transform2D final_xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !l->interpolated) {
+ final_xform = l->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(l->xform_prev, l->xform_curr, final_xform, f);
+ }
+ // Convert light position to canvas coordinates, as all computation is done in canvas coordinates to avoid precision loss.
+ Vector2 canvas_light_pos = p_canvas_transform.xform(final_xform.get_origin());
state.light_uniforms[index].position[0] = canvas_light_pos.x;
state.light_uniforms[index].position[1] = canvas_light_pos.y;
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index baa198626c0c..bafabf16b854 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -31,6 +31,7 @@
#include "renderer_viewport.h"
#include "core/config/project_settings.h"
+#include "core/math/transform_interpolator.h"
#include "core/object/worker_thread_pool.h"
#include "renderer_canvas_cull.h"
#include "renderer_scene_cull.h"
@@ -339,7 +340,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
if (sdf_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
@@ -378,7 +386,14 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Vector2 offset = tsize / 2.0;
cl->rect_cache = Rect2(-offset + cl->texture_offset, tsize);
- cl->xform_cache = xf * cl->xform;
+
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
+ cl->xform_cache = xf * cl->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
+ cl->xform_cache = xf * cl->xform_cache;
+ }
if (clip_rect.intersects_transformed(cl->xform_cache, cl->rect_cache)) {
cl->filter_next_ptr = lights;
@@ -386,7 +401,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
Transform2D scale;
scale.scale(cl->rect_cache.size);
scale.columns[2] = cl->rect_cache.position;
- cl->light_shader_xform = xf * cl->xform * scale;
+ cl->light_shader_xform = cl->xform_cache * scale;
if (cl->use_shadow) {
cl->shadows_next_ptr = lights_with_shadow;
if (lights_with_shadow == nullptr) {
@@ -406,7 +421,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (cl->enabled) {
cl->filter_next_ptr = directional_lights;
directional_lights = cl;
- cl->xform_cache = xf * cl->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !cl->interpolated) {
+ cl->xform_cache = xf * cl->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(cl->xform_prev, cl->xform_curr, cl->xform_cache, f);
+ cl->xform_cache = xf * cl->xform_cache;
+ }
cl->xform_cache.columns[2] = Vector2(); //translation is pointless
if (cl->use_shadow) {
cl->shadows_next_ptr = directional_lights_with_shadow;
@@ -441,7 +462,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
if (shadow_rect.intersects_transformed(F->xform_cache, F->aabb_cache)) {
F->next = occluders;
occluders = F;
@@ -521,7 +548,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
if (!F->enabled) {
continue;
}
- F->xform_cache = xf * F->xform;
+ if (!RSG::canvas->_interpolation_data.interpolation_enabled || !F->interpolated) {
+ F->xform_cache = xf * F->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(F->xform_prev, F->xform_curr, F->xform_cache, f);
+ F->xform_cache = xf * F->xform_cache;
+ }
Transform2D localizer = F->xform_cache.affine_inverse();
for (int j = 0; j < point_count; j++) {
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b4c539dcff43..f614070f689e 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -370,6 +370,16 @@ void RenderingServerDefault::_thread_loop() {
_finish();
}
+/* INTERPOLATION */
+
+void RenderingServerDefault::tick() {
+ RSG::canvas->tick();
+}
+
+void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
+ RSG::canvas->set_physics_interpolation_enabled(p_enabled);
+}
+
/* EVENT QUEUING */
void RenderingServerDefault::sync() {
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 99fd683e1d4d..139624c777b0 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -926,6 +926,10 @@ public:
FUNC1(canvas_item_set_debug_redraw, bool)
FUNC0RC(bool, canvas_item_get_debug_redraw)
+ FUNC2(canvas_item_set_interpolated, RID, bool)
+ FUNC1(canvas_item_reset_physics_interpolation, RID)
+ FUNC2(canvas_item_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_light)
FUNC2(canvas_light_set_mode, RID, CanvasLightMode)
@@ -952,6 +956,10 @@ public:
FUNC2(canvas_light_set_shadow_color, RID, const Color &)
FUNC2(canvas_light_set_shadow_smooth, RID, float)
+ FUNC2(canvas_light_set_interpolated, RID, bool)
+ FUNC1(canvas_light_reset_physics_interpolation, RID)
+ FUNC2(canvas_light_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_light_occluder)
FUNC2(canvas_light_occluder_attach_to_canvas, RID, RID)
FUNC2(canvas_light_occluder_set_enabled, RID, bool)
@@ -960,6 +968,10 @@ public:
FUNC2(canvas_light_occluder_set_transform, RID, const Transform2D &)
FUNC2(canvas_light_occluder_set_light_mask, RID, int)
+ FUNC2(canvas_light_occluder_set_interpolated, RID, bool)
+ FUNC1(canvas_light_occluder_reset_physics_interpolation, RID)
+ FUNC2(canvas_light_occluder_transform_physics_interpolation, RID, const Transform2D &)
+
FUNCRIDSPLIT(canvas_occluder_polygon)
FUNC3(canvas_occluder_polygon_set_shape, RID, const Vector &, bool)
@@ -1021,6 +1033,11 @@ public:
}
}
+ /* INTERPOLATION */
+
+ virtual void tick() override;
+ virtual void set_physics_interpolation_enabled(bool p_enabled) override;
+
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) override;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 1ed04248392f..994f6ad8b455 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3219,6 +3219,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_modulate", "item", "color"), &RenderingServer::canvas_item_set_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_self_modulate", "item", "color"), &RenderingServer::canvas_item_set_self_modulate);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_behind_parent", "item", "enabled"), &RenderingServer::canvas_item_set_draw_behind_parent);
+ ClassDB::bind_method(D_METHOD("canvas_item_set_interpolated", "item", "interpolated"), &RenderingServer::canvas_item_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_item_reset_physics_interpolation", "item"), &RenderingServer::canvas_item_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_item_transform_physics_interpolation", "item", "transform"), &RenderingServer::canvas_item_transform_physics_interpolation);
/* Primitives */
@@ -3302,6 +3305,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_color", "light", "color"), &RenderingServer::canvas_light_set_shadow_color);
ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_smooth", "light", "smooth"), &RenderingServer::canvas_light_set_shadow_smooth);
ClassDB::bind_method(D_METHOD("canvas_light_set_blend_mode", "light", "mode"), &RenderingServer::canvas_light_set_blend_mode);
+ ClassDB::bind_method(D_METHOD("canvas_light_set_interpolated", "light", "interpolated"), &RenderingServer::canvas_light_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_light_reset_physics_interpolation", "light"), &RenderingServer::canvas_light_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_light_transform_physics_interpolation", "light", "transform"), &RenderingServer::canvas_light_transform_physics_interpolation);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_POINT);
BIND_ENUM_CONSTANT(CANVAS_LIGHT_MODE_DIRECTIONAL);
@@ -3324,6 +3330,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_as_sdf_collision", "occluder", "enable"), &RenderingServer::canvas_light_occluder_set_as_sdf_collision);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_transform", "occluder", "transform"), &RenderingServer::canvas_light_occluder_set_transform);
ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_light_mask", "occluder", "mask"), &RenderingServer::canvas_light_occluder_set_light_mask);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_interpolated", "occluder", "interpolated"), &RenderingServer::canvas_light_occluder_set_interpolated);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_reset_physics_interpolation", "occluder"), &RenderingServer::canvas_light_occluder_reset_physics_interpolation);
+ ClassDB::bind_method(D_METHOD("canvas_light_occluder_transform_physics_interpolation", "occluder", "transform"), &RenderingServer::canvas_light_occluder_transform_physics_interpolation);
/* CANVAS LIGHT OCCLUDER POLYGON */
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index a67ae0a66ca2..a3a77bc57bad 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -1476,6 +1476,10 @@ public:
virtual void canvas_item_set_debug_redraw(bool p_enabled) = 0;
virtual bool canvas_item_get_debug_redraw() const = 0;
+ virtual void canvas_item_set_interpolated(RID p_item, bool p_interpolated) = 0;
+ virtual void canvas_item_reset_physics_interpolation(RID p_item) = 0;
+ virtual void canvas_item_transform_physics_interpolation(RID p_item, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT */
virtual RID canvas_light_create() = 0;
@@ -1523,6 +1527,10 @@ public:
virtual void canvas_light_set_shadow_color(RID p_light, const Color &p_color) = 0;
virtual void canvas_light_set_shadow_smooth(RID p_light, float p_smooth) = 0;
+ virtual void canvas_light_set_interpolated(RID p_light, bool p_interpolated) = 0;
+ virtual void canvas_light_reset_physics_interpolation(RID p_light) = 0;
+ virtual void canvas_light_transform_physics_interpolation(RID p_light, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT OCCLUDER */
virtual RID canvas_light_occluder_create() = 0;
@@ -1533,6 +1541,10 @@ public:
virtual void canvas_light_occluder_set_transform(RID p_occluder, const Transform2D &p_xform) = 0;
virtual void canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) = 0;
+ virtual void canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) = 0;
+ virtual void canvas_light_occluder_reset_physics_interpolation(RID p_occluder) = 0;
+ virtual void canvas_light_occluder_transform_physics_interpolation(RID p_occluder, const Transform2D &p_transform) = 0;
+
/* CANVAS LIGHT OCCLUDER POLYGON */
virtual RID canvas_occluder_polygon_create() = 0;
@@ -1604,6 +1616,11 @@ public:
virtual void free(RID p_rid) = 0; // Free RIDs associated with the rendering server.
+ /* INTERPOLATION */
+
+ virtual void tick() = 0;
+ virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
+
/* EVENT QUEUING */
virtual void request_frame_drawn_callback(const Callable &p_callable) = 0;