@combeenation/3d-viewer 16.0.0 → 17.0.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 (51) hide show
  1. package/README.md +2 -2
  2. package/dist/lib-cjs/buildinfo.json +1 -1
  3. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  4. package/dist/lib-cjs/helper/decals-helper.d.ts +0 -8
  5. package/dist/lib-cjs/helper/decals-helper.js +20 -17
  6. package/dist/lib-cjs/helper/decals-helper.js.map +1 -1
  7. package/dist/lib-cjs/index.d.ts +3 -0
  8. package/dist/lib-cjs/index.js +3 -0
  9. package/dist/lib-cjs/index.js.map +1 -1
  10. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js +9 -1
  11. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js.map +1 -1
  12. package/dist/lib-cjs/internal/cloning-helper.d.ts +3 -2
  13. package/dist/lib-cjs/internal/cloning-helper.js +66 -43
  14. package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
  15. package/dist/lib-cjs/internal/metadata-helper.d.ts +15 -13
  16. package/dist/lib-cjs/internal/metadata-helper.js +11 -17
  17. package/dist/lib-cjs/internal/metadata-helper.js.map +1 -1
  18. package/dist/lib-cjs/internal/tags-helper.js +9 -9
  19. package/dist/lib-cjs/internal/tags-helper.js.map +1 -1
  20. package/dist/lib-cjs/manager/debug-manager.d.ts +11 -19
  21. package/dist/lib-cjs/manager/debug-manager.js +67 -50
  22. package/dist/lib-cjs/manager/debug-manager.js.map +1 -1
  23. package/dist/lib-cjs/manager/gltf-export-manager.js +2 -7
  24. package/dist/lib-cjs/manager/gltf-export-manager.js.map +1 -1
  25. package/dist/lib-cjs/manager/material-manager.js +0 -1
  26. package/dist/lib-cjs/manager/material-manager.js.map +1 -1
  27. package/dist/lib-cjs/manager/model-manager.d.ts +9 -0
  28. package/dist/lib-cjs/manager/model-manager.js +19 -8
  29. package/dist/lib-cjs/manager/model-manager.js.map +1 -1
  30. package/dist/lib-cjs/manager/parameter-manager.d.ts +0 -14
  31. package/dist/lib-cjs/manager/parameter-manager.js +0 -19
  32. package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
  33. package/dist/lib-cjs/manager/texture-manager.js +1 -1
  34. package/dist/lib-cjs/manager/texture-manager.js.map +1 -1
  35. package/dist/lib-cjs/viewer.d.ts +1 -0
  36. package/dist/lib-cjs/viewer.js +10 -1
  37. package/dist/lib-cjs/viewer.js.map +1 -1
  38. package/package.json +12 -13
  39. package/src/helper/decals-helper.ts +27 -23
  40. package/src/index.ts +3 -0
  41. package/src/internal/cbn-custom-babylon-loader-plugin.ts +9 -1
  42. package/src/internal/cloning-helper.ts +83 -55
  43. package/src/internal/metadata-helper.ts +31 -28
  44. package/src/internal/tags-helper.ts +1 -2
  45. package/src/manager/debug-manager.ts +83 -81
  46. package/src/manager/gltf-export-manager.ts +4 -9
  47. package/src/manager/material-manager.ts +0 -2
  48. package/src/manager/model-manager.ts +31 -14
  49. package/src/manager/parameter-manager.ts +0 -24
  50. package/src/manager/texture-manager.ts +1 -1
  51. package/src/viewer.ts +13 -1
