import React, { useRef, useState } from "react"; import { Vector3, Raycaster, BufferGeometry, LineBasicMaterial, Line, Mesh, Group, } from "three"; import { useThree, useFrame } from "@react-three/fiber"; import { Html } from "@react-three/drei"; interface DistanceFindingControlsProps { boundingBoxRef: React.RefObject; object: number; } const DistanceFindingControls = ({ boundingBoxRef, object, }: DistanceFindingControlsProps) => { const { camera, scene } = useThree(); const [labelValues, setLabelValues] = useState<{ textPosX: any; textNegX: any; textPosZ: any; textNegZ: any; }>({ textPosX: "", textNegX: "", textPosZ: "", textNegZ: "", }); // Refs for measurement lines const line1 = useRef(null); const line2 = useRef(null); const line3 = useRef(null); const line4 = useRef(null); const line5 = useRef(null); // Refs for measurement text labels const textPosX = useRef(null); const textNegX = useRef(null); const textPosZ = useRef(null); const textNegZ = useRef(null); const textPosY = useRef(null); // Store line geometries to avoid recreation const lineGeometries = useRef({ posX: new BufferGeometry(), negX: new BufferGeometry(), posZ: new BufferGeometry(), negZ: new BufferGeometry(), posY: new BufferGeometry(), }); useFrame(() => { if (!boundingBoxRef?.current) return; boundingBoxRef.current.geometry.computeBoundingBox(); const bbox = boundingBoxRef.current.geometry.boundingBox; if (!bbox) return; const size = { x: bbox.max.x - bbox.min.x, y: bbox.max.y - bbox.min.y, z: bbox.max.z - bbox.min.z, }; const vec = boundingBoxRef.current?.getWorldPosition(new Vector3()).clone(); if (!vec) return; updateLine({ line: line1.current, geometry: lineGeometries.current.posX, direction: new Vector3(1, 0, 0), // Positive X angle: "pos", mesh: textPosX, vec, size, }); updateLine({ line: line2.current, geometry: lineGeometries.current.negX, direction: new Vector3(-1, 0, 0), // Negative X angle: "neg", mesh: textNegX, vec, size, }); updateLine({ line: line3.current, geometry: lineGeometries.current.posZ, direction: new Vector3(0, 0, 1), // Positive Z angle: "pos", mesh: textPosZ, vec, size, }); updateLine({ line: line4.current, geometry: lineGeometries.current.negZ, direction: new Vector3(0, 0, -1), // Negative Z angle: "neg", mesh: textNegZ, vec, size, }); updateLine({ line: line5.current, geometry: lineGeometries.current.posY, direction: new Vector3(0, -1, 0), // Down (Y) angle: "posY", mesh: textPosY, vec, size, }); }); const updateLine = ({ line, geometry, direction, angle, mesh, vec, size, }: { line: Line | null; geometry: BufferGeometry; direction: Vector3; angle: string; mesh: React.RefObject; vec: Vector3; size: { x: number; y: number; z: number }; }) => { if (!line) return; const points = []; if (angle === "pos") { points[0] = new Vector3(vec.x, vec.y, vec.z).add( new Vector3((direction.x * size.x) / 2, 0, (direction.z * size.z) / 2) ); } else if (angle === "neg") { points[0] = new Vector3(vec.x, vec.y, vec.z).sub( new Vector3((-direction.x * size.x) / 2, 0, (-direction.z * size.z) / 2) ); } else if (angle === "posY") { points[0] = new Vector3(vec.x, vec.y, vec.z).sub( new Vector3(0, size.y / 2, 0) ); } const ray = new Raycaster(); if (camera) ray.camera = camera; ray.set(new Vector3(vec.x, vec.y, vec.z), direction); ray.params.Line.threshold = 0.1; // Find intersection points const wallsGroup = scene.children.find((val) => val?.name.includes("Walls") ); const intersects = wallsGroup ? ray.intersectObjects([wallsGroup], true) : []; // Find intersection point if (intersects[0]) { for (const intersect of intersects) { if (intersect.object.name.includes("Wall")) { points[1] = angle !== "posY" ? intersect.point : new Vector3(vec.x, 0, vec.z); // Floor break; } } } if (points[1]) { geometry.dispose(); geometry.setFromPoints([points[0], points[1]]); line.geometry = geometry; // Calculate the distance only once const distance = points[0].distanceTo(points[1]).toFixed(2); // Update measurement text if (mesh?.current) { geometry.computeBoundingSphere(); const center = geometry.boundingSphere?.center; if (center) { mesh.current.position.copy(center); } const label = document.getElementById(mesh.current.name); if (label) { label.innerText = `${distance}m`; // Update specific label state based on the label ID switch (label.id) { case "textPosX": setLabelValues((prevState) => ({ ...prevState, textPosX: distance })); break; case "textNegX": setLabelValues((prevState) => ({ ...prevState, textNegX: distance })); break; case "textPosZ": setLabelValues((prevState) => ({ ...prevState, textPosZ: distance })); break; case "textNegZ": setLabelValues((prevState) => ({ ...prevState, textNegZ: distance })); break; default: break; } } } } else { // No intersection found - clear the line geometry.dispose(); geometry.setFromPoints([new Vector3(), new Vector3()]); line.geometry = geometry; const label = document.getElementById(mesh?.current?.name ?? ""); if (label) { label.innerText = ""; // Clear the corresponding label value in the state switch (label.id) { case "textPosX": setLabelValues((prevState) => ({ ...prevState, textPosX: "" })); break; case "textNegX": setLabelValues((prevState) => ({ ...prevState, textNegX: "" })); break; case "textPosZ": setLabelValues((prevState) => ({ ...prevState, textPosZ: "" })); break; case "textNegZ": setLabelValues((prevState) => ({ ...prevState, textNegZ: "" })); break; default: break; } } } }; const Material = new LineBasicMaterial({ color: "#d2baff" }); return ( <> {/* Measurement text labels */} {boundingBoxRef.current && object > 0 && ( <>
{labelValues.textPosX}
{labelValues.textNegX}
{labelValues.textPosZ}
{labelValues.textNegZ}
{/* Measurement lines */} ) } ); }; export default DistanceFindingControls;