createVerticesFromGoogleEarthEnterpriseBuffer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import AxisAlignedBoundingBox from '../Core/AxisAlignedBoundingBox.js';
  2. import BoundingSphere from '../Core/BoundingSphere.js';
  3. import Cartesian2 from '../Core/Cartesian2.js';
  4. import Cartesian3 from '../Core/Cartesian3.js';
  5. import Cartographic from '../Core/Cartographic.js';
  6. import defaultValue from '../Core/defaultValue.js';
  7. import defined from '../Core/defined.js';
  8. import Ellipsoid from '../Core/Ellipsoid.js';
  9. import EllipsoidalOccluder from '../Core/EllipsoidalOccluder.js';
  10. import CesiumMath from '../Core/Math.js';
  11. import Matrix4 from '../Core/Matrix4.js';
  12. import OrientedBoundingBox from '../Core/OrientedBoundingBox.js';
  13. import Rectangle from '../Core/Rectangle.js';
  14. import RuntimeError from '../Core/RuntimeError.js';
  15. import TerrainEncoding from '../Core/TerrainEncoding.js';
  16. import Transforms from '../Core/Transforms.js';
  17. import WebMercatorProjection from '../Core/WebMercatorProjection.js';
  18. import createTaskProcessorWorker from './createTaskProcessorWorker.js';
  19. var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
  20. var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
  21. var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
  22. var sizeOfFloat = Float32Array.BYTES_PER_ELEMENT;
  23. var sizeOfDouble = Float64Array.BYTES_PER_ELEMENT;
  24. function indexOfEpsilon(arr, elem, elemType) {
  25. elemType = defaultValue(elemType, CesiumMath);
  26. var count = arr.length;
  27. for (var i = 0; i < count; ++i) {
  28. if (elemType.equalsEpsilon(arr[i], elem, CesiumMath.EPSILON12)) {
  29. return i;
  30. }
  31. }
  32. return -1;
  33. }
  34. function createVerticesFromGoogleEarthEnterpriseBuffer(parameters, transferableObjects) {
  35. parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
  36. parameters.rectangle = Rectangle.clone(parameters.rectangle);
  37. var statistics = processBuffer(parameters.buffer, parameters.relativeToCenter, parameters.ellipsoid,
  38. parameters.rectangle, parameters.nativeRectangle, parameters.exaggeration, parameters.skirtHeight,
  39. parameters.includeWebMercatorT, parameters.negativeAltitudeExponentBias, parameters.negativeElevationThreshold);
  40. var vertices = statistics.vertices;
  41. transferableObjects.push(vertices.buffer);
  42. var indices = statistics.indices;
  43. transferableObjects.push(indices.buffer);
  44. return {
  45. vertices : vertices.buffer,
  46. indices : indices.buffer,
  47. numberOfAttributes : statistics.encoding.getStride(),
  48. minimumHeight : statistics.minimumHeight,
  49. maximumHeight : statistics.maximumHeight,
  50. boundingSphere3D : statistics.boundingSphere3D,
  51. orientedBoundingBox : statistics.orientedBoundingBox,
  52. occludeePointInScaledSpace : statistics.occludeePointInScaledSpace,
  53. encoding : statistics.encoding,
  54. vertexCountWithoutSkirts : statistics.vertexCountWithoutSkirts,
  55. skirtIndex : statistics.skirtIndex,
  56. westIndicesSouthToNorth : statistics.westIndicesSouthToNorth,
  57. southIndicesEastToWest : statistics.southIndicesEastToWest,
  58. eastIndicesNorthToSouth : statistics.eastIndicesNorthToSouth,
  59. northIndicesWestToEast : statistics.northIndicesWestToEast
  60. };
  61. }
  62. var scratchCartographic = new Cartographic();
  63. var scratchCartesian = new Cartesian3();
  64. var minimumScratch = new Cartesian3();
  65. var maximumScratch = new Cartesian3();
  66. var matrix4Scratch = new Matrix4();
  67. function processBuffer(buffer, relativeToCenter, ellipsoid, rectangle, nativeRectangle, exaggeration, skirtHeight, includeWebMercatorT, negativeAltitudeExponentBias, negativeElevationThreshold) {
  68. var geographicWest;
  69. var geographicSouth;
  70. var geographicEast;
  71. var geographicNorth;
  72. var rectangleWidth, rectangleHeight;
  73. if (!defined(rectangle)) {
  74. geographicWest = CesiumMath.toRadians(nativeRectangle.west);
  75. geographicSouth = CesiumMath.toRadians(nativeRectangle.south);
  76. geographicEast = CesiumMath.toRadians(nativeRectangle.east);
  77. geographicNorth = CesiumMath.toRadians(nativeRectangle.north);
  78. rectangleWidth = CesiumMath.toRadians(rectangle.width);
  79. rectangleHeight = CesiumMath.toRadians(rectangle.height);
  80. } else {
  81. geographicWest = rectangle.west;
  82. geographicSouth = rectangle.south;
  83. geographicEast = rectangle.east;
  84. geographicNorth = rectangle.north;
  85. rectangleWidth = rectangle.width;
  86. rectangleHeight = rectangle.height;
  87. }
  88. // Keep track of quad borders so we can remove duplicates around the borders
  89. var quadBorderLatitudes = [geographicSouth, geographicNorth];
  90. var quadBorderLongitudes = [geographicWest, geographicEast];
  91. var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid);
  92. var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch);
  93. var southMercatorY;
  94. var oneOverMercatorHeight;
  95. if (includeWebMercatorT) {
  96. southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth);
  97. oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY);
  98. }
  99. var dv = new DataView(buffer);
  100. var minHeight = Number.POSITIVE_INFINITY;
  101. var maxHeight = Number.NEGATIVE_INFINITY;
  102. var minimum = minimumScratch;
  103. minimum.x = Number.POSITIVE_INFINITY;
  104. minimum.y = Number.POSITIVE_INFINITY;
  105. minimum.z = Number.POSITIVE_INFINITY;
  106. var maximum = maximumScratch;
  107. maximum.x = Number.NEGATIVE_INFINITY;
  108. maximum.y = Number.NEGATIVE_INFINITY;
  109. maximum.z = Number.NEGATIVE_INFINITY;
  110. // Compute sizes
  111. var offset = 0;
  112. var size = 0;
  113. var indicesSize = 0;
  114. var quadSize;
  115. var quad;
  116. for (quad = 0; quad < 4; ++quad) {
  117. var o = offset;
  118. quadSize = dv.getUint32(o, true);
  119. o += sizeOfUint32;
  120. var x = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);
  121. o += sizeOfDouble;
  122. if (indexOfEpsilon(quadBorderLongitudes, x) === -1) {
  123. quadBorderLongitudes.push(x);
  124. }
  125. var y = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);
  126. o += sizeOfDouble;
  127. if (indexOfEpsilon(quadBorderLatitudes, y) === -1) {
  128. quadBorderLatitudes.push(y);
  129. }
  130. o += 2 * sizeOfDouble; // stepX + stepY
  131. var c = dv.getInt32(o, true); // Read point count
  132. o += sizeOfInt32;
  133. size += c;
  134. c = dv.getInt32(o, true); // Read index count
  135. indicesSize += c * 3;
  136. offset += quadSize + sizeOfUint32; // Jump to next quad
  137. }
  138. // Quad Border points to remove duplicates
  139. var quadBorderPoints = [];
  140. var quadBorderIndices = [];
  141. // Create arrays
  142. var positions = new Array(size);
  143. var uvs = new Array(size);
  144. var heights = new Array(size);
  145. var webMercatorTs = includeWebMercatorT ? new Array(size) : [];
  146. var indices = new Array(indicesSize);
  147. // Points are laid out in rows starting at SW, so storing border points as we
  148. // come across them all points will be adjacent.
  149. var westBorder = [];
  150. var southBorder = [];
  151. var eastBorder = [];
  152. var northBorder = [];
  153. // Each tile is split into 4 parts
  154. var pointOffset = 0;
  155. var indicesOffset = 0;
  156. offset = 0;
  157. for (quad = 0; quad < 4; ++quad) {
  158. quadSize = dv.getUint32(offset, true);
  159. offset += sizeOfUint32;
  160. var startQuad = offset;
  161. var originX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  162. offset += sizeOfDouble;
  163. var originY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  164. offset += sizeOfDouble;
  165. var stepX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  166. var halfStepX = stepX * 0.5;
  167. offset += sizeOfDouble;
  168. var stepY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
  169. var halfStepY = stepY * 0.5;
  170. offset += sizeOfDouble;
  171. var numPoints = dv.getInt32(offset, true);
  172. offset += sizeOfInt32;
  173. var numFaces = dv.getInt32(offset, true);
  174. offset += sizeOfInt32;
  175. //var level = dv.getInt32(offset, true);
  176. offset += sizeOfInt32;
  177. // Keep track of quad indices to overall tile indices
  178. var indicesMapping = new Array(numPoints);
  179. for (var i = 0; i < numPoints; ++i) {
  180. var longitude = originX + dv.getUint8(offset++) * stepX;
  181. scratchCartographic.longitude = longitude;
  182. var latitude = originY + dv.getUint8(offset++) * stepY;
  183. scratchCartographic.latitude = latitude;
  184. var height = dv.getFloat32(offset, true);
  185. offset += sizeOfFloat;
  186. // In order to support old clients, negative altitude values are stored as
  187. // height/-2^32. Old clients see the value as really close to 0 but new clients multiply
  188. // by -2^32 to get the real negative altitude value.
  189. if (height !== 0 && height < negativeElevationThreshold) {
  190. height *= -Math.pow(2, negativeAltitudeExponentBias);
  191. }
  192. // Height is stored in units of (1/EarthRadius) or (1/6371010.0)
  193. height *= 6371010.0 * exaggeration;
  194. scratchCartographic.height = height;
  195. // Is it along a quad border - if so check if already exists and use that index
  196. if (indexOfEpsilon(quadBorderLongitudes, longitude) !== -1 ||
  197. indexOfEpsilon(quadBorderLatitudes, latitude) !== -1) {
  198. var index = indexOfEpsilon(quadBorderPoints, scratchCartographic, Cartographic);
  199. if (index === -1) {
  200. quadBorderPoints.push(Cartographic.clone(scratchCartographic));
  201. quadBorderIndices.push(pointOffset);
  202. } else {
  203. indicesMapping[i] = quadBorderIndices[index];
  204. continue;
  205. }
  206. }
  207. indicesMapping[i] = pointOffset;
  208. if (Math.abs(longitude - geographicWest) < halfStepX) {
  209. westBorder.push({
  210. index : pointOffset,
  211. cartographic : Cartographic.clone(scratchCartographic)
  212. });
  213. } else if (Math.abs(longitude - geographicEast) < halfStepX) {
  214. eastBorder.push({
  215. index : pointOffset,
  216. cartographic : Cartographic.clone(scratchCartographic)
  217. });
  218. } else if (Math.abs(latitude - geographicSouth) < halfStepY) {
  219. southBorder.push({
  220. index : pointOffset,
  221. cartographic : Cartographic.clone(scratchCartographic)
  222. });
  223. } else if (Math.abs(latitude - geographicNorth) < halfStepY) {
  224. northBorder.push({
  225. index : pointOffset,
  226. cartographic : Cartographic.clone(scratchCartographic)
  227. });
  228. }
  229. minHeight = Math.min(height, minHeight);
  230. maxHeight = Math.max(height, maxHeight);
  231. heights[pointOffset] = height;
  232. var pos = ellipsoid.cartographicToCartesian(scratchCartographic);
  233. positions[pointOffset] = pos;
  234. if (includeWebMercatorT) {
  235. webMercatorTs[pointOffset] = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
  236. }
  237. Matrix4.multiplyByPoint(toENU, pos, scratchCartesian);
  238. Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);
  239. Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);
  240. var u = (longitude - geographicWest) / (geographicEast - geographicWest);
  241. u = CesiumMath.clamp(u, 0.0, 1.0);
  242. var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth);
  243. v = CesiumMath.clamp(v, 0.0, 1.0);
  244. uvs[pointOffset] = new Cartesian2(u, v);
  245. ++pointOffset;
  246. }
  247. var facesElementCount = numFaces * 3;
  248. for (var j = 0; j < facesElementCount; ++j, ++indicesOffset) {
  249. indices[indicesOffset] = indicesMapping[dv.getUint16(offset, true)];
  250. offset += sizeOfUint16;
  251. }
  252. if (quadSize !== (offset - startQuad)) {
  253. throw new RuntimeError('Invalid terrain tile.');
  254. }
  255. }
  256. positions.length = pointOffset;
  257. uvs.length = pointOffset;
  258. heights.length = pointOffset;
  259. if (includeWebMercatorT) {
  260. webMercatorTs.length = pointOffset;
  261. }
  262. var vertexCountWithoutSkirts = pointOffset;
  263. var skirtIndex = indicesOffset;
  264. // Add skirt points
  265. var skirtOptions = {
  266. hMin : minHeight,
  267. lastBorderPoint : undefined,
  268. skirtHeight : skirtHeight,
  269. toENU : toENU,
  270. ellipsoid : ellipsoid,
  271. minimum : minimum,
  272. maximum : maximum
  273. };
  274. // Sort counter clockwise from NW corner
  275. // Corner points are in the east/west arrays
  276. westBorder.sort(function(a, b) {
  277. return b.cartographic.latitude - a.cartographic.latitude;
  278. });
  279. southBorder.sort(function(a, b) {
  280. return a.cartographic.longitude - b.cartographic.longitude;
  281. });
  282. eastBorder.sort(function(a, b) {
  283. return a.cartographic.latitude - b.cartographic.latitude;
  284. });
  285. northBorder.sort(function(a, b) {
  286. return b.cartographic.longitude - a.cartographic.longitude;
  287. });
  288. var percentage = 0.00001;
  289. addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions,
  290. westBorder, -percentage * rectangleWidth, true, -percentage * rectangleHeight);
  291. addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions,
  292. southBorder, -percentage * rectangleHeight, false);
  293. addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions,
  294. eastBorder, percentage * rectangleWidth, true, percentage * rectangleHeight);
  295. addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions,
  296. northBorder, percentage * rectangleHeight, false);
  297. // Since the corner between the north and west sides is in the west array, generate the last
  298. // two triangles between the last north vertex and the first west vertex
  299. if (westBorder.length > 0 && northBorder.length > 0) {
  300. var firstBorderIndex = westBorder[0].index;
  301. var firstSkirtIndex = vertexCountWithoutSkirts;
  302. var lastBorderIndex = northBorder[northBorder.length - 1].index;
  303. var lastSkirtIndex = positions.length - 1;
  304. indices.push(lastBorderIndex, lastSkirtIndex, firstSkirtIndex, firstSkirtIndex, firstBorderIndex, lastBorderIndex);
  305. }
  306. size = positions.length; // Get new size with skirt vertices
  307. var boundingSphere3D = BoundingSphere.fromPoints(positions);
  308. var orientedBoundingBox;
  309. if (defined(rectangle) && rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
  310. // Here, rectangle.width < pi/2, and rectangle.height < pi
  311. // (though it would still work with rectangle.width up to pi)
  312. orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minHeight, maxHeight, ellipsoid);
  313. }
  314. var occluder = new EllipsoidalOccluder(ellipsoid);
  315. var occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions);
  316. var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
  317. var encoding = new TerrainEncoding(aaBox, skirtOptions.hMin, maxHeight, fromENU, false, includeWebMercatorT);
  318. var vertices = new Float32Array(size * encoding.getStride());
  319. var bufferIndex = 0;
  320. for (var k = 0; k < size; ++k) {
  321. bufferIndex = encoding.encode(vertices, bufferIndex, positions[k], uvs[k], heights[k], undefined, webMercatorTs[k]);
  322. }
  323. var westIndicesSouthToNorth = westBorder.map(function(vertex) { return vertex.index; }).reverse();
  324. var southIndicesEastToWest = southBorder.map(function(vertex) { return vertex.index; }).reverse();
  325. var eastIndicesNorthToSouth = eastBorder.map(function(vertex) { return vertex.index; }).reverse();
  326. var northIndicesWestToEast = northBorder.map(function(vertex) { return vertex.index; }).reverse();
  327. southIndicesEastToWest.unshift(eastIndicesNorthToSouth[eastIndicesNorthToSouth.length - 1]);
  328. southIndicesEastToWest.push(westIndicesSouthToNorth[0]);
  329. northIndicesWestToEast.unshift(westIndicesSouthToNorth[westIndicesSouthToNorth.length - 1]);
  330. northIndicesWestToEast.push(eastIndicesNorthToSouth[0]);
  331. return {
  332. vertices : vertices,
  333. indices : new Uint16Array(indices),
  334. maximumHeight : maxHeight,
  335. minimumHeight : minHeight,
  336. encoding : encoding,
  337. boundingSphere3D : boundingSphere3D,
  338. orientedBoundingBox : orientedBoundingBox,
  339. occludeePointInScaledSpace : occludeePointInScaledSpace,
  340. vertexCountWithoutSkirts : vertexCountWithoutSkirts,
  341. skirtIndex : skirtIndex,
  342. westIndicesSouthToNorth : westIndicesSouthToNorth,
  343. southIndicesEastToWest : southIndicesEastToWest,
  344. eastIndicesNorthToSouth : eastIndicesNorthToSouth,
  345. northIndicesWestToEast : northIndicesWestToEast
  346. };
  347. }
  348. function addSkirt(positions, heights, uvs, webMercatorTs, indices, skirtOptions,
  349. borderPoints, fudgeFactor, eastOrWest, cornerFudge) {
  350. var count = borderPoints.length;
  351. for (var j = 0; j < count; ++j) {
  352. var borderPoint = borderPoints[j];
  353. var borderCartographic = borderPoint.cartographic;
  354. var borderIndex = borderPoint.index;
  355. var currentIndex = positions.length;
  356. var longitude = borderCartographic.longitude;
  357. var latitude = borderCartographic.latitude;
  358. latitude = CesiumMath.clamp(latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); // Don't go over the poles
  359. var height = borderCartographic.height - skirtOptions.skirtHeight;
  360. skirtOptions.hMin = Math.min(skirtOptions.hMin, height);
  361. Cartographic.fromRadians(longitude, latitude, height, scratchCartographic);
  362. // Adjust sides to angle out
  363. if (eastOrWest) {
  364. scratchCartographic.longitude += fudgeFactor;
  365. }
  366. // Adjust top or bottom to angle out
  367. // Since corners are in the east/west arrays angle the first and last points as well
  368. if (!eastOrWest) {
  369. scratchCartographic.latitude += fudgeFactor;
  370. } else if (j === (count - 1)) {
  371. scratchCartographic.latitude += cornerFudge;
  372. } else if (j === 0) {
  373. scratchCartographic.latitude -= cornerFudge;
  374. }
  375. var pos = skirtOptions.ellipsoid.cartographicToCartesian(scratchCartographic);
  376. positions.push(pos);
  377. heights.push(height);
  378. uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point
  379. if (webMercatorTs.length > 0) {
  380. webMercatorTs.push(webMercatorTs[borderIndex]);
  381. }
  382. Matrix4.multiplyByPoint(skirtOptions.toENU, pos, scratchCartesian);
  383. var minimum = skirtOptions.minimum;
  384. var maximum = skirtOptions.maximum;
  385. Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);
  386. Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);
  387. var lastBorderPoint = skirtOptions.lastBorderPoint;
  388. if (defined(lastBorderPoint)) {
  389. var lastBorderIndex = lastBorderPoint.index;
  390. indices.push(lastBorderIndex, currentIndex - 1, currentIndex, currentIndex, borderIndex, lastBorderIndex);
  391. }
  392. skirtOptions.lastBorderPoint = borderPoint;
  393. }
  394. }
  395. export default createTaskProcessorWorker(createVerticesFromGoogleEarthEnterpriseBuffer);