MSFT_lod.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
  8. import { INode, IMaterial, IBuffer, IScene } from "../glTFLoaderInterfaces";
  9. import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
  10. import { GLTFLoader, ArrayItem } from "../glTFLoader";
  11. import { IProperty } from 'babylonjs-gltf2interface';
  12. const NAME = "MSFT_lod";
  13. interface IMSFTLOD {
  14. ids: number[];
  15. }
  16. interface IBufferInfo {
  17. start: number;
  18. end: number;
  19. loaded: Deferred<ArrayBufferView>;
  20. }
  21. /**
  22. * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod)
  23. */
  24. export class MSFT_lod implements IGLTFLoaderExtension {
  25. /**
  26. * The name of this extension.
  27. */
  28. public readonly name = NAME;
  29. /**
  30. * Defines whether this extension is enabled.
  31. */
  32. public enabled: boolean;
  33. /**
  34. * Defines a number that determines the order the extensions are applied.
  35. */
  36. public order = 100;
  37. /**
  38. * Maximum number of LODs to load, starting from the lowest LOD.
  39. */
  40. public maxLODsToLoad = 10;
  41. /**
  42. * Observable raised when all node LODs of one level are loaded.
  43. * The event data is the index of the loaded LOD starting from zero.
  44. * Dispose the loader to cancel the loading of the next level of LODs.
  45. */
  46. public onNodeLODsLoadedObservable = new Observable<number>();
  47. /**
  48. * Observable raised when all material LODs of one level are loaded.
  49. * The event data is the index of the loaded LOD starting from zero.
  50. * Dispose the loader to cancel the loading of the next level of LODs.
  51. */
  52. public onMaterialLODsLoadedObservable = new Observable<number>();
  53. private _loader: GLTFLoader;
  54. private _bufferLODs = new Array<IBufferInfo>();
  55. private _nodeIndexLOD: Nullable<number> = null;
  56. private _nodeSignalLODs = new Array<Deferred<void>>();
  57. private _nodePromiseLODs = new Array<Array<Promise<any>>>();
  58. private _nodeBufferLODs = new Array<IBufferInfo>();
  59. private _materialIndexLOD: Nullable<number> = null;
  60. private _materialSignalLODs = new Array<Deferred<void>>();
  61. private _materialPromiseLODs = new Array<Array<Promise<any>>>();
  62. private _materialBufferLODs = new Array<IBufferInfo>();
  63. /** @hidden */
  64. constructor(loader: GLTFLoader) {
  65. this._loader = loader;
  66. this.enabled = this._loader.isExtensionUsed(NAME);
  67. }
  68. /** @hidden */
  69. public dispose() {
  70. delete this._loader;
  71. this._nodeIndexLOD = null;
  72. this._nodeSignalLODs.length = 0;
  73. this._nodePromiseLODs.length = 0;
  74. this._nodeBufferLODs.length = 0;
  75. this._materialIndexLOD = null;
  76. this._materialSignalLODs.length = 0;
  77. this._materialPromiseLODs.length = 0;
  78. this._materialBufferLODs.length = 0;
  79. this.onMaterialLODsLoadedObservable.clear();
  80. this.onNodeLODsLoadedObservable.clear();
  81. }
  82. /** @hidden */
  83. public onReady(): void {
  84. for (let indexLOD = 0; indexLOD < this._nodePromiseLODs.length; indexLOD++) {
  85. const promise = Promise.all(this._nodePromiseLODs[indexLOD]).then(() => {
  86. if (indexLOD !== 0) {
  87. this._loader.endPerformanceCounter(`Node LOD ${indexLOD}`);
  88. this._loader.log(`Loaded node LOD ${indexLOD}`);
  89. }
  90. this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
  91. if (indexLOD !== this._nodePromiseLODs.length - 1) {
  92. this._loader.startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
  93. this._loadBufferLOD(this._nodeBufferLODs, indexLOD + 1);
  94. if (this._nodeSignalLODs[indexLOD]) {
  95. this._nodeSignalLODs[indexLOD].resolve();
  96. }
  97. }
  98. });
  99. this._loader._completePromises.push(promise);
  100. }
  101. for (let indexLOD = 0; indexLOD < this._materialPromiseLODs.length; indexLOD++) {
  102. const promise = Promise.all(this._materialPromiseLODs[indexLOD]).then(() => {
  103. if (indexLOD !== 0) {
  104. this._loader.endPerformanceCounter(`Material LOD ${indexLOD}`);
  105. this._loader.log(`Loaded material LOD ${indexLOD}`);
  106. }
  107. this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
  108. if (indexLOD !== this._materialPromiseLODs.length - 1) {
  109. this._loader.startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
  110. this._loadBufferLOD(this._materialBufferLODs, indexLOD + 1);
  111. if (this._materialSignalLODs[indexLOD]) {
  112. this._materialSignalLODs[indexLOD].resolve();
  113. }
  114. }
  115. });
  116. this._loader._completePromises.push(promise);
  117. }
  118. }
  119. /** @hidden */
  120. public loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>> {
  121. const promise = this._loader.loadSceneAsync(context, scene);
  122. this._loadBufferLOD(this._bufferLODs, 0);
  123. return promise;
  124. }
  125. /** @hidden */
  126. public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
  127. return GLTFLoader.LoadExtensionAsync<IMSFTLOD, TransformNode>(context, node, this.name, (extensionContext, extension) => {
  128. let firstPromise: Promise<TransformNode>;
  129. const nodeLODs = this._getLODs(extensionContext, node, this._loader.gltf.nodes, extension.ids);
  130. this._loader.logOpen(`${extensionContext}`);
  131. for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
  132. const nodeLOD = nodeLODs[indexLOD];
  133. if (indexLOD !== 0) {
  134. this._nodeIndexLOD = indexLOD;
  135. this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
  136. }
  137. const assign = (babylonTransformNode: TransformNode) => { babylonTransformNode.setEnabled(false); };
  138. const promise = this._loader.loadNodeAsync(`#/nodes/${nodeLOD.index}`, nodeLOD, assign).then((babylonMesh) => {
  139. if (indexLOD !== 0) {
  140. // TODO: should not rely on _babylonTransformNode
  141. const previousNodeLOD = nodeLODs[indexLOD - 1];
  142. if (previousNodeLOD._babylonTransformNode) {
  143. this._disposeTransformNode(previousNodeLOD._babylonTransformNode);
  144. delete previousNodeLOD._babylonTransformNode;
  145. }
  146. }
  147. babylonMesh.setEnabled(true);
  148. return babylonMesh;
  149. });
  150. this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
  151. if (indexLOD === 0) {
  152. firstPromise = promise;
  153. }
  154. else {
  155. this._nodeIndexLOD = null;
  156. this._nodePromiseLODs[indexLOD].push(promise);
  157. }
  158. }
  159. this._loader.logClose();
  160. return firstPromise!;
  161. });
  162. }
  163. /** @hidden */
  164. public _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>> {
  165. // Don't load material LODs if already loading a node LOD.
  166. if (this._nodeIndexLOD) {
  167. return null;
  168. }
  169. return GLTFLoader.LoadExtensionAsync<IMSFTLOD, Material>(context, material, this.name, (extensionContext, extension) => {
  170. let firstPromise: Promise<Material>;
  171. const materialLODs = this._getLODs(extensionContext, material, this._loader.gltf.materials, extension.ids);
  172. this._loader.logOpen(`${extensionContext}`);
  173. for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
  174. const materialLOD = materialLODs[indexLOD];
  175. if (indexLOD !== 0) {
  176. this._materialIndexLOD = indexLOD;
  177. }
  178. const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD.index}`, materialLOD, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  179. if (indexLOD === 0) {
  180. assign(babylonMaterial);
  181. }
  182. }).then((babylonMaterial) => {
  183. if (indexLOD !== 0) {
  184. assign(babylonMaterial);
  185. // TODO: should not rely on _data
  186. const previousDataLOD = materialLODs[indexLOD - 1]._data!;
  187. if (previousDataLOD[babylonDrawMode]) {
  188. this._disposeMaterials([previousDataLOD[babylonDrawMode].babylonMaterial]);
  189. delete previousDataLOD[babylonDrawMode];
  190. }
  191. }
  192. return babylonMaterial;
  193. });
  194. this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
  195. if (indexLOD === 0) {
  196. firstPromise = promise;
  197. }
  198. else {
  199. this._materialIndexLOD = null;
  200. this._materialPromiseLODs[indexLOD].push(promise);
  201. }
  202. }
  203. this._loader.logClose();
  204. return firstPromise!;
  205. });
  206. }
  207. /** @hidden */
  208. public _loadUriAsync(context: string, property: IProperty, uri: string): Nullable<Promise<ArrayBufferView>> {
  209. // Defer the loading of uris if loading a node or material LOD.
  210. if (this._nodeIndexLOD !== null) {
  211. this._loader.log(`deferred`);
  212. const previousIndexLOD = this._nodeIndexLOD - 1;
  213. this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
  214. return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
  215. return this._loader.loadUriAsync(context, property, uri);
  216. });
  217. }
  218. else if (this._materialIndexLOD !== null) {
  219. this._loader.log(`deferred`);
  220. const previousIndexLOD = this._materialIndexLOD - 1;
  221. this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
  222. return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
  223. return this._loader.loadUriAsync(context, property, uri);
  224. });
  225. }
  226. return null;
  227. }
  228. /** @hidden */
  229. public loadBufferAsync(context: string, buffer: IBuffer, byteOffset: number, byteLength: number): Nullable<Promise<ArrayBufferView>> {
  230. if (this._loader.parent.useRangeRequests && !buffer.uri) {
  231. if (!this._loader.bin) {
  232. throw new Error(`${context}: Uri is missing or the binary glTF is missing its binary chunk`);
  233. }
  234. const loadAsync = (bufferLODs: Array<IBufferInfo>, indexLOD: number) => {
  235. const start = byteOffset;
  236. const end = start + byteLength - 1;
  237. let bufferLOD = bufferLODs[indexLOD];
  238. if (bufferLOD) {
  239. bufferLOD.start = Math.min(bufferLOD.start, start);
  240. bufferLOD.end = Math.max(bufferLOD.end, end);
  241. }
  242. else {
  243. bufferLOD = { start: start, end: end, loaded: new Deferred() };
  244. bufferLODs[indexLOD] = bufferLOD;
  245. }
  246. return bufferLOD.loaded.promise.then((data) => {
  247. return new Uint8Array(data.buffer, data.byteOffset + byteOffset - bufferLOD.start, byteLength);
  248. });
  249. };
  250. this._loader.log(`deferred`);
  251. if (this._nodeIndexLOD !== null) {
  252. return loadAsync(this._nodeBufferLODs, this._nodeIndexLOD);
  253. }
  254. else if (this._materialIndexLOD !== null) {
  255. return loadAsync(this._materialBufferLODs, this._materialIndexLOD);
  256. }
  257. else {
  258. return loadAsync(this._bufferLODs, 0);
  259. }
  260. }
  261. return null;
  262. }
  263. private _loadBufferLOD(bufferLODs: Array<IBufferInfo>, indexLOD: number): void {
  264. const bufferLOD = bufferLODs[indexLOD];
  265. if (bufferLOD) {
  266. this._loader.log(`Loading buffer range [${bufferLOD.start}-${bufferLOD.end}]`);
  267. this._loader.bin!.readAsync(bufferLOD.start, bufferLOD.end - bufferLOD.start + 1).then((data) => {
  268. bufferLOD.loaded.resolve(data);
  269. }, (error) => {
  270. bufferLOD.loaded.reject(error);
  271. });
  272. }
  273. }
  274. /**
  275. * Gets an array of LOD properties from lowest to highest.
  276. */
  277. private _getLODs<T>(context: string, property: T, array: ArrayLike<T> | undefined, ids: number[]): T[] {
  278. if (this.maxLODsToLoad <= 0) {
  279. throw new Error("maxLODsToLoad must be greater than zero");
  280. }
  281. const properties = new Array<T>();
  282. for (let i = ids.length - 1; i >= 0; i--) {
  283. properties.push(ArrayItem.Get(`${context}/ids/${ids[i]}`, array, ids[i]));
  284. if (properties.length === this.maxLODsToLoad) {
  285. return properties;
  286. }
  287. }
  288. properties.push(property);
  289. return properties;
  290. }
  291. private _disposeTransformNode(babylonTransformNode: TransformNode): void {
  292. const babylonMaterials = new Array<Material>();
  293. const babylonMaterial = (babylonTransformNode as Mesh).material;
  294. if (babylonMaterial) {
  295. babylonMaterials.push(babylonMaterial);
  296. }
  297. for (const babylonMesh of babylonTransformNode.getChildMeshes()) {
  298. if (babylonMesh.material) {
  299. babylonMaterials.push(babylonMesh.material);
  300. }
  301. }
  302. babylonTransformNode.dispose();
  303. const babylonMaterialsToDispose = babylonMaterials.filter((babylonMaterial) => this._loader.babylonScene.meshes.every((mesh) => mesh.material != babylonMaterial));
  304. this._disposeMaterials(babylonMaterialsToDispose);
  305. }
  306. private _disposeMaterials(babylonMaterials: Material[]): void {
  307. const babylonTextures: { [uniqueId: number]: BaseTexture } = {};
  308. for (const babylonMaterial of babylonMaterials) {
  309. for (const babylonTexture of babylonMaterial.getActiveTextures()) {
  310. babylonTextures[babylonTexture.uniqueId] = babylonTexture;
  311. }
  312. babylonMaterial.dispose();
  313. }
  314. for (const uniqueId in babylonTextures) {
  315. for (const babylonMaterial of this._loader.babylonScene.materials) {
  316. if (babylonMaterial.hasTexture(babylonTextures[uniqueId])) {
  317. delete babylonTextures[uniqueId];
  318. }
  319. }
  320. }
  321. for (const uniqueId in babylonTextures) {
  322. babylonTextures[uniqueId].dispose();
  323. }
  324. }
  325. }
  326. GLTFLoader.RegisterExtension(NAME, (loader) => new MSFT_lod(loader));