diff --git a/core/local_vector.h b/core/local_vector.h
index 8a571c8a34..79b018ed0f 100644
--- a/core/local_vector.h
+++ b/core/local_vector.h
@@ -101,6 +101,22 @@ public:
}
}
+ 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 0000000000..d840044fcd
--- /dev/null
+++ b/core/math/interpolator.cpp
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* 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(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction, Method p_method) {
+ switch (p_method) {
+ default: {
+ interpolate_transform_linear(p_prev, p_curr, r_result, p_fraction);
+ } break;
+ case INTERP_SLERP: {
+ r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction);
+ r_result.basis = p_prev.basis.slerp(p_curr.basis, p_fraction);
+ } break;
+ }
+}
+
+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;
+}
+
+bool Interpolator::should_slerp(const Basis &p_a, const Basis &p_b) {
+ // the two basis should be suitable, and also if they are close enough,
+ // no need for a slerp anyway.
+ bool slerp = false;
+ if (p_a.is_rotation()) {
+ Quat from(p_a);
+
+ if (from.is_normalized() && p_b.is_rotation()) {
+ Quat to(p_b);
+ if (to.is_normalized()) {
+ // are they close together?
+ // calc cosine
+ real_t cosom = Math::abs(from.dot(to));
+ if ((1.0 - cosom) > CMP_EPSILON) {
+ slerp = true;
+ }
+ }
+ }
+ }
+
+ return slerp;
+}
diff --git a/core/math/interpolator.h b/core/math/interpolator.h
new file mode 100644
index 0000000000..2c35ed51fe
--- /dev/null
+++ b/core/math/interpolator.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* 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:
+ enum Method {
+ INTERP_LERP,
+ INTERP_SLERP,
+ };
+
+ static void interpolate_transform_linear(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction);
+ static void interpolate_transform(const Transform &p_prev, const Transform &p_curr, Transform &r_result, real_t p_fraction, Method p_method);
+ static real_t checksum_transform(const Transform &p_transform);
+ static bool should_slerp(const Basis &p_a, const Basis &p_b);
+};
+
+#endif // INTERPOLATOR_H
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 7775940993..84047c8ee9 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -675,6 +675,11 @@
Sets whether this is an instance load placeholder. See [InstancePlaceholder].
+
+
+
+
+
@@ -787,6 +792,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 3a4462769b..eec944bf17 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1127,6 +1127,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_timer_sync.cpp b/main/main_timer_sync.cpp
index 508c3dc26d..c22dd8b918 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 19fe77e860..bb80459a21 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: {
@@ -112,6 +116,9 @@ void Camera::_notification(int p_what) {
viewport->_camera_set(this);
}
+ ERR_FAIL_COND(get_world().is_null());
+ VisualServer::get_singleton()->camera_set_scenario(camera, get_world()->get_scenario());
+
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
_request_camera_update();
@@ -119,7 +126,14 @@ 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: {
+ VisualServer::get_singleton()->camera_set_scenario(camera, RID());
+
if (!get_tree()->is_node_being_edited(this)) {
if (is_current()) {
clear_current();
diff --git a/scene/3d/camera.h b/scene/3d/camera.h
index 5396fce634..b742cc0eba 100644
--- a/scene/3d/camera.h
+++ b/scene/3d/camera.h
@@ -94,6 +94,8 @@ protected:
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/visual_instance.cpp b/scene/3d/visual_instance.cpp
index 7e51fe7075..38e943b663 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 6487fc199e..ce206fdc5f 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 0e2af71ce6..d0454d3e62 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,22 @@ bool Node::can_process() const {
return true;
}
+void Node::set_physics_interpolated(bool p_interpolated) {
+ // if swapping from interpolated to non-interpolated, use this as
+ // an extra means to cause a teleport
+ if (is_physics_interpolated() && !p_interpolated) {
+ teleport();
+ }
+
+ _propagate_physics_interpolated(p_interpolated);
+}
+
+void Node::teleport() {
+ if ((get_tree() && get_tree()->is_physics_interpolation_enabled()) && is_physics_interpolated()) {
+ propagate_notification(NOTIFICATION_TELEPORT);
+ }
+}
+
float Node::get_physics_process_delta_time() const {
if (data.tree) {
return data.tree->get_physics_process_time();
@@ -2828,6 +2860,11 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal);
+ // Disabled for now
+ // 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("get_tree"), &Node::get_tree);
ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANCING | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS));
@@ -2907,6 +2944,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);
@@ -2952,6 +2990,9 @@ void Node::_bind_methods() {
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");
+ // Disabled for now
+ // 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")));
BIND_VMETHOD(MethodInfo("_enter_tree"));
@@ -2990,6 +3031,7 @@ Node::Node() {
data.idle_process_internal = false;
data.inside_tree = false;
data.ready_notified = false;
+ data.physics_interpolated = true;
data.owner = nullptr;
data.OW = nullptr;
diff --git a/scene/main/node.h b/scene/main/node.h
index 1f37f2e6fc..96f1190ef8 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -100,9 +100,6 @@ private:
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,31 @@ private:
Map rpc_methods;
Map rpc_properties;
- // 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_internal;
- bool idle_process_internal;
+ // variables used to properly sort the node when processing, ignored otherwise
+ //should move all the stuff below to bits
+ bool physics_process : 1;
+ bool idle_process : 1;
- bool input;
- bool unhandled_input;
- bool unhandled_key_input;
+ bool physics_process_internal : 1;
+ bool idle_process_internal : 1;
- bool parent_owned;
- bool in_constructor;
- bool use_placeholder;
+ bool input : 1;
+ bool unhandled_input : 1;
+ bool unhandled_key_input : 1;
+ bool physics_interpolated : 1;
- bool display_folded;
- bool editable_instance;
+ bool parent_owned : 1;
+ bool in_constructor : 1;
+ bool use_placeholder : 1;
+
+ bool display_folded : 1;
+ bool editable_instance : 1;
+
+ bool inside_tree : 1;
+ bool ready_notified : 1; //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 +166,7 @@ private:
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 +197,8 @@ protected:
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
+ virtual void _physics_interpolated_changed();
+
void _propagate_replace_owner(Node *p_owner, Node *p_by_owner);
static void _bind_methods();
@@ -226,6 +232,7 @@ public:
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,
@@ -386,6 +393,10 @@ public:
bool can_process() const;
bool can_process_notification(int p_what) const;
+ void set_physics_interpolated(bool p_interpolated);
+ _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; }
+ void teleport();
+
void request_ready();
static void print_stray_nodes();
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 2dc6e3a7fb..da5fc6fe61 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -465,11 +465,42 @@ void SceneTree::init() {
MainLoop::init();
}
+void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
+ // disallow interpolation in editor
+ if (Engine::get_singleton()->is_editor_hint()) {
+ p_enabled = false;
+ }
+
+ if (p_enabled == _physics_interpolation_enabled) {
+ return;
+ }
+
+ _physics_interpolation_enabled = p_enabled;
+
+ if (root->get_world().is_valid()) {
+ RID scenario = root->get_world()->get_scenario();
+ if (scenario.is_valid()) {
+ VisualServer::get_singleton()->scenario_set_physics_interpolation_enabled(scenario, p_enabled);
+ }
+ }
+}
+
+bool SceneTree::is_physics_interpolation_enabled() const {
+ return _physics_interpolation_enabled;
+}
+
bool SceneTree::iteration(float p_time) {
root_lock++;
current_frame++;
+ if (root->get_world().is_valid()) {
+ RID scenario = root->get_world()->get_scenario();
+ if (scenario.is_valid()) {
+ VisualServer::get_singleton()->scenario_tick(scenario);
+ }
+ }
+
flush_transform_notifications();
MainLoop::iteration(p_time);
@@ -602,6 +633,13 @@ bool SceneTree::idle(float p_time) {
#endif
+ if (root->get_world().is_valid()) {
+ RID scenario = root->get_world()->get_scenario();
+ if (scenario.is_valid()) {
+ VisualServer::get_singleton()->scenario_pre_draw(scenario, true);
+ }
+ }
+
return _quit;
}
@@ -2036,6 +2074,7 @@ SceneTree::SceneTree() {
call_lock = 0;
root_lock = 0;
node_count = 0;
+ _physics_interpolation_enabled = false;
//create with mainloop
@@ -2045,6 +2084,7 @@ SceneTree::SceneTree() {
if (!root->get_world().is_valid()) {
root->set_world(Ref(memnew(World)));
}
+ set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", true));
// Initialize network state
multiplayer_poll = true;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 83f1bd909f..fddc78a187 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -117,6 +117,7 @@ private:
bool _quit;
bool initialized;
bool input_handled;
+ bool _physics_interpolation_enabled;
Size2 last_screen_size;
StringName tree_changed_name;
@@ -407,6 +408,9 @@ public:
void set_refuse_new_network_connections(bool p_refuse);
bool is_refusing_new_network_connections() const;
+ void set_physics_interpolation_enabled(bool p_enabled);
+ bool is_physics_interpolation_enabled() const;
+
static void add_idle_callback(IdleCallback p_callback);
SceneTree();
~SceneTree();
diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h
index 452662c275..b693d39da3 100644
--- a/servers/visual/rasterizer.h
+++ b/servers/visual/rasterizer.h
@@ -32,6 +32,7 @@
#define RASTERIZER_H
#include "core/math/camera_matrix.h"
+#include "core/math/interpolator.h"
#include "servers/visual_server.h"
#include "core/self_list.h"
@@ -89,8 +90,16 @@ public:
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 +115,21 @@ public:
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;
+ Interpolator::Method interpolation_method : 3;
+
+ // For fixed timestep interpolation.
+ // Note 32 bits is plenty for checksum, no need for real_t
+ float transform_checksum_curr;
+ float transform_checksum_prev;
float depth; //used for sorting
@@ -137,6 +156,12 @@ public:
lightmap_capture = nullptr;
lightmap_slice = -1;
lightmap_uv_rect = Rect2(0, 0, 1, 1);
+ on_interpolate_list = false;
+ on_interpolate_transform_list = false;
+ interpolated = true;
+ interpolation_method = Interpolator::INTERP_LERP;
+ 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 25a4e655d4..f95b35e8e3 100644
--- a/servers/visual/visual_server_raster.cpp
+++ b/servers/visual/visual_server_raster.cpp
@@ -94,7 +94,17 @@ void VisualServerRaster::request_frame_drawn_callback(Object *p_where, const Str
frame_drawn_callbacks.push_back(fdc);
}
+void VisualServerRaster::scenario_tick(RID p_scenario) {
+ VSG::scene->_scenario_tick(p_scenario);
+}
+
+void VisualServerRaster::scenario_pre_draw(RID p_scenario, bool p_will_draw) {
+ VSG::scene->_scenario_pre_draw(p_scenario, p_will_draw);
+}
+
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 9e7c754206..5b3ffa502b 100644
--- a/servers/visual/visual_server_raster.h
+++ b/servers/visual/visual_server_raster.h
@@ -436,10 +436,13 @@ public:
/* CAMERA API */
BIND0R(RID, camera_create)
+ BIND2(camera_set_scenario, RID, RID)
BIND4(camera_set_perspective, RID, float, float, float)
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)
@@ -540,6 +543,7 @@ public:
BIND2(scenario_set_environment, RID, RID)
BIND3(scenario_set_reflection_atlas_size, RID, int, int)
BIND2(scenario_set_fallback_environment, RID, RID)
+ BIND2(scenario_set_physics_interpolation_enabled, RID, bool)
/* INSTANCING API */
BIND0R(RID, instance_create)
@@ -548,6 +552,8 @@ public:
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)
@@ -735,6 +741,8 @@ public:
virtual bool has_changed() const;
virtual void init();
virtual void finish();
+ virtual void scenario_tick(RID p_scenario);
+ virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw);
/* STATUS INFORMATION */
diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp
index f80814b89c..d708bc5c3b 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,11 +39,33 @@
/* CAMERA API */
+Transform VisualServerScene::Camera::get_transform() const {
+ if (!is_currently_interpolated()) {
+ return transform;
+ }
+
+ Transform final;
+ Interpolator::interpolate_transform(transform_prev, transform, final, Engine::get_singleton()->get_physics_interpolation_fraction(), interpolation_method);
+ return final;
+}
+
RID VisualServerScene::camera_create() {
Camera *camera = memnew(Camera);
return camera_owner.make_rid(camera);
}
+void VisualServerScene::camera_set_scenario(RID p_camera, RID p_scenario) {
+ Camera *camera = camera_owner.get(p_camera);
+ ERR_FAIL_COND(!camera);
+
+ if (p_scenario.is_valid()) {
+ camera->scenario = scenario_owner.get(p_scenario);
+ ERR_FAIL_COND(!camera->scenario);
+ } else {
+ camera->scenario = nullptr;
+ }
+}
+
void VisualServerScene::camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) {
Camera *camera = camera_owner.get(p_camera);
ERR_FAIL_COND(!camera);
@@ -71,10 +94,37 @@ 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) {
+ Camera *camera = camera_owner.get(p_camera);
+ ERR_FAIL_COND(!camera);
+
+ if (camera->is_currently_interpolated()) {
+ camera->scenario->_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);
+
camera->transform = p_transform.orthonormalized();
+
+ if (camera->is_currently_interpolated()) {
+ if (!camera->on_interpolate_transform_list) {
+ camera->scenario->_interpolation_data.camera_transform_update_list_curr->push_back(p_camera);
+ camera->on_interpolate_transform_list = true;
+ }
+
+ // decide on the interpolation method .. slerp if possible
+ bool can_slerp = Interpolator::should_slerp(camera->transform_prev.basis, camera->transform.basis);
+ camera->interpolation_method = can_slerp ? Interpolator::INTERP_SLERP : Interpolator::INTERP_LERP;
+ }
}
void VisualServerScene::camera_set_cull_mask(RID p_camera, uint32_t p_layers) {
@@ -212,6 +262,7 @@ void VisualServerScene::SpatialPartitioningScene_Octree::set_balance(float p_bal
VisualServerScene::Scenario::Scenario() {
debug = VS::SCENARIO_DEBUG_DISABLED;
+ _interpolation_data.interpolation_enabled = false;
bool use_bvh_or_octree = GLOBAL_GET("rendering/quality/spatial_partitioning/use_bvh");
@@ -379,6 +430,33 @@ RID VisualServerScene::scenario_create() {
return scenario_rid;
}
+void VisualServerScene::scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled) {
+ Scenario *scenario = scenario_owner.get(p_scenario);
+ ERR_FAIL_COND(!scenario);
+ scenario->_interpolation_data.interpolation_enabled = p_enabled;
+}
+
+void VisualServerScene::_scenario_tick(RID p_scenario) {
+ Scenario *scenario = scenario_owner.get(p_scenario);
+ ERR_FAIL_COND(!scenario);
+
+ if (scenario->is_physics_interpolation_enabled()) {
+ update_interpolation_transform_list(scenario->_interpolation_data, true);
+ }
+}
+
+void VisualServerScene::_scenario_pre_draw(RID p_scenario, bool p_will_draw) {
+ Scenario *scenario = scenario_owner.get(p_scenario);
+ ERR_FAIL_COND(!scenario);
+
+ // even when running and not drawing scenes, we still need to clear intermediate per frame
+ // interpolation data .. hence the p_will_draw flag (so we can reduce the processing if the frame
+ // will not be drawn)
+ if (scenario->is_physics_interpolation_enabled()) {
+ update_interpolate_list(scenario->_interpolation_data, p_will_draw);
+ }
+}
+
void VisualServerScene::scenario_set_debug(RID p_scenario, VS::ScenarioDebugMode p_debug_mode) {
Scenario *scenario = scenario_owner.get(p_scenario);
ERR_FAIL_COND(!scenario);
@@ -675,30 +753,248 @@ 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) {
+ Instance *instance = instance_owner.get(p_instance);
+ ERR_FAIL_COND(!instance);
+
+ if (instance->is_currently_interpolated()) {
+ instance->scenario->_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 (!instance->is_currently_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);
+ 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_curr = p_transform;
+
+ // decide on the interpolation method .. slerp if possible
+ bool can_slerp = Interpolator::should_slerp(instance->transform_prev.basis, instance->transform_curr.basis);
+ instance->interpolation_method = can_slerp ? Interpolator::INTERP_SLERP : Interpolator::INTERP_LERP;
+
+ // 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) {
+ instance->scenario->_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
+ instance->on_interpolate_transform_list = true;
+ } else {
+ DEV_ASSERT(!instance->scenario->_interpolation_data.instance_transform_update_list_curr->size());
+ }
+
+ if (!instance->on_interpolate_list) {
+ instance->scenario->_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
+ instance->on_interpolate_list = true;
+ } else {
+ DEV_ASSERT(!instance->scenario->_interpolation_data.instance_interpolate_update_list.size());
+ }
+
+ _instance_queue_update(instance, true);
+ }
}
+
+void VisualServerScene::Scenario::InterpolationData::notify_free_camera(RID p_rid) {
+ if (!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::Scenario::InterpolationData::notify_free_instance(RID p_rid) {
+ if (!interpolation_enabled) {
+ return;
+ }
+
+ // if the instance was on any of the lists, remove
+ instance_interpolate_update_list.erase_multiple_unordered(p_rid);
+ instance_transform_update_list_curr->erase_multiple_unordered(p_rid);
+ instance_transform_update_list_prev->erase_multiple_unordered(p_rid);
+ instance_teleport_list.erase_multiple_unordered(p_rid);
+}
+
+void VisualServerScene::update_interpolation_transform_list(Scenario::InterpolationData &r_interpolation_data, 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 < r_interpolation_data.instance_transform_update_list_prev->size(); n++) {
+ const RID &rid = (*r_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) {
+ r_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 < r_interpolation_data.instance_transform_update_list_curr->size(); n++) {
+ const RID &rid = (*r_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(r_interpolation_data.instance_transform_update_list_curr, r_interpolation_data.instance_transform_update_list_prev);
+
+ // prepare for the next iteration
+ r_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 < r_interpolation_data.camera_transform_update_list_prev->size(); n++) {
+ const RID &rid = (*r_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 < r_interpolation_data.camera_transform_update_list_curr->size(); n++) {
+ const RID &rid = (*r_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(r_interpolation_data.camera_transform_update_list_curr, r_interpolation_data.camera_transform_update_list_prev);
+
+ // prepare for the next iteration
+ r_interpolation_data.camera_transform_update_list_curr->clear();
+}
+
+void VisualServerScene::update_interpolate_list(Scenario::InterpolationData &r_interpolation_data, bool p_process) {
+ // teleported instances
+ for (unsigned int n = 0; n < r_interpolation_data.instance_teleport_list.size(); n++) {
+ const RID &rid = r_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;
+ }
+ }
+
+ r_interpolation_data.instance_teleport_list.clear();
+
+ // camera teleports
+ for (unsigned int n = 0; n < r_interpolation_data.camera_teleport_list.size(); n++) {
+ const RID &rid = r_interpolation_data.camera_teleport_list[n];
+ Camera *camera = camera_owner.getornull(rid);
+ if (camera) {
+ camera->transform_prev = camera->transform;
+ }
+ }
+
+ r_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 < r_interpolation_data.instance_interpolate_update_list.size(); i++) {
+ const RID &rid = r_interpolation_data.instance_interpolate_update_list[i];
+ Instance *instance = instance_owner.getornull(rid);
+ if (instance) {
+ Interpolator::interpolate_transform(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method);
+ }
+ } // 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 +1825,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 (p_instance->is_currently_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 +1883,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 +2682,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
}
@@ -4032,9 +4339,12 @@ bool VisualServerScene::free(RID p_rid) {
if (camera_owner.owns(p_rid)) {
Camera *camera = camera_owner.get(p_rid);
+ if (camera->scenario) {
+ camera->scenario->_interpolation_data.notify_free_camera(p_rid);
+ }
+
camera_owner.free(p_rid);
memdelete(camera);
-
} else if (scenario_owner.owns(p_rid)) {
Scenario *scenario = scenario_owner.get(p_rid);
@@ -4053,6 +4363,10 @@ bool VisualServerScene::free(RID p_rid) {
Instance *instance = instance_owner.get(p_rid);
+ if (instance->scenario) {
+ instance->scenario->_interpolation_data.notify_free_instance(p_rid);
+ }
+
instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1));
instance_set_scenario(p_rid, RID());
instance_set_base(p_rid, RID());
@@ -4063,6 +4377,7 @@ bool VisualServerScene::free(RID p_rid) {
instance_owner.free(p_rid);
memdelete(instance);
+
} 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 ca5b2285dc..99e978b435 100644
--- a/servers/visual/visual_server_scene.h
+++ b/servers/visual/visual_server_scene.h
@@ -58,6 +58,7 @@ public:
static VisualServerScene *singleton;
/* CAMERA API */
+ struct Scenario;
struct Camera : public RID_Data {
enum Type {
@@ -71,12 +72,27 @@ public:
float size;
Vector2 offset;
uint32_t visible_layers;
- bool vaspect;
RID env;
+ // transform_prev is only used when using fixed timestep interpolation
Transform transform;
+ Transform transform_prev;
+
+ Scenario *scenario;
+
+ bool interpolated : 1;
+ bool on_interpolate_transform_list : 1;
+
+ bool vaspect : 1;
+ Interpolator::Method interpolation_method : 3;
+
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;
+ bool is_currently_interpolated() const { return scenario && scenario->is_physics_interpolation_enabled() && interpolated; }
+
Camera() {
visible_layers = 0xFFFFFFFF;
fov = 70;
@@ -86,17 +102,24 @@ public:
size = 1.0;
offset = Vector2();
vaspect = false;
+ scenario = nullptr;
previous_room_id_hint = -1;
+ interpolated = true;
+ on_interpolate_transform_list = false;
+ interpolation_method = Interpolator::INTERP_LERP;
}
};
mutable RID_Owner camera_owner;
virtual RID camera_create();
+ virtual void camera_set_scenario(RID p_camera, RID p_scenario);
virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far);
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);
@@ -199,6 +222,26 @@ public:
SelfList::List instances;
+ bool is_physics_interpolation_enabled() const { return _interpolation_data.interpolation_enabled; }
+
+ // 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;
+
+ bool interpolation_enabled;
+ } _interpolation_data;
+
Scenario();
~Scenario() { memdelete(sps); }
};
@@ -214,6 +257,9 @@ public:
virtual void scenario_set_environment(RID p_scenario, RID p_environment);
virtual void scenario_set_fallback_environment(RID p_scenario, RID p_environment);
virtual void scenario_set_reflection_atlas_size(RID p_scenario, int p_size, int p_subdiv);
+ virtual void scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled);
+ void _scenario_tick(RID p_scenario);
+ void _scenario_pre_draw(RID p_scenario, bool p_will_draw);
/* INSTANCING API */
@@ -266,6 +312,8 @@ public:
singleton->_instance_queue_update(this, p_aabb, p_materials);
}
+ bool is_currently_interpolated() const { return scenario && scenario->is_physics_interpolation_enabled() && interpolated; }
+
Instance() :
scenario_item(this),
update_item(this) {
@@ -307,6 +355,7 @@ public:
};
SelfList::List _instance_update_list;
+
void _instance_queue_update(Instance *p_instance, bool p_update_aabb, bool p_update_materials = false);
struct InstanceGeometryData : public InstanceBaseData {
@@ -517,6 +566,8 @@ public:
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 +760,10 @@ public:
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(Scenario::InterpolationData &r_interpolation_data, bool p_process = true);
+ void update_interpolate_list(Scenario::InterpolationData &r_interpolation_data, 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 11b157c2fd..6dbe107550 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_scenario_tick(RID p_scenario) {
+ if (!draw_pending.decrement()) {
+ visual_server->scenario_tick(p_scenario);
+ }
+}
+
+void VisualServerWrapMT::thread_scenario_pre_draw(RID p_scenario, bool p_will_draw) {
+ if (!draw_pending.decrement()) {
+ visual_server->scenario_pre_draw(p_scenario, p_will_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::scenario_tick(RID p_scenario) {
+ if (create_thread) {
+ draw_pending.increment();
+ command_queue.push(this, &VisualServerWrapMT::thread_scenario_tick, p_scenario);
+ } else {
+ visual_server->scenario_tick(p_scenario);
+ }
+}
+
+void VisualServerWrapMT::scenario_pre_draw(RID p_scenario, bool p_will_draw) {
+ if (create_thread) {
+ draw_pending.increment();
+ command_queue.push(this, &VisualServerWrapMT::thread_scenario_pre_draw, p_scenario, p_will_draw);
+ } else {
+ visual_server->scenario_pre_draw(p_scenario, p_will_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 fe252639bb..01ec65f670 100644
--- a/servers/visual/visual_server_wrap_mt.h
+++ b/servers/visual/visual_server_wrap_mt.h
@@ -54,6 +54,8 @@ class VisualServerWrapMT : public VisualServer {
SafeNumeric draw_pending;
void thread_draw(bool p_swap_buffers, double frame_step);
void thread_flush();
+ void thread_scenario_tick(RID p_scenario);
+ void thread_scenario_pre_draw(RID p_scenario, bool p_will_draw);
void thread_exit();
@@ -367,10 +369,13 @@ public:
/* CAMERA API */
FUNCRID(camera)
+ FUNC2(camera_set_scenario, RID, RID)
FUNC4(camera_set_perspective, RID, float, float, float)
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)
@@ -463,6 +468,7 @@ public:
FUNC2(scenario_set_environment, RID, RID)
FUNC3(scenario_set_reflection_atlas_size, RID, int, int)
FUNC2(scenario_set_fallback_environment, RID, RID)
+ FUNC2(scenario_set_physics_interpolation_enabled, RID, bool)
/* INSTANCING API */
FUNCRID(instance)
@@ -471,6 +477,8 @@ public:
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)
@@ -653,6 +661,8 @@ public:
virtual void finish();
virtual void draw(bool p_swap_buffers, double frame_step);
virtual void sync();
+ virtual void scenario_tick(RID p_scenario);
+ virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw);
FUNC0RC(bool, has_changed)
/* RENDER INFO */
diff --git a/servers/visual_server.h b/servers/visual_server.h
index b6da7a9136..3146e62a41 100644
--- a/servers/visual_server.h
+++ b/servers/visual_server.h
@@ -613,10 +613,13 @@ public:
/* CAMERA API */
virtual RID camera_create() = 0;
+ virtual void camera_set_scenario(RID p_camera, RID p_scenario) = 0;
virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) = 0;
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;
@@ -827,6 +830,7 @@ public:
virtual void scenario_set_environment(RID p_scenario, RID p_environment) = 0;
virtual void scenario_set_reflection_atlas_size(RID p_scenario, int p_size, int p_subdiv) = 0;
virtual void scenario_set_fallback_environment(RID p_scenario, RID p_environment) = 0;
+ virtual void scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled) = 0;
/* INSTANCING API */
@@ -854,6 +858,8 @@ public:
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;
@@ -1103,6 +1109,8 @@ public:
virtual bool has_changed() const = 0;
virtual void init() = 0;
virtual void finish() = 0;
+ virtual void scenario_tick(RID p_scenario) = 0;
+ virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw) = 0;
/* STATUS INFORMATION */