Geometry3DTileContent.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import defer from "../Core/defer.js";
  4. import defined from "../Core/defined.js";
  5. import destroyObject from "../Core/destroyObject.js";
  6. import DeveloperError from "../Core/DeveloperError.js";
  7. import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
  8. import Matrix4 from "../Core/Matrix4.js";
  9. import RuntimeError from "../Core/RuntimeError.js";
  10. import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js";
  11. import Vector3DTileGeometry from "./Vector3DTileGeometry.js";
  12. /**
  13. * <p>
  14. * Implements the {@link Cesium3DTileContent} interface.
  15. * </p>
  16. *
  17. * @alias Geometry3DTileContent
  18. * @constructor
  19. *
  20. * @private
  21. */
  22. function Geometry3DTileContent(
  23. tileset,
  24. tile,
  25. resource,
  26. arrayBuffer,
  27. byteOffset
  28. ) {
  29. this._tileset = tileset;
  30. this._tile = tile;
  31. this._resource = resource;
  32. this._geometries = undefined;
  33. this._contentReadyPromise = undefined;
  34. this._readyPromise = defer();
  35. this._metadata = undefined;
  36. this._batchTable = undefined;
  37. this._features = undefined;
  38. /**
  39. * Part of the {@link Cesium3DTileContent} interface.
  40. */
  41. this.featurePropertiesDirty = false;
  42. this._group = undefined;
  43. initialize(this, arrayBuffer, byteOffset);
  44. }
  45. Object.defineProperties(Geometry3DTileContent.prototype, {
  46. featuresLength: {
  47. get: function () {
  48. return defined(this._batchTable) ? this._batchTable.featuresLength : 0;
  49. },
  50. },
  51. pointsLength: {
  52. get: function () {
  53. return 0;
  54. },
  55. },
  56. trianglesLength: {
  57. get: function () {
  58. if (defined(this._geometries)) {
  59. return this._geometries.trianglesLength;
  60. }
  61. return 0;
  62. },
  63. },
  64. geometryByteLength: {
  65. get: function () {
  66. if (defined(this._geometries)) {
  67. return this._geometries.geometryByteLength;
  68. }
  69. return 0;
  70. },
  71. },
  72. texturesByteLength: {
  73. get: function () {
  74. return 0;
  75. },
  76. },
  77. batchTableByteLength: {
  78. get: function () {
  79. return defined(this._batchTable) ? this._batchTable.memorySizeInBytes : 0;
  80. },
  81. },
  82. innerContents: {
  83. get: function () {
  84. return undefined;
  85. },
  86. },
  87. readyPromise: {
  88. get: function () {
  89. return this._readyPromise.promise;
  90. },
  91. },
  92. tileset: {
  93. get: function () {
  94. return this._tileset;
  95. },
  96. },
  97. tile: {
  98. get: function () {
  99. return this._tile;
  100. },
  101. },
  102. url: {
  103. get: function () {
  104. return this._resource.getUrlComponent(true);
  105. },
  106. },
  107. metadata: {
  108. get: function () {
  109. return this._metadata;
  110. },
  111. set: function (value) {
  112. this._metadata = value;
  113. },
  114. },
  115. batchTable: {
  116. get: function () {
  117. return this._batchTable;
  118. },
  119. },
  120. group: {
  121. get: function () {
  122. return this._group;
  123. },
  124. set: function (value) {
  125. this._group = value;
  126. },
  127. },
  128. });
  129. function createColorChangedCallback(content) {
  130. return function (batchId, color) {
  131. if (defined(content._geometries)) {
  132. content._geometries.updateCommands(batchId, color);
  133. }
  134. };
  135. }
  136. function getBatchIds(featureTableJson, featureTableBinary) {
  137. let boxBatchIds;
  138. let cylinderBatchIds;
  139. let ellipsoidBatchIds;
  140. let sphereBatchIds;
  141. let i;
  142. const numberOfBoxes = defaultValue(featureTableJson.BOXES_LENGTH, 0);
  143. const numberOfCylinders = defaultValue(featureTableJson.CYLINDERS_LENGTH, 0);
  144. const numberOfEllipsoids = defaultValue(
  145. featureTableJson.ELLIPSOIDS_LENGTH,
  146. 0
  147. );
  148. const numberOfSpheres = defaultValue(featureTableJson.SPHERES_LENGTH, 0);
  149. if (numberOfBoxes > 0 && defined(featureTableJson.BOX_BATCH_IDS)) {
  150. const boxBatchIdsByteOffset =
  151. featureTableBinary.byteOffset + featureTableJson.BOX_BATCH_IDS.byteOffset;
  152. boxBatchIds = new Uint16Array(
  153. featureTableBinary.buffer,
  154. boxBatchIdsByteOffset,
  155. numberOfBoxes
  156. );
  157. }
  158. if (numberOfCylinders > 0 && defined(featureTableJson.CYLINDER_BATCH_IDS)) {
  159. const cylinderBatchIdsByteOffset =
  160. featureTableBinary.byteOffset +
  161. featureTableJson.CYLINDER_BATCH_IDS.byteOffset;
  162. cylinderBatchIds = new Uint16Array(
  163. featureTableBinary.buffer,
  164. cylinderBatchIdsByteOffset,
  165. numberOfCylinders
  166. );
  167. }
  168. if (numberOfEllipsoids > 0 && defined(featureTableJson.ELLIPSOID_BATCH_IDS)) {
  169. const ellipsoidBatchIdsByteOffset =
  170. featureTableBinary.byteOffset +
  171. featureTableJson.ELLIPSOID_BATCH_IDS.byteOffset;
  172. ellipsoidBatchIds = new Uint16Array(
  173. featureTableBinary.buffer,
  174. ellipsoidBatchIdsByteOffset,
  175. numberOfEllipsoids
  176. );
  177. }
  178. if (numberOfSpheres > 0 && defined(featureTableJson.SPHERE_BATCH_IDS)) {
  179. const sphereBatchIdsByteOffset =
  180. featureTableBinary.byteOffset +
  181. featureTableJson.SPHERE_BATCH_IDS.byteOffset;
  182. sphereBatchIds = new Uint16Array(
  183. featureTableBinary.buffer,
  184. sphereBatchIdsByteOffset,
  185. numberOfSpheres
  186. );
  187. }
  188. const atLeastOneDefined =
  189. defined(boxBatchIds) ||
  190. defined(cylinderBatchIds) ||
  191. defined(ellipsoidBatchIds) ||
  192. defined(sphereBatchIds);
  193. const atLeastOneUndefined =
  194. (numberOfBoxes > 0 && !defined(boxBatchIds)) ||
  195. (numberOfCylinders > 0 && !defined(cylinderBatchIds)) ||
  196. (numberOfEllipsoids > 0 && !defined(ellipsoidBatchIds)) ||
  197. (numberOfSpheres > 0 && !defined(sphereBatchIds));
  198. if (atLeastOneDefined && atLeastOneUndefined) {
  199. throw new RuntimeError(
  200. "If one group of batch ids is defined, then all batch ids must be defined."
  201. );
  202. }
  203. const allUndefinedBatchIds =
  204. !defined(boxBatchIds) &&
  205. !defined(cylinderBatchIds) &&
  206. !defined(ellipsoidBatchIds) &&
  207. !defined(sphereBatchIds);
  208. if (allUndefinedBatchIds) {
  209. let id = 0;
  210. if (!defined(boxBatchIds) && numberOfBoxes > 0) {
  211. boxBatchIds = new Uint16Array(numberOfBoxes);
  212. for (i = 0; i < numberOfBoxes; ++i) {
  213. boxBatchIds[i] = id++;
  214. }
  215. }
  216. if (!defined(cylinderBatchIds) && numberOfCylinders > 0) {
  217. cylinderBatchIds = new Uint16Array(numberOfCylinders);
  218. for (i = 0; i < numberOfCylinders; ++i) {
  219. cylinderBatchIds[i] = id++;
  220. }
  221. }
  222. if (!defined(ellipsoidBatchIds) && numberOfEllipsoids > 0) {
  223. ellipsoidBatchIds = new Uint16Array(numberOfEllipsoids);
  224. for (i = 0; i < numberOfEllipsoids; ++i) {
  225. ellipsoidBatchIds[i] = id++;
  226. }
  227. }
  228. if (!defined(sphereBatchIds) && numberOfSpheres > 0) {
  229. sphereBatchIds = new Uint16Array(numberOfSpheres);
  230. for (i = 0; i < numberOfSpheres; ++i) {
  231. sphereBatchIds[i] = id++;
  232. }
  233. }
  234. }
  235. return {
  236. boxes: boxBatchIds,
  237. cylinders: cylinderBatchIds,
  238. ellipsoids: ellipsoidBatchIds,
  239. spheres: sphereBatchIds,
  240. };
  241. }
  242. const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  243. function initialize(content, arrayBuffer, byteOffset) {
  244. byteOffset = defaultValue(byteOffset, 0);
  245. const uint8Array = new Uint8Array(arrayBuffer);
  246. const view = new DataView(arrayBuffer);
  247. byteOffset += sizeOfUint32; // Skip magic number
  248. const version = view.getUint32(byteOffset, true);
  249. if (version !== 1) {
  250. throw new RuntimeError(
  251. `Only Geometry tile version 1 is supported. Version ${version} is not.`
  252. );
  253. }
  254. byteOffset += sizeOfUint32;
  255. const byteLength = view.getUint32(byteOffset, true);
  256. byteOffset += sizeOfUint32;
  257. if (byteLength === 0) {
  258. content._readyPromise.resolve(content);
  259. return;
  260. }
  261. const featureTableJSONByteLength = view.getUint32(byteOffset, true);
  262. byteOffset += sizeOfUint32;
  263. if (featureTableJSONByteLength === 0) {
  264. throw new RuntimeError(
  265. "Feature table must have a byte length greater than zero"
  266. );
  267. }
  268. const featureTableBinaryByteLength = view.getUint32(byteOffset, true);
  269. byteOffset += sizeOfUint32;
  270. const batchTableJSONByteLength = view.getUint32(byteOffset, true);
  271. byteOffset += sizeOfUint32;
  272. const batchTableBinaryByteLength = view.getUint32(byteOffset, true);
  273. byteOffset += sizeOfUint32;
  274. const featureTableJson = getJsonFromTypedArray(
  275. uint8Array,
  276. byteOffset,
  277. featureTableJSONByteLength
  278. );
  279. byteOffset += featureTableJSONByteLength;
  280. const featureTableBinary = new Uint8Array(
  281. arrayBuffer,
  282. byteOffset,
  283. featureTableBinaryByteLength
  284. );
  285. byteOffset += featureTableBinaryByteLength;
  286. let batchTableJson;
  287. let batchTableBinary;
  288. if (batchTableJSONByteLength > 0) {
  289. // PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the
  290. // arraybuffer/string compressed in memory and then decompress it when it is first accessed.
  291. //
  292. // We could also make another request for it, but that would make the property set/get
  293. // API async, and would double the number of numbers in some cases.
  294. batchTableJson = getJsonFromTypedArray(
  295. uint8Array,
  296. byteOffset,
  297. batchTableJSONByteLength
  298. );
  299. byteOffset += batchTableJSONByteLength;
  300. if (batchTableBinaryByteLength > 0) {
  301. // Has a batch table binary
  302. batchTableBinary = new Uint8Array(
  303. arrayBuffer,
  304. byteOffset,
  305. batchTableBinaryByteLength
  306. );
  307. // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
  308. batchTableBinary = new Uint8Array(batchTableBinary);
  309. }
  310. }
  311. const numberOfBoxes = defaultValue(featureTableJson.BOXES_LENGTH, 0);
  312. const numberOfCylinders = defaultValue(featureTableJson.CYLINDERS_LENGTH, 0);
  313. const numberOfEllipsoids = defaultValue(
  314. featureTableJson.ELLIPSOIDS_LENGTH,
  315. 0
  316. );
  317. const numberOfSpheres = defaultValue(featureTableJson.SPHERES_LENGTH, 0);
  318. const totalPrimitives =
  319. numberOfBoxes + numberOfCylinders + numberOfEllipsoids + numberOfSpheres;
  320. const batchTable = new Cesium3DTileBatchTable(
  321. content,
  322. totalPrimitives,
  323. batchTableJson,
  324. batchTableBinary,
  325. createColorChangedCallback(content)
  326. );
  327. content._batchTable = batchTable;
  328. if (totalPrimitives === 0) {
  329. return;
  330. }
  331. const modelMatrix = content.tile.computedTransform;
  332. let center;
  333. if (defined(featureTableJson.RTC_CENTER)) {
  334. center = Cartesian3.unpack(featureTableJson.RTC_CENTER);
  335. Matrix4.multiplyByPoint(modelMatrix, center, center);
  336. }
  337. const batchIds = getBatchIds(featureTableJson, featureTableBinary);
  338. if (
  339. numberOfBoxes > 0 ||
  340. numberOfCylinders > 0 ||
  341. numberOfEllipsoids > 0 ||
  342. numberOfSpheres > 0
  343. ) {
  344. let boxes;
  345. let cylinders;
  346. let ellipsoids;
  347. let spheres;
  348. if (numberOfBoxes > 0) {
  349. const boxesByteOffset =
  350. featureTableBinary.byteOffset + featureTableJson.BOXES.byteOffset;
  351. boxes = new Float32Array(
  352. featureTableBinary.buffer,
  353. boxesByteOffset,
  354. Vector3DTileGeometry.packedBoxLength * numberOfBoxes
  355. );
  356. }
  357. if (numberOfCylinders > 0) {
  358. const cylindersByteOffset =
  359. featureTableBinary.byteOffset + featureTableJson.CYLINDERS.byteOffset;
  360. cylinders = new Float32Array(
  361. featureTableBinary.buffer,
  362. cylindersByteOffset,
  363. Vector3DTileGeometry.packedCylinderLength * numberOfCylinders
  364. );
  365. }
  366. if (numberOfEllipsoids > 0) {
  367. const ellipsoidsByteOffset =
  368. featureTableBinary.byteOffset + featureTableJson.ELLIPSOIDS.byteOffset;
  369. ellipsoids = new Float32Array(
  370. featureTableBinary.buffer,
  371. ellipsoidsByteOffset,
  372. Vector3DTileGeometry.packedEllipsoidLength * numberOfEllipsoids
  373. );
  374. }
  375. if (numberOfSpheres > 0) {
  376. const spheresByteOffset =
  377. featureTableBinary.byteOffset + featureTableJson.SPHERES.byteOffset;
  378. spheres = new Float32Array(
  379. featureTableBinary.buffer,
  380. spheresByteOffset,
  381. Vector3DTileGeometry.packedSphereLength * numberOfSpheres
  382. );
  383. }
  384. content._geometries = new Vector3DTileGeometry({
  385. boxes: boxes,
  386. boxBatchIds: batchIds.boxes,
  387. cylinders: cylinders,
  388. cylinderBatchIds: batchIds.cylinders,
  389. ellipsoids: ellipsoids,
  390. ellipsoidBatchIds: batchIds.ellipsoids,
  391. spheres: spheres,
  392. sphereBatchIds: batchIds.spheres,
  393. center: center,
  394. modelMatrix: modelMatrix,
  395. batchTable: batchTable,
  396. boundingVolume: content.tile.boundingVolume.boundingVolume,
  397. });
  398. }
  399. }
  400. function createFeatures(content) {
  401. const featuresLength = content.featuresLength;
  402. if (!defined(content._features) && featuresLength > 0) {
  403. const features = new Array(featuresLength);
  404. if (defined(content._geometries)) {
  405. content._geometries.createFeatures(content, features);
  406. }
  407. content._features = features;
  408. }
  409. }
  410. Geometry3DTileContent.prototype.hasProperty = function (batchId, name) {
  411. return this._batchTable.hasProperty(batchId, name);
  412. };
  413. Geometry3DTileContent.prototype.getFeature = function (batchId) {
  414. //>>includeStart('debug', pragmas.debug);
  415. const featuresLength = this.featuresLength;
  416. if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
  417. throw new DeveloperError(
  418. `batchId is required and between zero and featuresLength - 1 (${
  419. featuresLength - 1
  420. }).`
  421. );
  422. }
  423. //>>includeEnd('debug');
  424. createFeatures(this);
  425. return this._features[batchId];
  426. };
  427. Geometry3DTileContent.prototype.applyDebugSettings = function (enabled, color) {
  428. if (defined(this._geometries)) {
  429. this._geometries.applyDebugSettings(enabled, color);
  430. }
  431. };
  432. Geometry3DTileContent.prototype.applyStyle = function (style) {
  433. createFeatures(this);
  434. if (defined(this._geometries)) {
  435. this._geometries.applyStyle(style, this._features);
  436. }
  437. };
  438. Geometry3DTileContent.prototype.update = function (tileset, frameState) {
  439. if (defined(this._geometries)) {
  440. this._geometries.classificationType = this._tileset.classificationType;
  441. this._geometries.debugWireframe = this._tileset.debugWireframe;
  442. this._geometries.update(frameState);
  443. }
  444. if (defined(this._batchTable) && this._geometries._ready) {
  445. this._batchTable.update(tileset, frameState);
  446. }
  447. if (!defined(this._contentReadyPromise)) {
  448. const that = this;
  449. this._contentReadyPromise = this._geometries.readyPromise.then(function () {
  450. that._readyPromise.resolve(that);
  451. });
  452. }
  453. };
  454. Geometry3DTileContent.prototype.isDestroyed = function () {
  455. return false;
  456. };
  457. Geometry3DTileContent.prototype.destroy = function () {
  458. this._geometries = this._geometries && this._geometries.destroy();
  459. this._batchTable = this._batchTable && this._batchTable.destroy();
  460. return destroyObject(this);
  461. };
  462. export default Geometry3DTileContent;