PolygonPipeline.js 16 KB

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