@inweb/viewer-three 26.11.0 → 26.12.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.
Files changed (44) hide show
  1. package/dist/extensions/components/AxesHelperComponent.js +1 -1
  2. package/dist/extensions/components/AxesHelperComponent.js.map +1 -1
  3. package/dist/extensions/components/AxesHelperComponent.min.js +1 -1
  4. package/dist/extensions/components/AxesHelperComponent.module.js +1 -1
  5. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -1
  6. package/dist/extensions/components/InfoPanelComponent.js +170 -0
  7. package/dist/extensions/components/InfoPanelComponent.js.map +1 -0
  8. package/dist/extensions/components/InfoPanelComponent.min.js +24 -0
  9. package/dist/extensions/components/InfoPanelComponent.module.js +164 -0
  10. package/dist/extensions/components/InfoPanelComponent.module.js.map +1 -0
  11. package/dist/extensions/components/StatsPanelComponent.js +9 -3
  12. package/dist/extensions/components/StatsPanelComponent.js.map +1 -1
  13. package/dist/extensions/components/StatsPanelComponent.min.js +1 -1
  14. package/dist/extensions/components/StatsPanelComponent.module.js +9 -3
  15. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -1
  16. package/dist/extensions/loaders/PotreeLoader.js +55 -4
  17. package/dist/extensions/loaders/PotreeLoader.js.map +1 -1
  18. package/dist/extensions/loaders/PotreeLoader.min.js +1 -1
  19. package/dist/extensions/loaders/PotreeLoader.module.js +52 -0
  20. package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -1
  21. package/dist/viewer-three.js +435 -14
  22. package/dist/viewer-three.js.map +1 -1
  23. package/dist/viewer-three.min.js +8 -3
  24. package/dist/viewer-three.module.js +379 -10
  25. package/dist/viewer-three.module.js.map +1 -1
  26. package/extensions/components/AxesHelperComponent.ts +1 -1
  27. package/extensions/components/InfoPanelComponent.ts +197 -0
  28. package/extensions/components/StatsPanelComponent.ts +10 -3
  29. package/extensions/loaders/Potree/PotreeModelImpl.ts +72 -0
  30. package/lib/Viewer/Viewer.d.ts +2 -1
  31. package/lib/Viewer/components/InfoComponent.d.ts +22 -0
  32. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +2 -0
  33. package/lib/Viewer/models/IModelImpl.d.ts +2 -1
  34. package/lib/Viewer/models/ModelImpl.d.ts +2 -0
  35. package/package.json +5 -5
  36. package/src/Viewer/Viewer.ts +41 -5
  37. package/src/Viewer/components/InfoComponent.ts +187 -0
  38. package/src/Viewer/components/index.ts +2 -0
  39. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +16 -8
  40. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +25 -0
  41. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +67 -1
  42. package/src/Viewer/loaders/RangesLoader.ts +11 -1
  43. package/src/Viewer/models/IModelImpl.ts +3 -1
  44. package/src/Viewer/models/ModelImpl.ts +158 -0
@@ -21,9 +21,9 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Loader, loadersRegistry, CANVAS_EVENTS } from '@inweb/viewer-core';
24
+ import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Info, Loader, loadersRegistry, 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, Line3, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, 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, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Clock, Sphere, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, REVISION, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, 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';
@@ -2633,6 +2633,140 @@ class LightComponent {
2633
2633
  }
2634
2634
  }
2635
2635
 
