diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java
new file mode 100644
index 000000000000..a11acb87dd7a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java
@@ -0,0 +1,423 @@
+package com.thealgorithms.geometry;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Implementation of the Bentley–Ottmann algorithm for finding all intersection
+ * points among a set of line segments in O((n + k) log n) time.
+ *
+ *
Uses a sweep-line approach with an event queue and status structure to
+ * efficiently detect intersections in 2D plane geometry.
+ *
+ * @see
+ * Bentley–Ottmann algorithm
+ */
+public final class BentleyOttmann {
+
+ private BentleyOttmann() {
+ }
+
+ private static final double EPS = 1e-9;
+ private static double currentSweepX;
+
+ /**
+ * Represents a line segment with two endpoints.
+ */
+ public static class Segment {
+ final Point2D.Double p1;
+ final Point2D.Double p2;
+ final int id; // Unique identifier for each segment
+
+ Segment(Point2D.Double p1, Point2D.Double p2) {
+ this.p1 = p1;
+ this.p2 = p2;
+ this.id = segmentCounter++;
+ }
+
+ private static int segmentCounter = 0;
+
+ /**
+ * Computes the y-coordinate of this segment at a given x value.
+ */
+ double getY(double x) {
+ if (Math.abs(p2.x - p1.x) < EPS) {
+ // Vertical segment: return midpoint y
+ return (p1.y + p2.y) / 2.0;
+ }
+ double t = (x - p1.x) / (p2.x - p1.x);
+ return p1.y + t * (p2.y - p1.y);
+ }
+
+ Point2D.Double leftPoint() {
+ return p1.x < p2.x ? p1 : p1.x > p2.x ? p2 : p1.y < p2.y ? p1 : p2;
+ }
+
+ Point2D.Double rightPoint() {
+ return p1.x > p2.x ? p1 : p1.x < p2.x ? p2 : p1.y > p2.y ? p1 : p2;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("S%d[(%.2f, %.2f), (%.2f, %.2f)]", id, p1.x, p1.y, p2.x, p2.y);
+ }
+ }
+
+ /**
+ * Event types for the sweep line algorithm.
+ */
+ private enum EventType { START, END, INTERSECTION }
+
+ /**
+ * Represents an event in the event queue.
+ */
+ private static class Event implements Comparable {
+ final Point2D.Double point;
+ final EventType type;
+ final Set segments; // Segments involved in this event
+
+ Event(Point2D.Double point, EventType type) {
+ this.point = point;
+ this.type = type;
+ this.segments = new HashSet<>();
+ }
+
+ void addSegment(Segment s) {
+ segments.add(s);
+ }
+
+ @Override
+ public int compareTo(Event other) {
+ // Sort by x-coordinate, then by y-coordinate
+ int cmp = Double.compare(this.point.x, other.point.x);
+ if (cmp == 0) {
+ cmp = Double.compare(this.point.y, other.point.y);
+ }
+ if (cmp == 0) {
+ // Process END events before START events at same point
+ cmp = this.type.compareTo(other.type);
+ }
+ return cmp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Event e)) {
+ return false;
+ }
+ return pointsEqual(this.point, e.point);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Math.round(point.x * 1e6), Math.round(point.y * 1e6));
+ }
+ }
+
+ /**
+ * Comparator for segments in the status structure (sweep line).
+ * Orders segments by their y-coordinate at the current sweep line position.
+ */
+ private static final class StatusComparator implements Comparator {
+ @Override
+ public int compare(Segment s1, Segment s2) {
+ if (s1.id == s2.id) {
+ return 0;
+ }
+
+ double y1 = s1.getY(currentSweepX);
+ double y2 = s2.getY(currentSweepX);
+
+ int cmp = Double.compare(y1, y2);
+ if (Math.abs(y1 - y2) < EPS) {
+ // If y-coordinates are equal, use segment id for consistency
+ return Integer.compare(s1.id, s2.id);
+ }
+ return cmp;
+ }
+ }
+
+ /**
+ * Finds all intersection points among a set of line segments.
+ *
+ * An intersection point is reported when two or more segments cross or touch.
+ * For overlapping segments, only actual crossing/touching points are reported,
+ * not all points along the overlap.
+ *
+ * @param segments list of line segments represented as pairs of points
+ * @return a set of intersection points where segments meet or cross
+ * @throws IllegalArgumentException if the list is null or contains null points
+ */
+ public static Set findIntersections(List segments) {
+ if (segments == null) {
+ throw new IllegalArgumentException("Segment list must not be null");
+ }
+
+ Segment.segmentCounter = 0; // Reset counter
+ Set intersections = new HashSet<>();
+ PriorityQueue eventQueue = new PriorityQueue<>();
+ TreeSet status = new TreeSet<>(new StatusComparator());
+ Map eventMap = new HashMap<>();
+
+ // Initialize event queue with segment start and end points
+ for (Segment s : segments) {
+ Point2D.Double left = s.leftPoint();
+ Point2D.Double right = s.rightPoint();
+
+ Event startEvent = getOrCreateEvent(eventMap, left, EventType.START);
+ startEvent.addSegment(s);
+
+ Event endEvent = getOrCreateEvent(eventMap, right, EventType.END);
+ endEvent.addSegment(s);
+ }
+
+ // Add all unique events to the queue
+ for (Event e : eventMap.values()) {
+ if (!e.segments.isEmpty()) {
+ eventQueue.add(e);
+ }
+ }
+
+ // Process events
+ while (!eventQueue.isEmpty()) {
+ Event event = eventQueue.poll();
+ currentSweepX = event.point.x;
+
+ handleEvent(event, status, eventQueue, eventMap, intersections);
+ }
+
+ return intersections;
+ }
+
+ private static Event getOrCreateEvent(Map eventMap, Point2D.Double point, EventType type) {
+ // Find existing event at this point
+ for (Map.Entry entry : eventMap.entrySet()) {
+ if (pointsEqual(entry.getKey(), point)) {
+ return entry.getValue();
+ }
+ }
+ // Create new event
+ Event event = new Event(point, type);
+ eventMap.put(point, event);
+ return event;
+ }
+
+ private static void handleEvent(Event event, TreeSet status, PriorityQueue eventQueue, Map eventMap, Set intersections) {
+ Point2D.Double p = event.point;
+ Set segmentsAtPoint = new HashSet<>(event.segments);
+
+ // Check segments in status structure (much smaller than allSegments)
+ for (Segment s : status) {
+ if (pointsEqual(s.p1, p) || pointsEqual(s.p2, p) || (onSegment(s, p) && !pointsEqual(s.p1, p) && !pointsEqual(s.p2, p))) {
+ segmentsAtPoint.add(s);
+ }
+ }
+
+ // If 2 or more segments meet at this point, it's an intersection
+ if (segmentsAtPoint.size() >= 2) {
+ intersections.add(p);
+ }
+
+ // Categorize segments
+ Set upperSegs = new HashSet<>(); // Segments starting at p
+ Set lowerSegs = new HashSet<>(); // Segments ending at p
+ Set containingSegs = new HashSet<>(); // Segments containing p in interior
+
+ for (Segment s : segmentsAtPoint) {
+ if (pointsEqual(s.leftPoint(), p)) {
+ upperSegs.add(s);
+ } else if (pointsEqual(s.rightPoint(), p)) {
+ lowerSegs.add(s);
+ } else {
+ containingSegs.add(s);
+ }
+ }
+
+ // Remove ending segments and segments containing p from status
+ status.removeAll(lowerSegs);
+ status.removeAll(containingSegs);
+
+ // Update sweep line position slightly past the event
+ currentSweepX = p.x + EPS;
+
+ // Add starting segments and re-add containing segments
+ status.addAll(upperSegs);
+ status.addAll(containingSegs);
+
+ if (upperSegs.isEmpty() && containingSegs.isEmpty()) {
+ // Find neighbors and check for new intersections
+ Segment sl = getNeighbor(status, lowerSegs, true);
+ Segment sr = getNeighbor(status, lowerSegs, false);
+ if (sl != null && sr != null) {
+ findNewEvent(sl, sr, p, eventQueue, eventMap);
+ }
+ } else {
+ Set unionSegs = new HashSet<>(upperSegs);
+ unionSegs.addAll(containingSegs);
+
+ Segment leftmost = getLeftmost(unionSegs, status);
+ Segment rightmost = getRightmost(unionSegs, status);
+
+ if (leftmost != null) {
+ Segment sl = status.lower(leftmost);
+ if (sl != null) {
+ findNewEvent(sl, leftmost, p, eventQueue, eventMap);
+ }
+ }
+
+ if (rightmost != null) {
+ Segment sr = status.higher(rightmost);
+ if (sr != null) {
+ findNewEvent(rightmost, sr, p, eventQueue, eventMap);
+ }
+ }
+ }
+ }
+
+ private static Segment getNeighbor(NavigableSet status, Set removed, boolean lower) {
+ if (removed.isEmpty()) {
+ return null;
+ }
+ Segment ref = removed.iterator().next();
+ return lower ? status.lower(ref) : status.higher(ref);
+ }
+
+ private static Segment getLeftmost(Set segments, SortedSet status) {
+ Segment leftmost = null;
+ for (Segment s : segments) {
+ if (leftmost == null || Objects.requireNonNull(status.comparator()).compare(s, leftmost) < 0) {
+ leftmost = s;
+ }
+ }
+ return leftmost;
+ }
+
+ private static Segment getRightmost(Set segments, SortedSet status) {
+ Segment rightmost = null;
+ for (Segment s : segments) {
+ if (status.comparator() != null && (rightmost == null || status.comparator().compare(s, rightmost) > 0)) {
+ rightmost = s;
+ }
+ }
+ return rightmost;
+ }
+
+ private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentPoint, PriorityQueue eventQueue, Map eventMap) {
+ Point2D.Double intersection = getIntersection(s1, s2);
+
+ if (intersection != null && intersection.x > currentPoint.x - EPS && !pointsEqual(intersection, currentPoint)) {
+
+ // Check if event already exists
+ boolean exists = false;
+ for (Map.Entry entry : eventMap.entrySet()) {
+ if (pointsEqual(entry.getKey(), intersection)) {
+ exists = true;
+ Event existingEvent = entry.getValue();
+ existingEvent.addSegment(s1);
+ existingEvent.addSegment(s2);
+ break;
+ }
+ }
+
+ if (!exists) {
+ Event newEvent = new Event(intersection, EventType.INTERSECTION);
+ newEvent.addSegment(s1);
+ newEvent.addSegment(s2);
+ eventMap.put(intersection, newEvent);
+ eventQueue.add(newEvent);
+ }
+ }
+ }
+
+ private static Point2D.Double getIntersection(Segment s1, Segment s2) {
+ double x1 = s1.p1.x;
+ double y1 = s1.p1.y;
+ double x2 = s1.p2.x;
+ double y2 = s1.p2.y;
+ double x3 = s2.p1.x;
+ double y3 = s2.p1.y;
+ double x4 = s2.p2.x;
+ double y4 = s2.p2.y;
+
+ double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+ if (Math.abs(denom) < EPS) {
+ // Parallel or collinear
+ if (areCollinear(s1, s2)) {
+ // For collinear segments, check if they overlap
+ // Return any overlapping point
+ List overlapPoints = new ArrayList<>();
+
+ if (onSegment(s1, s2.p1)) {
+ overlapPoints.add(s2.p1);
+ }
+ if (onSegment(s1, s2.p2)) {
+ overlapPoints.add(s2.p2);
+ }
+ if (onSegment(s2, s1.p1)) {
+ overlapPoints.add(s1.p1);
+ }
+ if (onSegment(s2, s1.p2)) {
+ overlapPoints.add(s1.p2);
+ }
+
+ // Remove duplicates and return the first point
+ if (!overlapPoints.isEmpty()) {
+ // Find the point that's not an endpoint of both segments
+ for (Point2D.Double pt : overlapPoints) {
+ boolean isS1Endpoint = pointsEqual(pt, s1.p1) || pointsEqual(pt, s1.p2);
+ boolean isS2Endpoint = pointsEqual(pt, s2.p1) || pointsEqual(pt, s2.p2);
+
+ // If it's an endpoint of both, it's a touching point
+ if (isS1Endpoint && isS2Endpoint) {
+ return pt;
+ }
+ }
+ // Return the first overlap point
+ return overlapPoints.getFirst();
+ }
+ }
+ return null;
+ }
+
+ double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
+ double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
+
+ if (t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS) {
+ double px = x1 + t * (x2 - x1);
+ double py = y1 + t * (y2 - y1);
+ return new Point2D.Double(px, py);
+ }
+
+ return null;
+ }
+
+ private static boolean areCollinear(Segment s1, Segment s2) {
+ double cross1 = crossProduct(s1.p1, s1.p2, s2.p1);
+ double cross2 = crossProduct(s1.p1, s1.p2, s2.p2);
+ return Math.abs(cross1) < EPS && Math.abs(cross2) < EPS;
+ }
+
+ private static double crossProduct(Point2D.Double a, Point2D.Double b, Point2D.Double c) {
+ return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
+ }
+
+ private static boolean onSegment(Segment s, Point2D.Double p) {
+ return p.x >= Math.min(s.p1.x, s.p2.x) - EPS && p.x <= Math.max(s.p1.x, s.p2.x) + EPS && p.y >= Math.min(s.p1.y, s.p2.y) - EPS && p.y <= Math.max(s.p1.y, s.p2.y) + EPS && Math.abs(crossProduct(s.p1, s.p2, p)) < EPS;
+ }
+
+ private static boolean pointsEqual(Point2D.Double p1, Point2D.Double p2) {
+ return Math.abs(p1.x - p2.x) < EPS && Math.abs(p1.y - p2.y) < EPS;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java
new file mode 100644
index 000000000000..99240566a2f3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java
@@ -0,0 +1,324 @@
+package com.thealgorithms.geometry;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Comprehensive unit tests for {@link BentleyOttmann}.
+ *
+ * This test suite validates the correctness of the Bentley–Ottmann algorithm
+ * implementation by checking intersection points between multiple line segment configurations.
+ *
+ * Test cases include typical, edge, degenerate geometrical setups, and performance tests.
+ */
+public class BentleyOttmannTest {
+
+ private static final double EPS = 1e-6;
+
+ @Test
+ void testSingleIntersection() {
+ List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertEquals(1, intersections.size());
+ Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0));
+ }
+
+ @Test
+ void testVerticalIntersection() {
+ List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertEquals(1, intersections.size());
+ Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0));
+ }
+
+ @Test
+ void testNoIntersection() {
+ List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(intersections.isEmpty());
+ }
+
+ @Test
+ void testCoincidentSegments() {
+ List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+
+ Assertions.assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)");
+ Assertions.assertTrue(containsPoint(intersections, 1.0, 1.0));
+ Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0));
+ }
+
+ @Test
+ void testHorizontalIntersection() {
+ List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0));
+ }
+
+ @Test
+ void testEmptyList() {
+ List segments = List.of();
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(intersections.isEmpty());
+ }
+
+ @Test
+ void testSingleSegment() {
+ List segments = List.of(newSegment(0, 0, 5, 5));
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(intersections.isEmpty());
+ }
+
+ @Test
+ void testNullListThrowsException() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> BentleyOttmann.findIntersections(null));
+ }
+
+ @Test
+ void testParallelSegments() {
+ // Test 1: Parallel diagonal segments
+ List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4));
+ Assertions.assertTrue(BentleyOttmann.findIntersections(diagonalSegments).isEmpty());
+
+ // Test 2: Parallel vertical segments
+ List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5));
+ Assertions.assertTrue(BentleyOttmann.findIntersections(verticalSegments).isEmpty());
+
+ // Test 3: Parallel horizontal segments
+ List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3));
+ Assertions.assertTrue(BentleyOttmann.findIntersections(horizontalSegments).isEmpty());
+ }
+
+ @Test
+ void testTouchingEndpoints() {
+ List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertEquals(1, intersections.size());
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0));
+ }
+
+ @Test
+ void testOverlappingCollinearSegments() {
+ List segments = List.of(newSegment(0, 0, 4, 4), newSegment(2, 2, 6, 6));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ // Overlapping collinear segments share the point (2,2) where second starts
+ // and (4,4) where first ends - at least one should be detected
+ Assertions.assertFalse(intersections.isEmpty(), "Should find at least one overlap point");
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain either (2,2) or (4,4)");
+ }
+
+ @Test
+ void testMultipleSegmentsAtOnePoint() {
+ // Star pattern: 4 segments meeting at (2, 2)
+ List segments = List.of(newSegment(0, 2, 4, 2), // horizontal
+ newSegment(2, 0, 2, 4), // vertical
+ newSegment(0, 0, 4, 4), // diagonal /
+ newSegment(0, 4, 4, 0) // diagonal \
+ );
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0));
+ // All segments meet at (2, 2), so should be reported once
+ Assertions.assertEquals(1, intersections.size());
+ }
+
+ @Test
+ void testGridPattern() {
+ // 3x3 grid: should have 9 intersection points
+ List segments = new ArrayList<>();
+
+ // Vertical lines at x = 0, 1, 2
+ for (int i = 0; i <= 2; i++) {
+ segments.add(newSegment(i, 0, i, 2));
+ }
+
+ // Horizontal lines at y = 0, 1, 2
+ for (int i = 0; i <= 2; i++) {
+ segments.add(newSegment(0, i, 2, i));
+ }
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+
+ // Each vertical line crosses each horizontal line
+ // 3 vertical × 3 horizontal = 9 intersections
+ Assertions.assertEquals(9, intersections.size(), "3x3 grid should have 9 intersections");
+
+ // Verify all grid points are present
+ for (int x = 0; x <= 2; x++) {
+ for (int y = 0; y <= 2; y++) {
+ Assertions.assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y));
+ }
+ }
+ }
+
+ @Test
+ void testTriangleIntersections() {
+ // Three segments forming a triangle
+ List segments = List.of(newSegment(0, 0, 4, 0), // base
+ newSegment(0, 0, 2, 3), // left side
+ newSegment(4, 0, 2, 3) // right side
+ );
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ // Triangle vertices are intersections
+ Assertions.assertTrue(containsPoint(intersections, 0.0, 0.0));
+ Assertions.assertTrue(containsPoint(intersections, 4.0, 0.0));
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 3.0));
+ Assertions.assertEquals(3, intersections.size());
+ }
+
+ @Test
+ void testCrossingDiagonals() {
+ // X pattern with multiple crossings
+ List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present");
+ Assertions.assertEquals(1, intersections.size());
+ }
+
+ @Test
+ void testVerySmallSegments() {
+ List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertEquals(1, intersections.size());
+ Assertions.assertTrue(containsPoint(intersections, 0.0015, 0.0015));
+ }
+
+ @Test
+ void testSegmentsShareCommonPoint() {
+ List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4));
+
+ Set intersectionsSameStart = BentleyOttmann.findIntersections(segmentsSameStart);
+ Assertions.assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0));
+ List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4));
+
+ Set intersectionsSameEnd = BentleyOttmann.findIntersections(segmentsSameEnd);
+ Assertions.assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0));
+ }
+
+ @Test
+ void testSegmentsAtAngles() {
+ // Segments at 45, 90, 135 degrees
+ List segments = List.of(newSegment(0, 2, 4, 2), // horizontal
+ newSegment(2, 0, 2, 4), // vertical
+ newSegment(0, 0, 4, 4), // 45 degrees
+ newSegment(0, 4, 4, 0) // 135 degrees
+ );
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0));
+ }
+
+ @Test
+ void testPerformanceWithManySegments() {
+ // Generate 100 random segments
+ Random random = new Random(42); // Fixed seed for reproducibility
+ List segments = new ArrayList<>();
+
+ for (int i = 0; i < 100; i++) {
+ double x1 = random.nextDouble() * 100;
+ double y1 = random.nextDouble() * 100;
+ double x2 = random.nextDouble() * 100;
+ double y2 = random.nextDouble() * 100;
+ segments.add(newSegment(x1, y1, x2, y2));
+ }
+
+ long startTime = System.currentTimeMillis();
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ long endTime = System.currentTimeMillis();
+
+ long duration = endTime - startTime;
+
+ // Should complete in reasonable time (< 1 second for 100 segments)
+ Assertions.assertTrue(duration < 1000, "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms");
+
+ // Just verify it returns a valid result
+ Assertions.assertNotNull(intersections);
+ System.out.println("Performance test: 100 segments processed in " + duration + "ms, found " + intersections.size() + " intersections");
+ }
+
+ @Test
+ void testIssueExample() {
+ // Example from the GitHub issue
+ List segments = List.of(newSegment(1, 1, 5, 5), // Segment A
+ newSegment(1, 5, 5, 1), // Segment B
+ newSegment(3, 0, 3, 6) // Segment C
+ );
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+
+ // Expected output: [(3, 3)]
+ Assertions.assertEquals(1, intersections.size(), "Should find exactly one intersection");
+ Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)");
+ }
+
+ @Test
+ void testEventTypeOrdering() {
+ // Multiple events at the same point with different types
+ List segments = List.of(newSegment(2, 2, 6, 2), // ends at (2,2)
+ newSegment(0, 2, 2, 2), // ends at (2,2)
+ newSegment(2, 2, 2, 6), // starts at (2,2)
+ newSegment(2, 0, 2, 2) // ends at (2,2)
+ );
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0));
+ }
+
+ @Test
+ void testCollinearOverlapWithInteriorPoint() {
+ // Test collinear segments where one segment's interior overlaps another
+ List segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4));
+ Set intersections = BentleyOttmann.findIntersections(segments);
+
+ // Should find at least one overlap point (where segments touch/overlap)
+ Assertions.assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments");
+ Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point");
+ }
+
+ @Test
+ void testCollinearTouchingAtBothEndpoints() {
+ // Test collinear segments that touch at both endpoints
+ // This triggers the "endpoint of both" logic (line 354-355)
+ List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+ Assertions.assertEquals(1, intersections.size());
+ Assertions.assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point");
+ }
+
+ @Test
+ void testCollinearOverlapPartialInterior() {
+ // Test case where segments overlap but one point is inside, one is endpoint
+ List segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7));
+
+ Set intersections = BentleyOttmann.findIntersections(segments);
+
+ // Should detect the overlap region
+ Assertions.assertFalse(intersections.isEmpty());
+ // The algorithm should return at least one of the boundary points
+ Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0));
+ }
+
+ private static BentleyOttmann.Segment newSegment(double x1, double y1, double x2, double y2) {
+ return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2));
+ }
+
+ private static boolean containsPoint(Set points, double x, double y) {
+ return points.stream().anyMatch(p -> Math.abs(p.x - x) < EPS && Math.abs(p.y - y) < EPS);
+ }
+}