智慧水务管理系统 - 精河县供水工程综合管理平台

ShadowVolumeAppearance.js 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  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 defined from "../Core/defined.js";
  7. import EncodedCartesian3 from "../Core/EncodedCartesian3.js";
  8. import GeometryInstanceAttribute from "../Core/GeometryInstanceAttribute.js";
  9. import CesiumMath from "../Core/Math.js";
  10. import Matrix4 from "../Core/Matrix4.js";
  11. import Rectangle from "../Core/Rectangle.js";
  12. import Transforms from "../Core/Transforms.js";
  13. import ShaderSource from "../Renderer/ShaderSource.js";
  14. import PerInstanceColorAppearance from "../Scene/PerInstanceColorAppearance.js";
  15. import ShadowVolumeAppearanceFS from "../Shaders/ShadowVolumeAppearanceFS.js";
  16. /**
  17. * Creates shaders for a ClassificationPrimitive to use a given Appearance, as well as for picking.
  18. *
  19. * @param {boolean} extentsCulling Discard fragments outside the instance's texture coordinate extents.
  20. * @param {boolean} planarExtents If true, texture coordinates will be computed using planes instead of spherical coordinates.
  21. * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive via GroundPrimitive.
  22. * @private
  23. */
  24. function ShadowVolumeAppearance(extentsCulling, planarExtents, appearance) {
  25. //>>includeStart('debug', pragmas.debug);
  26. Check.typeOf.bool("extentsCulling", extentsCulling);
  27. Check.typeOf.bool("planarExtents", planarExtents);
  28. Check.typeOf.object("appearance", appearance);
  29. //>>includeEnd('debug');
  30. this._projectionExtentDefines = {
  31. eastMostYhighDefine: "",
  32. eastMostYlowDefine: "",
  33. westMostYhighDefine: "",
  34. westMostYlowDefine: "",
  35. };
  36. // Compute shader dependencies
  37. const colorShaderDependencies = new ShaderDependencies();
  38. colorShaderDependencies.requiresTextureCoordinates = extentsCulling;
  39. colorShaderDependencies.requiresEC = !appearance.flat;
  40. const pickShaderDependencies = new ShaderDependencies();
  41. pickShaderDependencies.requiresTextureCoordinates = extentsCulling;
  42. if (appearance instanceof PerInstanceColorAppearance) {
  43. // PerInstanceColorAppearance doesn't have material.shaderSource, instead it has its own vertex and fragment shaders
  44. colorShaderDependencies.requiresNormalEC = !appearance.flat;
  45. } else {
  46. // Scan material source for what hookups are needed. Assume czm_materialInput materialInput.
  47. const materialShaderSource = `${appearance.material.shaderSource}\n${appearance.fragmentShaderSource}`;
  48. colorShaderDependencies.normalEC =
  49. materialShaderSource.indexOf("materialInput.normalEC") !== -1 ||
  50. materialShaderSource.indexOf("czm_getDefaultMaterial") !== -1;
  51. colorShaderDependencies.positionToEyeEC =
  52. materialShaderSource.indexOf("materialInput.positionToEyeEC") !== -1;
  53. colorShaderDependencies.tangentToEyeMatrix =
  54. materialShaderSource.indexOf("materialInput.tangentToEyeMatrix") !== -1;
  55. colorShaderDependencies.st =
  56. materialShaderSource.indexOf("materialInput.st") !== -1;
  57. }
  58. this._colorShaderDependencies = colorShaderDependencies;
  59. this._pickShaderDependencies = pickShaderDependencies;
  60. this._appearance = appearance;
  61. this._extentsCulling = extentsCulling;
  62. this._planarExtents = planarExtents;
  63. }
  64. /**
  65. * Create the fragment shader for a ClassificationPrimitive's color pass when rendering for color.
  66. *
  67. * @param {boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  68. * @returns {ShaderSource} Shader source for the fragment shader.
  69. */
  70. ShadowVolumeAppearance.prototype.createFragmentShader = function (
  71. columbusView2D,
  72. ) {
  73. //>>includeStart('debug', pragmas.debug);
  74. Check.typeOf.bool("columbusView2D", columbusView2D);
  75. //>>includeEnd('debug');
  76. const appearance = this._appearance;
  77. const dependencies = this._colorShaderDependencies;
  78. const defines = [];
  79. if (!columbusView2D && !this._planarExtents) {
  80. defines.push("SPHERICAL");
  81. }
  82. if (dependencies.requiresEC) {
  83. defines.push("REQUIRES_EC");
  84. }
  85. if (dependencies.requiresWC) {
  86. defines.push("REQUIRES_WC");
  87. }
  88. if (dependencies.requiresTextureCoordinates) {
  89. defines.push("TEXTURE_COORDINATES");
  90. }
  91. if (this._extentsCulling) {
  92. defines.push("CULL_FRAGMENTS");
  93. }
  94. if (dependencies.requiresNormalEC) {
  95. defines.push("NORMAL_EC");
  96. }
  97. if (appearance instanceof PerInstanceColorAppearance) {
  98. defines.push("PER_INSTANCE_COLOR");
  99. }
  100. // Material inputs. Use of parameters in the material is different
  101. // from requirement of the parameters in the overall shader, for example,
  102. // texture coordinates may be used for fragment culling but not for the material itself.
  103. if (dependencies.normalEC) {
  104. defines.push("USES_NORMAL_EC");
  105. }
  106. if (dependencies.positionToEyeEC) {
  107. defines.push("USES_POSITION_TO_EYE_EC");
  108. }
  109. if (dependencies.tangentToEyeMatrix) {
  110. defines.push("USES_TANGENT_TO_EYE");
  111. }
  112. if (dependencies.st) {
  113. defines.push("USES_ST");
  114. }
  115. if (appearance.flat) {
  116. defines.push("FLAT");
  117. }
  118. let materialSource = "";
  119. if (!(appearance instanceof PerInstanceColorAppearance)) {
  120. materialSource = appearance.material.shaderSource;
  121. }
  122. return new ShaderSource({
  123. defines: defines,
  124. sources: [materialSource, ShadowVolumeAppearanceFS],
  125. });
  126. };
  127. ShadowVolumeAppearance.prototype.createPickFragmentShader = function (
  128. columbusView2D,
  129. ) {
  130. //>>includeStart('debug', pragmas.debug);
  131. Check.typeOf.bool("columbusView2D", columbusView2D);
  132. //>>includeEnd('debug');
  133. const dependencies = this._pickShaderDependencies;
  134. const defines = ["PICK"];
  135. if (!columbusView2D && !this._planarExtents) {
  136. defines.push("SPHERICAL");
  137. }
  138. if (dependencies.requiresEC) {
  139. defines.push("REQUIRES_EC");
  140. }
  141. if (dependencies.requiresWC) {
  142. defines.push("REQUIRES_WC");
  143. }
  144. if (dependencies.requiresTextureCoordinates) {
  145. defines.push("TEXTURE_COORDINATES");
  146. }
  147. if (this._extentsCulling) {
  148. defines.push("CULL_FRAGMENTS");
  149. }
  150. return new ShaderSource({
  151. defines: defines,
  152. sources: [ShadowVolumeAppearanceFS],
  153. pickColorQualifier: "in",
  154. });
  155. };
  156. /**
  157. * Create the vertex shader for a ClassificationPrimitive's color pass on the final of 3 shadow volume passes
  158. *
  159. * @param {string[]} defines External defines to pass to the vertex shader.
  160. * @param {string} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position.
  161. * @param {boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  162. * @param {MapProjection} mapProjection Current scene's map projection.
  163. * @returns {string} Shader source for the vertex shader.
  164. */
  165. ShadowVolumeAppearance.prototype.createVertexShader = function (
  166. defines,
  167. vertexShaderSource,
  168. columbusView2D,
  169. mapProjection,
  170. ) {
  171. //>>includeStart('debug', pragmas.debug);
  172. Check.defined("defines", defines);
  173. Check.typeOf.string("vertexShaderSource", vertexShaderSource);
  174. Check.typeOf.bool("columbusView2D", columbusView2D);
  175. Check.defined("mapProjection", mapProjection);
  176. //>>includeEnd('debug');
  177. return createShadowVolumeAppearanceVS(
  178. this._colorShaderDependencies,
  179. this._planarExtents,
  180. columbusView2D,
  181. defines,
  182. vertexShaderSource,
  183. this._appearance,
  184. mapProjection,
  185. this._projectionExtentDefines,
  186. );
  187. };
  188. /**
  189. * Create the vertex shader for a ClassificationPrimitive's pick pass on the final of 3 shadow volume passes
  190. *
  191. * @param {string[]} defines External defines to pass to the vertex shader.
  192. * @param {string} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position and picking.
  193. * @param {boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
  194. * @param {MapProjection} mapProjection Current scene's map projection.
  195. * @returns {string} Shader source for the vertex shader.
  196. */
  197. ShadowVolumeAppearance.prototype.createPickVertexShader = function (
  198. defines,
  199. vertexShaderSource,
  200. columbusView2D,
  201. mapProjection,
  202. ) {
  203. //>>includeStart('debug', pragmas.debug);
  204. Check.defined("defines", defines);
  205. Check.typeOf.string("vertexShaderSource", vertexShaderSource);
  206. Check.typeOf.bool("columbusView2D", columbusView2D);
  207. Check.defined("mapProjection", mapProjection);
  208. //>>includeEnd('debug');
  209. return createShadowVolumeAppearanceVS(
  210. this._pickShaderDependencies,
  211. this._planarExtents,
  212. columbusView2D,
  213. defines,
  214. vertexShaderSource,
  215. undefined,
  216. mapProjection,
  217. this._projectionExtentDefines,
  218. );
  219. };
  220. const longitudeExtentsCartesianScratch = new Cartesian3();
  221. const longitudeExtentsCartographicScratch = new Cartographic();
  222. const longitudeExtentsEncodeScratch = {
  223. high: 0.0,
  224. low: 0.0,
  225. };
  226. function createShadowVolumeAppearanceVS(
  227. shaderDependencies,
  228. planarExtents,
  229. columbusView2D,
  230. defines,
  231. vertexShaderSource,
  232. appearance,
  233. mapProjection,
  234. projectionExtentDefines,
  235. ) {
  236. const allDefines = defines.slice();
  237. if (projectionExtentDefines.eastMostYhighDefine === "") {
  238. const eastMostCartographic = longitudeExtentsCartographicScratch;
  239. eastMostCartographic.longitude = CesiumMath.PI;
  240. eastMostCartographic.latitude = 0.0;
  241. eastMostCartographic.height = 0.0;
  242. const eastMostCartesian = mapProjection.project(
  243. eastMostCartographic,
  244. longitudeExtentsCartesianScratch,
  245. );
  246. let encoded = EncodedCartesian3.encode(
  247. eastMostCartesian.x,
  248. longitudeExtentsEncodeScratch,
  249. );
  250. projectionExtentDefines.eastMostYhighDefine = `EAST_MOST_X_HIGH ${encoded.high.toFixed(
  251. `${encoded.high}`.length + 1,
  252. )}`;
  253. projectionExtentDefines.eastMostYlowDefine = `EAST_MOST_X_LOW ${encoded.low.toFixed(
  254. `${encoded.low}`.length + 1,
  255. )}`;
  256. const westMostCartographic = longitudeExtentsCartographicScratch;
  257. westMostCartographic.longitude = -CesiumMath.PI;
  258. westMostCartographic.latitude = 0.0;
  259. westMostCartographic.height = 0.0;
  260. const westMostCartesian = mapProjection.project(
  261. westMostCartographic,
  262. longitudeExtentsCartesianScratch,
  263. );
  264. encoded = EncodedCartesian3.encode(
  265. westMostCartesian.x,
  266. longitudeExtentsEncodeScratch,
  267. );
  268. projectionExtentDefines.westMostYhighDefine = `WEST_MOST_X_HIGH ${encoded.high.toFixed(
  269. `${encoded.high}`.length + 1,
  270. )}`;
  271. projectionExtentDefines.westMostYlowDefine = `WEST_MOST_X_LOW ${encoded.low.toFixed(
  272. `${encoded.low}`.length + 1,
  273. )}`;
  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. const 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. const points2D = points2DScratch;
  412. const minXYCorner = Cartesian2.unpack(
  413. textureCoordinateRotationPoints,
  414. 0,
  415. points2D[0],
  416. );
  417. const maxYCorner = Cartesian2.unpack(
  418. textureCoordinateRotationPoints,
  419. 2,
  420. points2D[1],
  421. );
  422. const 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. const inverseExtentX =
  434. 1.0 / pointLineDistance(minXYCorner, maxYCorner, maxXCorner);
  435. const 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. const cartographicScratch = new Cartographic();
  445. const cornerScratch = new Cartesian3();
  446. const northWestScratch = new Cartesian3();
  447. const southEastScratch = new Cartesian3();
  448. const highLowScratch = { high: 0.0, low: 0.0 };
  449. function add2DTextureCoordinateAttributes(rectangle, projection, attributes) {
  450. // Compute corner positions in double precision
  451. const carto = cartographicScratch;
  452. carto.height = 0.0;
  453. carto.longitude = rectangle.west;
  454. carto.latitude = rectangle.south;
  455. const southWestCorner = projection.project(carto, cornerScratch);
  456. carto.latitude = rectangle.north;
  457. const northWest = projection.project(carto, northWestScratch);
  458. carto.longitude = rectangle.east;
  459. carto.latitude = rectangle.south;
  460. const 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. const valuesHigh = [0, 0, 0, 0];
  469. const valuesLow = [0, 0, 0, 0];
  470. let 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. const enuMatrixScratch = new Matrix4();
  496. const inverseEnuScratch = new Matrix4();
  497. const rectanglePointCartesianScratch = new Cartesian3();
  498. const rectangleCenterScratch = new Cartographic();
  499. const 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. const centerCartographic = Rectangle.center(
  528. rectangle,
  529. rectangleCenterScratch,
  530. );
  531. centerCartographic.height = height;
  532. const centerCartesian = Cartographic.toCartesian(
  533. centerCartographic,
  534. ellipsoid,
  535. rectanglePointCartesianScratch,
  536. );
  537. const enuMatrix = Transforms.eastNorthUpToFixedFrame(
  538. centerCartesian,
  539. ellipsoid,
  540. enuMatrixScratch,
  541. );
  542. const inverseEnu = Matrix4.inverse(enuMatrix, inverseEnuScratch);
  543. const west = rectangle.west;
  544. const east = rectangle.east;
  545. const north = rectangle.north;
  546. const south = rectangle.south;
  547. const cartographics = pointsCartographicScratch;
  548. cartographics[0].latitude = south;
  549. cartographics[0].longitude = west;
  550. cartographics[1].latitude = north;
  551. cartographics[1].longitude = west;
  552. cartographics[2].latitude = north;
  553. cartographics[2].longitude = east;
  554. cartographics[3].latitude = south;
  555. cartographics[3].longitude = east;
  556. const longitudeCenter = (west + east) * 0.5;
  557. const latitudeCenter = (north + south) * 0.5;
  558. cartographics[4].latitude = south;
  559. cartographics[4].longitude = longitudeCenter;
  560. cartographics[5].latitude = north;
  561. cartographics[5].longitude = longitudeCenter;
  562. cartographics[6].latitude = latitudeCenter;
  563. cartographics[6].longitude = west;
  564. cartographics[7].latitude = latitudeCenter;
  565. cartographics[7].longitude = east;
  566. let minX = Number.POSITIVE_INFINITY;
  567. let maxX = Number.NEGATIVE_INFINITY;
  568. let minY = Number.POSITIVE_INFINITY;
  569. let maxY = Number.NEGATIVE_INFINITY;
  570. for (let i = 0; i < 8; i++) {
  571. cartographics[i].height = height;
  572. const pointCartesian = Cartographic.toCartesian(
  573. cartographics[i],
  574. ellipsoid,
  575. rectanglePointCartesianScratch,
  576. );
  577. Matrix4.multiplyByPoint(inverseEnu, pointCartesian, pointCartesian);
  578. pointCartesian.z = 0.0; // flatten into XY plane of ENU coordinate system
  579. minX = Math.min(minX, pointCartesian.x);
  580. maxX = Math.max(maxX, pointCartesian.x);
  581. minY = Math.min(minY, pointCartesian.y);
  582. maxY = Math.max(maxY, pointCartesian.y);
  583. }
  584. const southWestCorner = southWestCornerResult;
  585. southWestCorner.x = minX;
  586. southWestCorner.y = minY;
  587. southWestCorner.z = 0.0;
  588. Matrix4.multiplyByPoint(enuMatrix, southWestCorner, southWestCorner);
  589. const southEastCorner = eastVectorResult;
  590. southEastCorner.x = maxX;
  591. southEastCorner.y = minY;
  592. southEastCorner.z = 0.0;
  593. Matrix4.multiplyByPoint(enuMatrix, southEastCorner, southEastCorner);
  594. // make eastward vector
  595. Cartesian3.subtract(southEastCorner, southWestCorner, eastVectorResult);
  596. const northWestCorner = northVectorResult;
  597. northWestCorner.x = minX;
  598. northWestCorner.y = maxY;
  599. northWestCorner.z = 0.0;
  600. Matrix4.multiplyByPoint(enuMatrix, northWestCorner, northWestCorner);
  601. // make eastward vector
  602. Cartesian3.subtract(northWestCorner, southWestCorner, northVectorResult);
  603. }
  604. const eastwardScratch = new Cartesian3();
  605. const northwardScratch = new Cartesian3();
  606. const encodeScratch = new EncodedCartesian3();
  607. /**
  608. * Gets an attributes object containing:
  609. * - 3 high-precision points as 6 GeometryInstanceAttributes. These points are used to compute eye-space planes.
  610. * - 1 texture coordinate rotation GeometryInstanceAttributes
  611. * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
  612. * These points are used to compute eye-space planes like above.
  613. *
  614. * Used to compute texture coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances.
  615. *
  616. * @see ShadowVolumeAppearance
  617. * @private
  618. *
  619. * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound
  620. * @param {number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
  621. * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
  622. * @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
  623. * @param {number} [height=0] The maximum height for the shadow volume.
  624. * @returns {object} An attributes dictionary containing planar texture coordinate attributes.
  625. */
  626. ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function (
  627. boundingRectangle,
  628. textureCoordinateRotationPoints,
  629. ellipsoid,
  630. projection,
  631. height,
  632. ) {
  633. //>>includeStart('debug', pragmas.debug);
  634. Check.typeOf.object("boundingRectangle", boundingRectangle);
  635. Check.defined(
  636. "textureCoordinateRotationPoints",
  637. textureCoordinateRotationPoints,
  638. );
  639. Check.typeOf.object("ellipsoid", ellipsoid);
  640. Check.typeOf.object("projection", projection);
  641. //>>includeEnd('debug');
  642. const corner = cornerScratch;
  643. const eastward = eastwardScratch;
  644. const northward = northwardScratch;
  645. computeRectangleBounds(
  646. boundingRectangle,
  647. ellipsoid,
  648. height ?? 0.0,
  649. corner,
  650. eastward,
  651. northward,
  652. );
  653. const attributes = {};
  654. addTextureCoordinateRotationAttributes(
  655. attributes,
  656. textureCoordinateRotationPoints,
  657. );
  658. const encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch);
  659. attributes.southWest_HIGH = new GeometryInstanceAttribute({
  660. componentDatatype: ComponentDatatype.FLOAT,
  661. componentsPerAttribute: 3,
  662. normalize: false,
  663. value: Cartesian3.pack(encoded.high, [0, 0, 0]),
  664. });
  665. attributes.southWest_LOW = new GeometryInstanceAttribute({
  666. componentDatatype: ComponentDatatype.FLOAT,
  667. componentsPerAttribute: 3,
  668. normalize: false,
  669. value: Cartesian3.pack(encoded.low, [0, 0, 0]),
  670. });
  671. attributes.eastward = new GeometryInstanceAttribute({
  672. componentDatatype: ComponentDatatype.FLOAT,
  673. componentsPerAttribute: 3,
  674. normalize: false,
  675. value: Cartesian3.pack(eastward, [0, 0, 0]),
  676. });
  677. attributes.northward = new GeometryInstanceAttribute({
  678. componentDatatype: ComponentDatatype.FLOAT,
  679. componentsPerAttribute: 3,
  680. normalize: false,
  681. value: Cartesian3.pack(northward, [0, 0, 0]),
  682. });
  683. add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
  684. return attributes;
  685. };
  686. const spherePointScratch = new Cartesian3();
  687. function latLongToSpherical(latitude, longitude, ellipsoid, result) {
  688. const cartographic = cartographicScratch;
  689. cartographic.latitude = latitude;
  690. cartographic.longitude = longitude;
  691. cartographic.height = 0.0;
  692. const spherePoint = Cartographic.toCartesian(
  693. cartographic,
  694. ellipsoid,
  695. spherePointScratch,
  696. );
  697. // Project into plane with vertical for latitude
  698. const magXY = Math.sqrt(
  699. spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y,
  700. );
  701. // Use fastApproximateAtan2 for alignment with shader
  702. const sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);
  703. const sphereLongitude = CesiumMath.fastApproximateAtan2(
  704. spherePoint.x,
  705. spherePoint.y,
  706. );
  707. result.x = sphereLatitude;
  708. result.y = sphereLongitude;
  709. return result;
  710. }
  711. const sphericalScratch = new Cartesian2();
  712. /**
  713. * Gets an attributes object containing:
  714. * - the southwest corner of a rectangular area in spherical coordinates, as well as the inverse of the latitude/longitude range.
  715. * These are computed using the same atan2 approximation used in the shader.
  716. * - 1 texture coordinate rotation GeometryInstanceAttributes
  717. * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
  718. * These points are used to compute eye-space planes like above.
  719. *
  720. * Used when computing texture coordinates for large-area ClassificationPrimitives with materials or
  721. * multiple non-overlapping instances.
  722. * @see ShadowVolumeAppearance
  723. * @private
  724. *
  725. * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound
  726. * @param {number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
  727. * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
  728. * @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
  729. * @returns {object} An attributes dictionary containing spherical texture coordinate attributes.
  730. */
  731. ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function (
  732. boundingRectangle,
  733. textureCoordinateRotationPoints,
  734. ellipsoid,
  735. projection,
  736. ) {
  737. //>>includeStart('debug', pragmas.debug);
  738. Check.typeOf.object("boundingRectangle", boundingRectangle);
  739. Check.defined(
  740. "textureCoordinateRotationPoints",
  741. textureCoordinateRotationPoints,
  742. );
  743. Check.typeOf.object("ellipsoid", ellipsoid);
  744. Check.typeOf.object("projection", projection);
  745. //>>includeEnd('debug');
  746. // rectangle cartographic coords !== spherical because it's on an ellipsoid
  747. const southWestExtents = latLongToSpherical(
  748. boundingRectangle.south,
  749. boundingRectangle.west,
  750. ellipsoid,
  751. sphericalScratch,
  752. );
  753. let south = southWestExtents.x;
  754. let west = southWestExtents.y;
  755. const northEastExtents = latLongToSpherical(
  756. boundingRectangle.north,
  757. boundingRectangle.east,
  758. ellipsoid,
  759. sphericalScratch,
  760. );
  761. let north = northEastExtents.x;
  762. let east = northEastExtents.y;
  763. // If the bounding rectangle crosses the IDL, rotate the spherical extents so the cross no longer happens.
  764. // This rotation must happen in the shader too.
  765. let rotationRadians = 0.0;
  766. if (west > east) {
  767. rotationRadians = CesiumMath.PI - west;
  768. west = -CesiumMath.PI;
  769. east += rotationRadians;
  770. }
  771. // Slightly pad extents to avoid floating point error when fragment culling at edges.
  772. south -= CesiumMath.EPSILON5;
  773. west -= CesiumMath.EPSILON5;
  774. north += CesiumMath.EPSILON5;
  775. east += CesiumMath.EPSILON5;
  776. const longitudeRangeInverse = 1.0 / (east - west);
  777. const latitudeRangeInverse = 1.0 / (north - south);
  778. const attributes = {
  779. sphericalExtents: new GeometryInstanceAttribute({
  780. componentDatatype: ComponentDatatype.FLOAT,
  781. componentsPerAttribute: 4,
  782. normalize: false,
  783. value: [south, west, latitudeRangeInverse, longitudeRangeInverse],
  784. }),
  785. longitudeRotation: new GeometryInstanceAttribute({
  786. componentDatatype: ComponentDatatype.FLOAT,
  787. componentsPerAttribute: 1,
  788. normalize: false,
  789. value: [rotationRadians],
  790. }),
  791. };
  792. addTextureCoordinateRotationAttributes(
  793. attributes,
  794. textureCoordinateRotationPoints,
  795. );
  796. add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
  797. return attributes;
  798. };
  799. ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function (
  800. attributes,
  801. ) {
  802. return (
  803. defined(attributes.southWest_HIGH) &&
  804. defined(attributes.southWest_LOW) &&
  805. defined(attributes.northward) &&
  806. defined(attributes.eastward) &&
  807. defined(attributes.planes2D_HIGH) &&
  808. defined(attributes.planes2D_LOW) &&
  809. defined(attributes.uMaxVmax) &&
  810. defined(attributes.uvMinAndExtents)
  811. );
  812. };
  813. ShadowVolumeAppearance.hasAttributesForSphericalExtents = function (
  814. attributes,
  815. ) {
  816. return (
  817. defined(attributes.sphericalExtents) &&
  818. defined(attributes.longitudeRotation) &&
  819. defined(attributes.planes2D_HIGH) &&
  820. defined(attributes.planes2D_LOW) &&
  821. defined(attributes.uMaxVmax) &&
  822. defined(attributes.uvMinAndExtents)
  823. );
  824. };
  825. function shouldUseSpherical(rectangle) {
  826. return (
  827. Math.max(rectangle.width, rectangle.height) >
  828. ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS
  829. );
  830. }
  831. /**
  832. * Computes whether the given rectangle is wide enough that texture coordinates
  833. * over its area should be computed using spherical extents instead of distance to planes.
  834. *
  835. * @param {Rectangle} rectangle A rectangle
  836. * @private
  837. */
  838. ShadowVolumeAppearance.shouldUseSphericalCoordinates = function (rectangle) {
  839. //>>includeStart('debug', pragmas.debug);
  840. Check.typeOf.object("rectangle", rectangle);
  841. //>>includeEnd('debug');
  842. return shouldUseSpherical(rectangle);
  843. };
  844. /**
  845. * Texture coordinates for ground primitives are computed either using spherical coordinates for large areas or
  846. * using distance from planes for small areas.
  847. *
  848. * @type {number}
  849. * @constant
  850. * @private
  851. */
  852. ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0);
  853. export default ShadowVolumeAppearance;