MSFT_lod.ts 12 KB

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