@inweb/viewer-three 27.4.7 → 27.6.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.
Files changed (94) hide show
  1. package/dist/extensions/components/AxesHelperComponent.js +3 -0
  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 +3 -0
  5. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -1
  6. package/dist/extensions/components/ExtentsHelperComponent.js +6 -2
  7. package/dist/extensions/components/ExtentsHelperComponent.js.map +1 -1
  8. package/dist/extensions/components/ExtentsHelperComponent.min.js +1 -1
  9. package/dist/extensions/components/ExtentsHelperComponent.module.js +6 -2
  10. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +1 -1
  11. package/dist/extensions/components/GridHelperComponent.js +1 -0
  12. package/dist/extensions/components/GridHelperComponent.js.map +1 -1
  13. package/dist/extensions/components/GridHelperComponent.min.js +1 -1
  14. package/dist/extensions/components/GridHelperComponent.module.js +1 -0
  15. package/dist/extensions/components/GridHelperComponent.module.js.map +1 -1
  16. package/dist/extensions/components/LightHelperComponent.js +2 -1
  17. package/dist/extensions/components/LightHelperComponent.js.map +1 -1
  18. package/dist/extensions/components/LightHelperComponent.min.js +1 -1
  19. package/dist/extensions/components/LightHelperComponent.module.js +2 -1
  20. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -1
  21. package/dist/extensions/components/StatsPanelComponent.js +0 -1
  22. package/dist/extensions/components/StatsPanelComponent.js.map +1 -1
  23. package/dist/extensions/components/StatsPanelComponent.min.js +1 -1
  24. package/dist/extensions/components/StatsPanelComponent.module.js +0 -1
  25. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -1
  26. package/dist/extensions/loaders/GLTFCloudLoader.js +7 -2
  27. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -1
  28. package/dist/extensions/loaders/GLTFCloudLoader.min.js +1 -1
  29. package/dist/extensions/loaders/GLTFCloudLoader.module.js +7 -2
  30. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -1
  31. package/dist/extensions/loaders/GLTFFileLoader.js +2 -1
  32. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -1
  33. package/dist/extensions/loaders/GLTFFileLoader.min.js +1 -1
  34. package/dist/extensions/loaders/GLTFFileLoader.module.js +2 -1
  35. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -1
  36. package/dist/extensions/loaders/IFCXLoader.js +10 -5
  37. package/dist/extensions/loaders/IFCXLoader.js.map +1 -1
  38. package/dist/extensions/loaders/IFCXLoader.min.js +1 -1
  39. package/dist/extensions/loaders/IFCXLoader.module.js +10 -5
  40. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -1
  41. package/dist/viewer-three.js +1901 -569
  42. package/dist/viewer-three.js.map +1 -1
  43. package/dist/viewer-three.min.js +4 -4
  44. package/dist/viewer-three.module.js +1366 -451
  45. package/dist/viewer-three.module.js.map +1 -1
  46. package/extensions/components/AxesHelperComponent.ts +3 -0
  47. package/extensions/components/ExtentsHelperComponent.ts +5 -2
  48. package/extensions/components/GridHelperComponent.ts +1 -0
  49. package/extensions/components/LightHelperComponent.ts +2 -1
  50. package/extensions/components/StatsPanelComponent.ts +0 -1
  51. package/extensions/loaders/GLTFCloudLoader.ts +8 -2
  52. package/extensions/loaders/GLTFFileLoader.ts +3 -2
  53. package/extensions/loaders/IFCX/IFCXFileLoader.ts +11 -5
  54. package/lib/Viewer/Viewer.d.ts +6 -8
  55. package/lib/Viewer/components/CameraComponent.d.ts +1 -1
  56. package/lib/Viewer/components/ClippingPlaneComponent.d.ts +8 -0
  57. package/lib/Viewer/components/HighlighterComponent.d.ts +2 -2
  58. package/lib/Viewer/components/InfoComponent.d.ts +1 -1
  59. package/lib/Viewer/components/SectionsComponent.d.ts +15 -0
  60. package/lib/Viewer/components/WCSHelperComponent.d.ts +2 -2
  61. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +6 -6
  62. package/lib/Viewer/draggers/OrbitDragger.d.ts +1 -1
  63. package/lib/Viewer/measurement/Snapper.d.ts +4 -4
  64. package/package.json +5 -5
  65. package/src/Viewer/Viewer.ts +59 -48
  66. package/src/Viewer/commands/GetSelected2.ts +1 -1
  67. package/src/Viewer/commands/SetSelected.ts +1 -1
  68. package/src/Viewer/commands/index.ts +1 -1
  69. package/src/Viewer/components/BackgroundComponent.ts +2 -1
  70. package/src/Viewer/components/CameraComponent.ts +6 -7
  71. package/src/Viewer/components/CanvasRemoveComponent.ts +0 -1
  72. package/src/Viewer/{scenes/Helpers.ts → components/ClippingPlaneComponent.ts} +22 -12
  73. package/src/Viewer/components/HighlighterComponent.ts +9 -5
  74. package/src/Viewer/components/HighlighterUtils.ts +2 -2
  75. package/src/Viewer/components/InfoComponent.ts +4 -4
  76. package/src/Viewer/components/SectionsComponent.ts +119 -0
  77. package/src/Viewer/components/SelectionComponent.ts +5 -3
  78. package/src/Viewer/components/WCSHelperComponent.ts +8 -6
  79. package/src/Viewer/components/index.ts +4 -0
  80. package/src/Viewer/draggers/CuttingPlaneDragger.ts +57 -34
  81. package/src/Viewer/draggers/MeasureLineDragger.ts +1 -1
  82. package/src/Viewer/draggers/OrbitDragger.ts +3 -3
  83. package/src/Viewer/helpers/SectionsHelper.js +1061 -0
  84. package/src/Viewer/helpers/WCSHelper.ts +31 -5
  85. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +417 -92
  86. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +19 -14
  87. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +76 -9
  88. package/src/Viewer/loaders/GLTFBinaryParser.ts +2 -2
  89. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +3 -2
  90. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +6 -4
  91. package/src/Viewer/measurement/Snapper.ts +6 -7
  92. package/src/Viewer/models/ModelImpl.ts +65 -28
  93. package/lib/Viewer/scenes/Helpers.d.ts +0 -7
  94. package/src/Viewer/postprocessing/SSAARenderPass.js +0 -245
