Skip to content

Commit 41dc1b3

Browse files
committed
feat: added rotating calipers
1 parent 4fca4f1 commit 41dc1b3

File tree

2 files changed

+193
-45
lines changed

2 files changed

+193
-45
lines changed

src/main/java/com/thealgorithms/geometry/RotatingCalipers.java

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.thealgorithms.geometry;
22

3-
import java.util.ArrayList;
43
import java.util.Collections;
54
import java.util.List;
65

@@ -16,14 +15,10 @@ private RotatingCalipers() {
1615
}
1716

1817
// -------------------- Inner Classes --------------------
19-
public static record PointPair(Point p1, Point p2, double distance) {
18+
public record PointPair(Point p1, Point p2, double distance) {
2019
}
2120

22-
public static record Rectangle(PointD[] corners, double width, double height, double area) {
23-
}
24-
25-
// Double-based point for precise rectangle corners
26-
public static record PointD(double x, double y) {
21+
public record Rectangle(Point[] corners, double width, double height, double area) {
2722
}
2823

2924
// -------------------- Diameter --------------------
@@ -37,7 +32,8 @@ public static PointPair diameter(List<Point> points) {
3732
int n = hull.size();
3833

3934
double maxDist = 0;
40-
Point bestA = hull.get(0), bestB = hull.get(0);
35+
Point bestA = hull.get(0);
36+
Point bestB = hull.get(0);
4137

4238
int j = 1;
4339
for (int i = 0; i < n; i++) {
@@ -91,12 +87,18 @@ public static double width(List<Point> points) {
9187
double minProjV = Double.MAX_VALUE;
9288
double maxProjV = -Double.MAX_VALUE;
9389
for (Point p : hull) {
94-
// Project relative to edge starting point 'a'
95-
double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy;
96-
minProjV = Math.min(minProjV, projV);
97-
maxProjV = Math.max(maxProjV, projV);
90+
double projV = p.x() * vx + p.y() * vy;
91+
if (projV < minProjV) {
92+
minProjV = projV;
93+
}
94+
if (projV > maxProjV) {
95+
maxProjV = projV;
96+
}
97+
}
98+
double width = maxProjV - minProjV;
99+
if (width < minWidth) {
100+
minWidth = width;
98101
}
99-
minWidth = Math.min(minWidth, maxProjV - minProjV);
100102
}
101103
return minWidth;
102104
}
@@ -112,7 +114,7 @@ public static Rectangle minAreaBoundingRectangle(List<Point> points) {
112114
int n = hull.size();
113115

114116
double minArea = Double.MAX_VALUE;
115-
PointD[] bestCorners = null;
117+
Point[] bestCorners = null;
116118
double bestWidth = 0;
117119
double bestHeight = 0;
118120

@@ -128,17 +130,26 @@ public static Rectangle minAreaBoundingRectangle(List<Point> points) {
128130
double vx = -uy;
129131
double vy = ux;
130132

131-
double minU = Double.MAX_VALUE, maxU = -Double.MAX_VALUE;
132-
double minV = Double.MAX_VALUE, maxV = -Double.MAX_VALUE;
133+
double minU = Double.MAX_VALUE;
134+
double maxU = -Double.MAX_VALUE;
135+
double minV = Double.MAX_VALUE;
136+
double maxV = -Double.MAX_VALUE;
133137

134138
for (Point p : hull) {
135-
// Project relative to edge 'a'
136-
double projU = (p.x() - a.x()) * ux + (p.y() - a.y()) * uy;
137-
double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy;
138-
minU = Math.min(minU, projU);
139-
maxU = Math.max(maxU, projU);
140-
minV = Math.min(minV, projV);
141-
maxV = Math.max(maxV, projV);
139+
double projU = p.x() * ux + p.y() * uy;
140+
double projV = p.x() * vx + p.y() * vy;
141+
if (projU < minU) {
142+
minU = projU;
143+
}
144+
if (projU > maxU) {
145+
maxU = projU;
146+
}
147+
if (projV < minV) {
148+
minV = projV;
149+
}
150+
if (projV > maxV) {
151+
maxV = projV;
152+
}
142153
}
143154

144155
double width = maxU - minU;
@@ -149,9 +160,12 @@ public static Rectangle minAreaBoundingRectangle(List<Point> points) {
149160
minArea = area;
150161
bestWidth = width;
151162
bestHeight = height;
152-
153-
bestCorners = new PointD[] {new PointD(a.x() + ux * minU + vx * minV, a.y() + uy * minU + vy * minV), new PointD(a.x() + ux * maxU + vx * minV, a.y() + uy * maxU + vy * minV), new PointD(a.x() + ux * maxU + vx * maxV, a.y() + uy * maxU + vy * maxV),
154-
new PointD(a.x() + ux * minU + vx * maxV, a.y() + uy * minU + vy * maxV)};
163+
bestCorners = new Point[] {
164+
new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)),
165+
new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)),
166+
new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)),
167+
new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV))
168+
};
155169
}
156170
}
157171

