From 2674ffe1b7328095dc1daf0f11df9353afbf74c3 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Mon, 9 Jun 2025 09:31:56 +0100 Subject: [PATCH] FTI - `global_transform_interpolated()` on demand for invisible nodes --- scene/3d/node_3d.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 74a73975890..503e4cf7b0a 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -502,13 +502,98 @@ Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fr return res; } +// Visible nodes - get_global_transform_interpolated is cheap. +// Invisible nodes - get_global_transform_interpolated is expensive, try to avoid. Transform3D Node3D::get_global_transform_interpolated() { #if 1 // Pass through if physics interpolation is switched off. // This is a convenience, as it allows you to easy turn off interpolation // without changing any code. - if (data.fti_global_xform_interp_set && is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame() && is_visible_in_tree()) { - return data.global_transform_interpolated; + if (is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !Engine::get_singleton()->is_in_physics_frame()) { + // Note that with SceneTreeFTI, we may want to calculate interpolated transform for a node + // with physics interpolation set to OFF, if it has a parent that is ON. + + // Cheap case. + // We already pre-cache the visible_in_tree for VisualInstances, but NOT for Node3Ds, so we have to + // deal with non-VIs the slow way. + if (Object::cast_to(this) && _is_vi_visible() && data.fti_global_xform_interp_set) { + return data.global_transform_interpolated; + } + + // Find out if visible in tree. + // If not visible in tree, find the FIRST ancestor that is visible in tree. + const Node3D *visible_parent = nullptr; + const Node3D *s = this; + bool visible = true; + bool visible_in_tree = true; + + while (s) { + if (!s->data.visible) { + visible_in_tree = false; + visible = false; + } else { + if (!visible) { + visible_parent = s; + visible = true; + } + } + s = s->data.parent; + } + + // Simplest case, we can return the interpolated xform calculated by SceneTreeFTI. + if (visible_in_tree) { + return data.fti_global_xform_interp_set ? data.global_transform_interpolated : get_global_transform(); + } else if (visible_parent) { + // INVISIBLE case. Not visible, but there is a visible ancestor somewhere in the chain. + if (_get_scene_tree_depth() < 1) { + // This should not happen unless there a problem has been introduced in the scene tree depth code. + // Print a non-spammy error and return something reasonable. + ERR_PRINT_ONCE("depth is < 1."); + return get_global_transform(); + } + + // The interpolated xform is not already calculated for invisible nodes, but we can calculate this + // manually on demand if there is a visible parent. + // First create the chain (backwards), from the node up to first visible parent. + const Node3D **parents = (const Node3D **)alloca((sizeof(const Node3D *) * _get_scene_tree_depth())); + int32_t num_parents = 0; + + s = this; + while (s) { + if (s == visible_parent) { + // Finished. + break; + } + + parents[num_parents++] = s; + s = s->data.parent; + } + + // Now calculate the interpolated chain forwards. + float interpolation_fraction = Engine::get_singleton()->get_physics_interpolation_fraction(); + + // Seed the xform with the visible parent. + Transform3D xform = visible_parent->data.fti_global_xform_interp_set ? visible_parent->data.global_transform_interpolated : visible_parent->get_global_transform(); + Transform3D local_interp; + + // Backwards through the list is forwards through the chain through the tree. + for (int32_t n = num_parents - 1; n >= 0; n--) { + s = parents[n]; + + if (s->is_physics_interpolated()) { + // Make sure to call `get_transform()` rather than using local_transform directly, because + // local_transform may be dirty and need updating from rotation / scale. + TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, interpolation_fraction); + } else { + local_interp = s->get_transform(); + } + xform *= local_interp; + } + + // We could save this in case of multiple calls, + // but probably not necessary. + return xform; + } } return get_global_transform();