diff --git a/src/main/java/com/thealgorithms/geometry/LineIntersection.java b/src/main/java/com/thealgorithms/geometry/LineIntersection.java new file mode 100644 index 000000000000..fc28212381de --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/LineIntersection.java @@ -0,0 +1,70 @@ +package com.thealgorithms.geometry; + +/** + * Utility class to check if two line segments intersect. + * + *

This class provides methods to determine whether two given line segments + * intersect or not, using orientation tests. + * + *

Time Complexity: O(1) + * + * @author Sandeep + */ +public final class LineIntersection { + + private LineIntersection() { + } + + /** + * Represents a point in 2D space. + */ + public static final class Point { + public final double x; + public final double y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + } + + private static int orientation(Point p, Point q, Point r) { + double val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + if (val == 0.0) { + return 0; // collinear + } + return (val > 0.0) ? 1 : 2; // clockwise or counterclockwise + } + + private static boolean onSegment(Point p, Point q, Point r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) + && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); + } + + /** + * Checks whether two line segments (p1,q1) and (p2,q2) intersect. + * + * @param p1 starting point of first segment + * @param q1 ending point of first segment + * @param p2 starting point of second segment + * @param q2 ending point of second segment + * @return true if the segments intersect, false otherwise + */ + public static boolean doIntersect(Point p1, Point q1, Point p2, Point q2) { + int o1 = orientation(p1, q1, p2); + int o2 = orientation(p1, q1, q2); + int o3 = orientation(p2, q2, p1); + int o4 = orientation(p2, q2, q1); + + if (o1 != o2 && o3 != o4) { + return true; + } + + if (o1 == 0 && onSegment(p1, p2, q1)) return true; + if (o2 == 0 && onSegment(p1, q2, q1)) return true; + if (o3 == 0 && onSegment(p2, p1, q2)) return true; + if (o4 == 0 && onSegment(p2, q1, q2)) return true; + + return false; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/closestPairOfPoints.java b/src/main/java/com/thealgorithms/geometry/closestPairOfPoints.java new file mode 100644 index 000000000000..3fb2f28f6bf0 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/closestPairOfPoints.java @@ -0,0 +1,48 @@ +package com.thealgorithms.geometry; + +import java.util.*; + +public final class ClosestPairOfPoints { + private ClosestPairOfPoints() {} + + public static double closestPair(List points) { + List sortedByX = new ArrayList<>(points); + sortedByX.sort(Comparator.comparingDouble(p -> p.x)); + return divide(sortedByX); + } + + private static double divide(List pts) { + int n = pts.size(); + if (n <= 3) return bruteForce(pts); + + int mid = n / 2; + Point midPoint = pts.get(mid); + + double dl = divide(pts.subList(0, mid)); + double dr = divide(pts.subList(mid, n)); + double d = Math.min(dl, dr); + + List strip = new ArrayList<>(); + for (Point p : pts) { + if (Math.abs(p.x - midPoint.x) < d) strip.add(p); + } + + strip.sort(Comparator.comparingDouble(p -> p.y)); + for (int i = 0; i < strip.size(); ++i) { + for (int j = i + 1; j < strip.size() && (strip.get(j).y - strip.get(i).y) < d; ++j) { + d = Math.min(d, strip.get(i).distance(strip.get(j))); + } + } + return d; + } + + private static double bruteForce(List pts) { + double min = Double.POSITIVE_INFINITY; + for (int i = 0; i < pts.size(); ++i) { + for (int j = i + 1; j < pts.size(); ++j) { + min = Math.min(min, pts.get(i).distance(pts.get(j))); + } + } + return min; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/delaunayTriangulation.java b/src/main/java/com/thealgorithms/geometry/delaunayTriangulation.java new file mode 100644 index 000000000000..9374edd3b684 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/delaunayTriangulation.java @@ -0,0 +1,110 @@ +package com.thealgorithms.geometry; + +import java.util.*; + +/** + * Delaunay Triangulation using the Bowyer–Watson algorithm. + * + * Delaunay triangulation ensures that no point lies inside the circumcircle + * of any triangle in the triangulation. + * + * Time Complexity: O(n^2) average + * + * Reference: https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm + */ +public final class DelaunayTriangulation { + + private DelaunayTriangulation() {} + + public static List triangulate(List points) { + List triangles = new ArrayList<>(); + + // 1. Create super triangle large enough to encompass all points + double minX = points.stream().mapToDouble(p -> p.x).min().orElse(0); + double minY = points.stream().mapToDouble(p -> p.y).min().orElse(0); + double maxX = points.stream().mapToDouble(p -> p.x).max().orElse(0); + double maxY = points.stream().mapToDouble(p -> p.y).max().orElse(0); + + double dx = maxX - minX; + double dy = maxY - minY; + double deltaMax = Math.max(dx, dy); + double midx = (minX + maxX) / 2; + double midy = (minY + maxY) / 2; + + Point p1 = new Point(midx - 20 * deltaMax, midy - deltaMax); + Point p2 = new Point(midx, midy + 20 * deltaMax); + Point p3 = new Point(midx + 20 * deltaMax, midy - deltaMax); + Triangle superTriangle = new Triangle(p1, p2, p3); + triangles.add(superTriangle); + + // 2. Add points one by one + for (Point p : points) { + List badTriangles = new ArrayList<>(); + + for (Triangle t : triangles) { + if (t.isPointInsideCircumcircle(p)) { + badTriangles.add(t); + } + } + + List polygon = new ArrayList<>(); + for (Triangle t : badTriangles) { + for (Edge e : t.getEdges()) { + boolean shared = false; + for (Triangle t2 : badTriangles) { + if (t2 != t && t2.hasEdge(e)) { + shared = true; + break; + } + } + if (!shared) polygon.add(e); + } + } + + triangles.removeAll(badTriangles); + for (Edge e : polygon) { + triangles.add(new Triangle(e.p1, e.p2, p)); + } + } + + // 3. Remove triangles that share vertices with super triangle + triangles.removeIf(t -> t.hasVertex(p1) || t.hasVertex(p2) || t.hasVertex(p3)); + return triangles; + } + + /** Helper record for representing an Edge. */ + public record Edge(Point p1, Point p2) {} + + /** Helper class for representing a Triangle. */ + public static class Triangle { + final Point a, b, c; + + public Triangle(Point a, Point b, Point c) { + this.a = a; + this.b = b; + this.c = c; + } + + public boolean hasVertex(Point p) { + return p.equals(a) || p.equals(b) || p.equals(c); + } + + public boolean hasEdge(Edge e) { + return hasVertex(e.p1) && hasVertex(e.p2); + } + + public List getEdges() { + return List.of(new Edge(a, b), new Edge(b, c), new Edge(c, a)); + } + + public boolean isPointInsideCircumcircle(Point p) { + double ax = a.x - p.x, ay = a.y - p.y; + double bx = b.x - p.x, by = b.y - p.y; + double cx = c.x - p.x, cy = c.y - p.y; + double det = (ax * ax + ay * ay) * (bx * cy - by * cx) + - (bx * bx + by * by) * (ax * cy - ay * cx) + + (cx * cx + cy * cy) * (ax * by - ay * bx); + return det > 0; + } + } +} diff --git a/src/main/java/com/thealgorithms/geometry/lineSegmentIntersection.java b/src/main/java/com/thealgorithms/geometry/lineSegmentIntersection.java new file mode 100644 index 000000000000..83c2aa868845 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/lineSegmentIntersection.java @@ -0,0 +1,32 @@ +package com.thealgorithms.geometry; + +/** + * Line Segment Intersection detection using orientation method. + * + * Time Complexity: O(1) + */ +public final class LineSegmentIntersection { + private LineSegmentIntersection() {} + + public static boolean doIntersect(Point p1, Point q1, Point p2, Point q2) { + int o1 = Point.orientation(p1, q1, p2); + int o2 = Point.orientation(p1, q1, q2); + int o3 = Point.orientation(p2, q2, p1); + int o4 = Point.orientation(p2, q2, q1); + + if (o1 != o2 && o3 != o4) return true; + + // Collinear cases + if (o1 == 0 && onSegment(p1, p2, q1)) return true; + if (o2 == 0 && onSegment(p1, q2, q1)) return true; + if (o3 == 0 && onSegment(p2, p1, q2)) return true; + if (o4 == 0 && onSegment(p2, q1, q2)) return true; + + return false; + } + + private static boolean onSegment(Point p, Point q, Point r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) + && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); + } +} 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..214efe8e3b0e --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/rotatingCalipers.java @@ -0,0 +1,33 @@ +package com.thealgorithms.geometry; + +import java.util.List; + +/** + * Rotating Calipers algorithm to find the farthest pair of points (diameter) + * from a convex polygon. + * + * Time Complexity: O(n) + * + * Reference: https://cp-algorithms.com/geometry/rotating_calipers.html + */ +public final class RotatingCalipers { + private RotatingCalipers() {} + + public static double findDiameter(List points) { + int n = points.size(); + if (n < 2) return 0; + + int j = 1; + double maxDist = 0; + + for (int i = 0; i < n; i++) { + Point nextI = points.get((i + 1) % n); + while (Point.cross(nextI.subtract(points.get(i)), points.get((j + 1) % n).subtract(points.get(j))) > 0) { + j = (j + 1) % n; + } + maxDist = Math.max(maxDist, points.get(i).distance(points.get(j))); + } + + return maxDist; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/voronoiDiagram.java b/src/main/java/com/thealgorithms/geometry/voronoiDiagram.java new file mode 100644 index 000000000000..a014a0777ba7 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/voronoiDiagram.java @@ -0,0 +1,44 @@ +package com.thealgorithms.geometry; + +import java.awt.geom.Line2D; +import java.util.*; + +/** + * A simplified Voronoi Diagram generator using perpendicular bisectors. + * + * This implementation computes the Voronoi edges between each pair of sites + * by finding their perpendicular bisectors (not a full Fortune’s algorithm). + * + * Time Complexity: O(n^2) + */ +public final class VoronoiDiagram { + + private VoronoiDiagram() {} + + public static List computeVoronoiEdges(List points) { + List edges = new ArrayList<>(); + + for (int i = 0; i < points.size(); i++) { + for (int j = i + 1; j < points.size(); j++) { + Point p1 = points.get(i); + Point p2 = points.get(j); + Point mid = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); + + double dx = p2.x - p1.x; + double dy = p2.y - p1.y; + + // Perpendicular slope + double length = Math.sqrt(dx * dx + dy * dy); + double ux = -dy / length; + double uy = dx / length; + + // Extend line in both directions + double scale = 1000; + Point start = new Point(mid.x + ux * scale, mid.y + uy * scale); + Point end = new Point(mid.x - ux * scale, mid.y - uy * scale); + edges.add(new Line2D.Double(start.x, start.y, end.x, end.y)); + } + } + return edges; + } +} diff --git a/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java new file mode 100644 index 000000000000..1454b2678d98 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.geometry; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class LineIntersectionTest { + + @Test + void testIntersectingSegments() { + LineIntersection.Point p1 = new LineIntersection.Point(1, 1); + LineIntersection.Point q1 = new LineIntersection.Point(4, 4); + LineIntersection.Point p2 = new LineIntersection.Point(1, 4); + LineIntersection.Point q2 = new LineIntersection.Point(4, 1); + assertTrue(LineIntersection.doIntersect(p1, q1, p2, q2)); + } + + @Test + void testNonIntersectingSegments() { + LineIntersection.Point p1 = new LineIntersection.Point(1, 1); + LineIntersection.Point q1 = new LineIntersection.Point(2, 2); + LineIntersection.Point p2 = new LineIntersection.Point(3, 3); + LineIntersection.Point q2 = new LineIntersection.Point(4, 4); + assertFalse(LineIntersection.doIntersect(p1, q1, p2, q2)); + } + + @Test + void testCollinearOverlappingSegments() { + LineIntersection.Point p1 = new LineIntersection.Point(1, 1); + LineIntersection.Point q1 = new LineIntersection.Point(5, 5); + LineIntersection.Point p2 = new LineIntersection.Point(2, 2); + LineIntersection.Point q2 = new LineIntersection.Point(6, 6); + assertTrue(LineIntersection.doIntersect(p1, q1, p2, q2)); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/closestPairOfPointsTest.java b/src/test/java/com/thealgorithms/geometry/closestPairOfPointsTest.java new file mode 100644 index 000000000000..68d49b25a809 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/closestPairOfPointsTest.java @@ -0,0 +1,18 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class ClosestPairOfPointsTest { + @Test + void testClosestPair() { + var pts = Arrays.asList( + new Point(2, 3), new Point(12, 30), + new Point(40, 50), new Point(5, 1), + new Point(12, 10), new Point(3, 4) + ); + double result = ClosestPairOfPoints.closestPair(pts); + assertEquals(Math.sqrt(2), result, 1e-6); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/delaunayTriangulationTest.java b/src/test/java/com/thealgorithms/geometry/delaunayTriangulationTest.java new file mode 100644 index 000000000000..f47ff59969ab --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/delaunayTriangulationTest.java @@ -0,0 +1,21 @@ +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 DelaunayTriangulationTest { + + @Test + void testBasicTriangulation() { + List points = Arrays.asList( + new Point(0, 0), new Point(1, 0), + new Point(0, 1), new Point(1, 1) + ); + + List triangles = DelaunayTriangulation.triangulate(points); + assertFalse(triangles.isEmpty()); + assertTrue(triangles.size() >= 2); // At least 2 triangles for a square + } +} diff --git a/src/test/java/com/thealgorithms/geometry/lineSegmentIntersectionTest.java b/src/test/java/com/thealgorithms/geometry/lineSegmentIntersectionTest.java new file mode 100644 index 000000000000..ea0d75aa0ba1 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/lineSegmentIntersectionTest.java @@ -0,0 +1,20 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +public class LineSegmentIntersectionTest { + @Test + void testIntersectingSegments() { + Point p1 = new Point(1, 1), q1 = new Point(4, 4); + Point p2 = new Point(1, 4), q2 = new Point(4, 1); + assertTrue(LineSegmentIntersection.doIntersect(p1, q1, p2, q2)); + } + + @Test + void testNonIntersectingSegments() { + Point p1 = new Point(1, 1), q1 = new Point(2, 2); + Point p2 = new Point(3, 3), q2 = new Point(4, 4); + assertFalse(LineSegmentIntersection.doIntersect(p1, q1, p2, q2)); + } +} 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..2a31efad467d --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/rotatingCalipersTest.java @@ -0,0 +1,17 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class RotatingCalipersTest { + @Test + void testDiameterSquare() { + var square = Arrays.asList( + new Point(0, 0), new Point(0, 1), + new Point(1, 1), new Point(1, 0) + ); + double diameter = RotatingCalipers.findDiameter(square); + assertEquals(Math.sqrt(2), diameter, 1e-6); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/voronoiDiagramTest.java b/src/test/java/com/thealgorithms/geometry/voronoiDiagramTest.java new file mode 100644 index 000000000000..3371b79275e7 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/voronoiDiagramTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.*; +import java.awt.geom.Line2D; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class VoronoiDiagramTest { + + @Test + void testVoronoiEdges() { + List points = Arrays.asList( + new Point(0, 0), new Point(1, 0), + new Point(0, 1) + ); + + List edges = VoronoiDiagram.computeVoronoiEdges(points); + assertFalse(edges.isEmpty()); + assertTrue(edges.size() > 0); + } +}