diff --git a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx index 96ef288..a613699 100644 --- a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx @@ -12,6 +12,7 @@ import Directional from "../../../../assets/image/aisleTypes/Directional.png"; import Dotted from "../../../../assets/image/aisleTypes/Dotted.png"; import Solid from "../../../../assets/image/aisleTypes/Solid.png"; import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import InputToggle from "../../../ui/inputs/InputToggle"; interface TextureItem { color: AisleColors; @@ -24,7 +25,7 @@ const AisleProperties: React.FC = () => { const [collapsePresets, setCollapsePresets] = useState(false); const [collapseTexture, setCollapseTexture] = useState(true); - const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength } = useBuilderStore(); + const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore(); const aisleTextureList: TextureItem[] = [ { color: "yellow", id: "yellow1", brief: "pedestrian walkways", texture: "" }, @@ -90,6 +91,10 @@ const AisleProperties: React.FC = () => { } }; + const handleIsFlippedChange = () => { + setIsFlipped(!aisleIsFlipped) + }; + const dashLengthValue = useMemo(() => { return dashLength.toString(); }, [aisleType, dashLength]); @@ -110,6 +115,10 @@ const AisleProperties: React.FC = () => { return aisleLength.toString(); }, [aisleType, aisleLength]); + const aisleIsFlipped = useMemo(() => { + return isFlipped; + }, [aisleType, isFlipped]); + const renderAdvancedProperties = () => { switch (aisleType) { case 'dashed-aisle': @@ -185,6 +194,19 @@ const AisleProperties: React.FC = () => { } ); + case 'junction-aisle': + return ( + <> + {aisleType && + + } + + ) default: return null; } diff --git a/app/src/modules/builder/aisle/Instances/aisleInstances.tsx b/app/src/modules/builder/aisle/Instances/aisleInstances.tsx index 8964434..227b2de 100644 --- a/app/src/modules/builder/aisle/Instances/aisleInstances.tsx +++ b/app/src/modules/builder/aisle/Instances/aisleInstances.tsx @@ -16,8 +16,8 @@ function AisleInstances() { aisles.forEach(aisle => { aisle.points.forEach(point => { - if (!seenUuids.has(point.uuid)) { - seenUuids.add(point.uuid); + if (!seenUuids.has(point.pointUuid)) { + seenUuids.add(point.pointUuid); points.push(point); } }); @@ -27,7 +27,7 @@ function AisleInstances() { }, [aisles]); useEffect(() => { - console.log('aisles: ', aisles); + // console.log('aisles: ', aisles); }, [aisles]); return ( @@ -35,7 +35,7 @@ function AisleInstances() { {toggleView && {allPoints.map((point) => ( - + ))} } @@ -46,13 +46,13 @@ function AisleInstances() { const distance = new Vector3(...aisle.points[0].position).distanceTo(new Vector3(...aisle.points[1].position)); return ( - < React.Fragment key={aisle.uuid}> - + < React.Fragment key={aisle.aisleUuid}> + {toggleView &&
{distance.toFixed(2)} m
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleInstance.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleInstance.tsx index 9afd3d6..c8ecdd7 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleInstance.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleInstance.tsx @@ -1,6 +1,9 @@ +import ArrowAisle from './aisleTypes/arrowAisle'; import ArrowsAisle from './aisleTypes/arrowsAisle'; +import CircleAisle from './aisleTypes/circleAisle'; import DashedAisle from './aisleTypes/dashedAisle'; import DottedAisle from './aisleTypes/dottedAisle'; +import JunctionAisle from './aisleTypes/junctionAisle'; import SolidAisle from './aisleTypes/solidAisle'; function AisleInstance({ aisle }: { readonly aisle: Aisle }) { @@ -22,6 +25,18 @@ function AisleInstance({ aisle }: { readonly aisle: Aisle }) { {aisle.type.aisleType === 'arrows-aisle' && ( )} + + {aisle.type.aisleType === 'arrow-aisle' && ( + + )} + + {aisle.type.aisleType === 'circle-aisle' && ( + + )} + + {aisle.type.aisleType === 'junction-aisle' && ( + + )} ); } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx new file mode 100644 index 0000000..7a4e270 --- /dev/null +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx @@ -0,0 +1,88 @@ +import * as THREE from 'three'; +import { useMemo, useRef } from 'react'; +import { Extrude } from '@react-three/drei'; +import * as Constants from '../../../../../../types/world/worldConstants'; +import { useToolMode } from '../../../../../../store/builder/store'; +import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; + +function ArrowAisle({ aisle }: { readonly aisle: Aisle }) { + const aisleRef = useRef(null); + const { toolMode } = useToolMode(); + const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + + const arrow = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null; + + const start = new THREE.Vector3(...aisle.points[0].position); + const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + + const direction = new THREE.Vector3().subVectors(end, start); + const length = direction.length(); + direction.normalize(); + + const shape = new THREE.Shape(); + const arrowHeadLength = width * 2; + const shaftLength = length - arrowHeadLength; + + if (shaftLength > 0) { + shape.moveTo(0, 0); + shape.lineTo(width, -arrowHeadLength); + shape.lineTo(width / 2, -arrowHeadLength); + shape.lineTo(width / 2, -length); + shape.lineTo(-width / 2, -length); + shape.lineTo(-width / 2, -arrowHeadLength); + shape.lineTo(-width, -arrowHeadLength); + shape.lineTo(0, 0); + } else { + shape.moveTo(0, 0); + shape.lineTo(width, -length); + shape.lineTo(-width, -length); + shape.lineTo(0, 0); + } + + shape.closePath(); + + const position = end; + const angle = Math.atan2(direction.x, direction.z); + + return { shape, position, rotationY: angle }; + }, [aisle]); + + const handleClick = () => { + if (toolMode === 'move' && !hoveredPoint) { + setSelectedAisle(aisleRef.current); + } + } + + if (!arrow) return null; + + return ( + { + setSelectedAisle(null); + }} + > + + + + + + + ); +} + +export default ArrowAisle; \ No newline at end of file diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx new file mode 100644 index 0000000..34caf99 --- /dev/null +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx @@ -0,0 +1,82 @@ +import * as THREE from 'three'; +import { useMemo, useRef } from 'react'; +import { Extrude } from '@react-three/drei'; +import * as Constants from '../../../../../../types/world/worldConstants'; +import { useToolMode } from '../../../../../../store/builder/store'; +import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; + +function CircleAisle({ aisle }: { readonly aisle: Aisle }) { + const aisleRef = useRef(null); + const { toolMode } = useToolMode(); + const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + + const circle = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null; + + const center = new THREE.Vector3(...aisle.points[0].position); + const widthCenter = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + + const middleRadius = center.distanceTo(widthCenter); + + const innerRadius = Math.max(0, middleRadius - width / 2); + const outerRadius = middleRadius + width / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + if (innerRadius > 0) { + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + } + + return { + shape, + position: center, + rotationY: 0 + }; + }, [aisle]); + + const handleClick = () => { + if (toolMode === 'move' && !hoveredPoint) { + setSelectedAisle(aisleRef.current); + } + } + + if (!circle) return null; + + return ( + { + setSelectedAisle(null); + }} + > + + + + + + + ); +} + +export default CircleAisle; \ No newline at end of file diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx new file mode 100644 index 0000000..52cab85 --- /dev/null +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx @@ -0,0 +1,125 @@ +import * as THREE from 'three'; +import { useMemo, useRef } from 'react'; +import { Extrude } from '@react-three/drei'; +import * as Constants from '../../../../../../types/world/worldConstants'; +import { useToolMode } from '../../../../../../store/builder/store'; +import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; + +function JunctionAisle({ aisle }: { readonly aisle: Aisle }) { + const aisleRef = useRef(null); + const { toolMode } = useToolMode(); + const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + + const arrows = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null; + + const start = new THREE.Vector3(...aisle.points[0].position); + const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + const isFlipped = aisle.type.isFlipped || false; + + const direction = new THREE.Vector3().subVectors(end, start); + const length = direction.length(); + direction.normalize(); + + const mainShape = new THREE.Shape(); + const arrowHeadLength = width * 2; + const shaftLength = length - arrowHeadLength; + + if (shaftLength > 0) { + mainShape.moveTo(0, 0); + mainShape.lineTo(width, -arrowHeadLength); + mainShape.lineTo(width / 2, -arrowHeadLength); + mainShape.lineTo(width / 2, -length); + mainShape.lineTo(-width / 2, -length); + mainShape.lineTo(-width / 2, -arrowHeadLength); + mainShape.lineTo(-width, -arrowHeadLength); + mainShape.lineTo(0, 0); + } else { + mainShape.moveTo(0, 0); + mainShape.lineTo(width, -length); + mainShape.lineTo(-width, -length); + mainShape.lineTo(0, 0); + } + mainShape.closePath(); + + const secondaryLength = length / 4; + const secondaryShape = new THREE.Shape(); + const secondaryHeadLength = width * 2; + const secondaryShaftLength = secondaryLength - secondaryHeadLength; + + if (secondaryShaftLength > 0) { + secondaryShape.moveTo(0, 0); + secondaryShape.lineTo(width / 2, 0); + secondaryShape.lineTo(width / 2, secondaryShaftLength); + secondaryShape.lineTo(width, secondaryShaftLength); + secondaryShape.lineTo(0, secondaryLength); + secondaryShape.lineTo(-width, secondaryShaftLength); + secondaryShape.lineTo(-width / 2, secondaryShaftLength); + secondaryShape.lineTo(-width / 2, 0); + secondaryShape.lineTo(0, 0); + } else { + secondaryShape.moveTo(0, 0); + secondaryShape.lineTo(width, 0); + secondaryShape.lineTo(0, secondaryLength); + secondaryShape.lineTo(-width, 0); + secondaryShape.lineTo(0, 0); + } + secondaryShape.closePath(); + + const mainPosition = end; + const mainAngle = Math.atan2(direction.x, direction.z); + + const perpendicularDirection = isFlipped + ? new THREE.Vector3(direction.z, 0, -direction.x).normalize() + : new THREE.Vector3(-direction.z, 0, direction.x).normalize(); + + const secondaryAngle = Math.atan2(perpendicularDirection.x, perpendicularDirection.z); + + const secondaryPosition = new THREE.Vector3().lerpVectors(start, end, 0.75); + + return [ + { shape: mainShape, position: mainPosition, rotationY: mainAngle }, + { shape: secondaryShape, position: secondaryPosition, rotationY: secondaryAngle + Math.PI } + ]; + }, [aisle]); + + const handleClick = () => { + if (toolMode === 'move' && !hoveredPoint) { + setSelectedAisle(aisleRef.current); + } + } + + if (!arrows) return null; + + return ( + { + setSelectedAisle(null); + }} + > + {arrows.map((arrow, index) => ( + + + + + + ))} + + ); +} + +export default JunctionAisle; \ No newline at end of file diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index 0b14967..dca5654 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useThree } from '@react-three/fiber'; import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; import { useAisleStore } from '../../../../store/builder/useAisleStore'; @@ -15,45 +15,37 @@ function AisleCreator() { const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); const { addAisle, getAislePointById } = useAisleStore(); + const drag = useRef(false); + const isLeftMouseDown = useRef(false); const [tempPoints, setTempPoints] = useState([]); const [isCreating, setIsCreating] = useState(false); - const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint } = useBuilderStore(); - - // useEffect(() => { - // if (tempPoints.length > 0) { - // setTempPoints([]); - // setIsCreating(false); - // } - // }, [aisleType]); + const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, snappedPosition, snappedPoint } = useBuilderStore(); useEffect(() => { const canvasElement = gl.domElement; - let drag = false; - let isLeftMouseDown = false; - const onMouseDown = (evt: any) => { if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; + isLeftMouseDown.current = true; + drag.current = false; } }; const onMouseUp = (evt: any) => { if (evt.button === 0) { - isLeftMouseDown = false; + isLeftMouseDown.current = false; } }; const onMouseMove = () => { if (isLeftMouseDown) { - drag = true; + drag.current = true; } }; const onMouseClick = () => { - if (drag || !toggleView) return; + if (drag.current || !toggleView) return; raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); @@ -63,14 +55,14 @@ function AisleCreator() { const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Aisle-Point'); const newPoint: Point = { - uuid: THREE.MathUtils.generateUUID(), + pointUuid: THREE.MathUtils.generateUUID(), pointType: 'Aisle', position: [position.x, position.y, position.z], layer: activeLayer }; if (snappedPosition && snappedPoint) { - newPoint.uuid = snappedPoint.uuid; + newPoint.pointUuid = snappedPoint.pointUuid; newPoint.position = snappedPosition; newPoint.layer = snappedPoint.layer; } @@ -82,7 +74,7 @@ function AisleCreator() { if (intersects && !snappedPoint) { const point = getAislePointById(intersects.object.uuid); if (point) { - newPoint.uuid = point.uuid; + newPoint.pointUuid = point.pointUuid; newPoint.position = point.position; newPoint.layer = point.layer; } @@ -95,7 +87,7 @@ function AisleCreator() { setIsCreating(true); } else { const aisle: Aisle = { - uuid: THREE.MathUtils.generateUUID(), + aisleUuid: THREE.MathUtils.generateUUID(), points: [tempPoints[0], newPoint], type: { aisleType: 'solid-aisle', @@ -113,7 +105,7 @@ function AisleCreator() { setIsCreating(true); } else { const aisle: Aisle = { - uuid: THREE.MathUtils.generateUUID(), + aisleUuid: THREE.MathUtils.generateUUID(), points: [tempPoints[0], newPoint], type: { aisleType: 'dashed-aisle', @@ -133,7 +125,7 @@ function AisleCreator() { setIsCreating(true); } else { const aisle: Aisle = { - uuid: THREE.MathUtils.generateUUID(), + aisleUuid: THREE.MathUtils.generateUUID(), points: [tempPoints[0], newPoint], type: { aisleType: 'stripped-aisle', @@ -151,7 +143,7 @@ function AisleCreator() { setIsCreating(true); } else { const aisle: Aisle = { - uuid: THREE.MathUtils.generateUUID(), + aisleUuid: THREE.MathUtils.generateUUID(), points: [tempPoints[0], newPoint], type: { aisleType: 'dotted-aisle', @@ -164,7 +156,23 @@ function AisleCreator() { setTempPoints([newPoint]); } } else if (aisleType === 'arrow-aisle') { - console.log('Creating arrow-aisle'); + + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: 'arrow-aisle', + aisleColor: aisleColor, + aisleWidth: aisleWidth + } + }; + addAisle(aisle); + setTempPoints([newPoint]); + } } else if (aisleType === 'arrows-aisle') { if (tempPoints.length === 0) { @@ -172,7 +180,7 @@ function AisleCreator() { setIsCreating(true); } else { const aisle: Aisle = { - uuid: THREE.MathUtils.generateUUID(), + aisleUuid: THREE.MathUtils.generateUUID(), points: [tempPoints[0], newPoint], type: { aisleType: 'arrows-aisle', @@ -188,9 +196,42 @@ function AisleCreator() { } else if (aisleType === 'arc-aisle') { console.log('Creating arc-aisle'); } else if (aisleType === 'circle-aisle') { - console.log('Creating circle-aisle'); + + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: 'circle-aisle', + aisleColor: aisleColor, + aisleWidth: aisleWidth + } + }; + addAisle(aisle); + setTempPoints([newPoint]); + } } else if (aisleType === 'junction-aisle') { - console.log('Creating junction-aisle'); + + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: 'junction-aisle', + aisleColor: aisleColor, + aisleWidth: aisleWidth, + isFlipped: isFlipped + } + }; + addAisle(aisle); + setTempPoints([newPoint]); + } } }; @@ -211,6 +252,11 @@ function AisleCreator() { } else { setTempPoints([]); setIsCreating(false); + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); } return () => { @@ -228,7 +274,7 @@ function AisleCreator() { <> {tempPoints.map((point) => ( - + ))} diff --git a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx index a4c331e..5bb18fd 100644 --- a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx @@ -13,7 +13,7 @@ interface ReferenceAisleProps { } function ReferenceAisle({ tempPoints }: Readonly) { - const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, setSnappedPosition, setSnappedPoint } = useBuilderStore(); + const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setSnappedPosition, setSnappedPoint } = useBuilderStore(); const { pointer, raycaster, camera } = useThree(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); @@ -24,7 +24,6 @@ function ReferenceAisle({ tempPoints }: Readonly) { const [tempAisle, setTempAisle] = useState(null); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); - // Calculate directional snap based on current and previous points const directionalSnap = useDirectionalSnapping( currentPosition, tempPoints[0]?.position || null @@ -61,11 +60,11 @@ function ReferenceAisle({ tempPoints }: Readonly) { if (aisleType === 'solid-aisle' || aisleType === 'stripped-aisle') { setTempAisle({ - uuid: 'temp-aisle', + aisleUuid: 'temp-aisle', points: [ tempPoints[0], { - uuid: 'temp-point', + pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer @@ -79,11 +78,11 @@ function ReferenceAisle({ tempPoints }: Readonly) { }); } else if (aisleType === 'dashed-aisle') { setTempAisle({ - uuid: 'temp-aisle', + aisleUuid: 'temp-aisle', points: [ tempPoints[0], { - uuid: 'temp-point', + pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer @@ -99,11 +98,11 @@ function ReferenceAisle({ tempPoints }: Readonly) { }); } else if (aisleType === 'dotted-aisle') { setTempAisle({ - uuid: 'temp-aisle', + aisleUuid: 'temp-aisle', points: [ tempPoints[0], { - uuid: 'temp-point', + pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer @@ -116,15 +115,31 @@ function ReferenceAisle({ tempPoints }: Readonly) { gapLength: gapLength } }); - } else if (aisleType === 'arrow-aisle') { - console.log(); - } else if (aisleType === 'arrows-aisle') { + } else if (aisleType === 'arrow-aisle' || aisleType === 'circle-aisle') { setTempAisle({ - uuid: 'temp-aisle', + aisleUuid: 'temp-aisle', points: [ tempPoints[0], { - uuid: 'temp-point', + pointUuid: 'temp-point', + pointType: 'Aisle', + position: finalPosition.current, + layer: activeLayer + } + ], + type: { + aisleType: aisleType, + aisleColor: aisleColor, + aisleWidth: aisleWidth, + } + }); + } else if (aisleType === 'arrows-aisle') { + setTempAisle({ + aisleUuid: 'temp-aisle', + points: [ + tempPoints[0], + { + pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer @@ -140,10 +155,25 @@ function ReferenceAisle({ tempPoints }: Readonly) { }); } else if (aisleType === 'arc-aisle') { console.log(); - } else if (aisleType === 'circle-aisle') { - console.log(); } else if (aisleType === 'junction-aisle') { - console.log(); + setTempAisle({ + aisleUuid: 'temp-aisle', + points: [ + tempPoints[0], + { + pointUuid: 'temp-point', + pointType: 'Aisle', + position: finalPosition.current, + layer: activeLayer + } + ], + type: { + aisleType: aisleType, + aisleColor: aisleColor, + aisleWidth: aisleWidth, + isFlipped: isFlipped, + } + }); } } } else if (tempAisle !== null) { @@ -165,8 +195,14 @@ function ReferenceAisle({ tempPoints }: Readonly) { return ; case 'dotted-aisle': return ; + case 'arrow-aisle': + return ; case 'arrows-aisle': - return + return ; + case 'circle-aisle': + return ; + case 'junction-aisle': + return ; default: return null; } @@ -182,7 +218,7 @@ function ReferenceAisle({ tempPoints }: Readonly) { {toggleView && ) { sprite >
{distance.toFixed(2)} m
@@ -446,3 +482,227 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { ); } + +function ArrowAisle({ aisle }: { readonly aisle: Aisle }) { + const arrow = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null; + + const start = new THREE.Vector3(...aisle.points[0].position); + const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + + const direction = new THREE.Vector3().subVectors(end, start); + const length = direction.length(); + direction.normalize(); + + const shape = new THREE.Shape(); + const arrowHeadLength = width * 2; + const shaftLength = length - arrowHeadLength; + + if (shaftLength > 0) { + shape.moveTo(0, 0); + shape.lineTo(width, -arrowHeadLength); + shape.lineTo(width / 2, -arrowHeadLength); + shape.lineTo(width / 2, -length); + shape.lineTo(-width / 2, -length); + shape.lineTo(-width / 2, -arrowHeadLength); + shape.lineTo(-width, -arrowHeadLength); + shape.lineTo(0, 0); + } else { + shape.moveTo(0, 0); + shape.lineTo(width, -length); + shape.lineTo(-width, -length); + shape.lineTo(0, 0); + } + + shape.closePath(); + + const position = end; + const angle = Math.atan2(direction.x, direction.z); + + return { shape, position, rotationY: angle }; + }, [aisle]); + + if (!arrow) return null; + + return ( + + + + + + + + ); +} + +function CircleAisle({ aisle }: { readonly aisle: Aisle }) { + const circle = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null; + + const center = new THREE.Vector3(...aisle.points[0].position); + const widthCenter = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + + const middleRadius = center.distanceTo(widthCenter); + + const innerRadius = Math.max(0, middleRadius - width / 2); + const outerRadius = middleRadius + width / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + if (innerRadius > 0) { + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + } + + return { + shape, + position: center, + rotationY: 0 + }; + }, [aisle]); + + if (!circle) return null; + + return ( + + + + + + + + ); +} + +function JunctionAisle({ aisle }: { readonly aisle: Aisle }) { + const arrows = useMemo(() => { + if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null; + + const start = new THREE.Vector3(...aisle.points[0].position); + const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; + const isFlipped = aisle.type.isFlipped || false; + + const direction = new THREE.Vector3().subVectors(end, start); + const length = direction.length(); + direction.normalize(); + + const mainShape = new THREE.Shape(); + const arrowHeadLength = width * 2; + const shaftLength = length - arrowHeadLength; + + if (shaftLength > 0) { + mainShape.moveTo(0, 0); + mainShape.lineTo(width, -arrowHeadLength); + mainShape.lineTo(width / 2, -arrowHeadLength); + mainShape.lineTo(width / 2, -length); + mainShape.lineTo(-width / 2, -length); + mainShape.lineTo(-width / 2, -arrowHeadLength); + mainShape.lineTo(-width, -arrowHeadLength); + mainShape.lineTo(0, 0); + } else { + mainShape.moveTo(0, 0); + mainShape.lineTo(width, -length); + mainShape.lineTo(-width, -length); + mainShape.lineTo(0, 0); + } + mainShape.closePath(); + + const secondaryLength = length / 4; + const secondaryShape = new THREE.Shape(); + const secondaryHeadLength = width * 2; + const secondaryShaftLength = secondaryLength - secondaryHeadLength; + + if (secondaryShaftLength > 0) { + secondaryShape.moveTo(0, 0); + secondaryShape.lineTo(width / 2, 0); + secondaryShape.lineTo(width / 2, secondaryShaftLength); + secondaryShape.lineTo(width, secondaryShaftLength); + secondaryShape.lineTo(0, secondaryLength); + secondaryShape.lineTo(-width, secondaryShaftLength); + secondaryShape.lineTo(-width / 2, secondaryShaftLength); + secondaryShape.lineTo(-width / 2, 0); + secondaryShape.lineTo(0, 0); + } else { + secondaryShape.moveTo(0, 0); + secondaryShape.lineTo(width, 0); + secondaryShape.lineTo(0, secondaryLength); + secondaryShape.lineTo(-width, 0); + secondaryShape.lineTo(0, 0); + } + secondaryShape.closePath(); + + const mainPosition = end; + const mainAngle = Math.atan2(direction.x, direction.z); + + const perpendicularDirection = isFlipped + ? new THREE.Vector3(direction.z, 0, -direction.x).normalize() + : new THREE.Vector3(-direction.z, 0, direction.x).normalize(); + + const secondaryAngle = Math.atan2(perpendicularDirection.x, perpendicularDirection.z); + + const secondaryPosition = new THREE.Vector3().lerpVectors(start, end, 0.75); + + return [ + { shape: mainShape, position: mainPosition, rotationY: mainAngle }, + { + shape: secondaryShape, + position: secondaryPosition, + rotationY: secondaryAngle + Math.PI + } + ]; + }, [aisle]); + + if (!arrows) return null; + + return ( + + {arrows.map((arrow, index) => ( + + + + + + ))} + + ); +} \ No newline at end of file diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index 3263b90..911d78c 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -35,7 +35,6 @@ import * as Types from "../../types/world/worldTypes"; import SocketResponses from "../collaboration/socket/socketResponses.dev"; import FloorPlanGroup from "./groups/floorPlanGroup"; import FloorGroup from "./groups/floorGroup"; -import FloorGroupAilse from "./groups/floorGroupAisle"; import Draw from "./functions/draw"; import WallsAndWallItems from "./groups/wallsAndWallItems"; import Ground from "../scene/environment/ground"; @@ -275,26 +274,6 @@ export default function Builder() { - {/* */} - { - if (aisle.length >= 2 && aisle[0] && aisle[1]) { - const start: Types.Vector3 = aisle[0][0]; - const end: Types.Vector3 = aisle[1][0]; - - const direction = new THREE.Vector3( - end.x - start.x, - end.y - start.y, - end.z - start.z - ).normalize(); - - const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); - const offsetDistance = CONSTANTS.aisleConfig.width; - - const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, offsetDistance); - const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -offsetDistance); - const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, offsetDistance); - const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -offsetDistance); - - const stripShape = new THREE.Shape(); - stripShape.moveTo(leftStart.x, leftStart.z); - stripShape.lineTo(leftEnd.x, leftEnd.z); - stripShape.lineTo(rightEnd.x, rightEnd.z); - stripShape.lineTo(rightStart.x, rightStart.z); - stripShape.lineTo(leftStart.x, leftStart.z); - - const extrudeSettings = { - depth: CONSTANTS.aisleConfig.height, - bevelEnabled: false, - }; - - const stripGeometry = new THREE.ExtrudeGeometry(stripShape, extrudeSettings); - const stripMaterial = new THREE.MeshStandardMaterial({ - color: CONSTANTS.aisleConfig.defaultColor, - polygonOffset: true, - polygonOffsetFactor: -1, - polygonOffsetUnits: -1, - }); - - const stripMesh = new THREE.Mesh(stripGeometry, stripMaterial); - stripMesh.receiveShadow = true; - stripMesh.castShadow = true; - - stripMesh.position.y = (aisle[0][2] - 1) * CONSTANTS.wallConfig.height + 0.01; - stripMesh.rotateX(Math.PI / 2); - - floorGroupAisle.current.add(stripMesh); - } -} diff --git a/app/src/modules/builder/geomentries/aisles/loadAisles.ts b/app/src/modules/builder/geomentries/aisles/loadAisles.ts deleted file mode 100644 index 437ae66..0000000 --- a/app/src/modules/builder/geomentries/aisles/loadAisles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as Types from '../../../../types/world/worldTypes'; -import addAisleToScene from './addAilseToScene'; -import * as CONSTANTS from '../../../../types/world/worldConstants'; - -export default async function loadAisles( - lines: Types.RefLines, - floorGroupAisle: Types.RefGroup -) { - // console.log('lines: ', lines.current[0][0][0]); - if (!floorGroupAisle.current) return - floorGroupAisle.current.children = []; - const aisles = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName); - - if (aisles.length > 0) { - aisles.forEach((aisle: Types.Line) => { - addAisleToScene(aisle, floorGroupAisle) - }) - } -} \ No newline at end of file diff --git a/app/src/modules/builder/groups/floorGroupAisle.tsx b/app/src/modules/builder/groups/floorGroupAisle.tsx deleted file mode 100644 index 2c65b64..0000000 --- a/app/src/modules/builder/groups/floorGroupAisle.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import * as THREE from 'three'; -import * as Types from '../../../types/world/worldTypes'; -import * as CONSTANTS from '../../../types/world/worldConstants'; -import { useThree } from "@react-three/fiber"; -import { useToggleView, useActiveLayer, useSocketStore, useDeletePointOrLine, useUpdateScene, useNewLines, useToolMode } from "../../../store/builder/store"; -import { useEffect } from "react"; -import removeSoloPoint from "../geomentries/points/removeSoloPoint"; -import removeReferenceLine from "../geomentries/lines/removeReferenceLine"; -import getClosestIntersection from "../geomentries/lines/getClosestIntersection"; -import addPointToScene from "../geomentries/points/addPointToScene"; -import arrayLineToObject from '../geomentries/lines/lineConvertions/arrayLineToObject'; -import addLineToScene from "../geomentries/lines/addLineToScene"; -import loadAisles from '../geomentries/aisles/loadAisles'; - -const FloorGroupAilse = ({ floorGroupAisle, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => { - const { toggleView } = useToggleView(); - const { setDeletePointOrLine } = useDeletePointOrLine(); - const { toolMode } = useToolMode(); - const { socket } = useSocketStore(); - const { activeLayer } = useActiveLayer(); - const { gl, raycaster } = useThree(); - const { updateScene, setUpdateScene } = useUpdateScene(); - const { setNewLines } = useNewLines(); - - useEffect(() => { - if (updateScene) { - loadAisles(lines, floorGroupAisle); - setUpdateScene(false); - } - }, [updateScene]) - - useEffect(() => { - if (toolMode === "Aisle") { - setDeletePointOrLine(false); - } else { - removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint); - removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line); - } - }, [toolMode]); - - useEffect(() => { - - const canvasElement = gl.domElement; - - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - } - }; - - const onMouseUp = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = false; - } - } - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - }; - - const onContextMenu = (e: any) => { - e.preventDefault(); - if (toolMode === "Aisle") { - removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint); - removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line); - } - }; - - const onMouseClick = (evt: any) => { - if (!plane.current || drag) return; - - const intersects = raycaster.intersectObject(plane.current, true); - let intersectionPoint = intersects[0].point; - const points = floorPlanGroupPoint.current?.children ?? []; - const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible); - let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true); - - - if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) { - const lineType = intersectsLines[0].object.userData.linePoints[0][3]; - if (lineType === CONSTANTS.lineConfig.aisleName) { - // console.log("intersected a aisle line"); - const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints); - let intersection = getClosestIntersection(ThroughPoint, intersectionPoint); - if (!intersection) return; - const point = addPointToScene(intersection, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName); - (line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]); - if (line.current.length >= 2 && line.current[0] && line.current[1]) { - lines.current.push(line.current as Types.Line); - - const data = arrayLineToObject(line.current as Types.Line); - - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - - //REST - - // setLine(organization, data.layer!, data.line!, data.type!); - - //SOCKET - - const input = { - organization: organization, - layer: data.layer, - line: data.line, - type: data.type, - socketId: socket.id - } - - socket.emit('v1:Line:create', input); - - setNewLines([line.current]); - - addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine); - let lastPoint = line.current[line.current.length - 1]; - line.current = [lastPoint]; - } - } - } else if (intersectsPoint && intersects && intersects.length > 0) { - if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.aisleName) { - // console.log("intersected a aisle point"); - intersectionPoint = intersectsPoint.object.position; - (line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]); - if (line.current.length >= 2 && line.current[0] && line.current[1]) { - lines.current.push(line.current as Types.Line); - - const data = arrayLineToObject(line.current as Types.Line); - - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - - //REST - - // setLine(organization, data.layer!, data.line!, data.type!); - - //SOCKET - - const input = { - organization: organization, - layer: data.layer, - line: data.line, - type: data.type, - socketId: socket.id - } - - socket.emit('v1:Line:create', input); - - setNewLines([line.current]); - - addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine); - let lastPoint = line.current[line.current.length - 1]; - line.current = [lastPoint]; - ispreSnapped.current = false; - isSnapped.current = false; - } - } - } else if (intersects && intersects.length > 0) { - // console.log("intersected a empty area"); - let uuid: string = ""; - if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) { - intersectionPoint = anglesnappedPoint.current; - const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName); - uuid = point.uuid; - } else if (isSnapped.current && snappedPoint.current && line.current.length > 0) { - intersectionPoint = snappedPoint.current; - uuid = isSnappedUUID.current!; - } else if (ispreSnapped.current && snappedPoint.current) { - intersectionPoint = snappedPoint.current; - uuid = isSnappedUUID.current!; - } else { - const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName); - uuid = point.uuid; - } - (line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]); - - if (line.current.length >= 2 && line.current[0] && line.current[1]) { - lines.current.push(line.current as Types.Line); - - const data = arrayLineToObject(line.current as Types.Line); - - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - - //REST - - // setLine(organization, data.layer!, data.line!, data.type!); - - //SOCKET - - const input = { - organization: organization, - layer: data.layer, - line: data.line, - type: data.type, - socketId: socket.id - } - - socket.emit('v1:Line:create', input); - - setNewLines([line.current]); - - addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine); - let lastPoint = line.current[line.current.length - 1]; - line.current = [lastPoint]; - ispreSnapped.current = false; - isSnapped.current = false; - } - } - } - - - if (toolMode === 'Aisle') { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("click", onMouseClick); - canvasElement.addEventListener("contextmenu", onContextMenu); - } - - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("click", onMouseClick); - canvasElement.removeEventListener("contextmenu", onContextMenu); - }; - }, [toolMode]) - - - return ( - - - ) -} - -export default FloorGroupAilse; \ No newline at end of file diff --git a/app/src/modules/builder/groups/zoneGroup.tsx b/app/src/modules/builder/groups/zoneGroup.tsx index a18e265..349fe07 100644 --- a/app/src/modules/builder/groups/zoneGroup.tsx +++ b/app/src/modules/builder/groups/zoneGroup.tsx @@ -3,15 +3,15 @@ import { Html, Line, Sphere } from "@react-three/drei"; import { useThree, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { - useActiveLayer, - useDeleteTool, - useDeletePointOrLine, - useSocketStore, - useToggleView, - useToolMode, - useRemovedLayer, - useZones, - useZonePoints, + useActiveLayer, + useDeleteTool, + useDeletePointOrLine, + useSocketStore, + useToggleView, + useToolMode, + useRemovedLayer, + useZones, + useZonePoints, } from "../../../store/builder/store"; import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi"; @@ -21,42 +21,42 @@ import { computeArea } from "../functions/computeArea"; import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; const ZoneGroup: React.FC = () => { - const { camera, pointer, gl, raycaster, scene, controls } = useThree(); - const [startPoint, setStartPoint] = useState(null); - const [endPoint, setEndPoint] = useState(null); - const { zones, setZones } = useZones(); - const { zonePoints, setZonePoints } = useZonePoints(); - const [isDragging, setIsDragging] = useState(false); - const { selectedZone } = useSelectedZoneStore(); - const [draggedSphere, setDraggedSphere] = useState( - null - ); - const plane = useMemo( - () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), - [] - ); - const { toggleView } = useToggleView(); - const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); - const { removedLayer, setRemovedLayer } = useRemovedLayer(); - const { toolMode } = useToolMode(); - const { setDeleteTool } = useDeleteTool(); - const { activeLayer } = useActiveLayer(); - const { socket } = useSocketStore(); + const { camera, pointer, gl, raycaster, scene, controls } = useThree(); + const [startPoint, setStartPoint] = useState(null); + const [endPoint, setEndPoint] = useState(null); + const { zones, setZones } = useZones(); + const { zonePoints, setZonePoints } = useZonePoints(); + const [isDragging, setIsDragging] = useState(false); + const { selectedZone } = useSelectedZoneStore(); + const [draggedSphere, setDraggedSphere] = useState( + null + ); + const plane = useMemo( + () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), + [] + ); + const { toggleView } = useToggleView(); + const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); + const { removedLayer, setRemovedLayer } = useRemovedLayer(); + const { toolMode } = useToolMode(); + const { setDeleteTool } = useDeleteTool(); + const { activeLayer } = useActiveLayer(); + const { socket } = useSocketStore(); - const groupsRef = useRef(); + const groupsRef = useRef(); - const zoneMaterial = useMemo( - () => - new THREE.ShaderMaterial({ - side: THREE.DoubleSide, - vertexShader: ` + const zoneMaterial = useMemo( + () => + new THREE.ShaderMaterial({ + side: THREE.DoubleSide, + vertexShader: ` varying vec2 vUv; void main(){ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); vUv = uv; } `, - fragmentShader: ` + fragmentShader: ` varying vec2 vUv; uniform vec3 uOuterColor; void main(){ @@ -64,671 +64,674 @@ const ZoneGroup: React.FC = () => { gl_FragColor = vec4(uOuterColor, alpha); } `, - uniforms: { - uOuterColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, - }, - transparent: true, - depthWrite: false, - }), - [] - ); - - useEffect(() => { - const fetchZones = async () => { - const email = localStorage.getItem("email"); - if (!email) return; - - const organization = email.split("@")[1].split(".")[0]; - const data = await getZonesApi(organization); - - if (data.data && data.data.length > 0) { - const fetchedZones = data.data.map((zone: any) => ({ - zoneId: zone.zoneId, - zoneName: zone.zoneName, - points: zone.points, - viewPortCenter: zone.viewPortCenter, - viewPortposition: zone.viewPortposition, - layer: zone.layer, - })); - - setZones(fetchedZones); - - const fetchedPoints = data.data.flatMap((zone: any) => - zone.points - .slice(0, 4) - .map( - (point: [number, number, number]) => new THREE.Vector3(...point) - ) - ); - - setZonePoints(fetchedPoints); - } - }; - - fetchZones(); - }, []); - - useEffect(() => { - localStorage.setItem("zones", JSON.stringify(zones)); - }, [zones]); - - useEffect(() => { - if (removedLayer) { - const updatedZones = zones.filter( - (zone: any) => zone.layer !== removedLayer - ); - setZones(updatedZones); - - const updatedzonePoints = zonePoints.filter((_: any, index: any) => { - const zoneIndex = Math.floor(index / 4); - return zones[zoneIndex]?.layer !== removedLayer; - }); - setZonePoints(updatedzonePoints); - - zones - .filter((zone: any) => zone.layer === removedLayer) - .forEach((zone: any) => { - deleteZoneFromBackend(zone.zoneId); - }); - - setRemovedLayer(null); - } - }, [removedLayer]); - - useEffect(() => { - if (toolMode !== "Zone") { - setStartPoint(null); - setEndPoint(null); - } else { - setDeletePointOrLine(false); - setDeleteTool(false); - } - if (!toggleView) { - setStartPoint(null); - setEndPoint(null); - } - }, [toolMode, toggleView]); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const addZoneToBackend = async (zone: { - zoneId: string; - zoneName: string; - points: [number, number, number][]; - layer: string; - }) => { - const email = localStorage.getItem("email"); - const userId = localStorage.getItem("userId"); - const organization = email!.split("@")[1].split(".")[0]; - - const calculateCenter = (points: number[][]) => { - if (!points || points.length === 0) return null; - - let sumX = 0, - sumY = 0, - sumZ = 0; - const numPoints = points.length; - - points.forEach(([x, y, z]) => { - sumX += x; - sumY += y; - sumZ += z; - }); - - return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ - number, - number, - number - ]; - }; - - const target: [number, number, number] | null = calculateCenter( - zone.points + uniforms: { + uOuterColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, + }, + transparent: true, + depthWrite: false, + }), + [] ); - if (!target || zone.points.length < 4) return; - const position = [target[0], 10, target[2]]; - const input = { - userId: userId, - organization: organization, - zoneData: { - zoneName: zone.zoneName, - zoneId: zone.zoneId, - points: zone.points, - viewPortCenter: target, - viewPortposition: position, - layer: zone.layer, - }, - }; + useEffect(() => { + const fetchZones = async () => { + const email = localStorage.getItem("email"); + if (!email) return; - socket.emit("v2:zone:set", input); - }; + const organization = email.split("@")[1].split(".")[0]; + const data = await getZonesApi(organization); - // eslint-disable-next-line react-hooks/exhaustive-deps - const updateZoneToBackend = async (zone: { - zoneId: string; - zoneName: string; - points: [number, number, number][]; - layer: string; - }) => { - const email = localStorage.getItem("email"); - const userId = localStorage.getItem("userId"); - const organization = email!.split("@")[1].split(".")[0]; + if (data.data && data.data.length > 0) { + const fetchedZones = data.data.map((zone: any) => ({ + zoneId: zone.zoneId, + zoneName: zone.zoneName, + points: zone.points, + viewPortCenter: zone.viewPortCenter, + viewPortposition: zone.viewPortposition, + layer: zone.layer, + })); - const calculateCenter = (points: number[][]) => { - if (!points || points.length === 0) return null; + console.log('fetchedZones: ', fetchedZones); + setZones(fetchedZones); - let sumX = 0, - sumY = 0, - sumZ = 0; - const numPoints = points.length; + const fetchedPoints = data.data.flatMap((zone: any) => + zone.points + .slice(0, 4) + .map( + (point: [number, number, number]) => new THREE.Vector3(...point) + ) + ); - points.forEach(([x, y, z]) => { - sumX += x; - sumY += y; - sumZ += z; - }); - - return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ - number, - number, - number - ]; - }; - - const target: [number, number, number] | null = calculateCenter( - zone.points - ); - if (!target || zone.points.length < 4) return; - const position = [target[0], 10, target[2]]; - - const input = { - userId: userId, - organization: organization, - zoneData: { - zoneName: zone.zoneName, - zoneId: zone.zoneId, - points: zone.points, - viewPortCenter: target, - viewPortposition: position, - layer: zone.layer, - }, - }; - - socket.emit("v2:zone:set", input); - }; - - const deleteZoneFromBackend = async (zoneId: string) => { - const email = localStorage.getItem("email"); - const userId = localStorage.getItem("userId"); - const organization = email!.split("@")[1].split(".")[0]; - - const input = { - userId: userId, - organization: organization, - zoneId: zoneId, - }; - - socket.emit("v2:zone:delete", input); - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - const handleDeleteZone = (zoneId: string) => { - const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId); - setZones(updatedZones); - - const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId); - if (zoneIndex !== -1) { - const zonePointsToRemove = zonePoints.slice( - zoneIndex * 4, - zoneIndex * 4 + 4 - ); - zonePointsToRemove.forEach((point: any) => - groupsRef.current.remove(point) - ); - const updatedzonePoints = zonePoints.filter( - (_: any, index: any) => - index < zoneIndex * 4 || index >= zoneIndex * 4 + 4 - ); - setZonePoints(updatedzonePoints); - } - deleteZoneFromBackend(zoneId); - }; - - useEffect(() => { - if (!camera || !toggleView) return; - const canvasElement = gl.domElement; - - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects( - groupsRef.current.children, - true - ); - - if (intersects.length > 0 && toolMode === "move") { - const clickedObject = intersects[0].object; - const sphereIndex = zonePoints.findIndex((point: any) => - point.equals(clickedObject.position) - ); - if (sphereIndex !== -1) { - (controls as any).enabled = false; - setDraggedSphere(zonePoints[sphereIndex]); - setIsDragging(true); - } - } - } - }; - - const onMouseUp = (evt: any) => { - if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) { - isLeftMouseDown = false; - - if (!startPoint && toolMode !== "move") { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - setStartPoint(point); - setEndPoint(null); - } - } else if (startPoint && toolMode !== "move") { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (!point) return; - - const points = [ - [startPoint.x, 0.15, startPoint.z], - [point.x, 0.15, startPoint.z], - [point.x, 0.15, point.z], - [startPoint.x, 0.15, point.z], - [startPoint.x, 0.15, startPoint.z], - ] as [number, number, number][]; - - const zoneName = `Zone ${zones.length + 1}`; - const zoneId = THREE.MathUtils.generateUUID(); - const newZone = { - zoneId, - zoneName, - points: points, - layer: activeLayer, - }; - - const newZones = [...zones, newZone]; - - setZones(newZones); - - const newzonePoints = [ - new THREE.Vector3(startPoint.x, 0.15, startPoint.z), - new THREE.Vector3(point.x, 0.15, startPoint.z), - new THREE.Vector3(point.x, 0.15, point.z), - new THREE.Vector3(startPoint.x, 0.15, point.z), - ]; - - const updatedZonePoints = [...zonePoints, ...newzonePoints]; - setZonePoints(updatedZonePoints); - - addZoneToBackend(newZone); - setStartPoint(null); - setEndPoint(null); - } - } else if ( - evt.button === 0 && - !drag && - !isDragging && - deletePointOrLine - ) { - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects( - groupsRef.current.children, - true - ); - - if (intersects.length > 0) { - const clickedObject = intersects[0].object; - - const sphereIndex = zonePoints.findIndex((point: any) => - point.equals(clickedObject.position) - ); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - const zoneId = zones[zoneIndex].zoneId; - handleDeleteZone(zoneId); - return; - } - } - } - - if (evt.button === 0) { - if (isDragging && draggedSphere) { - setIsDragging(false); - setDraggedSphere(null); - - const sphereIndex = zonePoints.findIndex( - (point: any) => point === draggedSphere - ); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - - if (zoneIndex !== -1 && zones[zoneIndex]) { - updateZoneToBackend(zones[zoneIndex]); + setZonePoints(fetchedPoints); } - } + }; + + fetchZones(); + }, []); + + useEffect(() => { + console.log('zones: ', zones); + localStorage.setItem("zones", JSON.stringify(zones)); + }, [zones]); + + useEffect(() => { + if (removedLayer) { + const updatedZones = zones.filter( + (zone: any) => zone.layer !== removedLayer + ); + setZones(updatedZones); + + const updatedzonePoints = zonePoints.filter((_: any, index: any) => { + const zoneIndex = Math.floor(index / 4); + return zones[zoneIndex]?.layer !== removedLayer; + }); + setZonePoints(updatedzonePoints); + + zones + .filter((zone: any) => zone.layer === removedLayer) + .forEach((zone: any) => { + deleteZoneFromBackend(zone.zoneId); + }); + + setRemovedLayer(null); } - } + }, [removedLayer]); + + useEffect(() => { + if (toolMode !== "Zone") { + setStartPoint(null); + setEndPoint(null); + } else { + setDeletePointOrLine(false); + setDeleteTool(false); + } + if (!toggleView) { + setStartPoint(null); + setEndPoint(null); + } + }, [toolMode, toggleView]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const addZoneToBackend = async (zone: { + zoneId: string; + zoneName: string; + points: [number, number, number][]; + layer: string; + }) => { + console.log('zoneId: ', zone); + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; + + const calculateCenter = (points: number[][]) => { + if (!points || points.length === 0) return null; + + let sumX = 0, + sumY = 0, + sumZ = 0; + const numPoints = points.length; + + points.forEach(([x, y, z]) => { + sumX += x; + sumY += y; + sumZ += z; + }); + + return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ + number, + number, + number + ]; + }; + + const target: [number, number, number] | null = calculateCenter( + zone.points + ); + if (!target || zone.points.length < 4) return; + const position = [target[0], 10, target[2]]; + + const input = { + userId: userId, + organization: organization, + zoneData: { + zoneName: zone.zoneName, + zoneId: zone.zoneId, + points: zone.points, + viewPortCenter: target, + viewPortposition: position, + layer: zone.layer, + }, + }; + + socket.emit("v2:zone:set", input); }; - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects( - groupsRef.current.children, - true - ); + // eslint-disable-next-line react-hooks/exhaustive-deps + const updateZoneToBackend = async (zone: { + zoneId: string; + zoneName: string; + points: [number, number, number][]; + layer: string; + }) => { + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; - if ( - intersects.length > 0 && - intersects[0].object.name.includes("point") - ) { - gl.domElement.style.cursor = - toolMode === "move" ? "pointer" : "default"; - } else { - gl.domElement.style.cursor = "default"; - } - if (isDragging && draggedSphere) { + const calculateCenter = (points: number[][]) => { + if (!points || points.length === 0) return null; + + let sumX = 0, + sumY = 0, + sumZ = 0; + const numPoints = points.length; + + points.forEach(([x, y, z]) => { + sumX += x; + sumY += y; + sumZ += z; + }); + + return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [ + number, + number, + number + ]; + }; + + const target: [number, number, number] | null = calculateCenter( + zone.points + ); + if (!target || zone.points.length < 4) return; + const position = [target[0], 10, target[2]]; + + const input = { + userId: userId, + organization: organization, + zoneData: { + zoneName: zone.zoneName, + zoneId: zone.zoneId, + points: zone.points, + viewPortCenter: target, + viewPortposition: position, + layer: zone.layer, + }, + }; + + socket.emit("v2:zone:set", input); + }; + + const deleteZoneFromBackend = async (zoneId: string) => { + const email = localStorage.getItem("email"); + const userId = localStorage.getItem("userId"); + const organization = email!.split("@")[1].split(".")[0]; + + const input = { + userId: userId, + organization: organization, + zoneId: zoneId, + }; + + socket.emit("v2:zone:delete", input); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + const handleDeleteZone = (zoneId: string) => { + const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId); + setZones(updatedZones); + + const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId); + if (zoneIndex !== -1) { + const zonePointsToRemove = zonePoints.slice( + zoneIndex * 4, + zoneIndex * 4 + 4 + ); + zonePointsToRemove.forEach((point: any) => + groupsRef.current.remove(point) + ); + const updatedzonePoints = zonePoints.filter( + (_: any, index: any) => + index < zoneIndex * 4 || index >= zoneIndex * 4 + 4 + ); + setZonePoints(updatedzonePoints); + } + deleteZoneFromBackend(zoneId); + }; + + useEffect(() => { + if (!camera || !toggleView) return; + const canvasElement = gl.domElement; + + let drag = false; + let isLeftMouseDown = false; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = true; + drag = false; + + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); + + if (intersects.length > 0 && toolMode === "move") { + const clickedObject = intersects[0].object; + const sphereIndex = zonePoints.findIndex((point: any) => + point.equals(clickedObject.position) + ); + if (sphereIndex !== -1) { + (controls as any).enabled = false; + setDraggedSphere(zonePoints[sphereIndex]); + setIsDragging(true); + } + } + } + }; + + const onMouseUp = (evt: any) => { + if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) { + isLeftMouseDown = false; + + if (!startPoint && toolMode !== "move") { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (point) { + setStartPoint(point); + setEndPoint(null); + } + } else if (startPoint && toolMode !== "move") { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!point) return; + + const points = [ + [startPoint.x, 0.15, startPoint.z], + [point.x, 0.15, startPoint.z], + [point.x, 0.15, point.z], + [startPoint.x, 0.15, point.z], + [startPoint.x, 0.15, startPoint.z], + ] as [number, number, number][]; + + const zoneName = `Zone ${zones.length + 1}`; + const zoneId = THREE.MathUtils.generateUUID(); + const newZone = { + zoneId, + zoneName, + points: points, + layer: activeLayer, + }; + + const newZones = [...zones, newZone]; + + setZones(newZones); + + const newzonePoints = [ + new THREE.Vector3(startPoint.x, 0.15, startPoint.z), + new THREE.Vector3(point.x, 0.15, startPoint.z), + new THREE.Vector3(point.x, 0.15, point.z), + new THREE.Vector3(startPoint.x, 0.15, point.z), + ]; + + const updatedZonePoints = [...zonePoints, ...newzonePoints]; + setZonePoints(updatedZonePoints); + + addZoneToBackend(newZone); + setStartPoint(null); + setEndPoint(null); + } + } else if ( + evt.button === 0 && + !drag && + !isDragging && + deletePointOrLine + ) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); + + if (intersects.length > 0) { + const clickedObject = intersects[0].object; + + const sphereIndex = zonePoints.findIndex((point: any) => + point.equals(clickedObject.position) + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); + const zoneId = zones[zoneIndex].zoneId; + handleDeleteZone(zoneId); + return; + } + } + } + + if (evt.button === 0) { + if (isDragging && draggedSphere) { + setIsDragging(false); + setDraggedSphere(null); + + const sphereIndex = zonePoints.findIndex( + (point: any) => point === draggedSphere + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); + + if (zoneIndex !== -1 && zones[zoneIndex]) { + updateZoneToBackend(zones[zoneIndex]); + } + } + } + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) { + drag = true; + } + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + groupsRef.current.children, + true + ); + + if ( + intersects.length > 0 && + intersects[0].object.name.includes("point") + ) { + gl.domElement.style.cursor = + toolMode === "move" ? "pointer" : "default"; + } else { + gl.domElement.style.cursor = "default"; + } + if (isDragging && draggedSphere) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (point) { + draggedSphere.set(point.x, 0.15, point.z); + + const sphereIndex = zonePoints.findIndex( + (point: any) => point === draggedSphere + ); + if (sphereIndex !== -1) { + const zoneIndex = Math.floor(sphereIndex / 4); + const cornerIndex = sphereIndex % 4; + + const updatedZones = zones.map((zone: any, index: number) => { + if (index === zoneIndex) { + const updatedPoints = [...zone.points]; + updatedPoints[cornerIndex] = [point.x, 0.15, point.z]; + updatedPoints[4] = updatedPoints[0]; + return { ...zone, points: updatedPoints }; + } + return zone; + }); + + setZones(updatedZones); + } + } + } + }; + + const onContext = (event: any) => { + event.preventDefault(); + setStartPoint(null); + setEndPoint(null); + }; + + if (toolMode === "Zone" || deletePointOrLine || toolMode === "move") { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("contextmenu", onContext); + } + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("contextmenu", onContext); + }; + }, [ + gl, + camera, + startPoint, + toggleView, + scene, + toolMode, + zones, + isDragging, + deletePointOrLine, + zonePoints, + draggedSphere, + activeLayer, + raycaster, + pointer, + controls, + plane, + setZones, + setZonePoints, + addZoneToBackend, + handleDeleteZone, + updateZoneToBackend, + ]); + + useFrame(() => { + if (!startPoint) return; raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const point = raycaster.ray.intersectPlane(plane, intersectionPoint); if (point) { - draggedSphere.set(point.x, 0.15, point.z); - - const sphereIndex = zonePoints.findIndex( - (point: any) => point === draggedSphere - ); - if (sphereIndex !== -1) { - const zoneIndex = Math.floor(sphereIndex / 4); - const cornerIndex = sphereIndex % 4; - - const updatedZones = zones.map((zone: any, index: number) => { - if (index === zoneIndex) { - const updatedPoints = [...zone.points]; - updatedPoints[cornerIndex] = [point.x, 0.15, point.z]; - updatedPoints[4] = updatedPoints[0]; - return { ...zone, points: updatedPoints }; - } - return zone; - }); - - setZones(updatedZones); - } + setEndPoint(point); } - } - }; + }); - const onContext = (event: any) => { - event.preventDefault(); - setStartPoint(null); - setEndPoint(null); - }; + return ( + + + {zones.map((zone: any) => ( + + {zone.points + .slice(0, -1) + .map((point: [number, number, number], index: number) => { + const nextPoint = zone.points[index + 1]; - if (toolMode === "Zone" || deletePointOrLine || toolMode === "move") { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("contextmenu", onContext); - } - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("contextmenu", onContext); - }; - }, [ - gl, - camera, - startPoint, - toggleView, - scene, - toolMode, - zones, - isDragging, - deletePointOrLine, - zonePoints, - draggedSphere, - activeLayer, - raycaster, - pointer, - controls, - plane, - setZones, - setZonePoints, - addZoneToBackend, - handleDeleteZone, - updateZoneToBackend, - ]); + const point1 = new THREE.Vector3(point[0], point[1], point[2]); + const point2 = new THREE.Vector3( + nextPoint[0], + nextPoint[1], + nextPoint[2] + ); - useFrame(() => { - if (!startPoint) return; - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - setEndPoint(point); - } - }); + const planeWidth = point1.distanceTo(point2); + const planeHeight = CONSTANTS.zoneConfig.height; - return ( - - - {zones.map((zone: any) => ( - - {zone.points - .slice(0, -1) - .map((point: [number, number, number], index: number) => { - const nextPoint = zone.points[index + 1]; + const midpoint = new THREE.Vector3( + (point1.x + point2.x) / 2, + CONSTANTS.zoneConfig.height / 2 + + (zone.layer - 1) * CONSTANTS.zoneConfig.height, + (point1.z + point2.z) / 2 + ); - const point1 = new THREE.Vector3(point[0], point[1], point[2]); - const point2 = new THREE.Vector3( - nextPoint[0], - nextPoint[1], - nextPoint[2] - ); + const angle = Math.atan2( + point2.z - point1.z, + point2.x - point1.x + ); - const planeWidth = point1.distanceTo(point2); - const planeHeight = CONSTANTS.zoneConfig.height; + return ( + + + + + ); + })} + {!toggleView && + (() => { + const points3D = zone.points || []; + const coords2D = points3D.map((p: any) => [p[0], p[2]]); - const midpoint = new THREE.Vector3( - (point1.x + point2.x) / 2, - CONSTANTS.zoneConfig.height / 2 + - (zone.layer - 1) * CONSTANTS.zoneConfig.height, - (point1.z + point2.z) / 2 - ); + // Ensure the polygon is closed + if ( + coords2D.length >= 4 && + (coords2D[0][0] !== coords2D[coords2D.length - 1][0] || + coords2D[0][1] !== coords2D[coords2D.length - 1][1]) + ) { + coords2D.push(coords2D[0]); + } + if (coords2D.length < 4) return null; - const angle = Math.atan2( - point2.z - point1.z, - point2.x - point1.x - ); + const polygon = turf.polygon([coords2D]); + const center2D = turf.center(polygon).geometry.coordinates; - return ( - - - sum + p[1], + 0 + ); + const avgY = points3D.length > 0 ? sumY / points3D.length : 0; + + const htmlPosition: [number, number, number] = [ + center2D[0], + avgY + (CONSTANTS.zoneConfig.height || 0) + 1.5, + center2D[1], + ]; + + return ( + +
{zone.zoneName}
+ + ); + })()} +
+ ))} +
+ + {zones + .filter((zone: any) => zone.layer === activeLayer) + .map((zone: any) => ( + { + e.stopPropagation(); + if (deletePointOrLine) { + handleDeleteZone(zone.zoneId); + } + }} + /> + ))} + + + {zones.map((zone: any, index: any) => { + if (!toggleView) return null; + const points3D = zone.points; + const coords2D = points3D.map((p: any) => [p[0], p[2]]); + + if ( + coords2D.length < 4 || + coords2D[0][0] !== coords2D[coords2D.length - 1][0] || + coords2D[0][1] !== coords2D[coords2D.length - 1][1] + ) { + coords2D.push(coords2D[0]); + } + if (coords2D.length < 4) return null; + + const polygon = turf.polygon([coords2D]); + const center2D = turf.center(polygon).geometry.coordinates; + + const sumY = points3D.reduce((sum: number, p: any) => sum + p[1], 0); + const avgY = sumY / points3D.length; + + const area = computeArea(points3D, "zone"); + const formattedArea = `${area.toFixed(2)} m²`; + + const htmlPosition: [number, number, number] = [ + center2D[0], + avgY + CONSTANTS.zoneConfig.height, + center2D[1], + ]; + return ( + +
+ {zone.zoneName} ({formattedArea}) +
+ + ); + })} +
+ + + {zones + .filter((zone: any) => zone.layer === activeLayer) + .flatMap((zone: any) => + zone.points.slice(0, 4).map((point: any, pointIndex: number) => ( + + + + )) + )} + + + {startPoint && endPoint && ( + - - ); - })} - {!toggleView && - (() => { - const points3D = zone.points || []; - const coords2D = points3D.map((p: any) => [p[0], p[2]]); - - // Ensure the polygon is closed - if ( - coords2D.length >= 4 && - (coords2D[0][0] !== coords2D[coords2D.length - 1][0] || - coords2D[0][1] !== coords2D[coords2D.length - 1][1]) - ) { - coords2D.push(coords2D[0]); - } - if (coords2D.length < 4) return null; - - const polygon = turf.polygon([coords2D]); - const center2D = turf.center(polygon).geometry.coordinates; - - // Calculate the average Y value - const sumY = points3D.reduce( - (sum: number, p: any) => sum + p[1], - 0 - ); - const avgY = points3D.length > 0 ? sumY / points3D.length : 0; - - const htmlPosition: [number, number, number] = [ - center2D[0], - avgY + (CONSTANTS.zoneConfig.height || 0) + 1.5, - center2D[1], - ]; - - return ( - -
{zone.zoneName}
- - ); - })()} -
- ))} -
- - {zones - .filter((zone: any) => zone.layer === activeLayer) - .map((zone: any) => ( - { - e.stopPropagation(); - if (deletePointOrLine) { - handleDeleteZone(zone.zoneId); - } - }} - /> - ))} - - - {zones.map((zone: any, index: any) => { - if (!toggleView) return null; - const points3D = zone.points; - const coords2D = points3D.map((p: any) => [p[0], p[2]]); - - if ( - coords2D.length < 4 || - coords2D[0][0] !== coords2D[coords2D.length - 1][0] || - coords2D[0][1] !== coords2D[coords2D.length - 1][1] - ) { - coords2D.push(coords2D[0]); - } - if (coords2D.length < 4) return null; - - const polygon = turf.polygon([coords2D]); - const center2D = turf.center(polygon).geometry.coordinates; - - const sumY = points3D.reduce((sum: number, p: any) => sum + p[1], 0); - const avgY = sumY / points3D.length; - - const area = computeArea(points3D, "zone"); - const formattedArea = `${area.toFixed(2)} m²`; - - const htmlPosition: [number, number, number] = [ - center2D[0], - avgY + CONSTANTS.zoneConfig.height, - center2D[1], - ]; - return ( - -
- {zone.zoneName} ({formattedArea}) -
- - ); - })} -
- - - {zones - .filter((zone: any) => zone.layer === activeLayer) - .flatMap((zone: any) => - zone.points.slice(0, 4).map((point: any, pointIndex: number) => ( - - - - )) - )} - - - {startPoint && endPoint && ( - - )} - -
- ); + )} +
+
+ ); }; export default ZoneGroup; diff --git a/app/src/modules/builder/point/helpers/useAisleDragSnap.tsx b/app/src/modules/builder/point/helpers/useAisleDragSnap.tsx index 123c7e3..d97e9d4 100644 --- a/app/src/modules/builder/point/helpers/useAisleDragSnap.tsx +++ b/app/src/modules/builder/point/helpers/useAisleDragSnap.tsx @@ -16,7 +16,7 @@ export function useAislePointSnapping(point: Point) { } => { if (!CAN_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; - const connectedPoints = getConnectedPoints(point.uuid); + const connectedPoints = getConnectedPoints(point.pointUuid); if (connectedPoints.length === 0) { return { position: newPosition, @@ -68,7 +68,7 @@ export function useAislePointSnapping(point: Point) { isSnapped, snapSources }; - }, [point.uuid, getConnectedPoints]); + }, [point.pointUuid, getConnectedPoints]); return { snapPosition }; } diff --git a/app/src/modules/builder/point/helpers/usePointSnapping.tsx b/app/src/modules/builder/point/helpers/usePointSnapping.tsx index 4d61b8a..8d56085 100644 --- a/app/src/modules/builder/point/helpers/usePointSnapping.tsx +++ b/app/src/modules/builder/point/helpers/usePointSnapping.tsx @@ -13,7 +13,7 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string if (!currentPoint) return []; return aisles.flatMap(aisle => - aisle.points.filter(point => point.uuid !== currentPoint.uuid) + aisle.points.filter(point => point.pointUuid !== currentPoint.uuid) ); }, [aisles, currentPoint]); diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index a6c22c9..5c000f7 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -17,7 +17,7 @@ function Point({ point }: { readonly point: Point }) { const { toolMode } = useToolMode(); const { setPosition, removePoint } = useAisleStore(); const { snapPosition } = useAislePointSnapping(point); - const { checkSnapForAisle } = usePointSnapping({ uuid: point.uuid, pointType: point.pointType, position: point.position }); + const { checkSnapForAisle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); const { hoveredPoint, setHoveredPoint } = useBuilderStore(); const { deletePointOrLine } = useDeletePointOrLine(); @@ -63,7 +63,7 @@ function Point({ point }: { readonly point: Point }) { const aisleSnappedPosition = snapPosition(newPosition); const finalSnappedPosition = checkSnapForAisle(aisleSnappedPosition.position); - setPosition(point.uuid, finalSnappedPosition.position); + setPosition(point.pointUuid, finalSnappedPosition.position); } } } @@ -76,7 +76,7 @@ function Point({ point }: { readonly point: Point }) { const handlePointClick = (point: Point) => { if (deletePointOrLine) { - const removedAisles = removePoint(point.uuid); + const removedAisles = removePoint(point.pointUuid); if (removedAisles.length > 0) { setHoveredPoint(null); console.log(removedAisles); @@ -85,7 +85,7 @@ function Point({ point }: { readonly point: Point }) { } useEffect(() => { - if (hoveredPoint && hoveredPoint.uuid !== point.uuid) { + if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) { setIsHovered(false); } }, [hoveredPoint]) @@ -102,8 +102,8 @@ function Point({ point }: { readonly point: Point }) { onDragEnd={() => { handleDragEnd(point) }} > { @@ -116,7 +116,7 @@ function Point({ point }: { readonly point: Point }) { } }} onPointerOut={() => { - if (hoveredPoint && hoveredPoint.uuid === point.uuid) { + if (hoveredPoint && hoveredPoint.pointUuid === point.pointUuid) { setHoveredPoint(null); } setIsHovered(false) diff --git a/app/src/store/builder/useAisleStore.ts b/app/src/store/builder/useAisleStore.ts index 71197c2..f379e35 100644 --- a/app/src/store/builder/useAisleStore.ts +++ b/app/src/store/builder/useAisleStore.ts @@ -30,7 +30,7 @@ interface AisleStore { ) => void; setArcAisleWidth: (aisleUuid: string, width: number) => void; setCircleAisleWidth: (aisleUuid: string, width: number) => void; - setJunctionAisleWidth: (aisleUuid: string, width: number) => void; + setJunctionAisleProperties: (aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean; }) => void; getAisleById: (uuid: string) => Aisle | undefined; getAislePointById: (uuid: string) => Point | undefined; @@ -51,21 +51,21 @@ export const useAisleStore = create()( }), updateAisle: (uuid, updated) => set((state) => { - const aisle = state.aisles.find((a) => a.uuid === uuid); + const aisle = state.aisles.find((a) => a.aisleUuid === uuid); if (aisle) { Object.assign(aisle, updated); } }), removeAisle: (uuid) => set((state) => { - state.aisles = state.aisles.filter((a) => a.uuid !== uuid); + state.aisles = state.aisles.filter((a) => a.aisleUuid !== uuid); }), removePoint: (uuid) => { const removedAisles: Aisle[] = []; set((state) => { state.aisles = state.aisles.filter((aisle) => { - const hasPoint = aisle.points.some((point) => point.uuid === uuid); + const hasPoint = aisle.points.some((point) => point.pointUuid === uuid); if (hasPoint) { removedAisles.push(JSON.parse(JSON.stringify(aisle))); return false; @@ -79,7 +79,7 @@ export const useAisleStore = create()( setPosition: (pointUuid, position) => set((state) => { for (const aisle of state.aisles) { - const point = aisle.points.find(p => p.uuid === pointUuid); + const point = aisle.points.find(p => p.pointUuid === pointUuid); if (point) { point.position = position; } @@ -88,7 +88,7 @@ export const useAisleStore = create()( setLayer: (pointUuid, layer) => set((state) => { for (const aisle of state.aisles) { - const point = aisle.points.find(p => p.uuid === pointUuid); + const point = aisle.points.find(p => p.pointUuid === pointUuid); if (point) { point.layer = layer; } @@ -96,7 +96,7 @@ export const useAisleStore = create()( }), setColor: (aisleUuid, color) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle) { aisle.type.aisleColor = color; } @@ -104,14 +104,14 @@ export const useAisleStore = create()( // Type-specific property setters setSolidAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'solid-aisle') { aisle.type.aisleWidth = width; } }), setDashedAisleProperties: (aisleUuid, props) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'dashed-aisle') { if (props.aisleWidth !== undefined) aisle.type.aisleWidth = props.aisleWidth; if (props.dashLength !== undefined) aisle.type.dashLength = props.dashLength; @@ -120,14 +120,14 @@ export const useAisleStore = create()( }), setStrippedAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'stripped-aisle') { aisle.type.aisleWidth = width; } }), setDottedAisleProperties: (aisleUuid, props) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'dotted-aisle') { if (props.dotRadius !== undefined) aisle.type.dotRadius = props.dotRadius; if (props.gapLength !== undefined) aisle.type.gapLength = props.gapLength; @@ -135,14 +135,14 @@ export const useAisleStore = create()( }), setArrowAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'arrow-aisle') { aisle.type.aisleWidth = width; } }), setArrowsAisleProperties: (aisleUuid, props) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'arrows-aisle') { if (props.aisleWidth !== undefined) aisle.type.aisleWidth = props.aisleWidth; if (props.aisleLength !== undefined) aisle.type.aisleLength = props.aisleLength; @@ -151,33 +151,34 @@ export const useAisleStore = create()( }), setArcAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'arc-aisle') { aisle.type.aisleWidth = width; } }), setCircleAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'circle-aisle') { aisle.type.aisleWidth = width; } }), - setJunctionAisleWidth: (aisleUuid, width) => set((state) => { - const aisle = state.aisles.find(a => a.uuid === aisleUuid); + setJunctionAisleProperties: (aisleUuid, props) => set((state) => { + const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === 'junction-aisle') { - aisle.type.aisleWidth = width; + if (props.aisleWidth !== undefined) aisle.type.aisleWidth = props.aisleWidth; + if (props.isFlipped !== undefined) aisle.type.isFlipped = props.isFlipped; } }), getAisleById: (uuid) => { - return get().aisles.find((a) => a.uuid === uuid); + return get().aisles.find((a) => a.aisleUuid === uuid); }, getAislePointById: (uuid) => { for (const aisle of get().aisles) { - const point = aisle.points.find(p => p.uuid === uuid); + const point = aisle.points.find(p => p.pointUuid === uuid); if (point) { return point; } @@ -189,8 +190,8 @@ export const useAisleStore = create()( const connected: Point[] = []; for (const aisle of get().aisles) { for (const point of aisle.points) { - if (point.uuid === uuid) { - connected.push(...aisle.points.filter(p => p.uuid !== uuid)); + if (point.pointUuid === uuid) { + connected.push(...aisle.points.filter(p => p.pointUuid !== uuid)); } } } @@ -198,7 +199,7 @@ export const useAisleStore = create()( }, getAisleType: (uuid: string) => { - const aisle = get().aisles.find(a => a.uuid === uuid); + const aisle = get().aisles.find(a => a.aisleUuid === uuid); return aisle?.type as T | undefined; }, })) diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index dc3524e..8b209dc 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -25,6 +25,9 @@ interface BuilderState { // Arrows aisle properties aisleLength: number; + // Junction aisle properties + isFlipped: boolean; + // Setters for common properties setHoveredPoint: (point: Point | null) => void; @@ -47,6 +50,9 @@ interface BuilderState { // Setters for arrows aisle setAisleLength: (length: number) => void; + // Setters for junction aisle + setIsFlipped: (isFlipped: boolean) => void; + // Batch setters setDashedAisleProperties: (width: number, dashLength: number, gapLength: number) => void; setDottedAisleProperties: (width: number, dotRadius: number, gapLength: number) => void; @@ -71,6 +77,7 @@ export const useBuilderStore = create()( gapLength: 0.3, dotRadius: 0.1, aisleLength: 0.6, + isFlipped: false, // Individual setters @@ -133,6 +140,11 @@ export const useBuilderStore = create()( state.aisleLength = length; }); }, + setIsFlipped: (isFlipped) => { + set((state) => { + state.isFlipped = isFlipped; + }); + }, // Batch setters setDashedAisleProperties: (width, dashLength, gapLength) => { diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index b9f7616..c43805c 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -34,7 +34,7 @@ type Assets = Asset[]; type PointTypes = 'Aisle' | 'Wall' | 'Floor' | 'Zone'; interface Point { - uuid: string; + pointUuid: string; pointType: PointTypes; position: [number, number, number]; layer: number; @@ -102,12 +102,13 @@ interface JunctionAisle { aisleType: 'junction-aisle'; aisleColor: AisleColors; aisleWidth: number; + isFlipped: boolean; } type AisleType = SolidAisle | DashedAisle | StrippedAisle | DottedAisle | ArrowAisle | ArrowsAisle | ArcAisle | CircleAisle | JunctionAisle; interface Aisle { - uuid: string; + aisleUuid: string; points: [Point, Point]; type: AisleType; }