architwin 1.14.8 → 1.14.9
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.
- package/lib/architwin.js +1 -1
- package/lib/atwinui/components/toolbar/i18n.js +19 -1
- package/lib/atwinui/components/toolbar/spacePartition/roomFormPane.d.ts +24 -1
- package/lib/atwinui/components/toolbar/spacePartition/roomFormPane.js +754 -211
- package/lib/atwinui/components/toolbar/spacePartition/roomTreePane.d.ts +7 -1
- package/lib/atwinui/components/toolbar/spacePartition/roomTreePane.js +104 -9
- package/lib/atwinui/events.js +36 -5
- package/lib/loaders/polydrawerLoader.d.ts +13 -3
- package/lib/loaders/polydrawerLoader.js +329 -55
- package/lib/math/geometry.d.ts +3 -2
- package/lib/math/geometry.js +23 -9
- package/lib/types.d.ts +30 -0
- package/lib/types.js +1 -0
- package/package.json +1 -1
- package/static/atwinui.css +252 -233
- package/static/utility.css +694 -278
|
@@ -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
|
-
|
|
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.
|
|
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}
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/lib/math/geometry.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export declare class PolygonCalculator {
|
|
2
2
|
/**
|
|
3
|
-
* Calculates both area and perimeter of a polygon
|
|
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
|
};
|
package/lib/math/geometry.js
CHANGED
|
@@ -49,11 +49,12 @@
|
|
|
49
49
|
// }
|
|
50
50
|
export class PolygonCalculator {
|
|
51
51
|
/**
|
|
52
|
-
* Calculates both area and perimeter of a polygon
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
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 {
|
package/lib/types.d.ts
CHANGED
|
@@ -261,6 +261,12 @@ export interface IObjectData {
|
|
|
261
261
|
node: Scene.INode;
|
|
262
262
|
type?: string;
|
|
263
263
|
iframeId?: string;
|
|
264
|
+
children?: {
|
|
265
|
+
component?: Scene.IComponent;
|
|
266
|
+
node?: Scene.INode;
|
|
267
|
+
type?: string;
|
|
268
|
+
index?: number;
|
|
269
|
+
}[];
|
|
264
270
|
}
|
|
265
271
|
export interface IObjectFilters {
|
|
266
272
|
type?: string | undefined;
|
|
@@ -452,6 +458,9 @@ export interface ComponentOptions {
|
|
|
452
458
|
floorLevel?: number | undefined;
|
|
453
459
|
lineComponentType?: string | undefined;
|
|
454
460
|
id?: number | undefined;
|
|
461
|
+
drawingMode?: string | undefined;
|
|
462
|
+
targetIndex?: number | undefined;
|
|
463
|
+
targetUUID?: string | undefined;
|
|
455
464
|
}
|
|
456
465
|
export interface VectorCoords {
|
|
457
466
|
object_position: Vector3;
|
|
@@ -725,6 +734,7 @@ export declare enum SPACE_EVENTS {
|
|
|
725
734
|
PARTITION_CLICKED = "PARTITION_CLICKED",
|
|
726
735
|
PARTITION_DISPOSED = "PARTITION_DISPOSED",
|
|
727
736
|
PARTITION_RENDERED = "PARTITION_RENDERED",
|
|
737
|
+
PARTITION_COLLIDER_CLICKED = "PARTITION_COLLIDER_CLICKED",
|
|
728
738
|
DRAW_HISTORY = "DRAW_HISTORY",
|
|
729
739
|
BASEPOINT_UPDATED = "BASEPOINT_UPDATED",
|
|
730
740
|
MINIMAP_CHANGED = "MINIMAP_CHANGED",
|
|
@@ -1084,6 +1094,7 @@ export interface FloorPolyData {
|
|
|
1084
1094
|
perimeter: number | undefined;
|
|
1085
1095
|
floor_level: number;
|
|
1086
1096
|
edges: Array<EdgePolyData>;
|
|
1097
|
+
material?: string | undefined;
|
|
1087
1098
|
}
|
|
1088
1099
|
export interface WallPolyData {
|
|
1089
1100
|
uuid?: string | undefined;
|
|
@@ -1095,11 +1106,29 @@ export interface WallPolyData {
|
|
|
1095
1106
|
is_visible?: boolean | undefined;
|
|
1096
1107
|
is_deleted?: boolean | undefined;
|
|
1097
1108
|
};
|
|
1109
|
+
material?: string | undefined;
|
|
1098
1110
|
area: number;
|
|
1099
1111
|
perimeter: number | undefined;
|
|
1100
1112
|
width: number | undefined;
|
|
1101
1113
|
wall_height: number;
|
|
1102
1114
|
edges: Array<EdgePolyData>;
|
|
1115
|
+
windows?: Array<WindowPolyData>;
|
|
1116
|
+
}
|
|
1117
|
+
export interface WindowPolyData {
|
|
1118
|
+
path: Array<Vector3>;
|
|
1119
|
+
uuid?: string | undefined;
|
|
1120
|
+
name?: string | undefined;
|
|
1121
|
+
index?: number | undefined;
|
|
1122
|
+
options?: {
|
|
1123
|
+
color?: number | undefined;
|
|
1124
|
+
opacity?: number | undefined;
|
|
1125
|
+
is_visible?: boolean | undefined;
|
|
1126
|
+
is_deleted?: boolean | undefined;
|
|
1127
|
+
};
|
|
1128
|
+
area: number;
|
|
1129
|
+
width: number | undefined;
|
|
1130
|
+
edges: Array<EdgePolyData>;
|
|
1131
|
+
material?: string | undefined;
|
|
1103
1132
|
}
|
|
1104
1133
|
export interface PolygonData {
|
|
1105
1134
|
path: Array<Vector3>;
|
|
@@ -1113,6 +1142,7 @@ export interface PolygonData {
|
|
|
1113
1142
|
wall_height: number;
|
|
1114
1143
|
totalWallArea?: number;
|
|
1115
1144
|
totalWallPerimeter?: number;
|
|
1145
|
+
material?: string | undefined;
|
|
1116
1146
|
}
|
|
1117
1147
|
export interface PolyWallVisibility {
|
|
1118
1148
|
index: number;
|
package/lib/types.js
CHANGED
|
@@ -95,6 +95,7 @@ export var SPACE_EVENTS;
|
|
|
95
95
|
SPACE_EVENTS["PARTITION_CLICKED"] = "PARTITION_CLICKED";
|
|
96
96
|
SPACE_EVENTS["PARTITION_DISPOSED"] = "PARTITION_DISPOSED";
|
|
97
97
|
SPACE_EVENTS["PARTITION_RENDERED"] = "PARTITION_RENDERED";
|
|
98
|
+
SPACE_EVENTS["PARTITION_COLLIDER_CLICKED"] = "PARTITION_COLLIDER_CLICKED";
|
|
98
99
|
SPACE_EVENTS["DRAW_HISTORY"] = "DRAW_HISTORY";
|
|
99
100
|
SPACE_EVENTS["BASEPOINT_UPDATED"] = "BASEPOINT_UPDATED";
|
|
100
101
|
SPACE_EVENTS["MINIMAP_CHANGED"] = "MINIMAP_CHANGED";
|