v3-ui #98

Merged
Vishnu merged 51 commits from v3-ui into main 2025-06-10 06:46:08 +00:00
11 changed files with 296 additions and 143 deletions
Showing only changes of commit de19ea9f83 - Show all commits

View File

@ -6,21 +6,24 @@ import useModuleStore from '../../../store/useModuleStore';
import CompareLayOut from '../../ui/compareVersion/CompareLayOut'; import CompareLayOut from '../../ui/compareVersion/CompareLayOut';
import ComparisonResult from '../../ui/compareVersion/ComparisonResult'; import ComparisonResult from '../../ui/compareVersion/ComparisonResult';
import { useComparisonProduct } from '../../../store/simulation/useSimulationStore'; import { useComparisonProduct } from '../../../store/simulation/useSimulationStore';
import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; import { usePauseButtonStore, usePlayButtonStore } from '../../../store/usePlayButtonStore';
function ComparisonScene() { function ComparisonScene() {
const { isPlaying } = usePlayButtonStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { products } = useProductStore(); const { products } = useProductStore();
const { isVersionSaved } = useSaveVersion(); const { isVersionSaved } = useSaveVersion();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { comparisonProduct, setComparisonProduct } = useComparisonProduct(); const { comparisonProduct, setComparisonProduct } = useComparisonProduct();
const { setIsPaused } = usePauseButtonStore();
const handleSelectLayout = (option: string) => { const handleSelectLayout = (option: string) => {
const product = products.find((product) => product.productName === option); const product = products.find((product) => product.productName === option);
if (product) { if (product) {
setComparisonProduct(product.productId, product.productName); setComparisonProduct(product.productId, product.productName);
setIsPlaying(true);
setIsPaused(true);
} }
}; };
return ( return (

View File

@ -18,7 +18,7 @@ import ControlsPlayer from '../controls/ControlsPlayer';
import SelectFloorPlan from '../../temporary/SelectFloorPlan'; import SelectFloorPlan from '../../temporary/SelectFloorPlan';
import { createHandleDrop } from '../../../modules/visualization/functions/handleUiDrop'; import { createHandleDrop } from '../../../modules/visualization/functions/handleUiDrop';
import Scene from '../../../modules/scene/scene'; 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 { useProductContext } from '../../../modules/simulation/products/productContext';
import { useProductStore } from '../../../store/simulation/useProductStore'; import { useProductStore } from '../../../store/simulation/useProductStore';
import RegularDropDown from '../../ui/inputs/RegularDropDown'; import RegularDropDown from '../../ui/inputs/RegularDropDown';
@ -38,6 +38,7 @@ function MainScene() {
const { visualizationSocket } = useSocketStore(); const { visualizationSocket } = useSocketStore();
const { selectedZone } = useSelectedZoneStore(); const { selectedZone } = useSelectedZoneStore();
const { setFloatingWidget } = useFloatingWidget(); const { setFloatingWidget } = useFloatingWidget();
const { comparisonProduct } = useComparisonProduct();
const handleSelectLayout = (option: string) => { const handleSelectLayout = (option: string) => {
const product = products.find((product) => product.productName === option); const product = products.find((product) => product.productName === option);
@ -64,8 +65,8 @@ function MainScene() {
{activeModule !== "market" && !isPlaying && !isVersionSaved && ( {activeModule !== "market" && !isPlaying && !isVersionSaved && (
<Tools /> <Tools />
)} )}
{isPlaying && activeModule === "simulation" && <SimulationPlayer />} {(isPlaying || comparisonProduct !== null) && activeModule === "simulation" && <SimulationPlayer />}
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />} {(isPlaying || comparisonProduct !== null) && activeModule !== "simulation" && <ControlsPlayer />}
{/* remove this later */} {/* remove this later */}
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />} {activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}

View File

@ -10,6 +10,7 @@ import OuterClick from "../../../utils/outerClick";
import { useProductStore } from "../../../store/simulation/useProductStore"; import { useProductStore } from "../../../store/simulation/useProductStore";
import Scene from "../../../modules/scene/scene"; import Scene from "../../../modules/scene/scene";
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore"; import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
import { usePauseButtonStore, usePlayButtonStore } from "../../../store/usePlayButtonStore";
const CompareLayOut = () => { const CompareLayOut = () => {
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } = useComparisonProduct(); const { comparisonProduct, setComparisonProduct, clearComparisonProduct } = useComparisonProduct();
@ -22,6 +23,8 @@ const CompareLayOut = () => {
const startWidthRef = useRef<number>(0); const startWidthRef = useRef<number>(0);
const startXRef = useRef<number>(0); const startXRef = useRef<number>(0);
const { setIsVersionSaved } = useSaveVersion(); const { setIsVersionSaved } = useSaveVersion();
const { setIsPlaying } = usePlayButtonStore();
const { setIsPaused } = usePauseButtonStore();
OuterClick({ OuterClick({
contextClassName: ["displayLayouts-container", "selectLayout"], contextClassName: ["displayLayouts-container", "selectLayout"],
@ -64,6 +67,7 @@ const CompareLayOut = () => {
setWidth("0px"); setWidth("0px");
setIsVersionSaved(false); setIsVersionSaved(false);
clearComparisonProduct(); clearComparisonProduct();
setIsPlaying(false);
} else { } else {
setWidth(`${finalWidthVw}vw`); setWidth(`${finalWidthVw}vw`);
} }
@ -109,6 +113,8 @@ const CompareLayOut = () => {
if (product) { if (product) {
setComparisonProduct(product.productId, product.productName); setComparisonProduct(product.productId, product.productName);
setLoadingProgress(1); setLoadingProgress(1);
setIsPlaying(true);
setIsPaused(true);
} }
}; };

View File

@ -24,6 +24,7 @@ import ProductionCapacity from "../analysis/ThroughputSummary";
import ThroughputSummary from "../analysis/ProductionCapacity"; import ThroughputSummary from "../analysis/ProductionCapacity";
import ROISummary from "../analysis/ROISummary"; import ROISummary from "../analysis/ROISummary";
import { usePlayerStore } from "../../../store/useUIToggleStore"; import { usePlayerStore } from "../../../store/useUIToggleStore";
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
const SimulationPlayer: React.FC = () => { const SimulationPlayer: React.FC = () => {
const MAX_SPEED = 4; // Maximum speed const MAX_SPEED = 4; // Maximum speed
@ -31,7 +32,6 @@ const SimulationPlayer: React.FC = () => {
const isDragging = useRef(false); const isDragging = useRef(false);
const sliderRef = useRef<HTMLDivElement>(null); const sliderRef = useRef<HTMLDivElement>(null);
const [expand, setExpand] = useState(true); const [expand, setExpand] = useState(true);
const [playSimulation, setPlaySimulation] = useState(false);
const { hidePlayer, setHidePlayer } = usePlayerStore(); const { hidePlayer, setHidePlayer } = usePlayerStore();
const { speed, setSpeed } = useAnimationPlaySpeed(); const { speed, setSpeed } = useAnimationPlaySpeed();
@ -40,6 +40,7 @@ const SimulationPlayer: React.FC = () => {
const { isPaused, setIsPaused } = usePauseButtonStore(); const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore(); const { isReset, setReset } = useResetButtonStore();
const { subModule } = useSubModuleStore(); const { subModule } = useSubModuleStore();
const { clearComparisonProduct } = useComparisonProduct();
useEffect(() => { useEffect(() => {
if (isReset) { if (isReset) {
@ -55,17 +56,15 @@ const SimulationPlayer: React.FC = () => {
setIsPaused(false); setIsPaused(false);
setSpeed(1); setSpeed(1);
echo.info("Simulation RESET....."); echo.info("Simulation RESET.....");
setPlaySimulation(false); // local state reset
}; };
const handlePlayStop = () => { const handlePlayStop = () => {
setIsPaused(!isPaused); setIsPaused(!isPaused);
echo.warn(`Simulation is ${isPaused ? "Resumed" : "Paused"}`); echo.warn(`Simulation is ${isPaused ? "Resumed" : "Paused"}`);
setPlaySimulation(!playSimulation);
}; };
const handleExit = () => { const handleExit = () => {
setPlaySimulation(false);
setIsPlaying(false); setIsPlaying(false);
setIsPaused(false); setIsPaused(false);
clearComparisonProduct();
setActiveTool("cursor"); setActiveTool("cursor");
echo.info("Exit Simulation"); echo.info("Exit Simulation");
}; };
@ -210,7 +209,7 @@ const SimulationPlayer: React.FC = () => {
{!hidePlayer && subModule !== "analysis" && ( {!hidePlayer && subModule !== "analysis" && (
<div className="header"> <div className="header">
<InfoIcon /> <InfoIcon />
{playSimulation {isPaused
? "Paused - system idle." ? "Paused - system idle."
: "Running simulation..."} : "Running simulation..."}
</div> </div>
@ -237,7 +236,7 @@ const SimulationPlayer: React.FC = () => {
}} }}
> >
<PlayStopIcon /> <PlayStopIcon />
{playSimulation ? "Play" : "Stop"} {isPaused ? "Play" : "Stop"}
</button> </button>
)} )}
<button <button
@ -298,11 +297,10 @@ const SimulationPlayer: React.FC = () => {
</div> </div>
{index < intervals.length - 1 && ( {index < intervals.length - 1 && (
<div <div
className={`line ${ className={`line ${progress >= ((index + 1) / totalSegments) * 100
progress >= ((index + 1) / totalSegments) * 100 ? "filled"
? "filled" : ""
: "" }`}
}`}
></div> ></div>
)} )}
</React.Fragment> </React.Fragment>
@ -342,9 +340,8 @@ const SimulationPlayer: React.FC = () => {
<div className="custom-slider"> <div className="custom-slider">
<button <button
id="slider-handle" id="slider-handle"
className={`slider-handle ${ className={`slider-handle ${isDragging ? "dragging" : ""
isDragging ? "dragging" : "" }`}
}`}
style={{ left: `${calculateHandlePosition()}%` }} style={{ left: `${calculateHandlePosition()}%` }}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
> >

View File

@ -51,6 +51,7 @@ import DxfFile from "./dfx/LoadBlueprint";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import AislesGroup from "./aisle/aislesGroup"; import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup"; import WallGroup from "./wall/wallGroup";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
export default function Builder() { 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. 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 { setWalls } = useWalls();
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate(); const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
const { projectId } = useParams(); const { projectId } = useParams();
const { setHoveredPoint } = useBuilderStore();
// const loader = new GLTFLoader(); // const loader = new GLTFLoader();
// const dracoLoader = new DRACOLoader(); // const dracoLoader = new DRACOLoader();
@ -138,6 +140,7 @@ export default function Builder() {
dragPointControls dragPointControls
); );
} else { } else {
setHoveredPoint(null);
setToolMode(null); setToolMode(null);
setDeletePointOrLine(false); setDeletePointOrLine(false);
loadWalls(lines, setWalls); loadWalls(lines, setWalls);

View File

@ -46,6 +46,9 @@ function Line({ points }: Readonly<LineProps>) {
return ( return (
<Tube <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]} args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
> >
<meshStandardMaterial color={colors.defaultLineColor} /> <meshStandardMaterial color={colors.defaultLineColor} />

View File

@ -122,6 +122,12 @@ function Point({ point }: { readonly point: Point }) {
setHoveredPoint(null); 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 <mesh
key={point.pointUuid} key={point.pointUuid}
uuid={point.pointUuid} uuid={point.pointUuid}
name='Aisle-Point' name={`${point.pointType}-Point`}
position={new THREE.Vector3(...point.position)} position={new THREE.Vector3(...point.position)}
onClick={() => { onClick={() => {
handlePointClick(point); handlePointClick(point);

View File

@ -3,168 +3,263 @@ import * as THREE from 'three';
import * as turf from '@turf/turf'; import * as turf from '@turf/turf';
export function useWallClassification(walls: Walls) { export function useWallClassification(walls: Walls) {
// Find all rooms from the given walls // Find all minimal rooms from the given walls
const findRooms = () => { const findRooms = () => {
if (walls.length < 3) return []; if (walls.length < 3) return [];
// Build a graph of point connections
const graph = new Map<string, string[]>();
const pointMap = new Map<string, Point>(); const pointMap = new Map<string, Point>();
const connections = new Map<string, string[]>();
// Build connection graph
walls.forEach(wall => { walls.forEach(wall => {
const [p1, p2] = wall.points; const [p1, p2] = wall.points;
if (!pointMap.has(p1.pointUuid)) pointMap.set(p1.pointUuid, p1); pointMap.set(p1.pointUuid, p1);
if (!pointMap.has(p2.pointUuid)) pointMap.set(p2.pointUuid, p2); pointMap.set(p2.pointUuid, p2);
if (!connections.has(p1.pointUuid)) connections.set(p1.pointUuid, []); // Add connection from p1 to p2
if (!connections.has(p2.pointUuid)) connections.set(p2.pointUuid, []); if (!graph.has(p1.pointUuid)) graph.set(p1.pointUuid, []);
graph.get(p1.pointUuid)?.push(p2.pointUuid);
connections.get(p1.pointUuid)?.push(p2.pointUuid); // Add connection from p2 to p1
connections.get(p2.pointUuid)?.push(p1.pointUuid); 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 visited = new Set<string>();
const rooms: Point[][] = [];
// Modified DFS to find all possible cycles const findCycles = (startNode: string, currentNode: string, path: string[], depth = 0) => {
const findAllCycles = (current: string, path: string[]) => { if (depth > 20) return; // Prevent infinite recursion
visited.add(current);
path.push(current); visited.add(currentNode);
path.push(currentNode);
const neighbors = graph.get(currentNode) || [];
const neighbors = connections.get(current) || [];
for (const neighbor of neighbors) { for (const neighbor of neighbors) {
if (path.length > 2 && neighbor === path[0]) { if (path.length > 2 && neighbor === startNode) {
// Found a cycle (potential room) // Found a cycle that returns to start
const roomPoints = [...path, neighbor].map(uuid => pointMap.get(uuid)!); const cycle = [...path, neighbor];
// Check if this room is valid and not already found // Check if this is a new unique cycle
if (isValidRoom(roomPoints) && !roomAlreadyExists(rooms, roomPoints)) { if (!cycleExists(allCycles, cycle)) {
rooms.push(roomPoints); allCycles.push(cycle);
} }
continue; continue;
} }
if (!path.includes(neighbor)) { if (!path.includes(neighbor)) {
findAllCycles(neighbor, [...path]); findCycles(startNode, neighbor, [...path], depth + 1);
} }
} }
}; };
// Start from each point to find all possible cycles // Start cycle detection from each node
for (const [pointUuid] of connections) { for (const [pointUuid] of graph) {
if (!visited.has(pointUuid)) { findCycles(pointUuid, pointUuid, []);
findAllCycles(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 // Check if a cycle already exists in our list (considering different orders)
const isValidRoom = (roomPoints: Point[]) => { const cycleExists = (allCycles: string[][], newCycle: string[]) => {
if (roomPoints.length < 4) return false; const newSet = new Set(newCycle);
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 return allCycles.some(existingCycle => {
const roomAlreadyExists = (existingRooms: Point[][], newRoom: Point[]) => { if (existingCycle.length !== newCycle.length) return false;
const newRoomIds = newRoom.map(p => p.pointUuid).sort().join('-'); const existingSet = new Set(existingCycle);
return existingRooms.some(room => { return setsEqual(newSet, existingSet);
const roomIds = room.map(p => p.pointUuid).sort().join('-');
return roomIds === newRoomIds;
}); });
}; };
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 // Remove duplicate rooms (same set of points in different orders)
const getWallOrientation = (wall: Wall) => { const removeDuplicateRooms = (rooms: Point[][]) => {
const [p1, p2] = wall.points; const uniqueRooms: Point[][] = [];
const orientations: Array<{ isOutsideFacing: boolean }> = []; const roomHashes = new Set<string>();
for (const room of rooms) { 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++) { for (let i = 0; i < room.length - 1; i++) {
const roomP1 = room[i]; const p1 = room[i];
const roomP2 = room[i + 1]; const p2 = room[i + 1];
// Check both directions // Find the wall that connects these two points
if ((p1.pointUuid === roomP1.pointUuid && p2.pointUuid === roomP2.pointUuid) || const wall = walls.find(w =>
(p1.pointUuid === roomP2.pointUuid && p2.pointUuid === roomP1.pointUuid)) { (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])); if (wall) roomWalls.add(wall);
const centroid = new THREE.Vector3(); }
roomPoints.forEach(p => centroid.add(p)); });
centroid.divideScalar(roomPoints.length);
const wallVector = new THREE.Vector3( return Array.from(roomWalls);
p2.position[0] - p1.position[0], };
0,
p2.position[2] - p1.position[2]
);
// Normal depends on wall direction // Determine wall orientation relative to room
let normal = new THREE.Vector3(-wallVector.z, 0, wallVector.x).normalize(); const getWallOrientation = (wall: Wall) => {
if (p1.pointUuid === roomP2.pointUuid) { const roomWalls = getRoomWalls();
normal = new THREE.Vector3(wallVector.z, 0, -wallVector.x).normalize(); const isRoomWall = roomWalls.includes(wall);
}
const testPoint = new THREE.Vector3( if (!isRoomWall) {
(p1.position[0] + p2.position[0]) / 2 + normal.x, return {
0, isRoomWall: false,
(p1.position[2] + p2.position[2]) / 2 + normal.z isOutsideFacing: false
); };
}
const pointInside = turf.booleanPointInPolygon( // Find which room this wall belongs to
turf.point([testPoint.x, testPoint.z]), const containingRoom = rooms.find(room => {
turf.polygon([room.map(p => [p.position[0], p.position[2]])]) for (let i = 0; i < room.length - 1; i++) {
); const p1 = room[i];
const p2 = room[i + 1];
orientations.push({ if ((wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) ||
isOutsideFacing: !pointInside (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 { return {
isRoomWall: orientations.length > 0, isRoomWall: true,
orientations // Now tracks all orientations for walls in multiple rooms 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); const orientation = getWallOrientation(wall);
if (!orientation.isRoomWall) { 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 return orientation.isOutsideFacing
if (orientation.orientations.length === 2) { ? { front: 'outside', back: 'inside' }
// Wall is between two rooms - one side faces each room's interior : { front: 'inside', back: 'outside' };
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 { return {
rooms, rooms,
getWallType, getWallType,

View File

@ -41,7 +41,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
outside.colorSpace = THREE.SRGBColorSpace; outside.colorSpace = THREE.SRGBColorSpace;
return [inside, outside]; return [inside, outside];
}, [wallLength, wall.wallHeight, textureLoader]); }, [wallLength, wall.wallHeight]);
const materials = useMemo(() => { const materials = useMemo(() => {
// For segment walls (not in a room), use inside material on both sides // 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

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useEffect, useMemo } 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 Line from '../../line/line';
@ -14,25 +14,47 @@ function WallInstances() {
// console.log('walls: ', walls); // console.log('walls: ', 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 ( return (
<> <>
<group name='Walls-Group'>
<Geometry computeVertexNormals useGroups> <Geometry computeVertexNormals useGroups>
{walls.map((wall) => ( {walls.map((wall) => (
<WallInstance key={wall.wallUuid} wall={wall} /> <WallInstance key={wall.wallUuid} wall={wall} />
))} ))}
</Geometry> </Geometry>
</group>
{toggleView && ( {toggleView && (
<> <>
{walls.map((wall) => ( <group name='Wall-Points-Group'>
<React.Fragment key={wall.wallUuid}> {allPoints.map((point) => (
<Point point={wall.points[0]} /> <Point key={point.pointUuid} point={point} />
<Line points={wall.points} /> ))}
<Point point={wall.points[1]} /> </group>
</React.Fragment>
))} <group name='Wall-Lines-Group'>
{walls.map((wall) => (
<React.Fragment key={wall.wallUuid}>
<Line points={wall.points} />
</React.Fragment>
))}
</group>
</> </>
)} )}
</> </>

View File

@ -52,7 +52,7 @@ function WallCreator() {
let position = raycaster.ray.intersectPlane(plane, intersectionPoint); let position = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (!position) return; 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 = { const newPoint: Point = {
pointUuid: THREE.MathUtils.generateUUID(), pointUuid: THREE.MathUtils.generateUUID(),
@ -137,7 +137,7 @@ function WallCreator() {
<> <>
{toggleView && {toggleView &&
<> <>
<group name='Aisle-Reference-Points-Group'> <group name='Wall-Reference-Points-Group'>
{tempPoints.map((point) => ( {tempPoints.map((point) => (
<ReferencePoint key={point.pointUuid} point={point} /> <ReferencePoint key={point.pointUuid} point={point} />
))} ))}