1use core::ops::{Add, Mul, Range, Sub};
7
8use arrayvec::ArrayVec;
9
10use crate::{
11 Affine, Nearest, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveCurvature,
12 ParamCurveDeriv, ParamCurveExtrema, ParamCurveNearest, PathEl, Point, Rect, Shape, Vec2,
13 DEFAULT_ACCURACY, MAX_EXTREMA,
14};
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Line {
21 pub p0: Point,
23 pub p1: Point,
25}
26
27impl Line {
28 #[inline(always)]
30 pub fn new(p0: impl Into<Point>, p1: impl Into<Point>) -> Line {
31 Line {
32 p0: p0.into(),
33 p1: p1.into(),
34 }
35 }
36
37 #[must_use]
40 #[inline(always)]
41 pub fn reversed(&self) -> Line {
42 Self {
43 p0: self.p1,
44 p1: self.p0,
45 }
46 }
47
48 #[inline]
50 pub fn length(self) -> f64 {
51 self.arclen(DEFAULT_ACCURACY)
52 }
53
54 #[must_use]
59 #[inline]
60 pub fn midpoint(&self) -> Point {
61 self.p0.midpoint(self.p1)
62 }
63
64 pub fn crossing_point(self, other: Line) -> Option<Point> {
66 let ab = self.p1 - self.p0;
67 let cd = other.p1 - other.p0;
68 let pcd = ab.cross(cd);
69 if pcd == 0.0 {
70 return None;
71 }
72 let h = ab.cross(self.p0 - other.p0) / pcd;
73 Some(other.p0 + cd * h)
74 }
75
76 #[inline]
80 pub const fn is_finite(self) -> bool {
81 self.p0.is_finite() && self.p1.is_finite()
82 }
83
84 #[inline]
88 pub const fn is_nan(self) -> bool {
89 self.p0.is_nan() || self.p1.is_nan()
90 }
91}
92
93impl From<(Point, Point)> for Line {
94 #[inline(always)]
95 fn from((from, to): (Point, Point)) -> Self {
96 Line::new(from, to)
97 }
98}
99
100impl From<(Point, Vec2)> for Line {
101 #[inline(always)]
102 fn from((origin, displacement): (Point, Vec2)) -> Self {
103 Line::new(origin, origin + displacement)
104 }
105}
106
107impl ParamCurve for Line {
108 #[inline]
109 fn eval(&self, t: f64) -> Point {
110 self.p0.lerp(self.p1, t)
111 }
112
113 #[inline]
114 fn subsegment(&self, range: Range<f64>) -> Line {
115 Line {
116 p0: self.eval(range.start),
117 p1: self.eval(range.end),
118 }
119 }
120
121 #[inline(always)]
122 fn start(&self) -> Point {
123 self.p0
124 }
125
126 #[inline(always)]
127 fn end(&self) -> Point {
128 self.p1
129 }
130}
131
132impl ParamCurveDeriv for Line {
133 type DerivResult = ConstPoint;
134
135 #[inline]
136 fn deriv(&self) -> ConstPoint {
137 ConstPoint((self.p1 - self.p0).to_point())
138 }
139}
140
141impl ParamCurveArclen for Line {
142 #[inline]
143 fn arclen(&self, _accuracy: f64) -> f64 {
144 (self.p1 - self.p0).hypot()
145 }
146
147 #[inline]
148 fn inv_arclen(&self, arclen: f64, _accuracy: f64) -> f64 {
149 arclen / (self.p1 - self.p0).hypot()
150 }
151}
152
153impl ParamCurveArea for Line {
154 #[inline]
155 fn signed_area(&self) -> f64 {
156 self.p0.to_vec2().cross(self.p1.to_vec2()) * 0.5
157 }
158}
159
160impl ParamCurveNearest for Line {
161 #[inline]
162 fn nearest(&self, p: Point, _accuracy: f64) -> Nearest {
163 let d = self.p1 - self.p0;
164 let v = p - self.p0;
165
166 let t = d.dot(v) / d.hypot2();
172
173 #[expect(
176 clippy::manual_clamp,
177 reason = "`clamp` uses slightly more instructions than chained `max` and `min` on x86 and aarch64"
178 )]
179 let t = { t.max(0.).min(1.) };
180
181 let distance_sq = (v - t * d).hypot2();
183
184 Nearest { distance_sq, t }
185 }
186}
187
188impl ParamCurveCurvature for Line {
189 #[inline(always)]
190 fn curvature(&self, _t: f64) -> f64 {
191 0.0
192 }
193}
194
195impl ParamCurveExtrema for Line {
196 #[inline]
197 fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
198 ArrayVec::new()
199 }
200}
201
202#[derive(Clone, Copy, Debug)]
204#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub struct ConstPoint(Point);
207
208impl ConstPoint {
209 #[inline]
213 pub const fn is_finite(self) -> bool {
214 self.0.is_finite()
215 }
216
217 #[inline]
221 pub const fn is_nan(self) -> bool {
222 self.0.is_nan()
223 }
224}
225
226impl ParamCurve for ConstPoint {
227 #[inline(always)]
228 fn eval(&self, _t: f64) -> Point {
229 self.0
230 }
231
232 #[inline(always)]
233 fn subsegment(&self, _range: Range<f64>) -> ConstPoint {
234 *self
235 }
236}
237
238impl ParamCurveDeriv for ConstPoint {
239 type DerivResult = ConstPoint;
240
241 #[inline(always)]
242 fn deriv(&self) -> ConstPoint {
243 ConstPoint(Point::new(0.0, 0.0))
244 }
245}
246
247impl ParamCurveArclen for ConstPoint {
248 #[inline(always)]
249 fn arclen(&self, _accuracy: f64) -> f64 {
250 0.0
251 }
252
253 #[inline(always)]
254 fn inv_arclen(&self, _arclen: f64, _accuracy: f64) -> f64 {
255 0.0
256 }
257}
258
259impl Mul<Line> for Affine {
260 type Output = Line;
261
262 #[inline]
263 fn mul(self, other: Line) -> Line {
264 Line {
265 p0: self * other.p0,
266 p1: self * other.p1,
267 }
268 }
269}
270
271impl Add<Vec2> for Line {
272 type Output = Line;
273
274 #[inline]
275 fn add(self, v: Vec2) -> Line {
276 Line::new(self.p0 + v, self.p1 + v)
277 }
278}
279
280impl Sub<Vec2> for Line {
281 type Output = Line;
282
283 #[inline]
284 fn sub(self, v: Vec2) -> Line {
285 Line::new(self.p0 - v, self.p1 - v)
286 }
287}
288
289#[doc(hidden)]
291pub struct LinePathIter {
292 line: Line,
293 ix: usize,
294}
295
296impl Shape for Line {
297 type PathElementsIter<'iter> = LinePathIter;
298
299 #[inline]
300 fn path_elements(&self, _tolerance: f64) -> LinePathIter {
301 LinePathIter { line: *self, ix: 0 }
302 }
303
304 #[inline(always)]
309 fn area(&self) -> f64 {
310 0.0
311 }
312
313 #[inline]
314 fn perimeter(&self, _accuracy: f64) -> f64 {
315 (self.p1 - self.p0).hypot()
316 }
317
318 #[inline(always)]
320 fn winding(&self, _pt: Point) -> i32 {
321 0
322 }
323
324 #[inline(always)]
325 fn bounding_box(&self) -> Rect {
326 Rect::from_points(self.p0, self.p1)
327 }
328
329 #[inline(always)]
330 fn as_line(&self) -> Option<Line> {
331 Some(*self)
332 }
333}
334
335impl Iterator for LinePathIter {
336 type Item = PathEl;
337
338 fn next(&mut self) -> Option<PathEl> {
339 self.ix += 1;
340 match self.ix {
341 1 => Some(PathEl::MoveTo(self.line.p0)),
342 2 => Some(PathEl::LineTo(self.line.p1)),
343 _ => None,
344 }
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use crate::{Line, ParamCurveArclen, Point};
351
352 #[test]
353 fn line_reversed() {
354 let l = Line::new((0.0, 0.0), (1.0, 1.0));
355 let f = l.reversed();
356
357 assert_eq!(l.p0, f.p1);
358 assert_eq!(l.p1, f.p0);
359
360 assert_eq!(l, f.reversed());
362 }
363
364 #[test]
365 fn line_arclen() {
366 let l = Line::new((0.0, 0.0), (1.0, 1.0));
367 let true_len = 2.0f64.sqrt();
368 let epsilon = 1e-9;
369 assert!(l.arclen(epsilon) - true_len < epsilon);
370
371 let t = l.inv_arclen(true_len / 3.0, epsilon);
372 assert!((t - 1.0 / 3.0).abs() < epsilon);
373 }
374
375 #[test]
376 fn line_midpoint() {
377 let l = Line::new((0.0, 0.0), (2.0, 4.0));
378 assert_eq!(l.midpoint(), Point::new(1.0, 2.0));
379 }
380
381 #[test]
382 fn line_is_finite() {
383 assert!((Line {
384 p0: Point { x: 0., y: 0. },
385 p1: Point { x: 1., y: 1. }
386 })
387 .is_finite());
388
389 assert!(!(Line {
390 p0: Point { x: 0., y: 0. },
391 p1: Point {
392 x: f64::INFINITY,
393 y: 1.
394 }
395 })
396 .is_finite());
397
398 assert!(!(Line {
399 p0: Point { x: 0., y: 0. },
400 p1: Point {
401 x: 0.,
402 y: f64::INFINITY
403 }
404 })
405 .is_finite());
406 }
407
408 #[test]
409 fn line_nearest() {
410 use crate::{ParamCurve, ParamCurveNearest};
411
412 const EPSILON: f64 = 1e-9;
413
414 let line = Line::new((-4., 0.), (2., 1.));
415
416 let point = Point::new(4., 0.);
418 let nearest = line.nearest(point, 0.);
419 assert_eq!(nearest.t, 1.);
420 assert!((nearest.distance_sq - line.p1.distance_squared(point)).abs() < EPSILON);
421
422 let point = Point::new(0., -50.);
424 let nearest = line.nearest(point, 0.);
425 assert_eq!(nearest.t, 0.);
426 assert!((nearest.distance_sq - line.p0.distance_squared(point)).abs() < EPSILON);
427
428 let point = Point::new(-1., 0.5);
430 let nearest = line.nearest(point, 0.);
431 assert!(nearest.t > 0. && nearest.t < 1.);
432 assert!(
434 (line.eval(nearest.t).distance_squared(point) - nearest.distance_sq).abs() < EPSILON
435 );
436
437 assert!(line.eval(nearest.t * 0.95).distance_squared(point) > nearest.distance_sq);
440 assert!(line.eval(nearest.t * 1.05).distance_squared(point) > nearest.distance_sq);
441 }
442}