earcut.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. /**
  2. *
  3. * Earcut https://github.com/mapbox/earcut
  4. *
  5. * Copyright (c) 2015, Mapbox
  6. *
  7. * Permission to use, copy, modify, and/or distribute this software for any purpose
  8. * with or without fee is hereby granted, provided that the above copyright notice
  9. * and this permission notice appear in all copies.
  10. *
  11. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  12. * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  13. * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  14. * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  15. * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  16. * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
  17. * THIS SOFTWARE.
  18. */
  19. 'use strict';
  20. //module.exports = earcut;
  21. function earcut(data, holeIndices, dim) {
  22. dim = dim || 2;
  23. var hasHoles = holeIndices && holeIndices.length,
  24. outerLen = hasHoles ? holeIndices[0] * dim : data.length,
  25. outerNode = filterPoints(data, linkedList(data, 0, outerLen, dim, true)),
  26. triangles = [];
  27. if (!outerNode) return triangles;
  28. var minX, minY, maxX, maxY, x, y, size;
  29. if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
  30. // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
  31. if (data.length > 80 * dim) {
  32. minX = maxX = data[0];
  33. minY = maxY = data[1];
  34. for (var i = dim; i < outerLen; i += dim) {
  35. x = data[i];
  36. y = data[i + 1];
  37. if (x < minX) minX = x;
  38. if (y < minY) minY = y;
  39. if (x > maxX) maxX = x;
  40. if (y > maxY) maxY = y;
  41. }
  42. // minX, minY and size are later used to transform coords into integers for z-order calculation
  43. size = Math.max(maxX - minX, maxY - minY);
  44. }
  45. earcutLinked(data, outerNode, triangles, dim, minX, minY, size);
  46. return triangles;
  47. }
  48. // create a circular doubly linked list from polygon points in the specified winding order
  49. function linkedList(data, start, end, dim, clockwise) {
  50. var sum = 0,
  51. i, j, last;
  52. // calculate original winding order of a polygon ring
  53. for (i = start, j = end - dim; i < end; i += dim) {
  54. sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
  55. j = i;
  56. }
  57. // link points into circular doubly-linked list in the specified winding order
  58. if (clockwise === (sum > 0)) {
  59. for (i = start; i < end; i += dim) last = insertNode(i, last);
  60. } else {
  61. for (i = end - dim; i >= start; i -= dim) last = insertNode(i, last);
  62. }
  63. return last;
  64. }
  65. // eliminate colinear or duplicate points
  66. function filterPoints(data, start, end) {
  67. if (!start) return start;
  68. if (!end) end = start;
  69. var node = start,
  70. again;
  71. do {
  72. again = false;
  73. if (!node.steiner && (equals(data, node.i, node.next.i) || orient(data, node.prev.i, node.i, node.next.i) === 0)) {
  74. removeNode(node);
  75. node = end = node.prev;
  76. if (node === node.next) return null;
  77. again = true;
  78. } else {
  79. node = node.next;
  80. }
  81. } while (again || node !== end);
  82. return end;
  83. }
  84. // main ear slicing loop which triangulates a polygon (given as a linked list)
  85. function earcutLinked(data, ear, triangles, dim, minX, minY, size, pass) {
  86. if (!ear) return;
  87. // interlink polygon nodes in z-order
  88. if (!pass && minX !== undefined) indexCurve(data, ear, minX, minY, size);
  89. var stop = ear,
  90. prev, next;
  91. // iterate through ears, slicing them one by one
  92. while (ear.prev !== ear.next) {
  93. prev = ear.prev;
  94. next = ear.next;
  95. if (isEar(data, ear, minX, minY, size)) {
  96. // cut off the triangle
  97. triangles.push(prev.i / dim);
  98. triangles.push(ear.i / dim);
  99. triangles.push(next.i / dim);
  100. removeNode(ear);
  101. // skipping the next vertice leads to less sliver triangles
  102. ear = next.next;
  103. stop = next.next;
  104. continue;
  105. }
  106. ear = next;
  107. // if we looped through the whole remaining polygon and can't find any more ears
  108. if (ear === stop) {
  109. // try filtering points and slicing again
  110. if (!pass) {
  111. earcutLinked(data, filterPoints(data, ear), triangles, dim, minX, minY, size, 1);
  112. // if this didn't work, try curing all small self-intersections locally
  113. } else if (pass === 1) {
  114. ear = cureLocalIntersections(data, ear, triangles, dim);
  115. earcutLinked(data, ear, triangles, dim, minX, minY, size, 2);
  116. // as a last resort, try splitting the remaining polygon into two
  117. } else if (pass === 2) {
  118. splitEarcut(data, ear, triangles, dim, minX, minY, size);
  119. }
  120. break;
  121. }
  122. }
  123. }
  124. // check whether a polygon node forms a valid ear with adjacent nodes
  125. function isEar(data, ear, minX, minY, size) {
  126. var a = ear.prev.i,
  127. b = ear.i,
  128. c = ear.next.i,
  129. ax = data[a], ay = data[a + 1],
  130. bx = data[b], by = data[b + 1],
  131. cx = data[c], cy = data[c + 1],
  132. abd = ax * by - ay * bx,
  133. acd = ax * cy - ay * cx,
  134. cbd = cx * by - cy * bx,
  135. A = abd - acd - cbd;
  136. if (A <= 0) return false; // reflex, can't be an ear
  137. // now make sure we don't have other points inside the potential ear;
  138. // the code below is a bit verbose and repetitive but this is done for performance
  139. var cay = cy - ay,
  140. acx = ax - cx,
  141. aby = ay - by,
  142. bax = bx - ax,
  143. i, px, py, s, t, k, node;
  144. // if we use z-order curve hashing, iterate through the curve
  145. if (minX !== undefined) {
  146. // triangle bbox; min & max are calculated like this for speed
  147. var minTX = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx),
  148. minTY = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy),
  149. maxTX = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx),
  150. maxTY = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy),
  151. // z-order range for the current triangle bbox;
  152. minZ = zOrder(minTX, minTY, minX, minY, size),
  153. maxZ = zOrder(maxTX, maxTY, minX, minY, size);
  154. // first look for points inside the triangle in increasing z-order
  155. node = ear.nextZ;
  156. while (node && node.z <= maxZ) {
  157. i = node.i;
  158. node = node.nextZ;
  159. if (i === a || i === c) continue;
  160. px = data[i];
  161. py = data[i + 1];
  162. s = cay * px + acx * py - acd;
  163. if (s >= 0) {
  164. t = aby * px + bax * py + abd;
  165. if (t >= 0) {
  166. k = A - s - t;
  167. if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false;
  168. }
  169. }
  170. }
  171. // then look for points in decreasing z-order
  172. node = ear.prevZ;
  173. while (node && node.z >= minZ) {
  174. i = node.i;
  175. node = node.prevZ;
  176. if (i === a || i === c) continue;
  177. px = data[i];
  178. py = data[i + 1];
  179. s = cay * px + acx * py - acd;
  180. if (s >= 0) {
  181. t = aby * px + bax * py + abd;
  182. if (t >= 0) {
  183. k = A - s - t;
  184. if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false;
  185. }
  186. }
  187. }
  188. // if we don't use z-order curve hash, simply iterate through all other points
  189. } else {
  190. node = ear.next.next;
  191. while (node !== ear.prev) {
  192. i = node.i;
  193. node = node.next;
  194. px = data[i];
  195. py = data[i + 1];
  196. s = cay * px + acx * py - acd;
  197. if (s >= 0) {
  198. t = aby * px + bax * py + abd;
  199. if (t >= 0) {
  200. k = A - s - t;
  201. if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false;
  202. }
  203. }
  204. }
  205. }
  206. return true;
  207. }
  208. // go through all polygon nodes and cure small local self-intersections
  209. function cureLocalIntersections(data, start, triangles, dim) {
  210. var node = start;
  211. do {
  212. var a = node.prev,
  213. b = node.next.next;
  214. // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
  215. if (a.i !== b.i && intersects(data, a.i, node.i, node.next.i, b.i) &&
  216. locallyInside(data, a, b) && locallyInside(data, b, a) &&
  217. orient(data, a.i, node.i, b.i) && orient(data, a.i, node.next.i, b.i)) {
  218. triangles.push(a.i / dim);
  219. triangles.push(node.i / dim);
  220. triangles.push(b.i / dim);
  221. // remove two nodes involved
  222. removeNode(node);
  223. removeNode(node.next);
  224. node = start = b;
  225. }
  226. node = node.next;
  227. } while (node !== start);
  228. return node;
  229. }
  230. // try splitting polygon into two and triangulate them independently
  231. function splitEarcut(data, start, triangles, dim, minX, minY, size) {
  232. // look for a valid diagonal that divides the polygon into two
  233. var a = start;
  234. do {
  235. var b = a.next.next;
  236. while (b !== a.prev) {
  237. if (a.i !== b.i && isValidDiagonal(data, a, b)) {
  238. // split the polygon in two by the diagonal
  239. var c = splitPolygon(a, b);
  240. // filter colinear points around the cuts
  241. a = filterPoints(data, a, a.next);
  242. c = filterPoints(data, c, c.next);
  243. // run earcut on each half
  244. earcutLinked(data, a, triangles, dim, minX, minY, size);
  245. earcutLinked(data, c, triangles, dim, minX, minY, size);
  246. return;
  247. }
  248. b = b.next;
  249. }
  250. a = a.next;
  251. } while (a !== start);
  252. }
  253. // link every hole into the outer loop, producing a single-ring polygon without holes
  254. function eliminateHoles(data, holeIndices, outerNode, dim) {
  255. var queue = [],
  256. i, len, start, end, list;
  257. for (i = 0, len = holeIndices.length; i < len; i++) {
  258. start = holeIndices[i] * dim;
  259. end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
  260. list = linkedList(data, start, end, dim, false);
  261. if (list === list.next) list.steiner = true;
  262. list = filterPoints(data, list);
  263. if (list) queue.push(getLeftmost(data, list));
  264. }
  265. queue.sort(function (a, b) {
  266. return data[a.i] - data[b.i];
  267. });
  268. // process holes from left to right
  269. for (i = 0; i < queue.length; i++) {
  270. eliminateHole(data, queue[i], outerNode);
  271. outerNode = filterPoints(data, outerNode, outerNode.next);
  272. }
  273. return outerNode;
  274. }
  275. // find a bridge between vertices that connects hole with an outer ring and and link it
  276. function eliminateHole(data, holeNode, outerNode) {
  277. outerNode = findHoleBridge(data, holeNode, outerNode);
  278. if (outerNode) {
  279. var b = splitPolygon(outerNode, holeNode);
  280. filterPoints(data, b, b.next);
  281. }
  282. }
  283. // David Eberly's algorithm for finding a bridge between hole and outer polygon
  284. function findHoleBridge(data, holeNode, outerNode) {
  285. var node = outerNode,
  286. i = holeNode.i,
  287. px = data[i],
  288. py = data[i + 1],
  289. qMax = -Infinity,
  290. mNode, a, b;
  291. // find a segment intersected by a ray from the hole's leftmost point to the left;
  292. // segment's endpoint with lesser x will be potential connection point
  293. do {
  294. a = node.i;
  295. b = node.next.i;
  296. if (py <= data[a + 1] && py >= data[b + 1]) {
  297. var qx = data[a] + (py - data[a + 1]) * (data[b] - data[a]) / (data[b + 1] - data[a + 1]);
  298. if (qx <= px && qx > qMax) {
  299. qMax = qx;
  300. mNode = data[a] < data[b] ? node : node.next;
  301. }
  302. }
  303. node = node.next;
  304. } while (node !== outerNode);
  305. if (!mNode) return null;
  306. // look for points strictly inside the triangle of hole point, segment intersection and endpoint;
  307. // if there are no points found, we have a valid connection;
  308. // otherwise choose the point of the minimum angle with the ray as connection point
  309. var bx = data[mNode.i],
  310. by = data[mNode.i + 1],
  311. pbd = px * by - py * bx,
  312. pcd = px * py - py * qMax,
  313. cpy = py - py,
  314. pcx = px - qMax,
  315. pby = py - by,
  316. bpx = bx - px,
  317. A = pbd - pcd - (qMax * by - py * bx),
  318. sign = A <= 0 ? -1 : 1,
  319. stop = mNode,
  320. tanMin = Infinity,
  321. mx, my, amx, s, t, tan;
  322. node = mNode.next;
  323. while (node !== stop) {
  324. mx = data[node.i];
  325. my = data[node.i + 1];
  326. amx = px - mx;
  327. if (amx >= 0 && mx >= bx) {
  328. s = (cpy * mx + pcx * my - pcd) * sign;
  329. if (s >= 0) {
  330. t = (pby * mx + bpx * my + pbd) * sign;
  331. if (t >= 0 && A * sign - s - t >= 0) {
  332. tan = Math.abs(py - my) / amx; // tangential
  333. if ((tan < tanMin || (tan === tanMin && mx > bx)) &&
  334. locallyInside(data, node, holeNode)) {
  335. mNode = node;
  336. tanMin = tan;
  337. }
  338. }
  339. }
  340. }
  341. node = node.next;
  342. }
  343. return mNode;
  344. }
  345. // interlink polygon nodes in z-order
  346. function indexCurve(data, start, minX, minY, size) {
  347. var node = start;
  348. do {
  349. if (node.z === null) node.z = zOrder(data[node.i], data[node.i + 1], minX, minY, size);
  350. node.prevZ = node.prev;
  351. node.nextZ = node.next;
  352. node = node.next;
  353. } while (node !== start);
  354. node.prevZ.nextZ = null;
  355. node.prevZ = null;
  356. sortLinked(node);
  357. }
  358. // Simon Tatham's linked list merge sort algorithm
  359. // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
  360. function sortLinked(list) {
  361. var i, p, q, e, tail, numMerges, pSize, qSize,
  362. inSize = 1;
  363. do {
  364. p = list;
  365. list = null;
  366. tail = null;
  367. numMerges = 0;
  368. while (p) {
  369. numMerges++;
  370. q = p;
  371. pSize = 0;
  372. for (i = 0; i < inSize; i++) {
  373. pSize++;
  374. q = q.nextZ;
  375. if (!q) break;
  376. }
  377. qSize = inSize;
  378. while (pSize > 0 || (qSize > 0 && q)) {
  379. if (pSize === 0) {
  380. e = q;
  381. q = q.nextZ;
  382. qSize--;
  383. } else if (qSize === 0 || !q) {
  384. e = p;
  385. p = p.nextZ;
  386. pSize--;
  387. } else if (p.z <= q.z) {
  388. e = p;
  389. p = p.nextZ;
  390. pSize--;
  391. } else {
  392. e = q;
  393. q = q.nextZ;
  394. qSize--;
  395. }
  396. if (tail) tail.nextZ = e;
  397. else list = e;
  398. e.prevZ = tail;
  399. tail = e;
  400. }
  401. p = q;
  402. }
  403. tail.nextZ = null;
  404. inSize *= 2;
  405. } while (numMerges > 1);
  406. return list;
  407. }
  408. // z-order of a point given coords and size of the data bounding box
  409. function zOrder(x, y, minX, minY, size) {
  410. // coords are transformed into non-negative 15-bit integer range
  411. x = 32767 * (x - minX) / size;
  412. y = 32767 * (y - minY) / size;
  413. x = (x | (x << 8)) & 0x00FF00FF;
  414. x = (x | (x << 4)) & 0x0F0F0F0F;
  415. x = (x | (x << 2)) & 0x33333333;
  416. x = (x | (x << 1)) & 0x55555555;
  417. y = (y | (y << 8)) & 0x00FF00FF;
  418. y = (y | (y << 4)) & 0x0F0F0F0F;
  419. y = (y | (y << 2)) & 0x33333333;
  420. y = (y | (y << 1)) & 0x55555555;
  421. return x | (y << 1);
  422. }
  423. // find the leftmost node of a polygon ring
  424. function getLeftmost(data, start) {
  425. var node = start,
  426. leftmost = start;
  427. do {
  428. if (data[node.i] < data[leftmost.i]) leftmost = node;
  429. node = node.next;
  430. } while (node !== start);
  431. return leftmost;
  432. }
  433. // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
  434. function isValidDiagonal(data, a, b) {
  435. return a.next.i !== b.i && a.prev.i !== b.i &&
  436. !intersectsPolygon(data, a, a.i, b.i) &&
  437. locallyInside(data, a, b) && locallyInside(data, b, a) &&
  438. middleInside(data, a, a.i, b.i);
  439. }
  440. // winding order of triangle formed by 3 given points
  441. function orient(data, p, q, r) {
  442. var o = (data[q + 1] - data[p + 1]) * (data[r] - data[q]) - (data[q] - data[p]) * (data[r + 1] - data[q + 1]);
  443. return o > 0 ? 1 :
  444. o < 0 ? -1 : 0;
  445. }
  446. // check if two points are equal
  447. function equals(data, p1, p2) {
  448. return data[p1] === data[p2] && data[p1 + 1] === data[p2 + 1];
  449. }
  450. // check if two segments intersect
  451. function intersects(data, p1, q1, p2, q2) {
  452. return orient(data, p1, q1, p2) !== orient(data, p1, q1, q2) &&
  453. orient(data, p2, q2, p1) !== orient(data, p2, q2, q1);
  454. }
  455. // check if a polygon diagonal intersects any polygon segments
  456. function intersectsPolygon(data, start, a, b) {
  457. var node = start;
  458. do {
  459. var p1 = node.i,
  460. p2 = node.next.i;
  461. if (p1 !== a && p2 !== a && p1 !== b && p2 !== b && intersects(data, p1, p2, a, b)) return true;
  462. node = node.next;
  463. } while (node !== start);
  464. return false;
  465. }
  466. // check if a polygon diagonal is locally inside the polygon
  467. function locallyInside(data, a, b) {
  468. return orient(data, a.prev.i, a.i, a.next.i) === -1 ?
  469. orient(data, a.i, b.i, a.next.i) !== -1 && orient(data, a.i, a.prev.i, b.i) !== -1 :
  470. orient(data, a.i, b.i, a.prev.i) === -1 || orient(data, a.i, a.next.i, b.i) === -1;
  471. }
  472. // check if the middle point of a polygon diagonal is inside the polygon
  473. function middleInside(data, start, a, b) {
  474. var node = start,
  475. inside = false,
  476. px = (data[a] + data[b]) / 2,
  477. py = (data[a + 1] + data[b + 1]) / 2;
  478. do {
  479. var p1 = node.i,
  480. p2 = node.next.i;
  481. if (((data[p1 + 1] > py) !== (data[p2 + 1] > py)) &&
  482. (px < (data[p2] - data[p1]) * (py - data[p1 + 1]) / (data[p2 + 1] - data[p1 + 1]) + data[p1]))
  483. inside = !inside;
  484. node = node.next;
  485. } while (node !== start);
  486. return inside;
  487. }
  488. // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
  489. // if one belongs to the outer ring and another to a hole, it merges it into a single ring
  490. function splitPolygon(a, b) {
  491. var a2 = new Node(a.i),
  492. b2 = new Node(b.i),
  493. an = a.next,
  494. bp = b.prev;
  495. a.next = b;
  496. b.prev = a;
  497. a2.next = an;
  498. an.prev = a2;
  499. b2.next = a2;
  500. a2.prev = b2;
  501. bp.next = b2;
  502. b2.prev = bp;
  503. return b2;
  504. }
  505. // create a node and optionally link it with previous one (in a circular doubly linked list)
  506. function insertNode(i, last) {
  507. var node = new Node(i);
  508. if (!last) {
  509. node.prev = node;
  510. node.next = node;
  511. } else {
  512. node.next = last.next;
  513. node.prev = last;
  514. last.next.prev = node;
  515. last.next = node;
  516. }
  517. return node;
  518. }
  519. function removeNode(node) {
  520. node.next.prev = node.prev;
  521. node.prev.next = node.next;
  522. if (node.prevZ) node.prevZ.nextZ = node.nextZ;
  523. if (node.nextZ) node.nextZ.prevZ = node.prevZ;
  524. }
  525. function Node(i) {
  526. // vertex coordinates
  527. this.i = i;
  528. // previous and next vertice nodes in a polygon ring
  529. this.prev = null;
  530. this.next = null;
  531. // z-order curve value
  532. this.z = null;
  533. // previous and next nodes in z-order
  534. this.prevZ = null;
  535. this.nextZ = null;
  536. // indicates whether this is a steiner point
  537. this.steiner = false;
  538. }