GroundPrimitive.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. import ApproximateTerrainHeights from "../Core/ApproximateTerrainHeights.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartographic from "../Core/Cartographic.js";
  5. import Check from "../Core/Check.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import deprecationWarning from "../Core/deprecationWarning.js";
  9. import destroyObject from "../Core/destroyObject.js";
  10. import DeveloperError from "../Core/DeveloperError.js";
  11. import GeometryInstance from "../Core/GeometryInstance.js";
  12. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  13. import Rectangle from "../Core/Rectangle.js";
  14. import TerrainExaggeration from "../Core/TerrainExaggeration.js";
  15. import ClassificationPrimitive from "./ClassificationPrimitive.js";
  16. import ClassificationType from "./ClassificationType.js";
  17. import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js";
  18. import SceneMode from "./SceneMode.js";
  19. import ShadowVolumeAppearance from "./ShadowVolumeAppearance.js";
  20. const GroundPrimitiveUniformMap = {
  21. u_globeMinimumAltitude: function () {
  22. return 55000.0;
  23. },
  24. };
  25. /**
  26. * A ground primitive represents geometry draped over terrain or 3D Tiles in the {@link Scene}.
  27. * <p>
  28. * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including
  29. * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement,
  30. * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix
  31. * and match most of them and add a new geometry or appearance independently of each other.
  32. * </p>
  33. * <p>
  34. * Support for the WEBGL_depth_texture extension is required to use GeometryInstances with different PerInstanceColors
  35. * or materials besides PerInstanceColorAppearance.
  36. * </p>
  37. * <p>
  38. * Textured GroundPrimitives were designed for notional patterns and are not meant for precisely mapping
  39. * textures to terrain - for that use case, use {@link SingleTileImageryProvider}.
  40. * </p>
  41. * <p>
  42. * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there
  43. * will be rendering artifacts for some viewing angles.
  44. * </p>
  45. * <p>
  46. * Valid geometries are {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}.
  47. * </p>
  48. *
  49. * @alias GroundPrimitive
  50. * @constructor
  51. *
  52. * @param {object} [options] Object with the following properties:
  53. * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render.
  54. * @param {Appearance} [options.appearance] The appearance used to render the primitive. Defaults to a flat PerInstanceColorAppearance when GeometryInstances have a color attribute.
  55. * @param {boolean} [options.show=true] Determines if this primitive will be shown.
  56. * @param {boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
  57. * @param {boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
  58. * @param {boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
  59. * @param {boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
  60. * @param {boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved.
  61. * @param {boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first.
  62. * @param {ClassificationType} [options.classificationType=ClassificationType.BOTH] Determines whether terrain, 3D Tiles or both will be classified.
  63. * @param {boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  64. * @param {boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be <code>true</code> on
  65. * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be <code>false</code>.
  66. *
  67. * @example
  68. * // Example 1: Create primitive with a single instance
  69. * const rectangleInstance = new Cesium.GeometryInstance({
  70. * geometry : new Cesium.RectangleGeometry({
  71. * rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
  72. * }),
  73. * id : 'rectangle',
  74. * attributes : {
  75. * color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
  76. * }
  77. * });
  78. * scene.primitives.add(new Cesium.GroundPrimitive({
  79. * geometryInstances : rectangleInstance
  80. * }));
  81. *
  82. * // Example 2: Batch instances
  83. * const color = new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5); // Both instances must have the same color.
  84. * const rectangleInstance = new Cesium.GeometryInstance({
  85. * geometry : new Cesium.RectangleGeometry({
  86. * rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
  87. * }),
  88. * id : 'rectangle',
  89. * attributes : {
  90. * color : color
  91. * }
  92. * });
  93. * const ellipseInstance = new Cesium.GeometryInstance({
  94. * geometry : new Cesium.EllipseGeometry({
  95. * center : Cesium.Cartesian3.fromDegrees(-105.0, 40.0),
  96. * semiMinorAxis : 300000.0,
  97. * semiMajorAxis : 400000.0
  98. * }),
  99. * id : 'ellipse',
  100. * attributes : {
  101. * color : color
  102. * }
  103. * });
  104. * scene.primitives.add(new Cesium.GroundPrimitive({
  105. * geometryInstances : [rectangleInstance, ellipseInstance]
  106. * }));
  107. *
  108. * @see Primitive
  109. * @see ClassificationPrimitive
  110. * @see GeometryInstance
  111. * @see Appearance
  112. */
  113. function GroundPrimitive(options) {
  114. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  115. let appearance = options.appearance;
  116. const geometryInstances = options.geometryInstances;
  117. if (!defined(appearance) && defined(geometryInstances)) {
  118. const geometryInstancesArray = Array.isArray(geometryInstances)
  119. ? geometryInstances
  120. : [geometryInstances];
  121. const geometryInstanceCount = geometryInstancesArray.length;
  122. for (let i = 0; i < geometryInstanceCount; i++) {
  123. const attributes = geometryInstancesArray[i].attributes;
  124. if (defined(attributes) && defined(attributes.color)) {
  125. appearance = new PerInstanceColorAppearance({
  126. flat: true,
  127. });
  128. break;
  129. }
  130. }
  131. }
  132. /**
  133. * The {@link Appearance} used to shade this primitive. Each geometry
  134. * instance is shaded with the same appearance. Some appearances, like
  135. * {@link PerInstanceColorAppearance} allow giving each instance unique
  136. * properties.
  137. *
  138. * @type Appearance
  139. *
  140. * @default undefined
  141. */
  142. this.appearance = appearance;
  143. /**
  144. * The geometry instances rendered with this primitive. This may
  145. * be <code>undefined</code> if <code>options.releaseGeometryInstances</code>
  146. * is <code>true</code> when the primitive is constructed.
  147. * <p>
  148. * Changing this property after the primitive is rendered has no effect.
  149. * </p>
  150. *
  151. * @readonly
  152. * @type {Array|GeometryInstance}
  153. *
  154. * @default undefined
  155. */
  156. this.geometryInstances = options.geometryInstances;
  157. /**
  158. * Determines if the primitive will be shown. This affects all geometry
  159. * instances in the primitive.
  160. *
  161. * @type {boolean}
  162. *
  163. * @default true
  164. */
  165. this.show = defaultValue(options.show, true);
  166. /**
  167. * Determines whether terrain, 3D Tiles or both will be classified.
  168. *
  169. * @type {ClassificationType}
  170. *
  171. * @default ClassificationType.BOTH
  172. */
  173. this.classificationType = defaultValue(
  174. options.classificationType,
  175. ClassificationType.BOTH
  176. );
  177. /**
  178. * This property is for debugging only; it is not for production use nor is it optimized.
  179. * <p>
  180. * Draws the bounding sphere for each draw command in the primitive.
  181. * </p>
  182. *
  183. * @type {boolean}
  184. *
  185. * @default false
  186. */
  187. this.debugShowBoundingVolume = defaultValue(
  188. options.debugShowBoundingVolume,
  189. false
  190. );
  191. /**
  192. * This property is for debugging only; it is not for production use nor is it optimized.
  193. * <p>
  194. * Draws the shadow volume for each geometry in the primitive.
  195. * </p>
  196. *
  197. * @type {boolean}
  198. *
  199. * @default false
  200. */
  201. this.debugShowShadowVolume = defaultValue(
  202. options.debugShowShadowVolume,
  203. false
  204. );
  205. this._boundingVolumes = [];
  206. this._boundingVolumes2D = [];
  207. this._ready = false;
  208. const groundPrimitive = this;
  209. // This is here for backwards compatibility. This promise wrapper can be removed once readyPromise is removed.
  210. this._readyPromise = new Promise((resolve, reject) => {
  211. groundPrimitive._completeLoad = () => {
  212. if (this._ready) {
  213. return;
  214. }
  215. this._ready = true;
  216. if (this.releaseGeometryInstances) {
  217. this.geometryInstances = undefined;
  218. }
  219. const error = this._error;
  220. if (!defined(error)) {
  221. resolve(this);
  222. } else {
  223. reject(error);
  224. }
  225. };
  226. });
  227. this._primitive = undefined;
  228. this._maxHeight = undefined;
  229. this._minHeight = undefined;
  230. this._maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  231. this._minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  232. this._boundingSpheresKeys = [];
  233. this._boundingSpheres = [];
  234. this._useFragmentCulling = false;
  235. // Used when inserting in an OrderedPrimitiveCollection
  236. this._zIndex = undefined;
  237. const that = this;
  238. this._classificationPrimitiveOptions = {
  239. geometryInstances: undefined,
  240. appearance: undefined,
  241. vertexCacheOptimize: defaultValue(options.vertexCacheOptimize, false),
  242. interleave: defaultValue(options.interleave, false),
  243. releaseGeometryInstances: defaultValue(
  244. options.releaseGeometryInstances,
  245. true
  246. ),
  247. allowPicking: defaultValue(options.allowPicking, true),
  248. asynchronous: defaultValue(options.asynchronous, true),
  249. compressVertices: defaultValue(options.compressVertices, true),
  250. _createBoundingVolumeFunction: undefined,
  251. _updateAndQueueCommandsFunction: undefined,
  252. _pickPrimitive: that,
  253. _extruded: true,
  254. _uniformMap: GroundPrimitiveUniformMap,
  255. };
  256. }
  257. Object.defineProperties(GroundPrimitive.prototype, {
  258. /**
  259. * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
  260. *
  261. * @memberof GroundPrimitive.prototype
  262. *
  263. * @type {boolean}
  264. * @readonly
  265. *
  266. * @default true
  267. */
  268. vertexCacheOptimize: {
  269. get: function () {
  270. return this._classificationPrimitiveOptions.vertexCacheOptimize;
  271. },
  272. },
  273. /**
  274. * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
  275. *
  276. * @memberof GroundPrimitive.prototype
  277. *
  278. * @type {boolean}
  279. * @readonly
  280. *
  281. * @default false
  282. */
  283. interleave: {
  284. get: function () {
  285. return this._classificationPrimitiveOptions.interleave;
  286. },
  287. },
  288. /**
  289. * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
  290. *
  291. * @memberof GroundPrimitive.prototype
  292. *
  293. * @type {boolean}
  294. * @readonly
  295. *
  296. * @default true
  297. */
  298. releaseGeometryInstances: {
  299. get: function () {
  300. return this._classificationPrimitiveOptions.releaseGeometryInstances;
  301. },
  302. },
  303. /**
  304. * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved.
  305. *
  306. * @memberof GroundPrimitive.prototype
  307. *
  308. * @type {boolean}
  309. * @readonly
  310. *
  311. * @default true
  312. */
  313. allowPicking: {
  314. get: function () {
  315. return this._classificationPrimitiveOptions.allowPicking;
  316. },
  317. },
  318. /**
  319. * Determines if the geometry instances will be created and batched on a web worker.
  320. *
  321. * @memberof GroundPrimitive.prototype
  322. *
  323. * @type {boolean}
  324. * @readonly
  325. *
  326. * @default true
  327. */
  328. asynchronous: {
  329. get: function () {
  330. return this._classificationPrimitiveOptions.asynchronous;
  331. },
  332. },
  333. /**
  334. * When <code>true</code>, geometry vertices are compressed, which will save memory.
  335. *
  336. * @memberof GroundPrimitive.prototype
  337. *
  338. * @type {boolean}
  339. * @readonly
  340. *
  341. * @default true
  342. */
  343. compressVertices: {
  344. get: function () {
  345. return this._classificationPrimitiveOptions.compressVertices;
  346. },
  347. },
  348. /**
  349. * Determines if the primitive is complete and ready to render. If this property is
  350. * true, the primitive will be rendered the next time that {@link GroundPrimitive#update}
  351. * is called.
  352. *
  353. * @memberof GroundPrimitive.prototype
  354. *
  355. * @type {boolean}
  356. * @readonly
  357. */
  358. ready: {
  359. get: function () {
  360. return this._ready;
  361. },
  362. },
  363. /**
  364. * Gets a promise that resolves when the primitive is ready to render.
  365. * @memberof GroundPrimitive.prototype
  366. * @type {Promise<GroundPrimitive>}
  367. * @readonly
  368. * @deprecated
  369. */
  370. readyPromise: {
  371. get: function () {
  372. deprecationWarning(
  373. "GroundPrimitive.readyPromise",
  374. "GroundPrimitive.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for GroundPrimitive.ready to return true instead."
  375. );
  376. return this._readyPromise;
  377. },
  378. },
  379. });
  380. /**
  381. * Determines if GroundPrimitive rendering is supported.
  382. *
  383. * @function
  384. * @param {Scene} scene The scene.
  385. * @returns {boolean} <code>true</code> if GroundPrimitives are supported; otherwise, returns <code>false</code>
  386. */
  387. GroundPrimitive.isSupported = ClassificationPrimitive.isSupported;
  388. function getComputeMaximumHeightFunction(primitive) {
  389. return function (granularity, ellipsoid) {
  390. const r = ellipsoid.maximumRadius;
  391. const delta = r / Math.cos(granularity * 0.5) - r;
  392. return primitive._maxHeight + delta;
  393. };
  394. }
  395. function getComputeMinimumHeightFunction(primitive) {
  396. return function (granularity, ellipsoid) {
  397. return primitive._minHeight;
  398. };
  399. }
  400. const scratchBVCartesianHigh = new Cartesian3();
  401. const scratchBVCartesianLow = new Cartesian3();
  402. const scratchBVCartesian = new Cartesian3();
  403. const scratchBVCartographic = new Cartographic();
  404. const scratchBVRectangle = new Rectangle();
  405. function getRectangle(frameState, geometry) {
  406. const ellipsoid = frameState.mapProjection.ellipsoid;
  407. if (
  408. !defined(geometry.attributes) ||
  409. !defined(geometry.attributes.position3DHigh)
  410. ) {
  411. if (defined(geometry.rectangle)) {
  412. return geometry.rectangle;
  413. }
  414. return undefined;
  415. }
  416. const highPositions = geometry.attributes.position3DHigh.values;
  417. const lowPositions = geometry.attributes.position3DLow.values;
  418. const length = highPositions.length;
  419. let minLat = Number.POSITIVE_INFINITY;
  420. let minLon = Number.POSITIVE_INFINITY;
  421. let maxLat = Number.NEGATIVE_INFINITY;
  422. let maxLon = Number.NEGATIVE_INFINITY;
  423. for (let i = 0; i < length; i += 3) {
  424. const highPosition = Cartesian3.unpack(
  425. highPositions,
  426. i,
  427. scratchBVCartesianHigh
  428. );
  429. const lowPosition = Cartesian3.unpack(
  430. lowPositions,
  431. i,
  432. scratchBVCartesianLow
  433. );
  434. const position = Cartesian3.add(
  435. highPosition,
  436. lowPosition,
  437. scratchBVCartesian
  438. );
  439. const cartographic = ellipsoid.cartesianToCartographic(
  440. position,
  441. scratchBVCartographic
  442. );
  443. const latitude = cartographic.latitude;
  444. const longitude = cartographic.longitude;
  445. minLat = Math.min(minLat, latitude);
  446. minLon = Math.min(minLon, longitude);
  447. maxLat = Math.max(maxLat, latitude);
  448. maxLon = Math.max(maxLon, longitude);
  449. }
  450. const rectangle = scratchBVRectangle;
  451. rectangle.north = maxLat;
  452. rectangle.south = minLat;
  453. rectangle.east = maxLon;
  454. rectangle.west = minLon;
  455. return rectangle;
  456. }
  457. function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
  458. const result = ApproximateTerrainHeights.getMinimumMaximumHeights(
  459. rectangle,
  460. ellipsoid
  461. );
  462. primitive._minTerrainHeight = result.minimumTerrainHeight;
  463. primitive._maxTerrainHeight = result.maximumTerrainHeight;
  464. }
  465. function createBoundingVolume(groundPrimitive, frameState, geometry) {
  466. const ellipsoid = frameState.mapProjection.ellipsoid;
  467. const rectangle = getRectangle(frameState, geometry);
  468. const obb = OrientedBoundingBox.fromRectangle(
  469. rectangle,
  470. groundPrimitive._minHeight,
  471. groundPrimitive._maxHeight,
  472. ellipsoid
  473. );
  474. groundPrimitive._boundingVolumes.push(obb);
  475. if (!frameState.scene3DOnly) {
  476. const projection = frameState.mapProjection;
  477. const boundingVolume = BoundingSphere.fromRectangleWithHeights2D(
  478. rectangle,
  479. projection,
  480. groundPrimitive._maxHeight,
  481. groundPrimitive._minHeight
  482. );
  483. Cartesian3.fromElements(
  484. boundingVolume.center.z,
  485. boundingVolume.center.x,
  486. boundingVolume.center.y,
  487. boundingVolume.center
  488. );
  489. groundPrimitive._boundingVolumes2D.push(boundingVolume);
  490. }
  491. }
  492. function boundingVolumeIndex(commandIndex, length) {
  493. return Math.floor((commandIndex % length) / 2);
  494. }
  495. function updateAndQueueRenderCommand(
  496. groundPrimitive,
  497. command,
  498. frameState,
  499. modelMatrix,
  500. cull,
  501. boundingVolume,
  502. debugShowBoundingVolume
  503. ) {
  504. // Use derived appearance command for 2D if needed
  505. const classificationPrimitive = groundPrimitive._primitive;
  506. if (
  507. frameState.mode !== SceneMode.SCENE3D &&
  508. command.shaderProgram === classificationPrimitive._spColor &&
  509. classificationPrimitive._needs2DShader
  510. ) {
  511. command = command.derivedCommands.appearance2D;
  512. }
  513. command.owner = groundPrimitive;
  514. command.modelMatrix = modelMatrix;
  515. command.boundingVolume = boundingVolume;
  516. command.cull = cull;
  517. command.debugShowBoundingVolume = debugShowBoundingVolume;
  518. frameState.commandList.push(command);
  519. }
  520. function updateAndQueuePickCommand(
  521. groundPrimitive,
  522. command,
  523. frameState,
  524. modelMatrix,
  525. cull,
  526. boundingVolume
  527. ) {
  528. // Use derived pick command for 2D if needed
  529. const classificationPrimitive = groundPrimitive._primitive;
  530. if (
  531. frameState.mode !== SceneMode.SCENE3D &&
  532. command.shaderProgram === classificationPrimitive._spPick &&
  533. classificationPrimitive._needs2DShader
  534. ) {
  535. command = command.derivedCommands.pick2D;
  536. }
  537. command.owner = groundPrimitive;
  538. command.modelMatrix = modelMatrix;
  539. command.boundingVolume = boundingVolume;
  540. command.cull = cull;
  541. frameState.commandList.push(command);
  542. }
  543. function updateAndQueueCommands(
  544. groundPrimitive,
  545. frameState,
  546. colorCommands,
  547. pickCommands,
  548. modelMatrix,
  549. cull,
  550. debugShowBoundingVolume,
  551. twoPasses
  552. ) {
  553. let boundingVolumes;
  554. if (frameState.mode === SceneMode.SCENE3D) {
  555. boundingVolumes = groundPrimitive._boundingVolumes;
  556. } else {
  557. boundingVolumes = groundPrimitive._boundingVolumes2D;
  558. }
  559. const classificationType = groundPrimitive.classificationType;
  560. const queueTerrainCommands =
  561. classificationType !== ClassificationType.CESIUM_3D_TILE;
  562. const queue3DTilesCommands =
  563. classificationType !== ClassificationType.TERRAIN;
  564. const passes = frameState.passes;
  565. const classificationPrimitive = groundPrimitive._primitive;
  566. let i;
  567. let boundingVolume;
  568. let command;
  569. if (passes.render) {
  570. const colorLength = colorCommands.length;
  571. for (i = 0; i < colorLength; ++i) {
  572. boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)];
  573. if (queueTerrainCommands) {
  574. command = colorCommands[i];
  575. updateAndQueueRenderCommand(
  576. groundPrimitive,
  577. command,
  578. frameState,
  579. modelMatrix,
  580. cull,
  581. boundingVolume,
  582. debugShowBoundingVolume
  583. );
  584. }
  585. if (queue3DTilesCommands) {
  586. command = colorCommands[i].derivedCommands.tileset;
  587. updateAndQueueRenderCommand(
  588. groundPrimitive,
  589. command,
  590. frameState,
  591. modelMatrix,
  592. cull,
  593. boundingVolume,
  594. debugShowBoundingVolume
  595. );
  596. }
  597. }
  598. if (frameState.invertClassification) {
  599. const ignoreShowCommands = classificationPrimitive._commandsIgnoreShow;
  600. const ignoreShowCommandsLength = ignoreShowCommands.length;
  601. for (i = 0; i < ignoreShowCommandsLength; ++i) {
  602. boundingVolume = boundingVolumes[i];
  603. command = ignoreShowCommands[i];
  604. updateAndQueueRenderCommand(
  605. groundPrimitive,
  606. command,
  607. frameState,
  608. modelMatrix,
  609. cull,
  610. boundingVolume,
  611. debugShowBoundingVolume
  612. );
  613. }
  614. }
  615. }
  616. if (passes.pick) {
  617. const pickLength = pickCommands.length;
  618. let pickOffsets;
  619. if (!groundPrimitive._useFragmentCulling) {
  620. // Must be using pick offsets
  621. pickOffsets = classificationPrimitive._primitive._pickOffsets;
  622. }
  623. for (i = 0; i < pickLength; ++i) {
  624. boundingVolume = boundingVolumes[boundingVolumeIndex(i, pickLength)];
  625. if (!groundPrimitive._useFragmentCulling) {
  626. const pickOffset = pickOffsets[boundingVolumeIndex(i, pickLength)];
  627. boundingVolume = boundingVolumes[pickOffset.index];
  628. }
  629. if (queueTerrainCommands) {
  630. command = pickCommands[i];
  631. updateAndQueuePickCommand(
  632. groundPrimitive,
  633. command,
  634. frameState,
  635. modelMatrix,
  636. cull,
  637. boundingVolume
  638. );
  639. }
  640. if (queue3DTilesCommands) {
  641. command = pickCommands[i].derivedCommands.tileset;
  642. updateAndQueuePickCommand(
  643. groundPrimitive,
  644. command,
  645. frameState,
  646. modelMatrix,
  647. cull,
  648. boundingVolume
  649. );
  650. }
  651. }
  652. }
  653. }
  654. /**
  655. * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
  656. * GroundPrimitive synchronously.
  657. *
  658. * @returns {Promise<void>} A promise that will resolve once the terrain heights have been loaded.
  659. *
  660. */
  661. GroundPrimitive.initializeTerrainHeights = function () {
  662. return ApproximateTerrainHeights.initialize();
  663. };
  664. /**
  665. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  666. * get the draw commands needed to render this primitive.
  667. * <p>
  668. * Do not call this function directly. This is documented just to
  669. * list the exceptions that may be propagated when the scene is rendered:
  670. * </p>
  671. *
  672. * @exception {DeveloperError} For synchronous GroundPrimitive, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.
  673. * @exception {DeveloperError} All instance geometries must have the same primitiveType.
  674. * @exception {DeveloperError} Appearance and material have a uniform with the same name.
  675. */
  676. GroundPrimitive.prototype.update = function (frameState) {
  677. if (!defined(this._primitive) && !defined(this.geometryInstances)) {
  678. return;
  679. }
  680. if (!ApproximateTerrainHeights.initialized) {
  681. //>>includeStart('debug', pragmas.debug);
  682. if (!this.asynchronous) {
  683. throw new DeveloperError(
  684. "For synchronous GroundPrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve."
  685. );
  686. }
  687. //>>includeEnd('debug');
  688. GroundPrimitive.initializeTerrainHeights();
  689. return;
  690. }
  691. const that = this;
  692. const primitiveOptions = this._classificationPrimitiveOptions;
  693. if (!defined(this._primitive)) {
  694. const ellipsoid = frameState.mapProjection.ellipsoid;
  695. let instance;
  696. let geometry;
  697. let instanceType;
  698. const instances = Array.isArray(this.geometryInstances)
  699. ? this.geometryInstances
  700. : [this.geometryInstances];
  701. const length = instances.length;
  702. const groundInstances = new Array(length);
  703. let i;
  704. let rectangle;
  705. for (i = 0; i < length; ++i) {
  706. instance = instances[i];
  707. geometry = instance.geometry;
  708. const instanceRectangle = getRectangle(frameState, geometry);
  709. if (!defined(rectangle)) {
  710. rectangle = Rectangle.clone(instanceRectangle);
  711. } else if (defined(instanceRectangle)) {
  712. Rectangle.union(rectangle, instanceRectangle, rectangle);
  713. }
  714. const id = instance.id;
  715. if (defined(id) && defined(instanceRectangle)) {
  716. const boundingSphere = ApproximateTerrainHeights.getBoundingSphere(
  717. instanceRectangle,
  718. ellipsoid
  719. );
  720. this._boundingSpheresKeys.push(id);
  721. this._boundingSpheres.push(boundingSphere);
  722. }
  723. instanceType = geometry.constructor;
  724. if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) {
  725. //>>includeStart('debug', pragmas.debug);
  726. throw new DeveloperError(
  727. "Not all of the geometry instances have GroundPrimitive support."
  728. );
  729. //>>includeEnd('debug');
  730. }
  731. }
  732. // Now compute the min/max heights for the primitive
  733. setMinMaxTerrainHeights(this, rectangle, ellipsoid);
  734. const exaggeration = frameState.terrainExaggeration;
  735. const exaggerationRelativeHeight =
  736. frameState.terrainExaggerationRelativeHeight;
  737. this._minHeight = TerrainExaggeration.getHeight(
  738. this._minTerrainHeight,
  739. exaggeration,
  740. exaggerationRelativeHeight
  741. );
  742. this._maxHeight = TerrainExaggeration.getHeight(
  743. this._maxTerrainHeight,
  744. exaggeration,
  745. exaggerationRelativeHeight
  746. );
  747. const useFragmentCulling = GroundPrimitive._supportsMaterials(
  748. frameState.context
  749. );
  750. this._useFragmentCulling = useFragmentCulling;
  751. if (useFragmentCulling) {
  752. // Determine whether to add spherical or planar extent attributes for computing texture coordinates.
  753. // This depends on the size of the GeometryInstances.
  754. let attributes;
  755. let usePlanarExtents = true;
  756. for (i = 0; i < length; ++i) {
  757. instance = instances[i];
  758. geometry = instance.geometry;
  759. rectangle = getRectangle(frameState, geometry);
  760. if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) {
  761. usePlanarExtents = false;
  762. break;
  763. }
  764. }
  765. for (i = 0; i < length; ++i) {
  766. instance = instances[i];
  767. geometry = instance.geometry;
  768. instanceType = geometry.constructor;
  769. const boundingRectangle = getRectangle(frameState, geometry);
  770. const textureCoordinateRotationPoints =
  771. geometry.textureCoordinateRotationPoints;
  772. if (usePlanarExtents) {
  773. attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(
  774. boundingRectangle,
  775. textureCoordinateRotationPoints,
  776. ellipsoid,
  777. frameState.mapProjection,
  778. this._maxHeight
  779. );
  780. } else {
  781. attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(
  782. boundingRectangle,
  783. textureCoordinateRotationPoints,
  784. ellipsoid,
  785. frameState.mapProjection
  786. );
  787. }
  788. const instanceAttributes = instance.attributes;
  789. for (const attributeKey in instanceAttributes) {
  790. if (instanceAttributes.hasOwnProperty(attributeKey)) {
  791. attributes[attributeKey] = instanceAttributes[attributeKey];
  792. }
  793. }
  794. groundInstances[i] = new GeometryInstance({
  795. geometry: instanceType.createShadowVolume(
  796. geometry,
  797. getComputeMinimumHeightFunction(this),
  798. getComputeMaximumHeightFunction(this)
  799. ),
  800. attributes: attributes,
  801. id: instance.id,
  802. });
  803. }
  804. } else {
  805. // ClassificationPrimitive will check if the colors are all the same if it detects lack of fragment culling attributes
  806. for (i = 0; i < length; ++i) {
  807. instance = instances[i];
  808. geometry = instance.geometry;
  809. instanceType = geometry.constructor;
  810. groundInstances[i] = new GeometryInstance({
  811. geometry: instanceType.createShadowVolume(
  812. geometry,
  813. getComputeMinimumHeightFunction(this),
  814. getComputeMaximumHeightFunction(this)
  815. ),
  816. attributes: instance.attributes,
  817. id: instance.id,
  818. });
  819. }
  820. }
  821. primitiveOptions.geometryInstances = groundInstances;
  822. primitiveOptions.appearance = this.appearance;
  823. primitiveOptions._createBoundingVolumeFunction = function (
  824. frameState,
  825. geometry
  826. ) {
  827. createBoundingVolume(that, frameState, geometry);
  828. };
  829. primitiveOptions._updateAndQueueCommandsFunction = function (
  830. primitive,
  831. frameState,
  832. colorCommands,
  833. pickCommands,
  834. modelMatrix,
  835. cull,
  836. debugShowBoundingVolume,
  837. twoPasses
  838. ) {
  839. updateAndQueueCommands(
  840. that,
  841. frameState,
  842. colorCommands,
  843. pickCommands,
  844. modelMatrix,
  845. cull,
  846. debugShowBoundingVolume,
  847. twoPasses
  848. );
  849. };
  850. this._primitive = new ClassificationPrimitive(primitiveOptions);
  851. }
  852. this._primitive.appearance = this.appearance;
  853. this._primitive.show = this.show;
  854. this._primitive.debugShowShadowVolume = this.debugShowShadowVolume;
  855. this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
  856. this._primitive.update(frameState);
  857. frameState.afterRender.push(() => {
  858. if (!this._ready && defined(this._primitive) && this._primitive.ready) {
  859. this._completeLoad();
  860. }
  861. });
  862. };
  863. /**
  864. * @private
  865. */
  866. GroundPrimitive.prototype.getBoundingSphere = function (id) {
  867. const index = this._boundingSpheresKeys.indexOf(id);
  868. if (index !== -1) {
  869. return this._boundingSpheres[index];
  870. }
  871. return undefined;
  872. };
  873. /**
  874. * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
  875. *
  876. * @param {*} id The id of the {@link GeometryInstance}.
  877. * @returns {object} The typed array in the attribute's format or undefined if the is no instance with id.
  878. *
  879. * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
  880. *
  881. * @example
  882. * const attributes = primitive.getGeometryInstanceAttributes('an id');
  883. * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
  884. * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
  885. */
  886. GroundPrimitive.prototype.getGeometryInstanceAttributes = function (id) {
  887. //>>includeStart('debug', pragmas.debug);
  888. if (!defined(this._primitive)) {
  889. throw new DeveloperError(
  890. "must call update before calling getGeometryInstanceAttributes"
  891. );
  892. }
  893. //>>includeEnd('debug');
  894. return this._primitive.getGeometryInstanceAttributes(id);
  895. };
  896. /**
  897. * Returns true if this object was destroyed; otherwise, false.
  898. * <p>
  899. * If this object was destroyed, it should not be used; calling any function other than
  900. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  901. * </p>
  902. *
  903. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  904. *
  905. * @see GroundPrimitive#destroy
  906. */
  907. GroundPrimitive.prototype.isDestroyed = function () {
  908. return false;
  909. };
  910. /**
  911. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  912. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  913. * <p>
  914. * Once an object is destroyed, it should not be used; calling any function other than
  915. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  916. * assign the return value (<code>undefined</code>) to the object as done in the example.
  917. * </p>
  918. *
  919. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  920. *
  921. * @example
  922. * e = e && e.destroy();
  923. *
  924. * @see GroundPrimitive#isDestroyed
  925. */
  926. GroundPrimitive.prototype.destroy = function () {
  927. this._primitive = this._primitive && this._primitive.destroy();
  928. return destroyObject(this);
  929. };
  930. /**
  931. * Exposed for testing.
  932. *
  933. * @param {Context} context Rendering context
  934. * @returns {boolean} Whether or not the current context supports materials on GroundPrimitives.
  935. * @private
  936. */
  937. GroundPrimitive._supportsMaterials = function (context) {
  938. return context.depthTexture;
  939. };
  940. /**
  941. * Checks if the given Scene supports materials on GroundPrimitives.
  942. * Materials on GroundPrimitives require support for the WEBGL_depth_texture extension.
  943. *
  944. * @param {Scene} scene The current scene.
  945. * @returns {boolean} Whether or not the current scene supports materials on GroundPrimitives.
  946. */
  947. GroundPrimitive.supportsMaterials = function (scene) {
  948. //>>includeStart('debug', pragmas.debug);
  949. Check.typeOf.object("scene", scene);
  950. //>>includeEnd('debug');
  951. return GroundPrimitive._supportsMaterials(scene.frameState.context);
  952. };
  953. export default GroundPrimitive;