Triangulator.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. using Microsoft.Xna.Framework;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Text;
  6. namespace MonoGame.Extended.Triangulation
  7. {
  8. /// <summary>
  9. /// MIT Licensed: https://github.com/nickgravelyn/Triangulator
  10. ///
  11. /// A static class exposing methods for triangulating 2D polygons. This is the sole public
  12. /// class in the entire library; all other classes/structures are intended as internal-only
  13. /// objects used only to assist in triangulation.
  14. ///
  15. /// This class makes use of the DEBUG conditional and produces quite verbose output when built
  16. /// in Debug mode. This is quite useful for debugging purposes, but can slow the process down
  17. /// quite a bit. For optimal performance, build the library in Release mode.
  18. ///
  19. /// The triangulation is also not optimized for garbage sensitive processing. The point of the
  20. /// library is a robust, yet simple, system for triangulating 2D shapes. It is intended to be
  21. /// used as part of your content pipeline or at load-time. It is not something you want to be
  22. /// using each and every frame unless you really don't care about garbage.
  23. /// </summary>
  24. public static class Triangulator
  25. {
  26. #region Fields
  27. static readonly IndexableCyclicalLinkedList<Vertex> polygonVertices = new IndexableCyclicalLinkedList<Vertex>();
  28. static readonly IndexableCyclicalLinkedList<Vertex> earVertices = new IndexableCyclicalLinkedList<Vertex>();
  29. static readonly CyclicalList<Vertex> convexVertices = new CyclicalList<Vertex>();
  30. static readonly CyclicalList<Vertex> reflexVertices = new CyclicalList<Vertex>();
  31. #endregion
  32. #region Public Methods
  33. #region Triangulate
  34. /// <summary>
  35. /// Triangulates a 2D polygon produced the indexes required to render the points as a triangle list.
  36. /// </summary>
  37. /// <param name="inputVertices">The polygon vertices in counter-clockwise winding order.</param>
  38. /// <param name="desiredWindingOrder">The desired output winding order.</param>
  39. /// <param name="outputVertices">The resulting vertices that include any reversals of winding order and holes.</param>
  40. /// <param name="indices">The resulting indices for rendering the shape as a triangle list.</param>
  41. public static void Triangulate(
  42. Vector2[] inputVertices,
  43. WindingOrder desiredWindingOrder,
  44. out Vector2[] outputVertices,
  45. out int[] indices)
  46. {
  47. Log("\nBeginning triangulation...");
  48. List<Triangle> triangles = new List<Triangle>();
  49. //make sure we have our vertices wound properly
  50. if (DetermineWindingOrder(inputVertices) == WindingOrder.Clockwise)
  51. outputVertices = ReverseWindingOrder(inputVertices);
  52. else
  53. outputVertices = (Vector2[])inputVertices.Clone();
  54. //clear all of the lists
  55. polygonVertices.Clear();
  56. earVertices.Clear();
  57. convexVertices.Clear();
  58. reflexVertices.Clear();
  59. //generate the cyclical list of vertices in the polygon
  60. for (int i = 0; i < outputVertices.Length; i++)
  61. polygonVertices.AddLast(new Vertex(outputVertices[i], i));
  62. //categorize all of the vertices as convex, reflex, and ear
  63. FindConvexAndReflexVertices();
  64. FindEarVertices();
  65. //clip all the ear vertices
  66. while (polygonVertices.Count > 3 && earVertices.Count > 0)
  67. ClipNextEar(triangles);
  68. //if there are still three points, use that for the last triangle
  69. if (polygonVertices.Count == 3)
  70. triangles.Add(new Triangle(
  71. polygonVertices[0].Value,
  72. polygonVertices[1].Value,
  73. polygonVertices[2].Value));
  74. //add all of the triangle indices to the output array
  75. indices = new int[triangles.Count * 3];
  76. //move the if statement out of the loop to prevent all the
  77. //redundant comparisons
  78. if (desiredWindingOrder == WindingOrder.CounterClockwise)
  79. {
  80. for (int i = 0; i < triangles.Count; i++)
  81. {
  82. indices[(i * 3)] = triangles[i].A.Index;
  83. indices[(i * 3) + 1] = triangles[i].B.Index;
  84. indices[(i * 3) + 2] = triangles[i].C.Index;
  85. }
  86. }
  87. else
  88. {
  89. for (int i = 0; i < triangles.Count; i++)
  90. {
  91. indices[(i * 3)] = triangles[i].C.Index;
  92. indices[(i * 3) + 1] = triangles[i].B.Index;
  93. indices[(i * 3) + 2] = triangles[i].A.Index;
  94. }
  95. }
  96. }
  97. #endregion
  98. #region CutHoleInShape
  99. /// <summary>
  100. /// Cuts a hole into a shape.
  101. /// </summary>
  102. /// <param name="shapeVerts">An array of vertices for the primary shape.</param>
  103. /// <param name="holeVerts">An array of vertices for the hole to be cut. It is assumed that these vertices lie completely within the shape verts.</param>
  104. /// <returns>The new array of vertices that can be passed to Triangulate to properly triangulate the shape with the hole.</returns>
  105. public static Vector2[] CutHoleInShape(Vector2[] shapeVerts, Vector2[] holeVerts)
  106. {
  107. Log("\nCutting hole into shape...");
  108. //make sure the shape vertices are wound counter clockwise and the hole vertices clockwise
  109. shapeVerts = EnsureWindingOrder(shapeVerts, WindingOrder.CounterClockwise);
  110. holeVerts = EnsureWindingOrder(holeVerts, WindingOrder.Clockwise);
  111. //clear all of the lists
  112. polygonVertices.Clear();
  113. earVertices.Clear();
  114. convexVertices.Clear();
  115. reflexVertices.Clear();
  116. //generate the cyclical list of vertices in the polygon
  117. for (int i = 0; i < shapeVerts.Length; i++)
  118. polygonVertices.AddLast(new Vertex(shapeVerts[i], i));
  119. CyclicalList<Vertex> holePolygon = new CyclicalList<Vertex>();
  120. for (int i = 0; i < holeVerts.Length; i++)
  121. holePolygon.Add(new Vertex(holeVerts[i], i + polygonVertices.Count));
  122. #if DEBUG
  123. StringBuilder vString = new StringBuilder();
  124. foreach (Vertex v in polygonVertices)
  125. vString.Append(string.Format("{0}, ", v));
  126. Log("Shape Vertices: {0}", vString);
  127. vString = new StringBuilder();
  128. foreach (Vertex v in holePolygon)
  129. vString.Append(string.Format("{0}, ", v));
  130. Log("Hole Vertices: {0}", vString);
  131. #endif
  132. FindConvexAndReflexVertices();
  133. FindEarVertices();
  134. //find the hole vertex with the largest X value
  135. Vertex rightMostHoleVertex = holePolygon[0];
  136. foreach (Vertex v in holePolygon)
  137. if (v.Position.X > rightMostHoleVertex.Position.X)
  138. rightMostHoleVertex = v;
  139. //construct a list of all line segments where at least one vertex
  140. //is to the right of the rightmost hole vertex with one vertex
  141. //above the hole vertex and one below
  142. List<LineSegment> segmentsToTest = new List<LineSegment>();
  143. for (int i = 0; i < polygonVertices.Count; i++)
  144. {
  145. Vertex a = polygonVertices[i].Value;
  146. Vertex b = polygonVertices[i + 1].Value;
  147. if ((a.Position.X > rightMostHoleVertex.Position.X || b.Position.X > rightMostHoleVertex.Position.X) &&
  148. ((a.Position.Y >= rightMostHoleVertex.Position.Y && b.Position.Y <= rightMostHoleVertex.Position.Y) ||
  149. (a.Position.Y <= rightMostHoleVertex.Position.Y && b.Position.Y >= rightMostHoleVertex.Position.Y)))
  150. segmentsToTest.Add(new LineSegment(a, b));
  151. }
  152. //now we try to find the closest intersection point heading to the right from
  153. //our hole vertex.
  154. float? closestPoint = null;
  155. LineSegment closestSegment = new LineSegment();
  156. foreach (LineSegment segment in segmentsToTest)
  157. {
  158. float? intersection = segment.IntersectsWithRay(rightMostHoleVertex.Position, Vector2.UnitX);
  159. if (intersection != null)
  160. {
  161. if (closestPoint == null || closestPoint.Value > intersection.Value)
  162. {
  163. closestPoint = intersection;
  164. closestSegment = segment;
  165. }
  166. }
  167. }
  168. //if closestPoint is null, there were no collisions (likely from improper input data),
  169. //but we'll just return without doing anything else
  170. if (closestPoint == null)
  171. return shapeVerts;
  172. //otherwise we can find our mutually visible vertex to split the polygon
  173. Vector2 I = rightMostHoleVertex.Position + Vector2.UnitX * closestPoint.Value;
  174. Vertex P = (closestSegment.A.Position.X > closestSegment.B.Position.X)
  175. ? closestSegment.A
  176. : closestSegment.B;
  177. //construct triangle MIP
  178. Triangle mip = new Triangle(rightMostHoleVertex, new Vertex(I, 1), P);
  179. //see if any of the reflex vertices lie inside of the MIP triangle
  180. List<Vertex> interiorReflexVertices = new List<Vertex>();
  181. foreach (Vertex v in reflexVertices)
  182. if (mip.ContainsPoint(v))
  183. interiorReflexVertices.Add(v);
  184. //if there are any interior reflex vertices, find the one that, when connected
  185. //to our rightMostHoleVertex, forms the line closest to Vector2.UnitX
  186. if (interiorReflexVertices.Count > 0)
  187. {
  188. float closestDot = -1f;
  189. foreach (Vertex v in interiorReflexVertices)
  190. {
  191. //compute the dot product of the vector against the UnitX
  192. Vector2 d = Vector2.Normalize(v.Position - rightMostHoleVertex.Position);
  193. float dot = Vector2.Dot(Vector2.UnitX, d);
  194. //if this line is the closest we've found
  195. if (dot > closestDot)
  196. {
  197. //save the value and save the vertex as P
  198. closestDot = dot;
  199. P = v;
  200. }
  201. }
  202. }
  203. //now we just form our output array by injecting the hole vertices into place
  204. //we know we have to inject the hole into the main array after point P going from
  205. //rightMostHoleVertex around and then back to P.
  206. int mIndex = holePolygon.IndexOf(rightMostHoleVertex);
  207. int injectPoint = polygonVertices.IndexOf(P);
  208. Log("Inserting hole at injection point {0} starting at hole vertex {1}.",
  209. P,
  210. rightMostHoleVertex);
  211. for (int i = mIndex; i <= mIndex + holePolygon.Count; i++)
  212. {
  213. Log("Inserting vertex {0} after vertex {1}.", holePolygon[i], polygonVertices[injectPoint].Value);
  214. polygonVertices.AddAfter(polygonVertices[injectPoint++], holePolygon[i]);
  215. }
  216. polygonVertices.AddAfter(polygonVertices[injectPoint], P);
  217. #if DEBUG
  218. vString = new StringBuilder();
  219. foreach (Vertex v in polygonVertices)
  220. vString.Append(string.Format("{0}, ", v));
  221. Log("New Shape Vertices: {0}\n", vString);
  222. #endif
  223. //finally we write out the new polygon vertices and return them out
  224. Vector2[] newShapeVerts = new Vector2[polygonVertices.Count];
  225. for (int i = 0; i < polygonVertices.Count; i++)
  226. newShapeVerts[i] = polygonVertices[i].Value.Position;
  227. return newShapeVerts;
  228. }
  229. #endregion
  230. #region EnsureWindingOrder
  231. /// <summary>
  232. /// Ensures that a set of vertices are wound in a particular order, reversing them if necessary.
  233. /// </summary>
  234. /// <param name="vertices">The vertices of the polygon.</param>
  235. /// <param name="windingOrder">The desired winding order.</param>
  236. /// <returns>A new set of vertices if the winding order didn't match; otherwise the original set.</returns>
  237. public static Vector2[] EnsureWindingOrder(Vector2[] vertices, WindingOrder windingOrder)
  238. {
  239. Log("\nEnsuring winding order of {0}...", windingOrder);
  240. if (DetermineWindingOrder(vertices) != windingOrder)
  241. {
  242. Log("Reversing vertices...");
  243. return ReverseWindingOrder(vertices);
  244. }
  245. Log("No reversal needed.");
  246. return vertices;
  247. }
  248. #endregion
  249. #region ReverseWindingOrder
  250. /// <summary>
  251. /// Reverses the winding order for a set of vertices.
  252. /// </summary>
  253. /// <param name="vertices">The vertices of the polygon.</param>
  254. /// <returns>The new vertices for the polygon with the opposite winding order.</returns>
  255. public static Vector2[] ReverseWindingOrder(Vector2[] vertices)
  256. {
  257. Log("\nReversing winding order...");
  258. Vector2[] newVerts = new Vector2[vertices.Length];
  259. #if DEBUG
  260. StringBuilder vString = new StringBuilder();
  261. foreach (Vector2 v in vertices)
  262. vString.Append(string.Format("{0}, ", v));
  263. Log("Original Vertices: {0}", vString);
  264. #endif
  265. newVerts[0] = vertices[0];
  266. for (int i = 1; i < newVerts.Length; i++)
  267. newVerts[i] = vertices[vertices.Length - i];
  268. #if DEBUG
  269. vString = new StringBuilder();
  270. foreach (Vector2 v in newVerts)
  271. vString.Append(string.Format("{0}, ", v));
  272. Log("New Vertices After Reversal: {0}\n", vString);
  273. #endif
  274. return newVerts;
  275. }
  276. #endregion
  277. #region DetermineWindingOrder
  278. /// <summary>
  279. /// Determines the winding order of a polygon given a set of vertices.
  280. /// </summary>
  281. /// <param name="vertices">The vertices of the polygon.</param>
  282. /// <returns>The calculated winding order of the polygon.</returns>
  283. public static WindingOrder DetermineWindingOrder(Vector2[] vertices)
  284. {
  285. int clockWiseCount = 0;
  286. int counterClockWiseCount = 0;
  287. Vector2 p1 = vertices[0];
  288. for (int i = 1; i < vertices.Length; i++)
  289. {
  290. Vector2 p2 = vertices[i];
  291. Vector2 p3 = vertices[(i + 1) % vertices.Length];
  292. Vector2 e1 = p1 - p2;
  293. Vector2 e2 = p3 - p2;
  294. if (e1.X * e2.Y - e1.Y * e2.X >= 0)
  295. clockWiseCount++;
  296. else
  297. counterClockWiseCount++;
  298. p1 = p2;
  299. }
  300. return (clockWiseCount > counterClockWiseCount)
  301. ? WindingOrder.Clockwise
  302. : WindingOrder.CounterClockwise;
  303. }
  304. #endregion
  305. #endregion
  306. #region Private Methods
  307. #region ClipNextEar
  308. private static void ClipNextEar(ICollection<Triangle> triangles)
  309. {
  310. //find the triangle
  311. Vertex ear = earVertices[0].Value;
  312. Vertex prev = polygonVertices[polygonVertices.IndexOf(ear) - 1].Value;
  313. Vertex next = polygonVertices[polygonVertices.IndexOf(ear) + 1].Value;
  314. triangles.Add(new Triangle(ear, next, prev));
  315. //remove the ear from the shape
  316. earVertices.RemoveAt(0);
  317. polygonVertices.RemoveAt(polygonVertices.IndexOf(ear));
  318. Log("\nRemoved Ear: {0}", ear);
  319. //validate the neighboring vertices
  320. ValidateAdjacentVertex(prev);
  321. ValidateAdjacentVertex(next);
  322. //write out the states of each of the lists
  323. #if DEBUG
  324. StringBuilder rString = new StringBuilder();
  325. foreach (Vertex v in reflexVertices)
  326. rString.Append(string.Format("{0}, ", v.Index));
  327. Log("Reflex Vertices: {0}", rString);
  328. StringBuilder cString = new StringBuilder();
  329. foreach (Vertex v in convexVertices)
  330. cString.Append(string.Format("{0}, ", v.Index));
  331. Log("Convex Vertices: {0}", cString);
  332. StringBuilder eString = new StringBuilder();
  333. foreach (Vertex v in earVertices)
  334. eString.Append(string.Format("{0}, ", v.Index));
  335. Log("Ear Vertices: {0}", eString);
  336. #endif
  337. }
  338. #endregion
  339. #region ValidateAdjacentVertex
  340. private static void ValidateAdjacentVertex(Vertex vertex)
  341. {
  342. Log("Validating: {0}...", vertex);
  343. if (reflexVertices.Contains(vertex))
  344. {
  345. if (IsConvex(vertex))
  346. {
  347. reflexVertices.Remove(vertex);
  348. convexVertices.Add(vertex);
  349. Log("Vertex: {0} now convex", vertex);
  350. }
  351. else
  352. {
  353. Log("Vertex: {0} still reflex", vertex);
  354. }
  355. }
  356. if (convexVertices.Contains(vertex))
  357. {
  358. bool wasEar = earVertices.Contains(vertex);
  359. bool isEar = IsEar(vertex);
  360. if (wasEar && !isEar)
  361. {
  362. earVertices.Remove(vertex);
  363. Log("Vertex: {0} no longer ear", vertex);
  364. }
  365. else if (!wasEar && isEar)
  366. {
  367. earVertices.AddFirst(vertex);
  368. Log("Vertex: {0} now ear", vertex);
  369. }
  370. else
  371. {
  372. Log("Vertex: {0} still ear", vertex);
  373. }
  374. }
  375. }
  376. #endregion
  377. #region FindConvexAndReflexVertices
  378. private static void FindConvexAndReflexVertices()
  379. {
  380. for (int i = 0; i < polygonVertices.Count; i++)
  381. {
  382. Vertex v = polygonVertices[i].Value;
  383. if (IsConvex(v))
  384. {
  385. convexVertices.Add(v);
  386. Log("Convex: {0}", v);
  387. }
  388. else
  389. {
  390. reflexVertices.Add(v);
  391. Log("Reflex: {0}", v);
  392. }
  393. }
  394. }
  395. #endregion
  396. #region FindEarVertices
  397. private static void FindEarVertices()
  398. {
  399. for (int i = 0; i < convexVertices.Count; i++)
  400. {
  401. Vertex c = convexVertices[i];
  402. if (IsEar(c))
  403. {
  404. earVertices.AddLast(c);
  405. Log("Ear: {0}", c);
  406. }
  407. }
  408. }
  409. #endregion
  410. #region IsEar
  411. private static bool IsEar(Vertex c)
  412. {
  413. Vertex p = polygonVertices[polygonVertices.IndexOf(c) - 1].Value;
  414. Vertex n = polygonVertices[polygonVertices.IndexOf(c) + 1].Value;
  415. Log("Testing vertex {0} as ear with triangle {1}, {0}, {2}...", c, p, n);
  416. foreach (Vertex t in reflexVertices)
  417. {
  418. if (t.Equals(p) || t.Equals(c) || t.Equals(n))
  419. continue;
  420. if (Triangle.ContainsPoint(p, c, n, t))
  421. {
  422. Log("\tTriangle contains vertex {0}...", t);
  423. return false;
  424. }
  425. }
  426. return true;
  427. }
  428. #endregion
  429. #region IsConvex
  430. private static bool IsConvex(Vertex c)
  431. {
  432. Vertex p = polygonVertices[polygonVertices.IndexOf(c) - 1].Value;
  433. Vertex n = polygonVertices[polygonVertices.IndexOf(c) + 1].Value;
  434. Vector2 d1 = Vector2.Normalize(c.Position - p.Position);
  435. Vector2 d2 = Vector2.Normalize(n.Position - c.Position);
  436. Vector2 n2 = new Vector2(-d2.Y, d2.X);
  437. return (Vector2.Dot(d1, n2) <= 0f);
  438. }
  439. #endregion
  440. #region IsReflex
  441. private static bool IsReflex(Vertex c)
  442. {
  443. return !IsConvex(c);
  444. }
  445. #endregion
  446. #region Log
  447. [Conditional("DEBUG")]
  448. private static void Log(string format, params object[] parameters)
  449. {
  450. //System.Console.WriteLine(format, parameters);
  451. }
  452. #endregion
  453. #endregion
  454. }
  455. /// <summary>
  456. /// Specifies a desired winding order for the shape vertices.
  457. /// </summary>
  458. public enum WindingOrder
  459. {
  460. Clockwise,
  461. CounterClockwise
  462. }
  463. }