Move door animations into viewer system

This commit is contained in:
sudhir
2026-05-07 10:11:25 +05:30
parent 0de07d6130
commit cef3f24dad
8 changed files with 181 additions and 109 deletions
@@ -6,6 +6,7 @@ import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three/webgpu'
import useViewer from '../../store/use-viewer'
import { CeilingSystem } from '../../systems/ceiling/ceiling-system'
import { DoorAnimationSystem } from '../../systems/door/door-animation-system'
import { DoorSystem } from '../../systems/door/door-system'
import { FenceSystem } from '../../systems/fence/fence-system'
import { GuideSystem } from '../../systems/guide/guide-system'
@@ -225,6 +226,7 @@ const Viewer: React.FC<ViewerProps> = ({
<WallCutout />
{/* Core systems */}
<CeilingSystem />
<DoorAnimationSystem />
<DoorSystem />
<FenceSystem />
<ItemSystem />
@@ -0,0 +1,59 @@
import { type AnyNodeId, type DoorNode, emitter, useInteractive, useScene } from '@pascal-app/core'
import { useFrame } from '@react-three/fiber'
const easeDoorAnimation = (value: number) => value * value * (3 - 2 * value)
function markDoorDirty(doorId: AnyNodeId) {
const scene = useScene.getState()
const node = scene.nodes[doorId]
scene.dirtyNodes.add(doorId)
if (node?.parentId) scene.dirtyNodes.add(node.parentId as AnyNodeId)
}
export const DoorAnimationSystem = () => {
useFrame(({ clock }) => {
const interactive = useInteractive.getState()
const entries = Object.entries(interactive.doorAnimations)
if (entries.length === 0) return
const now = clock.getElapsedTime() * 1000
for (const [doorId, animation] of entries) {
const typedDoorId = doorId as AnyNodeId
const scene = useScene.getState()
const node = scene.nodes[typedDoorId]
if (node?.type !== 'door') {
interactive.cancelDoorAnimation(typedDoorId)
interactive.removeDoorOpenState(typedDoorId)
continue
}
const startedAt = animation.startedAt ?? now
if (animation.startedAt === null) {
interactive.startDoorAnimation(typedDoorId, { ...animation, startedAt })
}
const progress = Math.min(1, (now - startedAt) / animation.durationMs)
const value = animation.from + (animation.to - animation.from) * easeDoorAnimation(progress)
interactive.setDoorOpenState(typedDoorId, { [animation.field]: value })
markDoorDirty(typedDoorId)
if (progress < 1) continue
interactive.cancelDoorAnimation(typedDoorId)
if (animation.persist) {
scene.updateNode(typedDoorId, { [animation.field]: animation.to })
interactive.removeDoorOpenState(typedDoorId)
markDoorDirty(typedDoorId)
} else {
interactive.setDoorOpenState(typedDoorId, { [animation.field]: animation.to })
}
emitter.emit('door:animation-completed', {
doorId: typedDoorId as DoorNode['id'],
field: animation.field,
})
}
}, 2)
return null
}