wall item checks
This commit is contained in:
@@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user