Przeglądaj źródła

refactor: make CImage::resolveTransform iterative and reuse computed transform (#602)

resolveTransform walked the parent-object chain recursively; rewrite it as
an iterative loop that collects the chain (bounded by the existing
kMaxParentDepth guard) and folds transforms from the root down. The
per-node computation is extracted into a localTransform() helper.

updateScreenSpacePosition previously called resolveTransform a second time
just to read the angle, repeating the full traversal. updateGeometryBuffers
now returns the ResolvedTransform it already computes so the caller reuses
it. No behaviour change.

Fixes #598
Karashi 3 miesięcy temu
rodzic
commit
f38303d63d

+ 40 - 24
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -126,8 +126,7 @@ std::optional<PuppetMeshBlock> findPuppetMeshBlock (
 }
 }
 
-CImage::ResolvedTransform CImage::resolveTransform (const Object& object, const int depth) const {
-    constexpr int kMaxParentDepth = 32;
+CImage::ResolvedTransform CImage::localTransform (const Object& object) {
     glm::vec3 origin = object.origin->value->getVec3 ();
     glm::vec3 scale = glm::vec3 (1.0f);
     float angle = 0.0f;
@@ -144,30 +143,46 @@ CImage::ResolvedTransform CImage::resolveTransform (const Object& object, const
 	angle = object.groupAngles->value->getVec3 ().z;
     }
 
-    if (!object.parent.has_value ()) {
-	return { origin, scale, angle };
-    }
+    return { origin, scale, angle };
+}
 
-    if (depth >= kMaxParentDepth) {
-	sLog.error ("Parent transform chain is too deep; possible cycle at object id=", object.id);
-	return { origin, scale, angle };
-    }
+CImage::ResolvedTransform CImage::resolveTransform (const Object& object) const {
+    constexpr int kMaxParentDepth = 32;
 
-    const auto* parentObject = this->getScene ().getObject (object.parent.value ());
-    if (parentObject == nullptr) {
-	return { origin, scale, angle };
+    // Walk up the parent chain leaf-first, bounded by kMaxParentDepth to guard
+    // against cycles. chain[0] is the requested object; the last entry is the root.
+    const Object* chain[kMaxParentDepth + 1];
+    int count = 0;
+    const Object* current = &object;
+    chain[count++] = current;
+
+    while (current->parent.has_value ()) {
+	if (count > kMaxParentDepth) {
+	    sLog.error ("Parent transform chain is too deep; possible cycle at object id=", current->id);
+	    break;
+	}
+	const auto* parentObject = this->getScene ().getObject (current->parent.value ());
+	if (parentObject == nullptr) {
+	    break;
+	}
+	current = &parentObject->getObject ();
+	chain[count++] = current;
     }
 
-    const auto& parent = parentObject->getObject ();
-    const auto parentTransform = this->resolveTransform (parent, depth + 1);
-    const glm::vec2 local = rotateVec2 ({ origin.x * parentTransform.scale.x, origin.y * parentTransform.scale.y }, parentTransform.angle);
-    origin.x = parentTransform.origin.x + local.x;
-    origin.y = parentTransform.origin.y + local.y;
-    origin.z = parentTransform.origin.z + origin.z * parentTransform.scale.z;
-    scale *= parentTransform.scale;
-    angle += parentTransform.angle;
+    // Accumulate top-down: the root's local transform is already its resolved
+    // transform, then fold each child onto its already-resolved parent.
+    ResolvedTransform resolved = localTransform (*chain[count - 1]);
+    for (int i = count - 2; i >= 0; --i) {
+	ResolvedTransform local = localTransform (*chain[i]);
+	const glm::vec2 offset =
+	    rotateVec2 ({ local.origin.x * resolved.scale.x, local.origin.y * resolved.scale.y }, resolved.angle);
+	local.origin.x = resolved.origin.x + offset.x;
+	local.origin.y = resolved.origin.y + offset.y;
+	local.origin.z = resolved.origin.z + local.origin.z * resolved.scale.z;
+	resolved = { local.origin, local.scale * resolved.scale, local.angle + resolved.angle };
+    }
 
-    return { origin, scale, angle };
+    return resolved;
 }
 
 CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
@@ -1060,7 +1075,7 @@ void CImage::uploadGeometryBuffers (const glm::vec2& size) {
     this->m_modelMatrix = glm::ortho<float> (0.0, size.x, 0.0, size.y);
 }
 
-void CImage::updateGeometryBuffers () {
+CImage::ResolvedTransform CImage::updateGeometryBuffers () {
     auto sceneWidth = static_cast<float> (this->getScene ().getWidth ());
     auto sceneHeight = static_cast<float> (this->getScene ().getHeight ());
     const auto transform = this->resolveTransform (this->getImage ());
@@ -1075,14 +1090,15 @@ void CImage::updateGeometryBuffers () {
 
     this->updateScenePosition (origin, size, scale, sceneWidth, sceneHeight);
     this->uploadGeometryBuffers (size);
+    return transform;
 }
 
 void CImage::updateScreenSpacePosition () {
-    this->updateGeometryBuffers ();
+    const ResolvedTransform transform = this->updateGeometryBuffers ();
 
     // Build rotation from angles (already in radians from scene.json — see CParticle.cpp:2119)
     // Negate X and Z rotations to account for Y-flipped coordinate system (CParticle.cpp:2120)
-    const float angle = this->resolveTransform (this->getImage ()).angle;
+    const float angle = transform.angle;
     glm::mat4 rotModel = glm::mat4 (1.0f);
     if (angle != 0.0f) {
 	rotModel = glm::translate (rotModel, this->m_sceneCenter);

+ 8 - 2
src/WallpaperEngine/Render/Objects/CImage.h

@@ -70,13 +70,19 @@ protected:
 	float angle;
     };
 
-    [[nodiscard]] ResolvedTransform resolveTransform (const WallpaperEngine::Data::Model::Object& object, int depth = 0) const;
+    [[nodiscard]] ResolvedTransform resolveTransform (const WallpaperEngine::Data::Model::Object& object) const;
+
+    /**
+     * Computes the object's own transform (origin/scale/angle) without walking the
+     * parent chain. Used as the per-node step of resolveTransform.
+     */
+    [[nodiscard]] static ResolvedTransform localTransform (const WallpaperEngine::Data::Model::Object& object);
 
 private:
     bool loadPuppetMesh (const glm::vec2& size);
     void updatePuppetPositionBuffer (const glm::vec2& size);
     void setupPuppetGeometryCallback (Effects::CPass* pass) const;
-    void updateGeometryBuffers ();
+    ResolvedTransform updateGeometryBuffers ();
     [[nodiscard]] glm::vec2 resolveGeometrySize (float sceneWidth, float sceneHeight, glm::vec3& origin) const;
     void updateScenePosition (const glm::vec3& origin, const glm::vec2& size, const glm::vec3& scale, float sceneWidth, float sceneHeight);
     void uploadGeometryBuffers (const glm::vec2& size);