import * as THREE from "three"; import { useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { useToolMode } from "../../../store/builder/store"; import { Html, Line } from "@react-three/drei"; const MeasurementTool = () => { const { gl, raycaster, pointer, camera, scene } = useThree(); const { toolMode } = useToolMode(); const [points, setPoints] = useState([]); const [linePoints, setLinePoints] = useState(null); const groupRef = useRef(null); 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.name.includes("zonePlane") && !intersect.object.name.includes("SelectionGroup") && !intersect.object.name.includes("selectionAssetGroup") && !intersect.object.name.includes( "SelectionGroupBoundingBoxLine" ) && !intersect.object.name.includes("SelectionGroupBoundingBox") && !intersect.object.name.includes("SelectionGroupBoundingLine") && 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) { setPoints([]); setLinePoints(null); } }; if (toolMode === "MeasurementScale") { canvasElement.addEventListener("pointerdown", onMouseDown); canvasElement.addEventListener("pointermove", onMouseMove); canvasElement.addEventListener("pointerup", onMouseUp); canvasElement.addEventListener("contextmenu", onContextMenu); } else { setPoints([]); setLinePoints(null); } return () => { canvasElement.removeEventListener("pointerdown", onMouseDown); canvasElement.removeEventListener("pointermove", onMouseMove); canvasElement.removeEventListener("pointerup", onMouseUp); canvasElement.removeEventListener("contextmenu", onContextMenu); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [toolMode, camera, raycaster, pointer, scene, points]); useFrame(() => { if (points.length === 1) { // live preview for second point 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.name.includes("zonePlane") && !intersect.object.name.includes("SelectionGroup") && !intersect.object.name.includes("selectionAssetGroup") && !intersect.object.name.includes("SelectionGroupBoundingBoxLine") && !intersect.object.name.includes("SelectionGroupBoundingBox") && !intersect.object.name.includes("SelectionGroupBoundingLine") && intersect.object.type !== "GridHelper" ); if (intersects.length > 0) { const tempEnd = intersects[0].point.clone(); updateMeasurement(points[0], tempEnd); } } else if (points.length === 2) { // second point already fixed updateMeasurement(points[0], points[1]); } else { setLinePoints(null); } }); const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { setLinePoints([start.clone(), end.clone()]); }; return ( {linePoints && ( <> {/* Outline line */} {/* Main line */} )} {points.map((point, index) => (
))} {linePoints && linePoints.length === 2 && (
{linePoints[0].distanceTo(linePoints[1]).toFixed(2)} m
)}
); }; export default MeasurementTool;