This commit is contained in:
lawnjelly 2021-11-11 11:26:42 +00:00 committed by GitHub
commit 9057332f73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 810 additions and 45 deletions

View file

@ -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() { void invert() {
for (U i = 0; i < count / 2; i++) { for (U i = 0; i < count / 2; i++) {
SWAP(data[i], data[count - i - 1]); SWAP(data[i], data[count - i - 1]);

View file

@ -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;
}

56
core/math/interpolator.h Normal file
View file

@ -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

View file

@ -675,6 +675,11 @@
Sets whether this is an instance load placeholder. See [InstancePlaceholder]. Sets whether this is an instance load placeholder. See [InstancePlaceholder].
</description> </description>
</method> </method>
<method name="teleport">
<return type="void" />
<description>
</description>
</method>
<method name="update_configuration_warning"> <method name="update_configuration_warning">
<return type="void" /> <return type="void" />
<description> <description>
@ -787,6 +792,8 @@
<constant name="NOTIFICATION_POST_ENTER_TREE" value="27"> <constant name="NOTIFICATION_POST_ENTER_TREE" value="27">
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 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.
</constant> </constant>
<constant name="NOTIFICATION_TELEPORT" value="28">
</constant>
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002"> <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
Notification received from the OS when the mouse enters the game window. Notification received from the OS when the mouse enters the game window.
Implemented on desktop and web platforms. Implemented on desktop and web platforms.

View file

@ -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. 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. [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.
</member> </member>
<member name="physics/common/physics_interpolation" type="bool" setter="" getter="" default="true">
</member>
<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5"> <member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
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. 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]. [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].

View file

@ -291,6 +291,17 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
// before advance_core considers changing the physics_steps return from // before advance_core considers changing the physics_steps return from
// the typical values as defined by typical_physics_steps // the typical values as defined by typical_physics_steps
float MainTimerSync::get_physics_jitter_fix() { 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(); return Engine::get_singleton()->get_physics_jitter_fix();
} }

View file

@ -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) { void Camera::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_WORLD: { case NOTIFICATION_ENTER_WORLD: {
@ -112,6 +116,9 @@ void Camera::_notification(int p_what) {
viewport->_camera_set(this); viewport->_camera_set(this);
} }
ERR_FAIL_COND(get_world().is_null());
VisualServer::get_singleton()->camera_set_scenario(camera, get_world()->get_scenario());
} break; } break;
case NOTIFICATION_TRANSFORM_CHANGED: { case NOTIFICATION_TRANSFORM_CHANGED: {
_request_camera_update(); _request_camera_update();
@ -119,7 +126,14 @@ void Camera::_notification(int p_what) {
velocity_tracker->update_position(get_global_transform().origin); velocity_tracker->update_position(get_global_transform().origin);
} }
} break; } break;
case NOTIFICATION_TELEPORT: {
if (is_physics_interpolated()) {
VisualServer::get_singleton()->camera_teleport(camera);
}
} break;
case NOTIFICATION_EXIT_WORLD: { case NOTIFICATION_EXIT_WORLD: {
VisualServer::get_singleton()->camera_set_scenario(camera, RID());
if (!get_tree()->is_node_being_edited(this)) { if (!get_tree()->is_node_being_edited(this)) {
if (is_current()) { if (is_current()) {
clear_current(); clear_current();

View file

@ -94,6 +94,8 @@ protected:
virtual void _request_camera_update(); virtual void _request_camera_update();
void _update_camera_mode(); void _update_camera_mode();
virtual void _physics_interpolated_changed();
void _notification(int p_what); void _notification(int p_what);
virtual void _validate_property(PropertyInfo &p_property) const; virtual void _validate_property(PropertyInfo &p_property) const;

View file

@ -85,6 +85,13 @@ void VisualInstance::_notification(int p_what) {
VisualServer::get_singleton()->instance_set_transform(instance, gt); VisualServer::get_singleton()->instance_set_transform(instance, gt);
} }
} break; } 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: { case NOTIFICATION_EXIT_WORLD: {
VisualServer::get_singleton()->instance_set_scenario(instance, RID()); VisualServer::get_singleton()->instance_set_scenario(instance, RID());
VisualServer::get_singleton()->instance_attach_skeleton(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 { RID VisualInstance::get_instance() const {
return instance; return instance;
} }

View file

@ -49,6 +49,7 @@ class VisualInstance : public CullInstance {
protected: protected:
void _update_visibility(); void _update_visibility();
virtual void _refresh_portal_mode(); virtual void _refresh_portal_mode();
virtual void _physics_interpolated_changed();
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();

View file

@ -31,6 +31,7 @@
#include "node.h" #include "node.h"
#include "core/core_string_names.h" #include "core/core_string_names.h"
#include "core/engine.h"
#include "core/io/resource_loader.h" #include "core/io/resource_loader.h"
#include "core/message_queue.h" #include "core/message_queue.h"
#include "core/print_string.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() { void Node::_propagate_enter_tree() {
// this needs to happen to all children before any 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 // to be used when not wanted
} }
void Node::_physics_interpolated_changed() {}
void Node::set_physics_process(bool p_process) { void Node::set_physics_process(bool p_process) {
if (data.physics_process == p_process) { if (data.physics_process == p_process) {
return; return;
@ -754,6 +770,22 @@ bool Node::can_process() const {
return true; 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 { float Node::get_physics_process_delta_time() const {
if (data.tree) { if (data.tree) {
return data.tree->get_physics_process_time(); 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("set_physics_process_internal", "enable"), &Node::set_physics_process_internal);
ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal); ClassDB::bind_method(D_METHOD("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("get_tree"), &Node::get_tree);
ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANCING | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)); 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_PROCESS);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
BIND_CONSTANT(NOTIFICATION_TELEPORT);
BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER); BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT); 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::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::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("_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta")));
BIND_VMETHOD(MethodInfo("_enter_tree")); BIND_VMETHOD(MethodInfo("_enter_tree"));
@ -2990,6 +3031,7 @@ Node::Node() {
data.idle_process_internal = false; data.idle_process_internal = false;
data.inside_tree = false; data.inside_tree = false;
data.ready_notified = false; data.ready_notified = false;
data.physics_interpolated = true;
data.owner = nullptr; data.owner = nullptr;
data.OW = nullptr; data.OW = nullptr;

View file

@ -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. int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name; StringName name;
SceneTree *tree; 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 #ifdef TOOLS_ENABLED
NodePath import_path; //path used when imported, used by scene editors to keep tracking NodePath import_path; //path used when imported, used by scene editors to keep tracking
#endif #endif
@ -120,25 +117,31 @@ private:
Map<StringName, MultiplayerAPI::RPCMode> rpc_methods; Map<StringName, MultiplayerAPI::RPCMode> rpc_methods;
Map<StringName, MultiplayerAPI::RPCMode> rpc_properties; Map<StringName, MultiplayerAPI::RPCMode> 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; int process_priority;
bool physics_process_internal; // variables used to properly sort the node when processing, ignored otherwise
bool idle_process_internal; //should move all the stuff below to bits
bool physics_process : 1;
bool idle_process : 1;
bool input; bool physics_process_internal : 1;
bool unhandled_input; bool idle_process_internal : 1;
bool unhandled_key_input;
bool parent_owned; bool input : 1;
bool in_constructor; bool unhandled_input : 1;
bool use_placeholder; bool unhandled_key_input : 1;
bool physics_interpolated : 1;
bool display_folded; bool parent_owned : 1;
bool editable_instance; 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; mutable NodePath *path_cache;
@ -163,6 +166,7 @@ private:
void _propagate_exit_tree(); void _propagate_exit_tree();
void _propagate_after_exit_tree(); void _propagate_after_exit_tree();
void _propagate_validate_owner(); void _propagate_validate_owner();
void _propagate_physics_interpolated(bool p_interpolated);
void _print_stray_nodes(); void _print_stray_nodes();
void _propagate_pause_owner(Node *p_owner); void _propagate_pause_owner(Node *p_owner);
Array _get_node_and_resource(const NodePath &p_path); Array _get_node_and_resource(const NodePath &p_path);
@ -193,6 +197,8 @@ protected:
virtual void remove_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child);
virtual void move_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); void _propagate_replace_owner(Node *p_owner, Node *p_by_owner);
static void _bind_methods(); static void _bind_methods();
@ -226,6 +232,7 @@ public:
NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PROCESS = 25,
NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
NOTIFICATION_POST_ENTER_TREE = 27, NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_TELEPORT = 28,
//keep these linked to node //keep these linked to node
NOTIFICATION_WM_MOUSE_ENTER = MainLoop::NOTIFICATION_WM_MOUSE_ENTER, NOTIFICATION_WM_MOUSE_ENTER = MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
NOTIFICATION_WM_MOUSE_EXIT = MainLoop::NOTIFICATION_WM_MOUSE_EXIT, NOTIFICATION_WM_MOUSE_EXIT = MainLoop::NOTIFICATION_WM_MOUSE_EXIT,
@ -386,6 +393,10 @@ public:
bool can_process() const; bool can_process() const;
bool can_process_notification(int p_what) 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(); void request_ready();
static void print_stray_nodes(); static void print_stray_nodes();

View file

@ -465,11 +465,42 @@ void SceneTree::init() {
MainLoop::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) { bool SceneTree::iteration(float p_time) {
root_lock++; root_lock++;
current_frame++; 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(); flush_transform_notifications();
MainLoop::iteration(p_time); MainLoop::iteration(p_time);
@ -602,6 +633,13 @@ bool SceneTree::idle(float p_time) {
#endif #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; return _quit;
} }
@ -2036,6 +2074,7 @@ SceneTree::SceneTree() {
call_lock = 0; call_lock = 0;
root_lock = 0; root_lock = 0;
node_count = 0; node_count = 0;
_physics_interpolation_enabled = false;
//create with mainloop //create with mainloop
@ -2045,6 +2084,7 @@ SceneTree::SceneTree() {
if (!root->get_world().is_valid()) { if (!root->get_world().is_valid()) {
root->set_world(Ref<World>(memnew(World))); root->set_world(Ref<World>(memnew(World)));
} }
set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", true));
// Initialize network state // Initialize network state
multiplayer_poll = true; multiplayer_poll = true;

View file

@ -117,6 +117,7 @@ private:
bool _quit; bool _quit;
bool initialized; bool initialized;
bool input_handled; bool input_handled;
bool _physics_interpolation_enabled;
Size2 last_screen_size; Size2 last_screen_size;
StringName tree_changed_name; StringName tree_changed_name;
@ -407,6 +408,9 @@ public:
void set_refuse_new_network_connections(bool p_refuse); void set_refuse_new_network_connections(bool p_refuse);
bool is_refusing_new_network_connections() const; 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); static void add_idle_callback(IdleCallback p_callback);
SceneTree(); SceneTree();
~SceneTree(); ~SceneTree();

View file

@ -32,6 +32,7 @@
#define RASTERIZER_H #define RASTERIZER_H
#include "core/math/camera_matrix.h" #include "core/math/camera_matrix.h"
#include "core/math/interpolator.h"
#include "servers/visual_server.h" #include "servers/visual_server.h"
#include "core/self_list.h" #include "core/self_list.h"
@ -89,8 +90,16 @@ public:
RID skeleton; RID skeleton;
RID material_override; 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; 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; int depth_layer;
uint32_t layer_mask; uint32_t layer_mask;
@ -106,11 +115,21 @@ public:
VS::ShadowCastingSetting cast_shadows; VS::ShadowCastingSetting cast_shadows;
//fit in 32 bits //fit in 32 bits
bool mirror : 8; bool mirror : 1;
bool receive_shadows : 8; bool receive_shadows : 1;
bool visible : 8; bool visible : 1;
bool baked_light : 4; //this flag is only to know if it actually did use baked light bool baked_light : 1; //this flag is only to know if it actually did use baked light
bool redraw_if_visible : 4; 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 float depth; //used for sorting
@ -137,6 +156,12 @@ public:
lightmap_capture = nullptr; lightmap_capture = nullptr;
lightmap_slice = -1; lightmap_slice = -1;
lightmap_uv_rect = Rect2(0, 0, 1, 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;
} }
}; };

View file

@ -94,7 +94,17 @@ void VisualServerRaster::request_frame_drawn_callback(Object *p_where, const Str
frame_drawn_callbacks.push_back(fdc); 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) { 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 //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"); VS::get_singleton()->emit_signal("frame_pre_draw");

View file

@ -436,10 +436,13 @@ public:
/* CAMERA API */ /* CAMERA API */
BIND0R(RID, camera_create) BIND0R(RID, camera_create)
BIND2(camera_set_scenario, RID, RID)
BIND4(camera_set_perspective, RID, float, float, float) BIND4(camera_set_perspective, RID, float, float, float)
BIND4(camera_set_orthogonal, RID, float, float, float) BIND4(camera_set_orthogonal, RID, float, float, float)
BIND5(camera_set_frustum, RID, float, Vector2, float, float) BIND5(camera_set_frustum, RID, float, Vector2, float, float)
BIND2(camera_set_transform, RID, const Transform &) 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_cull_mask, RID, uint32_t)
BIND2(camera_set_environment, RID, RID) BIND2(camera_set_environment, RID, RID)
BIND2(camera_set_use_vertical_aspect, RID, bool) BIND2(camera_set_use_vertical_aspect, RID, bool)
@ -540,6 +543,7 @@ public:
BIND2(scenario_set_environment, RID, RID) BIND2(scenario_set_environment, RID, RID)
BIND3(scenario_set_reflection_atlas_size, RID, int, int) BIND3(scenario_set_reflection_atlas_size, RID, int, int)
BIND2(scenario_set_fallback_environment, RID, RID) BIND2(scenario_set_fallback_environment, RID, RID)
BIND2(scenario_set_physics_interpolation_enabled, RID, bool)
/* INSTANCING API */ /* INSTANCING API */
BIND0R(RID, instance_create) BIND0R(RID, instance_create)
@ -548,6 +552,8 @@ public:
BIND2(instance_set_scenario, RID, RID) BIND2(instance_set_scenario, RID, RID)
BIND2(instance_set_layer_mask, RID, uint32_t) BIND2(instance_set_layer_mask, RID, uint32_t)
BIND2(instance_set_transform, RID, const Transform &) 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) BIND2(instance_attach_object_instance_id, RID, ObjectID)
BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_blend_shape_weight, RID, int, float)
BIND3(instance_set_surface_material, RID, int, RID) BIND3(instance_set_surface_material, RID, int, RID)
@ -735,6 +741,8 @@ public:
virtual bool has_changed() const; virtual bool has_changed() const;
virtual void init(); virtual void init();
virtual void finish(); virtual void finish();
virtual void scenario_tick(RID p_scenario);
virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw);
/* STATUS INFORMATION */ /* STATUS INFORMATION */

View file

@ -30,6 +30,7 @@
#include "visual_server_scene.h" #include "visual_server_scene.h"
#include "core/math/interpolator.h"
#include "core/os/os.h" #include "core/os/os.h"
#include "visual_server_globals.h" #include "visual_server_globals.h"
#include "visual_server_raster.h" #include "visual_server_raster.h"
@ -38,11 +39,33 @@
/* CAMERA API */ /* 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() { RID VisualServerScene::camera_create() {
Camera *camera = memnew(Camera); Camera *camera = memnew(Camera);
return camera_owner.make_rid(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) { 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); Camera *camera = camera_owner.get(p_camera);
ERR_FAIL_COND(!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; 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) { void VisualServerScene::camera_set_transform(RID p_camera, const Transform &p_transform) {
Camera *camera = camera_owner.get(p_camera); Camera *camera = camera_owner.get(p_camera);
ERR_FAIL_COND(!camera); ERR_FAIL_COND(!camera);
camera->transform = p_transform.orthonormalized(); 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) { 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() { VisualServerScene::Scenario::Scenario() {
debug = VS::SCENARIO_DEBUG_DISABLED; debug = VS::SCENARIO_DEBUG_DISABLED;
_interpolation_data.interpolation_enabled = false;
bool use_bvh_or_octree = GLOBAL_GET("rendering/quality/spatial_partitioning/use_bvh"); bool use_bvh_or_octree = GLOBAL_GET("rendering/quality/spatial_partitioning/use_bvh");
@ -379,6 +430,33 @@ RID VisualServerScene::scenario_create() {
return scenario_rid; 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) { void VisualServerScene::scenario_set_debug(RID p_scenario, VS::ScenarioDebugMode p_debug_mode) {
Scenario *scenario = scenario_owner.get(p_scenario); Scenario *scenario = scenario_owner.get(p_scenario);
ERR_FAIL_COND(!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; 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) { void VisualServerScene::instance_set_transform(RID p_instance, const Transform &p_transform) {
Instance *instance = instance_owner.get(p_instance); Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!instance); ERR_FAIL_COND(!instance);
if (instance->transform == p_transform) { if (!instance->is_currently_interpolated()) {
return; //must be checked to avoid worst evil if (instance->transform == p_transform) {
} return; //must be checked to avoid worst evil
}
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
const Vector3 &v = i < 3 ? p_transform.basis.elements[i] : p_transform.origin; 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_inf(v.x));
ERR_FAIL_COND(Math::is_nan(v.x)); ERR_FAIL_COND(Math::is_nan(v.x));
ERR_FAIL_COND(Math::is_inf(v.y)); ERR_FAIL_COND(Math::is_inf(v.y));
ERR_FAIL_COND(Math::is_nan(v.y)); ERR_FAIL_COND(Math::is_nan(v.y));
ERR_FAIL_COND(Math::is_inf(v.z)); ERR_FAIL_COND(Math::is_inf(v.z));
ERR_FAIL_COND(Math::is_nan(v.z)); ERR_FAIL_COND(Math::is_nan(v.z));
} }
#endif #endif
instance->transform = p_transform; instance->transform = p_transform;
_instance_queue_update(instance, true); _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) { void VisualServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
Instance *instance = instance_owner.get(p_instance); Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!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) { void VisualServerScene::_update_instance(Instance *p_instance) {
p_instance->version++; 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) { if (p_instance->base_type == VS::INSTANCE_LIGHT) {
InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data); InstanceLightData *light = static_cast<InstanceLightData *>(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; light->shadow_dirty = true;
} }
if (p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) { if (p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) {
InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(p_instance->base_data); InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(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; reflection_probe->reflection_dirty = true;
} }
if (p_instance->base_type == VS::INSTANCE_PARTICLES) { 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) { 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; 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; p_instance->transformed_aabb = new_aabb;
@ -2378,8 +2682,11 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view
} break; } break;
} }
_prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint); // This getter allows optional fixed timestep interpolation for the camera.
_render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); 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 #endif
} }
@ -4032,9 +4339,12 @@ bool VisualServerScene::free(RID p_rid) {
if (camera_owner.owns(p_rid)) { if (camera_owner.owns(p_rid)) {
Camera *camera = camera_owner.get(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); camera_owner.free(p_rid);
memdelete(camera); memdelete(camera);
} else if (scenario_owner.owns(p_rid)) { } else if (scenario_owner.owns(p_rid)) {
Scenario *scenario = scenario_owner.get(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); 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_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1));
instance_set_scenario(p_rid, RID()); instance_set_scenario(p_rid, RID());
instance_set_base(p_rid, RID()); instance_set_base(p_rid, RID());
@ -4063,6 +4377,7 @@ bool VisualServerScene::free(RID p_rid) {
instance_owner.free(p_rid); instance_owner.free(p_rid);
memdelete(instance); memdelete(instance);
} else if (room_owner.owns(p_rid)) { } else if (room_owner.owns(p_rid)) {
Room *room = room_owner.get(p_rid); Room *room = room_owner.get(p_rid);
room_owner.free(p_rid); room_owner.free(p_rid);

View file

@ -58,6 +58,7 @@ public:
static VisualServerScene *singleton; static VisualServerScene *singleton;
/* CAMERA API */ /* CAMERA API */
struct Scenario;
struct Camera : public RID_Data { struct Camera : public RID_Data {
enum Type { enum Type {
@ -71,12 +72,27 @@ public:
float size; float size;
Vector2 offset; Vector2 offset;
uint32_t visible_layers; uint32_t visible_layers;
bool vaspect;
RID env; RID env;
// transform_prev is only used when using fixed timestep interpolation
Transform transform; 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; 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() { Camera() {
visible_layers = 0xFFFFFFFF; visible_layers = 0xFFFFFFFF;
fov = 70; fov = 70;
@ -86,17 +102,24 @@ public:
size = 1.0; size = 1.0;
offset = Vector2(); offset = Vector2();
vaspect = false; vaspect = false;
scenario = nullptr;
previous_room_id_hint = -1; previous_room_id_hint = -1;
interpolated = true;
on_interpolate_transform_list = false;
interpolation_method = Interpolator::INTERP_LERP;
} }
}; };
mutable RID_Owner<Camera> camera_owner; mutable RID_Owner<Camera> camera_owner;
virtual RID camera_create(); 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_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_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_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_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_cull_mask(RID p_camera, uint32_t p_layers);
virtual void camera_set_environment(RID p_camera, RID p_env); virtual void camera_set_environment(RID p_camera, RID p_env);
virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable); virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable);
@ -199,6 +222,26 @@ public:
SelfList<Instance>::List instances; SelfList<Instance>::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<RID> instance_interpolate_update_list;
LocalVector<RID> instance_transform_update_lists[2];
LocalVector<RID> *instance_transform_update_list_curr = &instance_transform_update_lists[0];
LocalVector<RID> *instance_transform_update_list_prev = &instance_transform_update_lists[1];
LocalVector<RID> instance_teleport_list;
LocalVector<RID> camera_transform_update_lists[2];
LocalVector<RID> *camera_transform_update_list_curr = &camera_transform_update_lists[0];
LocalVector<RID> *camera_transform_update_list_prev = &camera_transform_update_lists[1];
LocalVector<RID> camera_teleport_list;
bool interpolation_enabled;
} _interpolation_data;
Scenario(); Scenario();
~Scenario() { memdelete(sps); } ~Scenario() { memdelete(sps); }
}; };
@ -214,6 +257,9 @@ public:
virtual void scenario_set_environment(RID p_scenario, RID p_environment); 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_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_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 */ /* INSTANCING API */
@ -266,6 +312,8 @@ public:
singleton->_instance_queue_update(this, p_aabb, p_materials); singleton->_instance_queue_update(this, p_aabb, p_materials);
} }
bool is_currently_interpolated() const { return scenario && scenario->is_physics_interpolation_enabled() && interpolated; }
Instance() : Instance() :
scenario_item(this), scenario_item(this),
update_item(this) { update_item(this) {
@ -307,6 +355,7 @@ public:
}; };
SelfList<Instance>::List _instance_update_list; SelfList<Instance>::List _instance_update_list;
void _instance_queue_update(Instance *p_instance, bool p_update_aabb, bool p_update_materials = false); void _instance_queue_update(Instance *p_instance, bool p_update_aabb, bool p_update_materials = false);
struct InstanceGeometryData : public InstanceBaseData { struct InstanceGeometryData : public InstanceBaseData {
@ -517,6 +566,8 @@ public:
virtual void instance_set_scenario(RID p_instance, RID p_scenario); 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_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_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_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_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); virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material);
@ -709,6 +760,10 @@ public:
void render_camera(Ref<ARVRInterface> &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas); void render_camera(Ref<ARVRInterface> &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas);
void update_dirty_instances(); 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 //probes
struct GIProbeDataHeader { struct GIProbeDataHeader {
uint32_t version; uint32_t version;

View file

@ -36,6 +36,18 @@ void VisualServerWrapMT::thread_exit() {
exit.set(); 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) { void VisualServerWrapMT::thread_draw(bool p_swap_buffers, double frame_step) {
if (!draw_pending.decrement()) { if (!draw_pending.decrement()) {
visual_server->draw(p_swap_buffers, frame_step); 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) { void VisualServerWrapMT::draw(bool p_swap_buffers, double frame_step) {
if (create_thread) { if (create_thread) {
draw_pending.increment(); draw_pending.increment();

View file

@ -54,6 +54,8 @@ class VisualServerWrapMT : public VisualServer {
SafeNumeric<uint64_t> draw_pending; SafeNumeric<uint64_t> draw_pending;
void thread_draw(bool p_swap_buffers, double frame_step); void thread_draw(bool p_swap_buffers, double frame_step);
void thread_flush(); 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(); void thread_exit();
@ -367,10 +369,13 @@ public:
/* CAMERA API */ /* CAMERA API */
FUNCRID(camera) FUNCRID(camera)
FUNC2(camera_set_scenario, RID, RID)
FUNC4(camera_set_perspective, RID, float, float, float) FUNC4(camera_set_perspective, RID, float, float, float)
FUNC4(camera_set_orthogonal, RID, float, float, float) FUNC4(camera_set_orthogonal, RID, float, float, float)
FUNC5(camera_set_frustum, RID, float, Vector2, float, float) FUNC5(camera_set_frustum, RID, float, Vector2, float, float)
FUNC2(camera_set_transform, RID, const Transform &) 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_cull_mask, RID, uint32_t)
FUNC2(camera_set_environment, RID, RID) FUNC2(camera_set_environment, RID, RID)
FUNC2(camera_set_use_vertical_aspect, RID, bool) FUNC2(camera_set_use_vertical_aspect, RID, bool)
@ -463,6 +468,7 @@ public:
FUNC2(scenario_set_environment, RID, RID) FUNC2(scenario_set_environment, RID, RID)
FUNC3(scenario_set_reflection_atlas_size, RID, int, int) FUNC3(scenario_set_reflection_atlas_size, RID, int, int)
FUNC2(scenario_set_fallback_environment, RID, RID) FUNC2(scenario_set_fallback_environment, RID, RID)
FUNC2(scenario_set_physics_interpolation_enabled, RID, bool)
/* INSTANCING API */ /* INSTANCING API */
FUNCRID(instance) FUNCRID(instance)
@ -471,6 +477,8 @@ public:
FUNC2(instance_set_scenario, RID, RID) FUNC2(instance_set_scenario, RID, RID)
FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC2(instance_set_layer_mask, RID, uint32_t)
FUNC2(instance_set_transform, RID, const Transform &) 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) FUNC2(instance_attach_object_instance_id, RID, ObjectID)
FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_blend_shape_weight, RID, int, float)
FUNC3(instance_set_surface_material, RID, int, RID) FUNC3(instance_set_surface_material, RID, int, RID)
@ -653,6 +661,8 @@ public:
virtual void finish(); virtual void finish();
virtual void draw(bool p_swap_buffers, double frame_step); virtual void draw(bool p_swap_buffers, double frame_step);
virtual void sync(); 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) FUNC0RC(bool, has_changed)
/* RENDER INFO */ /* RENDER INFO */

View file

@ -613,10 +613,13 @@ public:
/* CAMERA API */ /* CAMERA API */
virtual RID camera_create() = 0; 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_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_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_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_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_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_environment(RID p_camera, RID p_env) = 0;
virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable) = 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_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_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_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 */ /* INSTANCING API */
@ -854,6 +858,8 @@ public:
virtual void instance_set_scenario(RID p_instance, RID p_scenario) = 0; 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_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_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_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_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; 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 bool has_changed() const = 0;
virtual void init() = 0; virtual void init() = 0;
virtual void finish() = 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 */ /* STATUS INFORMATION */