TerrainFillMesh.js 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. import AttributeCompression from '../Core/AttributeCompression.js';
  2. import binarySearch from '../Core/binarySearch.js';
  3. import BoundingSphere from '../Core/BoundingSphere.js';
  4. import Cartesian2 from '../Core/Cartesian2.js';
  5. import Cartesian3 from '../Core/Cartesian3.js';
  6. import Cartesian4 from '../Core/Cartesian4.js';
  7. import Cartographic from '../Core/Cartographic.js';
  8. import defined from '../Core/defined.js';
  9. import DeveloperError from '../Core/DeveloperError.js';
  10. import HeightmapTerrainData from '../Core/HeightmapTerrainData.js';
  11. import CesiumMath from '../Core/Math.js';
  12. import OrientedBoundingBox from '../Core/OrientedBoundingBox.js';
  13. import Queue from '../Core/Queue.js';
  14. import Rectangle from '../Core/Rectangle.js';
  15. import TerrainEncoding from '../Core/TerrainEncoding.js';
  16. import TerrainMesh from '../Core/TerrainMesh.js';
  17. import TileEdge from '../Core/TileEdge.js';
  18. import WebMercatorProjection from '../Core/WebMercatorProjection.js';
  19. import GlobeSurfaceTile from './GlobeSurfaceTile.js';
  20. import TileSelectionResult from './TileSelectionResult.js';
  21. function TerrainFillMesh(tile) {
  22. this.tile = tile;
  23. this.frameLastUpdated = undefined;
  24. this.westMeshes = []; // north to south (CCW)
  25. this.westTiles = [];
  26. this.southMeshes = []; // west to east (CCW)
  27. this.southTiles = [];
  28. this.eastMeshes = []; // south to north (CCW)
  29. this.eastTiles = [];
  30. this.northMeshes = []; // east to west (CCW)
  31. this.northTiles = [];
  32. this.southwestMesh = undefined;
  33. this.southwestTile = undefined;
  34. this.southeastMesh = undefined;
  35. this.southeastTile = undefined;
  36. this.northwestMesh = undefined;
  37. this.northwestTile = undefined;
  38. this.northeastMesh = undefined;
  39. this.northeastTile = undefined;
  40. this.changedThisFrame = true;
  41. this.visitedFrame = undefined;
  42. this.enqueuedFrame = undefined;
  43. this.mesh = undefined;
  44. this.vertexArray = undefined;
  45. this.waterMaskTexture = undefined;
  46. this.waterMaskTranslationAndScale = new Cartesian4();
  47. }
  48. TerrainFillMesh.prototype.update = function(tileProvider, frameState, vertexArraysToDestroy) {
  49. if (this.changedThisFrame) {
  50. createFillMesh(tileProvider, frameState, this.tile, vertexArraysToDestroy);
  51. this.changedThisFrame = false;
  52. }
  53. };
  54. TerrainFillMesh.prototype.destroy = function(vertexArraysToDestroy) {
  55. if (defined(this.vertexArray)) {
  56. if (defined(vertexArraysToDestroy)) {
  57. vertexArraysToDestroy.push(this.vertexArray);
  58. } else {
  59. GlobeSurfaceTile._freeVertexArray(this.vertexArray, vertexArraysToDestroy);
  60. }
  61. this.vertexArray = undefined;
  62. }
  63. if (defined(this.waterMaskTexture)) {
  64. --this.waterMaskTexture.referenceCount;
  65. if (this.waterMaskTexture.referenceCount === 0) {
  66. this.waterMaskTexture.destroy();
  67. }
  68. this.waterMaskTexture = undefined;
  69. }
  70. return undefined;
  71. };
  72. var traversalQueueScratch = new Queue();
  73. TerrainFillMesh.updateFillTiles = function(tileProvider, renderedTiles, frameState, vertexArraysToDestroy) {
  74. // We want our fill tiles to look natural, which means they should align perfectly with
  75. // adjacent loaded tiles, and their edges that are not adjacent to loaded tiles should have
  76. // sensible heights (e.g. the average of the heights of loaded edges). Some fill tiles may
  77. // be adjacent only to other fill tiles, and in that case heights should be assigned fanning
  78. // outward from the loaded tiles so that there are no sudden changes in height.
  79. // We do this with a breadth-first traversal of the rendered tiles, starting with the loaded
  80. // ones. Graph nodes are tiles and graph edges connect to other rendered tiles that are spatially adjacent
  81. // to those tiles. As we visit each node, we propagate tile edges to adjacent tiles. If there's no data
  82. // for a tile edge, we create an edge with an average height and then propagate it. If an edge is partially defined
  83. // (e.g. an edge is adjacent to multiple more-detailed tiles and only some of them are loaded), we
  84. // fill in the rest of the edge with the same height.
  85. var quadtree = tileProvider._quadtree;
  86. var levelZeroTiles = quadtree._levelZeroTiles;
  87. var lastSelectionFrameNumber = quadtree._lastSelectionFrameNumber;
  88. var traversalQueue = traversalQueueScratch;
  89. traversalQueue.clear();
  90. // Add the tiles with real geometry to the traversal queue.
  91. for (var i = 0; i < renderedTiles.length; ++i) {
  92. var renderedTile = renderedTiles[i];
  93. if (defined(renderedTile.data.vertexArray)) {
  94. traversalQueue.enqueue(renderedTiles[i]);
  95. }
  96. }
  97. var tile = traversalQueue.dequeue();
  98. while (tile !== undefined) {
  99. var tileToWest = tile.findTileToWest(levelZeroTiles);
  100. var tileToSouth = tile.findTileToSouth(levelZeroTiles);
  101. var tileToEast = tile.findTileToEast(levelZeroTiles);
  102. var tileToNorth = tile.findTileToNorth(levelZeroTiles);
  103. visitRenderedTiles(tileProvider, frameState, tile, tileToWest, lastSelectionFrameNumber, TileEdge.EAST, false, traversalQueue, vertexArraysToDestroy);
  104. visitRenderedTiles(tileProvider, frameState, tile, tileToSouth, lastSelectionFrameNumber, TileEdge.NORTH, false, traversalQueue, vertexArraysToDestroy);
  105. visitRenderedTiles(tileProvider, frameState, tile, tileToEast, lastSelectionFrameNumber, TileEdge.WEST, false, traversalQueue, vertexArraysToDestroy);
  106. visitRenderedTiles(tileProvider, frameState, tile, tileToNorth, lastSelectionFrameNumber, TileEdge.SOUTH, false, traversalQueue, vertexArraysToDestroy);
  107. var tileToNorthwest = tileToWest.findTileToNorth(levelZeroTiles);
  108. var tileToSouthwest = tileToWest.findTileToSouth(levelZeroTiles);
  109. var tileToNortheast = tileToEast.findTileToNorth(levelZeroTiles);
  110. var tileToSoutheast = tileToEast.findTileToSouth(levelZeroTiles);
  111. visitRenderedTiles(tileProvider, frameState, tile, tileToNorthwest, lastSelectionFrameNumber, TileEdge.SOUTHEAST, false, traversalQueue, vertexArraysToDestroy);
  112. visitRenderedTiles(tileProvider, frameState, tile, tileToNortheast, lastSelectionFrameNumber, TileEdge.SOUTHWEST, false, traversalQueue, vertexArraysToDestroy);
  113. visitRenderedTiles(tileProvider, frameState, tile, tileToSouthwest, lastSelectionFrameNumber, TileEdge.NORTHEAST, false, traversalQueue, vertexArraysToDestroy);
  114. visitRenderedTiles(tileProvider, frameState, tile, tileToSoutheast, lastSelectionFrameNumber, TileEdge.NORTHWEST, false, traversalQueue, vertexArraysToDestroy);
  115. tile = traversalQueue.dequeue();
  116. }
  117. };
  118. function visitRenderedTiles(tileProvider, frameState, sourceTile, startTile, currentFrameNumber, tileEdge, downOnly, traversalQueue, vertexArraysToDestroy) {
  119. if (startTile === undefined) {
  120. // There are no tiles North or South of the poles.
  121. return;
  122. }
  123. var tile = startTile;
  124. while (tile && (tile._lastSelectionResultFrame !== currentFrameNumber || TileSelectionResult.wasKicked(tile._lastSelectionResult) || TileSelectionResult.originalResult(tile._lastSelectionResult) === TileSelectionResult.CULLED)) {
  125. // This tile wasn't visited or it was visited and then kicked, so walk up to find the closest ancestor that was rendered.
  126. // We also walk up if the tile was culled, because if siblings were kicked an ancestor may have been rendered.
  127. if (downOnly) {
  128. return;
  129. }
  130. var parent = tile.parent;
  131. if (tileEdge >= TileEdge.NORTHWEST && parent !== undefined) {
  132. // When we're looking for a corner, verify that the parent tile is still relevant.
  133. // That is, the parent and child must share the corner in question.
  134. switch (tileEdge) {
  135. case TileEdge.NORTHWEST:
  136. tile = tile === parent.northwestChild ? parent : undefined;
  137. break;
  138. case TileEdge.NORTHEAST:
  139. tile = tile === parent.northeastChild ? parent : undefined;
  140. break;
  141. case TileEdge.SOUTHWEST:
  142. tile = tile === parent.southwestChild ? parent : undefined;
  143. break;
  144. case TileEdge.SOUTHEAST:
  145. tile = tile === parent.southeastChild ? parent : undefined;
  146. break;
  147. }
  148. } else {
  149. tile = parent;
  150. }
  151. }
  152. if (tile === undefined) {
  153. return;
  154. }
  155. if (tile._lastSelectionResult === TileSelectionResult.RENDERED) {
  156. if (defined(tile.data.vertexArray)) {
  157. // No further processing necessary for renderable tiles.
  158. return;
  159. }
  160. visitTile(tileProvider, frameState, sourceTile, tile, tileEdge, currentFrameNumber, traversalQueue, vertexArraysToDestroy);
  161. return;
  162. }
  163. if (TileSelectionResult.originalResult(startTile._lastSelectionResult) === TileSelectionResult.CULLED) {
  164. return;
  165. }
  166. // This tile was refined, so find rendered children, if any.
  167. // Visit the tiles in counter-clockwise order.
  168. switch (tileEdge) {
  169. case TileEdge.WEST:
  170. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  171. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  172. break;
  173. case TileEdge.EAST:
  174. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  175. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  176. break;
  177. case TileEdge.SOUTH:
  178. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  179. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  180. break;
  181. case TileEdge.NORTH:
  182. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  183. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  184. break;
  185. case TileEdge.NORTHWEST:
  186. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  187. break;
  188. case TileEdge.NORTHEAST:
  189. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.northeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  190. break;
  191. case TileEdge.SOUTHWEST:
  192. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southwestChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  193. break;
  194. case TileEdge.SOUTHEAST:
  195. visitRenderedTiles(tileProvider, frameState, sourceTile, startTile.southeastChild, currentFrameNumber, tileEdge, true, traversalQueue, vertexArraysToDestroy);
  196. break;
  197. default:
  198. throw new DeveloperError('Invalid edge');
  199. }
  200. }
  201. function visitTile(tileProvider, frameState, sourceTile, destinationTile, tileEdge, frameNumber, traversalQueue, vertexArraysToDestroy) {
  202. var destinationSurfaceTile = destinationTile.data;
  203. if (destinationSurfaceTile.fill === undefined) {
  204. destinationSurfaceTile.fill = new TerrainFillMesh(destinationTile);
  205. } else if (destinationSurfaceTile.fill.visitedFrame === frameNumber) {
  206. // Don't propagate edges to tiles that have already been visited this frame.
  207. return;
  208. }
  209. if (destinationSurfaceTile.fill.enqueuedFrame !== frameNumber) {
  210. // First time visiting this tile this frame, add it to the traversal queue.
  211. destinationSurfaceTile.fill.enqueuedFrame = frameNumber;
  212. destinationSurfaceTile.fill.changedThisFrame = false;
  213. traversalQueue.enqueue(destinationTile);
  214. }
  215. propagateEdge(tileProvider, frameState, sourceTile, destinationTile, tileEdge, vertexArraysToDestroy);
  216. }
  217. function propagateEdge(tileProvider, frameState, sourceTile, destinationTile, tileEdge, vertexArraysToDestroy) {
  218. var destinationFill = destinationTile.data.fill;
  219. var sourceMesh;
  220. var sourceFill = sourceTile.data.fill;
  221. if (defined(sourceFill)) {
  222. sourceFill.visitedFrame = frameState.frameNumber;
  223. // Source is a fill, create/update it if necessary.
  224. if (sourceFill.changedThisFrame) {
  225. createFillMesh(tileProvider, frameState, sourceTile, vertexArraysToDestroy);
  226. sourceFill.changedThisFrame = false;
  227. }
  228. sourceMesh = sourceTile.data.fill.mesh;
  229. } else {
  230. sourceMesh = sourceTile.data.mesh;
  231. }
  232. var edgeMeshes;
  233. var edgeTiles;
  234. switch (tileEdge) {
  235. case TileEdge.WEST:
  236. edgeMeshes = destinationFill.westMeshes;
  237. edgeTiles = destinationFill.westTiles;
  238. break;
  239. case TileEdge.SOUTH:
  240. edgeMeshes = destinationFill.southMeshes;
  241. edgeTiles = destinationFill.southTiles;
  242. break;
  243. case TileEdge.EAST:
  244. edgeMeshes = destinationFill.eastMeshes;
  245. edgeTiles = destinationFill.eastTiles;
  246. break;
  247. case TileEdge.NORTH:
  248. edgeMeshes = destinationFill.northMeshes;
  249. edgeTiles = destinationFill.northTiles;
  250. break;
  251. // Corners are simpler.
  252. case TileEdge.NORTHWEST:
  253. destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.northwestMesh !== sourceMesh;
  254. destinationFill.northwestMesh = sourceMesh;
  255. destinationFill.northwestTile = sourceTile;
  256. return;
  257. case TileEdge.NORTHEAST:
  258. destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.northeastMesh !== sourceMesh;
  259. destinationFill.northeastMesh = sourceMesh;
  260. destinationFill.northeastTile = sourceTile;
  261. return;
  262. case TileEdge.SOUTHWEST:
  263. destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.southwestMesh !== sourceMesh;
  264. destinationFill.southwestMesh = sourceMesh;
  265. destinationFill.southwestTile = sourceTile;
  266. return;
  267. case TileEdge.SOUTHEAST:
  268. destinationFill.changedThisFrame = destinationFill.changedThisFrame || destinationFill.southeastMesh !== sourceMesh;
  269. destinationFill.southeastMesh = sourceMesh;
  270. destinationFill.southeastTile = sourceTile;
  271. return;
  272. }
  273. if (sourceTile.level <= destinationTile.level) {
  274. // Source edge completely spans the destination edge.
  275. destinationFill.changedThisFrame = destinationFill.changedThisFrame || edgeMeshes[0] !== sourceMesh || edgeMeshes.length !== 1;
  276. edgeMeshes[0] = sourceMesh;
  277. edgeTiles[0] = sourceTile;
  278. edgeMeshes.length = 1;
  279. edgeTiles.length = 1;
  280. return;
  281. }
  282. // Source edge is a subset of the destination edge.
  283. // Figure out the range of meshes we're replacing.
  284. var startIndex, endIndex, existingTile, existingRectangle;
  285. var sourceRectangle = sourceTile.rectangle;
  286. var epsilon;
  287. var destinationRectangle = destinationTile.rectangle;
  288. switch (tileEdge) {
  289. case TileEdge.WEST:
  290. epsilon = (destinationRectangle.north - destinationRectangle.south) * CesiumMath.EPSILON5;
  291. for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
  292. existingTile = edgeTiles[startIndex];
  293. existingRectangle = existingTile.rectangle;
  294. if (CesiumMath.greaterThan(sourceRectangle.north, existingRectangle.south, epsilon)) {
  295. break;
  296. }
  297. }
  298. for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
  299. existingTile = edgeTiles[endIndex];
  300. existingRectangle = existingTile.rectangle;
  301. if (CesiumMath.greaterThanOrEquals(sourceRectangle.south, existingRectangle.north, epsilon)) {
  302. break;
  303. }
  304. }
  305. break;
  306. case TileEdge.SOUTH:
  307. epsilon = (destinationRectangle.east - destinationRectangle.west) * CesiumMath.EPSILON5;
  308. for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
  309. existingTile = edgeTiles[startIndex];
  310. existingRectangle = existingTile.rectangle;
  311. if (CesiumMath.lessThan(sourceRectangle.west, existingRectangle.east, epsilon)) {
  312. break;
  313. }
  314. }
  315. for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
  316. existingTile = edgeTiles[endIndex];
  317. existingRectangle = existingTile.rectangle;
  318. if (CesiumMath.lessThanOrEquals(sourceRectangle.east, existingRectangle.west, epsilon)) {
  319. break;
  320. }
  321. }
  322. break;
  323. case TileEdge.EAST:
  324. epsilon = (destinationRectangle.north - destinationRectangle.south) * CesiumMath.EPSILON5;
  325. for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
  326. existingTile = edgeTiles[startIndex];
  327. existingRectangle = existingTile.rectangle;
  328. if (CesiumMath.lessThan(sourceRectangle.south, existingRectangle.north, epsilon)) {
  329. break;
  330. }
  331. }
  332. for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
  333. existingTile = edgeTiles[endIndex];
  334. existingRectangle = existingTile.rectangle;
  335. if (CesiumMath.lessThanOrEquals(sourceRectangle.north, existingRectangle.south, epsilon)) {
  336. break;
  337. }
  338. }
  339. break;
  340. case TileEdge.NORTH:
  341. epsilon = (destinationRectangle.east - destinationRectangle.west) * CesiumMath.EPSILON5;
  342. for (startIndex = 0; startIndex < edgeTiles.length; ++startIndex) {
  343. existingTile = edgeTiles[startIndex];
  344. existingRectangle = existingTile.rectangle;
  345. if (CesiumMath.greaterThan(sourceRectangle.east, existingRectangle.west, epsilon)) {
  346. break;
  347. }
  348. }
  349. for (endIndex = startIndex; endIndex < edgeTiles.length; ++endIndex) {
  350. existingTile = edgeTiles[endIndex];
  351. existingRectangle = existingTile.rectangle;
  352. if (CesiumMath.greaterThanOrEquals(sourceRectangle.west, existingRectangle.east, epsilon)) {
  353. break;
  354. }
  355. }
  356. break;
  357. }
  358. if (endIndex - startIndex === 1) {
  359. destinationFill.changedThisFrame = destinationFill.changedThisFrame || edgeMeshes[startIndex] !== sourceMesh;
  360. edgeMeshes[startIndex] = sourceMesh;
  361. edgeTiles[startIndex] = sourceTile;
  362. } else {
  363. destinationFill.changedThisFrame = true;
  364. edgeMeshes.splice(startIndex, endIndex - startIndex, sourceMesh);
  365. edgeTiles.splice(startIndex, endIndex - startIndex, sourceTile);
  366. }
  367. }
  368. var cartographicScratch = new Cartographic();
  369. var centerCartographicScratch = new Cartographic();
  370. var cartesianScratch = new Cartesian3();
  371. var normalScratch = new Cartesian3();
  372. var octEncodedNormalScratch = new Cartesian2();
  373. var uvScratch2 = new Cartesian2();
  374. var uvScratch = new Cartesian2();
  375. function HeightAndNormal() {
  376. this.height = 0.0;
  377. this.encodedNormal = new Cartesian2();
  378. }
  379. function fillMissingCorner(fill, ellipsoid, u, v, corner, adjacentCorner1, adjacentCorner2, oppositeCorner, vertex) {
  380. if (defined(corner)) {
  381. return corner;
  382. }
  383. var height;
  384. if (defined(adjacentCorner1) && defined(adjacentCorner2)) {
  385. height = (adjacentCorner1.height + adjacentCorner2.height) * 0.5;
  386. } else if (defined(adjacentCorner1)) {
  387. height = adjacentCorner1.height;
  388. } else if (defined(adjacentCorner2)) {
  389. height = adjacentCorner2.height;
  390. } else if (defined(oppositeCorner)) {
  391. height = oppositeCorner.height;
  392. } else {
  393. var surfaceTile = fill.tile.data;
  394. var tileBoundingRegion = surfaceTile.tileBoundingRegion;
  395. var minimumHeight = 0.0;
  396. var maximumHeight = 0.0;
  397. if (defined(tileBoundingRegion)) {
  398. minimumHeight = tileBoundingRegion.minimumHeight;
  399. maximumHeight = tileBoundingRegion.maximumHeight;
  400. }
  401. height = (minimumHeight + maximumHeight) * 0.5;
  402. }
  403. getVertexWithHeightAtCorner(fill, ellipsoid, u, v, height, vertex);
  404. return vertex;
  405. }
  406. var heightRangeScratch = {
  407. minimumHeight: 0.0,
  408. maximumHeight: 0.0
  409. };
  410. var swVertexScratch = new HeightAndNormal();
  411. var seVertexScratch = new HeightAndNormal();
  412. var nwVertexScratch = new HeightAndNormal();
  413. var neVertexScratch = new HeightAndNormal();
  414. var heightmapBuffer = typeof Uint8Array !== 'undefined' ? new Uint8Array(9 * 9) : undefined;
  415. function createFillMesh(tileProvider, frameState, tile, vertexArraysToDestroy) {
  416. GlobeSurfaceTile.initialize(tile, tileProvider.terrainProvider, tileProvider._imageryLayers);
  417. var surfaceTile = tile.data;
  418. var fill = surfaceTile.fill;
  419. var rectangle = tile.rectangle;
  420. var ellipsoid = tile.tilingScheme.ellipsoid;
  421. var nwCorner = getCorner(fill, ellipsoid, 0.0, 1.0, fill.northwestTile, fill.northwestMesh, fill.northTiles, fill.northMeshes, fill.westTiles, fill.westMeshes, nwVertexScratch);
  422. var swCorner = getCorner(fill, ellipsoid, 0.0, 0.0, fill.southwestTile, fill.southwestMesh, fill.westTiles, fill.westMeshes, fill.southTiles, fill.southMeshes, swVertexScratch);
  423. var seCorner = getCorner(fill, ellipsoid, 1.0, 0.0, fill.southeastTile, fill.southeastMesh, fill.southTiles, fill.southMeshes, fill.eastTiles, fill.eastMeshes, seVertexScratch);
  424. var neCorner = getCorner(fill, ellipsoid, 1.0, 1.0, fill.northeastTile, fill.northeastMesh, fill.eastTiles, fill.eastMeshes, fill.northTiles, fill.northMeshes, neVertexScratch);
  425. nwCorner = fillMissingCorner(fill, ellipsoid, 0.0, 1.0, nwCorner, swCorner, neCorner, seCorner, nwVertexScratch);
  426. swCorner = fillMissingCorner(fill, ellipsoid, 0.0, 0.0, swCorner, nwCorner, seCorner, neCorner, swVertexScratch);
  427. seCorner = fillMissingCorner(fill, ellipsoid, 1.0, 1.0, seCorner, swCorner, neCorner, nwCorner, seVertexScratch);
  428. neCorner = fillMissingCorner(fill, ellipsoid, 1.0, 1.0, neCorner, seCorner, nwCorner, swCorner, neVertexScratch);
  429. var southwestHeight = swCorner.height;
  430. var southeastHeight = seCorner.height;
  431. var northwestHeight = nwCorner.height;
  432. var northeastHeight = neCorner.height;
  433. var minimumHeight = Math.min(southwestHeight, southeastHeight, northwestHeight, northeastHeight);
  434. var maximumHeight = Math.max(southwestHeight, southeastHeight, northwestHeight, northeastHeight);
  435. var middleHeight = (minimumHeight + maximumHeight) * 0.5;
  436. var i;
  437. var len;
  438. // For low-detail tiles, our usual fill tile approach will create tiles that
  439. // look really blocky because they don't have enough vertices to account for the
  440. // Earth's curvature. But the height range will also typically be well within
  441. // the allowed geometric error for those levels. So fill such tiles with a
  442. // constant-height heightmap.
  443. var geometricError = tileProvider.getLevelMaximumGeometricError(tile.level);
  444. var minCutThroughRadius = ellipsoid.maximumRadius - geometricError;
  445. var maxTileWidth = Math.acos(minCutThroughRadius / ellipsoid.maximumRadius) * 4.0;
  446. // When the tile width is greater than maxTileWidth as computed above, the error
  447. // of a normal fill tile from globe curvature alone will exceed the allowed geometric
  448. // error. Terrain won't change that much. However, we can allow more error than that.
  449. // A little blockiness during load is acceptable. For the WGS84 ellipsoid and
  450. // standard geometric error setup, the value here will have us use a heightmap
  451. // at levels 1, 2, and 3.
  452. maxTileWidth *= 1.5;
  453. if (rectangle.width > maxTileWidth && (maximumHeight - minimumHeight) <= geometricError) {
  454. var terrainData = new HeightmapTerrainData({
  455. width: 9,
  456. height: 9,
  457. buffer: heightmapBuffer,
  458. structure: {
  459. // Use the maximum as the constant height so that this tile's skirt
  460. // covers any cracks with adjacent tiles.
  461. heightOffset: maximumHeight
  462. }
  463. });
  464. fill.mesh = terrainData._createMeshSync(tile.tilingScheme, tile.x, tile.y, tile.level, 1.0);
  465. } else {
  466. var encoding = new TerrainEncoding(undefined, undefined, undefined, undefined, true, true);
  467. var centerCartographic = centerCartographicScratch;
  468. centerCartographic.longitude = (rectangle.east + rectangle.west) * 0.5;
  469. centerCartographic.latitude = (rectangle.north + rectangle.south) * 0.5;
  470. centerCartographic.height = middleHeight;
  471. encoding.center = ellipsoid.cartographicToCartesian(centerCartographic, encoding.center);
  472. // At _most_, we have vertices for the 4 corners, plus 1 center, plus every adjacent edge vertex.
  473. // In reality there will be less most of the time, but close enough; better
  474. // to overestimate than to re-allocate/copy/traverse the vertices twice.
  475. // Also, we'll often be able to squeeze the index data into the extra space in the buffer.
  476. var maxVertexCount = 5;
  477. var meshes;
  478. meshes = fill.westMeshes;
  479. for (i = 0, len = meshes.length; i < len; ++i) {
  480. maxVertexCount += meshes[i].eastIndicesNorthToSouth.length;
  481. }
  482. meshes = fill.southMeshes;
  483. for (i = 0, len = meshes.length; i < len; ++i) {
  484. maxVertexCount += meshes[i].northIndicesWestToEast.length;
  485. }
  486. meshes = fill.eastMeshes;
  487. for (i = 0, len = meshes.length; i < len; ++i) {
  488. maxVertexCount += meshes[i].westIndicesSouthToNorth.length;
  489. }
  490. meshes = fill.northMeshes;
  491. for (i = 0, len = meshes.length; i < len; ++i) {
  492. maxVertexCount += meshes[i].southIndicesEastToWest.length;
  493. }
  494. var heightRange = heightRangeScratch;
  495. heightRange.minimumHeight = minimumHeight;
  496. heightRange.maximumHeight = maximumHeight;
  497. var stride = encoding.getStride();
  498. var typedArray = new Float32Array(maxVertexCount * stride);
  499. var nextIndex = 0;
  500. var northwestIndex = nextIndex;
  501. nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 0.0, 1.0, nwCorner.height, nwCorner.encodedNormal, 1.0, heightRange);
  502. nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.westTiles, fill.westMeshes, TileEdge.EAST, heightRange);
  503. var southwestIndex = nextIndex;
  504. nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 0.0, 0.0, swCorner.height, swCorner.encodedNormal, 0.0, heightRange);
  505. nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.southTiles, fill.southMeshes, TileEdge.NORTH, heightRange);
  506. var southeastIndex = nextIndex;
  507. nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 1.0, 0.0, seCorner.height, seCorner.encodedNormal, 0.0, heightRange);
  508. nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.eastTiles, fill.eastMeshes, TileEdge.WEST, heightRange);
  509. var northeastIndex = nextIndex;
  510. nextIndex = addVertexWithComputedPosition(ellipsoid, rectangle, encoding, typedArray, nextIndex, 1.0, 1.0, neCorner.height, neCorner.encodedNormal, 1.0, heightRange);
  511. nextIndex = addEdge(fill, ellipsoid, encoding, typedArray, nextIndex, fill.northTiles, fill.northMeshes, TileEdge.SOUTH, heightRange);
  512. minimumHeight = heightRange.minimumHeight;
  513. maximumHeight = heightRange.maximumHeight;
  514. var obb = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, tile.tilingScheme.ellipsoid);
  515. // Add a single vertex at the center of the tile.
  516. var southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(rectangle.south);
  517. var oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(rectangle.north) - southMercatorY);
  518. var centerWebMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(centerCartographic.latitude) - southMercatorY) * oneOverMercatorHeight;
  519. ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, normalScratch);
  520. var centerEncodedNormal = AttributeCompression.octEncode(normalScratch, octEncodedNormalScratch);
  521. var centerIndex = nextIndex;
  522. encoding.encode(typedArray, nextIndex * stride, obb.center, Cartesian2.fromElements(0.5, 0.5, uvScratch), middleHeight, centerEncodedNormal, centerWebMercatorT);
  523. ++nextIndex;
  524. var vertexCount = nextIndex;
  525. var bytesPerIndex = vertexCount < 256 ? 1 : 2;
  526. var indexCount = (vertexCount - 1) * 3; // one triangle per edge vertex
  527. var indexDataBytes = indexCount * bytesPerIndex;
  528. var availableBytesInBuffer = (typedArray.length - vertexCount * stride) * Float32Array.BYTES_PER_ELEMENT;
  529. var indices;
  530. if (availableBytesInBuffer >= indexDataBytes) {
  531. // Store the index data in the same buffer as the vertex data.
  532. var startIndex = vertexCount * stride * Float32Array.BYTES_PER_ELEMENT;
  533. indices = vertexCount < 256
  534. ? new Uint8Array(typedArray.buffer, startIndex, indexCount)
  535. : new Uint16Array(typedArray.buffer, startIndex, indexCount);
  536. } else {
  537. // Allocate a new buffer for the index data.
  538. indices = vertexCount < 256 ? new Uint8Array(indexCount) : new Uint16Array(indexCount);
  539. }
  540. typedArray = new Float32Array(typedArray.buffer, 0, vertexCount * stride);
  541. var indexOut = 0;
  542. for (i = 0; i < vertexCount - 2; ++i) {
  543. indices[indexOut++] = centerIndex;
  544. indices[indexOut++] = i;
  545. indices[indexOut++] = i + 1;
  546. }
  547. indices[indexOut++] = centerIndex;
  548. indices[indexOut++] = i;
  549. indices[indexOut++] = 0;
  550. var westIndicesSouthToNorth = [];
  551. for (i = southwestIndex; i >= northwestIndex; --i) {
  552. westIndicesSouthToNorth.push(i);
  553. }
  554. var southIndicesEastToWest = [];
  555. for (i = southeastIndex; i >= southwestIndex; --i) {
  556. southIndicesEastToWest.push(i);
  557. }
  558. var eastIndicesNorthToSouth = [];
  559. for (i = northeastIndex; i >= southeastIndex; --i) {
  560. eastIndicesNorthToSouth.push(i);
  561. }
  562. var northIndicesWestToEast = [];
  563. northIndicesWestToEast.push(0);
  564. for (i = centerIndex - 1; i >= northeastIndex; --i) {
  565. northIndicesWestToEast.push(i);
  566. }
  567. fill.mesh = new TerrainMesh(
  568. encoding.center,
  569. typedArray,
  570. indices,
  571. minimumHeight,
  572. maximumHeight,
  573. BoundingSphere.fromOrientedBoundingBox(obb),
  574. computeOccludeePoint(tileProvider, obb.center, rectangle, maximumHeight),
  575. encoding.getStride(),
  576. obb,
  577. encoding,
  578. frameState.terrainExaggeration,
  579. westIndicesSouthToNorth,
  580. southIndicesEastToWest,
  581. eastIndicesNorthToSouth,
  582. northIndicesWestToEast
  583. );
  584. }
  585. var context = frameState.context;
  586. if (defined(fill.vertexArray)) {
  587. if (defined(vertexArraysToDestroy)) {
  588. vertexArraysToDestroy.push(fill.vertexArray);
  589. } else {
  590. GlobeSurfaceTile._freeVertexArray(fill.vertexArray);
  591. }
  592. }
  593. fill.vertexArray = GlobeSurfaceTile._createVertexArrayForMesh(context, fill.mesh);
  594. surfaceTile.processImagery(tile, tileProvider.terrainProvider, frameState, true);
  595. var oldTexture = fill.waterMaskTexture;
  596. fill.waterMaskTexture = undefined;
  597. if (tileProvider.terrainProvider.hasWaterMask) {
  598. var waterSourceTile = surfaceTile._findAncestorTileWithTerrainData(tile);
  599. if (defined(waterSourceTile) && defined(waterSourceTile.data.waterMaskTexture)) {
  600. fill.waterMaskTexture = waterSourceTile.data.waterMaskTexture;
  601. ++fill.waterMaskTexture.referenceCount;
  602. surfaceTile._computeWaterMaskTranslationAndScale(tile, waterSourceTile, fill.waterMaskTranslationAndScale);
  603. }
  604. }
  605. if (defined(oldTexture)) {
  606. --oldTexture.referenceCount;
  607. if (oldTexture.referenceCount === 0) {
  608. oldTexture.destroy();
  609. }
  610. }
  611. }
  612. function addVertexWithComputedPosition(ellipsoid, rectangle, encoding, buffer, index, u, v, height, encodedNormal, webMercatorT, heightRange) {
  613. var cartographic = cartographicScratch;
  614. cartographic.longitude = CesiumMath.lerp(rectangle.west, rectangle.east, u);
  615. cartographic.latitude = CesiumMath.lerp(rectangle.south, rectangle.north, v);
  616. cartographic.height = height;
  617. var position = ellipsoid.cartographicToCartesian(cartographic, cartesianScratch);
  618. var uv = uvScratch2;
  619. uv.x = u;
  620. uv.y = v;
  621. encoding.encode(buffer, index * encoding.getStride(), position, uv, height, encodedNormal, webMercatorT);
  622. heightRange.minimumHeight = Math.min(heightRange.minimumHeight, height);
  623. heightRange.maximumHeight = Math.max(heightRange.maximumHeight, height);
  624. return index + 1;
  625. }
  626. var sourceRectangleScratch = new Rectangle();
  627. function transformTextureCoordinates(sourceTile, targetTile, coordinates, result) {
  628. var sourceRectangle = sourceTile.rectangle;
  629. var targetRectangle = targetTile.rectangle;
  630. // Handle transforming across the anti-meridian.
  631. if (targetTile.x === 0 && coordinates.x === 1.0 && sourceTile.x === sourceTile.tilingScheme.getNumberOfXTilesAtLevel(sourceTile.level) - 1) {
  632. sourceRectangle = Rectangle.clone(sourceTile.rectangle, sourceRectangleScratch);
  633. sourceRectangle.west -= CesiumMath.TWO_PI;
  634. sourceRectangle.east -= CesiumMath.TWO_PI;
  635. } else if (sourceTile.x === 0 && coordinates.x === 0.0 && targetTile.x === targetTile.tilingScheme.getNumberOfXTilesAtLevel(targetTile.level) - 1) {
  636. sourceRectangle = Rectangle.clone(sourceTile.rectangle, sourceRectangleScratch);
  637. sourceRectangle.west += CesiumMath.TWO_PI;
  638. sourceRectangle.east += CesiumMath.TWO_PI;
  639. }
  640. var sourceWidth = sourceRectangle.east - sourceRectangle.west;
  641. var umin = (targetRectangle.west - sourceRectangle.west) / sourceWidth;
  642. var umax = (targetRectangle.east - sourceRectangle.west) / sourceWidth;
  643. var sourceHeight = sourceRectangle.north - sourceRectangle.south;
  644. var vmin = (targetRectangle.south - sourceRectangle.south) / sourceHeight;
  645. var vmax = (targetRectangle.north - sourceRectangle.south) / sourceHeight;
  646. var u = (coordinates.x - umin) / (umax - umin);
  647. var v = (coordinates.y - vmin) / (vmax - vmin);
  648. // Ensure that coordinates very near the corners are at the corners.
  649. if (Math.abs(u) < Math.EPSILON5) {
  650. u = 0.0;
  651. } else if (Math.abs(u - 1.0) < Math.EPSILON5) {
  652. u = 1.0;
  653. }
  654. if (Math.abs(v) < Math.EPSILON5) {
  655. v = 0.0;
  656. } else if (Math.abs(v - 1.0) < Math.EPSILON5) {
  657. v = 1.0;
  658. }
  659. result.x = u;
  660. result.y = v;
  661. return result;
  662. }
  663. var encodedNormalScratch = new Cartesian2();
  664. function getVertexFromTileAtCorner(sourceMesh, sourceIndex, u, v, vertex) {
  665. var sourceEncoding = sourceMesh.encoding;
  666. var sourceVertices = sourceMesh.vertices;
  667. vertex.height = sourceEncoding.decodeHeight(sourceVertices, sourceIndex);
  668. if (sourceEncoding.hasVertexNormals) {
  669. sourceEncoding.getOctEncodedNormal(sourceVertices, sourceIndex, vertex.encodedNormal);
  670. } else {
  671. var normal = vertex.encodedNormal;
  672. normal.x = 0.0;
  673. normal.y = 0.0;
  674. }
  675. }
  676. var encodedNormalScratch2 = new Cartesian2();
  677. var cartesianScratch2 = new Cartesian3();
  678. function getInterpolatedVertexAtCorner(ellipsoid, sourceTile, targetTile, sourceMesh, previousIndex, nextIndex, u, v, interpolateU, vertex) {
  679. var sourceEncoding = sourceMesh.encoding;
  680. var sourceVertices = sourceMesh.vertices;
  681. var previousUv = transformTextureCoordinates(sourceTile, targetTile, sourceEncoding.decodeTextureCoordinates(sourceVertices, previousIndex, uvScratch), uvScratch);
  682. var nextUv = transformTextureCoordinates(sourceTile, targetTile, sourceEncoding.decodeTextureCoordinates(sourceVertices, nextIndex, uvScratch2), uvScratch2);
  683. var ratio;
  684. if (interpolateU) {
  685. ratio = (u - previousUv.x) / (nextUv.x - previousUv.x);
  686. } else {
  687. ratio = (v - previousUv.y) / (nextUv.y - previousUv.y);
  688. }
  689. var height1 = sourceEncoding.decodeHeight(sourceVertices, previousIndex);
  690. var height2 = sourceEncoding.decodeHeight(sourceVertices, nextIndex);
  691. var targetRectangle = targetTile.rectangle;
  692. cartographicScratch.longitude = CesiumMath.lerp(targetRectangle.west, targetRectangle.east, u);
  693. cartographicScratch.latitude = CesiumMath.lerp(targetRectangle.south, targetRectangle.north, v);
  694. vertex.height = cartographicScratch.height = CesiumMath.lerp(height1, height2, ratio);
  695. var normal;
  696. if (sourceEncoding.hasVertexNormals) {
  697. var encodedNormal1 = sourceEncoding.getOctEncodedNormal(sourceVertices, previousIndex, encodedNormalScratch);
  698. var encodedNormal2 = sourceEncoding.getOctEncodedNormal(sourceVertices, nextIndex, encodedNormalScratch2);
  699. var normal1 = AttributeCompression.octDecode(encodedNormal1.x, encodedNormal1.y, cartesianScratch);
  700. var normal2 = AttributeCompression.octDecode(encodedNormal2.x, encodedNormal2.y, cartesianScratch2);
  701. normal = Cartesian3.lerp(normal1, normal2, ratio, cartesianScratch);
  702. Cartesian3.normalize(normal, normal);
  703. AttributeCompression.octEncode(normal, vertex.encodedNormal);
  704. } else {
  705. normal = ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, cartesianScratch);
  706. AttributeCompression.octEncode(normal, vertex.encodedNormal);
  707. }
  708. }
  709. function getVertexWithHeightAtCorner(terrainFillMesh, ellipsoid, u, v, height, vertex) {
  710. vertex.height = height;
  711. var normal = ellipsoid.geodeticSurfaceNormalCartographic(cartographicScratch, cartesianScratch);
  712. AttributeCompression.octEncode(normal, vertex.encodedNormal);
  713. }
  714. function getCorner(
  715. terrainFillMesh,
  716. ellipsoid,
  717. u, v,
  718. cornerTile, cornerMesh,
  719. previousEdgeTiles, previousEdgeMeshes,
  720. nextEdgeTiles, nextEdgeMeshes,
  721. vertex
  722. ) {
  723. var gotCorner =
  724. getCornerFromEdge(terrainFillMesh, ellipsoid, previousEdgeMeshes, previousEdgeTiles, false, u, v, vertex) ||
  725. getCornerFromEdge(terrainFillMesh, ellipsoid, nextEdgeMeshes, nextEdgeTiles, true, u, v, vertex);
  726. if (gotCorner) {
  727. return vertex;
  728. }
  729. var vertexIndex;
  730. if (meshIsUsable(cornerTile, cornerMesh)) {
  731. // Corner mesh is valid, copy its corner vertex to this mesh.
  732. if (u === 0.0) {
  733. if (v === 0.0) {
  734. // southwest destination, northeast source
  735. vertexIndex = cornerMesh.eastIndicesNorthToSouth[0];
  736. } else {
  737. // northwest destination, southeast source
  738. vertexIndex = cornerMesh.southIndicesEastToWest[0];
  739. }
  740. } else if (v === 0.0) {
  741. // southeast destination, northwest source
  742. vertexIndex = cornerMesh.northIndicesWestToEast[0];
  743. } else {
  744. // northeast destination, southwest source
  745. vertexIndex = cornerMesh.westIndicesSouthToNorth[0];
  746. }
  747. getVertexFromTileAtCorner(cornerMesh, vertexIndex, u, v, vertex);
  748. return vertex;
  749. }
  750. // There is no precise vertex available from the corner or from either adjacent edge.
  751. // This is either because there are no tiles at all at the edges and corner, or
  752. // because the tiles at the edge are higher-level-number and don't extend all the way
  753. // to the corner.
  754. // Try to grab a height from the adjacent edges.
  755. var height;
  756. if (u === 0.0) {
  757. if (v === 0.0) {
  758. // southwest
  759. height = getClosestHeightToCorner(
  760. terrainFillMesh.westMeshes, terrainFillMesh.westTiles, TileEdge.EAST,
  761. terrainFillMesh.southMeshes, terrainFillMesh.southTiles, TileEdge.NORTH,
  762. u, v);
  763. } else {
  764. // northwest
  765. height = getClosestHeightToCorner(
  766. terrainFillMesh.northMeshes, terrainFillMesh.northTiles, TileEdge.SOUTH,
  767. terrainFillMesh.westMeshes, terrainFillMesh.westTiles, TileEdge.EAST,
  768. u, v);
  769. }
  770. } else if (v === 0.0) {
  771. // southeast
  772. height = getClosestHeightToCorner(
  773. terrainFillMesh.southMeshes, terrainFillMesh.southTiles, TileEdge.NORTH,
  774. terrainFillMesh.eastMeshes, terrainFillMesh.eastTiles, TileEdge.WEST,
  775. u, v);
  776. } else {
  777. // northeast
  778. height = getClosestHeightToCorner(
  779. terrainFillMesh.eastMeshes, terrainFillMesh.eastTiles, TileEdge.WEST,
  780. terrainFillMesh.northMeshes, terrainFillMesh.northTiles, TileEdge.SOUTH,
  781. u, v);
  782. }
  783. if (defined(height)) {
  784. getVertexWithHeightAtCorner(terrainFillMesh, ellipsoid, u, v, height, vertex);
  785. return vertex;
  786. }
  787. // No heights available that are closer than the adjacent corners.
  788. return undefined;
  789. }
  790. function getClosestHeightToCorner(
  791. previousMeshes, previousTiles, previousEdge,
  792. nextMeshes, nextTiles, nextEdge,
  793. u, v
  794. ) {
  795. var height1 = getNearestHeightOnEdge(previousMeshes, previousTiles, false, previousEdge, u, v);
  796. var height2 = getNearestHeightOnEdge(nextMeshes, nextTiles, true, nextEdge, u, v);
  797. if (defined(height1) && defined(height2)) {
  798. // It would be slightly better to do a weighted average of the two heights
  799. // based on their distance from the corner, but it shouldn't matter much in practice.
  800. return (height1 + height2) * 0.5;
  801. } else if (defined(height1)) {
  802. return height1;
  803. }
  804. return height2;
  805. }
  806. function addEdge(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTiles, edgeMeshes, tileEdge, heightRange) {
  807. for (var i = 0; i < edgeTiles.length; ++i) {
  808. nextIndex = addEdgeMesh(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTiles[i], edgeMeshes[i], tileEdge, heightRange);
  809. }
  810. return nextIndex;
  811. }
  812. function addEdgeMesh(terrainFillMesh, ellipsoid, encoding, typedArray, nextIndex, edgeTile, edgeMesh, tileEdge, heightRange) {
  813. // Handle copying edges across the anti-meridian.
  814. var sourceRectangle = edgeTile.rectangle;
  815. if (tileEdge === TileEdge.EAST && terrainFillMesh.tile.x === 0) {
  816. sourceRectangle = Rectangle.clone(edgeTile.rectangle, sourceRectangleScratch);
  817. sourceRectangle.west -= CesiumMath.TWO_PI;
  818. sourceRectangle.east -= CesiumMath.TWO_PI;
  819. } else if (tileEdge === TileEdge.WEST && edgeTile.x === 0) {
  820. sourceRectangle = Rectangle.clone(edgeTile.rectangle, sourceRectangleScratch);
  821. sourceRectangle.west += CesiumMath.TWO_PI;
  822. sourceRectangle.east += CesiumMath.TWO_PI;
  823. }
  824. var targetRectangle = terrainFillMesh.tile.rectangle;
  825. var lastU;
  826. var lastV;
  827. if (nextIndex > 0) {
  828. encoding.decodeTextureCoordinates(typedArray, nextIndex - 1, uvScratch);
  829. lastU = uvScratch.x;
  830. lastV = uvScratch.y;
  831. }
  832. var indices;
  833. var compareU;
  834. switch (tileEdge) {
  835. case TileEdge.WEST:
  836. indices = edgeMesh.westIndicesSouthToNorth;
  837. compareU = false;
  838. break;
  839. case TileEdge.NORTH:
  840. indices = edgeMesh.northIndicesWestToEast;
  841. compareU = true;
  842. break;
  843. case TileEdge.EAST:
  844. indices = edgeMesh.eastIndicesNorthToSouth;
  845. compareU = false;
  846. break;
  847. case TileEdge.SOUTH:
  848. indices = edgeMesh.southIndicesEastToWest;
  849. compareU = true;
  850. break;
  851. }
  852. var sourceTile = edgeTile;
  853. var targetTile = terrainFillMesh.tile;
  854. var sourceEncoding = edgeMesh.encoding;
  855. var sourceVertices = edgeMesh.vertices;
  856. var targetStride = encoding.getStride();
  857. var southMercatorY;
  858. var oneOverMercatorHeight;
  859. if (sourceEncoding.hasWebMercatorT) {
  860. southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(targetRectangle.south);
  861. oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(targetRectangle.north) - southMercatorY);
  862. }
  863. for (var i = 0; i < indices.length; ++i) {
  864. var index = indices[i];
  865. var uv = sourceEncoding.decodeTextureCoordinates(sourceVertices, index, uvScratch);
  866. transformTextureCoordinates(sourceTile, targetTile, uv, uv);
  867. var u = uv.x;
  868. var v = uv.y;
  869. var uOrV = compareU ? u : v;
  870. if (uOrV < 0.0 || uOrV > 1.0) {
  871. // Vertex is outside the target tile - skip it.
  872. continue;
  873. }
  874. if (Math.abs(u - lastU) < CesiumMath.EPSILON5 && Math.abs(v - lastV) < CesiumMath.EPSILON5) {
  875. // Vertex is very close to the previous one - skip it.
  876. continue;
  877. }
  878. var nearlyEdgeU = Math.abs(u) < CesiumMath.EPSILON5 || Math.abs(u - 1.0) < CesiumMath.EPSILON5;
  879. var nearlyEdgeV = Math.abs(v) < CesiumMath.EPSILON5 || Math.abs(v - 1.0) < CesiumMath.EPSILON5;
  880. if (nearlyEdgeU && nearlyEdgeV) {
  881. // Corner vertex - skip it.
  882. continue;
  883. }
  884. var position = sourceEncoding.decodePosition(sourceVertices, index, cartesianScratch);
  885. var height = sourceEncoding.decodeHeight(sourceVertices, index);
  886. var normal;
  887. if (sourceEncoding.hasVertexNormals) {
  888. normal = sourceEncoding.getOctEncodedNormal(sourceVertices, index, octEncodedNormalScratch);
  889. } else {
  890. normal = octEncodedNormalScratch;
  891. normal.x = 0.0;
  892. normal.y = 0.0;
  893. }
  894. var webMercatorT = v;
  895. if (sourceEncoding.hasWebMercatorT) {
  896. var latitude = CesiumMath.lerp(targetRectangle.south, targetRectangle.north, v);
  897. webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
  898. }
  899. encoding.encode(typedArray, nextIndex * targetStride, position, uv, height, normal, webMercatorT);
  900. heightRange.minimumHeight = Math.min(heightRange.minimumHeight, height);
  901. heightRange.maximumHeight = Math.max(heightRange.maximumHeight, height);
  902. ++nextIndex;
  903. }
  904. return nextIndex;
  905. }
  906. function getNearestHeightOnEdge(meshes, tiles, isNext, edge, u, v) {
  907. var meshStart;
  908. var meshEnd;
  909. var meshStep;
  910. if (isNext) {
  911. meshStart = 0;
  912. meshEnd = meshes.length;
  913. meshStep = 1;
  914. } else {
  915. meshStart = meshes.length - 1;
  916. meshEnd = -1;
  917. meshStep = -1;
  918. }
  919. for (var meshIndex = meshStart; meshIndex !== meshEnd; meshIndex += meshStep) {
  920. var mesh = meshes[meshIndex];
  921. var tile = tiles[meshIndex];
  922. if (!meshIsUsable(tile, mesh)) {
  923. continue;
  924. }
  925. var indices;
  926. switch (edge) {
  927. case TileEdge.WEST:
  928. indices = mesh.westIndicesSouthToNorth;
  929. break;
  930. case TileEdge.SOUTH:
  931. indices = mesh.southIndicesEastToWest;
  932. break;
  933. case TileEdge.EAST:
  934. indices = mesh.eastIndicesNorthToSouth;
  935. break;
  936. case TileEdge.NORTH:
  937. indices = mesh.northIndicesWestToEast;
  938. break;
  939. }
  940. var index = indices[isNext ? 0 : indices.length - 1];
  941. if (defined(index)) {
  942. return mesh.encoding.decodeHeight(mesh.vertices, index);
  943. }
  944. }
  945. return undefined;
  946. }
  947. function meshIsUsable(tile, mesh) {
  948. return defined(mesh) && (!defined(tile.data.fill) || !tile.data.fill.changedThisFrame);
  949. }
  950. function getCornerFromEdge(terrainFillMesh, ellipsoid, edgeMeshes, edgeTiles, isNext, u, v, vertex) {
  951. var edgeVertices;
  952. var compareU;
  953. var increasing;
  954. var vertexIndexIndex;
  955. var vertexIndex;
  956. var sourceTile = edgeTiles[isNext ? 0 : edgeMeshes.length - 1];
  957. var sourceMesh = edgeMeshes[isNext ? 0 : edgeMeshes.length - 1];
  958. if (meshIsUsable(sourceTile, sourceMesh)) {
  959. // Previous mesh is valid, but we don't know yet if it covers this corner.
  960. if (u === 0.0) {
  961. if (v === 0.0) {
  962. // southwest
  963. edgeVertices = isNext ? sourceMesh.northIndicesWestToEast : sourceMesh.eastIndicesNorthToSouth;
  964. compareU = isNext;
  965. increasing = isNext;
  966. } else {
  967. // northwest
  968. edgeVertices = isNext ? sourceMesh.eastIndicesNorthToSouth : sourceMesh.southIndicesEastToWest;
  969. compareU = !isNext;
  970. increasing = false;
  971. }
  972. } else if (v === 0.0) {
  973. // southeast
  974. edgeVertices = isNext ? sourceMesh.westIndicesSouthToNorth : sourceMesh.northIndicesWestToEast;
  975. compareU = !isNext;
  976. increasing = true;
  977. } else {
  978. // northeast
  979. edgeVertices = isNext ? sourceMesh.southIndicesEastToWest : sourceMesh.westIndicesSouthToNorth;
  980. compareU = isNext;
  981. increasing = !isNext;
  982. }
  983. if (edgeVertices.length > 0) {
  984. // The vertex we want will very often be the first/last vertex so check that first.
  985. vertexIndexIndex = isNext ? 0 : edgeVertices.length - 1;
  986. vertexIndex = edgeVertices[vertexIndexIndex];
  987. sourceMesh.encoding.decodeTextureCoordinates(sourceMesh.vertices, vertexIndex, uvScratch);
  988. var targetUv = transformTextureCoordinates(sourceTile, terrainFillMesh.tile, uvScratch, uvScratch);
  989. if (targetUv.x === u && targetUv.y === v) {
  990. // Vertex is good!
  991. getVertexFromTileAtCorner(sourceMesh, vertexIndex, u, v, vertex);
  992. return true;
  993. }
  994. // The last vertex is not the one we need, try binary searching for the right one.
  995. vertexIndexIndex = binarySearch(edgeVertices, compareU ? u : v, function(vertexIndex, textureCoordinate) {
  996. sourceMesh.encoding.decodeTextureCoordinates(sourceMesh.vertices, vertexIndex, uvScratch);
  997. var targetUv = transformTextureCoordinates(sourceTile, terrainFillMesh.tile, uvScratch, uvScratch);
  998. if (increasing) {
  999. if (compareU) {
  1000. return targetUv.x - u;
  1001. }
  1002. return targetUv.y - v;
  1003. } else if (compareU) {
  1004. return u - targetUv.x;
  1005. }
  1006. return v - targetUv.y;
  1007. });
  1008. if (vertexIndexIndex < 0) {
  1009. vertexIndexIndex = ~vertexIndexIndex;
  1010. if (vertexIndexIndex > 0 && vertexIndexIndex < edgeVertices.length) {
  1011. // The corner falls between two vertices, so interpolate between them.
  1012. getInterpolatedVertexAtCorner(ellipsoid, sourceTile, terrainFillMesh.tile, sourceMesh, edgeVertices[vertexIndexIndex - 1], edgeVertices[vertexIndexIndex], u, v, compareU, vertex);
  1013. return true;
  1014. }
  1015. } else {
  1016. // Found a vertex that fits in the corner exactly.
  1017. getVertexFromTileAtCorner(sourceMesh, edgeVertices[vertexIndexIndex], u, v, vertex);
  1018. return true;
  1019. }
  1020. }
  1021. }
  1022. return false;
  1023. }
  1024. var cornerPositionsScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
  1025. function computeOccludeePoint(tileProvider, center, rectangle, height, result) {
  1026. var ellipsoidalOccluder = tileProvider.quadtree._occluders.ellipsoid;
  1027. var ellipsoid = ellipsoidalOccluder.ellipsoid;
  1028. var cornerPositions = cornerPositionsScratch;
  1029. Cartesian3.fromRadians(rectangle.west, rectangle.south, height, ellipsoid, cornerPositions[0]);
  1030. Cartesian3.fromRadians(rectangle.east, rectangle.south, height, ellipsoid, cornerPositions[1]);
  1031. Cartesian3.fromRadians(rectangle.west, rectangle.north, height, ellipsoid, cornerPositions[2]);
  1032. Cartesian3.fromRadians(rectangle.east, rectangle.north, height, ellipsoid, cornerPositions[3]);
  1033. return ellipsoidalOccluder.computeHorizonCullingPoint(center, cornerPositions, result);
  1034. }
  1035. export default TerrainFillMesh;