@inweb/viewer-three 27.2.3 → 27.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "27.2.3",
3
+ "version": "27.3.0",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,10 +35,10 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~27.2.3",
39
- "@inweb/eventemitter2": "~27.2.3",
40
- "@inweb/markup": "~27.2.3",
41
- "@inweb/viewer-core": "~27.2.3"
38
+ "@inweb/client": "~27.3.0",
39
+ "@inweb/eventemitter2": "~27.3.0",
40
+ "@inweb/markup": "~27.3.0",
41
+ "@inweb/viewer-core": "~27.3.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@streamparser/json": "^0.0.22",
@@ -110,6 +110,12 @@ export class DynamicGltfLoader {
110
110
  this.objectTransforms = new Map(); // originalObject -> Matrix4
111
111
  this.transformedGeometries = new Map(); // mergedObject.uuid -> original position data
112
112
 
113
+ // TODO: Remove when highlight is implemented via shader.
114
+ // Sync transforms to original (hidden) objects so GeometryHighlighter
115
+ // wireframes follow the exploded positions.
116
+ this.syncTransformsToOriginalObjects = true;
117
+ this._originalObjectMatrices = new Map(); // object -> original matrix (for restore)
118
+
113
119
  this.activeChunkLoads = 0;
114
120
  this.chunkQueue = [];
115
121
 
@@ -1495,6 +1501,7 @@ export class DynamicGltfLoader {
1495
1501
 
1496
1502
  this.objectTransforms.clear();
1497
1503
  this.transformedGeometries.clear();
1504
+ this._originalObjectMatrices.clear();
1498
1505
 
1499
1506
  this.totalLoadedObjects = 0;
1500
1507
  this.currentMemoryUsage = 0;
@@ -2562,6 +2569,76 @@ export class DynamicGltfLoader {
2562
2569
  this.updateMaterialUniforms();
2563
2570
  }
2564
2571
  }
2572
+
2573
+ // TODO: Remove this block when highlight is implemented via shader.
2574
+ // Sync transforms to original (hidden) objects so that
2575
+ // GeometryHighlighter wireframes follow exploded positions.
2576
+ if (this.syncTransformsToOriginalObjects) {
2577
+ this._syncOriginalObjectTransforms(objectTransformMap);
2578
+ }
2579
+ }
2580
+
2581
+ // TODO: Remove when highlight is implemented via shader.
2582
+ _syncOriginalObjectTransforms(objectTransformMap) {
2583
+ // First, restore any previously transformed objects back to their saved position
2584
+ for (const [obj, savedPos] of this._originalObjectMatrices) {
2585
+ obj.position.copy(savedPos);
2586
+ if (obj.userData.highlight) {
2587
+ obj.userData.highlight.position.copy(savedPos);
2588
+ }
2589
+ }
2590
+ this._originalObjectMatrices.clear();
2591
+
2592
+ // The transform matrix is in the structure root's local space.
2593
+ // Original objects may be nested inside groups with their own transforms,
2594
+ // so we must convert the offset from structure-root-local → parent-local space.
2595
+ const _offset = new Vector3();
2596
+ const _parentInverse = new Matrix4();
2597
+
2598
+ if (objectTransformMap instanceof Map) {
2599
+ for (const [object, matrix] of objectTransformMap.entries()) {
2600
+ if (!object.userData?.handle) continue;
2601
+
2602
+ // Save original position before modifying
2603
+ if (!this._originalObjectMatrices.has(object)) {
2604
+ this._originalObjectMatrices.set(object, object.position.clone());
2605
+ }
2606
+
2607
+ // Extract translation from the transform matrix (structure-root-local space)
2608
+ _offset.setFromMatrixPosition(matrix);
2609
+
2610
+ // Convert offset from structure-root-local to world space,
2611
+ // then from world space to parent-local space.
2612
+ // The structure root's matrixWorld converts local→world.
2613
+ // The parent's inverse matrixWorld converts world→parent-local.
2614
+ // But we only need to transform a direction/offset (not a point),
2615
+ // so we transform two points and subtract.
2616
+ if (object.userData.structureId) {
2617
+ const rootGroup = this.structureRoots.get(object.userData.structureId);
2618
+ if (rootGroup && object.parent && object.parent !== rootGroup) {
2619
+ // Transform offset: structureRoot-local → world → parent-local
2620
+ // For a vector (not point): apply rotation/scale only
2621
+ const origin = new Vector3(0, 0, 0);
2622
+ origin.applyMatrix4(rootGroup.matrixWorld);
2623
+ _offset.applyMatrix4(rootGroup.matrixWorld);
2624
+ _offset.sub(origin);
2625
+
2626
+ const parentOrigin = new Vector3(0, 0, 0);
2627
+ _parentInverse.copy(object.parent.matrixWorld).invert();
2628
+ parentOrigin.applyMatrix4(_parentInverse);
2629
+ _offset.applyMatrix4(_parentInverse);
2630
+ _offset.sub(parentOrigin);
2631
+ }
2632
+ }
2633
+
2634
+ object.position.add(_offset);
2635
+
2636
+ // Also update highlight wireframe if it exists right now
2637
+ if (object.userData.highlight) {
2638
+ object.userData.highlight.position.copy(object.position);
2639
+ }
2640
+ }
2641
+ }
2565
2642
  }
