209 lines
6.6 KiB
TypeScript
209 lines
6.6 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 material = new LineBasicMaterial({ color: "#d2baff" });
|
|
|
|
const DIRECTION_LABEL_MAP = {
|
|
textPosX: "textPosX",
|
|
textNegX: "textNegX",
|
|
textPosZ: "textPosZ",
|
|
textNegZ: "textNegZ",
|
|
} as const;
|
|
|
|
const DistanceFindingControls = ({
|
|
boundingBoxRef,
|
|
object,
|
|
}: DistanceFindingControlsProps) => {
|
|
const { camera, scene } = useThree();
|
|
const [labelValues, setLabelValues] = useState<Record<string, string>>({
|
|
textPosX: "",
|
|
textNegX: "",
|
|
textPosZ: "",
|
|
textNegZ: "",
|
|
});
|
|
|
|
const lineRefs = {
|
|
posX: useRef<Line>(null),
|
|
negX: useRef<Line>(null),
|
|
posZ: useRef<Line>(null),
|
|
negZ: useRef<Line>(null),
|
|
posY: useRef<Line>(null),
|
|
};
|
|
|
|
const textRefs = {
|
|
textPosX: useRef<Group>(null),
|
|
textNegX: useRef<Group>(null),
|
|
textPosZ: useRef<Group>(null),
|
|
textNegZ: useRef<Group>(null),
|
|
textPosY: useRef<Group>(null),
|
|
};
|
|
|
|
const lineGeometries = useRef({
|
|
posX: new BufferGeometry(),
|
|
negX: new BufferGeometry(),
|
|
posZ: new BufferGeometry(),
|
|
negZ: new BufferGeometry(),
|
|
posY: new BufferGeometry(),
|
|
});
|
|
|
|
useFrame(() => {
|
|
const bboxMesh = boundingBoxRef?.current;
|
|
if (!bboxMesh) return;
|
|
|
|
bboxMesh.geometry.computeBoundingBox();
|
|
const bbox = bboxMesh.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 center = bboxMesh.getWorldPosition(new Vector3()).clone();
|
|
if (!center) return;
|
|
|
|
updateLine("posX", new Vector3(1, 0, 0), "pos", size, center);
|
|
updateLine("negX", new Vector3(-1, 0, 0), "neg", size, center);
|
|
updateLine("posZ", new Vector3(0, 0, 1), "pos", size, center);
|
|
updateLine("negZ", new Vector3(0, 0, -1), "neg", size, center);
|
|
updateLine("posY", new Vector3(0, -1, 0), "posY", size, center);
|
|
});
|
|
|
|
const updateLine = (
|
|
key: keyof typeof lineRefs,
|
|
direction: Vector3,
|
|
angle: string,
|
|
size: { x: number; y: number; z: number },
|
|
origin: Vector3
|
|
) => {
|
|
const line = lineRefs[key].current;
|
|
const geometry = lineGeometries.current[key];
|
|
const mesh = textRefs[`text${key[0].toUpperCase() + key.slice(1)}` as keyof typeof textRefs];
|
|
|
|
if (!line) return;
|
|
|
|
const points: Vector3[] = [];
|
|
const halfSize = new Vector3(size.x / 2, size.y / 2, size.z / 2);
|
|
|
|
if (angle === "pos") {
|
|
points[0] = origin.clone().add(direction.clone().multiply(halfSize));
|
|
} else if (angle === "neg") {
|
|
points[0] = origin.clone().sub(direction.clone().multiply(halfSize));
|
|
} else if (angle === "posY") {
|
|
points[0] = origin.clone().sub(new Vector3(0, size.y / 2, 0));
|
|
}
|
|
|
|
const ray = new Raycaster();
|
|
ray.camera = camera;
|
|
ray.set(origin, direction);
|
|
ray.params.Line.threshold = 0.1;
|
|
|
|
const wallsGroup = scene.children.find((val) =>
|
|
val?.name.includes("Walls")
|
|
);
|
|
const intersects = wallsGroup
|
|
? ray.intersectObjects([wallsGroup], true)
|
|
: [];
|
|
|
|
const intersect = intersects.find((i) =>
|
|
i.object.name.includes("Wall")
|
|
);
|
|
|
|
if (intersect) {
|
|
points[1] = angle !== "posY" ? intersect.point : new Vector3(origin.x, 0, origin.z);
|
|
}
|
|
|
|
if (points[1]) {
|
|
geometry.dispose();
|
|
geometry.setFromPoints(points);
|
|
line.geometry = geometry;
|
|
|
|
const distance = points[0].distanceTo(points[1]).toFixed(2);
|
|
|
|
if (mesh?.current) {
|
|
geometry.computeBoundingSphere();
|
|
mesh.current.position.copy(geometry.boundingSphere!.center);
|
|
|
|
const labelEl = document.getElementById(mesh.current.name);
|
|
if (labelEl) {
|
|
labelEl.innerText = `${distance}m`;
|
|
|
|
if (DIRECTION_LABEL_MAP[labelEl.id as keyof typeof DIRECTION_LABEL_MAP]) {
|
|
setLabelValues((prev) => ({
|
|
...prev,
|
|
[labelEl.id]: distance,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
geometry.dispose();
|
|
geometry.setFromPoints([new Vector3(), new Vector3()]);
|
|
line.geometry = geometry;
|
|
|
|
const labelEl = document.getElementById(mesh?.current?.name ?? "");
|
|
if (labelEl && DIRECTION_LABEL_MAP[labelEl.id as keyof typeof DIRECTION_LABEL_MAP]) {
|
|
labelEl.innerText = "";
|
|
setLabelValues((prev) => ({
|
|
...prev,
|
|
[labelEl.id]: "",
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
|
|
const renderLabel = (id: keyof typeof textRefs) => (
|
|
<group name={id} ref={textRefs[id]}>
|
|
<Html
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
zIndexRange={[1, 0]}
|
|
style={{
|
|
pointerEvents: "none",
|
|
visibility: labelValues[id] === "" ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<div className="distance-label" id={id}>{labelValues[id]}</div>
|
|
</Html>
|
|
</group>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{boundingBoxRef.current && object > 0 && (
|
|
<group
|
|
name="DistanceFindingControls"
|
|
>
|
|
{renderLabel("textPosX")}
|
|
{renderLabel("textNegX")}
|
|
{renderLabel("textPosZ")}
|
|
{renderLabel("textNegZ")}
|
|
|
|
<primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.posX} />
|
|
<primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.negX} />
|
|
<primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.posZ} />
|
|
<primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.negZ} />
|
|
</group>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default DistanceFindingControls;
|