Merge ae67e51ceb
into 0529813cf2
This commit is contained in:
commit
9057332f73
|
@ -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]);
|
||||
|
|
87
core/math/interpolator.cpp
Normal file
87
core/math/interpolator.cpp
Normal 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
56
core/math/interpolator.h
Normal 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
|
|
@ -675,6 +675,11 @@
|
|||
Sets whether this is an instance load placeholder. See [InstancePlaceholder].
|
||||
</description>
|
||||
</method>
|
||||
<method name="teleport">
|
||||
<return type="void" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="update_configuration_warning">
|
||||
<return type="void" />
|
||||
<description>
|
||||
|
@ -787,6 +792,8 @@
|
|||
<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.
|
||||
</constant>
|
||||
<constant name="NOTIFICATION_TELEPORT" value="28">
|
||||
</constant>
|
||||
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
|
||||
Notification received from the OS when the mouse enters the game window.
|
||||
Implemented on desktop and web platforms.
|
||||
|
|
|
@ -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.
|
||||
</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">
|
||||
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].
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<StringName, MultiplayerAPI::RPCMode> rpc_methods;
|
||||
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;
|
||||
|
||||
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();
|
||||
|
|
|
@ -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<World>(memnew(World)));
|
||||
}
|
||||
set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", true));
|
||||
|
||||
// Initialize network state
|
||||
multiplayer_poll = true;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
if (p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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> 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<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() { 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<Instance>::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<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();
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -54,6 +54,8 @@ class VisualServerWrapMT : public VisualServer {
|
|||
SafeNumeric<uint64_t> 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 */
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
Loading…
Reference in a new issue