PolygonPipeline.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import earcut from '../ThirdParty/earcut-2.1.1.js';
  2. import Cartesian2 from './Cartesian2.js';
  3. import Cartesian3 from './Cartesian3.js';
  4. import Cartographic from './Cartographic.js';
  5. import Check from './Check.js';
  6. import ComponentDatatype from './ComponentDatatype.js';
  7. import defaultValue from './defaultValue.js';
  8. import defined from './defined.js';
  9. import Ellipsoid from './Ellipsoid.js';
  10. import EllipsoidRhumbLine from './EllipsoidRhumbLine.js';
  11. import Geometry from './Geometry.js';
  12. import GeometryAttribute from './GeometryAttribute.js';
  13. import CesiumMath from './Math.js';
  14. import PrimitiveType from './PrimitiveType.js';
  15. import WindingOrder from './WindingOrder.js';
  16. var scaleToGeodeticHeightN = new Cartesian3();
  17. var scaleToGeodeticHeightP = new Cartesian3();
  18. /**
  19. * @private
  20. */
  21. var PolygonPipeline = {};
  22. /**
  23. * @exception {DeveloperError} At least three positions are required.
  24. */
  25. PolygonPipeline.computeArea2D = function(positions) {
  26. //>>includeStart('debug', pragmas.debug);
  27. Check.defined('positions', positions);
  28. Check.typeOf.number.greaterThanOrEquals('positions.length', positions.length, 3);
  29. //>>includeEnd('debug');
  30. var length = positions.length;
  31. var area = 0.0;
  32. for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) {
  33. var v0 = positions[i0];
  34. var v1 = positions[i1];
  35. area += (v0.x * v1.y) - (v1.x * v0.y);
  36. }
  37. return area * 0.5;
  38. };
  39. /**
  40. * @returns {WindingOrder} The winding order.
  41. *
  42. * @exception {DeveloperError} At least three positions are required.
  43. */
  44. PolygonPipeline.computeWindingOrder2D = function(positions) {
  45. var area = PolygonPipeline.computeArea2D(positions);
  46. return (area > 0.0) ? WindingOrder.COUNTER_CLOCKWISE : WindingOrder.CLOCKWISE;
  47. };
  48. /**
  49. * Triangulate a polygon.
  50. *
  51. * @param {Cartesian2[]} positions Cartesian2 array containing the vertices of the polygon
  52. * @param {Number[]} [holes] An array of the staring indices of the holes.
  53. * @returns {Number[]} Index array representing triangles that fill the polygon
  54. */
  55. PolygonPipeline.triangulate = function(positions, holes) {
  56. //>>includeStart('debug', pragmas.debug);
  57. Check.defined('positions', positions);
  58. //>>includeEnd('debug');
  59. var flattenedPositions = Cartesian2.packArray(positions);
  60. return earcut(flattenedPositions, holes, 2);
  61. };
  62. var subdivisionV0Scratch = new Cartesian3();
  63. var subdivisionV1Scratch = new Cartesian3();
  64. var subdivisionV2Scratch = new Cartesian3();
  65. var subdivisionS0Scratch = new Cartesian3();
  66. var subdivisionS1Scratch = new Cartesian3();
  67. var subdivisionS2Scratch = new Cartesian3();
  68. var subdivisionMidScratch = new Cartesian3();
  69. /**
  70. * Subdivides positions and raises points to the surface of the ellipsoid.
  71. *
  72. * @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on.
  73. * @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon.
  74. * @param {Number[]} indices An array of indices that determines the triangles in the polygon.
  75. * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  76. *
  77. * @exception {DeveloperError} At least three indices are required.
  78. * @exception {DeveloperError} The number of indices must be divisable by three.
  79. * @exception {DeveloperError} Granularity must be greater than zero.
  80. */
  81. PolygonPipeline.computeSubdivision = function(ellipsoid, positions, indices, granularity) {
  82. granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE);
  83. //>>includeStart('debug', pragmas.debug);
  84. Check.typeOf.object('ellipsoid', ellipsoid);
  85. Check.defined('positions', positions);
  86. Check.defined('indices', indices);
  87. Check.typeOf.number.greaterThanOrEquals('indices.length', indices.length, 3);
  88. Check.typeOf.number.equals('indices.length % 3', '0', indices.length % 3, 0);
  89. Check.typeOf.number.greaterThan('granularity', granularity, 0.0);
  90. //>>includeEnd('debug');
  91. // triangles that need (or might need) to be subdivided.
  92. var triangles = indices.slice(0);
  93. // New positions due to edge splits are appended to the positions list.
  94. var i;
  95. var length = positions.length;
  96. var subdividedPositions = new Array(length * 3);
  97. var q = 0;
  98. for (i = 0; i < length; i++) {
  99. var item = positions[i];
  100. subdividedPositions[q++] = item.x;
  101. subdividedPositions[q++] = item.y;
  102. subdividedPositions[q++] = item.z;
  103. }
  104. var subdividedIndices = [];
  105. // Used to make sure shared edges are not split more than once.
  106. var edges = {};
  107. var radius = ellipsoid.maximumRadius;
  108. var minDistance = CesiumMath.chordLength(granularity, radius);
  109. var minDistanceSqrd = minDistance * minDistance;
  110. while (triangles.length > 0) {
  111. var i2 = triangles.pop();
  112. var i1 = triangles.pop();
  113. var i0 = triangles.pop();
  114. var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch);
  115. var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch);
  116. var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch);
  117. var s0 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v0, subdivisionS0Scratch), radius, subdivisionS0Scratch);
  118. var s1 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v1, subdivisionS1Scratch), radius, subdivisionS1Scratch);
  119. var s2 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v2, subdivisionS2Scratch), radius, subdivisionS2Scratch);
  120. var g0 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s0, s1, subdivisionMidScratch));
  121. var g1 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s1, s2, subdivisionMidScratch));
  122. var g2 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s2, s0, subdivisionMidScratch));
  123. var max = Math.max(g0, g1, g2);
  124. var edge;
  125. var mid;
  126. // if the max length squared of a triangle edge is greater than the chord length of squared
  127. // of the granularity, subdivide the triangle
  128. if (max > minDistanceSqrd) {
  129. if (g0 === max) {
  130. edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);
  131. i = edges[edge];
  132. if (!defined(i)) {
  133. mid = Cartesian3.add(v0, v1, subdivisionMidScratch);
  134. Cartesian3.multiplyByScalar(mid, 0.5, mid);
  135. subdividedPositions.push(mid.x, mid.y, mid.z);
  136. i = subdividedPositions.length / 3 - 1;
  137. edges[edge] = i;
  138. }
  139. triangles.push(i0, i, i2);
  140. triangles.push(i, i1, i2);
  141. } else if (g1 === max) {
  142. edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2);
  143. i = edges[edge];
  144. if (!defined(i)) {
  145. mid = Cartesian3.add(v1, v2, subdivisionMidScratch);
  146. Cartesian3.multiplyByScalar(mid, 0.5, mid);
  147. subdividedPositions.push(mid.x, mid.y, mid.z);
  148. i = subdividedPositions.length / 3 - 1;
  149. edges[edge] = i;
  150. }
  151. triangles.push(i1, i, i0);
  152. triangles.push(i, i2, i0);
  153. } else if (g2 === max) {
  154. edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0);
  155. i = edges[edge];
  156. if (!defined(i)) {
  157. mid = Cartesian3.add(v2, v0, subdivisionMidScratch);
  158. Cartesian3.multiplyByScalar(mid, 0.5, mid);
  159. subdividedPositions.push(mid.x, mid.y, mid.z);
  160. i = subdividedPositions.length / 3 - 1;
  161. edges[edge] = i;
  162. }
  163. triangles.push(i2, i, i1);
  164. triangles.push(i, i0, i1);
  165. }
  166. } else {
  167. subdividedIndices.push(i0);
  168. subdividedIndices.push(i1);
  169. subdividedIndices.push(i2);
  170. }
  171. }
  172. return new Geometry({
  173. attributes : {
  174. position : new GeometryAttribute({
  175. componentDatatype : ComponentDatatype.DOUBLE,
  176. componentsPerAttribute : 3,
  177. values : subdividedPositions
  178. })
  179. },
  180. indices : subdividedIndices,
  181. primitiveType : PrimitiveType.TRIANGLES
  182. });
  183. };
  184. var subdivisionC0Scratch = new Cartographic();
  185. var subdivisionC1Scratch = new Cartographic();
  186. var subdivisionC2Scratch = new Cartographic();
  187. var subdivisionCartographicScratch = new Cartographic();
  188. /**
  189. * Subdivides positions on rhumb lines and raises points to the surface of the ellipsoid.
  190. *
  191. * @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on.
  192. * @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon.
  193. * @param {Number[]} indices An array of indices that determines the triangles in the polygon.
  194. * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  195. *
  196. * @exception {DeveloperError} At least three indices are required.
  197. * @exception {DeveloperError} The number of indices must be divisable by three.
  198. * @exception {DeveloperError} Granularity must be greater than zero.
  199. */
  200. PolygonPipeline.computeRhumbLineSubdivision = function(ellipsoid, positions, indices, granularity) {
  201. granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE);
  202. //>>includeStart('debug', pragmas.debug);
  203. Check.typeOf.object('ellipsoid', ellipsoid);
  204. Check.defined('positions', positions);
  205. Check.defined('indices', indices);
  206. Check.typeOf.number.greaterThanOrEquals('indices.length', indices.length, 3);
  207. Check.typeOf.number.equals('indices.length % 3', '0', indices.length % 3, 0);
  208. Check.typeOf.number.greaterThan('granularity', granularity, 0.0);
  209. //>>includeEnd('debug');
  210. // triangles that need (or might need) to be subdivided.
  211. var triangles = indices.slice(0);
  212. // New positions due to edge splits are appended to the positions list.
  213. var i;
  214. var length = positions.length;
  215. var subdividedPositions = new Array(length * 3);
  216. var q = 0;
  217. for (i = 0; i < length; i++) {
  218. var item = positions[i];
  219. subdividedPositions[q++] = item.x;
  220. subdividedPositions[q++] = item.y;
  221. subdividedPositions[q++] = item.z;
  222. }
  223. var subdividedIndices = [];
  224. // Used to make sure shared edges are not split more than once.
  225. var edges = {};
  226. var radius = ellipsoid.maximumRadius;
  227. var minDistance = CesiumMath.chordLength(granularity, radius);
  228. var rhumb0 = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  229. var rhumb1 = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  230. var rhumb2 = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  231. while (triangles.length > 0) {
  232. var i2 = triangles.pop();
  233. var i1 = triangles.pop();
  234. var i0 = triangles.pop();
  235. var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch);
  236. var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch);
  237. var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch);
  238. var c0 = ellipsoid.cartesianToCartographic(v0, subdivisionC0Scratch);
  239. var c1 = ellipsoid.cartesianToCartographic(v1, subdivisionC1Scratch);
  240. var c2 = ellipsoid.cartesianToCartographic(v2, subdivisionC2Scratch);
  241. rhumb0.setEndPoints(c0, c1);
  242. var g0 = rhumb0.surfaceDistance;
  243. rhumb1.setEndPoints(c1, c2);
  244. var g1 = rhumb1.surfaceDistance;
  245. rhumb2.setEndPoints(c2, c0);
  246. var g2 = rhumb2.surfaceDistance;
  247. var max = Math.max(g0, g1, g2);
  248. var edge;
  249. var mid;
  250. var midHeight;
  251. var midCartesian3;
  252. // if the max length squared of a triangle edge is greater than granularity, subdivide the triangle
  253. if (max > minDistance) {
  254. if (g0 === max) {
  255. edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);
  256. i = edges[edge];
  257. if (!defined(i)) {
  258. mid = rhumb0.interpolateUsingFraction(0.5, subdivisionCartographicScratch);
  259. midHeight = (c0.height + c1.height) * 0.5;
  260. midCartesian3 = Cartesian3.fromRadians(mid.longitude, mid.latitude, midHeight, ellipsoid, subdivisionMidScratch);
  261. subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
  262. i = subdividedPositions.length / 3 - 1;
  263. edges[edge] = i;
  264. }
  265. triangles.push(i0, i, i2);
  266. triangles.push(i, i1, i2);
  267. } else if (g1 === max) {
  268. edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2);
  269. i = edges[edge];
  270. if (!defined(i)) {
  271. mid = rhumb1.interpolateUsingFraction(0.5, subdivisionCartographicScratch);
  272. midHeight = (c1.height + c2.height) * 0.5;
  273. midCartesian3 = Cartesian3.fromRadians(mid.longitude, mid.latitude, midHeight, ellipsoid, subdivisionMidScratch);
  274. subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
  275. i = subdividedPositions.length / 3 - 1;
  276. edges[edge] = i;
  277. }
  278. triangles.push(i1, i, i0);
  279. triangles.push(i, i2, i0);
  280. } else if (g2 === max) {
  281. edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0);
  282. i = edges[edge];
  283. if (!defined(i)) {
  284. mid = rhumb2.interpolateUsingFraction(0.5, subdivisionCartographicScratch);
  285. midHeight = (c2.height + c0.height) * 0.5;
  286. midCartesian3 = Cartesian3.fromRadians(mid.longitude, mid.latitude, midHeight, ellipsoid, subdivisionMidScratch);
  287. subdividedPositions.push(midCartesian3.x, midCartesian3.y, midCartesian3.z);
  288. i = subdividedPositions.length / 3 - 1;
  289. edges[edge] = i;
  290. }
  291. triangles.push(i2, i, i1);
  292. triangles.push(i, i0, i1);
  293. }
  294. } else {
  295. subdividedIndices.push(i0);
  296. subdividedIndices.push(i1);
  297. subdividedIndices.push(i2);
  298. }
  299. }
  300. return new Geometry({
  301. attributes : {
  302. position : new GeometryAttribute({
  303. componentDatatype : ComponentDatatype.DOUBLE,
  304. componentsPerAttribute : 3,
  305. values : subdividedPositions
  306. })
  307. },
  308. indices : subdividedIndices,
  309. primitiveType : PrimitiveType.TRIANGLES
  310. });
  311. };
  312. /**
  313. * Scales each position of a geometry's position attribute to a height, in place.
  314. *
  315. * @param {Number[]} positions The array of numbers representing the positions to be scaled
  316. * @param {Number} [height=0.0] The desired height to add to the positions
  317. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
  318. * @param {Boolean} [scaleToSurface=true] <code>true</code> if the positions need to be scaled to the surface before the height is added.
  319. * @returns {Number[]} The input array of positions, scaled to height
  320. */
  321. PolygonPipeline.scaleToGeodeticHeight = function(positions, height, ellipsoid, scaleToSurface) {
  322. ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
  323. var n = scaleToGeodeticHeightN;
  324. var p = scaleToGeodeticHeightP;
  325. height = defaultValue(height, 0.0);
  326. scaleToSurface = defaultValue(scaleToSurface, true);
  327. if (defined(positions)) {
  328. var length = positions.length;
  329. for ( var i = 0; i < length; i += 3) {
  330. Cartesian3.fromArray(positions, i, p);
  331. if (scaleToSurface) {
  332. p = ellipsoid.scaleToGeodeticSurface(p, p);
  333. }
  334. if (height !== 0) {
  335. n = ellipsoid.geodeticSurfaceNormal(p, n);
  336. Cartesian3.multiplyByScalar(n, height, n);
  337. Cartesian3.add(p, n, p);
  338. }
  339. positions[i] = p.x;
  340. positions[i + 1] = p.y;
  341. positions[i + 2] = p.z;
  342. }
  343. }
  344. return positions;
  345. };
  346. export default PolygonPipeline;