Skip to content

Commit c45b2b8

Browse files
authored
Add line clipping algorithms (#5580)
1 parent 357fc6a commit c45b2b8

File tree

6 files changed

+458
-0
lines changed

6 files changed

+458
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.thealgorithms.lineclipping;
2+
3+
import com.thealgorithms.lineclipping.utils.Line;
4+
import com.thealgorithms.lineclipping.utils.Point;
5+
6+
/**
7+
* @author shikarisohan
8+
* @since 10/4/24
9+
* Cohen-Sutherland Line Clipping Algorithm
10+
*
11+
* This algorithm is used to clip a line segment to a rectangular window.
12+
* It assigns a region code to each endpoint of the line segment, and
13+
* then efficiently determines whether the line segment is fully inside,
14+
* fully outside, or partially inside the window.
15+
*
16+
* Reference:
17+
* https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
18+
*
19+
* Clipping window boundaries are defined as (xMin, yMin) and (xMax, yMax).
20+
* The algorithm computes the clipped line segment if it's partially or
21+
* fully inside the clipping window.
22+
*/
23+
public class CohenSutherland {
24+
25+
// Region codes for the 9 regions
26+
private static final int INSIDE = 0; // 0000
27+
private static final int LEFT = 1; // 0001
28+
private static final int RIGHT = 2; // 0010
29+
private static final int BOTTOM = 4; // 0100
30+
private static final int TOP = 8; // 1000
31+
32+
// Define the clipping window
33+
double xMin;
34+
double yMin;
35+
double xMax;
36+
double yMax;
37+
38+
public CohenSutherland(double xMin, double yMin, double xMax, double yMax) {
39+
this.xMin = xMin;
40+
this.yMin = yMin;
41+
this.xMax = xMax;
42+
this.yMax = yMax;
43+
}
44+
45+
// Compute the region code for a point (x, y)
46+
private int computeCode(double x, double y) {
47+
int code = INSIDE;
48+
49+
if (x < xMin) // to the left of rectangle
50+
{
51+
code |= LEFT;
52+
} else if (x > xMax) // to the right of rectangle
53+
{
54+
code |= RIGHT;
55+
}
56+
if (y < yMin) // below the rectangle
57+
{
58+
code |= BOTTOM;
59+
} else if (y > yMax) // above the rectangle
60+
{
61+
code |= TOP;
62+
}
63+
64+
return code;
65+
}
66+
67+
// Cohen-Sutherland algorithm to return the clipped line
68+
public Line cohenSutherlandClip(Line line) {
69+
double x1 = line.start.x;
70+
double y1 = line.start.y;
71+
double x2 = line.end.x;
72+
double y2 = line.end.y;
73+
74+
int code1 = computeCode(x1, y1);
75+
int code2 = computeCode(x2, y2);
76+
boolean accept = false;
77+
78+
while (true) {
79+
if ((code1 == 0) && (code2 == 0)) {
80+
// Both points are inside the rectangle
81+
accept = true;
82+
break;
83+
} else if ((code1 & code2) != 0) {
84+
// Both points are outside the rectangle in the same region
85+
break;
86+
} else {
87+
// Some segment of the line is inside the rectangle
88+
double x = 0;
89+
double y = 0;
90+
91+
// Pick an endpoint that is outside the rectangle
92+
int codeOut = (code1 != 0) ? code1 : code2;
93+
94+
// Find the intersection point using the line equation
95+
if ((codeOut & TOP) != 0) {
96+
// Point is above the rectangle
97+
x = x1 + (x2 - x1) * (yMax - y1) / (y2 - y1);
98+
y = yMax;
99+
} else if ((codeOut & BOTTOM) != 0) {
100+
// Point is below the rectangle
101+
x = x1 + (x2 - x1) * (yMin - y1) / (y2 - y1);
102+
y = yMin;
103+
} else if ((codeOut & RIGHT) != 0) {
104+
// Point is to the right of the rectangle
105+
y = y1 + (y2 - y1) * (xMax - x1) / (x2 - x1);
106+
x = xMax;
107+
} else if ((codeOut & LEFT) != 0) {
108+
// Point is to the left of the rectangle
109+
y = y1 + (y2 - y1) * (xMin - x1) / (x2 - x1);
110+
x = xMin;
111+
}
112+
113+
// Replace the point outside the rectangle with the intersection point
114+
if (codeOut == code1) {
115+
x1 = x;
116+
y1 = y;
117+
code1 = computeCode(x1, y1);
118+
} else {
119+
x2 = x;
120+
y2 = y;
121+
code2 = computeCode(x2, y2);
122+
}
123+
}
124+
}
125+
126+
if (accept) {
127+
return new Line(new Point(x1, y1), new Point(x2, y2));
128+
} else {
129+
130+
return null; // The line is fully rejected
131+
}
132+
}
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.thealgorithms.lineclipping;
2+
3+
import com.thealgorithms.lineclipping.utils.Line;
4+
import com.thealgorithms.lineclipping.utils.Point;
5+
6+
/**
7+
* @author shikarisohan
8+
* @since 10/5/24
9+
*
10+
* * The Liang-Barsky line clipping algorithm is an efficient algorithm for
11+
* * line clipping against a rectangular window. It is based on the parametric
12+
* * equation of a line and checks the intersections of the line with the
13+
* * window boundaries. This algorithm calculates the intersection points,
14+
* * if any, and returns the clipped line that lies inside the window.
15+
* *
16+
* * Reference:
17+
* * https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm
18+
*
19+
* Clipping window boundaries are defined as (xMin, yMin) and (xMax, yMax).
20+
* The algorithm computes the clipped line segment if it's partially or
21+
* fully inside the clipping window.
22+
*/
23+
public class LiangBarsky {
24+
25+
// Define the clipping window
26+
double xMin;
27+
double xMax;
28+
double yMin;
29+
double yMax;
30+
31+
public LiangBarsky(double xMin, double yMin, double xMax, double yMax) {
32+
this.xMin = xMin;
33+
this.yMin = yMin;
34+
this.xMax = xMax;
35+
this.yMax = yMax;
36+
}
37+
38+
// Liang-Barsky algorithm to return the clipped line
39+
public Line liangBarskyClip(Line line) {
40+
double dx = line.end.x - line.start.x;
41+
double dy = line.end.y - line.start.y;
42+
43+
double[] p = {-dx, dx, -dy, dy};
44+
double[] q = {line.start.x - xMin, xMax - line.start.x, line.start.y - yMin, yMax - line.start.y};
45+
46+
double[] resultT = clipLine(p, q);
47+
48+
if (resultT == null) {
49+
return null; // Line is outside the clipping window
50+
}
51+
52+
return calculateClippedLine(line, resultT[0], resultT[1], dx, dy);
53+
}
54+
55+
// clip the line by adjusting t0 and t1 for each edge
56+
private double[] clipLine(double[] p, double[] q) {
57+
double t0 = 0.0;
58+
double t1 = 1.0;
59+
60+
for (int i = 0; i < 4; i++) {
61+
double t = q[i] / p[i];
62+
if (p[i] == 0 && q[i] < 0) {
63+
return null; // Line is outside the boundary
64+
} else if (p[i] < 0) {
65+
if (t > t1) {
66+
return null;
67+
} // Line is outside
68+
if (t > t0) {
69+
t0 = t;
70+
} // Update t0
71+
} else if (p[i] > 0) {
72+
if (t < t0) {
73+
return null;
74+
} // Line is outside
75+
if (t < t1) {
76+
t1 = t;
77+
} // Update t1
78+
}
79+
}
80+
81+
return new double[] {t0, t1}; // Return valid t0 and t1
82+
}
83+
84+
// calculate the clipped line based on t0 and t1
85+
private Line calculateClippedLine(Line line, double t0, double t1, double dx, double dy) {
86+
double clippedX1 = line.start.x + t0 * dx;
87+
double clippedY1 = line.start.y + t0 * dy;
88+
double clippedX2 = line.start.x + t1 * dx;
89+
double clippedY2 = line.start.y + t1 * dy;
90+
91+
return new Line(new Point(clippedX1, clippedY1), new Point(clippedX2, clippedY2));
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.thealgorithms.lineclipping.utils;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* @author moksedursohan
7+
* @since 10/4/24
8+
*/
9+
public class Line {
10+
11+
public Point start;
12+
public Point end;
13+
14+
public Line() {
15+
}
16+
17+
public Line(Point start, Point end) {
18+
this.start = start;
19+
this.end = end;
20+
}
21+
22+
@Override
23+
public boolean equals(Object o) {
24+
if (this == o) {
25+
return true;
26+
}
27+
if (!(o instanceof Line line)) {
28+
return false;
29+
}
30+
31+
return Objects.equals(start, line.start) && Objects.equals(end, line.end);
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return Objects.hash(start, end);
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return "Line from " + start + " to " + end;
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.thealgorithms.lineclipping.utils;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* @author moksedursohan
7+
* @since 10/4/24
8+
*/
9+
public class Point {
10+
11+
public double x;
12+
public double y;
13+
14+
public Point() {
15+
}
16+
17+
public Point(double x, double y) {
18+
this.x = x;
19+
this.y = y;
20+
}
21+
22+
@Override
23+
public boolean equals(Object o) {
24+
if (this == o) {
25+
return true;
26+
}
27+
if (!(o instanceof Point point)) {
28+
return false;
29+
}
30+
31+
return Double.compare(x, point.x) == 0 && Double.compare(y, point.y) == 0;
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return Objects.hash(x, y);
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return "(" + x + ", " + y + ")";
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.thealgorithms.lineclipping;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
7+
import com.thealgorithms.lineclipping.utils.Line;
8+
import com.thealgorithms.lineclipping.utils.Point;
9+
import org.junit.jupiter.api.Test;
10+
11+
/**
12+
* @author shikarisohan
13+
* @since 10/4/24
14+
*/
15+
class CohenSutherlandTest {
16+
17+
// Define the clipping window (1.0, 1.0) to (10.0, 10.0)
18+
CohenSutherland cs = new CohenSutherland(1.0, 1.0, 10.0, 10.0);
19+
20+
@Test
21+
void testLineCompletelyInside() {
22+
// Line fully inside the clipping window
23+
Line line = new Line(new Point(2.0, 2.0), new Point(8.0, 8.0));
24+
Line clippedLine = cs.cohenSutherlandClip(line);
25+
26+
assertNotNull(clippedLine, "Line should not be null.");
27+
assertEquals(line, clippedLine, "Line inside the window should remain unchanged.");
28+
}
29+
30+
@Test
31+
void testLineCompletelyOutside() {
32+
// Line completely outside and above the clipping window
33+
Line line = new Line(new Point(11.0, 12.0), new Point(15.0, 18.0));
34+
Line clippedLine = cs.cohenSutherlandClip(line);
35+
36+
assertNull(clippedLine, "Line should be null because it's completely outside.");
37+
}
38+
39+
@Test
40+
void testLinePartiallyInside() {
41+
// Line partially inside the clipping window
42+
Line line = new Line(new Point(5.0, 5.0), new Point(12.0, 12.0));
43+
Line expectedClippedLine = new Line(new Point(5.0, 5.0), new Point(10.0, 10.0)); // Clipped at (10, 10)
44+
Line clippedLine = cs.cohenSutherlandClip(line);
45+
46+
assertNotNull(clippedLine, "Line should not be null.");
47+
assertEquals(expectedClippedLine, clippedLine, "Line should be clipped correctly.");
48+
}
49+
50+
@Test
51+
void testLineOnBoundary() {
52+
// Line exactly on the boundary of the clipping window
53+
Line line = new Line(new Point(1.0, 5.0), new Point(10.0, 5.0));
54+
Line clippedLine = cs.cohenSutherlandClip(line);
55+
56+
assertNotNull(clippedLine, "Line should not be null.");
57+
assertEquals(line, clippedLine, "Line on the boundary should remain unchanged.");
58+
}
59+
60+
@Test
61+
void testDiagonalLineThroughClippingWindow() {
62+
// Diagonal line crossing from outside to outside through the window
63+
Line line = new Line(new Point(0.0, 0.0), new Point(12.0, 12.0));
64+
Line expectedClippedLine = new Line(new Point(1.0, 1.0), new Point(10.0, 10.0)); // Clipped at both boundaries
65+
Line clippedLine = cs.cohenSutherlandClip(line);
66+
67+
assertNotNull(clippedLine, "Line should not be null.");
68+
assertEquals(expectedClippedLine, clippedLine, "Diagonal line should be clipped correctly.");
69+
}
70+
71+
@Test
72+
void testVerticalLineClipping() {
73+
// Vertical line crossing the top and bottom of the clipping window
74+
Line line = new Line(new Point(5.0, 0.0), new Point(5.0, 12.0));
75+
Line expectedClippedLine = new Line(new Point(5.0, 1.0), new Point(5.0, 10.0)); // Clipped at yMin and yMax
76+
Line clippedLine = cs.cohenSutherlandClip(line);
77+
78+
assertNotNull(clippedLine, "Line should not be null.");
79+
assertEquals(expectedClippedLine, clippedLine, "Vertical line should be clipped correctly.");
80+
}
81+
}

0 commit comments

Comments
 (0)