MSFT_lod.js 14 KB

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