Refine window corner shape support

This commit is contained in:
sudhir
2026-05-07 13:34:16 +05:30
parent eef19804d1
commit 133d81e563
13 changed files with 3034 additions and 6 deletions
@@ -0,0 +1,108 @@
import { type AnyNodeId, sceneRegistry, useInteractive, useScene } from '@pascal-app/core'
import { useFrame } from '@react-three/fiber'
import {
AWNING_WINDOW_SASH_NAME,
CASEMENT_WINDOW_SASH_NAME,
FRENCH_CASEMENT_LEFT_SASH_NAME,
FRENCH_CASEMENT_RIGHT_SASH_NAME,
HOPPER_WINDOW_SASH_NAME,
} from './window-system'
const easeWindowAnimation = (value: number) => value * value * (3 - 2 * value)
function markWindowDirty(windowId: AnyNodeId) {
const scene = useScene.getState()
const node = scene.nodes[windowId]
scene.dirtyNodes.add(windowId)
}
function applyDirectWindowAnimation(windowId: AnyNodeId, value: number) {
const node = useScene.getState().nodes[windowId]
if (node?.type !== 'window') return false
const mesh = sceneRegistry.nodes.get(windowId)
if (node.windowType === 'casement') {
if ((node.casementStyle ?? 'single') === 'french') {
const leftSash = mesh?.getObjectByName(FRENCH_CASEMENT_LEFT_SASH_NAME)
const rightSash = mesh?.getObjectByName(FRENCH_CASEMENT_RIGHT_SASH_NAME)
if (!(leftSash && rightSash)) return false
leftSash.rotation.y = -value * (Math.PI / 2)
rightSash.rotation.y = value * (Math.PI / 2)
return true
}
const sash = mesh?.getObjectByName(CASEMENT_WINDOW_SASH_NAME)
if (!sash) return false
const hingeSign = (node.hingesSide ?? 'left') === 'left' ? -1 : 1
sash.rotation.y = hingeSign * value * (Math.PI / 2)
return true
}
if (node.windowType === 'awning') {
const sash = mesh?.getObjectByName(AWNING_WINDOW_SASH_NAME)
if (!sash) return false
sash.rotation.x = -value * (Math.PI / 3)
return true
}
if (node.windowType === 'hopper') {
const sash =
mesh?.getObjectByName(AWNING_WINDOW_SASH_NAME) ??
mesh?.getObjectByName(HOPPER_WINDOW_SASH_NAME)
if (!sash) return false
sash.rotation.x = -value * (Math.PI / 3)
return true
}
return false
}
export const WindowAnimationSystem = () => {
useFrame(({ clock }) => {
const interactive = useInteractive.getState()
const entries = Object.entries(interactive.windowAnimations)
if (entries.length === 0) return
const now = clock.getElapsedTime() * 1000
for (const [windowId, animation] of entries) {
const typedWindowId = windowId as AnyNodeId
const scene = useScene.getState()
const node = scene.nodes[typedWindowId]
if (node?.type !== 'window') {
interactive.cancelWindowAnimation(typedWindowId)
interactive.removeWindowOpenState(typedWindowId)
continue
}
const startedAt = animation.startedAt ?? now
if (animation.startedAt === null) {
interactive.startWindowAnimation(typedWindowId, { ...animation, startedAt })
}
const progress = Math.min(1, (now - startedAt) / animation.durationMs)
const value = animation.from + (animation.to - animation.from) * easeWindowAnimation(progress)
interactive.setWindowOpenState(typedWindowId, { [animation.field]: value })
const appliedDirectly = applyDirectWindowAnimation(typedWindowId, value)
if (!appliedDirectly) markWindowDirty(typedWindowId)
if (progress < 1) continue
interactive.cancelWindowAnimation(typedWindowId)
if (animation.persist) {
scene.updateNode(typedWindowId, { [animation.field]: animation.to })
interactive.removeWindowOpenState(typedWindowId)
markWindowDirty(typedWindowId)
} else {
interactive.setWindowOpenState(typedWindowId, { [animation.field]: animation.to })
}
}
}, 2)
return null
}
File diff suppressed because it is too large Load Diff