@inweb/viewer-three 26.8.0 → 26.8.2

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 (54) hide show
  1. package/dist/plugins/components/RoomEnvironmentComponent.js +75 -40
  2. package/dist/plugins/components/RoomEnvironmentComponent.js.map +1 -1
  3. package/dist/plugins/components/RoomEnvironmentComponent.min.js +1 -1
  4. package/dist/plugins/components/StatsPanelComponent.js +1 -1
  5. package/dist/plugins/components/StatsPanelComponent.js.map +1 -1
  6. package/dist/plugins/components/StatsPanelComponent.min.js +1 -1
  7. package/dist/plugins/components/StatsPanelComponent.module.js +1 -1
  8. package/dist/plugins/components/StatsPanelComponent.module.js.map +1 -1
  9. package/dist/plugins/loaders/GLTFCloudLoader.js +225 -94
  10. package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -1
  11. package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -1
  12. package/dist/plugins/loaders/IFCXLoader.js +1977 -881
  13. package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
  14. package/dist/plugins/loaders/IFCXLoader.min.js +1 -1
  15. package/dist/plugins/loaders/IFCXLoader.module.js +477 -154
  16. package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
  17. package/dist/viewer-three.js +31149 -5503
  18. package/dist/viewer-three.js.map +1 -1
  19. package/dist/viewer-three.min.js +3 -3
  20. package/dist/viewer-three.module.js +406 -298
  21. package/dist/viewer-three.module.js.map +1 -1
  22. package/lib/Viewer/Viewer.d.ts +17 -3
  23. package/lib/Viewer/commands/SetDefaultViewPosition.d.ts +6 -6
  24. package/lib/Viewer/components/HighlighterComponent.d.ts +5 -4
  25. package/lib/Viewer/components/SelectionComponent.d.ts +1 -1
  26. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +3 -1
  27. package/lib/Viewer/models/IModelImpl.d.ts +27 -0
  28. package/lib/Viewer/models/ModelImpl.d.ts +27 -0
  29. package/lib/Viewer/scenes/Helpers.d.ts +7 -0
  30. package/lib/index.d.ts +2 -1
  31. package/package.json +9 -9
  32. package/plugins/components/StatsPanelComponent.ts +1 -1
  33. package/plugins/loaders/IFCX/IFCXLoader.ts +4 -7
  34. package/plugins/loaders/IFCX/render.js +686 -181
  35. package/plugins/loaders/IFCXCloudLoader.ts +1 -1
  36. package/src/Viewer/Viewer.ts +124 -48
  37. package/src/Viewer/commands/SetDefaultViewPosition.ts +8 -8
  38. package/src/Viewer/components/CameraComponent.ts +20 -16
  39. package/src/Viewer/components/ExtentsComponent.ts +1 -0
  40. package/src/Viewer/components/HighlighterComponent.ts +78 -80
  41. package/src/Viewer/components/LightComponent.ts +10 -4
  42. package/src/Viewer/components/ResizeCanvasComponent.ts +1 -0
  43. package/src/Viewer/components/SelectionComponent.ts +1 -1
  44. package/src/Viewer/helpers/WCSHelper.ts +8 -5
  45. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +33 -16
  46. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +12 -5
  47. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +100 -20
  48. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +4 -2
  49. package/src/Viewer/loaders/GLTFFileLoader.ts +1 -1
  50. package/src/Viewer/models/IModelImpl.ts +67 -0
  51. package/src/Viewer/models/ModelImpl.ts +214 -0
  52. package/src/Viewer/postprocessing/SSAARenderPass.js +245 -0
  53. package/src/Viewer/scenes/Helpers.ts +42 -0
  54. package/src/index.ts +2 -1
