fix elevation issue
This commit is contained in:
@@ -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,8 +511,8 @@ 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],
|
||||||
@@ -513,7 +523,6 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
|||||||
cursorRef.current.position.y = slabElevation
|
cursorRef.current.position.y = slabElevation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user