@inweb/viewer-three 26.7.6 → 26.8.1

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.
@@ -2,7 +2,7 @@ import { draggersRegistry, commandsRegistry, componentsRegistry, Loader, loaders
2
2
 
3
3
  export * from "@inweb/viewer-core";
4
4
 
5
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Matrix4, Vector4, Raycaster, Controls, Clock, Sphere, MathUtils, Box3, Color, AmbientLight, DirectionalLight, HemisphereLight, WebGLRenderTarget, UnsignedByteType, RGBAFormat, EdgesGeometry, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, MeshStandardMaterial, FrontSide, PointsMaterial, Material, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, PerspectiveCamera, Scene, WebGLRenderer, LinearToneMapping } from "three";
5
+ import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Matrix4, Vector4, Raycaster, Controls, Clock, Sphere, MathUtils, Box3, Color, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, EdgesGeometry, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, PointsMaterial, Material, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, MeshStandardMaterial, PerspectiveCamera, Scene, WebGLRenderer, LinearToneMapping } from "three";
6
6
 
7
7
  import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
8
8
 
@@ -1676,6 +1676,7 @@ function setSelected(viewer, handles = []) {
1676
1676
  selection.clearSelection();
1677
1677
  viewer.models.forEach((model => {
1678
1678
  const objects = model.getObjectsByHandles(handles);
1679
+ model.showObjects(objects);
1679
1680
  selection.select(objects, model);
1680
1681
  }));
1681
1682
  viewer.update();
@@ -1729,12 +1730,12 @@ commands.registerCommand("clearSelected", clearSelected);
1729
1730
 
1730
1731
  commands.registerCommand("clearSlices", clearSlices);
1731
1732
 
1733
+ commands.registerCommand("collect", collect);
1734
+
1732
1735
  commands.registerCommand("createPreview", createPreview);
1733
1736
 
1734
1737
  commands.registerCommand("explode", explode);
1735
1738
 
1736
- commands.registerCommand("collect", collect);
1737
-
1738
1739
  commands.registerCommand("getDefaultViewPositions", getDefaultViewPositions);
1739
1740
 
1740
1741
  commands.registerCommand("getModels", getModels);
@@ -1910,11 +1911,12 @@ class LightComponent {
1910
1911
  if (this.viewer.extents.isEmpty()) return;
1911
1912
  const extentsCenter = this.viewer.extents.getCenter(new Vector3);
1912
1913
  const extentsSize = this.viewer.extents.getBoundingSphere(new Sphere).radius;
1913
- this.directionalLight.position.set(.5, 0, .866).multiplyScalar(extentsSize * 2).add(extentsCenter);
1914
+ const front = (new Vector3).copy(this.viewer.camera.up).cross(new Vector3(1, 0, 0)).negate();
1915
+ this.directionalLight.position.copy(this.viewer.camera.up).applyAxisAngle(front, -Math.PI * 30 / 180).multiplyScalar(extentsSize * 2).add(extentsCenter);
1914
1916
  this.directionalLight.target.position.copy(extentsCenter);
1915
- this.frontLight.position.set(0, extentsSize * 2, 0).add(extentsCenter);
1917
+ this.frontLight.position.copy(front).multiplyScalar(extentsSize * 2).add(extentsCenter);
1916
1918
  this.frontLight.target.position.copy(extentsCenter);
1917
- this.hemisphereLight.position.set(0, extentsSize * 3, 0).add(extentsCenter);
1919
+ this.hemisphereLight.position.copy(front).multiplyScalar(extentsSize * 3).add(extentsCenter);
1918
1920
  this.viewer.scene.add(this.ambientLight);
1919
1921
  this.viewer.scene.add(this.directionalLight);
1920
1922
  this.viewer.scene.add(this.frontLight);
@@ -1922,16 +1924,9 @@ class LightComponent {
1922
1924
  };
1923
1925
  this.viewer = viewer;
1924
1926
  this.ambientLight = new AmbientLight(16777215, 1);
1925
- this.viewer.scene.add(this.ambientLight);
1926
1927
  this.directionalLight = new DirectionalLight(16777215, 1);
1927
- this.directionalLight.position.set(.5, 0, .866);
1928
- this.viewer.scene.add(this.directionalLight);
1929
1928
  this.frontLight = new DirectionalLight(16777215, 1.25);
1930
- this.frontLight.position.set(0, 1, 0);
1931
- this.viewer.scene.add(this.frontLight);
1932
1929
  this.hemisphereLight = new HemisphereLight(16777215, 4473924, 1.25);
1933
- this.hemisphereLight.position.set(0, 0, 1);
1934
- this.viewer.scene.add(this.hemisphereLight);
1935
1930
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
1936
1931
  this.viewer.addEventListener("clear", this.geometryEnd);
1937
1932
  }
@@ -2050,19 +2045,25 @@ class HighlighterUtils {
2050
2045
  class HighlighterComponent {
2051
2046
  constructor(viewer) {
2052
2047
  this.geometryEnd = () => {
2053
- const {facesColor: facesColor, facesTransparancy: facesTransparancy, edgesColor: edgesColor} = this.viewer.options;
2054
- this.highlightMaterial = new MeshBasicMaterial({
2048
+ const {facesColor: facesColor, facesTransparancy: facesTransparancy, edgesColor: edgesColor, edgesOverlap: edgesOverlap, facesOverlap: facesOverlap} = this.viewer.options;
2049
+ this.highlightMaterial = new MeshPhongMaterial({
2055
2050
  color: new Color(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255),
2056
2051
  transparent: true,
2057
2052
  opacity: (255 - facesTransparancy) / 255,
2058
- depthTest: false,
2059
- depthWrite: false
2053
+ depthTest: !facesOverlap,
2054
+ depthWrite: !facesOverlap,
2055
+ specular: 2236962,
2056
+ shininess: 10,
2057
+ reflectivity: .05,
2058
+ polygonOffset: true,
2059
+ polygonOffsetFactor: 1,
2060
+ polygonOffsetUnits: 1
2060
2061
  });
2061
2062
  this.outlineMaterial = new LineMaterial({
2062
2063
  color: new Color(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255),
2063
2064
  linewidth: 1.5,
2064
- depthTest: false,
2065
- depthWrite: false,
2065
+ depthTest: !edgesOverlap,
2066
+ depthWrite: !edgesOverlap,
2066
2067
  resolution: new Vector2(window.innerWidth, window.innerHeight)
2067
2068
  });
2068
2069
  this.highlightLineMaterial = new LineBasicMaterial({
@@ -2075,18 +2076,26 @@ class HighlighterComponent {
2075
2076
  linewidth: 5,
2076
2077
  transparent: true,
2077
2078
  opacity: .8,
2078
- depthTest: true,
2079
- depthWrite: true,
2079
+ depthTest: !edgesOverlap,
2080
+ depthWrite: !edgesOverlap,
2080
2081
  resolution: new Vector2(window.innerWidth, window.innerHeight)
2081
2082
  });
2082
2083
  };
2083
2084
  this.optionsChange = () => {
2084
- const {facesColor: facesColor, facesTransparancy: facesTransparancy, edgesColor: edgesColor} = this.viewer.options;
2085
+ const {facesColor: facesColor, facesTransparancy: facesTransparancy, edgesColor: edgesColor, edgesVisibility: edgesVisibility, edgesOverlap: edgesOverlap, facesOverlap: facesOverlap} = this.viewer.options;
2085
2086
  this.highlightMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
2086
2087
  this.highlightMaterial.opacity = (255 - facesTransparancy) / 255;
2088
+ this.highlightMaterial.depthTest = !facesOverlap;
2089
+ this.highlightMaterial.depthWrite = !facesOverlap;
2087
2090
  this.outlineMaterial.color.setRGB(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255);
2091
+ this.outlineMaterial.depthTest = !edgesOverlap;
2092
+ this.outlineMaterial.depthWrite = !edgesOverlap;
2088
2093
  this.highlightLineMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
2089
2094
  this.highlightLineGlowMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
2095
+ this.viewer.selected.forEach((selected => {
2096
+ const wireframe = selected.userData.highlightWireframe;
2097
+ if (wireframe) wireframe.visible = edgesVisibility;
2098
+ }));
2090
2099
  this.viewer.update();
2091
2100
  };
2092
2101
  this.viewer = viewer;
@@ -2111,6 +2120,7 @@ class HighlighterComponent {
2111
2120
  this.viewer.removeEventListener("resize", this.viewerResize);
2112
2121
  }
2113
2122
  highlight(objects) {
2123
+ const {edgesVisibility: edgesVisibility} = this.viewer.options;
2114
2124
  if (!Array.isArray(objects)) objects = [ objects ];
2115
2125
  if (!objects.length) return;
2116
2126
  objects.forEach((object => {
@@ -2123,20 +2133,22 @@ class HighlighterComponent {
2123
2133
  wireframe.position.copy(object.position);
2124
2134
  wireframe.rotation.copy(object.rotation);
2125
2135
  wireframe.scale.copy(object.scale);
2136
+ wireframe.visible = edgesVisibility;
2126
2137
  object.parent.add(wireframe);
2127
- object.userData.highlightwireframe = wireframe;
2138
+ object.userData.highlightWireframe = wireframe;
2128
2139
  object.userData.originalMaterial = object.material;
2129
2140
  object.material = this.highlightLineMaterial;
2130
2141
  object.isHighlighted = true;
2131
2142
  } else if (object.isMesh) {
2132
- const edgesGeometry = new EdgesGeometry(object.geometry, 30);
2143
+ const edgesGeometry = new EdgesGeometry(object.geometry, 60);
2133
2144
  const lineGeometry = (new LineSegmentsGeometry).fromEdgesGeometry(edgesGeometry);
2134
2145
  const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
2135
2146
  wireframe.position.copy(object.position);
2136
2147
  wireframe.rotation.copy(object.rotation);
2137
2148
  wireframe.scale.copy(object.scale);
2149
+ wireframe.visible = edgesVisibility;
2138
2150
  object.parent.add(wireframe);
2139
- object.userData.highlightwireframe = wireframe;
2151
+ object.userData.highlightWireframe = wireframe;
2140
2152
  object.userData.originalMaterial = object.material;
2141
2153
  object.material = this.highlightMaterial;
2142
2154
  object.isHighlighted = true;
@@ -2150,9 +2162,9 @@ class HighlighterComponent {
2150
2162
  if (!object.isHighlighted) return;
2151
2163
  object.isHighlighted = false;
2152
2164
  object.material = object.userData.originalMaterial;
2153
- object.userData.highlightwireframe.removeFromParent();
2165
+ object.userData.highlightWireframe.removeFromParent();
2154
2166
  delete object.userData.originalMaterial;
2155
- delete object.userData.highlightwireframe;
2167
+ delete object.userData.highlightWireframe;
2156
2168
  }));
2157
2169
  }
2158
2170
  viewerResize(event) {
@@ -2176,10 +2188,10 @@ class SelectionComponent {
2176
2188
  this.viewer.models.forEach((model => {
2177
2189
  const objects = model.getVisibleObjects();
2178
2190
  const intersects = this.getPointerIntersects(upPosition, objects);
2179
- intersections.push(...intersects.map((x => ({
2180
- ...x,
2191
+ if (intersects.length > 0) intersections.push({
2192
+ ...intersects[0],
2181
2193
  model: model
2182
- }))));
2194
+ });
2183
2195
  }));
2184
2196
  intersections = intersections.sort(((a, b) => a.distance - b.distance));
2185
2197
  if (!event.shiftKey) this.clearSelection();
@@ -2249,7 +2261,6 @@ class SelectionComponent {
2249
2261
  }
2250
2262
  if (!Array.isArray(objects)) objects = [ objects ];
2251
2263
  if (!objects.length) return;
2252
- model.showObjects(objects);
2253
2264
  model.showOriginalObjects(objects);
2254
2265
  this.highlighter.highlight(objects);
2255
2266
  objects.forEach((object => this.viewer.selected.push(object)));
@@ -2633,8 +2644,8 @@ class DynamicModelImpl extends ModelImpl {
2633
2644
  return objects;
2634
2645
  }
2635
2646
  hideObjects(objects) {
2636
- this.getOwnObjects(objects).map((object => object.userData.handle)).forEach((handle => this.gltfLoader.hiddenHandles.add(handle)));
2637
- this.gltfLoader.syncHiddenObjects();
2647
+ const handles = this.getHandlesByObjects(objects);
2648
+ this.gltfLoader.hideObjects(handles);
2638
2649
  return this;
2639
2650
  }
2640
2651
  isolateObjects(objects) {
@@ -2643,21 +2654,20 @@ class DynamicModelImpl extends ModelImpl {
2643
2654
  return this;
2644
2655
  }
2645
2656
  showObjects(objects) {
2646
- this.getOwnObjects(objects).map((object => object.userData.handle)).forEach((handle => this.gltfLoader.hiddenHandles.delete(handle)));
2647
- this.gltfLoader.syncHiddenObjects();
2657
+ const handles = this.getHandlesByObjects(objects);
2658
+ this.gltfLoader.showObjects(handles);
2648
2659
  return this;
2649
2660
  }
2650
2661
  showAllObjects() {
2651
- this.gltfLoader.hiddenHandles.clear();
2652
- this.gltfLoader.syncHiddenObjects();
2662
+ this.gltfLoader.showAllHiddenObjects();
2653
2663
  return this;
2654
2664
  }
2655
2665
  showOriginalObjects(objects) {
2656
- this.getOwnObjects(objects).forEach((object => object.visible = true));
2666
+ this.gltfLoader.showOriginalObjects(objects);
2657
2667
  return this;
2658
2668
  }
2659
2669
  hideOriginalObjects(objects) {
2660
- this.getOwnObjects(objects).forEach((object => object.visible = false));
2670
+ this.gltfLoader.hideOriginalObjects(objects);
2661
2671
  return this;
2662
2672
  }
2663
2673
  }
@@ -2681,6 +2691,10 @@ const GL_CONSTANTS = {
2681
2691
  TRIANGLE_FAN: 6
2682
2692
  };
2683
2693
 
2694
+ const MAX_GAP = 128 * 1024;
2695
+
2696
+ const MAX_CHUNK = 30 * 1024 * 1024;
2697
+
2684
2698
  class GltfStructure {
2685
2699
  constructor(id) {
2686
2700
  this.id = `${id}`;
@@ -2718,42 +2732,99 @@ class GltfStructure {
2718
2732
  return this.json;
2719
2733
  }
2720
2734
  scheduleRequest(request) {
2721
- this.pendingRequests.push(request);
2722
- if (this.batchTimeout) {
2723
- clearTimeout(this.batchTimeout);
2724
- }
2725
- this.batchTimeout = setTimeout((() => this.processBatch()), this.batchDelay);
2726
2735
  return new Promise(((resolve, reject) => {
2727
- request.resolve = resolve;
2728
- request.reject = reject;
2736
+ this.pendingRequests.push({
2737
+ ...request,
2738
+ _resolve: resolve,
2739
+ _reject: reject
2740
+ });
2729
2741
  }));
2730
2742
  }
2731
- async processBatch() {
2732
- if (this.pendingRequests.length === 0) return;
2733
- const currentBatch = [ ...this.pendingRequests ];
2743
+ async flushBufferRequests() {
2744
+ if (!this.pendingRequests || this.pendingRequests.length === 0) return;
2745
+ const requests = [ ...this.pendingRequests ];
2734
2746
  this.pendingRequests = [];
2735
- if (this.batchTimeout) {
2736
- clearTimeout(this.batchTimeout);
2737
- this.batchTimeout = null;
2738
- }
2739
- try {
2740
- for (let i = 0; i < currentBatch.length; i += this.maxRangesPerRequest) {
2741
- const batchRequests = currentBatch.slice(i, i + this.maxRangesPerRequest);
2742
- const buffer = await this.loadController.loadBinaryData(batchRequests);
2743
- let currentOffset = 0;
2744
- batchRequests.forEach((request => {
2745
- const view = this.createTypedArray(buffer, currentOffset, request.length, request.componentType);
2746
- request.resolve(view);
2747
- currentOffset += request.length;
2748
- }));
2747
+ requests.sort(((a, b) => a.offset - b.offset));
2748
+ const mergedRanges = [];
2749
+ let current = {
2750
+ start: requests[0].offset,
2751
+ end: requests[0].offset + requests[0].length,
2752
+ requests: [ requests[0] ]
2753
+ };
2754
+ for (let i = 1; i < requests.length; i++) {
2755
+ const req = requests[i];
2756
+ const gap = req.offset - current.end;
2757
+ const newEnd = Math.max(current.end, req.offset + req.length);
2758
+ const projectedSize = newEnd - current.start;
2759
+ if (gap <= MAX_GAP && projectedSize <= MAX_CHUNK) {
2760
+ current.end = newEnd;
2761
+ current.requests.push(req);
2762
+ } else {
2763
+ mergedRanges.push(current);
2764
+ current = {
2765
+ start: req.offset,
2766
+ end: req.offset + req.length,
2767
+ requests: [ req ]
2768
+ };
2749
2769
  }
2750
- } catch (error) {
2751
- console.error("Error processing batch:", error);
2752
- currentBatch.forEach((request => request.reject(error)));
2753
2770
  }
2754
- if (this.pendingRequests.length > 0) {
2755
- this.batchTimeout = setTimeout((() => this.processBatch()), this.batchDelay);
2771
+ mergedRanges.push(current);
2772
+ const finalRanges = [];
2773
+ for (const range of mergedRanges) {
2774
+ let {start: start, end: end, requests: requests} = range;
2775
+ while (end - start > MAX_CHUNK) {
2776
+ let splitIdx = 0;
2777
+ for (let i = 0; i < requests.length; i++) {
2778
+ if (requests[i].offset + requests[i].length - start > MAX_CHUNK) {
2779
+ break;
2780
+ }
2781
+ splitIdx = i;
2782
+ }
2783
+ const chunkRequests = requests.slice(0, splitIdx + 1);
2784
+ const chunkEnd = chunkRequests[chunkRequests.length - 1].offset + chunkRequests[chunkRequests.length - 1].length;
2785
+ finalRanges.push({
2786
+ start: start,
2787
+ end: chunkEnd,
2788
+ requests: chunkRequests
2789
+ });
2790
+ requests = requests.slice(splitIdx + 1);
2791
+ if (requests.length > 0) {
2792
+ start = requests[0].offset;
2793
+ end = requests[0].offset + requests[0].length;
2794
+ for (let i = 1; i < requests.length; i++) {
2795
+ end = Math.max(end, requests[i].offset + requests[i].length);
2796
+ }
2797
+ }
2798
+ }
2799
+ if (requests.length > 0) {
2800
+ finalRanges.push({
2801
+ start: start,
2802
+ end: end,
2803
+ requests: requests
2804
+ });
2805
+ }
2756
2806
  }
2807
+ const promises = finalRanges.map((async range => {
2808
+ const length = range.end - range.start;
2809
+ const buffer = await this.loadController.loadBinaryData([ {
2810
+ offset: range.start,
2811
+ length: length
2812
+ } ]);
2813
+ for (const req of range.requests) {
2814
+ const relOffset = req.offset - range.start;
2815
+ try {
2816
+ req._resolve({
2817
+ buffer: buffer,
2818
+ relOffset: relOffset,
2819
+ length: req.length
2820
+ });
2821
+ } catch (e) {
2822
+ req._reject(e);
2823
+ }
2824
+ }
2825
+ }));
2826
+ await Promise.all(promises);
2827
+ this.pendingRequests = [];
2757
2828
  }
2758
2829
  getBufferView(byteOffset, byteLength, componentType) {
2759
2830
  return this.scheduleRequest({
@@ -2912,62 +2983,48 @@ class GltfStructure {
2912
2983
  }
2913
2984
  await Promise.all(texturePromises);
2914
2985
  }
2915
- loadMaterials() {
2986
+ async loadMaterials() {
2916
2987
  if (!this.json.materials) return this.materials;
2917
2988
  for (let i = 0; i < this.json.materials.length; i++) {
2918
2989
  const materialDef = this.json.materials[i];
2919
- const material = this.createMaterial(materialDef);
2990
+ const material = await this.createMaterial(materialDef);
2991
+ material.name = materialDef.name;
2920
2992
  this.materials.set(i, material);
2921
2993
  }
2922
2994
  return this.materials;
2923
2995
  }
2924
2996
  createMaterial(materialDef) {
2925
- const material = new MeshStandardMaterial;
2997
+ const params = {};
2926
2998
  if (materialDef.pbrMetallicRoughness) {
2927
2999
  const pbr = materialDef.pbrMetallicRoughness;
2928
3000
  if (pbr.baseColorFactor) {
2929
- material.color.fromArray(pbr.baseColorFactor);
2930
- material.opacity = pbr.baseColorFactor[3];
3001
+ params.color = (new Color).fromArray(pbr.baseColorFactor);
3002
+ params.opacity = pbr.baseColorFactor[3];
3003
+ if (params.opacity < 1) params.transparent = true;
2931
3004
  }
2932
3005
  if (pbr.baseColorTexture) {
2933
- material.map = this.textureCache.get(pbr.baseColorTexture.index);
2934
- }
2935
- if (pbr.metallicFactor !== undefined) {
2936
- material.metalness = pbr.metallicFactor;
2937
- }
2938
- if (pbr.roughnessFactor !== undefined) {
2939
- material.roughness = pbr.roughnessFactor;
2940
- }
2941
- if (pbr.metallicRoughnessTexture) {
2942
- material.metalnessMap = this.textureCache.get(pbr.metallicRoughnessTexture.index);
2943
- material.roughnessMap = material.metalnessMap;
2944
- }
2945
- }
2946
- if (materialDef.normalTexture) {
2947
- material.normalMap = this.textureCache.get(materialDef.normalTexture.index);
2948
- if (materialDef.normalTexture.scale !== undefined) {
2949
- material.normalScale.set(materialDef.normalTexture.scale, materialDef.normalTexture.scale);
3006
+ params.map = this.textureCache.get(pbr.baseColorTexture.index);
2950
3007
  }
2951
3008
  }
3009
+ params.specular = 2236962;
3010
+ params.shininess = 10;
3011
+ params.reflectivity = .05;
3012
+ params.polygonOffset = true;
3013
+ params.polygonOffsetFactor = 1;
3014
+ params.polygonOffsetUnits = 1;
2952
3015
  if (materialDef.emissiveFactor) {
2953
- material.emissive.fromArray(materialDef.emissiveFactor);
2954
- }
2955
- if (materialDef.emissiveTexture) {
2956
- material.emissiveMap = this.textureCache.get(materialDef.emissiveTexture.index);
3016
+ params.emissive = (new Color).fromArray(materialDef.emissiveFactor);
2957
3017
  }
2958
- if (materialDef.occlusionTexture) {
2959
- material.aoMap = this.textureCache.get(materialDef.occlusionTexture.index);
2960
- if (materialDef.occlusionTexture.strength !== undefined) {
2961
- material.aoMapIntensity = materialDef.occlusionTexture.strength;
2962
- }
3018
+ if (materialDef.normalTexture) {
3019
+ params.normalMap = this.textureCache.get(materialDef.normalTexture.index);
2963
3020
  }
2964
3021
  if (materialDef.alphaMode === "BLEND") {
2965
- material.transparent = true;
2966
- } else if (materialDef.alphaMode === "MASK") {
2967
- material.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : .5;
3022
+ params.transparent = true;
3023
+ }
3024
+ if (materialDef.doubleSided) {
3025
+ params.side = DoubleSide;
2968
3026
  }
2969
- material.side = materialDef.doubleSided ? DoubleSide : FrontSide;
2970
- material.name = materialDef.name;
3027
+ const material = new MeshPhongMaterial(params);
2971
3028
  return material;
2972
3029
  }
2973
3030
  disposeMaterials() {
@@ -3212,12 +3269,17 @@ class DynamicGltfLoader {
3212
3269
  this.mergedLineSegments = new Set;
3213
3270
  this.mergedPoints = new Set;
3214
3271
  this.isolatedObjects = [];
3215
- this.useVAO = !!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext;
3272
+ //!!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext
3273
+ this.useVAO = false;
3274
+ this.visibleEdges = true;
3216
3275
  this.handleToOptimizedObjects = new Map;
3217
3276
  this.hiddenHandles = new Set;
3218
3277
  this.newOptimizedObjects = new Set;
3219
3278
  this.oldOptimizeObjects = new Set;
3220
3279
  }
3280
+ setVisibleEdges(visible) {
3281
+ this.visibleEdges = visible;
3282
+ }
3221
3283
  getAvailableMemory() {
3222
3284
  let memoryLimit = 6 * 1024 * 1024 * 1024;
3223
3285
  try {
@@ -3235,7 +3297,7 @@ class DynamicGltfLoader {
3235
3297
  } catch (error) {
3236
3298
  console.warn("Error detecting available memory:", error);
3237
3299
  }
3238
- return memoryLimit;
3300
+ return memoryLimit / 3;
3239
3301
  }
3240
3302
  getAbortController() {
3241
3303
  return this.abortController;
@@ -3316,35 +3378,132 @@ class DynamicGltfLoader {
3316
3378
  this.updateMemoryIndicator();
3317
3379
  console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
3318
3380
  }
3319
- async loadNode(nodeId) {
3381
+ async loadNode(nodeId, onLoadFinishCb) {
3320
3382
  const node = this.nodes.get(nodeId);
3321
3383
  if (!node || node.loaded || node.loading) return;
3322
3384
  node.loading = true;
3323
3385
  const meshDef = node.structure.getJson().meshes[node.meshIndex];
3324
3386
  try {
3325
- for (const primitive of meshDef.primitives) {
3326
- const positionAccessor = primitive.attributes.POSITION;
3387
+ const bufferRequests = [];
3388
+ const primitiveReqMap = new Map;
3389
+ for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
3390
+ const primitive = meshDef.primitives[primIdx];
3391
+ const reqs = [];
3392
+ if (primitive.attributes.POSITION !== undefined) {
3393
+ const accessorIndex = primitive.attributes.POSITION;
3394
+ const accessor = node.structure.json.accessors[accessorIndex];
3395
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
3396
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
3397
+ const components = node.structure.getNumComponents(accessor.type);
3398
+ const count = accessor.count;
3399
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
3400
+ reqs.push({
3401
+ offset: byteOffset,
3402
+ length: byteLength,
3403
+ componentType: accessor.componentType,
3404
+ accessorIndex: accessorIndex,
3405
+ type: "position",
3406
+ primIdx: primIdx
3407
+ });
3408
+ }
3409
+ if (primitive.attributes.NORMAL !== undefined) {
3410
+ const accessorIndex = primitive.attributes.NORMAL;
3411
+ const accessor = node.structure.json.accessors[accessorIndex];
3412
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
3413
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
3414
+ const components = node.structure.getNumComponents(accessor.type);
3415
+ const count = accessor.count;
3416
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
3417
+ reqs.push({
3418
+ offset: byteOffset,
3419
+ length: byteLength,
3420
+ componentType: accessor.componentType,
3421
+ accessorIndex: accessorIndex,
3422
+ type: "normal",
3423
+ primIdx: primIdx
3424
+ });
3425
+ }
3426
+ if (primitive.attributes.TEXCOORD_0 !== undefined) {
3427
+ const accessorIndex = primitive.attributes.TEXCOORD_0;
3428
+ const accessor = node.structure.json.accessors[accessorIndex];
3429
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
3430
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
3431
+ const components = node.structure.getNumComponents(accessor.type);
3432
+ const count = accessor.count;
3433
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
3434
+ reqs.push({
3435
+ offset: byteOffset,
3436
+ length: byteLength,
3437
+ componentType: accessor.componentType,
3438
+ accessorIndex: accessorIndex,
3439
+ type: "uv",
3440
+ primIdx: primIdx
3441
+ });
3442
+ }
3443
+ if (primitive.indices !== undefined) {
3444
+ const accessorIndex = primitive.indices;
3445
+ const accessor = node.structure.json.accessors[accessorIndex];
3446
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
3447
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
3448
+ const components = node.structure.getNumComponents(accessor.type);
3449
+ const count = accessor.count;
3450
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
3451
+ reqs.push({
3452
+ offset: byteOffset,
3453
+ length: byteLength,
3454
+ componentType: accessor.componentType,
3455
+ accessorIndex: accessorIndex,
3456
+ type: "index",
3457
+ primIdx: primIdx
3458
+ });
3459
+ }
3460
+ primitiveReqMap.set(primIdx, reqs);
3461
+ bufferRequests.push(...reqs);
3462
+ }
3463
+ if (bufferRequests.length === 0) {
3464
+ node.loaded = true;
3465
+ node.loading = false;
3466
+ return;
3467
+ }
3468
+ bufferRequests.sort(((a, b) => a.offset - b.offset));
3469
+ const minOffset = bufferRequests[0].offset;
3470
+ const maxOffset = Math.max(...bufferRequests.map((r => r.offset + r.length)));
3471
+ const totalLength = maxOffset - minOffset;
3472
+ const {buffer: buffer, relOffset: baseRelOffset} = await node.structure.scheduleRequest({
3473
+ offset: minOffset,
3474
+ length: totalLength,
3475
+ componentType: null
3476
+ });
3477
+ for (const req of bufferRequests) {
3478
+ const relOffset = req.offset - minOffset;
3479
+ req.data = node.structure.createTypedArray(buffer, baseRelOffset + relOffset, req.length, req.componentType);
3480
+ }
3481
+ for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
3482
+ const primitive = meshDef.primitives[primIdx];
3327
3483
  const geometry = new BufferGeometry;
3328
- const attributes = new Map;
3329
- attributes.set("position", node.structure.createBufferAttribute(positionAccessor));
3484
+ const reqs = primitiveReqMap.get(primIdx);
3485
+ if (primitive.attributes.POSITION !== undefined) {
3486
+ const req = reqs.find((r => r.type === "position" && r.accessorIndex === primitive.attributes.POSITION));
3487
+ const accessor = node.structure.json.accessors[primitive.attributes.POSITION];
3488
+ const components = node.structure.getNumComponents(accessor.type);
3489
+ geometry.setAttribute("position", new BufferAttribute(req.data, components));
3490
+ }
3330
3491
  if (primitive.attributes.NORMAL !== undefined) {
3331
- attributes.set("normal", node.structure.createBufferAttribute(primitive.attributes.NORMAL));
3492
+ const req = reqs.find((r => r.type === "normal" && r.accessorIndex === primitive.attributes.NORMAL));
3493
+ const accessor = node.structure.json.accessors[primitive.attributes.NORMAL];
3494
+ const components = node.structure.getNumComponents(accessor.type);
3495
+ geometry.setAttribute("normal", new BufferAttribute(req.data, components));
3332
3496
  }
3333
3497
  if (primitive.attributes.TEXCOORD_0 !== undefined) {
3334
- attributes.set("uv", node.structure.createBufferAttribute(primitive.attributes.TEXCOORD_0));
3498
+ const req = reqs.find((r => r.type === "uv" && r.accessorIndex === primitive.attributes.TEXCOORD_0));
3499
+ const accessor = node.structure.json.accessors[primitive.attributes.TEXCOORD_0];
3500
+ const components = node.structure.getNumComponents(accessor.type);
3501
+ geometry.setAttribute("uv", new BufferAttribute(req.data, components));
3335
3502
  }
3336
- const loadedAttributes = await Promise.all([ ...attributes.entries() ].map((async ([name, promise]) => {
3337
- const attribute = await promise;
3338
- return [ name, attribute ];
3339
- })));
3340
- loadedAttributes.forEach((([name, attribute]) => {
3341
- geometry.setAttribute(name, attribute);
3342
- }));
3343
3503
  if (primitive.indices !== undefined) {
3344
- const indexAttribute = await node.structure.createBufferAttribute(primitive.indices);
3345
- geometry.setIndex(indexAttribute);
3504
+ const req = reqs.find((r => r.type === "index" && r.accessorIndex === primitive.indices));
3505
+ geometry.setIndex(new BufferAttribute(req.data, 1));
3346
3506
  }
3347
- this.currentPrimitiveMode = primitive.mode;
3348
3507
  let material;
3349
3508
  if (primitive.material !== undefined) {
3350
3509
  material = node.structure.materials.get(primitive.material) || this.createDefaultMaterial();
@@ -3423,6 +3582,9 @@ class DynamicGltfLoader {
3423
3582
  const geometrySize = this.estimateGeometrySize(node.object);
3424
3583
  this.geometryCache.set(node.object.uuid, geometrySize);
3425
3584
  this.currentMemoryUsage += geometrySize;
3585
+ if (onLoadFinishCb) {
3586
+ onLoadFinishCb();
3587
+ }
3426
3588
  } catch (error) {
3427
3589
  if (error.name !== "AbortError") {
3428
3590
  console.error(`Error loading node ${nodeId}:`, error);
@@ -3520,7 +3682,7 @@ class DynamicGltfLoader {
3520
3682
  const volumeB = sizeB.x * sizeB.y * sizeB.z;
3521
3683
  return volumeB - volumeA;
3522
3684
  }));
3523
- if (!ignoreEdges) {
3685
+ if (!ignoreEdges && this.visibleEdges) {
3524
3686
  this.nodesToLoad.push(...this.edgeNodes);
3525
3687
  }
3526
3688
  this.dispatchEvent("databasechunk", {
@@ -3628,28 +3790,12 @@ class DynamicGltfLoader {
3628
3790
  async processNodes() {
3629
3791
  const nodesToLoad = this.nodesToLoad;
3630
3792
  let loadedCount = 0;
3793
+ let lastLoadedCount = 0;
3631
3794
  const totalNodes = nodesToLoad.length;
3632
- try {
3633
- while (loadedCount < totalNodes) {
3634
- const batch = nodesToLoad.slice(loadedCount, loadedCount + this.batchSize);
3635
- const batchPromises = [];
3636
- for (const nodeId of batch) {
3637
- if (this.abortController.signal.aborted) {
3638
- throw new DOMException("Loading aborted", "AbortError");
3639
- }
3640
- const estimatedSize = await this.estimateNodeSize(nodeId);
3641
- if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
3642
- console.log(`Memory limit reached after loading ${loadedCount} nodes`);
3643
- this.dispatchEvent("geometryerror", {
3644
- message: "Memory limit reached"
3645
- });
3646
- this.dispatchEvent("update");
3647
- return loadedCount;
3648
- }
3649
- batchPromises.push(this.loadNode(nodeId));
3650
- }
3651
- await Promise.all(batchPromises);
3652
- loadedCount += batch.length;
3795
+ const loadProgress = async () => {
3796
+ loadedCount++;
3797
+ if (loadedCount - lastLoadedCount > 1e3) {
3798
+ lastLoadedCount = loadedCount;
3653
3799
  this.updateMemoryIndicator();
3654
3800
  this.dispatchEvent("geometryprogress", {
3655
3801
  percentage: Math.round(loadedCount / totalNodes * 100),
@@ -3665,6 +3811,28 @@ class DynamicGltfLoader {
3665
3811
  setTimeout(resolve, 0);
3666
3812
  }));
3667
3813
  }
3814
+ };
3815
+ try {
3816
+ const loadOperations = [];
3817
+ for (const nodeId of nodesToLoad) {
3818
+ if (this.abortController.signal.aborted) {
3819
+ throw new DOMException("Loading aborted", "AbortError");
3820
+ }
3821
+ const estimatedSize = await this.estimateNodeSize(nodeId);
3822
+ if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
3823
+ console.log(`Memory limit reached after loading ${loadedCount} nodes`);
3824
+ this.dispatchEvent("geometryerror", {
3825
+ message: "Memory limit reached"
3826
+ });
3827
+ this.dispatchEvent("update");
3828
+ return loadedCount;
3829
+ }
3830
+ loadOperations.push(this.loadNode(nodeId, loadProgress));
3831
+ }
3832
+ for (const structure of this.structures) {
3833
+ loadOperations.push(structure.flushBufferRequests());
3834
+ }
3835
+ await Promise.all(loadOperations);
3668
3836
  this.dispatchEvent("geometryend", {
3669
3837
  totalLoaded: loadedCount,
3670
3838
  totalNodes: totalNodes
@@ -4468,13 +4636,29 @@ class DynamicGltfLoader {
4468
4636
  }
4469
4637
  }));
4470
4638
  }
4639
+ getStructureGeometryExtent(structureId) {
4640
+ const extent = new Box3;
4641
+ for (const [nodeId, node] of this.nodes.entries()) {
4642
+ if (!node.geometryExtents) continue;
4643
+ if (!nodeId.startsWith(structureId + "_")) continue;
4644
+ if (node.object && this.hiddenHandles && this.hiddenHandles.has(node.object.userData.handle)) continue;
4645
+ const transformedBox = node.geometryExtents.clone();
4646
+ if (node.group && node.group.matrix) {
4647
+ transformedBox.applyMatrix4(node.group.matrix);
4648
+ if (node.group.parent && node.group.parent.matrix) {
4649
+ transformedBox.applyMatrix4(node.group.parent.matrix);
4650
+ }
4651
+ }
4652
+ extent.union(transformedBox);
4653
+ }
4654
+ return extent;
4655
+ }
4471
4656
  }
4472
4657
 
4473
4658
  class GLTFCloudDynamicLoader {
4474
4659
  constructor(viewer) {
4475
4660
  this.requestId = 0;
4476
4661
  this.viewer = viewer;
4477
- this.scene = new Group;
4478
4662
  }
4479
4663
  dispose() {
4480
4664
  if (this.gltfLoader) this.gltfLoader.clear();
@@ -4483,21 +4667,22 @@ class GLTFCloudDynamicLoader {
4483
4667
  return typeof file === "object" && typeof file.database === "string" && typeof file.downloadResource === "function" && typeof file.downloadResourceRange === "function" && /.gltf$/i.test(file.database);
4484
4668
  }
4485
4669
  async load(model, format, params) {
4486
- this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, this.viewer.scene, this.viewer.renderer);
4670
+ const scene = new Group;
4671
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
4487
4672
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
4488
4673
  this.gltfLoader.addEventListener("databasechunk", (data => {
4489
- const modelImpl = new DynamicModelImpl(this.scene);
4674
+ const modelImpl = new DynamicModelImpl(scene);
4490
4675
  modelImpl.loader = this;
4491
4676
  modelImpl.gltfLoader = this.gltfLoader;
4492
4677
  modelImpl.viewer = this.viewer;
4493
- this.viewer.scene.add(this.scene);
4678
+ this.viewer.scene.add(scene);
4494
4679
  this.viewer.models.push(modelImpl);
4495
4680
  this.viewer.syncOptions();
4496
4681
  this.viewer.syncOverlay();
4497
4682
  this.viewer.update();
4498
4683
  this.viewer.emitEvent({
4499
4684
  type: "databasechunk",
4500
- data: data,
4685
+ data: scene,
4501
4686
  file: model.file,
4502
4687
  model: model
4503
4688
  });
@@ -4511,12 +4696,6 @@ class GLTFCloudDynamicLoader {
4511
4696
  model: model
4512
4697
  });
4513
4698
  }));
4514
- this.gltfLoader.addEventListener("geometrymemory", (data => {
4515
- this.viewer.emit({
4516
- type: "geometryprogress",
4517
- data: data
4518
- });
4519
- }));
4520
4699
  this.gltfLoader.addEventListener("geometryerror", (data => {
4521
4700
  this.viewer.emitEvent({
4522
4701
  type: "geometryerror",
@@ -4559,9 +4738,7 @@ class GLTFCloudDynamicLoader {
4559
4738
  return this;
4560
4739
  }
4561
4740
  cancel() {
4562
- if (this.gltfLoader) {
4563
- this.gltfLoader.abortLoading();
4564
- }
4741
+ if (this.gltfLoader) this.gltfLoader.abortLoading();
4565
4742
  }
4566
4743
  }
4567
4744