@inweb/viewer-three 26.9.6 → 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
  }
@@ -1825,6 +1921,7 @@ function regenerateAll(viewer) {
1825
1921
  }
1826
1922
 
1827
1923
  function resetView(viewer) {
1924
+ const reset = viewer.getComponent("ResetComponent");
1828
1925
  viewer.executeCommand("setActiveDragger");
1829
1926
  viewer.executeCommand("clearSlices");
1830
1927
  viewer.executeCommand("clearOverlay");
@@ -1833,7 +1930,7 @@ function resetView(viewer) {
1833
1930
  viewer.executeCommand("showAll");
1834
1931
  viewer.executeCommand("explode", 0);
1835
1932
  viewer.executeCommand("zoomToExtents", true);
1836
- viewer.executeCommand("k3DViewSW");
1933
+ reset.resetCameraPosition();
1837
1934
  viewer.emit({ type: "resetview" });
1838
1935
  }
1839
1936
 
@@ -2400,12 +2497,12 @@ class SelectionComponent {
2400
2497
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
2401
2498
  const coords = new Vector2(x, y);
2402
2499
  this.raycaster.setFromCamera(coords, this.viewer.camera);
2403
- this.raycaster.params = this.raycaster.params = {
2500
+ this.raycaster.params = {
2404
2501
  Mesh: {},
2405
- Line: { threshold: 0.25 },
2406
- Line2: { threshold: 0.25 },
2502
+ Line: { threshold: 0.05 },
2503
+ Line2: { threshold: 0.05 },
2407
2504
  LOD: {},
2408
- Points: { threshold: 0.1 },
2505
+ Points: { threshold: 0.01 },
2409
2506
  Sprite: {},
2410
2507
  };
2411
2508
  return this.raycaster.intersectObjects(objects, false);
@@ -2571,6 +2668,31 @@ class WCSHelperComponent {
2571
2668
  }
2572
2669
  }
2573
2670
 
2671
+ class ResetComponent {
2672
+ constructor(viewer) {
2673
+ this.savedCameraPosition = null;
2674
+ this.onDatabaseChunk = () => {
2675
+ this.savedCameraPosition = {
2676
+ position: this.viewer.camera.position.clone(),
2677
+ up: this.viewer.camera.up.clone(),
2678
+ direction: this.viewer.camera.getWorldDirection(new Vector3()),
2679
+ };
2680
+ };
2681
+ this.viewer = viewer;
2682
+ this.viewer.addEventListener("databasechunk", this.onDatabaseChunk);
2683
+ }
2684
+ dispose() {
2685
+ this.viewer.removeEventListener("databasechunk", this.onDatabaseChunk);
2686
+ }
2687
+ resetCameraPosition() {
2688
+ if (this.savedCameraPosition) {
2689
+ this.viewer.camera.position.copy(this.savedCameraPosition.position);
2690
+ this.viewer.camera.up.copy(this.savedCameraPosition.up);
2691
+ this.viewer.camera.lookAt(this.savedCameraPosition.position.clone().add(this.savedCameraPosition.direction));
2692
+ }
2693
+ }
2694
+ }
2695
+
2574
2696
  const components = componentsRegistry("threejs");
2575
2697
  components.registerComponent("ExtentsComponent", (viewer) => new ExtentsComponent(viewer));
2576
2698
  components.registerComponent("CameraComponent", (viewer) => new CameraComponent(viewer));
@@ -2581,6 +2703,7 @@ components.registerComponent("RenderLoopComponent", (viewer) => new RenderLoopCo
2581
2703
  components.registerComponent("HighlighterComponent", (viewer) => new HighlighterComponent(viewer));
2582
2704
  components.registerComponent("SelectionComponent", (viewer) => new SelectionComponent(viewer));
2583
2705
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
2706
+ components.registerComponent("ResetComponent", (viewer) => new ResetComponent(viewer));
2584
2707
 
2585
2708
  class GLTFLoadingManager extends LoadingManager {
2586
2709
  constructor(file, params = {}) {
@@ -3371,6 +3494,12 @@ class DynamicGltfLoader {
3371
3494
  this.maxConcurrentChunks = 8;
3372
3495
  this.activeChunkLoads = 0;
3373
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();
3374
3503
  }
3375
3504
  setVisibleEdges(visible) {
3376
3505
  this.visibleEdges = visible;
@@ -4077,6 +4206,50 @@ class DynamicGltfLoader {
4077
4206
  this.originalObjects.clear();
4078
4207
  this.originalObjectsToSelection.clear();
4079
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
+ }
4080
4253
  clear() {
4081
4254
  this.chunkQueue = [];
4082
4255
  this.structures.forEach((structure) => {
@@ -4182,6 +4355,9 @@ class DynamicGltfLoader {
4182
4355
  this.loadedGeometrySize = 0;
4183
4356
  this.abortController = new AbortController();
4184
4357
  this.updateMemoryIndicator();
4358
+ this.objectIdToIndex.clear();
4359
+ this.maxObjectId = 0;
4360
+ this.objectVisibility = new Float32Array();
4185
4361
  }
4186
4362
  setStructureTransform(structureId, matrix) {
4187
4363
  const rootGroup = this.structureRoots.get(structureId);
@@ -4331,18 +4507,43 @@ class DynamicGltfLoader {
4331
4507
  this.originalObjectsToSelection.add(obj);
4332
4508
  }
4333
4509
  });
4510
+ this.initializeObjectVisibility();
4511
+ console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
4334
4512
  this.dispatchEvent("update");
4335
4513
  }
4336
4514
  mergeMeshGroups(materialGroups, rootGroup) {
4337
4515
  for (const group of materialGroups) {
4516
+ if (!group.material) {
4517
+ console.warn("Skipping mesh group with null material");
4518
+ continue;
4519
+ }
4338
4520
  try {
4339
4521
  const geometries = [];
4340
4522
  const handles = new Set();
4341
4523
  const optimizedObjects = [];
4524
+ const objectMapping = new Map();
4525
+ let currentVertexOffset = 0;
4342
4526
  for (const mesh of group.objects) {
4343
4527
  const geometry = mesh.geometry.clone();
4344
4528
  mesh.updateWorldMatrix(true, false);
4345
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;
4346
4547
  geometries.push(geometry);
4347
4548
  optimizedObjects.push(mesh);
4348
4549
  handles.add(mesh.userData.handle);
@@ -4350,13 +4551,26 @@ class DynamicGltfLoader {
4350
4551
  const mergedObjects = [];
4351
4552
  if (geometries.length > 0) {
4352
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));
4353
4560
  if (this.useVAO) {
4354
4561
  this.createVAO(mergedGeometry);
4355
4562
  }
4356
- const mergedMesh = new Mesh(mergedGeometry, group.material);
4563
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
4564
+ const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
4357
4565
  rootGroup.add(mergedMesh);
4358
4566
  this.mergedMesh.add(mergedMesh);
4359
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);
4360
4574
  mergedObjects.push(mergedMesh);
4361
4575
  geometries.forEach((geometry) => {
4362
4576
  geometry.dispose();
@@ -4382,8 +4596,14 @@ class DynamicGltfLoader {
4382
4596
  mergeLineGroups(materialGroups, rootGroup) {
4383
4597
  for (const group of materialGroups) {
4384
4598
  if (group.objects.length === 0) continue;
4599
+ if (!group.material) {
4600
+ console.warn("Skipping line group with null material");
4601
+ continue;
4602
+ }
4385
4603
  const handles = new Set();
4386
4604
  let totalVertices = 0;
4605
+ const objectMapping = new Map();
4606
+ let currentVertexOffset = 0;
4387
4607
  group.objects.map((line) => {
4388
4608
  handles.add(line.userData.handle);
4389
4609
  totalVertices += line.geometry.attributes.position.count;
@@ -4396,6 +4616,15 @@ class DynamicGltfLoader {
4396
4616
  const geometry = line.geometry;
4397
4617
  const positionAttr = geometry.attributes.position;
4398
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;
4399
4628
  line.updateWorldMatrix(true, false);
4400
4629
  const matrix = line.matrixWorld;
4401
4630
  const vector = new Vector3();
@@ -4416,7 +4645,24 @@ class DynamicGltfLoader {
4416
4645
  geometry.setIndex(indices);
4417
4646
  geometry.computeBoundingSphere();
4418
4647
  geometry.computeBoundingBox();
4419
- 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);
4420
4666
  const mergedObjects = [mergedLine];
4421
4667
  if (this.useVAO) {
4422
4668
  this.createVAO(mergedLine);
@@ -4424,6 +4670,12 @@ class DynamicGltfLoader {
4424
4670
  rootGroup.add(mergedLine);
4425
4671
  this.mergedLines.add(mergedLine);
4426
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);
4427
4679
  handles.forEach((handle) => {
4428
4680
  if (this.handleToOptimizedObjects.has(handle)) {
4429
4681
  const existingObjects = this.handleToOptimizedObjects.get(handle);
@@ -4437,14 +4689,37 @@ class DynamicGltfLoader {
4437
4689
  }
4438
4690
  mergeLineSegmentGroups(materialGroups, rootGroup) {
4439
4691
  for (const group of materialGroups) {
4692
+ if (!group.material) {
4693
+ console.warn("Skipping line segment group with null material");
4694
+ continue;
4695
+ }
4440
4696
  try {
4441
4697
  const geometries = [];
4442
4698
  const optimizedObjects = [];
4443
4699
  const handles = new Set();
4700
+ const objectMapping = new Map();
4701
+ let currentVertexOffset = 0;
4444
4702
  for (const line of group.objects) {
4445
4703
  const geometry = line.geometry.clone();
4446
4704
  line.updateWorldMatrix(true, false);
4447
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;
4448
4723
  geometries.push(geometry);
4449
4724
  optimizedObjects.push(line);
4450
4725
  handles.add(line.userData.handle);
@@ -4452,13 +4727,26 @@ class DynamicGltfLoader {
4452
4727
  const mergedObjects = [];
4453
4728
  if (geometries.length > 0) {
4454
4729
  const mergedGeometry = mergeGeometries(geometries, false);
4455
- 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);
4456
4738
  if (this.useVAO) {
4457
4739
  this.createVAO(mergedLine);
4458
4740
  }
4459
4741
  rootGroup.add(mergedLine);
4460
4742
  this.mergedLineSegments.add(mergedLine);
4461
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);
4462
4750
  mergedObjects.push(mergedLine);
4463
4751
  geometries.forEach((geometry) => {
4464
4752
  geometry.dispose();
@@ -4483,6 +4771,10 @@ class DynamicGltfLoader {
4483
4771
  }
4484
4772
  mergePointsGroups(materialGroups, rootGroup) {
4485
4773
  for (const group of materialGroups) {
4774
+ if (!group.material) {
4775
+ console.warn("Skipping points group with null material");
4776
+ continue;
4777
+ }
4486
4778
  try {
4487
4779
  const geometries = [];
4488
4780
  const optimizedObjects = [];
@@ -4665,97 +4957,51 @@ class DynamicGltfLoader {
4665
4957
  });
4666
4958
  this.syncHiddenObjects();
4667
4959
  }
4668
- syncHiddenObjects() {
4669
- if (this.oldOptimizeObjects.size !== 0) {
4670
- for (const obj of this.oldOptimizeObjects) {
4671
- obj.visible = true;
4672
- }
4673
- this.oldOptimizeObjects.clear();
4674
- }
4675
- if (this.newOptimizedObjects.size !== 0) {
4676
- for (const obj of this.newOptimizedObjects) {
4677
- obj.visible = false;
4678
- obj.geometry.dispose();
4679
- 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
+ }
4680
4973
  }
4681
- this.newOptimizedObjects.clear();
4974
+ mergedObject.geometry.attributes.visibility.needsUpdate = true;
4682
4975
  }
4683
- if (this.hiddenHandles.size === 0) {
4976
+ }
4977
+ syncHiddenObjects() {
4978
+ if (this.mergedObjectMap.size === 0) {
4979
+ console.log("No merged objects to sync");
4684
4980
  return;
4685
4981
  }
4686
- this.hiddenHandles.forEach((handle) => {
4687
- const objects = this.handleToOptimizedObjects.get(handle);
4688
- if (objects) {
4689
- 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;
4690
4985
  }
4691
- });
4692
- this.oldOptimizeObjects.forEach((optimizedObject) => {
4693
- optimizedObject.visible = false;
4694
- const originObjects = this.optimizedOriginalMap.get(optimizedObject);
4695
- const updateListToOptimize = [];
4696
- originObjects.forEach((obj) => {
4697
- if (!this.hiddenHandles.has(obj.userData.handle)) {
4698
- 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;
4699
4990
  }
4700
4991
  });
4701
- const firstObject = updateListToOptimize[0];
4702
- if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
4703
- const geometries = updateListToOptimize.map((obj) => {
4704
- const geometry = obj.geometry.clone();
4705
- obj.updateWorldMatrix(true, false);
4706
- geometry.applyMatrix4(obj.matrixWorld);
4707
- return geometry;
4708
- });
4709
- const newMergedGeometry = mergeGeometries(geometries);
4710
- const mergedObject =
4711
- firstObject instanceof Mesh
4712
- ? new Mesh(newMergedGeometry, optimizedObject.material)
4713
- : new LineSegments(newMergedGeometry, optimizedObject.material);
4714
- mergedObject.visible = true;
4715
- optimizedObject.parent.add(mergedObject);
4716
- this.newOptimizedObjects.add(mergedObject);
4717
- geometries.forEach((geometry) => {
4718
- geometry.dispose();
4719
- });
4720
- } else if (firstObject instanceof Line) {
4721
- let totalVertices = 0;
4722
- updateListToOptimize.map((line) => {
4723
- totalVertices += line.geometry.attributes.position.count;
4724
- });
4725
- const positions = new Float32Array(totalVertices * 3);
4726
- let posOffset = 0;
4727
- const indices = [];
4728
- let vertexOffset = 0;
4729
- updateListToOptimize.forEach((line) => {
4730
- const geometry = line.geometry;
4731
- const positionAttr = geometry.attributes.position;
4732
- const vertexCount = positionAttr.count;
4733
- line.updateWorldMatrix(true, false);
4734
- const matrix = line.matrixWorld;
4735
- const vector = new Vector3();
4736
- for (let i = 0; i < vertexCount; i++) {
4737
- vector.fromBufferAttribute(positionAttr, i);
4738
- vector.applyMatrix4(matrix);
4739
- positions[posOffset++] = vector.x;
4740
- positions[posOffset++] = vector.y;
4741
- positions[posOffset++] = vector.z;
4742
- }
4743
- for (let i = 0; i < vertexCount - 1; i++) {
4744
- indices.push(vertexOffset + i, vertexOffset + i + 1);
4745
- }
4746
- vertexOffset += vertexCount;
4747
- });
4748
- const geometry = new BufferGeometry();
4749
- geometry.setAttribute("position", new BufferAttribute(positions, 3));
4750
- geometry.setIndex(indices);
4751
- geometry.computeBoundingSphere();
4752
- geometry.computeBoundingBox();
4753
- const mergedLine = new LineSegments(geometry, optimizedObject.material);
4754
- mergedLine.visible = true;
4755
- optimizedObject.parent.add(mergedLine);
4756
- this.newOptimizedObjects.add(mergedLine);
4757
- }
4758
- });
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
+ }
4759
5005
  }
4760
5006
  getStructureGeometryExtent(structureId) {
4761
5007
  const extent = new Box3();