Skip to content

Commit 934fcf8

Browse files
fix : correct division by zero bug in ConvexHull orientation function
1 parent 08d8c6b commit 934fcf8

File tree

3 files changed

+315
-109
lines changed

3 files changed

+315
-109
lines changed

Geometry/ConvexHullGraham.js

Lines changed: 77 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,84 +6,101 @@
66
* convex polygon that contains all the points of it.
77
*/
88

9+
/**
10+
* @function convexHull
11+
* @description Computes the convex hull of a set of points using the Monotone Chain algorithm (Andrew's variant of Graham Scan)
12+
* @param {Array<{x: number, y: number}>} points - Array of points with x and y coordinates
13+
* @return {Array<{x: number, y: number}>} Array of points forming the convex hull in counter-clockwise order
14+
* @throws {RangeError} If fewer than 3 points are provided
15+
* @see [Wikipedia](https://en.wikipedia.org/wiki/Graham_scan)
16+
* @see [Convex Hull](https://en.wikipedia.org/wiki/Convex_hull)
17+
* @example convexHull([{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 0}]) => [{x: 0, y: 0}, {x: 2, y: 0}, {x: 1, y: 1}]
18+
*/
19+
20+
/**
21+
* @function compare
22+
* @description Comparator function to sort points lexicographically (first by x, then by y)
23+
* @param {{x: number, y: number}} a - First point
24+
* @param {{x: number, y: number}} b - Second point
25+
* @return {number} -1 if a < b, 1 if a > b, 0 if equal
26+
* @private
27+
*/
28+
929
function compare(a, b) {
1030
// Compare Function to Sort the points, a and b are points to compare
1131
if (a.x < b.x) return -1
12-
if (a.x === b.x && a.y < b.y) return -1
13-
return 1
32+
if (a.x > b.x) return 1
33+
if (a.y < b.y) return -1
34+
if (a.y > b.y) return 1
35+
return 0
1436
}
37+
38+
/**
39+
* @function orientation
40+
* @description Determines the orientation of three points using cross product
41+
* @param {{x: number, y: number}} a - First point
42+
* @param {{x: number, y: number}} b - Second point
43+
* @param {{x: number, y: number}} c - Third point
44+
* @return {number} 0 if collinear, 1 if clockwise, -1 if counter-clockwise
45+
* @private
46+
*/
1547
function orientation(a, b, c) {
1648
// Check orientation of Line(a, b) and Line(b, c)
17-
const alpha = (b.y - a.y) / (b.x - a.x)
18-
const beta = (c.y - b.y) / (c.x - b.x)
49+
const crossProduct = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y)
1950

20-
// Clockwise
21-
if (alpha > beta) return 1
22-
// Anticlockwise
23-
else if (beta > alpha) return -1
24-
// Colinear
25-
return 0
51+
if (crossProduct === 0) return 0
52+
return crossProduct > 0 ? 1 : -1
2653
}
2754

55+
/**
56+
* @function convexHull
57+
* @description Main function implementing the Monotone Chain algorithm
58+
* @param {Array<{x: number, y: number}>} points - Input points
59+
* @return {Array<{x: number, y: number}>} Points forming the convex hull
60+
* @throws {RangeError} If fewer than 3 points provided
61+
*/
2862
function convexHull(points) {
2963
const pointsLen = points.length
30-
if (pointsLen <= 2) {
31-
throw new Error('Minimum of 3 points is required to form closed polygon!')
64+
if (pointsLen < 3) {
65+
throw new RangeError(
66+
'Minimum of 3 points is required to form closed polygon!'
67+
)
3268
}
3369

34-
points.sort(compare)
35-
const p1 = points[0]
36-
const p2 = points[pointsLen - 1]
37-
38-
// Divide Hull in two halves
39-
const upperPoints = []
40-
const lowerPoints = []
41-
42-
upperPoints.push(p1)
43-
lowerPoints.push(p1)
44-
45-
for (let i = 1; i < pointsLen; i++) {
46-
if (i === pointsLen - 1 || orientation(p1, points[i], p2) !== -1) {
47-
let upLen = upperPoints.length
70+
const sortedPoints = [...points].sort(compare)
4871

49-
while (
50-
upLen >= 2 &&
51-
orientation(
52-
upperPoints[upLen - 2],
53-
upperPoints[upLen - 1],
54-
points[i]
55-
) === -1
56-
) {
57-
upperPoints.pop()
58-
upLen = upperPoints.length
59-
}
60-
upperPoints.push(points[i])
72+
// Build lower hull
73+
const lower = []
74+
for (const point of sortedPoints) {
75+
while (
76+
lower.length >= 2 &&
77+
orientation(lower[lower.length - 2], lower[lower.length - 1], point) !==
78+
-1
79+
) {
80+
lower.pop()
6181
}
62-
if (i === pointsLen - 1 || orientation(p1, points[i], p2) !== 1) {
63-
let lowLen = lowerPoints.length
64-
while (
65-
lowLen >= 2 &&
66-
orientation(
67-
lowerPoints[lowLen - 2],
68-
lowerPoints[lowLen - 1],
69-
points[i]
70-
) === 1
71-
) {
72-
lowerPoints.pop()
73-
lowLen = lowerPoints.length
74-
}
75-
lowerPoints.push(points[i])
76-
}
77-
}
78-
const hull = []
79-
for (let i = 1; i < upperPoints.length - 1; i++) {
80-
hull.push(upperPoints[i])
82+
lower.push(point)
8183
}
82-
for (let i = lowerPoints.length - 1; i >= 0; i--) {
83-
hull.push(lowerPoints[i])
84+
85+
// Build upper hull
86+
const upper = []
87+
for (let i = sortedPoints.length - 1; i >= 0; i--) {
88+
const point = sortedPoints[i]
89+
while (
90+
upper.length >= 2 &&
91+
orientation(upper[upper.length - 2], upper[upper.length - 1], point) !==
92+
-1
93+
) {
94+
upper.pop()
95+
}
96+
upper.push(point)
8497
}
8598

86-
return hull
99+
// Remove duplicate last points and concatenate
100+
lower.pop()
101+
upper.pop()
102+
103+
return lower.concat(upper)
87104
}
88105

89106
export { convexHull }

0 commit comments

Comments
 (0)