@@ -105,15 +105,18 @@ export class WCSHelper extends Object3D {
105
105
  this.quaternion.copy(this.camera.quaternion).invert();
106
106
  this.updateMatrixWorld();
107
107
 
108
- const clippingPlanes = renderer.clippingPlanes;
109
- const viewport = renderer.getViewport(new Vector4());
108
+ const oldAutoClear = renderer.autoClear;
109
+ const oldClippingPlanes = renderer.clippingPlanes;
110
+ const oldViewport = renderer.getViewport(new Vector4());
110
111
 
111
- renderer.setViewport(this.position.x, this.position.y, this.size, this.size);
112
+ renderer.autoClear = false;
112
113
  renderer.clippingPlanes = [];
114
+ renderer.setViewport(this.position.x, this.position.y, this.size, this.size);
113
115
  renderer.clearDepth();
114
116
  renderer.render(this, this.orthoCamera);
115
117
 
116
- renderer.setViewport(viewport);
117
- renderer.clippingPlanes = clippingPlanes;
118
+ renderer.setViewport(oldViewport);
119
+ renderer.clippingPlanes = oldClippingPlanes;
120
+ renderer.autoClear = oldAutoClear;
118
121
  }
119
122
  }
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  BufferGeometry,
3
3
  PointsMaterial,
4
- Material,
5
4
  Points,
6
5
  Mesh,
7
6
  TriangleStripDrawMode,
