wall item checks

This commit is contained in:
wass08
2026-01-20 07:56:40 +09:00
parent a35b982b4f
commit f59e12193d
5 changed files with 60 additions and 31 deletions
+2 -2
View File
@@ -267,13 +267,13 @@ const DraftSelector = () => {
const itemSelectedAt = useRef<number>(0); const itemSelectedAt = useRef<number>(0);
useEffect(() => { useEffect(() => {
emitter.on("building:enter", (event) => { emitter.on("building:enter", (event) => {
console.log("Entered building:", event.node.id); // console.log("Entered building:", event.node.id);
const itemMesh = sceneRegistry.nodes.get(event.node.id); const itemMesh = sceneRegistry.nodes.get(event.node.id);
selectedObjects.length = 0; selectedObjects.length = 0;
selectedObjects.push(itemMesh); selectedObjects.push(itemMesh);
}); });
emitter.on("building:leave", (event) => { emitter.on("building:leave", (event) => {
console.log("Leaving building:", event.node.id); // console.log("Leaving building:", event.node.id);
selectedObjects.length = 0; selectedObjects.length = 0;
}); });
+34 -18
View File
@@ -14,7 +14,7 @@ import { useViewer } from "@pascal-app/viewer";
import { useFrame } from "@react-three/fiber"; import { useFrame } from "@react-three/fiber";
import { is } from "@react-three/fiber/dist/declarations/src/core/utils"; import { is } from "@react-three/fiber/dist/declarations/src/core/utils";
import { use, useEffect, useRef } from "react"; import { use, useEffect, useRef } from "react";
import { BoxGeometry, Line, Mesh, Vector3 } from "three"; import { BoxGeometry, Line, Mesh, MeshStandardMaterial, Vector3 } from "three";
import { randInt } from "three/src/math/MathUtils.js"; import { randInt } from "three/src/math/MathUtils.js";
export const ItemTool: React.FC = () => { export const ItemTool: React.FC = () => {
@@ -23,13 +23,13 @@ export const ItemTool: React.FC = () => {
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 { canPlaceOnFloor, canPlaceOnWall } = useSpatialQuery(); const { canPlaceOnFloor, canPlaceOnWall } = useSpatialQuery();
const isOnWall = useRef(false);
useEffect(() => { useEffect(() => {
if (!selectedItem) { if (!selectedItem) {
return; return;
} }
let isOnWall = false;
let currentWallId: string | null = null; let currentWallId: string | null = null;
const checkCanPlace = () => { const checkCanPlace = () => {
@@ -37,7 +37,7 @@ export const ItemTool: React.FC = () => {
if (currentLevelId && draftItem.current) { if (currentLevelId && draftItem.current) {
let placeable = true; let placeable = true;
if (draftItem.current.asset.attachTo) { if (draftItem.current.asset.attachTo) {
if (!isOnWall || !currentWallId) { if (!isOnWall.current || !currentWallId) {
placeable = false; placeable = false;
} else { } else {
const result = canPlaceOnWall( const result = canPlaceOnWall(
@@ -60,10 +60,12 @@ export const ItemTool: React.FC = () => {
).valid; ).valid;
} }
if (placeable) { if (placeable) {
cursorRef.current.material.color.set("green"); (cursorRef.current.material as MeshStandardMaterial).color.set(
"green",
);
return true; return true;
} else { } else {
cursorRef.current.material.color.set("red"); (cursorRef.current.material as MeshStandardMaterial).color.set("red");
return false; return false;
} }
} }
@@ -91,7 +93,7 @@ export const ItemTool: React.FC = () => {
const onGridMove = (event: GridEvent) => { const onGridMove = (event: GridEvent) => {
if (!cursorRef.current) return; if (!cursorRef.current) return;
if (isOnWall) return; if (isOnWall.current) return;
gridPosition.current.set( gridPosition.current.set(
Math.round(event.position[0] * 2) / 2, Math.round(event.position[0] * 2) / 2,
@@ -103,18 +105,18 @@ export const ItemTool: React.FC = () => {
0, 0,
gridPosition.current.z, gridPosition.current.z,
); );
checkCanPlace();
if (draftItem.current) { if (draftItem.current) {
draftItem.current.position = [ draftItem.current.position = [
gridPosition.current.x, gridPosition.current.x,
0, 0,
gridPosition.current.z, gridPosition.current.z,
]; ];
checkCanPlace();
} }
}; };
const onGridClick = (event: GridEvent) => { const onGridClick = (event: GridEvent) => {
const { currentLevelId } = useViewer.getState(); const { currentLevelId } = useViewer.getState();
if (isOnWall) return; if (isOnWall.current) return;
if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; if (!currentLevelId || !draftItem.current || !checkCanPlace()) return;
@@ -134,7 +136,7 @@ export const ItemTool: React.FC = () => {
draftItem.current?.asset.attachTo === "wall-side" draftItem.current?.asset.attachTo === "wall-side"
) { ) {
event.stopPropagation(); event.stopPropagation();
isOnWall = true; isOnWall.current = true;
currentWallId = event.node.id; currentWallId = event.node.id;
gridPosition.current.set( gridPosition.current.set(
Math.round(event.localPosition[0] * 2) / 2, Math.round(event.localPosition[0] * 2) / 2,
@@ -155,7 +157,7 @@ export const ItemTool: React.FC = () => {
}; };
const onWallLeave = (event: WallEvent) => { const onWallLeave = (event: WallEvent) => {
isOnWall = false; isOnWall.current = false;
currentWallId = null; currentWallId = null;
event.stopPropagation(); event.stopPropagation();
if (!draftItem.current) return; if (!draftItem.current) return;
@@ -174,7 +176,7 @@ export const ItemTool: React.FC = () => {
const onWallClick = (event: WallEvent) => { const onWallClick = (event: WallEvent) => {
event.stopPropagation(); event.stopPropagation();
if (!isOnWall) return; if (!isOnWall.current) return;
const currentLevelId = useViewer.getState().currentLevelId; const currentLevelId = useViewer.getState().currentLevelId;
if (!currentLevelId || !draftItem.current || !checkCanPlace()) return; if (!currentLevelId || !draftItem.current || !checkCanPlace()) return;
@@ -197,7 +199,7 @@ export const ItemTool: React.FC = () => {
}; };
const onWallMove = (event: WallEvent) => { const onWallMove = (event: WallEvent) => {
if (isOnWall === false) return; if (isOnWall.current === false) return;
event.stopPropagation(); event.stopPropagation();
if (!draftItem.current) return; if (!draftItem.current) return;
gridPosition.current.set( gridPosition.current.set(
@@ -206,18 +208,32 @@ export const ItemTool: React.FC = () => {
Math.round(event.localPosition[2] * 2) / 2, Math.round(event.localPosition[2] * 2) / 2,
); );
cursorRef.current.position.set( cursorRef.current.position.set(
event.position[0], Math.round(event.position[0] * 2) / 2,
event.position[1], Math.round(event.position[1] * 2) / 2,
event.position[2], Math.round(event.position[2] * 2) / 2,
); );
if (draftItem.current) { const {
node: { start, end },
} = event;
const dx = end[0] - start[0];
const dz = end[1] - start[1];
const { normal } = event;
const wallAngle = Math.atan2(dx, dz);
cursorRef.current.rotation.y = wallAngle + Math.PI / 2;
const canPlace = checkCanPlace();
if (draftItem.current && canPlace) {
draftItem.current.position = [ draftItem.current.position = [
gridPosition.current.x, gridPosition.current.x,
gridPosition.current.y, gridPosition.current.y,
gridPosition.current.z, gridPosition.current.z,
]; ];
const draftItemMesh = sceneRegistry.nodes.get(draftItem.current.id);
if (draftItemMesh) {
draftItemMesh.position.copy(gridPosition.current);
}
useScene.getState().dirtyNodes.add(event.node.id); useScene.getState().dirtyNodes.add(event.node.id);
checkCanPlace();
} }
}; };
@@ -254,7 +270,7 @@ export const ItemTool: React.FC = () => {
}, [selectedItem]); }, [selectedItem]);
useFrame((_, delta) => { useFrame((_, delta) => {
if (draftItem.current) { if (draftItem.current && !isOnWall.current) {
const draftItemMesh = sceneRegistry.nodes.get(draftItem.current.id); const draftItemMesh = sceneRegistry.nodes.get(draftItem.current.id);
if (draftItemMesh) { if (draftItemMesh) {
draftItemMesh.position.lerp(gridPosition.current, delta * 20); draftItemMesh.position.lerp(gridPosition.current, delta * 20);
@@ -325,7 +325,7 @@ export const CATALOG_ITEMS: AssetInput[] = [
name: "Window", name: "Window",
thumbnail: "/items/window-small/thumbnail.webp", thumbnail: "/items/window-small/thumbnail.webp",
src: "/items/window-small/model.glb", src: "/items/window-small/model.glb",
offset: [0, 0, 0], offset: [0, 0.15, 0],
scale: [1, 1, 1], scale: [1, 1, 1],
rotation: [0, Math.PI, 0], rotation: [0, Math.PI, 0],
dimensions: [1, 1.5, 0.1], dimensions: [1, 1.5, 0.1],
@@ -34,6 +34,11 @@ export class SpatialGridManager {
return Math.sqrt(dx * dx + dy * dy); return Math.sqrt(dx * dx + dy * dy);
} }
private getWallHeight(wallId: string): number {
const wall = this.walls.get(wallId);
return wall?.height ?? 2.5; // Default wall height
}
// Called when nodes change // Called when nodes change
handleNodeCreated(node: AnyNode, levelId: string) { handleNodeCreated(node: AnyNode, levelId: string) {
if (node.type === "wall") { if (node.type === "wall") {
@@ -52,16 +57,16 @@ export class SpatialGridManager {
if (wallLength > 0) { if (wallLength > 0) {
const [width, height] = item.asset.dimensions; const [width, height] = item.asset.dimensions;
const halfW = width / wallLength / 2; const halfW = width / wallLength / 2;
const halfH = height / 2;
// Calculate t from local X position (position[0] is distance along wall) // Calculate t from local X position (position[0] is distance along wall)
const t = item.position[0] / wallLength; const t = item.position[0] / wallLength;
// position[1] is the bottom of the item
this.getWallGrid(levelId).insert({ this.getWallGrid(levelId).insert({
itemId: item.id, itemId: item.id,
wallId: wallId, wallId: wallId,
tStart: t - halfW, tStart: t - halfW,
tEnd: t + halfW, tEnd: t + halfW,
yStart: item.position[1] - halfH, yStart: item.position[1],
yEnd: item.position[1] + halfH, yEnd: item.position[1] + height,
}); });
} }
} }
@@ -95,16 +100,16 @@ export class SpatialGridManager {
if (wallLength > 0) { if (wallLength > 0) {
const [width, height] = item.asset.dimensions; const [width, height] = item.asset.dimensions;
const halfW = width / wallLength / 2; const halfW = width / wallLength / 2;
const halfH = height / 2;
// Calculate t from local X position (position[0] is distance along wall) // Calculate t from local X position (position[0] is distance along wall)
const t = item.position[0] / wallLength; const t = item.position[0] / wallLength;
// position[1] is the bottom of the item
this.getWallGrid(levelId).insert({ this.getWallGrid(levelId).insert({
itemId: item.id, itemId: item.id,
wallId: wallId, wallId: wallId,
tStart: t - halfW, tStart: t - halfW,
tEnd: t + halfW, tEnd: t + halfW,
yStart: item.position[1] - halfH, yStart: item.position[1],
yEnd: item.position[1] + halfH, yEnd: item.position[1] + height,
}); });
} }
} }
@@ -165,12 +170,14 @@ export class SpatialGridManager {
if (wallLength === 0) { if (wallLength === 0) {
return { valid: false, conflictIds: [] }; return { valid: false, conflictIds: [] };
} }
const wallHeight = this.getWallHeight(wallId);
// Convert local X position to parametric t (0-1) // Convert local X position to parametric t (0-1)
const tCenter = localX / wallLength; const tCenter = localX / wallLength;
const [itemWidth, itemHeight] = dimensions; const [itemWidth, itemHeight] = dimensions;
return this.getWallGrid(levelId).canPlaceOnWall( return this.getWallGrid(levelId).canPlaceOnWall(
wallId, wallId,
wallLength, wallLength,
wallHeight,
tCenter, tCenter,
itemWidth, itemWidth,
localY, localY,
@@ -14,18 +14,24 @@ export class WallSpatialGrid {
canPlaceOnWall( canPlaceOnWall(
wallId: string, wallId: string,
wallLength: number, wallLength: number,
wallHeight: number,
tCenter: number, tCenter: number,
itemWidth: number, itemWidth: number,
yCenter: number, yBottom: number,
itemHeight: number, itemHeight: number,
ignoreIds: string[] = [], ignoreIds: string[] = [],
): { valid: boolean; conflictIds: string[] } { ): { valid: boolean; conflictIds: string[] } {
const halfW = itemWidth / wallLength / 2; const halfW = itemWidth / wallLength / 2;
const halfH = itemHeight / 2;
const tStart = tCenter - halfW; const tStart = tCenter - halfW;
const tEnd = tCenter + halfW; const tEnd = tCenter + halfW;
const yStart = yCenter - halfH; // yBottom is the bottom of the item, so yEnd = yBottom + itemHeight
const yEnd = yCenter + halfH; const yStart = yBottom;
const yEnd = yBottom + itemHeight;
// Check wall boundaries
if (tStart < 0 || tEnd > 1 || yStart < 0 || yEnd > wallHeight) {
return { valid: false, conflictIds: [] };
}
const existing = this.wallItems.get(wallId) ?? []; const existing = this.wallItems.get(wallId) ?? [];
const ignoreSet = new Set(ignoreIds); const ignoreSet = new Set(ignoreIds);