Skip to content

fix(doors): bake open-animation clips for every operation door + un-flipped rest pose#444

Open
wass08 wants to merge 6 commits into
mainfrom
fix/bake-door-window-anims
Open

fix(doors): bake open-animation clips for every operation door + un-flipped rest pose#444
wass08 wants to merge 6 commits into
mainfrom
fix/bake-door-window-anims

Conversation

@wass08

@wass08 wass08 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

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 operationState straight 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:

  • Each builder in door-system.tsx emits its moving parts in a named group at the closed pose.
  • A new exported 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.ts bakeDoorClip dispatches operation types to bakeOperationDoorClip (samples 16 segments → position/quaternion/scale tracks for any moving group; resets closed) vs the renamed bakeSwingDoorClip.

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

  • Folding fold direction was inverted (+z into the room) — corrected to fold −z, parity-tested panel-for-panel.
  • Open-clip names were ${node.name}: open, so multiple same-named windows/doors collided (the baked viewer keys useAnimations by clip name) — now ${id}: open (unique; display name stays in extras.label).
  • Un-flipped rest pose (last commit): a folding door saved open baked a 180°-flipped rest. The export clones + decomposes the door matrix, which re-derives a gimbal-flipped euler (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 poseDoorMovingParts drives live view and export, and bakeOperationDoorClip samples 16 steps into position/rotation/scale tracks (roll-up uses a scale approximation on door-rollup-curtain while live roll-up still rebuilds slats).

GLB open clips are named ${nodeId}: open instead of display names so duplicate labels do not collapse useAnimations actions; human names stay in extras.label.

Folding rig is a parented panel chain; pose resets the full euler so export does not bake a gimbal-flipped rest when operationState was 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.

wass08 and others added 3 commits June 26, 2026 10:24
…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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7530de5. Configure here.

wass08 and others added 2 commits June 26, 2026 15:51
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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 61f3921. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant