1. Integerated DashBoard, #97
|
@ -280,7 +280,7 @@ export default function Builder() {
|
|||
plane={plane}
|
||||
/>
|
||||
|
||||
<WallGroup />
|
||||
{/* <WallGroup /> */}
|
||||
|
||||
<AislesGroup />
|
||||
|
||||
|
|
|
@ -121,9 +121,20 @@ async function loadWalls(
|
|||
}
|
||||
});
|
||||
setWalls(Walls);
|
||||
}else{
|
||||
} else {
|
||||
setWalls([]);
|
||||
}
|
||||
}
|
||||
|
||||
export default loadWalls;
|
||||
|
||||
|
||||
// A----- B----- C
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// F----- E----- D
|
||||
|
||||
// 1. A -> B, B -> C, C -> D, D -> E, E -> F, F -> A, B -> E
|
||||
|
||||
// 2. E -> F, F -> A, A -> B, B -> E, E -> D, D -> C, C -> B
|
|
@ -157,7 +157,7 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin
|
|||
}
|
||||
|
||||
if (toolMode === "Wall") {
|
||||
// drawWall(raycaster, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket);
|
||||
drawWall(raycaster, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket);
|
||||
}
|
||||
|
||||
if (toolMode === "Floor") {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useThree } from '@react-three/fiber';
|
|||
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
|
||||
import { usePointSnapping } from './helpers/usePointSnapping';
|
||||
import { useAislePointSnapping } from './helpers/useAisleDragSnap';
|
||||
import { useWallStore } from '../../../store/builder/useWallStore';
|
||||
|
||||
function Point({ point }: { readonly point: Point }) {
|
||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||
|
@ -15,7 +16,8 @@ function Point({ point }: { readonly point: Point }) {
|
|||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setPosition, removePoint } = useAisleStore();
|
||||
const { setPosition: setAislePosition, removePoint: removeAislePoint } = useAisleStore();
|
||||
const { setPosition: setWallPosition, removePoint: removeWallPoint } = useWallStore();
|
||||
const { snapPosition } = useAislePointSnapping(point);
|
||||
const { checkSnapForAisle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
|
||||
const { hoveredPoint, setHoveredPoint } = useBuilderStore();
|
||||
|
@ -95,7 +97,13 @@ function Point({ point }: { readonly point: Point }) {
|
|||
const aisleSnappedPosition = snapPosition(newPosition);
|
||||
const finalSnappedPosition = checkSnapForAisle(aisleSnappedPosition.position);
|
||||
|
||||
setPosition(point.pointUuid, finalSnappedPosition.position);
|
||||
setAislePosition(point.pointUuid, finalSnappedPosition.position);
|
||||
}
|
||||
} else if (point.pointType === 'Wall') {
|
||||
if (position) {
|
||||
const newPosition: [number, number, number] = [position.x, position.y, position.z];
|
||||
|
||||
setWallPosition(point.pointUuid, newPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +117,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||
const handlePointClick = (point: Point) => {
|
||||
if (deletePointOrLine) {
|
||||
if (point.pointType === 'Aisle') {
|
||||
const removedAisles = removePoint(point.pointUuid);
|
||||
const removedAisles = removeAislePoint(point.pointUuid);
|
||||
if (removedAisles.length > 0) {
|
||||
setHoveredPoint(null);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import { useMemo } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
export function useWallClassification(walls: Walls) {
|
||||
// Find all rooms from the given walls
|
||||
const findRooms = () => {
|
||||
if (walls.length < 3) return [];
|
||||
|
||||
const pointMap = new Map<string, Point>();
|
||||
const connections = new Map<string, string[]>();
|
||||
|
||||
// Build connection graph
|
||||
walls.forEach(wall => {
|
||||
const [p1, p2] = wall.points;
|
||||
if (!pointMap.has(p1.pointUuid)) pointMap.set(p1.pointUuid, p1);
|
||||
if (!pointMap.has(p2.pointUuid)) pointMap.set(p2.pointUuid, p2);
|
||||
|
||||
if (!connections.has(p1.pointUuid)) connections.set(p1.pointUuid, []);
|
||||
if (!connections.has(p2.pointUuid)) connections.set(p2.pointUuid, []);
|
||||
|
||||
connections.get(p1.pointUuid)?.push(p2.pointUuid);
|
||||
connections.get(p2.pointUuid)?.push(p1.pointUuid);
|
||||
});
|
||||
console.log('connections: ', connections);
|
||||
|
||||
const visited = new Set<string>();
|
||||
const rooms: Point[][] = [];
|
||||
|
||||
// Modified DFS to find all possible cycles
|
||||
const findAllCycles = (current: string, path: string[]) => {
|
||||
visited.add(current);
|
||||
path.push(current);
|
||||
|
||||
const neighbors = connections.get(current) || [];
|
||||
for (const neighbor of neighbors) {
|
||||
if (path.length > 2 && neighbor === path[0]) {
|
||||
// Found a cycle (potential room)
|
||||
const roomPoints = [...path, neighbor].map(uuid => pointMap.get(uuid)!);
|
||||
|
||||
// Check if this room is valid and not already found
|
||||
if (isValidRoom(roomPoints) && !roomAlreadyExists(rooms, roomPoints)) {
|
||||
rooms.push(roomPoints);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.includes(neighbor)) {
|
||||
findAllCycles(neighbor, [...path]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start from each point to find all possible cycles
|
||||
for (const [pointUuid] of connections) {
|
||||
if (!visited.has(pointUuid)) {
|
||||
findAllCycles(pointUuid, []);
|
||||
}
|
||||
}
|
||||
|
||||
return rooms;
|
||||
};
|
||||
|
||||
// Helper function to check if room is valid
|
||||
const isValidRoom = (roomPoints: Point[]) => {
|
||||
if (roomPoints.length < 4) return false;
|
||||
const coordinates = roomPoints.map(p => [p.position[0], p.position[2]]);
|
||||
const polygon = turf.polygon([coordinates]);
|
||||
return turf.booleanValid(polygon);
|
||||
};
|
||||
|
||||
// Helper function to check for duplicate rooms
|
||||
const roomAlreadyExists = (existingRooms: Point[][], newRoom: Point[]) => {
|
||||
const newRoomIds = newRoom.map(p => p.pointUuid).sort().join('-');
|
||||
return existingRooms.some(room => {
|
||||
const roomIds = room.map(p => p.pointUuid).sort().join('-');
|
||||
return roomIds === newRoomIds;
|
||||
});
|
||||
};
|
||||
|
||||
const rooms = useMemo(() => findRooms(), [walls]);
|
||||
|
||||
// Modified to track all rooms a wall belongs to
|
||||
const getWallOrientation = (wall: Wall) => {
|
||||
const [p1, p2] = wall.points;
|
||||
const orientations: Array<{ isOutsideFacing: boolean }> = [];
|
||||
|
||||
for (const room of rooms) {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const roomP1 = room[i];
|
||||
const roomP2 = room[i + 1];
|
||||
|
||||
// Check both directions
|
||||
if ((p1.pointUuid === roomP1.pointUuid && p2.pointUuid === roomP2.pointUuid) ||
|
||||
(p1.pointUuid === roomP2.pointUuid && p2.pointUuid === roomP1.pointUuid)) {
|
||||
|
||||
const roomPoints = room.map(p => new THREE.Vector3(p.position[0], 0, p.position[2]));
|
||||
const centroid = new THREE.Vector3();
|
||||
roomPoints.forEach(p => centroid.add(p));
|
||||
centroid.divideScalar(roomPoints.length);
|
||||
|
||||
const wallVector = new THREE.Vector3(
|
||||
p2.position[0] - p1.position[0],
|
||||
0,
|
||||
p2.position[2] - p1.position[2]
|
||||
);
|
||||
|
||||
// Normal depends on wall direction
|
||||
let normal = new THREE.Vector3(-wallVector.z, 0, wallVector.x).normalize();
|
||||
if (p1.pointUuid === roomP2.pointUuid) {
|
||||
normal = new THREE.Vector3(wallVector.z, 0, -wallVector.x).normalize();
|
||||
}
|
||||
|
||||
const testPoint = new THREE.Vector3(
|
||||
(p1.position[0] + p2.position[0]) / 2 + normal.x,
|
||||
0,
|
||||
(p1.position[2] + p2.position[2]) / 2 + normal.z
|
||||
);
|
||||
|
||||
const pointInside = turf.booleanPointInPolygon(
|
||||
turf.point([testPoint.x, testPoint.z]),
|
||||
turf.polygon([room.map(p => [p.position[0], p.position[2]])])
|
||||
);
|
||||
|
||||
orientations.push({
|
||||
isOutsideFacing: !pointInside
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isRoomWall: orientations.length > 0,
|
||||
orientations // Now tracks all orientations for walls in multiple rooms
|
||||
};
|
||||
};
|
||||
|
||||
const getWallMaterialSide = (wall: Wall) => {
|
||||
const orientation = getWallOrientation(wall);
|
||||
|
||||
if (!orientation.isRoomWall) {
|
||||
return { front: 'inside', back: 'inside' }; // Both sides same for segment walls
|
||||
}
|
||||
|
||||
// For walls in multiple rooms, we need to determine which side faces which room
|
||||
if (orientation.orientations.length === 2) {
|
||||
// Wall is between two rooms - one side faces each room's interior
|
||||
return {
|
||||
front: 'inside',
|
||||
back: 'inside'
|
||||
};
|
||||
} else if (orientation.orientations.length === 1) {
|
||||
// Wall is part of only one room (exterior wall)
|
||||
return orientation.orientations[0].isOutsideFacing
|
||||
? { front: 'outside', back: 'inside' }
|
||||
: { front: 'inside', back: 'outside' };
|
||||
}
|
||||
|
||||
// Default case (shouldn't normally happen)
|
||||
return { front: 'inside', back: 'inside' };
|
||||
};
|
||||
|
||||
// Rest of the functions remain the same
|
||||
const getWallType = (wall: Wall) => getWallOrientation(wall).isRoomWall ? 'room' : 'segment';
|
||||
const isRoomWall = (wall: Wall) => getWallOrientation(wall).isRoomWall;
|
||||
const isSegmentWall = (wall: Wall) => !getWallOrientation(wall).isRoomWall;
|
||||
|
||||
return {
|
||||
rooms,
|
||||
getWallType,
|
||||
isRoomWall,
|
||||
isSegmentWall,
|
||||
getWallMaterialSide,
|
||||
findRooms,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
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;
|
|
@ -0,0 +1,92 @@
|
|||
import * as THREE from 'three';
|
||||
import { useMemo } 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 { useWallStore } from '../../../../../store/builder/useWallStore';
|
||||
import { useWallClassification } from './helpers/useWallClassification';
|
||||
|
||||
function Wall({ wall }: { readonly wall: Wall }) {
|
||||
const { walls } = useWallStore();
|
||||
const { getWallMaterialSide, isRoomWall, rooms } = useWallClassification(walls);
|
||||
console.log('rooms: ', rooms);
|
||||
const materialSide = getWallMaterialSide(wall);
|
||||
const [startPoint, endPoint] = wall.points;
|
||||
|
||||
const startX = startPoint.position[0];
|
||||
const startZ = startPoint.position[2];
|
||||
const endX = endPoint.position[0];
|
||||
const endZ = endPoint.position[2];
|
||||
|
||||
const wallLength = Math.sqrt((endX - startX) ** 2 + (endZ - startZ) ** 2);
|
||||
const angle = Math.atan2(endZ - startZ, endX - startX);
|
||||
|
||||
const centerX = (startX + endX) / 2;
|
||||
const centerZ = (startZ + endZ) / 2;
|
||||
const centerY = wall.wallHeight / 2;
|
||||
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
|
||||
const [insideWallTexture, outsideWallTexture] = useMemo(() => {
|
||||
const inside = textureLoader.load(insideMaterial);
|
||||
inside.wrapS = inside.wrapT = THREE.RepeatWrapping;
|
||||
inside.repeat.set(wallLength / 10, wall.wallHeight / 10);
|
||||
inside.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
const outside = textureLoader.load(outsideMaterial);
|
||||
outside.wrapS = outside.wrapT = THREE.RepeatWrapping;
|
||||
outside.repeat.set(wallLength / 10, wall.wallHeight / 10);
|
||||
outside.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
return [inside, outside];
|
||||
}, [wallLength, wall.wallHeight, textureLoader]);
|
||||
|
||||
const materials = useMemo(() => {
|
||||
// For segment walls (not in a room), use inside material on both sides
|
||||
const frontMaterial = isRoomWall(wall)
|
||||
? (materialSide.front === 'inside' ? insideWallTexture : outsideWallTexture)
|
||||
: insideWallTexture;
|
||||
|
||||
const backMaterial = isRoomWall(wall)
|
||||
? (materialSide.back === 'inside' ? insideWallTexture : outsideWallTexture)
|
||||
: insideWallTexture;
|
||||
|
||||
return [
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: Constants.wallConfig.defaultColor,
|
||||
side: THREE.DoubleSide,
|
||||
map: frontMaterial
|
||||
}),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: Constants.wallConfig.defaultColor,
|
||||
side: THREE.DoubleSide,
|
||||
map: backMaterial
|
||||
}),
|
||||
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, materialSide, isRoomWall, wall]);
|
||||
|
||||
const geometry = useWallGeometry(wallLength, wall.wallHeight, wall.wallThickness);
|
||||
|
||||
return (
|
||||
<group
|
||||
name={`Wall-${wall.wallUuid}`}
|
||||
userData={wall}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default Wall;
|
|
@ -1,18 +1,13 @@
|
|||
import Line from '../../../line/line'
|
||||
import Point from '../../../point/point';
|
||||
import { useToggleView } from '../../../../../store/builder/store';
|
||||
import { useToggleView } from "../../../../../store/builder/store";
|
||||
import Wall from "./wall"
|
||||
|
||||
function WallInstance({ wall }: { readonly wall: Wall }) {
|
||||
const { toggleView } = useToggleView();
|
||||
|
||||
return (
|
||||
<>
|
||||
{toggleView && (
|
||||
<>
|
||||
<Point point={wall.points[0]} />
|
||||
<Line points={wall.points} />
|
||||
<Point point={wall.points[1]} />
|
||||
</>
|
||||
{!toggleView && (
|
||||
<Wall wall={wall} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useWallStore } from '../../../../store/builder/useWallStore'
|
||||
import WallInstance from './instance/wallInstance';
|
||||
import Line from '../../line/line';
|
||||
import Point from '../../point/point';
|
||||
import { useToggleView } from '../../../../store/builder/store';
|
||||
import { Geometry } from '@react-three/csg';
|
||||
|
||||
function WallInstances() {
|
||||
const { walls } = useWallStore();
|
||||
const { toggleView } = useToggleView();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('walls: ', walls);
|
||||
|
@ -11,9 +16,25 @@ function WallInstances() {
|
|||
|
||||
return (
|
||||
<>
|
||||
|
||||
<Geometry computeVertexNormals useGroups>
|
||||
{walls.map((wall) => (
|
||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||
))}
|
||||
</Geometry>
|
||||
|
||||
|
||||
{toggleView && (
|
||||
<>
|
||||
{walls.map((wall) => (
|
||||
<React.Fragment key={wall.wallUuid}>
|
||||
<Point point={wall.points[0]} />
|
||||
<Line points={wall.points} />
|
||||
<Point point={wall.points[1]} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ export const useBuilderStore = create<BuilderState>()(
|
|||
|
||||
// Wall
|
||||
|
||||
wallThickness: 2,
|
||||
wallThickness: 0.1,
|
||||
wallHeight: 7,
|
||||
|
||||
setWallThickness: (thickness: number) => {
|
||||
|
|
Loading…
Reference in New Issue