@combeenation/3d-viewer 15.1.0-beta1 → 16.0.0-alpha1

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.
@@ -1,16 +1,54 @@
1
- import { AssetContainer, ISceneLoaderPlugin, InstancedMesh, SceneLoader } from '../index';
1
+ import {
2
+ AssetContainer,
3
+ DecalConfiguration,
4
+ ISceneLoaderPlugin,
5
+ InstancedMesh,
6
+ SceneLoader,
7
+ ViewerError,
8
+ ViewerErrorIds,
9
+ createDecalMesh,
10
+ } from '../index';
2
11
  import { setInternalMetadataValue } from './metadata-helper';
3
12
  import { deleteAllTags, getTags, setTagsAsString } from './tags-helper';
4
- import { isArray, isString } from 'lodash-es';
13
+ import { isArray, isNumber, isString } from 'lodash-es';
14
+
15
+ /**
16
+ * Contains cbn custom data, like decals.
17
+ * This is just a temporary type, as the `loadAssetContainer` function only returns an asset container, which can be
18
+ * altered by our file loader plugin.
19
+ * After loading the model, `cbnData` is cropped and a pure asset container is available for further processing.
20
+ */
21
+ export class ExtendedAssetContainer extends AssetContainer {
22
+ cbnData?: CbnBabylonFileData;
23
+ }
24
+
25
+ export type CbnBabylonFileData = { decals?: ParsedDecalConfiguration[] };
26
+ export type ParsedDecalConfiguration = DecalConfiguration & { materialId?: string };
27
+
28
+ type DataWithMeshes = { meshes: unknown[] };
29
+ type DataWithDecalConfigurations = { cbnData: { decals: unknown[] } };
30
+
31
+ type MeshData = {
32
+ name: string;
33
+ materialId?: string;
34
+ instances?: unknown[];
35
+ };
36
+
37
+ type InstanceData = {
38
+ name: string;
39
+ tags?: string;
40
+ };
5
41
 
6
42
  /**
7
43
  * Create and return a custom loader plugin to be registered with SceneLoader, that allows
8
44
  * us to run our own code against the input data before using the standard procedure to
9
45
  * import.
10
- * The main use case is to mark missing material in meshes, which will get loaded on demand at the first time the
11
- * dedicated mesh gets visible.
12
- * This is the case if the babylon file is a Combeenation "3d asset" which comes without materials, as the materials
13
- * are defined as "material assets".
46
+ * The main use cases are:
47
+ * - Marking missing material in meshes, which will get loaded on demand at the first time the dedicated mesh gets
48
+ * visible. This is the case if the babylon file is a Combeenation "3d asset" which comes without materials, as the
49
+ * materials are defined as "material assets".
50
+ * - Interpreting custom cbn data that have been injected in the babylon file, e.g. by the "decals editor" in the asset
51
+ * editor area
14
52
  */
