VertexSorter.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // -----------------------------------------------------------------------
  2. // <copyright file="VertexSorter.cs" company="">
  3. // Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
  4. // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
  5. // </copyright>
  6. // -----------------------------------------------------------------------
  7. namespace UnityEngine.U2D.Animation.TriangleNet
  8. .Tools
  9. {
  10. using System;
  11. using Animation.TriangleNet.Geometry;
  12. /// <summary>
  13. /// Sort an array of points using quicksort.
  14. /// </summary>
  15. internal class VertexSorter
  16. {
  17. private const int RANDOM_SEED = 57113;
  18. Random rand;
  19. Vertex[] points;
  20. VertexSorter(Vertex[] points, int seed)
  21. {
  22. this.points = points;
  23. this.rand = new Random(seed);
  24. }
  25. /// <summary>
  26. /// Sorts the given vertex array by x-coordinate.
  27. /// </summary>
  28. /// <param name="array">The vertex array.</param>
  29. /// <param name="seed">Random seed used for pivoting.</param>
  30. internal static void Sort(Vertex[] array, int seed = RANDOM_SEED)
  31. {
  32. var qs = new VertexSorter(array, seed);
  33. qs.QuickSort(0, array.Length - 1);
  34. }
  35. /// <summary>
  36. /// Impose alternating cuts on given vertex array.
  37. /// </summary>
  38. /// <param name="array">The vertex array.</param>
  39. /// <param name="length">The number of vertices to sort.</param>
  40. /// <param name="seed">Random seed used for pivoting.</param>
  41. internal static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED)
  42. {
  43. var qs = new VertexSorter(array, seed);
  44. int divider = length >> 1;
  45. // Re-sort the array of vertices to accommodate alternating cuts.
  46. if (length - divider >= 2)
  47. {
  48. if (divider >= 2)
  49. {
  50. qs.AlternateAxes(0, divider - 1, 1);
  51. }
  52. qs.AlternateAxes(divider, length - 1, 1);
  53. }
  54. }
  55. #region Quicksort
  56. /// <summary>
  57. /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
  58. /// </summary>
  59. /// <param name="left"></param>
  60. /// <param name="right"></param>
  61. /// <remarks>
  62. /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
  63. /// the usual quicksort mistakes.
  64. /// </remarks>
  65. private void QuickSort(int left, int right)
  66. {
  67. int oleft = left;
  68. int oright = right;
  69. int arraysize = right - left + 1;
  70. int pivot;
  71. double pivotx, pivoty;
  72. Vertex temp;
  73. var array = this.points;
  74. if (arraysize < 32)
  75. {
  76. // Insertion sort
  77. for (int i = left + 1; i <= right; i++)
  78. {
  79. var a = array[i];
  80. int j = i - 1;
  81. while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y)))
  82. {
  83. array[j + 1] = array[j];
  84. j--;
  85. }
  86. array[j + 1] = a;
  87. }
  88. return;
  89. }
  90. // Choose a random pivot to split the array.
  91. pivot = rand.Next(left, right);
  92. pivotx = array[pivot].x;
  93. pivoty = array[pivot].y;
  94. // Split the array.
  95. left--;
  96. right++;
  97. while (left < right)
  98. {
  99. // Search for a vertex whose x-coordinate is too large for the left.
  100. do
  101. {
  102. left++;
  103. }
  104. while ((left <= right) && ((array[left].x < pivotx) ||
  105. ((array[left].x == pivotx) && (array[left].y < pivoty))));
  106. // Search for a vertex whose x-coordinate is too small for the right.
  107. do
  108. {
  109. right--;
  110. }
  111. while ((left <= right) && ((array[right].x > pivotx) ||
  112. ((array[right].x == pivotx) && (array[right].y > pivoty))));
  113. if (left < right)
  114. {
  115. // Swap the left and right vertices.
  116. temp = array[left];
  117. array[left] = array[right];
  118. array[right] = temp;
  119. }
  120. }
  121. if (left > oleft)
  122. {
  123. // Recursively sort the left subset.
  124. QuickSort(oleft, left);
  125. }
  126. if (oright > right + 1)
  127. {
  128. // Recursively sort the right subset.
  129. QuickSort(right + 1, oright);
  130. }
  131. }
  132. #endregion
  133. #region Alternate axes
  134. /// <summary>
  135. /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
  136. /// alternating cuts.
  137. /// </summary>
  138. /// <param name="left"></param>
  139. /// <param name="right"></param>
  140. /// <param name="axis"></param>
  141. /// <remarks>
  142. /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1.
  143. /// For the base case, subsets containing only two or three vertices are
  144. /// always sorted by x-coordinate.
  145. /// </remarks>
  146. private void AlternateAxes(int left, int right, int axis)
  147. {
  148. int size = right - left + 1;
  149. int divider = size >> 1;
  150. if (size <= 3)
  151. {
  152. // Recursive base case: subsets of two or three vertices will be
  153. // handled specially, and should always be sorted by x-coordinate.
  154. axis = 0;
  155. }
  156. // Partition with a horizontal or vertical cut.
  157. if (axis == 0)
  158. {
  159. VertexMedianX(left, right, left + divider);
  160. }
  161. else
  162. {
  163. VertexMedianY(left, right, left + divider);
  164. }
  165. // Recursively partition the subsets with a cross cut.
  166. if (size - divider >= 2)
  167. {
  168. if (divider >= 2)
  169. {
  170. AlternateAxes(left, left + divider - 1, 1 - axis);
  171. }
  172. AlternateAxes(left + divider, right, 1 - axis);
  173. }
  174. }
  175. /// <summary>
  176. /// An order statistic algorithm, almost. Shuffles an array of vertices so that the
  177. /// first 'median' vertices occur lexicographically before the remaining vertices.
  178. /// </summary>
  179. /// <param name="left"></param>
  180. /// <param name="right"></param>
  181. /// <param name="median"></param>
  182. /// <remarks>
  183. /// Uses the x-coordinate as the primary key. Very similar to the QuickSort()
  184. /// procedure, but runs in randomized linear time.
  185. /// </remarks>
  186. private void VertexMedianX(int left, int right, int median)
  187. {
  188. int arraysize = right - left + 1;
  189. int oleft = left, oright = right;
  190. int pivot;
  191. double pivot1, pivot2;
  192. Vertex temp;
  193. var array = this.points;
  194. if (arraysize == 2)
  195. {
  196. // Recursive base case.
  197. if ((array[left].x > array[right].x) ||
  198. ((array[left].x == array[right].x) &&
  199. (array[left].y > array[right].y)))
  200. {
  201. temp = array[right];
  202. array[right] = array[left];
  203. array[left] = temp;
  204. }
  205. return;
  206. }
  207. // Choose a random pivot to split the array.
  208. pivot = rand.Next(left, right);
  209. pivot1 = array[pivot].x;
  210. pivot2 = array[pivot].y;
  211. left--;
  212. right++;
  213. while (left < right)
  214. {
  215. // Search for a vertex whose x-coordinate is too large for the left.
  216. do
  217. {
  218. left++;
  219. }
  220. while ((left <= right) && ((array[left].x < pivot1) ||
  221. ((array[left].x == pivot1) && (array[left].y < pivot2))));
  222. // Search for a vertex whose x-coordinate is too small for the right.
  223. do
  224. {
  225. right--;
  226. }
  227. while ((left <= right) && ((array[right].x > pivot1) ||
  228. ((array[right].x == pivot1) && (array[right].y > pivot2))));
  229. if (left < right)
  230. {
  231. // Swap the left and right vertices.
  232. temp = array[left];
  233. array[left] = array[right];
  234. array[right] = temp;
  235. }
  236. }
  237. // Unlike in vertexsort(), at most one of the following conditionals is true.
  238. if (left > median)
  239. {
  240. // Recursively shuffle the left subset.
  241. VertexMedianX(oleft, left - 1, median);
  242. }
  243. if (right < median - 1)
  244. {
  245. // Recursively shuffle the right subset.
  246. VertexMedianX(right + 1, oright, median);
  247. }
  248. }
  249. /// <summary>
  250. /// An order statistic algorithm, almost. Shuffles an array of vertices so that
  251. /// the first 'median' vertices occur lexicographically before the remaining vertices.
  252. /// </summary>
  253. /// <param name="left"></param>
  254. /// <param name="right"></param>
  255. /// <param name="median"></param>
  256. /// <remarks>
  257. /// Uses the y-coordinate as the primary key. Very similar to the QuickSort()
  258. /// procedure, but runs in randomized linear time.
  259. /// </remarks>
  260. private void VertexMedianY(int left, int right, int median)
  261. {
  262. int arraysize = right - left + 1;
  263. int oleft = left, oright = right;
  264. int pivot;
  265. double pivot1, pivot2;
  266. Vertex temp;
  267. var array = this.points;
  268. if (arraysize == 2)
  269. {
  270. // Recursive base case.
  271. if ((array[left].y > array[right].y) ||
  272. ((array[left].y == array[right].y) &&
  273. (array[left].x > array[right].x)))
  274. {
  275. temp = array[right];
  276. array[right] = array[left];
  277. array[left] = temp;
  278. }
  279. return;
  280. }
  281. // Choose a random pivot to split the array.
  282. pivot = rand.Next(left, right);
  283. pivot1 = array[pivot].y;
  284. pivot2 = array[pivot].x;
  285. left--;
  286. right++;
  287. while (left < right)
  288. {
  289. // Search for a vertex whose x-coordinate is too large for the left.
  290. do
  291. {
  292. left++;
  293. }
  294. while ((left <= right) && ((array[left].y < pivot1) ||
  295. ((array[left].y == pivot1) && (array[left].x < pivot2))));
  296. // Search for a vertex whose x-coordinate is too small for the right.
  297. do
  298. {
  299. right--;
  300. }
  301. while ((left <= right) && ((array[right].y > pivot1) ||
  302. ((array[right].y == pivot1) && (array[right].x > pivot2))));
  303. if (left < right)
  304. {
  305. // Swap the left and right vertices.
  306. temp = array[left];
  307. array[left] = array[right];
  308. array[right] = temp;
  309. }
  310. }
  311. // Unlike in QuickSort(), at most one of the following conditionals is true.
  312. if (left > median)
  313. {
  314. // Recursively shuffle the left subset.
  315. VertexMedianY(oleft, left - 1, median);
  316. }
  317. if (right < median - 1)
  318. {
  319. // Recursively shuffle the right subset.
  320. VertexMedianY(right + 1, oright, median);
  321. }
  322. }
  323. #endregion
  324. }
  325. }