EllipsoidPrimitive.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. import BoundingSphere from "../Core/BoundingSphere.js";
  2. import BoxGeometry from "../Core/BoxGeometry.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import combine from "../Core/combine.js";
  5. import defaultValue from "../Core/defaultValue.js";
  6. import defined from "../Core/defined.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import Matrix4 from "../Core/Matrix4.js";
  10. import VertexFormat from "../Core/VertexFormat.js";
  11. import BufferUsage from "../Renderer/BufferUsage.js";
  12. import DrawCommand from "../Renderer/DrawCommand.js";
  13. import Pass from "../Renderer/Pass.js";
  14. import RenderState from "../Renderer/RenderState.js";
  15. import ShaderProgram from "../Renderer/ShaderProgram.js";
  16. import ShaderSource from "../Renderer/ShaderSource.js";
  17. import VertexArray from "../Renderer/VertexArray.js";
  18. import EllipsoidFS from "../Shaders/EllipsoidFS.js";
  19. import EllipsoidVS from "../Shaders/EllipsoidVS.js";
  20. import BlendingState from "./BlendingState.js";
  21. import CullFace from "./CullFace.js";
  22. import Material from "./Material.js";
  23. import SceneMode from "./SceneMode.js";
  24. const attributeLocations = {
  25. position: 0,
  26. };
  27. /**
  28. * A renderable ellipsoid. It can also draw spheres when the three {@link EllipsoidPrimitive#radii} components are equal.
  29. * <p>
  30. * This is only supported in 3D. The ellipsoid is not shown in 2D or Columbus view.
  31. * </p>
  32. *
  33. * @alias EllipsoidPrimitive
  34. * @constructor
  35. *
  36. * @param {Object} [options] Object with the following properties:
  37. * @param {Cartesian3} [options.center=Cartesian3.ZERO] The center of the ellipsoid in the ellipsoid's model coordinates.
  38. * @param {Cartesian3} [options.radii] The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
  39. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
  40. * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
  41. * @param {Material} [options.material=Material.ColorType] The surface appearance of the primitive.
  42. * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
  43. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  44. *
  45. * @private
  46. */
  47. function EllipsoidPrimitive(options) {
  48. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  49. /**
  50. * The center of the ellipsoid in the ellipsoid's model coordinates.
  51. * <p>
  52. * The default is {@link Cartesian3.ZERO}.
  53. * </p>
  54. *
  55. * @type {Cartesian3}
  56. * @default {@link Cartesian3.ZERO}
  57. *
  58. * @see EllipsoidPrimitive#modelMatrix
  59. */
  60. this.center = Cartesian3.clone(defaultValue(options.center, Cartesian3.ZERO));
  61. this._center = new Cartesian3();
  62. /**
  63. * The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
  64. * When these are the same, the ellipsoid is a sphere.
  65. * <p>
  66. * The default is <code>undefined</code>. The ellipsoid is not drawn until a radii is provided.
  67. * </p>
  68. *
  69. * @type {Cartesian3}
  70. * @default undefined
  71. *
  72. *
  73. * @example
  74. * // A sphere with a radius of 2.0
  75. * e.radii = new Cesium.Cartesian3(2.0, 2.0, 2.0);
  76. *
  77. * @see EllipsoidPrimitive#modelMatrix
  78. */
  79. this.radii = Cartesian3.clone(options.radii);
  80. this._radii = new Cartesian3();
  81. this._oneOverEllipsoidRadiiSquared = new Cartesian3();
  82. this._boundingSphere = new BoundingSphere();
  83. /**
  84. * The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
  85. * When this is the identity matrix, the ellipsoid is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  86. * Local reference frames can be used by providing a different transformation matrix, like that returned
  87. * by {@link Transforms.eastNorthUpToFixedFrame}.
  88. *
  89. * @type {Matrix4}
  90. * @default {@link Matrix4.IDENTITY}
  91. *
  92. * @example
  93. * const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
  94. * e.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
  95. */
  96. this.modelMatrix = Matrix4.clone(
  97. defaultValue(options.modelMatrix, Matrix4.IDENTITY)
  98. );
  99. this._modelMatrix = new Matrix4();
  100. this._computedModelMatrix = new Matrix4();
  101. /**
  102. * Determines if the ellipsoid primitive will be shown.
  103. *
  104. * @type {Boolean}
  105. * @default true
  106. */
  107. this.show = defaultValue(options.show, true);
  108. /**
  109. * The surface appearance of the ellipsoid. This can be one of several built-in {@link Material} objects or a custom material, scripted with
  110. * {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}.
  111. * <p>
  112. * The default material is <code>Material.ColorType</code>.
  113. * </p>
  114. *
  115. * @type {Material}
  116. * @default Material.fromType(Material.ColorType)
  117. *
  118. *
  119. * @example
  120. * // 1. Change the color of the default material to yellow
  121. * e.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
  122. *
  123. * // 2. Change material to horizontal stripes
  124. * e.material = Cesium.Material.fromType(Cesium.Material.StripeType);
  125. *
  126. * @see {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}
  127. */
  128. this.material = defaultValue(
  129. options.material,
  130. Material.fromType(Material.ColorType)
  131. );
  132. this._material = undefined;
  133. this._translucent = undefined;
  134. /**
  135. * User-defined object returned when the ellipsoid is picked.
  136. *
  137. * @type Object
  138. *
  139. * @default undefined
  140. *
  141. * @see Scene#pick
  142. */
  143. this.id = options.id;
  144. this._id = undefined;
  145. /**
  146. * This property is for debugging only; it is not for production use nor is it optimized.
  147. * <p>
  148. * Draws the bounding sphere for each draw command in the primitive.
  149. * </p>
  150. *
  151. * @type {Boolean}
  152. *
  153. * @default false
  154. */
  155. this.debugShowBoundingVolume = defaultValue(
  156. options.debugShowBoundingVolume,
  157. false
  158. );
  159. /**
  160. * @private
  161. */
  162. this.onlySunLighting = defaultValue(options.onlySunLighting, false);
  163. this._onlySunLighting = false;
  164. /**
  165. * @private
  166. */
  167. this._depthTestEnabled = defaultValue(options.depthTestEnabled, true);
  168. this._useLogDepth = false;
  169. this._sp = undefined;
  170. this._rs = undefined;
  171. this._va = undefined;
  172. this._pickSP = undefined;
  173. this._pickId = undefined;
  174. this._colorCommand = new DrawCommand({
  175. owner: defaultValue(options._owner, this),
  176. });
  177. this._pickCommand = new DrawCommand({
  178. owner: defaultValue(options._owner, this),
  179. pickOnly: true,
  180. });
  181. const that = this;
  182. this._uniforms = {
  183. u_radii: function () {
  184. return that.radii;
  185. },
  186. u_oneOverEllipsoidRadiiSquared: function () {
  187. return that._oneOverEllipsoidRadiiSquared;
  188. },
  189. };
  190. this._pickUniforms = {
  191. czm_pickColor: function () {
  192. return that._pickId.color;
  193. },
  194. };
  195. }
  196. function getVertexArray(context) {
  197. let vertexArray = context.cache.ellipsoidPrimitive_vertexArray;
  198. if (defined(vertexArray)) {
  199. return vertexArray;
  200. }
  201. const geometry = BoxGeometry.createGeometry(
  202. BoxGeometry.fromDimensions({
  203. dimensions: new Cartesian3(2.0, 2.0, 2.0),
  204. vertexFormat: VertexFormat.POSITION_ONLY,
  205. })
  206. );
  207. vertexArray = VertexArray.fromGeometry({
  208. context: context,
  209. geometry: geometry,
  210. attributeLocations: attributeLocations,
  211. bufferUsage: BufferUsage.STATIC_DRAW,
  212. interleave: true,
  213. });
  214. context.cache.ellipsoidPrimitive_vertexArray = vertexArray;
  215. return vertexArray;
  216. }
  217. const logDepthExtension =
  218. "#ifdef GL_EXT_frag_depth \n" +
  219. "#extension GL_EXT_frag_depth : enable \n" +
  220. "#endif \n\n";
  221. /**
  222. * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
  223. * get the draw commands needed to render this primitive.
  224. * <p>
  225. * Do not call this function directly. This is documented just to
  226. * list the exceptions that may be propagated when the scene is rendered:
  227. * </p>
  228. *
  229. * @exception {DeveloperError} this.material must be defined.
  230. */
  231. EllipsoidPrimitive.prototype.update = function (frameState) {
  232. if (
  233. !this.show ||
  234. frameState.mode !== SceneMode.SCENE3D ||
  235. !defined(this.center) ||
  236. !defined(this.radii)
  237. ) {
  238. return;
  239. }
  240. //>>includeStart('debug', pragmas.debug);
  241. if (!defined(this.material)) {
  242. throw new DeveloperError("this.material must be defined.");
  243. }
  244. //>>includeEnd('debug');
  245. const context = frameState.context;
  246. const translucent = this.material.isTranslucent();
  247. const translucencyChanged = this._translucent !== translucent;
  248. if (!defined(this._rs) || translucencyChanged) {
  249. this._translucent = translucent;
  250. // If this render state is ever updated to use a non-default
  251. // depth range, the hard-coded values in EllipsoidVS.glsl need
  252. // to be updated as well.
  253. this._rs = RenderState.fromCache({
  254. // Cull front faces - not back faces - so the ellipsoid doesn't
  255. // disappear if the viewer enters the bounding box.
  256. cull: {
  257. enabled: true,
  258. face: CullFace.FRONT,
  259. },
  260. depthTest: {
  261. enabled: this._depthTestEnabled,
  262. },
  263. // Only write depth when EXT_frag_depth is supported since the depth for
  264. // the bounding box is wrong; it is not the true depth of the ray casted ellipsoid.
  265. depthMask: !translucent && context.fragmentDepth,
  266. blending: translucent ? BlendingState.ALPHA_BLEND : undefined,
  267. });
  268. }
  269. if (!defined(this._va)) {
  270. this._va = getVertexArray(context);
  271. }
  272. let boundingSphereDirty = false;
  273. const radii = this.radii;
  274. if (!Cartesian3.equals(this._radii, radii)) {
  275. Cartesian3.clone(radii, this._radii);
  276. const r = this._oneOverEllipsoidRadiiSquared;
  277. r.x = 1.0 / (radii.x * radii.x);
  278. r.y = 1.0 / (radii.y * radii.y);
  279. r.z = 1.0 / (radii.z * radii.z);
  280. boundingSphereDirty = true;
  281. }
  282. if (
  283. !Matrix4.equals(this.modelMatrix, this._modelMatrix) ||
  284. !Cartesian3.equals(this.center, this._center)
  285. ) {
  286. Matrix4.clone(this.modelMatrix, this._modelMatrix);
  287. Cartesian3.clone(this.center, this._center);
  288. // Translate model coordinates used for rendering such that the origin is the center of the ellipsoid.
  289. Matrix4.multiplyByTranslation(
  290. this.modelMatrix,
  291. this.center,
  292. this._computedModelMatrix
  293. );
  294. boundingSphereDirty = true;
  295. }
  296. if (boundingSphereDirty) {
  297. Cartesian3.clone(Cartesian3.ZERO, this._boundingSphere.center);
  298. this._boundingSphere.radius = Cartesian3.maximumComponent(radii);
  299. BoundingSphere.transform(
  300. this._boundingSphere,
  301. this._computedModelMatrix,
  302. this._boundingSphere
  303. );
  304. }
  305. const materialChanged = this._material !== this.material;
  306. this._material = this.material;
  307. this._material.update(context);
  308. const lightingChanged = this.onlySunLighting !== this._onlySunLighting;
  309. this._onlySunLighting = this.onlySunLighting;
  310. const useLogDepth = frameState.useLogDepth;
  311. const useLogDepthChanged = this._useLogDepth !== useLogDepth;
  312. this._useLogDepth = useLogDepth;
  313. const colorCommand = this._colorCommand;
  314. let vs;
  315. let fs;
  316. // Recompile shader when material, lighting, or translucency changes
  317. if (
  318. materialChanged ||
  319. lightingChanged ||
  320. translucencyChanged ||
  321. useLogDepthChanged
  322. ) {
  323. vs = new ShaderSource({
  324. sources: [EllipsoidVS],
  325. });
  326. fs = new ShaderSource({
  327. sources: [this.material.shaderSource, EllipsoidFS],
  328. });
  329. if (this.onlySunLighting) {
  330. fs.defines.push("ONLY_SUN_LIGHTING");
  331. }
  332. if (!translucent && context.fragmentDepth) {
  333. fs.defines.push("WRITE_DEPTH");
  334. }
  335. if (this._useLogDepth) {
  336. vs.defines.push("LOG_DEPTH");
  337. fs.defines.push("LOG_DEPTH");
  338. fs.sources.push(logDepthExtension);
  339. }
  340. this._sp = ShaderProgram.replaceCache({
  341. context: context,
  342. shaderProgram: this._sp,
  343. vertexShaderSource: vs,
  344. fragmentShaderSource: fs,
  345. attributeLocations: attributeLocations,
  346. });
  347. colorCommand.vertexArray = this._va;
  348. colorCommand.renderState = this._rs;
  349. colorCommand.shaderProgram = this._sp;
  350. colorCommand.uniformMap = combine(this._uniforms, this.material._uniforms);
  351. colorCommand.executeInClosestFrustum = translucent;
  352. }
  353. const commandList = frameState.commandList;
  354. const passes = frameState.passes;
  355. if (passes.render) {
  356. colorCommand.boundingVolume = this._boundingSphere;
  357. colorCommand.debugShowBoundingVolume = this.debugShowBoundingVolume;
  358. colorCommand.modelMatrix = this._computedModelMatrix;
  359. colorCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
  360. commandList.push(colorCommand);
  361. }
  362. if (passes.pick) {
  363. const pickCommand = this._pickCommand;
  364. if (!defined(this._pickId) || this._id !== this.id) {
  365. this._id = this.id;
  366. this._pickId = this._pickId && this._pickId.destroy();
  367. this._pickId = context.createPickId({
  368. primitive: this,
  369. id: this.id,
  370. });
  371. }
  372. // Recompile shader when material changes
  373. if (
  374. materialChanged ||
  375. lightingChanged ||
  376. !defined(this._pickSP) ||
  377. useLogDepthChanged
  378. ) {
  379. vs = new ShaderSource({
  380. sources: [EllipsoidVS],
  381. });
  382. fs = new ShaderSource({
  383. sources: [this.material.shaderSource, EllipsoidFS],
  384. pickColorQualifier: "uniform",
  385. });
  386. if (this.onlySunLighting) {
  387. fs.defines.push("ONLY_SUN_LIGHTING");
  388. }
  389. if (!translucent && context.fragmentDepth) {
  390. fs.defines.push("WRITE_DEPTH");
  391. }
  392. if (this._useLogDepth) {
  393. vs.defines.push("LOG_DEPTH");
  394. fs.defines.push("LOG_DEPTH");
  395. fs.sources.push(logDepthExtension);
  396. }
  397. this._pickSP = ShaderProgram.replaceCache({
  398. context: context,
  399. shaderProgram: this._pickSP,
  400. vertexShaderSource: vs,
  401. fragmentShaderSource: fs,
  402. attributeLocations: attributeLocations,
  403. });
  404. pickCommand.vertexArray = this._va;
  405. pickCommand.renderState = this._rs;
  406. pickCommand.shaderProgram = this._pickSP;
  407. pickCommand.uniformMap = combine(
  408. combine(this._uniforms, this._pickUniforms),
  409. this.material._uniforms
  410. );
  411. pickCommand.executeInClosestFrustum = translucent;
  412. }
  413. pickCommand.boundingVolume = this._boundingSphere;
  414. pickCommand.modelMatrix = this._computedModelMatrix;
  415. pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
  416. commandList.push(pickCommand);
  417. }
  418. };
  419. /**
  420. * Returns true if this object was destroyed; otherwise, false.
  421. * <br /><br />
  422. * If this object was destroyed, it should not be used; calling any function other than
  423. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  424. *
  425. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  426. *
  427. * @see EllipsoidPrimitive#destroy
  428. */
  429. EllipsoidPrimitive.prototype.isDestroyed = function () {
  430. return false;
  431. };
  432. /**
  433. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  434. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  435. * <br /><br />
  436. * Once an object is destroyed, it should not be used; calling any function other than
  437. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  438. * assign the return value (<code>undefined</code>) to the object as done in the example.
  439. *
  440. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  441. *
  442. *
  443. * @example
  444. * e = e && e.destroy();
  445. *
  446. * @see EllipsoidPrimitive#isDestroyed
  447. */
  448. EllipsoidPrimitive.prototype.destroy = function () {
  449. this._sp = this._sp && this._sp.destroy();
  450. this._pickSP = this._pickSP && this._pickSP.destroy();
  451. this._pickId = this._pickId && this._pickId.destroy();
  452. return destroyObject(this);
  453. };
  454. export default EllipsoidPrimitive;