Skip to content

Feat/door improvement#293

Open
sudhir9297 wants to merge 5 commits intopascalorg:mainfrom
sudhir9297:feat/door-improvement
Open

Feat/door improvement#293
sudhir9297 wants to merge 5 commits intopascalorg:mainfrom
sudhir9297:feat/door-improvement

Conversation

@sudhir9297
Copy link
Copy Markdown
Contributor

What does this PR do?

This PR significantly expands the door system with new door types, garage doors, animated open/close behavior, and improved first-person interaction.

It enables:

  • Multiple interior and garage door types
  • Smooth animated open/close interactions
  • Accurate movement and collision behavior in first-person view
  • More flexible door configurations (panel, glass, empty)
  • Cleaner architecture separating authored state from runtime animation

Door Types

  • Added interior door types:
    • Hinged
    • Double
    • French
    • Folding
    • Pocket
    • Barn
    • Sliding

Garage Doors

  • Added garage door category with:
    • Sectional garage door
    • Roll-up garage door
    • Tilt-up garage door

Animation & Interaction

  • Added animated open/close behavior:
    • R key toggles door state
    • Open slider stays in sync with toggle
    • Uses smooth tweened animation instead of instant jumps

Door Behavior Improvements

  • Folding doors:
    • Support 2-panel and 4-panel configurations
    • Panels fold correctly during opening
  • Pocket, barn, folding, and sliding doors:
    • Support panel, glass, and empty variants
    • Empty keeps frame but removes fill

Garage Door Mechanics

  • Sectional doors:
    • Open panel-by-panel along a curved path
    • UI 100% maps to correct fully open position
  • Roll-up doors:
    • Coil into compact roller (side/top)
  • Tilt-up doors:
    • Rotate upward as a single slab

First-Person View (FPV)

  • Door animations work correctly in FPV
  • Folding doors allow pass-through when open
  • Garage door colliders update based on open state
  • Improved interaction and collision handling for:
    • Sectional doors
    • Roll-up doors

Architecture Improvements

  • Door nodes store only final authored state
  • Animation handled via transient runtime state
  • Shared door operation helpers moved into core
  • Renderer and FPV colliders use unified open-state mapping

Bug Fixes

  • Removed double-door center line artifact
  • Fixed width/height resetting when switching door types
  • Fixed door flip value resetting during drag
  • Ensured sectional/garage border strips remain visible when open
  • Fixed roll-up door:
    • Z-fighting issues
    • Roller sizing inconsistencies

Copy link
Copy Markdown
Collaborator

@wass08 wass08 left a comment

Choose a reason for hiding this comment

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

door-interaction.ts in packages/editor/src/lib/ currently runs a requestAnimationFrame loop, mutating useInteractive per frame and committing to useScene on completion. It works, but it parks animation logic in the editor layer when animation is fundamentally a viewer concern. This blocks two things:

  1. The read-only /viewer/[id] route can't animate doors — it can't reach into @pascal-app/editor (per viewer-isolation.md).
  2. The editor maintains its own frame clock (window.requestAnimationFrame) that's independent of R3F's render loop, so animation timing isn't synchronized with the rest of the scene and won't pause/resume with the tab the way R3F's clock does.

We already have the right pattern in the codebase: InteractiveSystem in packages/viewer/src/systems/interactive/ handles item control values driving mesh animation. Doors with swingAngle/operationState are the same shape — make this consistent.

The change, at a high level

Three pieces, each in its right layer:

1. Extend useInteractive (core) with an animation queue.
Add a doorAnimations map and a startDoorAnimation / cancelDoorAnimation setter pair. These are pure state writes — no Three.js, no rAF, no timing. The store just holds the active tween descriptors (field, from, to, startedAt, durationMs, persist).

2. Add DoorAnimationSystem in packages/viewer/src/systems/door/.
A React component that renders nothing and uses useFrame to advance every active animation: read the queue from useInteractive, compute the eased value for each, write it via setDoorOpenState, mark the door (and parent wall) dirty. When a tween reaches t === 1: if persist, commit the final value via useScene.updateNode and clear the runtime entry; otherwise just leave the runtime value in place. Then remove the entry from doorAnimations. Mount the system inside <Viewer> next to the existing systems.

3. Shrink door-interaction.ts to a thin trigger.
Keep the constants (DOOR_SWING_OPEN_ANGLE, etc.) and replace animateDoorOpenState with a small toggleDoorOpenState(doorId) that picks the right field/from/to based on the door's doorType, then calls useInteractive.getState().startDoorAnimation(...). All callers (use-keyboard.ts, first-person-controls.tsx, anything else) switch to this one-liner. No rAF anywhere on the editor side.

Things to watch for

  • onComplete callbacks. The current animateDoorOpenState accepts an optional onComplete?: () => void. Every current call site uses fire-and-forget, so the simplest path is to drop it. If a future need arises, emit on the event bus (emitter.emit('door:animation-completed', { doorId, field })) — don't store callbacks in the zustand store.
  • Cancellation. startDoorAnimation should overwrite any existing entry for the same door (toggle-while-animating should reverse smoothly from the current value, not snap). The system reads the current displayed value from useInteractive.doors[id] rather than from, so the new tween starts at the visible position.
  • Dirty propagation. Make sure the system marks both the door node and its parent wall dirty each frame an animation is running, same as the current code does — otherwise the wall-cutout won't update.
  • Mount order. Place <DoorAnimationSystem /> before <DoorSystem /> in packages/viewer/src/components/viewer/index.tsx so per-frame state writes land before the renderer's frame-end reconciliation.
  • Persistence semantics. Today's door-interaction.ts defaults persist: true. Keep that default in the new API so undo/redo still captures door toggles as one history entry.

Why now, not later

The PR works correctly and shipping it doesn't break anything, so the temptation is to file this as a follow-up. The reason to do it before merging the main door work into the broader codebase: each new caller of animateDoorOpenState (panel buttons, future automation, walkthrough triggers) hardens the wrong shape and makes the eventual refactor more invasive. Refactor is mostly mechanical — state moves into the store, the rAF loop becomes a useFrame, callers swap to a thin wrapper. Doing it as part of this PR keeps the door domain clean from the start instead of carrying technical debt forward.

Acceptance criteria

  • door-interaction.ts contains no requestAnimationFrame calls and no module-level animation state map.
  • packages/editor/src/lib/ has no rAF orchestration around door state.
  • packages/viewer/src/systems/door/door-animation-system.tsx exists and is mounted inside <Viewer>.
  • useInteractive exposes startDoorAnimation / cancelDoorAnimation and a doorAnimations map; the existing setDoorOpenState / removeDoorOpenState stay (the system uses them internally).
  • Door toggling from the keyboard, first-person interaction, and any panel control all go through the same toggleDoorOpenState (or equivalent) thin wrapper.
  • Behavior is unchanged: same easing, same ~520ms duration, same persistence default, same end-state writes to scene history.

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.

2 participants