@combeenation/3d-viewer 18.1.0 → 18.2.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 (42) hide show
  1. package/dist/lib-cjs/buildinfo.json +1 -1
  2. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  3. package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
  4. package/dist/lib-cjs/internal/geometry-helper.d.ts +1 -8
  5. package/dist/lib-cjs/internal/geometry-helper.js +1 -32
  6. package/dist/lib-cjs/internal/geometry-helper.js.map +1 -1
  7. package/dist/lib-cjs/internal/metadata-helper.d.ts +2 -1
  8. package/dist/lib-cjs/internal/metadata-helper.js.map +1 -1
  9. package/dist/lib-cjs/internal/node-helper.d.ts +15 -0
  10. package/dist/lib-cjs/internal/node-helper.js +51 -0
  11. package/dist/lib-cjs/internal/node-helper.js.map +1 -0
  12. package/dist/lib-cjs/manager/camera-manager.d.ts +5 -5
  13. package/dist/lib-cjs/manager/camera-manager.js +5 -7
  14. package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
  15. package/dist/lib-cjs/manager/dimension-line-manager.d.ts +6 -0
  16. package/dist/lib-cjs/manager/dimension-line-manager.js +8 -0
  17. package/dist/lib-cjs/manager/dimension-line-manager.js.map +1 -1
  18. package/dist/lib-cjs/manager/gltf-export-manager.d.ts +11 -11
  19. package/dist/lib-cjs/manager/gltf-export-manager.js +29 -30
  20. package/dist/lib-cjs/manager/gltf-export-manager.js.map +1 -1
  21. package/dist/lib-cjs/manager/html-anchor-manager.d.ts +6 -6
  22. package/dist/lib-cjs/manager/html-anchor-manager.js +10 -8
  23. package/dist/lib-cjs/manager/html-anchor-manager.js.map +1 -1
  24. package/dist/lib-cjs/manager/parameter-manager.js +13 -4
  25. package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
  26. package/dist/lib-cjs/manager/scene-manager.d.ts +9 -9
  27. package/dist/lib-cjs/manager/scene-manager.js +32 -29
  28. package/dist/lib-cjs/manager/scene-manager.js.map +1 -1
  29. package/dist/lib-cjs/viewer.d.ts +4 -4
  30. package/dist/lib-cjs/viewer.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/internal/cloning-helper.ts +1 -1
  33. package/src/internal/geometry-helper.ts +0 -37
  34. package/src/internal/metadata-helper.ts +6 -1
  35. package/src/internal/node-helper.ts +73 -0
  36. package/src/manager/camera-manager.ts +10 -12
  37. package/src/manager/dimension-line-manager.ts +9 -0
  38. package/src/manager/gltf-export-manager.ts +31 -31
  39. package/src/manager/html-anchor-manager.ts +11 -9
  40. package/src/manager/parameter-manager.ts +13 -4
  41. package/src/manager/scene-manager.ts +40 -40
  42. package/src/viewer.ts +5 -4
