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 /// 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]
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 fn from((from: Point, to: Point): (Point, Point)) -> Self {
95 Line::new(p0:from, p1:to)
96 }
97}
98
99impl From<(Point, Vec2)> for Line {
100 fn from((origin: Point, displacement: Vec2): (Point, Vec2)) -> Self {
101 Line::new(p0:origin, p1:origin + displacement)
102 }
103}
104
105impl ParamCurve for Line {
106 #[inline]
107 fn eval(&self, t: f64) -> Point {
108 self.p0.lerp(self.p1, t)
109 }
110
111 #[inline]
112 fn subsegment(&self, range: Range<f64>) -> Line {
113 Line {
114 p0: self.eval(range.start),
115 p1: self.eval(range.end),
116 }
117 }
118
119 #[inline]
120 fn start(&self) -> Point {
121 self.p0
122 }
123
124 #[inline]
125 fn end(&self) -> Point {
126 self.p1
127 }
128}
129
130impl ParamCurveDeriv for Line {
131 type DerivResult = ConstPoint;
132
133 #[inline]
134 fn deriv(&self) -> ConstPoint {
135 ConstPoint((self.p1 - self.p0).to_point())
136 }
137}
138
139impl ParamCurveArclen for Line {
140 #[inline]
141 fn arclen(&self, _accuracy: f64) -> f64 {
142 (self.p1 - self.p0).hypot()
143 }
144
145 #[inline]
146 fn inv_arclen(&self, arclen: f64, _accuracy: f64) -> f64 {
147 arclen / (self.p1 - self.p0).hypot()
148 }
149}
150
151impl ParamCurveArea for Line {
152 #[inline]
153 fn signed_area(&self) -> f64 {
154 self.p0.to_vec2().cross(self.p1.to_vec2()) * 0.5
155 }
156}
157
158impl ParamCurveNearest for Line {
159 fn nearest(&self, p: Point, _accuracy: f64) -> Nearest {
160 let d: Vec2 = self.p1 - self.p0;
161 let dotp: f64 = d.dot(p - self.p0);
162 let d_squared: f64 = d.dot(d);
163 let (t: f64, distance_sq: f64) = if dotp <= 0.0 {
164 (0.0, (p - self.p0).hypot2())
165 } else if dotp >= d_squared {
166 (1.0, (p - self.p1).hypot2())
167 } else {
168 let t: f64 = dotp / d_squared;
169 let dist: f64 = (p - self.eval(t)).hypot2();
170 (t, dist)
171 };
172 Nearest { distance_sq, t }
173 }
174}
175
176impl ParamCurveCurvature for Line {
177 #[inline]
178 fn curvature(&self, _t: f64) -> f64 {
179 0.0
180 }
181}
182
183impl ParamCurveExtrema for Line {
184 #[inline]
185 fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
186 ArrayVec::new()
187 }
188}
189
190/// A trivial "curve" that is just a constant.
191#[derive(Clone, Copy, Debug)]
192#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194pub struct ConstPoint(Point);
195
196impl ConstPoint {
197 /// Is this point [finite]?
198 ///
199 /// [finite]: f64::is_finite
200 #[inline]
201 pub fn is_finite(self) -> bool {
202 self.0.is_finite()
203 }
204
205 /// Is this point [NaN]?
206 ///
207 /// [NaN]: f64::is_nan
208 #[inline]
209 pub fn is_nan(self) -> bool {
210 self.0.is_nan()
211 }
212}
213
214impl ParamCurve for ConstPoint {
215 #[inline]
216 fn eval(&self, _t: f64) -> Point {
217 self.0
218 }
219
220 #[inline]
221 fn subsegment(&self, _range: Range<f64>) -> ConstPoint {
222 *self
223 }
224}
225
226impl ParamCurveDeriv for ConstPoint {
227 type DerivResult = ConstPoint;
228
229 #[inline]
230 fn deriv(&self) -> ConstPoint {
231 ConstPoint(Point::new(x:0.0, y:0.0))
232 }
233}
234
235impl ParamCurveArclen for ConstPoint {
236 #[inline]
237 fn arclen(&self, _accuracy: f64) -> f64 {
238 0.0
239 }
240
241 #[inline]
242 fn inv_arclen(&self, _arclen: f64, _accuracy: f64) -> f64 {
243 0.0
244 }
245}
246
247impl Mul<Line> for Affine {
248 type Output = Line;
249
250 #[inline]
251 fn mul(self, other: Line) -> Line {
252 Line {
253 p0: self * other.p0,
254 p1: self * other.p1,
255 }
256 }
257}
258
259impl Add<Vec2> for Line {
260 type Output = Line;
261
262 #[inline]
263 fn add(self, v: Vec2) -> Line {
264 Line::new(self.p0 + v, self.p1 + v)
265 }
266}
267
268impl Sub<Vec2> for Line {
269 type Output = Line;
270
271 #[inline]
272 fn sub(self, v: Vec2) -> Line {
273 Line::new(self.p0 - v, self.p1 - v)
274 }
275}
276
277/// An iterator yielding the path for a single line.
278#[doc(hidden)]
279pub struct LinePathIter {
280 line: Line,
281 ix: usize,
282}
283
284impl Shape for Line {
285 type PathElementsIter<'iter> = LinePathIter;
286
287 #[inline]
288 fn path_elements(&self, _tolerance: f64) -> LinePathIter {
289 LinePathIter { line: *self, ix: 0 }
290 }
291
292 /// Returning zero here is consistent with the contract (area is
293 /// only meaningful for closed shapes), but an argument can be made
294 /// that the contract should be tightened to include the Green's
295 /// theorem contribution.
296 fn area(&self) -> f64 {
297 0.0
298 }
299
300 #[inline]
301 fn perimeter(&self, _accuracy: f64) -> f64 {
302 (self.p1 - self.p0).hypot()
303 }
304
305 /// Same consideration as `area`.
306 fn winding(&self, _pt: Point) -> i32 {
307 0
308 }
309
310 #[inline]
311 fn bounding_box(&self) -> Rect {
312 Rect::from_points(self.p0, self.p1)
313 }
314
315 #[inline]
316 fn as_line(&self) -> Option<Line> {
317 Some(*self)
318 }
319}
320
321impl Iterator for LinePathIter {
322 type Item = PathEl;
323
324 fn next(&mut self) -> Option<PathEl> {
325 self.ix += 1;
326 match self.ix {
327 1 => Some(PathEl::MoveTo(self.line.p0)),
328 2 => Some(PathEl::LineTo(self.line.p1)),
329 _ => None,
330 }
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use crate::{Line, ParamCurveArclen, Point};
337
338 #[test]
339 fn line_reversed() {
340 let l = Line::new((0.0, 0.0), (1.0, 1.0));
341 let f = l.reversed();
342
343 assert_eq!(l.p0, f.p1);
344 assert_eq!(l.p1, f.p0);
345
346 // Reversing it again should result in the original line
347 assert_eq!(l, f.reversed());
348 }
349
350 #[test]
351 fn line_arclen() {
352 let l = Line::new((0.0, 0.0), (1.0, 1.0));
353 let true_len = 2.0f64.sqrt();
354 let epsilon = 1e-9;
355 assert!(l.arclen(epsilon) - true_len < epsilon);
356
357 let t = l.inv_arclen(true_len / 3.0, epsilon);
358 assert!((t - 1.0 / 3.0).abs() < epsilon);
359 }
360
361 #[test]
362 fn line_midpoint() {
363 let l = Line::new((0.0, 0.0), (2.0, 4.0));
364 assert_eq!(l.midpoint(), Point::new(1.0, 2.0));
365 }
366
367 #[test]
368 fn line_is_finite() {
369 assert!((Line {
370 p0: Point { x: 0., y: 0. },
371 p1: Point { x: 1., y: 1. }
372 })
373 .is_finite());
374
375 assert!(!(Line {
376 p0: Point { x: 0., y: 0. },
377 p1: Point {
378 x: f64::INFINITY,
379 y: 1.
380 }
381 })
382 .is_finite());
383
384 assert!(!(Line {
385 p0: Point { x: 0., y: 0. },
386 p1: Point {
387 x: 0.,
388 y: f64::INFINITY
389 }
390 })
391 .is_finite());
392 }
393}
394