Home | History | Annotate | Download | only in math
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.math;
     18 
     19 import com.badlogic.gdx.utils.FloatArray;
     20 import com.badlogic.gdx.utils.IntArray;
     21 import com.badlogic.gdx.utils.ShortArray;
     22 
     23 /** Computes the convex hull of a set of points using the monotone chain convex hull algorithm (aka Andrew's algorithm).
     24  * @author Nathan Sweet */
     25 public class ConvexHull {
     26 	private final IntArray quicksortStack = new IntArray();
     27 	private float[] sortedPoints;
     28 	private final FloatArray hull = new FloatArray();
     29 	private final IntArray indices = new IntArray();
     30 	private final ShortArray originalIndices = new ShortArray(false, 0);
     31 
     32 	/** @see #computePolygon(float[], int, int, boolean) */
     33 	public FloatArray computePolygon (FloatArray points, boolean sorted) {
     34 		return computePolygon(points.items, 0, points.size, sorted);
     35 	}
     36 
     37 	/** @see #computePolygon(float[], int, int, boolean) */
     38 	public FloatArray computePolygon (float[] polygon, boolean sorted) {
     39 		return computePolygon(polygon, 0, polygon.length, sorted);
     40 	}
     41 
     42 	/** Returns a list of points on the convex hull in counter-clockwise order. Note: the last point in the returned list is the
     43 	 * same as the first one. */
     44 	/** Returns the convex hull polygon for the given point cloud.
     45 	 * @param points x,y pairs describing points. Duplicate points will result in undefined behavior.
     46 	 * @param sorted If false, the points will be sorted by the x coordinate then the y coordinate, which is required by the convex
     47 	 *           hull algorithm. If sorting is done the input array is not modified and count additional working memory is needed.
     48 	 * @return pairs of coordinates that describe the convex hull polygon in counterclockwise order. Note the returned array is
     49 	 *         reused for later calls to the same method. */
     50 	public FloatArray computePolygon (float[] points, int offset, int count, boolean sorted) {
     51 		int end = offset + count;
     52 
     53 		if (!sorted) {
     54 			if (sortedPoints == null || sortedPoints.length < count) sortedPoints = new float[count];
     55 			System.arraycopy(points, offset, sortedPoints, 0, count);
     56 			points = sortedPoints;
     57 			offset = 0;
     58 			sort(points, count);
     59 		}
     60 
     61 		FloatArray hull = this.hull;
     62 		hull.clear();
     63 
     64 		// Lower hull.
     65 		for (int i = offset; i < end; i += 2) {
     66 			float x = points[i];
     67 			float y = points[i + 1];
     68 			while (hull.size >= 4 && ccw(x, y) <= 0)
     69 				hull.size -= 2;
     70 			hull.add(x);
     71 			hull.add(y);
     72 		}
     73 
     74 		// Upper hull.
     75 		for (int i = end - 4, t = hull.size + 2; i >= offset; i -= 2) {
     76 			float x = points[i];
     77 			float y = points[i + 1];
     78 			while (hull.size >= t && ccw(x, y) <= 0)
     79 				hull.size -= 2;
     80 			hull.add(x);
     81 			hull.add(y);
     82 		}
     83 
     84 		return hull;
     85 	}
     86 
     87 	/** @see #computeIndices(float[], int, int, boolean, boolean) */
     88 	public IntArray computeIndices (FloatArray points, boolean sorted, boolean yDown) {
     89 		return computeIndices(points.items, 0, points.size, sorted, yDown);
     90 	}
     91 
     92 	/** @see #computeIndices(float[], int, int, boolean, boolean) */
     93 	public IntArray computeIndices (float[] polygon, boolean sorted, boolean yDown) {
     94 		return computeIndices(polygon, 0, polygon.length, sorted, yDown);
     95 	}
     96 
     97 	/** Computes a hull the same as {@link #computePolygon(float[], int, int, boolean)} but returns indices of the specified points. */
     98 	public IntArray computeIndices (float[] points, int offset, int count, boolean sorted, boolean yDown) {
     99 		int end = offset + count;
    100 
    101 		if (!sorted) {
    102 			if (sortedPoints == null || sortedPoints.length < count) sortedPoints = new float[count];
    103 			System.arraycopy(points, offset, sortedPoints, 0, count);
    104 			points = sortedPoints;
    105 			offset = 0;
    106 			sortWithIndices(points, count, yDown);
    107 		}
    108 
    109 		IntArray indices = this.indices;
    110 		indices.clear();
    111 
    112 		FloatArray hull = this.hull;
    113 		hull.clear();
    114 
    115 		// Lower hull.
    116 		for (int i = offset, index = i / 2; i < end; i += 2, index++) {
    117 			float x = points[i];
    118 			float y = points[i + 1];
    119 			while (hull.size >= 4 && ccw(x, y) <= 0) {
    120 				hull.size -= 2;
    121 				indices.size--;
    122 			}
    123 			hull.add(x);
    124 			hull.add(y);
    125 			indices.add(index);
    126 		}
    127 
    128 		// Upper hull.
    129 		for (int i = end - 4, index = i / 2, t = hull.size + 2; i >= offset; i -= 2, index--) {
    130 			float x = points[i];
    131 			float y = points[i + 1];
    132 			while (hull.size >= t && ccw(x, y) <= 0) {
    133 				hull.size -= 2;
    134 				indices.size--;
    135 			}
    136 			hull.add(x);
    137 			hull.add(y);
    138 			indices.add(index);
    139 		}
    140 
    141 		// Convert sorted to unsorted indices.
    142 		if (!sorted) {
    143 			short[] originalIndicesArray = originalIndices.items;
    144 			int[] indicesArray = indices.items;
    145 			for (int i = 0, n = indices.size; i < n; i++)
    146 				indicesArray[i] = originalIndicesArray[indicesArray[i]];
    147 		}
    148 
    149 		return indices;
    150 	}
    151 
    152 	/** Returns > 0 if the points are a counterclockwise turn, < 0 if clockwise, and 0 if colinear. */
    153 	private float ccw (float p3x, float p3y) {
    154 		FloatArray hull = this.hull;
    155 		int size = hull.size;
    156 		float p1x = hull.get(size - 4);
    157 		float p1y = hull.get(size - 3);
    158 		float p2x = hull.get(size - 2);
    159 		float p2y = hull.peek();
    160 		return (p2x - p1x) * (p3y - p1y) - (p2y - p1y) * (p3x - p1x);
    161 	}
    162 
    163 	/** Sorts x,y pairs of values by the x value, then the y value.
    164 	 * @param count Number of indices, must be even. */
    165 	private void sort (float[] values, int count) {
    166 		int lower = 0;
    167 		int upper = count - 1;
    168 		IntArray stack = quicksortStack;
    169 		stack.add(lower);
    170 		stack.add(upper - 1);
    171 		while (stack.size > 0) {
    172 			upper = stack.pop();
    173 			lower = stack.pop();
    174 			if (upper <= lower) continue;
    175 			int i = quicksortPartition(values, lower, upper);
    176 			if (i - lower > upper - i) {
    177 				stack.add(lower);
    178 				stack.add(i - 2);
    179 			}
    180 			stack.add(i + 2);
    181 			stack.add(upper);
    182 			if (upper - i >= i - lower) {
    183 				stack.add(lower);
    184 				stack.add(i - 2);
    185 			}
    186 		}
    187 	}
    188 
    189 	private int quicksortPartition (final float[] values, int lower, int upper) {
    190 		float x = values[lower];
    191 		float y = values[lower + 1];
    192 		int up = upper;
    193 		int down = lower;
    194 		float temp;
    195 		short tempIndex;
    196 		while (down < up) {
    197 			while (down < up && values[down] <= x)
    198 				down = down + 2;
    199 			while (values[up] > x || (values[up] == x && values[up + 1] < y))
    200 				up = up - 2;
    201 			if (down < up) {
    202 				temp = values[down];
    203 				values[down] = values[up];
    204 				values[up] = temp;
    205 
    206 				temp = values[down + 1];
    207 				values[down + 1] = values[up + 1];
    208 				values[up + 1] = temp;
    209 			}
    210 		}
    211 		values[lower] = values[up];
    212 		values[up] = x;
    213 
    214 		values[lower + 1] = values[up + 1];
    215 		values[up + 1] = y;
    216 
    217 		return up;
    218 	}
    219 
    220 	/** Sorts x,y pairs of values by the x value, then the y value and stores unsorted original indices.
    221 	 * @param count Number of indices, must be even. */
    222 	private void sortWithIndices (float[] values, int count, boolean yDown) {
    223 		int pointCount = count / 2;
    224 		originalIndices.clear();
    225 		originalIndices.ensureCapacity(pointCount);
    226 		short[] originalIndicesArray = originalIndices.items;
    227 		for (short i = 0; i < pointCount; i++)
    228 			originalIndicesArray[i] = i;
    229 
    230 		int lower = 0;
    231 		int upper = count - 1;
    232 		IntArray stack = quicksortStack;
    233 		stack.add(lower);
    234 		stack.add(upper - 1);
    235 		while (stack.size > 0) {
    236 			upper = stack.pop();
    237 			lower = stack.pop();
    238 			if (upper <= lower) continue;
    239 			int i = quicksortPartitionWithIndices(values, lower, upper, yDown, originalIndicesArray);
    240 			if (i - lower > upper - i) {
    241 				stack.add(lower);
    242 				stack.add(i - 2);
    243 			}
    244 			stack.add(i + 2);
    245 			stack.add(upper);
    246 			if (upper - i >= i - lower) {
    247 				stack.add(lower);
    248 				stack.add(i - 2);
    249 			}
    250 		}
    251 	}
    252 
    253 	private int quicksortPartitionWithIndices (final float[] values, int lower, int upper, boolean yDown, short[] originalIndices) {
    254 		float x = values[lower];
    255 		float y = values[lower + 1];
    256 		int up = upper;
    257 		int down = lower;
    258 		float temp;
    259 		short tempIndex;
    260 		while (down < up) {
    261 			while (down < up && values[down] <= x)
    262 				down = down + 2;
    263 			if (yDown) {
    264 				while (values[up] > x || (values[up] == x && values[up + 1] < y))
    265 					up = up - 2;
    266 			} else {
    267 				while (values[up] > x || (values[up] == x && values[up + 1] > y))
    268 					up = up - 2;
    269 			}
    270 			if (down < up) {
    271 				temp = values[down];
    272 				values[down] = values[up];
    273 				values[up] = temp;
    274 
    275 				temp = values[down + 1];
    276 				values[down + 1] = values[up + 1];
    277 				values[up + 1] = temp;
    278 
    279 				tempIndex = originalIndices[down / 2];
    280 				originalIndices[down / 2] = originalIndices[up / 2];
    281 				originalIndices[up / 2] = tempIndex;
    282 			}
    283 		}
    284 		values[lower] = values[up];
    285 		values[up] = x;
    286 
    287 		values[lower + 1] = values[up + 1];
    288 		values[up + 1] = y;
    289 
    290 		tempIndex = originalIndices[lower / 2];
    291 		originalIndices[lower / 2] = originalIndices[up / 2];
    292 		originalIndices[up / 2] = tempIndex;
    293 
    294 		return up;
    295 	}
    296 }
    297