Instanced3DModel3DTileContent.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. import AttributeCompression from "../Core/AttributeCompression.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Color from "../Core/Color.js";
  4. import ComponentDatatype from "../Core/ComponentDatatype.js";
  5. import defined from "../Core/defined.js";
  6. import deprecationWarning from "../Core/deprecationWarning.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import Ellipsoid from "../Core/Ellipsoid.js";
  10. import getStringFromTypedArray from "../Core/getStringFromTypedArray.js";
  11. import Matrix3 from "../Core/Matrix3.js";
  12. import Matrix4 from "../Core/Matrix4.js";
  13. import Quaternion from "../Core/Quaternion.js";
  14. import RequestType from "../Core/RequestType.js";
  15. import RuntimeError from "../Core/RuntimeError.js";
  16. import Transforms from "../Core/Transforms.js";
  17. import TranslationRotationScale from "../Core/TranslationRotationScale.js";
  18. import Pass from "../Renderer/Pass.js";
  19. import Axis from "./Axis.js";
  20. import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js";
  21. import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
  22. import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js";
  23. import I3dmParser from "./I3dmParser.js";
  24. import ModelInstanceCollection from "./ModelInstanceCollection.js";
  25. import ModelAnimationLoop from "./ModelAnimationLoop.js";
  26. /**
  27. * Represents the contents of a
  28. * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Instanced3DModel|Instanced 3D Model}
  29. * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset.
  30. * <p>
  31. * Implements the {@link Cesium3DTileContent} interface.
  32. * </p>
  33. *
  34. * @alias Instanced3DModel3DTileContent
  35. * @constructor
  36. *
  37. * @private
  38. */
  39. function Instanced3DModel3DTileContent(
  40. tileset,
  41. tile,
  42. resource,
  43. arrayBuffer,
  44. byteOffset
  45. ) {
  46. this._tileset = tileset;
  47. this._tile = tile;
  48. this._resource = resource;
  49. this._modelInstanceCollection = undefined;
  50. this._metadata = undefined;
  51. this._batchTable = undefined;
  52. this._features = undefined;
  53. this.featurePropertiesDirty = false;
  54. this._group = undefined;
  55. initialize(this, arrayBuffer, byteOffset);
  56. }
  57. // This can be overridden for testing purposes
  58. Instanced3DModel3DTileContent._deprecationWarning = deprecationWarning;
  59. Object.defineProperties(Instanced3DModel3DTileContent.prototype, {
  60. featuresLength: {
  61. get: function () {
  62. return this._batchTable.featuresLength;
  63. },
  64. },
  65. pointsLength: {
  66. get: function () {
  67. return 0;
  68. },
  69. },
  70. trianglesLength: {
  71. get: function () {
  72. const model = this._modelInstanceCollection._model;
  73. if (defined(model)) {
  74. return model.trianglesLength;
  75. }
  76. return 0;
  77. },
  78. },
  79. geometryByteLength: {
  80. get: function () {
  81. const model = this._modelInstanceCollection._model;
  82. if (defined(model)) {
  83. return model.geometryByteLength;
  84. }
  85. return 0;
  86. },
  87. },
  88. texturesByteLength: {
  89. get: function () {
  90. const model = this._modelInstanceCollection._model;
  91. if (defined(model)) {
  92. return model.texturesByteLength;
  93. }
  94. return 0;
  95. },
  96. },
  97. batchTableByteLength: {
  98. get: function () {
  99. return this._batchTable.memorySizeInBytes;
  100. },
  101. },
  102. innerContents: {
  103. get: function () {
  104. return undefined;
  105. },
  106. },
  107. readyPromise: {
  108. get: function () {
  109. return this._modelInstanceCollection.readyPromise;
  110. },
  111. },
  112. tileset: {
  113. get: function () {
  114. return this._tileset;
  115. },
  116. },
  117. tile: {
  118. get: function () {
  119. return this._tile;
  120. },
  121. },
  122. url: {
  123. get: function () {
  124. return this._resource.getUrlComponent(true);
  125. },
  126. },
  127. metadata: {
  128. get: function () {
  129. return this._metadata;
  130. },
  131. set: function (value) {
  132. this._metadata = value;
  133. },
  134. },
  135. batchTable: {
  136. get: function () {
  137. return this._batchTable;
  138. },
  139. },
  140. group: {
  141. get: function () {
  142. return this._group;
  143. },
  144. set: function (value) {
  145. this._group = value;
  146. },
  147. },
  148. });
  149. function getPickIdCallback(content) {
  150. return function () {
  151. return content._batchTable.getPickId();
  152. };
  153. }
  154. const propertyScratch1 = new Array(4);
  155. const propertyScratch2 = new Array(4);
  156. function initialize(content, arrayBuffer, byteOffset) {
  157. const i3dm = I3dmParser.parse(arrayBuffer, byteOffset);
  158. const gltfFormat = i3dm.gltfFormat;
  159. const gltfView = i3dm.gltf;
  160. const featureTableJson = i3dm.featureTableJson;
  161. const featureTableBinary = i3dm.featureTableBinary;
  162. const batchTableJson = i3dm.batchTableJson;
  163. const batchTableBinary = i3dm.batchTableBinary;
  164. const featureTable = new Cesium3DTileFeatureTable(
  165. featureTableJson,
  166. featureTableBinary
  167. );
  168. const instancesLength = featureTable.getGlobalProperty("INSTANCES_LENGTH");
  169. featureTable.featuresLength = instancesLength;
  170. if (!defined(instancesLength)) {
  171. throw new RuntimeError(
  172. "Feature table global property: INSTANCES_LENGTH must be defined"
  173. );
  174. }
  175. content._batchTable = new Cesium3DTileBatchTable(
  176. content,
  177. instancesLength,
  178. batchTableJson,
  179. batchTableBinary
  180. );
  181. const tileset = content._tileset;
  182. // Create model instance collection
  183. const collectionOptions = {
  184. instances: new Array(instancesLength),
  185. batchTable: content._batchTable,
  186. cull: false, // Already culled by 3D Tiles
  187. url: undefined,
  188. requestType: RequestType.TILES3D,
  189. gltf: undefined,
  190. basePath: undefined,
  191. incrementallyLoadTextures: false,
  192. upAxis: tileset._gltfUpAxis,
  193. forwardAxis: Axis.X,
  194. opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions during the 3D Tiles pass
  195. pickIdLoaded: getPickIdCallback(content),
  196. imageBasedLighting: tileset.imageBasedLighting,
  197. specularEnvironmentMaps: tileset.specularEnvironmentMaps,
  198. backFaceCulling: tileset.backFaceCulling,
  199. showOutline: tileset.showOutline,
  200. showCreditsOnScreen: tileset.showCreditsOnScreen,
  201. };
  202. if (gltfFormat === 0) {
  203. let gltfUrl = getStringFromTypedArray(gltfView);
  204. // We need to remove padding from the end of the model URL in case this tile was part of a composite tile.
  205. // This removes all white space and null characters from the end of the string.
  206. gltfUrl = gltfUrl.replace(/[\s\0]+$/, "");
  207. collectionOptions.url = content._resource.getDerivedResource({
  208. url: gltfUrl,
  209. });
  210. } else {
  211. collectionOptions.gltf = gltfView;
  212. collectionOptions.basePath = content._resource.clone();
  213. }
  214. const eastNorthUp = featureTable.getGlobalProperty("EAST_NORTH_UP");
  215. let rtcCenter;
  216. const rtcCenterArray = featureTable.getGlobalProperty(
  217. "RTC_CENTER",
  218. ComponentDatatype.FLOAT,
  219. 3
  220. );
  221. if (defined(rtcCenterArray)) {
  222. rtcCenter = Cartesian3.unpack(rtcCenterArray);
  223. }
  224. const instances = collectionOptions.instances;
  225. const instancePosition = new Cartesian3();
  226. const instancePositionArray = new Array(3);
  227. const instanceNormalRight = new Cartesian3();
  228. const instanceNormalUp = new Cartesian3();
  229. const instanceNormalForward = new Cartesian3();
  230. const instanceRotation = new Matrix3();
  231. const instanceQuaternion = new Quaternion();
  232. let instanceScale = new Cartesian3();
  233. const instanceTranslationRotationScale = new TranslationRotationScale();
  234. const instanceTransform = new Matrix4();
  235. for (let i = 0; i < instancesLength; i++) {
  236. // Get the instance position
  237. let position = featureTable.getProperty(
  238. "POSITION",
  239. ComponentDatatype.FLOAT,
  240. 3,
  241. i,
  242. propertyScratch1
  243. );
  244. if (!defined(position)) {
  245. position = instancePositionArray;
  246. const positionQuantized = featureTable.getProperty(
  247. "POSITION_QUANTIZED",
  248. ComponentDatatype.UNSIGNED_SHORT,
  249. 3,
  250. i,
  251. propertyScratch1
  252. );
  253. if (!defined(positionQuantized)) {
  254. throw new RuntimeError(
  255. "Either POSITION or POSITION_QUANTIZED must be defined for each instance."
  256. );
  257. }
  258. const quantizedVolumeOffset = featureTable.getGlobalProperty(
  259. "QUANTIZED_VOLUME_OFFSET",
  260. ComponentDatatype.FLOAT,
  261. 3
  262. );
  263. if (!defined(quantizedVolumeOffset)) {
  264. throw new RuntimeError(
  265. "Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions."
  266. );
  267. }
  268. const quantizedVolumeScale = featureTable.getGlobalProperty(
  269. "QUANTIZED_VOLUME_SCALE",
  270. ComponentDatatype.FLOAT,
  271. 3
  272. );
  273. if (!defined(quantizedVolumeScale)) {
  274. throw new RuntimeError(
  275. "Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions."
  276. );
  277. }
  278. for (let j = 0; j < 3; j++) {
  279. position[j] =
  280. (positionQuantized[j] / 65535.0) * quantizedVolumeScale[j] +
  281. quantizedVolumeOffset[j];
  282. }
  283. }
  284. Cartesian3.unpack(position, 0, instancePosition);
  285. if (defined(rtcCenter)) {
  286. Cartesian3.add(instancePosition, rtcCenter, instancePosition);
  287. }
  288. instanceTranslationRotationScale.translation = instancePosition;
  289. // Get the instance rotation
  290. const normalUp = featureTable.getProperty(
  291. "NORMAL_UP",
  292. ComponentDatatype.FLOAT,
  293. 3,
  294. i,
  295. propertyScratch1
  296. );
  297. const normalRight = featureTable.getProperty(
  298. "NORMAL_RIGHT",
  299. ComponentDatatype.FLOAT,
  300. 3,
  301. i,
  302. propertyScratch2
  303. );
  304. let hasCustomOrientation = false;
  305. if (defined(normalUp)) {
  306. if (!defined(normalRight)) {
  307. throw new RuntimeError(
  308. "To define a custom orientation, both NORMAL_UP and NORMAL_RIGHT must be defined."
  309. );
  310. }
  311. Cartesian3.unpack(normalUp, 0, instanceNormalUp);
  312. Cartesian3.unpack(normalRight, 0, instanceNormalRight);
  313. hasCustomOrientation = true;
  314. } else {
  315. const octNormalUp = featureTable.getProperty(
  316. "NORMAL_UP_OCT32P",
  317. ComponentDatatype.UNSIGNED_SHORT,
  318. 2,
  319. i,
  320. propertyScratch1
  321. );
  322. const octNormalRight = featureTable.getProperty(
  323. "NORMAL_RIGHT_OCT32P",
  324. ComponentDatatype.UNSIGNED_SHORT,
  325. 2,
  326. i,
  327. propertyScratch2
  328. );
  329. if (defined(octNormalUp)) {
  330. if (!defined(octNormalRight)) {
  331. throw new RuntimeError(
  332. "To define a custom orientation with oct-encoded vectors, both NORMAL_UP_OCT32P and NORMAL_RIGHT_OCT32P must be defined."
  333. );
  334. }
  335. AttributeCompression.octDecodeInRange(
  336. octNormalUp[0],
  337. octNormalUp[1],
  338. 65535,
  339. instanceNormalUp
  340. );
  341. AttributeCompression.octDecodeInRange(
  342. octNormalRight[0],
  343. octNormalRight[1],
  344. 65535,
  345. instanceNormalRight
  346. );
  347. hasCustomOrientation = true;
  348. } else if (eastNorthUp) {
  349. Transforms.eastNorthUpToFixedFrame(
  350. instancePosition,
  351. Ellipsoid.WGS84,
  352. instanceTransform
  353. );
  354. Matrix4.getMatrix3(instanceTransform, instanceRotation);
  355. } else {
  356. Matrix3.clone(Matrix3.IDENTITY, instanceRotation);
  357. }
  358. }
  359. if (hasCustomOrientation) {
  360. Cartesian3.cross(
  361. instanceNormalRight,
  362. instanceNormalUp,
  363. instanceNormalForward
  364. );
  365. Cartesian3.normalize(instanceNormalForward, instanceNormalForward);
  366. Matrix3.setColumn(
  367. instanceRotation,
  368. 0,
  369. instanceNormalRight,
  370. instanceRotation
  371. );
  372. Matrix3.setColumn(
  373. instanceRotation,
  374. 1,
  375. instanceNormalUp,
  376. instanceRotation
  377. );
  378. Matrix3.setColumn(
  379. instanceRotation,
  380. 2,
  381. instanceNormalForward,
  382. instanceRotation
  383. );
  384. }
  385. Quaternion.fromRotationMatrix(instanceRotation, instanceQuaternion);
  386. instanceTranslationRotationScale.rotation = instanceQuaternion;
  387. // Get the instance scale
  388. instanceScale = Cartesian3.fromElements(1.0, 1.0, 1.0, instanceScale);
  389. const scale = featureTable.getProperty(
  390. "SCALE",
  391. ComponentDatatype.FLOAT,
  392. 1,
  393. i
  394. );
  395. if (defined(scale)) {
  396. Cartesian3.multiplyByScalar(instanceScale, scale, instanceScale);
  397. }
  398. const nonUniformScale = featureTable.getProperty(
  399. "SCALE_NON_UNIFORM",
  400. ComponentDatatype.FLOAT,
  401. 3,
  402. i,
  403. propertyScratch1
  404. );
  405. if (defined(nonUniformScale)) {
  406. instanceScale.x *= nonUniformScale[0];
  407. instanceScale.y *= nonUniformScale[1];
  408. instanceScale.z *= nonUniformScale[2];
  409. }
  410. instanceTranslationRotationScale.scale = instanceScale;
  411. // Get the batchId
  412. let batchId = featureTable.getProperty(
  413. "BATCH_ID",
  414. ComponentDatatype.UNSIGNED_SHORT,
  415. 1,
  416. i
  417. );
  418. if (!defined(batchId)) {
  419. // If BATCH_ID semantic is undefined, batchId is just the instance number
  420. batchId = i;
  421. }
  422. // Create the model matrix and the instance
  423. Matrix4.fromTranslationRotationScale(
  424. instanceTranslationRotationScale,
  425. instanceTransform
  426. );
  427. const modelMatrix = instanceTransform.clone();
  428. instances[i] = {
  429. modelMatrix: modelMatrix,
  430. batchId: batchId,
  431. };
  432. }
  433. content._modelInstanceCollection = new ModelInstanceCollection(
  434. collectionOptions
  435. );
  436. content._modelInstanceCollection.readyPromise
  437. .catch(function () {
  438. // Any readyPromise failure is handled in modelInstanceCollection
  439. })
  440. .then(function (collection) {
  441. if (content._modelInstanceCollection.ready) {
  442. collection.activeAnimations.addAll({
  443. loop: ModelAnimationLoop.REPEAT,
  444. });
  445. }
  446. });
  447. }
  448. function createFeatures(content) {
  449. const featuresLength = content.featuresLength;
  450. if (!defined(content._features) && featuresLength > 0) {
  451. const features = new Array(featuresLength);
  452. for (let i = 0; i < featuresLength; ++i) {
  453. features[i] = new Cesium3DTileFeature(content, i);
  454. }
  455. content._features = features;
  456. }
  457. }
  458. Instanced3DModel3DTileContent.prototype.hasProperty = function (batchId, name) {
  459. return this._batchTable.hasProperty(batchId, name);
  460. };
  461. Instanced3DModel3DTileContent.prototype.getFeature = function (batchId) {
  462. const featuresLength = this.featuresLength;
  463. //>>includeStart('debug', pragmas.debug);
  464. if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
  465. throw new DeveloperError(
  466. `batchId is required and between zero and featuresLength - 1 (${
  467. featuresLength - 1
  468. }).`
  469. );
  470. }
  471. //>>includeEnd('debug');
  472. createFeatures(this);
  473. return this._features[batchId];
  474. };
  475. Instanced3DModel3DTileContent.prototype.applyDebugSettings = function (
  476. enabled,
  477. color
  478. ) {
  479. color = enabled ? color : Color.WHITE;
  480. this._batchTable.setAllColor(color);
  481. };
  482. Instanced3DModel3DTileContent.prototype.applyStyle = function (style) {
  483. this._batchTable.applyStyle(style);
  484. };
  485. Instanced3DModel3DTileContent.prototype.update = function (
  486. tileset,
  487. frameState
  488. ) {
  489. const commandStart = frameState.commandList.length;
  490. // In the PROCESSING state we may be calling update() to move forward
  491. // the content's resource loading. In the READY state, it will
  492. // actually generate commands.
  493. this._batchTable.update(tileset, frameState);
  494. this._modelInstanceCollection.modelMatrix = this._tile.computedTransform;
  495. this._modelInstanceCollection.shadows = this._tileset.shadows;
  496. this._modelInstanceCollection.lightColor = this._tileset.lightColor;
  497. this._modelInstanceCollection.imageBasedLighting = this._tileset.imageBasedLighting;
  498. this._modelInstanceCollection.backFaceCulling = this._tileset.backFaceCulling;
  499. this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe;
  500. this._modelInstanceCollection.showCreditsOnScreen = this._tileset.showCreditsOnScreen;
  501. this._modelInstanceCollection.splitDirection = this._tileset.splitDirection;
  502. const model = this._modelInstanceCollection._model;
  503. if (defined(model)) {
  504. // Update for clipping planes
  505. const tilesetClippingPlanes = this._tileset.clippingPlanes;
  506. model.referenceMatrix = this._tileset.clippingPlanesOriginMatrix;
  507. if (defined(tilesetClippingPlanes) && this._tile.clippingPlanesDirty) {
  508. // Dereference the clipping planes from the model if they are irrelevant - saves on shading
  509. // Link/Dereference directly to avoid ownership checks.
  510. model._clippingPlanes =
  511. tilesetClippingPlanes.enabled && this._tile._isClipped
  512. ? tilesetClippingPlanes
  513. : undefined;
  514. }
  515. // If the model references a different ClippingPlaneCollection due to the tileset's collection being replaced with a
  516. // ClippingPlaneCollection that gives this tile the same clipping status, update the model to use the new ClippingPlaneCollection.
  517. if (
  518. defined(tilesetClippingPlanes) &&
  519. defined(model._clippingPlanes) &&
  520. model._clippingPlanes !== tilesetClippingPlanes
  521. ) {
  522. model._clippingPlanes = tilesetClippingPlanes;
  523. }
  524. }
  525. this._modelInstanceCollection.update(frameState);
  526. // If any commands were pushed, add derived commands
  527. const commandEnd = frameState.commandList.length;
  528. if (
  529. commandStart < commandEnd &&
  530. (frameState.passes.render || frameState.passes.pick)
  531. ) {
  532. this._batchTable.addDerivedCommands(frameState, commandStart, false);
  533. }
  534. };
  535. Instanced3DModel3DTileContent.prototype.isDestroyed = function () {
  536. return false;
  537. };
  538. Instanced3DModel3DTileContent.prototype.destroy = function () {
  539. this._modelInstanceCollection =
  540. this._modelInstanceCollection && this._modelInstanceCollection.destroy();
  541. this._batchTable = this._batchTable && this._batchTable.destroy();
  542. return destroyObject(this);
  543. };
  544. export default Instanced3DModel3DTileContent;