diff --git a/app/src/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index 393f533..4ca56fd 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -2,241 +2,188 @@ 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 } from "@react-three/drei"; +import { Html, Line } from "@react-three/drei"; const MeasurementTool = () => { - const { gl, raycaster, pointer, camera, scene } = useThree(); - const { toolMode } = useToolMode(); + 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 [points, setPoints] = useState([]); + const [linePoints, setLinePoints] = useState(null); + const groupRef = useRef(null); - 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; - 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) { - 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); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [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.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) { - 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 onMouseDown = () => { + isLeftMouseDown = true; + drag = false; }; - const resetMeasurement = () => { - setTubeGeometry(null); - setStartConePosition(null); - setEndConePosition(null); - }; + 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" + ); - 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])); + if (intersects.length > 0) { + const intersectionPoint = intersects[0].point.clone(); + if (points.length < 2) { + setPoints([...points, intersectionPoint]); + } else { + setPoints([intersectionPoint]); + } } - }, [points]); + } + }; - return ( - - {startConePosition && ( - - - - - )} - {endConePosition && ( - - - - - )} - {tubeGeometry && ( - - - - )} + const onMouseMove = () => { + if (isLeftMouseDown) drag = true; + }; - {startConePosition && endConePosition && ( - -
- {(startConePosition.distanceTo(endConePosition) + (coneSize.height)).toFixed(2)} m -
- - )} -
- ); + 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; diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 6358c81..dee2536 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -130,15 +130,25 @@ svg { display: none; } - .c-jiwtRJ{ + .c-jiwtRJ { align-items: center; } } -.stats{ +.stats { top: auto !important; bottom: 36px !important; left: 12px !important; border-radius: 6px; overflow: hidden; } + +.measurement-point { + height: 12px; + width: 12px; + border-radius: 50%; + background: #b18ef1; + outline: 2px solid black; + outline-offset: -1px; + transform: translate(-50%, -50%); +}