@@ -1,12 +1,14 @@
1
1
  import {
2
+ AdvancedDynamicTexture,
2
3
  AxesViewer,
3
4
  BoundingSphere,
4
5
  Color3,
5
- DynamicTexture,
6
6
  IInspectorOptions,
7
+ Matrix,
8
+ Mesh,
7
9
  MeshBuilder,
8
- Scene,
9
10
  StandardMaterial,
11
+ TextBlock,
10
12
  TransformNode,
11
13
  UtilityLayerRenderer,
12
14
  Vector3,
@@ -14,19 +16,10 @@ import {
14
16
  ViewerEvent,
15
17
  } from '../index';
16
18
 
17
- type DebugAxisKeys = 'X' | 'Y' | 'Z';
18
- type DebugAxisConfig = { color: Color3; position: Vector3 };
19
-
20
19
  /**
21
20
  * Manager for debugging functionalities
22
21
  */
23
22
  export class DebugManager {
24
- protected static _DEBUG_AXIS_MAP: Record<DebugAxisKeys, DebugAxisConfig> = {
25
- X: { color: Color3.Red().scale(0.5), position: new Vector3(1, 0, 0) },
26
- Y: { color: Color3.Green().scale(0.5), position: new Vector3(0, 1, 0) },
27
- Z: { color: Color3.Blue().scale(0.5), position: new Vector3(0, 0, 1) },
28
- };
29
- protected static _WORLD_COORD_ROOT_KEY = '__world_coordinates__';
30
23
  protected static _BOUNDING_SPHERE_KEY = '__bounding_sphere__';
31
24
 
32
25
  protected _axesViewer: AxesViewer | null = null;
@@ -91,38 +84,63 @@ The inspector can only be used in development builds.`);
91
84
  }
92
85
 
93
86
  /**
94
- * Show world coordinate system with given dimension.\
95
- * This is especially usefull when working on project with tricky positioning. (e.g. clones)
87
+ * Displays the coordinate system.\
88
+ * Shows the local coordinate system of a specified node or the world coordinate system if no node is provided.\
89
+ * The size defaults to one-third of the scene's bounding sphere radius but can be set manually.
96
90
  */
97
- public showWorldCoordinates(dimension: number): void {
91
+ public showCoordinateSystem(node?: TransformNode, size?: number): void {
92
+ // calculate the default size of not provided
93
+ if (!size) {
94
+ const sceneBoundingInfo = this.viewer.calculateBoundingInfo();
95
+ const radius = sceneBoundingInfo.boundingSphere.radius;
96
+
97
+ // takes a third of the radius from scene boundingsphere
98
+ size = radius * (1 / 3);
99
+ }
100
+
98
101
  // make sure to remove already existing debug coordinate systems
99
- this.hideWorldCoordinates();
102
+ this.hideCoordinateSystem();
100
103
 
101
104
  // draw in utility layer, so that there is no interaction with the actually scene content
102
105
  // (e.g. glb export, autofocus)
103
106
  const utilityLayerScene = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene;
104
-
105
- const worldCoordRoot = new TransformNode(DebugManager._WORLD_COORD_ROOT_KEY, utilityLayerScene);
106
107
  // axes viewer coordinate system is a bit too large
107
108
  // multiply with unify factor to create arrows which exactly match the length of the input dimension
108
109
  const factor = DebugManager._getWorldCoordinatesAxesUnifyFactor();
109
- this._axesViewer = new AxesViewer(utilityLayerScene, dimension * factor);
110
+ this._axesViewer = new AxesViewer(utilityLayerScene, size * factor);
110
111
 
111
- DebugManager._prepareWorldCoordinateAxis('X', this._axesViewer.xAxis, worldCoordRoot, dimension, utilityLayerScene);
112
- DebugManager._prepareWorldCoordinateAxis('Y', this._axesViewer.yAxis, worldCoordRoot, dimension, utilityLayerScene);
113
- DebugManager._prepareWorldCoordinateAxis('Z', this._axesViewer.zAxis, worldCoordRoot, dimension, utilityLayerScene);
114
- }
112
+ if (node) {
113
+ // get the world rotation of the mesh and extract the x, y, z axes from that
114
+ // NOTE: `node.getDirection` can't be used because it considers the scaling as well, resulting in distorted axes
115
+ const worldRotMatrix = new Matrix();
116
+ node.absoluteRotationQuaternion.toRotationMatrix(worldRotMatrix);
115
117
 
116
- public hideWorldCoordinates(): void {
117
- const utilityLayerScene = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene;
118
+ const xAxis = Vector3.FromArray(worldRotMatrix.m.slice(0, 3)); // First column
119
+ const yAxis = Vector3.FromArray(worldRotMatrix.m.slice(4, 7)); // Second column
120
+ const zAxis = Vector3.FromArray(worldRotMatrix.m.slice(8, 11)); // Third column
118
121
 
119
- this._axesViewer?.dispose();
120
- this._axesViewer = null;
122
+ // consider scaling only for axis direction
123
+ if (node.absoluteScaling.x < 0) {
124
+ xAxis.scaleInPlace(-1);
125
+ }
126
+ if (node.absoluteScaling.y < 0) {
127
+ yAxis.scaleInPlace(-1);
128
+ }
129
+ if (node.absoluteScaling.z < 0) {
130
+ zAxis.scaleInPlace(-1);
131
+ }
121
132
 
122
- const worldCoordRoot = utilityLayerScene.getTransformNodeByName(DebugManager._WORLD_COORD_ROOT_KEY);
123
- if (worldCoordRoot) {
124
- worldCoordRoot.dispose(false, true);
133
+ this._axesViewer.update(node.absolutePosition, xAxis, yAxis, zAxis);
125
134
  }
135
+
136
+ DebugManager._prepareAxisVisual('X', this._axesViewer.xAxis);
137
+ DebugManager._prepareAxisVisual('Y', this._axesViewer.yAxis);
138
+ DebugManager._prepareAxisVisual('Z', this._axesViewer.zAxis);
139
+ }
140
+
141
+ public hideCoordinateSystem(): void {
142
+ this._axesViewer?.dispose();
143
+ this._axesViewer = null;
126
144
  }
127
145
 
128
146
  /**
@@ -138,58 +156,6 @@ The inspector can only be used in development builds.`);
138
156
  this._removeBoundingSphereForAutofocus();
139
157
  }
