GroundPrimitive.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  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 defer from "../Core/defer.js";
  8. import defined from "../Core/defined.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. this._readyPromise = defer();
  209. this._primitive = undefined;
  210. this._maxHeight = undefined;
  211. this._minHeight = undefined;
  212. this._maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
  213. this._minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
  214. this._boundingSpheresKeys = [];
  215. this._boundingSpheres = [];
  216. this._useFragmentCulling = false;
  217. // Used when inserting in an OrderedPrimitiveCollection
  218. this._zIndex = undefined;
  219. const that = this;
  220. this._classificationPrimitiveOptions = {
  221. geometryInstances: undefined,
  222. appearance: undefined,
  223. vertexCacheOptimize: defaultValue(options.vertexCacheOptimize, false),
  224. interleave: defaultValue(options.interleave, false),
  225. releaseGeometryInstances: defaultValue(
  226. options.releaseGeometryInstances,
  227. true
  228. ),
  229. allowPicking: defaultValue(options.allowPicking, true),
  230. asynchronous: defaultValue(options.asynchronous, true),
  231. compressVertices: defaultValue(options.compressVertices, true),
  232. _createBoundingVolumeFunction: undefined,
  233. _updateAndQueueCommandsFunction: undefined,
  234. _pickPrimitive: that,
  235. _extruded: true,
  236. _uniformMap: GroundPrimitiveUniformMap,
  237. };
  238. }
  239. Object.defineProperties(GroundPrimitive.prototype, {
  240. /**
  241. * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
  242. *
  243. * @memberof GroundPrimitive.prototype
  244. *
  245. * @type {Boolean}
  246. * @readonly
  247. *
  248. * @default true
  249. */
  250. vertexCacheOptimize: {
  251. get: function () {
  252. return this._classificationPrimitiveOptions.vertexCacheOptimize;
  253. },
  254. },
  255. /**
  256. * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
  257. *
  258. * @memberof GroundPrimitive.prototype
  259. *
  260. * @type {Boolean}
  261. * @readonly
  262. *
  263. * @default false
  264. */
  265. interleave: {
  266. get: function () {
  267. return this._classificationPrimitiveOptions.interleave;
  268. },
  269. },
  270. /**
  271. * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
  272. *
  273. * @memberof GroundPrimitive.prototype
  274. *
  275. * @type {Boolean}
  276. * @readonly
  277. *
  278. * @default true
  279. */
  280. releaseGeometryInstances: {
  281. get: function () {
  282. return this._classificationPrimitiveOptions.releaseGeometryInstances;
  283. },
  284. },
  285. /**
  286. * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved.
  287. *
  288. * @memberof GroundPrimitive.prototype
  289. *
  290. * @type {Boolean}
  291. * @readonly
  292. *
  293. * @default true
  294. */
  295. allowPicking: {
  296. get: function () {
  297. return this._classificationPrimitiveOptions.allowPicking;
  298. },
  299. },
  300. /**
  301. * Determines if the geometry instances will be created and batched on a web worker.
  302. *
  303. * @memberof GroundPrimitive.prototype
  304. *
  305. * @type {Boolean}
  306. * @readonly
  307. *
  308. * @default true
  309. */
  310. asynchronous: {
  311. get: function () {
  312. return this._classificationPrimitiveOptions.asynchronous;
  313. },
  314. },
  315. /**
  316. * When <code>true</code>, geometry vertices are compressed, which will save memory.
  317. *
  318. * @memberof GroundPrimitive.prototype
  319. *
  320. * @type {Boolean}
  321. * @readonly
  322. *
  323. * @default true
  324. */
  325. compressVertices: {
  326. get: function () {
  327. return this._classificationPrimitiveOptions.compressVertices;
  328. },
  329. },
  330. /**
  331. * Determines if the primitive is complete and ready to render. If this property is
  332. * true, the primitive will be rendered the next time that {@link GroundPrimitive#update}
  333. * is called.
  334. *
  335. * @memberof GroundPrimitive.prototype
  336. *
  337. * @type {Boolean}
  338. * @readonly
  339. */
  340. ready: {
  341. get: function () {
  342. return this._ready;
  343. },
  344. },
  345. /**
  346. * Gets a promise that resolves when the primitive is ready to render.
  347. * @memberof GroundPrimitive.prototype
  348. * @type {Promise.<GroundPrimitive>}
  349. * @readonly
  350. */
  351. readyPromise: {
  352. get: function () {
  353. return this._readyPromise.promise;
  354. },
  355. },
  356. });
  357. /**
  358. * Determines if GroundPrimitive rendering is supported.
  359. *
  360. * @function
  361. * @param {Scene} scene The scene.
  362. * @returns {Boolean} <code>true</code> if GroundPrimitives are supported; otherwise, returns <code>false</code>
  363. */
  364. GroundPrimitive.isSupported = ClassificationPrimitive.isSupported;
  365. function getComputeMaximumHeightFunction(primitive) {
  366. return function (granularity, ellipsoid) {
  367. const r = ellipsoid.maximumRadius;
  368. const delta = r / Math.cos(granularity * 0.5) - r;
  369. return primitive._maxHeight + delta;
  370. };
  371. }
  372. function getComputeMinimumHeightFunction(primitive) {
  373. return function (granularity, ellipsoid) {
  374. return primitive._minHeight;
  375. };
  376. }
  377. const scratchBVCartesianHigh = new Cartesian3();
  378. const scratchBVCartesianLow = new Cartesian3();
  379. const scratchBVCartesian = new Cartesian3();
  380. const scratchBVCartographic = new Cartographic();
  381. const scratchBVRectangle = new Rectangle();
  382. function getRectangle(frameState, geometry) {
  383. const ellipsoid = frameState.mapProjection.ellipsoid;
  384. if (
  385. !defined(geometry.attributes) ||
  386. !defined(geometry.attributes.position3DHigh)
  387. ) {
  388. if (defined(geometry.rectangle)) {
  389. return geometry.rectangle;
  390. }
  391. return undefined;
  392. }
  393. const highPositions = geometry.attributes.position3DHigh.values;
  394. const lowPositions = geometry.attributes.position3DLow.values;
  395. const length = highPositions.length;
  396. let minLat = Number.POSITIVE_INFINITY;
  397. let minLon = Number.POSITIVE_INFINITY;
  398. let maxLat = Number.NEGATIVE_INFINITY;
  399. let maxLon = Number.NEGATIVE_INFINITY;
  400. for (let i = 0; i < length; i += 3) {
  401. const highPosition = Cartesian3.unpack(
  402. highPositions,
  403. i,
  404. scratchBVCartesianHigh
  405. );
  406. const lowPosition = Cartesian3.unpack(
  407. lowPositions,
  408. i,
  409. scratchBVCartesianLow
  410. );
  411. const position = Cartesian3.add(
  412. highPosition,
  413. lowPosition,
  414. scratchBVCartesian
  415. );
  416. const cartographic = ellipsoid.cartesianToCartographic(
  417. position,
  418. scratchBVCartographic
  419. );
  420. const latitude = cartographic.latitude;
  421. const longitude = cartographic.longitude;
  422. minLat = Math.min(minLat, latitude);
  423. minLon = Math.min(minLon, longitude);
  424. maxLat = Math.max(maxLat, latitude);
  425. maxLon = Math.max(maxLon, longitude);
  426. }
  427. const rectangle = scratchBVRectangle;
  428. rectangle.north = maxLat;
  429. rectangle.south = minLat;
  430. rectangle.east = maxLon;
  431. rectangle.west = minLon;
  432. return rectangle;
  433. }
  434. function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
  435. const result = ApproximateTerrainHeights.getMinimumMaximumHeights(
  436. rectangle,
  437. ellipsoid
  438. );
  439. primitive._minTerrainHeight = result.minimumTerrainHeight;
  440. primitive._maxTerrainHeight = result.maximumTerrainHeight;
  441. }
  442. function createBoundingVolume(groundPrimitive, frameState, geometry) {
  443. const ellipsoid = frameState.mapProjection.ellipsoid;
  444. const rectangle = getRectangle(frameState, geometry);
  445. const obb = OrientedBoundingBox.fromRectangle(
  446. rectangle,
  447. groundPrimitive._minHeight,
  448. groundPrimitive._maxHeight,
  449. ellipsoid
  450. );
  451. groundPrimitive._boundingVolumes.push(obb);
  452. if (!frameState.scene3DOnly) {
  453. const projection = frameState.mapProjection;
  454. const boundingVolume = BoundingSphere.fromRectangleWithHeights2D(
  455. rectangle,
  456. projection,
  457. groundPrimitive._maxHeight,
  458. groundPrimitive._minHeight
  459. );
  460. Cartesian3.fromElements(
  461. boundingVolume.center.z,
  462. boundingVolume.center.x,
  463. boundingVolume.center.y,
  464. boundingVolume.center
  465. );
  466. groundPrimitive._boundingVolumes2D.push(boundingVolume);
  467. }
  468. }
  469. function boundingVolumeIndex(commandIndex, length) {
  470. return Math.floor((commandIndex % length) / 2);
  471. }
  472. function updateAndQueueRenderCommand(
  473. groundPrimitive,
  474. command,
  475. frameState,
  476. modelMatrix,
  477. cull,
  478. boundingVolume,
  479. debugShowBoundingVolume
  480. ) {
  481. // Use derived appearance command for 2D if needed
  482. const classificationPrimitive = groundPrimitive._primitive;
  483. if (
  484. frameState.mode !== SceneMode.SCENE3D &&
  485. command.shaderProgram === classificationPrimitive._spColor &&
  486. classificationPrimitive._needs2DShader
  487. ) {
  488. command = command.derivedCommands.appearance2D;
  489. }
  490. command.owner = groundPrimitive;
  491. command.modelMatrix = modelMatrix;
  492. command.boundingVolume = boundingVolume;
  493. command.cull = cull;
  494. command.debugShowBoundingVolume = debugShowBoundingVolume;
  495. frameState.commandList.push(command);
  496. }
  497. function updateAndQueuePickCommand(
  498. groundPrimitive,
  499. command,
  500. frameState,
  501. modelMatrix,
  502. cull,
  503. boundingVolume
  504. ) {
  505. // Use derived pick command for 2D if needed
  506. const classificationPrimitive = groundPrimitive._primitive;
  507. if (
  508. frameState.mode !== SceneMode.SCENE3D &&
  509. command.shaderProgram === classificationPrimitive._spPick &&
  510. classificationPrimitive._needs2DShader
  511. ) {
  512. command = command.derivedCommands.pick2D;
  513. }
  514. command.owner = groundPrimitive;
  515. command.modelMatrix = modelMatrix;
  516. command.boundingVolume = boundingVolume;
  517. command.cull = cull;
  518. frameState.commandList.push(command);
  519. }
  520. function updateAndQueueCommands(
  521. groundPrimitive,
  522. frameState,
  523. colorCommands,
  524. pickCommands,
  525. modelMatrix,
  526. cull,
  527. debugShowBoundingVolume,
  528. twoPasses
  529. ) {
  530. let boundingVolumes;
  531. if (frameState.mode === SceneMode.SCENE3D) {
  532. boundingVolumes = groundPrimitive._boundingVolumes;
  533. } else {
  534. boundingVolumes = groundPrimitive._boundingVolumes2D;
  535. }
  536. const classificationType = groundPrimitive.classificationType;
  537. const queueTerrainCommands =
  538. classificationType !== ClassificationType.CESIUM_3D_TILE;
  539. const queue3DTilesCommands =
  540. classificationType !== ClassificationType.TERRAIN;
  541. const passes = frameState.passes;
  542. const classificationPrimitive = groundPrimitive._primitive;
  543. let i;
  544. let boundingVolume;
  545. let command;
  546. if (passes.render) {
  547. const colorLength = colorCommands.length;
  548. for (i = 0; i < colorLength; ++i) {
  549. boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)];
  550. if (queueTerrainCommands) {
  551. command = colorCommands[i];
  552. updateAndQueueRenderCommand(
  553. groundPrimitive,
  554. command,
  555. frameState,
  556. modelMatrix,
  557. cull,
  558. boundingVolume,
  559. debugShowBoundingVolume
  560. );
  561. }
  562. if (queue3DTilesCommands) {
  563. command = colorCommands[i].derivedCommands.tileset;
  564. updateAndQueueRenderCommand(
  565. groundPrimitive,
  566. command,
  567. frameState,
  568. modelMatrix,
  569. cull,
  570. boundingVolume,
  571. debugShowBoundingVolume
  572. );
  573. }
  574. }
  575. if (frameState.invertClassification) {
  576. const ignoreShowCommands = classificationPrimitive._commandsIgnoreShow;
  577. const ignoreShowCommandsLength = ignoreShowCommands.length;
  578. for (i = 0; i < ignoreShowCommandsLength; ++i) {
  579. boundingVolume = boundingVolumes[i];
  580. command = ignoreShowCommands[i];
  581. updateAndQueueRenderCommand(
  582. groundPrimitive,
  583. command,
  584. frameState,
  585. modelMatrix,
  586. cull,
  587. boundingVolume,
  588. debugShowBoundingVolume
  589. );
  590. }
  591. }
  592. }
  593. if (passes.pick) {
  594. const pickLength = pickCommands.length;
  595. let pickOffsets;
  596. if (!groundPrimitive._useFragmentCulling) {
  597. // Must be using pick offsets
  598. pickOffsets = classificationPrimitive._primitive._pickOffsets;
  599. }
  600. for (i = 0; i < pickLength; ++i) {
  601. boundingVolume = boundingVolumes[boundingVolumeIndex(i, pickLength)];
  602. if (!groundPrimitive._useFragmentCulling) {
  603. const pickOffset = pickOffsets[boundingVolumeIndex(i, pickLength)];
  604. boundingVolume = boundingVolumes[pickOffset.index];
  605. }
  606. if (queueTerrainCommands) {
  607. command = pickCommands[i];
  608. updateAndQueuePickCommand(
  609. groundPrimitive,
  610. command,
  611. frameState,
  612. modelMatrix,
  613. cull,
  614. boundingVolume
  615. );
  616. }
  617. if (queue3DTilesCommands) {
  618. command = pickCommands[i].derivedCommands.tileset;
  619. updateAndQueuePickCommand(
  620. groundPrimitive,
  621. command,
  622. frameState,
  623. modelMatrix,
  624. cull,
  625. boundingVolume
  626. );
  627. }
  628. }
  629. }
  630. }
  631. /**
  632. * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
  633. * GroundPrimitive synchronously.
  634. *
  635. * @returns {Promise<void>} A promise that will resolve once the terrain heights have been loaded.
  636. *
  637. */
  638. GroundPrimitive.initializeTerrainHeights = function () {
  639. return ApproximateTerrainHeights.initialize();
  640. };
  641. /**
  642. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  643. * get the draw commands needed to render this primitive.
  644. * <p>
  645. * Do not call this function directly. This is documented just to
  646. * list the exceptions that may be propagated when the scene is rendered:
  647. * </p>
  648. *
  649. * @exception {DeveloperError} For synchronous GroundPrimitive, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.
  650. * @exception {DeveloperError} All instance geometries must have the same primitiveType.
  651. * @exception {DeveloperError} Appearance and material have a uniform with the same name.
  652. */
  653. GroundPrimitive.prototype.update = function (frameState) {
  654. if (!defined(this._primitive) && !defined(this.geometryInstances)) {
  655. return;
  656. }
  657. if (!ApproximateTerrainHeights.initialized) {
  658. //>>includeStart('debug', pragmas.debug);
  659. if (!this.asynchronous) {
  660. throw new DeveloperError(
  661. "For synchronous GroundPrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve."
  662. );
  663. }
  664. //>>includeEnd('debug');
  665. GroundPrimitive.initializeTerrainHeights();
  666. return;
  667. }
  668. const that = this;
  669. const primitiveOptions = this._classificationPrimitiveOptions;
  670. if (!defined(this._primitive)) {
  671. const ellipsoid = frameState.mapProjection.ellipsoid;
  672. let instance;
  673. let geometry;
  674. let instanceType;
  675. const instances = Array.isArray(this.geometryInstances)
  676. ? this.geometryInstances
  677. : [this.geometryInstances];
  678. const length = instances.length;
  679. const groundInstances = new Array(length);
  680. let i;
  681. let rectangle;
  682. for (i = 0; i < length; ++i) {
  683. instance = instances[i];
  684. geometry = instance.geometry;
  685. const instanceRectangle = getRectangle(frameState, geometry);
  686. if (!defined(rectangle)) {
  687. rectangle = Rectangle.clone(instanceRectangle);
  688. } else if (defined(instanceRectangle)) {
  689. Rectangle.union(rectangle, instanceRectangle, rectangle);
  690. }
  691. const id = instance.id;
  692. if (defined(id) && defined(instanceRectangle)) {
  693. const boundingSphere = ApproximateTerrainHeights.getBoundingSphere(
  694. instanceRectangle,
  695. ellipsoid
  696. );
  697. this._boundingSpheresKeys.push(id);
  698. this._boundingSpheres.push(boundingSphere);
  699. }
  700. instanceType = geometry.constructor;
  701. if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) {
  702. //>>includeStart('debug', pragmas.debug);
  703. throw new DeveloperError(
  704. "Not all of the geometry instances have GroundPrimitive support."
  705. );
  706. //>>includeEnd('debug');
  707. }
  708. }
  709. // Now compute the min/max heights for the primitive
  710. setMinMaxTerrainHeights(this, rectangle, ellipsoid);
  711. const exaggeration = frameState.terrainExaggeration;
  712. const exaggerationRelativeHeight =
  713. frameState.terrainExaggerationRelativeHeight;
  714. this._minHeight = TerrainExaggeration.getHeight(
  715. this._minTerrainHeight,
  716. exaggeration,
  717. exaggerationRelativeHeight
  718. );
  719. this._maxHeight = TerrainExaggeration.getHeight(
  720. this._maxTerrainHeight,
  721. exaggeration,
  722. exaggerationRelativeHeight
  723. );
  724. const useFragmentCulling = GroundPrimitive._supportsMaterials(
  725. frameState.context
  726. );
  727. this._useFragmentCulling = useFragmentCulling;
  728. if (useFragmentCulling) {
  729. // Determine whether to add spherical or planar extent attributes for computing texture coordinates.
  730. // This depends on the size of the GeometryInstances.
  731. let attributes;
  732. let usePlanarExtents = true;
  733. for (i = 0; i < length; ++i) {
  734. instance = instances[i];
  735. geometry = instance.geometry;
  736. rectangle = getRectangle(frameState, geometry);
  737. if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) {
  738. usePlanarExtents = false;
  739. break;
  740. }
  741. }
  742. for (i = 0; i < length; ++i) {
  743. instance = instances[i];
  744. geometry = instance.geometry;
  745. instanceType = geometry.constructor;
  746. const boundingRectangle = getRectangle(frameState, geometry);
  747. const textureCoordinateRotationPoints =
  748. geometry.textureCoordinateRotationPoints;
  749. if (usePlanarExtents) {
  750. attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(
  751. boundingRectangle,
  752. textureCoordinateRotationPoints,
  753. ellipsoid,
  754. frameState.mapProjection,
  755. this._maxHeight
  756. );
  757. } else {
  758. attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(
  759. boundingRectangle,
  760. textureCoordinateRotationPoints,
  761. ellipsoid,
  762. frameState.mapProjection
  763. );
  764. }
  765. const instanceAttributes = instance.attributes;
  766. for (const attributeKey in instanceAttributes) {
  767. if (instanceAttributes.hasOwnProperty(attributeKey)) {
  768. attributes[attributeKey] = instanceAttributes[attributeKey];
  769. }
  770. }
  771. groundInstances[i] = new GeometryInstance({
  772. geometry: instanceType.createShadowVolume(
  773. geometry,
  774. getComputeMinimumHeightFunction(this),
  775. getComputeMaximumHeightFunction(this)
  776. ),
  777. attributes: attributes,
  778. id: instance.id,
  779. });
  780. }
  781. } else {
  782. // ClassificationPrimitive will check if the colors are all the same if it detects lack of fragment culling attributes
  783. for (i = 0; i < length; ++i) {
  784. instance = instances[i];
  785. geometry = instance.geometry;
  786. instanceType = geometry.constructor;
  787. groundInstances[i] = new GeometryInstance({
  788. geometry: instanceType.createShadowVolume(
  789. geometry,
  790. getComputeMinimumHeightFunction(this),
  791. getComputeMaximumHeightFunction(this)
  792. ),
  793. attributes: instance.attributes,
  794. id: instance.id,
  795. });
  796. }
  797. }
  798. primitiveOptions.geometryInstances = groundInstances;
  799. primitiveOptions.appearance = this.appearance;
  800. primitiveOptions._createBoundingVolumeFunction = function (
  801. frameState,
  802. geometry
  803. ) {
  804. createBoundingVolume(that, frameState, geometry);
  805. };
  806. primitiveOptions._updateAndQueueCommandsFunction = function (
  807. primitive,
  808. frameState,
  809. colorCommands,
  810. pickCommands,
  811. modelMatrix,
  812. cull,
  813. debugShowBoundingVolume,
  814. twoPasses
  815. ) {
  816. updateAndQueueCommands(
  817. that,
  818. frameState,
  819. colorCommands,
  820. pickCommands,
  821. modelMatrix,
  822. cull,
  823. debugShowBoundingVolume,
  824. twoPasses
  825. );
  826. };
  827. this._primitive = new ClassificationPrimitive(primitiveOptions);
  828. this._primitive.readyPromise.then(function (primitive) {
  829. that._ready = true;
  830. if (that.releaseGeometryInstances) {
  831. that.geometryInstances = undefined;
  832. }
  833. const error = primitive._error;
  834. if (!defined(error)) {
  835. that._readyPromise.resolve(that);
  836. } else {
  837. that._readyPromise.reject(error);
  838. }
  839. });
  840. }
  841. this._primitive.appearance = this.appearance;
  842. this._primitive.show = this.show;
  843. this._primitive.debugShowShadowVolume = this.debugShowShadowVolume;
  844. this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
  845. this._primitive.update(frameState);
  846. };
  847. /**
  848. * @private
  849. */
  850. GroundPrimitive.prototype.getBoundingSphere = function (id) {
  851. const index = this._boundingSpheresKeys.indexOf(id);
  852. if (index !== -1) {
  853. return this._boundingSpheres[index];
  854. }
  855. return undefined;
  856. };
  857. /**
  858. * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
  859. *
  860. * @param {*} id The id of the {@link GeometryInstance}.
  861. * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
  862. *
  863. * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
  864. *
  865. * @example
  866. * const attributes = primitive.getGeometryInstanceAttributes('an id');
  867. * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
  868. * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
  869. */
  870. GroundPrimitive.prototype.getGeometryInstanceAttributes = function (id) {
  871. //>>includeStart('debug', pragmas.debug);
  872. if (!defined(this._primitive)) {
  873. throw new DeveloperError(
  874. "must call update before calling getGeometryInstanceAttributes"
  875. );
  876. }
  877. //>>includeEnd('debug');
  878. return this._primitive.getGeometryInstanceAttributes(id);
  879. };
  880. /**
  881. * Returns true if this object was destroyed; otherwise, false.
  882. * <p>
  883. * If this object was destroyed, it should not be used; calling any function other than
  884. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  885. * </p>
  886. *
  887. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  888. *
  889. * @see GroundPrimitive#destroy
  890. */
  891. GroundPrimitive.prototype.isDestroyed = function () {
  892. return false;
  893. };
  894. /**
  895. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  896. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  897. * <p>
  898. * Once an object is destroyed, it should not be used; calling any function other than
  899. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  900. * assign the return value (<code>undefined</code>) to the object as done in the example.
  901. * </p>
  902. *
  903. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  904. *
  905. * @example
  906. * e = e && e.destroy();
  907. *
  908. * @see GroundPrimitive#isDestroyed
  909. */
  910. GroundPrimitive.prototype.destroy = function () {
  911. this._primitive = this._primitive && this._primitive.destroy();
  912. return destroyObject(this);
  913. };
  914. /**
  915. * Exposed for testing.
  916. *
  917. * @param {Context} context Rendering context
  918. * @returns {Boolean} Whether or not the current context supports materials on GroundPrimitives.
  919. * @private
  920. */
  921. GroundPrimitive._supportsMaterials = function (context) {
  922. return context.depthTexture;
  923. };
  924. /**
  925. * Checks if the given Scene supports materials on GroundPrimitives.
  926. * Materials on GroundPrimitives require support for the WEBGL_depth_texture extension.
  927. *
  928. * @param {Scene} scene The current scene.
  929. * @returns {Boolean} Whether or not the current scene supports materials on GroundPrimitives.
  930. */
  931. GroundPrimitive.supportsMaterials = function (scene) {
  932. //>>includeStart('debug', pragmas.debug);
  933. Check.typeOf.object("scene", scene);
  934. //>>includeEnd('debug');
  935. return GroundPrimitive._supportsMaterials(scene.frameState.context);
  936. };
  937. export default GroundPrimitive;