@combeenation/3d-viewer 5.1.2 → 5.2.0-alpha4

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 (221) hide show
  1. package/README.md +111 -111
  2. package/dist/lib-cjs/api/classes/animationInterface.d.ts +8 -8
  3. package/dist/lib-cjs/api/classes/animationInterface.js +2 -1
  4. package/dist/lib-cjs/api/classes/dottedPath.d.ts +79 -79
  5. package/dist/lib-cjs/api/classes/dottedPath.js +166 -187
  6. package/dist/lib-cjs/api/classes/dottedPath.js.map +1 -1
  7. package/dist/lib-cjs/api/classes/element.d.ts +149 -149
  8. package/dist/lib-cjs/api/classes/element.js +668 -823
  9. package/dist/lib-cjs/api/classes/element.js.map +1 -1
  10. package/dist/lib-cjs/api/classes/event.d.ts +326 -326
  11. package/dist/lib-cjs/api/classes/event.js +349 -371
  12. package/dist/lib-cjs/api/classes/event.js.map +1 -1
  13. package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +26 -26
  14. package/dist/lib-cjs/api/classes/eventBroadcaster.js +49 -53
  15. package/dist/lib-cjs/api/classes/eventBroadcaster.js.map +1 -1
  16. package/dist/lib-cjs/api/classes/parameter.d.ts +336 -336
  17. package/dist/lib-cjs/api/classes/parameter.js +461 -471
  18. package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
  19. package/dist/lib-cjs/api/classes/parameterObservable.d.ts +36 -36
  20. package/dist/lib-cjs/api/classes/parameterObservable.js +97 -101
  21. package/dist/lib-cjs/api/classes/parameterObservable.js.map +1 -1
  22. package/dist/lib-cjs/api/classes/parameterizable.d.ts +15 -15
  23. package/dist/lib-cjs/api/classes/parameterizable.js +102 -149
  24. package/dist/lib-cjs/api/classes/parameterizable.js.map +1 -1
  25. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +45 -44
  26. package/dist/lib-cjs/api/classes/placementAnimation.js +175 -163
  27. package/dist/lib-cjs/api/classes/placementAnimation.js.map +1 -1
  28. package/dist/lib-cjs/api/classes/variant.d.ts +234 -234
  29. package/dist/lib-cjs/api/classes/variant.js +836 -1203
  30. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  31. package/dist/lib-cjs/api/classes/variantInstance.d.ts +45 -45
  32. package/dist/lib-cjs/api/classes/variantInstance.js +101 -108
  33. package/dist/lib-cjs/api/classes/variantInstance.js.map +1 -1
  34. package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +17 -17
  35. package/dist/lib-cjs/api/classes/variantParameterizable.js +88 -99
  36. package/dist/lib-cjs/api/classes/variantParameterizable.js.map +1 -1
  37. package/dist/lib-cjs/api/classes/viewer.d.ts +187 -184
  38. package/dist/lib-cjs/api/classes/viewer.js +593 -731
  39. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  40. package/dist/lib-cjs/api/classes/viewerLight.d.ts +66 -66
  41. package/dist/lib-cjs/api/classes/viewerLight.js +343 -392
  42. package/dist/lib-cjs/api/classes/viewerLight.js.map +1 -1
  43. package/dist/lib-cjs/api/internal/lensRendering.d.ts +8 -8
  44. package/dist/lib-cjs/api/internal/lensRendering.js +11 -11
  45. package/dist/lib-cjs/api/internal/lensRendering.js.map +1 -1
  46. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +13 -13
  47. package/dist/lib-cjs/api/internal/sceneSetup.js +225 -238
  48. package/dist/lib-cjs/api/internal/sceneSetup.js.map +1 -1
  49. package/dist/lib-cjs/api/manager/animationManager.d.ts +30 -29
  50. package/dist/lib-cjs/api/manager/animationManager.js +126 -130
  51. package/dist/lib-cjs/api/manager/animationManager.js.map +1 -1
  52. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +65 -65
  53. package/dist/lib-cjs/api/manager/gltfExportManager.js +196 -223
  54. package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -1
  55. package/dist/lib-cjs/api/manager/sceneManager.d.ts +31 -31
  56. package/dist/lib-cjs/api/manager/sceneManager.js +127 -152
  57. package/dist/lib-cjs/api/manager/sceneManager.js.map +1 -1
  58. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +92 -92
  59. package/dist/lib-cjs/api/manager/variantInstanceManager.js +260 -335
  60. package/dist/lib-cjs/api/manager/variantInstanceManager.js.map +1 -1
  61. package/dist/lib-cjs/api/store/specStorage.d.ts +24 -24
  62. package/dist/lib-cjs/api/store/specStorage.js +50 -51
  63. package/dist/lib-cjs/api/store/specStorage.js.map +1 -1
  64. package/dist/lib-cjs/api/util/babylonHelper.d.ts +174 -174
  65. package/dist/lib-cjs/api/util/babylonHelper.js +591 -611
  66. package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
  67. package/dist/lib-cjs/api/util/globalTypes.d.ts +366 -366
  68. package/dist/lib-cjs/api/util/globalTypes.js +1 -1
  69. package/dist/lib-cjs/api/util/resourceHelper.d.ts +58 -58
  70. package/dist/lib-cjs/api/util/resourceHelper.js +201 -257
  71. package/dist/lib-cjs/api/util/resourceHelper.js.map +1 -1
  72. package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +35 -35
  73. package/dist/lib-cjs/api/util/sceneLoaderHelper.js +141 -140
  74. package/dist/lib-cjs/api/util/sceneLoaderHelper.js.map +1 -1
  75. package/dist/lib-cjs/api/util/stringHelper.d.ts +9 -9
  76. package/dist/lib-cjs/api/util/stringHelper.js +23 -25
  77. package/dist/lib-cjs/api/util/stringHelper.js.map +1 -1
  78. package/dist/lib-cjs/api/util/structureHelper.d.ts +9 -9
  79. package/dist/lib-cjs/api/util/structureHelper.js +48 -48
  80. package/dist/lib-cjs/api/util/structureHelper.js.map +1 -1
  81. package/dist/lib-cjs/buildinfo.json +3 -3
  82. package/dist/lib-cjs/index.d.ts +51 -49
  83. package/dist/lib-cjs/index.js +110 -89
  84. package/dist/lib-cjs/index.js.map +1 -1
  85. package/dist/lib-es6/api/classes/animationInterface.d.ts +8 -8
  86. package/dist/lib-es6/api/classes/animationInterface.js +1 -1
  87. package/dist/lib-es6/api/classes/dottedPath.d.ts +79 -79
  88. package/dist/lib-es6/api/classes/dottedPath.js +162 -184
  89. package/dist/lib-es6/api/classes/dottedPath.js.map +1 -1
  90. package/dist/lib-es6/api/classes/element.d.ts +149 -149
  91. package/dist/lib-es6/api/classes/element.js +664 -820
  92. package/dist/lib-es6/api/classes/element.js.map +1 -1
  93. package/dist/lib-es6/api/classes/event.d.ts +326 -326
  94. package/dist/lib-es6/api/classes/event.js +345 -368
  95. package/dist/lib-es6/api/classes/event.js.map +1 -1
  96. package/dist/lib-es6/api/classes/eventBroadcaster.d.ts +26 -26
  97. package/dist/lib-es6/api/classes/eventBroadcaster.js +42 -50
  98. package/dist/lib-es6/api/classes/eventBroadcaster.js.map +1 -1
  99. package/dist/lib-es6/api/classes/parameter.d.ts +336 -336
  100. package/dist/lib-es6/api/classes/parameter.js +457 -468
  101. package/dist/lib-es6/api/classes/parameter.js.map +1 -1
  102. package/dist/lib-es6/api/classes/parameterObservable.d.ts +36 -36
  103. package/dist/lib-es6/api/classes/parameterObservable.js +93 -98
  104. package/dist/lib-es6/api/classes/parameterObservable.js.map +1 -1
  105. package/dist/lib-es6/api/classes/parameterizable.d.ts +15 -15
  106. package/dist/lib-es6/api/classes/parameterizable.js +98 -146
  107. package/dist/lib-es6/api/classes/parameterizable.js.map +1 -1
  108. package/dist/lib-es6/api/classes/placementAnimation.d.ts +45 -44
  109. package/dist/lib-es6/api/classes/placementAnimation.js +171 -160
  110. package/dist/lib-es6/api/classes/placementAnimation.js.map +1 -1
  111. package/dist/lib-es6/api/classes/variant.d.ts +234 -234
  112. package/dist/lib-es6/api/classes/variant.js +832 -1198
  113. package/dist/lib-es6/api/classes/variant.js.map +1 -1
  114. package/dist/lib-es6/api/classes/variantInstance.d.ts +45 -45
  115. package/dist/lib-es6/api/classes/variantInstance.js +97 -105
  116. package/dist/lib-es6/api/classes/variantInstance.js.map +1 -1
  117. package/dist/lib-es6/api/classes/variantParameterizable.d.ts +17 -17
  118. package/dist/lib-es6/api/classes/variantParameterizable.js +84 -96
  119. package/dist/lib-es6/api/classes/variantParameterizable.js.map +1 -1
  120. package/dist/lib-es6/api/classes/viewer.d.ts +187 -184
  121. package/dist/lib-es6/api/classes/viewer.js +586 -728
  122. package/dist/lib-es6/api/classes/viewer.js.map +1 -1
  123. package/dist/lib-es6/api/classes/viewerLight.d.ts +66 -66
  124. package/dist/lib-es6/api/classes/viewerLight.js +316 -389
  125. package/dist/lib-es6/api/classes/viewerLight.js.map +1 -1
  126. package/dist/lib-es6/api/internal/lensRendering.d.ts +8 -8
  127. package/dist/lib-es6/api/internal/lensRendering.js +8 -8
  128. package/dist/lib-es6/api/internal/lensRendering.js.map +1 -1
  129. package/dist/lib-es6/api/internal/sceneSetup.d.ts +13 -13
  130. package/dist/lib-es6/api/internal/sceneSetup.js +197 -233
  131. package/dist/lib-es6/api/internal/sceneSetup.js.map +1 -1
  132. package/dist/lib-es6/api/manager/animationManager.d.ts +30 -29
  133. package/dist/lib-es6/api/manager/animationManager.js +122 -127
  134. package/dist/lib-es6/api/manager/animationManager.js.map +1 -1
  135. package/dist/lib-es6/api/manager/gltfExportManager.d.ts +65 -65
  136. package/dist/lib-es6/api/manager/gltfExportManager.js +192 -220
  137. package/dist/lib-es6/api/manager/gltfExportManager.js.map +1 -1
  138. package/dist/lib-es6/api/manager/sceneManager.d.ts +31 -31
  139. package/dist/lib-es6/api/manager/sceneManager.js +123 -149
  140. package/dist/lib-es6/api/manager/sceneManager.js.map +1 -1
  141. package/dist/lib-es6/api/manager/variantInstanceManager.d.ts +92 -92
  142. package/dist/lib-es6/api/manager/variantInstanceManager.js +256 -332
  143. package/dist/lib-es6/api/manager/variantInstanceManager.js.map +1 -1
  144. package/dist/lib-es6/api/store/specStorage.d.ts +24 -24
  145. package/dist/lib-es6/api/store/specStorage.js +46 -48
  146. package/dist/lib-es6/api/store/specStorage.js.map +1 -1
  147. package/dist/lib-es6/api/util/babylonHelper.d.ts +174 -174
  148. package/dist/lib-es6/api/util/babylonHelper.js +561 -581
  149. package/dist/lib-es6/api/util/babylonHelper.js.map +1 -1
  150. package/dist/lib-es6/api/util/globalTypes.d.ts +366 -366
  151. package/dist/lib-es6/api/util/globalTypes.js +1 -1
  152. package/dist/lib-es6/api/util/resourceHelper.d.ts +58 -58
  153. package/dist/lib-es6/api/util/resourceHelper.js +191 -247
  154. package/dist/lib-es6/api/util/resourceHelper.js.map +1 -1
  155. package/dist/lib-es6/api/util/sceneLoaderHelper.d.ts +35 -35
  156. package/dist/lib-es6/api/util/sceneLoaderHelper.js +131 -133
  157. package/dist/lib-es6/api/util/sceneLoaderHelper.js.map +1 -1
  158. package/dist/lib-es6/api/util/stringHelper.d.ts +9 -9
  159. package/dist/lib-es6/api/util/stringHelper.js +19 -21
  160. package/dist/lib-es6/api/util/stringHelper.js.map +1 -1
  161. package/dist/lib-es6/api/util/structureHelper.d.ts +9 -9
  162. package/dist/lib-es6/api/util/structureHelper.js +45 -45
  163. package/dist/lib-es6/api/util/structureHelper.js.map +1 -1
  164. package/dist/lib-es6/buildinfo.json +3 -3
  165. package/dist/lib-es6/index.d.ts +51 -49
  166. package/dist/lib-es6/index.js +48 -50
  167. package/dist/lib-es6/index.js.map +1 -1
  168. package/dist/lib-full/lens-rendering.js +2 -0
  169. package/dist/lib-full/lens-rendering.js.map +1 -0
  170. package/dist/lib-full/main.js +3 -0
  171. package/dist/lib-full/main.js.LICENSE.txt +9 -0
  172. package/dist/lib-full/main.js.map +1 -0
  173. package/package.json +76 -88
  174. package/src/api/classes/animationInterface.ts +10 -10
  175. package/src/api/classes/dottedPath.ts +181 -181
  176. package/src/api/classes/element.ts +716 -716
  177. package/src/api/classes/event.ts +367 -367
  178. package/src/api/classes/eventBroadcaster.ts +52 -52
  179. package/src/api/classes/parameter.ts +494 -494
  180. package/src/api/classes/parameterObservable.ts +100 -100
  181. package/src/api/classes/parameterizable.ts +87 -87
  182. package/src/api/classes/placementAnimation.ts +161 -160
  183. package/src/api/classes/variant.ts +904 -905
  184. package/src/api/classes/variantInstance.ts +97 -97
  185. package/src/api/classes/variantParameterizable.ts +85 -85
  186. package/src/api/classes/viewer.ts +669 -668
  187. package/src/api/classes/viewerLight.ts +334 -334
  188. package/src/api/internal/debugViewer.ts +90 -90
  189. package/src/api/internal/lensRendering.ts +9 -10
  190. package/src/api/internal/sceneSetup.ts +204 -204
  191. package/src/api/manager/animationManager.ts +143 -142
  192. package/src/api/manager/gltfExportManager.ts +191 -191
  193. package/src/api/manager/sceneManager.ts +127 -127
  194. package/src/api/manager/variantInstanceManager.ts +265 -265
  195. package/src/api/store/specStorage.ts +51 -51
  196. package/src/api/util/babylonHelper.ts +663 -663
  197. package/src/api/util/globalTypes.ts +413 -413
  198. package/src/api/util/resourceHelper.ts +189 -189
  199. package/src/api/util/sceneLoaderHelper.ts +148 -147
  200. package/src/api/util/stringHelper.ts +23 -23
  201. package/src/api/util/structureHelper.ts +49 -49
  202. package/src/buildinfo.json +3 -3
  203. package/src/dev.ts +61 -64
  204. package/src/index.ts +96 -94
  205. package/src/types.d.ts +28 -28
  206. package/dist/lib-cjs/api/classes/elementParameterizable.d.ts +0 -14
  207. package/dist/lib-cjs/api/classes/elementParameterizable.js +0 -135
  208. package/dist/lib-cjs/api/classes/elementParameterizable.js.map +0 -1
  209. package/dist/lib-cjs/api/internal/debugViewer.d.ts +0 -13
  210. package/dist/lib-cjs/api/internal/debugViewer.js +0 -87
  211. package/dist/lib-cjs/api/internal/debugViewer.js.map +0 -1
  212. package/dist/lib-es6/api/internal/debugViewer.d.ts +0 -13
  213. package/dist/lib-es6/api/internal/debugViewer.js +0 -84
  214. package/dist/lib-es6/api/internal/debugViewer.js.map +0 -1
  215. package/dist/webpack-stats.json +0 -0
  216. package/src/commonjs.tsconfig.json +0 -10
  217. package/src/declaration.tsconfig.json +0 -8
  218. package/src/es6.tsconfig.json +0 -10
  219. package/src/pagesconfig.json +0 -81
  220. package/src/tsconfig.json +0 -33
  221. package/src/tsconfig.types.json +0 -9
