diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 083a74f2fd87..20e1f560dac2 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -30,6 +30,51 @@ #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.elements[0] = p_curr.elements[0]; + r_result.elements[1] = p_curr.elements[1]; + r_result.set_origin(Vector2::linear_interpolate(p1, 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 = Vector2::linear_interpolate(v1, 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), Vector2::linear_interpolate(p1, p2, p_fraction)); + r_result.scale_basis(Vector2::linear_interpolate(s1, s2, p_fraction)); +} + void TransformInterpolator::interpolate_transform(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction) { r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction); diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h index 9e1cfeea9d4c..d2d5709f4262 100644 --- a/core/math/transform_interpolator.h +++ b/core/math/transform_interpolator.h @@ -46,7 +46,7 @@ // several frames may occur between each physics tick, which will make it cheaper // than performing every frame. -class Transform; +struct Transform2D; class TransformInterpolator { public: @@ -66,6 +66,7 @@ class TransformInterpolator { static Quat _basis_to_quat_unchecked(const Basis &p_basis); static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f); static bool _basis_is_orthogonal_any_scale(const Basis &p_basis); + static bool _sign(real_t p_val) { return p_val >= 0; } static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction); @@ -75,6 +76,7 @@ class TransformInterpolator { // These will be slower. static void interpolate_transform(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction); static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction); // Optimized function when you know ahead of time the method static void interpolate_transform_via_method(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction, Method p_method); diff --git a/core/os/main_loop.h b/core/os/main_loop.h index c859911cd407..5af6b2326bb3 100644 --- a/core/os/main_loop.h +++ b/core/os/main_loop.h @@ -67,6 +67,7 @@ class MainLoop : public Object { virtual void input_text(const String &p_text); virtual void init(); + virtual void iteration_prepare() {} virtual bool iteration(float p_time); virtual void iteration_end() {} virtual bool idle(float p_time); diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index a8b87374a664..35ceb20b794a 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -825,6 +825,7 @@ Controls whether the control will be able to receive mouse button input events through [method _gui_input] and how these events should be handled. Also controls whether the control can receive the [signal mouse_entered], and [signal mouse_exited] signals. See the constants to learn what each does. + Enables whether rendering of [CanvasItem] based children should be clipped to this control's rectangle. If [code]true[/code], parts of a child which would be visibly outside of this control's rectangle will not be rendered. diff --git a/doc/classes/ParallaxLayer.xml b/doc/classes/ParallaxLayer.xml index 69d0562ddf43..37c2b0f0d16d 100644 --- a/doc/classes/ParallaxLayer.xml +++ b/doc/classes/ParallaxLayer.xml @@ -23,6 +23,7 @@ Multiplies the ParallaxLayer's motion. If an axis is set to [code]0[/code], it will not scroll. + diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index cd0460b974ae..a4d4b20c0c57 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -325,6 +325,14 @@ Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + + + + + 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. + + @@ -375,6 +383,14 @@ Sets the index for the [CanvasItem]. + + + + + + Turns on and off physics interpolation for the canvas item. + + @@ -463,6 +479,16 @@ 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. + This is particularly useful for large worlds utilising a shifting origin. + + @@ -493,6 +519,14 @@ Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method. + + + + + 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. + + @@ -501,6 +535,14 @@ Enables or disables light occluder. + + + + + + Turns on and off physics interpolation for the occluder. + + @@ -525,6 +567,24 @@ Sets a light occluder's [Transform2D]. + + + + + + Transforms both the current and previous stored transform for an occluder. + This allows transforming an occluder without creating a "glitch" in the interpolation. + This is particularly useful for large worlds utilising a shifting origin. + + + + + + + Prevents physics interpolation for the current physics tick. + This is useful when moving a light to a new location, to give an instantaneous change rather than interpolation from the previous location. + + @@ -557,6 +617,14 @@ Sets a canvas light's height. + + + + + + Turns on and off physics interpolation for the light. + + @@ -679,6 +747,16 @@ 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 light. + This allows transforming a light without creating a "glitch" in the interpolation. + This is particularly useful for large worlds utilising a shifting origin. + + diff --git a/main/main.cpp b/main/main.cpp index 9bcb1d3d035f..52284788d5d5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2324,6 +2324,12 @@ bool Main::iteration() { PhysicsServer::get_singleton()->flush_queries(); + // Prepare the fixed timestep interpolated nodes + // BEFORE they are updated by the physics 2D, + // otherwise the current and previous transforms + // may be the same, and no interpolation takes place. + OS::get_singleton()->get_main_loop()->iteration_prepare(); + Physics2DServer::get_singleton()->sync(); Physics2DServer::get_singleton()->flush_queries(); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index eb64c0e83526..56554f2b7e15 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -55,8 +55,12 @@ void Camera2D::_update_scroll() { if (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); Size2 screen_size = viewport->get_visible_rect().size; @@ -67,13 +71,24 @@ void Camera2D::_update_scroll() { } void Camera2D::_update_process_mode() { - // smoothing can be enabled in the editor but will never be active - if (process_mode == CAMERA2D_PROCESS_IDLE) { - set_process_internal(smoothing_active); - set_physics_process_internal(false); + if (is_physics_interpolated_and_enabled()) { + set_process_internal(is_current()); + set_physics_process_internal(is_current()); + +#ifdef TOOLS_ENABLED + if (process_mode == CAMERA2D_PROCESS_IDLE) { + WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation."); + } +#endif } else { - set_process_internal(false); - set_physics_process_internal(smoothing_active); + // Smoothing can be enabled in the editor but will never be active. + if (process_mode == CAMERA2D_PROCESS_IDLE) { + set_process_internal(smoothing_active); + set_physics_process_internal(false); + } else { + set_process_internal(false); + set_physics_process_internal(smoothing_active); + } } } @@ -178,8 +193,19 @@ Transform2D Camera2D::get_camera_transform() { } } + // TODO .. + // There is a bug here. + // 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 (smoothing_active) { - float c = smoothing * (process_mode == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + // Note that if we are using physics interpolation, + // processing will always be physics based (it ignores the process mode set in the UI). + bool physics_process = (process_mode == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled(); + float delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time(); + float c = smoothing * delta; smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos; ret_camera_pos = smoothed_camera_pos; } else { @@ -234,17 +260,49 @@ Transform2D Camera2D::get_camera_transform() { return (xform).affine_inverse(); } +void Camera2D::_ensure_update_interpolation_data() { + // 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; + } +} + 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_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 (!smoothing_active) { + if (!smoothing_active && !is_physics_interpolated_and_enabled()) { _update_scroll(); } + if (is_physics_interpolated_and_enabled()) { + _ensure_update_interpolation_data(); + _interpolation_data.xform_curr = get_camera_transform(); + } } break; case NOTIFICATION_ENTER_TREE: { @@ -400,10 +458,15 @@ Camera2D::Camera2DProcessMode Camera2D::get_process_mode() const { } void Camera2D::_make_current(Object *p_which) { + bool new_current = false; + if (p_which == this) { - current = true; - } else { - current = false; + new_current = true; + } + + if (new_current != current) { + current = new_current; + _update_process_mode(); } } @@ -413,6 +476,7 @@ void Camera2D::_set_current(bool p_current) { } current = p_current; + _update_process_mode(); update(); } @@ -427,6 +491,7 @@ void Camera2D::make_current() { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", this); } _update_scroll(); + _update_process_mode(); } void Camera2D::clear_current() { @@ -434,6 +499,7 @@ void Camera2D::clear_current() { if (is_inside_tree()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", (Object *)nullptr); } + _update_process_mode(); } void Camera2D::set_limit(Margin p_margin, int p_limit) { diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index d7c9f5eff5f5..f8d8f5ecfdfb 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -84,6 +84,7 @@ class Camera2D : public Node2D { void _update_process_mode(); void _update_scroll(); void _setup_viewport(); + void _ensure_update_interpolation_data(); void _make_current(Object *p_which); void _set_current(bool p_current); @@ -94,6 +95,12 @@ class Camera2D : public Node2D { Camera2DProcessMode process_mode; + struct InterpolationData { + Transform2D xform_curr; + Transform2D xform_prev; + uint32_t last_update_physics_tick = 0; + } _interpolation_data; + protected: virtual Transform2D get_camera_transform(); void _notification(int p_what); diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index cd7db01bc4e4..8539ce9093d5 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -562,6 +562,10 @@ void CanvasItem::_exit_canvas() { } } +void CanvasItem::_physics_interpolated_changed() { + VisualServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated()); +} + void CanvasItem::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -605,6 +609,12 @@ void CanvasItem::_notification(int p_what) { } global_invalid = true; } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + VisualServer::get_singleton()->canvas_item_reset_physics_interpolation(canvas_item); + } + } break; + case NOTIFICATION_DRAW: case NOTIFICATION_TRANSFORM_CHANGED: { } break; diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 9bf81ef1b092..a78096e4a03f 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -218,6 +218,7 @@ class CanvasItem : public Node { void _exit_canvas(); void _notify_transform(CanvasItem *p_node); + virtual void _physics_interpolated_changed(); void _set_on_top(bool p_on_top) { set_draw_behind_parent(!p_on_top); } bool _is_on_top() const { return !is_draw_behind_parent_enabled(); } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index dff6188b2274..799ea828fbc9 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -297,22 +297,31 @@ Color Light2D::get_shadow_color() const { return shadow_color; } -void Light2D::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - VS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, get_canvas()); - _update_light_visibility(); - } - - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - VS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform()); - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - _update_light_visibility(); - } +void Light2D::_physics_interpolated_changed() { + VisualServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated()); +} - if (p_what == NOTIFICATION_EXIT_TREE) { - VS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID()); - _update_light_visibility(); +void Light2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + VS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, get_canvas()); + _update_light_visibility(); + } break; + case NOTIFICATION_EXIT_TREE: { + VS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID()); + _update_light_visibility(); + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + VS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform()); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + _update_light_visibility(); + } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + VisualServer::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light); + } + } break; } } diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 8e9697f1c92e..31043d2cf983 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -80,6 +80,7 @@ class Light2D : public Node2D { void _update_light_visibility(); virtual void owner_changed_notify(); + virtual void _physics_interpolated_changed(); protected: void _notification(int p_what); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index b9e083ad8243..d200a031d999 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -159,43 +159,52 @@ void LightOccluder2D::_poly_changed() { #endif } -void LightOccluder2D::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_CANVAS) { - VS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); - VS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); - VS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); - } - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - VS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - VS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); - } +void LightOccluder2D::_physics_interpolated_changed() { + VisualServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated()); +} - if (p_what == NOTIFICATION_DRAW) { - if (Engine::get_singleton()->is_editor_hint()) { - if (occluder_polygon.is_valid()) { - PoolVector poly = occluder_polygon->get_polygon(); - - if (poly.size()) { - if (occluder_polygon->is_closed()) { - Vector color; - color.push_back(Color(0, 0, 0, 0.6)); - draw_polygon(Variant(poly), color); - } else { - int ps = poly.size(); - PoolVector::Read r = poly.read(); - for (int i = 0; i < ps - 1; i++) { - draw_line(r[i], r[i + 1], Color(0, 0, 0, 0.6), 3); +void LightOccluder2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_CANVAS: { + VS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); + VS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); + VS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + VS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + VS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); + } break; + case NOTIFICATION_DRAW: { + if (Engine::get_singleton()->is_editor_hint()) { + if (occluder_polygon.is_valid()) { + PoolVector poly = occluder_polygon->get_polygon(); + + if (poly.size()) { + if (occluder_polygon->is_closed()) { + Vector color; + color.push_back(Color(0, 0, 0, 0.6)); + draw_polygon(Variant(poly), color); + } else { + int ps = poly.size(); + PoolVector::Read r = poly.read(); + for (int i = 0; i < ps - 1; i++) { + draw_line(r[i], r[i + 1], Color(0, 0, 0, 0.6), 3); + } } } } } - } - } - - if (p_what == NOTIFICATION_EXIT_CANVAS) { - VS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID()); + } break; + case NOTIFICATION_EXIT_CANVAS: { + VS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID()); + } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + VisualServer::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 428554d92993..518c2d5f59e2 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -86,6 +86,7 @@ class LightOccluder2D : public Node2D { Ref occluder_polygon; void _poly_changed(); + virtual void _physics_interpolated_changed(); protected: void _notification(int p_what); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 98b7883c58f8..a82c3ae98fbb 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -163,4 +163,7 @@ void ParallaxLayer::_bind_methods() { ParallaxLayer::ParallaxLayer() { motion_scale = Size2(1, 1); + + // 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/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index fbbc7e50a9f5..5cde307ae154 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -117,17 +117,6 @@ void VisualInstance::_notification(int p_what) { if (_is_vi_visible() && is_physics_interpolated()) { VisualServer::get_singleton()->instance_reset_physics_interpolation(instance); } -#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) - else if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { - String node_name = is_inside_tree() ? String(get_path()) : String(get_name()); - if (!_is_vi_visible()) { - WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with unhidden nodes: \"" + node_name + "\"."); - } - if (!is_physics_interpolated()) { - WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with interpolated nodes: \"" + node_name + "\"."); - } - } -#endif } break; case NOTIFICATION_EXIT_WORLD: { VisualServer::get_singleton()->instance_set_scenario(instance, RID()); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index fb716a97b4e2..087234bc28cd 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -3003,6 +3003,8 @@ Control::Control() { } data.focus_mode = FOCUS_NONE; data.modal_prev_focus_owner = 0; + + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } Control::~Control() { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 7a5fb85568dd..ca098afd2d53 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -553,6 +553,12 @@ void SceneTree::client_physics_interpolation_remove_spatial(SelfList *p _client_physics_interpolation._spatials_list.remove(p_elem); } +void SceneTree::iteration_prepare() { + if (_physics_interpolation_enabled) { + VisualServer::get_singleton()->tick(); + } +} + void SceneTree::iteration_end() { // When physics interpolation is active, we want all pending transforms // to be flushed to the VisualServer before finishing a physics tick. @@ -566,10 +572,6 @@ bool SceneTree::iteration(float p_time) { current_frame++; - if (_physics_interpolation_enabled) { - VisualServer::get_singleton()->tick(); - } - // Any objects performing client physics interpolation // should be given an opportunity to keep their previous transforms // up to take before each new physics tick. diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index d9fc29acf1ea..ef8e6ccad401 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -313,6 +313,7 @@ class SceneTree : public MainLoop { virtual void input_event(const Ref &p_event); virtual void init(); + virtual void iteration_prepare(); virtual bool iteration(float p_time); virtual void iteration_end(); virtual bool idle(float p_time); diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 4260a40af14b..b912445fc798 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -725,9 +725,12 @@ class RasterizerCanvas { }; struct Light : public RID_Data { - 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; @@ -766,6 +769,8 @@ class RasterizerCanvas { Light() { enabled = true; + on_interpolate_transform_list = false; + interpolated = true; color = Color(1, 1, 1); shadow_color = Color(0, 0, 0, 0); height = 0; @@ -977,13 +982,19 @@ class RasterizerCanvas { Rect2 rect; }; - Transform2D xform; + // 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 distance_field : 1; bool light_masked : 1; + bool on_interpolate_transform_list : 1; + bool interpolated : 1; mutable bool custom_rect : 1; mutable bool rect_dirty : 1; mutable bool bound_dirty : 1; @@ -1030,6 +1041,13 @@ class RasterizerCanvas { // in local space. Rect2 local_bound; + // When using interpolation, the local bound for culling + // should be a combined bound of the previous and current. + // To keep this up to date, we need to keep track of the previous + // bound separately rather than just the combined bound. + Rect2 local_bound_prev; + uint32_t local_bound_last_update_tick; + const Rect2 &get_rect() const { if (custom_rect) { return rect; @@ -1196,6 +1214,7 @@ class RasterizerCanvas { memdelete(skinning_data); skinning_data = nullptr; } + on_interpolate_transform_list = false; } Item() { light_mask = 1; @@ -1215,6 +1234,9 @@ class RasterizerCanvas { distance_field = false; light_masked = false; update_when_visible = false; + on_interpolate_transform_list = false; + interpolated = true; + local_bound_last_update_tick = 0; } virtual ~Item() { clear(); @@ -1233,12 +1255,15 @@ class RasterizerCanvas { virtual void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) = 0; struct LightOccluderInstance : public RID_Data { - bool enabled; + bool enabled : 1; + bool on_interpolate_transform_list : 1; + bool interpolated : 1; RID canvas; RID polygon; RID polygon_buffer; Rect2 aabb_cache; - Transform2D xform; + Transform2D xform_curr; + Transform2D xform_prev; Transform2D xform_cache; int light_mask; VS::CanvasOccluderPolygonCullMode cull_cache; @@ -1250,6 +1275,8 @@ class RasterizerCanvas { next = nullptr; light_mask = 1; cull_cache = VS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED; + on_interpolate_transform_list = false; + interpolated = true; } }; diff --git a/servers/visual/visual_server_canvas.cpp b/servers/visual/visual_server_canvas.cpp index 2d4a414a8d66..59d0c848829a 100644 --- a/servers/visual/visual_server_canvas.cpp +++ b/servers/visual/visual_server_canvas.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "visual_server_canvas.h" +#include "core/math/transform_interpolator.h" #include "visual_server_globals.h" #include "visual_server_raster.h" #include "visual_server_viewport.h" @@ -65,7 +66,7 @@ void _collect_ysort_children(VisualServerCanvas::Item *p_canvas_item, Transform2 r_items[r_index] = child_items[i]; child_items[i]->ysort_modulate = p_modulate; child_items[i]->ysort_xform = p_transform; - child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform.elements[2]); + child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.elements[2]); child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr; child_items[i]->ysort_index = r_index; } @@ -74,7 +75,7 @@ void _collect_ysort_children(VisualServerCanvas::Item *p_canvas_item, Transform2 if (child_items[i]->sort_y) { _collect_ysort_children(child_items[i], - p_transform * child_items[i]->xform, + 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); @@ -241,6 +242,37 @@ void VisualServerCanvas::_calculate_canvas_item_bound(Item *p_canvas_item, Rect2 } _finalize_and_merge_local_bound_to_branch(ci, r_branch_bound); + + // If we are interpolating, we want to modify the local_bound (combined) + // to include both the previous AND current bounds. + if (local_bound && _interpolation_data.interpolation_enabled && ci->interpolated) { + Rect2 bound_prev = ci->local_bound_prev; + + // Keep track of the previously assigned exact bound for the next tick. + ci->local_bound_prev = ci->local_bound; + + // The combined bound is the exact current bound merged with the previous exact bound. + ci->local_bound = ci->local_bound.merge(bound_prev); + + // This can overflow, it's no problem, it is just rough to detect when items stop + // having local bounds updated, so we can set prev to curr. + ci->local_bound_last_update_tick = Engine::get_singleton()->get_physics_frames(); + + // Detect special case of overflow. + // This is omitted but included for reference. + // It is such a rare possibility, and even if it did occur + // so it should just result in slightly larger culling bounds + // probably for one tick (and no visual errors). + // Would occur once every 828.5 days at 60 ticks per second + // with uint32_t counter. +#if 0 + if (!ci->local_bound_last_update_tick) { + // Prevents it being treated as non-dirty. + // Just has an increased delay of one tick in this very rare occurrence. + ci->local_bound_last_update_tick = 1; + } +#endif + } } void VisualServerCanvas::_finalize_and_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 *r_branch_bound) { @@ -275,7 +307,7 @@ void VisualServerCanvas::_merge_local_bound_to_branch(Item *p_canvas_item, Rect2 return; } - Rect2 this_item_total_local_bound = p_canvas_item->xform.xform(p_canvas_item->local_bound); + Rect2 this_item_total_local_bound = p_canvas_item->xform_curr.xform(p_canvas_item->local_bound); if (!r_branch_bound->has_no_area()) { *r_branch_bound = r_branch_bound->merge(this_item_total_local_bound); @@ -297,10 +329,17 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c } Rect2 rect = ci->get_rect(); - Transform2D xform = ci->xform; - xform = p_transform * xform; - Rect2 global_rect = xform.xform(rect); + 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); + } + final_xform = p_transform * final_xform; + + Rect2 global_rect = final_xform.xform(rect); global_rect.position += p_clip_rect.position; if (ci->use_parent_material && p_material_owner) { @@ -360,14 +399,14 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c continue; } if (ci->sort_y) { - _render_canvas_item_cull_by_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner); + _render_canvas_item_cull_by_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner); } else { - _render_canvas_item_cull_by_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner); + _render_canvas_item_cull_by_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner); } } if (ci->copy_back_buffer) { - ci->copy_back_buffer->screen_rect = xform.xform(ci->copy_back_buffer->rect).clip(p_clip_rect); + ci->copy_back_buffer->screen_rect = final_xform.xform(ci->copy_back_buffer->rect).clip(p_clip_rect); } if (ci->update_when_visible) { @@ -376,7 +415,7 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c if ((!ci->commands.empty() && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) { //something to draw? - ci->final_transform = xform; + ci->final_transform = final_xform; ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a); ci->global_rect_cache = global_rect; ci->global_rect_cache.position -= p_clip_rect.position; @@ -401,9 +440,9 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c continue; } if (ci->sort_y) { - _render_canvas_item_cull_by_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner); + _render_canvas_item_cull_by_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner); } else { - _render_canvas_item_cull_by_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner); + _render_canvas_item_cull_by_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner); } } } @@ -418,9 +457,22 @@ void VisualServerCanvas::_render_canvas_item_cull_by_node(Item *p_canvas_item, c // This should have been calculated as a pre-process. DEV_ASSERT(!ci->bound_dirty); + // If we are interpolating, and the updates have stopped, we can reduce the local bound. + if (ci->local_bound_last_update_tick && (ci->local_bound_last_update_tick != Engine::get_singleton()->get_physics_frames())) { + // The combined bound is reduced to the last calculated exact bound. + ci->local_bound = ci->local_bound_prev; + ci->local_bound_last_update_tick = 0; + } + Rect2 rect = ci->get_rect(); - Transform2D final_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); + } final_xform = p_transform * final_xform; Rect2 global_rect = final_xform.xform(rect); @@ -825,7 +877,16 @@ void VisualServerCanvas::canvas_item_set_transform(RID p_item, const Transform2D Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!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()); + } + } + + canvas_item->xform_curr = p_transform; // Special case! // Modifying the transform DOES NOT affect the local bound. @@ -1435,6 +1496,64 @@ void VisualServerCanvas::canvas_item_set_skeleton_relative_xform(RID p_item, Tra } } +// Useful especially for origin shifting. +void VisualServerCanvas::canvas_item_transform_physics_interpolation(RID p_item, Transform2D p_transform) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + canvas_item->xform_prev = p_transform * canvas_item->xform_prev; + canvas_item->xform_curr = p_transform * canvas_item->xform_curr; +} + +void VisualServerCanvas::canvas_item_reset_physics_interpolation(RID p_item) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + canvas_item->xform_prev = canvas_item->xform_curr; +} + +void VisualServerCanvas::canvas_item_set_interpolated(RID p_item, bool p_interpolated) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + canvas_item->interpolated = p_interpolated; +} + +void VisualServerCanvas::canvas_light_set_interpolated(RID p_light, bool p_interpolated) { + RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light); + ERR_FAIL_COND(!clight); + clight->interpolated = p_interpolated; +} + +void VisualServerCanvas::canvas_light_reset_physics_interpolation(RID p_light) { + RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light); + ERR_FAIL_COND(!clight); + clight->xform_prev = clight->xform_curr; +} + +void VisualServerCanvas::canvas_light_transform_physics_interpolation(RID p_light, Transform2D p_transform) { + RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light); + ERR_FAIL_COND(!clight); + clight->xform_prev = p_transform * clight->xform_prev; + clight->xform_curr = p_transform * clight->xform_curr; +} + +void VisualServerCanvas::canvas_light_occluder_set_interpolated(RID p_occluder, bool p_interpolated) { + RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder); + ERR_FAIL_COND(!occluder); + occluder->interpolated = p_interpolated; +} + +void VisualServerCanvas::canvas_light_occluder_reset_physics_interpolation(RID p_occluder) { + RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder); + ERR_FAIL_COND(!occluder); + occluder->xform_prev = occluder->xform_curr; +} + +void VisualServerCanvas::canvas_light_occluder_transform_physics_interpolation(RID p_occluder, Transform2D p_transform) { + RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder); + ERR_FAIL_COND(!occluder); + occluder->xform_prev = p_transform * occluder->xform_prev; + occluder->xform_curr = p_transform * occluder->xform_curr; +} + void VisualServerCanvas::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) { Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item); @@ -1573,7 +1692,16 @@ void VisualServerCanvas::canvas_light_set_transform(RID p_light, const Transform RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light); ERR_FAIL_COND(!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()); + } + } + + clight->xform_curr = p_transform; } void VisualServerCanvas::canvas_light_set_texture(RID p_light, RID p_texture) { RasterizerCanvas::Light *clight = canvas_light_owner.get(p_light); @@ -1760,7 +1888,16 @@ void VisualServerCanvas::canvas_light_occluder_set_transform(RID p_occluder, con RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder); ERR_FAIL_COND(!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()); + } + } + + occluder->xform_curr = p_xform; } void VisualServerCanvas::canvas_light_occluder_set_light_mask(RID p_occluder, int p_mask) { RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_occluder); @@ -1871,6 +2008,7 @@ bool VisualServerCanvas::free(RID p_rid) { Item *canvas_item = canvas_item_owner.get(p_rid); ERR_FAIL_COND_V(!canvas_item, true); _make_bound_dirty(canvas_item); + _interpolation_data.notify_free_canvas_item(p_rid, *canvas_item); if (canvas_item->parent.is_valid()) { if (canvas_owner.owns(canvas_item->parent)) { @@ -1904,6 +2042,7 @@ bool VisualServerCanvas::free(RID p_rid) { } else if (canvas_light_owner.owns(p_rid)) { RasterizerCanvas::Light *canvas_light = canvas_light_owner.get(p_rid); ERR_FAIL_COND_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(canvas_light->canvas); @@ -1924,6 +2063,7 @@ bool VisualServerCanvas::free(RID p_rid) { } else if (canvas_light_occluder_owner.owns(p_rid)) { RasterizerCanvas::LightOccluderInstance *occluder = canvas_light_occluder_owner.get(p_rid); ERR_FAIL_COND_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(occluder->polygon); @@ -2056,6 +2196,81 @@ void VisualServerCanvas::_print_tree_down(int p_child_id, int p_depth, const Ite #endif +void VisualServerCanvas::tick() { + if (_interpolation_data.interpolation_enabled) { + update_interpolation_tick(true); + } +} + +void VisualServerCanvas::update_interpolation_tick(bool p_process) { +#define GODOT_UPDATE_INTERPOLATION_TICK(LIST_PREV, LIST_CURR, TYPE, OWNER_LIST) \ + /* Detect any that were on the previous transform list that are no longer active. */ \ + for (unsigned int n = 0; n < _interpolation_data.LIST_PREV->size(); n++) { \ + const RID &rid = (*_interpolation_data.LIST_PREV)[n]; \ + TYPE *item = OWNER_LIST.getornull(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.LIST_CURR->size(); n++) { \ + const RID &rid = (*_interpolation_data.LIST_CURR)[n]; \ + TYPE *item = OWNER_LIST.getornull(rid); \ + if (item) { \ + item->xform_prev = item->xform_curr; \ + item->on_interpolate_transform_list = false; \ + } \ + } \ + } \ + SWAP(_interpolation_data.LIST_CURR, _interpolation_data.LIST_PREV); \ + _interpolation_data.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, RasterizerCanvas::Light, canvas_light_owner); + GODOT_UPDATE_INTERPOLATION_TICK(canvas_light_occluder_transform_update_list_prev, canvas_light_occluder_transform_update_list_curr, RasterizerCanvas::LightOccluderInstance, canvas_light_occluder_owner); + +#undef GODOT_UPDATE_INTERPOLATION_TICK +} + +void VisualServerCanvas::InterpolationData::notify_free_canvas_item(RID p_rid, VisualServerCanvas::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 VisualServerCanvas::InterpolationData::notify_free_canvas_light(RID p_rid, RasterizerCanvas::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 VisualServerCanvas::InterpolationData::notify_free_canvas_light_occluder(RID p_rid, RasterizerCanvas::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); +} + VisualServerCanvas::VisualServerCanvas() { z_list = (RasterizerCanvas::Item **)memalloc(z_range * sizeof(RasterizerCanvas::Item *)); z_last_list = (RasterizerCanvas::Item **)memalloc(z_range * sizeof(RasterizerCanvas::Item *)); diff --git a/servers/visual/visual_server_canvas.h b/servers/visual/visual_server_canvas.h index e59849b9e128..704cb3b7c058 100644 --- a/servers/visual/visual_server_canvas.h +++ b/servers/visual/visual_server_canvas.h @@ -256,9 +256,13 @@ class VisualServerCanvas { void canvas_item_set_use_parent_material(RID p_item, bool p_enable); void canvas_item_attach_skeleton(RID p_item, RID p_skeleton); + void _canvas_item_skeleton_moved(RID p_item); void canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform); Rect2 _debug_canvas_item_get_rect(RID p_item); - void _canvas_item_skeleton_moved(RID p_item); + + 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, Transform2D p_transform); RID canvas_light_create(); void canvas_light_attach_to_canvas(RID p_light, RID p_canvas); @@ -274,9 +278,7 @@ class VisualServerCanvas { void canvas_light_set_layer_range(RID p_light, int p_min_layer, int p_max_layer); void canvas_light_set_item_cull_mask(RID p_light, int p_mask); void canvas_light_set_item_shadow_cull_mask(RID p_light, int p_mask); - void canvas_light_set_mode(RID p_light, VS::CanvasLightMode p_mode); - void canvas_light_set_shadow_enabled(RID p_light, bool p_enabled); void canvas_light_set_shadow_buffer_size(RID p_light, int p_size); void canvas_light_set_shadow_gradient_length(RID p_light, float p_length); @@ -284,6 +286,10 @@ class VisualServerCanvas { 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, Transform2D p_transform); + RID canvas_light_occluder_create(); void canvas_light_occluder_attach_to_canvas(RID p_occluder, RID p_canvas); void canvas_light_occluder_set_enabled(RID p_occluder, bool p_enabled); @@ -291,6 +297,10 @@ class VisualServerCanvas { 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, Transform2D p_transform); + RID canvas_occluder_polygon_create(); void canvas_occluder_polygon_set_shape(RID p_occluder_polygon, const PoolVector &p_shape, bool p_closed); void canvas_occluder_polygon_set_shape_as_lines(RID p_occluder_polygon, const PoolVector &p_shape); @@ -298,6 +308,32 @@ class VisualServerCanvas { void canvas_occluder_polygon_set_cull_mode(RID p_occluder_polygon, VS::CanvasOccluderPolygonCullMode p_mode); bool free(RID p_rid); + + // 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, VisualServerCanvas::Item &r_canvas_item); + void notify_free_canvas_light(RID p_rid, RasterizerCanvas::Light &r_canvas_light); + void notify_free_canvas_light_occluder(RID p_rid, RasterizerCanvas::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; + VisualServerCanvas(); ~VisualServerCanvas(); }; diff --git a/servers/visual/visual_server_raster.cpp b/servers/visual/visual_server_raster.cpp index 9011bb32a3ed..b848aef82e00 100644 --- a/servers/visual/visual_server_raster.cpp +++ b/servers/visual/visual_server_raster.cpp @@ -136,6 +136,20 @@ void VisualServerRaster::draw(bool p_swap_buffers, double frame_step) { void VisualServerRaster::sync() { } +void VisualServerRaster::set_physics_interpolation_enabled(bool p_enabled) { + VSG::scene->set_physics_interpolation_enabled(p_enabled); + VSG::canvas->set_physics_interpolation_enabled(p_enabled); +} + +void VisualServerRaster::tick() { + VSG::scene->tick(); + VSG::canvas->tick(); +} + +void VisualServerRaster::pre_draw(bool p_will_draw) { + VSG::scene->pre_draw(p_will_draw); +} + bool VisualServerRaster::has_changed(ChangedPriority p_priority) const { switch (p_priority) { default: { diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index a0a1b1db49ae..b592d7ad4555 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -454,9 +454,6 @@ class VisualServerRaster : public VisualServer { /* EVENT QUEUING */ - BIND0N(tick) - BIND1N(pre_draw, bool) - /* CAMERA API */ BIND0R(RID, camera_create) @@ -559,10 +556,6 @@ class VisualServerRaster : public VisualServer { #undef BINDBASE #define BINDBASE VSG::scene - /* INTERPOLATION */ - - BIND1(set_physics_interpolation_enabled, bool) - /* SCENARIO API */ BIND0R(RID, scenario_create) @@ -719,17 +712,19 @@ class VisualServerRaster : public VisualServer { BIND2(canvas_item_set_z_index, RID, int) BIND2(canvas_item_set_z_as_relative_to_parent, RID, bool) BIND3(canvas_item_set_copy_to_backbuffer, RID, bool, const Rect2 &) - BIND2(canvas_item_attach_skeleton, RID, RID) - BIND2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) - BIND1R(Rect2, _debug_canvas_item_get_rect, RID) - BIND1(canvas_item_clear, RID) BIND2(canvas_item_set_draw_index, RID, int) - BIND2(canvas_item_set_material, RID, RID) - BIND2(canvas_item_set_use_parent_material, RID, bool) + BIND2(canvas_item_attach_skeleton, RID, RID) + BIND2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) + BIND1R(Rect2, _debug_canvas_item_get_rect, RID) + + BIND2(canvas_item_set_interpolated, RID, bool) + BIND1(canvas_item_reset_physics_interpolation, RID) + BIND2(canvas_item_transform_physics_interpolation, RID, Transform2D) + BIND0R(RID, canvas_light_create) BIND2(canvas_light_attach_to_canvas, RID, RID) BIND2(canvas_light_set_enabled, RID, bool) @@ -754,6 +749,10 @@ class VisualServerRaster : public VisualServer { BIND2(canvas_light_set_shadow_color, RID, const Color &) BIND2(canvas_light_set_shadow_smooth, RID, float) + BIND2(canvas_light_set_interpolated, RID, bool) + BIND1(canvas_light_reset_physics_interpolation, RID) + BIND2(canvas_light_transform_physics_interpolation, RID, Transform2D) + BIND0R(RID, canvas_light_occluder_create) BIND2(canvas_light_occluder_attach_to_canvas, RID, RID) BIND2(canvas_light_occluder_set_enabled, RID, bool) @@ -761,6 +760,10 @@ class VisualServerRaster : public VisualServer { BIND2(canvas_light_occluder_set_transform, RID, const Transform2D &) BIND2(canvas_light_occluder_set_light_mask, RID, int) + BIND2(canvas_light_occluder_set_interpolated, RID, bool) + BIND1(canvas_light_occluder_reset_physics_interpolation, RID) + BIND2(canvas_light_occluder_transform_physics_interpolation, RID, Transform2D) + BIND0R(RID, canvas_occluder_polygon_create) BIND3(canvas_occluder_polygon_set_shape, RID, const PoolVector &, bool) BIND2(canvas_occluder_polygon_set_shape_as_lines, RID, const PoolVector &) @@ -780,12 +783,16 @@ class VisualServerRaster : public VisualServer { virtual void request_frame_drawn_callback(Object *p_where, const StringName &p_method, const Variant &p_userdata); + virtual void tick(); + virtual void pre_draw(bool p_will_draw); virtual void draw(bool p_swap_buffers, double frame_step); virtual void sync(); virtual bool has_changed(ChangedPriority p_priority = CHANGED_PRIORITY_ANY) const; virtual void init(); virtual void finish(); + virtual void set_physics_interpolation_enabled(bool p_enabled); + /* STATUS INFORMATION */ virtual uint64_t get_render_info(RenderInfo p_info); diff --git a/servers/visual/visual_server_viewport.cpp b/servers/visual/visual_server_viewport.cpp index 6e65457c190a..770076b617cf 100644 --- a/servers/visual/visual_server_viewport.cpp +++ b/servers/visual/visual_server_viewport.cpp @@ -135,7 +135,14 @@ void VisualServerViewport::_draw_viewport(Viewport *p_viewport, ARVRInterface::E Vector2 offset = tsize / 2.0; cl->rect_cache = Rect2(-offset + cl->texture_offset, tsize); - cl->xform_cache = xf * cl->xform; + + if (!VSG::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; @@ -180,13 +187,22 @@ void VisualServerViewport::_draw_viewport(Viewport *p_viewport, ARVRInterface::E Transform2D xf = _canvas_get_transform(p_viewport, canvas, &E->get(), clip_rect.size); for (Set::Element *F = canvas->occluders.front(); F; F = F->next()) { - if (!F->get()->enabled) { + RasterizerCanvas::LightOccluderInstance *occluder = F->get(); + if (!occluder->enabled) { continue; } - F->get()->xform_cache = xf * F->get()->xform; - if (shadow_rect.intersects_transformed(F->get()->xform_cache, F->get()->aabb_cache)) { - F->get()->next = occluders; - occluders = F->get(); + + if (!VSG::canvas->_interpolation_data.interpolation_enabled || !occluder->interpolated) { + occluder->xform_cache = xf * occluder->xform_curr; + } else { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + TransformInterpolator::interpolate_transform_2d(occluder->xform_prev, occluder->xform_curr, occluder->xform_cache, f); + occluder->xform_cache = xf * occluder->xform_cache; + } + + if (shadow_rect.intersects_transformed(occluder->xform_cache, occluder->aabb_cache)) { + occluder->next = occluders; + occluders = occluder; } } } diff --git a/servers/visual/visual_server_wrap_mt.cpp b/servers/visual/visual_server_wrap_mt.cpp index 0f259412a57b..61d7a5033b1e 100644 --- a/servers/visual/visual_server_wrap_mt.cpp +++ b/servers/visual/visual_server_wrap_mt.cpp @@ -70,6 +70,30 @@ void VisualServerWrapMT::thread_loop() { /* EVENT QUEUING */ +void VisualServerWrapMT::set_physics_interpolation_enabled(bool p_enabled) { + if (Thread::get_caller_id() != server_thread) { + command_queue.push(visual_server, &VisualServer::set_physics_interpolation_enabled, p_enabled); + } else { + visual_server->set_physics_interpolation_enabled(p_enabled); + } +} + +void VisualServerWrapMT::tick() { + if (Thread::get_caller_id() != server_thread) { + command_queue.push(visual_server, &VisualServer::tick); + } else { + visual_server->tick(); + } +} + +void VisualServerWrapMT::pre_draw(bool p_will_draw) { + if (Thread::get_caller_id() != server_thread) { + command_queue.push(visual_server, &VisualServer::pre_draw, p_will_draw); + } else { + visual_server->pre_draw(p_will_draw); + } +} + void VisualServerWrapMT::sync() { if (create_thread) { command_queue.push_and_sync(this, &VisualServerWrapMT::thread_flush); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 5230a26ee10e..016c52231c6e 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -464,10 +464,6 @@ class VisualServerWrapMT : public VisualServer { FUNC7(environment_set_fog_depth, RID, bool, float, float, float, bool, float) FUNC5(environment_set_fog_height, RID, bool, float, float, float) - /* INTERPOLATION API */ - - FUNC1(set_physics_interpolation_enabled, bool) - /* SCENARIO API */ FUNCRID(scenario) @@ -620,17 +616,19 @@ class VisualServerWrapMT : public VisualServer { FUNC2(canvas_item_set_z_index, RID, int) FUNC2(canvas_item_set_z_as_relative_to_parent, RID, bool) FUNC3(canvas_item_set_copy_to_backbuffer, RID, bool, const Rect2 &) - FUNC2(canvas_item_attach_skeleton, RID, RID) - FUNC2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) - FUNC1R(Rect2, _debug_canvas_item_get_rect, RID) - FUNC1(canvas_item_clear, RID) FUNC2(canvas_item_set_draw_index, RID, int) - FUNC2(canvas_item_set_material, RID, RID) - FUNC2(canvas_item_set_use_parent_material, RID, bool) + FUNC2(canvas_item_attach_skeleton, RID, RID) + FUNC2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) + FUNC1R(Rect2, _debug_canvas_item_get_rect, RID) + + FUNC2(canvas_item_set_interpolated, RID, bool) + FUNC1(canvas_item_reset_physics_interpolation, RID) + FUNC2(canvas_item_transform_physics_interpolation, RID, Transform2D) + FUNC0R(RID, canvas_light_create) FUNC2(canvas_light_attach_to_canvas, RID, RID) FUNC2(canvas_light_set_enabled, RID, bool) @@ -655,6 +653,10 @@ class VisualServerWrapMT : public VisualServer { 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, Transform2D) + FUNCRID(canvas_light_occluder) FUNC2(canvas_light_occluder_attach_to_canvas, RID, RID) FUNC2(canvas_light_occluder_set_enabled, RID, bool) @@ -662,6 +664,10 @@ class VisualServerWrapMT : public VisualServer { 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, Transform2D) + FUNCRID(canvas_occluder_polygon) FUNC3(canvas_occluder_polygon_set_shape, RID, const PoolVector &, bool) FUNC2(canvas_occluder_polygon_set_shape_as_lines, RID, const PoolVector &) @@ -683,11 +689,12 @@ class VisualServerWrapMT : public VisualServer { virtual void init(); virtual void finish(); + virtual void tick(); + virtual void pre_draw(bool p_will_draw); virtual void draw(bool p_swap_buffers, double frame_step); virtual void sync(); - FUNC0(tick) - FUNC1(pre_draw, bool) FUNC1RC(bool, has_changed, ChangedPriority) + virtual void set_physics_interpolation_enabled(bool p_enabled); /* RENDER INFO */ diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 7a2e00cb9643..15b67e6b1195 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2198,6 +2198,9 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_set_z_index", "item", "z_index"), &VisualServer::canvas_item_set_z_index); ClassDB::bind_method(D_METHOD("canvas_item_set_z_as_relative_to_parent", "item", "enabled"), &VisualServer::canvas_item_set_z_as_relative_to_parent); ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &VisualServer::canvas_item_set_copy_to_backbuffer); + ClassDB::bind_method(D_METHOD("canvas_item_set_interpolated", "item", "interpolated"), &VisualServer::canvas_item_set_interpolated); + ClassDB::bind_method(D_METHOD("canvas_item_reset_physics_interpolation", "item"), &VisualServer::canvas_item_reset_physics_interpolation); + ClassDB::bind_method(D_METHOD("canvas_item_transform_physics_interpolation", "item", "xform"), &VisualServer::canvas_item_transform_physics_interpolation); ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &VisualServer::canvas_item_clear); ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &VisualServer::canvas_item_set_draw_index); ClassDB::bind_method(D_METHOD("canvas_item_set_material", "item", "material"), &VisualServer::canvas_item_set_material); @@ -2224,6 +2227,9 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_filter", "light", "filter"), &VisualServer::canvas_light_set_shadow_filter); ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_color", "light", "color"), &VisualServer::canvas_light_set_shadow_color); ClassDB::bind_method(D_METHOD("canvas_light_set_shadow_smooth", "light", "smooth"), &VisualServer::canvas_light_set_shadow_smooth); + ClassDB::bind_method(D_METHOD("canvas_light_set_interpolated", "light", "interpolated"), &VisualServer::canvas_light_set_interpolated); + ClassDB::bind_method(D_METHOD("canvas_light_reset_physics_interpolation", "light"), &VisualServer::canvas_light_reset_physics_interpolation); + ClassDB::bind_method(D_METHOD("canvas_light_transform_physics_interpolation", "light", "xform"), &VisualServer::canvas_light_transform_physics_interpolation); ClassDB::bind_method(D_METHOD("canvas_light_occluder_create"), &VisualServer::canvas_light_occluder_create); ClassDB::bind_method(D_METHOD("canvas_light_occluder_attach_to_canvas", "occluder", "canvas"), &VisualServer::canvas_light_occluder_attach_to_canvas); @@ -2231,6 +2237,9 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_polygon", "occluder", "polygon"), &VisualServer::canvas_light_occluder_set_polygon); ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_transform", "occluder", "transform"), &VisualServer::canvas_light_occluder_set_transform); ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_light_mask", "occluder", "mask"), &VisualServer::canvas_light_occluder_set_light_mask); + ClassDB::bind_method(D_METHOD("canvas_light_occluder_set_interpolated", "occluder", "interpolated"), &VisualServer::canvas_light_occluder_set_interpolated); + ClassDB::bind_method(D_METHOD("canvas_light_occluder_reset_physics_interpolation", "occluder"), &VisualServer::canvas_light_occluder_reset_physics_interpolation); + ClassDB::bind_method(D_METHOD("canvas_light_occluder_transform_physics_interpolation", "occluder", "xform"), &VisualServer::canvas_light_occluder_transform_physics_interpolation); ClassDB::bind_method(D_METHOD("canvas_occluder_polygon_create"), &VisualServer::canvas_occluder_polygon_create); ClassDB::bind_method(D_METHOD("canvas_occluder_polygon_set_shape", "occluder_polygon", "shape", "closed"), &VisualServer::canvas_occluder_polygon_set_shape); diff --git a/servers/visual_server.h b/servers/visual_server.h index 81df90ae7f09..d504e601466e 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -1052,6 +1052,10 @@ class VisualServer : public Object { virtual void canvas_item_set_z_index(RID p_item, int p_z) = 0; virtual void canvas_item_set_z_as_relative_to_parent(RID p_item, bool p_enable) = 0; virtual void canvas_item_set_copy_to_backbuffer(RID p_item, bool p_enable, const Rect2 &p_rect) = 0; + virtual void canvas_item_clear(RID p_item) = 0; + virtual void canvas_item_set_draw_index(RID p_item, int p_index) = 0; + virtual void canvas_item_set_material(RID p_item, RID p_material) = 0; + virtual void canvas_item_set_use_parent_material(RID p_item, bool p_enable) = 0; virtual void canvas_item_attach_skeleton(RID p_item, RID p_skeleton) = 0; virtual void canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform) = 0; @@ -1065,12 +1069,9 @@ class VisualServer : public Object { } virtual Rect2 _debug_canvas_item_get_rect(RID p_item) = 0; - virtual void canvas_item_clear(RID p_item) = 0; - virtual void canvas_item_set_draw_index(RID p_item, int p_index) = 0; - - virtual void canvas_item_set_material(RID p_item, RID p_material) = 0; - - virtual void canvas_item_set_use_parent_material(RID p_item, bool p_enable) = 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, Transform2D p_transform) = 0; virtual RID canvas_light_create() = 0; virtual void canvas_light_attach_to_canvas(RID p_light, RID p_canvas) = 0; @@ -1087,6 +1088,10 @@ class VisualServer : public Object { virtual void canvas_light_set_item_cull_mask(RID p_light, int p_mask) = 0; virtual void canvas_light_set_item_shadow_cull_mask(RID p_light, int p_mask) = 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, Transform2D p_transform) = 0; + enum CanvasLightMode { CANVAS_LIGHT_MODE_ADD, CANVAS_LIGHT_MODE_SUB, @@ -1119,6 +1124,10 @@ class VisualServer : public Object { 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, Transform2D p_transform) = 0; + virtual RID canvas_occluder_polygon_create() = 0; virtual void canvas_occluder_polygon_set_shape(RID p_occluder_polygon, const PoolVector &p_shape, bool p_closed) = 0; virtual void canvas_occluder_polygon_set_shape_as_lines(RID p_occluder_polygon, const PoolVector &p_shape) = 0;