first commit
This commit is contained in:
742
app/src/modules/builder/groups/zoneGroup.tsx
Normal file
742
app/src/modules/builder/groups/zoneGroup.tsx
Normal file
@@ -0,0 +1,742 @@
|
||||
import React, { useState, useEffect, useMemo, useRef } from "react";
|
||||
import { Html, Line, Sphere } from "@react-three/drei";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
useActiveLayer,
|
||||
useDeleteTool,
|
||||
useDeletePointOrLine,
|
||||
useSocketStore,
|
||||
useToggleView,
|
||||
useToolMode,
|
||||
useRemovedLayer,
|
||||
useZones,
|
||||
useZonePoints,
|
||||
} from "../../../store/builder/store";
|
||||
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
|
||||
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
import * as turf from "@turf/turf";
|
||||
import { computeArea } from "../functions/computeArea";
|
||||
import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const ZoneGroup: React.FC = () => {
|
||||
const { camera, pointer, gl, raycaster, scene, controls } = useThree();
|
||||
const [startPoint, setStartPoint] = useState<THREE.Vector3 | null>(null);
|
||||
const [endPoint, setEndPoint] = useState<THREE.Vector3 | null>(null);
|
||||
const { zones, setZones } = useZones();
|
||||
const { zonePoints, setZonePoints } = useZonePoints();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const [draggedSphere, setDraggedSphere] = useState<THREE.Vector3 | null>(
|
||||
null
|
||||
);
|
||||
const plane = useMemo(
|
||||
() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
|
||||
[]
|
||||
);
|
||||
const { toggleView } = useToggleView();
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { removedLayer, setRemovedLayer } = useRemovedLayer();
|
||||
const { toolMode } = useToolMode();
|
||||
const { setDeleteTool } = useDeleteTool();
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { socket } = useSocketStore();
|
||||
const { projectId } = useParams();
|
||||
|
||||
const groupsRef = useRef<any>();
|
||||
|
||||
const zoneMaterial = useMemo(
|
||||
() =>
|
||||
new THREE.ShaderMaterial({
|
||||
side: THREE.DoubleSide,
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main(){
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
vUv = uv;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 uOuterColor;
|
||||
void main(){
|
||||
float alpha = 1.0 - vUv.y;
|
||||
gl_FragColor = vec4(uOuterColor, alpha);
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
uOuterColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) },
|
||||
},
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZones = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
if (!email) return;
|
||||
|
||||
const organization = email.split("@")[1].split(".")[0];
|
||||
const data = await getZonesApi(organization, projectId);
|
||||
// console.log('data: ', data);
|
||||
|
||||
if (data.length > 0) {
|
||||
const fetchedZones = data.map((zone: any) => ({
|
||||
zoneUuid: zone.zoneUuid,
|
||||
zoneName: zone.zoneName,
|
||||
points: zone.points,
|
||||
viewPortCenter: zone.viewPortCenter,
|
||||
viewPortposition: zone.viewPortposition,
|
||||
layer: zone.layer,
|
||||
}));
|
||||
|
||||
setZones(fetchedZones);
|
||||
|
||||
const fetchedPoints = data.flatMap((zone: any) =>
|
||||
zone.points
|
||||
.slice(0, 4)
|
||||
.map(
|
||||
(point: [number, number, number]) => new THREE.Vector3(...point)
|
||||
)
|
||||
);
|
||||
|
||||
setZonePoints(fetchedPoints);
|
||||
}
|
||||
};
|
||||
|
||||
fetchZones();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("zones", JSON.stringify(zones));
|
||||
}, [zones]);
|
||||
|
||||
useEffect(() => {
|
||||
if (removedLayer) {
|
||||
const updatedZones = zones.filter(
|
||||
(zone: any) => zone.layer !== removedLayer
|
||||
);
|
||||
setZones(updatedZones);
|
||||
|
||||
const updatedzonePoints = zonePoints.filter((_: any, index: any) => {
|
||||
const zoneIndex = Math.floor(index / 4);
|
||||
return zones[zoneIndex]?.layer !== removedLayer;
|
||||
});
|
||||
setZonePoints(updatedzonePoints);
|
||||
|
||||
zones
|
||||
.filter((zone: any) => zone.layer === removedLayer)
|
||||
.forEach((zone: any) => {
|
||||
deleteZoneFromBackend(zone.zoneUuid);
|
||||
});
|
||||
|
||||
setRemovedLayer(null);
|
||||
}
|
||||
}, [removedLayer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (toolMode !== "Zone") {
|
||||
setStartPoint(null);
|
||||
setEndPoint(null);
|
||||
} else {
|
||||
setDeletePointOrLine(false);
|
||||
setDeleteTool(false);
|
||||
}
|
||||
if (!toggleView) {
|
||||
setStartPoint(null);
|
||||
setEndPoint(null);
|
||||
}
|
||||
}, [toolMode, toggleView]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const addZoneToBackend = async (zone: {
|
||||
zoneUuid: string;
|
||||
zoneName: string;
|
||||
points: [number, number, number][];
|
||||
layer: string;
|
||||
}) => {
|
||||
console.log('zoneUuid: ', zone);
|
||||
const email = localStorage.getItem("email");
|
||||
const userId = localStorage.getItem("userId");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
const calculateCenter = (points: number[][]) => {
|
||||
if (!points || points.length === 0) return null;
|
||||
|
||||
let sumX = 0,
|
||||
sumY = 0,
|
||||
sumZ = 0;
|
||||
const numPoints = points.length;
|
||||
|
||||
points.forEach(([x, y, z]) => {
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
sumZ += z;
|
||||
});
|
||||
|
||||
return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
];
|
||||
};
|
||||
|
||||
const target: [number, number, number] | null = calculateCenter(
|
||||
zone.points
|
||||
);
|
||||
if (!target || zone.points.length < 4) return;
|
||||
const position = [target[0], 10, target[2]];
|
||||
|
||||
const input = {
|
||||
userId: userId,
|
||||
projectId,
|
||||
organization: organization,
|
||||
zoneData: {
|
||||
zoneName: zone.zoneName,
|
||||
zoneUuid: zone.zoneUuid,
|
||||
points: zone.points,
|
||||
viewPortCenter: target,
|
||||
viewPortposition: position,
|
||||
layer: zone.layer,
|
||||
},
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:set", input);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const updateZoneToBackend = async (zone: {
|
||||
zoneUuid: string;
|
||||
zoneName: string;
|
||||
points: [number, number, number][];
|
||||
layer: string;
|
||||
}) => {
|
||||
const email = localStorage.getItem("email");
|
||||
const userId = localStorage.getItem("userId");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
const calculateCenter = (points: number[][]) => {
|
||||
if (!points || points.length === 0) return null;
|
||||
|
||||
let sumX = 0,
|
||||
sumY = 0,
|
||||
sumZ = 0;
|
||||
const numPoints = points.length;
|
||||
|
||||
points.forEach(([x, y, z]) => {
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
sumZ += z;
|
||||
});
|
||||
|
||||
return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
];
|
||||
};
|
||||
|
||||
const target: [number, number, number] | null = calculateCenter(
|
||||
zone.points
|
||||
);
|
||||
if (!target || zone.points.length < 4) return;
|
||||
const position = [target[0], 10, target[2]];
|
||||
|
||||
const input = {
|
||||
userId: userId,
|
||||
projectId,
|
||||
organization: organization,
|
||||
zoneData: {
|
||||
zoneName: zone.zoneName,
|
||||
zoneUuid: zone.zoneUuid,
|
||||
points: zone.points,
|
||||
viewPortCenter: target,
|
||||
viewPortposition: position,
|
||||
layer: zone.layer,
|
||||
},
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:set", input);
|
||||
};
|
||||
|
||||
const deleteZoneFromBackend = async (zoneUuid: string) => {
|
||||
const email = localStorage.getItem("email");
|
||||
const userId = localStorage.getItem("userId");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
const input = {
|
||||
userId: userId,
|
||||
projectId,
|
||||
organization: organization,
|
||||
zoneUuid: zoneUuid,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:delete", input);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const handleDeleteZone = (zoneUuid: string) => {
|
||||
const updatedZones = zones.filter((zone: any) => zone.zoneUuid !== zoneUuid);
|
||||
setZones(updatedZones);
|
||||
|
||||
const zoneIndex = zones.findIndex((zone: any) => zone.zoneUuid === zoneUuid);
|
||||
if (zoneIndex !== -1) {
|
||||
const zonePointsToRemove = zonePoints.slice(
|
||||
zoneIndex * 4,
|
||||
zoneIndex * 4 + 4
|
||||
);
|
||||
zonePointsToRemove.forEach((point: any) =>
|
||||
groupsRef.current.remove(point)
|
||||
);
|
||||
const updatedzonePoints = zonePoints.filter(
|
||||
(_: any, index: any) =>
|
||||
index < zoneIndex * 4 || index >= zoneIndex * 4 + 4
|
||||
);
|
||||
setZonePoints(updatedzonePoints);
|
||||
}
|
||||
deleteZoneFromBackend(zoneUuid);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !toggleView) return;
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(
|
||||
groupsRef.current.children,
|
||||
true
|
||||
);
|
||||
|
||||
if (intersects.length > 0 && toolMode === "move") {
|
||||
const clickedObject = intersects[0].object;
|
||||
const sphereIndex = zonePoints.findIndex((point: any) =>
|
||||
point.equals(clickedObject.position)
|
||||
);
|
||||
if (sphereIndex !== -1) {
|
||||
(controls as any).enabled = false;
|
||||
setDraggedSphere(zonePoints[sphereIndex]);
|
||||
setIsDragging(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: any) => {
|
||||
if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) {
|
||||
isLeftMouseDown = false;
|
||||
|
||||
if (!startPoint && toolMode !== "move") {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (point) {
|
||||
setStartPoint(point);
|
||||
setEndPoint(null);
|
||||
}
|
||||
} else if (startPoint && toolMode !== "move") {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (!point) return;
|
||||
|
||||
const points = [
|
||||
[startPoint.x, 0.15, startPoint.z],
|
||||
[point.x, 0.15, startPoint.z],
|
||||
[point.x, 0.15, point.z],
|
||||
[startPoint.x, 0.15, point.z],
|
||||
[startPoint.x, 0.15, startPoint.z],
|
||||
] as [number, number, number][];
|
||||
|
||||
const zoneName = `Zone ${zones.length + 1}`;
|
||||
const zoneUuid = THREE.MathUtils.generateUUID();
|
||||
const newZone = {
|
||||
zoneUuid,
|
||||
zoneName,
|
||||
points: points,
|
||||
layer: activeLayer,
|
||||
};
|
||||
|
||||
const newZones = [...zones, newZone];
|
||||
|
||||
setZones(newZones);
|
||||
|
||||
const newzonePoints = [
|
||||
new THREE.Vector3(startPoint.x, 0.15, startPoint.z),
|
||||
new THREE.Vector3(point.x, 0.15, startPoint.z),
|
||||
new THREE.Vector3(point.x, 0.15, point.z),
|
||||
new THREE.Vector3(startPoint.x, 0.15, point.z),
|
||||
];
|
||||
|
||||
const updatedZonePoints = [...zonePoints, ...newzonePoints];
|
||||
setZonePoints(updatedZonePoints);
|
||||
|
||||
addZoneToBackend(newZone);
|
||||
setStartPoint(null);
|
||||
setEndPoint(null);
|
||||
}
|
||||
} else if (
|
||||
evt.button === 0 &&
|
||||
!drag &&
|
||||
!isDragging &&
|
||||
deletePointOrLine
|
||||
) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(
|
||||
groupsRef.current.children,
|
||||
true
|
||||
);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const clickedObject = intersects[0].object;
|
||||
|
||||
const sphereIndex = zonePoints.findIndex((point: any) =>
|
||||
point.equals(clickedObject.position)
|
||||
);
|
||||
if (sphereIndex !== -1) {
|
||||
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||
const zoneUuid = zones[zoneIndex].zoneUuid;
|
||||
handleDeleteZone(zoneUuid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.button === 0) {
|
||||
if (isDragging && draggedSphere) {
|
||||
setIsDragging(false);
|
||||
setDraggedSphere(null);
|
||||
|
||||
const sphereIndex = zonePoints.findIndex(
|
||||
(point: any) => point === draggedSphere
|
||||
);
|
||||
if (sphereIndex !== -1) {
|
||||
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||
|
||||
if (zoneIndex !== -1 && zones[zoneIndex]) {
|
||||
updateZoneToBackend(zones[zoneIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (!groupsRef.current) return;
|
||||
if (isLeftMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(
|
||||
groupsRef.current.children,
|
||||
true
|
||||
);
|
||||
|
||||
if (
|
||||
intersects.length > 0 &&
|
||||
intersects[0].object.name.includes("point")
|
||||
) {
|
||||
gl.domElement.style.cursor =
|
||||
toolMode === "move" ? "pointer" : "default";
|
||||
} else {
|
||||
gl.domElement.style.cursor = "default";
|
||||
}
|
||||
if (isDragging && draggedSphere) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (point) {
|
||||
draggedSphere.set(point.x, 0.15, point.z);
|
||||
|
||||
const sphereIndex = zonePoints.findIndex(
|
||||
(point: any) => point === draggedSphere
|
||||
);
|
||||
if (sphereIndex !== -1) {
|
||||
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||
const cornerIndex = sphereIndex % 4;
|
||||
|
||||
const updatedZones = zones.map((zone: any, index: number) => {
|
||||
if (index === zoneIndex) {
|
||||
const updatedPoints = [...zone.points];
|
||||
updatedPoints[cornerIndex] = [point.x, 0.15, point.z];
|
||||
updatedPoints[4] = updatedPoints[0];
|
||||
return { ...zone, points: updatedPoints };
|
||||
}
|
||||
return zone;
|
||||
});
|
||||
|
||||
setZones(updatedZones);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onContext = (event: any) => {
|
||||
event.preventDefault();
|
||||
setStartPoint(null);
|
||||
setEndPoint(null);
|
||||
};
|
||||
|
||||
if (toolMode === "Zone" || deletePointOrLine || toolMode === "move") {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("contextmenu", onContext);
|
||||
}
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("contextmenu", onContext);
|
||||
};
|
||||
}, [
|
||||
gl,
|
||||
camera,
|
||||
startPoint,
|
||||
toggleView,
|
||||
scene,
|
||||
toolMode,
|
||||
zones,
|
||||
isDragging,
|
||||
deletePointOrLine,
|
||||
zonePoints,
|
||||
draggedSphere,
|
||||
activeLayer,
|
||||
raycaster,
|
||||
pointer,
|
||||
controls,
|
||||
plane,
|
||||
setZones,
|
||||
setZonePoints,
|
||||
addZoneToBackend,
|
||||
handleDeleteZone,
|
||||
updateZoneToBackend,
|
||||
]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!startPoint) return;
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (point) {
|
||||
setEndPoint(point);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={groupsRef} name="zoneGroup">
|
||||
<group name="zones" visible={!toggleView}>
|
||||
{zones.map((zone: any) => (
|
||||
<group
|
||||
key={zone.zoneUuid}
|
||||
name={zone.zoneName}
|
||||
visible={zone.zoneUuid === selectedZone.zoneUuid}
|
||||
>
|
||||
{zone.points
|
||||
.slice(0, -1)
|
||||
.map((point: [number, number, number], index: number) => {
|
||||
const nextPoint = zone.points[index + 1];
|
||||
|
||||
const point1 = new THREE.Vector3(point[0], point[1], point[2]);
|
||||
const point2 = new THREE.Vector3(
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
nextPoint[2]
|
||||
);
|
||||
|
||||
const planeWidth = point1.distanceTo(point2);
|
||||
const planeHeight = CONSTANTS.zoneConfig.height;
|
||||
|
||||
const midpoint = new THREE.Vector3(
|
||||
(point1.x + point2.x) / 2,
|
||||
CONSTANTS.zoneConfig.height / 2 +
|
||||
(zone.layer - 1) * CONSTANTS.zoneConfig.height,
|
||||
(point1.z + point2.z) / 2
|
||||
);
|
||||
|
||||
const angle = Math.atan2(
|
||||
point2.z - point1.z,
|
||||
point2.x - point1.x
|
||||
);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
key={index}
|
||||
position={midpoint}
|
||||
rotation={[0, -angle, 0]}
|
||||
>
|
||||
<planeGeometry args={[planeWidth, planeHeight]} />
|
||||
<primitive
|
||||
object={zoneMaterial.clone()}
|
||||
attach="material"
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
{!toggleView &&
|
||||
(() => {
|
||||
const points3D = zone.points || [];
|
||||
const coords2D = points3D.map((p: any) => [p[0], p[2]]);
|
||||
|
||||
// Ensure the polygon is closed
|
||||
if (
|
||||
coords2D.length >= 4 &&
|
||||
(coords2D[0][0] !== coords2D[coords2D.length - 1][0] ||
|
||||
coords2D[0][1] !== coords2D[coords2D.length - 1][1])
|
||||
) {
|
||||
coords2D.push(coords2D[0]);
|
||||
}
|
||||
if (coords2D.length < 4) return null;
|
||||
|
||||
const polygon = turf.polygon([coords2D]);
|
||||
const center2D = turf.center(polygon).geometry.coordinates;
|
||||
|
||||
// Calculate the average Y value
|
||||
const sumY = points3D.reduce(
|
||||
(sum: number, p: any) => sum + p[1],
|
||||
0
|
||||
);
|
||||
const avgY = points3D.length > 0 ? sumY / points3D.length : 0;
|
||||
|
||||
const htmlPosition: [number, number, number] = [
|
||||
center2D[0],
|
||||
avgY + (CONSTANTS.zoneConfig.height || 0) + 1.5,
|
||||
center2D[1],
|
||||
];
|
||||
|
||||
return (
|
||||
<Html
|
||||
// data
|
||||
key={zone.zoneUuid}
|
||||
position={htmlPosition}
|
||||
// class
|
||||
className="zone-name-wrapper"
|
||||
// others
|
||||
center
|
||||
>
|
||||
<div className="zone-name">{zone.zoneName}</div>
|
||||
</Html>
|
||||
);
|
||||
})()}
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
<group name="zoneLines" visible={toggleView}>
|
||||
{zones
|
||||
.filter((zone: any) => zone.layer === activeLayer)
|
||||
.map((zone: any) => (
|
||||
<Line
|
||||
key={zone.zoneUuid}
|
||||
points={zone.points}
|
||||
color="#007BFF"
|
||||
lineWidth={3}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (deletePointOrLine) {
|
||||
handleDeleteZone(zone.zoneUuid);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</group>
|
||||
<group name="zoneArea" visible={toggleView}>
|
||||
{zones.map((zone: any, index: any) => {
|
||||
if (!toggleView) return null;
|
||||
const points3D = zone.points;
|
||||
const coords2D = points3D.map((p: any) => [p[0], p[2]]);
|
||||
|
||||
if (
|
||||
coords2D.length < 4 ||
|
||||
coords2D[0][0] !== coords2D[coords2D.length - 1][0] ||
|
||||
coords2D[0][1] !== coords2D[coords2D.length - 1][1]
|
||||
) {
|
||||
coords2D.push(coords2D[0]);
|
||||
}
|
||||
if (coords2D.length < 4) return null;
|
||||
|
||||
const polygon = turf.polygon([coords2D]);
|
||||
const center2D = turf.center(polygon).geometry.coordinates;
|
||||
|
||||
const sumY = points3D.reduce((sum: number, p: any) => sum + p[1], 0);
|
||||
const avgY = sumY / points3D.length;
|
||||
|
||||
const area = computeArea(points3D, "zone");
|
||||
const formattedArea = `${area.toFixed(2)} m²`;
|
||||
|
||||
const htmlPosition: [number, number, number] = [
|
||||
center2D[0],
|
||||
avgY + CONSTANTS.zoneConfig.height,
|
||||
center2D[1],
|
||||
];
|
||||
return (
|
||||
<Html
|
||||
// data
|
||||
key={`${index}-${zone}`}
|
||||
position={htmlPosition}
|
||||
// class
|
||||
wrapperClass="distance-text-wrapper"
|
||||
className="distance-text"
|
||||
// other
|
||||
zIndexRange={[1, 0]}
|
||||
prepend
|
||||
center
|
||||
sprite
|
||||
>
|
||||
<div
|
||||
className={`distance area line-${zone}`}
|
||||
key={`${index}-${zone}`}
|
||||
>
|
||||
{zone.zoneName} ({formattedArea})
|
||||
</div>
|
||||
</Html>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
|
||||
<group name="zonePoints" visible={toggleView}>
|
||||
{zones
|
||||
.filter((zone: any) => zone.layer === activeLayer)
|
||||
.flatMap((zone: any) =>
|
||||
zone.points.slice(0, 4).map((point: any, pointIndex: number) => (
|
||||
<Sphere
|
||||
key={`${zone.zoneUuid}-point-${pointIndex}`}
|
||||
position={new THREE.Vector3(...point)}
|
||||
args={[0.3, 16, 16]}
|
||||
name={`point-${zone.zoneUuid}-${pointIndex}`}
|
||||
>
|
||||
<meshBasicMaterial color="red" />
|
||||
</Sphere>
|
||||
))
|
||||
)}
|
||||
</group>
|
||||
<group name="tempGroup" visible={toggleView}>
|
||||
{startPoint && endPoint && (
|
||||
<Line
|
||||
points={[
|
||||
[startPoint.x, 0.15, startPoint.z],
|
||||
[endPoint.x, 0.15, startPoint.z],
|
||||
[endPoint.x, 0.15, endPoint.z],
|
||||
[startPoint.x, 0.15, endPoint.z],
|
||||
[startPoint.x, 0.15, startPoint.z],
|
||||
]}
|
||||
color="#C164FF"
|
||||
lineWidth={3}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZoneGroup;
|
||||
Reference in New Issue
Block a user