#ifndef _3D_DISABLED
#include "scene_tree_fti.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/math/transform_interpolator.h"
#include "core/os/os.h"
#include "scene/3d/visual_instance_3d.h"
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
#include "scene_tree_fti_tests.h"
#endif
#ifdef DEV_ENABLED
#endif
void SceneTreeFTI::_reset_node3d_flags(Node3D &r_node) {
r_node.data.fti_on_tick_xform_list = false;
r_node.data.fti_on_tick_property_list = false;
r_node.data.fti_on_frame_xform_list = false;
r_node.data.fti_on_frame_property_list = false;
r_node.data.fti_global_xform_interp_set = false;
r_node.data.fti_frame_xform_force_update = false;
r_node.data.fti_processed = false;
}
void SceneTreeFTI::_reset_flags(Node *p_node) {
Node3D *s = Object::cast_to<Node3D>(p_node);
if (s) {
_reset_node3d_flags(*s);
s->data.local_transform_prev = s->get_transform();
}
for (int n = 0; n < p_node->get_child_count(); n++) {
_reset_flags(p_node->get_child(n));
}
}
void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
if (data.enabled == p_enabled) {
return;
}
MutexLock(data.mutex);
data.tick_xform_list[0].clear();
data.tick_xform_list[1].clear();
data.frame_xform_list.clear();
data.frame_xform_list_forced.clear();
data.tick_property_list[0].clear();
data.tick_property_list[1].clear();
data.frame_property_list.clear();
data.request_reset_list.clear();
_clear_depth_lists();
if (p_root) {
_reset_flags(p_root);
}
data.enabled = p_enabled;
}
void SceneTreeFTI::tick_update() {
if (!data.enabled) {
return;
}
MutexLock(data.mutex);
_update_request_resets();
uint32_t curr_mirror = data.mirror;
uint32_t prev_mirror = curr_mirror ? 0 : 1;
LocalVector<Node3D *> &curr = data.tick_xform_list[curr_mirror];
LocalVector<Node3D *> &prev = data.tick_xform_list[prev_mirror];
for (uint32_t n = 0; n < prev.size(); n++) {
Node3D *s = prev[n];
if (!s->data.fti_on_tick_xform_list) {
s->fti_pump_xform();
s->data.fti_is_identity_xform = s->data.local_transform == Transform3D();
s->data.global_transform_interpolated = s->get_global_transform();
if (s->data.fti_on_frame_xform_list) {
_node_remove_from_frame_list(*s, false);
}
if (!s->data.fti_frame_xform_force_update) {
_node_add_to_frame_list(*s, true);
}
}
}
LocalVector<Node3D *> &curr_prop = data.tick_property_list[curr_mirror];
LocalVector<Node3D *> &prev_prop = data.tick_property_list[prev_mirror];
for (uint32_t n = 0; n < prev_prop.size(); n++) {
Node3D *s = prev_prop[n];
if (!s->data.fti_on_tick_property_list) {
s->fti_pump_xform();
s->fti_update_servers_property();
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
}
}
}
for (uint32_t n = 0; n < curr_prop.size(); n++) {
Node3D *s = curr_prop[n];
s->data.fti_on_tick_property_list = false;
s->fti_pump_property();
}
for (uint32_t n = 0; n < curr.size(); n++) {
Node3D *s = curr[n];
s->data.fti_on_tick_xform_list = false;
s->fti_pump_xform();
}
prev.clear();
prev_prop.clear();
data.mirror = prev_mirror;
}
void SceneTreeFTI::_update_request_resets() {
for (uint32_t n = 0; n < data.request_reset_list.size(); n++) {
Node3D *s = data.request_reset_list[n];
if (s->_is_physics_interpolation_reset_requested()) {
if (s->_is_vi_visible() && !s->_is_using_identity_transform()) {
s->notification(Node3D::NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
s->_set_physics_interpolation_reset_requested(false);
}
}
data.request_reset_list.clear();
}
void SceneTreeFTI::node_3d_request_reset(Node3D *p_node) {
DEV_CHECK_ONCE(data.enabled);
DEV_ASSERT(p_node);
MutexLock(data.mutex);
if (!p_node->_is_physics_interpolation_reset_requested()) {
p_node->_set_physics_interpolation_reset_requested(true);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
#endif
data.request_reset_list.push_back(p_node);
}
}
void SceneTreeFTI::_node_3d_notify_set_property(Node3D &r_node) {
if (!r_node.is_physics_interpolated()) {
return;
}
DEV_CHECK_ONCE(data.enabled);
if (!r_node.data.fti_on_tick_property_list) {
r_node.data.fti_on_tick_property_list = true;
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_node) == -1);
#endif
data.tick_property_list[data.mirror].push_back(&r_node);
}
if (!r_node.data.fti_on_frame_property_list) {
r_node.data.fti_on_frame_property_list = true;
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.frame_property_list.find(&r_node) == -1);
#endif
data.frame_property_list.push_back(&r_node);
}
}
void SceneTreeFTI::_create_depth_lists() {
uint32_t first_list = data.frame_start ? 0 : 1;
for (uint32_t l = first_list; l < 2; l++) {
LocalVector<Node3D *> &source_list = l == 0 ? data.frame_xform_list : data.frame_xform_list_forced;
#ifdef DEBUG_ENABLED
bool log_nodes_moved_on_frame = (data.traversal_mode == TM_DEBUG) && !data.frame_start && data.periodic_debug_log;
if (log_nodes_moved_on_frame) {
if (source_list.size()) {
print_line(String("\n") + itos(source_list.size()) + " nodes moved during frame:");
} else {
print_line("0 nodes moved during frame.");
}
}
#endif
for (uint32_t n = 0; n < source_list.size(); n++) {
Node3D *s = source_list[n];
s->data.fti_processed = false;
int32_t depth = s->_get_scene_tree_depth();
DEV_ASSERT(depth >= 0);
depth = MIN(depth, (int32_t)data.scene_tree_depth_limit);
LocalVector<Node3D *> &dest_list = data.dirty_node_depth_lists[depth];
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
if (dest_list.find(s) != -1) {
ERR_FAIL_COND(dest_list.find(s) != -1);
}
#endif
#ifdef DEBUG_ENABLED
if (log_nodes_moved_on_frame) {
print_line("\t" + s->get_name());
}
#endif
if ((l == 0) && s->data.fti_frame_xform_force_update) {
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_ASSERT(data.frame_xform_list_forced.find(s) != -1);
#endif
continue;
}
dest_list.push_back(s);
}
}
}
void SceneTreeFTI::_clear_depth_lists() {
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
data.dirty_node_depth_lists[d].clear();
}
}
void SceneTreeFTI::_node_add_to_frame_list(Node3D &r_node, bool p_forced) {
if (p_forced) {
DEV_ASSERT(!r_node.data.fti_frame_xform_force_update);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
int64_t found = data.frame_xform_list_forced.find(&r_node);
if (found != -1) {
ERR_FAIL_COND(found != -1);
}
#endif
data.frame_xform_list_forced.push_back(&r_node);
r_node.data.fti_frame_xform_force_update = true;
} else {
DEV_ASSERT(!r_node.data.fti_on_frame_xform_list);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
int64_t found = data.frame_xform_list.find(&r_node);
if (found != -1) {
ERR_FAIL_COND(found != -1);
}
#endif
data.frame_xform_list.push_back(&r_node);
r_node.data.fti_on_frame_xform_list = true;
}
}
void SceneTreeFTI::_node_remove_from_frame_list(Node3D &r_node, bool p_forced) {
if (p_forced) {
DEV_ASSERT(r_node.data.fti_frame_xform_force_update);
data.frame_xform_list_forced.erase_unordered(&r_node);
r_node.data.fti_frame_xform_force_update = false;
} else {
DEV_ASSERT(r_node.data.fti_on_frame_xform_list);
data.frame_xform_list.erase_unordered(&r_node);
r_node.data.fti_on_frame_xform_list = false;
}
}
void SceneTreeFTI::_node_3d_notify_set_xform(Node3D &r_node) {
DEV_CHECK_ONCE(data.enabled);
if (!r_node.is_physics_interpolated()) {
if (!r_node.data.fti_frame_xform_force_update) {
_node_add_to_frame_list(r_node, true);
}
r_node.data.fti_is_identity_xform = r_node.get_transform() == Transform3D();
return;
}
r_node.data.fti_is_identity_xform = false;
if (!r_node.data.fti_on_tick_xform_list) {
r_node.data.fti_on_tick_xform_list = true;
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_node) == -1);
#endif
data.tick_xform_list[data.mirror].push_back(&r_node);
}
if (!r_node.data.fti_on_frame_xform_list) {
_node_add_to_frame_list(r_node, false);
}
if (data.in_frame) {
if (!r_node.data.fti_frame_xform_force_update) {
_node_add_to_frame_list(r_node, true);
}
}
}
void SceneTreeFTI::node_3d_notify_delete(Node3D *p_node) {
if (!data.enabled) {
return;
}
ERR_FAIL_NULL(p_node);
MutexLock(data.mutex);
if (p_node->data.fti_on_frame_xform_list) {
_node_remove_from_frame_list(*p_node, false);
}
if (p_node->data.fti_frame_xform_force_update) {
_node_remove_from_frame_list(*p_node, true);
}
p_node->_set_physics_interpolation_reset_requested(false);
_reset_node3d_flags(*p_node);
data.tick_xform_list[0].erase_unordered(p_node);
data.tick_xform_list[1].erase_unordered(p_node);
data.tick_property_list[0].erase_unordered(p_node);
data.tick_property_list[1].erase_unordered(p_node);
data.frame_property_list.erase_unordered(p_node);
data.request_reset_list.erase_unordered(p_node);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_property_list[0].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_property_list[1].find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_property_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_xform_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_xform_list_forced.find(p_node) == -1);
#endif
}
void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) {
Node3D *s = Object::cast_to<Node3D>(p_node);
#ifdef DEBUG_ENABLED
data.debug_node_count++;
#endif
if (s && !s->data.visible) {
return;
}
p_node->_update_children_cache();
Span<Node *> children = p_node->data.children_cache.span();
uint32_t num_children = children.size();
if (!s) {
for (uint32_t n = 0; n < num_children; n++) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
}
return;
}
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) {
_ALLOW_DISCARD_ s->get_global_transform();
}
if (!p_active) {
if (data.frame_start) {
if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) {
p_active = true;
}
} else {
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) {
p_active = true;
#if 0
if (data.periodic_debug_log) {
print_line("activating on : " + s->get_name());
}
#endif
}
}
}
if (data.frame_start) {
s->data.fti_global_xform_interp_set = p_active;
}
if (p_active) {
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
bool dirty = s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
if (data.periodic_debug_log && !data.use_optimized_traversal_method && !data.frame_start) {
String sz;
for (int n = 0; n < p_depth; n++) {
sz += "\t";
}
print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : "") + (s->get_transform() == Transform3D() ? "\t[IDENTITY]" : ""));
}
#endif
Transform3D local_interp;
if (s->is_physics_interpolated()) {
if (s->data.fti_on_tick_xform_list) {
TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction);
} else {
local_interp = s->get_transform();
}
} else {
local_interp = s->get_transform();
}
if (!s->is_set_as_top_level()) {
if (p_parent_global_xform) {
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? *p_parent_global_xform : ((*p_parent_global_xform) * local_interp);
} else {
const Node3D *parent = s->get_parent_node_3d();
if (parent) {
const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->get_global_transform();
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? parent_glob : parent_glob * local_interp;
} else {
s->data.global_transform_interpolated = local_interp;
}
}
} else {
s->data.global_transform_interpolated = local_interp;
}
if (s->data.disable_scale) {
s->data.global_transform_interpolated.basis.orthonormalize();
}
s->fti_update_servers_xform();
s->data.fti_processed = true;
#ifdef DEBUG_ENABLED
data.debug_nodes_processed++;
#endif
}
s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
for (uint32_t n = 0; n < num_children; n++) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
}
}
void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
if (!data.enabled || !p_root) {
return;
}
MutexLock(data.mutex);
data.frame_start = p_frame_start;
data.in_frame = true;
_update_request_resets();
float interpolation_fraction = Engine::get_singleton()->get_physics_interpolation_fraction();
uint32_t frame = Engine::get_singleton()->get_frames_drawn();
uint64_t before = 0;
#ifdef DEBUG_ENABLED
if (data.traversal_mode == TM_DEBUG) {
before = OS::get_singleton()->get_ticks_usec();
if (p_frame_start && ((frame % ((60 * 15) - 3)) == 0)) {
data.periodic_debug_log = true;
}
}
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
if (data.periodic_debug_log) {
print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n");
}
#endif
#endif
data.debug_node_count = 0;
data.debug_nodes_processed = 0;
uint32_t half_frame = p_frame_start ? (frame * 2) : ((frame * 2) + 1);
bool print_debug_stats = false;
switch (data.traversal_mode) {
case TM_LEGACY: {
data.use_optimized_traversal_method = false;
} break;
case TM_DEBUG: {
data.use_optimized_traversal_method = (frame % 2) == 1;
print_debug_stats = (frame % ((60 * 8) - 1)) == 0;
} break;
default: {
data.use_optimized_traversal_method = true;
} break;
}
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
_tests->frame_update(p_root, half_frame, interpolation_fraction);
#else
uint32_t skipped = 0;
if (!data.use_optimized_traversal_method) {
_update_dirty_nodes(p_root, half_frame, interpolation_fraction, false);
} else {
_create_depth_lists();
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
const LocalVector<Node3D *> &list = data.dirty_node_depth_lists[d];
#if 0
if (list.size() > 0) {
print_line("depth " + itos(d) + ", contains " + itos(list.size()));
}
#endif
for (uint32_t n = 0; n < list.size(); n++) {
Node3D *s = list[n];
if (s->data.fti_processed) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
if (Object::cast_to<VisualInstance3D>(s)) {
if (!s->_is_vi_visible()) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
} else if (!s->is_visible_in_tree()) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
_update_dirty_nodes(s, half_frame, interpolation_fraction, true);
}
}
_clear_depth_lists();
}
if (print_debug_stats) {
uint64_t after = OS::get_singleton()->get_ticks_usec();
print_line(String(data.use_optimized_traversal_method ? "FTI optimized" : "FTI reference") + " nodes traversed : " + itos(data.debug_node_count) + (skipped == 0 ? "" : ", skipped " + itos(skipped)) + ", processed : " + itos(data.debug_nodes_processed) + ", took " + itos(after - before) + " usec " + (data.frame_start ? "(start)" : "(end)"));
}
#endif
uint32_t forced_list_size = data.frame_xform_list_forced.size();
for (uint32_t n = 0; n < forced_list_size; n++) {
Node3D *s = data.frame_xform_list_forced[n];
s->data.fti_frame_xform_force_update = false;
}
data.frame_xform_list_forced.clear();
if (!p_frame_start && data.periodic_debug_log) {
data.periodic_debug_log = false;
}
if (!p_frame_start) {
for (uint32_t n = 0; n < data.frame_property_list.size(); n++) {
Node3D *s = data.frame_property_list[n];
s->fti_update_servers_property();
}
}
if (!data.frame_start) {
data.in_frame = false;
}
}
SceneTreeFTI::SceneTreeFTI() {
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
_tests = memnew(SceneTreeFTITests(*this));
#endif
Variant traversal_mode_string = GLOBAL_DEF("physics/3d/physics_interpolation/scene_traversal", "DEFAULT");
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "physics/3d/physics_interpolation/scene_traversal", PROPERTY_HINT_ENUM, "DEFAULT,Legacy,Debug"));
data.traversal_mode = TM_DEFAULT;
if (traversal_mode_string == "Legacy") {
data.traversal_mode = TM_LEGACY;
} else if (traversal_mode_string == "Debug") {
#ifdef DEBUG_ENABLED
data.traversal_mode = TM_DEBUG;
#else
data.traversal_mode = TM_DEFAULT;
#endif
}
switch (data.traversal_mode) {
default: {
print_verbose("SceneTreeFTI: traversal method DEFAULT");
} break;
case TM_LEGACY: {
print_verbose("SceneTreeFTI: traversal method Legacy");
} break;
case TM_DEBUG: {
print_verbose("SceneTreeFTI: traversal method Debug");
} break;
}
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_EXTRA_CHECKS defined");
#endif
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_PRINT_TREE defined");
#endif
}
SceneTreeFTI::~SceneTreeFTI() {
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
if (_tests) {
memfree(_tests);
_tests = nullptr;
}
#endif
}
#endif