Browse Source

Merge pull request #107307 from lawnjelly/scenetreefti_get_interp_hidden

[3.x] FTI - `global_transform_interpolated()` on demand for invisible nodes
lawnjelly 3 months ago
parent
commit
aea8ebf1f3
1 changed files with 88 additions and 2 deletions
  1. 88 2
      scene/3d/spatial.cpp

+ 88 - 2
scene/3d/spatial.cpp

@@ -33,6 +33,7 @@
 #include "core/engine.h"
 #include "core/math/transform_interpolator.h"
 #include "core/message_queue.h"
+#include "scene/3d/visual_instance.h"
 #include "scene/main/scene_tree.h"
 #include "scene/main/viewport.h"
 #include "scene/scene_string_names.h"
@@ -457,13 +458,98 @@ Transform Spatial::_get_global_transform_interpolated(real_t p_interpolation_fra
 	return res;
 }
 
+// Visible nodes - get_global_transform_interpolated is cheap.
+// Invisible nodes - get_global_transform_interpolated is expensive, try to avoid.
 Transform Spatial::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 Spatials, so we have to
+		// deal with non-VIs the slow way.
+		if (Object::cast_to<VisualInstance>(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 Spatial *visible_parent = nullptr;
+		const Spatial *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 Spatial **parents = (const Spatial **)alloca((sizeof(const Spatial *) * _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.
+			Transform xform = visible_parent->data.fti_global_xform_interp_set ? visible_parent->data.global_transform_interpolated : visible_parent->get_global_transform();
+			Transform 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(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();