Refactor: Update useEffect dependencies to include activeLayer for ReferenceAisle, ReferenceFloor, ReferenceWall; enhance usePointSnapping with zone snapping functionality and add ReferenceZone component for zone management
This commit is contained in:
@@ -177,7 +177,7 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTempAisle(null);
|
setTempAisle(null);
|
||||||
}, [toolMode, toggleView, tempPoints.length, aisleType, aisleWidth, aisleColor]);
|
}, [toolMode, toggleView, tempPoints.length, aisleType, aisleWidth, aisleColor, activeLayer]);
|
||||||
|
|
||||||
if (!tempAisle) return null;
|
if (!tempAisle) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ function ReferenceFloor({ tempPoints }: Readonly<ReferenceFloorProps>) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTempFloor(null);
|
setTempFloor(null);
|
||||||
}, [toolMode, toggleView, tempPoints.length, floorDepth, bevelStrength, isBeveled]);
|
}, [toolMode, toggleView, tempPoints.length, floorDepth, bevelStrength, isBeveled, activeLayer]);
|
||||||
|
|
||||||
const allLines = useMemo(() => {
|
const allLines = useMemo(() => {
|
||||||
if (!tempFloor || tempFloor.points.length < 2) return [];
|
if (!tempFloor || tempFloor.points.length < 2) return [];
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ 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 } = 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();
|
||||||
const { floors, getConnectedPoints: getConnectedFloorPoints } = floorStore();
|
const { floors, getConnectedPoints: getConnectedFloorPoints } = floorStore();
|
||||||
|
const { zones, getConnectedPoints: getConnectedZonePoints } = zoneStore();
|
||||||
|
|
||||||
// Wall Snapping
|
// Wall Snapping
|
||||||
|
|
||||||
@@ -278,6 +279,93 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
|
|||||||
};
|
};
|
||||||
}, [currentPoint, getConnectedFloorPoints]);
|
}, [currentPoint, getConnectedFloorPoints]);
|
||||||
|
|
||||||
|
// Zone Snapping
|
||||||
|
|
||||||
|
const getAllOtherZonePoints = useCallback(() => {
|
||||||
|
if (!currentPoint) return [];
|
||||||
|
|
||||||
|
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 otherPoints = getAllOtherZonePoints();
|
||||||
|
if (tempPoints) {
|
||||||
|
otherPoints.concat(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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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[] = [];
|
||||||
|
|
||||||
|
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, getConnectedZonePoints]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
snapAislePoint,
|
snapAislePoint,
|
||||||
snapAisleAngle,
|
snapAisleAngle,
|
||||||
@@ -285,5 +373,7 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
|
|||||||
snapWallAngle,
|
snapWallAngle,
|
||||||
snapFloorPoint,
|
snapFloorPoint,
|
||||||
snapFloorAngle,
|
snapFloorAngle,
|
||||||
|
snapZonePoint,
|
||||||
|
snapZoneAngle,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -82,7 +82,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTempWall(null);
|
setTempWall(null);
|
||||||
}, [toolMode, toggleView, tempPoints.length, wallHeight, wallThickness]);
|
}, [toolMode, toggleView, tempPoints.length, wallHeight, wallThickness, activeLayer]);
|
||||||
|
|
||||||
if (!tempWall) return null;
|
if (!tempWall) return null;
|
||||||
|
|
||||||
|
|||||||
163
app/src/modules/builder/zone/zoneCreator/referenceZone.tsx
Normal file
163
app/src/modules/builder/zone/zoneCreator/referenceZone.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { useEffect, useRef, useState, useMemo } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
|
import { Extrude, 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 ReferenceZoneProps {
|
||||||
|
tempPoints: Point[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReferenceZone({ tempPoints }: Readonly<ReferenceZoneProps>) {
|
||||||
|
const { zoneColor, zoneHeight, setSnappedPosition, setSnappedPoint } = useBuilderStore();
|
||||||
|
const { pointer, raycaster, camera } = useThree();
|
||||||
|
const { toolMode } = useToolMode();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||||
|
const finalPosition = useRef<[number, number, number] | null>(null);
|
||||||
|
|
||||||
|
const [tempZone, setTempZone] = useState<Zone | null>(null);
|
||||||
|
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
|
||||||
|
|
||||||
|
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[tempPoints.length - 1]?.position || null);
|
||||||
|
const { snapZonePoint } = usePointSnapping({ uuid: 'temp-Zone', pointType: 'Zone', position: directionalSnap.position || [0, 0, 0], });
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (toolMode === 'Zone' && toggleView && tempPoints.length > 0) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
|
||||||
|
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
||||||
|
|
||||||
|
if (!intersectionPoint) return;
|
||||||
|
const snapped = snapZonePoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints.slice(0, -2));
|
||||||
|
|
||||||
|
if (snapped.isSnapped && snapped.snappedPoint) {
|
||||||
|
finalPosition.current = snapped.position;
|
||||||
|
setSnappedPosition(snapped.position);
|
||||||
|
setSnappedPoint(snapped.snappedPoint);
|
||||||
|
} else if (directionalSnap.isSnapped) {
|
||||||
|
finalPosition.current = directionalSnap.position;
|
||||||
|
setSnappedPosition(directionalSnap.position);
|
||||||
|
setSnappedPoint(null);
|
||||||
|
} else {
|
||||||
|
finalPosition.current = [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z];
|
||||||
|
setSnappedPosition(null);
|
||||||
|
setSnappedPoint(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finalPosition.current) return;
|
||||||
|
|
||||||
|
const zonePoints: Point[] = [
|
||||||
|
...tempPoints,
|
||||||
|
{
|
||||||
|
pointUuid: 'temp-point',
|
||||||
|
pointType: 'Zone',
|
||||||
|
position: finalPosition.current,
|
||||||
|
layer: activeLayer,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
setTempZone({
|
||||||
|
zoneUuid: 'temp-zone',
|
||||||
|
zoneName: 'temp-zone',
|
||||||
|
points: zonePoints,
|
||||||
|
zoneColor,
|
||||||
|
zoneHeight,
|
||||||
|
viewPortPosition: [0, 0, 0],
|
||||||
|
viewPortTarget: [0, 0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (tempZone !== null) {
|
||||||
|
setTempZone(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTempZone(null);
|
||||||
|
}, [toolMode, toggleView, tempPoints.length, zoneColor, zoneHeight, activeLayer]);
|
||||||
|
|
||||||
|
const allLines = useMemo(() => {
|
||||||
|
if (!tempZone || tempZone.points.length < 2) return [];
|
||||||
|
|
||||||
|
const lines: [Point, Point][] = [];
|
||||||
|
const pts = tempZone.points;
|
||||||
|
|
||||||
|
for (let i = 0; i < pts.length - 1; i++) {
|
||||||
|
lines.push([pts[i], pts[i + 1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}, [tempZone]);
|
||||||
|
|
||||||
|
if (!tempZone) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group name="Wall-Reference-Group">
|
||||||
|
{allLines.map(([p1, p2], idx) => {
|
||||||
|
const v1 = new THREE.Vector3(...p1.position);
|
||||||
|
const v2 = new THREE.Vector3(...p2.position);
|
||||||
|
const mid = new THREE.Vector3().addVectors(v1, v2).multiplyScalar(0.5);
|
||||||
|
const dist = v1.distanceTo(v2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group key={`${p1.pointUuid}-${p2.pointUuid}`}>
|
||||||
|
<ReferenceLine points={[p1, p2]} />
|
||||||
|
{toggleView && (
|
||||||
|
<Html
|
||||||
|
position={[mid.x, 1, mid.z]}
|
||||||
|
wrapperClass="distance-text-wrapper"
|
||||||
|
className="distance-text"
|
||||||
|
zIndexRange={[1, 0]}
|
||||||
|
prepend
|
||||||
|
sprite
|
||||||
|
>
|
||||||
|
<div className={`distance ref-line-${idx}`}>{dist.toFixed(2)} m</div>
|
||||||
|
</Html>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{tempPoints.length >= 3 && (
|
||||||
|
<Zone zone={tempZone.points} />
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReferenceZone;
|
||||||
|
|
||||||
|
|
||||||
|
function Zone({ zone }: { zone: Point[] }) {
|
||||||
|
const savedTheme: string | null = localStorage.getItem('theme');
|
||||||
|
|
||||||
|
const shape = useMemo(() => {
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
const points = zone.map(p => new THREE.Vector2(p.position[0], p.position[2]));
|
||||||
|
if (points.length < 3) return null;
|
||||||
|
shape.moveTo(points[0].x, points[0].y);
|
||||||
|
points.forEach((pt) => { shape.lineTo(pt.x, pt.y); });
|
||||||
|
return shape;
|
||||||
|
}, [zone]);
|
||||||
|
|
||||||
|
if (!shape) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group name="Zone-Ref" rotation={[Math.PI / 2, 0, 0]}>
|
||||||
|
<Extrude
|
||||||
|
args={[shape, { depth: 0.001, bevelEnabled: false }]}
|
||||||
|
position={[0, 0, 0]}
|
||||||
|
receiveShadow
|
||||||
|
>
|
||||||
|
<meshStandardMaterial color={savedTheme === "dark" ? "#d2baff" : "#6f42c1"} depthWrite={false} transparent opacity={0.3} side={THREE.DoubleSide} />
|
||||||
|
</Extrude>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,14 @@
|
|||||||
import React from 'react'
|
import * as THREE from 'three'
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { useThree } from '@react-three/fiber';
|
||||||
|
import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
|
||||||
|
import { useSceneContext } from '../../../scene/sceneContext';
|
||||||
|
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useVersionContext } from '../../version/versionContext';
|
||||||
|
import { getUserData } from '../../../../functions/getUserData';
|
||||||
|
import ReferencePoint from '../../point/reference/referencePoint';
|
||||||
|
import ReferenceZone from './referenceZone';
|
||||||
|
|
||||||
function ZoneCreator() {
|
function ZoneCreator() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface ZoneStore {
|
|||||||
setViewPort: (uuid: string, position: [number, number, number], target: [number, number, number]) => void;
|
setViewPort: (uuid: string, position: [number, number, number], target: [number, number, number]) => void;
|
||||||
|
|
||||||
getZoneById: (uuid: string) => Zone | undefined;
|
getZoneById: (uuid: string) => Zone | undefined;
|
||||||
|
getConnectedPoints: (uuid: string) => Point[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createZoneStore = () => {
|
export const createZoneStore = () => {
|
||||||
@@ -83,6 +84,17 @@ export const createZoneStore = () => {
|
|||||||
getZoneById: (uuid) => {
|
getZoneById: (uuid) => {
|
||||||
return get().zones.find(z => z.zoneUuid === uuid);
|
return get().zones.find(z => z.zoneUuid === uuid);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getConnectedPoints: (pointUuid) => {
|
||||||
|
const connected: Point[] = [];
|
||||||
|
for (const zone of get().zones) {
|
||||||
|
if (zone.points.some(p => p.pointUuid === pointUuid)) {
|
||||||
|
connected.push(...zone.points.filter(p => p.pointUuid !== pointUuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user