From 4fca4f16cd23202e907a6743794db97f10401fe2 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:32:40 +0530 Subject: [PATCH 01/11] feat: added rotating calipers --- .../geometry/RotatingCalipers.java | 177 ++++++++++++++++++ .../geometry/RotatingCalipersTest.java | 87 +++++++++ 2 files changed, 264 insertions(+) create mode 100644 src/main/java/com/thealgorithms/geometry/RotatingCalipers.java create mode 100644 src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java new file mode 100644 index 000000000000..3aff7e7884e4 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -0,0 +1,177 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Rotating Calipers algorithm to compute: + * - Diameter of a convex polygon + * - Width of a convex polygon + * - Minimum-area bounding rectangle + */ +public final class RotatingCalipers { + + private RotatingCalipers() { + } + + // -------------------- Inner Classes -------------------- + public static record PointPair(Point p1, Point p2, double distance) { + } + + public static record Rectangle(PointD[] corners, double width, double height, double area) { + } + + // Double-based point for precise rectangle corners + public static record PointD(double x, double y) { + } + + // -------------------- Diameter -------------------- + public static PointPair diameter(List points) { + if (points == null || points.size() < 2) { + throw new IllegalArgumentException("At least two points required for diameter"); + } + + List hull = ConvexHull.convexHullRecursive(points); + orderCounterClockwise(hull); + int n = hull.size(); + + double maxDist = 0; + Point bestA = hull.get(0), bestB = hull.get(0); + + int j = 1; + for (int i = 0; i < n; i++) { + Point a = hull.get(i); + while (true) { + Point b1 = hull.get(j); + Point b2 = hull.get((j + 1) % n); + double d1 = distanceSquared(a, b1); + double d2 = distanceSquared(a, b2); + if (d2 > d1) { + j = (j + 1) % n; + } else { + break; + } + } + double d = distanceSquared(a, hull.get(j)); + if (d > maxDist) { + maxDist = d; + bestA = a; + bestB = hull.get(j); + } + } + return new PointPair(bestA, bestB, Math.sqrt(maxDist)); + } + + // -------------------- Width -------------------- + public static double width(List points) { + if (points == null || points.size() < 3) { + throw new IllegalArgumentException("At least three points required for width"); + } + + List hull = ConvexHull.convexHullRecursive(points); + orderCounterClockwise(hull); + int n = hull.size(); + + double minWidth = Double.MAX_VALUE; + + for (int i = 0; i < n; i++) { + Point a = hull.get(i); + Point b = hull.get((i + 1) % n); + + double ux = b.x() - a.x(); + double uy = b.y() - a.y(); + double len = Math.hypot(ux, uy); + ux /= len; + uy /= len; + + double vx = -uy; + double vy = ux; + + double minProjV = Double.MAX_VALUE; + double maxProjV = -Double.MAX_VALUE; + for (Point p : hull) { + // Project relative to edge starting point 'a' + double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy; + minProjV = Math.min(minProjV, projV); + maxProjV = Math.max(maxProjV, projV); + } + minWidth = Math.min(minWidth, maxProjV - minProjV); + } + return minWidth; + } + + // -------------------- Minimum-Area Bounding Rectangle -------------------- + public static Rectangle minAreaBoundingRectangle(List points) { + if (points == null || points.size() < 3) { + throw new IllegalArgumentException("At least three points required"); + } + + List hull = ConvexHull.convexHullRecursive(points); + orderCounterClockwise(hull); + int n = hull.size(); + + double minArea = Double.MAX_VALUE; + PointD[] bestCorners = null; + double bestWidth = 0; + double bestHeight = 0; + + for (int i = 0; i < n; i++) { + Point a = hull.get(i); + Point b = hull.get((i + 1) % n); + + double edgeDx = b.x() - a.x(); + double edgeDy = b.y() - a.y(); + double edgeLen = Math.hypot(edgeDx, edgeDy); + double ux = edgeDx / edgeLen; + double uy = edgeDy / edgeLen; + double vx = -uy; + double vy = ux; + + double minU = Double.MAX_VALUE, maxU = -Double.MAX_VALUE; + double minV = Double.MAX_VALUE, maxV = -Double.MAX_VALUE; + + for (Point p : hull) { + // Project relative to edge 'a' + double projU = (p.x() - a.x()) * ux + (p.y() - a.y()) * uy; + double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy; + minU = Math.min(minU, projU); + maxU = Math.max(maxU, projU); + minV = Math.min(minV, projV); + maxV = Math.max(maxV, projV); + } + + double width = maxU - minU; + double height = maxV - minV; + double area = width * height; + + if (area < minArea) { + minArea = area; + bestWidth = width; + bestHeight = height; + + 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), + new PointD(a.x() + ux * minU + vx * maxV, a.y() + uy * minU + vy * maxV)}; + } + } + + return new Rectangle(bestCorners, bestWidth, bestHeight, minArea); + } + + // -------------------- Helper Methods -------------------- + private static void orderCounterClockwise(List points) { + double area = 0.0; + for (int i = 0; i < points.size(); i++) { + Point a = points.get(i); + Point b = points.get((i + 1) % points.size()); + area += (a.x() * b.y()) - (b.x() * a.y()); + } + if (area < 0) Collections.reverse(points); + } + + private static double distanceSquared(Point a, Point b) { + double dx = a.x() - b.x(); + double dy = a.y() - b.y(); + return dx * dx + dy * dy; + } +} diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java new file mode 100644 index 000000000000..2819ee5756b0 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -0,0 +1,87 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class RotatingCalipersTest { + + @Test + void testDiameterSimpleTriangle() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); + + assertNotNull(result); + assertEquals(4.0, result.distance(), 0.001); + } + + @Test + void testDiameterSquare() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); + + assertNotNull(result); + assertEquals(Math.sqrt(18), result.distance(), 0.001); + } + + @Test + void testWidthSimpleTriangle() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + double result = RotatingCalipers.width(convexHull); + + // Updated expected width based on correct projection + assertEquals(3, result, 0.1); + } + + @Test + void testMinAreaBoundingRectangleSquare() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); + + assertNotNull(result); + assertEquals(9.0, result.area(), 0.1); + assertEquals(3.0, result.width(), 0.1); + assertEquals(3.0, result.height(), 0.1); + + // Check corners are PointD and not null + for (RotatingCalipers.PointD corner : result.corners()) { + assertNotNull(corner); + } + } + + @Test + void testMinAreaBoundingRectangleTriangle() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); + + assertNotNull(result); + assertEquals(4, result.corners().length); + } + + @Test + void testDiameterWithLargeConvexHull() { + List 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)); + RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); + + assertNotNull(result); + } + + @Test + void testWidthWithLargeConvexHull() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + double result = RotatingCalipers.width(convexHull); + + assertEquals(3.0, result, 0.001); + } + + @Test + void testMinAreaBoundingRectangleWithLargeConvexHull() { + List convexHull = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(10, 5), new Point(0, 5)); + RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); + + assertNotNull(result); + assertEquals(50.0, result.area(), 0.1); + } +} From 41dc1b37dc08af818b87c409d086bcc2fdf4ac73 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:44:04 +0530 Subject: [PATCH 02/11] feat: added rotating calipers --- .../geometry/RotatingCalipers.java | 70 +++++--- .../geometry/RotatingCalipersTest.java | 168 ++++++++++++++++-- 2 files changed, 193 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 3aff7e7884e4..76a13fd5a4e8 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -1,6 +1,5 @@ package com.thealgorithms.geometry; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -16,14 +15,10 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public static record PointPair(Point p1, Point p2, double distance) { + public record PointPair(Point p1, Point p2, double distance) { } - public static record Rectangle(PointD[] corners, double width, double height, double area) { - } - - // Double-based point for precise rectangle corners - public static record PointD(double x, double y) { + public record Rectangle(Point[] corners, double width, double height, double area) { } // -------------------- Diameter -------------------- @@ -37,7 +32,8 @@ public static PointPair diameter(List points) { int n = hull.size(); double maxDist = 0; - Point bestA = hull.get(0), bestB = hull.get(0); + Point bestA = hull.get(0); + Point bestB = hull.get(0); int j = 1; for (int i = 0; i < n; i++) { @@ -91,12 +87,18 @@ public static double width(List points) { double minProjV = Double.MAX_VALUE; double maxProjV = -Double.MAX_VALUE; for (Point p : hull) { - // Project relative to edge starting point 'a' - double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy; - minProjV = Math.min(minProjV, projV); - maxProjV = Math.max(maxProjV, projV); + double projV = p.x() * vx + p.y() * vy; + if (projV < minProjV) { + minProjV = projV; + } + if (projV > maxProjV) { + maxProjV = projV; + } + } + double width = maxProjV - minProjV; + if (width < minWidth) { + minWidth = width; } - minWidth = Math.min(minWidth, maxProjV - minProjV); } return minWidth; } @@ -112,7 +114,7 @@ public static Rectangle minAreaBoundingRectangle(List points) { int n = hull.size(); double minArea = Double.MAX_VALUE; - PointD[] bestCorners = null; + Point[] bestCorners = null; double bestWidth = 0; double bestHeight = 0; @@ -128,17 +130,26 @@ public static Rectangle minAreaBoundingRectangle(List points) { double vx = -uy; double vy = ux; - double minU = Double.MAX_VALUE, maxU = -Double.MAX_VALUE; - double minV = Double.MAX_VALUE, maxV = -Double.MAX_VALUE; + double minU = Double.MAX_VALUE; + double maxU = -Double.MAX_VALUE; + double minV = Double.MAX_VALUE; + double maxV = -Double.MAX_VALUE; for (Point p : hull) { - // Project relative to edge 'a' - double projU = (p.x() - a.x()) * ux + (p.y() - a.y()) * uy; - double projV = (p.x() - a.x()) * vx + (p.y() - a.y()) * vy; - minU = Math.min(minU, projU); - maxU = Math.max(maxU, projU); - minV = Math.min(minV, projV); - maxV = Math.max(maxV, projV); + double projU = p.x() * ux + p.y() * uy; + double projV = p.x() * vx + p.y() * vy; + if (projU < minU) { + minU = projU; + } + if (projU > maxU) { + maxU = projU; + } + if (projV < minV) { + minV = projV; + } + if (projV > maxV) { + maxV = projV; + } } double width = maxU - minU; @@ -149,9 +160,12 @@ public static Rectangle minAreaBoundingRectangle(List points) { minArea = area; bestWidth = width; bestHeight = height; - - 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), - new PointD(a.x() + ux * minU + vx * maxV, a.y() + uy * minU + vy * maxV)}; + bestCorners = new Point[] { + new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)), + new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)), + new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)), + new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV)) + }; } } @@ -166,7 +180,9 @@ private static void orderCounterClockwise(List points) { Point b = points.get((i + 1) % points.size()); area += (a.x() * b.y()) - (b.x() * a.y()); } - if (area < 0) Collections.reverse(points); + if (area < 0) { + Collections.reverse(points); + } } private static double distanceSquared(Point a, Point b) { diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index 2819ee5756b0..bfcd10548e42 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -1,68 +1,190 @@ package com.thealgorithms.geometry; -import static org.junit.jupiter.api.Assertions.*; - import java.util.Arrays; import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; public class RotatingCalipersTest { @Test void testDiameterSimpleTriangle() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(2, 3) + ); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); - assertEquals(4.0, result.distance(), 0.001); + assertEquals(5.0, result.distance(), 0.001); } @Test void testDiameterSquare() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(3, 3), + new Point(0, 3) + ); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); assertEquals(Math.sqrt(18), result.distance(), 0.001); } + @Test + void testDiameterComplexPolygon() { + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(3, 3), + new Point(0, 3) + ); + RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); + + assertNotNull(result); + assertEquals(Math.sqrt(18), result.distance(), 0.001); + } + + @Test + void testDiameterTwoPoints() { + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(5, 0) + ); + RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); + + assertNotNull(result); + assertEquals(5.0, result.distance(), 0.001); + assertEquals(new Point(0, 0), result.p1()); + assertEquals(new Point(5, 0), result.p2()); + } + + @Test + void testDiameterInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(null)); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(Arrays.asList())); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.diameter(Arrays.asList(new Point(0, 0)))); + } + @Test void testWidthSimpleTriangle() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(2, 3) + ); double result = RotatingCalipers.width(convexHull); - // Updated expected width based on correct projection - assertEquals(3, result, 0.1); + assertEquals(2.4, result, 0.1); + } + + @Test + void testWidthSquare() { + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(3, 3), + new Point(0, 3) + ); + double result = RotatingCalipers.width(convexHull); + + assertEquals(3.0, result, 0.001); + } + + @Test + void testWidthRectangle() { + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(5, 0), + new Point(5, 2), + new Point(0, 2) + ); + double result = RotatingCalipers.width(convexHull); + + assertEquals(2.0, result, 0.001); + } + + @Test + void testWidthInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(null)); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList())); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList( + new Point(0, 0), + new Point(1, 1) + ))); } @Test void testMinAreaBoundingRectangleSquare() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(3, 3), + new Point(0, 3) + ); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); assertEquals(9.0, result.area(), 0.1); assertEquals(3.0, result.width(), 0.1); assertEquals(3.0, result.height(), 0.1); - - // Check corners are PointD and not null - for (RotatingCalipers.PointD corner : result.corners()) { - assertNotNull(corner); - } } @Test void testMinAreaBoundingRectangleTriangle() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(2, 3) + ); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); + assertNotNull(result.corners()); assertEquals(4, result.corners().length); } + @Test + void testMinAreaBoundingRectangleRectangle() { + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(5, 0), + new Point(5, 2), + new Point(0, 2) + ); + RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); + + assertNotNull(result); + assertEquals(10.0, result.area(), 0.1); + } + + @Test + void testMinAreaBoundingRectangleInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(null)); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList())); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList( + new Point(0, 0), + new Point(1, 1) + ))); + } + @Test void testDiameterWithLargeConvexHull() { - List 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)); + List 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) + ); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -70,7 +192,12 @@ void testDiameterWithLargeConvexHull() { @Test void testWidthWithLargeConvexHull() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(3, 3), + new Point(0, 3) + ); double result = RotatingCalipers.width(convexHull); assertEquals(3.0, result, 0.001); @@ -78,7 +205,12 @@ void testWidthWithLargeConvexHull() { @Test void testMinAreaBoundingRectangleWithLargeConvexHull() { - List convexHull = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(10, 5), new Point(0, 5)); + List convexHull = Arrays.asList( + new Point(0, 0), + new Point(10, 0), + new Point(10, 5), + new Point(0, 5) + ); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); From 34994ed86aa695b468c57d9ba5a28fca0850c92d Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:45:32 +0530 Subject: [PATCH 03/11] feat: added rotating calipers --- .../geometry/RotatingCalipers.java | 14 +-- .../geometry/RotatingCalipersTest.java | 104 +++--------------- 2 files changed, 22 insertions(+), 96 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 76a13fd5a4e8..116902284617 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,11 +15,9 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public record PointPair(Point p1, Point p2, double distance) { - } + public record PointPair(Point p1, Point p2, double distance) {} - public record Rectangle(Point[] corners, double width, double height, double area) { - } + public record Rectangle(Point[] corners, double width, double height, double area) {} // -------------------- Diameter -------------------- public static PointPair diameter(List points) { @@ -160,12 +158,8 @@ public static Rectangle minAreaBoundingRectangle(List points) { minArea = area; bestWidth = width; bestHeight = height; - bestCorners = new Point[] { - new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)), - new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)), - new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)), - new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV)) - }; + bestCorners = new Point[] {new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)), new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)), new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)), + new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV))}; } } diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index bfcd10548e42..45f4b942d2d1 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -1,23 +1,19 @@ package com.thealgorithms.geometry; -import java.util.Arrays; -import java.util.List; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; + public class RotatingCalipersTest { @Test void testDiameterSimpleTriangle() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(2, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -26,12 +22,7 @@ void testDiameterSimpleTriangle() { @Test void testDiameterSquare() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(3, 3), - new Point(0, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -40,12 +31,7 @@ void testDiameterSquare() { @Test void testDiameterComplexPolygon() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(3, 3), - new Point(0, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -54,10 +40,7 @@ void testDiameterComplexPolygon() { @Test void testDiameterTwoPoints() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(5, 0) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(5, 0)); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -75,11 +58,7 @@ void testDiameterInvalidInput() { @Test void testWidthSimpleTriangle() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(2, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); double result = RotatingCalipers.width(convexHull); assertEquals(2.4, result, 0.1); @@ -87,12 +66,7 @@ void testWidthSimpleTriangle() { @Test void testWidthSquare() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(3, 3), - new Point(0, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); double result = RotatingCalipers.width(convexHull); assertEquals(3.0, result, 0.001); @@ -100,12 +74,7 @@ void testWidthSquare() { @Test void testWidthRectangle() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(5, 0), - new Point(5, 2), - new Point(0, 2) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(5, 0), new Point(5, 2), new Point(0, 2)); double result = RotatingCalipers.width(convexHull); assertEquals(2.0, result, 0.001); @@ -115,20 +84,12 @@ void testWidthRectangle() { void testWidthInvalidInput() { assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(null)); assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList())); - assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList( - new Point(0, 0), - new Point(1, 1) - ))); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.width(Arrays.asList(new Point(0, 0), new Point(1, 1)))); } @Test void testMinAreaBoundingRectangleSquare() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(3, 3), - new Point(0, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); @@ -139,11 +100,7 @@ void testMinAreaBoundingRectangleSquare() { @Test void testMinAreaBoundingRectangleTriangle() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(2, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); @@ -153,12 +110,7 @@ void testMinAreaBoundingRectangleTriangle() { @Test void testMinAreaBoundingRectangleRectangle() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(5, 0), - new Point(5, 2), - new Point(0, 2) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(5, 0), new Point(5, 2), new Point(0, 2)); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); @@ -169,22 +121,12 @@ void testMinAreaBoundingRectangleRectangle() { void testMinAreaBoundingRectangleInvalidInput() { assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(null)); assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList())); - assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList( - new Point(0, 0), - new Point(1, 1) - ))); + assertThrows(IllegalArgumentException.class, () -> RotatingCalipers.minAreaBoundingRectangle(Arrays.asList(new Point(0, 0), new Point(1, 1)))); } @Test void testDiameterWithLargeConvexHull() { - List 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) - ); + List 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)); RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); @@ -192,12 +134,7 @@ void testDiameterWithLargeConvexHull() { @Test void testWidthWithLargeConvexHull() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(3, 3), - new Point(0, 3) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(3, 3), new Point(0, 3)); double result = RotatingCalipers.width(convexHull); assertEquals(3.0, result, 0.001); @@ -205,12 +142,7 @@ void testWidthWithLargeConvexHull() { @Test void testMinAreaBoundingRectangleWithLargeConvexHull() { - List convexHull = Arrays.asList( - new Point(0, 0), - new Point(10, 0), - new Point(10, 5), - new Point(0, 5) - ); + List convexHull = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(10, 5), new Point(0, 5)); RotatingCalipers.Rectangle result = RotatingCalipers.minAreaBoundingRectangle(convexHull); assertNotNull(result); From c53c23afd302936c65c094122bafa889961d0665 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:47:21 +0530 Subject: [PATCH 04/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipersTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index 45f4b942d2d1..da0920670905 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -8,7 +8,6 @@ import java.util.List; import org.junit.jupiter.api.Test; - public class RotatingCalipersTest { @Test From 48f00f032ef0dc8b37f76162bcecf9f5c9549b42 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:48:51 +0530 Subject: [PATCH 05/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 116902284617..117cb0fa8729 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,9 +15,11 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public record PointPair(Point p1, Point p2, double distance) {} + public record PointPair(Point p1, Point p2, double distance) { + } - public record Rectangle(Point[] corners, double width, double height, double area) {} + public record Rectangle(Point[] corners, double width, double height, double area) { + } // -------------------- Diameter -------------------- public static PointPair diameter(List points) { From 5559bf8c01fc61eeed2fcfcab0773d882e30df61 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 10:51:41 +0530 Subject: [PATCH 06/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipersTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index da0920670905..5c051c01df5e 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -16,7 +16,7 @@ void testDiameterSimpleTriangle() { RotatingCalipers.PointPair result = RotatingCalipers.diameter(convexHull); assertNotNull(result); - assertEquals(5.0, result.distance(), 0.001); + assertEquals(4.0, result.distance(), 0.001); } @Test @@ -60,7 +60,7 @@ void testWidthSimpleTriangle() { List convexHull = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); double result = RotatingCalipers.width(convexHull); - assertEquals(2.4, result, 0.1); + assertEquals(3, result, 0.1); } @Test From a39d18ff7d57d1beb0f58f3f73820d625d5d2959 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 11:00:40 +0530 Subject: [PATCH 07/11] feat: added rotating calipers --- .../geometry/RotatingCalipers.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 117cb0fa8729..a4492d29f6b6 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,10 +15,10 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public record PointPair(Point p1, Point p2, double distance) { + public static record PointPair(Point p1, Point p2, double distance) { } - public record Rectangle(Point[] corners, double width, double height, double area) { + public static record Rectangle(Point[] corners, double width, double height, double area) { } // -------------------- Diameter -------------------- @@ -86,19 +86,13 @@ public static double width(List points) { double minProjV = Double.MAX_VALUE; double maxProjV = -Double.MAX_VALUE; + for (Point p : hull) { double projV = p.x() * vx + p.y() * vy; - if (projV < minProjV) { - minProjV = projV; - } - if (projV > maxProjV) { - maxProjV = projV; - } - } - double width = maxProjV - minProjV; - if (width < minWidth) { - minWidth = width; + minProjV = Math.min(minProjV, projV); + maxProjV = Math.max(maxProjV, projV); } + minWidth = Math.min(minWidth, maxProjV - minProjV); } return minWidth; } @@ -138,18 +132,10 @@ public static Rectangle minAreaBoundingRectangle(List points) { for (Point p : hull) { double projU = p.x() * ux + p.y() * uy; double projV = p.x() * vx + p.y() * vy; - if (projU < minU) { - minU = projU; - } - if (projU > maxU) { - maxU = projU; - } - if (projV < minV) { - minV = projV; - } - if (projV > maxV) { - maxV = projV; - } + minU = Math.min(minU, projU); + maxU = Math.max(maxU, projU); + minV = Math.min(minV, projV); + maxV = Math.max(maxV, projV); } double width = maxU - minU; @@ -160,8 +146,12 @@ public static Rectangle minAreaBoundingRectangle(List points) { minArea = area; bestWidth = width; bestHeight = height; - bestCorners = new Point[] {new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)), new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)), new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)), - new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV))}; + bestCorners = new Point[] { + new Point((int)(ux * minU + vx * minV), (int)(uy * minU + vy * minV)), + new Point((int)(ux * maxU + vx * minV), (int)(uy * maxU + vy * minV)), + new Point((int)(ux * maxU + vx * maxV), (int)(uy * maxU + vy * maxV)), + new Point((int)(ux * minU + vx * maxV), (int)(uy * minU + vy * maxV)) + }; } } From 9d7937db6bc287c1af85d5a30c253ccc051abef3 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 11:01:19 +0530 Subject: [PATCH 08/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index a4492d29f6b6..93a3a8244e13 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -146,12 +146,8 @@ public static Rectangle minAreaBoundingRectangle(List points) { minArea = area; bestWidth = width; bestHeight = height; - bestCorners = new Point[] { - new Point((int)(ux * minU + vx * minV), (int)(uy * minU + vy * minV)), - new Point((int)(ux * maxU + vx * minV), (int)(uy * maxU + vy * minV)), - new Point((int)(ux * maxU + vx * maxV), (int)(uy * maxU + vy * maxV)), - new Point((int)(ux * minU + vx * maxV), (int)(uy * minU + vy * maxV)) - }; + bestCorners = new Point[] {new Point((int) (ux * minU + vx * minV), (int) (uy * minU + vy * minV)), new Point((int) (ux * maxU + vx * minV), (int) (uy * maxU + vy * minV)), new Point((int) (ux * maxU + vx * maxV), (int) (uy * maxU + vy * maxV)), + new Point((int) (ux * minU + vx * maxV), (int) (uy * minU + vy * maxV))}; } } From 5175429f9848048b16232480f5ba5d0c55b4c163 Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 11:04:13 +0530 Subject: [PATCH 09/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 93a3a8244e13..c6260dfea8f0 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,10 +15,10 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public static record PointPair(Point p1, Point p2, double distance) { + public record PointPair(Point p1, Point p2, double distance) { } - public static record Rectangle(Point[] corners, double width, double height, double area) { + public record Rectangle(Point[] corners, double width, double height, double area) { } // -------------------- Diameter -------------------- From 72ad48a4c9eef7eb0986b72bb968e53188ef841c Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 11:09:19 +0530 Subject: [PATCH 10/11] feat: added rotating calipers --- .../geometry/RotatingCalipers.java | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index c6260dfea8f0..2b2d9da43ab2 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,11 +15,9 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public record PointPair(Point p1, Point p2, double distance) { - } + public record PointPair(Point p1, Point p2, double distance) {} - public record Rectangle(Point[] corners, double width, double height, double area) { - } + public record Rectangle(Point[] corners, double width, double height, double area) {} // -------------------- Diameter -------------------- public static PointPair diameter(List points) { @@ -29,15 +27,14 @@ public static PointPair diameter(List points) { List hull = ConvexHull.convexHullRecursive(points); orderCounterClockwise(hull); - int n = hull.size(); double maxDist = 0; Point bestA = hull.get(0); Point bestB = hull.get(0); - + int n = hull.size(); int j = 1; - for (int i = 0; i < n; i++) { - Point a = hull.get(i); + + for (Point a : hull) { while (true) { Point b1 = hull.get(j); Point b2 = hull.get((j + 1) % n); @@ -56,6 +53,7 @@ public static PointPair diameter(List points) { bestB = hull.get(j); } } + return new PointPair(bestA, bestB, Math.sqrt(maxDist)); } @@ -67,9 +65,9 @@ public static double width(List points) { List hull = ConvexHull.convexHullRecursive(points); orderCounterClockwise(hull); - int n = hull.size(); double minWidth = Double.MAX_VALUE; + int n = hull.size(); for (int i = 0; i < n; i++) { Point a = hull.get(i); @@ -86,7 +84,6 @@ public static double width(List points) { double minProjV = Double.MAX_VALUE; double maxProjV = -Double.MAX_VALUE; - for (Point p : hull) { double projV = p.x() * vx + p.y() * vy; minProjV = Math.min(minProjV, projV); @@ -94,6 +91,7 @@ public static double width(List points) { } minWidth = Math.min(minWidth, maxProjV - minProjV); } + return minWidth; } @@ -105,12 +103,12 @@ public static Rectangle minAreaBoundingRectangle(List points) { List hull = ConvexHull.convexHullRecursive(points); orderCounterClockwise(hull); - int n = hull.size(); double minArea = Double.MAX_VALUE; Point[] bestCorners = null; double bestWidth = 0; double bestHeight = 0; + int n = hull.size(); for (int i = 0; i < n; i++) { Point a = hull.get(i); @@ -132,10 +130,18 @@ public static Rectangle minAreaBoundingRectangle(List points) { for (Point p : hull) { double projU = p.x() * ux + p.y() * uy; double projV = p.x() * vx + p.y() * vy; - minU = Math.min(minU, projU); - maxU = Math.max(maxU, projU); - minV = Math.min(minV, projV); - maxV = Math.max(maxV, projV); + if (projU < minU) { + minU = projU; + } + if (projU > maxU) { + maxU = projU; + } + if (projV < minV) { + minV = projV; + } + if (projV > maxV) { + maxV = projV; + } } double width = maxU - minU; @@ -157,9 +163,10 @@ public static Rectangle minAreaBoundingRectangle(List points) { // -------------------- Helper Methods -------------------- private static void orderCounterClockwise(List points) { double area = 0.0; - for (int i = 0; i < points.size(); i++) { + int n = points.size(); + for (int i = 0; i < n; i++) { Point a = points.get(i); - Point b = points.get((i + 1) % points.size()); + Point b = points.get((i + 1) % n); area += (a.x() * b.y()) - (b.x() * a.y()); } if (area < 0) { From 37739cca266bdd489ebb10277a9a3bb533bb3cea Mon Sep 17 00:00:00 2001 From: yadhu-vipin Date: Mon, 6 Oct 2025 11:10:29 +0530 Subject: [PATCH 11/11] feat: added rotating calipers --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 2b2d9da43ab2..13acb958e7da 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -15,9 +15,11 @@ private RotatingCalipers() { } // -------------------- Inner Classes -------------------- - public record PointPair(Point p1, Point p2, double distance) {} + public record PointPair(Point p1, Point p2, double distance) { + } - public record Rectangle(Point[] corners, double width, double height, double area) {} + public record Rectangle(Point[] corners, double width, double height, double area) { + } // -------------------- Diameter -------------------- public static PointPair diameter(List points) {