SimplifyTools.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Microsoft.Xna.Framework;
  5. namespace FarseerPhysics.Common.PolygonManipulation
  6. {
  7. public static class SimplifyTools
  8. {
  9. private static bool[] _usePt;
  10. private static double _distanceTolerance;
  11. /// <summary>
  12. /// Removes all collinear points on the polygon.
  13. /// </summary>
  14. /// <param name="vertices">The polygon that needs simplification.</param>
  15. /// <param name="collinearityTolerance">The collinearity tolerance.</param>
  16. /// <returns>A simplified polygon.</returns>
  17. public static Vertices CollinearSimplify(Vertices vertices, float collinearityTolerance)
  18. {
  19. //We can't simplify polygons under 3 vertices
  20. if (vertices.Count < 3)
  21. return vertices;
  22. Vertices simplified = new Vertices();
  23. for (int i = 0; i < vertices.Count; i++)
  24. {
  25. int prevId = vertices.PreviousIndex(i);
  26. int nextId = vertices.NextIndex(i);
  27. Vector2 prev = vertices[prevId];
  28. Vector2 current = vertices[i];
  29. Vector2 next = vertices[nextId];
  30. //If they collinear, continue
  31. if (MathUtils.Collinear(ref prev, ref current, ref next, collinearityTolerance))
  32. continue;
  33. simplified.Add(current);
  34. }
  35. return simplified;
  36. }
  37. /// <summary>
  38. /// Removes all collinear points on the polygon.
  39. /// Has a default bias of 0
  40. /// </summary>
  41. /// <param name="vertices">The polygon that needs simplification.</param>
  42. /// <returns>A simplified polygon.</returns>
  43. public static Vertices CollinearSimplify(Vertices vertices)
  44. {
  45. return CollinearSimplify(vertices, 0);
  46. }
  47. /// <summary>
  48. /// Ramer-Douglas-Peucker polygon simplification algorithm. This is the general recursive version that does not use the
  49. /// speed-up technique by using the Melkman convex hull.
  50. ///
  51. /// If you pass in 0, it will remove all collinear points
  52. /// </summary>
  53. /// <returns>The simplified polygon</returns>
  54. public static Vertices DouglasPeuckerSimplify(Vertices vertices, float distanceTolerance)
  55. {
  56. _distanceTolerance = distanceTolerance;
  57. _usePt = new bool[vertices.Count];
  58. for (int i = 0; i < vertices.Count; i++)
  59. _usePt[i] = true;
  60. SimplifySection(vertices, 0, vertices.Count - 1);
  61. Vertices result = new Vertices();
  62. for (int i = 0; i < vertices.Count; i++)
  63. if (_usePt[i])
  64. result.Add(vertices[i]);
  65. return result;
  66. }
  67. private static void SimplifySection(Vertices vertices, int i, int j)
  68. {
  69. if ((i + 1) == j)
  70. return;
  71. Vector2 A = vertices[i];
  72. Vector2 B = vertices[j];
  73. double maxDistance = -1.0;
  74. int maxIndex = i;
  75. for (int k = i + 1; k < j; k++)
  76. {
  77. double distance = DistancePointLine(vertices[k], A, B);
  78. if (distance > maxDistance)
  79. {
  80. maxDistance = distance;
  81. maxIndex = k;
  82. }
  83. }
  84. if (maxDistance <= _distanceTolerance)
  85. for (int k = i + 1; k < j; k++)
  86. _usePt[k] = false;
  87. else
  88. {
  89. SimplifySection(vertices, i, maxIndex);
  90. SimplifySection(vertices, maxIndex, j);
  91. }
  92. }
  93. private static double DistancePointPoint(Vector2 p, Vector2 p2)
  94. {
  95. double dx = p.X - p2.X;
  96. double dy = p.Y - p2.X;
  97. return Math.Sqrt(dx * dx + dy * dy);
  98. }
  99. private static double DistancePointLine(Vector2 p, Vector2 A, Vector2 B)
  100. {
  101. // if start == end, then use point-to-point distance
  102. if (A.X == B.X && A.Y == B.Y)
  103. return DistancePointPoint(p, A);
  104. // otherwise use comp.graphics.algorithms Frequently Asked Questions method
  105. /*(1) AC dot AB
  106. r = ---------
  107. ||AB||^2
  108. r has the following meaning:
  109. r=0 Point = A
  110. r=1 Point = B
  111. r<0 Point is on the backward extension of AB
  112. r>1 Point is on the forward extension of AB
  113. 0<r<1 Point is interior to AB
  114. */
  115. double r = ((p.X - A.X) * (B.X - A.X) + (p.Y - A.Y) * (B.Y - A.Y))
  116. /
  117. ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y));
  118. if (r <= 0.0) return DistancePointPoint(p, A);
  119. if (r >= 1.0) return DistancePointPoint(p, B);
  120. /*(2)
  121. (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
  122. s = -----------------------------
  123. Curve^2
  124. Then the distance from C to Point = |s|*Curve.
  125. */
  126. double s = ((A.Y - p.Y) * (B.X - A.X) - (A.X - p.X) * (B.Y - A.Y))
  127. /
  128. ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y));
  129. return Math.Abs(s) * Math.Sqrt(((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)));
  130. }
  131. //From physics2d.net
  132. public static Vertices ReduceByArea(Vertices vertices, float areaTolerance)
  133. {
  134. if (vertices.Count <= 3)
  135. return vertices;
  136. if (areaTolerance < 0)
  137. {
  138. throw new ArgumentOutOfRangeException("areaTolerance", "must be equal to or greater then zero.");
  139. }
  140. Vertices result = new Vertices();
  141. Vector2 v1, v2, v3;
  142. float old1, old2, new1;
  143. v1 = vertices[vertices.Count - 2];
  144. v2 = vertices[vertices.Count - 1];
  145. areaTolerance *= 2;
  146. for (int index = 0; index < vertices.Count; ++index, v2 = v3)
  147. {
  148. if (index == vertices.Count - 1)
  149. {
  150. if (result.Count == 0)
  151. {
  152. throw new ArgumentOutOfRangeException("areaTolerance", "The tolerance is too high!");
  153. }
  154. v3 = result[0];
  155. }
  156. else
  157. {
  158. v3 = vertices[index];
  159. }
  160. MathUtils.Cross(ref v1, ref v2, out old1);
  161. MathUtils.Cross(ref v2, ref v3, out old2);
  162. MathUtils.Cross(ref v1, ref v3, out new1);
  163. if (Math.Abs(new1 - (old1 + old2)) > areaTolerance)
  164. {
  165. result.Add(v2);
  166. v1 = v2;
  167. }
  168. }
  169. return result;
  170. }
  171. //From Eric Jordan's convex decomposition library
  172. /// <summary>
  173. /// Merges all parallel edges in the list of vertices
  174. /// </summary>
  175. /// <param name="vertices">The vertices.</param>
  176. /// <param name="tolerance">The tolerance.</param>
  177. public static void MergeParallelEdges(Vertices vertices, float tolerance)
  178. {
  179. if (vertices.Count <= 3)
  180. return; //Can't do anything useful here to a triangle
  181. bool[] mergeMe = new bool[vertices.Count];
  182. int newNVertices = vertices.Count;
  183. //Gather points to process
  184. for (int i = 0; i < vertices.Count; ++i)
  185. {
  186. int lower = (i == 0) ? (vertices.Count - 1) : (i - 1);
  187. int middle = i;
  188. int upper = (i == vertices.Count - 1) ? (0) : (i + 1);
  189. float dx0 = vertices[middle].X - vertices[lower].X;
  190. float dy0 = vertices[middle].Y - vertices[lower].Y;
  191. float dx1 = vertices[upper].Y - vertices[middle].X;
  192. float dy1 = vertices[upper].Y - vertices[middle].Y;
  193. float norm0 = (float)Math.Sqrt(dx0 * dx0 + dy0 * dy0);
  194. float norm1 = (float)Math.Sqrt(dx1 * dx1 + dy1 * dy1);
  195. if (!(norm0 > 0.0f && norm1 > 0.0f) && newNVertices > 3)
  196. {
  197. //Merge identical points
  198. mergeMe[i] = true;
  199. --newNVertices;
  200. }
  201. dx0 /= norm0;
  202. dy0 /= norm0;
  203. dx1 /= norm1;
  204. dy1 /= norm1;
  205. float cross = dx0 * dy1 - dx1 * dy0;
  206. float dot = dx0 * dx1 + dy0 * dy1;
  207. if (Math.Abs(cross) < tolerance && dot > 0 && newNVertices > 3)
  208. {
  209. mergeMe[i] = true;
  210. --newNVertices;
  211. }
  212. else
  213. mergeMe[i] = false;
  214. }
  215. if (newNVertices == vertices.Count || newNVertices == 0)
  216. return;
  217. int currIndex = 0;
  218. //Copy the vertices to a new list and clear the old
  219. Vertices oldVertices = new Vertices(vertices);
  220. vertices.Clear();
  221. for (int i = 0; i < oldVertices.Count; ++i)
  222. {
  223. if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices)
  224. continue;
  225. Debug.Assert(currIndex < newNVertices);
  226. vertices.Add(oldVertices[i]);
  227. ++currIndex;
  228. }
  229. }
  230. //Misc
  231. /// <summary>
  232. /// Merges the identical points in the polygon.
  233. /// </summary>
  234. /// <param name="vertices">The vertices.</param>
  235. /// <returns></returns>
  236. public static Vertices MergeIdenticalPoints(Vertices vertices)
  237. {
  238. //We use a dictonary here because HashSet is not avaliable on all platforms.
  239. HashSet<Vector2> results = new HashSet<Vector2>();
  240. for (int i = 0; i < vertices.Count; i++)
  241. {
  242. results.Add(vertices[i]);
  243. }
  244. Vertices returnResults = new Vertices();
  245. foreach (Vector2 v in results)
  246. {
  247. returnResults.Add(v);
  248. }
  249. return returnResults;
  250. }
  251. /// <summary>
  252. /// Reduces the polygon by distance.
  253. /// </summary>
  254. /// <param name="vertices">The vertices.</param>
  255. /// <param name="distance">The distance between points. Points closer than this will be 'joined'.</param>
  256. /// <returns></returns>
  257. public static Vertices ReduceByDistance(Vertices vertices, float distance)
  258. {
  259. //We can't simplify polygons under 3 vertices
  260. if (vertices.Count < 3)
  261. return vertices;
  262. Vertices simplified = new Vertices();
  263. for (int i = 0; i < vertices.Count; i++)
  264. {
  265. Vector2 current = vertices[i];
  266. Vector2 next = vertices.NextVertex(i);
  267. //If they are closer than the distance, continue
  268. if ((next - current).LengthSquared() <= distance)
  269. continue;
  270. simplified.Add(current);
  271. }
  272. return simplified;
  273. }
  274. /// <summary>
  275. /// Reduces the polygon by removing the Nth vertex in the vertices list.
  276. /// </summary>
  277. /// <param name="vertices">The vertices.</param>
  278. /// <param name="nth">The Nth point to remove. Example: 5.</param>
  279. /// <returns></returns>
  280. public static Vertices ReduceByNth(Vertices vertices, int nth)
  281. {
  282. //We can't simplify polygons under 3 vertices
  283. if (vertices.Count < 3)
  284. return vertices;
  285. if (nth == 0)
  286. return vertices;
  287. Vertices result = new Vertices(vertices.Count);
  288. for (int i = 0; i < vertices.Count; i++)
  289. {
  290. if (i % nth == 0)
  291. continue;
  292. result.Add(vertices[i]);
  293. }
  294. return result;
  295. }
  296. }
  297. }