@@ -0,0 +1,73 @@
1
+ import { AbstractMesh, DimensionLineManager, HtmlAnchorManager, ParameterSubject, Tags, TransformNode } from '..';
2
+
3
+ type NodeCheckCriteria = {
4
+ isInList?: (TransformNode | ParameterSubject)[];
5
+ isDisabled?: boolean;
6
+ hasInvalidBoundingInfo?: boolean;
7
+ hasInfiniteDistance?: boolean;
8
+ isGeneratedBackgroundMesh?: boolean;
9
+ isHtmlAnchorMesh?: boolean;
10
+ isDimensionLine?: boolean;
11
+ };
12
+
13
+ const _DEFAULT_BACKGROUND_PARENT_NAME = 'BackgroundHelper';
14
+
15
+ /**
16
+ * Checks if transform node matches any of the given criterias.
17
+ */
18
+ export function nodeMatchesAnyCriteria(nodeToCheck: TransformNode, criteria: NodeCheckCriteria): boolean {
19
+ const {
20
+ isInList,
21
+ isDisabled,
22
+ hasInvalidBoundingInfo,
23
+ hasInfiniteDistance,
24
+ isGeneratedBackgroundMesh,
25
+ isHtmlAnchorMesh,
26
+ isDimensionLine,
27
+ } = criteria ?? {};
28
+
29
+ const matchesIsInList = (isInList ?? []).some(geometryToExclude => {
30
+ if (geometryToExclude instanceof TransformNode) {
31
+ // CAUTION: node has to be the same instance
32
+ return geometryToExclude === nodeToCheck;
33
+ }
34
+ if (geometryToExclude.tagName || geometryToExclude.nodeName) {
35
+ const nameMatches = !!geometryToExclude.nodeName && geometryToExclude.nodeName === nodeToCheck.name;
36
+ const tagMatches = !!geometryToExclude.tagName && Tags.MatchesQuery(nodeToCheck, geometryToExclude.tagName);
37
+ return nameMatches || tagMatches;
38
+ }
39
+ return false;
40
+ });
41
+
42
+ const isMesh = nodeToCheck instanceof AbstractMesh;
43
+ const matchesIsDisabled = !!isDisabled && !nodeToCheck.isEnabled(false);
44
+ const matchesHasInvalidBoundingInfo =
45
+ !!hasInvalidBoundingInfo && isMesh && nodeToCheck.getBoundingInfo().boundingSphere.radius === 0;
46
+ const matchesHasInfiniteDistance = !!hasInfiniteDistance && nodeToCheck.infiniteDistance;
47
+ // this targets generated ground and skybox meshes from `scene.createDefaultEnvironment`, as they are located
48
+ // underneath `BackgroundHelper`
49
+ const matchesIsGeneratedBackgroundMesh =
50
+ !!isGeneratedBackgroundMesh && nodeToCheck.name === _DEFAULT_BACKGROUND_PARENT_NAME;
51
+ const matchesIsHtmlAnchorMesh = !!isHtmlAnchorMesh && HtmlAnchorManager.isHtmlAnchorMesh(nodeToCheck);
52
+ const matchesIsDimensionLine = !!isDimensionLine && DimensionLineManager.isDimensionLineMesh(nodeToCheck);
53
+
54
+ let matchesAnyCriteria =
55
+ matchesIsInList ||
56
+ matchesIsDisabled ||
57
+ matchesHasInvalidBoundingInfo ||
58
+ matchesHasInfiniteDistance ||
59
+ matchesIsGeneratedBackgroundMesh ||
60
+ matchesIsHtmlAnchorMesh ||
61
+ matchesIsDimensionLine;
62
+ // consider parent as well, BUT ONLY for list and disabled state, as the other criterias have nothing to do with a
63
+ // child-parent relation
64
+ if (!matchesAnyCriteria && nodeToCheck.parent instanceof TransformNode) {
65
+ matchesAnyCriteria = nodeMatchesAnyCriteria(nodeToCheck.parent, {
66
+ isInList,
67
+ isDisabled,
68
+ isGeneratedBackgroundMesh,
69
+ });
70
+ }
71
+
72
+ return matchesAnyCriteria;
73
+ }
@@ -3,7 +3,7 @@ import {
3
3
  AnimationGroup,
4
4
  ArcRotateCamera,
5
5
  BoundingSphere,
6
- ExcludedGeometryList,
6
+ NodeDescription,
7
7
  ScreenshotTools,
8
8
  Vector3,
9
9
  Viewer,
@@ -11,7 +11,7 @@ import {
11
11
  ViewerErrorIds,
12
12
  ViewerEvent,
13
13
  } from '../index';
14
- import { isNodeExcluded } from '../internal/geometry-helper';
14
+ import { nodeMatchesAnyCriteria } from '../internal/node-helper';
15
15
  import { createScreenshotCanvas, trimCanvas } from '../internal/screenshot-helper';
16
16
 
17
17
  export type CameraPosition = {
@@ -31,8 +31,8 @@ export type AutofocusSettings = {
31
31
  alpha?: number;
32
32
  /** Desired vertical camera angle, this won't be overwritten by the autofocus function */
33
33
  beta?: number;
34
- /** Optional list of geometry to be excluded from consideration */
35
- exclude?: ExcludedGeometryList;
34
+ /** Optional list of nodes to be excluded from camera distance calculation for autofocusing */
35
+ excludeNodes?: NodeDescription[];
36
36
  durationMs?: number;
37
37
  };
38
38
 
@@ -62,8 +62,8 @@ export type ScreenshotSettings = {
62
62
  /** Can be used to automatically calculate camera `radius` and `target`, if these 2 settings are not defined
63
63
  * explicitely */
64
64
  autofocusScene?: boolean;
65
- /** Optional list of geometry to be excluded from consideration */
66
- exclude?: ExcludedGeometryList;
65
+ /** Optional list of nodes to be excluded from screenshot */
66
+ excludeNodes?: NodeDescription[];
67
67
  /**
68
68
  * Optional list of html anchor groups (see {@link HtmlAnchorManager}), that should be excluded from the screenshot.\
69
69
  * Excludes ALL html anchor groups if set to `true`.
@@ -160,7 +160,7 @@ export class CameraManager {
160
160
  }
161
161
 
162
162
  // get bounding box of all visible meshes, this is the base for the autofocus algorithm
163
- const boundingInfo = this.viewer.sceneManager.calculateBoundingInfo(settings?.exclude);
163
+ const boundingInfo = this.viewer.sceneManager.calculateBoundingInfo(settings?.excludeNodes);
164
164
  // optionally show bounding sphere for debugging purpose
165
165
  this.viewer.eventManager.fireEvent(ViewerEvent.AutofocusStart, boundingInfo.boundingSphere);
166
166
 
@@ -306,7 +306,7 @@ export class CameraManager {
306
306
  radius,
307
307
  target,
308
308
  autofocusScene,
309
- exclude,
309
+ excludeNodes,
310
310
  excludeHtmlAnchorGroups,
311
311
  cropTransparentArea,
312
312
  fileName,
@@ -321,7 +321,7 @@ export class CameraManager {
321
321
  const screenshotCam = this.viewer.scene.activeCamera?.clone(
322
322
  CameraManager._SCREENSHOT_CAMERA_NAME
323
323
  ) as ArcRotateCamera;
324
- const boundingInfo = autofocusScene ? this.viewer.sceneManager.calculateBoundingInfo(exclude) : undefined;
324
+ const boundingInfo = autofocusScene ? this.viewer.sceneManager.calculateBoundingInfo(excludeNodes) : undefined;
325
325
 
326
326
  if (alpha !== undefined) {
327
327
  screenshotCam.alpha = alpha;
@@ -363,10 +363,8 @@ export class CameraManager {
363
363
  useLayerMask,
364
364
  quality,
365
365
  texture => {
366
- // NOTE: this doesn't work ATM, see https://github.com/BabylonJS/Babylon.js/pull/16081#issuecomment-2652861176
367
- // we'll have to wait for a fix or solve the visibility in a different way (e.g. camera layer mask)
368
366
  texture.renderList = this.viewer.scene.meshes.filter(
369
- mesh => !settings?.exclude || !isNodeExcluded(mesh, settings.exclude)
367
+ mesh => !nodeMatchesAnyCriteria(mesh, { isInList: excludeNodes })
370
368
  );
371
369
  }
372
370
  );
@@ -82,6 +82,15 @@ export class DimensionLineManager {
82
82
  };
83
83
  } = {};
84
84
 
85
+ /**
86
+ * Check if input node is a dimension line mesh, created by this manager.\
87
+ * Dimension lines have a dedicated tag set, which is checked here.
88
+ * @internal
89
+ */
90
+ public static isDimensionLineMesh(node: TransformNode): boolean {
91
+ return Tags.MatchesQuery(node, DimensionLineManager.DIMENSION_LINE_KEY);
92
+ }
93
+
85
94
  /** @internal */
86
95
  public constructor(protected viewer: Viewer) {}
87
96
 
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  Animation,
3
3
  DynamicTexture,
4
- ExcludedGeometryList,
5
4
  GLTF2Export,
6
5
  IExportOptions,
7
6
  InstancedMesh,
8
7
  Material,
9
8
  Mesh,
10
9
  Node,
10
+ NodeDescription,
11
11
  PBRMaterial,
12
12
  RenderTargetTexture,
13
13
  TransformNode,
@@ -15,13 +15,13 @@ import {
15
15
  } from '../index';
16
16
  import { getIsScaledDownDevice } from '../internal/device-helper';
17
17
  import { bakeGeometryOfMesh, createMeshFromInstancedMesh, resetTransformation } from '../internal/geometry-helper';
18
- import { isNodeExcluded } from '../internal/geometry-helper';
19
18
  import {
20
19
  clearInternalMetadataValue,
21
20
  cloneInternalMetadata,
22
21
  getInternalMetadataValue,
23
22
  setInternalMetadataValue,
24
23
  } from '../internal/metadata-helper';
24
+ import { nodeMatchesAnyCriteria } from '../internal/node-helper';
25
25
 
26
26
  /**
27
27
  * Manager for gltf export and augmented reality features
@@ -35,15 +35,15 @@ export class GltfExportManager {
35
35
  * stay `true` in order to make the AR export work.
36
36
  * We could theoretically allow it if AR optimization is not desired, but this may confuse the user.
37
37
  */
38
- protected static _gltfExportOptions(optimizeForAR: boolean, excluded?: ExcludedGeometryList): IExportOptions {
38
+ protected static _gltfExportOptions(optimizeForAR: boolean, excludeNodes?: NodeDescription[]): IExportOptions {
39
39
  return {
40
40
  shouldExportNode: function (node: Node): boolean {
41
41
  if (optimizeForAR) {
42
42
  // we explicitely marked nodes, that should be exported in AR mode
43
- return getInternalMetadataValue(node, 'exportNode');
43
+ return !!getInternalMetadataValue(node, 'exportNode');
44
44
  } else {
45
45
  // use the default export node check (enabled state, exclusion list, etc...)
46
- return GltfExportManager._shouldExportNode(node, excluded);
46
+ return GltfExportManager._shouldExportNode(node, excludeNodes);
47
47
  }
48
48
  },
49
49
 
@@ -54,19 +54,19 @@ export class GltfExportManager {
54
54
  /**
55
55
  * Checks if a node should be available in the export
56
56
  */
57
- protected static _shouldExportNode(node: Node, excluded?: ExcludedGeometryList): boolean {
57
+ protected static _shouldExportNode(node: Node, excludeNodes?: NodeDescription[]): boolean {
58
58
  if (!(node instanceof TransformNode)) {
59
59
  return false;
60
60
  }
61
- // TODO WTT: think of adding "BackgroundHelper" and nodes with "infiniteDistance" here as well, at least in AR mode
62
- if (!node.isEnabled()) {
63
- return false;
64
- }
65
- if (excluded && isNodeExcluded(node, excluded)) {
66
- return false;
67
- }
68
61
 
69
- return true;
62
+ // maybe add some other criterias like "hasInfiniteDistance" and "isGeneratedBackgroundMesh" here as well
63
+ const isExcluded = nodeMatchesAnyCriteria(node, {
64
+ isInList: excludeNodes,
65
+ isDisabled: true,
66
+ isHtmlAnchorMesh: true,
67
+ });
68
+
69
+ return !isExcluded;
70
70
  }
71
71
 
72
72
  /**
@@ -120,19 +120,19 @@ export class GltfExportManager {
120
120
  * @param filename Optional name of the exported .GLB file.
121
121
  * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
122
122
  * is mostly targeting Apples .usdz format.
123
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
123
+ * @param excludeNodes Optional list of nodes to be excluded from the export.
124
124
  */
125
125
  public async exportGlb(
126
126
  filename = 'glb-export.glb',
127
127
  optimizeForAR: boolean = false,
128
- excluded?: ExcludedGeometryList
128
+ excludeNodes?: NodeDescription[]
129
129
  ): Promise<File | undefined> {
130
- await this._exportPreProcess(optimizeForAR, excluded);
130
+ await this._exportPreProcess(optimizeForAR, excludeNodes);
131
131
 
132
132
  const glbData = await GLTF2Export.GLBAsync(
133
133
  this.viewer.scene,
134
134
  'dummy',
135
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
135
+ GltfExportManager._gltfExportOptions(optimizeForAR, excludeNodes)
136
136
  );
137
137
 
138
138
  await this._exportPostProcess(optimizeForAR);
@@ -155,19 +155,19 @@ export class GltfExportManager {
155
155
  * @param filename Name of the main (text-based) .GLTF file referring to separate texture files.
156
156
  * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
157
157
  * is mostly targeting Apples .usdz format.
158
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
158
+ * @param excludeNodes Optional list of nodes to be excluded from the export.
159
159
  */
160
160
  public async exportGltfToFile(
161
161
  filename: string,
162
162
  optimizeForAR: boolean = false,
163
- excluded?: ExcludedGeometryList
163
+ excludeNodes?: NodeDescription[]
164
164
  ): Promise<void> {
165
- await this._exportPreProcess(optimizeForAR, excluded);
165
+ await this._exportPreProcess(optimizeForAR, excludeNodes);
166
166
 
167
167
  const gltf = await GLTF2Export.GLTFAsync(
168
168
  this.viewer.scene,
169
169
  filename,
170
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
170
+ GltfExportManager._gltfExportOptions(optimizeForAR, excludeNodes)
171
171
  );
172
172
  gltf.downloadFiles();
173
173
 
@@ -179,19 +179,19 @@ export class GltfExportManager {
179
179
  * @param filename Name of the main (text-based) .GLTF file referring to seperate texture files.
180
180
  * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
181
181
  * is mostly targeting Apples .usdz format.
182
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
182
+ * @param excludeNodes Optional list of nodes to be excluded from the export.
183
183
  */
184
184
  public async exportGlbToFile(
185
185
  filename: string,
186
186
  optimizeForAR: boolean = false,
187
- excluded?: ExcludedGeometryList
187
+ excludeNodes?: NodeDescription[]
188
188
  ): Promise<void> {
189
- await this._exportPreProcess(optimizeForAR, excluded);
189
+ await this._exportPreProcess(optimizeForAR, excludeNodes);
190
190
 
191
191
  const glb = await GLTF2Export.GLBAsync(
192
192
  this.viewer.scene,
193
193
  filename,
194
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
194
+ GltfExportManager._gltfExportOptions(optimizeForAR, excludeNodes)
195
195
  );
196
196
  glb.downloadFiles();
197
197
 
@@ -202,7 +202,7 @@ export class GltfExportManager {
202
202
  * Prepares scene for GLB export.
203
203
  * This is very important for AR exports, since we have to do a lot of conversions to satisfy Apples .usdz format.
204
204
  */
205
- protected async _exportPreProcess(optimizeForAR: boolean, excluded?: ExcludedGeometryList): Promise<void> {
205
+ protected async _exportPreProcess(optimizeForAR: boolean, excludeNodes?: NodeDescription[]): Promise<void> {
206
206
  if (!optimizeForAR) {
207
207
  // actually nothing to do if AR optimization is not required
208
208
  return;
@@ -235,7 +235,7 @@ export class GltfExportManager {
235
235
  // export
236
236
  this.viewer.scene.rootNodes
237
237
  // .filter(rootNode => rootNode.name !== GltfExportManager._EXPORT_ROOT_NAME)
238
- .forEach(rootNode => this._prepareNodeForExport(rootNode, null, excluded));
238
+ .forEach(rootNode => this._prepareNodeForExport(rootNode, null, excludeNodes));
239
239
 
240
240
  // bake transformation of all meshes, so that no negative scalings are left
241
241
  // it's important that this is done AFTER instanced meshes have been converted (_prepareNodeForExport)
@@ -282,9 +282,9 @@ export class GltfExportManager {
282
282
  protected _prepareNodeForExport(
283
283
  node: Node,
284
284
  clonedParent: TransformNode | null,
285
- excluded?: ExcludedGeometryList
285
+ excludeNodes?: NodeDescription[]
286
286
  ): void {
287
- if (!GltfExportManager._shouldExportNode(node, excluded)) {
287
+ if (!GltfExportManager._shouldExportNode(node, excludeNodes)) {
288
288
  return;
289
289
  }
290
290
 
@@ -314,7 +314,7 @@ export class GltfExportManager {
314
314
 
315
315
  // handle children
316
316
  const childs = transformNode.getChildTransformNodes(true);
317
- childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
317
+ childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excludeNodes));
318
318
  }
319
319
 
320
320
  /**
@@ -13,6 +13,7 @@ import {
13
13
  Viewer,
14
14
  Viewport,
15
15
  } from '..';
16
+ import { getInternalMetadataValue, setInternalMetadataValue } from '../internal/metadata-helper';
16
17
  import html2canvas from 'html2canvas';
17
18
 
18
19
  export type HtmlAnchorOptions = {
@@ -70,6 +71,15 @@ export class HtmlAnchorManager {
70
71
 
71
72
  protected _anchorMeshMaterial: StandardMaterial | null = null;
72
73
 
74
+ /**
75
+ * Check if input node is a html anchor mesh, created by this manager.\
76
+ * This check is done via internal metadata check.
77
+ * @internal
78
+ */
79
+ public static isHtmlAnchorMesh(node: TransformNode): boolean {
80
+ return !!getInternalMetadataValue(node, 'isHtmlAnchorMesh');
81
+ }
82
+
73
83
  /** @internal */
74
84
  public constructor(protected viewer: Viewer) {
75
85
  const canvas = viewer.canvas;
@@ -141,6 +151,7 @@ export class HtmlAnchorManager {
141
151
  // it's important that the occlusion check mesh is rendered after all "normal" meshes, which should be taken into
142
152
  // account for the occlusion check
143
153
  anchorMesh.renderingGroupId = 1;
154
+ setInternalMetadataValue(anchorMesh, 'isHtmlAnchorMesh', true);
144
155
 
145
156
  this._htmlAnchors[name] = { parentHtmlElement, anchorMesh, options };
146
157
  }
@@ -266,15 +277,6 @@ export class HtmlAnchorManager {
266
277
  return anchorKeys;
267
278
  }
268
279
 
269
- /**
270
- * Check if input mesh is a html anchor mesh, created by this manager.\
271
- * This check is done via the material, as all html anchor meshes have the generic `_anchorMeshMaterial` set.
272
- * @internal
273
- */
274
- public isHtmlAnchorMesh(mesh: AbstractMesh): boolean {
275
- return !!this._anchorMeshMaterial && mesh.material === this._anchorMeshMaterial;
276
- }
277
-
278
280
  protected _updateHtmlAnchor(
279
281
  parentHtmlElement: HTMLDivElement,
280
282
  anchorMesh: AbstractMesh,
@@ -501,13 +501,17 @@ export class ParameterManager {
501
501
  const setMaterialProms = activatedNodes.map(async an => {
502
502
  const anVisibleNested = this.getNestedVisibilityParameterValueOfNode(an);
503
503
  const anDeferredMaterial = getInternalMetadataValue(an, 'deferredMaterial');
504
- // skip applying material if it's already set via the parameter
505
504
  const anMaterialParamValue = this._getParameterValueOfNode(an, BuiltInParameter.Material) as
506
505
  | string
507
506
  | undefined;
508
- if (anVisibleNested && anDeferredMaterial && !anMaterialParamValue) {
507
+ // get latest requested material, either from (new) parameter value or from deferred material
508
+ const anRequestedMaterial = anMaterialParamValue ?? anDeferredMaterial;
509
+ // material observer might have been executed before visibility observer
510
+ // => skip the material application in this case, as if it would be done twice
511
+ const anMaterialToBeSet = getInternalMetadataValue(an, 'materialToBeSet');
512
+ if (anVisibleNested && anRequestedMaterial && !anMaterialToBeSet) {
509
513
  await this.viewer.materialManager.setMaterialOnMesh(
510
- anDeferredMaterial,
514
+ anRequestedMaterial,
511
515
  // this cast is fine, as deferred material can only be set on meshes
512
516
  an as AbstractMesh
513
517
  );
@@ -535,7 +539,12 @@ export class ParameterManager {
535
539
  // only set material if the mesh is visible, or gets visible by the parameter update
536
540
  const visible = this.getNestedVisibilityParameterValueOfNode(node);
537
541
  if (visible) {
538
- await this.viewer.materialManager.setMaterialOnMesh(material, node);
542
+ // visibility observer might have been executed before material observer
543
+ // => skip the material application in this case, as if it would be done twice
544
+ const anMaterialToBeSet = getInternalMetadataValue(node, 'materialToBeSet');
545
+ if (!anMaterialToBeSet) {
546
+ await this.viewer.materialManager.setMaterialOnMesh(material, node);
547
+ }
539
548
  } else {
540
549
  setInternalMetadataValue(node, 'deferredMaterial', material);
541
550
  }
@@ -2,13 +2,12 @@ import {
2
2
  AbstractMesh,
3
3
  ArcRotateCamera,
4
4
  AssetContainer,
5
- BackgroundMaterial,
6
5
  BoundingInfo,
7
6
  CameraManager,
8
7
  Color4,
9
8
  CubeTexture,
10
- ExcludedGeometryList,
11
9
  IShadowGenerator,
10
+ NodeDescription,
12
11
  TransformNode,
13
12
  Vector3,
14
13
  Viewer,
@@ -16,7 +15,7 @@ import {
16
15
  ViewerErrorIds,
17
16
  } from '../index';
18
17
  import { BaseAsset, loadAsset, prepareAssetForScene } from '../internal/asset-helper';
19
- import { isNodeExcluded } from '../internal/geometry-helper';
18
+ import { nodeMatchesAnyCriteria } from '../internal/node-helper';
20
19
  import { merge } from 'lodash-es';
21
20
 
22
21
  /**
@@ -63,9 +62,9 @@ export type UpdateGroundSettings = {
63
62
  */
64
63
  factor?: number;
65
64
  /**
66
- * Optional list of geometry to be excluded from scene size calculation
65
+ * Optional list of nodes to be excluded from scene size calculation
67
66
  */
68
- excludeGeometry?: ExcludedGeometryList;
67
+ excludeNodes?: NodeDescription[];
69
68
  };
70
69
 
71
70
  export type UpdateShadowsSettings = {
@@ -75,13 +74,13 @@ export type UpdateShadowsSettings = {
75
74
  */
76
75
  shadowGenerators?: IShadowGenerator[];
77
76
  /**
78
- * Optional list of geometry which should not be defined as shadow caster
77
+ * Optional list of nodes which should not be defined as shadow caster
79
78
  */
80
- excludeCaster?: ExcludedGeometryList;
79
+ excludeCasterNodes?: NodeDescription[];
81
80
  /**
82
- * Optional list of geometry which should not be defined as shadow receiver
81
+ * Optional list of nodes which should not be defined as shadow receiver
83
82
  */
84
- excludeReceiver?: ExcludedGeometryList;
83
+ excludeReceiverNodes?: NodeDescription[];
85
84
  };
86
85
 
87
86
  /**
@@ -277,36 +276,27 @@ export class SceneManager {
277
276
  * Calculates size of the current scene.\
278
277
  * Only takes meshes into considerations that:
279
278
  * - are visible
280
- * - are not excluded by "excludeGeometry" parameter
279
+ * - are not excluded by "excludeNodes" parameter
281
280
  * - do not have a material of type "BackgroundMaterial"
282
281
  * - do not have an infinite distance (like sky boxes)
283
282
  */
284
- public calculateBoundingInfo(excludeGeometry?: ExcludedGeometryList): BoundingInfo {
283
+ public calculateBoundingInfo(excludeNodes?: NodeDescription[]): BoundingInfo {
285
284
  // CB-6062: workaround for BoundingBox not respecting render loop
286
285
  this.viewer.scene.render();
287
286
 
288
287
  const { max, min } = this.viewer.scene.meshes
289
288
  .filter(mesh => {
290
- const isEnabled = mesh.isEnabled();
291
- // ignore meshes with invalid bounding infos
292
- const hasValidBBoxInfo = mesh.getBoundingInfo().boundingSphere.radius > 0;
293
- // ignore meshes with infinite distance, typically these are sky boxes
294
- const hasInfiniteDistance = mesh.infiniteDistance;
295
- // ignore meshes with "BackgroundMaterial" - usually a ground or skybox
296
- const hasBackgroundMaterial = mesh.material instanceof BackgroundMaterial;
297
- // ignore dummy meshes for html anchor occlusion check
298
- const isHtmlAnchorMesh = this.viewer.htmlAnchorManager.isHtmlAnchorMesh(mesh);
299
289
  // ignore excluded meshes
300
- const isExcluded = excludeGeometry ? isNodeExcluded(mesh, excludeGeometry) : false;
301
-
302
- return (
303
- isEnabled &&
304
- hasValidBBoxInfo &&
305
- !hasInfiniteDistance &&
306
- !hasBackgroundMaterial &&
307
- !isHtmlAnchorMesh &&
308
- !isExcluded
309
- );
290
+ const isExcluded = nodeMatchesAnyCriteria(mesh, {
291
+ isInList: excludeNodes,
292
+ isDisabled: true,
293
+ hasInvalidBoundingInfo: true,
294
+ hasInfiniteDistance: true,
295
+ isGeneratedBackgroundMesh: true,
296
+ isHtmlAnchorMesh: true,
297
+ });
298
+
299
+ return !isExcluded;
310
300
  })
311
301
  .reduce(
312
302
  (accBBoxMinMax, curMesh, idx) => {
@@ -331,7 +321,7 @@ export class SceneManager {
331
321
  * plane mesh can be used as well.
332
322
  */
333
323
  public updateGround(settings?: UpdateGroundSettings): void {
334
- const bboxInfo = this.calculateBoundingInfo(settings?.excludeGeometry);
324
+ const bboxInfo = this.calculateBoundingInfo(settings?.excludeNodes);
335
325
 
336
326
  const groundMesh = settings?.groundMesh ?? this.viewer.scene.getMeshByName(SceneManager._DEFAULT_GROUND_PLANE_NAME);
337
327
  if (!groundMesh) {
@@ -343,7 +333,10 @@ export class SceneManager {
343
333
  const factor =
344
334
  bboxInfo.boundingSphere.radius * 2 * (settings?.factor ?? SceneManager._DEFAULT_GROUND_SIZING_FACTOR);
345
335
  const position = bboxInfo.boundingSphere.centerWorld;
346
- groundMesh.scaling = new Vector3(factor, factor, 1);
336
+ // scale in all dimensions, in this case it doesn't matter in which coordinate space (XY, XZ) the ground has been
337
+ // built
338
+ // also the flat dimension of the plane is independent of the scaling, it will always remain a plane
339
+ groundMesh.scaling = new Vector3(factor, factor, factor);
347
340
  groundMesh.position = new Vector3(position.x, 0, position.z);
348
341
  }
349
342
 
@@ -358,16 +351,23 @@ export class SceneManager {
358
351
 
359
352
  const shadowCasterMeshes: AbstractMesh[] = [];
360
353
  this.viewer.scene.meshes.forEach(mesh => {
361
- const isShadowReceiver = settings?.excludeReceiver ? !isNodeExcluded(mesh, settings.excludeReceiver) : true;
362
- if (isShadowReceiver) {
363
- mesh.receiveShadows = true;
364
- }
354
+ mesh.receiveShadows = !nodeMatchesAnyCriteria(mesh, {
355
+ isInList: settings?.excludeReceiverNodes,
356
+ isHtmlAnchorMesh: true,
357
+ isDimensionLine: true,
358
+ });
365
359
 
366
- // excluding background meshes like ground or walls from shadow casters is benefitial to reduce the size of the
360
+ // exclude "helper meshes" (e.g. skybox, ground, dimension lines, html anchors) here to reduce the size of the
367
361
  // poisson filters auto shadow plane calculation, which will result in better shadow resolution
368
- const hasBackgroundMaterial = mesh.material instanceof BackgroundMaterial;
369
- const isShadowCaster = settings?.excludeCaster ? !isNodeExcluded(mesh, settings.excludeCaster) : true;
370
- if (isShadowCaster && !hasBackgroundMaterial) {
362
+ const excludeAsShadowCaster = nodeMatchesAnyCriteria(mesh, {
363
+ isInList: settings?.excludeCasterNodes,
364
+ hasInvalidBoundingInfo: true,
365
+ hasInfiniteDistance: true,
366
+ isGeneratedBackgroundMesh: true,
367
+ isHtmlAnchorMesh: true,
368
+ isDimensionLine: true,
369
+ });
370
+ if (!excludeAsShadowCaster) {
371
371
  shadowCasterMeshes.push(mesh);
372
372
  }
373
373
  });
package/src/viewer.ts CHANGED
@@ -13,11 +13,12 @@ import {
13
13
  HtmlAnchorManager,
14
14
  MaterialManager,
15
15
  ModelManager,
16
+ NodeParameterSubject,
16
17
  NullEngine,
17
18
  ParameterManager,
18
- ParameterSubject,
19
19
  Scene,
20
20
  SceneManager,
21
+ TagParameterSubject,
21
22
  TextureManager,
22
23
  TransformNode,
23
24
  } from './index';
@@ -26,10 +27,10 @@ import { getIsScaledDownDevice } from './internal/device-helper';
26
27
  import { cloneDeep, merge } from 'lodash-es';
27
28
 
28
29
  /**
29
- * Use this to define geometry to be excluded from autofocus, GLB export, etc.
30
+ * Use this type to select nodes directly or via node or tag name.\
31
+ * This pattern is used for excluding nodes from autofocus, GLB export, etc.
30
32
  */
31
- export type ExcludedGeometry = TransformNode | ParameterSubject;
32
- export type ExcludedGeometryList = ExcludedGeometry[];
33
+ export type NodeDescription = TransformNode | TagParameterSubject | NodeParameterSubject;
33
34
 
34
35
  export type LimitTextureSizeConfig = {
35
36
  size: 512 | 1024;