Physics Interpolation - Add InterpolatedProperty

And add some basic interpolated properties to Camera.
This commit is contained in:
lawnjelly
2025-04-02 10:30:22 +01:00
parent 4d6605bed0
commit 18c01b21e5
11 changed files with 409 additions and 64 deletions

View File

@@ -552,10 +552,14 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file,
#endif
#ifdef DEV_ENABLED
#define DEV_CHECK_ONCE(m_cond) \
if (unlikely(!(m_cond))) { \
ERR_PRINT_ONCE("DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \
} else \
#define DEV_CHECK_ONCE(m_cond) \
if (true) { \
static bool first_print = true; \
if (first_print && unlikely(!(m_cond))) { \
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \
first_print = false; \
} \
} else \
((void)0)
#else
#define DEV_CHECK_ONCE(m_cond)

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* interpolated_property.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "interpolated_property.h"
#include "core/math/math_defs.h"
#include "core/math/vector2.h"
namespace InterpolatedPropertyFuncs {
float lerp(float p_a, float p_b, float p_fraction) {
return Math::lerp(p_a, p_b, p_fraction);
}
Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction) {
return p_a.linear_interpolate(p_b, p_fraction);
}
} //namespace InterpolatedPropertyFuncs

View File

@@ -0,0 +1,109 @@
/**************************************************************************/
/* interpolated_property.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 INTERPOLATED_PROPERTY_H
#define INTERPOLATED_PROPERTY_H
struct Vector2;
namespace InterpolatedPropertyFuncs {
float lerp(float p_a, float p_b, float p_fraction);
Vector2 lerp(const Vector2 &p_a, const Vector2 &p_b, float p_fraction);
} //namespace InterpolatedPropertyFuncs
// This class is intended to reduce the boiler plate involved to
// support custom properties to be physics interpolated.
template <class T>
class InterpolatedProperty {
// Only needs interpolating / updating the servers when
// curr and prev are different.
bool _needs_interpolating = false;
T _interpolated;
T curr;
T prev;
public:
// FTI depends on the constant flow between current values
// (on the current tick) and stored previous values (on the previous tick).
// These should be updated both on each tick, and also on resets.
void pump() {
prev = curr;
_needs_interpolating = false;
}
void reset() { pump(); }
void set_interpolated_value(const T &p_val) {
_interpolated = p_val;
}
const T &interpolated() const { return _interpolated; }
bool needs_interpolating() const { return _needs_interpolating; }
bool interpolate(float p_interpolation_fraction) {
if (_needs_interpolating) {
_interpolated = InterpolatedPropertyFuncs::lerp(prev, curr, p_interpolation_fraction);
return true;
}
return false;
}
operator T() const {
return curr;
}
bool operator==(const T &p_o) const {
return p_o == curr;
}
bool operator!=(const T &p_o) const {
return p_o != curr;
}
InterpolatedProperty &operator=(T p_val) {
curr = p_val;
_interpolated = p_val;
_needs_interpolating = true;
return *this;
}
InterpolatedProperty(T p_val) {
curr = p_val;
_interpolated = p_val;
pump();
}
InterpolatedProperty() {
// Ensure either the constructor is run,
// or the memory is zeroed if using a fundamental type.
_interpolated = T{};
curr = T{};
prev = T{};
}
};
#endif // INTERPOLATED_PROPERTY_H

View File

@@ -45,11 +45,63 @@ void Camera::_request_camera_update() {
_update_camera();
}
void Camera::fti_update_servers() {
void Camera::fti_pump_property() {
switch (mode) {
default:
break;
case PROJECTION_PERSPECTIVE: {
fov.pump();
} break;
case PROJECTION_ORTHOGONAL: {
size.pump();
} break;
case PROJECTION_FRUSTUM: {
size.pump();
frustum_offset.pump();
} break;
}
near.pump();
far.pump();
Spatial::fti_pump_property();
}
void Camera::fti_update_servers_property() {
if (camera.is_valid()) {
float f = Engine::get_singleton()->get_physics_interpolation_fraction();
switch (mode) {
default:
break;
case PROJECTION_PERSPECTIVE: {
// If there have been changes due to interpolation, update the servers.
if (fov.interpolate(f) || near.interpolate(f) || far.interpolate(f)) {
VisualServer::get_singleton()->camera_set_perspective(camera, fov.interpolated(), near.interpolated(), far.interpolated());
}
} break;
case PROJECTION_ORTHOGONAL: {
if (size.interpolate(f) || near.interpolate(f) || far.interpolate(f)) {
VisualServer::get_singleton()->camera_set_orthogonal(camera, size.interpolated(), near.interpolated(), far.interpolated());
}
} break;
case PROJECTION_FRUSTUM: {
if (size.interpolate(f) || frustum_offset.interpolate(f) || near.interpolate(f) || far.interpolate(f)) {
VisualServer::get_singleton()->camera_set_frustum(camera, size.interpolated(), frustum_offset.interpolated(), near.interpolated(), far.interpolated());
}
} break;
}
}
Spatial::fti_update_servers_property();
}
void Camera::fti_update_servers_xform() {
if (camera.is_valid()) {
Transform tr = _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
VisualServer::get_singleton()->camera_set_transform(camera, tr);
}
Spatial::fti_update_servers_xform();
}
void Camera::_update_camera_mode() {
@@ -57,7 +109,6 @@ void Camera::_update_camera_mode() {
switch (mode) {
case PROJECTION_PERSPECTIVE: {
set_perspective(fov, near, far);
} break;
case PROJECTION_ORTHOGONAL: {
set_orthogonal(size, near, far);
@@ -66,6 +117,8 @@ void Camera::_update_camera_mode() {
set_frustum(size, frustum_offset, near, far);
} break;
}
fti_notify_node_changed(false);
}
void Camera::_validate_property(PropertyInfo &p_property) const {
@@ -812,11 +865,11 @@ ClippedCamera::ProcessMode ClippedCamera::get_process_mode() const {
return process_mode;
}
void ClippedCamera::fti_pump() {
void ClippedCamera::fti_pump_xform() {
_interpolation_data.clip_offset_prev = _interpolation_data.clip_offset_curr;
// Must call the base class.
Spatial::fti_pump();
Camera::fti_pump_xform();
}
void ClippedCamera::_physics_interpolated_changed() {
@@ -833,9 +886,9 @@ Transform ClippedCamera::_get_adjusted_camera_transform(const Transform &p_xform
return t;
}
void ClippedCamera::fti_update_servers() {
void ClippedCamera::fti_update_servers_xform() {
clip_offset = ((_interpolation_data.clip_offset_curr - _interpolation_data.clip_offset_prev) * Engine::get_singleton()->get_physics_interpolation_fraction()) + _interpolation_data.clip_offset_prev;
Camera::fti_update_servers();
Camera::fti_update_servers_xform();
}
void ClippedCamera::_notification(int p_what) {

View File

@@ -31,6 +31,7 @@
#ifndef CAMERA_H
#define CAMERA_H
#include "core/interpolated_property.h"
#include "scene/3d/spatial.h"
#include "scene/3d/spatial_velocity_tracker.h"
#include "scene/main/viewport.h"
@@ -66,10 +67,12 @@ private:
Projection mode;
float fov;
float size;
Vector2 frustum_offset;
float near, far;
InterpolatedProperty<float> fov;
InterpolatedProperty<float> near;
InterpolatedProperty<float> far;
InterpolatedProperty<float> size;
InterpolatedProperty<Vector2> frustum_offset;
float v_offset;
float h_offset;
KeepAspect keep_aspect;
@@ -109,7 +112,10 @@ protected:
void _update_camera();
virtual void _request_camera_update();
void _update_camera_mode();
virtual void fti_update_servers();
virtual void fti_pump_property();
virtual void fti_update_servers_property();
virtual void fti_update_servers_xform();
void _notification(int p_what);
virtual void _validate_property(PropertyInfo &p_property) const;
@@ -230,9 +236,9 @@ private:
protected:
virtual Transform _get_adjusted_camera_transform(const Transform &p_xform) const;
virtual void fti_pump();
virtual void fti_pump_xform();
virtual void fti_update_servers_xform();
virtual void _physics_interpolated_changed();
virtual void fti_update_servers();
///////////////////////////////////////////////////////
void _notification(int p_what);

View File

@@ -181,13 +181,13 @@ void Spatial::_notification(int p_what) {
if (is_physics_interpolated_and_enabled()) {
// Always reset FTI when entering tree.
fti_pump();
fti_pump_xform();
// No need to interpolate as we are doing a reset.
data.global_transform_interpolated = get_global_transform();
// Make sure servers are up to date.
fti_update_servers();
fti_update_servers_xform();
}
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -266,7 +266,14 @@ void Spatial::_notification(int p_what) {
if (data.client_physics_interpolation_data) {
data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
}
data.local_transform_prev = data.local_transform;
// In most cases, nodes derived from Spatial will have to
// already have reset code available for SceneTreeFTI,
// so it makes sense for them to reuse this method
// rather than respond individually to NOTIFICATION_RESET_PHYSICS_INTERPOLATION,
// unless they need to perform specific tasks (like changing process modes).
fti_pump_xform();
fti_pump_property();
} break;
case NOTIFICATION_PAUSED: {
@@ -302,7 +309,7 @@ void Spatial::set_global_rotation(const Vector3 &p_euler_rad) {
set_global_transform(transform);
}
void Spatial::fti_pump() {
void Spatial::fti_pump_xform() {
if (data.dirty & DIRTY_LOCAL) {
_update_local_transform();
}
@@ -310,9 +317,9 @@ void Spatial::fti_pump() {
data.local_transform_prev = data.local_transform;
}
void Spatial::fti_notify_node_changed() {
void Spatial::fti_notify_node_changed(bool p_transform_changed) {
if (is_inside_tree()) {
get_tree()->get_scene_tree_fti().spatial_notify_set_transform(*this);
get_tree()->get_scene_tree_fti().spatial_notify_changed(*this, p_transform_changed);
}
}
@@ -1107,8 +1114,10 @@ Spatial::Spatial() :
data.vi_visible = true;
data.merging_allowed = true;
data.fti_on_frame_list = false;
data.fti_on_tick_list = false;
data.fti_on_frame_xform_list = false;
data.fti_on_frame_property_list = false;
data.fti_on_tick_xform_list = false;
data.fti_on_tick_property_list = false;
data.fti_global_xform_interp_set = false;
data.merging_mode = MERGING_MODE_INHERIT;

View File

@@ -123,8 +123,10 @@ private:
bool disable_scale : 1;
// Scene tree interpolation
bool fti_on_frame_list : 1;
bool fti_on_tick_list : 1;
bool fti_on_frame_xform_list : 1;
bool fti_on_frame_property_list : 1;
bool fti_on_tick_xform_list : 1;
bool fti_on_tick_property_list : 1;
bool fti_global_xform_interp_set : 1;
bool merging_allowed : 1;
@@ -166,18 +168,23 @@ protected:
// Calling this announces to the FTI system that a node has been moved,
// or requires an update in terms of interpolation
// (e.g. changing Camera zoom even if position hasn't changed).
void fti_notify_node_changed();
void fti_notify_node_changed(bool p_transform_changed = true);
// Opportunity after FTI to update the servers
// with global_transform_interpolated,
// and any custom interpolated data in derived classes.
// Make sure to call the parent class fti_update_servers(),
// so all data is updated to the servers.
virtual void fti_update_servers() {}
virtual void fti_update_servers_xform() {}
virtual void fti_update_servers_property() {}
// Pump the FTI data, also gives a chance for inherited classes
// to pump custom data, but they *must* call the base class here too.
virtual void fti_pump();
// This is the opportunity for classes to move current values for
// transforms and properties to stored previous values,
// and this should take place both on ticks, and during resets.
virtual void fti_pump_xform();
virtual void fti_pump_property() {}
void _notification(int p_what);
static void _bind_methods();

View File

@@ -82,7 +82,7 @@ void VisualInstance::set_instance_use_identity_transform(bool p_enable) {
}
}
void VisualInstance::fti_update_servers() {
void VisualInstance::fti_update_servers_xform() {
if (!_is_using_identity_transform()) {
VisualServer::get_singleton()->instance_set_transform(get_instance(), _get_cached_global_transform_interpolated());
}

View File

@@ -52,7 +52,7 @@ protected:
void _update_visibility();
virtual void _refresh_portal_mode();
void set_instance_use_identity_transform(bool p_enable);
virtual void fti_update_servers();
virtual void fti_update_servers_xform();
void _notification(int p_what);
static void _bind_methods();

View File

@@ -38,12 +38,16 @@
#include "scene/3d/spatial.h"
#include "scene/3d/visual_instance.h"
// Uncomment this to enable some slow extra DEV_ENABLED
// checks to ensure there aren't more than one object added to the lists.
// #define GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
void SceneTreeFTI::_reset_flags(Node *p_node) {
Spatial *s = Object::cast_to<Spatial>(p_node);
if (s) {
s->data.fti_on_frame_list = false;
s->data.fti_on_tick_list = false;
s->data.fti_on_frame_xform_list = false;
s->data.fti_on_tick_xform_list = false;
// In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// will reset this, but this should help cover hidden nodes.
@@ -60,8 +64,8 @@ void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
return;
}
data.spatial_tick_list[0].clear();
data.spatial_tick_list[1].clear();
data.tick_xform_list[0].clear();
data.tick_xform_list[1].clear();
// Spatial flags must be reset.
if (p_root) {
@@ -79,57 +83,125 @@ void SceneTreeFTI::tick_update() {
uint32_t curr_mirror = data.mirror;
uint32_t prev_mirror = curr_mirror ? 0 : 1;
LocalVector<Spatial *> &curr = data.spatial_tick_list[curr_mirror];
LocalVector<Spatial *> &prev = data.spatial_tick_list[prev_mirror];
LocalVector<Spatial *> &curr = data.tick_xform_list[curr_mirror];
LocalVector<Spatial *> &prev = data.tick_xform_list[prev_mirror];
// First detect on the previous list but not on this tick list.
for (uint32_t n = 0; n < prev.size(); n++) {
Spatial *s = prev[n];
if (!s->data.fti_on_tick_list) {
if (!s->data.fti_on_tick_xform_list) {
// Needs a reset so jittering will stop.
s->fti_pump();
s->fti_pump_xform();
// This may not get updated so set it to the same as global xform.
// TODO: double check this is the best value.
s->data.global_transform_interpolated = s->get_global_transform();
// Remove from interpolation list.
if (s->data.fti_on_frame_list) {
s->data.fti_on_frame_list = false;
if (s->data.fti_on_frame_xform_list) {
s->data.fti_on_frame_xform_list = false;
}
}
}
LocalVector<Spatial *> &curr_prop = data.tick_property_list[curr_mirror];
LocalVector<Spatial *> &prev_prop = data.tick_property_list[prev_mirror];
// Detect on the previous property list but not on this tick list.
for (uint32_t n = 0; n < prev_prop.size(); n++) {
Spatial *s = prev_prop[n];
if (!s->data.fti_on_tick_property_list) {
// Needs a reset so jittering will stop.
s->fti_pump_xform();
// Remove from interpolation list.
if (s->data.fti_on_frame_property_list) {
s->data.fti_on_frame_property_list = false;
data.frame_property_list.erase_unordered(s);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.frame_property_list.find(s) == -1);
#endif
}
}
}
// Pump all on the property list that are NOT on the tick list.
for (uint32_t n = 0; n < curr_prop.size(); n++) {
Spatial *s = curr_prop[n];
// Reset, needs to be marked each tick.
s->data.fti_on_tick_property_list = false;
s->fti_pump_property();
}
// Now pump all on the current list.
for (uint32_t n = 0; n < curr.size(); n++) {
Spatial *s = curr[n];
// Reset, needs to be marked each tick.
s->data.fti_on_tick_list = false;
s->data.fti_on_tick_xform_list = false;
// Pump.
s->fti_pump();
s->fti_pump_xform();
}
// Clear previous list and flip.
prev.clear();
prev_prop.clear();
data.mirror = prev_mirror;
}
void SceneTreeFTI::_spatial_notify_set_transform(Spatial &r_spatial) {
// This may be checked by the calling routine already,
// but needs to be double checked for custom SceneTrees.
if (!data.enabled || !r_spatial.is_physics_interpolated()) {
void SceneTreeFTI::_spatial_notify_set_property(Spatial &r_spatial) {
if (!r_spatial.is_physics_interpolated()) {
return;
}
if (!r_spatial.data.fti_on_tick_list) {
r_spatial.data.fti_on_tick_list = true;
data.spatial_tick_list[data.mirror].push_back(&r_spatial);
DEV_CHECK_ONCE(data.enabled);
// Note that a Spatial can be on BOTH the transform list and the property list.
if (!r_spatial.data.fti_on_tick_property_list) {
r_spatial.data.fti_on_tick_property_list = true;
// Should only appear once in the property list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_spatial) == -1);
#endif
data.tick_property_list[data.mirror].push_back(&r_spatial);
}
if (!r_spatial.data.fti_on_frame_list) {
r_spatial.data.fti_on_frame_list = true;
if (!r_spatial.data.fti_on_frame_property_list) {
r_spatial.data.fti_on_frame_property_list = true;
// Should only appear once in the property frame list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.frame_property_list.find(&r_spatial) == -1);
#endif
data.frame_property_list.push_back(&r_spatial);
}
}
void SceneTreeFTI::_spatial_notify_set_xform(Spatial &r_spatial) {
if (!r_spatial.is_physics_interpolated()) {
return;
}
DEV_CHECK_ONCE(data.enabled);
if (!r_spatial.data.fti_on_tick_xform_list) {
r_spatial.data.fti_on_tick_xform_list = true;
// Should only appear once in the xform list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_spatial) == -1);
#endif
data.tick_xform_list[data.mirror].push_back(&r_spatial);
}
if (!r_spatial.data.fti_on_frame_xform_list) {
r_spatial.data.fti_on_frame_xform_list = true;
}
}
@@ -138,14 +210,33 @@ void SceneTreeFTI::spatial_notify_delete(Spatial *p_spatial) {
return;
}
if (p_spatial->data.fti_on_frame_list) {
p_spatial->data.fti_on_frame_list = false;
ERR_FAIL_NULL(p_spatial);
if (p_spatial->data.fti_on_frame_xform_list) {
p_spatial->data.fti_on_frame_xform_list = false;
}
// This can potentially be optimized for large scenes with large churn,
// as it will be doing a linear search through the lists.
data.spatial_tick_list[0].erase_unordered(p_spatial);
data.spatial_tick_list[1].erase_unordered(p_spatial);
data.tick_xform_list[0].erase_unordered(p_spatial);
data.tick_xform_list[1].erase_unordered(p_spatial);
data.tick_property_list[0].erase_unordered(p_spatial);
data.tick_property_list[1].erase_unordered(p_spatial);
data.frame_property_list.erase_unordered(p_spatial);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
// There should only be one occurrence on the lists.
// Check this in DEV_ENABLED builds.
DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_spatial) == -1);
DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_spatial) == -1);
DEV_CHECK_ONCE(data.tick_property_list[0].find(p_spatial) == -1);
DEV_CHECK_ONCE(data.tick_property_list[1].find(p_spatial) == -1);
DEV_CHECK_ONCE(data.frame_property_list.find(p_spatial) == -1);
#endif
}
void SceneTreeFTI::_update_dirty_spatials(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform *p_parent_global_xform, int p_depth) {
@@ -176,7 +267,7 @@ void SceneTreeFTI::_update_dirty_spatials(Node *p_node, uint32_t p_current_frame
if (!p_active) {
if (data.frame_start) {
// On the frame start, activate whenever we hit something that requests interpolation.
if (s->data.fti_on_frame_list) {
if (s->data.fti_on_frame_xform_list) {
p_active = true;
}
} else {
@@ -236,7 +327,7 @@ void SceneTreeFTI::_update_dirty_spatials(Node *p_node, uint32_t p_current_frame
}
// Upload to VisualServer the interpolated global xform.
s->fti_update_servers();
s->fti_update_servers_xform();
} // if active.
@@ -283,6 +374,15 @@ void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
print_line("Took " + itos(after - before) + " usec " + (data.frame_start ? "start" : "end"));
}
#endif
// Update the properties once off at the end of the frame.
// No need for two passes for properties.
if (!p_frame_start) {
for (uint32_t n = 0; n < data.frame_property_list.size(); n++) {
Spatial *s = data.frame_property_list[n];
s->fti_update_servers_property();
}
}
}
#endif // ndef _3D_DISABLED

View File

@@ -47,7 +47,7 @@ public:
void set_enabled(Node *p_root, bool p_enabled) {}
bool is_enabled() const { return false; }
void spatial_notify_set_transform(Spatial &r_spatial) {}
void spatial_notify_changed(Spatial &r_spatial, bool p_transform_changed) {}
void spatial_notify_delete(Spatial *p_spatial) {}
};
#else
@@ -66,7 +66,13 @@ public:
class SceneTreeFTI {
struct Data {
// Prev / Curr lists of spatials having local xforms pumped.
LocalVector<Spatial *> spatial_tick_list[2];
LocalVector<Spatial *> tick_xform_list[2];
// Prev / Curr lists of spatials having actively interpolated properties.
LocalVector<Spatial *> tick_property_list[2];
LocalVector<Spatial *> frame_property_list;
uint32_t mirror = 0;
bool enabled = false;
@@ -82,15 +88,20 @@ class SceneTreeFTI {
void _update_dirty_spatials(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform *p_parent_global_xform = nullptr, int p_depth = 0);
void _reset_flags(Node *p_node);
void _spatial_notify_set_transform(Spatial &r_spatial);
void _spatial_notify_set_xform(Spatial &r_spatial);
void _spatial_notify_set_property(Spatial &r_spatial);
public:
// Hottest function, allow inlining the data.enabled check.
void spatial_notify_set_transform(Spatial &r_spatial) {
void spatial_notify_changed(Spatial &r_spatial, bool p_transform_changed) {
if (!data.enabled) {
return;
}
_spatial_notify_set_transform(r_spatial);
if (p_transform_changed) {
_spatial_notify_set_xform(r_spatial);
} else {
_spatial_notify_set_property(r_spatial);
}
}
void spatial_notify_delete(Spatial *p_spatial);