1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.geometry.hull.euclidean.twod;
18
19 import java.util.Collection;
20 import java.util.Iterator;
21
22 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
23 import org.apache.commons.geometry.euclidean.twod.Vector2D;
24
25 /**
26 * Abstract base class for convex hull generators in the two-dimensional Euclidean space.
27 */
28 abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D {
29
30 /** Precision context used to compare floating point numbers. */
31 private final DoublePrecisionContext precision;
32
33 /**
34 * Indicates if collinear points on the hull shall be present in the output.
35 * If {@code false}, only the extreme points are added to the hull.
36 */
37 private final boolean includeCollinearPoints;
38
39 /**
40 * Simple constructor.
41 *
42 * @param includeCollinearPoints indicates if collinear points on the hull shall be
43 * added as hull vertices
44 * @param precision precision context used to compare floating point numbers
45 */
46 protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints,
47 final DoublePrecisionContext precision) {
48 this.includeCollinearPoints = includeCollinearPoints;
49 this.precision = precision;
50 }
51
52 /** Get the object used to determine floating point equality for this region.
53 * @return the floating point precision context for the instance
54 */
55 public DoublePrecisionContext getPrecision() {
56 return precision;
57 }
58
59 /**
60 * Returns if collinear points on the hull will be added as hull vertices.
61 * @return {@code true} if collinear points are added as hull vertices, or {@code false}
62 * if only extreme points are present.
63 */
64 public boolean isIncludeCollinearPoints() {
65 return includeCollinearPoints;
66 }
67
68 /** {@inheritDoc} */
69 @Override
70 public ConvexHull2D generate(final Collection<Vector2D> points) {
71 Collection<Vector2D> hullVertices = null;
72 if (points.size() < 2) {
73 hullVertices = points;
74 } else {
75 hullVertices = findHullVertices(points);
76 }
77
78 if (!isConvex(hullVertices)) {
79 throw new IllegalStateException("Convex hull algorithm failed to generate solution");
80 }
81
82 return new ConvexHull2D(hullVertices, precision);
83 }
84
85 /**
86 * Find the convex hull vertices from the set of input points.
87 * @param points the set of input points
88 * @return the convex hull vertices in CCW winding
89 */
90 protected abstract Collection<Vector2D> findHullVertices(Collection<Vector2D> points);
91
92 /** Return true if the given vertices define a convex hull.
93 * @param vertices the hull vertices
94 * @return {@code true} if the vertices form a convex hull, {@code false} otherwise
95 */
96 private boolean isConvex(final Collection<Vector2D> vertices) {
97 final int size = vertices.size();
98
99 if (size < 3) {
100 // 1 or 2 points always define a convex set
101 return true;
102 }
103
104 final Iterator<Vector2D> it = vertices.iterator();
105
106 Vector2D p1 = it.next();
107 Vector2D p2 = it.next();
108 Vector2D p3;
109
110 Vector2D v1;
111 Vector2D v2;
112
113 while (it.hasNext()) {
114 p3 = it.next();
115
116 v1 = p1.vectorTo(p2);
117 v2 = p2.vectorTo(p3);
118
119 // negative signed areas mean a clockwise winding
120 if (precision.compare(v1.signedArea(v2), 0.0) < 0) {
121 return false;
122 }
123
124 p1 = p2;
125 p2 = p3;
126 }
127
128 return true;
129 }
130 }