From 69148705c6870687e74da10db6618d9a967f73e7 Mon Sep 17 00:00:00 2001 From: wass08 Date: Mon, 19 Jan 2026 08:29:43 +0900 Subject: [PATCH] check wall + clean attach/detach --- .../components/tools/item/item-tool.tsx | 50 +++++++++++++++---- .../spatial-grid/spatial-grid-manager.ts | 21 ++++++-- .../hooks/spatial-grid/use-spatial-query.ts | 27 ++++++++-- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/apps/editor/components/tools/item/item-tool.tsx b/apps/editor/components/tools/item/item-tool.tsx index 5a633618..d6685924 100644 --- a/apps/editor/components/tools/item/item-tool.tsx +++ b/apps/editor/components/tools/item/item-tool.tsx @@ -21,7 +21,7 @@ export const ItemTool: React.FC = () => { const draftItem = useRef(null); const gridPosition = useRef(new Vector3(0, 0, 0)); const selectedItem = useEditor((state) => state.selectedItem); - const { canPlace } = useSpatialQuery(); + const { canPlaceOnFloor, canPlaceOnWall } = useSpatialQuery(); useEffect(() => { console.log("item-tool-reloaded"); @@ -35,14 +35,30 @@ export const ItemTool: React.FC = () => { const checkCanPlace = () => { const currentLevelId = useViewer.getState().currentLevelId; if (currentLevelId && draftItem.current) { - const placeable = canPlace( - currentLevelId, - [gridPosition.current.x, 0, gridPosition.current.z], - selectedItem.dimensions, - [0, 0, 0], - [draftItem.current.id], - ); - if (placeable.valid) { + let placeable = true; + if (draftItem.current.asset.attachTo) { + if (!isOnWall) { + placeable = false; + } else { + placeable = canPlaceOnWall( + currentLevelId, + draftItem.current.parentId as WallNode["id"], + gridPosition.current.x, + gridPosition.current.y, + draftItem.current.asset.dimensions, + [draftItem.current.id], + ).valid; + } + } else { + placeable = canPlaceOnFloor( + currentLevelId, + [gridPosition.current.x, 0, gridPosition.current.z], + draftItem.current.asset.dimensions, + [0, 0, 0], + [draftItem.current.id], + ).valid; + } + if (placeable) { cursorRef.current.material.color.set("green"); return true; } else { @@ -133,6 +149,7 @@ export const ItemTool: React.FC = () => { ], parentId: event.node.id, }); + checkCanPlace(); } }; @@ -140,6 +157,18 @@ export const ItemTool: React.FC = () => { console.log("Wall leave", event); isOnWall = false; event.stopPropagation(); + if (!draftItem.current) return; + const currentLevelId = useViewer.getState().currentLevelId; + draftItem.current.parentId = currentLevelId; + useScene.getState().updateNode(draftItem.current.id, { + position: [ + gridPosition.current.x, + gridPosition.current.y, + gridPosition.current.z, + ], + parentId: currentLevelId, + }); + checkCanPlace(); }; const onWallClick = (event: WallEvent) => { @@ -147,7 +176,7 @@ export const ItemTool: React.FC = () => { if (wallLocked) { return; } - const { currentLevelId } = useViewer.getState(); + const currentLevelId = useViewer.getState().currentLevelId; if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; wallLocked = true; @@ -166,6 +195,7 @@ export const ItemTool: React.FC = () => { useScene.temporal.getState().pause(); createDraftItem(); + checkCanPlace(); }; const onWallMove = (event: WallEvent) => { diff --git a/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts b/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts index 9dbed911..17147026 100644 --- a/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts +++ b/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts @@ -138,25 +138,36 @@ export class SpatialGridManager { return grid.canPlace(position, dimensions, rotation, ignoreIds); } + /** + * Check if an item can be placed on a wall + * @param levelId - the level containing the wall + * @param wallId - the wall to check + * @param localX - X position in wall-local space (distance from wall start) + * @param localY - Y position (height from floor) + * @param dimensions - item dimensions [width, height, depth] + * @param ignoreIds - item IDs to ignore in collision check + */ canPlaceOnWall( levelId: string, wallId: string, - tCenter: number, - itemWidth: number, - yCenter: number, - itemHeight: number, + localX: number, + localY: number, + dimensions: [number, number, number], ignoreIds?: string[], ) { const wallLength = this.getWallLength(wallId); if (wallLength === 0) { return { valid: false, conflictIds: [] }; } + // Convert local X position to parametric t (0-1) + const tCenter = localX / wallLength; + const [itemWidth, itemHeight] = dimensions; return this.getWallGrid(levelId).canPlaceOnWall( wallId, wallLength, tCenter, itemWidth, - yCenter, + localY, itemHeight, ignoreIds, ); diff --git a/packages/core/src/hooks/spatial-grid/use-spatial-query.ts b/packages/core/src/hooks/spatial-grid/use-spatial-query.ts index 62577408..7380049f 100644 --- a/packages/core/src/hooks/spatial-grid/use-spatial-query.ts +++ b/packages/core/src/hooks/spatial-grid/use-spatial-query.ts @@ -1,9 +1,9 @@ import { useCallback } from "react"; import { spatialGridManager } from "./spatial-grid-manager"; -import { LevelNode } from "../../schema"; +import { LevelNode, WallNode } from "../../schema"; export function useSpatialQuery() { - const canPlace = useCallback( + const canPlaceOnFloor = useCallback( ( levelId: LevelNode["id"], position: [number, number, number], @@ -22,5 +22,26 @@ export function useSpatialQuery() { [], ); - return { canPlace }; + const canPlaceOnWall = useCallback( + ( + levelId: LevelNode["id"], + wallId: WallNode["id"], + localX: number, + localY: number, + dimensions: [number, number, number], + ignoreIds?: string[], + ) => { + return spatialGridManager.canPlaceOnWall( + levelId, + wallId, + localX, + localY, + dimensions, + ignoreIds, + ); + }, + [], + ); + + return { canPlaceOnFloor, canPlaceOnWall }; }