Dwinzo_dev/app/src/modules/scene/controls/selectionControls/distanceFindingControls.tsx

253 lines
9.0 KiB
TypeScript

import React, { useEffect, useRef } from "react";
import { Vector3, Raycaster, BufferGeometry, LineBasicMaterial, Line, Object3D, Mesh } from "three";
import { useThree, useFrame } from "@react-three/fiber";
import { Group } from "three";
import { Html } from "@react-three/drei";
import * as THREE from "three";
interface DistanceFindingControlsProps {
boundingBoxRef: React.RefObject<Mesh>;
}
const DistanceFindingControls = ({ boundingBoxRef }: DistanceFindingControlsProps) => {
const { camera, scene } = useThree();
// 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;
}
}
}
// Update line geometry
if (points[1]) {
geometry.dispose();
geometry.setFromPoints([points[0], points[1]]);
line.geometry = geometry;
// 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 = `${points[0].distanceTo(points[1]).toFixed(2)}m`;
}
}
} 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 = "";
}
};
const Material = new LineBasicMaterial({ color: "red" });
return (
<>
{/* Measurement text labels */}
{boundingBoxRef && (
<>
<group name="textPosX" ref={textPosX}>
<Html zIndexRange={[1, 0]} style={{ pointerEvents: 'none' }}>
<div className="distance-label" id="textPosX" style={{
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '2px 5px',
borderRadius: '3px',
fontSize: '12px',
whiteSpace: 'nowrap'
}}></div>
</Html>
</group>
<group name="textNegX" ref={textNegX}>
<Html zIndexRange={[1, 0]} style={{ pointerEvents: 'none' }}>
<div className="distance-label" id="textNegX" style={{
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '2px 5px',
borderRadius: '3px',
fontSize: '12px',
whiteSpace: 'nowrap'
}}></div>
</Html>
</group>
<group name="textPosZ" ref={textPosZ}>
<Html zIndexRange={[2, 0]} style={{ pointerEvents: 'none' }}>
<div className="distance-label" id="textPosZ" style={{
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '2px 5px',
borderRadius: '3px',
fontSize: '12px',
whiteSpace: 'nowrap'
}}></div>
</Html>
</group>
<group name="textNegZ" ref={textNegZ}>
<Html zIndexRange={[1, 0]} style={{ pointerEvents: 'none' }}>
<div className="distance-label" id="textNegZ" style={{
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '2px 5px',
borderRadius: '3px',
fontSize: '12px',
whiteSpace: 'nowrap'
}}></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;