Skip to content

Commit 70adf6f

Browse files
Add midpoint ellipse algorithm (#5870)
1 parent e4ef072 commit 70adf6f

File tree

2 files changed

+230
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)