GroundPrimitive.js 31 KB

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