GltfJsonLoader.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import defined from "../Core/defined.js";
  4. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  5. import getMagic from "../Core/getMagic.js";
  6. import isDataUri from "../Core/isDataUri.js";
  7. import Resource from "../Core/Resource.js";
  8. import RuntimeError from "../Core/RuntimeError.js";
  9. import addDefaults from "./GltfPipeline/addDefaults.js";
  10. import addPipelineExtras from "./GltfPipeline/addPipelineExtras.js";
  11. import ForEach from "./GltfPipeline/ForEach.js";
  12. import parseGlb from "./GltfPipeline/parseGlb.js";
  13. import removePipelineExtras from "./GltfPipeline/removePipelineExtras.js";
  14. import updateVersion from "./GltfPipeline/updateVersion.js";
  15. import usesExtension from "./GltfPipeline/usesExtension.js";
  16. import ResourceLoader from "./ResourceLoader.js";
  17. import ResourceLoaderState from "./ResourceLoaderState.js";
  18. import ModelUtility from "./Model/ModelUtility.js";
  19. /**
  20. * Loads a glTF JSON from a glTF or glb.
  21. * <p>
  22. * Implements the {@link ResourceLoader} interface.
  23. * </p>
  24. *
  25. * @alias GltfJsonLoader
  26. * @constructor
  27. * @augments ResourceLoader
  28. *
  29. * @param {object} options Object with the following properties:
  30. * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies).
  31. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  32. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  33. * @param {Uint8Array} [options.typedArray] The typed array containing the glTF contents.
  34. * @param {object} [options.gltfJson] The parsed glTF JSON contents.
  35. * @param {string} [options.cacheKey] The cache key of the resource.
  36. *
  37. * @private
  38. */
  39. function GltfJsonLoader(options) {
  40. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  41. const resourceCache = options.resourceCache;
  42. const gltfResource = options.gltfResource;
  43. const baseResource = options.baseResource;
  44. const typedArray = options.typedArray;
  45. const gltfJson = options.gltfJson;
  46. const cacheKey = options.cacheKey;
  47. //>>includeStart('debug', pragmas.debug);
  48. Check.typeOf.func("options.resourceCache", resourceCache);
  49. Check.typeOf.object("options.gltfResource", gltfResource);
  50. Check.typeOf.object("options.baseResource", baseResource);
  51. //>>includeEnd('debug');
  52. this._resourceCache = resourceCache;
  53. this._gltfResource = gltfResource;
  54. this._baseResource = baseResource;
  55. this._typedArray = typedArray;
  56. this._gltfJson = gltfJson;
  57. this._cacheKey = cacheKey;
  58. this._gltf = undefined;
  59. this._bufferLoaders = [];
  60. this._state = ResourceLoaderState.UNLOADED;
  61. this._promise = undefined;
  62. }
  63. if (defined(Object.create)) {
  64. GltfJsonLoader.prototype = Object.create(ResourceLoader.prototype);
  65. GltfJsonLoader.prototype.constructor = GltfJsonLoader;
  66. }
  67. Object.defineProperties(GltfJsonLoader.prototype, {
  68. /**
  69. * The cache key of the resource.
  70. *
  71. * @memberof GltfJsonLoader.prototype
  72. *
  73. * @type {string}
  74. * @readonly
  75. * @private
  76. */
  77. cacheKey: {
  78. get: function () {
  79. return this._cacheKey;
  80. },
  81. },
  82. /**
  83. * The glTF JSON.
  84. *
  85. * @memberof GltfJsonLoader.prototype
  86. *
  87. * @type {object}
  88. * @readonly
  89. * @private
  90. */
  91. gltf: {
  92. get: function () {
  93. return this._gltf;
  94. },
  95. },
  96. });
  97. /**
  98. * Loads the resource.
  99. * @returns {Promise<GltfJsonLoader>} A promise which resolves to the loader when the resource loading is completed.
  100. * @private
  101. */
  102. GltfJsonLoader.prototype.load = async function () {
  103. if (defined(this._promise)) {
  104. return this._promise;
  105. }
  106. this._state = ResourceLoaderState.LOADING;
  107. if (defined(this._gltfJson)) {
  108. this._promise = processGltfJson(this, this._gltfJson);
  109. return this._promise;
  110. }
  111. if (defined(this._typedArray)) {
  112. this._promise = processGltfTypedArray(this, this._typedArray);
  113. return this._promise;
  114. }
  115. this._promise = loadFromUri(this);
  116. return this._promise;
  117. };
  118. async function loadFromUri(gltfJsonLoader) {
  119. let typedArray;
  120. try {
  121. const arrayBuffer = await gltfJsonLoader._fetchGltf();
  122. if (gltfJsonLoader.isDestroyed()) {
  123. return;
  124. }
  125. typedArray = new Uint8Array(arrayBuffer);
  126. } catch (error) {
  127. if (gltfJsonLoader.isDestroyed()) {
  128. return;
  129. }
  130. handleError(gltfJsonLoader, error);
  131. }
  132. return processGltfTypedArray(gltfJsonLoader, typedArray);
  133. }
  134. function handleError(gltfJsonLoader, error) {
  135. gltfJsonLoader.unload();
  136. gltfJsonLoader._state = ResourceLoaderState.FAILED;
  137. const errorMessage = `Failed to load glTF: ${gltfJsonLoader._gltfResource.url}`;
  138. throw gltfJsonLoader.getError(errorMessage, error);
  139. }
  140. async function upgradeVersion(gltfJsonLoader, gltf) {
  141. if (
  142. defined(gltf.asset) &&
  143. gltf.asset.version === "2.0" &&
  144. !usesExtension(gltf, "KHR_techniques_webgl") &&
  145. !usesExtension(gltf, "KHR_materials_common")
  146. ) {
  147. return Promise.resolve();
  148. }
  149. // Load all buffers into memory. updateVersion will read and in some cases modify
  150. // the buffer data, which it accesses from buffer.extras._pipeline.source
  151. const promises = [];
  152. ForEach.buffer(gltf, function (buffer) {
  153. if (
  154. !defined(buffer.extras._pipeline.source) && // Ignore uri if this buffer uses the glTF 1.0 KHR_binary_glTF extension
  155. defined(buffer.uri)
  156. ) {
  157. const resource = gltfJsonLoader._baseResource.getDerivedResource({
  158. url: buffer.uri,
  159. });
  160. const resourceCache = gltfJsonLoader._resourceCache;
  161. const bufferLoader = resourceCache.getExternalBufferLoader({
  162. resource: resource,
  163. });
  164. gltfJsonLoader._bufferLoaders.push(bufferLoader);
  165. promises.push(
  166. bufferLoader.load().then(function () {
  167. if (bufferLoader.isDestroyed()) {
  168. return;
  169. }
  170. buffer.extras._pipeline.source = bufferLoader.typedArray;
  171. })
  172. );
  173. }
  174. });
  175. await Promise.all(promises);
  176. updateVersion(gltf);
  177. }
  178. function decodeDataUris(gltf) {
  179. const promises = [];
  180. ForEach.buffer(gltf, function (buffer) {
  181. const bufferUri = buffer.uri;
  182. if (
  183. !defined(buffer.extras._pipeline.source) && // Ignore uri if this buffer uses the glTF 1.0 KHR_binary_glTF extension
  184. defined(bufferUri) &&
  185. isDataUri(bufferUri)
  186. ) {
  187. delete buffer.uri; // Delete the data URI to keep the cached glTF JSON small
  188. promises.push(
  189. Resource.fetchArrayBuffer(bufferUri).then(function (arrayBuffer) {
  190. buffer.extras._pipeline.source = new Uint8Array(arrayBuffer);
  191. })
  192. );
  193. }
  194. });
  195. return Promise.all(promises);
  196. }
  197. function loadEmbeddedBuffers(gltfJsonLoader, gltf) {
  198. const promises = [];
  199. ForEach.buffer(gltf, function (buffer, bufferId) {
  200. const source = buffer.extras._pipeline.source;
  201. if (defined(source) && !defined(buffer.uri)) {
  202. const resourceCache = gltfJsonLoader._resourceCache;
  203. const bufferLoader = resourceCache.getEmbeddedBufferLoader({
  204. parentResource: gltfJsonLoader._gltfResource,
  205. bufferId: bufferId,
  206. typedArray: source,
  207. });
  208. gltfJsonLoader._bufferLoaders.push(bufferLoader);
  209. promises.push(bufferLoader.load());
  210. }
  211. });
  212. return Promise.all(promises);
  213. }
  214. async function processGltfJson(gltfJsonLoader, gltf) {
  215. try {
  216. addPipelineExtras(gltf);
  217. await decodeDataUris(gltf);
  218. await upgradeVersion(gltfJsonLoader, gltf);
  219. addDefaults(gltf);
  220. await loadEmbeddedBuffers(gltfJsonLoader, gltf);
  221. removePipelineExtras(gltf);
  222. const version = gltf.asset.version;
  223. if (version !== "1.0" && version !== "2.0") {
  224. throw new RuntimeError(`Unsupported glTF version: ${version}`);
  225. }
  226. const extensionsRequired = gltf.extensionsRequired;
  227. if (defined(extensionsRequired)) {
  228. ModelUtility.checkSupportedExtensions(extensionsRequired);
  229. }
  230. gltfJsonLoader._gltf = gltf;
  231. gltfJsonLoader._state = ResourceLoaderState.READY;
  232. return gltfJsonLoader;
  233. } catch (error) {
  234. if (gltfJsonLoader.isDestroyed()) {
  235. return;
  236. }
  237. handleError(gltfJsonLoader, error);
  238. }
  239. }
  240. async function processGltfTypedArray(gltfJsonLoader, typedArray) {
  241. let gltf;
  242. try {
  243. if (getMagic(typedArray) === "glTF") {
  244. gltf = parseGlb(typedArray);
  245. } else {
  246. gltf = getJsonFromTypedArray(typedArray);
  247. }
  248. } catch (error) {
  249. if (gltfJsonLoader.isDestroyed()) {
  250. return;
  251. }
  252. handleError(gltfJsonLoader, error);
  253. }
  254. return processGltfJson(gltfJsonLoader, gltf);
  255. }
  256. /**
  257. * Unloads the resource.
  258. * @private
  259. */
  260. GltfJsonLoader.prototype.unload = function () {
  261. const bufferLoaders = this._bufferLoaders;
  262. const bufferLoadersLength = bufferLoaders.length;
  263. for (let i = 0; i < bufferLoadersLength; ++i) {
  264. bufferLoaders[i] =
  265. !bufferLoaders[i].isDestroyed() &&
  266. this._resourceCache.unload(bufferLoaders[i]);
  267. }
  268. this._bufferLoaders.length = 0;
  269. this._gltf = undefined;
  270. };
  271. /**
  272. * Exposed for testing
  273. *
  274. * @private
  275. */
  276. GltfJsonLoader.prototype._fetchGltf = function () {
  277. return this._gltfResource.fetchArrayBuffer();
  278. };
  279. export default GltfJsonLoader;