Instanced3DModel3DTileContent.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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 defaultValue from '../Core/defaultValue.js';
  6. import defined from '../Core/defined.js';
  7. import defineProperties from '../Core/defineProperties.js';
  8. import deprecationWarning from '../Core/deprecationWarning.js';
  9. import destroyObject from '../Core/destroyObject.js';
  10. import DeveloperError from '../Core/DeveloperError.js';
  11. import Ellipsoid from '../Core/Ellipsoid.js';
  12. import getStringFromTypedArray from '../Core/getStringFromTypedArray.js';
  13. import Matrix3 from '../Core/Matrix3.js';
  14. import Matrix4 from '../Core/Matrix4.js';
  15. import Quaternion from '../Core/Quaternion.js';
  16. import RequestType from '../Core/RequestType.js';
  17. import RuntimeError from '../Core/RuntimeError.js';
  18. import Transforms from '../Core/Transforms.js';
  19. import TranslationRotationScale from '../Core/TranslationRotationScale.js';
  20. import Pass from '../Renderer/Pass.js';
  21. import Axis from './Axis.js';
  22. import Cesium3DTileBatchTable from './Cesium3DTileBatchTable.js';
  23. import Cesium3DTileFeature from './Cesium3DTileFeature.js';
  24. import Cesium3DTileFeatureTable from './Cesium3DTileFeatureTable.js';
  25. import ModelInstanceCollection from './ModelInstanceCollection.js';
  26. /**
  27. * Represents the contents of a
  28. * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/TileFormats/Instanced3DModel|Instanced 3D Model}
  29. * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/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(tileset, tile, resource, arrayBuffer, byteOffset) {
  40. this._tileset = tileset;
  41. this._tile = tile;
  42. this._resource = resource;
  43. this._modelInstanceCollection = undefined;
  44. this._batchTable = undefined;
  45. this._features = undefined;
  46. this.featurePropertiesDirty = false;
  47. initialize(this, arrayBuffer, byteOffset);
  48. }
  49. // This can be overridden for testing purposes
  50. Instanced3DModel3DTileContent._deprecationWarning = deprecationWarning;
  51. defineProperties(Instanced3DModel3DTileContent.prototype, {
  52. featuresLength : {
  53. get : function() {
  54. return this._batchTable.featuresLength;
  55. }
  56. },
  57. pointsLength : {
  58. get : function() {
  59. return 0;
  60. }
  61. },
  62. trianglesLength : {
  63. get : function() {
  64. var model = this._modelInstanceCollection._model;
  65. if (defined(model)) {
  66. return model.trianglesLength;
  67. }
  68. return 0;
  69. }
  70. },
  71. geometryByteLength : {
  72. get : function() {
  73. var model = this._modelInstanceCollection._model;
  74. if (defined(model)) {
  75. return model.geometryByteLength;
  76. }
  77. return 0;
  78. }
  79. },
  80. texturesByteLength : {
  81. get : function() {
  82. var model = this._modelInstanceCollection._model;
  83. if (defined(model)) {
  84. return model.texturesByteLength;
  85. }
  86. return 0;
  87. }
  88. },
  89. batchTableByteLength : {
  90. get : function() {
  91. return this._batchTable.memorySizeInBytes;
  92. }
  93. },
  94. innerContents : {
  95. get : function() {
  96. return undefined;
  97. }
  98. },
  99. readyPromise : {
  100. get : function() {
  101. return this._modelInstanceCollection.readyPromise;
  102. }
  103. },
  104. tileset : {
  105. get : function() {
  106. return this._tileset;
  107. }
  108. },
  109. tile : {
  110. get : function() {
  111. return this._tile;
  112. }
  113. },
  114. url: {
  115. get: function() {
  116. return this._resource.getUrlComponent(true);
  117. }
  118. },
  119. batchTable : {
  120. get : function() {
  121. return this._batchTable;
  122. }
  123. }
  124. });
  125. function getPickIdCallback(content) {
  126. return function() {
  127. return content._batchTable.getPickId();
  128. };
  129. }
  130. var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  131. var propertyScratch1 = new Array(4);
  132. var propertyScratch2 = new Array(4);
  133. function initialize(content, arrayBuffer, byteOffset) {
  134. var byteStart = defaultValue(byteOffset, 0);
  135. byteOffset = byteStart;
  136. var uint8Array = new Uint8Array(arrayBuffer);
  137. var view = new DataView(arrayBuffer);
  138. byteOffset += sizeOfUint32; // Skip magic
  139. var version = view.getUint32(byteOffset, true);
  140. if (version !== 1) {
  141. throw new RuntimeError('Only Instanced 3D Model version 1 is supported. Version ' + version + ' is not.');
  142. }
  143. byteOffset += sizeOfUint32;
  144. var byteLength = view.getUint32(byteOffset, true);
  145. byteOffset += sizeOfUint32;
  146. var featureTableJsonByteLength = view.getUint32(byteOffset, true);
  147. if (featureTableJsonByteLength === 0) {
  148. throw new RuntimeError('featureTableJsonByteLength is zero, the feature table must be defined.');
  149. }
  150. byteOffset += sizeOfUint32;
  151. var featureTableBinaryByteLength = view.getUint32(byteOffset, true);
  152. byteOffset += sizeOfUint32;
  153. var batchTableJsonByteLength = view.getUint32(byteOffset, true);
  154. byteOffset += sizeOfUint32;
  155. var batchTableBinaryByteLength = view.getUint32(byteOffset, true);
  156. byteOffset += sizeOfUint32;
  157. var gltfFormat = view.getUint32(byteOffset, true);
  158. if (gltfFormat !== 1 && gltfFormat !== 0) {
  159. throw new RuntimeError('Only glTF format 0 (uri) or 1 (embedded) are supported. Format ' + gltfFormat + ' is not.');
  160. }
  161. byteOffset += sizeOfUint32;
  162. var featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength);
  163. var featureTableJson = JSON.parse(featureTableString);
  164. byteOffset += featureTableJsonByteLength;
  165. var featureTableBinary = new Uint8Array(arrayBuffer, byteOffset, featureTableBinaryByteLength);
  166. byteOffset += featureTableBinaryByteLength;
  167. var featureTable = new Cesium3DTileFeatureTable(featureTableJson, featureTableBinary);
  168. var instancesLength = featureTable.getGlobalProperty('INSTANCES_LENGTH');
  169. featureTable.featuresLength = instancesLength;
  170. if (!defined(instancesLength)) {
  171. throw new RuntimeError('Feature table global property: INSTANCES_LENGTH must be defined');
  172. }
  173. var batchTableJson;
  174. var batchTableBinary;
  175. if (batchTableJsonByteLength > 0) {
  176. var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength);
  177. batchTableJson = JSON.parse(batchTableString);
  178. byteOffset += batchTableJsonByteLength;
  179. if (batchTableBinaryByteLength > 0) {
  180. // Has a batch table binary
  181. batchTableBinary = new Uint8Array(arrayBuffer, byteOffset, batchTableBinaryByteLength);
  182. // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
  183. batchTableBinary = new Uint8Array(batchTableBinary);
  184. byteOffset += batchTableBinaryByteLength;
  185. }
  186. }
  187. content._batchTable = new Cesium3DTileBatchTable(content, instancesLength, batchTableJson, batchTableBinary);
  188. var gltfByteLength = byteStart + byteLength - byteOffset;
  189. if (gltfByteLength === 0) {
  190. throw new RuntimeError('glTF byte length is zero, i3dm must have a glTF to instance.');
  191. }
  192. var gltfView;
  193. if (byteOffset % 4 === 0) {
  194. gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength);
  195. } else {
  196. // Create a copy of the glb so that it is 4-byte aligned
  197. Instanced3DModel3DTileContent._deprecationWarning('i3dm-glb-unaligned', 'The embedded glb is not aligned to a 4-byte boundary.');
  198. gltfView = new Uint8Array(uint8Array.subarray(byteOffset, byteOffset + gltfByteLength));
  199. }
  200. var tileset = content._tileset;
  201. // Create model instance collection
  202. var collectionOptions = {
  203. instances : new Array(instancesLength),
  204. batchTable : content._batchTable,
  205. cull : false, // Already culled by 3D Tiles
  206. url : undefined,
  207. requestType : RequestType.TILES3D,
  208. gltf : undefined,
  209. basePath : undefined,
  210. incrementallyLoadTextures : false,
  211. upAxis : tileset._gltfUpAxis,
  212. forwardAxis : Axis.X,
  213. opaquePass : Pass.CESIUM_3D_TILE, // Draw opaque portions during the 3D Tiles pass
  214. pickIdLoaded : getPickIdCallback(content),
  215. imageBasedLightingFactor : tileset.imageBasedLightingFactor,
  216. lightColor : tileset.lightColor,
  217. luminanceAtZenith : tileset.luminanceAtZenith,
  218. sphericalHarmonicCoefficients : tileset.sphericalHarmonicCoefficients,
  219. specularEnvironmentMaps : tileset.specularEnvironmentMaps
  220. };
  221. if (gltfFormat === 0) {
  222. var gltfUrl = getStringFromTypedArray(gltfView);
  223. // We need to remove padding from the end of the model URL in case this tile was part of a composite tile.
  224. // This removes all white space and null characters from the end of the string.
  225. gltfUrl = gltfUrl.replace(/[\s\0]+$/, '');
  226. collectionOptions.url = content._resource.getDerivedResource({
  227. url: gltfUrl
  228. });
  229. } else {
  230. collectionOptions.gltf = gltfView;
  231. collectionOptions.basePath = content._resource.clone();
  232. }
  233. var eastNorthUp = featureTable.getGlobalProperty('EAST_NORTH_UP');
  234. var rtcCenter;
  235. var rtcCenterArray = featureTable.getGlobalProperty('RTC_CENTER', ComponentDatatype.FLOAT, 3);
  236. if (defined(rtcCenterArray)) {
  237. rtcCenter = Cartesian3.unpack(rtcCenterArray);
  238. }
  239. var instances = collectionOptions.instances;
  240. var instancePosition = new Cartesian3();
  241. var instancePositionArray = new Array(3);
  242. var instanceNormalRight = new Cartesian3();
  243. var instanceNormalUp = new Cartesian3();
  244. var instanceNormalForward = new Cartesian3();
  245. var instanceRotation = new Matrix3();
  246. var instanceQuaternion = new Quaternion();
  247. var instanceScale = new Cartesian3();
  248. var instanceTranslationRotationScale = new TranslationRotationScale();
  249. var instanceTransform = new Matrix4();
  250. for (var i = 0; i < instancesLength; i++) {
  251. // Get the instance position
  252. var position = featureTable.getProperty('POSITION', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
  253. if (!defined(position)) {
  254. position = instancePositionArray;
  255. var positionQuantized = featureTable.getProperty('POSITION_QUANTIZED', ComponentDatatype.UNSIGNED_SHORT, 3, i, propertyScratch1);
  256. if (!defined(positionQuantized)) {
  257. throw new RuntimeError('Either POSITION or POSITION_QUANTIZED must be defined for each instance.');
  258. }
  259. var quantizedVolumeOffset = featureTable.getGlobalProperty('QUANTIZED_VOLUME_OFFSET', ComponentDatatype.FLOAT, 3);
  260. if (!defined(quantizedVolumeOffset)) {
  261. throw new RuntimeError('Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions.');
  262. }
  263. var quantizedVolumeScale = featureTable.getGlobalProperty('QUANTIZED_VOLUME_SCALE', ComponentDatatype.FLOAT, 3);
  264. if (!defined(quantizedVolumeScale)) {
  265. throw new RuntimeError('Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions.');
  266. }
  267. for (var j = 0; j < 3; j++) {
  268. position[j] = (positionQuantized[j] / 65535.0 * quantizedVolumeScale[j]) + quantizedVolumeOffset[j];
  269. }
  270. }
  271. Cartesian3.unpack(position, 0, instancePosition);
  272. if (defined(rtcCenter)) {
  273. Cartesian3.add(instancePosition, rtcCenter, instancePosition);
  274. }
  275. instanceTranslationRotationScale.translation = instancePosition;
  276. // Get the instance rotation
  277. var normalUp = featureTable.getProperty('NORMAL_UP', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
  278. var normalRight = featureTable.getProperty('NORMAL_RIGHT', ComponentDatatype.FLOAT, 3, i, propertyScratch2);
  279. var hasCustomOrientation = false;
  280. if (defined(normalUp)) {
  281. if (!defined(normalRight)) {
  282. throw new RuntimeError('To define a custom orientation, both NORMAL_UP and NORMAL_RIGHT must be defined.');
  283. }
  284. Cartesian3.unpack(normalUp, 0, instanceNormalUp);
  285. Cartesian3.unpack(normalRight, 0, instanceNormalRight);
  286. hasCustomOrientation = true;
  287. } else {
  288. var octNormalUp = featureTable.getProperty('NORMAL_UP_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch1);
  289. var octNormalRight = featureTable.getProperty('NORMAL_RIGHT_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch2);
  290. if (defined(octNormalUp)) {
  291. if (!defined(octNormalRight)) {
  292. throw new RuntimeError('To define a custom orientation with oct-encoded vectors, both NORMAL_UP_OCT32P and NORMAL_RIGHT_OCT32P must be defined.');
  293. }
  294. AttributeCompression.octDecodeInRange(octNormalUp[0], octNormalUp[1], 65535, instanceNormalUp);
  295. AttributeCompression.octDecodeInRange(octNormalRight[0], octNormalRight[1], 65535, instanceNormalRight);
  296. hasCustomOrientation = true;
  297. } else if (eastNorthUp) {
  298. Transforms.eastNorthUpToFixedFrame(instancePosition, Ellipsoid.WGS84, instanceTransform);
  299. Matrix4.getMatrix3(instanceTransform, instanceRotation);
  300. } else {
  301. Matrix3.clone(Matrix3.IDENTITY, instanceRotation);
  302. }
  303. }
  304. if (hasCustomOrientation) {
  305. Cartesian3.cross(instanceNormalRight, instanceNormalUp, instanceNormalForward);
  306. Cartesian3.normalize(instanceNormalForward, instanceNormalForward);
  307. Matrix3.setColumn(instanceRotation, 0, instanceNormalRight, instanceRotation);
  308. Matrix3.setColumn(instanceRotation, 1, instanceNormalUp, instanceRotation);
  309. Matrix3.setColumn(instanceRotation, 2, instanceNormalForward, instanceRotation);
  310. }
  311. Quaternion.fromRotationMatrix(instanceRotation, instanceQuaternion);
  312. instanceTranslationRotationScale.rotation = instanceQuaternion;
  313. // Get the instance scale
  314. instanceScale = Cartesian3.fromElements(1.0, 1.0, 1.0, instanceScale);
  315. var scale = featureTable.getProperty('SCALE', ComponentDatatype.FLOAT, 1, i);
  316. if (defined(scale)) {
  317. Cartesian3.multiplyByScalar(instanceScale, scale, instanceScale);
  318. }
  319. var nonUniformScale = featureTable.getProperty('SCALE_NON_UNIFORM', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
  320. if (defined(nonUniformScale)) {
  321. instanceScale.x *= nonUniformScale[0];
  322. instanceScale.y *= nonUniformScale[1];
  323. instanceScale.z *= nonUniformScale[2];
  324. }
  325. instanceTranslationRotationScale.scale = instanceScale;
  326. // Get the batchId
  327. var batchId = featureTable.getProperty('BATCH_ID', ComponentDatatype.UNSIGNED_SHORT, 1, i);
  328. if (!defined(batchId)) {
  329. // If BATCH_ID semantic is undefined, batchId is just the instance number
  330. batchId = i;
  331. }
  332. // Create the model matrix and the instance
  333. Matrix4.fromTranslationRotationScale(instanceTranslationRotationScale, instanceTransform);
  334. var modelMatrix = instanceTransform.clone();
  335. instances[i] = {
  336. modelMatrix : modelMatrix,
  337. batchId : batchId
  338. };
  339. }
  340. content._modelInstanceCollection = new ModelInstanceCollection(collectionOptions);
  341. }
  342. function createFeatures(content) {
  343. var featuresLength = content.featuresLength;
  344. if (!defined(content._features) && (featuresLength > 0)) {
  345. var features = new Array(featuresLength);
  346. for (var i = 0; i < featuresLength; ++i) {
  347. features[i] = new Cesium3DTileFeature(content, i);
  348. }
  349. content._features = features;
  350. }
  351. }
  352. Instanced3DModel3DTileContent.prototype.hasProperty = function(batchId, name) {
  353. return this._batchTable.hasProperty(batchId, name);
  354. };
  355. Instanced3DModel3DTileContent.prototype.getFeature = function(batchId) {
  356. var featuresLength = this.featuresLength;
  357. //>>includeStart('debug', pragmas.debug);
  358. if (!defined(batchId) || (batchId < 0) || (batchId >= featuresLength)) {
  359. throw new DeveloperError('batchId is required and between zero and featuresLength - 1 (' + (featuresLength - 1) + ').');
  360. }
  361. //>>includeEnd('debug');
  362. createFeatures(this);
  363. return this._features[batchId];
  364. };
  365. Instanced3DModel3DTileContent.prototype.applyDebugSettings = function(enabled, color) {
  366. color = enabled ? color : Color.WHITE;
  367. this._batchTable.setAllColor(color);
  368. };
  369. Instanced3DModel3DTileContent.prototype.applyStyle = function(style) {
  370. this._batchTable.applyStyle(style);
  371. };
  372. Instanced3DModel3DTileContent.prototype.update = function(tileset, frameState) {
  373. var commandStart = frameState.commandList.length;
  374. // In the PROCESSING state we may be calling update() to move forward
  375. // the content's resource loading. In the READY state, it will
  376. // actually generate commands.
  377. this._batchTable.update(tileset, frameState);
  378. this._modelInstanceCollection.modelMatrix = this._tile.computedTransform;
  379. this._modelInstanceCollection.shadows = this._tileset.shadows;
  380. this._modelInstanceCollection.lightColor = this._tileset.lightColor;
  381. this._modelInstanceCollection.luminanceAtZenith = this._tileset.luminanceAtZenith;
  382. this._modelInstanceCollection.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients;
  383. this._modelInstanceCollection.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps;
  384. this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe;
  385. var model = this._modelInstanceCollection._model;
  386. if (defined(model)) {
  387. // Update for clipping planes
  388. var tilesetClippingPlanes = this._tileset.clippingPlanes;
  389. model.clippingPlanesOriginMatrix = this._tileset.clippingPlanesOriginMatrix;
  390. if (defined(tilesetClippingPlanes) && this._tile.clippingPlanesDirty) {
  391. // Dereference the clipping planes from the model if they are irrelevant - saves on shading
  392. // Link/Dereference directly to avoid ownership checks.
  393. model._clippingPlanes = (tilesetClippingPlanes.enabled && this._tile._isClipped) ? tilesetClippingPlanes : undefined;
  394. }
  395. // If the model references a different ClippingPlaneCollection due to the tileset's collection being replaced with a
  396. // ClippingPlaneCollection that gives this tile the same clipping status, update the model to use the new ClippingPlaneCollection.
  397. if (defined(tilesetClippingPlanes) && defined(model._clippingPlanes) && model._clippingPlanes !== tilesetClippingPlanes) {
  398. model._clippingPlanes = tilesetClippingPlanes;
  399. }
  400. }
  401. this._modelInstanceCollection.update(frameState);
  402. // If any commands were pushed, add derived commands
  403. var commandEnd = frameState.commandList.length;
  404. if ((commandStart < commandEnd) && (frameState.passes.render || frameState.passes.pick)) {
  405. this._batchTable.addDerivedCommands(frameState, commandStart, false);
  406. }
  407. };
  408. Instanced3DModel3DTileContent.prototype.isDestroyed = function() {
  409. return false;
  410. };
  411. Instanced3DModel3DTileContent.prototype.destroy = function() {
  412. this._modelInstanceCollection = this._modelInstanceCollection && this._modelInstanceCollection.destroy();
  413. this._batchTable = this._batchTable && this._batchTable.destroy();
  414. return destroyObject(this);
  415. };
  416. export default Instanced3DModel3DTileContent;