fix elevation issue

This commit is contained in:
wass08
2026-02-10 10:04:14 +09:00
parent ec51c0c6fc
commit 6ec6a55b05
3 changed files with 46 additions and 37 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ export default function Editor() {
<ActionMenu /> <ActionMenu />
<PanelManager /> <PanelManager />
<SidebarProvider className="fixed z-10"> <SidebarProvider className="fixed z-100">
<AppSidebar /> <AppSidebar />
</SidebarProvider> </SidebarProvider>
<Viewer selectionManager="custom"> <Viewer selectionManager="custom">
@@ -1,9 +1,12 @@
import type { AssetInput } from '@pascal-app/core'
import { import {
type AnyNodeId, type AnyNodeId,
type CeilingEvent, type CeilingEvent,
emitter, emitter,
type GridEvent, type GridEvent,
resolveLevelId,
sceneRegistry, sceneRegistry,
spatialGridManager,
useScene, useScene,
useSpatialQuery, useSpatialQuery,
type WallEvent, type WallEvent,
@@ -12,18 +15,17 @@ import {
import { useViewer } from '@pascal-app/viewer' import { useViewer } from '@pascal-app/viewer'
import { useFrame } from '@react-three/fiber' import { useFrame } from '@react-three/fiber'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { BoxGeometry, Euler, type Mesh, type MeshStandardMaterial, Quaternion, Vector3 } from 'three'
import { spatialGridManager } from '../../../../../packages/core/src/hooks/spatial-grid/spatial-grid-manager'
import { resolveLevelId } from '../../../../../packages/core/src/hooks/spatial-grid/spatial-grid-sync'
import { import {
ceilingStrategy, BoxGeometry,
checkCanPlace, Euler,
floorStrategy, type Mesh,
wallStrategy, type MeshStandardMaterial,
} from './placement-strategies' Quaternion,
Vector3,
} from 'three'
import { ceilingStrategy, checkCanPlace, floorStrategy, wallStrategy } from './placement-strategies'
import type { PlacementState, TransitionResult } from './placement-types' import type { PlacementState, TransitionResult } from './placement-types'
import type { DraftNodeHandle } from './use-draft-node' import type { DraftNodeHandle } from './use-draft-node'
import type { AssetInput } from '@pascal-app/core'
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1] const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
@@ -43,6 +45,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
config.initialState ?? { surface: 'floor', wallId: null, ceilingId: null }, config.initialState ?? { surface: 'floor', wallId: null, ceilingId: null },
) )
// Store config callbacks in refs to avoid re-running effect when they change
const configRef = useRef(config)
configRef.current = config
const { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling } = useSpatialQuery() const { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling } = useSpatialQuery()
const { asset, draftNode } = config const { asset, draftNode } = config
@@ -52,7 +58,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling } const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling }
// Reset placement state // Reset placement state
placementState.current = config.initialState ?? { placementState.current = configRef.current.initialState ?? {
surface: 'floor', surface: 'floor',
wallId: null, wallId: null,
ceilingId: null, ceilingId: null,
@@ -70,9 +76,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const revalidate = (): boolean => { const revalidate = (): boolean => {
const placeable = checkCanPlace(getContext(), validators) const placeable = checkCanPlace(getContext(), validators)
;(cursorRef.current.material as MeshStandardMaterial).color.set( ;(cursorRef.current.material as MeshStandardMaterial).color.set(placeable ? 'green' : 'red')
placeable ? 'green' : 'red',
)
return placeable return placeable
} }
@@ -110,7 +114,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
} }
// ---- Init draft ---- // ---- Init draft ----
config.initDraft(gridPosition.current) configRef.current.initDraft(gridPosition.current)
// Sync cursor to the draft mesh's world position and rotation // Sync cursor to the draft mesh's world position and rotation
if (draftNode.current) { if (draftNode.current) {
@@ -136,7 +140,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
if (!result) return if (!result) return
gridPosition.current.set(...result.gridPosition) gridPosition.current.set(...result.gridPosition)
cursorRef.current.position.set(...result.cursorPosition) // Only update X and Z for cursor - useFrame will handle Y (slab elevation)
cursorRef.current.position.x = result.cursorPosition[0]
cursorRef.current.position.z = result.cursorPosition[2]
const draft = draftNode.current const draft = draftNode.current
if (draft) draft.position = result.gridPosition if (draft) draft.position = result.gridPosition
@@ -152,7 +158,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const currentRotation: [number, number, number] = [0, cursorRef.current.rotation.y, 0] const currentRotation: [number, number, number] = [0, cursorRef.current.rotation.y, 0]
draftNode.commit(result.nodeUpdate) draftNode.commit(result.nodeUpdate)
if (config.onCommitted()) { if (configRef.current.onCommitted()) {
draftNode.create(gridPosition.current, asset, currentRotation) draftNode.create(gridPosition.current, asset, currentRotation)
revalidate() revalidate()
} }
@@ -265,7 +271,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
useScene.getState().dirtyNodes.add(result.dirtyNodeId) useScene.getState().dirtyNodes.add(result.dirtyNodeId)
} }
if (config.onCommitted()) { if (configRef.current.onCommitted()) {
const nodes = useScene.getState().nodes const nodes = useScene.getState().nodes
const enterResult = wallStrategy.enter(getContext(), event, resolveLevelId, nodes) const enterResult = wallStrategy.enter(getContext(), event, resolveLevelId, nodes)
if (enterResult) { if (enterResult) {
@@ -289,7 +295,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
applyTransition(result) applyTransition(result)
const draft = draftNode.current const draft = draftNode.current
if (draft) { if (draft) {
useScene.getState().updateNode(draft.id, { parentId: result.nodeUpdate.parentId as string }) useScene
.getState()
.updateNode(draft.id, { parentId: result.nodeUpdate.parentId as string })
} }
if (oldWallId) { if (oldWallId) {
useScene.getState().dirtyNodes.add(oldWallId as AnyNodeId) useScene.getState().dirtyNodes.add(oldWallId as AnyNodeId)
@@ -360,7 +368,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
event.stopPropagation() event.stopPropagation()
draftNode.commit(result.nodeUpdate) draftNode.commit(result.nodeUpdate)
if (config.onCommitted()) { if (configRef.current.onCommitted()) {
const nodes = useScene.getState().nodes const nodes = useScene.getState().nodes
const enterResult = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes) const enterResult = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes)
if (enterResult) { if (enterResult) {
@@ -384,7 +392,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
applyTransition(result) applyTransition(result)
const draft = draftNode.current const draft = draftNode.current
if (draft) { if (draft) {
useScene.getState().updateNode(draft.id, { parentId: result.nodeUpdate.parentId as string }) useScene
.getState()
.updateNode(draft.id, { parentId: result.nodeUpdate.parentId as string })
} }
if (oldCeilingId) { if (oldCeilingId) {
useScene.getState().dirtyNodes.add(oldCeilingId as AnyNodeId) useScene.getState().dirtyNodes.add(oldCeilingId as AnyNodeId)
@@ -404,9 +414,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const ROTATION_STEP = Math.PI / 2 const ROTATION_STEP = Math.PI / 2
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
// Escape / right-click → cancel // Escape / right-click → cancel
if (event.key === 'Escape' && config.onCancel) { if (event.key === 'Escape' && configRef.current.onCancel) {
event.preventDefault() event.preventDefault()
config.onCancel() configRef.current.onCancel()
return return
} }
@@ -434,9 +444,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
// ---- Right-click cancel ---- // ---- Right-click cancel ----
const onContextMenu = (event: MouseEvent) => { const onContextMenu = (event: MouseEvent) => {
if (config.onCancel) { if (configRef.current.onCancel) {
event.preventDefault() event.preventDefault()
config.onCancel() configRef.current.onCancel()
} }
} }
window.addEventListener('contextmenu', onContextMenu) window.addEventListener('contextmenu', onContextMenu)
@@ -501,17 +511,16 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
// Adjust Y for slab elevation (floor items on top of slabs) // Adjust Y for slab elevation (floor items on top of slabs)
if (!asset.attachTo) { if (!asset.attachTo) {
const levelId = useViewer.getState().selection.levelId const nodes = useScene.getState().nodes
if (levelId) { const levelId = resolveLevelId(draftNode.current, nodes)
const slabElevation = spatialGridManager.getSlabElevationForItem( const slabElevation = spatialGridManager.getSlabElevationForItem(
levelId, levelId,
[gridPosition.current.x, gridPosition.current.y, gridPosition.current.z], [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
asset.dimensions ?? DEFAULT_DIMENSIONS, asset.dimensions ?? DEFAULT_DIMENSIONS,
draftNode.current.rotation, draftNode.current.rotation,
) )
mesh.position.y = slabElevation mesh.position.y = slabElevation
cursorRef.current.position.y = slabElevation cursorRef.current.position.y = slabElevation
}
} }
} }
}) })
+1 -1
View File
@@ -27,7 +27,7 @@ export {
resolveLevelId, resolveLevelId,
} from './hooks/spatial-grid/spatial-grid-sync' } from './hooks/spatial-grid/spatial-grid-sync'
export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query' export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query'
export { pointInPolygon } from './hooks/spatial-grid/spatial-grid-manager' export { pointInPolygon, spatialGridManager } from './hooks/spatial-grid/spatial-grid-manager'
// Schema // Schema
export * from './schema' export * from './schema'
export { default as useScene } from './store/use-scene' export { default as useScene } from './store/use-scene'