@inweb/viewer-three 26.7.2 → 26.8.0

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