Clipper.Core.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Runtime.CompilerServices;
  5. namespace Clipper2Lib
  6. {
  7. public struct Point64
  8. {
  9. public long X;
  10. public long Y;
  11. #if USINGZ
  12. public long Z;
  13. public Point64(Point64 pt)
  14. {
  15. X = pt.X;
  16. Y = pt.Y;
  17. Z = pt.Z;
  18. }
  19. public Point64(Point64 pt, double scale)
  20. {
  21. X = (long) Math.Round(pt.X * scale);
  22. Y = (long) Math.Round(pt.Y * scale);
  23. Z = (long) Math.Round(pt.Z * scale);
  24. }
  25. public Point64(long x, long y, long z = 0)
  26. {
  27. X = x;
  28. Y = y;
  29. Z = z;
  30. }
  31. public Point64(double x, double y, double z = 0.0)
  32. {
  33. X = (long) Math.Round(x);
  34. Y = (long) Math.Round(y);
  35. Z = (long) Math.Round(z);
  36. }
  37. public Point64(PointD pt)
  38. {
  39. X = (long) Math.Round(pt.x);
  40. Y = (long) Math.Round(pt.y);
  41. Z = pt.z;
  42. }
  43. public Point64(PointD pt, double scale)
  44. {
  45. X = (long) Math.Round(pt.x * scale);
  46. Y = (long) Math.Round(pt.y * scale);
  47. Z = pt.z;
  48. }
  49. public static bool operator ==(Point64 lhs, Point64 rhs)
  50. {
  51. return lhs.X == rhs.X && lhs.Y == rhs.Y;
  52. }
  53. public static bool operator !=(Point64 lhs, Point64 rhs)
  54. {
  55. return lhs.X != rhs.X || lhs.Y != rhs.Y;
  56. }
  57. public static Point64 operator +(Point64 lhs, Point64 rhs)
  58. {
  59. return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z);
  60. }
  61. public static Point64 operator -(Point64 lhs, Point64 rhs)
  62. {
  63. return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z);
  64. }
  65. public override string ToString()
  66. {
  67. return $"{X},{Y},{Z} "; // nb: trailing space
  68. }
  69. #else
  70. public Point64(Point64 pt)
  71. {
  72. X = pt.X;
  73. Y = pt.Y;
  74. }
  75. public Point64(long x, long y)
  76. {
  77. X = x;
  78. Y = y;
  79. }
  80. public Point64(double x, double y)
  81. {
  82. X = (long)Math.Round(x);
  83. Y = (long)Math.Round(y);
  84. }
  85. public Point64(PointD pt)
  86. {
  87. X = (long)Math.Round(pt.x);
  88. Y = (long)Math.Round(pt.y);
  89. }
  90. public Point64(Point64 pt, double scale)
  91. {
  92. X = (long)Math.Round(pt.X * scale);
  93. Y = (long)Math.Round(pt.Y * scale);
  94. }
  95. public Point64(PointD pt, double scale)
  96. {
  97. X = (long)Math.Round(pt.x * scale);
  98. Y = (long)Math.Round(pt.y * scale);
  99. }
  100. public static bool operator ==(Point64 lhs, Point64 rhs)
  101. {
  102. return lhs.X == rhs.X && lhs.Y == rhs.Y;
  103. }
  104. public static bool operator !=(Point64 lhs, Point64 rhs)
  105. {
  106. return lhs.X != rhs.X || lhs.Y != rhs.Y;
  107. }
  108. public static Point64 operator +(Point64 lhs, Point64 rhs)
  109. {
  110. return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y);
  111. }
  112. public static Point64 operator -(Point64 lhs, Point64 rhs)
  113. {
  114. return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y);
  115. }
  116. public override string ToString()
  117. {
  118. return $"{X},{Y} "; // nb: trailing space
  119. }
  120. #endif
  121. public override bool Equals(object? obj)
  122. {
  123. if (obj != null && obj is Point64 p)
  124. {
  125. return this == p;
  126. }
  127. return false;
  128. }
  129. public override int GetHashCode()
  130. { return 0; }
  131. }
  132. public struct PointD
  133. {
  134. public double x;
  135. public double y;
  136. #if USINGZ
  137. public long z;
  138. public PointD(PointD pt)
  139. {
  140. x = pt.x;
  141. y = pt.y;
  142. z = pt.z;
  143. }
  144. public PointD(Point64 pt)
  145. {
  146. x = pt.X;
  147. y = pt.Y;
  148. z = pt.Z;
  149. }
  150. public PointD(Point64 pt, double scale)
  151. {
  152. x = pt.X * scale;
  153. y = pt.Y * scale;
  154. z = pt.Z;
  155. }
  156. public PointD(PointD pt, double scale)
  157. {
  158. x = pt.x * scale;
  159. y = pt.y * scale;
  160. z = pt.z;
  161. }
  162. public PointD(long x, long y, long z = 0)
  163. {
  164. this.x = x;
  165. this.y = y;
  166. this.z = z;
  167. }
  168. public PointD(double x, double y, long z = 0)
  169. {
  170. this.x = x;
  171. this.y = y;
  172. this.z = z;
  173. }
  174. public override string ToString()
  175. {
  176. return $"{x:F},{y:F},{z} ";
  177. }
  178. #else
  179. public PointD(PointD pt)
  180. {
  181. x = pt.x;
  182. y = pt.y;
  183. }
  184. public PointD(Point64 pt)
  185. {
  186. x = pt.X;
  187. y = pt.Y;
  188. }
  189. public PointD(PointD pt, double scale)
  190. {
  191. x = pt.x * scale;
  192. y = pt.y * scale;
  193. }
  194. public PointD(Point64 pt, double scale)
  195. {
  196. x = pt.X * scale;
  197. y = pt.Y * scale;
  198. }
  199. public PointD(long x, long y)
  200. {
  201. this.x = x;
  202. this.y = y;
  203. }
  204. public PointD(double x, double y)
  205. {
  206. this.x = x;
  207. this.y = y;
  208. }
  209. public override string ToString()
  210. {
  211. return $"{x:F},{y:F} ";
  212. }
  213. #endif
  214. public static bool operator ==(PointD lhs, PointD rhs)
  215. {
  216. return InternalClipper.IsAlmostZero(lhs.x - rhs.x) &&
  217. InternalClipper.IsAlmostZero(lhs.y - rhs.y);
  218. }
  219. public static bool operator !=(PointD lhs, PointD rhs)
  220. {
  221. return !InternalClipper.IsAlmostZero(lhs.x - rhs.x) ||
  222. !InternalClipper.IsAlmostZero(lhs.y - rhs.y);
  223. }
  224. public override bool Equals(object? obj)
  225. {
  226. if (obj != null && obj is PointD p)
  227. {
  228. return this == p;
  229. }
  230. return false;
  231. }
  232. public void Negate()
  233. { x = -x; y = -y; }
  234. public override int GetHashCode()
  235. { return 0; }
  236. }
  237. public struct Rect64
  238. {
  239. public long left;
  240. public long top;
  241. public long right;
  242. public long bottom;
  243. public Rect64(long l, long t, long r, long b)
  244. {
  245. left = l;
  246. top = t;
  247. right = r;
  248. bottom = b;
  249. }
  250. public Rect64(Rect64 rec)
  251. {
  252. left = rec.left;
  253. top = rec.top;
  254. right = rec.right;
  255. bottom = rec.bottom;
  256. }
  257. public long Width
  258. {
  259. get => right - left;
  260. set => right = left + value;
  261. }
  262. public long Height
  263. {
  264. get => bottom - top;
  265. set => bottom = top + value;
  266. }
  267. public bool IsEmpty()
  268. {
  269. return bottom <= top || right <= left;
  270. }
  271. public Point64 MidPoint()
  272. {
  273. return new Point64((left + right) / 2, (top + bottom) / 2);
  274. }
  275. public bool Contains(Point64 pt)
  276. {
  277. return pt.X > left && pt.X < right &&
  278. pt.Y > top && pt.Y < bottom;
  279. }
  280. public bool Contains(Rect64 rec)
  281. {
  282. return rec.left >= left && rec.right <= right &&
  283. rec.top >= top && rec.bottom <= bottom;
  284. }
  285. public bool Intersects(Rect64 rec)
  286. {
  287. return (Math.Max(left, rec.left) < Math.Min(right, rec.right)) &&
  288. (Math.Max(top, rec.top) < Math.Min(bottom, rec.bottom));
  289. }
  290. public Path64 AsPath()
  291. {
  292. Path64 result = new Path64(4)
  293. {
  294. new Point64(left, top),
  295. new Point64(right, top),
  296. new Point64(right, bottom),
  297. new Point64(left, bottom)
  298. };
  299. return result;
  300. }
  301. }
  302. public struct RectD
  303. {
  304. public double left;
  305. public double top;
  306. public double right;
  307. public double bottom;
  308. public RectD(double l, double t, double r, double b)
  309. {
  310. left = l;
  311. top = t;
  312. right = r;
  313. bottom = b;
  314. }
  315. public RectD(RectD rec)
  316. {
  317. left = rec.left;
  318. top = rec.top;
  319. right = rec.right;
  320. bottom = rec.bottom;
  321. }
  322. public double Width
  323. {
  324. get => right - left;
  325. set => right = left + value;
  326. }
  327. public double Height
  328. {
  329. get => bottom - top;
  330. set => bottom = top + value;
  331. }
  332. public bool IsEmpty()
  333. {
  334. return bottom <= top || right <= left;
  335. }
  336. public PointD MidPoint()
  337. {
  338. return new PointD((left + right) / 2, (top + bottom) / 2);
  339. }
  340. public bool Contains(PointD pt)
  341. {
  342. return pt.x > left && pt.x < right &&
  343. pt.y > top && pt.y < bottom;
  344. }
  345. public bool Contains(RectD rec)
  346. {
  347. return rec.left >= left && rec.right <= right &&
  348. rec.top >= top && rec.bottom <= bottom;
  349. }
  350. public bool Intersects(RectD rec)
  351. {
  352. return (Math.Max(left, rec.left) < Math.Min(right, rec.right)) &&
  353. (Math.Max(top, rec.top) < Math.Min(bottom, rec.bottom));
  354. }
  355. public PathD AsPath()
  356. {
  357. PathD result = new PathD(4)
  358. {
  359. new PointD(left, top),
  360. new PointD(right, top),
  361. new PointD(right, bottom),
  362. new PointD(left, bottom)
  363. };
  364. return result;
  365. }
  366. }
  367. public class Path64 : List<Point64>
  368. {
  369. private Path64() : base()
  370. {
  371. }
  372. public Path64(int capacity = 0) : base(capacity)
  373. {
  374. }
  375. public Path64(IEnumerable<Point64> path) : base(path)
  376. {
  377. }
  378. public override string ToString()
  379. {
  380. string s = "";
  381. foreach (Point64 p in this)
  382. {
  383. s = s + p.ToString() + " ";
  384. }
  385. return s;
  386. }
  387. }
  388. public class Paths64 : List<Path64>
  389. {
  390. private Paths64() : base()
  391. {
  392. }
  393. public Paths64(int capacity = 0) : base(capacity)
  394. {
  395. }
  396. public Paths64(IEnumerable<Path64> paths) : base(paths)
  397. {
  398. }
  399. public override string ToString()
  400. {
  401. string s = "";
  402. foreach (Path64 p in this)
  403. {
  404. s = s + p.ToString() + "\n";
  405. }
  406. return s;
  407. }
  408. }
  409. public class PathD : List<PointD>
  410. {
  411. private PathD() : base()
  412. {
  413. }
  414. public PathD(int capacity = 0) : base(capacity)
  415. {
  416. }
  417. public PathD(IEnumerable<PointD> path) : base(path)
  418. {
  419. }
  420. public override string ToString()
  421. {
  422. string s = "";
  423. foreach (PointD p in this)
  424. {
  425. s = s + p.ToString() + " ";
  426. }
  427. return s;
  428. }
  429. }
  430. public class PathsD : List<PathD>
  431. {
  432. private PathsD() : base()
  433. {
  434. }
  435. public PathsD(int capacity = 0) : base(capacity)
  436. {
  437. }
  438. public PathsD(IEnumerable<PathD> paths) : base(paths)
  439. {
  440. }
  441. public override string ToString()
  442. {
  443. string s = "";
  444. foreach (PathD p in this)
  445. {
  446. s = s + p.ToString() + "\n";
  447. }
  448. return s;
  449. }
  450. }
  451. // Note: all clipping operations except for Difference are commutative.
  452. public enum ClipType
  453. {
  454. None,
  455. Intersection,
  456. Union,
  457. Difference,
  458. Xor
  459. };
  460. public enum PathType
  461. {
  462. Subject,
  463. Clip
  464. };
  465. // By far the most widely used filling rules for polygons are EvenOdd
  466. // and NonZero, sometimes called Alternate and Winding respectively.
  467. // https://en.wikipedia.org/wiki/Nonzero-rule
  468. public enum FillRule
  469. {
  470. EvenOdd,
  471. NonZero,
  472. Positive,
  473. Negative
  474. };
  475. // PointInPolygon
  476. internal enum PipResult
  477. {
  478. Inside,
  479. Outside,
  480. OnEdge
  481. };
  482. public static class InternalClipper
  483. {
  484. internal const long MaxInt64 = 9223372036854775807;
  485. internal const long MaxCoord = MaxInt64 / 4;
  486. internal const double max_coord = MaxCoord;
  487. internal const double min_coord = -MaxCoord;
  488. internal const long Invalid64 = MaxInt64;
  489. internal const double floatingPointTolerance = 1E-12;
  490. internal const double defaultMinimumEdgeLength = 0.1;
  491. private static readonly string
  492. precision_range_error = "Error: Precision is out of range.";
  493. internal static void CheckPrecision(int precision)
  494. {
  495. if (precision < -8 || precision > 8)
  496. {
  497. throw new Exception(precision_range_error);
  498. }
  499. }
  500. internal static bool IsAlmostZero(double value)
  501. {
  502. return (Math.Abs(value) <= floatingPointTolerance);
  503. }
  504. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  505. internal static double CrossProduct(Point64 pt1, Point64 pt2, Point64 pt3)
  506. {
  507. // typecast to double to avoid potential int overflow
  508. return ((double)(pt2.X - pt1.X) * (pt3.Y - pt2.Y) -
  509. (double)(pt2.Y - pt1.Y) * (pt3.X - pt2.X));
  510. }
  511. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  512. internal static double DotProduct(Point64 pt1, Point64 pt2, Point64 pt3)
  513. {
  514. // typecast to double to avoid potential int overflow
  515. return ((double)(pt2.X - pt1.X) * (pt3.X - pt2.X) +
  516. (double)(pt2.Y - pt1.Y) * (pt3.Y - pt2.Y));
  517. }
  518. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  519. internal static double CrossProduct(PointD vec1, PointD vec2)
  520. {
  521. return (vec1.y * vec2.x - vec2.y * vec1.x);
  522. }
  523. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  524. internal static double DotProduct(PointD vec1, PointD vec2)
  525. {
  526. return (vec1.x * vec2.x + vec1.y * vec2.y);
  527. }
  528. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  529. internal static long CheckCastInt64(double val)
  530. {
  531. if ((val >= max_coord) || (val <= min_coord))
  532. {
  533. return Invalid64;
  534. }
  535. return (long)Math.Round(val);
  536. }
  537. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  538. internal static bool GetIntersectPt(Point64 ln1a,
  539. Point64 ln1b, Point64 ln2a, Point64 ln2b, out Point64 ip)
  540. {
  541. double dy1 = (ln1b.Y - ln1a.Y);
  542. double dx1 = (ln1b.X - ln1a.X);
  543. double dy2 = (ln2b.Y - ln2a.Y);
  544. double dx2 = (ln2b.X - ln2a.X);
  545. double cp = dy1 * dx2 - dy2 * dx1;
  546. if (cp == 0.0)
  547. {
  548. ip = new Point64();
  549. return false;
  550. }
  551. double qx = dx1 * ln1a.Y - dy1 * ln1a.X;
  552. double qy = dx2 * ln2a.Y - dy2 * ln2a.X;
  553. ip = new Point64(
  554. CheckCastInt64((dx1 * qy - dx2 * qx) / cp),
  555. CheckCastInt64((dy1 * qy - dy2 * qx) / cp));
  556. return (ip.X != Invalid64 && ip.Y != Invalid64);
  557. }
  558. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  559. internal static bool GetIntersectPoint(Point64 ln1a,
  560. Point64 ln1b, Point64 ln2a, Point64 ln2b, out PointD ip)
  561. {
  562. double dy1 = (ln1b.Y - ln1a.Y);
  563. double dx1 = (ln1b.X - ln1a.X);
  564. double dy2 = (ln2b.Y - ln2a.Y);
  565. double dx2 = (ln2b.X - ln2a.X);
  566. double q1 = dy1 * ln1a.X - dx1 * ln1a.Y;
  567. double q2 = dy2 * ln2a.X - dx2 * ln2a.Y;
  568. double cross_prod = dy1 * dx2 - dy2 * dx1;
  569. if (cross_prod == 0.0)
  570. {
  571. ip = new PointD();
  572. return false;
  573. }
  574. ip = new PointD(
  575. (dx2 * q1 - dx1 * q2) / cross_prod,
  576. (dy2 * q1 - dy1 * q2) / cross_prod);
  577. return true;
  578. }
  579. internal static bool SegsIntersect(Point64 seg1a,
  580. Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false)
  581. {
  582. if (inclusive)
  583. {
  584. double res1 = CrossProduct(seg1a, seg2a, seg2b);
  585. double res2 = CrossProduct(seg1b, seg2a, seg2b);
  586. if (res1 * res2 > 0)
  587. {
  588. return false;
  589. }
  590. double res3 = CrossProduct(seg2a, seg1a, seg1b);
  591. double res4 = CrossProduct(seg2b, seg1a, seg1b);
  592. if (res3 * res4 > 0)
  593. {
  594. return false;
  595. }
  596. // ensure NOT collinear
  597. return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0);
  598. }
  599. else
  600. {
  601. return (CrossProduct(seg1a, seg2a, seg2b) *
  602. CrossProduct(seg1b, seg2a, seg2b) < 0) &&
  603. (CrossProduct(seg2a, seg1a, seg1b) *
  604. CrossProduct(seg2b, seg1a, seg1b) < 0);
  605. }
  606. }
  607. public static Point64 GetClosestPtOnSegment(Point64 offPt,
  608. Point64 seg1, Point64 seg2)
  609. {
  610. if (seg1.X == seg2.X && seg1.Y == seg2.Y)
  611. {
  612. return seg1;
  613. }
  614. double dx = (seg2.X - seg1.X);
  615. double dy = (seg2.Y - seg1.Y);
  616. double q = ((offPt.X - seg1.X) * dx +
  617. (offPt.Y - seg1.Y) * dy) / ((dx * dx) + (dy * dy));
  618. if (q < 0)
  619. {
  620. q = 0;
  621. }
  622. else if (q > 1)
  623. {
  624. q = 1;
  625. }
  626. return new Point64(
  627. seg1.X + Math.Round(q * dx), seg1.Y + Math.Round(q * dy));
  628. }
  629. public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon)
  630. {
  631. int len = polygon.Count, i = len - 1;
  632. if (len < 3)
  633. {
  634. return PointInPolygonResult.IsOutside;
  635. }
  636. while (i >= 0 && polygon[i].Y == pt.Y)
  637. {
  638. --i;
  639. }
  640. if (i < 0)
  641. {
  642. return PointInPolygonResult.IsOutside;
  643. }
  644. int val = 0;
  645. bool isAbove = polygon[i].Y < pt.Y;
  646. i = 0;
  647. while (i < len)
  648. {
  649. if (isAbove)
  650. {
  651. while (i < len && polygon[i].Y < pt.Y)
  652. {
  653. i++;
  654. }
  655. if (i == len)
  656. {
  657. break;
  658. }
  659. }
  660. else
  661. {
  662. while (i < len && polygon[i].Y > pt.Y)
  663. {
  664. i++;
  665. }
  666. if (i == len)
  667. {
  668. break;
  669. }
  670. }
  671. Point64 prev;
  672. Point64 curr = polygon[i];
  673. if (i > 0)
  674. {
  675. prev = polygon[i - 1];
  676. }
  677. else
  678. {
  679. prev = polygon[len - 1];
  680. }
  681. if (curr.Y == pt.Y)
  682. {
  683. if (curr.X == pt.X || (curr.Y == prev.Y &&
  684. ((pt.X < prev.X) != (pt.X < curr.X))))
  685. {
  686. return PointInPolygonResult.IsOn;
  687. }
  688. i++;
  689. continue;
  690. }
  691. if (pt.X < curr.X && pt.X < prev.X)
  692. {
  693. // we're only interested in edges crossing on the left
  694. }
  695. else if (pt.X > prev.X && pt.X > curr.X)
  696. {
  697. val = 1 - val; // toggle val
  698. }
  699. else
  700. {
  701. double d = CrossProduct(prev, curr, pt);
  702. if (d == 0)
  703. {
  704. return PointInPolygonResult.IsOn;
  705. }
  706. if ((d < 0) == isAbove)
  707. {
  708. val = 1 - val;
  709. }
  710. }
  711. isAbove = !isAbove;
  712. i++;
  713. }
  714. if (val == 0)
  715. {
  716. return PointInPolygonResult.IsOutside;
  717. }
  718. return PointInPolygonResult.IsInside;
  719. }
  720. } // InternalClipper
  721. } // namespace