140
158
 
141
- /**
142
- * Adjust and enhance coordinate axes to fulfill our needs.
143
- * - moves nodes into common root
144
- * - adds text node
145
- */
146
- protected static _prepareWorldCoordinateAxis(
147
- text: 'X' | 'Y' | 'Z',
148
- axis: TransformNode,
149
- root: TransformNode,
150
- dimension: number,
151
- utilityLayerScene: Scene
152
- ): void {
153
- // create unique names
154
- axis.name = `${DebugManager._WORLD_COORD_ROOT_KEY}.${text}`;
155
- axis.parent = root;
156
-
157
- // create text mesh via dynamic texture
158
- const dynamicTexture = new DynamicTexture(
159
- `${DebugManager._WORLD_COORD_ROOT_KEY}.${text}`,
160
- 50,
161
- utilityLayerScene,
162
- true
163
- );
164
- dynamicTexture.hasAlpha = true;
165
- // 42.5 is a magic offset, so that the text is vertically centered for font size 50px
166
- // horizontal centering works well with the standard behaviour of Babylon.js (setting "null" as "width")
167
- dynamicTexture.drawText(text, null, 42.5, 'bold 50px Arial', 'white', 'transparent', true);
168
-
169
- const material = new StandardMaterial(`${DebugManager._WORLD_COORD_ROOT_KEY}.${text}`, utilityLayerScene);
170
- material.disableLighting = true;
171
- material.emissiveColor = DebugManager._DEBUG_AXIS_MAP[text].color;
172
- material.diffuseTexture = dynamicTexture;
173
-
174
- const plane = MeshBuilder.CreatePlane(
175
- `${DebugManager._WORLD_COORD_ROOT_KEY}.${text}.TextPlane`,
176
- { size: dimension / 10 },
177
- utilityLayerScene
178
- );
179
- // make sure that text is located outside of arrow
180
- plane.position = DebugManager._DEBUG_AXIS_MAP[text].position.multiplyByFloats(
181
- dimension * 1.05,
182
- dimension * 1.05,
183
- dimension * 1.05
184
- );
185
- plane.parent = root;
186
- plane.material = material;
187
- // will be rendered on top of "default" meshes => taken from Babylon.js repo "AxesViewer" implementation
188
- plane.renderingGroupId = 2;
189
- // setting billboard mode ensures, that the text is always readable
190
- plane.billboardMode = TransformNode.BILLBOARDMODE_ALL;
191
- }
192
-
193
159
  /**
194
160
  * Calculate factor for creating world coordinate axes with exactly one unit in length
195
161
  */
