230 lines
8.6 KiB
TypeScript
230 lines
8.6 KiB
TypeScript
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";
|
|
|
|
const MeasurementTool = () => {
|
|
const { gl, raycaster, pointer, camera, scene } = useThree();
|
|
const { toolMode } = useToolMode();
|
|
|
|
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
|
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
|
null
|
|
);
|
|
const groupRef = useRef<THREE.Group>(null);
|
|
const [startConePosition, setStartConePosition] =
|
|
useState<THREE.Vector3 | null>(null);
|
|
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
|
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 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;
|
|
|
|
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.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);
|
|
};
|
|
}, [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.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 resetMeasurement = () => {
|
|
setTubeGeometry(null);
|
|
setStartConePosition(null);
|
|
setEndConePosition(null);
|
|
};
|
|
|
|
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]));
|
|
}
|
|
}, [points]);
|
|
|
|
return (
|
|
<group ref={groupRef} name="MeasurementGroup">
|
|
{startConePosition && (
|
|
<mesh
|
|
name="MeasurementReference"
|
|
position={startConePosition}
|
|
quaternion={startConeQuaternion}
|
|
>
|
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
|
<meshBasicMaterial color="yellow" />
|
|
</mesh>
|
|
)}
|
|
{endConePosition && (
|
|
<mesh
|
|
name="MeasurementReference"
|
|
position={endConePosition}
|
|
quaternion={endConeQuaternion}
|
|
>
|
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
|
<meshBasicMaterial color="yellow" />
|
|
</mesh>
|
|
)}
|
|
{tubeGeometry && (
|
|
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
|
<meshBasicMaterial color="yellow" />
|
|
</mesh>
|
|
)}
|
|
|
|
{startConePosition && endConePosition && (
|
|
<Html
|
|
scale={THREE.MathUtils.clamp(
|
|
startConePosition.distanceTo(endConePosition) * 0.25,
|
|
0,
|
|
10
|
|
)}
|
|
position={[
|
|
(startConePosition.x + endConePosition.x) / 2,
|
|
(startConePosition.y + endConePosition.y) / 2,
|
|
(startConePosition.z + endConePosition.z) / 2,
|
|
]}
|
|
// class
|
|
wrapperClass="distance-text-wrapper"
|
|
className="distance-text"
|
|
// other
|
|
zIndexRange={[1, 0]}
|
|
prepend
|
|
sprite
|
|
>
|
|
<div>
|
|
{(startConePosition.distanceTo(endConePosition) + (coneSize.height)).toFixed(2)} m
|
|
</div>
|
|
</Html>
|
|
)}
|
|
</group>
|
|
);
|
|
};
|
|
|
|
export default MeasurementTool;
|