check wall + clean attach/detach

This commit is contained in:
wass08
2026-01-19 08:29:43 +09:00
parent a217d0d3a6
commit 69148705c6
3 changed files with 80 additions and 18 deletions
+40 -10
View File
@@ -21,7 +21,7 @@ export const ItemTool: React.FC = () => {
const draftItem = useRef<ItemNode | null>(null); const draftItem = useRef<ItemNode | null>(null);
const gridPosition = useRef(new Vector3(0, 0, 0)); const gridPosition = useRef(new Vector3(0, 0, 0));
const selectedItem = useEditor((state) => state.selectedItem); const selectedItem = useEditor((state) => state.selectedItem);
const { canPlace } = useSpatialQuery(); const { canPlaceOnFloor, canPlaceOnWall } = useSpatialQuery();
useEffect(() => { useEffect(() => {
console.log("item-tool-reloaded"); console.log("item-tool-reloaded");
@@ -35,14 +35,30 @@ export const ItemTool: React.FC = () => {
const checkCanPlace = () => { const checkCanPlace = () => {
const currentLevelId = useViewer.getState().currentLevelId; const currentLevelId = useViewer.getState().currentLevelId;
if (currentLevelId && draftItem.current) { if (currentLevelId && draftItem.current) {
const placeable = canPlace( let placeable = true;
currentLevelId, if (draftItem.current.asset.attachTo) {
[gridPosition.current.x, 0, gridPosition.current.z], if (!isOnWall) {
selectedItem.dimensions, placeable = false;
[0, 0, 0], } else {
[draftItem.current.id], placeable = canPlaceOnWall(
); currentLevelId,
if (placeable.valid) { 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"); cursorRef.current.material.color.set("green");
return true; return true;
} else { } else {
@@ -133,6 +149,7 @@ export const ItemTool: React.FC = () => {
], ],
parentId: event.node.id, parentId: event.node.id,
}); });
checkCanPlace();
} }
}; };
@@ -140,6 +157,18 @@ export const ItemTool: React.FC = () => {
console.log("Wall leave", event); console.log("Wall leave", event);
isOnWall = false; isOnWall = false;
event.stopPropagation(); 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) => { const onWallClick = (event: WallEvent) => {
@@ -147,7 +176,7 @@ export const ItemTool: React.FC = () => {
if (wallLocked) { if (wallLocked) {
return; return;
} }
const { currentLevelId } = useViewer.getState(); const currentLevelId = useViewer.getState().currentLevelId;
if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; if (!currentLevelId || !draftItem.current || !checkCanPlace()) return;
wallLocked = true; wallLocked = true;
@@ -166,6 +195,7 @@ export const ItemTool: React.FC = () => {
useScene.temporal.getState().pause(); useScene.temporal.getState().pause();
createDraftItem(); createDraftItem();
checkCanPlace();
}; };
const onWallMove = (event: WallEvent) => { const onWallMove = (event: WallEvent) => {
@@ -138,25 +138,36 @@ export class SpatialGridManager {
return grid.canPlace(position, dimensions, rotation, ignoreIds); 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( canPlaceOnWall(
levelId: string, levelId: string,
wallId: string, wallId: string,
tCenter: number, localX: number,
itemWidth: number, localY: number,
yCenter: number, dimensions: [number, number, number],
itemHeight: number,
ignoreIds?: string[], ignoreIds?: string[],
) { ) {
const wallLength = this.getWallLength(wallId); const wallLength = this.getWallLength(wallId);
if (wallLength === 0) { if (wallLength === 0) {
return { valid: false, conflictIds: [] }; 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( return this.getWallGrid(levelId).canPlaceOnWall(
wallId, wallId,
wallLength, wallLength,
tCenter, tCenter,
itemWidth, itemWidth,
yCenter, localY,
itemHeight, itemHeight,
ignoreIds, ignoreIds,
); );
@@ -1,9 +1,9 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { spatialGridManager } from "./spatial-grid-manager"; import { spatialGridManager } from "./spatial-grid-manager";
import { LevelNode } from "../../schema"; import { LevelNode, WallNode } from "../../schema";
export function useSpatialQuery() { export function useSpatialQuery() {
const canPlace = useCallback( const canPlaceOnFloor = useCallback(
( (
levelId: LevelNode["id"], levelId: LevelNode["id"],
position: [number, number, number], 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 };
} }