@combeenation/3d-viewer 5.0.3 → 5.1.0-rc1

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.
@@ -0,0 +1,101 @@
1
+ import { Scene } from '@babylonjs/core/scene';
2
+ import { ISceneLoaderPlugin } from '@babylonjs/core/Loading/sceneLoader';
3
+ import { AssetContainer } from '@babylonjs/core/assetContainer';
4
+ import { injectNodeMetadata } from './babylonHelper';
5
+ import has from 'lodash-es/has';
6
+ import { Material } from '@babylonjs/core/Materials/material';
7
+
8
+ const missingMaterialMetadataName = 'missingMaterial';
9
+
10
+ /**
11
+ * Create and return a custom loader plugin to be registered with SceneLoader, that allows
12
+ * us to run our own code against the input data before using the standard procedure to
13
+ * import.
14
+ * @param previousLoaderPlugin the actual loader that's executed after manipulating the input data
15
+ * @returns Custom loader plugin to be registered with SceneLoader.RegisterPlugin()
16
+ */
17
+ const getCustomCbnBabylonLoaderPlugin = function (previousLoaderPlugin: ISceneLoaderPlugin): ISceneLoaderPlugin {
18
+ const customLoader: ISceneLoaderPlugin = {
19
+ name: 'cbnCustomBabylonLoader',
20
+ extensions: '.babylon',
21
+ importMesh: previousLoaderPlugin.importMesh,
22
+ load: previousLoaderPlugin.load,
23
+ loadAssetContainer: function (scene, data, rootUrl, onError) {
24
+ //* 1) --- manipulate ORIGINAL data
25
+ const dataParsed = JSON.parse(data);
26
+ //* 2) --- call default (non-custom) loading method
27
+ const importedContainer = previousLoaderPlugin.loadAssetContainer(scene, data, rootUrl);
28
+ //* 3) --- manipulate IMPORTED data
29
+ addMissingMaterialMetadata(dataParsed, importedContainer);
30
+ //* 4) --- return imported data
31
+ return importedContainer;
32
+ },
33
+ };
34
+ return customLoader;
35
+ };
36
+
37
+ /**
38
+ * Return an observer to be applied to meshes in order to post-load missing materials
39
+ * upon set enabled/visible.
40
+ * @param targetMeshOrInstance AbstractMesh the observer will be applied to
41
+ * @param concerningMesh Mesh to look for missing materials on, and create/apply to (when found).
42
+ * @returns observer
43
+ */
44
+ const getMaterialPostLoadObserver = function (targetMeshOrInstance: AbstractMesh, concerningMesh: Mesh) {
45
+ return (_eventData: any, _eventState: any) => {
46
+ const hasBeenEnabled = targetMeshOrInstance.isEnabled(true);
47
+ const materialMissing = has(concerningMesh.metadata, missingMaterialMetadataName);
48
+ if (!hasBeenEnabled || !materialMissing) return;
49
+ // get id of missing material
50
+ const missingMatId = concerningMesh.metadata[missingMaterialMetadataName];
51
+ // try to find material on the scene
52
+ const existingMat = concerningMesh.getScene().getMaterialById(missingMatId);
53
+ // assign either existing material or freshly created one
54
+ concerningMesh.material = existingMat || getMaterialFromCbnAssets(missingMatId, concerningMesh.getScene());
55
+ // since the material is there now, we do not need the related metadata tag anymore
56
+ delete concerningMesh.metadata[missingMaterialMetadataName];
57
+ };
58
+ };
59
+
60
+ /**
61
+ * Internal function that compares the original meshes on a .babylon file with what was loaded,
62
+ * and tags missing materials with respective metadata on respective meshes.
63
+ * @param dataParsed original data
64
+ * @param container loaded data
65
+ */
66
+ const addMissingMaterialMetadata = function (dataParsed: any, container: AssetContainer) {
67
+ container.meshes.forEach(currMeshImported => {
68
+ for (const currMeshOriginal of dataParsed.meshes) {
69
+ if (currMeshOriginal.name !== currMeshImported.name) continue;
70
+ // we're dealing with the original version of the current imported mesh now
71
+ const materialOnImportedMesh = currMeshImported.material?.id;
72
+ const materialOnOriginalMesh = currMeshOriginal.materialId;
73
+ if (!materialOnOriginalMesh || materialOnImportedMesh === materialOnOriginalMesh) continue;
74
+ // TODO: Just for easier debugging. Remove before publishment...
75
+ console.info(
76
+ `Adding "${missingMaterialMetadataName}" info on mesh "${currMeshOriginal.name}" with material "${currMeshOriginal.materialId}"`
77
+ );
78
+ // if we're here, the imported mesh has different material than original one
79
+ window.Cbn?.Assets.assertMaterialExists(materialOnOriginalMesh);
80
+ injectNodeMetadata(currMeshImported, { [missingMaterialMetadataName]: materialOnOriginalMesh }, false);
81
+ break;
82
+ }
83
+ });
84
+ };
85
+
86
+ /**
87
+ * Look up the provided materials (see library import) and create and return one if found.
88
+ * @param materialId BabylonJs material-id. E.g. 'concrete".
89
+ * @param scene BabylonJs scene
90
+ * @returns PBRMaterial | null
91
+ */
92
+ export const getMaterialFromCbnAssets = function (materialId: string, scene: Scene): Material | null {
93
+ const materialDefinition = window.Cbn?.Assets.getMaterial(materialId);
94
+ // The generic `Material.Parse` actually returns a more specific material like `BABYLON.StandardMaterial`,
95
+ // `BABYLON.PBRMaterial` or stuff like `BABYLON.PBRMetallicRoughnessMaterial` etc. based on the given `customType`
96
+ // within the material JSON definition
97
+ const material = materialDefinition && Material.Parse(materialDefinition, scene, '');
98
+ return material || null;
99
+ };
100
+
101
+ export { getCustomCbnBabylonLoaderPlugin, getMaterialPostLoadObserver, missingMaterialMetadataName };
package/src/dev.ts CHANGED
@@ -3,7 +3,14 @@ import { set } from 'lodash-es';
3
3
 
