GltfJsonLoader.js 8.2 KB

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