Batched3DModel3DTileContent.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import Cartesian3 from "../Core/Cartesian3.js";
  2. import Color from "../Core/Color.js";
  3. import ComponentDatatype from "../Core/ComponentDatatype.js";
  4. import defined from "../Core/defined.js";
  5. import deprecationWarning from "../Core/deprecationWarning.js";
  6. import destroyObject from "../Core/destroyObject.js";
  7. import DeveloperError from "../Core/DeveloperError.js";
  8. import Matrix4 from "../Core/Matrix4.js";
  9. import RequestType from "../Core/RequestType.js";
  10. import Pass from "../Renderer/Pass.js";
  11. import Axis from "./Axis.js";
  12. import B3dmParser from "./B3dmParser.js";
  13. import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js";
  14. import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
  15. import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js";
  16. import ClassificationModel from "./ClassificationModel.js";
  17. import Model from "./Model.js";
  18. import ModelAnimationLoop from "./ModelAnimationLoop.js";
  19. import ModelUtility from "./ModelUtility.js";
  20. /**
  21. * Represents the contents of a
  22. * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}
  23. * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset.
  24. * <p>
  25. * Implements the {@link Cesium3DTileContent} interface.
  26. * </p>
  27. *
  28. * @alias Batched3DModel3DTileContent
  29. * @constructor
  30. *
  31. * @private
  32. */
  33. function Batched3DModel3DTileContent(
  34. tileset,
  35. tile,
  36. resource,
  37. arrayBuffer,
  38. byteOffset
  39. ) {
  40. this._tileset = tileset;
  41. this._tile = tile;
  42. this._resource = resource;
  43. this._model = undefined;
  44. this._batchTable = undefined;
  45. this._features = undefined;
  46. this._classificationType = tileset.vectorClassificationOnly
  47. ? undefined
  48. : tileset.classificationType;
  49. this._metadata = undefined;
  50. // Populate from gltf when available
  51. this._batchIdAttributeName = undefined;
  52. this._diffuseAttributeOrUniformName = {};
  53. this._rtcCenterTransform = undefined;
  54. this._contentModelMatrix = undefined;
  55. this.featurePropertiesDirty = false;
  56. this._group = undefined;
  57. initialize(this, arrayBuffer, byteOffset);
  58. }
  59. // This can be overridden for testing purposes
  60. Batched3DModel3DTileContent._deprecationWarning = deprecationWarning;
  61. Object.defineProperties(Batched3DModel3DTileContent.prototype, {
  62. featuresLength: {
  63. get: function () {
  64. return this.batchTable.featuresLength;
  65. },
  66. },
  67. pointsLength: {
  68. get: function () {
  69. return this._model.pointsLength;
  70. },
  71. },
  72. trianglesLength: {
  73. get: function () {
  74. return this._model.trianglesLength;
  75. },
  76. },
  77. geometryByteLength: {
  78. get: function () {
  79. return this._model.geometryByteLength;
  80. },
  81. },
  82. texturesByteLength: {
  83. get: function () {
  84. return this._model.texturesByteLength;
  85. },
  86. },
  87. batchTableByteLength: {
  88. get: function () {
  89. return this.batchTable.memorySizeInBytes;
  90. },
  91. },
  92. innerContents: {
  93. get: function () {
  94. return undefined;
  95. },
  96. },
  97. readyPromise: {
  98. get: function () {
  99. return this._model.readyPromise;
  100. },
  101. },
  102. tileset: {
  103. get: function () {
  104. return this._tileset;
  105. },
  106. },
  107. tile: {
  108. get: function () {
  109. return this._tile;
  110. },
  111. },
  112. url: {
  113. get: function () {
  114. return this._resource.getUrlComponent(true);
  115. },
  116. },
  117. metadata: {
  118. get: function () {
  119. return this._metadata;
  120. },
  121. set: function (value) {
  122. this._metadata = value;
  123. },
  124. },
  125. batchTable: {
  126. get: function () {
  127. return this._batchTable;
  128. },
  129. },
  130. group: {
  131. get: function () {
  132. return this._group;
  133. },
  134. set: function (value) {
  135. this._group = value;
  136. },
  137. },
  138. });
  139. function getBatchIdAttributeName(gltf) {
  140. let batchIdAttributeName = ModelUtility.getAttributeOrUniformBySemantic(
  141. gltf,
  142. "_BATCHID"
  143. );
  144. if (!defined(batchIdAttributeName)) {
  145. batchIdAttributeName = ModelUtility.getAttributeOrUniformBySemantic(
  146. gltf,
  147. "BATCHID"
  148. );
  149. if (defined(batchIdAttributeName)) {
  150. Batched3DModel3DTileContent._deprecationWarning(
  151. "b3dm-legacy-batchid",
  152. "The glTF in this b3dm uses the semantic `BATCHID`. Application-specific semantics should be prefixed with an underscore: `_BATCHID`."
  153. );
  154. }
  155. }
  156. return batchIdAttributeName;
  157. }
  158. function getVertexShaderCallback(content) {
  159. return function (vs, programId) {
  160. const batchTable = content._batchTable;
  161. const handleTranslucent = !defined(content._classificationType);
  162. const gltf = content._model.gltf;
  163. if (defined(gltf)) {
  164. content._batchIdAttributeName = getBatchIdAttributeName(gltf);
  165. content._diffuseAttributeOrUniformName[
  166. programId
  167. ] = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
  168. }
  169. const callback = batchTable.getVertexShaderCallback(
  170. handleTranslucent,
  171. content._batchIdAttributeName,
  172. content._diffuseAttributeOrUniformName[programId]
  173. );
  174. return defined(callback) ? callback(vs) : vs;
  175. };
  176. }
  177. function getFragmentShaderCallback(content) {
  178. return function (fs, programId) {
  179. const batchTable = content._batchTable;
  180. const handleTranslucent = !defined(content._classificationType);
  181. const gltf = content._model.gltf;
  182. if (defined(gltf)) {
  183. content._diffuseAttributeOrUniformName[
  184. programId
  185. ] = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
  186. }
  187. const callback = batchTable.getFragmentShaderCallback(
  188. handleTranslucent,
  189. content._diffuseAttributeOrUniformName[programId],
  190. false
  191. );
  192. return defined(callback) ? callback(fs) : fs;
  193. };
  194. }
  195. function getPickIdCallback(content) {
  196. return function () {
  197. return content._batchTable.getPickId();
  198. };
  199. }
  200. function getClassificationFragmentShaderCallback(content) {
  201. return function (fs) {
  202. const batchTable = content._batchTable;
  203. const callback = batchTable.getClassificationFragmentShaderCallback();
  204. return defined(callback) ? callback(fs) : fs;
  205. };
  206. }
  207. function createColorChangedCallback(content) {
  208. return function (batchId, color) {
  209. content._model.updateCommands(batchId, color);
  210. };
  211. }
  212. function initialize(content, arrayBuffer, byteOffset) {
  213. const tileset = content._tileset;
  214. const tile = content._tile;
  215. const resource = content._resource;
  216. const b3dm = B3dmParser.parse(arrayBuffer, byteOffset);
  217. let batchLength = b3dm.batchLength;
  218. const featureTableJson = b3dm.featureTableJson;
  219. const featureTableBinary = b3dm.featureTableBinary;
  220. const featureTable = new Cesium3DTileFeatureTable(
  221. featureTableJson,
  222. featureTableBinary
  223. );
  224. batchLength = featureTable.getGlobalProperty("BATCH_LENGTH");
  225. featureTable.featuresLength = batchLength;
  226. const batchTableJson = b3dm.batchTableJson;
  227. const batchTableBinary = b3dm.batchTableBinary;
  228. let colorChangedCallback;
  229. if (defined(content._classificationType)) {
  230. colorChangedCallback = createColorChangedCallback(content);
  231. }
  232. const batchTable = new Cesium3DTileBatchTable(
  233. content,
  234. batchLength,
  235. batchTableJson,
  236. batchTableBinary,
  237. colorChangedCallback
  238. );
  239. content._batchTable = batchTable;
  240. const gltfView = b3dm.gltf;
  241. const pickObject = {
  242. content: content,
  243. primitive: tileset,
  244. };
  245. content._rtcCenterTransform = Matrix4.IDENTITY;
  246. const rtcCenter = featureTable.getGlobalProperty(
  247. "RTC_CENTER",
  248. ComponentDatatype.FLOAT,
  249. 3
  250. );
  251. if (defined(rtcCenter)) {
  252. content._rtcCenterTransform = Matrix4.fromTranslation(
  253. Cartesian3.fromArray(rtcCenter)
  254. );
  255. }
  256. content._contentModelMatrix = Matrix4.multiply(
  257. tile.computedTransform,
  258. content._rtcCenterTransform,
  259. new Matrix4()
  260. );
  261. if (!defined(content._classificationType)) {
  262. // PERFORMANCE_IDEA: patch the shader on demand, e.g., the first time show/color changes.
  263. // The pick shader still needs to be patched.
  264. content._model = new Model({
  265. gltf: gltfView,
  266. cull: false, // The model is already culled by 3D Tiles
  267. releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory
  268. opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass
  269. basePath: resource,
  270. requestType: RequestType.TILES3D,
  271. modelMatrix: content._contentModelMatrix,
  272. upAxis: tileset._gltfUpAxis,
  273. forwardAxis: Axis.X,
  274. shadows: tileset.shadows,
  275. debugWireframe: tileset.debugWireframe,
  276. incrementallyLoadTextures: false,
  277. vertexShaderLoaded: getVertexShaderCallback(content),
  278. fragmentShaderLoaded: getFragmentShaderCallback(content),
  279. uniformMapLoaded: batchTable.getUniformMapCallback(),
  280. pickIdLoaded: getPickIdCallback(content),
  281. addBatchIdToGeneratedShaders: batchLength > 0, // If the batch table has values in it, generated shaders will need a batchId attribute
  282. pickObject: pickObject,
  283. lightColor: tileset.lightColor,
  284. imageBasedLighting: tileset.imageBasedLighting,
  285. backFaceCulling: tileset.backFaceCulling,
  286. showOutline: tileset.showOutline,
  287. showCreditsOnScreen: tileset.showCreditsOnScreen,
  288. });
  289. content._model.readyPromise.then(function (model) {
  290. model.activeAnimations.addAll({
  291. loop: ModelAnimationLoop.REPEAT,
  292. });
  293. });
  294. } else {
  295. // This transcodes glTF to an internal representation for geometry so we can take advantage of the re-batching of vector data.
  296. // For a list of limitations on the input glTF, see the documentation for classificationType of Cesium3DTileset.
  297. content._model = new ClassificationModel({
  298. gltf: gltfView,
  299. cull: false, // The model is already culled by 3D Tiles
  300. basePath: resource,
  301. requestType: RequestType.TILES3D,
  302. modelMatrix: content._contentModelMatrix,
  303. upAxis: tileset._gltfUpAxis,
  304. forwardAxis: Axis.X,
  305. debugWireframe: tileset.debugWireframe,
  306. vertexShaderLoaded: getVertexShaderCallback(content),
  307. classificationShaderLoaded: getClassificationFragmentShaderCallback(
  308. content
  309. ),
  310. uniformMapLoaded: batchTable.getUniformMapCallback(),
  311. pickIdLoaded: getPickIdCallback(content),
  312. classificationType: content._classificationType,
  313. batchTable: batchTable,
  314. });
  315. }
  316. }
  317. function createFeatures(content) {
  318. const featuresLength = content.featuresLength;
  319. if (!defined(content._features) && featuresLength > 0) {
  320. const features = new Array(featuresLength);
  321. for (let i = 0; i < featuresLength; ++i) {
  322. features[i] = new Cesium3DTileFeature(content, i);
  323. }
  324. content._features = features;
  325. }
  326. }
  327. Batched3DModel3DTileContent.prototype.hasProperty = function (batchId, name) {
  328. return this._batchTable.hasProperty(batchId, name);
  329. };
  330. Batched3DModel3DTileContent.prototype.getFeature = function (batchId) {
  331. //>>includeStart('debug', pragmas.debug);
  332. const featuresLength = this.featuresLength;
  333. if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
  334. throw new DeveloperError(
  335. `batchId is required and between zero and featuresLength - 1 (${
  336. featuresLength - 1
  337. }).`
  338. );
  339. }
  340. //>>includeEnd('debug');
  341. createFeatures(this);
  342. return this._features[batchId];
  343. };
  344. Batched3DModel3DTileContent.prototype.applyDebugSettings = function (
  345. enabled,
  346. color
  347. ) {
  348. color = enabled ? color : Color.WHITE;
  349. if (this.featuresLength === 0) {
  350. this._model.color = color;
  351. } else {
  352. this._batchTable.setAllColor(color);
  353. }
  354. };
  355. Batched3DModel3DTileContent.prototype.applyStyle = function (style) {
  356. if (this.featuresLength === 0) {
  357. const hasColorStyle = defined(style) && defined(style.color);
  358. const hasShowStyle = defined(style) && defined(style.show);
  359. this._model.color = hasColorStyle
  360. ? style.color.evaluateColor(undefined, this._model.color)
  361. : Color.clone(Color.WHITE, this._model.color);
  362. this._model.show = hasShowStyle ? style.show.evaluate(undefined) : true;
  363. } else {
  364. this._batchTable.applyStyle(style);
  365. }
  366. };
  367. Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) {
  368. const commandStart = frameState.commandList.length;
  369. const model = this._model;
  370. const tile = this._tile;
  371. const batchTable = this._batchTable;
  372. // In the PROCESSING state we may be calling update() to move forward
  373. // the content's resource loading. In the READY state, it will
  374. // actually generate commands.
  375. batchTable.update(tileset, frameState);
  376. this._contentModelMatrix = Matrix4.multiply(
  377. tile.computedTransform,
  378. this._rtcCenterTransform,
  379. this._contentModelMatrix
  380. );
  381. model.modelMatrix = this._contentModelMatrix;
  382. model.shadows = tileset.shadows;
  383. model.lightColor = tileset.lightColor;
  384. model.imageBasedLighting = tileset.imageBasedLighting;
  385. model.backFaceCulling = tileset.backFaceCulling;
  386. model.debugWireframe = tileset.debugWireframe;
  387. model.showCreditsOnScreen = tileset.showCreditsOnScreen;
  388. model.splitDirection = tileset.splitDirection;
  389. // Update clipping planes
  390. const tilesetClippingPlanes = tileset.clippingPlanes;
  391. model.referenceMatrix = tileset.clippingPlanesOriginMatrix;
  392. if (defined(tilesetClippingPlanes) && tile.clippingPlanesDirty) {
  393. // Dereference the clipping planes from the model if they are irrelevant.
  394. // Link/Dereference directly to avoid ownership checks.
  395. // This will also trigger synchronous shader regeneration to remove or add the clipping plane and color blending code.
  396. model._clippingPlanes =
  397. tilesetClippingPlanes.enabled && tile._isClipped
  398. ? tilesetClippingPlanes
  399. : undefined;
  400. }
  401. // If the model references a different ClippingPlaneCollection due to the tileset's collection being replaced with a
  402. // ClippingPlaneCollection that gives this tile the same clipping status, update the model to use the new ClippingPlaneCollection.
  403. if (
  404. defined(tilesetClippingPlanes) &&
  405. defined(model._clippingPlanes) &&
  406. model._clippingPlanes !== tilesetClippingPlanes
  407. ) {
  408. model._clippingPlanes = tilesetClippingPlanes;
  409. }
  410. model.update(frameState);
  411. // If any commands were pushed, add derived commands
  412. const commandEnd = frameState.commandList.length;
  413. if (
  414. commandStart < commandEnd &&
  415. (frameState.passes.render || frameState.passes.pick) &&
  416. !defined(this._classificationType)
  417. ) {
  418. batchTable.addDerivedCommands(frameState, commandStart);
  419. }
  420. };
  421. Batched3DModel3DTileContent.prototype.isDestroyed = function () {
  422. return false;
  423. };
  424. Batched3DModel3DTileContent.prototype.destroy = function () {
  425. this._model = this._model && this._model.destroy();
  426. this._batchTable = this._batchTable && this._batchTable.destroy();
  427. return destroyObject(this);
  428. };
  429. export default Batched3DModel3DTileContent;