fix(doors): bake open-animation clips for every operation door + un-flipped rest pose#444
fix(doors): bake open-animation clips for every operation door + un-flipped rest pose#444wass08 wants to merge 6 commits into
Conversation
…ket/barn doors Only swing doors (hinged/double/french) baked an open clip into the GLB — they carry a `pascalSwingLeaf` marker the exporter reads. Every operation door type (sliding, pocket, barn, folding, garage-sectional/rollup/tiltup) baked its `operationState` straight into mesh vertex positions at build time, so the exporter had no re-poseable node to sample and the artifact never flagged them `openable`. Give operation doors the same build-once + pose-at-t split windows already use. Each builder now emits its moving parts in a named group at the CLOSED pose, and `poseDoorMovingParts` (the single source of truth, shared by the live system and the GLB exporter) drives the open motion: - sliding/pocket/barn: rigid leaf translation - garage-tiltup: rigid hinge about the lintel - folding: hinged accordion chain (nested groups, per-joint fold) - garage-sectional: per-panel groups posed along the overhead curve - garage-rollup: the one type whose live geometry changes (slats roll onto a drum, which a glTF clip can't express) keeps its full-detail live rebuild; the curtain is wrapped in a top-pivoted group the exporter scales up into the lintel as the baked approximation. The exporter samples each operation door's motion into keyframe tracks (16 segments) so the non-linear rigs (curve, accordion) stay faithful, and stamps `extras.openable` + `extras.clips` so any glTF consumer can play it. Tests: per-type kinematics (groups build, rest closed, open) + sliding/roll-up clip baking (sampled position/scale tracks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…que per node
Two issues surfaced testing the baked viewer:
- Folding door folded toward +z (into the room) — the joint rotation sign
was inverted, so the accordion opened the wrong way ("weird position").
Flip to `(prevDirection - direction) * foldAngle` so leaves fold toward
−z, matching the original inline rig. Verified panel-for-panel against the
original formula at every operationState.
- Openable clips were named by display name (`<name>: open`), but the baked
viewer drives playback by clip name (`useAnimations` maps name → action).
Several windows share the name "Window 1", so their clips collapsed to one
action and triggering any one opened the first. Key the clip name by node
id (`<id>: open`) — unique, matching the item-loop convention; the
human-readable name still lives in `extras.label`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`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>
| } | ||
| } | ||
|
|
||
| poseDoorMovingParts(node, doorObject, 0) |
There was a problem hiding this comment.
Roll-up export rest not closed
Medium Severity
For roll-up garage doors, bakeOperationDoorClip doesn't fully reset the door to a closed state for GLB export. The poseDoorMovingParts(..., 0) call only adjusts scale, leaving the slat geometry's height partially open. This results in a mismatched GLB rest pose and an incorrect animation clip baseline.
Reviewed by Cursor Bugbot for commit 7530de5. Configure here.
The bundled ceiling-fan model.glb was stale (no animation clip, two slots). Replace it with the variant matching production storage: an `On` animation clip and a third `slot_base` paint slot. Same dimensions/offset, so no catalog metadata change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The walkthrough rode the default 50° orbit camera, which feels cramped on foot. Both walkthrough controllers (baked GlbWalkthroughController and the parametric WalkthroughControls fallback) now set a shared WALKTHROUGH_FOV = 60 on enter and restore the prior FOV on exit, leaving orbit framing untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(viewer): walkthrough FOV 60° + refresh ceiling-fan model
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 61f3921. Configure here.
| leafDepth + 0.018, | ||
| 0, | ||
| topY - visibleHeight, | ||
| -visibleHeight, |
There was a problem hiding this comment.
Roll-up GLB bake geometry mismatch
Medium Severity
The garage-rollup door's addGarageRollupDoor function builds the ROLLUP_CURTAIN_NAME group based on the current operationState. This conflicts with GLB export, which expects this group to be built at its full, closed height and then scaled by poseDoorMovingParts. As a result, GLB exports of garage-rollup doors will have an incorrect rest pose or missing animation if the door is not fully closed when exported.
Reviewed by Cursor Bugbot for commit 61f3921. Configure here.


What
Bake an open-animation clip into the GLB for every openable/slideable door driven by the edition panel — not just hinged/swing doors. Previously sliding, pocket, barn, folding, and all garage doors baked their
operationStatestraight into mesh vertices at build time, so the exported GLB had no re-poseable node and no open clip.How
Gives operation doors the same build-once + pose-at-t split the windows already use:
door-system.tsxemits its moving parts in a named group at the closed pose.poseDoorMovingParts(node, mesh, t)is the single source of truth for the open motion — the live system poses after each rebuild, and the GLB exporter poses a clone to sample keyframes.glb-export.tsbakeDoorClipdispatches operation types tobakeOperationDoorClip(samples 16 segments → position/quaternion/scale tracks for any moving group; resets closed) vs the renamedbakeSwingDoorClip.Per-type rigs: sliding/pocket/barn = leaf translate; garage-tiltup = rigid hinge about the lintel; folding = nested-group accordion chain; garage-sectional = per-panel groups along the overhead curve. garage-rollup is the one exception — its slats genuinely vanish onto a drum (not expressible as a glTF clip), so it keeps its live rebuild and the exporter scales a top-pivoted curtain group as the agreed approximation.
Follow-up fixes folded in
+zinto the room) — corrected to fold−z, parity-tested panel-for-panel.${node.name}: open, so multiple same-named windows/doors collided (the baked viewer keysuseAnimationsby clip name) — now${id}: open(unique; display name stays inextras.label).x=z=π) for any rotation beyond ±90° (folding reaches ~158°); the pose reset only zeroed.y, leaving the π residue. Fixed by setting the full euler triple in every pose branch.Tests
door-animation.test.ts— per-type kinematics.glb-export.test.ts— sliding (sampled position) + rollup (sampled scale) clips, and a regression asserting an identity rest pose for an open folding door.Validation
Baked a real project end-to-end locally (headless worker → served
lod0.glb): all door-fold panels rest at identity[0,0,0,1]with the open clip targeting every panel; sliding/pocket/barn/folding clips present with correct group targets.Note
Medium Risk
Touches door mesh construction, GLB animation baking, and clip naming used by the baked viewer—regressions could affect export rest poses or which door animates on interaction, but scope is well covered by new tests.
Overview
Operation doors (sliding, pocket, barn, folding, garage types) now follow the same build closed + pose at t pattern as windows: moving geometry lives in named groups, shared
poseDoorMovingPartsdrives live view and export, andbakeOperationDoorClipsamples 16 steps into position/rotation/scale tracks (roll-up uses a scale approximation ondoor-rollup-curtainwhile live roll-up still rebuilds slats).GLB open clips are named
${nodeId}: openinstead of display names so duplicate labels do not collapseuseAnimationsactions; human names stay inextras.label.Folding rig is a parented panel chain; pose resets the full euler so export does not bake a gimbal-flipped rest when
operationStatewas saved open.Walkthrough temporarily sets perspective camera FOV to 60° (
WALKTHROUGH_FOV) in editor and baked GLB controllers, restored on exit.Tests cover per-type kinematics, sliding/rollup export clips, and folding identity rest pose.
Reviewed by Cursor Bugbot for commit 61f3921. Bugbot is set up for automated code reviews on this repo. Configure here.