architwin 1.14.8 → 1.14.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { MpSdk } from "../../bundle/sdk";
2
2
  import { Pane } from 'tweakpane';
3
- import { EdgePolyData, WallPolyData } from "../types";
3
+ import { EdgePolyData, PolygonData, WallPolyData, WindowPolyData } from "../types";
4
4
  export declare class TubeLine {
5
5
  mpSdk: MpSdk;
6
6
  mesh: THREE.Mesh;
@@ -39,7 +39,11 @@ export declare class TubeLine {
39
39
  scrollSpeed: number;
40
40
  collider: boolean;
41
41
  visible: boolean;
42
+ drawingMode: string;
43
+ targetIndex: any;
44
+ polygonData: any;
42
45
  renderPolygonOnAdd: boolean;
46
+ targetUUID: any;
43
47
  };
44
48
  outputs: Record<string, unknown> & MpSdk.Scene.PredefinedOutputs;
45
49
  context: MpSdk.Scene.IComponentContext;
@@ -119,6 +123,7 @@ export declare class BufferGeometry {
119
123
  floorName: string;
120
124
  floorColor: number;
121
125
  wallColor: number;
126
+ windowColor: number;
122
127
  floorOpacity: number;
123
128
  wallOpacity: number;
124
129
  visible: boolean;
@@ -128,6 +133,10 @@ export declare class BufferGeometry {
128
133
  excludeHiddenWallsFromCalculation: boolean;
129
134
  wallHeight: number;
130
135
  floorLevel: any;
136
+ drawingMode: string;
137
+ windowData: any;
138
+ targetIndex: any;
139
+ targetUUID: any;
131
140
  };
132
141
  outputs: Record<string, unknown> & MpSdk.Scene.PredefinedOutputs;
133
142
  context: MpSdk.Scene.IComponentContext;
@@ -147,9 +156,10 @@ export declare class BufferGeometry {
147
156
  onInit: () => void;
148
157
  onTick: (delta: any) => void;
149
158
  onDestroy(): void;
150
- renderWall(): void;
151
159
  createLabelCanvas(text: string): HTMLCanvasElement;
152
- renderWalls(WALL_HEIGHT: number): (WallPolyData[] | EdgePolyData[])[];
160
+ renderWindow(WALL_HEIGHT: number): void;
161
+ renderWalls(WALL_HEIGHT: number, metadata?: PolygonData): (WallPolyData[] | EdgePolyData[])[];
162
+ renderWindows(windows: WindowPolyData[]): void;
153
163
  renderPolygon: () => void;
154
164
  }
155
165
  export declare const bufferGeometryType = "bufferGeometry";
@@ -4,6 +4,7 @@ import { SPACE_EVENTS } from "../types";
4
4
  import { getPolygonArea } from "../utils";
5
5
  import { PolygonCalculator, WallCalculator } from "../math";
6
6
  import i18n from "../atwinui/components/toolbar/i18n";
7
+ import log from "loglevel";
7
8
  export class TubeLine {
8
9
  constructor(mpSdk) {
9
10
  this.time = 0;
@@ -34,7 +35,11 @@ export class TubeLine {
34
35
  scrollSpeed: 1.35,
35
36
  collider: true,
36
37
  visible: false,
37
- renderPolygonOnAdd: true
38
+ drawingMode: 'floor',
39
+ targetIndex: undefined,
40
+ polygonData: undefined,
41
+ renderPolygonOnAdd: true,
42
+ targetUUID: undefined,
38
43
  };
39
44
  //@ts-expect-error
40
45
  this.outputs = {
@@ -77,11 +82,21 @@ export class TubeLine {
77
82
  if (this.inputs.path.length > 1) {
78
83
  this.renderTubeLine();
79
84
  console.log("paths ", this.inputs.path);
80
- if (isToolbarFeatureEnabled('roomCreation') && this.inputs.path.length >= 3 && this.inputs.renderPolygonOnAdd) {
85
+ if (isToolbarFeatureEnabled('roomCreation') && this.inputs.path.length >= 3 && this.inputs.drawingMode == 'window') {
86
+ const options = {
87
+ drawingMode: this.inputs.drawingMode,
88
+ targetIndex: this.inputs.targetIndex,
89
+ polygonData: this.inputs.polygonData,
90
+ targetUUID: this.inputs.targetUUID
91
+ };
92
+ renderPolygon(this.inputs.path, options);
93
+ }
94
+ else if (isToolbarFeatureEnabled('roomCreation') && this.inputs.path.length >= 3 && this.inputs.renderPolygonOnAdd) {
81
95
  const wallHeight = getWallBaseHeight();
82
96
  if (wallHeight) {
83
97
  const options = {
84
- wallHeight: wallHeight
98
+ wallHeight: wallHeight,
99
+ drawingMode: this.inputs.drawingMode
85
100
  };
86
101
  renderPolygon(this.inputs.path, options);
87
102
  }
@@ -481,6 +496,8 @@ export class BufferGeometry {
481
496
  floorName: '',
482
497
  floorColor: 0x0000ff,
483
498
  wallColor: 0xffd150,
499
+ // windowColor : 0xEAF2EF,
500
+ windowColor: 0xC09672,
484
501
  floorOpacity: 0.3,
485
502
  wallOpacity: 0.6,
486
503
  visible: true,
@@ -489,7 +506,11 @@ export class BufferGeometry {
489
506
  polygonData: undefined,
490
507
  excludeHiddenWallsFromCalculation: true,
491
508
  wallHeight: 2,
492
- floorLevel: undefined
509
+ floorLevel: undefined,
510
+ drawingMode: 'floor',
511
+ windowData: undefined,
512
+ targetIndex: undefined,
513
+ targetUUID: undefined,
493
514
  };
494
515
  this.emits = {
495
516
  changed: true,
@@ -557,13 +578,13 @@ export class BufferGeometry {
557
578
  const polyGeometry = new THREE.ShapeGeometry(polyShape);
558
579
  polyGeometry.rotateX(Math.PI / 2);
559
580
  const floorMesh = new THREE.Mesh(polyGeometry, new THREE.MeshBasicMaterial({
560
- color: this.inputs.floorColor,
581
+ color: this.inputs.drawingMode == 'floor' ? this.inputs.floorColor : this.inputs.windowColor,
561
582
  side: THREE.DoubleSide,
562
583
  opacity: this.inputs.floorOpacity,
563
584
  transparent: true
564
585
  }));
565
586
  floorMesh.translateY(floorLevel);
566
- floorMesh.name = this.inputs.uuid != '' ? `${this.inputs.uuid}_floor` : 'floor';
587
+ floorMesh.name = this.inputs.uuid != '' ? `${this.inputs.uuid}_${this.inputs.drawingMode}` : `${this.inputs.drawingMode}`;
567
588
  const floorConfig = polyData ? polyData.floor : this.inputs.floorData;
568
589
  const floorDimension = PolygonCalculator.calculatePolygonProperties(this.inputs.path);
569
590
  if (floorConfig && floorConfig.options) {
@@ -574,54 +595,185 @@ export class BufferGeometry {
574
595
  // }
575
596
  }
576
597
  this.groupedMesh.add(floorMesh);
577
- const floorArea = getPolygonArea(this.inputs.path);
578
- let [floors, walls] = this.renderWalls(WALL_HEIGHT);
579
- floorDataArray = floors;
580
- wallDataArray = walls;
581
- //end of wall loop
582
- const floorData = {
583
- options: {
584
- color: this.inputs.floorColor,
585
- opacity: this.inputs.floorOpacity,
586
- is_visible: floorMesh.visible
587
- },
588
- area: floorDimension.area,
589
- perimeter: floorDimension.perimeter,
590
- floor_level: floorLevel,
591
- edges: floorDataArray
592
- };
593
- if (polyData && polyData.floor.uuid) {
594
- floorData.uuid = polyData.floor.uuid;
598
+ try {
599
+ let twiceSignedArea = 0;
600
+ let centroidX = 0;
601
+ let centroidZ = 0;
602
+ for (let i = 0; i < shapeCoords.length; i++) {
603
+ const p1 = shapeCoords[i];
604
+ const p2 = shapeCoords[(i + 1) % shapeCoords.length];
605
+ const cross = p1.x * p2.y - p2.x * p1.y;
606
+ twiceSignedArea += cross;
607
+ centroidX += (p1.x + p2.x) * cross;
608
+ centroidZ += (p1.y + p2.y) * cross;
609
+ }
610
+ let centerX;
611
+ let centerZ;
612
+ if (Math.abs(twiceSignedArea) > 1e-8) {
613
+ const polygonArea = twiceSignedArea * 0.5;
614
+ centerX = centroidX / (6 * polygonArea);
615
+ centerZ = centroidZ / (6 * polygonArea);
616
+ }
617
+ else {
618
+ // Fallback: average of vertices
619
+ const sum = shapeCoords.reduce((acc, p) => ({ x: acc.x + p.x, z: acc.z + p.y }), { x: 0, z: 0 });
620
+ centerX = sum.x / shapeCoords.length;
621
+ centerZ = sum.z / shapeCoords.length;
622
+ }
623
+ const floorMaterial = polyData && polyData.floor.material ? polyData.floor.material : undefined;
624
+ // const floorLabelText = `Floor_Cement: ${floorDimension.area.toFixed(2)}m²`;
625
+ const floorLabelText = `Floor ${floorMaterial ? '_' + floorMaterial : ' (' + i18n.t('Area') + ')'} : ${floorDimension.area.toFixed(2)}m²`;
626
+ // const floorLabelText = `${floorMaterial ? floorMaterial : i18n.t('Area')}: ${floorDimension.area.toFixed(2)}m²`;
627
+ const floorCanvas = this.createLabelCanvas(floorLabelText);
628
+ const labelWidth = Math.max(1, Math.min(5, Math.sqrt(Math.max(0.0001, floorDimension.area))));
629
+ const labelHeight = labelWidth / 4;
630
+ const floorLabelGeometry = new THREE.PlaneGeometry(labelWidth, labelHeight);
631
+ const floorLabelMaterial = new THREE.MeshBasicMaterial({
632
+ map: new THREE.CanvasTexture(floorCanvas),
633
+ transparent: true,
634
+ side: THREE.DoubleSide,
635
+ });
636
+ const floorCenterLabel = new THREE.Mesh(floorLabelGeometry, floorLabelMaterial);
637
+ // Lay flat on the floor (XZ plane) and slightly offset in Y to avoid z-fighting
638
+ floorCenterLabel.rotation.x = -Math.PI / 2;
639
+ floorCenterLabel.position.set(centerX, floorLevel + 0.02, centerZ);
640
+ floorCenterLabel.name = this.inputs.uuid != '' ? `${this.inputs.uuid}_floorCenterLabel` : `floorCenterLabel`;
641
+ if (floorConfig && floorConfig.options) {
642
+ floorCenterLabel.visible = floorConfig.options.is_visible;
643
+ }
644
+ this.floorLabels.push(floorCenterLabel);
645
+ this.groupedMesh.add(floorCenterLabel);
595
646
  }
596
- let totalWallArea = 0;
597
- let totalWallLength = 0;
598
- for (let index = 0; index < wallDataArray.length; index++) {
599
- if (wallDataArray[index].options && wallDataArray[index].options.is_deleted) {
600
- continue;
647
+ catch (e) {
648
+ log.error('Error creating floor center label', e);
649
+ }
650
+ const floorArea = getPolygonArea(this.inputs.path);
651
+ let partitionData = undefined;
652
+ if (this.inputs.drawingMode == 'floor') {
653
+ let [floors, walls] = this.renderWalls(WALL_HEIGHT, polyData);
654
+ floorDataArray = floors;
655
+ wallDataArray = walls;
656
+ //end of wall loop
657
+ const floorData = {
658
+ options: {
659
+ color: this.inputs.floorColor,
660
+ opacity: this.inputs.floorOpacity,
661
+ is_visible: floorMesh.visible
662
+ },
663
+ area: floorDimension.area,
664
+ perimeter: floorDimension.perimeter,
665
+ floor_level: floorLevel,
666
+ edges: floorDataArray,
667
+ material: polyData && polyData.floor.material ? polyData.floor.material : undefined
668
+ };
669
+ if (polyData && polyData.floor.uuid) {
670
+ if (polyData.floor.uuid)
671
+ floorData.uuid = polyData.floor.uuid;
672
+ if (polyData.floor.material)
673
+ floorData.material = polyData.floor.material;
601
674
  }
602
- if (this.inputs.excludeHiddenWallsFromCalculation && wallDataArray[index].options && wallDataArray[index].options.is_visible == false) {
603
- continue;
675
+ let totalWallArea = 0;
676
+ let totalWallLength = 0;
677
+ for (let index = 0; index < wallDataArray.length; index++) {
678
+ if (wallDataArray[index].options && wallDataArray[index].options.is_deleted) {
679
+ continue;
680
+ }
681
+ if (this.inputs.excludeHiddenWallsFromCalculation && wallDataArray[index].options && wallDataArray[index].options.is_visible == false) {
682
+ continue;
683
+ }
684
+ totalWallArea += wallDataArray[index].area;
685
+ totalWallLength += wallDataArray[index].width;
686
+ }
687
+ const totalWallPerimeter = totalWallLength + (WALL_HEIGHT * 2);
688
+ //WINDOW RENDERING
689
+ try {
690
+ if (polyData && polyData.walls && polyData.walls.length) {
691
+ log.info("polyData.walls ", polyData.walls);
692
+ const windowsPolyData = polyData.walls.reduce((acc, wall, index) => {
693
+ if (wall.windows && wall.windows.length > 0) {
694
+ acc = [...acc, ...wall.windows];
695
+ wallDataArray[index].windows = wall.windows;
696
+ }
697
+ return acc;
698
+ }, []);
699
+ this.renderWindows(windowsPolyData);
700
+ }
604
701
  }
605
- totalWallArea += wallDataArray[index].area;
606
- totalWallLength += wallDataArray[index].width;
702
+ catch (error) {
703
+ log.error("Error rendering windows ", error);
704
+ }
705
+ //this.mesh = this.groupedMesh;
706
+ const currentOffsetValue = getPolygonFloorOffset();
707
+ const currentFloor = getCurrentFloor();
708
+ partitionData = {
709
+ path: this.inputs.path,
710
+ floor_offset: currentOffsetValue,
711
+ floor_sequence: currentFloor,
712
+ floor: floorData,
713
+ walls: wallDataArray,
714
+ wall_height: WALL_HEIGHT,
715
+ totalWallArea,
716
+ totalWallPerimeter,
717
+ options: {
718
+ is_visible: this.inputs.visible
719
+ }
720
+ };
721
+ console.log("floor mode partitionData ", partitionData);
607
722
  }
608
- const totalWallPerimeter = totalWallLength + (WALL_HEIGHT * 2);
609
- //this.mesh = this.groupedMesh;
610
- const currentOffsetValue = getPolygonFloorOffset();
611
- const currentFloor = getCurrentFloor();
612
- const partitionData = {
613
- path: this.inputs.path,
614
- floor_offset: currentOffsetValue,
615
- floor_sequence: currentFloor,
616
- floor: floorData,
617
- walls: wallDataArray,
618
- wall_height: WALL_HEIGHT,
619
- totalWallArea,
620
- totalWallPerimeter,
621
- options: {
622
- is_visible: this.inputs.visible
723
+ //IF DRAWING WINDOWS
724
+ else if (this.inputs.drawingMode == 'window' && this.inputs.polygonData && this.inputs.targetIndex != undefined) {
725
+ const polyData = this.inputs.polygonData;
726
+ const targetWallIndex = polyData.walls.findIndex(wall => wall.uuid == this.inputs.targetUUID);
727
+ if (targetWallIndex != undefined) {
728
+ if (!polyData.walls[targetWallIndex].windows)
729
+ polyData.walls[targetWallIndex].windows = [];
730
+ const currentWindowCount = polyData.walls[targetWallIndex].windows ? polyData.walls[targetWallIndex].windows.length + 1 : 0;
731
+ const windowDimension = PolygonCalculator.calculatePolygonProperties(this.inputs.path, true);
732
+ let windowDataArray = [];
733
+ for (let i = 0; i < this.inputs.path.length; i++) {
734
+ const startPoint = this.inputs.path[i];
735
+ const endPoint = this.inputs.path[(i + 1) % this.inputs.path.length];
736
+ const windowLength = new THREE.Vector3(endPoint.x - startPoint.x, endPoint.y - startPoint.y, endPoint.z - startPoint.z).length();
737
+ const windowEdges = {
738
+ start: startPoint,
739
+ end: endPoint,
740
+ length: windowLength
741
+ };
742
+ windowDataArray.push(windowEdges);
743
+ }
744
+ let windowName = `${this.inputs.uuid}_window-${this.inputs.targetIndex + 1}`;
745
+ let windowMaterial = undefined;
746
+ if (polyData.walls[targetWallIndex] && polyData.walls[targetWallIndex].windows[this.inputs.targetIndex]) {
747
+ const currWindow = polyData.walls[targetWallIndex].windows[this.inputs.targetIndex];
748
+ if (currWindow.name)
749
+ windowName = currWindow.name;
750
+ if (currWindow.material)
751
+ windowMaterial = currWindow.material;
752
+ }
753
+ const windowData = {
754
+ path: this.inputs.path,
755
+ name: windowName,
756
+ index: this.inputs.targetIndex,
757
+ options: {
758
+ color: this.inputs.windowColor,
759
+ is_visible: true,
760
+ is_deleted: false
761
+ },
762
+ area: windowDimension.area,
763
+ edges: windowDataArray,
764
+ material: windowMaterial
765
+ };
766
+ try {
767
+ const window = [windowData];
768
+ this.renderWindows(window);
769
+ }
770
+ catch (error) {
771
+ log.error("Error rendering windows ", error);
772
+ }
773
+ polyData.walls[targetWallIndex].windows[this.inputs.targetIndex] = (windowData);
774
+ partitionData = polyData;
623
775
  }
624
- };
776
+ }
625
777
  dispatchSpaceEvent(SPACE_EVENTS.PARTITION_UPDATED, partitionData);
626
778
  console.log("Polygon mesh ", this.groupedMesh, partitionData);
627
779
  console.log("partitionData ", partitionData);
@@ -652,9 +804,6 @@ export class BufferGeometry {
652
804
  this.mesh.geometry.dispose();
653
805
  }
654
806
  ;
655
- renderWall() {
656
- }
657
- ;
658
807
  createLabelCanvas(text) {
659
808
  const canvas = document.createElement('canvas');
660
809
  const wallContext = canvas.getContext('2d');
@@ -668,7 +817,16 @@ export class BufferGeometry {
668
817
  return canvas;
669
818
  }
670
819
  ;
671
- renderWalls(WALL_HEIGHT) {
820
+ renderWindow(WALL_HEIGHT) {
821
+ // const THREE = this.context.three
822
+ // const windowData:WindowPolyData = this.inputs.windowData
823
+ // const windowGeometry = new THREE.BoxGeometry(windowData.width, windowData.height, windowData.depth)
824
+ // const windowMesh = new THREE.Mesh(windowGeometry, new THREE.MeshBasicMaterial({color: windowData.color}))
825
+ // windowMesh.position.set(windowData.position.x, windowData.position.y, windowData.position.z)
826
+ // windowMesh.name = windowData.uuid != '' ? `${windowData.uuid}_window` : 'window'
827
+ // this.groupedMesh.add(windowMesh)
828
+ }
829
+ renderWalls(WALL_HEIGHT, metadata) {
672
830
  const THREE = this.context.three;
673
831
  let floorDataArray = [];
674
832
  let wallDataArray = [];
@@ -708,7 +866,11 @@ export class BufferGeometry {
708
866
  const wallLength = new THREE.Vector3(endPoint.x - startPoint.x, endPoint.y - startPoint.y, endPoint.z - startPoint.z).length();
709
867
  const wallArea = wallLength * WALL_HEIGHT;
710
868
  // Create wall label
711
- const wallCanvas = this.createLabelCanvas(`${i18n.t('Wall')} ${i + 1} (${i18n.t('Area')}): ${wallArea.toFixed(2)}m²`);
869
+ let wallMaterial;
870
+ if (metadata && metadata.walls[i] && metadata.walls[i].material)
871
+ wallMaterial = metadata.walls[i].material;
872
+ const wallCanvas = this.createLabelCanvas(`${i18n.t('Wall')} ${i + 1}${wallMaterial ? '_' + wallMaterial : ' (' + i18n.t('Area') + ')'}: ${wallArea.toFixed(2)}m²`);
873
+ // const wallCanvas = this.createLabelCanvas(`${i18n.t('Wall')} ${i+1}_Wood: ${wallArea.toFixed(2)}m²`)
712
874
  // Create floor edge label
713
875
  const floorCanvas = this.createLabelCanvas(`${i18n.t('Wall')} ${i + 1} (${i18n.t('Edge')}): ${wallLength.toFixed(2)}m`);
714
876
  const midPoint = {
@@ -817,7 +979,8 @@ export class BufferGeometry {
817
979
  width: wallDimensions.width,
818
980
  perimeter: wallDimensions.perimeter,
819
981
  wall_height: WALL_HEIGHT,
820
- edges: wallEdges
982
+ edges: wallEdges,
983
+ material: wallMaterial ? wallMaterial : undefined
821
984
  };
822
985
  if (polyData && polyData.walls[i].uuid) {
823
986
  wallData.uuid = polyData.walls[i].uuid;
@@ -828,6 +991,117 @@ export class BufferGeometry {
828
991
  return [floorDataArray, wallDataArray];
829
992
  }
830
993
  ;
994
+ renderWindows(windows) {
995
+ const THREE = this.context.three;
996
+ log.info("renderWindows ", windows);
997
+ if (windows && windows.length > 0) {
998
+ for (let i = 0; i < windows.length; i++) {
999
+ const path3D = windows[i].path;
1000
+ if (!path3D || path3D.length < 3) {
1001
+ continue;
1002
+ }
1003
+ const p0 = new THREE.Vector3(path3D[0].x, path3D[0].y, path3D[0].z);
1004
+ const p1 = new THREE.Vector3(path3D[1].x, path3D[1].y, path3D[1].z);
1005
+ const p2 = new THREE.Vector3(path3D[2].x, path3D[2].y, path3D[2].z);
1006
+ const v1 = new THREE.Vector3().subVectors(p1, p0);
1007
+ const v2 = new THREE.Vector3().subVectors(p2, p0);
1008
+ const normal = new THREE.Vector3().crossVectors(v1, v2);
1009
+ const normalLen = normal.length();
1010
+ if (normalLen === 0) {
1011
+ continue;
1012
+ }
1013
+ normal.divideScalar(normalLen);
1014
+ const uAxis = v1.clone().normalize();
1015
+ const vAxis = new THREE.Vector3().crossVectors(normal, uAxis).normalize();
1016
+ const shapePoints2D = [];
1017
+ for (let j = 0; j < path3D.length; j++) {
1018
+ const pj = new THREE.Vector3(path3D[j].x, path3D[j].y, path3D[j].z);
1019
+ const rel = new THREE.Vector3().subVectors(pj, p0);
1020
+ const x = rel.dot(uAxis);
1021
+ const y = rel.dot(vAxis);
1022
+ shapePoints2D.push(new THREE.Vector2(x, y));
1023
+ }
1024
+ const windowShape = new THREE.Shape();
1025
+ windowShape.moveTo(shapePoints2D[0].x, shapePoints2D[0].y);
1026
+ for (let j = 1; j < shapePoints2D.length; j++) {
1027
+ windowShape.lineTo(shapePoints2D[j].x, shapePoints2D[j].y);
1028
+ }
1029
+ windowShape.lineTo(shapePoints2D[0].x, shapePoints2D[0].y);
1030
+ const windowGeometry = new THREE.ShapeGeometry(windowShape);
1031
+ const basisMatrix = new THREE.Matrix4().makeBasis(uAxis, vAxis, normal);
1032
+ const translation = new THREE.Matrix4().makeTranslation(p0.x, p0.y, p0.z);
1033
+ const worldMatrix = new THREE.Matrix4().multiplyMatrices(translation, basisMatrix);
1034
+ // Apply a small offset along the wall normal to avoid z-fighting with coplanar planes
1035
+ const offset = -0.05;
1036
+ const offsetMatrix = new THREE.Matrix4().makeTranslation(normal.x * offset, normal.y * offset, normal.z * offset);
1037
+ const finalMatrix = new THREE.Matrix4().multiplyMatrices(offsetMatrix, worldMatrix);
1038
+ windowGeometry.applyMatrix4(finalMatrix);
1039
+ windowGeometry.computeVertexNormals();
1040
+ const windowMesh = new THREE.Mesh(windowGeometry, new THREE.MeshBasicMaterial({
1041
+ color: this.inputs.windowColor,
1042
+ side: THREE.DoubleSide,
1043
+ opacity: 1,
1044
+ transparent: true,
1045
+ depthWrite: false
1046
+ }));
1047
+ windowMesh.name = windows[i].uuid ? `${windows[i].uuid}_window` : `window-${i}`;
1048
+ // Add window center label if material is defined
1049
+ if (windows[i].material) {
1050
+ try {
1051
+ let twiceSignedArea = 0;
1052
+ let centroidU = 0;
1053
+ let centroidV = 0;
1054
+ for (let j = 0; j < shapePoints2D.length; j++) {
1055
+ const p1 = shapePoints2D[j];
1056
+ const p2 = shapePoints2D[(j + 1) % shapePoints2D.length];
1057
+ const cross = p1.x * p2.y - p2.x * p1.y;
1058
+ twiceSignedArea += cross;
1059
+ centroidU += (p1.x + p2.x) * cross;
1060
+ centroidV += (p1.y + p2.y) * cross;
1061
+ }
1062
+ let centerU;
1063
+ let centerV;
1064
+ if (Math.abs(twiceSignedArea) > 1e-8) {
1065
+ const polygonArea = twiceSignedArea * 0.5;
1066
+ centerU = centroidU / (6 * polygonArea);
1067
+ centerV = centroidV / (6 * polygonArea);
1068
+ }
1069
+ else {
1070
+ const sum = shapePoints2D.reduce((acc, p) => ({ x: acc.x + p.x, y: acc.y + p.y }), { x: 0, y: 0 });
1071
+ centerU = sum.x / shapePoints2D.length;
1072
+ centerV = sum.y / shapePoints2D.length;
1073
+ }
1074
+ // Create window label canvas
1075
+ const windowLabel = windows[i].name ? `${windows[i].name}_${windows[i].material}` : `Window ${i}_${windows[i].material}`;
1076
+ const windowCanvas = this.createLabelCanvas(windowLabel);
1077
+ const windowArea = Math.abs(twiceSignedArea * 0.5);
1078
+ const labelWidth = Math.max(0.5, Math.min(2, Math.sqrt(Math.max(0.0001, windowArea))));
1079
+ const labelHeight = labelWidth / 4;
1080
+ const windowLabelGeometry = new THREE.PlaneGeometry(labelWidth, labelHeight);
1081
+ const windowLabelMaterial = new THREE.MeshBasicMaterial({
1082
+ map: new THREE.CanvasTexture(windowCanvas),
1083
+ transparent: true,
1084
+ side: THREE.DoubleSide,
1085
+ });
1086
+ const windowCenterLabel = new THREE.Mesh(windowLabelGeometry, windowLabelMaterial);
1087
+ const center3D = new THREE.Vector3()
1088
+ .addScaledVector(uAxis, centerU)
1089
+ .addScaledVector(vAxis, centerV)
1090
+ .add(p0);
1091
+ const labelOffset = 0.01;
1092
+ windowCenterLabel.position.copy(center3D).addScaledVector(normal.clone().negate(), labelOffset);
1093
+ windowCenterLabel.lookAt(windowCenterLabel.position.clone().add(normal.clone().negate()));
1094
+ windowCenterLabel.name = windows[i].uuid ? `${windows[i].uuid}_windowLabel` : `windowLabel-${i}`;
1095
+ this.groupedMesh.add(windowCenterLabel);
1096
+ }
1097
+ catch (e) {
1098
+ log.error('Error creating window center label', e);
1099
+ }
1100
+ }
1101
+ this.groupedMesh.add(windowMesh);
1102
+ }
1103
+ }
1104
+ }
831
1105
  }
832
1106
  export const bufferGeometryType = 'bufferGeometry';
833
1107
  export const bufferGeometry = function (mpSdk) {
@@ -1,10 +1,11 @@
1
1
  export declare class PolygonCalculator {
2
2
  /**
3
- * Calculates both area and perimeter of a polygon floor from 3D coordinates
3
+ * Calculates both area and perimeter of a polygon from 3D coordinates
4
4
  * @param {Array<{x: number, y: number, z: number}>} coordinates - Array of 3D points
5
+ * @param {boolean} useVerticalPlane - If true, uses XY plane for vertical polygons. If false, uses XZ plane for horizontal polygons
5
6
  * @returns {{area: number, perimeter: number}} Object containing area and perimeter
6
7
  */
7
- static calculatePolygonProperties(coordinates: any): {
8
+ static calculatePolygonProperties(coordinates: any, useVerticalPlane?: boolean): {
8
9
  area: number;
9
10
  perimeter: number;
10
11
  };
@@ -49,11 +49,12 @@
49
49
  // }
50
50
  export class PolygonCalculator {
51
51
  /**
52
- * Calculates both area and perimeter of a polygon floor from 3D coordinates
52
+ * Calculates both area and perimeter of a polygon from 3D coordinates
53
53
  * @param {Array<{x: number, y: number, z: number}>} coordinates - Array of 3D points
54
+ * @param {boolean} useVerticalPlane - If true, uses XY plane for vertical polygons. If false, uses XZ plane for horizontal polygons
54
55
  * @returns {{area: number, perimeter: number}} Object containing area and perimeter
55
56
  */
56
- static calculatePolygonProperties(coordinates) {
57
+ static calculatePolygonProperties(coordinates, useVerticalPlane = false) {
57
58
  if (!Array.isArray(coordinates) || coordinates.length < 3) {
58
59
  throw new Error('At least 3 coordinates are required to form a polygon');
59
60
  }
@@ -62,19 +63,32 @@ export class PolygonCalculator {
62
63
  for (let i = 0; i < coordinates.length; i++) {
63
64
  const point1 = coordinates[i];
64
65
  const point2 = coordinates[(i + 1) % coordinates.length];
65
- // Calculate distance between two points in XZ plane (floor plane)
66
- const distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) +
67
- Math.pow(point2.z - point1.z, 2) // Using Z instead of Y for floor plane
68
- );
66
+ let distance;
67
+ if (useVerticalPlane) {
68
+ // Calculate distance between two points in XY plane (vertical plane for windows)
69
+ distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) +
70
+ Math.pow(point2.y - point1.y, 2));
71
+ }
72
+ else {
73
+ // Calculate distance between two points in XZ plane (floor plane)
74
+ distance = Math.sqrt(Math.pow(point2.x - point1.x, 2) +
75
+ Math.pow(point2.z - point1.z, 2));
76
+ }
69
77
  perimeter += distance;
70
78
  }
71
- // Calculate area using the Shoelace formula on XZ plane
79
+ // Calculate area using the Shoelace formula
72
80
  let area = 0;
73
81
  for (let i = 0; i < coordinates.length; i++) {
74
82
  const point1 = coordinates[i];
75
83
  const point2 = coordinates[(i + 1) % coordinates.length];
76
- // Use X and Z coordinates for area calculation since it's a floor
77
- area += (point1.x * point2.z - point2.x * point1.z);
84
+ if (useVerticalPlane) {
85
+ // Use X and Y coordinates for area calculation (vertical plane for windows)
86
+ area += (point1.x * point2.y - point2.x * point1.y);
87
+ }
88
+ else {
89
+ // Use X and Z coordinates for area calculation (horizontal plane for floors)
90
+ area += (point1.x * point2.z - point2.x * point1.z);
91
+ }
78
92
  }
79
93
  area = Math.abs(area) / 2;
80
94
  return {