fix(viewer): bake folding/garage doors at an un-flipped rest pose

`poseDoorMovingParts` assigned a single euler axis (`group.rotation.y` /
`.x`). The live system was fine because the group's euler stays a clean
(0, y, 0). But the GLB exporter clones the door and decomposes its matrix,
which re-derives a gimbal-flipped euler (x=z=π) for any rotation beyond
±90° — folding panels reach ~158°. The reset to t=0 then only zeroed `.y`,
leaving the π residue on x/z and baking a 180°-flipped rest pose (panels
folded out toward a wrong position even when closed).

Set the full euler triple via `.set()` in every pose branch so the other
two axes are always zeroed, clearing any decomposed residue. Add a
regression test that exports an open folding door and asserts an identity
rest pose for all panels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-26 12:12:11 -04:00
co-authored by Claude Opus 4.8
parent 4c81435e3b
commit 7530de5348
2 changed files with 38 additions and 4 deletions
@@ -1392,7 +1392,7 @@ export function poseDoorMovingParts(
// the group rotates about its own origin (see the closed build below).
const angle = (Math.PI / 2) * t
const hingeY = leafCenterY + leafHeight / 2
group.rotation.x = -angle
group.rotation.set(-angle, 0, 0)
group.position.set(0, hingeY * (1 - Math.cos(angle)), Math.sin(angle) * (hingeY - leafHeight))
return true
}
@@ -1410,7 +1410,11 @@ export function poseDoorMovingParts(
const direction = index % 2 === 0 ? -1 : 1
if (group) {
posed = true
group.rotation.y = (prevDirection - direction) * foldAngle
// Set the full triple (not just `.y`): when the export clones and
// decomposes the door matrix, a |Y| > π/2 rotation re-derives into a
// gimbal-flipped euler (x=z=π). Assigning only `.y` would leave that
// π residue on x/z and bake a flipped rest pose.
group.rotation.set(0, (prevDirection - direction) * foldAngle, 0)
}
prevDirection = direction
}
@@ -1446,7 +1450,7 @@ export function poseDoorMovingParts(
z = -(curveRadius + pathPosition - curveLength)
}
group.position.set(0, y, z)
group.rotation.x = rotationX
group.rotation.set(rotationX, 0, 0)
}
return posed
}