Skip to content

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

Merged
wass08 merged 8 commits into
mainfrom
fix/bake-door-window-anims
Jun 28, 2026
Merged

fix(doors): bake open-animation clips for every operation door + un-flipped rest pose#444
wass08 merged 8 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 live posing for many door types; regressions could show wrong rest poses or broken open clips in exported GLBs.

Overview
Operation doors (sliding, pocket, barn, folding, garage types) now follow the same build closed + pose at t pattern as windows: moving parts live in named groups, poseDoorMovingParts drives live state and GLB sampling, and bakeOperationDoorClip writes sampled position/rotation/scale tracks (rollup uses a scale approximation; live rollup still rebuilds slats).

Open clip names change from ${node.name}: open to ${id}: open so duplicate display names do not collide in baked useAnimations playback; labels stay in extras.label.

Folding doors are refactored to a nested panel chain with a full euler reset so export no longer bakes a 180°-flipped rest when saved open. Walkthrough mode applies shared WALKTHROUGH_FOV (60°) in both walkthrough controllers.

Reviewed by Cursor Bugbot for commit b0313a1. 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>
Comment thread packages/editor/src/lib/glb-export.ts
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.

Comment thread packages/viewer/src/systems/door/door-system.tsx
wass08 and others added 2 commits June 28, 2026 16:32
Fixes the lone biome organizeImports error so the quality gate passes on the
post-#448 base. Type-check (9/9), biome, and all suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wass08 wass08 merged commit a602335 into main Jun 28, 2026
2 of 3 checks passed
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