@@ -1,905 +1,904 @@
1
- import { AssetContainer } from '@babylonjs/core/assetContainer';
2
- import { Light } from '@babylonjs/core/Lights/light';
3
- import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
4
- import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
5
- import { Material } from '@babylonjs/core/Materials/material';
6
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
7
- import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
8
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
9
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
10
- import '@babylonjs/loaders/glTF/2.0/glTFLoader';
11
- import { cloneDeep, concat, get, has, isElement, isEmpty, isEqual, isString, merge, reject, set } from 'lodash-es';
12
- import { deactivateTransformNode, getDottedPathForNode, injectNodeMetadata } from '../util/babylonHelper';
13
- import { loadJson, mergeMaps } from '../util/resourceHelper';
14
- import { DottedPath } from './dottedPath';
15
- import { Element } from './element';
16
- import { Event } from './event';
17
- import { Parameter } from './parameter';
18
- import { Parameterizable } from './parameterizable';
19
- import { ParameterObservable } from './parameterObservable';
20
- import { VariantParameterizable } from './variantParameterizable';
21
- import { Viewer } from './viewer';
22
- import { ViewerLight } from './viewerLight';
23
- import { getMaterialFromCbnAssets } from '../util/sceneLoaderHelper';
24
-
25
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
26
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
27
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_sheen';
28
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_ior';
29
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness';
30
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_specular';
31
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_translucency';
32
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_transmission';
33
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_unlit';
34
- import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
35
-
36
- /**
37
- * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
38
- */
39
- export class Variant extends Parameterizable {
40
- public assetContainer: AssetContainer;
41
-
42
- public readonly elements: Element[] = [];
43
-
44
- public readonly viewerLights: ViewerLight[] = [];
45
-
46
- public structureJson: StructureJson;
47
-
48
- protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
49
-
50
- protected readonly _children: Map<string, Variant> = new Map();
51
-
52
- protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
53
-
54
- /**
55
- * @internal
56
- */
57
- private parametersInitialized: boolean = false;
58
-
59
- /**
60
- * Constructor.
61
- */
62
- protected constructor(
63
- public readonly name: string,
64
- protected readonly _structureJson: StructureJson,
65
- public readonly viewer: Viewer,
66
- public readonly parent?: Variant
67
- ) {
68
- super(cloneDeep(_structureJson.parameterDeclaration), cloneDeep(_structureJson.parameters));
69
- for (const parameter in _structureJson.parameterDeclaration) {
70
- if (!(parameter in (_structureJson.parameters || []))) {
71
- throw new Error(`No default value for parameter "${parameter}" defined.`);
72
- }
73
- }
74
- this.assetContainer = new AssetContainer(viewer.scene);
75
- this.structureJson = cloneDeep(_structureJson);
76
- }
77
-
78
- /**
79
- * Creates a {@link Variant} based on given parameters.
80
- *
81
- * @throws Error if "gltf" property is provided without a filename
82
- */
83
- public static async create(
84
- name: string,
85
- structureJson: StructureJson,
86
- viewer: Viewer,
87
- parent?: Variant
88
- ): Promise<Variant> {
89
- const variant = new Variant(name, structureJson, viewer, parent);
90
- await variant.loadAssets();
91
- return variant;
92
- }
93
-
94
- /**
95
- * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
96
- */
97
- get ancestors(): Variant[] {
98
- const ancestors = [];
99
- let variant: Variant = this;
100
- while (variant.parent) {
101
- ancestors.unshift(variant.parent);
102
- variant = variant.parent;
103
- }
104
- return ancestors;
105
- }
106
-
107
- /**
108
- * The root {@link Variant}.
109
- */
110
- get root(): Variant {
111
- return this.ancestors[0] ?? this;
112
- }
113
-
114
- /**
115
- * The {@link DottedPath} in the built tree of {@link Variant}s.
116
- * E.g. "_.top-1.sub-2.sub-sub-3"
117
- */
118
- get dottedPath(): DottedPath {
119
- const parentIds = this.ancestors.map(ancestor => {
120
- return ancestor.name;
121
- });
122
- return DottedPath.createFromParts(parentIds).addPart(this.name);
123
- }
124
-
125
- /**
126
- * The id representing a {@link DottedPath}.
127
- */
128
- get id(): string {
129
- const dottedPath = DottedPath.create(this.dottedPath);
130
- dottedPath.shiftPart(); // remove root
131
- return dottedPath.path || '_';
132
- }
133
-
134
- /**
135
- * The defined glTF Asset.
136
- */
137
- get glTF(): Asset | undefined {
138
- if (this.structureJson.glTF) {
139
- if (isString(this.structureJson.glTF)) {
140
- return {
141
- rootUrl: this.structureJson.glTF,
142
- fileName: undefined,
143
- };
144
- }
145
- if (isEmpty(this.structureJson.glTF.rootUrl)) {
146
- throw new Error(`No "rootUrl" defined in "glTF" definition for variant "${this.id}".`);
147
- }
148
- return this.structureJson.glTF;
149
- }
150
- }
151
-
152
- /**
153
- * The defined glTF URI.
154
- */
155
- get glTFUri(): string | undefined {
156
- if (this.glTF) {
157
- return [this.glTF.rootUrl, this.glTF.fileName].join('');
158
- }
159
- }
160
-
161
- /**
162
- * The inherited defined glTF URI.
163
- */
164
- get inheritedGlTFUri(): string | undefined {
165
- if (!this.glTFUri && this.parent) {
166
- return this.parent.inheritedGlTFUri;
167
- }
168
- return this.glTFUri;
169
- }
170
-
171
- /**
172
- * The TransformNodes of the {@link Variant}.
173
- */
174
- get nodes(): TransformNode[] {
175
- const rootNodes = this.assetContainer
176
- .getNodes()
177
- .filter(n => n instanceof TransformNode && !n.parent) as TransformNode[];
178
- return rootNodes;
179
- }
180
-
181
- /**
182
- * The {@link ViewerLight}s of the {@link Variant}.
183
- */
184
- get lights(): Light[] {
185
- return this.assetContainer.lights;
186
- }
187
-
188
- /**
189
- * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
190
- */
191
- get dottedNodes(): Map<DottedPath, TransformNode> {
192
- if (!this._dottedNodes) {
193
- const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode);
194
- const dottedNodes = new Map();
195
- nodes.forEach(node => {
196
- dottedNodes.set(node.metadata.dottedPath, node);
197
- });
198
- this._dottedNodes = dottedNodes;
199
- }
200
- return this._dottedNodes;
201
- }
202
-
203
- /**
204
- * The Materials of the {@link Variant}.
205
- */
206
- get materials(): Material[] {
207
- return this.assetContainer.materials;
208
- }
209
-
210
- /**
211
- * All {@link Element}s from this {@link Variant}'s parents.
212
- */
213
- get inheritedElements(): Element[] {
214
- let elements: Element[] = [];
215
- this.ancestors.forEach(ancestor => {
216
- elements = concat(elements, ancestor.elements);
217
- });
218
- return concat(elements, this.elements);
219
- }
220
-
221
- /**
222
- * All {@link ViewerLight}s inherited from this {@link Variant}'s parents.
223
- */
224
- get inheritedViewerLights(): ViewerLight[] {
225
- let viewerLights: ViewerLight[] = [];
226
- this.ancestors.forEach(ancestor => {
227
- viewerLights = concat(viewerLights, ancestor.viewerLights);
228
- });
229
- return concat(viewerLights, this.viewerLights);
230
- }
231
-
232
- /**
233
- * All TransformNodes inherited from this {@link Variant}'s parents.
234
- */
235
- get inheritedNodes(): TransformNode[] {
236
- let nodes: TransformNode[] = [];
237
- this.ancestors.forEach(ancestor => {
238
- nodes = concat(nodes, ancestor.nodes);
239
- });
240
- return concat(nodes, this.nodes);
241
- }
242
-
243
- /**
244
- * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
245
- */
246
- get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
247
- let dottedNodes = this.dottedNodes;
248
- this.ancestors.forEach(ancestor => {
249
- dottedNodes = mergeMaps(dottedNodes, ancestor.dottedNodes);
250
- });
251
- return dottedNodes;
252
- }
253
-
254
- /**
255
- * All Lights inherited from this {@link Variant}'s parents.
256
- */
257
- get inheritedLights(): Light[] {
258
- let lights: Light[] = [];
259
- this.ancestors.forEach(ancestor => {
260
- lights = concat(lights, ancestor.lights);
261
- });
262
- return concat(lights, this.lights);
263
- }
264
-
265
- /**
266
- * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
267
- */
268
- get inheritedParameterDeclaration(): ParameterDeclarations {
269
- let declaration = {};
270
- this.ancestors.forEach(ancestor => {
271
- merge(declaration, ancestor.parameterDeclaration);
272
- });
273
- return merge(declaration, this.parameterDeclaration);
274
- }
275
-
276
- /**
277
- * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
278
- */
279
- get inheritedParameters(): ParameterBag {
280
- let parameters = {};
281
- this.ancestors.forEach(ancestor => {
282
- merge(parameters, ancestor.parameters);
283
- });
284
- return merge(parameters, this.parameters);
285
- }
286
-
287
- /**
288
- * All Materials from this {@link Variant}'s parents.
289
- */
290
- get inheritedMaterials(): Material[] {
291
- let materials: Material[] = [];
292
- this.ancestors.forEach(ancestor => {
293
- materials = concat(materials, ancestor.materials);
294
- });
295
- return concat(materials, this.materials);
296
- }
297
-
298
- /**
299
- * Gets the direct children of the current {@link Variant}.
300
- */
301
- public async getChildren(): Promise<Variant[]> {
302
- const children: Variant[] = [];
303
- for (const name in this.structureJson.variants) {
304
- children.push(await this.getDescendant(name));
305
- }
306
- return children;
307
- }
308
-
309
- /**
310
- * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
311
- * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
312
- * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
313
- * {@link Variant}.
314
- */
315
- public async getDescendant(dottedPath: DottedPathArgument): Promise<Variant> {
316
- const _dottedPath = DottedPath.create(dottedPath);
317
- const [name, ...descendantParts] = _dottedPath.parts;
318
- let variant;
319
- if (this._children.has(name)) {
320
- variant = this._children.get(name);
321
- } else {
322
- if (!this.structureJson.variants) {
323
- throw new Error(`Missing key "variants" in JSON structure for variant "${this.id}".`);
324
- }
325
- if (!this.structureJson.variants[name]) {
326
- throw new Error(`Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".`);
327
- }
328
- if (this.structureJson.variants[name].file) {
329
- const file = this.structureJson.variants[name].file as string;
330
- variant = await Variant.create(name, await loadJson<StructureJson>(file), this.viewer, this);
331
- } else {
332
- variant = await Variant.create(name, this.structureJson.variants[name], this.viewer, this);
333
- }
334
- this._children.set(name, variant);
335
- }
336
- if (!variant) {
337
- throw new Error(`Variant "${_dottedPath.path}" was not created.`);
338
- }
339
- if (descendantParts.length > 0) {
340
- return await variant.getDescendant(DottedPath.createFromParts(descendantParts));
341
- }
342
- return variant;
343
- }
344
-
345
- /**
346
- * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
347
- * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
348
- */
349
- public async getElement(dottedPath: DottedPathArgument): Promise<Element> {
350
- const _dottedPath = DottedPath.create(dottedPath);
351
- const elementName = _dottedPath.popPart();
352
- let variant: Variant = this;
353
- if (_dottedPath.parts.length > 0) {
354
- variant = await this.getDescendant(_dottedPath);
355
- }
356
- if (variant.inheritedElements.length === 0) {
357
- throw new Error(
358
- `No elements for variant "${variant.id}" found. ` +
359
- `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
360
- );
361
- }
362
- let element;
363
- variant.inheritedElements.forEach(_element => {
364
- if (_element.name === elementName) {
365
- element = _element;
366
- }
367
- });
368
- if (!element) {
369
- throw new Error(`Element with name "${elementName}" does not exist for variant "${variant.id}".`);
370
- }
371
- return element;
372
- }
373
-
374
- /**
375
- * Gets the desired {@link ViewerLight} of the current {@link Variant} relative to its {@link DottedPath}.
376
- * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
377
- */
378
- public async getViewerLight(dottedPath: DottedPathArgument): Promise<ViewerLight> {
379
- const _dottedPath = DottedPath.create(dottedPath);
380
- const viewerLightName = _dottedPath.popPart();
381
- let variant: Variant = this;
382
- if (_dottedPath.parts.length > 0) {
383
- variant = await this.getDescendant(_dottedPath);
384
- }
385
- if (variant.inheritedViewerLights.length === 0) {
386
- throw new Error(
387
- `No viewerLights for variant "${variant.id}" found. ` +
388
- `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
389
- );
390
- }
391
- let viewerLight;
392
- variant.inheritedViewerLights.forEach(_viewerLight => {
393
- if (_viewerLight.name === viewerLightName) {
394
- viewerLight = _viewerLight;
395
- }
396
- });
397
- if (!viewerLight) {
398
- throw new Error(`ViewerLight with name "${viewerLightName}" does not exist for variant "${variant.id}".`);
399
- }
400
- return viewerLight;
401
- }
402
-
403
- /**
404
- * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
405
- */
406
- public async getNode(
407
- elementDottedPath: DottedPathArgument,
408
- nodeDottedPath: DottedPathArgument
409
- ): Promise<TransformNode> {
410
- const element = await this.getElement(elementDottedPath);
411
- return element.getNode(nodeDottedPath);
412
- }
413
-
414
- /**
415
- * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
416
- */
417
- public async getMesh(
418
- elementDottedPath: DottedPathArgument,
419
- meshDottedPath: DottedPathArgument
420
- ): Promise<Mesh | null> {
421
- const element = await this.getElement(elementDottedPath);
422
- return element.getMesh(meshDottedPath);
423
- }
424
-
425
- /**
426
- * Gets the Material defined in one of the variants glTFs by its id.
427
- */
428
- public getMaterial(id: string): Material {
429
- const scene = this.viewer.scene;
430
-
431
- for (const material of this.inheritedMaterials) {
432
- if (material.id === id) {
433
- return material;
434
- }
435
- }
436
- // fallback to dynamically created materials on scene
437
- for (const material of scene.materials) {
438
- if (material.id === id) {
439
- return material;
440
- }
441
- }
442
- const cbnAssetMaterial = getMaterialFromCbnAssets(id, scene);
443
- if (cbnAssetMaterial) {
444
- return cbnAssetMaterial;
445
- }
446
-
447
- throw new Error(`Material with id "${id}" does not exist for variant "${this.id}".`);
448
- }
449
-
450
- /**
451
- * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
452
- *
453
- * @emit {@link Event.VARIANT_CREATED}
454
- * @ignore
455
- */
456
- public async createLiving(parameters?: ParameterBag): Promise<Variant> {
457
- const parent = await this.parent?.createLiving();
458
- const variant = new Variant(this.name, this._structureJson, this.viewer, parent);
459
- parent?._children.set(variant.name, variant);
460
- variant.assetContainer = this.assetContainer;
461
- variant.parameterObservers = cloneDeep(this.parameterObservers);
462
- await variant.createElements();
463
- await variant.createViewerLights();
464
- variant.addParameterObservers();
465
- await variant.bootstrapParameters(parameters);
466
- this.broadcastEvent(Event.VARIANT_CREATED, variant);
467
- return variant;
468
- }
469
-
470
- /**
471
- * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
472
- */
473
- public destroy(): Variant {
474
- this.elements.forEach(element => element.destroy());
475
- if (this.parent) {
476
- this.parent.destroy();
477
- }
478
- this.broadcastEvent(Event.VARIANT_DESTROYED, this);
479
- return this;
480
- }
481
-
482
- /**
483
- * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
484
- * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
485
- *
486
- * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
487
- */
488
- public async commitParameters(parameters?: ParameterBag): Promise<Variant> {
489
- parameters = merge({}, parameters);
490
-
491
- // remember old parameter values for later comparison
492
- const oldParameters = cloneDeep(this.inheritedParameters);
493
-
494
- // replace patterns in given parameters
495
- let _parameters = JSON.stringify(parameters);
496
- for (const parameter in this.inheritedParameters) {
497
- const value = this.inheritedParameters[parameter];
498
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
499
- _parameters = _parameters.replace(search, value.toString());
500
- }
501
- merge(parameters, JSON.parse(_parameters));
502
-
503
- // merge inherited parameters and replaced given parameters
504
- const mergedParameters = merge({}, this.inheritedParameters, parameters);
505
-
506
- // replace patterns in structure parameters
507
- const structureParameters = this._structureJson.parameters || {};
508
- let _structureParameters = JSON.stringify(structureParameters);
509
- for (const parameter in mergedParameters) {
510
- const value = mergedParameters[parameter];
511
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
512
- _structureParameters = _structureParameters.replace(search, value.toString());
513
- }
514
- const replacedStructureParameters = JSON.parse(_structureParameters);
515
-
516
- // calculate which replaced structure parameters have changed and should overload given parameters
517
- const differentStructureParameters: ParameterBag = {};
518
- for (const parameter in replacedStructureParameters) {
519
- if (!isEqual(structureParameters[parameter], replacedStructureParameters[parameter])) {
520
- differentStructureParameters[parameter] = replacedStructureParameters[parameter];
521
- }
522
- }
523
-
524
- // merge replaced structure parameters and given inherited parameters to structure parameters
525
- merge(this.parameters, mergedParameters, differentStructureParameters);
526
-
527
- // inherited parameters are now the new parameters to process
528
- const newParameters = this.inheritedParameters;
529
-
530
- // replace all parameter patterns in structure json
531
- let structure = JSON.stringify(this._structureJson);
532
- for (const parameter in newParameters) {
533
- const value = newParameters[parameter];
534
- const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
535
- structure = structure.replace(search, value.toString());
536
- }
537
- this.structureJson = JSON.parse(structure);
538
-
539
- // handle parameter observers
540
- let observerPromises: Promise<void | ParameterObserver>[] = [];
541
- for (const parameter in newParameters) {
542
- const oldParameterValue = oldParameters[parameter];
543
- const newParameterValue = newParameters[parameter];
544
- this.assertParameter(this.inheritedParameterDeclaration, parameter, newParameterValue);
545
- if (oldParameterValue === newParameterValue && this.parametersInitialized) {
546
- continue;
547
- }
548
- // parameter changed
549
- const parameterObservers = mergeMaps(this._parameterObservers, this.parameterObservers);
550
- if (parameterObservers.has(parameter)) {
551
- const observers = parameterObservers.get(parameter)!;
552
- observerPromises = concat(
553
- observerPromises,
554
- observers.map(observer => {
555
- const observerResult = observer(this, oldParameterValue, newParameterValue);
556
- return Promise.resolve(observerResult).then(() => {
557
- if (this.parametersInitialized) {
558
- this.broadcastEvent(
559
- Event.VARIANT_PARAMETER_COMMITTED,
560
- this,
561
- parameter,
562
- oldParameterValue,
563
- newParameterValue
564
- );
565
- }
566
- });
567
- })
568
- );
569
- }
570
- }
571
- await Promise.all(observerPromises);
572
-
573
- // broadcast that bag has been committed
574
- this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
575
-
576
- // commit parameters to elements
577
- await this.commitParametersToElements(newParameters);
578
-
579
- // commit parameters to lights
580
- await this.commitParametersToViewerLights(newParameters);
581
-
582
- // propagate parameters to parent
583
- if (this.parent) {
584
- await this.parent.commitParameters(this.parameters);
585
- }
586
-
587
- return this;
588
- }
589
-
590
- /**
591
- * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
592
- * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
593
- */
594
- public async addTraceableObserver(
595
- dottedPath: DottedPathArgument,
596
- observer: CallableFunction,
597
- payload?: any
598
- ): Promise<Element> {
599
- const _dottedPath = DottedPath.create(dottedPath);
600
- const traceableName = _dottedPath.popPart();
601
- if (!traceableName) {
602
- throw new Error(
603
- `The dottedPath must consist of the element and the name of the defined corresponding ` +
604
- `traceable ("${_dottedPath.path}" given).`
605
- );
606
- }
607
- const element = await this.getElement(_dottedPath);
608
- return element.addTraceableObserver(traceableName, observer, payload);
609
- }
610
-
611
- /**
612
- * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
613
- * (for further processing).
614
- * @emits {@link Event.ASSET_LOADING_START}
615
- * @emits {@link Event.ASSET_LOADING_END}
616
- */
617
- protected async loadAssets(): Promise<Variant> {
618
- this.broadcastEvent(Event.ASSET_LOADING_START, this);
619
-
620
- const promisifiedLoader = new Promise<Variant>((resolve, reject) => {
621
- if (!this.structureJson) {
622
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
623
- return resolve(this);
624
- }
625
- if (!this.glTF) {
626
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
627
- return resolve(this);
628
- }
629
- SceneLoader.LoadAssetContainer(
630
- this.glTF.rootUrl,
631
- this.glTF.fileName,
632
- this.viewer.scene,
633
- // on success
634
- container => {
635
- this.assetContainer = container;
636
- const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode) as TransformNode[];
637
- nodes.forEach(node => {
638
- deactivateTransformNode(node, false);
639
- injectNodeMetadata(node, { dottedPath: getDottedPathForNode(node) }, false);
640
- });
641
- this.assetContainer.lights.forEach(light => {
642
- light.setEnabled(false);
643
- injectNodeMetadata(light, { dottedPath: getDottedPathForNode(light) }, false);
644
- this.viewer.scene.addLight(light);
645
- });
646
- this.assetContainer.cameras.forEach(camera => {
647
- camera.setEnabled(false);
648
- injectNodeMetadata(camera, { dottedPath: getDottedPathForNode(camera) }, false);
649
- this.viewer.scene.addCamera(camera);
650
- });
651
- this.assetContainer.materials.forEach(material => this.viewer.scene.materials.push(material));
652
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
653
- resolve(this);
654
- },
655
- // on progress
656
- undefined,
657
- // on error
658
- reason => {
659
- this.broadcastEvent(Event.ASSET_LOADING_END, this);
660
- throw new Error(`Error loading assets for variant "${this.id}": ${reason}.`);
661
- }
662
- );
663
- });
664
- return await promisifiedLoader;
665
- }
666
-
667
- /**
668
- * Commits given parameters to all {@link Element}s.
669
- */
670
- protected async commitParametersToElements(parameters: ParameterBag) {
671
- await Promise.all(
672
- this.elements.map(element => this.commitParametersToVariantParameterizable(parameters, element, 'elements'))
673
- );
674
- }
675
-
676
- /**
677
- * Commits given parameters to all {@link ViewerLight}s.
678
- */
679
- protected async commitParametersToViewerLights(parameters: ParameterBag) {
680
- await Promise.all(
681
- this.viewerLights.map(viewerLight =>
682
- this.commitParametersToVariantParameterizable(parameters, viewerLight, 'lights')
683
- )
684
- );
685
- }
686
-
687
- /**
688
- * Commits given parameters to a {@link VariantParameterizable} and updates the according definition with given
689
- * key in the {@link StructureJson}. The `definitionKey` "elements" for example will update the definition in
690
- * `this.structureJson.elements`.
691
- */
692
- protected async commitParametersToVariantParameterizable(
693
- parameters: ParameterBag,
694
- parameterizable: VariantParameterizable,
695
- definitionKey: string
696
- ): Promise<ParameterObservable> {
697
- const initialDefinition = get(this._structureJson, definitionKey)[parameterizable.name];
698
- let initialDefinitionStr = JSON.stringify(initialDefinition);
699
- const _parameters: ParameterBag = {};
700
- for (const parameter in parameters) {
701
- const dpp = DottedPath.create(parameter);
702
- if (dpp.shiftPart() !== parameterizable.name) {
703
- continue;
704
- }
705
- // we got a parameterizable ("element") parameter
706
- let parameterValue = parameters[parameter];
707
- const parameterizableParameter = dpp.path;
708
- // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
709
- // an exception for visibility to avoid overloading already applied element parameters with element parameters
710
- // defined in the variant spec ("dotted parameters").
711
- // @see https://github.com/Combeenation/3d-viewer/issues/44
712
- if (parameterizableParameter === Parameter.VISIBLE && parameters[Parameter.VISIBLE] === false) {
713
- parameterValue = false;
714
- }
715
- _parameters[parameterizableParameter] = parameterValue;
716
- const search = new RegExp(`\\$\\{${parameterizableParameter}\\}`, 'g');
717
- initialDefinitionStr = initialDefinitionStr.replace(search, parameterValue.toString());
718
- }
719
- const definition = get(this.structureJson, definitionKey);
720
- definition[this.name] = JSON.parse(initialDefinitionStr);
721
- set(this.structureJson, definitionKey, definition);
722
- return await parameterizable.commitParameters(_parameters);
723
- }
724
-
725
- /**
726
- * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
727
- */
728
- protected async commitParameterToElements(parameter: string, value: ParameterValue): Promise<Variant> {
729
- const promises = [];
730
- for (const element of this.elements) {
731
- const paramShowsElement = Parameter.VISIBLE === parameter && value;
732
-
733
- // Fixes https://combeenation.youtrack.cloud/issue/CB-7773
734
- // Don't enable/show the variants element if it is explicitly hidden via its element parameters.
735
- // E.g. via spec:
736
- // ```
737
- // variants: {
738
- // theVariant: {
739
- // elements: {
740
- // Main: ['root.main'],
741
- // Secondary: ['root.secondary'],
742
- // },
743
- // parameters: {
744
- // // !!! The element `Secondary` should **not** be enabled when its containing variant is enabled !!!
745
- // 'Secondary.visible': false,
746
- // }
747
- // }
748
- // }
749
- // ```
750
- const elVisibleParamPath = DottedPath.create([element.name, Parameter.VISIBLE]).toString();
751
- const elVisibleParamValue = this.inheritedParameters[elVisibleParamPath];
752
- const elVisibleParamValueParsed = elVisibleParamValue && Parameter.parseBoolean(elVisibleParamValue);
753
- const elIsHiddenViaParams = elVisibleParamValueParsed === false;
754
- if (paramShowsElement && elIsHiddenViaParams) continue;
755
-
756
- // To discuss: Is this a viable solution or is it too hacky to apply the elements material param, when the param
757
- // at hand is actually the visible param?
758
- // Fixes https://combeenation.youtrack.cloud/issue/CB-7809
759
- // Apply element material before showing the element to prevent loading of the elements "original" material which
760
- // is never shown when "overwritten" by elements material param.
761
- const elMaterialParamPath = DottedPath.create([element.name, Parameter.MATERIAL]).toString();
762
- const elMaterialParamValue = this.inheritedParameters[elMaterialParamPath];
763
- if (paramShowsElement && elMaterialParamValue) {
764
- // To discuss: Should we rather use `this.commitParametersToElements` here?
765
- // I'm not really sure about the actual differences, both seem to do the job 🤷‍♂️
766
- // To discuss: Should we actively `await` this (as is) or should we also push the resulting promise into the
767
- // `promises` array as dome some lines below?
768
- // I wasn't sure whether not awaiting this could result in "not guaranteed application order" of
769
- // new visibility & material param value...
770
- await element.commitParameter(Parameter.MATERIAL, elMaterialParamValue);
771
- }
772
-
773
- promises.push(element.commitParameter(parameter, value));
774
- }
775
- await Promise.all(promises);
776
- return this;
777
- }
778
-
779
- /**
780
- * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
781
- */
782
- protected async commitParameterToViewerLights(parameter: string, value: ParameterValue): Promise<Variant> {
783
- const promises = [];
784
- for (const viewerLight of this.viewerLights) {
785
- promises.push(viewerLight.commitParameter(parameter, value));
786
- }
787
- await Promise.all(promises);
788
- return this;
789
- }
790
-
791
- /**
792
- * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
793
- */
794
- protected addParameterObservers(): Variant {
795
- this._parameterObservers.set(Parameter.VISIBLE, [
796
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
797
- await variant.commitParameterToElements(Parameter.VISIBLE, newValue);
798
- await variant.commitParameterToViewerLights(Parameter.VISIBLE, newValue);
799
- },
800
- ]);
801
- this._parameterObservers.set(Parameter.SCALING, [
802
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
803
- await variant.commitParameterToElements(Parameter.SCALING, newValue);
804
- await variant.commitParameterToViewerLights(Parameter.SCALING, newValue);
805
- },
806
- ]);
807
- this._parameterObservers.set(Parameter.MATERIAL, [
808
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
809
- await variant.commitParameterToElements(Parameter.MATERIAL, newValue);
810
- },
811
- ]);
812
- this._parameterObservers.set(Parameter.MATERIAL_COLOR, [
813
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
814
- await variant.commitParameterToElements(Parameter.MATERIAL_COLOR, newValue);
815
- },
816
- ]);
817
- this._parameterObservers.set(Parameter.MATERIAL_METALLNESS, [
818
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
819
- await variant.commitParameterToElements(Parameter.MATERIAL_METALLNESS, newValue);
820
- },
821
- ]);
822
- this._parameterObservers.set(Parameter.MATERIAL_ROUGHNESS, [
823
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
824
- await variant.commitParameterToElements(Parameter.MATERIAL_ROUGHNESS, newValue);
825
- },
826
- ]);
827
- this._parameterObservers.set(Parameter.HIGHLIGHT_COLOR, [
828
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
829
- await variant.commitParameterToElements(Parameter.HIGHLIGHT_COLOR, newValue);
830
- },
831
- ]);
832
- this._parameterObservers.set(Parameter.HIGHLIGHTED, [
833
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
834
- await variant.commitParameterToElements(Parameter.HIGHLIGHTED, newValue);
835
- },
836
- ]);
837
- this._parameterObservers.set(Parameter.POSITION, [
838
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
839
- await variant.commitParameterToElements(Parameter.POSITION, newValue);
840
- await variant.commitParameterToViewerLights(Parameter.POSITION, newValue);
841
- },
842
- ]);
843
- this._parameterObservers.set(Parameter.ROTATION, [
844
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
845
- await variant.commitParameterToElements(Parameter.ROTATION, newValue);
846
- await variant.commitParameterToViewerLights(Parameter.ROTATION, newValue);
847
- },
848
- ]);
849
- this._parameterObservers.set(Parameter.CAST_SHADOW, [
850
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
851
- await variant.commitParameterToElements(Parameter.CAST_SHADOW, newValue);
852
- },
853
- ]);
854
- this._parameterObservers.set(Parameter.CAST_SHADOW_FROM_LIGHTS, [
855
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
856
- await variant.commitParameterToElements(Parameter.CAST_SHADOW_FROM_LIGHTS, newValue);
857
- },
858
- ]);
859
- this._parameterObservers.set(Parameter.RECEIVE_SHADOWS, [
860
- async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
861
- await variant.commitParameterToElements(Parameter.RECEIVE_SHADOWS, newValue);
862
- },
863
- ]);
864
- return this;
865
- }
866
-
867
- /**
868
- * Creates {@link Element}s and clones nodes into them.
869
- */
870
- protected async createElements(): Promise<Variant> {
871
- for (const name in this.structureJson.elements || {}) {
872
- this.elements.push(await Element.create(this, name));
873
- }
874
- // inject node meta to all inherited elements
875
- // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
876
- this.inheritedElements.forEach(element => {
877
- element.nodes.forEach(node => {
878
- injectNodeMetadata(node, { variant: this, variantParameterizable: element });
879
- });
880
- });
881
- return this;
882
- }
883
-
884
- /**
885
- * Creates {@link ViewerLight}s.
886
- */
887
- protected async createViewerLights(): Promise<Variant> {
888
- for (const name in this.structureJson.lights || {}) {
889
- this.viewerLights.push(await ViewerLight.create(this, name));
890
- }
891
- this.inheritedViewerLights.forEach(viewerLight => {
892
- injectNodeMetadata(viewerLight.light, { variant: this, variantParameterizable: viewerLight });
893
- });
894
- return this;
895
- }
896
-
897
- /**
898
- * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
899
- */
900
- protected async bootstrapParameters(parameters?: ParameterBag): Promise<Variant> {
901
- await this.commitParameters(merge(cloneDeep(this.parameters), parameters));
902
- concat(this.ancestors, this).forEach(ancestor => (ancestor.parametersInitialized = true));
903
- return this;
904
- }
905
- }
1
+ import { deactivateTransformNode, getDottedPathForNode, injectNodeMetadata } from '../util/babylonHelper';
2
+ import { loadJson, mergeMaps } from '../util/resourceHelper';
3
+ import { getMaterialFromCbnAssets } from '../util/sceneLoaderHelper';
4
+ import { DottedPath } from './dottedPath';
5
+ import { Element } from './element';
6
+ import { Event } from './event';
7
+ import { Parameter } from './parameter';
8
+ import { ParameterObservable } from './parameterObservable';
9
+ import { Parameterizable } from './parameterizable';
10
+ import { VariantParameterizable } from './variantParameterizable';
11
+ import { Viewer } from './viewer';
12
+ import { ViewerLight } from './viewerLight';
13
+ import { Light } from '@babylonjs/core/Lights/light';
14
+ import '@babylonjs/core/Loading/Plugins/babylonFileLoader';
15
+ import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
16
+ import { Material } from '@babylonjs/core/Materials/material';
17
+ import { Mesh } from '@babylonjs/core/Meshes/mesh';
18
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
19
+ import { AssetContainer } from '@babylonjs/core/assetContainer';
20
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression';
21
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_clearcoat';
22
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_ior';
23
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness';
24
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_sheen';
25
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_specular';
26
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_translucency';
27
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_transmission';
28
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_unlit';
29
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_materials_variants';
30
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_basisu';
31
+ import '@babylonjs/loaders/glTF/2.0/Extensions/KHR_texture_transform';
32
+ import '@babylonjs/loaders/glTF/2.0/glTFLoader';
33
+ import { cloneDeep, concat, get, has, isElement, isEmpty, isEqual, isString, merge, reject, set } from 'lodash-es';
34
+
35
+ /**
36
+ * A concrete "Variant". Most of these are handled by either the {@link Viewer} or {@link VariantInstance}.
37
+ */
38
+ export class Variant extends Parameterizable {
39
+ public assetContainer: AssetContainer;
40
+
41
+ public readonly elements: Element[] = [];
42
+
43
+ public readonly viewerLights: ViewerLight[] = [];
44
+
45
+ public structureJson: StructureJson;
46
+
47
+ protected _dottedNodes: Map<DottedPath, TransformNode> | undefined;
48
+
49
+ protected readonly _children: Map<string, Variant> = new Map();
50
+
51
+ protected readonly _parameterObservers: Map<string, ParameterObserver[]> = new Map();
52
+
53
+ /**
54
+ * @internal
55
+ */
56
+ private parametersInitialized: boolean = false;
57
+
58
+ /**
59
+ * Constructor.
60
+ */
61
+ protected constructor(
62
+ public readonly name: string,
63
+ protected readonly _structureJson: StructureJson,
64
+ public readonly viewer: Viewer,
65
+ public readonly parent?: Variant
66
+ ) {
67
+ super(cloneDeep(_structureJson.parameterDeclaration), cloneDeep(_structureJson.parameters));
68
+ for (const parameter in _structureJson.parameterDeclaration) {
69
+ if (!(parameter in (_structureJson.parameters || []))) {
70
+ throw new Error(`No default value for parameter "${parameter}" defined.`);
71
+ }
72
+ }
73
+ this.assetContainer = new AssetContainer(viewer.scene);
74
+ this.structureJson = cloneDeep(_structureJson);
75
+ }
76
+
77
+ /**
78
+ * Creates a {@link Variant} based on given parameters.
79
+ *
80
+ * @throws Error if "gltf" property is provided without a filename
81
+ */
82
+ public static async create(
83
+ name: string,
84
+ structureJson: StructureJson,
85
+ viewer: Viewer,
86
+ parent?: Variant
87
+ ): Promise<Variant> {
88
+ const variant = new Variant(name, structureJson, viewer, parent);
89
+ await variant.loadAssets();
90
+ return variant;
91
+ }
92
+
93
+ /**
94
+ * The ancestor {@link Variant}s ordered from top to bottom in the built tree.
95
+ */
96
+ get ancestors(): Variant[] {
97
+ const ancestors = [];
98
+ let variant: Variant = this;
99
+ while (variant.parent) {
100
+ ancestors.unshift(variant.parent);
101
+ variant = variant.parent;
102
+ }
103
+ return ancestors;
104
+ }
105
+
106
+ /**
107
+ * The root {@link Variant}.
108
+ */
109
+ get root(): Variant {
110
+ return this.ancestors[0] ?? this;
111
+ }
112
+
113
+ /**
114
+ * The {@link DottedPath} in the built tree of {@link Variant}s.
115
+ * E.g. "_.top-1.sub-2.sub-sub-3"
116
+ */
117
+ get dottedPath(): DottedPath {
118
+ const parentIds = this.ancestors.map(ancestor => {
119
+ return ancestor.name;
120
+ });
121
+ return DottedPath.createFromParts(parentIds).addPart(this.name);
122
+ }
123
+
124
+ /**
125
+ * The id representing a {@link DottedPath}.
126
+ */
127
+ get id(): string {
128
+ const dottedPath = DottedPath.create(this.dottedPath);
129
+ dottedPath.shiftPart(); // remove root
130
+ return dottedPath.path || '_';
131
+ }
132
+
133
+ /**
134
+ * The defined glTF Asset.
135
+ */
136
+ get glTF(): Asset | undefined {
137
+ if (this.structureJson.glTF) {
138
+ if (isString(this.structureJson.glTF)) {
139
+ return {
140
+ rootUrl: this.structureJson.glTF,
141
+ fileName: undefined,
142
+ };
143
+ }
144
+ if (isEmpty(this.structureJson.glTF.rootUrl)) {
145
+ throw new Error(`No "rootUrl" defined in "glTF" definition for variant "${this.id}".`);
146
+ }
147
+ return this.structureJson.glTF;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * The defined glTF URI.
153
+ */
154
+ get glTFUri(): string | undefined {
155
+ if (this.glTF) {
156
+ return [this.glTF.rootUrl, this.glTF.fileName].join('');
157
+ }
158
+ }
159
+
160
+ /**
161
+ * The inherited defined glTF URI.
162
+ */
163
+ get inheritedGlTFUri(): string | undefined {
164
+ if (!this.glTFUri && this.parent) {
165
+ return this.parent.inheritedGlTFUri;
166
+ }
167
+ return this.glTFUri;
168
+ }
169
+
170
+ /**
171
+ * The TransformNodes of the {@link Variant}.
172
+ */
173
+ get nodes(): TransformNode[] {
174
+ const rootNodes = this.assetContainer
175
+ .getNodes()
176
+ .filter(n => n instanceof TransformNode && !n.parent) as TransformNode[];
177
+ return rootNodes;
178
+ }
179
+
180
+ /**
181
+ * The {@link ViewerLight}s of the {@link Variant}.
182
+ */
183
+ get lights(): Light[] {
184
+ return this.assetContainer.lights;
185
+ }
186
+
187
+ /**
188
+ * All TransformNodes of the {@link Variant} mapped flat with a {@link DottedPath}.
189
+ */
190
+ get dottedNodes(): Map<DottedPath, TransformNode> {
191
+ if (!this._dottedNodes) {
192
+ const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode);
193
+ const dottedNodes = new Map();
194
+ nodes.forEach(node => {
195
+ dottedNodes.set(node.metadata.dottedPath, node);
196
+ });
197
+ this._dottedNodes = dottedNodes;
198
+ }
199
+ return this._dottedNodes;
200
+ }
201
+
202
+ /**
203
+ * The Materials of the {@link Variant}.
204
+ */
205
+ get materials(): Material[] {
206
+ return this.assetContainer.materials;
207
+ }
208
+
209
+ /**
210
+ * All {@link Element}s from this {@link Variant}'s parents.
211
+ */
212
+ get inheritedElements(): Element[] {
213
+ let elements: Element[] = [];
214
+ this.ancestors.forEach(ancestor => {
215
+ elements = concat(elements, ancestor.elements);
216
+ });
217
+ return concat(elements, this.elements);
218
+ }
219
+
220
+ /**
221
+ * All {@link ViewerLight}s inherited from this {@link Variant}'s parents.
222
+ */
223
+ get inheritedViewerLights(): ViewerLight[] {
224
+ let viewerLights: ViewerLight[] = [];
225
+ this.ancestors.forEach(ancestor => {
226
+ viewerLights = concat(viewerLights, ancestor.viewerLights);
227
+ });
228
+ return concat(viewerLights, this.viewerLights);
229
+ }
230
+
231
+ /**
232
+ * All TransformNodes inherited from this {@link Variant}'s parents.
233
+ */
234
+ get inheritedNodes(): TransformNode[] {
235
+ let nodes: TransformNode[] = [];
236
+ this.ancestors.forEach(ancestor => {
237
+ nodes = concat(nodes, ancestor.nodes);
238
+ });
239
+ return concat(nodes, this.nodes);
240
+ }
241
+
242
+ /**
243
+ * All TransformNodes inherited from this {@link Variant}'s parents mapped flat with a {@link DottedPath}.
244
+ */
245
+ get inheritedDottedNodes(): Map<DottedPath, TransformNode> {
246
+ let dottedNodes = this.dottedNodes;
247
+ this.ancestors.forEach(ancestor => {
248
+ dottedNodes = mergeMaps(dottedNodes, ancestor.dottedNodes);
249
+ });
250
+ return dottedNodes;
251
+ }
252
+
253
+ /**
254
+ * All Lights inherited from this {@link Variant}'s parents.
255
+ */
256
+ get inheritedLights(): Light[] {
257
+ let lights: Light[] = [];
258
+ this.ancestors.forEach(ancestor => {
259
+ lights = concat(lights, ancestor.lights);
260
+ });
261
+ return concat(lights, this.lights);
262
+ }
263
+
264
+ /**
265
+ * The {@link ParameterDeclarations} inherited from this {@link Variant}'s parents.
266
+ */
267
+ get inheritedParameterDeclaration(): ParameterDeclarations {
268
+ let declaration = {};
269
+ this.ancestors.forEach(ancestor => {
270
+ merge(declaration, ancestor.parameterDeclaration);
271
+ });
272
+ return merge(declaration, this.parameterDeclaration);
273
+ }
274
+
275
+ /**
276
+ * The {@link ParameterBag} inherited from this {@link Variant}'s parents.
277
+ */
278
+ get inheritedParameters(): ParameterBag {
279
+ let parameters = {};
280
+ this.ancestors.forEach(ancestor => {
281
+ merge(parameters, ancestor.parameters);
282
+ });
283
+ return merge(parameters, this.parameters);
284
+ }
285
+
286
+ /**
287
+ * All Materials from this {@link Variant}'s parents.
288
+ */
289
+ get inheritedMaterials(): Material[] {
290
+ let materials: Material[] = [];
291
+ this.ancestors.forEach(ancestor => {
292
+ materials = concat(materials, ancestor.materials);
293
+ });
294
+ return concat(materials, this.materials);
295
+ }
296
+
297
+ /**
298
+ * Gets the direct children of the current {@link Variant}.
299
+ */
300
+ public async getChildren(): Promise<Variant[]> {
301
+ const children: Variant[] = [];
302
+ for (const name in this.structureJson.variants) {
303
+ children.push(await this.getDescendant(name));
304
+ }
305
+ return children;
306
+ }
307
+
308
+ /**
309
+ * Gets a descendant {@link Variant} of the current {@link Variant} relative to its {@link DottedPath}.
310
+ * If you have the dotted path `_.product_x.variant_blue.with_yellow_highlight` in a tree and you operate on the
311
+ * `product_x`, you can call `this.getDescendant('variant_blue.with_yellow_highlight')` to get the lowermost
312
+ * {@link Variant}.
313
+ */
314
+ public async getDescendant(dottedPath: DottedPathArgument): Promise<Variant> {
315
+ const _dottedPath = DottedPath.create(dottedPath);
316
+ const [name, ...descendantParts] = _dottedPath.parts;
317
+ let variant;
318
+ if (this._children.has(name)) {
319
+ variant = this._children.get(name);
320
+ } else {
321
+ if (!this.structureJson.variants) {
322
+ throw new Error(`Missing key "variants" in JSON structure for variant "${this.id}".`);
323
+ }
324
+ if (!this.structureJson.variants[name]) {
325
+ throw new Error(`Variant "${_dottedPath.path}" not defined in JSON structure for variant "${this.id}".`);
326
+ }
327
+ if (this.structureJson.variants[name].file) {
328
+ const file = this.structureJson.variants[name].file as string;
329
+ variant = await Variant.create(name, await loadJson<StructureJson>(file), this.viewer, this);
330
+ } else {
331
+ variant = await Variant.create(name, this.structureJson.variants[name], this.viewer, this);
332
+ }
333
+ this._children.set(name, variant);
334
+ }
335
+ if (!variant) {
336
+ throw new Error(`Variant "${_dottedPath.path}" was not created.`);
337
+ }
338
+ if (descendantParts.length > 0) {
339
+ return await variant.getDescendant(DottedPath.createFromParts(descendantParts));
340
+ }
341
+ return variant;
342
+ }
343
+
344
+ /**
345
+ * Gets the desired {@link Element} of the current {@link Variant} relative to its {@link DottedPath}.
346
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
347
+ */
348
+ public async getElement(dottedPath: DottedPathArgument): Promise<Element> {
349
+ const _dottedPath = DottedPath.create(dottedPath);
350
+ const elementName = _dottedPath.popPart();
351
+ let variant: Variant = this;
352
+ if (_dottedPath.parts.length > 0) {
353
+ variant = await this.getDescendant(_dottedPath);
354
+ }
355
+ if (variant.inheritedElements.length === 0) {
356
+ throw new Error(
357
+ `No elements for variant "${variant.id}" found. ` +
358
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
359
+ );
360
+ }
361
+ let element;
362
+ variant.inheritedElements.forEach(_element => {
363
+ if (_element.name === elementName) {
364
+ element = _element;
365
+ }
366
+ });
367
+ if (!element) {
368
+ throw new Error(`Element with name "${elementName}" does not exist for variant "${variant.id}".`);
369
+ }
370
+ return element;
371
+ }
372
+
373
+ /**
374
+ * Gets the desired {@link ViewerLight} of the current {@link Variant} relative to its {@link DottedPath}.
375
+ * Uses the mechanism of {@link getDescendant} to resolve the appropriate variant in tree.
376
+ */
377
+ public async getViewerLight(dottedPath: DottedPathArgument): Promise<ViewerLight> {
378
+ const _dottedPath = DottedPath.create(dottedPath);
379
+ const viewerLightName = _dottedPath.popPart();
380
+ let variant: Variant = this;
381
+ if (_dottedPath.parts.length > 0) {
382
+ variant = await this.getDescendant(_dottedPath);
383
+ }
384
+ if (variant.inheritedViewerLights.length === 0) {
385
+ throw new Error(
386
+ `No viewerLights for variant "${variant.id}" found. ` +
387
+ `Either none are defined or they are not initialized (are you operating on the appropriate living?).`
388
+ );
389
+ }
390
+ let viewerLight;
391
+ variant.inheritedViewerLights.forEach(_viewerLight => {
392
+ if (_viewerLight.name === viewerLightName) {
393
+ viewerLight = _viewerLight;
394
+ }
395
+ });
396
+ if (!viewerLight) {
397
+ throw new Error(`ViewerLight with name "${viewerLightName}" does not exist for variant "${variant.id}".`);
398
+ }
399
+ return viewerLight;
400
+ }
401
+
402
+ /**
403
+ * A proxy for directly getting a Node from an {@link Element} by its {@link DottedPath}s.
404
+ */
405
+ public async getNode(
406
+ elementDottedPath: DottedPathArgument,
407
+ nodeDottedPath: DottedPathArgument
408
+ ): Promise<TransformNode> {
409
+ const element = await this.getElement(elementDottedPath);
410
+ return element.getNode(nodeDottedPath);
411
+ }
412
+
413
+ /**
414
+ * A proxy for directly getting a Mesh from an {@link Element} by its {@link DottedPath}s.
415
+ */
416
+ public async getMesh(
417
+ elementDottedPath: DottedPathArgument,
418
+ meshDottedPath: DottedPathArgument
419
+ ): Promise<Mesh | null> {
420
+ const element = await this.getElement(elementDottedPath);
421
+ return element.getMesh(meshDottedPath);
422
+ }
423
+
424
+ /**
425
+ * Gets the Material defined in one of the variants glTFs by its id.
426
+ */
427
+ public getMaterial(id: string): Material {
428
+ const scene = this.viewer.scene;
429
+
430
+ for (const material of this.inheritedMaterials) {
431
+ if (material.id === id) {
432
+ return material;
433
+ }
434
+ }
435
+ // fallback to dynamically created materials on scene
436
+ for (const material of scene.materials) {
437
+ if (material.id === id) {
438
+ return material;
439
+ }
440
+ }
441
+ const cbnAssetMaterial = getMaterialFromCbnAssets(id, scene);
442
+ if (cbnAssetMaterial) {
443
+ return cbnAssetMaterial;
444
+ }
445
+
446
+ throw new Error(`Material with id "${id}" does not exist for variant "${this.id}".`);
447
+ }
448
+
449
+ /**
450
+ * Creates a living clone of this {@link Variant}. Will clone all parent {@link Variant}s in tree.
451
+ *
452
+ * @emit {@link Event.VARIANT_CREATED}
453
+ * @ignore
454
+ */
455
+ public async createLiving(parameters?: ParameterBag): Promise<Variant> {
456
+ const parent = await this.parent?.createLiving();
457
+ const variant = new Variant(this.name, this._structureJson, this.viewer, parent);
458
+ parent?._children.set(variant.name, variant);
459
+ variant.assetContainer = this.assetContainer;
460
+ variant.parameterObservers = cloneDeep(this.parameterObservers);
461
+ await variant.createElements();
462
+ await variant.createViewerLights();
463
+ variant.addParameterObservers();
464
+ await variant.bootstrapParameters(parameters);
465
+ this.broadcastEvent(Event.VARIANT_CREATED, variant);
466
+ return variant;
467
+ }
468
+
469
+ /**
470
+ * Destroys this {@link Variant}, all parents and destroy the {@link Element}s.
471
+ */
472
+ public destroy(): Variant {
473
+ this.elements.forEach(element => element.destroy());
474
+ if (this.parent) {
475
+ this.parent.destroy();
476
+ }
477
+ this.broadcastEvent(Event.VARIANT_DESTROYED, this);
478
+ return this;
479
+ }
480
+
481
+ /**
482
+ * Places the given {@link ParameterBag} in the {@link Variant}'s parameters, replaces all patterns in the
483
+ * {@link StructureJson}, broadcasts all {@link ParameterObserver}s and delegates them to its {@link Element}s.
484
+ *
485
+ * @emit {@link Event.VARIANT_PARAMETER_COMMITTED}
486
+ */
487
+ public async commitParameters(parameters?: ParameterBag): Promise<Variant> {
488
+ parameters = merge({}, parameters);
489
+
490
+ // remember old parameter values for later comparison
491
+ const oldParameters = cloneDeep(this.inheritedParameters);
492
+
493
+ // replace patterns in given parameters
494
+ let _parameters = JSON.stringify(parameters);
495
+ for (const parameter in this.inheritedParameters) {
496
+ const value = this.inheritedParameters[parameter];
497
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
498
+ _parameters = _parameters.replace(search, value.toString());
499
+ }
500
+ merge(parameters, JSON.parse(_parameters));
501
+
502
+ // merge inherited parameters and replaced given parameters
503
+ const mergedParameters = merge({}, this.inheritedParameters, parameters);
504
+
505
+ // replace patterns in structure parameters
506
+ const structureParameters = this._structureJson.parameters || {};
507
+ let _structureParameters = JSON.stringify(structureParameters);
508
+ for (const parameter in mergedParameters) {
509
+ const value = mergedParameters[parameter];
510
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
511
+ _structureParameters = _structureParameters.replace(search, value.toString());
512
+ }
513
+ const replacedStructureParameters = JSON.parse(_structureParameters);
514
+
515
+ // calculate which replaced structure parameters have changed and should overload given parameters
516
+ const differentStructureParameters: ParameterBag = {};
517
+ for (const parameter in replacedStructureParameters) {
518
+ if (!isEqual(structureParameters[parameter], replacedStructureParameters[parameter])) {
519
+ differentStructureParameters[parameter] = replacedStructureParameters[parameter];
520
+ }
521
+ }
522
+
523
+ // merge replaced structure parameters and given inherited parameters to structure parameters
524
+ merge(this.parameters, mergedParameters, differentStructureParameters);
525
+
526
+ // inherited parameters are now the new parameters to process
527
+ const newParameters = this.inheritedParameters;
528
+
529
+ // replace all parameter patterns in structure json
530
+ let structure = JSON.stringify(this._structureJson);
531
+ for (const parameter in newParameters) {
532
+ const value = newParameters[parameter];
533
+ const search = new RegExp(`\\$\\{${parameter}\\}`, 'g');
534
+ structure = structure.replace(search, value.toString());
535
+ }
536
+ this.structureJson = JSON.parse(structure);
537
+
538
+ // handle parameter observers
539
+ let observerPromises: Promise<void | ParameterObserver>[] = [];
540
+ for (const parameter in newParameters) {
541
+ const oldParameterValue = oldParameters[parameter];
542
+ const newParameterValue = newParameters[parameter];
543
+ this.assertParameter(this.inheritedParameterDeclaration, parameter, newParameterValue);
544
+ if (oldParameterValue === newParameterValue && this.parametersInitialized) {
545
+ continue;
546
+ }
547
+ // parameter changed
548
+ const parameterObservers = mergeMaps(this._parameterObservers, this.parameterObservers);
549
+ if (parameterObservers.has(parameter)) {
550
+ const observers = parameterObservers.get(parameter)!;
551
+ observerPromises = concat(
552
+ observerPromises,
553
+ observers.map(observer => {
554
+ const observerResult = observer(this, oldParameterValue, newParameterValue);
555
+ return Promise.resolve(observerResult).then(() => {
556
+ if (this.parametersInitialized) {
557
+ this.broadcastEvent(
558
+ Event.VARIANT_PARAMETER_COMMITTED,
559
+ this,
560
+ parameter,
561
+ oldParameterValue,
562
+ newParameterValue
563
+ );
564
+ }
565
+ });
566
+ })
567
+ );
568
+ }
569
+ }
570
+ await Promise.all(observerPromises);
571
+
572
+ // broadcast that bag has been committed
573
+ this.broadcastEvent(Event.VARIANT_PARAMETER_BAG_COMMITTED, this, oldParameters, newParameters);
574
+
575
+ // commit parameters to elements
576
+ await this.commitParametersToElements(newParameters);
577
+
578
+ // commit parameters to lights
579
+ await this.commitParametersToViewerLights(newParameters);
580
+
581
+ // propagate parameters to parent
582
+ if (this.parent) {
583
+ await this.parent.commitParameters(this.parameters);
584
+ }
585
+
586
+ return this;
587
+ }
588
+
589
+ /**
590
+ * Adds an observer function for camera matrix changes for given `dottedPath` representing the {@link Element}
591
+ * and the `traceable`. The `observer` gets 2 parameters: the `AbstractMesh` and a `ClientRect` object.
592
+ */
593
+ public async addTraceableObserver(
594
+ dottedPath: DottedPathArgument,
595
+ observer: CallableFunction,
596
+ payload?: any
597
+ ): Promise<Element> {
598
+ const _dottedPath = DottedPath.create(dottedPath);
599
+ const traceableName = _dottedPath.popPart();
600
+ if (!traceableName) {
601
+ throw new Error(
602
+ `The dottedPath must consist of the element and the name of the defined corresponding ` +
603
+ `traceable ("${_dottedPath.path}" given).`
604
+ );
605
+ }
606
+ const element = await this.getElement(_dottedPath);
607
+ return element.addTraceableObserver(traceableName, observer, payload);
608
+ }
609
+
610
+ /**
611
+ * Loads {@link glTFUri} with assets, adds them to the {@link Variant}'s `assetContainer` and deactivates the meshes.
612
+ * (for further processing).
613
+ * @emits {@link Event.ASSET_LOADING_START}
614
+ * @emits {@link Event.ASSET_LOADING_END}
615
+ */
616
+ protected async loadAssets(): Promise<Variant> {
617
+ this.broadcastEvent(Event.ASSET_LOADING_START, this);
618
+
619
+ const promisifiedLoader = new Promise<Variant>((resolve, reject) => {
620
+ if (!this.structureJson) {
621
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
622
+ return resolve(this);
623
+ }
624
+ if (!this.glTF) {
625
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
626
+ return resolve(this);
627
+ }
628
+ SceneLoader.LoadAssetContainer(
629
+ this.glTF.rootUrl,
630
+ this.glTF.fileName,
631
+ this.viewer.scene,
632
+ // on success
633
+ container => {
634
+ this.assetContainer = container;
635
+ const nodes = this.assetContainer.getNodes().filter(n => n instanceof TransformNode) as TransformNode[];
636
+ nodes.forEach(node => {
637
+ deactivateTransformNode(node, false);
638
+ injectNodeMetadata(node, { dottedPath: getDottedPathForNode(node) }, false);
639
+ });
640
+ this.assetContainer.lights.forEach(light => {
641
+ light.setEnabled(false);
642
+ injectNodeMetadata(light, { dottedPath: getDottedPathForNode(light) }, false);
643
+ this.viewer.scene.addLight(light);
644
+ });
645
+ this.assetContainer.cameras.forEach(camera => {
646
+ camera.setEnabled(false);
647
+ injectNodeMetadata(camera, { dottedPath: getDottedPathForNode(camera) }, false);
648
+ this.viewer.scene.addCamera(camera);
649
+ });
650
+ this.assetContainer.materials.forEach(material => this.viewer.scene.materials.push(material));
651
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
652
+ resolve(this);
653
+ },
654
+ // on progress
655
+ undefined,
656
+ // on error
657
+ reason => {
658
+ this.broadcastEvent(Event.ASSET_LOADING_END, this);
659
+ throw new Error(`Error loading assets for variant "${this.id}": ${reason}.`);
660
+ }
661
+ );
662
+ });
663
+ return await promisifiedLoader;
664
+ }
665
+
666
+ /**
667
+ * Commits given parameters to all {@link Element}s.
668
+ */
669
+ protected async commitParametersToElements(parameters: ParameterBag) {
670
+ await Promise.all(
671
+ this.elements.map(element => this.commitParametersToVariantParameterizable(parameters, element, 'elements'))
672
+ );
673
+ }
674
+
675
+ /**
676
+ * Commits given parameters to all {@link ViewerLight}s.
677
+ */
678
+ protected async commitParametersToViewerLights(parameters: ParameterBag) {
679
+ await Promise.all(
680
+ this.viewerLights.map(viewerLight =>
681
+ this.commitParametersToVariantParameterizable(parameters, viewerLight, 'lights')
682
+ )
683
+ );
684
+ }
685
+
686
+ /**
687
+ * Commits given parameters to a {@link VariantParameterizable} and updates the according definition with given
688
+ * key in the {@link StructureJson}. The `definitionKey` "elements" for example will update the definition in
689
+ * `this.structureJson.elements`.
690
+ */
691
+ protected async commitParametersToVariantParameterizable(
692
+ parameters: ParameterBag,
693
+ parameterizable: VariantParameterizable,
694
+ definitionKey: string
695
+ ): Promise<ParameterObservable> {
696
+ const initialDefinition = get(this._structureJson, definitionKey)[parameterizable.name];
697
+ let initialDefinitionStr = JSON.stringify(initialDefinition);
698
+ const _parameters: ParameterBag = {};
699
+ for (const parameter in parameters) {
700
+ const dpp = DottedPath.create(parameter);
701
+ if (dpp.shiftPart() !== parameterizable.name) {
702
+ continue;
703
+ }
704
+ // we got a parameterizable ("element") parameter
705
+ let parameterValue = parameters[parameter];
706
+ const parameterizableParameter = dpp.path;
707
+ // If the variant is explicitly hidden, we must not override the visibility with element parameters. We need
708
+ // an exception for visibility to avoid overloading already applied element parameters with element parameters
709
+ // defined in the variant spec ("dotted parameters").
710
+ // @see https://github.com/Combeenation/3d-viewer/issues/44
711
+ if (parameterizableParameter === Parameter.VISIBLE && parameters[Parameter.VISIBLE] === false) {
712
+ parameterValue = false;
713
+ }
714
+ _parameters[parameterizableParameter] = parameterValue;
715
+ const search = new RegExp(`\\$\\{${parameterizableParameter}\\}`, 'g');
716
+ initialDefinitionStr = initialDefinitionStr.replace(search, parameterValue.toString());
717
+ }
718
+ const definition = get(this.structureJson, definitionKey);
719
+ definition[this.name] = JSON.parse(initialDefinitionStr);
720
+ set(this.structureJson, definitionKey, definition);
721
+ return await parameterizable.commitParameters(_parameters);
722
+ }
723
+
724
+ /**
725
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
726
+ */
727
+ protected async commitParameterToElements(parameter: string, value: ParameterValue): Promise<Variant> {
728
+ const promises = [];
729
+ for (const element of this.elements) {
730
+ const paramShowsElement = Parameter.VISIBLE === parameter && value;
731
+
732
+ // Fixes https://combeenation.youtrack.cloud/issue/CB-7773
733
+ // Don't enable/show the variants element if it is explicitly hidden via its element parameters.
734
+ // E.g. via spec:
735
+ // ```
736
+ // variants: {
737
+ // theVariant: {
738
+ // elements: {
739
+ // Main: ['root.main'],
740
+ // Secondary: ['root.secondary'],
741
+ // },
742
+ // parameters: {
743
+ // // !!! The element `Secondary` should **not** be enabled when its containing variant is enabled !!!
744
+ // 'Secondary.visible': false,
745
+ // }
746
+ // }
747
+ // }
748
+ // ```
749
+ const elVisibleParamPath = DottedPath.create([element.name, Parameter.VISIBLE]).toString();
750
+ const elVisibleParamValue = this.inheritedParameters[elVisibleParamPath];
751
+ const elVisibleParamValueParsed = elVisibleParamValue && Parameter.parseBoolean(elVisibleParamValue);
752
+ const elIsHiddenViaParams = elVisibleParamValueParsed === false;
753
+ if (paramShowsElement && elIsHiddenViaParams) continue;
754
+
755
+ // To discuss: Is this a viable solution or is it too hacky to apply the elements material param, when the param
756
+ // at hand is actually the visible param?
757
+ // Fixes https://combeenation.youtrack.cloud/issue/CB-7809
758
+ // Apply element material before showing the element to prevent loading of the elements "original" material which
759
+ // is never shown when "overwritten" by elements material param.
760
+ const elMaterialParamPath = DottedPath.create([element.name, Parameter.MATERIAL]).toString();
761
+ const elMaterialParamValue = this.inheritedParameters[elMaterialParamPath];
762
+ if (paramShowsElement && elMaterialParamValue) {
763
+ // To discuss: Should we rather use `this.commitParametersToElements` here?
764
+ // I'm not really sure about the actual differences, both seem to do the job 🤷‍♂️
765
+ // To discuss: Should we actively `await` this (as is) or should we also push the resulting promise into the
766
+ // `promises` array as dome some lines below?
767
+ // I wasn't sure whether not awaiting this could result in "not guaranteed application order" of
768
+ // new visibility & material param value...
769
+ await element.commitParameter(Parameter.MATERIAL, elMaterialParamValue);
770
+ }
771
+
772
+ promises.push(element.commitParameter(parameter, value));
773
+ }
774
+ await Promise.all(promises);
775
+ return this;
776
+ }
777
+
778
+ /**
779
+ * Commits given {@link Parameter} to the {@link Variant}'s {@link Element}s.
780
+ */
781
+ protected async commitParameterToViewerLights(parameter: string, value: ParameterValue): Promise<Variant> {
782
+ const promises = [];
783
+ for (const viewerLight of this.viewerLights) {
784
+ promises.push(viewerLight.commitParameter(parameter, value));
785
+ }
786
+ await Promise.all(promises);
787
+ return this;
788
+ }
789
+
790
+ /**
791
+ * Adds the default {@link ParameterObserver}s which are called every time {@link commitParameters} is called.
792
+ */
793
+ protected addParameterObservers(): Variant {
794
+ this._parameterObservers.set(Parameter.VISIBLE, [
795
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
796
+ await variant.commitParameterToElements(Parameter.VISIBLE, newValue);
797
+ await variant.commitParameterToViewerLights(Parameter.VISIBLE, newValue);
798
+ },
799
+ ]);
800
+ this._parameterObservers.set(Parameter.SCALING, [
801
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
802
+ await variant.commitParameterToElements(Parameter.SCALING, newValue);
803
+ await variant.commitParameterToViewerLights(Parameter.SCALING, newValue);
804
+ },
805
+ ]);
806
+ this._parameterObservers.set(Parameter.MATERIAL, [
807
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
808
+ await variant.commitParameterToElements(Parameter.MATERIAL, newValue);
809
+ },
810
+ ]);
811
+ this._parameterObservers.set(Parameter.MATERIAL_COLOR, [
812
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
813
+ await variant.commitParameterToElements(Parameter.MATERIAL_COLOR, newValue);
814
+ },
815
+ ]);
816
+ this._parameterObservers.set(Parameter.MATERIAL_METALLNESS, [
817
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
818
+ await variant.commitParameterToElements(Parameter.MATERIAL_METALLNESS, newValue);
819
+ },
820
+ ]);
821
+ this._parameterObservers.set(Parameter.MATERIAL_ROUGHNESS, [
822
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
823
+ await variant.commitParameterToElements(Parameter.MATERIAL_ROUGHNESS, newValue);
824
+ },
825
+ ]);
826
+ this._parameterObservers.set(Parameter.HIGHLIGHT_COLOR, [
827
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
828
+ await variant.commitParameterToElements(Parameter.HIGHLIGHT_COLOR, newValue);
829
+ },
830
+ ]);
831
+ this._parameterObservers.set(Parameter.HIGHLIGHTED, [
832
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
833
+ await variant.commitParameterToElements(Parameter.HIGHLIGHTED, newValue);
834
+ },
835
+ ]);
836
+ this._parameterObservers.set(Parameter.POSITION, [
837
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
838
+ await variant.commitParameterToElements(Parameter.POSITION, newValue);
839
+ await variant.commitParameterToViewerLights(Parameter.POSITION, newValue);
840
+ },
841
+ ]);
842
+ this._parameterObservers.set(Parameter.ROTATION, [
843
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
844
+ await variant.commitParameterToElements(Parameter.ROTATION, newValue);
845
+ await variant.commitParameterToViewerLights(Parameter.ROTATION, newValue);
846
+ },
847
+ ]);
848
+ this._parameterObservers.set(Parameter.CAST_SHADOW, [
849
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
850
+ await variant.commitParameterToElements(Parameter.CAST_SHADOW, newValue);
851
+ },
852
+ ]);
853
+ this._parameterObservers.set(Parameter.CAST_SHADOW_FROM_LIGHTS, [
854
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
855
+ await variant.commitParameterToElements(Parameter.CAST_SHADOW_FROM_LIGHTS, newValue);
856
+ },
857
+ ]);
858
+ this._parameterObservers.set(Parameter.RECEIVE_SHADOWS, [
859
+ async (variant: Variant, oldValue: ParameterValue, newValue: ParameterValue) => {
860
+ await variant.commitParameterToElements(Parameter.RECEIVE_SHADOWS, newValue);
861
+ },
862
+ ]);
863
+ return this;
864
+ }
865
+
866
+ /**
867
+ * Creates {@link Element}s and clones nodes into them.
868
+ */
869
+ protected async createElements(): Promise<Variant> {
870
+ for (const name in this.structureJson.elements || {}) {
871
+ this.elements.push(await Element.create(this, name));
872
+ }
873
+ // inject node meta to all inherited elements
874
+ // we do this to inject the deepest and most concrete variant information to all cloned nodes in the tree
875
+ this.inheritedElements.forEach(element => {
876
+ element.nodes.forEach(node => {
877
+ injectNodeMetadata(node, { variant: this, variantParameterizable: element });
878
+ });
879
+ });
880
+ return this;
881
+ }
882
+
883
+ /**
884
+ * Creates {@link ViewerLight}s.
885
+ */
886
+ protected async createViewerLights(): Promise<Variant> {
887
+ for (const name in this.structureJson.lights || {}) {
888
+ this.viewerLights.push(await ViewerLight.create(this, name));
889
+ }
890
+ this.inheritedViewerLights.forEach(viewerLight => {
891
+ injectNodeMetadata(viewerLight.light, { variant: this, variantParameterizable: viewerLight });
892
+ });
893
+ return this;
894
+ }
895
+
896
+ /**
897
+ * Bootstrapping for parameters. It sets the `parametersInitialized` to true for all ancestors.
898
+ */
899
+ protected async bootstrapParameters(parameters?: ParameterBag): Promise<Variant> {
900
+ await this.commitParameters(merge(cloneDeep(this.parameters), parameters));
901
+ concat(this.ancestors, this).forEach(ancestor => (ancestor.parametersInitialized = true));
902
+ return this;
903
+ }
904
+ }