4
4
  import { Emitter, Viewer } from '.';
5
5
 
6
- import { createSpec, beforeBootstrap, afterBootstrap, createUIelements } from '../assets/index';
6
+ import {
7
+ createSpec,
8
+ beforeBootstrap,
9
+ afterBootstrap,
10
+ createUIelements,
11
+ getMaterial,
12
+ mockMaterials,
13
+ } from '../assets/index';
7
14
 
8
15
  const loadingElement = document.getElementById('loading') as HTMLDivElement;
9
16
 
@@ -18,6 +25,21 @@ Emitter.on(Event.BOOTSTRAP_START, () => {
18
25
 
19
26
  document.addEventListener('DOMContentLoaded', main);
20
27
 
28
+ window.Cbn = {
29
+ Assets: {
30
+ getMaterial(materialId: string) {
31
+ const material = getMaterial(materialId);
32
+ if (material) return material;
33
+
34
+ // Fallback to random mock material
35
+ return mockMaterials[Math.floor(Math.random() * mockMaterials.length)];
36
+ },
37
+ assertMaterialExists(materialId: string): boolean {
38
+ return true;
39
+ },
40
+ },
41
+ };
42
+
21
43
  async function main() {
22
44
  const viewer = await bootstrapViewer();
23
45
  // "Export" for console testing...
package/src/tsconfig.json CHANGED
@@ -15,6 +15,7 @@
15
15
  "baseUrl": ".",
16
16
  "importHelpers": true,
17
17
  "resolveJsonModule": true,
18
+ "allowSyntheticDefaultImports": true,
18
19
  "lib": ["es6", "dom"]
19
20
  },
20
21
  "typedocOptions": {
package/src/types.d.ts CHANGED
@@ -1,3 +1,28 @@
1
1
  /** @ignore **/
2
2
  declare let IS_PRODUCTION: boolean;
3
3
  declare let VERSION_INFORMATION: string;
4
+
5
+ interface Window {
6
+ Cbn:
7
+ | undefined /* window.Cbn is only available when viewer runs inside Combeenation configurator */
8
+ | {
9
+ Assets: {
10
+ /**
11
+ * Retrieve material definition from configurator
12
+ *
13
+ * @param materialId
14
+ *
15
+ * @return Undefined if no definition for the given material name exists, otherwise an "JSON object" which can
16
+ * be passed to `BABYLON.Material.Parse` to create a runtime material object.
17
+ */
18
+ getMaterial(materialId: string): object | undefined;
19
+
20
+ /**
21
+ * Checks if a definition for a given material name exists in the configurator and issues a warning if not.
22
+ *
23
+ * @param materialId
24
+ */
25
+ assertMaterialExists(materialId: string): boolean;
26
+ };
27
+ };
28
+ }