// ----------------------------------------------------------------------- // // Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- namespace TriangleNet.Tools { using System; using TriangleNet.Geometry; /// /// Sort an array of points using quicksort. /// public class VertexSorter { private const int RANDOM_SEED = 57113; Random rand; Vertex[] points; VertexSorter(Vertex[] points, int seed) { this.points = points; this.rand = new Random(seed); } /// /// Sorts the given vertex array by x-coordinate. /// /// The vertex array. /// Random seed used for pivoting. public static void Sort(Vertex[] array, int seed = RANDOM_SEED) { var qs = new VertexSorter(array, seed); qs.QuickSort(0, array.Length - 1); } /// /// Impose alternating cuts on given vertex array. /// /// The vertex array. /// The number of vertices to sort. /// Random seed used for pivoting. public static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED) { var qs = new VertexSorter(array, seed); int divider = length >> 1; // Re-sort the array of vertices to accommodate alternating cuts. if (length - divider >= 2) { if (divider >= 2) { qs.AlternateAxes(0, divider - 1, 1); } qs.AlternateAxes(divider, length - 1, 1); } } #region Quicksort /// /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key. /// /// /// /// /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of /// the usual quicksort mistakes. /// private void QuickSort(int left, int right) { int oleft = left; int oright = right; int arraysize = right - left + 1; int pivot; double pivotx, pivoty; Vertex temp; var array = this.points; if (arraysize < 32) { // Insertion sort for (int i = left + 1; i <= right; i++) { var a = array[i]; int j = i - 1; while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y))) { array[j + 1] = array[j]; j--; } array[j + 1] = a; } return; } // Choose a random pivot to split the array. pivot = rand.Next(left, right); pivotx = array[pivot].x; pivoty = array[pivot].y; // Split the array. left--; right++; while (left < right) { // Search for a vertex whose x-coordinate is too large for the left. do { left++; } while ((left <= right) && ((array[left].x < pivotx) || ((array[left].x == pivotx) && (array[left].y < pivoty)))); // Search for a vertex whose x-coordinate is too small for the right. do { right--; } while ((left <= right) && ((array[right].x > pivotx) || ((array[right].x == pivotx) && (array[right].y > pivoty)))); if (left < right) { // Swap the left and right vertices. temp = array[left]; array[left] = array[right]; array[right] = temp; } } if (left > oleft) { // Recursively sort the left subset. QuickSort(oleft, left); } if (oright > right + 1) { // Recursively sort the right subset. QuickSort(right + 1, oright); } } #endregion #region Alternate axes /// /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with /// alternating cuts. /// /// /// /// /// /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. /// For the base case, subsets containing only two or three vertices are /// always sorted by x-coordinate. /// private void AlternateAxes(int left, int right, int axis) { int size = right - left + 1; int divider = size >> 1; if (size <= 3) { // Recursive base case: subsets of two or three vertices will be // handled specially, and should always be sorted by x-coordinate. axis = 0; } // Partition with a horizontal or vertical cut. if (axis == 0) { VertexMedianX(left, right, left + divider); } else { VertexMedianY(left, right, left + divider); } // Recursively partition the subsets with a cross cut. if (size - divider >= 2) { if (divider >= 2) { AlternateAxes(left, left + divider - 1, 1 - axis); } AlternateAxes(left + divider, right, 1 - axis); } } /// /// An order statistic algorithm, almost. Shuffles an array of vertices so that the /// first 'median' vertices occur lexicographically before the remaining vertices. /// /// /// /// /// /// Uses the x-coordinate as the primary key. Very similar to the QuickSort() /// procedure, but runs in randomized linear time. /// private void VertexMedianX(int left, int right, int median) { int arraysize = right - left + 1; int oleft = left, oright = right; int pivot; double pivot1, pivot2; Vertex temp; var array = this.points; if (arraysize == 2) { // Recursive base case. if ((array[left].x > array[right].x) || ((array[left].x == array[right].x) && (array[left].y > array[right].y))) { temp = array[right]; array[right] = array[left]; array[left] = temp; } return; } // Choose a random pivot to split the array. pivot = rand.Next(left, right); pivot1 = array[pivot].x; pivot2 = array[pivot].y; left--; right++; while (left < right) { // Search for a vertex whose x-coordinate is too large for the left. do { left++; } while ((left <= right) && ((array[left].x < pivot1) || ((array[left].x == pivot1) && (array[left].y < pivot2)))); // Search for a vertex whose x-coordinate is too small for the right. do { right--; } while ((left <= right) && ((array[right].x > pivot1) || ((array[right].x == pivot1) && (array[right].y > pivot2)))); if (left < right) { // Swap the left and right vertices. temp = array[left]; array[left] = array[right]; array[right] = temp; } } // Unlike in vertexsort(), at most one of the following conditionals is true. if (left > median) { // Recursively shuffle the left subset. VertexMedianX(oleft, left - 1, median); } if (right < median - 1) { // Recursively shuffle the right subset. VertexMedianX(right + 1, oright, median); } } /// /// An order statistic algorithm, almost. Shuffles an array of vertices so that /// the first 'median' vertices occur lexicographically before the remaining vertices. /// /// /// /// /// /// Uses the y-coordinate as the primary key. Very similar to the QuickSort() /// procedure, but runs in randomized linear time. /// private void VertexMedianY(int left, int right, int median) { int arraysize = right - left + 1; int oleft = left, oright = right; int pivot; double pivot1, pivot2; Vertex temp; var array = this.points; if (arraysize == 2) { // Recursive base case. if ((array[left].y > array[right].y) || ((array[left].y == array[right].y) && (array[left].x > array[right].x))) { temp = array[right]; array[right] = array[left]; array[left] = temp; } return; } // Choose a random pivot to split the array. pivot = rand.Next(left, right); pivot1 = array[pivot].y; pivot2 = array[pivot].x; left--; right++; while (left < right) { // Search for a vertex whose x-coordinate is too large for the left. do { left++; } while ((left <= right) && ((array[left].y < pivot1) || ((array[left].y == pivot1) && (array[left].x < pivot2)))); // Search for a vertex whose x-coordinate is too small for the right. do { right--; } while ((left <= right) && ((array[right].y > pivot1) || ((array[right].y == pivot1) && (array[right].x > pivot2)))); if (left < right) { // Swap the left and right vertices. temp = array[left]; array[left] = array[right]; array[right] = temp; } } // Unlike in QuickSort(), at most one of the following conditionals is true. if (left > median) { // Recursively shuffle the left subset. VertexMedianY(oleft, left - 1, median); } if (right < median - 1) { // Recursively shuffle the right subset. VertexMedianY(right + 1, oright, median); } } #endregion } }