@@ -14,7 +13,7 @@ import {
14
13
  Quaternion,
15
14
  Matrix4,
16
15
  Box3,
17
- MeshStandardMaterial,
16
+ MeshPhongMaterial,
18
17
  Color,
19
18
  MathUtils,
20
19
  PerspectiveCamera,
@@ -369,19 +368,19 @@ export class DynamicGltfLoader {
369
368
 
370
369
  let material;
371
370
  if (primitive.material !== undefined) {
372
- material = node.structure.materials.get(primitive.material) || this.createDefaultMaterial();
371
+ material = node.structure.getCachedMaterial(primitive.material, primitive.mode);
372
+
373
+ if (!material) {
374
+ const materialDef = node.structure.json.materials[primitive.material];
375
+ material = node.structure.createMaterial(materialDef, primitive.mode);
376
+ }
373
377
  } else {
374
- material = this.createDefaultMaterial();
378
+ material = this.createDefaultMaterial(primitive.mode);
375
379
  }
376
380
 
377
381
  let mesh;
378
382
  if (primitive.mode === GL_CONSTANTS.POINTS) {
379
- const pointsMaterial = new PointsMaterial();
380
- Material.prototype.copy.call(pointsMaterial, material);
381
- pointsMaterial.color.copy(material.color);
382
- pointsMaterial.map = material.map;
383
- pointsMaterial.sizeAttenuation = false;
384
- mesh = new Points(geometry, pointsMaterial);
383
+ mesh = new Points(geometry, material);
385
384
  } else if (
386
385
  primitive.mode === GL_CONSTANTS.TRIANGLES ||
387
386
  primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP ||
@@ -660,6 +659,10 @@ export class DynamicGltfLoader {
660
659
  this.edgeNodes.push(uniqueNodeId);
661
660
  }
662
661
 
662
+ if (meshDef.extras && meshDef.extras.handle) {
663
+ handle = `${structure.id}_${meshDef.extras.handle}`;
664
+ }
665
+
663
666
  this.nodes.set(uniqueNodeId, {
664
667
  position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
665
668
  nodeIndex: nodeId,
@@ -780,12 +783,12 @@ export class DynamicGltfLoader {
780
783
  });
781
784
  }
782
785
 
783
- createDefaultMaterial() {
784
- if (this.currentPrimitiveMode === GL_CONSTANTS.POINTS) {
786
+ createDefaultMaterial(primitiveMode = undefined) {
787
+ if (primitiveMode === GL_CONSTANTS.POINTS) {
785
788
  return new PointsMaterial({
786
789
  color: new Color(0x808080),
787
790
  size: 0.05,
788
- sizeAttenuation: true,
791
+ sizeAttenuation: false,
789
792
  alphaTest: 0.5,
790
793
  transparent: true,
791
794
  vertexColors: false,
@@ -793,11 +796,25 @@ export class DynamicGltfLoader {
793
796
  depthWrite: false,
794
797
  depthTest: true,
795
798
  });
799
+ } else if (
800
+ primitiveMode === GL_CONSTANTS.LINES ||
801
+ primitiveMode === GL_CONSTANTS.LINE_STRIP ||
802
+ primitiveMode === GL_CONSTANTS.LINE_LOOP
803
+ ) {
804
+ return new LineBasicMaterial({
805
+ color: 0x808080,
806
+ linewidth: 1.0,
807
+ alphaTest: 0.1,
808
+ depthTest: true,
809
+ depthWrite: true,
810
+ transparent: true,
811
+ opacity: 1.0,
812
+ });
796
813
  } else {
797
- return new MeshStandardMaterial({
814
+ return new MeshPhongMaterial({
798
815
  color: 0x808080,
799
- metalness: 0.0,
800
- roughness: 1.0,
816
+ specular: 0x222222,
817
+ shininess: 10,
801
818
  side: DoubleSide,
802
819
  });
803
820
  }
@@ -23,13 +23,14 @@
23
23
 
24
24
  import { Box3, Object3D } from "three";
25
25
 
26
- import { ModelImpl } from "../../model";
26
+ import { ModelImpl } from "../../models/ModelImpl";
27
27
  import { DynamicGltfLoader } from "./DynamicGltfLoader.js";
28
28
 
29
29
  // Dynamic model implementation.
30
30
 
31
31
  export class DynamicModelImpl extends ModelImpl {
32
32
  public gltfLoader: DynamicGltfLoader;
33
+ public modelId: number;
33
34
 
34
35
  override getExtents(target: Box3): Box3 {
35
36
  return target.union(this.gltfLoader.getTotalGeometryExtent());
@@ -55,26 +56,32 @@ export class DynamicModelImpl extends ModelImpl {
55
56
  const handlesSet = new Set(handles);
56
57
  const objects = [];
57
58
  handlesSet.forEach((handle) => {
58
- const handles = this.gltfLoader.handleToObjects.get(handle) || [];
59
+ const handle2 = `${this.modelId}_${handle}`; // <- props fix: Dynamic Loader uses handle with structure ID prefix
60
+ const handles = this.gltfLoader.handleToObjects.get(handle2) || [];
59
61
  objects.push(...Array.from(handles));
60
62
  });
61
63
  return objects;
62
64
  }
63
65
 
66
+ override getHandlesByObjects(objects: Object3D | Object3D[]): string[] {
67
+ const handles = super.getHandlesByObjects(objects);
68
+ return handles.map((x) => x.split("_").pop()); // <- props fix: remove structure ID prefix by the Dynamic Loader
69
+ }
70
+
64
71
  override hideObjects(objects: Object3D | Object3D[]): this {
65
- const handles = this.getHandlesByObjects(objects);
72
+ const handles = super.getHandlesByObjects(objects);
66
73
  this.gltfLoader.hideObjects(handles);
67
74
  return this;
68
75
  }
69
76
 
70
77
  override isolateObjects(objects: Object3D | Object3D[]): this {
71
- const handles = this.getHandlesByObjects(objects);
78
+ const handles = super.getHandlesByObjects(objects);
72
79
  this.gltfLoader.isolateObjects(new Set(handles));
73
80
  return this;
74
81
  }
75
82
 
76
83
  override showObjects(objects: Object3D | Object3D[]): this {
77
- const handles = this.getHandlesByObjects(objects);
84
+ const handles = super.getHandlesByObjects(objects);
78
85
  this.gltfLoader.showObjects(handles);
79
86
  return this;
80
87
  }
@@ -1,4 +1,12 @@
1
- import { TextureLoader, BufferAttribute, Color, DoubleSide, MeshPhongMaterial } from "three";
1
+ import {
2
+ TextureLoader,
3
+ BufferAttribute,
4
+ Color,
5
+ DoubleSide,
6
+ MeshPhongMaterial,
7
+ PointsMaterial,
8
+ LineBasicMaterial,
9
+ } from "three";
2
10
 
3
11
  export const GL_COMPONENT_TYPES = {
4
12
  5120: Int8Array,
@@ -48,6 +56,7 @@ export class GltfStructure {
48
56
  this.textureLoader = new TextureLoader();
49
57
  this.materials = new Map();
50
58
  this.textureCache = new Map();
59
+ this.materialCache = new Map();
51
60
  }
52
61
 
53
62
  async initialize(loadController) {
@@ -346,19 +355,29 @@ export class GltfStructure {
346
355
  await Promise.all(texturePromises);
347
356
  }
348
357
 
349
- async loadMaterials() {
358
+ loadMaterials() {
350
359
  if (!this.json.materials) return this.materials;
351
360
 
352
361
  for (let i = 0; i < this.json.materials.length; i++) {
353
362
  const materialDef = this.json.materials[i];
354
- const material = await this.createMaterial(materialDef);
355
- material.name = materialDef.name;
356
- this.materials.set(i, material);
363
+
364
+ const materialCacheKey = `material_${i}`;
365
+ this.materialCache.set(materialCacheKey, {
366
+ mesh: this.createMaterial(materialDef, GL_CONSTANTS.TRIANGLES),
367
+ points: this.createMaterial(materialDef, GL_CONSTANTS.POINTS),
368
+ lines: this.createMaterial(materialDef, GL_CONSTANTS.LINES),
369
+ });
370
+
371
+ this.materialCache.get(materialCacheKey).mesh.name = materialDef.name;
372
+ this.materialCache.get(materialCacheKey).points.name = materialDef.name;
373
+ this.materialCache.get(materialCacheKey).lines.name = materialDef.name;
374
+
375
+ this.materials.set(i, this.materialCache.get(materialCacheKey).mesh);
357
376
  }
358
377
  return this.materials;
359
378
  }
360
379
 
361
- createMaterial(materialDef) {
380
+ createMaterial(materialDef, primitiveMode = undefined) {
362
381
  const params = {};
363
382
 
364
383
  if (materialDef.pbrMetallicRoughness) {
@@ -373,21 +392,10 @@ export class GltfStructure {
373
392
  }
374
393
  }
375
394
 
376
- params.specular = 0x222222;
377
- params.shininess = 10;
378
- params.reflectivity = 0.05;
379
- params.polygonOffset = true;
380
- params.polygonOffsetFactor = 1;
381
- params.polygonOffsetUnits = 1;
382
-
383
395
  if (materialDef.emissiveFactor) {
384
396
  params.emissive = new Color().fromArray(materialDef.emissiveFactor);
385
397
  }
386
398
 
387
- if (materialDef.normalTexture) {
388
- params.normalMap = this.textureCache.get(materialDef.normalTexture.index);
389
- }
390
-
391
399
  if (materialDef.alphaMode === "BLEND") {
392
400
  params.transparent = true;
393
401
  }
@@ -395,16 +403,61 @@ export class GltfStructure {
395
403
  if (materialDef.doubleSided) {
396
404
  params.side = DoubleSide;
397
405
  }
398
- const material = new MeshPhongMaterial(params);
406
+ let material;
407
+
408
+ if (primitiveMode === GL_CONSTANTS.POINTS) {
409
+ params.sizeAttenuation = false;
410
+ material = new PointsMaterial(params);
411
+ material.sizeAttenuation = false;
412
+ } else if (
413
+ primitiveMode === GL_CONSTANTS.LINES ||
414
+ primitiveMode === GL_CONSTANTS.LINE_STRIP ||
415
+ primitiveMode === GL_CONSTANTS.LINE_LOOP
416
+ ) {
417
+ material = new LineBasicMaterial(params);
418
+ } else {
419
+ params.specular = 0x222222;
420
+ params.shininess = 10;
421
+ params.reflectivity = 0.05;
422
+ params.polygonOffset = true;
423
+ params.polygonOffsetFactor = 1;
424
+ params.polygonOffsetUnits = 1;
425
+
426
+ if (materialDef.normalTexture) {
427
+ params.normalMap = this.textureCache.get(materialDef.normalTexture.index);
428
+ }
429
+
430
+ material = new MeshPhongMaterial(params);
431
+ }
432
+
399
433
  return material;
400
434
  }
401
435
 
436
+ getCachedMaterial(materialIndex, primitiveMode) {
437
+ const materialCacheKey = `material_${materialIndex}`;
438
+ const materialCache = this.materialCache.get(materialCacheKey);
439
+
440
+ if (materialCache) {
441
+ if (primitiveMode === GL_CONSTANTS.POINTS) {
442
+ return materialCache.points;
443
+ } else if (
444
+ primitiveMode === GL_CONSTANTS.LINES ||
445
+ primitiveMode === GL_CONSTANTS.LINE_STRIP ||
446
+ primitiveMode === GL_CONSTANTS.LINE_LOOP
447
+ ) {
448
+ return materialCache.lines;
449
+ } else {
450
+ return materialCache.mesh;
451
+ }
452
+ }
453
+
454
+ return null;
455
+ }
456
+
402
457
  disposeMaterials() {
403
- // Dispose all textures
404
458
  this.textureCache.forEach((texture) => texture.dispose());
405
459
  this.textureCache.clear();
406
460
 
407
- // Dispose all materials
408
461
  this.materials.forEach((material) => {
409
462
  if (material.map) material.map.dispose();
410
463
  if (material.lightMap) material.lightMap.dispose();
@@ -419,6 +472,33 @@ export class GltfStructure {
419
472
  material.dispose();
420
473
  });
421
474
  this.materials.clear();
475
+
476
+ this.materialCache.forEach((materialCache) => {
477
+ if (materialCache.mesh) {
478
+ if (materialCache.mesh.map) materialCache.mesh.map.dispose();
479
+ if (materialCache.mesh.lightMap) materialCache.mesh.lightMap.dispose();
480
+ if (materialCache.mesh.bumpMap) materialCache.mesh.bumpMap.dispose();
481
+ if (materialCache.mesh.normalMap) materialCache.mesh.normalMap.dispose();
482
+ if (materialCache.mesh.specularMap) materialCache.mesh.specularMap.dispose();
483
+ if (materialCache.mesh.envMap) materialCache.mesh.envMap.dispose();
484
+ if (materialCache.mesh.aoMap) materialCache.mesh.aoMap.dispose();
485
+ if (materialCache.mesh.metalnessMap) materialCache.mesh.metalnessMap.dispose();
486
+ if (materialCache.mesh.roughnessMap) materialCache.mesh.roughnessMap.dispose();
487
+ if (materialCache.mesh.emissiveMap) materialCache.mesh.emissiveMap.dispose();
488
+ materialCache.mesh.dispose();
489
+ }
490
+
491
+ if (materialCache.points) {
492
+ if (materialCache.points.map) materialCache.points.map.dispose();
493
+ materialCache.points.dispose();
494
+ }
495
+
496
+ if (materialCache.lines) {
497
+ if (materialCache.lines.map) materialCache.lines.map.dispose();
498
+ materialCache.lines.dispose();
499
+ }
500
+ });
501
+ this.materialCache.clear();
422
502
  }
423
503
 
424
504
  estimateNodeSize(meshIndex) {
@@ -57,12 +57,14 @@ export class GLTFCloudDynamicLoader implements ILoader {
57
57
 
58
58
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
59
59
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
60
+ this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
60
61
 
61
62
  this.gltfLoader.addEventListener("databasechunk", (data) => {
62
63
  const modelImpl = new DynamicModelImpl(scene);
63
64
  modelImpl.loader = this;
64
- modelImpl.gltfLoader = this.gltfLoader;
65
65
  modelImpl.viewer = this.viewer;
66
+ modelImpl.gltfLoader = this.gltfLoader;
67
+ modelImpl.modelId = model.id;
66
68
 
67
69
  this.viewer.scene.add(scene);
68
70
  this.viewer.models.push(modelImpl);
@@ -124,7 +126,7 @@ export class GLTFCloudDynamicLoader implements ILoader {
124
126
  baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
125
127
  };
126
128
 
127
- const structure = new GltfStructure(1);
129
+ const structure = new GltfStructure(model.id);
128
130
  await structure.initialize(loadController);
129
131
 
130
132
  await this.gltfLoader.loadStructure(structure);
@@ -26,7 +26,7 @@ import { Loader } from "@inweb/viewer-core";
26
26
 
27
27
  import { Viewer } from "../Viewer";
28
28
  import { GLTFLoadingManager, GLTFLoadParams } from "./GLTFLoadingManager";
29
- import { ModelImpl } from "../model/ModelImpl";
29
+ import { ModelImpl } from "../models/ModelImpl";
30
30
 
31
31
  export class GLTFFileLoader extends Loader {
32
32
  public viewer: Viewer;
@@ -0,0 +1,67 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
3
+ // All rights reserved.
4
+ //
5
+ // This software and its documentation and related materials are owned by
6
+ // the Alliance. The software may only be incorporated into application
7
+ // programs owned by members of the Alliance, subject to a signed
8
+ // Membership Agreement and Supplemental Software License Agreement with the
9
+ // Alliance. The structure and organization of this software are the valuable
10
+ // trade secrets of the Alliance and its suppliers. The software is also
11
+ // protected by copyright law and international treaty provisions. Application
12
+ // programs incorporating this software must include the following statement
13
+ // with their copyright notices:
14
+ //
15
+ // This application incorporates Open Design Alliance software pursuant to a
16
+ // license agreement with Open Design Alliance.
17
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
18
+ // All rights reserved.
19
+ //
20
+ // By use of this software, its documentation or related materials, you
21
+ // acknowledge and accept the above terms.
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+
24
+ import { Box3, Object3D } from "three";
25
+ import { ILoader, IViewer } from "@inweb/viewer-core";
26
+
27
+ /**
28
+ * Model interface.
29
+ */
30
+ export interface IModelImpl {
31
+ handle: string;
32
+ scene: Object3D;
33
+ loader: ILoader;
34
+ viewer: IViewer;
35
+
36
+ dispose(): void;
37
+
38
+ getExtents(target: Box3): Box3;
39
+
40
+ getObjects(): Object3D[];
41
+
42
+ getVisibleObjects(): Object3D[];
43
+
44
+ hasObject(objects: Object3D): boolean;
45
+
46
+ getOwnObjects(objects: Object3D | Object3D[]): Object3D[];
47
+
48
+ getObjectsByHandles(handles: string | string[]): Object3D[];
49
+
50
+ getHandlesByObjects(objects: Object3D | Object3D[]): string[];
51
+
52
+ hideObjects(objects: Object3D | Object3D[]): this;
53
+
54
+ hideAllObjects(): this;
55
+
56
+ isolateObjects(objects: Object3D | Object3D[]): this;
57
+
58
+ showObjects(objects: Object3D | Object3D[]): this;
59
+
60
+ showAllObjects(): this;
61
+
62
+ showOriginalObjects(objects: Object3D | Object3D[]): this;
63
+
64
+ hideOriginalObjects(objects: Object3D | Object3D[]): this;
65
+
66
+ explode(scale: number, coeff?: number): this;
67
+ }
@@ -0,0 +1,214 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
3
+ // All rights reserved.
4
+ //
5
+ // This software and its documentation and related materials are owned by
6
+ // the Alliance. The software may only be incorporated into application
7
+ // programs owned by members of the Alliance, subject to a signed
8
+ // Membership Agreement and Supplemental Software License Agreement with the
9
+ // Alliance. The structure and organization of this software are the valuable
10
+ // trade secrets of the Alliance and its suppliers. The software is also
11
+ // protected by copyright law and international treaty provisions. Application
12
+ // programs incorporating this software must include the following statement
13
+ // with their copyright notices:
14
+ //
15
+ // This application incorporates Open Design Alliance software pursuant to a
16
+ // license agreement with Open Design Alliance.
17
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
18
+ // All rights reserved.
19
+ //
20
+ // By use of this software, its documentation or related materials, you
21
+ // acknowledge and accept the above terms.
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+
24
+ import { Box3, Object3D, Vector3 } from "three";
25
+ import { ILoader } from "@inweb/viewer-core";
26
+
27
+ import { IModelImpl } from "./IModelImpl";
28
+ import { Viewer } from "../Viewer";
29
+
30
+ // Basic model implementation.
31
+
32
+ export class ModelImpl implements IModelImpl {
33
+ public handle: string;
34
+ public scene: Object3D;
35
+ public loader: ILoader;
36
+ public viewer: Viewer;
37
+
38
+ constructor(scene: Object3D) {
39
+ this.handle = "1";
40
+ this.scene = scene;
41
+ }
42
+
43
+ dispose() {
44
+ function disposeMaterial(material: any) {
45
+ // if (material.alphaMap) material.alphaMap.dispose();
46
+ // if (material.anisotropyMap) material.anisotropyMap.dispose();
47
+ // if (material.aoMap) material.aoMap.dispose();
48
+ // if (material.bumpMap) material.bumpMap.dispose();
49
+ // if (material.clearcoatMap) material.clearcoatMap.dispose();
50
+ // if (material.clearcoatNormalMap) material.clearcoatNormalMap.dispose();
51
+ // if (material.clearcoatRoughnessMap) material.clearcoatRoughnessMap.dispose();
52
+ // if (material.displacementMap) material.displacementMap.dispose();
53
+ // if (material.emissiveMap) material.emissiveMap.dispose();
54
+ // if (material.gradientMap) material.gradientMap.dispose();
55
+ // if (material.envMap) material.envMap.dispose();
56
+ // if (material.iridescenceMap) material.iridescenceMap.dispose();
57
+ // if (material.lightMap) material.lightMap.dispose();
58
+ // if (material.map) material.map.dispose();
59
+ // if (material.metalnessMap) material.metalnessMap.dispose();
60
+ // if (material.normalMap) material.normalMap.dispose();
61
+ // if (material.roughnessMap) material.roughnessMap.dispose();
62
+ // if (material.sheenColorMap) material.sheenColorMap.dispose();
63
+ // if (material.sheenRoughnessMap) material.sheenRoughnessMap.dispose();
64
+ // if (material.specularMap) material.specularMap.dispose();
65
+ // if (material.thicknessMap) material.thicknessMap.dispose();
66
+ // if (material.transmissionMap) material.transmissionMap.dispose();
67
+ material.dispose();
68
+ }
69
+
70
+ function disposeMaterials(material: any) {
71
+ const materials = Array.isArray(material) ? material : [material];
72
+ materials.forEach((material: any) => disposeMaterial(material));
73
+ }
74
+
75
+ function disposeObject(object: any) {
76
+ if (object.geometry) object.geometry.dispose();
77
+ if (object.material) disposeMaterials(object.material);
78
+ }
79
+
80
+ this.scene.traverse(disposeObject);
81
+ this.scene.clear();
82
+ }
83
+
84
+ getExtents(target: Box3): Box3 {
85
+ this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
86
+ return target;
87
+ }
88
+
89
+ getObjects(): Object3D[] {
90
+ const objects = [];
91
+ this.scene.traverse((object) => objects.push(object));
92
+ return objects;
93
+ }
94
+
95
+ getVisibleObjects(): Object3D[] {
96
+ const objects = [];
97
+ this.scene.traverseVisible((object) => objects.push(object));
98
+ return objects;
99
+ }
100
+
101
+ hasObject(object: Object3D): boolean {
102
+ while (object) {
103
+ if (object === this.scene) return true;
104
+ object = object.parent;
105
+ }
106
+ return false;
107
+ }
108
+
109
+ getOwnObjects(objects: Object3D | Object3D[]): Object3D[] {
110
+ if (!Array.isArray(objects)) objects = [objects];
111
+ return objects.filter((object) => this.hasObject(object));
112
+ }
113
+
114
+ getObjectsByHandles(handles: string | string[]): Object3D[] {
115
+ const handleSet = new Set(handles);
116
+ const objects = [];
117
+ this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
118
+ return objects;
119
+ }
120
+
121
+ getHandlesByObjects(objects: Object3D | Object3D[]): string[] {
122
+ if (!Array.isArray(objects)) objects = [objects];
123
+ const handlesSet = new Set<string>();
124
+ this.getOwnObjects(objects).forEach((object) => handlesSet.add(object.userData.handle));
125
+ return Array.from(handlesSet);
126
+ }
127
+
128
+ hideObjects(objects: Object3D | Object3D[]): this {
129
+ if (!Array.isArray(objects)) objects = [objects];
130
+ this.getOwnObjects(objects).forEach((object) => (object.visible = false));
131
+ return this;
132
+ }
133
+
134
+ hideAllObjects(): this {
135
+ return this.isolateObjects([]);
136
+ }
137
+
138
+ isolateObjects(objects: Object3D | Object3D[]): this {
139
+ if (!Array.isArray(objects)) objects = [objects];
140
+ const visibleSet = new Set(objects);
141
+ this.getOwnObjects(objects).forEach((object) => object.traverseAncestors((parent) => visibleSet.add(parent)));
142
+ this.scene.traverse((object) => (object.visible = visibleSet.has(object)));
143
+ return this;
144
+ }
145
+
146
+ showObjects(objects: Object3D | Object3D[]): this {
147
+ if (!Array.isArray(objects)) objects = [objects];
148
+ this.getOwnObjects(objects).forEach((object) => {
149
+ object.visible = true;
150
+ object.traverseAncestors((parent) => (parent.visible = true));
151
+ });
152
+ return this;
153
+ }
154
+
155
+ showAllObjects(): this {
156
+ this.scene.traverse((object) => (object.visible = true));
157
+ return this;
158
+ }
159
+
160
+ showOriginalObjects(objects: Object3D | Object3D[]): this {
161
+ return this;
162
+ }
163
+
164
+ hideOriginalObjects(objects: Object3D | Object3D[]): this {
165
+ return this;
166
+ }
167
+
168
+ explode(scale = 0, coeff = 4): this {
169
+ function calcExplodeDepth(object: Object3D, depth: number): number {
170
+ let res = depth;
171
+ object.children.forEach((x: Object3D) => {
172
+ const objectDepth = calcExplodeDepth(x, depth + 1);
173
+ if (res < objectDepth) res = objectDepth;
174
+ });
175
+
176
+ object.userData.originalPosition = object.position.clone();
177
+ object.userData.originalCenter = new Box3().setFromObject(object).getCenter(new Vector3());
178
+ object.userData.isExplodeLocked = depth > 2 && object.children.length === 0;
179
+
180
+ return res;
181
+ }
182
+
183
+ scale /= 100;
184
+
185
+ if (!this.scene.userData.explodeDepth) this.scene.userData.explodeDepth = calcExplodeDepth(this.scene, 1);
186
+ const maxDepth = this.scene.userData.explodeDepth;
187
+
188
+ const scaledExplodeDepth = scale * maxDepth + 1;
189
+ const explodeDepth = 0 | scaledExplodeDepth;
190
+ const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
191
+
192
+ function explodeObject(object: Object3D, depth: number) {
193
+ object.position.copy(object.userData.originalPosition);
194
+
195
+ if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
196
+ let objectScale = scale * coeff;
197
+ if (depth === explodeDepth) objectScale *= currentSegmentFraction;
198
+
199
+ const parentCenter = object.parent.userData.originalCenter;
200
+ const objectCenter = object.userData.originalCenter;
201
+ const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
202
+
203
+ object.position.add(objectOffset);
204
+ }
205
+
206
+ object.children.forEach((x) => explodeObject(x, depth + 1));
207
+ }
208
+
209
+ explodeObject(this.scene, 0);
210
+ this.scene.updateMatrixWorld();
211
+
212
+ return this;
213
+ }
214
+ }