ShadowVolumeAppearance.js 30 KB

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