diff --git a/app/src/modules/builder/functions/computeAreaFrom3DPoints.ts b/app/src/modules/builder/functions/computeAreaFrom3DPoints.ts new file mode 100644 index 0000000..66ee841 --- /dev/null +++ b/app/src/modules/builder/functions/computeAreaFrom3DPoints.ts @@ -0,0 +1,17 @@ +import { Vector2 } from "three"; + +export function computeAreaFrom3DPoints(points3D: any) { + // Project 3D points onto XZ plane + const projected = points3D.map((p: any) => new Vector2(p[0], p[2])); + + // Shoelace formula for 2D polygon + let area = 0; + const n = projected.length; + for (let i = 0; i < n - 1; i++) { + const curr = projected[i]; + const next = projected[i + 1]; + area += curr.x * next.y - next.x * curr.y; + } + + return Math.abs(area) / 2; +} diff --git a/app/src/modules/builder/groups/zoneGroup.tsx b/app/src/modules/builder/groups/zoneGroup.tsx index 3bd9b8d..d1f4b92 100644 --- a/app/src/modules/builder/groups/zoneGroup.tsx +++ b/app/src/modules/builder/groups/zoneGroup.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo, useRef } from "react"; -import { Line, Sphere } from "@react-three/drei"; +import { Html, Line, Sphere } from "@react-three/drei"; import { useThree, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { @@ -17,6 +17,8 @@ import { import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi"; import * as CONSTANTS from "../../../types/world/worldConstants"; +import * as turf from '@turf/turf'; +import { computeAreaFrom3DPoints } from "../functions/computeAreaFrom3DPoints"; const ZoneGroup: React.FC = () => { const { camera, pointer, gl, raycaster, scene, controls } = useThree(); @@ -526,11 +528,12 @@ const ZoneGroup: React.FC = () => { } }); + return ( - {zones.map((zone: any) => ( - + {zones.map((zone: any, index: number) => ( + {zone.points .slice(0, -1) .map((point: [number, number, number], index: number) => { @@ -549,7 +552,7 @@ const ZoneGroup: React.FC = () => { const midpoint = new THREE.Vector3( (point1.x + point2.x) / 2, CONSTANTS.zoneConfig.height / 2 + - (zone.layer - 1) * CONSTANTS.zoneConfig.height, + (zone.layer - 1) * CONSTANTS.zoneConfig.height, (point1.z + point2.z) / 2 ); @@ -592,7 +595,60 @@ const ZoneGroup: React.FC = () => { }} /> ))} + + + {zones.map((zone: any, index: any) => { + const points3D = zone.points; + const coords2D = points3D.map((p: any) => [p[0], p[2]]); + + if ( + coords2D.length < 3 || + coords2D[0][0] !== coords2D[coords2D.length - 1][0] || + coords2D[0][1] !== coords2D[coords2D.length - 1][1] + ) { + coords2D.push(coords2D[0]); + } + + 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 = computeAreaFrom3DPoints(points3D); + const formattedArea = `${area.toFixed(2)} m²`; + + const htmlPosition: [number, number, number] = [ + center2D[0], + avgY + CONSTANTS.zoneConfig.height, + center2D[1] + ]; + return ( + +
+ {zone.zoneName} ({formattedArea}) +
+ + ); + })} +
+ {zones .filter((zone: any) => zone.layer === activeLayer) @@ -624,6 +680,7 @@ const ZoneGroup: React.FC = () => { /> )} +
); }; diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 40743e0..ff7fec3 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -19,6 +19,9 @@ border-radius: #{$border-radius-medium}; box-shadow: var(--box-shadow-light); } + .area{ + background: #008cff; + } } .pointer-none {