refactor: Enhance wall and comparison handling, improve state management in builder components
This commit is contained in:
parent
d57ee378aa
commit
e4196eee8c
|
@ -6,21 +6,24 @@ import useModuleStore from '../../../store/useModuleStore';
|
|||
import CompareLayOut from '../../ui/compareVersion/CompareLayOut';
|
||||
import ComparisonResult from '../../ui/compareVersion/ComparisonResult';
|
||||
import { useComparisonProduct } from '../../../store/simulation/useSimulationStore';
|
||||
import { usePlayButtonStore } from '../../../store/usePlayButtonStore';
|
||||
import { usePauseButtonStore, usePlayButtonStore } from '../../../store/usePlayButtonStore';
|
||||
|
||||
function ComparisonScene() {
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||
const { products } = useProductStore();
|
||||
const { isVersionSaved } = useSaveVersion();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { comparisonProduct, setComparisonProduct } = useComparisonProduct();
|
||||
const { setIsPaused } = usePauseButtonStore();
|
||||
|
||||
const handleSelectLayout = (option: string) => {
|
||||
const product = products.find((product) => product.productName === option);
|
||||
if (product) {
|
||||
setComparisonProduct(product.productId, product.productName);
|
||||
setIsPlaying(true);
|
||||
setIsPaused(true);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,7 @@ import ControlsPlayer from '../controls/ControlsPlayer';
|
|||
import SelectFloorPlan from '../../temporary/SelectFloorPlan';
|
||||
import { createHandleDrop } from '../../../modules/visualization/functions/handleUiDrop';
|
||||
import Scene from '../../../modules/scene/scene';
|
||||
import { useMainProduct } from '../../../store/simulation/useSimulationStore';
|
||||
import { useComparisonProduct, useMainProduct } from '../../../store/simulation/useSimulationStore';
|
||||
import { useProductContext } from '../../../modules/simulation/products/productContext';
|
||||
import { useProductStore } from '../../../store/simulation/useProductStore';
|
||||
import RegularDropDown from '../../ui/inputs/RegularDropDown';
|
||||
|
@ -38,6 +38,7 @@ function MainScene() {
|
|||
const { visualizationSocket } = useSocketStore();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { setFloatingWidget } = useFloatingWidget();
|
||||
const { comparisonProduct } = useComparisonProduct();
|
||||
|
||||
const handleSelectLayout = (option: string) => {
|
||||
const product = products.find((product) => product.productName === option);
|
||||
|
@ -64,8 +65,8 @@ function MainScene() {
|
|||
{activeModule !== "market" && !isPlaying && !isVersionSaved && (
|
||||
<Tools />
|
||||
)}
|
||||
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
|
||||
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}
|
||||
{(isPlaying || comparisonProduct !== null) && activeModule === "simulation" && <SimulationPlayer />}
|
||||
{(isPlaying || comparisonProduct !== null) && activeModule !== "simulation" && <ControlsPlayer />}
|
||||
|
||||
{/* remove this later */}
|
||||
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
|
||||
|
|
|
@ -10,6 +10,7 @@ import OuterClick from "../../../utils/outerClick";
|
|||
import { useProductStore } from "../../../store/simulation/useProductStore";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePauseButtonStore, usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } = useComparisonProduct();
|
||||
|
@ -22,6 +23,8 @@ const CompareLayOut = () => {
|
|||
const startWidthRef = useRef<number>(0);
|
||||
const startXRef = useRef<number>(0);
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { setIsPaused } = usePauseButtonStore();
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["displayLayouts-container", "selectLayout"],
|
||||
|
@ -64,6 +67,7 @@ const CompareLayOut = () => {
|
|||
setWidth("0px");
|
||||
setIsVersionSaved(false);
|
||||
clearComparisonProduct();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
setWidth(`${finalWidthVw}vw`);
|
||||
}
|
||||
|
@ -109,6 +113,8 @@ const CompareLayOut = () => {
|
|||
if (product) {
|
||||
setComparisonProduct(product.productId, product.productName);
|
||||
setLoadingProgress(1);
|
||||
setIsPlaying(true);
|
||||
setIsPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import ProductionCapacity from "../analysis/ThroughputSummary";
|
|||
import ThroughputSummary from "../analysis/ProductionCapacity";
|
||||
import ROISummary from "../analysis/ROISummary";
|
||||
import { usePlayerStore } from "../../../store/useUIToggleStore";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
|
||||
const SimulationPlayer: React.FC = () => {
|
||||
const MAX_SPEED = 4; // Maximum speed
|
||||
|
@ -31,7 +32,6 @@ const SimulationPlayer: React.FC = () => {
|
|||
const isDragging = useRef(false);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const [expand, setExpand] = useState(true);
|
||||
const [playSimulation, setPlaySimulation] = useState(false);
|
||||
const { hidePlayer, setHidePlayer } = usePlayerStore();
|
||||
|
||||
const { speed, setSpeed } = useAnimationPlaySpeed();
|
||||
|
@ -40,6 +40,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
const { isPaused, setIsPaused } = usePauseButtonStore();
|
||||
const { isReset, setReset } = useResetButtonStore();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { clearComparisonProduct } = useComparisonProduct();
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset) {
|
||||
|
@ -55,17 +56,15 @@ const SimulationPlayer: React.FC = () => {
|
|||
setIsPaused(false);
|
||||
setSpeed(1);
|
||||
echo.info("Simulation RESET.....");
|
||||
setPlaySimulation(false); // local state reset
|
||||
};
|
||||
const handlePlayStop = () => {
|
||||
setIsPaused(!isPaused);
|
||||
echo.warn(`Simulation is ${isPaused ? "Resumed" : "Paused"}`);
|
||||
setPlaySimulation(!playSimulation);
|
||||
};
|
||||
const handleExit = () => {
|
||||
setPlaySimulation(false);
|
||||
setIsPlaying(false);
|
||||
setIsPaused(false);
|
||||
clearComparisonProduct();
|
||||
setActiveTool("cursor");
|
||||
echo.info("Exit Simulation");
|
||||
};
|
||||
|
@ -210,7 +209,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
{!hidePlayer && subModule !== "analysis" && (
|
||||
<div className="header">
|
||||
<InfoIcon />
|
||||
{playSimulation
|
||||
{isPaused
|
||||
? "Paused - system idle."
|
||||
: "Running simulation..."}
|
||||
</div>
|
||||
|
@ -237,7 +236,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<PlayStopIcon />
|
||||
{playSimulation ? "Play" : "Stop"}
|
||||
{isPaused ? "Play" : "Stop"}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
|
@ -298,11 +297,10 @@ const SimulationPlayer: React.FC = () => {
|
|||
</div>
|
||||
{index < intervals.length - 1 && (
|
||||
<div
|
||||
className={`line ${
|
||||
progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
className={`line ${progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
></div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@ -342,9 +340,8 @@ const SimulationPlayer: React.FC = () => {
|
|||
<div className="custom-slider">
|
||||
<button
|
||||
id="slider-handle"
|
||||
className={`slider-handle ${
|
||||
isDragging ? "dragging" : ""
|
||||
}`}
|
||||
className={`slider-handle ${isDragging ? "dragging" : ""
|
||||
}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
|
|
|
@ -51,6 +51,7 @@ import DxfFile from "./dfx/LoadBlueprint";
|
|||
import { useParams } from "react-router-dom";
|
||||
import AislesGroup from "./aisle/aislesGroup";
|
||||
import WallGroup from "./wall/wallGroup";
|
||||
import { useBuilderStore } from "../../store/builder/useBuilderStore";
|
||||
|
||||
export default function Builder() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
|
@ -117,6 +118,7 @@ export default function Builder() {
|
|||
const { setWalls } = useWalls();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
const { projectId } = useParams();
|
||||
const { setHoveredPoint } = useBuilderStore();
|
||||
|
||||
// const loader = new GLTFLoader();
|
||||
// const dracoLoader = new DRACOLoader();
|
||||
|
@ -138,6 +140,7 @@ export default function Builder() {
|
|||
dragPointControls
|
||||
);
|
||||
} else {
|
||||
setHoveredPoint(null);
|
||||
setToolMode(null);
|
||||
setDeletePointOrLine(false);
|
||||
loadWalls(lines, setWalls);
|
||||
|
|
|
@ -46,6 +46,9 @@ function Line({ points }: Readonly<LineProps>) {
|
|||
|
||||
return (
|
||||
<Tube
|
||||
key={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
||||
uuid={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
||||
userData={points}
|
||||
args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
|
||||
>
|
||||
<meshStandardMaterial color={colors.defaultLineColor} />
|
||||
|
|
|
@ -122,6 +122,12 @@ function Point({ point }: { readonly point: Point }) {
|
|||
setHoveredPoint(null);
|
||||
}
|
||||
}
|
||||
if (point.pointType === 'Wall') {
|
||||
const removedAisles = removeWallPoint(point.pointUuid);
|
||||
if (removedAisles.length > 0) {
|
||||
setHoveredPoint(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +151,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||
<mesh
|
||||
key={point.pointUuid}
|
||||
uuid={point.pointUuid}
|
||||
name='Aisle-Point'
|
||||
name={`${point.pointType}-Point`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
onClick={() => {
|
||||
handlePointClick(point);
|
||||
|
|
|
@ -3,168 +3,263 @@ import * as THREE from 'three';
|
|||
import * as turf from '@turf/turf';
|
||||
|
||||
export function useWallClassification(walls: Walls) {
|
||||
// Find all rooms from the given 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>();
|
||||
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);
|
||||
pointMap.set(p1.pointUuid, p1);
|
||||
pointMap.set(p2.pointUuid, p2);
|
||||
|
||||
if (!connections.has(p1.pointUuid)) connections.set(p1.pointUuid, []);
|
||||
if (!connections.has(p2.pointUuid)) connections.set(p2.pointUuid, []);
|
||||
// Add connection from p1 to p2
|
||||
if (!graph.has(p1.pointUuid)) graph.set(p1.pointUuid, []);
|
||||
graph.get(p1.pointUuid)?.push(p2.pointUuid);
|
||||
|
||||
connections.get(p1.pointUuid)?.push(p2.pointUuid);
|
||||
connections.get(p2.pointUuid)?.push(p1.pointUuid);
|
||||
// Add connection from p2 to p1
|
||||
if (!graph.has(p2.pointUuid)) graph.set(p2.pointUuid, []);
|
||||
graph.get(p2.pointUuid)?.push(p1.pointUuid);
|
||||
});
|
||||
console.log('connections: ', connections);
|
||||
|
||||
// Find all minimal cycles (rooms) in the graph
|
||||
const allCycles: string[][] = [];
|
||||
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 findCycles = (startNode: string, currentNode: string, path: string[], depth = 0) => {
|
||||
if (depth > 20) return; // Prevent infinite recursion
|
||||
|
||||
visited.add(currentNode);
|
||||
path.push(currentNode);
|
||||
|
||||
const neighbors = graph.get(currentNode) || [];
|
||||
|
||||
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)!);
|
||||
if (path.length > 2 && neighbor === startNode) {
|
||||
// Found a cycle that returns to start
|
||||
const cycle = [...path, neighbor];
|
||||
|
||||
// Check if this room is valid and not already found
|
||||
if (isValidRoom(roomPoints) && !roomAlreadyExists(rooms, roomPoints)) {
|
||||
rooms.push(roomPoints);
|
||||
// Check if this is a new unique cycle
|
||||
if (!cycleExists(allCycles, cycle)) {
|
||||
allCycles.push(cycle);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.includes(neighbor)) {
|
||||
findAllCycles(neighbor, [...path]);
|
||||
findCycles(startNode, neighbor, [...path], depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start from each point to find all possible cycles
|
||||
for (const [pointUuid] of connections) {
|
||||
if (!visited.has(pointUuid)) {
|
||||
findAllCycles(pointUuid, []);
|
||||
}
|
||||
// Start cycle detection from each node
|
||||
for (const [pointUuid] of graph) {
|
||||
findCycles(pointUuid, pointUuid, []);
|
||||
}
|
||||
|
||||
return rooms;
|
||||
// Convert cycles to Point arrays and validate them
|
||||
const potentialRooms = allCycles
|
||||
.map(cycle => cycle.map(uuid => pointMap.get(uuid)!))
|
||||
.filter(room => isValidRoom(room));
|
||||
|
||||
// Filter out duplicate rooms (same set of points in different orders)
|
||||
const uniqueRooms = removeDuplicateRooms(potentialRooms);
|
||||
|
||||
return uniqueRooms;
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
// Check if a cycle already exists in our list (considering different orders)
|
||||
const cycleExists = (allCycles: string[][], newCycle: string[]) => {
|
||||
const newSet = new Set(newCycle);
|
||||
|
||||
// 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;
|
||||
return allCycles.some(existingCycle => {
|
||||
if (existingCycle.length !== newCycle.length) return false;
|
||||
const existingSet = new Set(existingCycle);
|
||||
return setsEqual(newSet, existingSet);
|
||||
});
|
||||
};
|
||||
|
||||
const rooms = useMemo(() => findRooms(), [walls]);
|
||||
// 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;
|
||||
};
|
||||
|
||||
// Modified to track all rooms a wall belongs to
|
||||
const getWallOrientation = (wall: Wall) => {
|
||||
const [p1, p2] = wall.points;
|
||||
const orientations: Array<{ isOutsideFacing: boolean }> = [];
|
||||
// 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]);
|
||||
|
||||
const createPolygon = (points: Point[]) => {
|
||||
const coordinates = points.map(p => [p.position[0], p.position[2]]);
|
||||
return turf.polygon([coordinates]);
|
||||
};
|
||||
|
||||
|
||||
// Get all walls that form part of any room
|
||||
const getRoomWalls = (): Wall[] => {
|
||||
const roomWalls = new Set<Wall>();
|
||||
|
||||
rooms.forEach(room => {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const roomP1 = room[i];
|
||||
const roomP2 = room[i + 1];
|
||||
const p1 = room[i];
|
||||
const p2 = room[i + 1];
|
||||
|
||||
// Check both directions
|
||||
if ((p1.pointUuid === roomP1.pointUuid && p2.pointUuid === roomP2.pointUuid) ||
|
||||
(p1.pointUuid === roomP2.pointUuid && p2.pointUuid === roomP1.pointUuid)) {
|
||||
// Find the wall that connects these two points
|
||||
const wall = walls.find(w =>
|
||||
(w.points[0].pointUuid === p1.pointUuid && w.points[1].pointUuid === p2.pointUuid) ||
|
||||
(w.points[0].pointUuid === p2.pointUuid && w.points[1].pointUuid === p1.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);
|
||||
if (wall) roomWalls.add(wall);
|
||||
}
|
||||
});
|
||||
|
||||
const wallVector = new THREE.Vector3(
|
||||
p2.position[0] - p1.position[0],
|
||||
0,
|
||||
p2.position[2] - p1.position[2]
|
||||
);
|
||||
return Array.from(roomWalls);
|
||||
};
|
||||
|
||||
// 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();
|
||||
}
|
||||
// Determine wall orientation relative to room
|
||||
const getWallOrientation = (wall: Wall) => {
|
||||
const roomWalls = getRoomWalls();
|
||||
const isRoomWall = roomWalls.includes(wall);
|
||||
|
||||
const testPoint = new THREE.Vector3(
|
||||
(p1.position[0] + p2.position[0]) / 2 + normal.x,
|
||||
0,
|
||||
(p1.position[2] + p2.position[2]) / 2 + normal.z
|
||||
);
|
||||
if (!isRoomWall) {
|
||||
return {
|
||||
isRoomWall: false,
|
||||
isOutsideFacing: false
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
// Find which room this wall belongs to
|
||||
const containingRoom = rooms.find(room => {
|
||||
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) ||
|
||||
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!containingRoom) {
|
||||
return {
|
||||
isRoomWall: false,
|
||||
isOutsideFacing: false
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the normal vector to determine outside direction
|
||||
const roomPoints = containingRoom.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 [p1, p2] = wall.points;
|
||||
const wallVector = new THREE.Vector3(
|
||||
p2.position[0] - p1.position[0],
|
||||
0,
|
||||
p2.position[2] - p1.position[2]
|
||||
);
|
||||
const normal = new THREE.Vector3(-wallVector.z, 0, wallVector.x).normalize();
|
||||
|
||||
// Check if normal points away from centroid (outside)
|
||||
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]),
|
||||
createPolygon(containingRoom)
|
||||
);
|
||||
|
||||
// Determine if the wall is in the same order as the room's edge
|
||||
let isSameOrder = false;
|
||||
for (let i = 0; i < containingRoom.length - 1; i++) {
|
||||
const roomP1 = containingRoom[i];
|
||||
const roomP2 = containingRoom[i + 1];
|
||||
if (p1.pointUuid === roomP1.pointUuid && p2.pointUuid === roomP2.pointUuid) {
|
||||
isSameOrder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isRoomWall: orientations.length > 0,
|
||||
orientations // Now tracks all orientations for walls in multiple rooms
|
||||
isRoomWall: true,
|
||||
isOutsideFacing: isSameOrder ? !pointInside : pointInside
|
||||
};
|
||||
};
|
||||
|
||||
const getWallMaterialSide = (wall: Wall) => {
|
||||
// Rest of the functions remain the same...
|
||||
const getWallType = (wall: Wall): 'room' | 'segment' => {
|
||||
return getWallOrientation(wall).isRoomWall ? 'room' : 'segment';
|
||||
};
|
||||
|
||||
const isRoomWall = (wall: Wall): boolean => {
|
||||
return getWallOrientation(wall).isRoomWall;
|
||||
};
|
||||
|
||||
const isSegmentWall = (wall: Wall): boolean => {
|
||||
return !getWallOrientation(wall).isRoomWall;
|
||||
};
|
||||
|
||||
const getWallMaterialSide = (wall: Wall): { front: 'inside' | 'outside', back: 'inside' | 'outside' } => {
|
||||
const orientation = getWallOrientation(wall);
|
||||
|
||||
if (!orientation.isRoomWall) {
|
||||
return { front: 'inside', back: 'inside' }; // Both sides same for segment walls
|
||||
return { front: 'inside', back: 'outside' };
|
||||
}
|
||||
|
||||
// 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' };
|
||||
return orientation.isOutsideFacing
|
||||
? { front: 'outside', back: 'inside' }
|
||||
: { front: 'inside', back: 'outside' };
|
||||
};
|
||||
|
||||
// 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,
|
||||
|
|
|
@ -41,7 +41,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||
outside.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
return [inside, outside];
|
||||
}, [wallLength, wall.wallHeight, textureLoader]);
|
||||
}, [wallLength, wall.wallHeight]);
|
||||
|
||||
const materials = useMemo(() => {
|
||||
// For segment walls (not in a room), use inside material on both sides
|
||||
|
@ -89,4 +89,21 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||
);
|
||||
}
|
||||
|
||||
export default Wall;
|
||||
export default Wall;
|
||||
|
||||
|
||||
// A--------------------------B--------------G
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// F--------------------------C--------------H
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// E--------------------------D
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useWallStore } from '../../../../store/builder/useWallStore'
|
||||
import WallInstance from './instance/wallInstance';
|
||||
import Line from '../../line/line';
|
||||
|
@ -14,25 +14,47 @@ function WallInstances() {
|
|||
// console.log('walls: ', walls);
|
||||
}, [walls])
|
||||
|
||||
const allPoints = useMemo(() => {
|
||||
const points: Point[] = [];
|
||||
const seenUuids = new Set<string>();
|
||||
|
||||
walls.forEach(aisle => {
|
||||
aisle.points.forEach(point => {
|
||||
if (!seenUuids.has(point.pointUuid)) {
|
||||
seenUuids.add(point.pointUuid);
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return points;
|
||||
}, [walls]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<Geometry computeVertexNormals useGroups>
|
||||
{walls.map((wall) => (
|
||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||
))}
|
||||
</Geometry>
|
||||
|
||||
<group name='Walls-Group'>
|
||||
<Geometry computeVertexNormals useGroups>
|
||||
{walls.map((wall) => (
|
||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||
))}
|
||||
</Geometry>
|
||||
</group>
|
||||
|
||||
{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>
|
||||
))}
|
||||
<group name='Wall-Points-Group'>
|
||||
{allPoints.map((point) => (
|
||||
<Point key={point.pointUuid} point={point} />
|
||||
))}
|
||||
</group>
|
||||
|
||||
<group name='Wall-Lines-Group'>
|
||||
{walls.map((wall) => (
|
||||
<React.Fragment key={wall.wallUuid}>
|
||||
<Line points={wall.points} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</group>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -52,7 +52,7 @@ function WallCreator() {
|
|||
let position = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (!position) return;
|
||||
|
||||
const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Aisle-Point');
|
||||
const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Point');
|
||||
|
||||
const newPoint: Point = {
|
||||
pointUuid: THREE.MathUtils.generateUUID(),
|
||||
|
@ -137,7 +137,7 @@ function WallCreator() {
|
|||
<>
|
||||
{toggleView &&
|
||||
<>
|
||||
<group name='Aisle-Reference-Points-Group'>
|
||||
<group name='Wall-Reference-Points-Group'>
|
||||
{tempPoints.map((point) => (
|
||||
<ReferencePoint key={point.pointUuid} point={point} />
|
||||
))}
|
||||
|
|
Loading…
Reference in New Issue