updated
This commit is contained in:
@@ -2,218 +2,131 @@ import { useMemo } from 'react';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
export function useWallClassification(walls: Walls) {
|
||||
// Find all minimal rooms from the given walls
|
||||
|
||||
const findRooms = () => {
|
||||
if (walls.length < 3) return [];
|
||||
|
||||
// Build a graph of point connections
|
||||
const graph = new Map<string, string[]>();
|
||||
const pointMap = new Map<string, Point>();
|
||||
// Map pointUuid to list of connected line segments
|
||||
const pointMap = new Map<string, Wall[]>();
|
||||
|
||||
walls.forEach(wall => {
|
||||
const [p1, p2] = wall.points;
|
||||
pointMap.set(p1.pointUuid, p1);
|
||||
pointMap.set(p2.pointUuid, p2);
|
||||
for (const wall of walls) {
|
||||
for (const point of wall.points) {
|
||||
const list = pointMap.get(point.pointUuid) || [];
|
||||
list.push(wall);
|
||||
pointMap.set(point.pointUuid, list);
|
||||
}
|
||||
}
|
||||
|
||||
// Add connection from p1 to p2
|
||||
if (!graph.has(p1.pointUuid)) graph.set(p1.pointUuid, []);
|
||||
graph.get(p1.pointUuid)?.push(p2.pointUuid);
|
||||
// Create graph of connected walls using pointUuid
|
||||
const visited = new Set<string>();
|
||||
const mergedLineStrings = [];
|
||||
|
||||
// Add connection from p2 to p1
|
||||
if (!graph.has(p2.pointUuid)) graph.set(p2.pointUuid, []);
|
||||
graph.get(p2.pointUuid)?.push(p1.pointUuid);
|
||||
const wallKey = (p1: Point, p2: Point) => `${p1.pointUuid}-${p2.pointUuid}`;
|
||||
|
||||
for (const wall of walls) {
|
||||
const key = wallKey(wall.points[0], wall.points[1]);
|
||||
if (visited.has(key) || visited.has(wallKey(wall.points[1], wall.points[0]))) continue;
|
||||
|
||||
let line: Point[] = [...wall.points];
|
||||
visited.add(key);
|
||||
|
||||
// Try to extend the line forward and backward by matching endpoints
|
||||
let extended = true;
|
||||
while (extended) {
|
||||
extended = false;
|
||||
|
||||
const last = line[line.length - 1];
|
||||
const nextWalls = pointMap.get(last.pointUuid) || [];
|
||||
|
||||
for (const next of nextWalls) {
|
||||
const [n1, n2] = next.points;
|
||||
const nextKey = wallKey(n1, n2);
|
||||
if (visited.has(nextKey) || visited.has(wallKey(n2, n1))) continue;
|
||||
|
||||
if (n1.pointUuid === last.pointUuid && n2.pointUuid !== line[line.length - 2]?.pointUuid) {
|
||||
line.push(n2);
|
||||
visited.add(nextKey);
|
||||
extended = true;
|
||||
break;
|
||||
} else if (n2.pointUuid === last.pointUuid && n1.pointUuid !== line[line.length - 2]?.pointUuid) {
|
||||
line.push(n1);
|
||||
visited.add(nextKey);
|
||||
extended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const first = line[0];
|
||||
const prevWalls = pointMap.get(first.pointUuid) || [];
|
||||
|
||||
for (const prev of prevWalls) {
|
||||
const [p1, p2] = prev.points;
|
||||
const prevKey = wallKey(p1, p2);
|
||||
if (visited.has(prevKey) || visited.has(wallKey(p2, p1))) continue;
|
||||
|
||||
if (p1.pointUuid === first.pointUuid && p2.pointUuid !== line[1]?.pointUuid) {
|
||||
line.unshift(p2);
|
||||
visited.add(prevKey);
|
||||
extended = true;
|
||||
break;
|
||||
} else if (p2.pointUuid === first.pointUuid && p1.pointUuid !== line[1]?.pointUuid) {
|
||||
line.unshift(p1);
|
||||
visited.add(prevKey);
|
||||
extended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create merged LineString
|
||||
const coords = line.map(p => [p.position[0], p.position[2]]);
|
||||
mergedLineStrings.push(turf.lineString(coords, {
|
||||
pointUuids: line.map(p => p.pointUuid)
|
||||
}));
|
||||
}
|
||||
|
||||
const lineStrings = turf.featureCollection(mergedLineStrings);
|
||||
|
||||
// Now polygonize merged line strings
|
||||
const polygons = turf.polygonize(lineStrings);
|
||||
|
||||
const rooms: Point[][] = [];
|
||||
|
||||
polygons.features.forEach(feature => {
|
||||
if (feature.geometry.type === 'Polygon') {
|
||||
const coordinates = feature.geometry.coordinates[0];
|
||||
const roomPoints: Point[] = [];
|
||||
|
||||
for (const [x, z] of coordinates) {
|
||||
const matchingPoint = walls.flatMap(wall => wall.points)
|
||||
.find(p =>
|
||||
p.position[0].toFixed(10) === x.toFixed(10) &&
|
||||
p.position[2].toFixed(10) === z.toFixed(10)
|
||||
);
|
||||
|
||||
if (matchingPoint) {
|
||||
roomPoints.push(matchingPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (roomPoints.length > 0 &&
|
||||
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
|
||||
roomPoints.push(roomPoints[0]);
|
||||
}
|
||||
|
||||
if (roomPoints.length >= 4) {
|
||||
rooms.push(roomPoints);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Find all minimal cycles (rooms) in the graph
|
||||
const allCycles: string[][] = [];
|
||||
|
||||
const findCycles = (startNode: string, currentNode: string, path: string[], depth = 0) => {
|
||||
if (depth > 20) return; // Prevent infinite recursion
|
||||
|
||||
path.push(currentNode);
|
||||
|
||||
const neighbors = graph.get(currentNode) || [];
|
||||
|
||||
for (const neighbor of neighbors) {
|
||||
if (path.length > 2 && neighbor === startNode) {
|
||||
// Found a cycle that returns to start
|
||||
const cycle = [...path, neighbor];
|
||||
|
||||
// Check if this is a new unique cycle
|
||||
if (!cycleExists(allCycles, cycle)) {
|
||||
allCycles.push(cycle);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.includes(neighbor)) {
|
||||
findCycles(startNode, neighbor, [...path], depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start cycle detection from each node
|
||||
for (const [pointUuid] of graph) {
|
||||
findCycles(pointUuid, pointUuid, []);
|
||||
}
|
||||
|
||||
// Convert cycles to Point arrays and validate them
|
||||
const potentialRooms = allCycles
|
||||
.map(cycle => cycle.map(uuid => pointMap.get(uuid)!))
|
||||
.filter(room => isValidRoom(room));
|
||||
|
||||
const uniqueRooms = removeDuplicateRooms(potentialRooms);
|
||||
|
||||
// ✅ New logic that only removes redundant supersets
|
||||
const filteredRooms = removeSupersetLikeRooms(uniqueRooms, walls);
|
||||
|
||||
return filteredRooms;
|
||||
|
||||
console.log('rooms: ', rooms);
|
||||
return rooms;
|
||||
};
|
||||
|
||||
const removeSupersetLikeRooms = (rooms: Point[][], walls: Wall[]): Point[][] => {
|
||||
const toRemove = new Set<number>();
|
||||
|
||||
const getPolygon = (points: Point[]) =>
|
||||
turf.polygon([points.map(p => [p.position[0], p.position[2]])]);
|
||||
|
||||
const getWallSet = (room: Point[]) => {
|
||||
const set = new Set<string>();
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i].pointUuid;
|
||||
const p2 = room[i + 1].pointUuid;
|
||||
|
||||
const wall = walls.find(w =>
|
||||
(w.points[0].pointUuid === p1 && w.points[1].pointUuid === p2) ||
|
||||
(w.points[0].pointUuid === p2 && w.points[1].pointUuid === p1)
|
||||
);
|
||||
|
||||
if (wall) {
|
||||
set.add(wall.wallUuid);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
const roomPolygons = rooms.map(getPolygon);
|
||||
const roomAreas = roomPolygons.map(poly => turf.area(poly));
|
||||
const wallSets = rooms.map(getWallSet);
|
||||
|
||||
// First, identify all rooms that are completely contained within others
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
for (let j = 0; j < rooms.length; j++) {
|
||||
if (i === j) continue;
|
||||
|
||||
// If room i completely contains room j
|
||||
if (turf.booleanContains(roomPolygons[i], roomPolygons[j])) {
|
||||
// Check if the contained room shares most of its walls with the containing room
|
||||
const sharedWalls = [...wallSets[j]].filter(w => wallSets[i].has(w));
|
||||
const shareRatio = sharedWalls.length / wallSets[j].size;
|
||||
|
||||
// If they share more than 50% of walls, mark the larger one for removal
|
||||
// UNLESS the smaller one is significantly smaller (likely a real room)
|
||||
if (shareRatio > 0.5 && (roomAreas[i] / roomAreas[j] > 2)) {
|
||||
toRemove.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: handle cases where a room is divided by segmented walls
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
if (toRemove.has(i)) continue;
|
||||
|
||||
for (let j = 0; j < rooms.length; j++) {
|
||||
if (i === j || toRemove.has(j)) continue;
|
||||
|
||||
// Check if these rooms share a significant portion of walls
|
||||
const sharedWalls = [...wallSets[i]].filter(w => wallSets[j].has(w));
|
||||
const shareRatio = Math.max(
|
||||
sharedWalls.length / wallSets[i].size,
|
||||
sharedWalls.length / wallSets[j].size
|
||||
);
|
||||
|
||||
// If they share more than 30% of walls and one is much larger
|
||||
if (shareRatio > 0.3) {
|
||||
const areaRatio = roomAreas[i] / roomAreas[j];
|
||||
if (areaRatio > 2) {
|
||||
// The larger room is likely the undivided version
|
||||
toRemove.add(i);
|
||||
} else if (areaRatio < 0.5) {
|
||||
// The smaller room might be an artifact
|
||||
toRemove.add(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rooms.filter((_, idx) => !toRemove.has(idx));
|
||||
};
|
||||
|
||||
// Check if a cycle already exists in our list (considering different orders)
|
||||
const cycleExists = (allCycles: string[][], newCycle: string[]) => {
|
||||
const newSet = new Set(newCycle);
|
||||
|
||||
return allCycles.some(existingCycle => {
|
||||
if (existingCycle.length !== newCycle.length) return false;
|
||||
const existingSet = new Set(existingCycle);
|
||||
return setsEqual(newSet, existingSet);
|
||||
});
|
||||
};
|
||||
|
||||
// Check if two sets are equal
|
||||
const setsEqual = <T,>(a: Set<T>, b: Set<T>) => {
|
||||
if (a.size !== b.size) return false;
|
||||
for (const item of a) if (!b.has(item)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Remove duplicate rooms (same set of points in different orders)
|
||||
const removeDuplicateRooms = (rooms: Point[][]) => {
|
||||
const uniqueRooms: Point[][] = [];
|
||||
const roomHashes = new Set<string>();
|
||||
|
||||
for (const room of rooms) {
|
||||
// Create a consistent hash for the room regardless of point order
|
||||
const hash = room
|
||||
.map(p => p.pointUuid)
|
||||
.sort()
|
||||
.join('-');
|
||||
|
||||
if (!roomHashes.has(hash)) {
|
||||
roomHashes.add(hash);
|
||||
uniqueRooms.push(room);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueRooms;
|
||||
};
|
||||
|
||||
// Check if a room is valid (closed, non-self-intersecting polygon)
|
||||
const isValidRoom = (points: Point[]): boolean => {
|
||||
// Must have at least 4 points (first and last are same)
|
||||
if (points.length < 4) return false;
|
||||
|
||||
// Must be a closed loop
|
||||
if (points[0].pointUuid !== points[points.length - 1].pointUuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const coordinates = points.map(p => [p.position[0], p.position[2]]);
|
||||
const polygon = turf.polygon([coordinates]);
|
||||
return turf.booleanValid(polygon);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Rest of the implementation remains the same...
|
||||
const rooms = useMemo(() => findRooms(), [walls]);
|
||||
|
||||
// Determine wall orientation relative to room
|
||||
const findWallType = (wall: Wall) => {
|
||||
// Find all rooms that contain this wall
|
||||
const containingRooms = rooms.filter(room => {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i];
|
||||
@@ -244,11 +157,7 @@ export function useWallClassification(walls: Walls) {
|
||||
}
|
||||
};
|
||||
|
||||
// Update the other functions to use the new return type
|
||||
const getWallType = (wall: Wall): {
|
||||
type: string;
|
||||
rooms: Point[][];
|
||||
} => {
|
||||
const getWallType = (wall: Wall) => {
|
||||
return findWallType(wall);
|
||||
};
|
||||
|
||||
@@ -261,11 +170,32 @@ export function useWallClassification(walls: Walls) {
|
||||
return findWallType(wall).type === 'segment';
|
||||
};
|
||||
|
||||
const isWallFlipped = (wall: Wall): boolean => {
|
||||
const wallType = findWallType(wall);
|
||||
if (wallType.type === 'segment') return false;
|
||||
|
||||
for (const room of wallType.rooms) {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i];
|
||||
const p2 = room[i + 1];
|
||||
if (wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) {
|
||||
return false;
|
||||
}
|
||||
if (wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
rooms,
|
||||
getWallType,
|
||||
isRoomWall,
|
||||
isSegmentWall,
|
||||
findRooms,
|
||||
isWallFlipped
|
||||
};
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
function useWallGeometry(wallLength: number, wallHeight: number, wallThickness: number) {
|
||||
return useMemo(() => {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
|
||||
const halfLength = wallLength / 2;
|
||||
const halfThickness = wallThickness / 2;
|
||||
const height = wallHeight;
|
||||
|
||||
const vertices = [
|
||||
-halfLength, -height / 2, halfThickness,
|
||||
-halfLength, height / 2, halfThickness,
|
||||
halfLength, height / 2, halfThickness,
|
||||
halfLength, -height / 2, halfThickness,
|
||||
-halfLength, -height / 2, -halfThickness,
|
||||
-halfLength, height / 2, -halfThickness,
|
||||
halfLength, height / 2, -halfThickness,
|
||||
halfLength, -height / 2, -halfThickness,
|
||||
-halfLength, height / 2, halfThickness,
|
||||
-halfLength, height / 2, -halfThickness,
|
||||
halfLength, height / 2, -halfThickness,
|
||||
halfLength, height / 2, halfThickness,
|
||||
-halfLength, -height / 2, halfThickness,
|
||||
-halfLength, -height / 2, -halfThickness,
|
||||
halfLength, -height / 2, -halfThickness,
|
||||
halfLength, -height / 2, halfThickness,
|
||||
];
|
||||
|
||||
const indices = [
|
||||
0, 1, 2, 0, 2, 3,
|
||||
4, 6, 5, 4, 7, 6,
|
||||
0, 4, 5, 0, 5, 1,
|
||||
3, 2, 6, 3, 6, 7,
|
||||
8, 9, 10, 8, 10, 11,
|
||||
12, 14, 13, 12, 15, 14
|
||||
];
|
||||
|
||||
geometry.setIndex(indices);
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
||||
geometry.setAttribute('uv', new THREE.Float32BufferAttribute([
|
||||
0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0
|
||||
], 2));
|
||||
|
||||
geometry.computeVertexNormals();
|
||||
|
||||
geometry.addGroup(0, 6, 0); // Front
|
||||
geometry.addGroup(6, 6, 1); // Back
|
||||
geometry.addGroup(12, 6, 2); // Left
|
||||
geometry.addGroup(18, 6, 3); // Right
|
||||
geometry.addGroup(24, 6, 4); // Top
|
||||
geometry.addGroup(30, 6, 5); // Bottom
|
||||
|
||||
return geometry;
|
||||
}, [wallLength, wallHeight, wallThickness]);
|
||||
}
|
||||
|
||||
export default useWallGeometry;
|
||||
@@ -2,26 +2,29 @@ import * as THREE from 'three';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import * as Constants from '../../../../../types/world/worldConstants';
|
||||
|
||||
import insideMaterial from '../../../../../assets/textures/floor/wall-tex.png';
|
||||
import outsideMaterial from '../../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
import useWallGeometry from './helpers/useWallGeometry';
|
||||
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
|
||||
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
import { useWallStore } from '../../../../../store/builder/useWallStore';
|
||||
import { useWallClassification } from './helpers/useWallClassification';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useWallVisibility } from '../../../../../store/builder/store';
|
||||
import { Decal } from '@react-three/drei';
|
||||
import { Base } from '@react-three/csg';
|
||||
import { Decal, PivotControls } from '@react-three/drei';
|
||||
import { Base, Geometry, Subtraction } from '@react-three/csg';
|
||||
|
||||
function Wall({ wall }: { readonly wall: Wall }) {
|
||||
const { walls } = useWallStore();
|
||||
const { getWallType } = useWallClassification(walls);
|
||||
const { getWallType, isWallFlipped } = useWallClassification(walls);
|
||||
const wallType = getWallType(wall);
|
||||
const [startPoint, endPoint] = wall.points;
|
||||
const [visible, setVisible] = useState(true);
|
||||
const { wallVisibility } = useWallVisibility();
|
||||
const meshRef = useRef<any>();
|
||||
const { camera } = useThree();
|
||||
|
||||
const wallFlipped = isWallFlipped(wall);
|
||||
|
||||
const [rawStart, rawEnd] = wall.points;
|
||||
const [startPoint, endPoint] = wallFlipped ? [rawStart, rawEnd] : [rawEnd, rawStart];
|
||||
|
||||
const startX = startPoint.position[0];
|
||||
const startZ = startPoint.position[2];
|
||||
const endX = endPoint.position[0];
|
||||
@@ -36,13 +39,13 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
|
||||
const [insideWallTexture, outsideWallTexture] = useMemo(() => {
|
||||
const inside = textureLoader.load(insideMaterial);
|
||||
const [defaultWallTexture, material1WallTexture] = useMemo(() => {
|
||||
const inside = textureLoader.load(defaultMaterial);
|
||||
inside.wrapS = inside.wrapT = THREE.RepeatWrapping;
|
||||
inside.repeat.set(wallLength / 10, wall.wallHeight / 10);
|
||||
inside.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
const outside = textureLoader.load(outsideMaterial);
|
||||
const outside = textureLoader.load(material1);
|
||||
outside.wrapS = outside.wrapT = THREE.RepeatWrapping;
|
||||
outside.repeat.set(wallLength / 10, wall.wallHeight / 10);
|
||||
outside.colorSpace = THREE.SRGBColorSpace;
|
||||
@@ -51,29 +54,25 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
}, [wallLength, wall.wallHeight]);
|
||||
|
||||
const materials = useMemo(() => {
|
||||
const frontMaterial = insideWallTexture;
|
||||
|
||||
const backMaterial = outsideWallTexture;
|
||||
|
||||
return [
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Left
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Right
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Top
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Bottom
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: Constants.wallConfig.defaultColor,
|
||||
side: THREE.DoubleSide,
|
||||
map: frontMaterial
|
||||
map: wall.insideMaterial === 'Default Material' ? defaultWallTexture : material1WallTexture,
|
||||
}),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: Constants.wallConfig.defaultColor,
|
||||
side: THREE.DoubleSide,
|
||||
map: backMaterial
|
||||
map: wall.outsideMaterial === 'Default Material`' ? defaultWallTexture : material1WallTexture,
|
||||
}),
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }), // Left
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }), // Right
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }), // Top
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }) // Bottom
|
||||
];
|
||||
}, [insideWallTexture, outsideWallTexture, wall]);
|
||||
}, [defaultWallTexture, material1WallTexture, wall]);
|
||||
|
||||
const geometry = useWallGeometry(wallLength, wall.wallHeight, wall.wallThickness);
|
||||
const geometry = useMemo(() => new THREE.BoxGeometry(wallLength, wall.wallHeight, wall.wallThickness), [wallLength, wall.wallHeight, wall.wallThickness]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!meshRef.current) return;
|
||||
@@ -83,6 +82,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
if (!wallVisibility && wallType.type === 'room') {
|
||||
meshRef.current.getWorldDirection(v);
|
||||
camera.getWorldDirection(u);
|
||||
if (!u || !v) return;
|
||||
setVisible((2 * v.dot(u)) <= 0.1);
|
||||
|
||||
} else {
|
||||
@@ -91,16 +91,19 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
})
|
||||
|
||||
return (
|
||||
<group
|
||||
<mesh
|
||||
name={`Wall-${wall.wallUuid}`}
|
||||
key={wall.wallUuid}
|
||||
userData={wall}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
visible={visible}
|
||||
>
|
||||
<Base ref={meshRef} geometry={geometry} visible>
|
||||
<Base
|
||||
ref={meshRef}
|
||||
geometry={geometry}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} object={material} attach={`material-${index}`} />
|
||||
<primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
|
||||
{wall.decals.map((decal) => {
|
||||
@@ -112,7 +115,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
scale={[decal.decalScale, decal.decalScale, 0.001]}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
map={outsideWallTexture}
|
||||
map={material1WallTexture}
|
||||
side={THREE.DoubleSide}
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-1}
|
||||
@@ -121,7 +124,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
)
|
||||
})}
|
||||
</Base>
|
||||
</group>
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ import WallInstance from './instance/wallInstance';
|
||||
import Line from '../../line/line';
|
||||
import Point from '../../point/point';
|
||||
import { useToggleView } from '../../../../store/builder/store';
|
||||
import { Base, Geometry, Subtraction } from '@react-three/csg';
|
||||
import { BoxGeometry } from 'three';
|
||||
import { Geometry } from '@react-three/csg';
|
||||
|
||||
function WallInstances() {
|
||||
const { walls } = useWallStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const ref = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('walls: ', walls);
|
||||
@@ -34,26 +32,25 @@ function WallInstances() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<group name='Walls-Group' ref={ref}>
|
||||
<Geometry computeVertexNormals>
|
||||
|
||||
{walls.map((wall) => (
|
||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||
))}
|
||||
{!toggleView && (
|
||||
|
||||
{/* <Base geometry={new BoxGeometry()} >
|
||||
<meshStandardMaterial />
|
||||
</Base>
|
||||
<mesh name='Walls-Group'>
|
||||
{/* <Base name="base" geometry={box} scale={[3, 3, 3]} /> */}
|
||||
|
||||
<Geometry>
|
||||
<Subtraction scale={[5, 11, 5]} >
|
||||
<Geometry>
|
||||
<Base geometry={new BoxGeometry()} />
|
||||
</Geometry>
|
||||
</Subtraction>
|
||||
</Geometry> */}
|
||||
</Geometry>
|
||||
</group>
|
||||
<Geometry useGroups>
|
||||
{walls.map((wall) => (
|
||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||
))}
|
||||
</Geometry>
|
||||
|
||||
{/* <Subtraction rotation={[0, Math.PI / 2, 0]} position={[-1.425, -0.45, 0]} scale={[1, 3, 1]}>
|
||||
<Geometry>
|
||||
<Base geometry={box} />
|
||||
</Geometry>
|
||||
</Subtraction> */}
|
||||
</mesh>
|
||||
)}
|
||||
|
||||
{toggleView && (
|
||||
<>
|
||||
@@ -76,4 +73,4 @@ function WallInstances() {
|
||||
)
|
||||
}
|
||||
|
||||
export default WallInstances
|
||||
export default WallInstances
|
||||
|
||||
@@ -26,7 +26,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
||||
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
|
||||
|
||||
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
|
||||
const { checkSnapForWall } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] });
|
||||
const { snapWallPoint } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] });
|
||||
|
||||
useFrame(() => {
|
||||
if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) {
|
||||
@@ -37,7 +37,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
||||
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
||||
|
||||
if (intersectionPoint) {
|
||||
const snapped = checkSnapForWall([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
||||
const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
||||
|
||||
if (snapped.isSnapped && snapped.snappedPoint) {
|
||||
finalPosition.current = snapped.position;
|
||||
@@ -68,8 +68,8 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
||||
setTempWall({
|
||||
wallUuid: 'temp-wall',
|
||||
points: wallPoints,
|
||||
outSideMaterial: 'default',
|
||||
inSideMaterial: 'default',
|
||||
outsideMaterial: 'default',
|
||||
insideMaterial: 'default',
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
|
||||
@@ -2,10 +2,12 @@ import * as THREE from 'three'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
|
||||
import * as Constants from '../../../../types/world/worldConstants';
|
||||
import { useWallStore } from '../../../../store/builder/useWallStore';
|
||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||
import ReferencePoint from '../../point/reference/referencePoint';
|
||||
import ReferenceWall from './referenceWall';
|
||||
import getClosestIntersection from '../../geomentries/lines/getClosestIntersection';
|
||||
|
||||
function WallCreator() {
|
||||
const { scene, camera, raycaster, gl, pointer } = useThree();
|
||||
@@ -14,10 +16,10 @@ function WallCreator() {
|
||||
const { toolMode } = useToolMode();
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { socket } = useSocketStore();
|
||||
const { addWall, getWallPointById } = useWallStore();
|
||||
const { addWall, getWallPointById, removeWall, getWallByPoints } = useWallStore();
|
||||
const drag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const { wallThickness, wallHeight, snappedPosition, snappedPoint } = useBuilderStore();
|
||||
const { wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint } = useBuilderStore();
|
||||
|
||||
const [tempPoints, setTempPoints] = useState<Point[]>([]);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -52,7 +54,118 @@ function WallCreator() {
|
||||
let position = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (!position) return;
|
||||
|
||||
const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Point');
|
||||
const pointIntersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Point');
|
||||
|
||||
const wallIntersect = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Line');
|
||||
if (wallIntersect && !pointIntersects) {
|
||||
const wall = getWallByPoints(wallIntersect.object.userData.points);
|
||||
if (wall) {
|
||||
const ThroughPoint = wallIntersect.object.userData.path.getPoints(Constants.lineConfig.lineIntersectionPoints);
|
||||
let intersectionPoint = getClosestIntersection(ThroughPoint, wallIntersect.point);
|
||||
const point1Vec = new THREE.Vector3(...wall.points[0].position);
|
||||
const point2Vec = new THREE.Vector3(...wall.points[1].position);
|
||||
|
||||
const lineDir = new THREE.Vector3().subVectors(point2Vec, point1Vec).normalize();
|
||||
|
||||
const point1ToIntersect = new THREE.Vector3().subVectors(intersectionPoint, point1Vec);
|
||||
|
||||
const dotProduct = point1ToIntersect.dot(lineDir);
|
||||
const projection = new THREE.Vector3().copy(lineDir).multiplyScalar(dotProduct).add(point1Vec);
|
||||
|
||||
const lineLength = point1Vec.distanceTo(point2Vec);
|
||||
let t = point1Vec.distanceTo(projection) / lineLength;
|
||||
t = Math.max(0, Math.min(1, t));
|
||||
|
||||
const closestPoint = new THREE.Vector3().lerpVectors(point1Vec, point2Vec, t);
|
||||
|
||||
removeWall(wall.wallUuid);
|
||||
|
||||
const point1: Point = {
|
||||
pointUuid: wall.points[0].pointUuid,
|
||||
pointType: 'Wall',
|
||||
position: wall.points[0].position,
|
||||
layer: wall.points[0].layer
|
||||
};
|
||||
|
||||
const point2: Point = {
|
||||
pointUuid: wall.points[1].pointUuid,
|
||||
pointType: 'Wall',
|
||||
position: wall.points[1].position,
|
||||
layer: wall.points[1].layer
|
||||
};
|
||||
|
||||
const newPoint: Point = {
|
||||
pointUuid: THREE.MathUtils.generateUUID(),
|
||||
pointType: 'Wall',
|
||||
position: closestPoint.toArray(),
|
||||
layer: activeLayer
|
||||
};
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
const wall2: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [point1, newPoint],
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
}
|
||||
addWall(wall2);
|
||||
|
||||
const wall3: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [point2, newPoint],
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
}
|
||||
addWall(wall3);
|
||||
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const wall1: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
};
|
||||
addWall(wall1);
|
||||
|
||||
const wall2: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [point1, newPoint],
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
}
|
||||
addWall(wall2);
|
||||
|
||||
const wall3: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [point2, newPoint],
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
}
|
||||
addWall(wall3);
|
||||
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newPoint: Point = {
|
||||
pointUuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -71,8 +184,8 @@ function WallCreator() {
|
||||
newPoint.position = snappedPosition;
|
||||
}
|
||||
|
||||
if (intersects && !snappedPoint) {
|
||||
const point = getWallPointById(intersects.object.uuid);
|
||||
if (pointIntersects && !snappedPoint) {
|
||||
const point = getWallPointById(pointIntersects.object.uuid);
|
||||
if (point) {
|
||||
newPoint.pointUuid = point.pointUuid;
|
||||
newPoint.position = point.position;
|
||||
@@ -87,8 +200,8 @@ function WallCreator() {
|
||||
const wall: Wall = {
|
||||
wallUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
outSideMaterial: 'default',
|
||||
inSideMaterial: 'default',
|
||||
outsideMaterial: insideMaterial,
|
||||
insideMaterial: outsideMaterial,
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
@@ -114,10 +227,8 @@ function WallCreator() {
|
||||
canvasElement.addEventListener("click", onMouseClick);
|
||||
canvasElement.addEventListener("contextmenu", onContext);
|
||||
} else {
|
||||
if (tempPoints.length > 0 || isCreating) {
|
||||
setTempPoints([]);
|
||||
setIsCreating(false);
|
||||
}
|
||||
setTempPoints([]);
|
||||
setIsCreating(false);
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
|
||||
Reference in New Issue
Block a user