@combeenation/3d-viewer 6.3.0 → 6.4.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/dist/lib-cjs/api/classes/viewer.js +6 -2
- package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
- package/dist/lib-cjs/api/util/babylonHelper.d.ts +4 -3
- package/dist/lib-cjs/api/util/babylonHelper.js +25 -5
- package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
- package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +3 -2
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js +36 -21
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js.map +1 -1
- package/dist/lib-cjs/api/util/stringHelper.d.ts +5 -1
- package/dist/lib-cjs/api/util/stringHelper.js +8 -1
- package/dist/lib-cjs/api/util/stringHelper.js.map +1 -1
- package/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/classes/viewer.ts +7 -2
- package/src/api/util/babylonHelper.ts +23 -10
- package/src/api/util/sceneLoaderHelper.ts +34 -20
- package/src/api/util/stringHelper.ts +8 -1
- package/src/dev.ts +4 -6
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import { SpecStorage } from '../store/specStorage';
|
|
|
8
8
|
import { backgroundDomeName, envHelperMetadataName } from '../util/babylonHelper';
|
|
9
9
|
import { debounce, loadJavascript, loadJson } from '../util/resourceHelper';
|
|
10
10
|
import { getCustomCbnBabylonLoaderPlugin } from '../util/sceneLoaderHelper';
|
|
11
|
+
import { replaceDots } from '../util/stringHelper';
|
|
11
12
|
import { isMeshIncludedInExclusionList } from '../util/structureHelper';
|
|
12
13
|
import { AnimationInterface } from './animationInterface';
|
|
13
14
|
import { Event } from './event';
|
|
@@ -79,6 +80,10 @@ export class Viewer extends EventBroadcaster {
|
|
|
79
80
|
* This data is most likely coming from babylon assets from the Combeenation system but other sources are also valid.
|
|
80
81
|
*/
|
|
81
82
|
public static generateSpec(genData: SpecGenerationData[]): StructureJson {
|
|
83
|
+
// dots in the variant name indicate inheritance, but this should not be the case for an auto-generated spec
|
|
84
|
+
// therefore dots are exchanged with slashes
|
|
85
|
+
const safeGenData: SpecGenerationData[] = genData.map(data => ({ ...data, name: replaceDots(data.name) }));
|
|
86
|
+
|
|
82
87
|
const spec: StructureJson = {
|
|
83
88
|
// common scene settings as suggested in the viewer docs
|
|
84
89
|
scene: {
|
|
@@ -98,7 +103,7 @@ export class Viewer extends EventBroadcaster {
|
|
|
98
103
|
// create one instance for each input entry
|
|
99
104
|
// name and variant are named accordingly from the input, instance will be hidden by default and lazy loading
|
|
100
105
|
// is activated
|
|
101
|
-
instances:
|
|
106
|
+
instances: safeGenData.map(instanceData => ({
|
|
102
107
|
name: instanceData.name,
|
|
103
108
|
variant: instanceData.name,
|
|
104
109
|
lazy: true,
|
|
@@ -110,7 +115,7 @@ export class Viewer extends EventBroadcaster {
|
|
|
110
115
|
// variants definition is also mapped to the input array, using the name as key and url as glTF source
|
|
111
116
|
// no elements are created here, since this should be done automatically from the system
|
|
112
117
|
// => create one element which contains all root nodes of the imported 3d file (GLB or Babylon)
|
|
113
|
-
variants:
|
|
118
|
+
variants: safeGenData.reduce((accVariants, curVariant) => {
|
|
114
119
|
accVariants[curVariant.name] = {
|
|
115
120
|
glTF: curVariant.url,
|
|
116
121
|
};
|
|
@@ -362,7 +362,7 @@ const changeEnvironment = function (scene: Scene, envDef: EnvironmentDefinition)
|
|
|
362
362
|
* Sets a material by a given material id as material property on the given node.
|
|
363
363
|
*
|
|
364
364
|
* If the material is not already available in the scene, the viewer tries to create a material based on a Combeenation
|
|
365
|
-
* [material asset](https://
|
|
365
|
+
* [material asset](https://docs.combeenation.com/docs/howto-create-and-use-babylon-and-material-asset).
|
|
366
366
|
* This of course only works if the viewer is used inside a Combeenation configurator.
|
|
367
367
|
*
|
|
368
368
|
* Furthermore this function also defers the material creation if the node is not visible yet to improve network &
|
|
@@ -409,7 +409,8 @@ const setMaterial = function (
|
|
|
409
409
|
};
|
|
410
410
|
|
|
411
411
|
/**
|
|
412
|
-
* Gets the Material either
|
|
412
|
+
* Gets the Material either from the given {@link Variant}, the given scene or tries to create it from an Combeenation
|
|
413
|
+
* material asset when inside a Combeenation configurator.
|
|
413
414
|
*/
|
|
414
415
|
const getOrCreateMaterial = function (scene: Scene, materialId: string, variant?: Variant): Material {
|
|
415
416
|
let chosenMaterial: Material | undefined | null;
|
|
@@ -431,17 +432,29 @@ const getOrCreateMaterial = function (scene: Scene, materialId: string, variant?
|
|
|
431
432
|
* Waits until the materials textures are loaded and sets the material on the node if there is no newer "set material"
|
|
432
433
|
* request
|
|
433
434
|
*/
|
|
434
|
-
const applyMaterialAfterTexturesLoaded = function (material: Material, node: AbstractMesh) {
|
|
435
|
+
const applyMaterialAfterTexturesLoaded = async function (material: Material, node: AbstractMesh) {
|
|
435
436
|
// set current material id as last valid id, in this case all previously set materials on the node will be invalidated
|
|
436
437
|
injectNodeMetadata(node, { [materialWaitingToBeSetMetadataName]: material.id }, false);
|
|
437
438
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
439
|
+
const promTexturesReady = new Promise<void>(resolve =>
|
|
440
|
+
BaseTexture.WhenAllReady(material.getActiveTextures(), resolve)
|
|
441
|
+
);
|
|
442
|
+
// this promise should only take some time on the first call of the corresponding shader (eg: PBRMaterial shader)
|
|
443
|
+
// on each other call of the same material/shader type there should be not be a waiting time, or at maximum one frame
|
|
444
|
+
// https://forum.babylonjs.com/t/mesh-flickering-when-setting-material-initially/37312
|
|
445
|
+
const promMaterialCompiled = material.forceCompilationAsync(node);
|
|
446
|
+
|
|
447
|
+
// material needs to fulfill 2 criterias before it's ready to use
|
|
448
|
+
// - textures need to be "ready" => downloaded
|
|
449
|
+
// - dedicated shader needs to be compiled
|
|
450
|
+
// if this would not be the case you can see some "flickering" when setting the material
|
|
451
|
+
await Promise.all([promTexturesReady, promMaterialCompiled]);
|
|
452
|
+
|
|
453
|
+
// textures ready, now check if the material is still up-to-date
|
|
454
|
+
if (material.id === node.metadata[materialWaitingToBeSetMetadataName]) {
|
|
455
|
+
node.material = material;
|
|
456
|
+
delete node.metadata[materialWaitingToBeSetMetadataName];
|
|
457
|
+
}
|
|
445
458
|
};
|
|
446
459
|
|
|
447
460
|
/**
|
|
@@ -2,6 +2,7 @@ import { applyMaterialAfterTexturesLoaded, getOrCreateMaterial, injectNodeMetada
|
|
|
2
2
|
import { ISceneLoaderPlugin } from '@babylonjs/core/Loading/sceneLoader';
|
|
3
3
|
import { Material } from '@babylonjs/core/Materials/material';
|
|
4
4
|
import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
|
|
5
|
+
import { Observer } from '@babylonjs/core/Misc/observable';
|
|
5
6
|
import { AssetContainer } from '@babylonjs/core/assetContainer';
|
|
6
7
|
//! overload DOM API Node due to name-clash with BJS
|
|
7
8
|
import { Node as BjsNode } from '@babylonjs/core/node';
|
|
@@ -9,6 +10,11 @@ import { Scene } from '@babylonjs/core/scene';
|
|
|
9
10
|
import { Nullable } from '@babylonjs/core/types';
|
|
10
11
|
import has from 'lodash-es/has';
|
|
11
12
|
|
|
13
|
+
// map for keeping track of active "node enable" observers
|
|
14
|
+
const enableObserverMap: {
|
|
15
|
+
[concerningNodeId: string]: { currNodeId: string; observer: Nullable<Observer<boolean>> }[];
|
|
16
|
+
} = {};
|
|
17
|
+
|
|
12
18
|
export const missingMaterialMetadataName = 'missingMaterial';
|
|
13
19
|
|
|
14
20
|
/**
|
|
@@ -41,12 +47,18 @@ export const getCustomCbnBabylonLoaderPlugin = function (previousLoaderPlugin: I
|
|
|
41
47
|
/**
|
|
42
48
|
* Return an observer to be applied to meshes in order to post-load missing materials
|
|
43
49
|
* upon set enabled/visible.
|
|
44
|
-
*
|
|
50
|
+
*
|
|
45
51
|
* @param concerningMesh Mesh to look for missing materials on, and create/apply to (when found).
|
|
46
52
|
* @returns observer
|
|
47
53
|
*/
|
|
48
|
-
export const getMaterialPostLoadObserver = function (
|
|
49
|
-
return (
|
|
54
|
+
export const getMaterialPostLoadObserver = function (concerningMesh: Mesh) {
|
|
55
|
+
return async () => {
|
|
56
|
+
const scene = concerningMesh.getScene();
|
|
57
|
+
|
|
58
|
+
// can't check `isEnabled` immediatly, since the enabled state of parents and childs is not synced yet
|
|
59
|
+
// postpone to "when scene ready" to ensure a correct parent-child enable relation
|
|
60
|
+
await scene.whenReadyAsync();
|
|
61
|
+
|
|
50
62
|
const hasBeenEnabled = concerningMesh.isEnabled(true);
|
|
51
63
|
const materialMissing = has(concerningMesh.metadata, missingMaterialMetadataName);
|
|
52
64
|
if (!hasBeenEnabled || !materialMissing) return;
|
|
@@ -57,6 +69,15 @@ export const getMaterialPostLoadObserver = function (targetMeshOrInstance: Abstr
|
|
|
57
69
|
applyMaterialAfterTexturesLoaded(material, concerningMesh);
|
|
58
70
|
// since the material is there now, we do not need the related metadata tag anymore
|
|
59
71
|
delete concerningMesh.metadata[missingMaterialMetadataName];
|
|
72
|
+
|
|
73
|
+
// remove all "enable" observers that were assigned to the concerning mesh
|
|
74
|
+
// the mesh got visible and therefore the observers are not needed anymore
|
|
75
|
+
enableObserverMap[concerningMesh.id].forEach(entry => {
|
|
76
|
+
const currNode = scene.getMeshById(entry.currNodeId);
|
|
77
|
+
currNode?.onEnabledStateChangedObservable.remove(entry.observer);
|
|
78
|
+
});
|
|
79
|
+
// also remove from the local observer map
|
|
80
|
+
delete enableObserverMap[concerningMesh.id];
|
|
60
81
|
};
|
|
61
82
|
};
|
|
62
83
|
|
|
@@ -98,30 +119,23 @@ export const addMissingMaterialObserver = function (node: BjsNode) {
|
|
|
98
119
|
// for each of our AbstractMeshes, set an observer on the AbstractMesh itself and all of its parents.
|
|
99
120
|
let currNode: Nullable<BjsNode> = node;
|
|
100
121
|
while (currNode) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// * `parentMesh` & `concerningMesh` are both enable
|
|
111
|
-
// -> material should be created as `concerningMesh` is now actually visible but it isn't, as all observers were
|
|
112
|
-
// only fired once 🔥
|
|
113
|
-
//
|
|
114
|
-
// However: Using `add` instead of `addOnce` requires rather complicated manual clean up work...
|
|
122
|
+
const callback = getMaterialPostLoadObserver(concerningNode);
|
|
123
|
+
const observer = currNode.onEnabledStateChangedObservable.add(callback);
|
|
124
|
+
|
|
125
|
+
// store the observer in a local map to keep track of the active "enable" observers
|
|
126
|
+
// observers will be removed when the concerning node gets enabled
|
|
127
|
+
if (!enableObserverMap[concerningNode.id]) {
|
|
128
|
+
enableObserverMap[concerningNode.id] = [];
|
|
129
|
+
}
|
|
130
|
+
enableObserverMap[concerningNode.id].push({ currNodeId: currNode.id, observer });
|
|
115
131
|
|
|
116
|
-
// add observer. needed only once per node, hence addOnce()
|
|
117
|
-
node.onEnabledStateChangedObservable.addOnce(getMaterialPostLoadObserver(currNode as AbstractMesh, concerningNode));
|
|
118
|
-
// console.log('## observer set on: ' + meshOrInstance.name);
|
|
119
132
|
currNode = currNode.parent;
|
|
120
133
|
}
|
|
121
134
|
};
|
|
122
135
|
|
|
123
136
|
/**
|
|
124
137
|
* Look up the provided materials (see library import) and create and return one if found.
|
|
138
|
+
*
|
|
125
139
|
* @param materialId BabylonJs material-id. E.g. 'concrete".
|
|
126
140
|
* @param scene BabylonJs scene
|
|
127
141
|
* @returns PBRMaterial | null
|
|
@@ -20,4 +20,11 @@ const camelToSnakeCase = function (str: string): string {
|
|
|
20
20
|
.toLowerCase();
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Replaces all dots from the input string with a desired character ('/' by default)
|
|
25
|
+
*/
|
|
26
|
+
const replaceDots = function (str: string, replaceChar = '/'): string {
|
|
27
|
+
return str.split('.').join(replaceChar);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { uuidv4, camelToSnakeCase, replaceDots };
|
package/src/dev.ts
CHANGED
|
@@ -12,12 +12,10 @@ import { set } from 'lodash-es';
|
|
|
12
12
|
const loadingElement = document.getElementById('loading') as HTMLDivElement;
|
|
13
13
|
|
|
14
14
|
Emitter.on(Event.BOOTSTRAP_START, () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
loadingElement!.style.display = 'none';
|
|
20
|
-
});
|
|
15
|
+
loadingElement!.style.display = 'block';
|
|
16
|
+
});
|
|
17
|
+
Emitter.on(Event.BOOTSTRAP_END, () => {
|
|
18
|
+
loadingElement!.style.display = 'none';
|
|
21
19
|
});
|
|
22
20
|
|
|
23
21
|
document.addEventListener('DOMContentLoaded', main);
|