1use crate::{LineSegment, Point, Window};
2use bitflags::bitflags;
3
4pub fn clip_line(mut line: LineSegment, window: Window) -> Option<LineSegment> {
53 let mut region_1 = Region::from_point(line.p1, window);
54 let mut region_2 = Region::from_point(line.p2, window);
55
56 while region_1 != Region::INSIDE || region_2 != Region::INSIDE {
57 if region_1.intersects(region_2) {
58 return None;
60 }
61
62 if region_1 != Region::INSIDE {
63 line.p1 = calculate_intersection(line.p1, line.p2, region_1, window);
64 region_1 = Region::from_point(line.p1, window);
65 } else {
66 line.p2 = calculate_intersection(line.p2, line.p1, region_2, window);
67 region_2 = Region::from_point(line.p2, window);
68 };
69 }
70
71 Some(line)
72}
73
74fn calculate_intersection(p1: Point, p2: Point, region: Region, window: Window) -> Point {
75 if region.contains(Region::LEFT) {
76 let y = p1.y + (p2.y - p1.y) * (window.x_min - p1.x) / (p2.x - p1.x);
77 Point { x: window.x_min, y }
78 } else if region.contains(Region::RIGHT) {
79 let y = p1.y + (p2.y - p1.y) * (window.x_max - p1.x) / (p2.x - p1.x);
80 Point { x: window.x_max, y }
81 } else if region.contains(Region::BOTTOM) {
82 let x = p1.x + (p2.x - p1.x) * (window.y_min - p1.y) / (p2.y - p1.y);
83 Point { x, y: window.y_min }
84 } else if region.contains(Region::TOP) {
85 let x = p1.x + (p2.x - p1.x) * (window.y_max - p1.y) / (p2.y - p1.y);
86 Point { x, y: window.y_max }
87 } else {
88 p1
89 }
90}
91
92bitflags! {
93 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
95 pub struct Region: u8 {
96 const INSIDE = 0b0000;
97 const LEFT = 0b0001;
98 const RIGHT = 0b0010;
99 const BOTTOM = 0b0100;
100 const TOP = 0b1000;
101 }
102}
103
104impl Region {
105 pub fn from_point(point: Point, window: Window) -> Self {
107 let mut region = Region::INSIDE;
108 if point.x < window.x_min {
109 region |= Region::LEFT;
110 } else if point.x > window.x_max {
111 region |= Region::RIGHT;
112 }
113 if point.y < window.y_min {
114 region |= Region::BOTTOM;
115 } else if point.y > window.y_max {
116 region |= Region::TOP;
117 }
118 region
119 }
120}
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_line_completely_inside() {
127 let line = clip_line(
128 LineSegment {
129 p1: Point { x: 2.0, y: 2.0 },
130 p2: Point { x: 8.0, y: 8.0 },
131 },
132 Window {
133 x_min: 1.0,
134 x_max: 9.0,
135 y_min: 1.0,
136 y_max: 9.0,
137 },
138 );
139 assert_eq!(
140 line,
141 Some(LineSegment {
142 p1: Point { x: 2.0, y: 2.0 },
143 p2: Point { x: 8.0, y: 8.0 }
144 })
145 );
146 }
147
148 #[test]
149 fn test_line_completely_outside() {
150 let line = clip_line(
151 LineSegment {
152 p1: Point { x: -1.0, y: -1.0 },
153 p2: Point { x: -5.0, y: -5.0 },
154 },
155 Window {
156 x_min: 1.0,
157 x_max: 9.0,
158 y_min: 1.0,
159 y_max: 9.0,
160 },
161 );
162 assert_eq!(line, None);
163 }
164
165 #[test]
166 fn test_line_partially_inside() {
167 let line = clip_line(
168 LineSegment {
169 p1: Point { x: 0.0, y: 0.0 },
170 p2: Point { x: 10.0, y: 10.0 },
171 },
172 Window {
173 x_min: 1.0,
174 x_max: 9.0,
175 y_min: 1.0,
176 y_max: 9.0,
177 },
178 );
179 assert_eq!(
180 line,
181 Some(LineSegment {
182 p1: Point { x: 1.0, y: 1.0 },
183 p2: Point { x: 9.0, y: 9.0 }
184 })
185 );
186 }
187
188 #[test]
189 fn test_line_vertical() {
190 let line = clip_line(
191 LineSegment {
192 p1: Point { x: 5.0, y: 0.0 },
193 p2: Point { x: 5.0, y: 10.0 },
194 },
195 Window {
196 x_min: 1.0,
197 x_max: 9.0,
198 y_min: 1.0,
199 y_max: 9.0,
200 },
201 );
202 assert_eq!(
203 line,
204 Some(LineSegment {
205 p1: Point { x: 5.0, y: 1.0 },
206 p2: Point { x: 5.0, y: 9.0 }
207 })
208 );
209 }
210
211 #[test]
212 fn test_line_horizontal() {
213 let line = clip_line(
214 LineSegment {
215 p1: Point { x: 0.0, y: 5.0 },
216 p2: Point { x: 10.0, y: 5.0 },
217 },
218 Window {
219 x_min: 1.0,
220 x_max: 9.0,
221 y_min: 1.0,
222 y_max: 9.0,
223 },
224 );
225 assert_eq!(
226 line,
227 Some(LineSegment {
228 p1: Point { x: 1.0, y: 5.0 },
229 p2: Point { x: 9.0, y: 5.0 }
230 })
231 );
232 }
233
234 #[test]
235 fn test_line_diagonal() {
236 let line = clip_line(
237 LineSegment {
238 p1: Point { x: -5.0, y: -5.0 },
239 p2: Point { x: 15.0, y: 15.0 },
240 },
241 Window {
242 x_min: 1.0,
243 x_max: 9.0,
244 y_min: 1.0,
245 y_max: 9.0,
246 },
247 );
248 assert_eq!(
249 line,
250 Some(LineSegment {
251 p1: Point { x: 1.0, y: 1.0 },
252 p2: Point { x: 9.0, y: 9.0 }
253 })
254 );
255 }
256}