@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.
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { draggersRegistry, commandsRegistry, componentsRegistry, Loader, loadersRegistry, Options, CANVAS_EVENTS } from '@inweb/viewer-core';
25
25
  export * from '@inweb/viewer-core';
26
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Matrix4, Vector4, Raycaster, Controls, Clock, MathUtils, Sphere, Box3, Color, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, EdgesGeometry, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, PerspectiveCamera, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
26
+ import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Matrix4, Vector4, MathUtils, Raycaster, EdgesGeometry, Controls, Clock, Sphere, Box3, Color, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, PerspectiveCamera, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
27
27
  import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
28
28
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
29
29
  import { Wireframe } from 'three/examples/jsm/lines/Wireframe.js';
@@ -900,6 +900,8 @@ class CuttingPlaneZAxisDragger extends CuttingPlaneDragger {
900
900
  }
901
901
 
902
902
  const PRECISION = 0.01;
903
+ const DESKTOP_SNAP_DISTANCE = 10;
904
+ const MOBILE_SNAP_DISTANCE = 50;
903
905
  class MeasureLineDragger extends OrbitDragger {
904
906
  constructor(viewer) {
905
907
  super(viewer);
@@ -914,7 +916,10 @@ class MeasureLineDragger extends OrbitDragger {
914
916
  this.onPointerMove = (event) => {
915
917
  if (this.orbit.enabled && this.orbit.state !== -1)
916
918
  return;
917
- this.line.endPoint = this.snapper.getSnapPoint(event);
919
+ const snapPoint = this.snapper.getSnapPoint(event);
920
+ if (snapPoint && this.line.endPoint && snapPoint.equals(this.line.endPoint))
921
+ return;
922
+ this.line.endPoint = snapPoint;
918
923
  this.line.render();
919
924
  if (this.line.startPoint)
920
925
  this.changed = true;
@@ -943,14 +948,14 @@ class MeasureLineDragger extends OrbitDragger {
943
948
  this.overlay.render();
944
949
  };
945
950
  this.updateSnapper = () => {
946
- this.snapper.update(this.viewer.scene);
951
+ this.snapper.update(this.viewer);
947
952
  };
948
953
  this.overlay = new MeasureOverlay(viewer.camera, viewer.canvas);
949
954
  this.overlay.attach();
950
955
  this.line = new MeasureLine(this.overlay);
951
956
  this.overlay.addLine(this.line);
952
957
  this.snapper = new MeasureSnapper(viewer.camera, viewer.canvas);
953
- this.snapper.update(viewer.scene);
958
+ this.updateSnapper();
954
959
  this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown);
955
960
  this.viewer.canvas.addEventListener("pointermove", this.onPointerMove);
956
961
  this.viewer.canvas.addEventListener("pointerup", this.onPointerUp);
@@ -973,20 +978,39 @@ class MeasureLineDragger extends OrbitDragger {
973
978
  this.viewer.removeEventListener("isolate", this.updateSnapper);
974
979
  this.viewer.removeEventListener("show", this.updateSnapper);
975
980
  this.viewer.removeEventListener("showall", this.updateSnapper);
981
+ this.snapper.dispose();
976
982
  this.overlay.detach();
977
983
  this.overlay.dispose();
978
984
  super.dispose();
979
985
  }
980
986
  }
987
+ const _vertex = new Vector3();
988
+ const _start = new Vector3();
989
+ const _end = new Vector3();
990
+ const _line = new Line3();
991
+ const _center = new Vector3();
992
+ const _projection = new Vector3();
981
993
  class MeasureSnapper {
982
994
  constructor(camera, canvas) {
983
- this.objects = [];
984
995
  this.camera = camera;
985
996
  this.canvas = canvas;
997
+ this.objects = [];
986
998
  this.raycaster = new Raycaster();
999
+ this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
1000
+ this.edgesCache = new WeakMap();
987
1001
  }
988
- getSnapPoint(event) {
989
- const mouse = new Vector2(event.clientX, event.clientY);
1002
+ dispose() {
1003
+ this.objects = [];
1004
+ }
1005
+ isMobile() {
1006
+ if (typeof navigator === "undefined")
1007
+ return false;
1008
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
1009
+ }
1010
+ getMousePosition(event, target) {
1011
+ return target.set(event.clientX, event.clientY);
1012
+ }
1013
+ getPointerIntersects(mouse, objects) {
990
1014
  const rect = this.canvas.getBoundingClientRect();
991
1015
  const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
992
1016
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
@@ -994,20 +1018,86 @@ class MeasureSnapper {
994
1018
  this.raycaster.setFromCamera(coords, this.camera);
995
1019
  this.raycaster.params = {
996
1020
  Mesh: {},
997
- Line: { threshold: 0.25 },
998
- Line2: { threshold: 0.25 },
1021
+ Line: { threshold: 0.05 },
1022
+ Line2: { threshold: 0.05 },
999
1023
  LOD: {},
1000
- Points: { threshold: 0.1 },
1024
+ Points: { threshold: 0.01 },
1001
1025
  Sprite: {},
1002
1026
  };
1003
- const intersects = this.raycaster.intersectObjects(this.objects, false);
1004
- if (intersects.length === 0)
1005
- return undefined;
1006
- return intersects[0].point;
1027
+ return this.raycaster.intersectObjects(objects, false);
1007
1028
  }
1008
- update(scene) {
1009
- this.objects = [];
1010
- scene.traverseVisible((child) => this.objects.push(child));
1029
+ getDetectRadius(point) {
1030
+ const camera = this.camera;
1031
+ if (camera.isOrthographicCamera) {
1032
+ const worldHeight = camera.top - camera.bottom;
1033
+ const canvasHeight = this.canvas.height;
1034
+ const worldUnitsPerPixel = worldHeight / canvasHeight;
1035
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
1036
+ }
1037
+ if (camera.isPerspectiveCamera) {
1038
+ const distance = camera.position.distanceTo(point);
1039
+ const worldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
1040
+ const canvasHeight = this.canvas.height;
1041
+ const worldUnitsPerPixel = worldHeight / canvasHeight;
1042
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
1043
+ }
1044
+ return 0.1;
1045
+ }
1046
+ getSnapPoint(event) {
1047
+ const mouse = this.getMousePosition(event, new Vector2());
1048
+ const intersections = this.getPointerIntersects(mouse, this.objects);
1049
+ if (intersections.length === 0)
1050
+ return undefined;
1051
+ const object = intersections[0].object;
1052
+ const intersectionPoint = intersections[0].point;
1053
+ const localPoint = object.worldToLocal(intersectionPoint.clone());
1054
+ let snapPoint;
1055
+ let snapDistance = this.getDetectRadius(intersectionPoint);
1056
+ const geometry = object.geometry;
1057
+ const positions = geometry.attributes.position.array;
1058
+ for (let i = 0; i < positions.length; i += 3) {
1059
+ _vertex.set(positions[i], positions[i + 1], positions[i + 2]);
1060
+ const distance = _vertex.distanceTo(localPoint);
1061
+ if (distance < snapDistance) {
1062
+ snapDistance = distance;
1063
+ snapPoint = _vertex.clone();
1064
+ }
1065
+ }
1066
+ if (snapPoint)
1067
+ return object.localToWorld(snapPoint);
1068
+ let edges = this.edgesCache.get(geometry);
1069
+ if (!edges) {
1070
+ edges = new EdgesGeometry(geometry);
1071
+ this.edgesCache.set(geometry, edges);
1072
+ }
1073
+ const edgePositions = edges.attributes.position.array;
1074
+ for (let i = 0; i < edgePositions.length; i += 6) {
1075
+ _start.set(edgePositions[i], edgePositions[i + 1], edgePositions[i + 2]);
1076
+ _end.set(edgePositions[i + 3], edgePositions[i + 4], edgePositions[i + 5]);
1077
+ _line.set(_start, _end);
1078
+ _line.getCenter(_center);
1079
+ const centerDistance = _center.distanceTo(localPoint);
1080
+ if (centerDistance < snapDistance) {
1081
+ snapDistance = centerDistance;
1082
+ snapPoint = _center.clone();
1083
+ continue;
1084
+ }
1085
+ _line.closestPointToPoint(localPoint, true, _projection);
1086
+ const lineDistance = _projection.distanceTo(localPoint);
1087
+ if (lineDistance < snapDistance) {
1088
+ snapDistance = lineDistance;
1089
+ snapPoint = _projection.clone();
1090
+ }
1091
+ }
1092
+ if (snapPoint)
1093
+ return object.localToWorld(snapPoint);
1094
+ return intersectionPoint.clone();
1095
+ }
1096
+ update(viewer) {
1097
+ this.objects.length = 0;
1098
+ viewer.models.forEach((model) => {
1099
+ model.getVisibleObjects().forEach((object) => this.objects.push(object));
1100
+ });
1011
1101
  }
1012
1102
  }
1013
1103
  class MeasureOverlay {
@@ -1029,6 +1119,8 @@ class MeasureOverlay {
1029
1119
  this.container.style.outline = "none";
1030
1120
  this.container.style.pointerEvents = "none";
1031
1121
  this.container.style.overflow = "hidden";
1122
+ if (!this.canvas.parentElement)
1123
+ return;
1032
1124
  this.canvas.parentElement.appendChild(this.container);
1033
1125
  }
1034
1126
  dispose() {
@@ -1059,7 +1151,7 @@ class MeasureOverlay {
1059
1151
  const _middlePoint = new Vector3();
1060
1152
  class MeasureLine {
1061
1153
  constructor(overlay) {
1062
- this.id = Date.now();
1154
+ this.id = MathUtils.generateUUID();
1063
1155
  this.unit = "";
1064
1156
  this.scale = 1.0;
1065
1157
  this.size = 10.0;
@@ -1083,6 +1175,10 @@ class MeasureLine {
1083
1175
  this.elementEndPoint.remove();
1084
1176
  this.elementLine.remove();
1085
1177
  this.elementLabel.remove();
1178
+ this.elementStartPoint = undefined;
1179
+ this.elementEndPoint = undefined;
1180
+ this.elementLine = undefined;
1181
+ this.elementLabel = undefined;
1086
1182
  }
1087
1183
  render() {
1088
1184
  const projector = this.overlay.projector;
@@ -1739,7 +1835,7 @@ function zoomTo(viewer, box) {
1739
1835
  if (camera.isPerspectiveCamera) {
1740
1836
  const offset = new Vector3(0, 0, 1)
1741
1837
  .applyQuaternion(camera.quaternion)
1742
- .multiplyScalar(boxSize / Math.tan(MathUtils.DEG2RAD * camera.fov * 0.5));
1838
+ .multiplyScalar(boxSize / Math.tan(MathUtils.degToRad(camera.fov * 0.5)));
1743
1839
  camera.position.copy(offset).add(boxCenter);
1744
1840
  camera.updateMatrixWorld();
1745
1841
  }
@@ -2401,12 +2497,12 @@ class SelectionComponent {
2401
2497
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
2402
2498
  const coords = new Vector2(x, y);
2403
2499
  this.raycaster.setFromCamera(coords, this.viewer.camera);
2404
- this.raycaster.params = this.raycaster.params = {
2500
+ this.raycaster.params = {
2405
2501
  Mesh: {},
2406
- Line: { threshold: 0.25 },
2407
- Line2: { threshold: 0.25 },
2502
+ Line: { threshold: 0.05 },
2503
+ Line2: { threshold: 0.05 },
2408
2504
  LOD: {},
2409
- Points: { threshold: 0.1 },
2505
+ Points: { threshold: 0.01 },
2410
2506
  Sprite: {},
2411
2507
  };
2412
2508
  return this.raycaster.intersectObjects(objects, false);
@@ -3398,6 +3494,12 @@ class DynamicGltfLoader {
3398
3494
  this.maxConcurrentChunks = 8;
3399
3495
  this.activeChunkLoads = 0;
3400
3496
  this.chunkQueue = [];
3497
+ this.objectIdToIndex = new Map();
3498
+ this.maxObjectId = 0;
3499
+ this.objectVisibility = new Float32Array();
3500
+ this.maxConcurrentChunks = 6;
3501
+ this.mergedObjectMap = new Map();
3502
+ this.mergedGeometryVisibility = new Map();
3401
3503
  }
3402
3504
  setVisibleEdges(visible) {
3403
3505
  this.visibleEdges = visible;
@@ -4104,6 +4206,50 @@ class DynamicGltfLoader {
4104
4206
  this.originalObjects.clear();
4105
4207
  this.originalObjectsToSelection.clear();
4106
4208
  }
4209
+ initializeObjectVisibility() {
4210
+ if (this.maxObjectId > 0) {
4211
+ this.objectVisibility = new Float32Array(this.maxObjectId);
4212
+ for (let i = 0; i < this.maxObjectId; i++) {
4213
+ this.objectVisibility[i] = 1.0;
4214
+ }
4215
+ console.log(`Initialized object visibility array: ${this.maxObjectId} objects`);
4216
+ }
4217
+ }
4218
+ createVisibilityMaterial(material) {
4219
+ material.onBeforeCompile = (shader) => {
4220
+ shader.vertexShader = shader.vertexShader.replace(
4221
+ "#include <common>",
4222
+ `
4223
+ #include <common>
4224
+ attribute float visibility;
4225
+ varying float vVisibility;
4226
+ `
4227
+ );
4228
+ shader.fragmentShader = shader.fragmentShader.replace(
4229
+ "#include <common>",
4230
+ `
4231
+ #include <common>
4232
+ varying float vVisibility;
4233
+ `
4234
+ );
4235
+ shader.vertexShader = shader.vertexShader.replace(
4236
+ "void main() {",
4237
+ `
4238
+ void main() {
4239
+ vVisibility = visibility;
4240
+ `
4241
+ );
4242
+ shader.fragmentShader = shader.fragmentShader.replace(
4243
+ "void main() {",
4244
+ `
4245
+ void main() {
4246
+ if (vVisibility < 0.5) discard;
4247
+ `
4248
+ );
4249
+ };
4250
+ material.needsUpdate = true;
4251
+ return material;
4252
+ }
4107
4253
  clear() {
4108
4254
  this.chunkQueue = [];
4109
4255
  this.structures.forEach((structure) => {
@@ -4209,6 +4355,9 @@ class DynamicGltfLoader {
4209
4355
  this.loadedGeometrySize = 0;
4210
4356
  this.abortController = new AbortController();
4211
4357
  this.updateMemoryIndicator();
4358
+ this.objectIdToIndex.clear();
4359
+ this.maxObjectId = 0;
4360
+ this.objectVisibility = new Float32Array();
4212
4361
  }
4213
4362
  setStructureTransform(structureId, matrix) {
4214
4363
  const rootGroup = this.structureRoots.get(structureId);
@@ -4358,18 +4507,43 @@ class DynamicGltfLoader {
4358
4507
  this.originalObjectsToSelection.add(obj);
4359
4508
  }
4360
4509
  });
4510
+ this.initializeObjectVisibility();
4511
+ console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
4361
4512
  this.dispatchEvent("update");
4362
4513
  }
4363
4514
  mergeMeshGroups(materialGroups, rootGroup) {
4364
4515
  for (const group of materialGroups) {
4516
+ if (!group.material) {
4517
+ console.warn("Skipping mesh group with null material");
4518
+ continue;
4519
+ }
4365
4520
  try {
4366
4521
  const geometries = [];
4367
4522
  const handles = new Set();
4368
4523
  const optimizedObjects = [];
4524
+ const objectMapping = new Map();
4525
+ let currentVertexOffset = 0;
4369
4526
  for (const mesh of group.objects) {
4370
4527
  const geometry = mesh.geometry.clone();
4371
4528
  mesh.updateWorldMatrix(true, false);
4372
4529
  geometry.applyMatrix4(mesh.matrixWorld);
4530
+ const handle = mesh.userData.handle;
4531
+ if (!this.objectIdToIndex.has(handle)) {
4532
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
4533
+ }
4534
+ const objectId = this.objectIdToIndex.get(handle);
4535
+ const vertexCount = geometry.attributes.position.count;
4536
+ const objectIds = new Float32Array(vertexCount);
4537
+ for (let i = 0; i < vertexCount; i++) {
4538
+ objectIds[i] = objectId;
4539
+ }
4540
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
4541
+ objectMapping.set(mesh, {
4542
+ geometry,
4543
+ startVertexIndex: currentVertexOffset,
4544
+ vertexCount: geometry.attributes.position.count,
4545
+ });
4546
+ currentVertexOffset += geometry.attributes.position.count;
4373
4547
  geometries.push(geometry);
4374
4548
  optimizedObjects.push(mesh);
4375
4549
  handles.add(mesh.userData.handle);
@@ -4377,13 +4551,26 @@ class DynamicGltfLoader {
4377
4551
  const mergedObjects = [];
4378
4552
  if (geometries.length > 0) {
4379
4553
  const mergedGeometry = mergeGeometries(geometries);
4554
+ const totalVertices = mergedGeometry.attributes.position.count;
4555
+ const visibilityArray = new Float32Array(totalVertices);
4556
+ for (let i = 0; i < totalVertices; i++) {
4557
+ visibilityArray[i] = 1.0;
4558
+ }
4559
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4380
4560
  if (this.useVAO) {
4381
4561
  this.createVAO(mergedGeometry);
4382
4562
  }
4383
- const mergedMesh = new Mesh(mergedGeometry, group.material);
4563
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
4564
+ const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
4384
4565
  rootGroup.add(mergedMesh);
4385
4566
  this.mergedMesh.add(mergedMesh);
4386
4567
  this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
4568
+ this.mergedObjectMap.set(mergedMesh.uuid, {
4569
+ objectMapping,
4570
+ visibilityArray,
4571
+ totalVertices,
4572
+ });
4573
+ this.mergedGeometryVisibility.set(mergedMesh, visibilityArray);
4387
4574
  mergedObjects.push(mergedMesh);
4388
4575
  geometries.forEach((geometry) => {
4389
4576
  geometry.dispose();
@@ -4409,8 +4596,14 @@ class DynamicGltfLoader {
4409
4596
  mergeLineGroups(materialGroups, rootGroup) {
4410
4597
  for (const group of materialGroups) {
4411
4598
  if (group.objects.length === 0) continue;
4599
+ if (!group.material) {
4600
+ console.warn("Skipping line group with null material");
4601
+ continue;
4602
+ }
4412
4603
  const handles = new Set();
4413
4604
  let totalVertices = 0;
4605
+ const objectMapping = new Map();
4606
+ let currentVertexOffset = 0;
4414
4607
  group.objects.map((line) => {
4415
4608
  handles.add(line.userData.handle);
4416
4609
  totalVertices += line.geometry.attributes.position.count;
@@ -4423,6 +4616,15 @@ class DynamicGltfLoader {
4423
4616
  const geometry = line.geometry;
4424
4617
  const positionAttr = geometry.attributes.position;
4425
4618
  const vertexCount = positionAttr.count;
4619
+ const handle = line.userData.handle;
4620
+ if (!this.objectIdToIndex.has(handle)) {
4621
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
4622
+ }
4623
+ objectMapping.set(line, {
4624
+ startVertexIndex: currentVertexOffset,
4625
+ vertexCount,
4626
+ });
4627
+ currentVertexOffset += vertexCount;
4426
4628
  line.updateWorldMatrix(true, false);
4427
4629
  const matrix = line.matrixWorld;
4428
4630
  const vector = new Vector3();
@@ -4443,7 +4645,24 @@ class DynamicGltfLoader {
4443
4645
  geometry.setIndex(indices);
4444
4646
  geometry.computeBoundingSphere();
4445
4647
  geometry.computeBoundingBox();
4446
- const mergedLine = new LineSegments(geometry, group.material);
4648
+ const objectIds = new Float32Array(totalVertices);
4649
+ let vertexIndex = 0;
4650
+ group.objects.forEach((line) => {
4651
+ const vertexCount = line.geometry.attributes.position.count;
4652
+ const handle = line.userData.handle;
4653
+ const objectId = this.objectIdToIndex.get(handle);
4654
+ for (let i = 0; i < vertexCount; i++) {
4655
+ objectIds[vertexIndex++] = objectId;
4656
+ }
4657
+ });
4658
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
4659
+ const visibilityArray = new Float32Array(totalVertices);
4660
+ for (let i = 0; i < totalVertices; i++) {
4661
+ visibilityArray[i] = 1.0;
4662
+ }
4663
+ geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4664
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
4665
+ const mergedLine = new LineSegments(geometry, visibilityMaterial);
4447
4666
  const mergedObjects = [mergedLine];
4448
4667
  if (this.useVAO) {
4449
4668
  this.createVAO(mergedLine);
@@ -4451,6 +4670,12 @@ class DynamicGltfLoader {
4451
4670
  rootGroup.add(mergedLine);
4452
4671
  this.mergedLines.add(mergedLine);
4453
4672
  this.optimizedOriginalMap.set(mergedLine, group.objects);
4673
+ this.mergedObjectMap.set(mergedLine.uuid, {
4674
+ objectMapping,
4675
+ visibilityArray,
4676
+ totalVertices,
4677
+ });
4678
+ this.mergedGeometryVisibility.set(mergedLine, visibilityArray);
4454
4679
  handles.forEach((handle) => {
4455
4680
  if (this.handleToOptimizedObjects.has(handle)) {
4456
4681
  const existingObjects = this.handleToOptimizedObjects.get(handle);
@@ -4464,14 +4689,37 @@ class DynamicGltfLoader {
4464
4689
  }
4465
4690
  mergeLineSegmentGroups(materialGroups, rootGroup) {
4466
4691
  for (const group of materialGroups) {
4692
+ if (!group.material) {
4693
+ console.warn("Skipping line segment group with null material");
4694
+ continue;
4695
+ }
4467
4696
  try {
4468
4697
  const geometries = [];
4469
4698
  const optimizedObjects = [];
4470
4699
  const handles = new Set();
4700
+ const objectMapping = new Map();
4701
+ let currentVertexOffset = 0;
4471
4702
  for (const line of group.objects) {
4472
4703
  const geometry = line.geometry.clone();
4473
4704
  line.updateWorldMatrix(true, false);
4474
4705
  geometry.applyMatrix4(line.matrixWorld);
4706
+ const handle = line.userData.handle;
4707
+ if (!this.objectIdToIndex.has(handle)) {
4708
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
4709
+ }
4710
+ const objectId = this.objectIdToIndex.get(handle);
4711
+ const vertexCount = geometry.attributes.position.count;
4712
+ const objectIds = new Float32Array(vertexCount);
4713
+ for (let i = 0; i < vertexCount; i++) {
4714
+ objectIds[i] = objectId;
4715
+ }
4716
+ geometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
4717
+ objectMapping.set(line, {
4718
+ geometry,
4719
+ startVertexIndex: currentVertexOffset,
4720
+ vertexCount: geometry.attributes.position.count,
4721
+ });
4722
+ currentVertexOffset += geometry.attributes.position.count;
4475
4723
  geometries.push(geometry);
4476
4724
  optimizedObjects.push(line);
4477
4725
  handles.add(line.userData.handle);
@@ -4479,13 +4727,26 @@ class DynamicGltfLoader {
4479
4727
  const mergedObjects = [];
4480
4728
  if (geometries.length > 0) {
4481
4729
  const mergedGeometry = mergeGeometries(geometries, false);
4482
- const mergedLine = new LineSegments(mergedGeometry, group.material);
4730
+ const totalVertices = mergedGeometry.attributes.position.count;
4731
+ const visibilityArray = new Float32Array(totalVertices);
4732
+ for (let i = 0; i < totalVertices; i++) {
4733
+ visibilityArray[i] = 1.0;
4734
+ }
4735
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
4736
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
4737
+ const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
4483
4738
  if (this.useVAO) {
4484
4739
  this.createVAO(mergedLine);
4485
4740
  }
4486
4741
  rootGroup.add(mergedLine);
4487
4742
  this.mergedLineSegments.add(mergedLine);
4488
4743
  this.optimizedOriginalMap.set(mergedLine, optimizedObjects);
4744
+ this.mergedObjectMap.set(mergedLine.uuid, {
4745
+ objectMapping,
4746
+ visibilityArray,
4747
+ totalVertices,
4748
+ });
4749
+ this.mergedGeometryVisibility.set(mergedLine, visibilityArray);
4489
4750
  mergedObjects.push(mergedLine);
4490
4751
  geometries.forEach((geometry) => {
4491
4752
  geometry.dispose();
@@ -4510,6 +4771,10 @@ class DynamicGltfLoader {
4510
4771
  }
4511
4772
  mergePointsGroups(materialGroups, rootGroup) {
4512
4773
  for (const group of materialGroups) {
4774
+ if (!group.material) {
4775
+ console.warn("Skipping points group with null material");
4776
+ continue;
4777
+ }
4513
4778
  try {
4514
4779
  const geometries = [];
4515
4780
  const optimizedObjects = [];
@@ -4692,97 +4957,51 @@ class DynamicGltfLoader {
4692
4957
  });
4693
4958
  this.syncHiddenObjects();
4694
4959
  }
4695
- syncHiddenObjects() {
4696
- if (this.oldOptimizeObjects.size !== 0) {
4697
- for (const obj of this.oldOptimizeObjects) {
4698
- obj.visible = true;
4699
- }
4700
- this.oldOptimizeObjects.clear();
4701
- }
4702
- if (this.newOptimizedObjects.size !== 0) {
4703
- for (const obj of this.newOptimizedObjects) {
4704
- obj.visible = false;
4705
- obj.geometry.dispose();
4706
- obj.parent.remove(obj);
4960
+ _updateVisibilityAttribute(mergedObject) {
4961
+ if (
4962
+ mergedObject.geometry &&
4963
+ mergedObject.geometry.attributes.visibility &&
4964
+ mergedObject.geometry.attributes.objectId
4965
+ ) {
4966
+ const visibilityArray = mergedObject.geometry.attributes.visibility.array;
4967
+ const objectIdArray = mergedObject.geometry.attributes.objectId.array;
4968
+ for (let i = 0; i < visibilityArray.length; i++) {
4969
+ const objectId = objectIdArray[i];
4970
+ if (objectId < this.objectVisibility.length) {
4971
+ visibilityArray[i] = this.objectVisibility[objectId];
4972
+ }
4707
4973
  }
4708
- this.newOptimizedObjects.clear();
4974
+ mergedObject.geometry.attributes.visibility.needsUpdate = true;
4709
4975
  }
4710
- if (this.hiddenHandles.size === 0) {
4976
+ }
4977
+ syncHiddenObjects() {
4978
+ if (this.mergedObjectMap.size === 0) {
4979
+ console.log("No merged objects to sync");
4711
4980
  return;
4712
4981
  }
4713
- this.hiddenHandles.forEach((handle) => {
4714
- const objects = this.handleToOptimizedObjects.get(handle);
4715
- if (objects) {
4716
- objects.forEach((x) => this.oldOptimizeObjects.add(x));
4982
+ if (this.objectVisibility.length > 0) {
4983
+ for (let i = 0; i < this.objectVisibility.length; i++) {
4984
+ this.objectVisibility[i] = 1.0;
4717
4985
  }
4718
- });
4719
- this.oldOptimizeObjects.forEach((optimizedObject) => {
4720
- optimizedObject.visible = false;
4721
- const originObjects = this.optimizedOriginalMap.get(optimizedObject);
4722
- const updateListToOptimize = [];
4723
- originObjects.forEach((obj) => {
4724
- if (!this.hiddenHandles.has(obj.userData.handle)) {
4725
- updateListToOptimize.push(obj);
4986
+ this.hiddenHandles.forEach((handle) => {
4987
+ const index = this.objectIdToIndex.get(handle);
4988
+ if (index !== undefined && index < this.objectVisibility.length) {
4989
+ this.objectVisibility[index] = 0.0;
4726
4990
  }
4727
4991
  });
4728
- const firstObject = updateListToOptimize[0];
4729
- if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
4730
- const geometries = updateListToOptimize.map((obj) => {
4731
- const geometry = obj.geometry.clone();
4732
- obj.updateWorldMatrix(true, false);
4733
- geometry.applyMatrix4(obj.matrixWorld);
4734
- return geometry;
4735
- });
4736
- const newMergedGeometry = mergeGeometries(geometries);
4737
- const mergedObject =
4738
- firstObject instanceof Mesh
4739
- ? new Mesh(newMergedGeometry, optimizedObject.material)
4740
- : new LineSegments(newMergedGeometry, optimizedObject.material);
4741
- mergedObject.visible = true;
4742
- optimizedObject.parent.add(mergedObject);
4743
- this.newOptimizedObjects.add(mergedObject);
4744
- geometries.forEach((geometry) => {
4745
- geometry.dispose();
4746
- });
4747
- } else if (firstObject instanceof Line) {
4748
- let totalVertices = 0;
4749
- updateListToOptimize.map((line) => {
4750
- totalVertices += line.geometry.attributes.position.count;
4751
- });
4752
- const positions = new Float32Array(totalVertices * 3);
4753
- let posOffset = 0;
4754
- const indices = [];
4755
- let vertexOffset = 0;
4756
- updateListToOptimize.forEach((line) => {
4757
- const geometry = line.geometry;
4758
- const positionAttr = geometry.attributes.position;
4759
- const vertexCount = positionAttr.count;
4760
- line.updateWorldMatrix(true, false);
4761
- const matrix = line.matrixWorld;
4762
- const vector = new Vector3();
4763
- for (let i = 0; i < vertexCount; i++) {
4764
- vector.fromBufferAttribute(positionAttr, i);
4765
- vector.applyMatrix4(matrix);
4766
- positions[posOffset++] = vector.x;
4767
- positions[posOffset++] = vector.y;
4768
- positions[posOffset++] = vector.z;
4769
- }
4770
- for (let i = 0; i < vertexCount - 1; i++) {
4771
- indices.push(vertexOffset + i, vertexOffset + i + 1);
4772
- }
4773
- vertexOffset += vertexCount;
4774
- });
4775
- const geometry = new BufferGeometry();
4776
- geometry.setAttribute("position", new BufferAttribute(positions, 3));
4777
- geometry.setIndex(indices);
4778
- geometry.computeBoundingSphere();
4779
- geometry.computeBoundingBox();
4780
- const mergedLine = new LineSegments(geometry, optimizedObject.material);
4781
- mergedLine.visible = true;
4782
- optimizedObject.parent.add(mergedLine);
4783
- this.newOptimizedObjects.add(mergedLine);
4784
- }
4785
- });
4992
+ }
4993
+ for (const mesh of this.mergedMesh) {
4994
+ this._updateVisibilityAttribute(mesh);
4995
+ }
4996
+ for (const line of this.mergedLines) {
4997
+ this._updateVisibilityAttribute(line);
4998
+ }
4999
+ for (const lineSegment of this.mergedLineSegments) {
5000
+ this._updateVisibilityAttribute(lineSegment);
5001
+ }
5002
+ for (const point of this.mergedPoints) {
5003
+ this._updateVisibilityAttribute(point);
5004
+ }
4786
5005
  }
4787
5006
  getStructureGeometryExtent(structureId) {
4788
5007
  const extent = new Box3();