@@ -208,6 +174,42 @@ The inspector can only be used in development builds.`);
208
174
  return factor;
209
175
  }
210
176
 
177
+ /**
178
+ * Creates and adds a label with a direction letter at the tip of each axis arrow.\
179
+ * Also adjust the brightness of the arrow and created label.
180
+ */
181
+ protected static _prepareAxisVisual(labelText: string, axisNode: TransformNode): void {
182
+ // fixed values used for label size and distance
183
+ const colorScale = 2; // used to multiply the original color scale of 0.5 - result is 1
184
+ const labelSize = 0.05;
185
+ const labelDistance = 0.4;
186
+
187
+ // inherit the color from the arrow and adjust brightness scaling to be 1
188
+ const axisMesh = axisNode.getChildMeshes()[0];
189
+ const axisMaterial = axisMesh.material as StandardMaterial;
190
+ const color = axisMaterial.emissiveColor.clone().scale(colorScale);
191
+
192
+ axisMaterial.emissiveColor = color;
193
+
194
+ const utilityLayerScene = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene;
195
+ const labelPlane = MeshBuilder.CreatePlane(`${labelText}_label`, { size: labelSize }, utilityLayerScene);
196
+ labelPlane.parent = axisNode;
197
+ labelPlane.position = new Vector3(0, 0, labelDistance);
198
+ // keep orientation of mesh, so that it is always visible, independent of the camera position
199
+ labelPlane.billboardMode = Mesh.BILLBOARDMODE_ALL;
200
+
201
+ // create GUI element
202
+ const textElement = new TextBlock();
203
+ textElement.text = labelText;
204
+ textElement.color = color.toHexString();
205
+ textElement.fontSize = '100%';
206
+ textElement.fontFamily = 'Arial';
207
+ textElement.fontWeight = 'bold';
208
+
209
+ const labelTexture = AdvancedDynamicTexture.CreateForMesh(labelPlane);
210
+ labelTexture.addControl(textElement);
211
+ }
212
+
211
213
  /**
212
214
  * Visualize bounding sphere which is used to zoom in via `autofocusActiveCamera` function
213
215
  */
@@ -19,6 +19,7 @@ import { bakeGeometryOfMesh, createMeshFromInstancedMesh, resetTransformation }
19
19
  import { isNodeExcluded } from '../internal/geometry-helper';
20
20
  import {
21
21
  clearInternalMetadataValue,
22
+ cloneInternalMetadata,
22
23
  getInternalMetadataValue,
23
24
  setInternalMetadataValue,
24
25
  } from '../internal/metadata-helper';
@@ -42,7 +43,7 @@ export class GltfExportManager {
42
43
  shouldExportNode: function (node: Node): boolean {
43
44
  if (optimizeForAR) {
44
45
  // we explicitely marked nodes, that should be exported in AR mode
45
- return getInternalMetadataValue(node, 'exportNode') as boolean;
46
+ return getInternalMetadataValue(node, 'exportNode');
46
47
  } else {
47
48
  // use the default export node check (enabled state, exclusion list, etc...)
48
49
  return GltfExportManager._shouldExportNode(node, excluded);
@@ -101,9 +102,7 @@ export class GltfExportManager {
101
102
 
102
103
  const newName = `${material.name}_clone`;
103
104
  const clonedMaterial = material.clone(newName)!;
104
- // internal metadata may contain large data which slows down the export (CB-10094)
105
- // it wasn't an issue for materials yet, but still this is safer than to make a deep clone
106
- clonedMaterial._internalMetadata = null;
105
+ cloneInternalMetadata(material, clonedMaterial);
107
106
  const clonedTextures = clonedMaterial.getActiveTextures();
108
107
 
109
108
  // mark all exported textures, so that they will be deleted after the export
@@ -310,11 +309,7 @@ export class GltfExportManager {
310
309
  transformNode instanceof InstancedMesh
311
310
  ? createMeshFromInstancedMesh(transformNode, clonedNodeName, clonedParent)
312
311
  : transformNode.clone(clonedNodeName, clonedParent, true)!;
313
-
314
- // nodes with node geometry have very large internal metadata, this leads to long export times (CB-10094)
315
- // existing internal metadata is not required for the export so we can just delete it
316
- // internal metadata will be set some lines beyond!
317
- clonedNode._internalMetadata = null;
312
+ cloneInternalMetadata(transformNode, clonedNode);
318
313
 
319
314
  // exchange material
320
315
  if (clonedNode instanceof Mesh) {
@@ -141,8 +141,6 @@ export class MaterialManager {
141
141
  });
142
142
  }
143
143
 
144
- this.viewer.parameterManager.removeParameterEntriesOfMaterial(material);
145
-
146
144
  material.dispose(true, true);
147
145
 
148
146
  delete this._clonedMaterials[materialId];
@@ -50,6 +50,14 @@ export type NodeNamingStrategy = (node: TransformNode, newModelName: string) =>
50
50
  export type TagNamingStrategy = (tag: string, newModelName: string) => string;
51
51
 
52
52
  export type ModelCloneOptions = {
53
+ /**
54
+ * `true:` Creates "[InstancedMeshes](https://doc.babylonjs.com/typedoc/classes/BABYLON.InstancedMesh)" from meshes of
55
+ * the base model instead of "independent" meshes. Most of the data, especially the geometry and material assignment
56
+ * is shared, only the transformation data (e.g. position) can be adjusted.\
57
+ * Activating this flag can lead to huge performance improvements, especially when working with a large amount of
58
+ * clones.
59
+ */
60
+ createInstance?: boolean;
53
61
  nodeNamingStrategy?: NodeNamingStrategy;
54
62
  tagNamingStrategy?: TagNamingStrategy;
55
63
  };
@@ -61,6 +69,8 @@ type Model = {
61
69
  assetContainer: AssetContainer;
62
70
  cbnBabylonFileData?: CbnBabylonFileData;
63
71
  isClone: boolean;
72
+ // only set for "instantiated" clones
73
+ sourceModelName?: string;
64
74
  visibilityCallId?: number;
65
75
  };
66
76
 
@@ -288,16 +298,9 @@ export class ModelManager {
288
298
  name: newModelName,
289
299
  url: sourceModel.url,
290
300
  state: 'loaded',
291
- assetContainer: cloneModelAssetContainer(
292
- sourceModel.assetContainer,
293
- newModelName,
294
- {
295
- nodeNamingStrategy: options?.nodeNamingStrategy,
296
- tagNamingStrategy: options?.tagNamingStrategy,
297
- },
298
- this.viewer.scene
299
- ),
301
+ assetContainer: cloneModelAssetContainer(sourceModel.assetContainer, newModelName, this.viewer.scene, options),
300
302
  isClone: true,
303
+ sourceModelName: options?.createInstance ? sourceModel.name : undefined,
301
304
  };
302
305
  this._modelAssets[newModelName] = clonedModel;
303
306
 
@@ -329,10 +332,6 @@ export class ModelManager {
329
332
  });
330
333
  }
331
334
 
332
- // parameter entries of deleted model can get in the way when recreating a clone with the same node names
333
- // that's why it's benefitial to wipe the parameter entries of the model as well after removing it
334
- this.viewer.parameterManager.removeParameterEntriesOfModel(model.assetContainer);
335
-
336
335
  model.assetContainer.dispose();
337
336
 
338
337
  delete this._modelAssets[name];
@@ -444,7 +443,7 @@ export class ModelManager {
444
443
  } catch (e) {
445
444
  throw new ViewerError({
446
445
  id: ViewerErrorIds.AssetLoadingFailed,
447
- message: `Error loading model "${model.name}" from url "${model.url}":\n${(e as Error).message}`,
446
+ message: (e as Error).message,
448
447
  });
449
448
  }
450
449
 
@@ -453,6 +452,15 @@ export class ModelManager {
453
452
  [...assetContainer.lights].forEach(light => light.dispose());
454
453
  [...assetContainer.cameras].forEach(camera => camera.dispose());
455
454
 
455
+ // materials should be a "global" thing and not assigned to an asset container
456
+ // this is not relevant in most of the cases, since materials are cropped from babylon models on the CBN server
457
+ // anyway
458
+ assetContainer.materials.forEach(material => {
459
+ this.viewer.scene.addMaterial(material);
460
+ material._parentContainer = null;
461
+ });
462
+ assetContainer.materials = [];
463
+
456
464
  // environment texture and intensity has been overwritten by load asset container function
457
465
  this.viewer.scene.environmentTexture = curEnvTexture;
458
466
  this.viewer.scene.environmentIntensity = curEnvIntensity;
@@ -473,6 +481,15 @@ export class ModelManager {
473
481
  * a smooth model switch behaviour.
474
482
  */
475
483
  protected async _prepareModelForScene(model: Model): Promise<void> {
484
+ if (model.sourceModelName) {
485
+ // source model has to be prepared for scene as well in "instantiate" mode, because materials would not be
486
+ // loaded otherwise
487
+ // => the instantiated model only consistes of Instanced Meshes and therefore has no material assignments on
488
+ // it's own
489
+ const sourceModel = this._getModel(model.sourceModelName)!;
490
+ await this._prepareModelForScene(sourceModel);
491
+ }
492
+
476
493
  await this.viewer.parameterManager.applyAllParameterValuesToModel(model.assetContainer);
477
494
  // parameter manager did his job, now apply the deferred materials to all meshes that are not explicitely hidden by
478
495
  // the parameter manager
@@ -345,20 +345,6 @@ export class ParameterManager {
345
345
  await this._applyParameterValues(this._parameterEntries, model);
346
346
  }
347
347
 
348
- /**
349
- * Removes existing parameter entries of all nodes of a model.\
350
- * This can be useful to clean up the parameter state when removing a (cloned) model.
351
- *
352
- * @internal
353
- */
354
- public removeParameterEntriesOfModel(model: IAssetContainer): void {
355
- const allNodeNames = [...model.meshes, ...model.transformNodes].map(node => node.name);
356
-
357
- this._parameterEntries = this._parameterEntries.filter(
358
- entry => !allNodeNames.includes(entry.subject.nodeName ?? '')
359
- );
360
- }
361
-
362
348
  /**
363
349
  * Applies all parameter values which are targeting a material.\
364
350
  * This can be useful when updating a material definition before creating it.
@@ -387,16 +373,6 @@ export class ParameterManager {
387
373
  await this._applyParameterValues(parameterEntriesToApply);
388
374
  }
389
375
 
390
- /**
391
- * Removes all parameter entries of a certain material.\
392
- * This can be useful to clean up the parameter state when removing a (cloned) material.
393
- *
394
- * @internal
395
- */
396
- public removeParameterEntriesOfMaterial(material: Material): void {
397
- this._parameterEntries = this._parameterEntries.filter(entry => entry.subject.materialName !== material.id);
398
- }
399
-
400
376
  /**
401
377
  * Applies subset of texture parameter values which are targeting a certain texture channel in a material.\
402
378
  * This can be useful when updating texture settings (e.g. uScale) before the texture is actually created.
@@ -26,7 +26,7 @@ export class TextureManager {
26
26
  const indexOfTextureQueryParam = texture.name.lastIndexOf(textureQueryString);
27
27
 
28
28
  if (isUrl && indexOfTextureQueryParam > -1) {
29
- texture.name = texture.name.substring(indexOfTextureQueryParam + textureQueryString.length);
29
+ texture.displayName = texture.name.substring(indexOfTextureQueryParam + textureQueryString.length);
30
30
  }
31
31
  }
32
32
  }
package/src/viewer.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import buildInfo from './buildinfo.json';
2
2
  import * as Index from './index';
3
3
  import {
4
+ BackgroundMaterial,
4
5
  BoundingInfo,
5
6
  CameraManager,
6
7
  DebugManager,
@@ -199,6 +200,7 @@ export class Viewer {
199
200
  * Only takes meshes into considerations that:
200
201
  * - are visible
201
202
  * - are not excluded by "excludeGeometry" parameter
203
+ * - do not have a material of type "BackgroundMaterial"
202
204
  * - do not have an infinite distance (like sky boxes)
203
205
  */
204
206
  public calculateBoundingInfo(excludeGeometry?: ExcludedGeometryList): BoundingInfo {
@@ -212,9 +214,12 @@ export class Viewer {
212
214
  const hasValidBBoxInfo = mesh.getBoundingInfo().boundingSphere.radius > 0;
213
215
  // ignore meshes with infinite distance, typically these are sky boxes
214
216
  const hasInfiniteDistance = mesh.infiniteDistance;
217
+ // ignore meshes with "BackgroundMaterial" - usually a ground or skybox
218
+ const hasBackgroundMaterial = mesh.material instanceof BackgroundMaterial;
215
219
  // ignore excluded meshes
216
220
  const isExcluded = excludeGeometry ? isNodeExcluded(mesh, excludeGeometry) : false;
217
- return isEnabled && hasValidBBoxInfo && !hasInfiniteDistance && !isExcluded;
221
+
222
+ return isEnabled && hasValidBBoxInfo && !hasInfiniteDistance && !hasBackgroundMaterial && !isExcluded;
218
223
  })
219
224
  .reduce(
220
225
  (accBBoxMinMax, curMesh, idx) => {
@@ -240,6 +245,13 @@ export class Viewer {
240
245
  // asset manager
241
246
  registerCustomCbnBabylonLoaderPlugin();
242
247
 
248
+ // without setting this flag, meshes with billboard mode set would not integrate into the node tree structure,
249
+ // that means the position would not be updated by the rotation of the parent node
250
+ // it's a bit dangerous to set this globally, but we need that for:
251
+ // - `DebugManager.showCoordinateSystem`
252
+ // - probably dimensions line handling (not implemented yet)
253
+ TransformNode.BillboardUseParentOrientation = true;
254
+
243
255
  const engine = new Engine(
244
256
  this.canvas,
245
257
  this._viewerSettings.antialiasing,