1 package org.djutils.draw.line;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.Iterator;
8 import java.util.List;
9
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.draw.Drawable2d;
12 import org.djutils.draw.bounds.Bounds2d;
13 import org.djutils.draw.point.Point2d;
14 import org.djutils.exceptions.Throw;
15
16
17
18
19
20
21
22
23
24
25
26
27
28 public final class ConvexHull
29 {
30
31
32
33 private ConvexHull()
34 {
35
36 }
37
38
39
40
41
42
43
44 public static Polygon2d convexHull(final Iterator<Point2d> iterator)
45 {
46 List<Point2d> list = new ArrayList<>();
47 iterator.forEachRemaining(list::add);
48 return convexHullAlshamrani(list);
49 }
50
51
52
53
54
55
56
57
58 public static Polygon2d convexHull(final Drawable2d... drawable2d)
59 {
60 return convexHull(Bounds2d.pointsOf(drawable2d));
61 }
62
63
64
65
66
67
68
69
70
71 public static Polygon2d convexHull(final Collection<Drawable2d> drawableCollection)
72 {
73 Throw.whenNull(drawableCollection, "drawableCollection");
74 Throw.when(drawableCollection.isEmpty(), DrawRuntimeException.class, "drawableCollection may not be empty");
75 return convexHull(Bounds2d.pointsOf(drawableCollection));
76 }
77
78
79
80
81
82
83 public static Polygon2d convexHull(final List<Point2d> list)
84 {
85 return convexHullAlshamrani(list);
86 }
87
88
89
90
91
92
93
94
95
96 private static boolean ccw(final Point2d a, final Point2d b, final Point2d c)
97 {
98 return ((b.x - a.x) * (c.y - a.y)) > ((b.y - a.y) * (c.x - a.x));
99 }
100
101
102
103
104
105
106
107 private static void cleanAndAppend(final List<Point2d> list, final Point2d newPoint)
108 {
109 Point2d last = list.get(list.size() - 1);
110 if (last.x == newPoint.x && last.y == newPoint.y)
111 {
112 return;
113 }
114 while (list.size() >= 2 && !ccw(list.get(list.size() - 2), list.get(list.size() - 1), newPoint))
115 {
116 list.remove(list.size() - 1);
117 }
118 list.add(newPoint);
119 }
120
121
122
123
124
125
126
127
128
129
130 public static Polygon2d convexHullAlshamrani(final List<Point2d> list)
131 {
132
133 Throw.whenNull(list, "list");
134 Throw.when(list.size() < 1, IllegalArgumentException.class, "Too few points in list");
135 Point2d minX = list.get(0);
136 Point2d minY = list.get(0);
137 Point2d maxX = list.get(0);
138 Point2d maxY = list.get(0);
139 for (Point2d point : list)
140 {
141 if (minX.x > point.x || minX.x == point.x && minX.y > point.y)
142 {
143 minX = point;
144 }
145 if (minY.y > point.y || minY.y == point.y && minY.x < point.x)
146 {
147 minY = point;
148 }
149 if (maxX.x < point.x || maxX.x == point.x && maxX.y > point.y)
150 {
151 maxX = point;
152 }
153 if (maxY.y < point.y || maxY.y == point.y && maxY.x < point.x)
154 {
155 maxY = point;
156 }
157 }
158
159
160
161
162 Comparator<Point2d> forwardComparator = new Comparator<Point2d>()
163 {
164 @Override
165 public int compare(final Point2d point1, final Point2d point2)
166 {
167 if (point1.x == point2.x)
168 {
169 return (int) Math.signum(point1.y - point2.y);
170 }
171 return (int) Math.signum(point1.x - point2.x);
172 }
173 };
174 Comparator<Point2d> reverseComparator = new Comparator<Point2d>()
175 {
176 @Override
177 public int compare(final Point2d point1, final Point2d point2)
178 {
179 if (point1.x == point2.x)
180 {
181 return (int) Math.signum(point1.y - point2.y);
182 }
183 return (int) Math.signum(point2.x - point1.x);
184 }
185 };
186 List<Point2d> lowerLeft = new ArrayList<>();
187 List<Point2d> lowerRight = new ArrayList<>();
188 List<Point2d> upperRight = new ArrayList<>();
189 List<Point2d> upperLeft = new ArrayList<>();
190
191 for (Point2d point : list)
192 {
193 if (point.x <= minY.x && point.y <= minX.y)
194 {
195 if (ccw(minX, point, minY))
196 {
197 lowerLeft.add(point);
198 }
199 }
200 else if (point.x >= minY.x && point.y <= maxX.y)
201 {
202 if (ccw(minY, point, maxX))
203 {
204 lowerRight.add(point);
205 }
206 }
207 else if (point.x >= maxY.x && point.y >= maxX.y)
208 {
209 if (ccw(maxX, point, maxY))
210 {
211 upperRight.add(point);
212 }
213 }
214 else if (point.x <= maxY.x && point.y >= minX.y)
215 {
216 if (ccw(maxY, point, minX))
217 {
218 upperLeft.add(point);
219 }
220 }
221 }
222 Collections.sort(lowerLeft, forwardComparator);
223 Collections.sort(lowerRight, forwardComparator);
224 Collections.sort(upperRight, reverseComparator);
225 Collections.sort(upperLeft, reverseComparator);
226
227 List<Point2d> result = new ArrayList<>();
228 result.add(minX);
229 for (Point2d point : lowerLeft)
230 {
231 cleanAndAppend(result, point);
232 }
233 cleanAndAppend(result, minY);
234 for (Point2d point : lowerRight)
235 {
236 cleanAndAppend(result, point);
237 }
238 cleanAndAppend(result, maxX);
239 for (Point2d point : upperRight)
240 {
241 cleanAndAppend(result, point);
242 }
243 cleanAndAppend(result, maxY);
244 for (Point2d point : upperLeft)
245 {
246 cleanAndAppend(result, point);
247 }
248 return new Polygon2d(result);
249 }
250
251
252
253
254
255
256
257
258
259 public static Polygon2d convexHullMonotone(final List<Point2d> list)
260 {
261 Collections.sort(list, new Comparator<Point2d>()
262 {
263 @Override
264 public int compare(final Point2d point1, final Point2d point2)
265 {
266 if (point1.x == point2.x)
267 {
268 return (int) Math.signum(point1.y - point2.y);
269 }
270 return (int) Math.signum(point1.x - point2.x);
271 }
272 });
273
274 List<Point2d> result = new ArrayList<>();
275
276 for (Point2d p : list)
277 {
278 while (result.size() >= 2 && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p))
279 {
280 result.remove(result.size() - 1);
281 }
282 result.add(p);
283 }
284
285 int lowLimit = result.size() + 1;
286 for (int i = list.size() - 1; i >= 0; i--)
287 {
288 Point2d p = list.get(i);
289 while (result.size() >= lowLimit && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p))
290 {
291 result.remove(result.size() - 1);
292 }
293 result.add(p);
294 }
295 if (result.size() > 0)
296 {
297 result.remove(result.size() - 1);
298 }
299
300 return new Polygon2d(result);
301 }
302
303 }