Skip to content

Commit 29c806b

Browse files
committed
test(geometry): Achieve 100% test coverage for BentleyOttmann
1 parent adc3197 commit 29c806b

File tree

2 files changed

+103
-26
lines changed

2 files changed

+103
-26
lines changed

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

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
package com.thealgorithms.geometry;
22

33
import java.awt.geom.Point2D;
4-
import java.util.*;
4+
import java.util.Comparator;
5+
import java.util.HashSet;
6+
import java.util.Objects;
7+
import java.util.Set;
8+
import java.util.List;
9+
import java.util.PriorityQueue;
10+
import java.util.TreeSet;
11+
import java.util.Map;
12+
import java.util.HashMap;
13+
import java.util.ArrayList;
514

615
/**
716
* Implementation of the Bentley–Ottmann algorithm for finding all intersection
@@ -102,7 +111,9 @@ public int compareTo(Event other) {
102111

103112
@Override
104113
public boolean equals(Object o) {
105-
if (!(o instanceof Event e)) return false;
114+
if (!(o instanceof Event e)) {
115+
return false;
116+
}
106117
return pointsEqual(this.point, e.point);
107118
}
108119

@@ -116,10 +127,12 @@ public int hashCode() {
116127
* Comparator for segments in the status structure (sweep line).
117128
* Orders segments by their y-coordinate at the current sweep line position.
118129
*/
119-
private static class StatusComparator implements Comparator<Segment> {
130+
private final static class StatusComparator implements Comparator<Segment> {
120131
@Override
121132
public int compare(Segment s1, Segment s2) {
122-
if (s1.id == s2.id) return 0;
133+
if (s1.id == s2.id) {
134+
return 0;
135+
}
123136

124137
double y1 = s1.getY(currentSweepX);
125138
double y2 = s2.getY(currentSweepX);
@@ -271,7 +284,9 @@ private static void handleEvent(Event event, TreeSet<Segment> status, PriorityQu
271284
}
272285

273286
private static Segment getNeighbor(TreeSet<Segment> status, Set<Segment> removed, boolean lower) {
274-
if (removed.isEmpty()) return null;
287+
if (removed.isEmpty()) {
288+
return null;
289+
}
275290
Segment ref = removed.iterator().next();
276291
return lower ? status.lower(ref) : status.higher(ref);
277292
}
@@ -324,10 +339,14 @@ private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentP
324339
}
325340

326341
private static Point2D.Double getIntersection(Segment s1, Segment s2) {
327-
double x1 = s1.p1.x, y1 = s1.p1.y;
328-
double x2 = s1.p2.x, y2 = s1.p2.y;
329-
double x3 = s2.p1.x, y3 = s2.p1.y;
330-
double x4 = s2.p2.x, y4 = s2.p2.y;
342+
double x1 = s1.p1.x;
343+
double y1 = s1.p1.y;
344+
double x2 = s1.p2.x;
345+
double y2 = s1.p2.y;
346+
double x3 = s2.p1.x;
347+
double y3 = s2.p1.y;
348+
double x4 = s2.p2.x;
349+
double y4 = s2.p2.y;
331350

332351
double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
333352

@@ -338,10 +357,18 @@ private static Point2D.Double getIntersection(Segment s1, Segment s2) {
338357
// Return any overlapping point
339358
List<Point2D.Double> overlapPoints = new ArrayList<>();
340359

341-
if (onSegment(s1, s2.p1)) overlapPoints.add(s2.p1);
342-
if (onSegment(s1, s2.p2)) overlapPoints.add(s2.p2);
343-
if (onSegment(s2, s1.p1)) overlapPoints.add(s1.p1);
344-
if (onSegment(s2, s1.p2)) overlapPoints.add(s1.p2);
360+
if (onSegment(s1, s2.p1)) {
361+
overlapPoints.add(s2.p1);
362+
}
363+
if (onSegment(s1, s2.p2)) {
364+
overlapPoints.add(s2.p2);
365+
}
366+
if (onSegment(s2, s1.p1)) {
367+
overlapPoints.add(s1.p1);
368+
}
369+
if (onSegment(s2, s1.p2)) {
370+
overlapPoints.add(s1.p2);
371+
}
345372

346373
// Remove duplicates and return the first point
347374
if (!overlapPoints.isEmpty()) {

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

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

3-
import static org.junit.jupiter.api.Assertions.*;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertFalse;
7+
import static org.junit.jupiter.api.Assertions.assertNotNull;
48

59
import java.awt.geom.Point2D;
610
import java.util.ArrayList;
@@ -49,10 +53,7 @@ void testNoIntersection() {
4953

5054
@Test
5155
void testCoincidentSegments() {
52-
List<Object> segments = List.of(
53-
newSegment(1, 1, 5, 5),
54-
newSegment(1, 1, 5, 5)
55-
);
56+
List<Object> segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5));
5657

5758
Set<Point2D.Double> intersections = BentleyOttmann.findIntersections(cast(segments));
5859

@@ -162,15 +163,15 @@ void testGridPattern() {
162163
// Verify all grid points are present
163164
for (int x = 0; x <= 2; x++) {
164165
for (int y = 0; y <= 2; y++) {
165-
assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); }
166+
assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y));
167+
}
166168
}
167169
}
168170

169171
@Test
170172
void testTriangleIntersections() {
171173
// Three segments forming a triangle
172-
List<Object> segments = List.of(
173-
newSegment(0, 0, 4, 0), // base
174+
List<Object> segments = List.of(newSegment(0, 0, 4, 0), // base
174175
newSegment(0, 0, 2, 3), // left side
175176
newSegment(4, 0, 2, 3) // right side
176177
);
@@ -219,8 +220,7 @@ void testSegmentsShareCommonPoint() {
219220
@Test
220221
void testSegmentsAtAngles() {
221222
// Segments at 45, 90, 135 degrees
222-
List<Object> segments = List.of(
223-
newSegment(0, 2, 4, 2), // horizontal
223+
List<Object> segments = List.of(newSegment(0, 2, 4, 2), // horizontal
224224
newSegment(2, 0, 2, 4), // vertical
225225
newSegment(0, 0, 4, 4), // 45 degrees
226226
newSegment(0, 4, 4, 0) // 135 degrees
@@ -261,8 +261,7 @@ void testPerformanceWithManySegments() {
261261
@Test
262262
void testIssueExample() {
263263
// Example from the GitHub issue
264-
List<Object> segments = List.of(
265-
newSegment(1, 1, 5, 5), // Segment A
264+
List<Object> segments = List.of(newSegment(1, 1, 5, 5), // Segment A
266265
newSegment(1, 5, 5, 1), // Segment B
267266
newSegment(3, 0, 3, 6) // Segment C
268267
);
@@ -274,13 +273,64 @@ void testIssueExample() {
274273
assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)");
275274
}
276275

276+
@Test
277+
void testEventTypeOrdering() {
278+
// Multiple events at the same point with different types
279+
List<Object> segments = List.of(
280+
newSegment(2, 2, 6, 2), // ends at (2,2)
281+
newSegment(0, 2, 2, 2), // ends at (2,2)
282+
newSegment(2, 2, 2, 6), // starts at (2,2)
283+
newSegment(2, 0, 2, 2) // ends at (2,2)
284+
);
285+
286+
Set<Point2D.Double> intersections = BentleyOttmann.findIntersections(cast(segments));
287+
assertTrue(containsPoint(intersections, 2.0, 2.0));
288+
}
289+
290+
@Test
291+
void testCollinearOverlapWithInteriorPoint() {
292+
// Test collinear segments where one segment's interior overlaps another
293+
List<Object> segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4));
294+
Set<Point2D.Double> intersections = BentleyOttmann.findIntersections(cast(segments));
295+
296+
// Should find at least one overlap point (where segments touch/overlap)
297+
assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments");
298+
assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point");
299+
}
300+
301+
@Test
302+
void testCollinearTouchingAtBothEndpoints() {
303+
// Test collinear segments that touch at both endpoints
304+
// This triggers the "endpoint of both" logic (line 354-355)
305+
List<Object> segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8));
306+
307+
Set<Point2D.Double> intersections = BentleyOttmann.findIntersections(cast(segments));
308+
assertEquals(1, intersections.size());
309+
assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point");
310+
}
311+
312+
@Test
313+
void testCollinearOverlapPartialInterior() {
314+
// Test case where segments overlap but one point is inside, one is endpoint
315+
List<Object> segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7));
316+
317+
Set<Point2D.Double> intersections = BentleyOttmann.findIntersections(cast(segments));
318+
319+
// Should detect the overlap region
320+
assertFalse(intersections.isEmpty());
321+
// The algorithm should return at least one of the boundary points
322+
assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0));
323+
}
324+
277325
private static Object newSegment(double x1, double y1, double x2, double y2) {
278326
return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2));
279327
}
280328

281329
private static List<BentleyOttmann.Segment> cast(List<Object> objs) {
282330
List<BentleyOttmann.Segment> result = new ArrayList<>();
283-
for (Object o : objs) result.add((BentleyOttmann.Segment) o);
331+
for (Object o : objs) {
332+
result.add((BentleyOttmann.Segment) o);
333+
}
284334
return result;
285335
}
286336

0 commit comments

Comments
 (0)