MSFT_lod.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import { Nullable } from "babylonjs/types";
  2. import { Observable } from "babylonjs/Misc/observable";
  3. import { Deferred } from "babylonjs/Misc/deferred";
  4. import { Material } from "babylonjs/Materials/material";
  5. import { TransformNode } from "babylonjs/Meshes/transformNode";
  6. import { Mesh } from "babylonjs/Meshes/mesh";
  7. import { INode, IMaterial } from "../glTFLoaderInterfaces";
  8. import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
  9. import { GLTFLoader, ArrayItem } from "../glTFLoader";
  10. import { IProperty } from 'babylonjs-gltf2interface';
  11. const NAME = "MSFT_lod";
  12. interface IMSFTLOD {
  13. ids: number[];
  14. }
  15. /**
  16. * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod)
  17. */
  18. export class MSFT_lod implements IGLTFLoaderExtension {
  19. /** The name of this extension. */
  20. public readonly name = NAME;
  21. /** Defines whether this extension is enabled. */
  22. public enabled = true;
  23. /**
  24. * Maximum number of LODs to load, starting from the lowest LOD.
  25. */
  26. public maxLODsToLoad = Number.MAX_VALUE;
  27. /**
  28. * Observable raised when all node LODs of one level are loaded.
  29. * The event data is the index of the loaded LOD starting from zero.
  30. * Dispose the loader to cancel the loading of the next level of LODs.
  31. */
  32. public onNodeLODsLoadedObservable = new Observable<number>();
  33. /**
  34. * Observable raised when all material LODs of one level are loaded.
  35. * The event data is the index of the loaded LOD starting from zero.
  36. * Dispose the loader to cancel the loading of the next level of LODs.
  37. */
  38. public onMaterialLODsLoadedObservable = new Observable<number>();
  39. private _loader: GLTFLoader;
  40. private _nodeIndexLOD: Nullable<number> = null;
  41. private _nodeSignalLODs = new Array<Deferred<void>>();
  42. private _nodePromiseLODs = new Array<Array<Promise<any>>>();
  43. private _materialIndexLOD: Nullable<number> = null;
  44. private _materialSignalLODs = new Array<Deferred<void>>();
  45. private _materialPromiseLODs = new Array<Array<Promise<any>>>();
  46. /** @hidden */
  47. constructor(loader: GLTFLoader) {
  48. this._loader = loader;
  49. }
  50. /** @hidden */
  51. public dispose() {
  52. delete this._loader;
  53. this._nodeIndexLOD = null;
  54. this._nodeSignalLODs.length = 0;
  55. this._nodePromiseLODs.length = 0;
  56. this._materialIndexLOD = null;
  57. this._materialSignalLODs.length = 0;
  58. this._materialPromiseLODs.length = 0;
  59. this.onMaterialLODsLoadedObservable.clear();
  60. this.onNodeLODsLoadedObservable.clear();
  61. }
  62. /** @hidden */
  63. public onReady(): void {
  64. for (let indexLOD = 0; indexLOD < this._nodePromiseLODs.length; indexLOD++) {
  65. const promise = Promise.all(this._nodePromiseLODs[indexLOD]).then(() => {
  66. if (indexLOD !== 0) {
  67. this._loader.endPerformanceCounter(`Node LOD ${indexLOD}`);
  68. }
  69. this._loader.log(`Loaded node LOD ${indexLOD}`);
  70. this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
  71. if (indexLOD !== this._nodePromiseLODs.length - 1) {
  72. this._loader.startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
  73. if (this._nodeSignalLODs[indexLOD]) {
  74. this._nodeSignalLODs[indexLOD].resolve();
  75. }
  76. }
  77. });
  78. this._loader._completePromises.push(promise);
  79. }
  80. for (let indexLOD = 0; indexLOD < this._materialPromiseLODs.length; indexLOD++) {
  81. const promise = Promise.all(this._materialPromiseLODs[indexLOD]).then(() => {
  82. if (indexLOD !== 0) {
  83. this._loader.endPerformanceCounter(`Material LOD ${indexLOD}`);
  84. }
  85. this._loader.log(`Loaded material LOD ${indexLOD}`);
  86. this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
  87. if (indexLOD !== this._materialPromiseLODs.length - 1) {
  88. this._loader.startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
  89. if (this._materialSignalLODs[indexLOD]) {
  90. this._materialSignalLODs[indexLOD].resolve();
  91. }
  92. }
  93. });
  94. this._loader._completePromises.push(promise);
  95. }
  96. }
  97. /** @hidden */
  98. public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
  99. return GLTFLoader.LoadExtensionAsync<IMSFTLOD, TransformNode>(context, node, this.name, (extensionContext, extension) => {
  100. let firstPromise: Promise<TransformNode>;
  101. const nodeLODs = this._getLODs(extensionContext, node, this._loader.gltf.nodes, extension.ids);
  102. this._loader.logOpen(`${extensionContext}`);
  103. for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
  104. const nodeLOD = nodeLODs[indexLOD];
  105. if (indexLOD !== 0) {
  106. this._nodeIndexLOD = indexLOD;
  107. this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
  108. }
  109. const assign = (babylonTransformNode: TransformNode) => { babylonTransformNode.setEnabled(false); };
  110. const promise = this._loader.loadNodeAsync(`#/nodes/${nodeLOD.index}`, nodeLOD, assign).then((babylonMesh) => {
  111. if (indexLOD !== 0) {
  112. // TODO: should not rely on _babylonMesh
  113. const previousNodeLOD = nodeLODs[indexLOD - 1];
  114. if (previousNodeLOD._babylonTransformNode) {
  115. previousNodeLOD._babylonTransformNode.dispose();
  116. delete previousNodeLOD._babylonTransformNode;
  117. this._disposeUnusedMaterials();
  118. }
  119. }
  120. babylonMesh.setEnabled(true);
  121. return babylonMesh;
  122. });
  123. if (indexLOD === 0) {
  124. firstPromise = promise;
  125. }
  126. else {
  127. this._nodeIndexLOD = null;
  128. }
  129. this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
  130. this._nodePromiseLODs[indexLOD].push(promise);
  131. }
  132. this._loader.logClose();
  133. return firstPromise!;
  134. });
  135. }
  136. /** @hidden */
  137. public _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>> {
  138. // Don't load material LODs if already loading a node LOD.
  139. if (this._nodeIndexLOD) {
  140. return null;
  141. }
  142. return GLTFLoader.LoadExtensionAsync<IMSFTLOD, Material>(context, material, this.name, (extensionContext, extension) => {
  143. let firstPromise: Promise<Material>;
  144. const materialLODs = this._getLODs(extensionContext, material, this._loader.gltf.materials, extension.ids);
  145. this._loader.logOpen(`${extensionContext}`);
  146. for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
  147. const materialLOD = materialLODs[indexLOD];
  148. if (indexLOD !== 0) {
  149. this._materialIndexLOD = indexLOD;
  150. }
  151. const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD.index}`, materialLOD, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  152. if (indexLOD === 0) {
  153. assign(babylonMaterial);
  154. }
  155. }).then((babylonMaterial) => {
  156. if (indexLOD !== 0) {
  157. assign(babylonMaterial);
  158. // TODO: should not rely on _data
  159. const previousDataLOD = materialLODs[indexLOD - 1]._data!;
  160. if (previousDataLOD[babylonDrawMode]) {
  161. previousDataLOD[babylonDrawMode].babylonMaterial.dispose();
  162. delete previousDataLOD[babylonDrawMode];
  163. }
  164. }
  165. return babylonMaterial;
  166. });
  167. if (indexLOD === 0) {
  168. firstPromise = promise;
  169. }
  170. else {
  171. this._materialIndexLOD = null;
  172. }
  173. this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
  174. this._materialPromiseLODs[indexLOD].push(promise);
  175. }
  176. this._loader.logClose();
  177. return firstPromise!;
  178. });
  179. }
  180. /** @hidden */
  181. public _loadUriAsync(context: string, property: IProperty, uri: string): Nullable<Promise<ArrayBufferView>> {
  182. // Defer the loading of uris if loading a material or node LOD.
  183. if (this._materialIndexLOD !== null) {
  184. this._loader.log(`deferred`);
  185. const previousIndexLOD = this._materialIndexLOD - 1;
  186. this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
  187. return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
  188. return this._loader.loadUriAsync(context, property, uri);
  189. });
  190. }
  191. else if (this._nodeIndexLOD !== null) {
  192. this._loader.log(`deferred`);
  193. const previousIndexLOD = this._nodeIndexLOD - 1;
  194. this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
  195. return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
  196. return this._loader.loadUriAsync(context, property, uri);
  197. });
  198. }
  199. return null;
  200. }
  201. /**
  202. * Gets an array of LOD properties from lowest to highest.
  203. */
  204. private _getLODs<T>(context: string, property: T, array: ArrayLike<T> | undefined, ids: number[]): T[] {
  205. if (this.maxLODsToLoad <= 0) {
  206. throw new Error("maxLODsToLoad must be greater than zero");
  207. }
  208. const properties = new Array<T>();
  209. for (let i = ids.length - 1; i >= 0; i--) {
  210. properties.push(ArrayItem.Get(`${context}/ids/${ids[i]}`, array, ids[i]));
  211. if (properties.length === this.maxLODsToLoad) {
  212. return properties;
  213. }
  214. }
  215. properties.push(property);
  216. return properties;
  217. }
  218. private _disposeUnusedMaterials(): void {
  219. // TODO: should not rely on _data
  220. const materials = this._loader.gltf.materials;
  221. if (materials) {
  222. for (const material of materials) {
  223. if (material._data) {
  224. for (const drawMode in material._data) {
  225. const data = material._data[drawMode];
  226. if (data.babylonMeshes.length === 0) {
  227. data.babylonMaterial.dispose(false, true);
  228. delete material._data[drawMode];
  229. }
  230. }
  231. }
  232. }
  233. }
  234. }
  235. }
  236. GLTFLoader.RegisterExtension(NAME, (loader) => new MSFT_lod(loader));