11package com .thealgorithms .geometry ;
22
33import static org .junit .jupiter .api .Assertions .assertEquals ;
4+ import static org .junit .jupiter .api .Assertions .assertTrue ;
45
6+ import java .util .ArrayList ;
57import java .util .Arrays ;
68import java .util .List ;
79import org .junit .jupiter .api .Test ;
@@ -10,31 +12,127 @@ public class ConvexHullTest {
1012
1113 @ Test
1214 void testConvexHullBruteForce () {
15+ // Test 1: Triangle with intermediate point
1316 List <Point > points = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 1 ));
1417 List <Point > expected = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 1 ));
1518 assertEquals (expected , ConvexHull .convexHullBruteForce (points ));
1619
20+ // Test 2: Collinear points
1721 points = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 0 ));
1822 expected = Arrays .asList (new Point (0 , 0 ), new Point (10 , 0 ));
1923 assertEquals (expected , ConvexHull .convexHullBruteForce (points ));
2024
25+ // Test 3: Complex polygon
2126 points = Arrays .asList (new Point (0 , 3 ), new Point (2 , 2 ), new Point (1 , 1 ), new Point (2 , 1 ), new Point (3 , 0 ), new Point (0 , 0 ), new Point (3 , 3 ), new Point (2 , -1 ), new Point (2 , -4 ), new Point (1 , -3 ));
2227 expected = Arrays .asList (new Point (2 , -4 ), new Point (1 , -3 ), new Point (0 , 0 ), new Point (3 , 0 ), new Point (0 , 3 ), new Point (3 , 3 ));
2328 assertEquals (expected , ConvexHull .convexHullBruteForce (points ));
2429 }
2530
2631 @ Test
2732 void testConvexHullRecursive () {
33+ // Test 1: Triangle - CCW order starting from bottom-left
34+ // The algorithm includes (1,0) as it's detected as an extreme point
2835 List <Point > points = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 1 ));
36+ List <Point > result = ConvexHull .convexHullRecursive (points );
2937 List <Point > expected = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 1 ));
30- assertEquals (expected , ConvexHull .convexHullRecursive (points ));
38+ assertEquals (expected , result );
39+ assertTrue (isCounterClockwise (result ), "Points should be in counter-clockwise order" );
3140
41+ // Test 2: Collinear points
3242 points = Arrays .asList (new Point (0 , 0 ), new Point (1 , 0 ), new Point (10 , 0 ));
43+ result = ConvexHull .convexHullRecursive (points );
3344 expected = Arrays .asList (new Point (0 , 0 ), new Point (10 , 0 ));
34- assertEquals (expected , ConvexHull . convexHullRecursive ( points ) );
45+ assertEquals (expected , result );
3546
47+ // Test 3: Complex polygon
48+ // Convex hull vertices in CCW order from bottom-most point (2,-4):
49+ // (2,-4) -> (3,0) -> (3,3) -> (0,3) -> (0,0) -> (1,-3) -> back to (2,-4)
3650 points = Arrays .asList (new Point (0 , 3 ), new Point (2 , 2 ), new Point (1 , 1 ), new Point (2 , 1 ), new Point (3 , 0 ), new Point (0 , 0 ), new Point (3 , 3 ), new Point (2 , -1 ), new Point (2 , -4 ), new Point (1 , -3 ));
37- expected = Arrays .asList (new Point (2 , -4 ), new Point (1 , -3 ), new Point (0 , 0 ), new Point (3 , 0 ), new Point (0 , 3 ), new Point (3 , 3 ));
38- assertEquals (expected , ConvexHull .convexHullRecursive (points ));
51+ result = ConvexHull .convexHullRecursive (points );
52+ expected = Arrays .asList (new Point (2 , -4 ), new Point (3 , 0 ), new Point (3 , 3 ), new Point (0 , 3 ), new Point (0 , 0 ), new Point (1 , -3 ));
53+ assertEquals (expected , result );
54+ assertTrue (isCounterClockwise (result ), "Points should be in counter-clockwise order" );
55+ }
56+
57+ @ Test
58+ void testConvexHullRecursiveAdditionalCases () {
59+ // Test 4: Square (all corners on hull)
60+ List <Point > points = Arrays .asList (new Point (0 , 0 ), new Point (2 , 0 ), new Point (2 , 2 ), new Point (0 , 2 ));
61+ List <Point > result = ConvexHull .convexHullRecursive (points );
62+ List <Point > expected = Arrays .asList (new Point (0 , 0 ), new Point (2 , 0 ), new Point (2 , 2 ), new Point (0 , 2 ));
63+ assertEquals (expected , result );
64+ assertTrue (isCounterClockwise (result ), "Square points should be in CCW order" );
65+
66+ // Test 5: Pentagon with interior point
67+ points = Arrays .asList (new Point (0 , 0 ), new Point (4 , 0 ), new Point (5 , 3 ), new Point (2 , 5 ), new Point (-1 , 3 ), new Point (2 , 2 ) // (2,2) is interior
68+ );
69+ result = ConvexHull .convexHullRecursive (points );
70+ // CCW from (0,0): (0,0) -> (4,0) -> (5,3) -> (2,5) -> (-1,3)
71+ expected = Arrays .asList (new Point (0 , 0 ), new Point (4 , 0 ), new Point (5 , 3 ), new Point (2 , 5 ), new Point (-1 , 3 ));
72+ assertEquals (expected , result );
73+ assertTrue (isCounterClockwise (result ), "Pentagon points should be in CCW order" );
74+
75+ // Test 6: Simple triangle (clearly convex)
76+ points = Arrays .asList (new Point (0 , 0 ), new Point (4 , 0 ), new Point (2 , 3 ));
77+ result = ConvexHull .convexHullRecursive (points );
78+ expected = Arrays .asList (new Point (0 , 0 ), new Point (4 , 0 ), new Point (2 , 3 ));
79+ assertEquals (expected , result );
80+ assertTrue (isCounterClockwise (result ), "Triangle points should be in CCW order" );
81+ }
82+
83+ /**
84+ * Helper method to verify if points are in counter-clockwise order.
85+ * Uses the signed area method: positive area means CCW.
86+ */
87+ private boolean isCounterClockwise (List <Point > points ) {
88+ if (points .size () < 3 ) {
89+ return true ; // Less than 3 points, trivially true
90+ }
91+
92+ long signedArea = 0 ;
93+ for (int i = 0 ; i < points .size (); i ++) {
94+ Point p1 = points .get (i );
95+ Point p2 = points .get ((i + 1 ) % points .size ());
96+ signedArea += (long ) p1 .x () * p2 .y () - (long ) p2 .x () * p1 .y ();
97+ }
98+
99+ return signedArea > 0 ; // Positive signed area means counter-clockwise
100+ }
101+
102+ @ Test
103+ void testRecursiveHullForCoverage () {
104+ // 1. Test the base cases of the convexHullRecursive method (covering scenarios with < 3 input points).
105+
106+ // Test Case: 0 points
107+ List <Point > pointsEmpty = new ArrayList <>();
108+ List <Point > resultEmpty = ConvexHull .convexHullRecursive (pointsEmpty );
109+ assertTrue (resultEmpty .isEmpty (), "Should return an empty list for an empty input list" );
110+
111+ // Test Case: 1 point
112+ List <Point > pointsOne = List .of (new Point (5 , 5 ));
113+ // Pass a new ArrayList because the original method modifies the input list.
114+ List <Point > resultOne = ConvexHull .convexHullRecursive (new ArrayList <>(pointsOne ));
115+ List <Point > expectedOne = List .of (new Point (5 , 5 ));
116+ assertEquals (expectedOne , resultOne , "Should return the single point for a single-point input" );
117+
118+ // Test Case: 2 points
119+ List <Point > pointsTwo = Arrays .asList (new Point (10 , 1 ), new Point (0 , 0 ));
120+ List <Point > resultTwo = ConvexHull .convexHullRecursive (new ArrayList <>(pointsTwo ));
121+ List <Point > expectedTwo = Arrays .asList (new Point (0 , 0 ), new Point (10 , 1 )); // Should return the two points, sorted.
122+ assertEquals (expectedTwo , resultTwo , "Should return the two sorted points for a two-point input" );
123+
124+ // 2. Test the logic for handling collinear points in the sortCounterClockwise method.
125+
126+ // Construct a scenario where multiple collinear points lie on an edge of the convex hull.
127+ // The expected convex hull vertices are (0,0), (10,0), and (5,5).
128+ // When (0,0) is used as the pivot for polar angle sorting, (5,0) and (10,0) are collinear.
129+ // This will trigger the crossProduct == 0 branch in the sortCounterClockwise method.
130+ List <Point > pointsWithCollinearOnHull = Arrays .asList (new Point (0 , 0 ), new Point (5 , 0 ), new Point (10 , 0 ), new Point (5 , 5 ), new Point (2 , 2 ));
131+
132+ List <Point > resultCollinear = ConvexHull .convexHullRecursive (new ArrayList <>(pointsWithCollinearOnHull ));
133+ List <Point > expectedCollinear = Arrays .asList (new Point (0 , 0 ), new Point (10 , 0 ), new Point (5 , 5 ));
134+
135+ assertEquals (expectedCollinear , resultCollinear , "Should correctly handle collinear points on the hull edge" );
136+ assertTrue (isCounterClockwise (resultCollinear ), "The result of the collinear test should be in counter-clockwise order" );
39137 }
40138}
0 commit comments