import { useEffect, useMemo, useRef, useState } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; import * as Constants from '../../../../types/world/worldConstants'; import { Extrude, Html } from '@react-three/drei'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; import { usePointSnapping } from '../../point/helpers/usePointSnapping'; interface ReferenceAisleProps { tempPoints: Point[]; } function ReferenceAisle({ tempPoints }: Readonly) { const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, 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 [tempAisle, setTempAisle] = useState(null); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null); const { snapAislePoint } = usePointSnapping({ uuid: 'temp-aisle', pointType: 'Aisle', position: directionalSnap.position || [0, 0, 0] }); useFrame(() => { if (toolMode === "Aisle" && toggleView && tempPoints.length === 1) { raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); raycaster.ray.intersectPlane(plane, intersectionPoint); setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); if (intersectionPoint) { const snapped = snapAislePoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); 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; if (aisleType === 'solid-aisle') { setTempAisle({ aisleUuid: 'temp-aisle', points: [ tempPoints[0], { pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer } ], type: { aisleType: aisleType, aisleColor: aisleColor, aisleWidth: aisleWidth } }); } else if (aisleType === 'dashed-aisle') { setTempAisle({ aisleUuid: 'temp-aisle', points: [ tempPoints[0], { pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer } ], type: { aisleType: aisleType, aisleColor: aisleColor, aisleWidth: aisleWidth, dashLength: dashLength, gapLength: gapLength } }); } else if (aisleType === 'dotted-aisle') { setTempAisle({ aisleUuid: 'temp-aisle', points: [ tempPoints[0], { pointUuid: 'temp-point', pointType: 'Aisle', position: finalPosition.current, layer: activeLayer } ], type: { aisleType: aisleType, aisleColor: aisleColor, dotRadius: dotRadius, gapLength: gapLength } }); } else if (aisleType === 'arrow-aisle' || aisleType === 'circle-aisle') { setTempAisle({ aisleUuid: 'temp-aisle', points: [ tempPoints[0], { 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 } ], type: { aisleType: aisleType, aisleColor: aisleColor, aisleWidth: aisleWidth, aisleLength: aisleLength, gapLength: gapLength } }); } else if (aisleType === 'junction-aisle' || aisleType === 'arc-aisle') { 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) { setTempAisle(null); } }); useEffect(() => { setTempAisle(null); }, [toolMode, toggleView, tempPoints.length, aisleType, aisleWidth, aisleColor, activeLayer]); if (!tempAisle) return null; const renderAisle = () => { switch (aisleType) { case 'solid-aisle': return ; case 'dashed-aisle': return ; case 'dotted-aisle': return ; case 'arrow-aisle': return ; case 'arrows-aisle': return ; case 'circle-aisle': return ; case 'junction-aisle': return ; case 'arc-aisle': return default: return null; } }; const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempAisle.points[0].position), new THREE.Vector3(...tempAisle.points[1].position)).divideScalar(2); const distance = new THREE.Vector3(...tempAisle.points[0].position).distanceTo(new THREE.Vector3(...tempAisle.points[1].position)); const rendertext = () => { return ( <> {toggleView &&
{distance.toFixed(2)} m
} ) } return ( {renderAisle()} {rendertext()} ); } export default ReferenceAisle; function SolidAisle({ aisle }: { readonly aisle: Aisle }) { const shape = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-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).normalize(); const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, width / 2); const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -width / 2); const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, width / 2); const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -width / 2); const shape = new THREE.Shape(); shape.moveTo(leftStart.x, leftStart.z); shape.lineTo(leftEnd.x, leftEnd.z); shape.lineTo(rightEnd.x, rightEnd.z); shape.lineTo(rightStart.x, rightStart.z); shape.closePath(); return shape; }, [aisle]); if (!shape) return null; return ( ); } function DashedAisle({ aisle }: { readonly aisle: Aisle }) { const shapes = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return []; 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 dashLength = aisle.type.dashLength || 0.5; const gapLength = aisle.type.gapLength || 0.3; const direction = new THREE.Vector3().subVectors(end, start).normalize(); const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); const totalLength = new THREE.Vector3().subVectors(end, start).length(); const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength)); const shapes = []; const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); for (let i = 0; i < segmentCount; i++) { const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength)); const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength); const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2); const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2); const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2); const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2); const shape = new THREE.Shape(); shape.moveTo(leftStart.x, leftStart.z); shape.lineTo(leftEnd.x, leftEnd.z); shape.lineTo(rightEnd.x, rightEnd.z); shape.lineTo(rightStart.x, rightStart.z); shape.closePath(); shapes.push(shape); } return shapes; }, [aisle]); if (shapes.length === 0) return null; return ( {shapes.map((shape, index) => ( ))} ); } function DottedAisle({ aisle }: { readonly aisle: Aisle }) { const shapes = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return []; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); const width = aisle.type.dotRadius || 0.1; const dotSpacing = aisle.type.gapLength || 0.5; const dotRadius = width * 0.6; const totalLength = new THREE.Vector3().subVectors(end, start).length(); const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing); const shapes = []; const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); for (let i = 0; i < dotCount; i++) { const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2); const shape = new THREE.Shape(); shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false); shapes.push(shape); } return shapes; }, [aisle]); if (shapes.length === 0) return null; return ( {shapes.map((shape, index) => ( ))} ); } function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { const arrows = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return []; 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 arrowLength = aisle.type.aisleLength || 0.6; const spacing = aisle.type.gapLength || 0.6; const direction = new THREE.Vector3().subVectors(end, start); const length = direction.length(); direction.normalize(); const count = Math.floor((length + spacing) / (arrowLength + spacing)); const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = []; for (let i = 0; i < count; i++) { const initialOffset = arrowLength; const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing)); const shape = new THREE.Shape(); const w = width * 0.8; const h = arrowLength; shape.moveTo(0, 0); shape.lineTo(w, h * 0.6); shape.lineTo(w * 0.4, h * 0.6); shape.lineTo(w * 0.4, h); shape.lineTo(-w * 0.4, h); shape.lineTo(-w * 0.4, h * 0.6); shape.lineTo(-w, h * 0.6); shape.lineTo(0, 0); const angle = Math.atan2(direction.x, direction.z) + Math.PI; arrowShapes.push({ shape, position: center, rotationY: angle }); } return arrowShapes; }, [aisle]); if (arrows.length === 0) return null; return ( {arrows.map(({ shape, position, rotationY }, index) => ( ))} ); } 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(center.x, center.z, outerRadius, 0, Math.PI * 2, false); if (innerRadius > 0) { const hole = new THREE.Path(); hole.absarc(center.x, center.z, innerRadius, 0, Math.PI * 2, true); shape.holes.push(hole); } return { shape, 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) => ( ))} ); } function ArcAisle({ aisle }: { readonly aisle: Aisle }) { const arc = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-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.5; const isFlipped = aisle.type.isFlipped || false; const direction = new THREE.Vector3().subVectors(end, start); const length = direction.length(); direction.normalize(); const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); if (!isFlipped) perpendicular.negate(); const arcHeight = length * 0.25; const midPoint = new THREE.Vector3().lerpVectors(start, end, 0.5); const controlPoint = new THREE.Vector3().copy(midPoint).addScaledVector(perpendicular, arcHeight); const widthOffset = perpendicular.clone().multiplyScalar(width / 2); const p1 = new THREE.Vector3().copy(start).add(widthOffset); const p2 = new THREE.Vector3().copy(end).add(widthOffset); const p3 = new THREE.Vector3().copy(end).sub(widthOffset); const p4 = new THREE.Vector3().copy(start).sub(widthOffset); const shape = new THREE.Shape(); shape.moveTo(p1.x, p1.z); shape.quadraticCurveTo( controlPoint.x + widthOffset.x, controlPoint.z + widthOffset.z, p2.x, p2.z ); shape.lineTo(p3.x, p3.z); shape.quadraticCurveTo( controlPoint.x - widthOffset.x, controlPoint.z - widthOffset.z, p4.x, p4.z ); shape.lineTo(p1.x, p1.z); return { shape, position: new THREE.Vector3(0, 0, 0), rotationY: 0 }; }, [aisle]); if (!arc) return null; return ( ); }