@@ -166,7 +180,9 @@ private static void orderCounterClockwise(List<Point> points) {
166180
Point b = points.get((i + 1) % points.size());
167181
area += (a.x() * b.y()) - (b.x() * a.y());
168182
}
169-
if (area < 0) Collections.reverse(points);
183+
if (area < 0) {
184+
Collections.reverse(points);
185+
}
170186
}
171187

172188
private static double distanceSquared(Point a, Point b) {

src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java

Lines changed: 150 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,216 @@
11
package com.thealgorithms.geometry;
22

3-
import static org.junit.jupiter.api.Assertions.*;
4-
53
import java.util.Arrays;
64
import java.util.List;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertNotNull;
8+
import static org.junit.jupiter.api.Assertions.assertThrows;
9+
710
import org.junit.jupiter.api.Test;
811

912
public class RotatingCalipersTest {
1013

1114
@Test
1215
void testDiameterSimpleTriangle() {
13-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3));
16+
List<Point> convexHull = Arrays.asList(
17+
new Point(0, 0),
18+
new Point(4, 0),
19+
new Point(2, 3)
20+
);
1421
RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull);
1522

1623
assertNotNull(result);
17-
assertEquals(4.0, result.distance(), 0.001);
24+
assertEquals(5.0, result.distance(), 0.001);
1825
}
1926

2027
@Test
2128
void testDiameterSquare() {
22-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3));
29+
List<Point> convexHull = Arrays.asList(
30+
new Point(0, 0),
31+
new Point(3, 0),
32+
new Point(3, 3),
33+
new Point(0, 3)
34+
);
2335
RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull);
2436

2537
assertNotNull(result);
2638
assertEquals(Math.sqrt(18), result.distance(), 0.001);
2739
}
2840

41+
@Test
42+
void testDiameterComplexPolygon() {
43+
List<Point> convexHull = Arrays.asList(
44+
new Point(0, 0),
45+
new Point(3, 0),
46+
new Point(3, 3),
47+
new Point(0, 3)
48+
);
49+
RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull);
50+
51+
assertNotNull(result);
52+
assertEquals(Math.sqrt(18), result.distance(), 0.001);
53+
}
54+
55+
@Test
56+
void testDiameterTwoPoints() {
57+
List<Point> convexHull = Arrays.asList(
58+
new Point(0, 0),
59+
new Point(5, 0)
60+
);
61+
RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull);
62+
63+
assertNotNull(result);
64+
assertEquals(5.0, result.distance(), 0.001);
65+
assertEquals(new Point(0, 0), result.p1());
66+
assertEquals(new Point(5, 0), result.p2());
67+
}
68+
69+
@Test
70+
void testDiameterInvalidInput() {
71+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(null));
72+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(Arrays.asList()));
73+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(Arrays.asList(new Point(0, 0))));
74+
}
75+
2976
@Test
3077
void testWidthSimpleTriangle() {
31-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3));
78+
List<Point> convexHull = Arrays.asList(
79+
new Point(0, 0),
80+
new Point(4, 0),
81+
new Point(2, 3)
82+
);
3283
double result = RotatingCalipers.width(convexHull);
3384