@@ -62,7 +62,7 @@ export class DynamicModelImpl extends ModelImpl {
62
62
  }
63
63
 
64
64
  override getObjects(): Object3D[] {
65
- const objects = [];
65
+ const objects: Object3D[] = [];
66
66
  this.gltfLoader.originalObjects.forEach((object: Object3D) => {
67
67
  objects.push(object);
68
68
  });
@@ -78,7 +78,7 @@ export class DynamicModelImpl extends ModelImpl {
78
78
 
79
79
  const handlesSet = new Set(handles);
80
80
 
81
- const objects = [];
81
+ const objects: Object3D[] = [];
82
82
  handlesSet.forEach((handle) => {
83
83
  objects.push(this.gltfLoader.getObjectsByHandle(handle));
84
84
  });
@@ -153,6 +153,13 @@ export class DynamicModelImpl extends ModelImpl {
153
153
  return target;
154
154
  };
155
155
 
156
+ const calcObjectOffset = (object: Object3D, target: Vector3): Vector3 => {
157
+ const parent = object.parent;
158
+ if (!parent || parent.userData.originalCenter === undefined) return target;
159
+
160
+ return target.subVectors(object.userData.originalCenter, parent.userData.originalCenter);
161
+ };
162
+
156
163
  const calcObjectDepth = (object: Object3D): number => {
157
164
  if (object.userData.depth !== undefined) return object.userData.depth;
158
165
 
@@ -162,17 +169,19 @@ export class DynamicModelImpl extends ModelImpl {
162
169
  object.userData.depth = depth;
163
170
  object.userData.originalPosition = object.position.clone();
164
171
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
172
+ object.userData.originalOffset = calcObjectOffset(object, new Vector3());
165
173
 
166
174
  return depth;
167
175
  };
168
176
 
169
177
  const explodeScale = scale / 100;
170
178
  const explodeRoot = this.scene.children[0];
179
+ const explodeObjects = this.getObjects();
171
180
 
172
- if (!explodeRoot.userData.explodeDepth) {
181
+ if (explodeRoot.userData.explodeDepth === undefined) {
173
182
  let maxDepth = 0;
174
183
 
175
- this.gltfLoader.originalObjects.forEach((object: Object3D) => {
184
+ explodeObjects.forEach((object: Object3D) => {
176
185
  const depth = calcObjectDepth(object);
177
186
  if (depth > maxDepth) maxDepth = depth;
178
187
  });
@@ -187,11 +196,11 @@ export class DynamicModelImpl extends ModelImpl {
187
196
 
188
197
  const offsetCache = new Map();
189
198
 
190
- const calcObjectOffset = (object: Object3D, target: Vector3): Vector3 => {
199
+ const calcExplodeOffset = (object: Object3D, target: Vector3): Vector3 => {
191
200
  if (offsetCache.has(object)) return target.copy(offsetCache.get(object));
192
201
 
193
202
  const parent = object.parent;
194
- if (parent && object !== explodeRoot) calcObjectOffset(parent, target);
203
+ if (parent && object !== explodeRoot) calcExplodeOffset(parent, target);
195
204
 
196
205
  const depth = object.userData.depth;
197
206
 
@@ -199,11 +208,7 @@ export class DynamicModelImpl extends ModelImpl {
199
208
  let objectScale = explodeScale * coeff;
200
209
  if (depth === explodeDepth) objectScale *= currentSegmentFraction;
201
210
 
202
- const parentCenter = parent.userData.originalCenter;
203
- const objectCenter = object.userData.originalCenter;
204
- const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
205
-
206
- target.add(localOffset);
211
+ target.addScaledVector(object.userData.originalOffset, objectScale);
207
212
  }
208
213
 
209
214
  offsetCache.set(object, target.clone());
@@ -213,9 +218,9 @@ export class DynamicModelImpl extends ModelImpl {
213
218
 
214
219
  const transformMap = new Map();
215
220
 
216
- this.gltfLoader.originalObjects.forEach((object: Object3D) => {
217
- const globalOffset = calcObjectOffset(object, new Vector3());
218
- transformMap.set(object, new Matrix4().makeTranslation(globalOffset));
221
+ explodeObjects.forEach((object: Object3D) => {
222
+ const offset = calcExplodeOffset(object, new Vector3());
223
+ transformMap.set(object, new Matrix4().makeTranslation(offset));
219
224
  });
220
225
 
221
226
  this.gltfLoader.applyObjectTransforms(transformMap);
@@ -88,6 +88,8 @@ export class GltfStructure {
88
88
  this._nextObjectId = 1;
89
89
  this.loadingAborted = false;
90
90
  this.criticalError = null;
91
+ // Holds decoded base64 buffer when the .gltf has an embedded data URI
92
+ this.embeddedBinaryChunk = null;
91
93
  }
92
94
 
93
95
  async initialize(loader) {
@@ -99,21 +101,53 @@ export class GltfStructure {
99
101
 
100
102
  this.json = json;
101
103
  this.loader = loader;
102
- this.uri = this.json.buffers[0].uri || "";
104
+
105
+ const bufferUri = this.json.buffers?.[0]?.uri || "";
106
+
107
+ // Detect embedded data URI (e.g. "data:application/octet-stream;base64,...")
108
+ // and decode it once into memory. The buffer URI is then cleared so that
109
+ // scheduleRequest serves data from memory instead of doing network requests.
110
+ if (bufferUri.startsWith("data:")) {
111
+ this.embeddedBinaryChunk = await this._decodeDataUri(bufferUri);
112
+ this.uri = "";
113
+ } else {
114
+ this.uri = bufferUri;
115
+ }
116
+ }
117
+
118
+ // Decode a data URI to ArrayBuffer using the browser's native fetch() —
119
+ // much faster than atob() + JS loop for large (10+ MB) base64 buffers.
120
+ async _decodeDataUri(dataUri) {
121
+ try {
122
+ const response = await fetch(dataUri);
123
+ return await response.arrayBuffer();
124
+ } catch (e) {
125
+ throw new Error(`DynamicLoader: Failed to decode embedded data URI: ${e.message}`);
126
+ }
103
127
  }
104
128
 
105
129
  clear() {
106
- this.json = null;
107
- this.loadController = null;
108
- this.pendingRequests = [];
109
130
  if (this.batchTimeout) {
110
131
  clearTimeout(this.batchTimeout);
111
132
  this.batchTimeout = null;
112
133
  }
113
134
 
114
- this.disposeMaterials();
115
- this.textureCache.clear();
116
- this.materials.clear();
135
+ this._rejectPendingRequests();
136
+
137
+ if (this.disposeMaterials) {
138
+ try {
139
+ this.disposeMaterials();
140
+ } catch (e) {
141
+ console.warn("DynamicLoader: error during disposeMaterials in clear():", e);
142
+ }
143
+ }
144
+ if (this.textureCache) this.textureCache.clear();
145
+ if (this.materials) this.materials.clear();
146
+
147
+ this.embeddedBinaryChunk = null;
148
+ this.json = null;
149
+ this.loadController = null;
150
+ this.uri = "";
117
151
 
118
152
  this.activeChunkLoads = 0;
119
153
  this.chunkQueue = [];
@@ -121,11 +155,38 @@ export class GltfStructure {
121
155
  this.criticalError = null;
122
156
  }
123
157
 
158
+ _rejectPendingRequests() {
159
+ const pending = this.pendingRequests;
160
+ this.pendingRequests = [];
161
+ if (!pending || pending.length === 0) return;
162
+
163
+ const cancelError = new Error("DynamicLoader: Structure cleared while requests pending");
164
+ for (let i = 0; i < pending.length; i++) {
165
+ const item = pending[i];
166
+ if (item && typeof item._reject === "function") {
167
+ try {
168
+ item._reject(cancelError);
169
+ } catch {
170
+ // Consumer may have already been disposed
171
+ }
172
+ }
173
+ }
174
+ }
175
+
124
176
  getJson() {
125
177
  return this.json;
126
178
  }
127
179
 
128
180
  scheduleRequest(request) {
181
+ // Fast path: buffer was an embedded data URI, decoded once into memory
182
+ if (this.embeddedBinaryChunk && !this.uri) {
183
+ return Promise.resolve({
184
+ buffer: this.embeddedBinaryChunk,
185
+ relOffset: request.offset,
186
+ length: request.length,
187
+ });
188
+ }
189
+
129
190
  return new Promise((resolve, reject) => {
130
191
  if (this.loadingAborted) {
131
192
  reject(
@@ -441,8 +502,13 @@ export class GltfStructure {
441
502
  return await this.textureLoader.loadAsync(fullUrl);
442
503
  } else if (image.bufferView !== undefined) {
443
504
  const bufferView = this.json.bufferViews[image.bufferView];
444
- const array = await this.getBufferView(bufferView.byteOffset || 0, bufferView.byteLength, 5121);
445
- const blob = new Blob([array], { type: image.mimeType });
505
+ const { buffer, relOffset } = await this.getBufferView(
506
+ bufferView.byteOffset || 0,
507
+ bufferView.byteLength,
508
+ 5121
509
+ );
510
+ const imageBytes = new Uint8Array(buffer, relOffset, bufferView.byteLength);
511
+ const blob = new Blob([imageBytes], { type: image.mimeType });
446
512
  const url = URL.createObjectURL(blob);
447
513
  const texture = await this.textureLoader.loadAsync(url);
448
514
  URL.revokeObjectURL(url);
@@ -479,6 +545,7 @@ export class GltfStructure {
479
545
  })
480
546
  );
481
547
  }
548
+ await this.flushBufferRequests();
482
549
  await Promise.all(texturePromises);
483
550
  }
484
551
 
@@ -86,10 +86,10 @@ export class GLTFBinaryParser {
86
86
  offset += chunkLength;
87
87
  }
88
88
 
89
- if (typeof this.content === "undefined") {
89
+ if (this.content === undefined) {
90
90
  throw new Error("GLTFBinaryParser: JSON content not found.");
91
91
  }
92
- if (typeof this.body === "undefined") {
92
+ if (this.body === undefined) {
93
93
  throw new Error("GLTFBinaryParser: Binary buffer chunk not found or type not supported.");
94
94
  }
95
95
  }
@@ -58,9 +58,10 @@ export class GLTFCloudDynamicLoader extends Loader {
58
58
  const scene = new Group();
59
59
 
60
60
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
61
- this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
61
+ this.gltfLoader.setMemoryLimit(this.viewer.options.memoryLimit);
62
62
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
63
- // this.gltfLoader.setMaxConcurrentChunks(this.viewer.options.maxConcurrentChunks);
63
+ this.gltfLoader.setMaxConcurrentChunks(params.maxConcurrentChunks);
64
+ this.gltfLoader.setDracoLoader(params.dracoLoader);
64
65
 
65
66
  const modelImpl = new DynamicModelImpl(scene);
66
67
  modelImpl.id = model.file.id;
@@ -49,21 +49,23 @@ export class GLTFFileDynamicLoader extends Loader {
49
49
  if (this.manager) this.manager.dispose();
50
50
  }
51
51
 
52
- override isSupport(file: any, format?: string): boolean {
52
+ override isSupport(file: any, format = ""): boolean {
53
53
  return (
54
54
  (typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
55
55
  /(gltf|glb)$/i.test(format)
56
56
  );
57
57
  }
58
58
 
59
- override async load(file: any, format?: string, params?: GLTFLoadParams): Promise<this> {
59
+ override async load(file: any, format?: string, params: GLTFLoadParams = {}): Promise<this> {
60
60
  this.manager = new GLTFLoadingManager(file, params);
61
61
 
62
62
  const scene = new Group();
63
63
 
64
64
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
65
- this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
66
- this.gltfLoader.visibleEdges = this.viewer.options.edgeModel;
65
+ this.gltfLoader.setMemoryLimit(this.viewer.options.memoryLimit);
66
+ this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
67
+ this.gltfLoader.setMaxConcurrentChunks(params.maxConcurrentChunks);
68
+ this.gltfLoader.setDracoLoader(params.dracoLoader);
67
69
 
68
70
  const modelImpl = new DynamicModelImpl(scene);
69
71
  modelImpl.id = params.modelId || this.extractFileName(file);
@@ -32,7 +32,6 @@ import {
32
32
  Raycaster,
33
33
  Vector2,
34
34
  Vector3,
35
- WebGLRenderer,
36
35
  } from "three";
37
36
 
38
37
  const DESKTOP_SNAP_DISTANCE = 10;
@@ -47,16 +46,16 @@ const _projection = new Vector3();
47
46
 
48
47
  export class Snapper {
49
48
  public camera: Camera;
50
- public renderer: WebGLRenderer;
49
+ public clippingPlanes: Plane[];
51
50
  public canvas: HTMLCanvasElement;
52
51
  public threshold: number;
53
52
  private raycaster: Raycaster;
54
53
  private detectRadiusInPixels: number;
55
54
  private edgesCache: WeakMap<any, EdgesGeometry>;
56
55
 
57
- constructor(camera: Camera, renderer: WebGLRenderer, canvas: HTMLCanvasElement) {
56
+ constructor(camera: Camera, clippingPlanes: Plane[], canvas: HTMLCanvasElement) {
58
57
  this.camera = camera;
59
- this.renderer = renderer;
58
+ this.clippingPlanes = clippingPlanes;
60
59
  this.canvas = canvas;
61
60
 
62
61
  this.threshold = 0.0001;
@@ -109,7 +108,7 @@ export class Snapper {
109
108
  let intersects = this.raycaster.intersectObjects(objects, recursive);
110
109
 
111
110
  if (clip) {
112
- const clippingPlanes = this.renderer.clippingPlanes || [];
111
+ const clippingPlanes = this.clippingPlanes;
113
112
  clippingPlanes.forEach((plane: Plane) => {
114
113
  intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
115
114
  });
@@ -152,7 +151,7 @@ export class Snapper {
152
151
  return 0.1;
153
152
  }
154
153
 
155
- getSnapPoint(mouse: Vector2, objects: Object3D[]): Vector3 {
154
+ getSnapPoint(mouse: Vector2, objects: Object3D[]): Vector3 | undefined {
156
155
  const intersections = this.getPointerIntersects(mouse, objects);
157
156
  if (intersections.length === 0) return undefined;
158
157
 
@@ -167,7 +166,7 @@ export class Snapper {
167
166
  const intersectionPoint = intersections[0].point;
168
167
  const localPoint = object.worldToLocal(intersectionPoint.clone());
169
168
 
170
- let snapPoint: Vector3;
169
+ let snapPoint: Vector3 | undefined = undefined;
171
170
  let snapDistance = this.getDetectRadius(intersectionPoint);
172
171
 
173
172
  const geometry = object.geometry;
@@ -36,6 +36,7 @@ export class ModelImpl implements IModelImpl {
36
36
  private originalObjects: Set<Object3D>;
37
37
 
38
38
  constructor(scene: Object3D) {
39
+ this.id = "";
39
40
  this.scene = scene;
40
41
 
41
42
  this.handleToObjects = new Map();
@@ -94,8 +95,8 @@ export class ModelImpl implements IModelImpl {
94
95
  if (object.material) disposeMaterials(object.material);
95
96
  }
96
97
 
97
- this.handleToObjects = undefined;
98
- this.originalObjects = undefined;
98
+ this.handleToObjects.clear();
99
+ this.originalObjects.clear();
99
100
 
100
101
  this.scene.traverse(disposeObject);
101
102
  this.scene.clear();
@@ -275,7 +276,30 @@ export class ModelImpl implements IModelImpl {
275
276
  }
276
277
 
277
278
  getExtents(target: Box3): Box3 {
278
- this.scene.traverseVisible((object) => target.expandByObject(object));
279
+ const _box = new Box3();
280
+
281
+ function expandByObject(object: any, target: Box3) {
282
+ if (!object.geometry) return;
283
+
284
+ object.updateWorldMatrix(false, false);
285
+
286
+ // Use object bounds for the BatchedMesh, InstancedMesh and SkinnedMesh, use geometry
287
+ // bounds for others. See Box3.expandByObject() for more details.
288
+
289
+ if (object.boundingBox !== undefined) {
290
+ if (object.boundingBox === null) object.computeBoundingBox();
291
+ _box.copy(object.boundingBox);
292
+ } else {
293
+ if (object.geometry.boundingBox === null) object.geometry.computeBoundingBox();
294
+ _box.copy(object.geometry.boundingBox);
295
+ }
296
+
297
+ _box.applyMatrix4(object.matrixWorld);
298
+
299
+ target.union(_box);
300
+ }
301
+
302
+ this.scene.traverseVisible((object) => expandByObject(object, target));
279
303
  return target;
280
304
  }
281
305
 
@@ -284,15 +308,15 @@ export class ModelImpl implements IModelImpl {
284
308
  }
285
309
 
286
310
  getVisibleObjects(): Object3D[] {
287
- const objects = [];
288
- this.scene.traverseVisible((object) => objects.push(object));
289
- return objects.filter((object) => object.userData.handle);
311
+ const objects: Object3D[] = [];
312
+ this.scene.traverseVisible((object) => object.userData.handle && objects.push(object));
313
+ return objects;
290
314
  }
291
315
 
292
316
  getObjectsByHandles(handles: string | string[]): Object3D[] {
293
317
  if (!Array.isArray(handles)) handles = [handles];
294
318
 
295
- const ownHandles = [];
319
+ const ownHandles: string[] = [];
296
320
  handles.forEach((handle) => {
297
321
  const index = handle.indexOf(":");
298
322
  if (index !== -1) {
@@ -304,7 +328,7 @@ export class ModelImpl implements IModelImpl {
304
328
 
305
329
  const handlesSet = new Set<string>(ownHandles);
306
330
 
307
- const objects = [];
331
+ const objects: Object3D[][] = [];
308
332
  handlesSet.forEach((handle) => {
309
333
  objects.push(Array.from(this.handleToObjects.get(handle) || []));
310
334
  });
@@ -403,25 +427,40 @@ export class ModelImpl implements IModelImpl {
403
427
  return target;
404
428
  };
405
429
 
406
- function calcExplodeDepth(object: Object3D, depth: number): number {
407
- let result = depth;
430
+ const calcObjectOffset = (object: Object3D, target: Vector3): Vector3 => {
431
+ const parent = object.parent;
432
+ if (!parent || parent.userData.originalCenter === undefined) return target;
408
433
 
409
- object.children.forEach((x: Object3D) => {
410
- const objectDepth = calcExplodeDepth(x, depth + 1);
411
- if (result < objectDepth) result = objectDepth;
412
- });
434
+ return target.subVectors(object.userData.originalCenter, parent.userData.originalCenter);
435
+ };
436
+
437
+ const calcObjectDepth = (object: Object3D): number => {
438
+ if (object.userData.depth !== undefined) return object.userData.depth;
413
439
 
440
+ const parent = object.parent;
441
+ const depth = parent && object !== explodeRoot ? calcObjectDepth(parent) + 1 : 0;
442
+
443
+ object.userData.depth = depth;
414
444
  object.userData.originalPosition = object.position.clone();
415
445
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
446
+ object.userData.originalOffset = calcObjectOffset(object, new Vector3());
416
447
 
417
- return result;
418
- }
448
+ return depth;
449
+ };
419
450
 
420
451
  const explodeScale = scale / 100;
421
452
  const explodeRoot = this.scene;
453
+ const explodeObjects = this.getObjects();
454
+
455
+ if (explodeRoot.userData.explodeDepth === undefined) {
456
+ let maxDepth = 0;
457
+
458
+ explodeObjects.forEach((object: Object3D) => {
459
+ const depth = calcObjectDepth(object);
460
+ if (depth > maxDepth) maxDepth = depth;
461
+ });
422
462
 
423
- if (!explodeRoot.userData.explodeDepth) {
424
- explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
463
+ explodeRoot.userData.explodeDepth = maxDepth;
425
464
  }
426
465
 
427
466
  const maxDepth = explodeRoot.userData.explodeDepth;
@@ -429,27 +468,25 @@ export class ModelImpl implements IModelImpl {
429
468
  const explodeDepth = 0 | scaledExplodeDepth;
430
469
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
431
470
 
432
- function explodeObject(object: any, depth: number) {
471
+ const explodeObject = (object: any) => {
433
472
  if (object.isCamera) return;
434
- if (object.userData.isHighlightWireframe) return;
435
473
 
436
474
  object.position.copy(object.userData.originalPosition);
437
475
 
476
+ const depth = object.userData.depth;
477
+
438
478
  if (depth > 0 && depth <= explodeDepth) {
439
479
  let objectScale = explodeScale * coeff;
440
480
  if (depth === explodeDepth) objectScale *= currentSegmentFraction;
441
481
 
442
- const parentCenter = object.parent.userData.originalCenter;
443
- const objectCenter = object.userData.originalCenter;
444
- const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
445
-
446
- object.position.add(localOffset);
482
+ object.position.addScaledVector(object.userData.originalOffset, objectScale);
447
483
  }
484
+ };
448
485
 
449
- object.children.forEach((x: Object3D) => explodeObject(x, depth + 1));
450
- }
486
+ explodeObjects.forEach((object: Object3D) => {
487
+ explodeObject(object);
488
+ });
451
489
 
452
- explodeObject(explodeRoot, 0);
453
490
  this.scene.updateMatrixWorld();
454
491
 
455
492
  return this;
@@ -1,7 +0,0 @@
1
- import { Scene, WebGLRenderer } from "three";
2
- export declare class Helpers extends Scene {
3
- private oldAutoClear;
4
- private oldClippingPlanes;
5
- onBeforeRender(renderer: WebGLRenderer): void;
6
- onAfterRender(renderer: WebGLRenderer): void;
7
- }