From 054466468c61b1c934970979fb0d52b53a003978 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Wed, 15 Sep 2021 12:30:45 +0100 Subject: [PATCH] Fixed Timestep Interpolation (3D) Adds fixed timestep interpolation to the visual server. --- core/engine.cpp | 4 + core/engine.h | 4 + core/local_vector.h | 16 ++ core/math/interpolator.cpp | 52 ++++ core/math/interpolator.h | 49 ++++ doc/classes/Node.xml | 2 + doc/classes/ProjectSettings.xml | 2 + main/main.cpp | 5 + main/main_timer_sync.cpp | 11 + scene/3d/camera.cpp | 15 ++ scene/3d/camera.h | 2 + scene/3d/physics_body.cpp | 14 +- scene/3d/spatial.cpp | 25 +- scene/3d/spatial.h | 5 + scene/3d/vehicle_body.cpp | 2 +- scene/3d/visual_instance.cpp | 11 + scene/3d/visual_instance.h | 1 + scene/main/node.cpp | 42 ++++ scene/main/node.h | 45 ++-- servers/visual/rasterizer.h | 32 ++- servers/visual/visual_server_raster.cpp | 15 ++ servers/visual/visual_server_raster.h | 6 + servers/visual/visual_server_scene.cpp | 291 +++++++++++++++++++++-- servers/visual/visual_server_scene.h | 36 +++ servers/visual/visual_server_wrap_mt.cpp | 30 +++ servers/visual/visual_server_wrap_mt.h | 8 + servers/visual_server.h | 6 + 27 files changed, 678 insertions(+), 53 deletions(-) create mode 100644 core/math/interpolator.cpp create mode 100644 core/math/interpolator.h diff --git a/core/engine.cpp b/core/engine.cpp index b6d5e772f5cc..738ad454ef72 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -75,6 +75,10 @@ uint32_t Engine::get_frame_delay() const { return _frame_delay; } +void Engine::set_physics_interpolation_enabled(bool p_enabled) { + _physics_interpolation_enabled = is_editor_hint() ? false : p_enabled; +} + void Engine::set_time_scale(float p_scale) { _time_scale = p_scale; } diff --git a/core/engine.h b/core/engine.h index 2a2f67508d39..070e2cb7f2b0 100644 --- a/core/engine.h +++ b/core/engine.h @@ -60,6 +60,7 @@ class Engine { bool _gpu_pixel_snap; uint64_t _physics_frames; float _physics_interpolation_fraction; + bool _physics_interpolation_enabled; bool _portals_active; bool _occlusion_culling_active; @@ -96,6 +97,9 @@ class Engine { float get_idle_frame_step() const { return _frame_step; } float get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; } + void set_physics_interpolation_enabled(bool p_enabled); + bool is_physics_interpolation_enabled() const { return _physics_interpolation_enabled; } + void set_time_scale(float p_scale); float get_time_scale() const; diff --git a/core/local_vector.h b/core/local_vector.h index 8a571c8a3469..79b018ed0f07 100644 --- a/core/local_vector.h +++ b/core/local_vector.h @@ -101,6 +101,22 @@ class LocalVector { } } + U erase_multiple_unordered(const T &p_val) { + U from = 0; + U count = 0; + while (true) { + int64_t idx = find(p_val, from); + + if (idx == -1) { + break; + } + remove_unordered(idx); + from = idx; + count++; + } + return count; + } + void invert() { for (U i = 0; i < count / 2; i++) { SWAP(data[i], data[count - i - 1]); diff --git a/core/math/interpolator.cpp b/core/math/interpolator.cpp new file mode 100644 index 000000000000..5b9faf9c8ccc --- /dev/null +++ b/core/math/interpolator.cpp @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* interpolator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "interpolator.h" + +#include "core/math/transform.h" + +void Interpolator::interpolate_transform_linear(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction) { + // interpolate translate + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + + // interpolate basis + for (int n = 0; n < 3; n++) { + r_result.basis.elements[n] = p_prev.basis.elements[n].linear_interpolate(p_curr.basis.elements[n], p_fraction); + } +} + +real_t Interpolator::checksum_transform(const Transform &p_transform) { + // just a really basic checksum, this can probably be improved + real_t sum = vec3_sum(p_transform.origin); + sum -= vec3_sum(p_transform.basis.elements[0]); + sum += vec3_sum(p_transform.basis.elements[1]); + sum -= vec3_sum(p_transform.basis.elements[2]); + return sum; +} diff --git a/core/math/interpolator.h b/core/math/interpolator.h new file mode 100644 index 000000000000..97519deec3c8 --- /dev/null +++ b/core/math/interpolator.h @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* interpolator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 INTERPOLATOR_H +#define INTERPOLATOR_H + +#include "core/math/math_defs.h" +#include "core/math/vector3.h" + +// Keep all the functions for fixed timestep interpolation together + +class Transform; + +class Interpolator { + static real_t vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; } + +public: + static void interpolate_transform_linear(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction); + static real_t checksum_transform(const Transform &p_transform); +}; + +#endif // INTERPOLATOR_H diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 183c80a6c6cd..e27ccbff8d11 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -786,6 +786,8 @@ Notification received when the node is ready, just before [constant NOTIFICATION_READY] is received. Unlike the latter, it's sent every time the node enters tree, instead of only once. + + Notification received from the OS when the mouse enters the game window. Implemented on desktop and web platforms. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ff0f8782ef5e..83e0633f1961 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1107,6 +1107,8 @@ The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. [b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.iterations_per_second] instead. + + 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 fine 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]. diff --git a/main/main.cpp b/main/main.cpp index 6af377ecede3..e0b26f3445eb 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1186,6 +1186,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60)); ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,1000,1")); + Engine::get_singleton()->set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", true)); Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0)); ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,1000,1")); @@ -2140,6 +2141,8 @@ bool Main::iteration() { bool exit = false; for (int iters = 0; iters < advance.physics_steps; ++iters) { + VisualServer::get_singleton()->tick(); + if (InputDefault::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { InputDefault::get_singleton()->flush_buffered_events(); } @@ -2199,6 +2202,8 @@ bool Main::iteration() { Engine::get_singleton()->frames_drawn++; force_redraw_requested = false; } + } else { + VisualServer::get_singleton()->no_draw(); } #ifndef TOOLS_ENABLED diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index da708d97cc80..07ce904ab9a3 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -291,6 +291,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 float MainTimerSync::get_physics_jitter_fix() { + // Turn off jitter fix when using fixed timestep interpolation + // Note this shouldn't be on UNTIL 2d interpolation is implemented, + // otherwise we will get people making 2d 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/3d/camera.cpp b/scene/3d/camera.cpp index 19fe77e860c9..6f3c5b54161d 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -98,6 +98,10 @@ void Camera::_update_camera() { } } +void Camera::_physics_interpolated_changed() { + VisualServer::get_singleton()->camera_set_interpolated(camera, is_physics_interpolated()); +} + void Camera::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -119,6 +123,11 @@ void Camera::_notification(int p_what) { velocity_tracker->update_position(get_global_transform().origin); } } break; + case NOTIFICATION_TELEPORT: { + if (is_physics_interpolated()) { + VisualServer::get_singleton()->camera_teleport(camera); + } + } break; case NOTIFICATION_EXIT_WORLD: { if (!get_tree()->is_node_being_edited(this)) { if (is_current()) { @@ -666,6 +675,12 @@ Camera::Camera() { doppler_tracking = DOPPLER_TRACKING_DISABLED; set_notify_transform(true); set_disable_scale(true); + _set_branch_physics_interpolated(true); + + // most nodes will teleport when calling set_transform, + // but cameras are different. + // (note: this logic is way overcomplicated imo). + _set_physics_teleport_on_transform(false); } Camera::~Camera() { diff --git a/scene/3d/camera.h b/scene/3d/camera.h index 5396fce634ce..b742cc0eba8a 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -94,6 +94,8 @@ class Camera : public Spatial { virtual void _request_camera_update(); void _update_camera_mode(); + virtual void _physics_interpolated_changed(); + void _notification(int p_what); virtual void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index ad687bde21f2..c15d4eb7900f 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -357,7 +357,7 @@ void RigidBody::_direct_state_changed(Object *p_state) { ERR_FAIL_COND_MSG(!state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState object as argument"); set_ignore_transform_notification(true); - set_global_transform(state->get_transform()); + _physics_set_global_transform(state->get_transform()); linear_velocity = state->get_linear_velocity(); angular_velocity = state->get_angular_velocity(); inverse_inertia_tensor = state->get_inverse_inertia_tensor(); @@ -1042,7 +1042,7 @@ bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_in if (!p_test_only) { gt.origin += result.motion; - set_global_transform(gt); + _physics_set_global_transform(gt); } return colliding; @@ -1134,7 +1134,7 @@ Vector3 KinematicBody::_move_and_slide_internal(const Vector3 &p_linear_velocity } else { gt.origin -= collision.travel; } - set_global_transform(gt); + _physics_set_global_transform(gt); return Vector3(); } } @@ -1189,7 +1189,7 @@ Vector3 KinematicBody::_move_and_slide_internal(const Vector3 &p_linear_velocity } if (apply) { gt.origin += col.travel; - set_global_transform(gt); + _physics_set_global_transform(gt); } } } @@ -1275,7 +1275,7 @@ bool KinematicBody::separate_raycast_shapes(bool p_infinite_inertia, Collision & } gt.origin += recover; - set_global_transform(gt); + _physics_set_global_transform(gt); if (deepest != -1) { r_collision.collider = sep_res[deepest].collider_id; @@ -1383,7 +1383,7 @@ void KinematicBody::_direct_state_changed(Object *p_state) { last_valid_transform = state->get_transform(); set_notify_local_transform(false); - set_global_transform(last_valid_transform); + _physics_set_global_transform(last_valid_transform); set_notify_local_transform(true); _on_transform_changed(); } @@ -1407,7 +1407,7 @@ void KinematicBody::_notification(int p_what) { PhysicsServer::get_singleton()->body_set_state(get_rid(), PhysicsServer::BODY_STATE_TRANSFORM, new_transform); //but then revert changes set_notify_local_transform(false); - set_global_transform(last_valid_transform); + _physics_set_global_transform(last_valid_transform); set_notify_local_transform(true); _on_transform_changed(); } diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index 0ed14a83661a..acb1181f4fa6 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -242,7 +242,27 @@ void Spatial::_notification(int p_what) { } } +void Spatial::_physics_set_transform(const Transform &p_transform) { + _set_branch_physics_interpolated(true); + _set_transform(p_transform); +} + +void Spatial::_physics_set_global_transform(const Transform &p_transform) { + _set_branch_physics_interpolated(true); + _set_global_transform(p_transform); +} + void Spatial::set_transform(const Transform &p_transform) { + _set_transform(p_transform); + _teleport(); +} + +void Spatial::set_global_transform(const Transform &p_transform) { + _set_global_transform(p_transform); + _teleport(); +} + +void Spatial::_set_transform(const Transform &p_transform) { data.local_transform = p_transform; data.dirty |= DIRTY_VECTORS; _change_notify("translation"); @@ -255,10 +275,9 @@ void Spatial::set_transform(const Transform &p_transform) { } } -void Spatial::set_global_transform(const Transform &p_transform) { +void Spatial::_set_global_transform(const Transform &p_transform) { Transform xform = (data.parent && !data.toplevel_active) ? data.parent->get_global_transform().affine_inverse() * p_transform : p_transform; - - set_transform(xform); + _set_transform(xform); } Transform Spatial::get_transform() const { diff --git a/scene/3d/spatial.h b/scene/3d/spatial.h index 8562ce658da0..301b70ac7747 100644 --- a/scene/3d/spatial.h +++ b/scene/3d/spatial.h @@ -128,6 +128,11 @@ class Spatial : public Node { } } + void _physics_set_transform(const Transform &p_transform); + void _physics_set_global_transform(const Transform &p_transform); + void _set_transform(const Transform &p_transform); + void _set_global_transform(const Transform &p_transform); + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index 4a41445409ec..357b5a5c3db3 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -833,7 +833,7 @@ void VehicleBody::_direct_state_changed(Object *p_state) { for (int i = 0; i < wheels.size(); i++) { _ray_cast(i, state); - wheels[i]->set_transform(state->get_transform().inverse() * wheels[i]->m_worldTransform); + wheels[i]->_physics_set_transform(state->get_transform().inverse() * wheels[i]->m_worldTransform); } _update_suspension(state); diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index 7e51fe707516..38e943b663ed 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -85,6 +85,13 @@ void VisualInstance::_notification(int p_what) { VisualServer::get_singleton()->instance_set_transform(instance, gt); } } break; + case NOTIFICATION_TELEPORT: { + if (_get_spatial_flags() & SPATIAL_FLAG_VI_VISIBLE) { + if (is_physics_interpolated()) { + VisualServer::get_singleton()->instance_teleport(instance); + } + } + } break; case NOTIFICATION_EXIT_WORLD: { VisualServer::get_singleton()->instance_set_scenario(instance, RID()); VisualServer::get_singleton()->instance_attach_skeleton(instance, RID()); @@ -101,6 +108,10 @@ void VisualInstance::_notification(int p_what) { } } +void VisualInstance::_physics_interpolated_changed() { + VisualServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated()); +} + RID VisualInstance::get_instance() const { return instance; } diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index 6487fc199e0a..ce206fdc5f6e 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -49,6 +49,7 @@ class VisualInstance : public CullInstance { protected: void _update_visibility(); virtual void _refresh_portal_mode(); + virtual void _physics_interpolated_changed(); void _notification(int p_what); static void _bind_methods(); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index dd06b9671e4b..48251524ff5d 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -31,6 +31,7 @@ #include "node.h" #include "core/core_string_names.h" +#include "core/engine.h" #include "core/io/resource_loader.h" #include "core/message_queue.h" #include "core/print_string.h" @@ -189,6 +190,19 @@ void Node::_propagate_ready() { } } +void Node::_propagate_physics_interpolated(bool p_interpolated) { + data.physics_interpolated = p_interpolated; + + // allow a call to the VisualServer etc in derived classes + _physics_interpolated_changed(); + + data.blocked++; + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_physics_interpolated(p_interpolated); + } + data.blocked--; +} + void Node::_propagate_enter_tree() { // this needs to happen to all children before any enter_tree @@ -376,6 +390,8 @@ void Node::move_child_notify(Node *p_child) { // to be used when not wanted } +void Node::_physics_interpolated_changed() {} + void Node::set_physics_process(bool p_process) { if (data.physics_process == p_process) { return; @@ -754,6 +770,25 @@ bool Node::can_process() const { return true; } +void Node::_set_physics_teleport_on_transform(bool p_enabled) { + data.physics_teleport_on_transform = p_enabled; +} + +void Node::_set_branch_physics_interpolated(bool p_interpolated) { + // most common case, noop + if (is_physics_interpolated() == p_interpolated) { + return; + } + _propagate_physics_interpolated(p_interpolated); +} + +void Node::_teleport() { + if (!Engine::get_singleton()->is_physics_interpolation_enabled() || !is_physics_interpolated() || !data.physics_teleport_on_transform) { + return; + } + propagate_notification(NOTIFICATION_TELEPORT); +} + float Node::get_physics_process_delta_time() const { if (data.tree) { return data.tree->get_physics_process_time(); @@ -2765,6 +2800,9 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_processing_unhandled_key_input"), &Node::is_processing_unhandled_key_input); ClassDB::bind_method(D_METHOD("set_pause_mode", "mode"), &Node::set_pause_mode); ClassDB::bind_method(D_METHOD("get_pause_mode"), &Node::get_pause_mode); + // ClassDB::bind_method(D_METHOD("set_physics_interpolated", "p_interpolated"), &Node::set_physics_interpolated); + // ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated); + // ClassDB::bind_method(D_METHOD("teleport"), &Node::teleport); ClassDB::bind_method(D_METHOD("can_process"), &Node::can_process); ClassDB::bind_method(D_METHOD("print_stray_nodes"), &Node::_print_stray_nodes); ClassDB::bind_method(D_METHOD("get_position_in_parent"), &Node::get_position_in_parent); @@ -2853,6 +2891,7 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); + BIND_CONSTANT(NOTIFICATION_TELEPORT); BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER); BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT); @@ -2897,6 +2936,7 @@ void Node::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_custom_multiplayer", "get_custom_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority"); + // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolated"), "set_physics_interpolated", "is_physics_interpolated"); BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta"))); @@ -2936,6 +2976,8 @@ Node::Node() { data.idle_process_internal = false; data.inside_tree = false; data.ready_notified = false; + data.physics_interpolated = false; + data.physics_teleport_on_transform = true; data.owner = nullptr; data.OW = nullptr; diff --git a/scene/main/node.h b/scene/main/node.h index 28f68ba56955..af3926168fa8 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -100,9 +100,6 @@ class Node : public Object { int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. StringName name; SceneTree *tree; - bool inside_tree; - bool ready_notified; //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; #ifdef TOOLS_ENABLED NodePath import_path; //path used when imported, used by scene editors to keep tracking #endif @@ -120,25 +117,32 @@ class Node : public Object { Map rpc_methods; Map rpc_properties; + int process_priority; + // variables used to properly sort the node when processing, ignored otherwise //should move all the stuff below to bits - bool physics_process; - bool idle_process; - int process_priority; + bool physics_process : 1; + bool idle_process : 1; - bool physics_process_internal; - bool idle_process_internal; + bool physics_process_internal : 1; + bool idle_process_internal : 1; - bool input; - bool unhandled_input; - bool unhandled_key_input; + bool input : 1; + bool unhandled_input : 1; + bool unhandled_key_input : 1; + bool physics_interpolated : 1; + bool physics_teleport_on_transform : 1; - bool parent_owned; - bool in_constructor; - bool use_placeholder; + bool parent_owned : 1; + bool in_constructor : 1; + bool use_placeholder : 1; - bool display_folded; - bool editable_instance; + bool display_folded : 1; + bool editable_instance : 1; + + bool inside_tree : 1; + bool ready_notified : 1; //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 : 1; mutable NodePath *path_cache; @@ -163,6 +167,7 @@ class Node : public Object { void _propagate_exit_tree(); void _propagate_after_exit_tree(); void _propagate_validate_owner(); + void _propagate_physics_interpolated(bool p_interpolated); void _print_stray_nodes(); void _propagate_pause_owner(Node *p_owner); Array _get_node_and_resource(const NodePath &p_path); @@ -193,6 +198,10 @@ class Node : public Object { virtual void remove_child_notify(Node *p_child); virtual void move_child_notify(Node *p_child); + virtual void _physics_interpolated_changed(); + void _set_branch_physics_interpolated(bool p_interpolated); + void _teleport(); + void _propagate_replace_owner(Node *p_owner, Node *p_by_owner); static void _bind_methods(); @@ -226,6 +235,7 @@ class Node : public Object { NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, NOTIFICATION_POST_ENTER_TREE = 27, + NOTIFICATION_TELEPORT = 28, //keep these linked to node NOTIFICATION_WM_MOUSE_ENTER = MainLoop::NOTIFICATION_WM_MOUSE_ENTER, NOTIFICATION_WM_MOUSE_EXIT = MainLoop::NOTIFICATION_WM_MOUSE_EXIT, @@ -379,6 +389,9 @@ class Node : public Object { bool can_process() const; bool can_process_notification(int p_what) const; + _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; } + void _set_physics_teleport_on_transform(bool p_enabled); + void request_ready(); static void print_stray_nodes(); diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 0f846affc813..818f04e94bf5 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -89,8 +89,16 @@ class RasterizerScene { RID skeleton; RID material_override; + // This is the main transform to be drawn with .. + // This will either be the interpolated transform (when using fixed timestep interpolation) + // or the ONLY transform (when not using FTI). Transform transform; + // for interpolation we store the current transform (this physics tick) + // and the transform in the previous tick + Transform transform_curr; + Transform transform_prev; + int depth_layer; uint32_t layer_mask; @@ -106,11 +114,20 @@ class RasterizerScene { VS::ShadowCastingSetting cast_shadows; //fit in 32 bits - bool mirror : 8; - bool receive_shadows : 8; - bool visible : 8; - bool baked_light : 4; //this flag is only to know if it actually did use baked light - bool redraw_if_visible : 4; + bool mirror : 1; + bool receive_shadows : 1; + bool visible : 1; + bool baked_light : 1; //this flag is only to know if it actually did use baked light + bool redraw_if_visible : 1; + + bool on_interpolate_list : 1; + bool on_interpolate_transform_list : 1; + bool interpolated : 1; + + // For fixed timestep interpolation. + // Note 32 bits is plenty for checksum, no need for real_t + float transform_checksum_curr; + float transform_checksum_prev; float depth; //used for sorting @@ -137,6 +154,11 @@ class RasterizerScene { lightmap_capture = nullptr; lightmap_slice = -1; lightmap_uv_rect = Rect2(0, 0, 1, 1); + on_interpolate_list = false; + on_interpolate_transform_list = false; + interpolated = false; + transform_checksum_curr = 0.0; + transform_checksum_prev = 0.0; } }; diff --git a/servers/visual/visual_server_raster.cpp b/servers/visual/visual_server_raster.cpp index 25a4e655d43b..2deebbb39e44 100644 --- a/servers/visual/visual_server_raster.cpp +++ b/servers/visual/visual_server_raster.cpp @@ -94,7 +94,22 @@ void VisualServerRaster::request_frame_drawn_callback(Object *p_where, const Str frame_drawn_callbacks.push_back(fdc); } +void VisualServerRaster::tick() { + VSG::scene->update_interpolation_transform_list(true); +} + +void VisualServerRaster::no_draw() { + // dummy version called if there is no draw() called. + // This is necessary to clear out intermediate per frame lists. + + // TODO : The update_interpolate_list could perhaps be simplified even + // more in this situation to do less work. + VSG::scene->update_interpolate_list(false); +} + void VisualServerRaster::draw(bool p_swap_buffers, double frame_step) { + VSG::scene->update_interpolate_list(); + //needs to be done before changes is reset to 0, to not force the editor to redraw VS::get_singleton()->emit_signal("frame_pre_draw"); diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index a8a0baf0eea8..10f2d7564dbb 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -438,6 +438,8 @@ class VisualServerRaster : public VisualServer { BIND4(camera_set_orthogonal, RID, float, float, float) BIND5(camera_set_frustum, RID, float, Vector2, float, float) BIND2(camera_set_transform, RID, const Transform &) + BIND2(camera_set_interpolated, RID, bool) + BIND1(camera_teleport, RID) BIND2(camera_set_cull_mask, RID, uint32_t) BIND2(camera_set_environment, RID, RID) BIND2(camera_set_use_vertical_aspect, RID, bool) @@ -546,6 +548,8 @@ class VisualServerRaster : public VisualServer { BIND2(instance_set_scenario, RID, RID) BIND2(instance_set_layer_mask, RID, uint32_t) BIND2(instance_set_transform, RID, const Transform &) + BIND2(instance_set_interpolated, RID, bool) + BIND1(instance_teleport, RID) BIND2(instance_attach_object_instance_id, RID, ObjectID) BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_surface_material, RID, int, RID) @@ -729,6 +733,8 @@ class VisualServerRaster : public VisualServer { virtual void request_frame_drawn_callback(Object *p_where, const StringName &p_method, const Variant &p_userdata); virtual void draw(bool p_swap_buffers, double frame_step); + virtual void no_draw(); + virtual void tick(); virtual void sync(); virtual bool has_changed() const; virtual void init(); diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index c3206562737a..bd135e298f16 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -30,6 +30,7 @@ #include "visual_server_scene.h" +#include "core/math/interpolator.h" #include "core/os/os.h" #include "visual_server_globals.h" #include "visual_server_raster.h" @@ -38,6 +39,16 @@ /* CAMERA API */ +Transform VisualServerScene::Camera::get_transform() const { + if (!Engine::get_singleton()->is_physics_interpolation_enabled() || !interpolated) { + return transform; + } + + Transform final; + Interpolator::interpolate_transform_linear(transform_prev, transform, final, Engine::get_singleton()->get_physics_interpolation_fraction()); + return final; +} + RID VisualServerScene::camera_create() { Camera *camera = memnew(Camera); return camera_owner.make_rid(camera); @@ -71,9 +82,27 @@ void VisualServerScene::camera_set_frustum(RID p_camera, float p_size, Vector2 p camera->zfar = p_z_far; } +void VisualServerScene::camera_teleport(RID p_camera) { + _interpolation_data.camera_teleport_list.push_back(p_camera); +} + +void VisualServerScene::camera_set_interpolated(RID p_camera, bool p_interpolated) { + Camera *camera = camera_owner.get(p_camera); + ERR_FAIL_COND(!camera); + camera->interpolated = p_interpolated; +} + void VisualServerScene::camera_set_transform(RID p_camera, const Transform &p_transform) { Camera *camera = camera_owner.get(p_camera); ERR_FAIL_COND(!camera); + + if (Engine::get_singleton()->is_physics_interpolation_enabled() && camera->interpolated) { + if (!camera->on_interpolate_transform_list) { + _interpolation_data.camera_transform_update_list_curr->push_back(p_camera); + camera->on_interpolate_transform_list = true; + } + } + camera->transform = p_transform.orthonormalized(); } @@ -675,30 +704,238 @@ void VisualServerScene::instance_set_layer_mask(RID p_instance, uint32_t p_mask) instance->layer_mask = p_mask; } + +void VisualServerScene::instance_teleport(RID p_instance) { + _interpolation_data.instance_teleport_list.push_back(p_instance); +} + +void VisualServerScene::instance_set_interpolated(RID p_instance, bool p_interpolated) { + Instance *instance = instance_owner.get(p_instance); + ERR_FAIL_COND(!instance); + instance->interpolated = p_interpolated; +} + void VisualServerScene::instance_set_transform(RID p_instance, const Transform &p_transform) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); - if (instance->transform == p_transform) { - return; //must be checked to avoid worst evil - } + if (!Engine::get_singleton()->is_physics_interpolation_enabled() || !instance->interpolated) { + if (instance->transform == p_transform) { + return; //must be checked to avoid worst evil + } #ifdef DEBUG_ENABLED - for (int i = 0; i < 4; i++) { - const Vector3 &v = i < 3 ? p_transform.basis.elements[i] : p_transform.origin; - ERR_FAIL_COND(Math::is_inf(v.x)); - ERR_FAIL_COND(Math::is_nan(v.x)); - ERR_FAIL_COND(Math::is_inf(v.y)); - ERR_FAIL_COND(Math::is_nan(v.y)); - ERR_FAIL_COND(Math::is_inf(v.z)); - ERR_FAIL_COND(Math::is_nan(v.z)); + for (int i = 0; i < 4; i++) { + const Vector3 &v = i < 3 ? p_transform.basis.elements[i] : p_transform.origin; + ERR_FAIL_COND(Math::is_inf(v.x)); + ERR_FAIL_COND(Math::is_nan(v.x)); + ERR_FAIL_COND(Math::is_inf(v.y)); + ERR_FAIL_COND(Math::is_nan(v.y)); + ERR_FAIL_COND(Math::is_inf(v.z)); + ERR_FAIL_COND(Math::is_nan(v.z)); + } + +#endif + instance->transform = p_transform; + _instance_queue_update(instance, true); + return; } + float new_checksum = Interpolator::checksum_transform(p_transform); + bool checksums_match = (instance->transform_checksum_curr == new_checksum) && (instance->transform_checksum_prev == new_checksum); + + // we can't entirely reject no changes because we need the interpolation + // system to keep on stewing + // bool no_change = (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform); + + // optimized check. First checks the checksums. If they pass it does the slow check at the end. + bool no_change = checksums_match && (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform); + + if (!no_change) { +#ifdef DEBUG_ENABLED + + for (int i = 0; i < 4; i++) { + const Vector3 &v = i < 3 ? p_transform.basis.elements[i] : p_transform.origin; + ERR_FAIL_COND(Math::is_inf(v.x)); + ERR_FAIL_COND(Math::is_nan(v.x)); + ERR_FAIL_COND(Math::is_inf(v.y)); + ERR_FAIL_COND(Math::is_nan(v.y)); + ERR_FAIL_COND(Math::is_inf(v.z)); + ERR_FAIL_COND(Math::is_nan(v.z)); + } + #endif - instance->transform = p_transform; - _instance_queue_update(instance, true); + + instance->transform_curr = p_transform; + + // keep checksums up to date + instance->transform_checksum_curr = new_checksum; + + // We temporarily need to also store the current transform here + // for use by the update_instance function, so the calculated AABB + // makes sense. + // instance->transform = p_transform; + + if (!instance->on_interpolate_transform_list) { + _interpolation_data.instance_transform_update_list_curr->push_back(p_instance); + instance->on_interpolate_transform_list = true; + } else { + CRASH_COND(!_interpolation_data.instance_transform_update_list_curr->size()); + } + + if (!instance->on_interpolate_list) { + _interpolation_data.instance_interpolate_update_list.push_back(p_instance); + instance->on_interpolate_list = true; + } else { + CRASH_COND(!_interpolation_data.instance_interpolate_update_list.size()); + } + + _instance_queue_update(instance, true); + } +} + +void VisualServerScene::InterpolationData::notify_free_camera(RID p_rid) { + if (!Engine::get_singleton()->is_physics_interpolation_enabled()) { + return; + } + + // if the camera was on any of the lists, remove + camera_transform_update_list_curr->erase_multiple_unordered(p_rid); + camera_transform_update_list_prev->erase_multiple_unordered(p_rid); + camera_teleport_list.erase_multiple_unordered(p_rid); } + +void VisualServerScene::InterpolationData::notify_free_instance(RID p_rid) { + if (!Engine::get_singleton()->is_physics_interpolation_enabled()) { + return; + } + + // if the instance was on any of the lists, remove + instance_interpolate_update_list.erase_multiple_unordered(p_rid); + instance_transform_update_list_curr->erase_multiple_unordered(p_rid); + instance_transform_update_list_prev->erase_multiple_unordered(p_rid); + instance_teleport_list.erase_multiple_unordered(p_rid); +} + +void VisualServerScene::update_interpolation_transform_list(bool p_process) { + // detect any that were on the previous transform list that are no longer active, + // we should remove them from the interpolate list + for (unsigned int n = 0; n < _interpolation_data.instance_transform_update_list_prev->size(); n++) { + const RID &rid = (*_interpolation_data.instance_transform_update_list_prev)[n]; + Instance *instance = instance_owner.getornull(rid); + + bool active = true; + + // no longer active? (either the instance deleted or no longer being transformed) + if (instance && !instance->on_interpolate_transform_list) { + active = false; + instance->on_interpolate_list = false; + + // make sure the most recent transform is set + instance->transform = instance->transform_curr; + + // and that both prev and current are the same, just in case of any interpolations + instance->transform_prev = instance->transform_curr; + } + + if (!instance) { + active = false; + } + + if (!active) { + _interpolation_data.instance_interpolate_update_list.erase(rid); + } + } + + // and now for any in the transform list (being actively interpolated), keep the previous transform + // value up to date ready for the next tick + if (p_process) { + for (unsigned int n = 0; n < _interpolation_data.instance_transform_update_list_curr->size(); n++) { + const RID &rid = (*_interpolation_data.instance_transform_update_list_curr)[n]; + Instance *instance = instance_owner.getornull(rid); + if (instance) { + instance->transform_prev = instance->transform_curr; + instance->transform_checksum_prev = instance->transform_checksum_curr; + instance->on_interpolate_transform_list = false; + } + } + } + + // we maintain a mirror list for the transform updates, so we can detect when an instance + // is no longer being transformed, and remove it from the interpolate list + SWAP(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev); + + // prepare for the next iteration + _interpolation_data.instance_transform_update_list_curr->clear(); + + // CAMERAS + // detect any that were on the previous transform list that are no longer active, + for (unsigned int n = 0; n < _interpolation_data.camera_transform_update_list_prev->size(); n++) { + const RID &rid = (*_interpolation_data.camera_transform_update_list_prev)[n]; + Camera *camera = camera_owner.getornull(rid); + + // no longer active? (either the instance deleted or no longer being transformed) + if (camera && !camera->on_interpolate_transform_list) { + camera->transform = camera->transform_prev; + } + } + + // cameras , swap any current with previous + for (unsigned int n = 0; n < _interpolation_data.camera_transform_update_list_curr->size(); n++) { + const RID &rid = (*_interpolation_data.camera_transform_update_list_curr)[n]; + Camera *camera = camera_owner.getornull(rid); + if (camera) { + camera->transform_prev = camera->transform; + camera->on_interpolate_transform_list = false; + } + } + + // we maintain a mirror list for the transform updates, so we can detect when an instance + // is no longer being transformed, and remove it from the interpolate list + SWAP(_interpolation_data.camera_transform_update_list_curr, _interpolation_data.camera_transform_update_list_prev); + + // prepare for the next iteration + _interpolation_data.camera_transform_update_list_curr->clear(); +} + +void VisualServerScene::update_interpolate_list(bool p_process) { + // teleported instances + for (unsigned int n = 0; n < _interpolation_data.instance_teleport_list.size(); n++) { + const RID &rid = _interpolation_data.instance_teleport_list[n]; + Instance *instance = instance_owner.getornull(rid); + if (instance) { + instance->transform_prev = instance->transform_curr; + instance->transform_checksum_prev = instance->transform_checksum_curr; + } + } + + _interpolation_data.instance_teleport_list.clear(); + + // camera teleports + for (unsigned int n = 0; n < _interpolation_data.camera_teleport_list.size(); n++) { + const RID &rid = _interpolation_data.camera_teleport_list[n]; + Camera *camera = camera_owner.getornull(rid); + if (camera) { + camera->transform_prev = camera->transform; + } + } + + _interpolation_data.camera_teleport_list.clear(); + + if (p_process) { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + + for (unsigned int i = 0; i < _interpolation_data.instance_interpolate_update_list.size(); i++) { + const RID &rid = _interpolation_data.instance_interpolate_update_list[i]; + Instance *instance = instance_owner.getornull(rid); + if (instance) { + Interpolator::interpolate_transform_linear(instance->transform_prev, instance->transform_curr, instance->transform, f); + } + } // for n + } +} + void VisualServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); @@ -1529,22 +1766,30 @@ void VisualServerScene::instance_geometry_set_as_instance_lod(RID p_instance, RI void VisualServerScene::_update_instance(Instance *p_instance) { p_instance->version++; + // when not using interpolation the transform is used straight + const Transform *instance_xform = &p_instance->transform; + + // but when using interpolation the current transform is the most up to date on the tick + if (Engine::get_singleton()->is_physics_interpolation_enabled() && p_instance->interpolated) { + instance_xform = &p_instance->transform_curr; + } + if (p_instance->base_type == VS::INSTANCE_LIGHT) { InstanceLightData *light = static_cast(p_instance->base_data); - VSG::scene_render->light_instance_set_transform(light->instance, p_instance->transform); + VSG::scene_render->light_instance_set_transform(light->instance, *instance_xform); light->shadow_dirty = true; } if (p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) { InstanceReflectionProbeData *reflection_probe = static_cast(p_instance->base_data); - VSG::scene_render->reflection_probe_instance_set_transform(reflection_probe->instance, p_instance->transform); + VSG::scene_render->reflection_probe_instance_set_transform(reflection_probe->instance, *instance_xform); reflection_probe->reflection_dirty = true; } if (p_instance->base_type == VS::INSTANCE_PARTICLES) { - VSG::storage->particles_set_emission_transform(p_instance->base, p_instance->transform); + VSG::storage->particles_set_emission_transform(p_instance->base, *instance_xform); } if (p_instance->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE) { @@ -1579,11 +1824,11 @@ void VisualServerScene::_update_instance(Instance *p_instance) { } } - p_instance->mirror = p_instance->transform.basis.determinant() < 0.0; + p_instance->mirror = instance_xform->basis.determinant() < 0.0; AABB new_aabb; - new_aabb = p_instance->transform.xform(p_instance->aabb); + new_aabb = instance_xform->xform(p_instance->aabb); p_instance->transformed_aabb = new_aabb; @@ -2378,8 +2623,11 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view } break; } - _prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint); - _render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); + // This getter allows optional fixed timestep interpolation for the camera. + Transform camera_transform = camera->get_transform(); + + _prepare_scene(camera_transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint); + _render_scene(camera_transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); #endif } @@ -4030,7 +4278,7 @@ bool VisualServerScene::free(RID p_rid) { camera_owner.free(p_rid); memdelete(camera); - + _interpolation_data.notify_free_camera(p_rid); } else if (scenario_owner.owns(p_rid)) { Scenario *scenario = scenario_owner.get(p_rid); @@ -4059,6 +4307,7 @@ bool VisualServerScene::free(RID p_rid) { instance_owner.free(p_rid); memdelete(instance); + _interpolation_data.notify_free_instance(p_rid); } else if (room_owner.owns(p_rid)) { Room *room = room_owner.get(p_rid); room_owner.free(p_rid); diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h index 1761bbb5e52e..73e1928217f2 100644 --- a/servers/visual/visual_server_scene.h +++ b/servers/visual/visual_server_scene.h @@ -74,9 +74,18 @@ class VisualServerScene { bool vaspect; RID env; + // transform_prev is only used when using fixed timestep interpolation Transform transform; + Transform transform_prev; + bool interpolated : 1; + bool on_interpolate_transform_list : 1; + int32_t previous_room_id_hint; + // call get transform to get either the transform straight, + // or the interpolated transform if using fixed timestep interpolation + Transform get_transform() const; + Camera() { visible_layers = 0xFFFFFFFF; fov = 70; @@ -87,6 +96,8 @@ class VisualServerScene { offset = Vector2(); vaspect = false; previous_room_id_hint = -1; + interpolated = false; + on_interpolate_transform_list = false; } }; @@ -97,6 +108,8 @@ class VisualServerScene { virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far); virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far); virtual void camera_set_transform(RID p_camera, const Transform &p_transform); + virtual void camera_set_interpolated(RID p_camera, bool p_interpolated); + virtual void camera_teleport(RID p_camera); virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers); virtual void camera_set_environment(RID p_camera, RID p_env); virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable); @@ -307,6 +320,23 @@ class VisualServerScene { }; SelfList::List _instance_update_list; + + // fixed timestep interpolation + struct InterpolationData { + void notify_free_camera(RID p_rid); + void notify_free_instance(RID p_rid); + LocalVector instance_interpolate_update_list; + LocalVector instance_transform_update_lists[2]; + LocalVector *instance_transform_update_list_curr = &instance_transform_update_lists[0]; + LocalVector *instance_transform_update_list_prev = &instance_transform_update_lists[1]; + LocalVector instance_teleport_list; + + LocalVector camera_transform_update_lists[2]; + LocalVector *camera_transform_update_list_curr = &camera_transform_update_lists[0]; + LocalVector *camera_transform_update_list_prev = &camera_transform_update_lists[1]; + LocalVector camera_teleport_list; + } _interpolation_data; + void _instance_queue_update(Instance *p_instance, bool p_update_aabb, bool p_update_materials = false); struct InstanceGeometryData : public InstanceBaseData { @@ -517,6 +547,8 @@ class VisualServerScene { virtual void instance_set_scenario(RID p_instance, RID p_scenario); virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask); virtual void instance_set_transform(RID p_instance, const Transform &p_transform); + virtual void instance_set_interpolated(RID p_instance, bool p_interpolated); + virtual void instance_teleport(RID p_instance); virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id); virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material); @@ -709,6 +741,10 @@ class VisualServerScene { void render_camera(Ref &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas); void update_dirty_instances(); + // interpolation + void update_interpolation_transform_list(bool p_process = true); + void update_interpolate_list(bool p_process = true); + //probes struct GIProbeDataHeader { uint32_t version; diff --git a/servers/visual/visual_server_wrap_mt.cpp b/servers/visual/visual_server_wrap_mt.cpp index 11b157c2fd36..5bba0883f0e6 100644 --- a/servers/visual/visual_server_wrap_mt.cpp +++ b/servers/visual/visual_server_wrap_mt.cpp @@ -36,6 +36,18 @@ void VisualServerWrapMT::thread_exit() { exit.set(); } +void VisualServerWrapMT::thread_tick() { + if (!draw_pending.decrement()) { + visual_server->tick(); + } +} + +void VisualServerWrapMT::thread_no_draw() { + if (!draw_pending.decrement()) { + visual_server->no_draw(); + } +} + void VisualServerWrapMT::thread_draw(bool p_swap_buffers, double frame_step) { if (!draw_pending.decrement()) { visual_server->draw(p_swap_buffers, frame_step); @@ -82,6 +94,24 @@ void VisualServerWrapMT::sync() { } } +void VisualServerWrapMT::tick() { + if (create_thread) { + draw_pending.increment(); + command_queue.push(this, &VisualServerWrapMT::thread_tick); + } else { + visual_server->tick(); + } +} + +void VisualServerWrapMT::no_draw() { + if (create_thread) { + draw_pending.increment(); + command_queue.push(this, &VisualServerWrapMT::thread_no_draw); + } else { + visual_server->no_draw(); + } +} + void VisualServerWrapMT::draw(bool p_swap_buffers, double frame_step) { if (create_thread) { draw_pending.increment(); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 5f412f143067..2fbae38fe626 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -53,6 +53,8 @@ class VisualServerWrapMT : public VisualServer { SafeNumeric draw_pending; void thread_draw(bool p_swap_buffers, double frame_step); + void thread_no_draw(); + void thread_tick(); void thread_flush(); void thread_exit(); @@ -369,6 +371,8 @@ class VisualServerWrapMT : public VisualServer { FUNC4(camera_set_orthogonal, RID, float, float, float) FUNC5(camera_set_frustum, RID, float, Vector2, float, float) FUNC2(camera_set_transform, RID, const Transform &) + FUNC2(camera_set_interpolated, RID, bool) + FUNC1(camera_teleport, RID) FUNC2(camera_set_cull_mask, RID, uint32_t) FUNC2(camera_set_environment, RID, RID) FUNC2(camera_set_use_vertical_aspect, RID, bool) @@ -469,6 +473,8 @@ class VisualServerWrapMT : public VisualServer { FUNC2(instance_set_scenario, RID, RID) FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC2(instance_set_transform, RID, const Transform &) + FUNC2(instance_set_interpolated, RID, bool) + FUNC1(instance_teleport, RID) FUNC2(instance_attach_object_instance_id, RID, ObjectID) FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_surface_material, RID, int, RID) @@ -650,6 +656,8 @@ class VisualServerWrapMT : public VisualServer { virtual void init(); virtual void finish(); virtual void draw(bool p_swap_buffers, double frame_step); + virtual void no_draw(); + virtual void tick(); virtual void sync(); FUNC0RC(bool, has_changed) diff --git a/servers/visual_server.h b/servers/visual_server.h index 306bb7bf5a86..51a87a71c4f0 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -612,6 +612,8 @@ class VisualServer : public Object { virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far) = 0; virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far) = 0; virtual void camera_set_transform(RID p_camera, const Transform &p_transform) = 0; + virtual void camera_set_interpolated(RID p_camera, bool p_interpolated) = 0; + virtual void camera_teleport(RID p_camera) = 0; virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers) = 0; virtual void camera_set_environment(RID p_camera, RID p_env) = 0; virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable) = 0; @@ -849,6 +851,8 @@ class VisualServer : public Object { virtual void instance_set_scenario(RID p_instance, RID p_scenario) = 0; virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_transform(RID p_instance, const Transform &p_transform) = 0; + virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0; + virtual void instance_teleport(RID p_instance) = 0; virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0; @@ -1094,6 +1098,8 @@ class VisualServer : public Object { /* EVENT QUEUING */ virtual void draw(bool p_swap_buffers = true, double frame_step = 0.0) = 0; + virtual void no_draw() = 0; // dummy version, call this if not drawing a frame to prevent memory leaks + virtual void tick() = 0; // physics tick virtual void sync() = 0; virtual bool has_changed() const = 0; virtual void init() = 0;