Refine window corner shape support
This commit is contained in:
@@ -19,6 +19,7 @@ import { SlabSystem } from '../../systems/slab/slab-system'
|
||||
import { StairSystem } from '../../systems/stair/stair-system'
|
||||
import { WallCutout } from '../../systems/wall/wall-cutout'
|
||||
import { WallSystem } from '../../systems/wall/wall-system'
|
||||
import { WindowAnimationSystem } from '../../systems/window/window-animation-system'
|
||||
import { WindowSystem } from '../../systems/window/window-system'
|
||||
import { ZoneSystem } from '../../systems/zone/zone-system'
|
||||
import { ErrorBoundary } from '../error-boundary'
|
||||
@@ -227,6 +228,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
||||
{/* Core systems */}
|
||||
<CeilingSystem />
|
||||
<DoorAnimationSystem />
|
||||
<WindowAnimationSystem />
|
||||
<DoorSystem />
|
||||
<FenceSystem />
|
||||
<ItemSystem />
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user