GltfStructuralMetadataLoader.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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 DeveloperError from "../Core/DeveloperError.js";
  6. import parseStructuralMetadata from "./parseStructuralMetadata.js";
  7. import parseFeatureMetadataLegacy from "./parseFeatureMetadataLegacy.js";
  8. import ResourceCache from "./ResourceCache.js";
  9. import ResourceLoader from "./ResourceLoader.js";
  10. import ResourceLoaderState from "./ResourceLoaderState.js";
  11. /**
  12. * Loads glTF structural metadata
  13. * <p>
  14. * Implements the {@link ResourceLoader} interface.
  15. * </p>
  16. *
  17. * @alias GltfStructuralMetadataLoader
  18. * @constructor
  19. * @augments ResourceLoader
  20. *
  21. * @param {Object} options Object with the following properties:
  22. * @param {Object} options.gltf The glTF JSON.
  23. * @param {String} [options.extension] The <code>EXT_structural_metadata</code> extension object. If this is undefined, then extensionLegacy must be defined.
  24. * @param {String} [options.extensionLegacy] The legacy <code>EXT_feature_metadata</code> extension for backwards compatibility.
  25. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  26. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  27. * @param {SupportedImageFormats} options.supportedImageFormats The supported image formats.
  28. * @param {String} [options.cacheKey] The cache key of the resource.
  29. * @param {Boolean} [options.asynchronous=true] Determines if WebGL resource creation will be spread out over several frames or block until all WebGL resources are created.
  30. *
  31. * @private
  32. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  33. */
  34. export default function GltfStructuralMetadataLoader(options) {
  35. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  36. const gltf = options.gltf;
  37. const extension = options.extension;
  38. const extensionLegacy = options.extensionLegacy;
  39. const gltfResource = options.gltfResource;
  40. const baseResource = options.baseResource;
  41. const supportedImageFormats = options.supportedImageFormats;
  42. const cacheKey = options.cacheKey;
  43. const asynchronous = defaultValue(options.asynchronous, true);
  44. //>>includeStart('debug', pragmas.debug);
  45. Check.typeOf.object("options.gltf", gltf);
  46. Check.typeOf.object("options.gltfResource", gltfResource);
  47. Check.typeOf.object("options.baseResource", baseResource);
  48. Check.typeOf.object("options.supportedImageFormats", supportedImageFormats);
  49. if (!defined(options.extension) && !defined(options.extensionLegacy)) {
  50. throw new DeveloperError(
  51. "One of options.extension or options.extensionLegacy must be specified"
  52. );
  53. }
  54. //>>includeEnd('debug');
  55. this._gltfResource = gltfResource;
  56. this._baseResource = baseResource;
  57. this._gltf = gltf;
  58. this._extension = extension;
  59. this._extensionLegacy = extensionLegacy;
  60. this._supportedImageFormats = supportedImageFormats;
  61. this._cacheKey = cacheKey;
  62. this._asynchronous = asynchronous;
  63. this._bufferViewLoaders = [];
  64. this._textureLoaders = [];
  65. this._schemaLoader = undefined;
  66. this._structuralMetadata = undefined;
  67. this._state = ResourceLoaderState.UNLOADED;
  68. this._promise = defer();
  69. }
  70. if (defined(Object.create)) {
  71. GltfStructuralMetadataLoader.prototype = Object.create(
  72. ResourceLoader.prototype
  73. );
  74. GltfStructuralMetadataLoader.prototype.constructor = GltfStructuralMetadataLoader;
  75. }
  76. Object.defineProperties(GltfStructuralMetadataLoader.prototype, {
  77. /**
  78. * A promise that resolves to the resource when the resource is ready.
  79. *
  80. * @memberof GltfStructuralMetadataLoader.prototype
  81. *
  82. * @type {Promise.<GltfStructuralMetadataLoader>}
  83. * @readonly
  84. * @private
  85. */
  86. promise: {
  87. get: function () {
  88. return this._promise.promise;
  89. },
  90. },
  91. /**
  92. * The cache key of the resource.
  93. *
  94. * @memberof GltfStructuralMetadataLoader.prototype
  95. *
  96. * @type {String}
  97. * @readonly
  98. * @private
  99. */
  100. cacheKey: {
  101. get: function () {
  102. return this._cacheKey;
  103. },
  104. },
  105. /**
  106. * The parsed structural metadata
  107. *
  108. * @memberof GltfStructuralMetadataLoader.prototype
  109. *
  110. * @type {StructuralMetadata}
  111. * @readonly
  112. * @private
  113. */
  114. structuralMetadata: {
  115. get: function () {
  116. return this._structuralMetadata;
  117. },
  118. },
  119. });
  120. /**
  121. * Loads the resource.
  122. * @private
  123. */
  124. GltfStructuralMetadataLoader.prototype.load = function () {
  125. const bufferViewsPromise = loadBufferViews(this);
  126. const texturesPromise = loadTextures(this);
  127. const schemaPromise = loadSchema(this);
  128. this._gltf = undefined; // No longer need to hold onto the glTF
  129. this._state = ResourceLoaderState.LOADING;
  130. const that = this;
  131. Promise.all([bufferViewsPromise, texturesPromise, schemaPromise])
  132. .then(function (results) {
  133. if (that.isDestroyed()) {
  134. return;
  135. }
  136. const bufferViews = results[0];
  137. const textures = results[1];
  138. const schema = results[2];
  139. if (defined(that._extension)) {
  140. that._structuralMetadata = parseStructuralMetadata({
  141. extension: that._extension,
  142. schema: schema,
  143. bufferViews: bufferViews,
  144. textures: textures,
  145. });
  146. } else {
  147. that._structuralMetadata = parseFeatureMetadataLegacy({
  148. extension: that._extensionLegacy,
  149. schema: schema,
  150. bufferViews: bufferViews,
  151. textures: textures,
  152. });
  153. }
  154. that._state = ResourceLoaderState.READY;
  155. that._promise.resolve(that);
  156. })
  157. .catch(function (error) {
  158. if (that.isDestroyed()) {
  159. return;
  160. }
  161. that.unload();
  162. that._state = ResourceLoaderState.FAILED;
  163. const errorMessage = "Failed to load structural metadata";
  164. that._promise.reject(that.getError(errorMessage, error));
  165. });
  166. };
  167. function gatherBufferViewIdsFromProperties(properties, bufferViewIdSet) {
  168. for (const propertyId in properties) {
  169. if (properties.hasOwnProperty(propertyId)) {
  170. const property = properties[propertyId];
  171. const values = property.values;
  172. const arrayOffsets = property.arrayOffsets;
  173. const stringOffsets = property.stringOffsets;
  174. // Using an object like a mathematical set
  175. if (defined(values)) {
  176. bufferViewIdSet[values] = true;
  177. }
  178. if (defined(arrayOffsets)) {
  179. bufferViewIdSet[arrayOffsets] = true;
  180. }
  181. if (defined(stringOffsets)) {
  182. bufferViewIdSet[stringOffsets] = true;
  183. }
  184. }
  185. }
  186. }
  187. function gatherBufferViewIdsFromPropertiesLegacy(properties, bufferViewIdSet) {
  188. for (const propertyId in properties) {
  189. if (properties.hasOwnProperty(propertyId)) {
  190. const property = properties[propertyId];
  191. const bufferView = property.bufferView;
  192. const arrayOffsetBufferView = property.arrayOffsetBufferView;
  193. const stringOffsetBufferView = property.stringOffsetBufferView;
  194. // Using an object like a mathematical set
  195. if (defined(bufferView)) {
  196. bufferViewIdSet[bufferView] = true;
  197. }
  198. if (defined(arrayOffsetBufferView)) {
  199. bufferViewIdSet[arrayOffsetBufferView] = true;
  200. }
  201. if (defined(stringOffsetBufferView)) {
  202. bufferViewIdSet[stringOffsetBufferView] = true;
  203. }
  204. }
  205. }
  206. }
  207. function gatherUsedBufferViewIds(extension) {
  208. const propertyTables = extension.propertyTables;
  209. const bufferViewIdSet = {};
  210. if (defined(propertyTables)) {
  211. for (let i = 0; i < propertyTables.length; i++) {
  212. const propertyTable = propertyTables[i];
  213. gatherBufferViewIdsFromProperties(
  214. propertyTable.properties,
  215. bufferViewIdSet
  216. );
  217. }
  218. }
  219. return bufferViewIdSet;
  220. }
  221. function gatherUsedBufferViewIdsLegacy(extensionLegacy) {
  222. const featureTables = extensionLegacy.featureTables;
  223. const bufferViewIdSet = {};
  224. if (defined(featureTables)) {
  225. for (const featureTableId in featureTables) {
  226. if (featureTables.hasOwnProperty(featureTableId)) {
  227. const featureTable = featureTables[featureTableId];
  228. const properties = featureTable.properties;
  229. if (defined(properties)) {
  230. gatherBufferViewIdsFromPropertiesLegacy(properties, bufferViewIdSet);
  231. }
  232. }
  233. }
  234. }
  235. return bufferViewIdSet;
  236. }
  237. function loadBufferViews(structuralMetadataLoader) {
  238. let bufferViewIds;
  239. if (defined(structuralMetadataLoader._extension)) {
  240. bufferViewIds = gatherUsedBufferViewIds(
  241. structuralMetadataLoader._extension
  242. );
  243. } else {
  244. bufferViewIds = gatherUsedBufferViewIdsLegacy(
  245. structuralMetadataLoader._extensionLegacy
  246. );
  247. }
  248. // Load the buffer views
  249. const bufferViewPromises = [];
  250. const bufferViewLoaders = {};
  251. for (const bufferViewId in bufferViewIds) {
  252. if (bufferViewIds.hasOwnProperty(bufferViewId)) {
  253. const bufferViewLoader = ResourceCache.loadBufferView({
  254. gltf: structuralMetadataLoader._gltf,
  255. bufferViewId: parseInt(bufferViewId),
  256. gltfResource: structuralMetadataLoader._gltfResource,
  257. baseResource: structuralMetadataLoader._baseResource,
  258. });
  259. bufferViewPromises.push(bufferViewLoader.promise);
  260. structuralMetadataLoader._bufferViewLoaders.push(bufferViewLoader);
  261. bufferViewLoaders[bufferViewId] = bufferViewLoader;
  262. }
  263. }
  264. // Return a promise to a map of buffer view IDs to typed arrays
  265. return Promise.all(bufferViewPromises).then(function () {
  266. const bufferViews = {};
  267. for (const bufferViewId in bufferViewLoaders) {
  268. if (bufferViewLoaders.hasOwnProperty(bufferViewId)) {
  269. const bufferViewLoader = bufferViewLoaders[bufferViewId];
  270. // Copy the typed array and let the underlying ArrayBuffer be freed
  271. const bufferViewTypedArray = new Uint8Array(
  272. bufferViewLoader.typedArray
  273. );
  274. bufferViews[bufferViewId] = bufferViewTypedArray;
  275. }
  276. }
  277. // Buffer views can be unloaded after the data has been copied
  278. unloadBufferViews(structuralMetadataLoader);
  279. return bufferViews;
  280. });
  281. }
  282. function gatherUsedTextureIds(structuralMetadataExtension) {
  283. // Gather the used textures
  284. const textureIds = {};
  285. const propertyTextures = structuralMetadataExtension.propertyTextures;
  286. if (defined(propertyTextures)) {
  287. for (let i = 0; i < propertyTextures.length; i++) {
  288. const propertyTexture = propertyTextures[i];
  289. const properties = propertyTexture.properties;
  290. if (defined(properties)) {
  291. gatherTextureIdsFromProperties(properties, textureIds);
  292. }
  293. }
  294. }
  295. return textureIds;
  296. }
  297. function gatherTextureIdsFromProperties(properties, textureIds) {
  298. for (const propertyId in properties) {
  299. if (properties.hasOwnProperty(propertyId)) {
  300. // in EXT_structural_metadata the property is a valid textureInfo.
  301. const textureInfo = properties[propertyId];
  302. textureIds[textureInfo.index] = textureInfo;
  303. }
  304. }
  305. }
  306. function gatherUsedTextureIdsLegacy(extensionLegacy) {
  307. // Gather the used textures
  308. const textureIds = {};
  309. const featureTextures = extensionLegacy.featureTextures;
  310. if (defined(featureTextures)) {
  311. for (const featureTextureId in featureTextures) {
  312. if (featureTextures.hasOwnProperty(featureTextureId)) {
  313. const featureTexture = featureTextures[featureTextureId];
  314. const properties = featureTexture.properties;
  315. if (defined(properties)) {
  316. gatherTextureIdsFromPropertiesLegacy(properties, textureIds);
  317. }
  318. }
  319. }
  320. }
  321. return textureIds;
  322. }
  323. function gatherTextureIdsFromPropertiesLegacy(properties, textureIds) {
  324. for (const propertyId in properties) {
  325. if (properties.hasOwnProperty(propertyId)) {
  326. const property = properties[propertyId];
  327. const textureInfo = property.texture;
  328. textureIds[textureInfo.index] = textureInfo;
  329. }
  330. }
  331. }
  332. function loadTextures(structuralMetadataLoader) {
  333. let textureIds;
  334. if (defined(structuralMetadataLoader._extension)) {
  335. textureIds = gatherUsedTextureIds(structuralMetadataLoader._extension);
  336. } else {
  337. textureIds = gatherUsedTextureIdsLegacy(
  338. structuralMetadataLoader._extensionLegacy
  339. );
  340. }
  341. const gltf = structuralMetadataLoader._gltf;
  342. const gltfResource = structuralMetadataLoader._gltfResource;
  343. const baseResource = structuralMetadataLoader._baseResource;
  344. const supportedImageFormats = structuralMetadataLoader._supportedImageFormats;
  345. const asynchronous = structuralMetadataLoader._asynchronous;
  346. // Load the textures
  347. const texturePromises = [];
  348. const textureLoaders = {};
  349. for (const textureId in textureIds) {
  350. if (textureIds.hasOwnProperty(textureId)) {
  351. const textureLoader = ResourceCache.loadTexture({
  352. gltf: gltf,
  353. textureInfo: textureIds[textureId],
  354. gltfResource: gltfResource,
  355. baseResource: baseResource,
  356. supportedImageFormats: supportedImageFormats,
  357. asynchronous: asynchronous,
  358. });
  359. texturePromises.push(textureLoader.promise);
  360. structuralMetadataLoader._textureLoaders.push(textureLoader);
  361. textureLoaders[textureId] = textureLoader;
  362. }
  363. }
  364. // Return a promise to a map of texture IDs to Texture objects
  365. return Promise.all(texturePromises).then(function () {
  366. const textures = {};
  367. for (const textureId in textureLoaders) {
  368. if (textureLoaders.hasOwnProperty(textureId)) {
  369. const textureLoader = textureLoaders[textureId];
  370. textures[textureId] = textureLoader.texture;
  371. }
  372. }
  373. return textures;
  374. });
  375. }
  376. function loadSchema(structuralMetadataLoader) {
  377. const extension = defaultValue(
  378. structuralMetadataLoader._extension,
  379. structuralMetadataLoader._extensionLegacy
  380. );
  381. let schemaLoader;
  382. if (defined(extension.schemaUri)) {
  383. const resource = structuralMetadataLoader._baseResource.getDerivedResource({
  384. url: extension.schemaUri,
  385. });
  386. schemaLoader = ResourceCache.loadSchema({
  387. resource: resource,
  388. });
  389. } else {
  390. schemaLoader = ResourceCache.loadSchema({
  391. schema: extension.schema,
  392. });
  393. }
  394. structuralMetadataLoader._schemaLoader = schemaLoader;
  395. return schemaLoader.promise.then(function (schemaLoader) {
  396. return schemaLoader.schema;
  397. });
  398. }
  399. /**
  400. * Processes the resource until it becomes ready.
  401. *
  402. * @param {FrameState} frameState The frame state.
  403. * @private
  404. */
  405. GltfStructuralMetadataLoader.prototype.process = function (frameState) {
  406. //>>includeStart('debug', pragmas.debug);
  407. Check.typeOf.object("frameState", frameState);
  408. //>>includeEnd('debug');
  409. if (this._state !== ResourceLoaderState.LOADING) {
  410. return;
  411. }
  412. const textureLoaders = this._textureLoaders;
  413. const textureLoadersLength = textureLoaders.length;
  414. for (let i = 0; i < textureLoadersLength; ++i) {
  415. const textureLoader = textureLoaders[i];
  416. textureLoader.process(frameState);
  417. }
  418. };
  419. function unloadBufferViews(structuralMetadataLoader) {
  420. const bufferViewLoaders = structuralMetadataLoader._bufferViewLoaders;
  421. const bufferViewLoadersLength = bufferViewLoaders.length;
  422. for (let i = 0; i < bufferViewLoadersLength; ++i) {
  423. ResourceCache.unload(bufferViewLoaders[i]);
  424. }
  425. structuralMetadataLoader._bufferViewLoaders.length = 0;
  426. }
  427. function unloadTextures(structuralMetadataLoader) {
  428. const textureLoaders = structuralMetadataLoader._textureLoaders;
  429. const textureLoadersLength = textureLoaders.length;
  430. for (let i = 0; i < textureLoadersLength; ++i) {
  431. ResourceCache.unload(textureLoaders[i]);
  432. }
  433. structuralMetadataLoader._textureLoaders.length = 0;
  434. }
  435. /**
  436. * Unloads the resource.
  437. * @private
  438. */
  439. GltfStructuralMetadataLoader.prototype.unload = function () {
  440. unloadBufferViews(this);
  441. unloadTextures(this);
  442. if (defined(this._schemaLoader)) {
  443. ResourceCache.unload(this._schemaLoader);
  444. }
  445. this._schemaLoader = undefined;
  446. this._structuralMetadata = undefined;
  447. };