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]
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 /// The length of the line.
38 #[inline]
39 pub fn length(self) -> f64 {
40 self.arclen(DEFAULT_ACCURACY)
41 }
42
43 /// Computes the point where two lines, if extended to infinity, would cross.
44 pub fn crossing_point(self, other: Line) -> Option<Point> {
45 let ab = self.p1 - self.p0;
46 let cd = other.p1 - other.p0;
47 let pcd = ab.cross(cd);
48 if pcd == 0.0 {
49 return None;
50 }
51 let h = ab.cross(self.p0 - other.p0) / pcd;
52 Some(other.p0 + cd * h)
53 }
54
55 /// Is this line finite?
56 #[inline]
57 pub fn is_finite(self) -> bool {
58 self.p0.is_finite() && self.p1.is_finite()
59 }
60
61 /// Is this line NaN?
62 #[inline]
63 pub fn is_nan(self) -> bool {
64 self.p0.is_nan() || self.p1.is_nan()
65 }
66}
67
68impl ParamCurve for Line {
69 #[inline]
70 fn eval(&self, t: f64) -> Point {
71 self.p0.lerp(self.p1, t)
72 }
73
74 #[inline]
75 fn subsegment(&self, range: Range<f64>) -> Line {
76 Line {
77 p0: self.eval(range.start),
78 p1: self.eval(range.end),
79 }
80 }
81
82 #[inline]
83 fn start(&self) -> Point {
84 self.p0
85 }
86
87 #[inline]
88 fn end(&self) -> Point {
89 self.p1
90 }
91}
92
93impl ParamCurveDeriv for Line {
94 type DerivResult = ConstPoint;
95
96 #[inline]
97 fn deriv(&self) -> ConstPoint {
98 ConstPoint((self.p1 - self.p0).to_point())
99 }
100}
101
102impl ParamCurveArclen for Line {
103 #[inline]
104 fn arclen(&self, _accuracy: f64) -> f64 {
105 (self.p1 - self.p0).hypot()
106 }
107
108 #[inline]
109 fn inv_arclen(&self, arclen: f64, _accuracy: f64) -> f64 {
110 arclen / (self.p1 - self.p0).hypot()
111 }
112}
113
114impl ParamCurveArea for Line {
115 #[inline]
116 fn signed_area(&self) -> f64 {
117 self.p0.to_vec2().cross(self.p1.to_vec2()) * 0.5
118 }
119}
120
121impl ParamCurveNearest for Line {
122 fn nearest(&self, p: Point, _accuracy: f64) -> Nearest {
123 let d: Vec2 = self.p1 - self.p0;
124 let dotp: f64 = d.dot(p - self.p0);
125 let d_squared: f64 = d.dot(d);
126 let (t: f64, distance_sq: f64) = if dotp <= 0.0 {
127 (0.0, (p - self.p0).hypot2())
128 } else if dotp >= d_squared {
129 (1.0, (p - self.p1).hypot2())
130 } else {
131 let t: f64 = dotp / d_squared;
132 let dist: f64 = (p - self.eval(t)).hypot2();
133 (t, dist)
134 };
135 Nearest { distance_sq, t }
136 }
137}
138
139impl ParamCurveCurvature for Line {
140 #[inline]
141 fn curvature(&self, _t: f64) -> f64 {
142 0.0
143 }
144}
145
146impl ParamCurveExtrema for Line {
147 #[inline]
148 fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
149 ArrayVec::new()
150 }
151}
152
153/// A trivial "curve" that is just a constant.
154#[derive(Clone, Copy, Debug)]
155#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157pub struct ConstPoint(Point);
158
159impl ConstPoint {
160 /// Is this point finite?
161 #[inline]
162 pub fn is_finite(self) -> bool {
163 self.0.is_finite()
164 }
165
166 /// Is this point NaN?
167 #[inline]
168 pub fn is_nan(self) -> bool {
169 self.0.is_nan()
170 }
171}
172
173impl ParamCurve for ConstPoint {
174 #[inline]
175 fn eval(&self, _t: f64) -> Point {
176 self.0
177 }
178
179 #[inline]
180 fn subsegment(&self, _range: Range<f64>) -> ConstPoint {
181 *self
182 }
183}
184
185impl ParamCurveDeriv for ConstPoint {
186 type DerivResult = ConstPoint;
187
188 #[inline]
189 fn deriv(&self) -> ConstPoint {
190 ConstPoint(Point::new(x:0.0, y:0.0))
191 }
192}
193
194impl ParamCurveArclen for ConstPoint {
195 #[inline]
196 fn arclen(&self, _accuracy: f64) -> f64 {
197 0.0
198 }
199
200 #[inline]
201 fn inv_arclen(&self, _arclen: f64, _accuracy: f64) -> f64 {
202 0.0
203 }
204}
205
206impl Mul<Line> for Affine {
207 type Output = Line;
208
209 #[inline]
210 fn mul(self, other: Line) -> Line {
211 Line {
212 p0: self * other.p0,
213 p1: self * other.p1,
214 }
215 }
216}
217
218impl Add<Vec2> for Line {
219 type Output = Line;
220
221 #[inline]
222 fn add(self, v: Vec2) -> Line {
223 Line::new(self.p0 + v, self.p1 + v)
224 }
225}
226
227impl Sub<Vec2> for Line {
228 type Output = Line;
229
230 #[inline]
231 fn sub(self, v: Vec2) -> Line {
232 Line::new(self.p0 - v, self.p1 - v)
233 }
234}
235
236/// An iterator yielding the path for a single line.
237#[doc(hidden)]
238pub struct LinePathIter {
239 line: Line,
240 ix: usize,
241}
242
243impl Shape for Line {
244 type PathElementsIter<'iter> = LinePathIter;
245
246 #[inline]
247 fn path_elements(&self, _tolerance: f64) -> LinePathIter {
248 LinePathIter { line: *self, ix: 0 }
249 }
250
251 /// Returning zero here is consistent with the contract (area is
252 /// only meaningful for closed shapes), but an argument can be made
253 /// that the contract should be tightened to include the Green's
254 /// theorem contribution.
255 fn area(&self) -> f64 {
256 0.0
257 }
258
259 #[inline]
260 fn perimeter(&self, _accuracy: f64) -> f64 {
261 (self.p1 - self.p0).hypot()
262 }
263
264 /// Same consideration as `area`.
265 fn winding(&self, _pt: Point) -> i32 {
266 0
267 }
268
269 #[inline]
270 fn bounding_box(&self) -> Rect {
271 Rect::from_points(self.p0, self.p1)
272 }
273
274 #[inline]
275 fn as_line(&self) -> Option<Line> {
276 Some(*self)
277 }
278}
279
280impl Iterator for LinePathIter {
281 type Item = PathEl;
282
283 fn next(&mut self) -> Option<PathEl> {
284 self.ix += 1;
285 match self.ix {
286 1 => Some(PathEl::MoveTo(self.line.p0)),
287 2 => Some(PathEl::LineTo(self.line.p1)),
288 _ => None,
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use crate::{Line, ParamCurveArclen, Point};
296
297 #[test]
298 fn line_arclen() {
299 let l = Line::new((0.0, 0.0), (1.0, 1.0));
300 let true_len = 2.0f64.sqrt();
301 let epsilon = 1e-9;
302 assert!(l.arclen(epsilon) - true_len < epsilon);
303
304 let t = l.inv_arclen(true_len / 3.0, epsilon);
305 assert!((t - 1.0 / 3.0).abs() < epsilon);
306 }
307
308 #[test]
309 fn line_is_finite() {
310 assert!((Line {
311 p0: Point { x: 0., y: 0. },
312 p1: Point { x: 1., y: 1. }
313 })
314 .is_finite());
315
316 assert!(!(Line {
317 p0: Point { x: 0., y: 0. },
318 p1: Point {
319 x: f64::INFINITY,
320 y: 1.
321 }
322 })
323 .is_finite());
324
325 assert!(!(Line {
326 p0: Point { x: 0., y: 0. },
327 p1: Point {
328 x: 0.,
329 y: f64::INFINITY
330 }
331 })
332 .is_finite());
333 }
334}
335