import * as THREE from "three"; import { useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { useToolMode } from "../../../store/store"; import { Html } from "@react-three/drei"; const MeasurementTool = () => { const { gl, raycaster, pointer, camera, scene } = useThree(); const { toolMode } = useToolMode(); const [points, setPoints] = useState([]); const [tubeGeometry, setTubeGeometry] = useState( null ); const groupRef = useRef(null); const [startConePosition, setStartConePosition] = useState(null); const [endConePosition, setEndConePosition] = useState( null ); const [startConeQuaternion, setStartConeQuaternion] = useState( new THREE.Quaternion() ); const [endConeQuaternion, setEndConeQuaternion] = useState( new THREE.Quaternion() ); const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 }); const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1; const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4; const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0; useEffect(() => { const canvasElement = gl.domElement; let drag = false; let isLeftMouseDown = false; const onMouseDown = () => { isLeftMouseDown = true; drag = false; }; const onMouseUp = (evt: any) => { isLeftMouseDown = false; if (evt.button === 0 && !drag) { raycaster.setFromCamera(pointer, camera); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("agv-collider") && !(intersect.object.type === "GridHelper") ); if (intersects.length > 0) { const intersectionPoint = intersects[0].point.clone(); if (points.length < 2) { setPoints([...points, intersectionPoint]); } else { setPoints([intersectionPoint]); } } } }; const onMouseMove = () => { if (isLeftMouseDown) drag = true; }; const onContextMenu = (evt: any) => { evt.preventDefault(); if (!drag) { evt.preventDefault(); setPoints([]); setTubeGeometry(null); } }; if (toolMode === "MeasurementScale") { canvasElement.addEventListener("pointerdown", onMouseDown); canvasElement.addEventListener("pointermove", onMouseMove); canvasElement.addEventListener("pointerup", onMouseUp); canvasElement.addEventListener("contextmenu", onContextMenu); } else { resetMeasurement(); setPoints([]); } return () => { canvasElement.removeEventListener("pointerdown", onMouseDown); canvasElement.removeEventListener("pointermove", onMouseMove); canvasElement.removeEventListener("pointerup", onMouseUp); canvasElement.removeEventListener("contextmenu", onContextMenu); }; }, [toolMode, camera, raycaster, pointer, scene, points]); useFrame(() => { if (points.length === 1) { raycaster.setFromCamera(pointer, camera); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("agv-collider") && !(intersect.object.type === "GridHelper") ); if (intersects.length > 0) { updateMeasurement(points[0], intersects[0].point); } } else if (points.length === 2) { updateMeasurement(points[0], points[1]); } else { resetMeasurement(); } }); const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { const distance = start.distanceTo(end); const radius = THREE.MathUtils.clamp( distance * 0.02, MIN_RADIUS, MAX_RADIUS ); const coneRadius = THREE.MathUtils.clamp( distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS ); const coneHeight = THREE.MathUtils.clamp( distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT ); setConeSize({ radius: coneRadius, height: coneHeight }); const direction = new THREE.Vector3().subVectors(end, start).normalize(); const offset = direction.clone().multiplyScalar(coneHeight * 0.5); let tubeStart = start.clone().add(offset); let tubeEnd = end.clone().sub(offset); tubeStart.y = Math.max(tubeStart.y, 0); tubeEnd.y = Math.max(tubeEnd.y, 0); const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]); setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false)); setStartConePosition(tubeStart); setEndConePosition(tubeEnd); setStartConeQuaternion(getArrowOrientation(start, end)); setEndConeQuaternion(getArrowOrientation(end, start)); }; const resetMeasurement = () => { setTubeGeometry(null); setStartConePosition(null); setEndConePosition(null); }; const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => { const direction = new THREE.Vector3() .subVectors(end, start) .normalize() .negate(); const quaternion = new THREE.Quaternion(); quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); return quaternion; }; useEffect(() => { if (points.length === 2) { console.log(points[0].distanceTo(points[1])); } }, [points]); return ( {startConePosition && ( )} {endConePosition && ( )} {tubeGeometry && ( )} {startConePosition && endConePosition && (
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
)}
); }; export default MeasurementTool;