From f3456aed422a392a8eb85d7b24b8f32d0802f6d3 Mon Sep 17 00:00:00 2001 From: wass08 Date: Mon, 19 Jan 2026 09:32:46 +0900 Subject: [PATCH] can place on wall --- apps/editor/components/editor.tsx | 1 + .../components/tools/item/item-tool.tsx | 18 ++++++------ .../spatial-grid/spatial-grid-manager.ts | 28 +++++++++++-------- .../hooks/spatial-grid/spatial-grid-sync.ts | 5 ++-- .../hooks/spatial-grid/use-spatial-query.ts | 2 +- .../hooks/spatial-grid/wall-spatial-grid.ts | 1 - packages/core/src/store/use-scene.ts | 5 +++- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/apps/editor/components/editor.tsx b/apps/editor/components/editor.tsx index aa1ad1fc..3083ee6d 100644 --- a/apps/editor/components/editor.tsx +++ b/apps/editor/components/editor.tsx @@ -34,6 +34,7 @@ import { ToolManager } from "./tools/tool-manager"; const selectedObjects: Object3D[] = []; initSpatialGridSync(); +useScene.getState().loadScene(); export default function Editor() { return ( diff --git a/apps/editor/components/tools/item/item-tool.tsx b/apps/editor/components/tools/item/item-tool.tsx index d6685924..016703e4 100644 --- a/apps/editor/components/tools/item/item-tool.tsx +++ b/apps/editor/components/tools/item/item-tool.tsx @@ -24,30 +24,31 @@ export const ItemTool: React.FC = () => { const { canPlaceOnFloor, canPlaceOnWall } = useSpatialQuery(); useEffect(() => { - console.log("item-tool-reloaded"); if (!selectedItem) { return; } let isOnWall = false; let wallLocked = false; + let currentWallId: string | null = null; const checkCanPlace = () => { const currentLevelId = useViewer.getState().currentLevelId; if (currentLevelId && draftItem.current) { let placeable = true; if (draftItem.current.asset.attachTo) { - if (!isOnWall) { + if (!isOnWall || !currentWallId) { placeable = false; } else { - placeable = canPlaceOnWall( + const result = canPlaceOnWall( currentLevelId, - draftItem.current.parentId as WallNode["id"], + currentWallId as WallNode["id"], gridPosition.current.x, gridPosition.current.y, draftItem.current.asset.dimensions, [draftItem.current.id], - ).valid; + ); + placeable = result.valid; } } else { placeable = canPlaceOnFloor( @@ -116,7 +117,6 @@ export const ItemTool: React.FC = () => { if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; - console.log("ongridclick action"); useScene.temporal.getState().resume(); useScene.getState().updateNode(draftItem.current.id, { position: [gridPosition.current.x, 0, gridPosition.current.z], @@ -128,13 +128,13 @@ export const ItemTool: React.FC = () => { }; const onWallEnter = (event: WallEvent) => { - console.log("Wall enter", event); event.stopPropagation(); if ( draftItem.current?.asset.attachTo === "wall" || draftItem.current?.asset.attachTo === "wall-side" ) { isOnWall = true; + currentWallId = event.node.id; gridPosition.current.set( Math.round(event.localPosition[0] * 2) / 2, Math.round(event.localPosition[1] * 2) / 2, @@ -154,8 +154,8 @@ export const ItemTool: React.FC = () => { }; const onWallLeave = (event: WallEvent) => { - console.log("Wall leave", event); isOnWall = false; + currentWallId = null; event.stopPropagation(); if (!draftItem.current) return; const currentLevelId = useViewer.getState().currentLevelId; @@ -180,7 +180,6 @@ export const ItemTool: React.FC = () => { if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; wallLocked = true; - console.log("onwallclick action"); useScene.temporal.getState().resume(); useScene.getState().updateNode(draftItem.current.id, { position: [ @@ -200,7 +199,6 @@ export const ItemTool: React.FC = () => { const onWallMove = (event: WallEvent) => { event.stopPropagation(); - console.log("Wall move", event); wallLocked = false; if (!draftItem.current) return; gridPosition.current.set( 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 17147026..f37cb4e4 100644 --- a/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts +++ b/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts @@ -45,18 +45,21 @@ export class SpatialGridManager { item.asset.attachTo === "wall" || item.asset.attachTo === "wall-side" ) { - // Wall-attached item - if (item.wallId && item.wallT !== undefined) { - const wallLength = this.getWallLength(item.wallId); + // Wall-attached item - use parentId as the wall ID + const wallId = item.parentId; + if (wallId && this.walls.has(wallId)) { + const wallLength = this.getWallLength(wallId); if (wallLength > 0) { const [width, height] = item.asset.dimensions; const halfW = width / wallLength / 2; const halfH = height / 2; + // Calculate t from local X position (position[0] is distance along wall) + const t = item.position[0] / wallLength; this.getWallGrid(levelId).insert({ itemId: item.id, - wallId: item.wallId, - tStart: item.wallT - halfW, - tEnd: item.wallT + halfW, + wallId: wallId, + tStart: t - halfW, + tEnd: t + halfW, yStart: item.position[1] - halfH, yEnd: item.position[1] + halfH, }); @@ -86,17 +89,20 @@ export class SpatialGridManager { ) { // Remove old placement and re-insert this.getWallGrid(levelId).removeByItemId(item.id); - if (item.wallId && item.wallT !== undefined) { - const wallLength = this.getWallLength(item.wallId); + const wallId = item.parentId; + if (wallId && this.walls.has(wallId)) { + const wallLength = this.getWallLength(wallId); if (wallLength > 0) { const [width, height] = item.asset.dimensions; const halfW = width / wallLength / 2; const halfH = height / 2; + // Calculate t from local X position (position[0] is distance along wall) + const t = item.position[0] / wallLength; this.getWallGrid(levelId).insert({ itemId: item.id, - wallId: item.wallId, - tStart: item.wallT - halfW, - tEnd: item.wallT + halfW, + wallId: wallId, + tStart: t - halfW, + tEnd: t + halfW, yStart: item.position[1] - halfH, yEnd: item.position[1] + halfH, }); diff --git a/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts b/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts index ab186b42..d8c96485 100644 --- a/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts +++ b/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts @@ -45,13 +45,14 @@ export function initSpatialGridSync() { } } - // Detect updated nodes (only items with position/rotation changes) + // Detect updated nodes (items with position/rotation/parentId changes) for (const [id, node] of Object.entries(state.nodes)) { const prev = prevState.nodes[id as AnyNode["id"]]; if (prev && node.type === "item" && prev.type === "item") { if ( !arraysEqual(node.position, prev.position) || - !arraysEqual(node.rotation, prev.rotation) + !arraysEqual(node.rotation, prev.rotation) || + node.parentId !== prev.parentId ) { const levelId = resolveLevelId(node, state.nodes); spatialGridManager.handleNodeUpdated(node, levelId); 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 7380049f..fe1e1ade 100644 --- a/packages/core/src/hooks/spatial-grid/use-spatial-query.ts +++ b/packages/core/src/hooks/spatial-grid/use-spatial-query.ts @@ -1,6 +1,6 @@ import { useCallback } from "react"; -import { spatialGridManager } from "./spatial-grid-manager"; import { LevelNode, WallNode } from "../../schema"; +import { spatialGridManager } from "./spatial-grid-manager"; export function useSpatialQuery() { const canPlaceOnFloor = useCallback( diff --git a/packages/core/src/hooks/spatial-grid/wall-spatial-grid.ts b/packages/core/src/hooks/spatial-grid/wall-spatial-grid.ts index c89d4e6b..677da88a 100644 --- a/packages/core/src/hooks/spatial-grid/wall-spatial-grid.ts +++ b/packages/core/src/hooks/spatial-grid/wall-spatial-grid.ts @@ -64,7 +64,6 @@ export class WallSpatialGrid { this.itemToWall.delete(itemId); } - // The missing method! removeByItemId(itemId: string) { const wallId = this.itemToWall.get(itemId); if (wallId) { diff --git a/packages/core/src/store/use-scene.ts b/packages/core/src/store/use-scene.ts index 9f165c22..ce7e1a44 100644 --- a/packages/core/src/store/use-scene.ts +++ b/packages/core/src/store/use-scene.ts @@ -69,24 +69,28 @@ const useScene = create()( start: [0, 0], end: [5, 0], children: [], + parentId: level0.id, }); const wall1 = WallNode.parse({ start: [0, 0], end: [0, 5], children: [], + parentId: level0.id, }); const wall2 = WallNode.parse({ start: [5, 5], end: [0, 5], children: [], + parentId: level0.id, }); const wall3 = WallNode.parse({ start: [5, 5], end: [5, 0], children: [], + parentId: level0.id, }); const window1 = ItemNode.parse({ @@ -165,7 +169,6 @@ const useScene = create()( ), ); -useScene.getState().loadScene(); export default useScene; // Subscribe to the temporal store (Undo/Redo events)