diff --git a/apps/editor/components/tools/window/move-window-tool.tsx b/apps/editor/components/tools/window/move-window-tool.tsx index be2d343f..63bccc95 100644 --- a/apps/editor/components/tools/window/move-window-tool.tsx +++ b/apps/editor/components/tools/window/move-window-tool.tsx @@ -2,6 +2,7 @@ import { type AnyNodeId, emitter, sceneRegistry, + spatialGridManager, useScene, type WallEvent, WindowNode, @@ -78,10 +79,17 @@ export const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWin if (wallId) useScene.getState().dirtyNodes.add(wallId as AnyNodeId) } + const getLevelId = () => useViewer.getState().selection.levelId const getLevelYOffset = () => { - const id = useViewer.getState().selection.levelId + const id = getLevelId() return id ? (sceneRegistry.nodes.get(id as AnyNodeId)?.position.y ?? 0) : 0 } + const getSlabElevation = (wallEvent: WallEvent) => + spatialGridManager.getSlabElevationForWall( + wallEvent.node.parentId ?? '', + wallEvent.node.start, + wallEvent.node.end, + ) const hideCursor = () => { if (cursorGroupRef.current) cursorGroupRef.current.visible = false @@ -102,6 +110,8 @@ export const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWin const onWallEnter = (event: WallEvent) => { if (!isValidWallSideFace(event.normal)) return + // Only interact with walls on the current level + if (event.node.parentId !== getLevelId()) return const side = getSideFromNormal(event.normal) const itemRotation = calculateItemRotation(event.normal) @@ -134,12 +144,18 @@ export const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWin movingWindowNode.id, ) - updateCursor(wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset()), cursorRotation, valid) + updateCursor( + wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset(), getSlabElevation(event)), + cursorRotation, + valid, + ) event.stopPropagation() } const onWallMove = (event: WallEvent) => { if (!isValidWallSideFace(event.normal)) return + // Only interact with walls on the current level + if (event.node.parentId !== getLevelId()) return const side = getSideFromNormal(event.normal) const itemRotation = calculateItemRotation(event.normal) @@ -172,12 +188,18 @@ export const MoveWindowTool: React.FC<{ node: WindowNode }> = ({ node: movingWin movingWindowNode.id, ) - updateCursor(wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset()), cursorRotation, valid) + updateCursor( + wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset(), getSlabElevation(event)), + cursorRotation, + valid, + ) event.stopPropagation() } const onWallClick = (event: WallEvent) => { if (!isValidWallSideFace(event.normal)) return + // Only interact with walls on the current level + if (event.node.parentId !== getLevelId()) return const side = getSideFromNormal(event.normal) const itemRotation = calculateItemRotation(event.normal) diff --git a/apps/editor/components/tools/window/window-math.ts b/apps/editor/components/tools/window/window-math.ts index 5419dfd4..25fa6b5e 100644 --- a/apps/editor/components/tools/window/window-math.ts +++ b/apps/editor/components/tools/window/window-math.ts @@ -1,16 +1,18 @@ import { type AnyNodeId, type ItemNode, useScene, type WallNode, type WindowNode } from '@pascal-app/core' /** - * Converts wall-local (X along wall, Y = height above level floor) to world XYZ. + * Converts wall-local (X along wall, Y = height above wall base) to world XYZ. * Wall XZ uses level-local coordinates (levels only offset in Y, not XZ). - * Pass levelYOffset (the level group's current world Y) so the cursor lands at the - * correct world height when the cursor group is at the scene root. + * Pass levelYOffset (the level group's current world Y) and slabElevation (the + * wall mesh's Y within the level group) so the cursor lands at the correct world + * height — matching how WallSystem positions the wall mesh at slabElevation. */ export function wallLocalToWorld( wallNode: WallNode, localX: number, localY: number, levelYOffset = 0, + slabElevation = 0, ): [number, number, number] { const wallAngle = Math.atan2( wallNode.end[1] - wallNode.start[1], @@ -18,7 +20,7 @@ export function wallLocalToWorld( ) return [ wallNode.start[0] + localX * Math.cos(wallAngle), - localY + levelYOffset, + slabElevation + localY + levelYOffset, wallNode.start[1] + localX * Math.sin(wallAngle), ] } diff --git a/apps/editor/components/tools/window/window-tool.tsx b/apps/editor/components/tools/window/window-tool.tsx index 3d275456..419deea8 100644 --- a/apps/editor/components/tools/window/window-tool.tsx +++ b/apps/editor/components/tools/window/window-tool.tsx @@ -2,6 +2,7 @@ import { type AnyNodeId, emitter, sceneRegistry, + spatialGridManager, useScene, type WallEvent, WindowNode, @@ -44,6 +45,12 @@ export const WindowTool: React.FC = () => { const id = getLevelId() return id ? (sceneRegistry.nodes.get(id as AnyNodeId)?.position.y ?? 0) : 0 } + const getSlabElevation = (wallEvent: WallEvent) => + spatialGridManager.getSlabElevationForWall( + wallEvent.node.parentId ?? '', + wallEvent.node.start, + wallEvent.node.end, + ) const markWallDirty = (wallId: string) => { useScene.getState().dirtyNodes.add(wallId as AnyNodeId) @@ -79,6 +86,8 @@ export const WindowTool: React.FC = () => { if (!isValidWallSideFace(event.normal)) return const levelId = getLevelId() if (!levelId) return + // Only interact with walls on the current level + if (event.node.parentId !== levelId) return destroyDraft() @@ -108,12 +117,18 @@ export const WindowTool: React.FC = () => { const valid = !hasWallChildOverlap(event.node.id, clampedX, clampedY, width, height, node.id) - updateCursor(wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset()), cursorRotation, valid) + updateCursor( + wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset(), getSlabElevation(event)), + cursorRotation, + valid, + ) event.stopPropagation() } const onWallMove = (event: WallEvent) => { if (!isValidWallSideFace(event.normal)) return + // Only interact with walls on the current level + if (event.node.parentId !== getLevelId()) return const side = getSideFromNormal(event.normal) const itemRotation = calculateItemRotation(event.normal) @@ -142,13 +157,19 @@ export const WindowTool: React.FC = () => { draftRef.current?.id, ) - updateCursor(wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset()), cursorRotation, valid) + updateCursor( + wallLocalToWorld(event.node, clampedX, clampedY, getLevelYOffset(), getSlabElevation(event)), + cursorRotation, + valid, + ) event.stopPropagation() } const onWallClick = (event: WallEvent) => { if (!draftRef.current) return if (!isValidWallSideFace(event.normal)) return + // Only interact with walls on the current level + if (event.node.parentId !== getLevelId()) return const side = getSideFromNormal(event.normal) const itemRotation = calculateItemRotation(event.normal)