340 lines
9.3 KiB
TypeScript
340 lines
9.3 KiB
TypeScript
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<Mesh>;
|
|
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<Line>(null);
|
|
const line2 = useRef<Line>(null);
|
|
const line3 = useRef<Line>(null);
|
|
const line4 = useRef<Line>(null);
|
|
const line5 = useRef<Line>(null);
|
|
|
|
// Refs for measurement text labels
|
|
const textPosX = useRef<Group>(null);
|
|
const textNegX = useRef<Group>(null);
|
|
const textPosZ = useRef<Group>(null);
|
|
const textNegZ = useRef<Group>(null);
|
|
const textPosY = useRef<Group>(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<Group>;
|
|
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 && (
|
|
<>
|
|
<group name="textPosX" ref={textPosX}>
|
|
<Html
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
zIndexRange={[1, 0]}
|
|
style={{
|
|
pointerEvents: "none",
|
|
visibility: labelValues.textPosX === "" ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<div className="distance-label" id="textPosX">{labelValues.textPosX}</div>
|
|
</Html>
|
|
</group>
|
|
|
|
<group name="textNegX" ref={textNegX}>
|
|
<Html
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
zIndexRange={[1, 0]}
|
|
style={{
|
|
pointerEvents: "none",
|
|
visibility: labelValues.textNegX === "" ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<div className="distance-label" id="textNegX">{labelValues.textNegX}</div>
|
|
</Html>
|
|
</group>
|
|
|
|
<group name="textPosZ" ref={textPosZ}>
|
|
<Html
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
zIndexRange={[2, 0]}
|
|
style={{
|
|
pointerEvents: "none",
|
|
visibility: labelValues.textPosZ === "" ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<div className="distance-label" id="textPosZ">{labelValues.textPosZ}</div>
|
|
</Html>
|
|
</group>
|
|
|
|
<group name="textNegZ" ref={textNegZ}>
|
|
<Html
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
zIndexRange={[1, 0]}
|
|
style={{
|
|
pointerEvents: "none",
|
|
visibility: labelValues.textNegZ === "" ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<div className="distance-label" id="textNegZ">{labelValues.textNegZ}</div>
|
|
</Html>
|
|
</group>
|
|
|
|
{/* Measurement lines */}
|
|
<primitive
|
|
object={new Line(new BufferGeometry(), Material)}
|
|
ref={line1}
|
|
/>
|
|
<primitive
|
|
object={new Line(new BufferGeometry(), Material)}
|
|
ref={line2}
|
|
/>
|
|
<primitive
|
|
object={new Line(new BufferGeometry(), Material)}
|
|
ref={line3}
|
|
/>
|
|
<primitive
|
|
object={new Line(new BufferGeometry(), Material)}
|
|
ref={line4}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default DistanceFindingControls;
|