34-
// Updated expected width based on correct projection
35-
assertEquals(3, result, 0.1);
85+
assertEquals(2.4, result, 0.1);
86+
}
87+
88+
@Test
89+
void testWidthSquare() {
90+
List<Point> convexHull = Arrays.asList(
91+
new Point(0, 0),
92+
new Point(3, 0),
93+
new Point(3, 3),
94+
new Point(0, 3)
95+
);
96+
double result = RotatingCalipers.width(convexHull);
97+
98+
assertEquals(3.0, result, 0.001);
99+
}
100+
101+
@Test
102+
void testWidthRectangle() {
103+
List<Point> convexHull = Arrays.asList(
104+
new Point(0, 0),
105+
new Point(5, 0),
106+
new Point(5, 2),
107+
new Point(0, 2)
108+
);
109+
double result = RotatingCalipers.width(convexHull);
110+
111+
assertEquals(2.0, result, 0.001);
112+
}
113+
114+
@Test
115+
void testWidthInvalidInput() {
116+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(null));
117+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList()));
118+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList(
119+
new Point(0, 0),
120+
new Point(1, 1)
121+
)));
36122
}
37123

38124
@Test
39125
void testMinAreaBoundingRectangleSquare() {
40-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3));
126+
List<Point> convexHull = Arrays.asList(
127+
new Point(0, 0),
128+
new Point(3, 0),
129+
new Point(3, 3),
130+
new Point(0, 3)
131+
);
41132
RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull);
42133

43134
assertNotNull(result);
44135
assertEquals(9.0, result.area(), 0.1);
45136
assertEquals(3.0, result.width(), 0.1);
46137
assertEquals(3.0, result.height(), 0.1);
47-
48-
// Check corners are PointD and not null
49-
for (RotatingCalipers.PointD corner : result.corners()) {
50-
assertNotNull(corner);
51-
}
52138
}
53139

54140
@Test
55141
void testMinAreaBoundingRectangleTriangle() {
56-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3));
142+
List<Point> convexHull = Arrays.asList(
143+
new Point(0, 0),
144+
new Point(4, 0),
145+
new Point(2, 3)
146+
);
57147
RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull);
58148

59149
assertNotNull(result);
150+
assertNotNull(result.corners());
60151
assertEquals(4, result.corners().length);
61152
}
62153

154+
@Test
155+
void testMinAreaBoundingRectangleRectangle() {
156+
List<Point> convexHull = Arrays.asList(
157+
new Point(0, 0),
158+
new Point(5, 0),
159+
new Point(5, 2),
160+
new Point(0, 2)
161+
);
162+
RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull);
163+
164+
assertNotNull(result);
165+
assertEquals(10.0, result.area(), 0.1);
166+
}
167+
168+
@Test
169+
void testMinAreaBoundingRectangleInvalidInput() {
170+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(null));
171+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList()));
172+
assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList(
173+
new Point(0, 0),
174+
new Point(1, 1)
175+
)));
176+
}
177+
63178
@Test
64179
void testDiameterWithLargeConvexHull() {
65-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3), new Point(2, -4), new Point(1, -3));
180+
List<Point> convexHull = Arrays.asList(
181+
new Point(0, 0),
182+
new Point(3, 0),
183+
new Point(3, 3),
184+
new Point(0, 3),
185+
new Point(2, -4),
186+
new Point(1, -3)
187+
);
66188
RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull);
67189

68190
assertNotNull(result);
69191
}
70192

71193
@Test
72194
void testWidthWithLargeConvexHull() {
73-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3));
195+
List<Point> convexHull = Arrays.asList(
196+
new Point(0, 0),
197+
new Point(3, 0),
198+
new Point(3, 3),
199+
new Point(0, 3)
200+
);
74201
double result = RotatingCalipers.width(convexHull);
75202

76203
assertEquals(3.0, result, 0.001);
77204
}
78205

79206
@Test
80207
void testMinAreaBoundingRectangleWithLargeConvexHull() {
81-
List<Point> convexHull = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(10, 5), new Point(0, 5));
208+
List<Point> convexHull = Arrays.asList(
209+
new Point(0, 0),
210+
new Point(10, 0),
211+
new Point(10, 5),
212+
new Point(0, 5)
213+
);
82214
RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull);
83215

84216
assertNotNull(result);

0 commit comments

Comments
 (0)