1. Integerated DashBoard, #97

Merged
Marikannan merged 31 commits from v3-wall into main 2025-06-04 12:09:09 +00:00
10 changed files with 387 additions and 20 deletions
Showing only changes of commit a69fd2af92 - Show all commits

View File

@ -280,7 +280,7 @@ export default function Builder() {
plane={plane} plane={plane}
/> />
<WallGroup /> {/* <WallGroup /> */}
<AislesGroup /> <AislesGroup />

View File

@ -121,9 +121,20 @@ async function loadWalls(
} }
}); });
setWalls(Walls); setWalls(Walls);
}else{ } else {
setWalls([]); setWalls([]);
} }
} }
export default loadWalls; 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

View File

@ -157,7 +157,7 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin
} }
if (toolMode === "Wall") { 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") { if (toolMode === "Floor") {

View File

@ -8,6 +8,7 @@ import { useThree } from '@react-three/fiber';
import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { usePointSnapping } from './helpers/usePointSnapping'; import { usePointSnapping } from './helpers/usePointSnapping';
import { useAislePointSnapping } from './helpers/useAisleDragSnap'; import { useAislePointSnapping } from './helpers/useAisleDragSnap';
import { useWallStore } from '../../../store/builder/useWallStore';
function Point({ point }: { readonly point: Point }) { function Point({ point }: { readonly point: Point }) {
const materialRef = useRef<THREE.ShaderMaterial>(null); 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 plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { setPosition, removePoint } = useAisleStore(); const { setPosition: setAislePosition, removePoint: removeAislePoint } = useAisleStore();
const { setPosition: setWallPosition, removePoint: removeWallPoint } = useWallStore();
const { snapPosition } = useAislePointSnapping(point); const { snapPosition } = useAislePointSnapping(point);
const { checkSnapForAisle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); const { checkSnapForAisle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
const { hoveredPoint, setHoveredPoint } = useBuilderStore(); const { hoveredPoint, setHoveredPoint } = useBuilderStore();
@ -95,7 +97,13 @@ function Point({ point }: { readonly point: Point }) {
const aisleSnappedPosition = snapPosition(newPosition); const aisleSnappedPosition = snapPosition(newPosition);
const finalSnappedPosition = checkSnapForAisle(aisleSnappedPosition.position); 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) => { const handlePointClick = (point: Point) => {
if (deletePointOrLine) { if (deletePointOrLine) {
if (point.pointType === 'Aisle') { if (point.pointType === 'Aisle') {
const removedAisles = removePoint(point.pointUuid); const removedAisles = removeAislePoint(point.pointUuid);
if (removedAisles.length > 0) { if (removedAisles.length > 0) {
setHoveredPoint(null); setHoveredPoint(null);
} }

View File

@ -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,
};
}

View File

@ -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;

View File

@ -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;

View File

@ -1,18 +1,13 @@
import Line from '../../../line/line' import { useToggleView } from "../../../../../store/builder/store";
import Point from '../../../point/point'; import Wall from "./wall"
import { useToggleView } from '../../../../../store/builder/store';
function WallInstance({ wall }: { readonly wall: Wall }) { function WallInstance({ wall }: { readonly wall: Wall }) {
const { toggleView } = useToggleView(); const { toggleView } = useToggleView();
return ( return (
<> <>
{toggleView && ( {!toggleView && (
<> <Wall wall={wall} />
<Point point={wall.points[0]} />
<Line points={wall.points} />
<Point point={wall.points[1]} />
</>
)} )}
</> </>
) )

View File

@ -1,9 +1,14 @@
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useWallStore } from '../../../../store/builder/useWallStore' import { useWallStore } from '../../../../store/builder/useWallStore'
import WallInstance from './instance/wallInstance'; 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() { function WallInstances() {
const { walls } = useWallStore(); const { walls } = useWallStore();
const { toggleView } = useToggleView();
useEffect(() => { useEffect(() => {
// console.log('walls: ', walls); // console.log('walls: ', walls);
@ -11,9 +16,25 @@ function WallInstances() {
return ( return (
<> <>
{walls.map((wall) => (
<WallInstance key={wall.wallUuid} wall={wall} /> <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>
))}
</>
)}
</> </>
) )
} }

View File

@ -80,7 +80,7 @@ export const useBuilderStore = create<BuilderState>()(
// Wall // Wall
wallThickness: 2, wallThickness: 0.1,
wallHeight: 7, wallHeight: 7,
setWallThickness: (thickness: number) => { setWallThickness: (thickness: number) => {