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

View File

@@ -19,7 +19,7 @@ function AisleCreator() {
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addAisle, getAislePointById } = aisleStore();
const { addAisle, getAislePointById, getConnectedPoints } = aisleStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
@@ -76,7 +76,9 @@ function AisleCreator() {
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;
}
@@ -194,7 +196,32 @@ function AisleCreator() {
canvasElement.removeEventListener("click", onMouseClick);
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 (
<>

View File

@@ -86,6 +86,10 @@ function FloorCreator() {
return;
}
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition;
}
@@ -288,7 +292,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick);
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 (
<>

View File

@@ -1,16 +1,16 @@
import * as THREE from 'three';
import { useCallback } from 'react';
import { useSceneContext } from '../../../scene/sceneContext';
import * as THREE from "three";
import { useCallback } from "react";
import { useSceneContext } from "../../../scene/sceneContext";
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const CAN_POINT_SNAP = true; // Whether snapping is enabled or not
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters
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 { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
@@ -21,350 +21,359 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const getAllOtherWallPoints = useCallback(() => {
if (!currentPoint) return [];
return walls.flatMap(wall =>
wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return walls.flatMap((wall) => wall.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [walls, currentPoint]);
const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapWallPoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherWallPoints();
if (tempPoints) {
otherPoints.push(tempPoints);
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') {
return { position: point.position, isSnapped: true, snappedPoint: point };
const otherPoints = getAllOtherWallPoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Wall") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
}
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherWallPoints]);
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherWallPoints]
);
const snapWallAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const snapWallAngle = useCallback(
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: []
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
},
[currentPoint, getConnectedAislePoints]
);
// Aisle Snapping
const getAllOtherAislePoints = useCallback(() => {
if (!currentPoint) return [];
return aisles.flatMap(aisle =>
aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return aisles.flatMap((aisle) => aisle.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [aisles, currentPoint]);
const snapAislePoint = useCallback((position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapAislePoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') {
return { position: point.position, isSnapped: true, snappedPoint: point };
const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Aisle") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
}
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherAislePoints]);
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherAislePoints]
);
const snapAisleAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const snapAisleAngle = useCallback(
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: []
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
},
[currentPoint, getConnectedAislePoints]
);
// Floor Snapping
const getAllOtherFloorPoints = useCallback(() => {
if (!currentPoint) return [];
return floors.flatMap(floor =>
floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return floors.flatMap((floor) => floor.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [floors, currentPoint]);
const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapFloorPoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherFloorPoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') {
return { position: point.position, isSnapped: true, snappedPoint: point };
let otherPoints = getAllOtherFloorPoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
}
const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherFloorPoints]);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapFloorAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Floor") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherFloorPoints]
);
const snapFloorAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedFloorPoints]);
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
},
[currentPoint, getConnectedFloorPoints]
);
// Zone Snapping
const getAllOtherZonePoints = useCallback(() => {
if (!currentPoint) return [];
return zones.flatMap(zone =>
zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return zones.flatMap((zone) => zone.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [zones, currentPoint]);
const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapZonePoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherZonePoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Zone') {
return { position: point.position, isSnapped: true, snappedPoint: point };
let otherPoints = getAllOtherZonePoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
}
const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherZonePoints]);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapZoneAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Zone") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherZonePoints]
);
const snapZoneAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedZonePoints]);
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
},
[currentPoint, getConnectedZonePoints]
);
return {
snapAislePoint,
@@ -376,4 +385,4 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
snapZonePoint,
snapZoneAngle,
};
};
};

View File

