v2-ui #84
|
@ -0,0 +1,253 @@
|
||||||
|
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;
|
|
@ -11,8 +11,10 @@ import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||||
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore";
|
||||||
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
|
import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
import { snapControls } from "../../../../utils/handleSnap";
|
import { snapControls } from "../../../../utils/handleSnap";
|
||||||
|
import DistanceFindingControls from "./distanceFindingControls";
|
||||||
|
|
||||||
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
|
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
|
||||||
|
|
||||||
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
|
|
||||||
|
@ -23,7 +25,6 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const itemsData = useRef<Types.FloorItems>([]);
|
const itemsData = useRef<Types.FloorItems>([]);
|
||||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("")
|
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("")
|
||||||
|
|
||||||
const email = localStorage.getItem('email')
|
const email = localStorage.getItem('email')
|
||||||
const organization = (email!.split("@")[1]).split(".")[0];
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
@ -156,17 +157,18 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
if (keyEvent === "Ctrl") {
|
if (keyEvent === "Ctrl") {
|
||||||
targetX = snapControls(targetX, "Ctrl");
|
targetX = snapControls(targetX, "Ctrl");
|
||||||
targetZ = snapControls(targetZ, "Ctrl");
|
targetZ = snapControls(targetZ, "Ctrl");
|
||||||
} else if (keyEvent === "Ctrl+Shift") {
|
|
||||||
targetX = snapControls(targetX, "Ctrl+Shift");
|
|
||||||
targetZ = snapControls(targetZ, "Ctrl+Shift");
|
|
||||||
} else if (keyEvent === "Shift") {
|
|
||||||
targetX = snapControls(targetX, "Shift");
|
|
||||||
targetZ = snapControls(targetZ, "Shift");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// else if (keyEvent === "Ctrl+Shift") {
|
||||||
|
// targetX = snapControls(targetX, "Ctrl+Shift");
|
||||||
|
// targetZ = snapControls(targetZ, "Ctrl+Shift");
|
||||||
|
// } else if (keyEvent === "Shift") {
|
||||||
|
// targetX = snapControls(targetX, "Shift");
|
||||||
|
// targetZ = snapControls(targetZ, "Shift");
|
||||||
|
// } else {
|
||||||
|
// }
|
||||||
|
|
||||||
const position = new THREE.Vector3();
|
const position = new THREE.Vector3();
|
||||||
|
|
||||||
if (boundingBoxRef.current) {
|
if (boundingBoxRef.current) {
|
||||||
boundingBoxRef.current.getWorldPosition(position);
|
boundingBoxRef.current.getWorldPosition(position);
|
||||||
selectionGroup.current.position.lerp(
|
selectionGroup.current.position.lerp(
|
||||||
|
@ -194,6 +196,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,7 +218,6 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
obj.position.copy(worldPosition);
|
obj.position.copy(worldPosition);
|
||||||
|
|
||||||
if (itemsGroupRef.current) {
|
if (itemsGroupRef.current) {
|
||||||
|
|
||||||
const newFloorItem: Types.FloorItemType = {
|
const newFloorItem: Types.FloorItemType = {
|
||||||
modelUuid: obj.uuid,
|
modelUuid: obj.uuid,
|
||||||
modelName: obj.userData.name,
|
modelName: obj.userData.name,
|
||||||
|
@ -311,7 +313,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
|
||||||
setKeyEvent("")
|
setKeyEvent("")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return <DistanceFindingControls boundingBoxRef={boundingBoxRef} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MoveControls
|
export default MoveControls
|
Loading…
Reference in New Issue