Refactor wall classification logic for improved performance and readability; optimize wall merging and polygon generation. Update wall and zone creators to handle snapping and point connections more effectively. Enhance camera switch view functionality and clean up shortcut key handling.

This commit is contained in:
2025-09-08 15:07:51 +05:30
parent 23e7ba39e8
commit da6400ea67
13 changed files with 460 additions and 514 deletions

View File

@@ -1,13 +1,5 @@
import React from "react"; import React from "react";
import { import { DocumentationIcon, HelpIcon, HomeIcon, LogoutIcon, NotificationIcon, ProjectsIcon, TutorialsIcon } from "../icons/DashboardIcon";
DocumentationIcon,
HelpIcon,
HomeIcon,
LogoutIcon,
NotificationIcon,
ProjectsIcon,
TutorialsIcon,
} from "../icons/DashboardIcon";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import darkThemeImage from "../../assets/image/darkThemeProject.png"; import darkThemeImage from "../../assets/image/darkThemeProject.png";
import lightThemeImage from "../../assets/image/lightThemeProject.png"; import lightThemeImage from "../../assets/image/lightThemeProject.png";
@@ -54,7 +46,6 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
projectUuid: projectId, projectUuid: projectId,
}; };
console.log('addProject: ', addProject);
projectSocket.emit("v1:project:add", addProject); projectSocket.emit("v1:project:add", addProject);
} else { } else {
// API // API
@@ -72,11 +63,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="side-pannel-header"> <div className="side-pannel-header">
<div className="user-container"> <div className="user-container">
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div> <div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
<div className="user-name"> <div className="user-name">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"}</div>
{userName
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
: "Anonymous"}
</div>
</div> </div>
<div className="notifications-container"> <div className="notifications-container">
<NotificationIcon /> <NotificationIcon />
@@ -87,26 +74,15 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
</div> </div>
<div className="side-bar-content-container"> <div className="side-bar-content-container">
<div className="side-bar-options-container"> <div className="side-bar-options-container">
<button <button className={activeTab === "Home" ? "option-list active" : "option-list"} onClick={() => setActiveTab("Home")}>
className={activeTab === "Home" ? "option-list active" : "option-list"}
onClick={() => setActiveTab("Home")}
>
<HomeIcon /> <HomeIcon />
Home Home
</button> </button>
<button <button className={activeTab === "Projects" ? "option-list active" : "option-list"} title="Projects" onClick={() => setActiveTab("Projects")}>
className={activeTab === "Projects" ? "option-list active" : "option-list"}
title="Projects"
onClick={() => setActiveTab("Projects")}
>
<ProjectsIcon /> <ProjectsIcon />
Projects Projects
</button> </button>
<button <button className={activeTab === "Trash" ? "option-list active" : "option-list"} title="Trash" onClick={() => setActiveTab("Trash")}>
className={activeTab === "Trash" ? "option-list active" : "option-list"}
title="Trash"
onClick={() => setActiveTab("Trash")}
>
<TrashIcon /> <TrashIcon />
Trash Trash
</button> </button>
@@ -123,9 +99,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
Tutorials Tutorials
</button> </button>
<button <button
className={ className={activeTab === "Documentation" ? "option-list active" : "option-list"}
activeTab === "Documentation" ? "option-list active" : "option-list"
}
title="coming soon" title="coming soon"
disabled disabled
onClick={() => { onClick={() => {

View File

@@ -19,7 +19,7 @@ function AisleCreator() {
const { activeLayer } = useActiveLayer(); const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext(); const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addAisle, getAislePointById } = aisleStore(); const { addAisle, getAislePointById, getConnectedPoints } = aisleStore();
const { push2D } = undoRedo2DStore(); const { push2D } = undoRedo2DStore();
const drag = useRef(false); const drag = useRef(false);
const isLeftMouseDown = useRef(false); const isLeftMouseDown = useRef(false);
@@ -76,7 +76,9 @@ function AisleCreator() {
newPoint.layer = snappedPoint.layer; newPoint.layer = snappedPoint.layer;
} }
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return; return;
} }
@@ -194,7 +196,32 @@ function AisleCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint, selectedVersion?.versionId]); }, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addAisle,
getAislePointById,
aisleType,
aisleWidth,
aisleColor,
dashLength,
gapLength,
dotRadius,
aisleLength,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return ( return (
<> <>

View File

@@ -86,6 +86,10 @@ function FloorCreator() {
return; return;
} }
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) { if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition; newPoint.position = snappedPosition;
} }
@@ -288,7 +292,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]); }, [camera, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
return ( return (
<> <>

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three'; import * as THREE from "three";
import { useCallback } from 'react'; import { useCallback } from "react";
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from "../../../scene/sceneContext";
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
@@ -10,7 +10,7 @@ const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping i
const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not
export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => { export const usePointSnapping = (currentPoint: { uuid: string; pointType: string; position: [number, number, number] } | null) => {
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore(); const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore(); const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
@@ -21,34 +21,29 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const getAllOtherWallPoints = useCallback(() => { const getAllOtherWallPoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return walls.flatMap(wall => return walls.flatMap((wall) => wall.points.filter((point) => point.pointUuid !== currentPoint.uuid));
wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [walls, currentPoint]); }, [walls, currentPoint]);
const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => { const snapWallPoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherWallPoints(); const otherPoints = getAllOtherWallPoints();
if (tempPoints) {
otherPoints.push(tempPoints);
}
const currentVec = new THREE.Vector3(...position); const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) { for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec); const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Wall") {
return { position: point.position, isSnapped: true, snappedPoint: point }; return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} }
return { position: position, isSnapped: false, snappedPoint: null }; return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherWallPoints]); },
[currentPoint, getAllOtherWallPoints]
);
const snapWallAngle = useCallback((newPosition: [number, number, number]): { const snapWallAngle = useCallback(
position: [number, number, number], (newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedWallPoints(currentPoint.uuid); const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
@@ -56,13 +51,13 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: newPosition, position: newPosition,
isSnapped: false, isSnapped: false,
snapSources: [] snapSources: [],
}; };
} }
const newPos = new THREE.Vector3(...newPosition); const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) { for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position); const cPos = new THREE.Vector3(...connectedPoint.position);
@@ -101,21 +96,22 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedAislePoints]); },
[currentPoint, getConnectedAislePoints]
);
// Aisle Snapping // Aisle Snapping
const getAllOtherAislePoints = useCallback(() => { const getAllOtherAislePoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return aisles.flatMap(aisle => return aisles.flatMap((aisle) => aisle.points.filter((point) => point.pointUuid !== currentPoint.uuid));
aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [aisles, currentPoint]); }, [aisles, currentPoint]);
const snapAislePoint = useCallback((position: [number, number, number]) => { const snapAislePoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherAislePoints(); const otherPoints = getAllOtherAislePoints();
@@ -123,19 +119,18 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
for (const point of otherPoints) { for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec); const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Aisle") {
return { position: point.position, isSnapped: true, snappedPoint: point }; return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} }
return { position: position, isSnapped: false, snappedPoint: null }; return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherAislePoints]); },
[currentPoint, getAllOtherAislePoints]
);
const snapAisleAngle = useCallback((newPosition: [number, number, number]): { const snapAisleAngle = useCallback(
position: [number, number, number], (newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedAislePoints(currentPoint.uuid); const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
@@ -143,13 +138,13 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: newPosition, position: newPosition,
isSnapped: false, isSnapped: false,
snapSources: [] snapSources: [],
}; };
} }
const newPos = new THREE.Vector3(...newPosition); const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) { for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position); const cPos = new THREE.Vector3(...connectedPoint.position);
@@ -188,21 +183,22 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedAislePoints]); },
[currentPoint, getConnectedAislePoints]
);
// Floor Snapping // Floor Snapping
const getAllOtherFloorPoints = useCallback(() => { const getAllOtherFloorPoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return floors.flatMap(floor => return floors.flatMap((floor) => floor.points.filter((point) => point.pointUuid !== currentPoint.uuid));
floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [floors, currentPoint]); }, [floors, currentPoint]);
const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => { const snapFloorPoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherFloorPoints(); let otherPoints = getAllOtherFloorPoints();
@@ -215,18 +211,23 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const pointVec = new THREE.Vector3(...point.position); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec); const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Floor") {
return { position: point.position, isSnapped: true, snappedPoint: point }; return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} }
return { position: position, isSnapped: false, snappedPoint: null }; return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherFloorPoints]); },
[currentPoint, getAllOtherFloorPoints]
);
const snapFloorAngle = useCallback((newPosition: [number, number, number]): { const snapFloorAngle = useCallback(
position: [number, number, number], (
isSnapped: boolean, newPosition: [number, number, number]
snapSources: THREE.Vector3[] ): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => { } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
@@ -236,8 +237,8 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
} }
const newPos = new THREE.Vector3(...newPosition); const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) { for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position); const cPos = new THREE.Vector3(...connectedPoint.position);
@@ -275,21 +276,22 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedFloorPoints]); },
[currentPoint, getConnectedFloorPoints]
);
// Zone Snapping // Zone Snapping
const getAllOtherZonePoints = useCallback(() => { const getAllOtherZonePoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return zones.flatMap(zone => return zones.flatMap((zone) => zone.points.filter((point) => point.pointUuid !== currentPoint.uuid));
zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [zones, currentPoint]); }, [zones, currentPoint]);
const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => { const snapZonePoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherZonePoints(); let otherPoints = getAllOtherZonePoints();
@@ -302,18 +304,23 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const pointVec = new THREE.Vector3(...point.position); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec); const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Zone') { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Zone") {
return { position: point.position, isSnapped: true, snappedPoint: point }; return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} }
return { position: position, isSnapped: false, snappedPoint: null }; return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherZonePoints]); },
[currentPoint, getAllOtherZonePoints]
);
const snapZoneAngle = useCallback((newPosition: [number, number, number]): { const snapZoneAngle = useCallback(
position: [number, number, number], (
isSnapped: boolean, newPosition: [number, number, number]
snapSources: THREE.Vector3[] ): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => { } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
@@ -323,8 +330,8 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
} }
const newPos = new THREE.Vector3(...newPosition); const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) { for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position); const cPos = new THREE.Vector3(...connectedPoint.position);
@@ -362,9 +369,11 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedZonePoints]); },
[currentPoint, getConnectedZonePoints]
);
return { return {
snapAislePoint, snapAislePoint,

View File

@@ -1,151 +1,55 @@
import { useMemo } from 'react'; import { useMemo } from "react";
import * as turf from '@turf/turf'; import * as turf from "@turf/turf";
export function useWallClassification(walls: Walls) { export function useWallClassification(walls: Walls) {
const findRooms = () => { const findRooms = () => {
if (walls.length < 3) return []; if (walls.length < 3) return [];
// Map pointUuid to list of connected line segments const wallSet = new Map<string, Wall>();
const pointMap = new Map<string, Wall[]>();
for (const wall of walls) { const makeKey = (p1: Point, p2: Point) => {
for (const point of wall.points) { return [p1.pointUuid, p2.pointUuid].sort().join("-");
const list = pointMap.get(point.pointUuid) || [];
list.push(wall);
pointMap.set(point.pointUuid, list);
}
}
// Create graph of connected walls using pointUuid
const visited = new Set<string>();
const mergedLineStrings = [];
const wallKey = (p1: Point, p2: Point) => `${p1.pointUuid}-${p2.pointUuid}`;
for (const wall of walls) {
const key = wallKey(wall.points[0], wall.points[1]);
if (visited.has(key) || visited.has(wallKey(wall.points[1], wall.points[0]))) continue;
let line: Point[] = [...wall.points];
visited.add(key);
// Try to extend the line forward and backward by matching endpoints
let extended = true;
while (extended) {
extended = false;
const last = line[line.length - 1];
const nextWalls = pointMap.get(last.pointUuid) || [];
for (const next of nextWalls) {
const [n1, n2] = next.points;
const nextKey = wallKey(n1, n2);
if (visited.has(nextKey) || visited.has(wallKey(n2, n1))) continue;
if (n1.pointUuid === last.pointUuid && n2.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n2);
visited.add(nextKey);
extended = true;
break;
} else if (n2.pointUuid === last.pointUuid && n1.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n1);
visited.add(nextKey);
extended = true;
break;
}
}
const first = line[0];
const prevWalls = pointMap.get(first.pointUuid) || [];
for (const prev of prevWalls) {
const [p1, p2] = prev.points;
const prevKey = wallKey(p1, p2);
if (visited.has(prevKey) || visited.has(wallKey(p2, p1))) continue;
if (p1.pointUuid === first.pointUuid && p2.pointUuid !== line[1]?.pointUuid) {
line.unshift(p2);
visited.add(prevKey);
extended = true;
break;
} else if (p2.pointUuid === first.pointUuid && p1.pointUuid !== line[1]?.pointUuid) {
line.unshift(p1);
visited.add(prevKey);
extended = true;
break;
}
}
}
// Create merged LineString
const coords = line.map(p => [p.position[0], p.position[2]]);
mergedLineStrings.push(turf.lineString(coords, {
pointUuids: line.map(p => p.pointUuid)
}));
}
const validLineStrings = mergedLineStrings.map(ls => {
const coords = ls.geometry.coordinates.map(coord => coord.join(','));
if (coords.length < 2) return null;
const start = coords[0];
const end = coords[coords.length - 1];
const middle = coords.slice(1, -1);
const seen = new Set<string>([start, end]);
const filteredMiddle: string[] = [];
for (const point of middle) {
if (!seen.has(point)) {
seen.add(point);
filteredMiddle.push(point);
}
}
const newCoords = [start, ...filteredMiddle, end];
if (newCoords.length >= 4) {
const resultCoords = newCoords.map(str => str.split(',').map(Number));
return {
...ls,
geometry: {
...ls.geometry,
coordinates: resultCoords,
},
}; };
for (const wall of walls) {
const [p1, p2] = wall.points;
if (p1.pointUuid === p2.pointUuid || (p1.position[0] === p2.position[0] && p1.position[1] === p2.position[1] && p1.position[2] === p2.position[2])) {
continue;
} }
return null; const key = makeKey(p1, p2);
}).filter(Boolean); if (!wallSet.has(key)) {
wallSet.set(key, wall);
}
}
if (validLineStrings.length === 0) return []; const uniqueWalls = Array.from(wallSet.values());
const lineStrings = turf.featureCollection(validLineStrings as any); const lineStrings = uniqueWalls.map((wall) => {
const polygons = turf.polygonize(lineStrings as any); const coords: [number, number][] = wall.points.map((p) => [p.position[0], p.position[2]]);
return turf.lineString(coords, { wallUuid: wall.wallUuid });
});
const collection = turf.featureCollection(lineStrings);
const polygons = turf.polygonize(collection);
const rooms: Point[][] = []; const rooms: Point[][] = [];
polygons.features.forEach(feature => { polygons.features.forEach((feature) => {
if (feature.geometry.type === 'Polygon') { if (feature.geometry.type === "Polygon") {
const coordinates = feature.geometry.coordinates[0]; const coords = feature.geometry.coordinates[0];
const roomPoints: Point[] = []; const roomPoints: Point[] = [];
for (const [x, z] of coordinates) { for (const [x, z] of coords) {
const matchingPoint = walls.flatMap(wall => wall.points) const matchingPoint = uniqueWalls.flatMap((w) => w.points).find((p) => p.position[0].toFixed(6) === x.toFixed(6) && p.position[2].toFixed(6) === z.toFixed(6));
.find(p =>
p.position[0].toFixed(10) === x.toFixed(10) &&
p.position[2].toFixed(10) === z.toFixed(10)
);
if (matchingPoint) { if (matchingPoint) {
roomPoints.push(matchingPoint); roomPoints.push(matchingPoint);
} }
} }
if (roomPoints.length > 0 && if (roomPoints.length > 0 && roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints.push(roomPoints[0]); roomPoints.push(roomPoints[0]);
} }
@@ -161,12 +65,14 @@ export function useWallClassification(walls: Walls) {
const rooms = useMemo(() => findRooms(), [walls]); const rooms = useMemo(() => findRooms(), [walls]);
const findWallType = (wall: Wall) => { const findWallType = (wall: Wall) => {
const containingRooms = rooms.filter(room => { const containingRooms = rooms.filter((room) => {
for (let i = 0; i < room.length - 1; i++) { for (let i = 0; i < room.length - 1; i++) {
const p1 = room[i]; const p1 = room[i];
const p2 = room[i + 1]; const p2 = room[i + 1];
if ((wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) || if (
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)) { (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 true;
} }
} }
@@ -175,18 +81,18 @@ export function useWallClassification(walls: Walls) {
if (containingRooms.length === 0) { if (containingRooms.length === 0) {
return { return {
type: 'segment', type: "segment",
rooms: [] rooms: [],
}; };
} else if (containingRooms.length === 1) { } else if (containingRooms.length === 1) {
return { return {
type: 'room', type: "room",
rooms: containingRooms rooms: containingRooms,
}; };
} else { } else {
return { return {
type: 'rooms', type: "rooms",
rooms: containingRooms rooms: containingRooms,
}; };
} }
}; };
@@ -197,16 +103,16 @@ export function useWallClassification(walls: Walls) {
const isRoomWall = (wall: Wall): boolean => { const isRoomWall = (wall: Wall): boolean => {
const type = findWallType(wall).type; const type = findWallType(wall).type;
return type === 'room' || type === 'rooms'; return type === "room" || type === "rooms";
}; };
const isSegmentWall = (wall: Wall): boolean => { const isSegmentWall = (wall: Wall): boolean => {
return findWallType(wall).type === 'segment'; return findWallType(wall).type === "segment";
}; };
const isWallFlipped = (wall: Wall): boolean => { const isWallFlipped = (wall: Wall): boolean => {
const wallType = findWallType(wall); const wallType = findWallType(wall);
if (wallType.type === 'segment') return false; if (wallType.type === "segment") return false;
for (const room of wallType.rooms) { for (const room of wallType.rooms) {
for (let i = 0; i < room.length - 1; i++) { for (let i = 0; i < room.length - 1; i++) {
@@ -223,13 +129,12 @@ export function useWallClassification(walls: Walls) {
return false; return false;
}; };
return { return {
rooms, rooms,
getWallType, getWallType,
isRoomWall, isRoomWall,
isSegmentWall, isSegmentWall,
findRooms, findRooms,
isWallFlipped isWallFlipped,
}; };
} }

View File

@@ -27,7 +27,7 @@ function WallInstances() {
const { rooms } = useWallClassification(walls); const { rooms } = useWallClassification(walls);
useEffect(() => { useEffect(() => {
// console.log('walls: ', walls); // console.log("walls: ", walls);
}, [walls]); }, [walls]);
useEffect(() => { useEffect(() => {

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from "react";
import * as THREE from 'three'; import * as THREE from "three";
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from "@react-three/fiber";
import { Html } from '@react-three/drei'; import { Html } from "@react-three/drei";
import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; import { useActiveLayer, useToolMode, useToggleView } from "../../../../store/builder/store";
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; import { useDirectionalSnapping } from "../../point/helpers/useDirectionalSnapping";
import { usePointSnapping } from '../../point/helpers/usePointSnapping'; import { usePointSnapping } from "../../point/helpers/usePointSnapping";
import ReferenceLine from '../../line/reference/referenceLine'; import ReferenceLine from "../../line/reference/referenceLine";
interface ReferenceWallProps { interface ReferenceWallProps {
tempPoints: Point[]; tempPoints: Point[];
@@ -26,10 +26,10 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null); const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
const { snapWallPoint } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] }); const { snapWallPoint } = usePointSnapping({ uuid: "temp-wall", pointType: "Wall", position: directionalSnap.position || [0, 0, 0] });
useFrame(() => { useFrame(() => {
if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) { if (toolMode === "Wall" && toggleView && tempPoints.length === 1) {
raycaster.setFromCamera(pointer, camera); raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3(); const intersectionPoint = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersectionPoint); raycaster.ray.intersectPlane(plane, intersectionPoint);
@@ -37,7 +37,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (!intersectionPoint) return; if (!intersectionPoint) return;
const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints[0]); const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (snapped.isSnapped && snapped.snappedPoint) { if (snapped.isSnapped && snapped.snappedPoint) {
finalPosition.current = snapped.position; finalPosition.current = snapped.position;
@@ -58,23 +58,22 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const wallPoints: [Point, Point] = [ const wallPoints: [Point, Point] = [
tempPoints[0], tempPoints[0],
{ {
pointUuid: 'temp-point', pointUuid: "temp-point",
pointType: 'Wall', pointType: "Wall",
position: finalPosition.current, position: finalPosition.current,
layer: activeLayer, layer: activeLayer,
} },
]; ];
setTempWall({ setTempWall({
wallUuid: 'temp-wall', wallUuid: "temp-wall",
points: wallPoints, points: wallPoints,
outsideMaterial: 'default', outsideMaterial: "default",
insideMaterial: 'default', insideMaterial: "default",
wallThickness: wallThickness, wallThickness: wallThickness,
wallHeight: wallHeight, wallHeight: wallHeight,
decals: [] decals: [],
}) });
} else if (tempWall !== null) { } else if (tempWall !== null) {
setTempWall(null); setTempWall(null);
} }
@@ -87,9 +86,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
if (!tempWall) return null; if (!tempWall) return null;
const renderWall = () => { const renderWall = () => {
return ( return <ReferenceLine points={tempWall.points} />;
<ReferenceLine points={tempWall.points} />
)
}; };
const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2); const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2);
@@ -113,8 +110,8 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
</Html> </Html>
)} )}
</> </>
) );
} };
return ( return (
<group name="Wall-Reference-Group"> <group name="Wall-Reference-Group">

View File

@@ -22,7 +22,7 @@ function WallCreator() {
const { activeLayer } = useActiveLayer(); const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext(); const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore(); const { addWall, getWallPointById, removeWall, getWallByPoints, getConnectedPoints } = wallStore();
const { push2D } = undoRedo2DStore(); const { push2D } = undoRedo2DStore();
const drag = useRef(false); const drag = useRef(false);
const isLeftMouseDown = useRef(false); const isLeftMouseDown = useRef(false);
@@ -379,7 +379,9 @@ function WallCreator() {
newPoint.layer = snappedPoint.layer; newPoint.layer = snappedPoint.layer;
} }
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return; return;
} }
@@ -485,7 +487,29 @@ function WallCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint, selectedVersion?.versionId]); }, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addWall,
getWallPointById,
wallThickness,
wallHeight,
insideMaterial,
outsideMaterial,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return ( return (
<> <>

View File

@@ -86,6 +86,10 @@ function ZoneCreator() {
return; return;
} }
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) { if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition; newPoint.position = snappedPosition;
} }
@@ -282,7 +286,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]); }, [gl, camera, scene, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
return ( return (
<> <>

View File

@@ -1,9 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import * as THREE from 'three'; import * as THREE from "three";
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei'; import { PerspectiveCamera, OrthographicCamera, CameraControls } from "@react-three/drei";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import * as CONSTANTS from '../../../types/world/worldConstants'; import * as CONSTANTS from "../../../types/world/worldConstants";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi"; import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { useToggleView } from "../../../store/builder/store"; import { useToggleView } from "../../../store/builder/store";
@@ -18,7 +18,8 @@ export default function SwitchView() {
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse; (controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else { } else {
if (!projectId) return; if (!projectId) return;
getCameraApi(projectId).then((data) => { getCameraApi(projectId)
.then((data) => {
if (data?.position && data?.target) { if (data?.position && data?.target) {
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z); (controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
(controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z); (controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z);
@@ -26,11 +27,12 @@ export default function SwitchView() {
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition); (controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget); (controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
} }
}).catch(() => { })
.catch(() => {
echo.error("Failed to retrieve camera position or target"); echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition); (controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget); (controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}) });
if (controls) { if (controls) {
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse; (controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;

View File

@@ -59,7 +59,12 @@ export default function Controls() {
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth); controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
if (!socket?.connected) { if (!socket?.connected) {
setCameraApi(projectId, new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition), new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)); setCameraApi(
projectId,
new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)
);
} else { } else {
const camData = { const camData = {
organization, organization,
@@ -143,7 +148,6 @@ export default function Controls() {
boundaryEnclosesCamera={true} boundaryEnclosesCamera={true}
dollyDragInverted dollyDragInverted
> >
<SwitchView /> <SwitchView />
<CamMode /> <CamMode />

View File

@@ -77,7 +77,7 @@ export const createVersionStore = () => {
setVersionName: (versionId: string, versionName: string) => { setVersionName: (versionId: string, versionName: string) => {
set((state) => { set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId); const version = get().getVersionById(versionId);
if (version) { if (version) {
version.versionName = versionName; version.versionName = versionName;
} }
@@ -86,7 +86,7 @@ export const createVersionStore = () => {
updateVersion: (versionId: string, versionName: string, versionDescription: string) => { updateVersion: (versionId: string, versionName: string, versionDescription: string) => {
set((state) => { set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId); const version = get().getVersionById(versionId);
if (version) { if (version) {
version.versionName = versionName; version.versionName = versionName;
version.versionDescription = versionDescription; version.versionDescription = versionDescription;

View File

@@ -230,10 +230,6 @@ const KeyPressListener: React.FC = () => {
setShowShortcuts(!showShortcuts); setShowShortcuts(!showShortcuts);
} }
// if (keyCombination === "Ctrl+Shift+P") {
// pref
// }
if (keyCombination === "U") { if (keyCombination === "U") {
setViewSceneLabels((prev) => !prev); setViewSceneLabels((prev) => !prev);
} }