ShadowVolumeAppearance.js 40 KB

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