ShadowVolumeAppearance.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Cartographic from "../Core/Cartographic.js";
  4. import Check from "../Core/Check.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import EncodedCartesian3 from "../Core/EncodedCartesian3.js";
  9. import GeometryInstanceAttribute from "../Core/GeometryInstanceAttribute.js";
  10. import CesiumMath from "../Core/Math.js";
  11. import Matrix4 from "../Core/Matrix4.js";
  12. import Rectangle from "../Core/Rectangle.js";
  13. import Transforms from "../Core/Transforms.js";
  14. import ShaderSource from "../Renderer/ShaderSource.js";
  15. import PerInstanceColorAppearance from "../Scene/PerInstanceColorAppearance.js";
  16. import ShadowVolumeAppearanceFS from "../Shaders/ShadowVolumeAppearanceFS.js";
  17. /**
  18. * Creates shaders for a ClassificationPrimitive to use a given Appearance, as well as for picking.
  19. *
  20. * @param {Boolean} extentsCulling Discard fragments outside the instance's texture coordinate extents.
  21. * @param {Boolean} planarExtents If true, texture coordinates will be computed using planes instead of spherical coordinates.
  22. * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive via GroundPrimitive.
  23. * @private
  24. */
  25. function ShadowVolumeAppearance(extentsCulling, planarExtents, appearance) {
  26. //>>includeStart('debug', pragmas.debug);
  27. Check.typeOf.bool("extentsCulling", extentsCulling);
  28. Check.typeOf.bool("planarExtents", planarExtents);
  29. Check.typeOf.object("appearance", appearance);
  30. //>>includeEnd('debug');
  31. this._projectionExtentDefines = {
  32. eastMostYhighDefine: "",
  33. eastMostYlowDefine: "",
  34. westMostYhighDefine: "",
  35. westMostYlowDefine: "",
  36. };
  37. // Compute shader dependencies
  38. var colorShaderDependencies = new ShaderDependencies();
  39. colorShaderDependencies.requiresTextureCoordinates = extentsCulling;
  40. colorShaderDependencies.requiresEC = !appearance.flat;
  41. var pickShaderDependencies = new ShaderDependencies();
  42. pickShaderDependencies.requiresTextureCoordinates = extentsCulling;
  43. if (appearance instanceof PerInstanceColorAppearance) {
  44. // PerInstanceColorAppearance doesn't have material.shaderSource, instead it has its own vertex and fragment shaders
  45. colorShaderDependencies.requiresNormalEC = !appearance.flat;
  46. } else {
  47. // Scan material source for what hookups are needed. Assume czm_materialInput materialInput.
  48. var materialShaderSource =
  49. appearance.material.shaderSource + "\n" + appearance.fragmentShaderSource;
  50. colorShaderDependencies.normalEC =
  51. materialShaderSource.indexOf("materialInput.normalEC") !== -1 ||
  52. materialShaderSource.indexOf("czm_getDefaultMaterial") !== -1;
  53. colorShaderDependencies.positionToEyeEC =
  54. materialShaderSource.indexOf("materialInput.positionToEyeEC") !== -1;
  55. colorShaderDependencies.tangentToEyeMatrix =
  56. materialShaderSource.indexOf("materialInput.tangentToEyeMatrix") !== -1;
  57. colorShaderDependencies.st =
  58. materialShaderSource.indexOf("materialInput.st") !== -1;
  59. }
  60. this._colorShaderDependencies = colorShaderDependencies;
  61. this._pickShaderDependencies = pickShaderDependencies;
  62. this._appearance = appearance;
  63. this._extentsCulling = extentsCulling;
  64. this._planarExtents = planarExtents;
  65. }
  66. /**
  67. * Create the fragment shader for a ClassificationPrimitive's color pass when rendering for color.
  68. *
  69. * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  70. * @returns {ShaderSource} Shader source for the fragment shader.
  71. */
  72. ShadowVolumeAppearance.prototype.createFragmentShader = function (
  73. columbusView2D
  74. ) {
  75. //>>includeStart('debug', pragmas.debug);
  76. Check.typeOf.bool("columbusView2D", columbusView2D);
  77. //>>includeEnd('debug');
  78. var appearance = this._appearance;
  79. var dependencies = this._colorShaderDependencies;
  80. var defines = [];
  81. if (!columbusView2D && !this._planarExtents) {
  82. defines.push("SPHERICAL");
  83. }
  84. if (dependencies.requiresEC) {
  85. defines.push("REQUIRES_EC");
  86. }
  87. if (dependencies.requiresWC) {
  88. defines.push("REQUIRES_WC");
  89. }
  90. if (dependencies.requiresTextureCoordinates) {
  91. defines.push("TEXTURE_COORDINATES");
  92. }
  93. if (this._extentsCulling) {
  94. defines.push("CULL_FRAGMENTS");
  95. }
  96. if (dependencies.requiresNormalEC) {
  97. defines.push("NORMAL_EC");
  98. }
  99. if (appearance instanceof PerInstanceColorAppearance) {
  100. defines.push("PER_INSTANCE_COLOR");
  101. }
  102. // Material inputs. Use of parameters in the material is different
  103. // from requirement of the parameters in the overall shader, for example,
  104. // texture coordinates may be used for fragment culling but not for the material itself.
  105. if (dependencies.normalEC) {
  106. defines.push("USES_NORMAL_EC");
  107. }
  108. if (dependencies.positionToEyeEC) {
  109. defines.push("USES_POSITION_TO_EYE_EC");
  110. }
  111. if (dependencies.tangentToEyeMatrix) {
  112. defines.push("USES_TANGENT_TO_EYE");
  113. }
  114. if (dependencies.st) {
  115. defines.push("USES_ST");
  116. }
  117. if (appearance.flat) {
  118. defines.push("FLAT");
  119. }
  120. var materialSource = "";
  121. if (!(appearance instanceof PerInstanceColorAppearance)) {
  122. materialSource = appearance.material.shaderSource;
  123. }
  124. return new ShaderSource({
  125. defines: defines,
  126. sources: [materialSource, ShadowVolumeAppearanceFS],
  127. });
  128. };
  129. ShadowVolumeAppearance.prototype.createPickFragmentShader = function (
  130. columbusView2D
  131. ) {
  132. //>>includeStart('debug', pragmas.debug);
  133. Check.typeOf.bool("columbusView2D", columbusView2D);
  134. //>>includeEnd('debug');
  135. var dependencies = this._pickShaderDependencies;
  136. var defines = ["PICK"];
  137. if (!columbusView2D && !this._planarExtents) {
  138. defines.push("SPHERICAL");
  139. }
  140. if (dependencies.requiresEC) {
  141. defines.push("REQUIRES_EC");
  142. }
  143. if (dependencies.requiresWC) {
  144. defines.push("REQUIRES_WC");
  145. }
  146. if (dependencies.requiresTextureCoordinates) {
  147. defines.push("TEXTURE_COORDINATES");
  148. }
  149. if (this._extentsCulling) {
  150. defines.push("CULL_FRAGMENTS");
  151. }
  152. return new ShaderSource({
  153. defines: defines,
  154. sources: [ShadowVolumeAppearanceFS],
  155. pickColorQualifier: "varying",
  156. });
  157. };
  158. /**
  159. * Create the vertex shader for a ClassificationPrimitive's color pass on the final of 3 shadow volume passes
  160. *
  161. * @param {String[]} defines External defines to pass to the vertex shader.
  162. * @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position.
  163. * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  164. * @param {MapProjection} mapProjection Current scene's map projection.
  165. * @returns {String} Shader source for the vertex shader.
  166. */
  167. ShadowVolumeAppearance.prototype.createVertexShader = function (
  168. defines,
  169. vertexShaderSource,
  170. columbusView2D,
  171. mapProjection
  172. ) {
  173. //>>includeStart('debug', pragmas.debug);
  174. Check.defined("defines", defines);
  175. Check.typeOf.string("vertexShaderSource", vertexShaderSource);
  176. Check.typeOf.bool("columbusView2D", columbusView2D);
  177. Check.defined("mapProjection", mapProjection);
  178. //>>includeEnd('debug');
  179. return createShadowVolumeAppearanceVS(
  180. this._colorShaderDependencies,
  181. this._planarExtents,
  182. columbusView2D,
  183. defines,
  184. vertexShaderSource,
  185. this._appearance,
  186. mapProjection,
  187. this._projectionExtentDefines
  188. );
  189. };
  190. /**
  191. * Create the vertex shader for a ClassificationPrimitive's pick pass on the final of 3 shadow volume passes
  192. *
  193. * @param {String[]} defines External defines to pass to the vertex shader.
  194. * @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position and picking.
  195. * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  196. * @param {MapProjection} mapProjection Current scene's map projection.
  197. * @returns {String} Shader source for the vertex shader.
  198. */
  199. ShadowVolumeAppearance.prototype.createPickVertexShader = function (
  200. defines,
  201. vertexShaderSource,
  202. columbusView2D,
  203. mapProjection
  204. ) {
  205. //>>includeStart('debug', pragmas.debug);
  206. Check.defined("defines", defines);
  207. Check.typeOf.string("vertexShaderSource", vertexShaderSource);
  208. Check.typeOf.bool("columbusView2D", columbusView2D);
  209. Check.defined("mapProjection", mapProjection);
  210. //>>includeEnd('debug');
  211. return createShadowVolumeAppearanceVS(
  212. this._pickShaderDependencies,
  213. this._planarExtents,
  214. columbusView2D,
  215. defines,
  216. vertexShaderSource,
  217. undefined,
  218. mapProjection,
  219. this._projectionExtentDefines
  220. );
  221. };
  222. var longitudeExtentsCartesianScratch = new Cartesian3();
  223. var longitudeExtentsCartographicScratch = new Cartographic();
  224. var longitudeExtentsEncodeScratch = {
  225. high: 0.0,
  226. low: 0.0,
  227. };
  228. function createShadowVolumeAppearanceVS(
  229. shaderDependencies,
  230. planarExtents,
  231. columbusView2D,
  232. defines,
  233. vertexShaderSource,
  234. appearance,
  235. mapProjection,
  236. projectionExtentDefines
  237. ) {
  238. var allDefines = defines.slice();
  239. if (projectionExtentDefines.eastMostYhighDefine === "") {
  240. var eastMostCartographic = longitudeExtentsCartographicScratch;
  241. eastMostCartographic.longitude = CesiumMath.PI;
  242. eastMostCartographic.latitude = 0.0;
  243. eastMostCartographic.height = 0.0;
  244. var eastMostCartesian = mapProjection.project(
  245. eastMostCartographic,
  246. longitudeExtentsCartesianScratch
  247. );
  248. var encoded = EncodedCartesian3.encode(
  249. eastMostCartesian.x,
  250. longitudeExtentsEncodeScratch
  251. );
  252. projectionExtentDefines.eastMostYhighDefine =
  253. "EAST_MOST_X_HIGH " +
  254. encoded.high.toFixed((encoded.high + "").length + 1);
  255. projectionExtentDefines.eastMostYlowDefine =
  256. "EAST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);
  257. var westMostCartographic = longitudeExtentsCartographicScratch;
  258. westMostCartographic.longitude = -CesiumMath.PI;
  259. westMostCartographic.latitude = 0.0;
  260. westMostCartographic.height = 0.0;
  261. var westMostCartesian = mapProjection.project(
  262. westMostCartographic,
  263. longitudeExtentsCartesianScratch
  264. );
  265. encoded = EncodedCartesian3.encode(
  266. westMostCartesian.x,
  267. longitudeExtentsEncodeScratch
  268. );
  269. projectionExtentDefines.westMostYhighDefine =
  270. "WEST_MOST_X_HIGH " +
  271. encoded.high.toFixed((encoded.high + "").length + 1);
  272. projectionExtentDefines.westMostYlowDefine =
  273. "WEST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);
  274. }
  275. if (columbusView2D) {
  276. allDefines.push(projectionExtentDefines.eastMostYhighDefine);
  277. allDefines.push(projectionExtentDefines.eastMostYlowDefine);
  278. allDefines.push(projectionExtentDefines.westMostYhighDefine);
  279. allDefines.push(projectionExtentDefines.westMostYlowDefine);
  280. }
  281. if (defined(appearance) && appearance instanceof PerInstanceColorAppearance) {
  282. allDefines.push("PER_INSTANCE_COLOR");
  283. }
  284. if (shaderDependencies.requiresTextureCoordinates) {
  285. allDefines.push("TEXTURE_COORDINATES");
  286. if (!(planarExtents || columbusView2D)) {
  287. allDefines.push("SPHERICAL");
  288. }
  289. if (columbusView2D) {
  290. allDefines.push("COLUMBUS_VIEW_2D");
  291. }
  292. }
  293. return new ShaderSource({
  294. defines: allDefines,
  295. sources: [vertexShaderSource],
  296. });
  297. }
  298. /**
  299. * Tracks shader dependencies.
  300. * @private
  301. */
  302. function ShaderDependencies() {
  303. this._requiresEC = false;
  304. this._requiresWC = false; // depends on eye coordinates, needed for material and for phong
  305. this._requiresNormalEC = false; // depends on eye coordinates, needed for material
  306. this._requiresTextureCoordinates = false; // depends on world coordinates, needed for material and for culling
  307. this._usesNormalEC = false;
  308. this._usesPositionToEyeEC = false;
  309. this._usesTangentToEyeMat = false;
  310. this._usesSt = false;
  311. }
  312. Object.defineProperties(ShaderDependencies.prototype, {
  313. // Set when assessing final shading (flat vs. phong) and culling using computed texture coordinates
  314. requiresEC: {
  315. get: function () {
  316. return this._requiresEC;
  317. },
  318. set: function (value) {
  319. this._requiresEC = value || this._requiresEC;
  320. },
  321. },
  322. requiresWC: {
  323. get: function () {
  324. return this._requiresWC;
  325. },
  326. set: function (value) {
  327. this._requiresWC = value || this._requiresWC;
  328. this.requiresEC = this._requiresWC;
  329. },
  330. },
  331. requiresNormalEC: {
  332. get: function () {
  333. return this._requiresNormalEC;
  334. },
  335. set: function (value) {
  336. this._requiresNormalEC = value || this._requiresNormalEC;
  337. this.requiresEC = this._requiresNormalEC;
  338. },
  339. },
  340. requiresTextureCoordinates: {
  341. get: function () {
  342. return this._requiresTextureCoordinates;
  343. },
  344. set: function (value) {
  345. this._requiresTextureCoordinates =
  346. value || this._requiresTextureCoordinates;
  347. this.requiresWC = this._requiresTextureCoordinates;
  348. },
  349. },
  350. // Get/Set when assessing material hookups
  351. normalEC: {
  352. set: function (value) {
  353. this.requiresNormalEC = value;
  354. this._usesNormalEC = value;
  355. },
  356. get: function () {
  357. return this._usesNormalEC;
  358. },
  359. },
  360. tangentToEyeMatrix: {
  361. set: function (value) {
  362. this.requiresWC = value;
  363. this.requiresNormalEC = value;
  364. this._usesTangentToEyeMat = value;
  365. },
  366. get: function () {
  367. return this._usesTangentToEyeMat;
  368. },
  369. },
  370. positionToEyeEC: {
  371. set: function (value) {
  372. this.requiresEC = value;
  373. this._usesPositionToEyeEC = value;
  374. },
  375. get: function () {
  376. return this._usesPositionToEyeEC;
  377. },
  378. },
  379. st: {
  380. set: function (value) {
  381. this.requiresTextureCoordinates = value;
  382. this._usesSt = value;
  383. },
  384. get: function () {
  385. return this._usesSt;
  386. },
  387. },
  388. });
  389. function pointLineDistance(point1, point2, point) {
  390. return (
  391. Math.abs(
  392. (point2.y - point1.y) * point.x -
  393. (point2.x - point1.x) * point.y +
  394. point2.x * point1.y -
  395. point2.y * point1.x
  396. ) / Cartesian2.distance(point2, point1)
  397. );
  398. }
  399. var points2DScratch = [
  400. new Cartesian2(),
  401. new Cartesian2(),
  402. new Cartesian2(),
  403. new Cartesian2(),
  404. ];
  405. // textureCoordinateRotationPoints form 2 lines in the computed UV space that remap to desired texture coordinates.
  406. // This allows simulation of baked texture coordinates for EllipseGeometry, RectangleGeometry, and PolygonGeometry.
  407. function addTextureCoordinateRotationAttributes(
  408. attributes,
  409. textureCoordinateRotationPoints
  410. ) {
  411. var points2D = points2DScratch;
  412. var minXYCorner = Cartesian2.unpack(
  413. textureCoordinateRotationPoints,
  414. 0,
  415. points2D[0]
  416. );
  417. var maxYCorner = Cartesian2.unpack(
  418. textureCoordinateRotationPoints,
  419. 2,
  420. points2D[1]
  421. );
  422. var maxXCorner = Cartesian2.unpack(
  423. textureCoordinateRotationPoints,
  424. 4,
  425. points2D[2]
  426. );
  427. attributes.uMaxVmax = new GeometryInstanceAttribute({
  428. componentDatatype: ComponentDatatype.FLOAT,
  429. componentsPerAttribute: 4,
  430. normalize: false,
  431. value: [maxYCorner.x, maxYCorner.y, maxXCorner.x, maxXCorner.y],
  432. });
  433. var inverseExtentX =
  434. 1.0 / pointLineDistance(minXYCorner, maxYCorner, maxXCorner);
  435. var inverseExtentY =
  436. 1.0 / pointLineDistance(minXYCorner, maxXCorner, maxYCorner);
  437. attributes.uvMinAndExtents = new GeometryInstanceAttribute({
  438. componentDatatype: ComponentDatatype.FLOAT,
  439. componentsPerAttribute: 4,
  440. normalize: false,
  441. value: [minXYCorner.x, minXYCorner.y, inverseExtentX, inverseExtentY],
  442. });
  443. }
  444. var cartographicScratch = new Cartographic();
  445. var cornerScratch = new Cartesian3();
  446. var northWestScratch = new Cartesian3();
  447. var southEastScratch = new Cartesian3();
  448. var highLowScratch = { high: 0.0, low: 0.0 };
  449. function add2DTextureCoordinateAttributes(rectangle, projection, attributes) {
  450. // Compute corner positions in double precision
  451. var carto = cartographicScratch;
  452. carto.height = 0.0;
  453. carto.longitude = rectangle.west;
  454. carto.latitude = rectangle.south;
  455. var southWestCorner = projection.project(carto, cornerScratch);
  456. carto.latitude = rectangle.north;
  457. var northWest = projection.project(carto, northWestScratch);
  458. carto.longitude = rectangle.east;
  459. carto.latitude = rectangle.south;
  460. var southEast = projection.project(carto, southEastScratch);
  461. // Since these positions are all in the 2D plane, there's a lot of zeros
  462. // and a lot of repetition. So we only need to encode 4 values.
  463. // Encode:
  464. // x: x value for southWestCorner
  465. // y: y value for southWestCorner
  466. // z: y value for northWest
  467. // w: x value for southEast
  468. var valuesHigh = [0, 0, 0, 0];
  469. var valuesLow = [0, 0, 0, 0];
  470. var encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch);
  471. valuesHigh[0] = encoded.high;
  472. valuesLow[0] = encoded.low;
  473. encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch);
  474. valuesHigh[1] = encoded.high;
  475. valuesLow[1] = encoded.low;
  476. encoded = EncodedCartesian3.encode(northWest.y, highLowScratch);
  477. valuesHigh[2] = encoded.high;
  478. valuesLow[2] = encoded.low;
  479. encoded = EncodedCartesian3.encode(southEast.x, highLowScratch);
  480. valuesHigh[3] = encoded.high;
  481. valuesLow[3] = encoded.low;
  482. attributes.planes2D_HIGH = new GeometryInstanceAttribute({
  483. componentDatatype: ComponentDatatype.FLOAT,
  484. componentsPerAttribute: 4,
  485. normalize: false,
  486. value: valuesHigh,
  487. });
  488. attributes.planes2D_LOW = new GeometryInstanceAttribute({
  489. componentDatatype: ComponentDatatype.FLOAT,
  490. componentsPerAttribute: 4,
  491. normalize: false,
  492. value: valuesLow,
  493. });
  494. }
  495. var enuMatrixScratch = new Matrix4();
  496. var inverseEnuScratch = new Matrix4();
  497. var rectanglePointCartesianScratch = new Cartesian3();
  498. var rectangleCenterScratch = new Cartographic();
  499. var pointsCartographicScratch = [
  500. new Cartographic(),
  501. new Cartographic(),
  502. new Cartographic(),
  503. new Cartographic(),
  504. new Cartographic(),
  505. new Cartographic(),
  506. new Cartographic(),
  507. new Cartographic(),
  508. ];
  509. /**
  510. * When computing planes to bound the rectangle,
  511. * need to factor in "bulge" and other distortion.
  512. * Flatten the ellipsoid-centered corners and edge-centers of the rectangle
  513. * into the plane of the local ENU system, compute bounds in 2D, and
  514. * project back to ellipsoid-centered.
  515. *
  516. * @private
  517. */
  518. function computeRectangleBounds(
  519. rectangle,
  520. ellipsoid,
  521. height,
  522. southWestCornerResult,
  523. eastVectorResult,
  524. northVectorResult
  525. ) {
  526. // Compute center of rectangle
  527. var centerCartographic = Rectangle.center(rectangle, rectangleCenterScratch);
  528. centerCartographic.height = height;
  529. var centerCartesian = Cartographic.toCartesian(
  530. centerCartographic,
  531. ellipsoid,
  532. rectanglePointCartesianScratch
  533. );
  534. var enuMatrix = Transforms.eastNorthUpToFixedFrame(
  535. centerCartesian,
  536. ellipsoid,
  537. enuMatrixScratch
  538. );
  539. var inverseEnu = Matrix4.inverse(enuMatrix, inverseEnuScratch);
  540. var west = rectangle.west;
  541. var east = rectangle.east;
  542. var north = rectangle.north;
  543. var south = rectangle.south;
  544. var cartographics = pointsCartographicScratch;
  545. cartographics[0].latitude = south;
  546. cartographics[0].longitude = west;
  547. cartographics[1].latitude = north;
  548. cartographics[1].longitude = west;
  549. cartographics[2].latitude = north;
  550. cartographics[2].longitude = east;
  551. cartographics[3].latitude = south;
  552. cartographics[3].longitude = east;
  553. var longitudeCenter = (west + east) * 0.5;
  554. var latitudeCenter = (north + south) * 0.5;
  555. cartographics[4].latitude = south;
  556. cartographics[4].longitude = longitudeCenter;
  557. cartographics[5].latitude = north;
  558. cartographics[5].longitude = longitudeCenter;
  559. cartographics[6].latitude = latitudeCenter;
  560. cartographics[6].longitude = west;
  561. cartographics[7].latitude = latitudeCenter;
  562. cartographics[7].longitude = east;
  563. var minX = Number.POSITIVE_INFINITY;
  564. var maxX = Number.NEGATIVE_INFINITY;
  565. var minY = Number.POSITIVE_INFINITY;
  566. var maxY = Number.NEGATIVE_INFINITY;
  567. for (var i = 0; i < 8; i++) {
  568. cartographics[i].height = height;
  569. var pointCartesian = Cartographic.toCartesian(
  570. cartographics[i],
  571. ellipsoid,
  572. rectanglePointCartesianScratch
  573. );
  574. Matrix4.multiplyByPoint(inverseEnu, pointCartesian, pointCartesian);
  575. pointCartesian.z = 0.0; // flatten into XY plane of ENU coordinate system
  576. minX = Math.min(minX, pointCartesian.x);
  577. maxX = Math.max(maxX, pointCartesian.x);
  578. minY = Math.min(minY, pointCartesian.y);
  579. maxY = Math.max(maxY, pointCartesian.y);
  580. }
  581. var southWestCorner = southWestCornerResult;
  582. southWestCorner.x = minX;
  583. southWestCorner.y = minY;
  584. southWestCorner.z = 0.0;
  585. Matrix4.multiplyByPoint(enuMatrix, southWestCorner, southWestCorner);
  586. var southEastCorner = eastVectorResult;
  587. southEastCorner.x = maxX;
  588. southEastCorner.y = minY;
  589. southEastCorner.z = 0.0;
  590. Matrix4.multiplyByPoint(enuMatrix, southEastCorner, southEastCorner);
  591. // make eastward vector
  592. Cartesian3.subtract(southEastCorner, southWestCorner, eastVectorResult);
  593. var northWestCorner = northVectorResult;
  594. northWestCorner.x = minX;
  595. northWestCorner.y = maxY;
  596. northWestCorner.z = 0.0;
  597. Matrix4.multiplyByPoint(enuMatrix, northWestCorner, northWestCorner);
  598. // make eastward vector
  599. Cartesian3.subtract(northWestCorner, southWestCorner, northVectorResult);
  600. }
  601. var eastwardScratch = new Cartesian3();
  602. var northwardScratch = new Cartesian3();
  603. var encodeScratch = new EncodedCartesian3();
  604. /**
  605. * Gets an attributes object containing:
  606. * - 3 high-precision points as 6 GeometryInstanceAttributes. These points are used to compute eye-space planes.
  607. * - 1 texture coordinate rotation GeometryInstanceAttributes
  608. * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
  609. * These points are used to compute eye-space planes like above.
  610. *
  611. * Used to compute texture coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances.
  612. *
  613. * @see ShadowVolumeAppearance
  614. * @private
  615. *
  616. * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound
  617. * @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
  618. * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
  619. * @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
  620. * @param {Number} [height=0] The maximum height for the shadow volume.
  621. * @returns {Object} An attributes dictionary containing planar texture coordinate attributes.
  622. */
  623. ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function (
  624. boundingRectangle,
  625. textureCoordinateRotationPoints,
  626. ellipsoid,
  627. projection,
  628. height
  629. ) {
  630. //>>includeStart('debug', pragmas.debug);
  631. Check.typeOf.object("boundingRectangle", boundingRectangle);
  632. Check.defined(
  633. "textureCoordinateRotationPoints",
  634. textureCoordinateRotationPoints
  635. );
  636. Check.typeOf.object("ellipsoid", ellipsoid);
  637. Check.typeOf.object("projection", projection);
  638. //>>includeEnd('debug');
  639. var corner = cornerScratch;
  640. var eastward = eastwardScratch;
  641. var northward = northwardScratch;
  642. computeRectangleBounds(
  643. boundingRectangle,
  644. ellipsoid,
  645. defaultValue(height, 0.0),
  646. corner,
  647. eastward,
  648. northward
  649. );
  650. var attributes = {};
  651. addTextureCoordinateRotationAttributes(
  652. attributes,
  653. textureCoordinateRotationPoints
  654. );
  655. var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch);
  656. attributes.southWest_HIGH = new GeometryInstanceAttribute({
  657. componentDatatype: ComponentDatatype.FLOAT,
  658. componentsPerAttribute: 3,
  659. normalize: false,
  660. value: Cartesian3.pack(encoded.high, [0, 0, 0]),
  661. });
  662. attributes.southWest_LOW = new GeometryInstanceAttribute({
  663. componentDatatype: ComponentDatatype.FLOAT,
  664. componentsPerAttribute: 3,
  665. normalize: false,
  666. value: Cartesian3.pack(encoded.low, [0, 0, 0]),
  667. });
  668. attributes.eastward = new GeometryInstanceAttribute({
  669. componentDatatype: ComponentDatatype.FLOAT,
  670. componentsPerAttribute: 3,
  671. normalize: false,
  672. value: Cartesian3.pack(eastward, [0, 0, 0]),
  673. });
  674. attributes.northward = new GeometryInstanceAttribute({
  675. componentDatatype: ComponentDatatype.FLOAT,
  676. componentsPerAttribute: 3,
  677. normalize: false,
  678. value: Cartesian3.pack(northward, [0, 0, 0]),
  679. });
  680. add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
  681. return attributes;
  682. };
  683. var spherePointScratch = new Cartesian3();
  684. function latLongToSpherical(latitude, longitude, ellipsoid, result) {
  685. var cartographic = cartographicScratch;
  686. cartographic.latitude = latitude;
  687. cartographic.longitude = longitude;
  688. cartographic.height = 0.0;
  689. var spherePoint = Cartographic.toCartesian(
  690. cartographic,
  691. ellipsoid,
  692. spherePointScratch
  693. );
  694. // Project into plane with vertical for latitude
  695. var magXY = Math.sqrt(
  696. spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y
  697. );
  698. // Use fastApproximateAtan2 for alignment with shader
  699. var sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);
  700. var sphereLongitude = CesiumMath.fastApproximateAtan2(
  701. spherePoint.x,
  702. spherePoint.y
  703. );
  704. result.x = sphereLatitude;
  705. result.y = sphereLongitude;
  706. return result;
  707. }
  708. var sphericalScratch = new Cartesian2();
  709. /**
  710. * Gets an attributes object containing:
  711. * - the southwest corner of a rectangular area in spherical coordinates, as well as the inverse of the latitude/longitude range.
  712. * These are computed using the same atan2 approximation used in the shader.
  713. * - 1 texture coordinate rotation GeometryInstanceAttributes
  714. * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
  715. * These points are used to compute eye-space planes like above.
  716. *
  717. * Used when computing texture coordinates for large-area ClassificationPrimitives with materials or
  718. * multiple non-overlapping instances.
  719. * @see ShadowVolumeAppearance
  720. * @private
  721. *
  722. * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound
  723. * @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
  724. * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
  725. * @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
  726. * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes.
  727. */
  728. ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function (
  729. boundingRectangle,
  730. textureCoordinateRotationPoints,
  731. ellipsoid,
  732. projection
  733. ) {
  734. //>>includeStart('debug', pragmas.debug);
  735. Check.typeOf.object("boundingRectangle", boundingRectangle);
  736. Check.defined(
  737. "textureCoordinateRotationPoints",
  738. textureCoordinateRotationPoints
  739. );
  740. Check.typeOf.object("ellipsoid", ellipsoid);
  741. Check.typeOf.object("projection", projection);
  742. //>>includeEnd('debug');
  743. // rectangle cartographic coords !== spherical because it's on an ellipsoid
  744. var southWestExtents = latLongToSpherical(
  745. boundingRectangle.south,
  746. boundingRectangle.west,
  747. ellipsoid,
  748. sphericalScratch
  749. );
  750. var south = southWestExtents.x;
  751. var west = southWestExtents.y;
  752. var northEastExtents = latLongToSpherical(
  753. boundingRectangle.north,
  754. boundingRectangle.east,
  755. ellipsoid,
  756. sphericalScratch
  757. );
  758. var north = northEastExtents.x;
  759. var east = northEastExtents.y;
  760. // If the bounding rectangle crosses the IDL, rotate the spherical extents so the cross no longer happens.
  761. // This rotation must happen in the shader too.
  762. var rotationRadians = 0.0;
  763. if (west > east) {
  764. rotationRadians = CesiumMath.PI - west;
  765. west = -CesiumMath.PI;
  766. east += rotationRadians;
  767. }
  768. // Slightly pad extents to avoid floating point error when fragment culling at edges.
  769. south -= CesiumMath.EPSILON5;
  770. west -= CesiumMath.EPSILON5;
  771. north += CesiumMath.EPSILON5;
  772. east += CesiumMath.EPSILON5;
  773. var longitudeRangeInverse = 1.0 / (east - west);
  774. var latitudeRangeInverse = 1.0 / (north - south);
  775. var attributes = {
  776. sphericalExtents: new GeometryInstanceAttribute({
  777. componentDatatype: ComponentDatatype.FLOAT,
  778. componentsPerAttribute: 4,
  779. normalize: false,
  780. value: [south, west, latitudeRangeInverse, longitudeRangeInverse],
  781. }),
  782. longitudeRotation: new GeometryInstanceAttribute({
  783. componentDatatype: ComponentDatatype.FLOAT,
  784. componentsPerAttribute: 1,
  785. normalize: false,
  786. value: [rotationRadians],
  787. }),
  788. };
  789. addTextureCoordinateRotationAttributes(
  790. attributes,
  791. textureCoordinateRotationPoints
  792. );
  793. add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
  794. return attributes;
  795. };
  796. ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function (
  797. attributes
  798. ) {
  799. return (
  800. defined(attributes.southWest_HIGH) &&
  801. defined(attributes.southWest_LOW) &&
  802. defined(attributes.northward) &&
  803. defined(attributes.eastward) &&
  804. defined(attributes.planes2D_HIGH) &&
  805. defined(attributes.planes2D_LOW) &&
  806. defined(attributes.uMaxVmax) &&
  807. defined(attributes.uvMinAndExtents)
  808. );
  809. };
  810. ShadowVolumeAppearance.hasAttributesForSphericalExtents = function (
  811. attributes
  812. ) {
  813. return (
  814. defined(attributes.sphericalExtents) &&
  815. defined(attributes.longitudeRotation) &&
  816. defined(attributes.planes2D_HIGH) &&
  817. defined(attributes.planes2D_LOW) &&
  818. defined(attributes.uMaxVmax) &&
  819. defined(attributes.uvMinAndExtents)
  820. );
  821. };
  822. function shouldUseSpherical(rectangle) {
  823. return (
  824. Math.max(rectangle.width, rectangle.height) >
  825. ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS
  826. );
  827. }
  828. /**
  829. * Computes whether the given rectangle is wide enough that texture coordinates
  830. * over its area should be computed using spherical extents instead of distance to planes.
  831. *
  832. * @param {Rectangle} rectangle A rectangle
  833. * @private
  834. */
  835. ShadowVolumeAppearance.shouldUseSphericalCoordinates = function (rectangle) {
  836. //>>includeStart('debug', pragmas.debug);
  837. Check.typeOf.object("rectangle", rectangle);
  838. //>>includeEnd('debug');
  839. return shouldUseSpherical(rectangle);
  840. };
  841. /**
  842. * Texture coordinates for ground primitives are computed either using spherical coordinates for large areas or
  843. * using distance from planes for small areas.
  844. *
  845. * @type {Number}
  846. * @constant
  847. * @private
  848. */
  849. ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0);
  850. export default ShadowVolumeAppearance;