@@ -1,151 +1,55 @@
import { useMemo } from 'react';
import * as turf from '@turf/turf';
import { useMemo } from "react";
import * as turf from "@turf/turf";
export function useWallClassification(walls: Walls) {
const findRooms = () => {
if (walls.length < 3) return [];
// Map pointUuid to list of connected line segments
const pointMap = new Map<string, Wall[]>();
const wallSet = new Map<string, Wall>();
const makeKey = (p1: Point, p2: Point) => {
return [p1.pointUuid, p2.pointUuid].sort().join("-");
};
for (const wall of walls) {
for (const point of wall.points) {
const list = pointMap.get(point.pointUuid) || [];
list.push(wall);
pointMap.set(point.pointUuid, list);
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;
}
const key = makeKey(p1, p2);
if (!wallSet.has(key)) {
wallSet.set(key, wall);
}
}
// Create graph of connected walls using pointUuid
const visited = new Set<string>();
const mergedLineStrings = [];
const uniqueWalls = Array.from(wallSet.values());
const wallKey = (p1: Point, p2: Point) => `${p1.pointUuid}-${p2.pointUuid}`;
const lineStrings = uniqueWalls.map((wall) => {
const coords: [number, number][] = wall.points.map((p) => [p.position[0], p.position[2]]);
return turf.lineString(coords, { wallUuid: wall.wallUuid });
});
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;
const collection = turf.featureCollection(lineStrings);
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,
},
};
}
return null;
}).filter(Boolean);
if (validLineStrings.length === 0) return [];
const lineStrings = turf.featureCollection(validLineStrings as any);
const polygons = turf.polygonize(lineStrings as any);
const polygons = turf.polygonize(collection);
const rooms: Point[][] = [];
polygons.features.forEach(feature => {
if (feature.geometry.type === 'Polygon') {
const coordinates = feature.geometry.coordinates[0];
polygons.features.forEach((feature) => {
if (feature.geometry.type === "Polygon") {
const coords = feature.geometry.coordinates[0];
const roomPoints: Point[] = [];
for (const [x, z] of coordinates) {
const matchingPoint = walls.flatMap(wall => wall.points)
.find(p =>
p.position[0].toFixed(10) === x.toFixed(10) &&
p.position[2].toFixed(10) === z.toFixed(10)
);
for (const [x, z] of coords) {
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));
if (matchingPoint) {
roomPoints.push(matchingPoint);
}
}
if (roomPoints.length > 0 &&
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
if (roomPoints.length > 0 && roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints.push(roomPoints[0]);
}
@@ -161,12 +65,14 @@ export function useWallClassification(walls: Walls) {
const rooms = useMemo(() => findRooms(), [walls]);
const findWallType = (wall: Wall) => {
const containingRooms = rooms.filter(room => {
const containingRooms = rooms.filter((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)) {
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;
}
}
@@ -175,18 +81,18 @@ export function useWallClassification(walls: Walls) {
if (containingRooms.length === 0) {
return {
type: 'segment',
rooms: []
type: "segment",
rooms: [],
};
} else if (containingRooms.length === 1) {
return {
type: 'room',
rooms: containingRooms
type: "room",
rooms: containingRooms,
};
} else {
return {
type: 'rooms',
rooms: containingRooms
type: "rooms",
rooms: containingRooms,
};
}
};
@@ -197,16 +103,16 @@ export function useWallClassification(walls: Walls) {
const isRoomWall = (wall: Wall): boolean => {
const type = findWallType(wall).type;
return type === 'room' || type === 'rooms';
return type === "room" || type === "rooms";
};
const isSegmentWall = (wall: Wall): boolean => {
return findWallType(wall).type === 'segment';
return findWallType(wall).type === "segment";
};
const isWallFlipped = (wall: Wall): boolean => {
const wallType = findWallType(wall);
if (wallType.type === 'segment') return false;
if (wallType.type === "segment") return false;
for (const room of wallType.rooms) {
for (let i = 0; i < room.length - 1; i++) {
@@ -223,13 +129,12 @@ export function useWallClassification(walls: Walls) {
return false;
};
return {
rooms,
getWallType,
isRoomWall,
isSegmentWall,
findRooms,
isWallFlipped
isWallFlipped,
};
}
}

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ function WallCreator() {
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore();
const { addWall, getWallPointById, removeWall, getWallByPoints, getConnectedPoints } = wallStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
@@ -379,7 +379,9 @@ function WallCreator() {
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;
}
@@ -485,7 +487,29 @@ function WallCreator() {
canvasElement.removeEventListener("click", onMouseClick);
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 (
<>

View File

@@ -86,6 +86,10 @@ function ZoneCreator() {
return;
}
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition;
}
@@ -282,7 +286,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick);
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 (
<>

View File

@@ -1,9 +1,9 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from 'three';
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei';
import * as THREE from "three";
import { PerspectiveCamera, OrthographicCamera, CameraControls } from "@react-three/drei";
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 { useToggleView } from "../../../store/builder/store";
@@ -18,19 +18,21 @@ export default function SwitchView() {
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else {
if (!projectId) return;
getCameraApi(projectId).then((data) => {
if (data?.position && data?.target) {
(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);
} else {
getCameraApi(projectId)
.then((data) => {
if (data?.position && data?.target) {
(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);
} else {
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
})
.catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
}).catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
})
});
if (controls) {
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
@@ -62,4 +64,4 @@ export default function SwitchView() {
)}
</>
);
}
}

View File

@@ -59,7 +59,12 @@ export default function Controls() {
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
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 {
const camData = {
organization,
@@ -143,7 +148,6 @@ export default function Controls() {
boundaryEnclosesCamera={true}
dollyDragInverted
>
<SwitchView />
<CamMode />

View File

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

View File

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