Skip to content

Commit 70e6f5f

Browse files
committed
feat: add midpoint ellipse algorithm
1 parent 2a518e3 commit 70e6f5f

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* The MidpointEllipse class implements the Midpoint Ellipse Drawing Algorithm.
8+
* This algorithm efficiently computes the points on an ellipse by dividing it into two regions
9+
* and using decision parameters to determine the next point to plot.
10+
*/
11+
public final class MidpointEllipse {
12+
13+
private MidpointEllipse() {
14+
// Private constructor to prevent instantiation
15+
}
16+
17+
/**
18+
* Draws an ellipse using the Midpoint Ellipse Algorithm.
19+
*
20+
* @param centerX the x-coordinate of the center of the ellipse
21+
* @param centerY the y-coordinate of the center of the ellipse
22+
* @param a the length of the semi-major axis (horizontal radius)
23+
* @param b the length of the semi-minor axis (vertical radius)
24+
* @return a list of points (represented as int arrays) that form the ellipse
25+
*/
26+
public static List<int[]> drawEllipse(int centerX, int centerY, int a, int b) {
27+
List<int[]> points = new ArrayList<>();
28+
29+
// Handle degenerate cases with early returns
30+
if (a == 0 && b == 0) {
31+
points.add(new int[] {centerX, centerY}); // Only the center point
32+
return points;
33+
}
34+
35+
if (a == 0) {
36+
// Semi-major axis is zero, create a vertical line
37+
for (int y = centerY - b; y <= centerY + b; y++) {
38+
points.add(new int[] {centerX, y});
39+
}
40+
return points; // Early return
41+
}
42+
43+
if (b == 0) {
44+
// Semi-minor axis is zero, create a horizontal line
45+
for (int x = centerX - a; x <= centerX + a; x++) {
46+
points.add(new int[] {x, centerY});
47+
}
48+
return points; // Early return
49+
}
50+
51+
// Normal case: Non-degenerate ellipse
52+
computeEllipsePoints(points, centerX, centerY, a, b);
53+
54+
return points; // Return all calculated points of the ellipse
55+
}
56+
57+
/**
58+
* Computes points of a non-degenerate ellipse using the Midpoint Ellipse Algorithm.
59+
*
60+
* @param points the list to which points will be added
61+
* @param centerX the x-coordinate of the center of the ellipse
62+
* @param centerY the y-coordinate of the center of the ellipse
63+
* @param a the length of the semi-major axis (horizontal radius)
64+
* @param b the length of the semi-minor axis (vertical radius)
65+
*/
66+
private static void computeEllipsePoints(List<int[]> points, int centerX, int centerY, int a, int b) {
67+
int x = 0; // Initial x-coordinate
68+
int y = b; // Initial y-coordinate
69+
70+
// Region 1: Initial decision parameter
71+
double d1 = (b * b) - (a * a * b) + (0.25 * a * a); // Decision variable for region 1
72+
double dx = 2.0 * b * b * x; // Change in x
73+
double dy = 2.0 * a * a * y; // Change in y
74+
75+
// Region 1: When the slope is less than 1
76+
while (dx < dy) {
77+
addEllipsePoints(points, centerX, centerY, x, y);
78+
79+
// Update decision parameter and variables
80+
if (d1 < 0) {
81+
x++;
82+
dx += (2 * b * b); // Update x change
83+
d1 += dx + (b * b); // Update decision parameter
84+
} else {
85+
x++;
86+
y--;
87+
dx += (2 * b * b); // Update x change
88+
dy -= (2 * a * a); // Update y change
89+
d1 += dx - dy + (b * b); // Update decision parameter
90+
}
91+
}
92+
93+
// Region 2: Initial decision parameter for the second region
94+
double d2 = ((b * b) * ((x + 0.5) * (x + 0.5))) + ((a * a) * ((y - 1) * (y - 1))) - (a * a * b * b);
95+
96+
// Region 2: When the slope is greater than or equal to 1
97+
while (y >= 0) {
98+
addEllipsePoints(points, centerX, centerY, x, y);
99+
100+
// Update decision parameter and variables
101+
if (d2 > 0) {
102+
y--;
103+
dy -= (2 * a * a); // Update y change
104+
d2 += (a * a) - dy; // Update decision parameter
105+
} else {
106+
y--;
107+
x++;
108+
dx += (2 * b * b); // Update x change
109+
dy -= (2 * a * a); // Update y change
110+
d2 += dx - dy + (a * a); // Update decision parameter
111+
}
112+
}
113+
}
114+
115+
/**
116+
* Adds points for all four quadrants of the ellipse based on symmetry.
117+
*
118+
* @param points the list to which points will be added
119+
* @param centerX the x-coordinate of the center of the ellipse
120+
* @param centerY the y-coordinate of the center of the ellipse
121+
* @param x the x-coordinate relative to the center
122+
* @param y the y-coordinate relative to the center
123+
*/
124+
private static void addEllipsePoints(List<int[]> points, int centerX, int centerY, int x, int y) {
125+
points.add(new int[] {centerX + x, centerY + y});
126+
points.add(new int[] {centerX - x, centerY + y});
127+
points.add(new int[] {centerX + x, centerY - y});
128+
points.add(new int[] {centerX - x, centerY - y});
129+
}
130+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.thealgorithms.geometry;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
import java.util.List;
7+
import java.util.stream.Stream;
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.Arguments;
11+
import org.junit.jupiter.params.provider.MethodSource;
12+
13+
class MidpointEllipseTest {
14+
15+
/**
16+
* Provides test cases for the drawEllipse method.
17+
* Each argument contains: centerX, centerY, a, b, and expected points.
18+
*
19+
* @return a stream of arguments for parameterized testing
20+
*/
21+
static Stream<Arguments> ellipseTestProvider() {
22+
return Stream.of(
23+
Arguments.of(0, 0, 5, 3, new int[][] {{0, 3}, {0, 3}, {0, -3}, {0, -3}, {1, 3}, {-1, 3}, {1, -3}, {-1, -3}, {2, 3}, {-2, 3}, {2, -3}, {-2, -3}, {3, 2}, {-3, 2}, {3, -2}, {-3, -2}, {4, 2}, {-4, 2}, {4, -2}, {-4, -2}, {5, 1}, {-5, 1}, {5, -1}, {-5, -1}, {5, 0}, {-5, 0}, {5, 0}, {-5, 0}}),
24+
Arguments.of(0, 0, 0, 5,
25+
new int[][] {
26+
{0, -5}, {0, -4}, {0, -3}, {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5} // Only vertical line points and center
27+
}),
28+
Arguments.of(0, 0, 5, 0,
29+
new int[][] {
30+
{-5, 0}, {-4, 0}, {-3, 0}, {-2, 0}, {-1, 0}, {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0} // Only horizontal line points and center
31+
}),
32+
Arguments.of(0, 0, 0, 0,
33+
new int[][] {
34+
{0, 0} // Only center point
35+
}),
36+
Arguments.of(0, 0, 4, 4,
37+
new int[][] {
38+
{0, 4},
39+
{0, 4},
40+
{0, -4},
41+
{0, -4},
42+
{1, 4},
43+
{-1, 4},
44+
{1, -4},
45+
{-1, -4},
46+
{2, 3},
47+
{-2, 3},
48+
{2, -3},
49+
{-2, -3},
50+
{3, 3},
51+
{-3, 3},
52+
{3, -3},
53+
{-3, -3},
54+
{3, 2},
55+
{-3, 2},
56+
{3, -2},
57+
{-3, -2},
58+
{4, 1},
59+
{-4, 1},
60+
{4, -1},
61+
{-4, -1},
62+
{4, 0},
63+
{-4, 0},
64+
{4, 0},
65+
{-4, 0},
66+
}));
67+
}
68+
69+
/**
70+
* Tests the drawEllipse method with various parameters.
71+
*
72+
* @param centerX the x-coordinate of the center of the ellipse
73+
* @param centerY the y-coordinate of the center of the ellipse
74+
* @param a the length of the semi-major axis
75+
* @param b the length of the semi-minor axis
76+
* @param expectedPoints the expected points forming the ellipse
77+
*/
78+
@ParameterizedTest
79+
@MethodSource("ellipseTestProvider")
80+
@DisplayName("Test drawing ellipses with various parameters")
81+
void testDrawEllipse(int centerX, int centerY, int a, int b, int[][] expectedPoints) {
82+
List<int[]> points = MidpointEllipse.drawEllipse(centerX, centerY, a, b);
83+
84+
// Validate the number of points and the specific points
85+
assertEquals(expectedPoints.length, points.size(), "Number of points should match expected.");
86+
87+
for (int i = 0; i < expectedPoints.length; i++) {
88+
assertArrayEquals(expectedPoints[i], points.get(i), "Point mismatch at index " + i);
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)