Skip to content

Commit 2a0139e

Browse files
committed
Add RotatingCalipers algorithm and tests
1 parent b031a0b commit 2a0139e

File tree

4 files changed

+451
-0
lines changed

4 files changed

+451
-0
lines changed

.github/RotatingCalipers.java

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.util.ArrayList;
4+
import java.util.Comparator;
5+
import java.util.List;
6+
7+
/**
8+
* RotatingCalipers - utility class for convex polygon computations:
9+
* - diameter (farthest pair)
10+
* - width (minimum distance between two parallel supporting lines)
11+
* - minimum-area bounding rectangle (simple implementation)
12+
*
13+
* Note: This implementation computes the convex hull (monotonic chain).
14+
* The min-area rectangle implementation below uses a simple edge-based projection
15+
* approach (O(n^2) worst-case) for clarity and correctness; it can be optimized
16+
* to the classic O(n) rotating-calipers minimum rectangle later.
17+
*
18+
* All methods are static. No instances.
19+
*/
20+
public final class RotatingCalipers {
21+
22+
private RotatingCalipers() {
23+
throw new UnsupportedOperationException("Utility class");
24+
}
25+
26+
/* ---------- Simple geometry primitives (replace with repo types if present) ---------- */
27+
28+
public static final class Point {
29+
public final double x;
30+
public final double y;
31+
32+
public Point(double x, double y) {
33+
this.x = x;
34+
this.y = y;
35+
}
36+
}
37+
38+
public static final class PointPair {
39+
public final Point a;
40+
public final Point b;
41+
42+
public PointPair(Point a, Point b) {
43+
this.a = a;
44+
this.b = b;
45+
}
46+
47+
public double distance() {
48+
double dx = a.x - b.x;
49+
double dy = a.y - b.y;
50+
return Math.hypot(dx, dy);
51+
}
52+
}
53+
54+
public static final class Rectangle {
55+
// center point, width along angle, height perpendicular, rotation angle in radians
56+
public final Point center;
57+
public final double width;
58+
public final double height;
59+
public final double angle;
60+
61+
public Rectangle(Point center, double width, double height, double angle) {
62+
this.center = center;
63+
this.width = width;
64+
this.height = height;
65+
this.angle = angle;
66+
}
67+
}
68+
69+
/* ---------- Helpers ---------- */
70+
71+
private static double cross(Point o, Point a, Point b) {
72+
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
73+
}
74+
75+
private static double dist2(Point a, Point b) {
76+
double dx = a.x - b.x;
77+
double dy = a.y - b.y;
78+
return dx * dx + dy * dy;
79+
}
80+
81+
/**
82+
* Monotonic chain convex hull. Returns hull in CCW order (no duplicate final vertex).
83+
* If input has <= 1 point, returns a copy.
84+
*/
85+
public static List<Point> convexHull(List<Point> pts) {
86+
List<Point> p = new ArrayList<>(pts);
87+
p.sort(Comparator.comparingDouble((Point q) -> q.x).thenComparingDouble(q -> q.y));
88+
int n = p.size();
89+
if (n <= 1) {
90+
return new ArrayList<>(p);
91+
}
92+
List<Point> lower = new ArrayList<>();
93+
for (Point pt : p) {
94+
while (lower.size() >= 2 && cross(lower.get(lower.size() - 2), lower.get(lower.size() - 1), pt) <= 0) {
95+
lower.remove(lower.size() - 1);
96+
}
97+
lower.add(pt);
98+
}
99+
List<Point> upper = new ArrayList<>();
100+
for (int i = n - 1; i >= 0; --i) {
101+
Point pt = p.get(i);
102+
while (upper.size() >= 2 && cross(upper.get(upper.size() - 2), upper.get(upper.size() - 1), pt) <= 0) {
103+
upper.remove(upper.size() - 1);
104+
}
105+
upper.add(pt);
106+
}
107+
// Concatenate without duplicating end points
108+
lower.remove(lower.size() - 1);
109+
upper.remove(upper.size() - 1);
110+
lower.addAll(upper);
111+
return lower;
112+
}
113+
114+
/**
115+
* Diameter - farthest pair of points. If given arbitrary points, hull is computed.
116+
*
117+
* Complexity: O(n) on hull size after hull computation.
118+
*/
119+
public static PointPair diameter(List<Point> points) {
120+
List<Point> ch = convexHull(points);
121+
int n = ch.size();
122+
if (n == 0) return new PointPair(null, null);
123+
if (n == 1) return new PointPair(ch.get(0), ch.get(0));
124+
if (n == 2) return new PointPair(ch.get(0), ch.get(1));
125+
126+
int j = 1;
127+
double best = 0;
128+
Point bestA = ch.get(0);
129+
Point bestB = ch.get(0);
130+
for (int i = 0; i < n; ++i) {
131+
int ni = (i + 1) % n;
132+
while (Math.abs(cross(ch.get(i), ch.get(ni), ch.get((j + 1) % n)))
133+
> Math.abs(cross(ch.get(i), ch.get(ni), ch.get(j)))) {
134+
j = (j + 1) % n;
135+
}
136+
double d2 = dist2(ch.get(i), ch.get(j));
137+
if (d2 > best) {
138+
best = d2;
139+
bestA = ch.get(i);
140+
bestB = ch.get(j);
141+
}
142+
d2 = dist2(ch.get(ni), ch.get(j));
143+
if (d2 > best) {
144+
best = d2;
145+
bestA = ch.get(ni);
146+
bestB = ch.get(j);
147+
}
148+
}
149+
return new PointPair(bestA, bestB);
150+
}
151+
152+
/**
153+
* Width - minimal distance between two parallel supporting lines of the convex polygon.
154+
*
155+
* Complexity: O(n) on hull size after hull computation.
156+
*/
157+
public static double width(List<Point> points) {
158+
List<Point> ch = convexHull(points);
159+
int n = ch.size();
160+
if (n <= 1) return 0.0;
161+
if (n == 2) {
162+
return Math.hypot(ch.get(1).x - ch.get(0).x, ch.get(1).y - ch.get(0).y);
163+
}
164+
165+
int j = 1;
166+
double minWidth = Double.POSITIVE_INFINITY;
167+
for (int i = 0; i < n; ++i) {
168+
int ni = (i + 1) % n;
169+
while (Math.abs(cross(ch.get(i), ch.get(ni), ch.get((j + 1) % n)))
170+
> Math.abs(cross(ch.get(i), ch.get(ni), ch.get(j)))) {
171+
j = (j + 1) % n;
172+
}
173+
double distance = Math.abs(cross(ch.get(i), ch.get(ni), ch.get(j)))
174+
/ Math.hypot(ch.get(ni).x - ch.get(i).x, ch.get(ni).y - ch.get(i).y);
175+
minWidth = Math.min(minWidth, distance);
176+
}
177+
return minWidth;
178+
}
179+
180+
/**
181+
* Minimum-area bounding rectangle (simple, reliable approach).
182+
* For each hull edge, rotate axes so edge is X-axis, project points, compute bounding rectangle.
183+
* This implementation is easier to reason about and test. It is O(m^2) for hull size m.
184+
*
185+
* Returns null for empty input.
186+
*/
187+
public static Rectangle minAreaBoundingRectangle(List<Point> points) {
188+
List<Point> ch = convexHull(points);
189+
int n = ch.size();
190+
if (n == 0) return null;
191+
if (n == 1) return new Rectangle(ch.get(0), 0.0, 0.0, 0.0);
192+
if (n == 2) {
193+
Point a = ch.get(0), b = ch.get(1);
194+
double w = Math.hypot(b.x - a.x, b.y - a.y);
195+
Point center = new Point((a.x + b.x) / 2.0, (a.y + b.y) / 2.0);
196+
double angle = Math.atan2(b.y - a.y, b.x - a.x);
197+
return new Rectangle(center, w, 0.0, angle);
198+
}
199+
200+
double bestArea = Double.POSITIVE_INFINITY;
201+
Rectangle bestRect = null;
202+
203+
for (int i = 0; i < n; ++i) {
204+
Point a = ch.get(i);
205+
Point b = ch.get((i + 1) % n);
206+
double dx = b.x - a.x, dy = b.y - a.y;
207+
double len = Math.hypot(dx, dy);
208+
double ux = dx / len, uy = dy / len; // axis along edge
209+
double vx = -uy, vy = ux; // perpendicular
210+
double minU = Double.POSITIVE_INFINITY, maxU = -Double.POSITIVE_INFINITY;
211+
double minV = Double.POSITIVE_INFINITY, maxV = -Double.POSITIVE_INFINITY;
212+
213+
for (Point p : ch) {
214+
double u = (p.x - a.x) * ux + (p.y - a.y) * uy;
215+
double v = (p.x - a.x) * vx + (p.y - a.y) * vy;
216+
minU = Math.min(minU, u);
217+
maxU = Math.max(maxU, u);
218+
minV = Math.min(minV, v);
219+
maxV = Math.max(maxV, v);
220+
}
221+
222+
double width = maxU - minU;
223+
double height = maxV - minV;
224+
double area = width * height;
225+
if (area < bestArea) {
226+
bestArea = area;
227+
double centerU = (minU + maxU) / 2.0;
228+
double centerV = (minV + maxV) / 2.0;
229+
Point center = new Point(a.x + centerU * ux + centerV * vx,
230+
a.y + centerU * uy + centerV * vy);
231+
double angle = Math.atan2(uy, ux);
232+
bestRect = new Rectangle(center, width, height, angle);
233+
}
234+
}
235+
return bestRect;
236+
}
237+
}
238+

.github/RotatingCalipersTest.java

Whitespace-only changes.

0 commit comments

Comments
 (0)