2636
+ class InfoComponent {
2637
+ constructor(viewer) {
2638
+ this.initialize = () => {
2639
+ try {
2640
+ const gl = this.viewer.renderer.getContext();
2641
+ const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
2642
+ if (dbgInfo) {
2643
+ this.viewer.info.system.webglRenderer = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
2644
+ this.viewer.info.system.webglVendor = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
2645
+ }
2646
+ }
2647
+ catch (error) {
2648
+ console.error("Error reading WebGL info.", error);
2649
+ }
2650
+ console.log("THREE.WebGLRenderer:", REVISION);
2651
+ console.log("WebGL Renderer:", this.viewer.info.system.webglRenderer);
2652
+ console.log("WebGL Vendor:", this.viewer.info.system.webglVendor);
2653
+ this.resize();
2654
+ this.optionsChange({ data: this.viewer.options });
2655
+ };
2656
+ this.clear = () => {
2657
+ this.viewer.info.performance.timeToFirstRender = 0;
2658
+ this.viewer.info.performance.loadTime = 0;
2659
+ this.viewer.info.scene.objects = 0;
2660
+ this.viewer.info.scene.triangles = 0;
2661
+ this.viewer.info.scene.points = 0;
2662
+ this.viewer.info.scene.lines = 0;
2663
+ this.viewer.info.scene.edges = 0;
2664
+ this.viewer.info.optimizedScene.objects = 0;
2665
+ this.viewer.info.optimizedScene.triangles = 0;
2666
+ this.viewer.info.optimizedScene.points = 0;
2667
+ this.viewer.info.optimizedScene.lines = 0;
2668
+ this.viewer.info.optimizedScene.edges = 0;
2669
+ this.viewer.info.memory.geometries = 0;
2670
+ this.viewer.info.memory.geometryBytes = 0;
2671
+ this.viewer.info.memory.textures = 0;
2672
+ this.viewer.info.memory.textureBytes = 0;
2673
+ this.viewer.info.memory.materials = 0;
2674
+ this.viewer.info.memory.totalEstimatedGpuBytes = 0;
2675
+ this.viewer.info.memory.usedJSHeapSize = 0;
2676
+ };
2677
+ this.optionsChange = ({ data: options }) => {
2678
+ if (options.antialiasing === false)
2679
+ this.viewer.info.render.antialiasing = "";
2680
+ else if (options.antialiasing === true)
2681
+ this.viewer.info.render.antialiasing = "mxaa";
2682
+ else
2683
+ this.viewer.info.render.antialiasing = options.antialiasing;
2684
+ };
2685
+ this.geometryStart = () => {
2686
+ this.startTime = performance.now();
2687
+ };
2688
+ this.databaseChunk = () => {
2689
+ this.viewer.info.performance.timeToFirstRender += performance.now() - this.startTime;
2690
+ console.log("Time to first render:", this.viewer.info.performance.timeToFirstRender, "ms");
2691
+ };
2692
+ this.geometryEnd = () => {
2693
+ const model = this.viewer.models[this.viewer.models.length - 1];
2694
+ const info = model.getInfo();
2695
+ this.viewer.info.scene.objects += info.scene.objects;
2696
+ this.viewer.info.scene.triangles += info.scene.triangles;
2697
+ this.viewer.info.scene.points += info.scene.points;
2698
+ this.viewer.info.scene.lines += info.scene.lines;
2699
+ this.viewer.info.scene.edges += info.scene.edges;
2700
+ this.viewer.info.optimizedScene.objects += info.optimizedScene.objects;
2701
+ this.viewer.info.optimizedScene.triangles += info.optimizedScene.triangles;
2702
+ this.viewer.info.optimizedScene.points += info.optimizedScene.points;
2703
+ this.viewer.info.optimizedScene.lines += info.optimizedScene.lines;
2704
+ this.viewer.info.optimizedScene.edges += info.optimizedScene.edges;
2705
+ this.viewer.info.memory.geometries += info.memory.geometries;
2706
+ this.viewer.info.memory.geometryBytes += info.memory.geometryBytes;
2707
+ this.viewer.info.memory.textures += info.memory.textures;
2708
+ this.viewer.info.memory.textureBytes += info.memory.textureBytes;
2709
+ this.viewer.info.memory.materials += info.memory.materials;
2710
+ this.viewer.info.memory.totalEstimatedGpuBytes += info.memory.totalEstimatedGpuBytes;
2711
+ const memory = performance["memory"];
2712
+ if (memory)
2713
+ this.viewer.info.memory.usedJSHeapSize = memory.usedJSHeapSize;
2714
+ this.viewer.info.performance.loadTime += performance.now() - this.startTime;
2715
+ console.log("Number of objects:", info.scene.objects);
2716
+ console.log("Number of objects after optimization:", info.optimizedScene.objects);
2717
+ console.log("Total geometry size:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
2718
+ console.log("File load time:", this.viewer.info.performance.loadTime, "ms");
2719
+ };
2720
+ this.resize = () => {
2721
+ const rendererSize = this.viewer.renderer.getSize(new Vector2());
2722
+ this.viewer.info.render.viewport.width = rendererSize.x;
2723
+ this.viewer.info.render.viewport.height = rendererSize.y;
2724
+ };
2725
+ this.render = () => {
2726
+ this.viewer.info.render.drawCalls = this.viewer.renderer.info.render.calls;
2727
+ this.viewer.info.render.triangles = this.viewer.renderer.info.render.triangles;
2728
+ this.viewer.info.render.points = this.viewer.renderer.info.render.points;
2729
+ this.viewer.info.render.lines = this.viewer.renderer.info.render.lines;
2730
+ };
2731
+ this.animate = () => {
2732
+ const time = performance.now();
2733
+ this.viewer.info.performance.frameTime = Math.round(time - this.beginTime);
2734
+ this.beginTime = time;
2735
+ this.frames++;
2736
+ if (time - this.prevTime >= 1000) {
2737
+ this.viewer.info.performance.fps = Math.round((this.frames * 1000) / (time - this.prevTime));
2738
+ this.prevTime = time;
2739
+ this.frames = 0;
2740
+ }
2741
+ };
2742
+ this.viewer = viewer;
2743
+ this.startTime = 0;
2744
+ this.beginTime = performance.now();
2745
+ this.prevTime = performance.now();
2746
+ this.frames = 0;
2747
+ this.viewer.addEventListener("initialize", this.initialize);
2748
+ this.viewer.addEventListener("clear", this.clear);
2749
+ this.viewer.addEventListener("optionschange", this.optionsChange);
2750
+ this.viewer.addEventListener("geometrystart", this.geometryStart);
2751
+ this.viewer.addEventListener("databasechunk", this.databaseChunk);
2752
+ this.viewer.addEventListener("geometryend", this.geometryEnd);
2753
+ this.viewer.addEventListener("resize", this.resize);
2754
+ this.viewer.addEventListener("render", this.render);
2755
+ this.viewer.addEventListener("animate", this.animate);
2756
+ }
2757
+ dispose() {
2758
+ this.viewer.removeEventListener("initialize", this.initialize);
2759
+ this.viewer.removeEventListener("clear", this.clear);
2760
+ this.viewer.removeEventListener("optionschange", this.optionsChange);
2761
+ this.viewer.removeEventListener("geometrystart", this.geometryStart);
2762
+ this.viewer.removeEventListener("databasechunk", this.databaseChunk);
2763
+ this.viewer.removeEventListener("geometryend", this.geometryEnd);
2764
+ this.viewer.removeEventListener("resize", this.resize);
2765
+ this.viewer.removeEventListener("render", this.render);
2766
+ this.viewer.addEventListener("animate", this.animate);
2767
+ }
2768
+ }
2769
+
2636
2770
  class RenderLoopComponent {
2637
2771
  constructor(viewer) {
2638
2772
  this.animate = (time = 0) => {
@@ -3119,6 +3253,7 @@ components.registerComponent("ExtentsComponent", (viewer) => new ExtentsComponen
3119
3253
  components.registerComponent("CameraComponent", (viewer) => new CameraComponent(viewer));
3120
3254
  components.registerComponent("BackgroundComponent", (viewer) => new BackgroundComponent(viewer));
3121
3255
  components.registerComponent("LightComponent", (viewer) => new LightComponent(viewer));
3256
+ components.registerComponent("InfoComponent", (viewer) => new InfoComponent(viewer));
3122
3257
  components.registerComponent("ResizeCanvasComponent", (viewer) => new ResizeCanvasComponent(viewer));
3123
3258
  components.registerComponent("RenderLoopComponent", (viewer) => new RenderLoopComponent(viewer));
3124
3259
  components.registerComponent("HighlighterComponent", (viewer) => new HighlighterComponent(viewer));
@@ -3159,6 +3294,132 @@ class ModelImpl {
3159
3294
  getPrecision() {
3160
3295
  return 2;
3161
3296
  }
3297
+ getInfo() {
3298
+ const geometries = new Set();
3299
+ const materials = new Set();
3300
+ const textures = new Set();
3301
+ let totalObjects = 0;
3302
+ let totalTriangles = 0;
3303
+ let totalPoints = 0;
3304
+ let totalLines = 0;
3305
+ let totalEdges = 0;
3306
+ let geometryBytes = 0;
3307
+ let textureBytes = 0;
3308
+ this.scene.traverse((object) => {
3309
+ totalObjects++;
3310
+ if (object.geometry) {
3311
+ const geometry = object.geometry;
3312
+ if (!geometries.has(geometry)) {
3313
+ geometries.add(geometry);
3314
+ if (geometry.attributes) {
3315
+ for (const name in geometry.attributes) {
3316
+ const attribute = geometry.attributes[name];
3317
+ if (attribute && attribute.array) {
3318
+ geometryBytes += attribute.array.byteLength;
3319
+ }
3320
+ }
3321
+ }
3322
+ if (geometry.index && geometry.index.array) {
3323
+ geometryBytes += geometry.index.array.byteLength;
3324
+ }
3325
+ }
3326
+ if (geometry.index) {
3327
+ const indexCount = geometry.index.count;
3328
+ if (object.isLine || object.isLineSegments) {
3329
+ totalLines += indexCount / 2;
3330
+ }
3331
+ else if (object.isPoints) {
3332
+ totalPoints += indexCount;
3333
+ }
3334
+ else {
3335
+ totalTriangles += indexCount / 3;
3336
+ }
3337
+ }
3338
+ else if (geometry.attributes && geometry.attributes.position) {
3339
+ const positionCount = geometry.attributes.position.count;
3340
+ if (object.isLine || object.isLineSegments) {
3341
+ totalLines += positionCount / 2;
3342
+ }
3343
+ else if (object.isPoints) {
3344
+ totalPoints += positionCount;
3345
+ }
3346
+ else {
3347
+ totalTriangles += positionCount / 3;
3348
+ }
3349
+ }
3350
+ if (object.isLineSegments && geometry.attributes.position) {
3351
+ totalEdges += geometry.attributes.position.count / 2;
3352
+ }
3353
+ }
3354
+ if (object.material) {
3355
+ const materialsArray = Array.isArray(object.material) ? object.material : [object.material];
3356
+ materialsArray.forEach((material) => {
3357
+ materials.add(material);
3358
+ if (material.map && !textures.has(material.map)) {
3359
+ textures.add(material.map);
3360
+ textureBytes += estimateTextureSize(material.map);
3361
+ }
3362
+ const textureProps = [
3363
+ "alphaMap",
3364
+ "aoMap",
3365
+ "bumpMap",
3366
+ "displacementMap",
3367
+ "emissiveMap",
3368
+ "envMap",
3369
+ "lightMap",
3370
+ "metalnessMap",
3371
+ "normalMap",
3372
+ "roughnessMap",
3373
+ "specularMap",
3374
+ "clearcoatMap",
3375
+ "clearcoatNormalMap",
3376
+ "clearcoatRoughnessMap",
3377
+ "iridescenceMap",
3378
+ "sheenColorMap",
3379
+ "sheenRoughnessMap",
3380
+ "thicknessMap",
3381
+ "transmissionMap",
3382
+ "anisotropyMap",
3383
+ "gradientMap",
3384
+ ];
3385
+ textureProps.forEach((prop) => {
3386
+ const texture = material[prop];
3387
+ if (texture && !textures.has(texture)) {
3388
+ textures.add(texture);
3389
+ textureBytes += estimateTextureSize(texture);
3390
+ }
3391
+ });
3392
+ });
3393
+ }
3394
+ });
3395
+ function estimateTextureSize(texture) {
3396
+ if (!texture.image)
3397
+ return 0;
3398
+ const width = texture.image.width || 0;
3399
+ const height = texture.image.height || 0;
3400
+ const bytesPerPixel = 4;
3401
+ const mipmapMultiplier = texture.generateMipmaps ? 1.33 : 1;
3402
+ return width * height * bytesPerPixel * mipmapMultiplier;
3403
+ }
3404
+ const info = new Info();
3405
+ info.scene.objects = totalObjects;
3406
+ info.scene.triangles = Math.floor(totalTriangles);
3407
+ info.scene.points = Math.floor(totalPoints);
3408
+ info.scene.lines = Math.floor(totalLines);
3409
+ info.scene.edges = Math.floor(totalEdges);
3410
+ info.memory.geometries = geometries.size;
3411
+ info.memory.geometryBytes = geometryBytes;
3412
+ info.memory.textures = textures.size;
3413
+ info.memory.textureBytes = Math.floor(textureBytes);
3414
+ info.memory.materials = materials.size;
3415
+ info.memory.totalEstimatedGpuBytes = geometryBytes + Math.floor(textureBytes);
3416
+ info.optimizedScene.objects = info.scene.objects;
3417
+ info.optimizedScene.triangles = info.scene.triangles;
3418
+ info.optimizedScene.points = info.scene.points;
3419
+ info.optimizedScene.lines = info.scene.lines;
3420
+ info.optimizedScene.edges = info.scene.edges;
3421
+ return info;
3422
+ }
3162
3423
  getExtents(target) {
3163
3424
  this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
3164
3425
  return target;
@@ -3291,6 +3552,24 @@ class ModelImpl {
3291
3552
  }
3292
3553
 
3293
3554
  class DynamicModelImpl extends ModelImpl {
3555
+ getInfo() {
3556
+ const stats = this.gltfLoader.getStats();
3557
+ const info = new Info();
3558
+ info.scene.objects = stats.scene.beforeOptimization.objects;
3559
+ info.scene.triangles = stats.scene.beforeOptimization.triangles;
3560
+ info.scene.lines = stats.scene.beforeOptimization.lines;
3561
+ info.scene.edges = stats.scene.beforeOptimization.edges;
3562
+ info.optimizedScene.objects = stats.scene.afterOptimization.objects;
3563
+ info.optimizedScene.triangles = stats.scene.afterOptimization.triangles;
3564
+ info.optimizedScene.lines = stats.scene.afterOptimization.lines;
3565
+ info.optimizedScene.edges = stats.scene.afterOptimization.edges;
3566
+ info.memory.geometries = stats.memory.geometries.count;
3567
+ info.memory.geometryBytes = stats.memory.geometries.bytes;
3568
+ info.memory.textures = stats.memory.textures.count;
3569
+ info.memory.materials = stats.memory.materials.count;
3570
+ info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;
3571
+ return info;
3572
+ }
3294
3573
  getExtents(target) {
3295
3574
  return target.union(this.gltfLoader.getTotalGeometryExtent());
3296
3575
  }
@@ -3407,6 +3686,8 @@ class GltfStructure {
3407
3686
  this.materialCache = new Map();
3408
3687
  this.uri = "";
3409
3688
  this._nextObjectId = 0;
3689
+ this.loadingAborted = false;
3690
+ this.criticalError = null;
3410
3691
  }
3411
3692
  async initialize(loader) {
3412
3693
  this.json = await this.loadController.loadJson();
@@ -3428,12 +3709,18 @@ class GltfStructure {
3428
3709
  this.materials.clear();
3429
3710
  this.activeChunkLoads = 0;
3430
3711
  this.chunkQueue = [];
3712
+ this.loadingAborted = false;
3713
+ this.criticalError = null;
3431
3714
  }
3432
3715
  getJson() {
3433
3716
  return this.json;
3434
3717
  }
3435
3718
  scheduleRequest(request) {
3436
3719
  return new Promise((resolve, reject) => {
3720
+ if (this.loadingAborted) {
3721
+ reject(this.criticalError || new Error("Structure loading has been aborted due to critical error"));
3722
+ return;
3723
+ }
3437
3724
  this.pendingRequests.push({
3438
3725
  ...request,
3439
3726
  _resolve: resolve,
@@ -3441,6 +3728,41 @@ class GltfStructure {
3441
3728
  });
3442
3729
  });
3443
3730
  }
3731
+ isCriticalHttpError(error) {
3732
+ if (!error) return false;
3733
+ const status = error.status || error.statusCode || error.code;
3734
+ if (typeof status === "number") {
3735
+ return status >= 400 && status < 600;
3736
+ }
3737
+ if (error.message) {
3738
+ const match = error.message.match(/HTTP\s+(\d{3})/i);
3739
+ if (match) {
3740
+ const code = parseInt(match[1], 10);
3741
+ return code >= 400 && code < 600;
3742
+ }
3743
+ }
3744
+ return false;
3745
+ }
3746
+ abortLoading(error) {
3747
+ if (this.loadingAborted) {
3748
+ return;
3749
+ }
3750
+ this.loadingAborted = true;
3751
+ this.criticalError = error;
3752
+ const requests = [...this.pendingRequests];
3753
+ this.pendingRequests = [];
3754
+ for (const req of requests) {
3755
+ if (req._reject) {
3756
+ req._reject(error);
3757
+ }
3758
+ }
3759
+ console.error(
3760
+ `❌ Critical error for structure "${this.id}". All further loading aborted.`,
3761
+ `\n Error: ${error.message || error}`,
3762
+ `\n Rejected ${requests.length} pending chunk requests.`
3763
+ );
3764
+ throw error;
3765
+ }
3444
3766
  async flushBufferRequests() {
3445
3767
  if (!this.pendingRequests || this.pendingRequests.length === 0) return;
3446
3768
  const requests = [...this.pendingRequests];
@@ -3503,6 +3825,12 @@ class GltfStructure {
3503
3825
  }
3504
3826
  }
3505
3827
  const promises = finalRanges.map(async (range, index) => {
3828
+ if (this.loadingAborted) {
3829
+ for (const req of range.requests) {
3830
+ req._reject(this.criticalError || new Error("Structure loading aborted"));
3831
+ }
3832
+ return;
3833
+ }
3506
3834
  await this.loader.waitForChunkSlot();
3507
3835
  try {
3508
3836
  const length = range.end - range.start;
@@ -3519,7 +3847,11 @@ class GltfStructure {
3519
3847
  for (const req of range.requests) {
3520
3848
  req._reject(error);
3521
3849
  }
3522
- console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
3850
+ if (this.isCriticalHttpError(error)) {
3851
+ this.abortLoading(error);
3852
+ } else {
3853
+ console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
3854
+ }
3523
3855
  } finally {
3524
3856
  this.loader.releaseChunkSlot();
3525
3857
  }
@@ -4310,10 +4642,14 @@ class DynamicGltfLoader {
4310
4642
  onLoadFinishCb();
4311
4643
  }
4312
4644
  } catch (error) {
4313
- if (error.name !== "AbortError") {
4314
- console.error(`Error loading node ${nodeId}:`, error);
4315
- }
4316
4645
  node.loading = false;
4646
+ if (error.name === "AbortError") {
4647
+ return;
4648
+ }
4649
+ if (node.structure && node.structure.loadingAborted) {
4650
+ return;
4651
+ }
4652
+ console.error(`Error loading node ${nodeId}:`, error);
4317
4653
  }
4318
4654
  }
4319
4655
  unloadNode(nodeId) {
@@ -5119,6 +5455,7 @@ class DynamicGltfLoader {
5119
5455
  }
5120
5456
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5121
5457
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
5458
+ mergedMesh.userData.isOptimized = true;
5122
5459
  rootGroup.add(mergedMesh);
5123
5460
  this.mergedMesh.add(mergedMesh);
5124
5461
  this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
@@ -5225,6 +5562,7 @@ class DynamicGltfLoader {
5225
5562
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5226
5563
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
5227
5564
  mergedLine.userData.isEdge = isEdge;
5565
+ mergedLine.userData.isOptimized = true;
5228
5566
  const mergedObjects = [mergedLine];
5229
5567
  if (this.useVAO) {
5230
5568
  this.createVAO(mergedLine);
@@ -5303,6 +5641,7 @@ class DynamicGltfLoader {
5303
5641
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5304
5642
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
5305
5643
  mergedLine.userData.isEdge = isEdge;
5644
+ mergedLine.userData.isOptimized = true;
5306
5645
  if (this.useVAO) {
5307
5646
  this.createVAO(mergedLine);
5308
5647
  }
@@ -5362,6 +5701,7 @@ class DynamicGltfLoader {
5362
5701
  if (geometries.length > 0) {
5363
5702
  const mergedGeometry = mergeGeometries(geometries, false);
5364
5703
  const mergedPoints = new Points(mergedGeometry, group.material);
5704
+ mergedPoints.userData.isOptimized = true;
5365
5705
  if (this.useVAO) {
5366
5706
  this.createVAO(mergedPoints);
5367
5707
  }
@@ -5437,6 +5777,7 @@ class DynamicGltfLoader {
5437
5777
  }
5438
5778
  const mergedLine = new LineSegments(finalGeometry, material);
5439
5779
  mergedLine.userData.structureId = structureId;
5780
+ mergedLine.userData.isOptimized = true;
5440
5781
  rootGroup.add(mergedLine);
5441
5782
  this.mergedLineSegments.add(mergedLine);
5442
5783
  lineSegmentsArray.forEach((obj) => {
@@ -5706,6 +6047,13 @@ class GLTFBinaryExtension {
5706
6047
  }
5707
6048
  }
5708
6049
 
6050
+ class FetchError extends Error {
6051
+ constructor(status, message) {
6052
+ super(message);
6053
+ this.name = "FetchError";
6054
+ this.status = status;
6055
+ }
6056
+ }
5709
6057
  class RangesLoader {
5710
6058
  constructor() {
5711
6059
  this.requestHeader = {};
@@ -5732,7 +6080,7 @@ class RangesLoader {
5732
6080
  };
5733
6081
  const response = await fetch(url, init);
5734
6082
  if (!response.ok) {
5735
- throw new Error(`Failed to fetch "${url}", status ${response.status}`);
6083
+ throw new FetchError(response.status, `Failed to fetch "${url}", status ${response.status}`);
5736
6084
  }
5737
6085
  if (response.status !== 206) {
5738
6086
  const arrayBuffer = await response.arrayBuffer();
@@ -6118,6 +6466,7 @@ class Viewer extends EventEmitter2 {
6118
6466
  this.options = new Options(this);
6119
6467
  this.loaders = [];
6120
6468
  this.models = [];
6469
+ this.info = new Info();
6121
6470
  this.canvasEvents = CANVAS_EVENTS.slice();
6122
6471
  this.canvaseventlistener = (event) => this.emit(event);
6123
6472
  this.selected = [];
@@ -6278,6 +6627,8 @@ class Viewer extends EventEmitter2 {
6278
6627
  const deltaTime = (time - this._renderTime) / 1000;
6279
6628
  this._renderTime = time;
6280
6629
  this._renderNeeded = false;
6630
+ this.renderer.info.autoReset = false;
6631
+ this.renderer.info.reset();
6281
6632
  if (this.options.antialiasing === true || this.options.antialiasing === "msaa") {
6282
6633
  this.renderer.render(this.scene, this.camera);
6283
6634
  this.renderer.render(this.helpers, this.camera);
@@ -6614,9 +6965,27 @@ class Viewer extends EventEmitter2 {
6614
6965
  const rect = this.canvas.getBoundingClientRect();
6615
6966
  const x = position.x / (rect.width / 2) - 1;
6616
6967
  const y = -position.y / (rect.height / 2) + 1;
6617
- const point = new Vector3(x, y, -1);
6618
- point.unproject(this.camera);
6619
- return { x: point.x, y: point.y, z: point.z };
6968
+ if (this.camera["isPerspectiveCamera"]) {
6969
+ const raycaster = new Raycaster();
6970
+ const mouse = new Vector2(x, y);
6971
+ raycaster.setFromCamera(mouse, this.camera);
6972
+ const cameraDirection = new Vector3();
6973
+ this.camera.getWorldDirection(cameraDirection);
6974
+ const targetPlane = new Plane().setFromNormalAndCoplanarPoint(cameraDirection, this.target);
6975
+ const intersectionPoint = new Vector3();
6976
+ raycaster.ray.intersectPlane(targetPlane, intersectionPoint);
6977
+ if (!intersectionPoint) {
6978
+ const point = new Vector3(x, y, -1);
6979
+ point.unproject(this.camera);
6980
+ return { x: point.x, y: point.y, z: point.z };
6981
+ }
6982
+ return { x: intersectionPoint.x, y: intersectionPoint.y, z: intersectionPoint.z };
6983
+ }
6984
+ else {
6985
+ const point = new Vector3(x, y, -1);
6986
+ point.unproject(this.camera);
6987
+ return { x: point.x, y: point.y, z: point.z };
6988
+ }
6620
6989
  }
6621
6990
  worldToScreen(position) {
6622
6991
  if (!this.renderer)