@inweb/viewer-three 26.9.7 → 26.9.8

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.
@@ -26,6 +26,9 @@ import {
26
26
  import { GL_CONSTANTS } from "./GltfStructure.js";
27
27
  import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
28
28
 
29
+ //#AI-GENERATED using Gemini 2.5 Pro, Claude-4-sonnet
30
+ //#Reviewed and adapted by dborysov@opendesign.com
31
+
29
32
  export class DynamicGltfLoader {
30
33
  constructor(camera, scene, renderer) {
31
34
  this.camera = camera;
@@ -98,6 +101,18 @@ export class DynamicGltfLoader {
98
101
  this.maxConcurrentChunks = 8;
99
102
  this.activeChunkLoads = 0;
100
103
  this.chunkQueue = [];
104
+
105
+ // GPU-accelerated visibility system
106
+ this.objectIdToIndex = new Map(); // objectId -> index
107
+ this.maxObjectId = 0; // Maximum object ID
108
+ this.objectVisibility = new Float32Array(); // Array of visibility flags for each object
109
+
110
+ // Chunk loading configuration
111
+ this.maxConcurrentChunks = 6; // Default limit
112
+
113
+ // Merged geometry tracking
114
+ this.mergedObjectMap = new Map(); // objectId -> {mergedObject, startIndex, endIndex, vertexCount}
115
+ this.mergedGeometryVisibility = new Map(); // mergedObject -> visibility array
101
116
  }
102
117
 
103
118
  setVisibleEdges(visible) {
@@ -929,6 +944,59 @@ export class DynamicGltfLoader {
929
944
  this.originalObjectsToSelection.clear();
930
945
  }
931
946
 
947
+ initializeObjectVisibility() {
948
+ if (this.maxObjectId > 0) {
949
+ this.objectVisibility = new Float32Array(this.maxObjectId);
950
+ for (let i = 0; i < this.maxObjectId; i++) {
951
+ this.objectVisibility[i] = 1.0;
952
+ }
953
+ console.log(`Initialized object visibility array: ${this.maxObjectId} objects`);
954
+ }
955
+ }
956
+
957
+ createVisibilityMaterial(material) {
958
+ // Применяем шейдер напрямую к оригинальному материалу
959
+ material.onBeforeCompile = (shader) => {
960
+ shader.vertexShader = shader.vertexShader.replace(
961
+ "#include <common>",
962
+ `
963
+ #include <common>
964
+ attribute float visibility;
965
+ varying float vVisibility;
966
+ `
967
+ );
968
+
969
+ shader.fragmentShader = shader.fragmentShader.replace(
970
+ "#include <common>",
971
+ `
972
+ #include <common>
973
+ varying float vVisibility;
974
+ `
975
+ );
976
+
977
+ shader.vertexShader = shader.vertexShader.replace(
978
+ "void main() {",
979
+ `
980
+ void main() {
981
+ vVisibility = visibility;
982
+ `
983
+ );
984
+
985
+ shader.fragmentShader = shader.fragmentShader.replace(
986
+ "void main() {",
987
+ `
988
+ void main() {
989
+ if (vVisibility < 0.5) discard;
990
+ `
991
+ );
992
+ };
993
+
994
+ // Принудительная перекомпиляция материала
995
+ material.needsUpdate = true;
996
+
997
+ return material;
998
+ }
999
+
932
1000
  clear() {
933
1001
  this.chunkQueue = [];
934
1002
 
@@ -939,7 +1007,6 @@ export class DynamicGltfLoader {
939
1007
  });
940
1008
  this.structures = [];
941
1009
 
942
- // Clear all nodes and unload their objects
943
1010
  this.nodes.forEach((node) => {
944
1011
  if (node.object) {
945
1012
  if (node.object.parent) {
@@ -1025,13 +1092,11 @@ export class DynamicGltfLoader {
1025
1092
  });
1026
1093
  this.mergedPoints.clear();
1027
1094
 
1028
- // Clear all caches
1029
1095
  this.geometryCache.clear();
1030
1096
  this.materialCache.clear();
1031
1097
  this.textureCache.clear();
1032
1098
  this.loadedMaterials.clear();
1033
1099
 
1034
- // Clear all maps and sets
1035
1100
  this.nodesToLoad = [];
1036
1101
  this.handleToObjects.clear();
1037
1102
  this.originalObjects.clear();
@@ -1043,7 +1108,6 @@ export class DynamicGltfLoader {
1043
1108
  this.oldOptimizeObjects.clear();
1044
1109
  this.isolatedObjects = [];
1045
1110
 
1046
- // Reset counters and state
1047
1111
  this.totalLoadedObjects = 0;
1048
1112
  this.lastUpdateTime = 0;
1049
1113
  this.currentMemoryUsage = 0;
@@ -1051,6 +1115,10 @@ export class DynamicGltfLoader {
1051
1115
 
1052
1116
  this.abortController = new AbortController();
1053
1117
  this.updateMemoryIndicator();
1118
+
1119
+ this.objectIdToIndex.clear();
1120
+ this.maxObjectId = 0;
1121
+ this.objectVisibility = new Float32Array();
1054
1122
  }
1055
1123
 
1056
1124
  setStructureTransform(structureId, matrix) {
@@ -1224,21 +1292,52 @@ export class DynamicGltfLoader {
1224
1292
  this.originalObjectsToSelection.add(obj);
1225
1293
  }
1226
1294
  });
1295
+ this.initializeObjectVisibility();
1296
+
1297
+ console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
1227
1298
 
1228
1299
  this.dispatchEvent("update");
1229
1300
  }
1230
1301
 
1231
1302
  mergeMeshGroups(materialGroups, rootGroup) {
1232
1303
  for (const group of materialGroups) {
1304
+ if (!group.material) {
1305
+ console.warn("Skipping mesh group with null material");
1306
+ continue;
1307
+ }
1308
+
1233
1309
  try {
1234
1310
  const geometries = [];
1235
1311
  const handles = new Set();
1236
1312
  const optimizedObjects = [];
1313
+ const objectMapping = new Map();
1314
+ let currentVertexOffset = 0;
1237
1315
 
1238
1316
  for (const mesh of group.objects) {
1239
1317
  const geometry = mesh.geometry.clone();
1240
1318
  mesh.updateWorldMatrix(true, false);
1241
1319
  geometry.applyMatrix4(mesh.matrixWorld);
1320
+
1321
+ const handle = mesh.userData.handle;
1322
+ if (!this.objectIdToIndex.has(handle)) {
1323
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
1324
+ }
1325
+ const objectId = this.objectIdToIndex.get(handle);
1326
+
1327
+ const vertexCount = geometry.attributes.position.count;
1328
+ const objectIds = new Float32Array(vertexCount);
1329
+ for (let i = 0; i < vertexCount; i++) {
1330
+ objectIds[i] = objectId;
1331
+ }
1332
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
1333
+
1334
+ objectMapping.set(mesh, {
1335
+ geometry,
1336
+ startVertexIndex: currentVertexOffset,
1337
+ vertexCount: geometry.attributes.position.count,
1338
+ });
1339
+
1340
+ currentVertexOffset += geometry.attributes.position.count;
1242
1341
  geometries.push(geometry);
1243
1342
 
1244
1343
  optimizedObjects.push(mesh);
@@ -1249,16 +1348,41 @@ export class DynamicGltfLoader {
1249
1348
 
1250
1349
  if (geometries.length > 0) {
1251
1350
  const mergedGeometry = mergeGeometries(geometries);
1351
+
1352
+ // Create visibility attribute
1353
+ const totalVertices = mergedGeometry.attributes.position.count;
1354
+ const visibilityArray = new Float32Array(totalVertices);
1355
+
1356
+ // Initialize all vertices as visible (1.0)
1357
+ for (let i = 0; i < totalVertices; i++) {
1358
+ visibilityArray[i] = 1.0;
1359
+ }
1360
+
1361
+ // Add visibility attribute to geometry
1362
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
1363
+
1252
1364
  if (this.useVAO) {
1253
1365
  this.createVAO(mergedGeometry);
1254
1366
  }
1255
1367
 
1256
- const mergedMesh = new Mesh(mergedGeometry, group.material);
1368
+ // Create visibility material
1369
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
1370
+
1371
+ const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
1257
1372
  rootGroup.add(mergedMesh);
1258
1373
 
1259
1374
  this.mergedMesh.add(mergedMesh);
1260
1375
  this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
1261
1376
 
1377
+ // Store object mappings with visibility tracking
1378
+ this.mergedObjectMap.set(mergedMesh.uuid, {
1379
+ objectMapping,
1380
+ visibilityArray,
1381
+ totalVertices,
1382
+ });
1383
+
1384
+ this.mergedGeometryVisibility.set(mergedMesh, visibilityArray);
1385
+
1262
1386
  mergedObjects.push(mergedMesh);
1263
1387
 
1264
1388
  geometries.forEach((geometry) => {
@@ -1288,8 +1412,16 @@ export class DynamicGltfLoader {
1288
1412
  for (const group of materialGroups) {
1289
1413
  if (group.objects.length === 0) continue;
1290
1414
 
1415
+ if (!group.material) {
1416
+ console.warn("Skipping line group with null material");
1417
+ continue;
1418
+ }
1419
+
1291
1420
  const handles = new Set();
1292
1421
  let totalVertices = 0;
1422
+ const objectMapping = new Map();
1423
+ let currentVertexOffset = 0;
1424
+
1293
1425
  group.objects.map((line) => {
1294
1426
  handles.add(line.userData.handle);
1295
1427
  totalVertices += line.geometry.attributes.position.count;
@@ -1306,6 +1438,18 @@ export class DynamicGltfLoader {
1306
1438
  const positionAttr = geometry.attributes.position;
1307
1439
  const vertexCount = positionAttr.count;
1308
1440
 
1441
+ const handle = line.userData.handle;
1442
+ if (!this.objectIdToIndex.has(handle)) {
1443
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
1444
+ }
1445
+ // const objectId = this.objectIdToIndex.get(handle);
1446
+
1447
+ objectMapping.set(line, {
1448
+ startVertexIndex: currentVertexOffset,
1449
+ vertexCount,
1450
+ });
1451
+ currentVertexOffset += vertexCount;
1452
+
1309
1453
  line.updateWorldMatrix(true, false);
1310
1454
  const matrix = line.matrixWorld;
1311
1455
  const vector = new Vector3();
@@ -1331,7 +1475,28 @@ export class DynamicGltfLoader {
1331
1475
  geometry.computeBoundingSphere();
1332
1476
  geometry.computeBoundingBox();
1333
1477
 
1334
- const mergedLine = new LineSegments(geometry, group.material);
1478
+ const objectIds = new Float32Array(totalVertices);
1479
+ let vertexIndex = 0;
1480
+ group.objects.forEach((line) => {
1481
+ const vertexCount = line.geometry.attributes.position.count;
1482
+ const handle = line.userData.handle;
1483
+ const objectId = this.objectIdToIndex.get(handle);
1484
+
1485
+ for (let i = 0; i < vertexCount; i++) {
1486
+ objectIds[vertexIndex++] = objectId;
1487
+ }
1488
+ });
1489
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
1490
+
1491
+ const visibilityArray = new Float32Array(totalVertices);
1492
+ for (let i = 0; i < totalVertices; i++) {
1493
+ visibilityArray[i] = 1.0;
1494
+ }
1495
+ geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
1496
+
1497
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
1498
+
1499
+ const mergedLine = new LineSegments(geometry, visibilityMaterial);
1335
1500
  const mergedObjects = [mergedLine];
1336
1501
  if (this.useVAO) {
1337
1502
  this.createVAO(mergedLine);
@@ -1340,6 +1505,14 @@ export class DynamicGltfLoader {
1340
1505
  this.mergedLines.add(mergedLine);
1341
1506
  this.optimizedOriginalMap.set(mergedLine, group.objects);
1342
1507
 
1508
+ this.mergedObjectMap.set(mergedLine.uuid, {
1509
+ objectMapping,
1510
+ visibilityArray,
1511
+ totalVertices,
1512
+ });
1513
+
1514
+ this.mergedGeometryVisibility.set(mergedLine, visibilityArray);
1515
+
1343
1516
  handles.forEach((handle) => {
1344
1517
  if (this.handleToOptimizedObjects.has(handle)) {
1345
1518
  const existingObjects = this.handleToOptimizedObjects.get(handle);
@@ -1354,15 +1527,42 @@ export class DynamicGltfLoader {
1354
1527
 
1355
1528
  mergeLineSegmentGroups(materialGroups, rootGroup) {
1356
1529
  for (const group of materialGroups) {
1530
+ if (!group.material) {
1531
+ console.warn("Skipping line segment group with null material");
1532
+ continue;
1533
+ }
1534
+
1357
1535
  try {
1358
1536
  const geometries = [];
1359
1537
  const optimizedObjects = [];
1360
1538
  const handles = new Set();
1539
+ const objectMapping = new Map();
1540
+ let currentVertexOffset = 0;
1361
1541
 
1362
1542
  for (const line of group.objects) {
1363
1543
  const geometry = line.geometry.clone();
1364
1544
  line.updateWorldMatrix(true, false);
1365
1545
  geometry.applyMatrix4(line.matrixWorld);
1546
+
1547
+ const handle = line.userData.handle;
1548
+ if (!this.objectIdToIndex.has(handle)) {
1549
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
1550
+ }
1551
+ const objectId = this.objectIdToIndex.get(handle);
1552
+ const vertexCount = geometry.attributes.position.count;
1553
+ const objectIds = new Float32Array(vertexCount);
1554
+ for (let i = 0; i < vertexCount; i++) {
1555
+ objectIds[i] = objectId;
1556
+ }
1557
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
1558
+
1559
+ objectMapping.set(line, {
1560
+ geometry,
1561
+ startVertexIndex: currentVertexOffset,
1562
+ vertexCount: geometry.attributes.position.count,
1563
+ });
1564
+
1565
+ currentVertexOffset += geometry.attributes.position.count;
1366
1566
  geometries.push(geometry);
1367
1567
  optimizedObjects.push(line);
1368
1568
  handles.add(line.userData.handle);
@@ -1372,7 +1572,19 @@ export class DynamicGltfLoader {
1372
1572
 
1373
1573
  if (geometries.length > 0) {
1374
1574
  const mergedGeometry = mergeGeometries(geometries, false);
1375
- const mergedLine = new LineSegments(mergedGeometry, group.material);
1575
+
1576
+ const totalVertices = mergedGeometry.attributes.position.count;
1577
+ const visibilityArray = new Float32Array(totalVertices);
1578
+
1579
+ for (let i = 0; i < totalVertices; i++) {
1580
+ visibilityArray[i] = 1.0;
1581
+ }
1582
+
1583
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
1584
+
1585
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
1586
+
1587
+ const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
1376
1588
 
1377
1589
  if (this.useVAO) {
1378
1590
  this.createVAO(mergedLine);
@@ -1381,6 +1593,15 @@ export class DynamicGltfLoader {
1381
1593
  rootGroup.add(mergedLine);
1382
1594
  this.mergedLineSegments.add(mergedLine);
1383
1595
  this.optimizedOriginalMap.set(mergedLine, optimizedObjects);
1596
+
1597
+ this.mergedObjectMap.set(mergedLine.uuid, {
1598
+ objectMapping,
1599
+ visibilityArray,
1600
+ totalVertices,
1601
+ });
1602
+
1603
+ this.mergedGeometryVisibility.set(mergedLine, visibilityArray);
1604
+
1384
1605
  mergedObjects.push(mergedLine);
1385
1606
 
1386
1607
  geometries.forEach((geometry) => {
@@ -1408,6 +1629,11 @@ export class DynamicGltfLoader {
1408
1629
 
1409
1630
  mergePointsGroups(materialGroups, rootGroup) {
1410
1631
  for (const group of materialGroups) {
1632
+ if (!group.material) {
1633
+ console.warn("Skipping points group with null material");
1634
+ continue;
1635
+ }
1636
+
1411
1637
  try {
1412
1638
  const geometries = [];
1413
1639
  const optimizedObjects = [];
@@ -1631,116 +1857,57 @@ export class DynamicGltfLoader {
1631
1857
  this.syncHiddenObjects();
1632
1858
  }
1633
1859
 
1634
- syncHiddenObjects() {
1635
- if (this.oldOptimizeObjects.size !== 0) {
1636
- for (const obj of this.oldOptimizeObjects) {
1637
- obj.visible = true;
1638
- }
1639
- this.oldOptimizeObjects.clear();
1640
- }
1860
+ _updateVisibilityAttribute(mergedObject) {
1861
+ if (
1862
+ mergedObject.geometry &&
1863
+ mergedObject.geometry.attributes.visibility &&
1864
+ mergedObject.geometry.attributes.objectId
1865
+ ) {
1866
+ const visibilityArray = mergedObject.geometry.attributes.visibility.array;
1867
+ const objectIdArray = mergedObject.geometry.attributes.objectId.array;
1641
1868
 
1642
- if (this.newOptimizedObjects.size !== 0) {
1643
- for (const obj of this.newOptimizedObjects) {
1644
- obj.visible = false;
1645
- obj.geometry.dispose();
1646
- obj.parent.remove(obj);
1869
+ for (let i = 0; i < visibilityArray.length; i++) {
1870
+ const objectId = objectIdArray[i];
1871
+ if (objectId < this.objectVisibility.length) {
1872
+ visibilityArray[i] = this.objectVisibility[objectId];
1873
+ }
1647
1874
  }
1648
- this.newOptimizedObjects.clear();
1875
+
1876
+ mergedObject.geometry.attributes.visibility.needsUpdate = true;
1649
1877
  }
1878
+ }
1650
1879
 
1651
- if (this.hiddenHandles.size === 0) {
1880
+ syncHiddenObjects() {
1881
+ if (this.mergedObjectMap.size === 0) {
1882
+ console.log("No merged objects to sync");
1652
1883
  return;
1653
1884
  }
1654
1885
 
1655
- this.hiddenHandles.forEach((handle) => {
1656
- const objects = this.handleToOptimizedObjects.get(handle);
1657
- if (objects) {
1658
- objects.forEach((x) => this.oldOptimizeObjects.add(x));
1886
+ if (this.objectVisibility.length > 0) {
1887
+ for (let i = 0; i < this.objectVisibility.length; i++) {
1888
+ this.objectVisibility[i] = 1.0;
1659
1889
  }
1660
- });
1661
1890
 
1662
- this.oldOptimizeObjects.forEach((optimizedObject) => {
1663
- optimizedObject.visible = false;
1664
-
1665
- const originObjects = this.optimizedOriginalMap.get(optimizedObject);
1666
- const updateListToOptimize = [];
1667
- originObjects.forEach((obj) => {
1668
- if (!this.hiddenHandles.has(obj.userData.handle)) {
1669
- updateListToOptimize.push(obj);
1891
+ this.hiddenHandles.forEach((handle) => {
1892
+ const index = this.objectIdToIndex.get(handle);
1893
+ if (index !== undefined && index < this.objectVisibility.length) {
1894
+ this.objectVisibility[index] = 0.0;
1670
1895
  }
1671
1896
  });
1897
+ }
1672
1898
 
1673
- const firstObject = updateListToOptimize[0];
1674
-
1675
- if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
1676
- const geometries = updateListToOptimize.map((obj) => {
1677
- const geometry = obj.geometry.clone();
1678
- obj.updateWorldMatrix(true, false);
1679
- geometry.applyMatrix4(obj.matrixWorld);
1680
- return geometry;
1681
- });
1682
-
1683
- const newMergedGeometry = mergeGeometries(geometries);
1684
- const mergedObject =
1685
- firstObject instanceof Mesh
1686
- ? new Mesh(newMergedGeometry, optimizedObject.material)
1687
- : new LineSegments(newMergedGeometry, optimizedObject.material);
1688
-
1689
- mergedObject.visible = true;
1690
- optimizedObject.parent.add(mergedObject);
1691
- this.newOptimizedObjects.add(mergedObject);
1692
-
1693
- geometries.forEach((geometry) => {
1694
- geometry.dispose();
1695
- });
1696
- } else if (firstObject instanceof Line) {
1697
- let totalVertices = 0;
1698
- updateListToOptimize.map((line) => {
1699
- totalVertices += line.geometry.attributes.position.count;
1700
- });
1701
-
1702
- const positions = new Float32Array(totalVertices * 3);
1703
- let posOffset = 0;
1704
-
1705
- const indices = [];
1706
- let vertexOffset = 0;
1707
-
1708
- updateListToOptimize.forEach((line) => {
1709
- const geometry = line.geometry;
1710
- const positionAttr = geometry.attributes.position;
1711
- const vertexCount = positionAttr.count;
1712
-
1713
- line.updateWorldMatrix(true, false);
1714
- const matrix = line.matrixWorld;
1715
- const vector = new Vector3();
1716
-
1717
- for (let i = 0; i < vertexCount; i++) {
1718
- vector.fromBufferAttribute(positionAttr, i);
1719
- vector.applyMatrix4(matrix);
1720
- positions[posOffset++] = vector.x;
1721
- positions[posOffset++] = vector.y;
1722
- positions[posOffset++] = vector.z;
1723
- }
1724
-
1725
- for (let i = 0; i < vertexCount - 1; i++) {
1726
- indices.push(vertexOffset + i, vertexOffset + i + 1);
1727
- }
1728
-
1729
- vertexOffset += vertexCount;
1730
- });
1731
-
1732
- const geometry = new BufferGeometry();
1733
- geometry.setAttribute("position", new BufferAttribute(positions, 3));
1734
- geometry.setIndex(indices);
1735
- geometry.computeBoundingSphere();
1736
- geometry.computeBoundingBox();
1737
-
1738
- const mergedLine = new LineSegments(geometry, optimizedObject.material);
1739
- mergedLine.visible = true;
1740
- optimizedObject.parent.add(mergedLine);
1741
- this.newOptimizedObjects.add(mergedLine);
1742
- }
1743
- });
1899
+ for (const mesh of this.mergedMesh) {
1900
+ this._updateVisibilityAttribute(mesh);
1901
+ }
1902
+ for (const line of this.mergedLines) {
1903
+ this._updateVisibilityAttribute(line);
1904
+ }
1905
+ for (const lineSegment of this.mergedLineSegments) {
1906
+ this._updateVisibilityAttribute(lineSegment);
1907
+ }
1908
+ for (const point of this.mergedPoints) {
1909
+ this._updateVisibilityAttribute(point);
1910
+ }
1744
1911
  }
1745
1912
 
1746
1913
  getStructureGeometryExtent(structureId) {