2566
2643
 
2567
2644
  createExplodeTransforms(objects = null, explodeCenter = null, explodeFactor = 1.5) {
@@ -2675,6 +2752,18 @@ export class DynamicGltfLoader {
2675
2752
  clearTransforms() {
2676
2753
  this.objectTransforms.clear();
2677
2754
  this._resetTransformData(true);
2755
+
2756
+ // TODO: Remove when highlight is implemented via shader.
2757
+ // Restore original object positions
2758
+ if (this.syncTransformsToOriginalObjects) {
2759
+ for (const [obj, savedPos] of this._originalObjectMatrices) {
2760
+ obj.position.copy(savedPos);
2761
+ if (obj.userData.highlight) {
2762
+ obj.userData.highlight.position.copy(savedPos);
2763
+ }
2764
+ }
2765
+ this._originalObjectMatrices.clear();
2766
+ }
2678
2767
  }
2679
2768
 
2680
2769
  clearHandleTransforms() {
@@ -133,7 +133,7 @@ export class DynamicModelImpl extends ModelImpl {
133
133
  }
134
134
 
135
135
  override explode(scale = 0, coeff = 4): this {
136
- const centers = new Map();
136
+ const centersCache = new Map();
137
137
 
138
138
  const calcObjectCenter = (object: Object3D, target: Vector3): Vector3 => {
139
139
  const extents = new Box3().setFromObject(object);
@@ -141,74 +141,85 @@ export class DynamicModelImpl extends ModelImpl {
141
141
  const handle = object.userData.handle;
142
142
  if (!handle) return extents.getCenter(target);
143
143
 
144
- const center = centers.get(handle);
144
+ const center = centersCache.get(handle);
145
145
  if (center) return target.copy(center);
146
146
 
147
147
  const objects = this.getObjectsByHandles(handle);
148
148
  objects.forEach((x: Object3D) => extents.expandByObject(x));
149
149
  extents.getCenter(target);
150
150
 
151
- centers.set(handle, target.clone());
151
+ centersCache.set(handle, target.clone());
152
152
 
153
153
  return target;
154
154
  };
155
155
 
156
- function calcExplodeDepth(object: Object3D, depth: number): number {
157
- let result = depth;
158
- object.children
159
- .filter((x: Object3D) => !x.userData.isOptimized)
160
- .forEach((x: Object3D) => {
161
- const objectDepth = calcExplodeDepth(x, depth + 1);
162
- if (result < objectDepth) result = objectDepth;
163
- });
156
+ const calcObjectDepth = (object: Object3D): number => {
157
+ if (object.userData.depth !== undefined) return object.userData.depth;
164
158
 
159
+ const parent = object.parent;
160
+ const depth = parent && object !== explodeRoot ? calcObjectDepth(parent) + 1 : 0;
161
+
162
+ object.userData.depth = depth;
165
163
  object.userData.originalPosition = object.position.clone();
166
164
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
167
165
 
168
- return result;
169
- }
166
+ return depth;
167
+ };
170
168
 
171
169
  const explodeScale = scale / 100;
172
170
  const explodeRoot = this.scene.children[0];
173
171
 
174
- if (!explodeRoot.userData.explodeDepth) explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
175
- const maxDepth = explodeRoot.userData.explodeDepth;
172
+ if (!explodeRoot.userData.explodeDepth) {
173
+ let maxDepth = 0;
174
+
175
+ this.gltfLoader.originalObjects.forEach((object: Object3D) => {
176
+ const depth = calcObjectDepth(object);
177
+ if (depth > maxDepth) maxDepth = depth;
178
+ });
176
179
 
180
+ explodeRoot.userData.explodeDepth = maxDepth;
181
+ }
182
+
183
+ const maxDepth = explodeRoot.userData.explodeDepth;
177
184
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
178
185
  const explodeDepth = 0 | scaledExplodeDepth;
179
186
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
180
187
 
181
- const transformMap = new Map();
188
+ const offsetCache = new Map();
189
+
190
+ const calcObjectOffset = (object: Object3D, target: Vector3): Vector3 => {
191
+ if (offsetCache.has(object)) return target.copy(offsetCache.get(object));
182
192
 
183
- function explodeObject(object, depth: number) {
184
- if (object.isCamera) return;
185
- if (object.userData.isHighlightWireframe) return;
193
+ const parent = object.parent;
194
+ if (parent && object !== explodeRoot) calcObjectOffset(parent, target);
186
195
 
187
- object.position.copy(object.userData.originalPosition);
196
+ const depth = object.userData.depth;
188
197
 
189
- if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
198
+ if (depth > 0 && depth <= explodeDepth) {
190
199
  let objectScale = explodeScale * coeff;
191
200
  if (depth === explodeDepth) objectScale *= currentSegmentFraction;
192
201
 
193
- const parentCenter = object.parent.userData.originalCenter;
202
+ const parentCenter = parent.userData.originalCenter;
194
203
  const objectCenter = object.userData.originalCenter;
195
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
204
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
196
205
 
197
- object.position.add(objectOffset);
198
-
199
- const matrix = new Matrix4().makeTranslation(objectOffset.x, objectOffset.y, objectOffset.z);
200
- transformMap.set(object, matrix);
206
+ target.add(localOffset);
201
207
  }
202
208
 
203
- object.children
204
- .filter((x: Object3D) => !x.userData.isOptimized)
205
- .forEach((x: Object3D) => explodeObject(x, depth + 1));
206
- }
209
+ offsetCache.set(object, target.clone());
207
210
 
208
- explodeObject(explodeRoot, 0);
209
- this.scene.updateMatrixWorld();
211
+ return target;
212
+ };
213
+
214
+ const transformMap = new Map();
215
+
216
+ this.gltfLoader.originalObjects.forEach((object: Object3D) => {
217
+ const globalOffset = calcObjectOffset(object, new Vector3());
218
+ transformMap.set(object, new Matrix4().makeTranslation(globalOffset));
219
+ });
210
220
 
211
221
  this.gltfLoader.applyObjectTransforms(transformMap);
222
+ this.scene.updateMatrixWorld();
212
223
 
213
224
  return this;
214
225
  }
@@ -383,35 +383,36 @@ export class ModelImpl implements IModelImpl {
383
383
  }
384
384
 
385
385
  explode(scale = 0, coeff = 4): this {
386
- const centers = new Map();
386
+ const centersCache = new Map();
387
387
 
388
- const getObjectCenter = (object: Object3D, target: Vector3): Vector3 => {
388
+ const calcObjectCenter = (object: Object3D, target: Vector3): Vector3 => {
389
389
  const extents = new Box3().setFromObject(object);
390
390
 
391
391
  const handle = object.userData.handle;
392
392
  if (!handle) return extents.getCenter(target);
393
393
 
394
- const center = centers.get(handle);
394
+ const center = centersCache.get(handle);
395
395
  if (center) return target.copy(center);
396
396
 
397
397
  const objects = this.getObjectsByHandles(handle);
398
398
  objects.forEach((x: Object3D) => extents.expandByObject(x));
399
399
  extents.getCenter(target);
400
400
 
401
- centers.set(handle, target.clone());
401
+ centersCache.set(handle, target.clone());
402
402
 
403
403
  return target;
404
404
  };
405
405
 
406
406
  function calcExplodeDepth(object: Object3D, depth: number): number {
407
407
  let result = depth;
408
+
408
409
  object.children.forEach((x: Object3D) => {
409
410
  const objectDepth = calcExplodeDepth(x, depth + 1);
410
411
  if (result < objectDepth) result = objectDepth;
411
412
  });
412
413
 
413
414
  object.userData.originalPosition = object.position.clone();
414
- object.userData.originalCenter = getObjectCenter(object, new Vector3());
415
+ object.userData.originalCenter = calcObjectCenter(object, new Vector3());
415
416
 
416
417
  return result;
417
418
  }
@@ -419,14 +420,16 @@ export class ModelImpl implements IModelImpl {
419
420
  const explodeScale = scale / 100;
420
421
  const explodeRoot = this.scene;
421
422
 
422
- if (!explodeRoot.userData.explodeDepth) explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
423
- const maxDepth = explodeRoot.userData.explodeDepth;
423
+ if (!explodeRoot.userData.explodeDepth) {
424
+ explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
425
+ }
424
426
 
427
+ const maxDepth = explodeRoot.userData.explodeDepth;
425
428
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
426
429
  const explodeDepth = 0 | scaledExplodeDepth;
427
430
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
428
431
 
429
- function explodeObject(object, depth: number) {
432
+ function explodeObject(object: any, depth: number) {
430
433
  if (object.isCamera) return;
431
434
  if (object.userData.isHighlightWireframe) return;
432
435
 
@@ -438,9 +441,9 @@ export class ModelImpl implements IModelImpl {
438
441
 
439
442
  const parentCenter = object.parent.userData.originalCenter;
440
443
  const objectCenter = object.userData.originalCenter;
441
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
444
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
442
445
 
443
- object.position.add(objectOffset);
446
+ object.position.add(localOffset);
444
447
  }
445
448
 
446
449
  object.children.forEach((x: Object3D) => explodeObject(x, depth + 1));