15
53
  export function registerCustomCbnBabylonLoaderPlugin(): void {
16
54
  // get original plugin for babylon files
@@ -23,46 +61,32 @@ export function registerCustomCbnBabylonLoaderPlugin(): void {
23
61
  extensions: '.babylon',
24
62
  importMesh: previousLoaderPlugin.importMesh,
25
63
  load: previousLoaderPlugin.load,
26
- loadAssetContainer: function (scene, data, rootUrl, onError) {
27
- // temporarily remove all morph target managers from scene before loading the model to avoid clashes with existing
28
- // unique ids => see CB-9928
29
- // NOTE: morph target managers still exist on the dedicated mesh when getting removed from the scene, only
30
- // `scene.getMorphTargetManagerById` is affected, which is used internally in the `loadAssetContainer` function
31
- const curMorphTargetManagers = scene.morphTargetManagers;
32
- scene.morphTargetManagers = [];
33
-
64
+ loadAssetContainer: (scene, data, rootUrl, onError): ExtendedAssetContainer => {
34
65
  const dataParsed = JSON.parse(data as string);
35
66
  const importedContainer = previousLoaderPlugin.loadAssetContainer(scene, data, rootUrl);
36
67
 
37
68
  _addMissingMaterialMetadata(dataParsed, importedContainer);
38
69
  _reconstructTagsForInstancedMeshes(dataParsed, importedContainer);
70
+ _createDecals(dataParsed, importedContainer);
39
71
 
40
- // restore existing morph target managers
41
- scene.morphTargetManagers.push(...curMorphTargetManagers);
72
+ // add `cbnData` to output asset container, so that this information can be store as metadata for the model
73
+ const extendedContainer = importedContainer as ExtendedAssetContainer;
74
+ extendedContainer.cbnData = dataParsed.cbnData;
42
75
 
43
- return importedContainer;
76
+ return extendedContainer;
44
77
  },
45
78
  };
46
79
 
47
80
  SceneLoader.RegisterPlugin(customLoader);
48
81
  }
49
82
 
50
- type InstanceData = {
51
- name: string;
52
- tags?: string;
53
- };
54
-
55
- function _isMeshInstanceData(data: any): data is InstanceData {
56
- const hasName = isString(data.name);
57
- const hasValidTags = !data.tags || isString(data.tags);
58
- return hasName && hasValidTags;
83
+ function _isDataWithMeshes(data: any): data is DataWithMeshes {
84
+ return isArray(data?.meshes);
59
85
  }
60
86
 
61
- type MeshData = {
62
- name: string;
63
- materialId?: string;
64
- instances?: unknown[];
65
- };
87
+ function _isDataWithDecalConfigurations(data: any): data is DataWithDecalConfigurations {
88
+ return isArray(data?.cbnData?.decals);
89
+ }
66
90
 
67
91
  function _isMeshData(data: any): data is MeshData {
68
92
  const hasName = isString(data.name);
@@ -71,10 +95,31 @@ function _isMeshData(data: any): data is MeshData {
71
95
  return hasName && hasValidMaterialId;
72
96
  }
73
97
 
74
- type DataWithMeshes = { meshes: unknown[] };
98
+ function _isMeshInstanceData(data: any): data is InstanceData {
99
+ const hasName = isString(data.name);
100
+ const hasValidTags = !data.tags || isString(data.tags);
75
101
 
76
- function _isDataWithMeshes(data: any): data is DataWithMeshes {
77
- return data && isArray(data.meshes);
102
+ return hasName && hasValidTags;
103
+ }
104
+
105
+ function _isDecalConfiguration(data: any): data is ParsedDecalConfiguration {
106
+ const hasValidProps =
107
+ isString(data.name) &&
108
+ isString(data.sourceMeshName) &&
109
+ isArray(data.position) &&
110
+ isArray(data.normal) &&
111
+ isArray(data.size) &&
112
+ isNumber(data.angle) &&
113
+ isNumber(data.offset);
114
+
115
+ if (!hasValidProps) {
116
+ throw new ViewerError({
117
+ id: ViewerErrorIds.InvalidDecalConfiguration,
118
+ message: `Configuration for decal "${data.name}" invalid`,
119
+ });
120
+ }
121
+
122
+ return hasValidProps;
78
123
  }
79
124
 
80
125
  /**
@@ -138,3 +183,30 @@ function _reconstructTagsForInstancedMeshes(dataParsed: unknown, container: Asse
138
183
  }
139
184
  });
140
185
  }
186
+
187
+ /**
188
+ * This function interprets the decal configuration, that is stored top level in the babylon file (`dataParsed`) and
189
+ * creates decal meshes in the given `container`.
190
+ *
191
+ * Having the decal information stored in that way is required for the "decals editor" in the asset manager, as meshes
192
+ * created from decals have to be updateable.
193
+ *
194
+ * @param container This is being manipulated, by adding decal meshes to it based on the decal configuration read from
195
+ * `dataParsed`.
196
+ */
197
+ function _createDecals(dataParsed: unknown, container: AssetContainer): void {
198
+ if (!_isDataWithDecalConfigurations(dataParsed)) return;
199
+
200
+ const validatedDecals = dataParsed.cbnData.decals.filter(_isDecalConfiguration);
201
+
202
+ validatedDecals.forEach(decalConfig => {
203
+ const { materialId, ...decalMeshConfig } = decalConfig;
204
+ const decalMesh = createDecalMesh(decalMeshConfig, container, false);
205
+
206
+ // optionally set material directly
207
+ if (materialId) {
208
+ window.Cbn?.Assets.assertMaterialExists(materialId);
209
+ setInternalMetadataValue(decalMesh, 'deferredMaterial', materialId);
210
+ }
211
+ });
212
+ }
@@ -1,7 +1,7 @@
1
1
  import { BaseTexture, Material, Node } from '../index';
2
2
  import { cloneDeep } from 'lodash-es';
3
3
 
4
- type MetadataValue = string | number | boolean | undefined;
4
+ type MetadataValue = string | number | boolean | object | undefined;
5
5
  type MetadataTarget = Node | Material | BaseTexture;
6
6
  type MetadataKeys =
7
7
  // CBN babylon loader
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AssetContainer,
3
3
  BuiltInParameter,
4
+ DecalConfiguration,
4
5
  MaterialManager,
5
6
  MeshBuilder,
6
7
  ParameterManager,
@@ -10,6 +11,11 @@ import {
10
11
  ViewerError,
11
12
  ViewerErrorIds,
12
13
  } from '../index';
14
+ import {
15
+ CbnBabylonFileData,
16
+ ExtendedAssetContainer,
17
+ ParsedDecalConfiguration,
18
+ } from '../internal/cbn-custom-babylon-loader-plugin';
13
19
  import { cloneModelAssetContainer } from '../internal/cloning-helper';
14
20
  import { getInternalMetadataValue } from '../internal/metadata-helper';
15
21
  import { isArray } from 'lodash-es';
@@ -43,6 +49,7 @@ type Model = {
43
49
  url: string;
44
50
  state: ModelAssetState;
45
51
  assetContainer: AssetContainer;
52
+ cbnBabylonFileData?: CbnBabylonFileData;
46
53
  isClone: boolean;
47
54
  visibilityCallId?: number;
48
55
  };
@@ -359,6 +366,43 @@ export class ModelManager {
359
366
  return model.assetContainer;
360
367
  }
361
368
 
369
+ /**
370
+ * Returns the decals configuration of a certain model.\
371
+ * The model will be loaded before being able to access this configuration.\
372
+ * Decals are already converted to "normal" meshes when loading a model, still the original decals configuration can
373
+ * be useful e.g. for alterning decals.
374
+ */
375
+ public async getDecalsConfigurationOfModel(name: string): Promise<DecalConfiguration[]> {
376
+ const model = this._getModel(name);
377
+ if (!model) {
378
+ throw new ViewerError({
379
+ id: ViewerErrorIds.ModelNotRegistered,
380
+ message: `Can't get decals configuration of model "${name}" as model is not registered`,
381
+ });
382
+ }
383
+
384
+ if (model.state === 'notLoaded') {
385
+ await this._loadModel(model);
386
+ } else if (model.state === 'loading') {
387
+ await this._loadModelPromises[model.name];
388
+ }
389
+
390
+ if (model.state !== 'inScene') {
391
+ await this._prepareModelForScene(model);
392
+ }
393
+
394
+ // crop materialId as this is not part of `DecalConfiguration`
395
+ const decals = ((model.cbnBabylonFileData?.decals ?? []) as ParsedDecalConfiguration[]).map<DecalConfiguration>(
396
+ parsedDecal => {
397
+ const croppedDecal = parsedDecal;
398
+ delete croppedDecal.materialId;
399
+ return croppedDecal;
400
+ }
401
+ );
402
+
403
+ return decals;
404
+ }
405
+
362
406
  /**
363
407
  * Get model by name
364
408
  */
@@ -384,7 +428,18 @@ export class ModelManager {
384
428
 
385
429
  let assetContainer;
386
430
  try {
387
- assetContainer = await SceneLoader.LoadAssetContainerAsync('', model.url, this.viewer.scene);
431
+ const fullContainer = (await SceneLoader.LoadAssetContainerAsync(
432
+ '',
433
+ model.url,
434
+ this.viewer.scene
435
+ )) as ExtendedAssetContainer;
436
+
437
+ // crop and store custom cbn data from .babylon file
438
+ model.cbnBabylonFileData = fullContainer.cbnData;
439
+ delete fullContainer.cbnData;
440
+
441
+ // from here it's a basic asset container again
442
+ assetContainer = fullContainer as AssetContainer;
388
443
  } catch (e) {
389
444
  throw new ViewerError({
390
445
  id: ViewerErrorIds.AssetLoadingFailed,
@@ -29,6 +29,7 @@ export const ViewerErrorIds = {
29
29
  TextureCouldNotBeParsed: 'TextureCouldNotBeParsed',
30
30
  MaterialAlreadyExists: 'MaterialAlreadyExists',
31
31
  NotAClonedMaterial: 'NotAClonedMaterial',
32
+ InvalidDecalConfiguration: 'InvalidDecalConfiguration',
32
33
  };
33
34
 
34
35
  /** @internal */