Model3DTileContent.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import Color from "../../Core/Color.js";
  2. import combine from "../../Core/combine.js";
  3. import defined from "../../Core/defined.js";
  4. import destroyObject from "../../Core/destroyObject.js";
  5. import deprecationWarning from "../../Core/deprecationWarning.js";
  6. import DeveloperError from "../../Core/DeveloperError.js";
  7. import Pass from "../../Renderer/Pass.js";
  8. import ModelAnimationLoop from "../ModelAnimationLoop.js";
  9. import Model from "./Model.js";
  10. /**
  11. * Represents the contents of a glTF, glb or
  12. * {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}
  13. * tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset.
  14. * <p>
  15. * Implements the {@link Cesium3DTileContent} interface.
  16. * </p>
  17. * This object is normally not instantiated directly, use {@link Model3DTileContent.fromGltf}, {@link Model3DTileContent.fromB3dm}, {@link Model3DTileContent.fromI3dm}, {@link Model3DTileContent.fromPnts}, or {@link Model3DTileContent.fromGeoJson}.
  18. *
  19. * @alias Model3DTileContent
  20. * @constructor
  21. * @private
  22. */
  23. function Model3DTileContent(tileset, tile, resource) {
  24. this._tileset = tileset;
  25. this._tile = tile;
  26. this._resource = resource;
  27. this._model = undefined;
  28. this._metadata = undefined;
  29. this._group = undefined;
  30. this._ready = false;
  31. this._resolveContent = undefined;
  32. this._readyPromise = undefined;
  33. }
  34. Object.defineProperties(Model3DTileContent.prototype, {
  35. featuresLength: {
  36. get: function () {
  37. const model = this._model;
  38. const featureTables = model.featureTables;
  39. const featureTableId = model.featureTableId;
  40. if (defined(featureTables) && defined(featureTables[featureTableId])) {
  41. return featureTables[featureTableId].featuresLength;
  42. }
  43. return 0;
  44. },
  45. },
  46. pointsLength: {
  47. get: function () {
  48. return this._model.statistics.pointsLength;
  49. },
  50. },
  51. trianglesLength: {
  52. get: function () {
  53. return this._model.statistics.trianglesLength;
  54. },
  55. },
  56. geometryByteLength: {
  57. get: function () {
  58. return this._model.statistics.geometryByteLength;
  59. },
  60. },
  61. texturesByteLength: {
  62. get: function () {
  63. return this._model.statistics.texturesByteLength;
  64. },
  65. },
  66. batchTableByteLength: {
  67. get: function () {
  68. const statistics = this._model.statistics;
  69. return (
  70. statistics.propertyTablesByteLength + statistics.batchTexturesByteLength
  71. );
  72. },
  73. },
  74. innerContents: {
  75. get: function () {
  76. return undefined;
  77. },
  78. },
  79. /**
  80. * Returns true when the tile's content is ready to render; otherwise false
  81. *
  82. * @memberof Model3DTileContent.prototype
  83. *
  84. * @type {boolean}
  85. * @readonly
  86. * @private
  87. */
  88. ready: {
  89. get: function () {
  90. return this._ready;
  91. },
  92. },
  93. /**
  94. * Gets the promise that will be resolved when the tile's content is ready to render.
  95. *
  96. * @memberof Model3DTileContent.prototype
  97. *
  98. * @type {Promise<Model3DTileContent>}
  99. * @readonly
  100. * @deprecated
  101. * @private
  102. */
  103. readyPromise: {
  104. get: function () {
  105. deprecationWarning(
  106. "Model3DTileContent.readyPromise",
  107. "Model3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Model3DTileContent.ready to return true instead."
  108. );
  109. return this._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. batchTable: {
  128. get: function () {
  129. const model = this._model;
  130. const featureTables = model.featureTables;
  131. const featureTableId = model.featureTableId;
  132. if (defined(featureTables) && defined(featureTables[featureTableId])) {
  133. return featureTables[featureTableId];
  134. }
  135. return undefined;
  136. },
  137. },
  138. metadata: {
  139. get: function () {
  140. return this._metadata;
  141. },
  142. set: function (value) {
  143. this._metadata = value;
  144. },
  145. },
  146. group: {
  147. get: function () {
  148. return this._group;
  149. },
  150. set: function (value) {
  151. this._group = value;
  152. },
  153. },
  154. });
  155. Model3DTileContent.prototype.getFeature = function (featureId) {
  156. const model = this._model;
  157. const featureTableId = model.featureTableId;
  158. //>>includeStart('debug', pragmas.debug);
  159. if (!defined(featureTableId)) {
  160. throw new DeveloperError(
  161. "No feature ID set is selected. Make sure Cesium3DTileset.featureIdLabel or Cesium3DTileset.instanceFeatureIdLabel is defined"
  162. );
  163. }
  164. //>>includeEnd('debug');
  165. const featureTable = model.featureTables[featureTableId];
  166. //>>includeStart('debug', pragmas.debug);
  167. if (!defined(featureTable)) {
  168. throw new DeveloperError(
  169. "No feature table found for the selected feature ID set"
  170. );
  171. }
  172. //>>includeEnd('debug');
  173. //>>includeStart('debug', pragmas.debug);
  174. const featuresLength = featureTable.featuresLength;
  175. if (!defined(featureId) || featureId < 0 || featureId >= featuresLength) {
  176. throw new DeveloperError(
  177. `featureId is required and must be between 0 and featuresLength - 1 (${
  178. featuresLength - 1
  179. }).`
  180. );
  181. }
  182. //>>includeEnd('debug');
  183. return featureTable.getFeature(featureId);
  184. };
  185. Model3DTileContent.prototype.hasProperty = function (featureId, name) {
  186. const model = this._model;
  187. const featureTableId = model.featureTableId;
  188. if (!defined(featureTableId)) {
  189. return false;
  190. }
  191. const featureTable = model.featureTables[featureTableId];
  192. return featureTable.hasProperty(featureId, name);
  193. };
  194. Model3DTileContent.prototype.applyDebugSettings = function (enabled, color) {
  195. color = enabled ? color : Color.WHITE;
  196. if (this.featuresLength === 0) {
  197. this._model.color = color;
  198. } else if (defined(this.batchTable)) {
  199. this.batchTable.setAllColor(color);
  200. }
  201. };
  202. Model3DTileContent.prototype.applyStyle = function (style) {
  203. // the setter will call model.applyStyle()
  204. this._model.style = style;
  205. };
  206. Model3DTileContent.prototype.update = function (tileset, frameState) {
  207. const model = this._model;
  208. const tile = this._tile;
  209. model.colorBlendAmount = tileset.colorBlendAmount;
  210. model.colorBlendMode = tileset.colorBlendMode;
  211. model.modelMatrix = tile.computedTransform;
  212. model.customShader = tileset.customShader;
  213. model.featureIdLabel = tileset.featureIdLabel;
  214. model.instanceFeatureIdLabel = tileset.instanceFeatureIdLabel;
  215. model.lightColor = tileset.lightColor;
  216. model.imageBasedLighting = tileset.imageBasedLighting;
  217. model.backFaceCulling = tileset.backFaceCulling;
  218. model.shadows = tileset.shadows;
  219. model.showCreditsOnScreen = tileset.showCreditsOnScreen;
  220. model.splitDirection = tileset.splitDirection;
  221. model.debugWireframe = tileset.debugWireframe;
  222. model.showOutline = tileset.showOutline;
  223. model.outlineColor = tileset.outlineColor;
  224. model.pointCloudShading = tileset.pointCloudShading;
  225. // Updating clipping planes requires more effort because of ownership checks
  226. const tilesetClippingPlanes = tileset.clippingPlanes;
  227. model.referenceMatrix = tileset.clippingPlanesOriginMatrix;
  228. if (defined(tilesetClippingPlanes) && tile.clippingPlanesDirty) {
  229. // Dereference the clipping planes from the model if they are irrelevant.
  230. model._clippingPlanes =
  231. tilesetClippingPlanes.enabled && tile._isClipped
  232. ? tilesetClippingPlanes
  233. : undefined;
  234. }
  235. // If the model references a different ClippingPlaneCollection from the tileset,
  236. // update the model to use the new ClippingPlaneCollection.
  237. if (
  238. defined(tilesetClippingPlanes) &&
  239. defined(model._clippingPlanes) &&
  240. model._clippingPlanes !== tilesetClippingPlanes
  241. ) {
  242. model._clippingPlanes = tilesetClippingPlanes;
  243. model._clippingPlanesState = 0;
  244. }
  245. model.update(frameState);
  246. if (!this._ready && model.ready) {
  247. // Animation can only be added once the model is ready
  248. model.activeAnimations.addAll({
  249. loop: ModelAnimationLoop.REPEAT,
  250. });
  251. this._ready = true;
  252. this._resolveContent = this._resolveContent && this._resolveContent(this);
  253. }
  254. };
  255. Model3DTileContent.prototype.isDestroyed = function () {
  256. return false;
  257. };
  258. Model3DTileContent.prototype.destroy = function () {
  259. this._model = this._model && this._model.destroy();
  260. return destroyObject(this);
  261. };
  262. Model3DTileContent.fromGltf = async function (tileset, tile, resource, gltf) {
  263. const content = new Model3DTileContent(tileset, tile, resource);
  264. const additionalOptions = {
  265. gltf: gltf,
  266. basePath: resource,
  267. };
  268. const modelOptions = makeModelOptions(
  269. tileset,
  270. tile,
  271. content,
  272. additionalOptions
  273. );
  274. const classificationType = tileset.vectorClassificationOnly
  275. ? undefined
  276. : tileset.classificationType;
  277. modelOptions.classificationType = classificationType;
  278. const model = await Model.fromGltfAsync(modelOptions);
  279. content._model = model;
  280. // This is for backwards compatibility. It can be removed once readyPromise is removed.
  281. content._readyPromise = new Promise((resolve) => {
  282. content._resolveContent = resolve;
  283. });
  284. return content;
  285. };
  286. Model3DTileContent.fromB3dm = async function (
  287. tileset,
  288. tile,
  289. resource,
  290. arrayBuffer,
  291. byteOffset
  292. ) {
  293. const content = new Model3DTileContent(tileset, tile, resource);
  294. const additionalOptions = {
  295. arrayBuffer: arrayBuffer,
  296. byteOffset: byteOffset,
  297. resource: resource,
  298. };
  299. const modelOptions = makeModelOptions(
  300. tileset,
  301. tile,
  302. content,
  303. additionalOptions
  304. );
  305. const classificationType = tileset.vectorClassificationOnly
  306. ? undefined
  307. : tileset.classificationType;
  308. modelOptions.classificationType = classificationType;
  309. const model = await Model.fromB3dm(modelOptions);
  310. content._model = model;
  311. // This is for backwards compatibility. It can be removed once readyPromise is removed.
  312. content._readyPromise = new Promise((resolve) => {
  313. content._resolveContent = resolve;
  314. });
  315. return content;
  316. };
  317. Model3DTileContent.fromI3dm = async function (
  318. tileset,
  319. tile,
  320. resource,
  321. arrayBuffer,
  322. byteOffset
  323. ) {
  324. const content = new Model3DTileContent(tileset, tile, resource);
  325. const additionalOptions = {
  326. arrayBuffer: arrayBuffer,
  327. byteOffset: byteOffset,
  328. resource: resource,
  329. };
  330. const modelOptions = makeModelOptions(
  331. tileset,
  332. tile,
  333. content,
  334. additionalOptions
  335. );
  336. const model = await Model.fromI3dm(modelOptions);
  337. content._model = model;
  338. // This is for backwards compatibility. It can be removed once readyPromise is removed.
  339. content._readyPromise = new Promise((resolve) => {
  340. content._resolveContent = resolve;
  341. });
  342. return content;
  343. };
  344. Model3DTileContent.fromPnts = async function (
  345. tileset,
  346. tile,
  347. resource,
  348. arrayBuffer,
  349. byteOffset
  350. ) {
  351. const content = new Model3DTileContent(tileset, tile, resource);
  352. const additionalOptions = {
  353. arrayBuffer: arrayBuffer,
  354. byteOffset: byteOffset,
  355. resource: resource,
  356. };
  357. const modelOptions = makeModelOptions(
  358. tileset,
  359. tile,
  360. content,
  361. additionalOptions
  362. );
  363. const model = await Model.fromPnts(modelOptions);
  364. content._model = model;
  365. // This is for backwards compatibility. It can be removed once readyPromise is removed.
  366. content._readyPromise = new Promise((resolve) => {
  367. content._resolveContent = resolve;
  368. });
  369. return content;
  370. };
  371. Model3DTileContent.fromGeoJson = async function (
  372. tileset,
  373. tile,
  374. resource,
  375. geoJson
  376. ) {
  377. const content = new Model3DTileContent(tileset, tile, resource);
  378. const additionalOptions = {
  379. geoJson: geoJson,
  380. resource: resource,
  381. };
  382. const modelOptions = makeModelOptions(
  383. tileset,
  384. tile,
  385. content,
  386. additionalOptions
  387. );
  388. const model = await Model.fromGeoJson(modelOptions);
  389. content._model = model;
  390. // This is for backwards compatibility. It can be removed once readyPromise is removed.
  391. content._readyPromise = new Promise((resolve) => {
  392. content._resolveContent = resolve;
  393. });
  394. return content;
  395. };
  396. function makeModelOptions(tileset, tile, content, additionalOptions) {
  397. const mainOptions = {
  398. cull: false, // The model is already culled by 3D Tiles
  399. releaseGltfJson: true, // Models are unique and will not benefit from caching so save memory
  400. opaquePass: Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass
  401. modelMatrix: tile.computedTransform,
  402. upAxis: tileset._modelUpAxis,
  403. forwardAxis: tileset._modelForwardAxis,
  404. incrementallyLoadTextures: false,
  405. customShader: tileset.customShader,
  406. content: content,
  407. colorBlendMode: tileset.colorBlendMode,
  408. colorBlendAmount: tileset.colorBlendAmount,
  409. lightColor: tileset.lightColor,
  410. imageBasedLighting: tileset.imageBasedLighting,
  411. featureIdLabel: tileset.featureIdLabel,
  412. instanceFeatureIdLabel: tileset.instanceFeatureIdLabel,
  413. pointCloudShading: tileset.pointCloudShading,
  414. clippingPlanes: tileset.clippingPlanes,
  415. backFaceCulling: tileset.backFaceCulling,
  416. shadows: tileset.shadows,
  417. showCreditsOnScreen: tileset.showCreditsOnScreen,
  418. splitDirection: tileset.splitDirection,
  419. enableDebugWireframe: tileset._enableDebugWireframe,
  420. debugWireframe: tileset.debugWireframe,
  421. projectTo2D: tileset._projectTo2D,
  422. enableShowOutline: tileset._enableShowOutline,
  423. showOutline: tileset.showOutline,
  424. outlineColor: tileset.outlineColor,
  425. };
  426. return combine(additionalOptions, mainOptions);
  427. }
  428. export default Model3DTileContent;