kurbo/
line.rs

1// Copyright 2018 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Lines.
5
6use 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/// A single line.
17#[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    /// The line's start point.
22    pub p0: Point,
23    /// The line's end point.
24    pub p1: Point,
25}
26
27impl Line {
28    /// Create a new line.
29    #[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    /// Returns a copy of this `Line` with the end points swapped so that it
38    /// points in the opposite direction.
39    #[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    /// The length of the line.
49    #[inline]
50    pub fn length(self) -> f64 {
51        self.arclen(DEFAULT_ACCURACY)
52    }
53
54    /// The midpoint of the line.
55    ///
56    /// This is the same as calling [`Point::midpoint`] with
57    /// the endpoints of this line.
58    #[must_use]
59    #[inline]
60    pub fn midpoint(&self) -> Point {
61        self.p0.midpoint(self.p1)
62    }
63
64    /// Computes the point where two lines, if extended to infinity, would cross.
65    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    /// Is this line `finite`?
77    ///
78    /// [finite]: f64::is_finite
79    #[inline]
80    pub fn is_finite(self) -> bool {
81        self.p0.is_finite() && self.p1.is_finite()
82    }
83
84    /// Is this line `NaN`?
85    ///
86    /// [NaN]: f64::is_nan
87    #[inline]
88    pub 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    fn nearest(&self, p: Point, _accuracy: f64) -> Nearest {
162        let d = self.p1 - self.p0;
163        let dotp = d.dot(p - self.p0);
164        let d_squared = d.dot(d);
165        let (t, distance_sq) = if dotp <= 0.0 {
166            (0.0, (p - self.p0).hypot2())
167        } else if dotp >= d_squared {
168            (1.0, (p - self.p1).hypot2())
169        } else {
170            let t = dotp / d_squared;
171            let dist = (p - self.eval(t)).hypot2();
172            (t, dist)
173        };
174        Nearest { distance_sq, t }
175    }
176}
177
178impl ParamCurveCurvature for Line {
179    #[inline(always)]
180    fn curvature(&self, _t: f64) -> f64 {
181        0.0
182    }
183}
184
185impl ParamCurveExtrema for Line {
186    #[inline]
187    fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
188        ArrayVec::new()
189    }
190}
191
192/// A trivial "curve" that is just a constant.
193#[derive(Clone, Copy, Debug)]
194#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct ConstPoint(Point);
197
198impl ConstPoint {
199    /// Is this point [finite]?
200    ///
201    /// [finite]: f64::is_finite
202    #[inline]
203    pub fn is_finite(self) -> bool {
204        self.0.is_finite()
205    }
206
207    /// Is this point [NaN]?
208    ///
209    /// [NaN]: f64::is_nan
210    #[inline]
211    pub fn is_nan(self) -> bool {
212        self.0.is_nan()
213    }
214}
215
216impl ParamCurve for ConstPoint {
217    #[inline(always)]
218    fn eval(&self, _t: f64) -> Point {
219        self.0
220    }
221
222    #[inline(always)]
223    fn subsegment(&self, _range: Range<f64>) -> ConstPoint {
224        *self
225    }
226}
227
228impl ParamCurveDeriv for ConstPoint {
229    type DerivResult = ConstPoint;
230
231    #[inline(always)]
232    fn deriv(&self) -> ConstPoint {
233        ConstPoint(Point::new(0.0, 0.0))
234    }
235}
236
237impl ParamCurveArclen for ConstPoint {
238    #[inline(always)]
239    fn arclen(&self, _accuracy: f64) -> f64 {
240        0.0
241    }
242
243    #[inline(always)]
244    fn inv_arclen(&self, _arclen: f64, _accuracy: f64) -> f64 {
245        0.0
246    }
247}
248
249impl Mul<Line> for Affine {
250    type Output = Line;
251
252    #[inline]
253    fn mul(self, other: Line) -> Line {
254        Line {
255            p0: self * other.p0,
256            p1: self * other.p1,
257        }
258    }
259}
260
261impl Add<Vec2> for Line {
262    type Output = Line;
263
264    #[inline]
265    fn add(self, v: Vec2) -> Line {
266        Line::new(self.p0 + v, self.p1 + v)
267    }
268}
269
270impl Sub<Vec2> for Line {
271    type Output = Line;
272
273    #[inline]
274    fn sub(self, v: Vec2) -> Line {
275        Line::new(self.p0 - v, self.p1 - v)
276    }
277}
278
279/// An iterator yielding the path for a single line.
280#[doc(hidden)]
281pub struct LinePathIter {
282    line: Line,
283    ix: usize,
284}
285
286impl Shape for Line {
287    type PathElementsIter<'iter> = LinePathIter;
288
289    #[inline]
290    fn path_elements(&self, _tolerance: f64) -> LinePathIter {
291        LinePathIter { line: *self, ix: 0 }
292    }
293
294    /// Returning zero here is consistent with the contract (area is
295    /// only meaningful for closed shapes), but an argument can be made
296    /// that the contract should be tightened to include the Green's
297    /// theorem contribution.
298    #[inline(always)]
299    fn area(&self) -> f64 {
300        0.0
301    }
302
303    #[inline]
304    fn perimeter(&self, _accuracy: f64) -> f64 {
305        (self.p1 - self.p0).hypot()
306    }
307
308    /// Same consideration as `area`.
309    #[inline(always)]
310    fn winding(&self, _pt: Point) -> i32 {
311        0
312    }
313
314    #[inline(always)]
315    fn bounding_box(&self) -> Rect {
316        Rect::from_points(self.p0, self.p1)
317    }
318
319    #[inline(always)]
320    fn as_line(&self) -> Option<Line> {
321        Some(*self)
322    }
323}
324
325impl Iterator for LinePathIter {
326    type Item = PathEl;
327
328    fn next(&mut self) -> Option<PathEl> {
329        self.ix += 1;
330        match self.ix {
331            1 => Some(PathEl::MoveTo(self.line.p0)),
332            2 => Some(PathEl::LineTo(self.line.p1)),
333            _ => None,
334        }
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use crate::{Line, ParamCurveArclen, Point};
341
342    #[test]
343    fn line_reversed() {
344        let l = Line::new((0.0, 0.0), (1.0, 1.0));
345        let f = l.reversed();
346
347        assert_eq!(l.p0, f.p1);
348        assert_eq!(l.p1, f.p0);
349
350        // Reversing it again should result in the original line
351        assert_eq!(l, f.reversed());
352    }
353
354    #[test]
355    fn line_arclen() {
356        let l = Line::new((0.0, 0.0), (1.0, 1.0));
357        let true_len = 2.0f64.sqrt();
358        let epsilon = 1e-9;
359        assert!(l.arclen(epsilon) - true_len < epsilon);
360
361        let t = l.inv_arclen(true_len / 3.0, epsilon);
362        assert!((t - 1.0 / 3.0).abs() < epsilon);
363    }
364
365    #[test]
366    fn line_midpoint() {
367        let l = Line::new((0.0, 0.0), (2.0, 4.0));
368        assert_eq!(l.midpoint(), Point::new(1.0, 2.0));
369    }
370
371    #[test]
372    fn line_is_finite() {
373        assert!((Line {
374            p0: Point { x: 0., y: 0. },
375            p1: Point { x: 1., y: 1. }
376        })
377        .is_finite());
378
379        assert!(!(Line {
380            p0: Point { x: 0., y: 0. },
381            p1: Point {
382                x: f64::INFINITY,
383                y: 1.
384            }
385        })
386        .is_finite());
387
388        assert!(!(Line {
389            p0: Point { x: 0., y: 0. },
390            p1: Point {
391                x: 0.,
392                y: f64::INFINITY
393            }
394        })
395        .is_finite());
396    }
397}