1 //! Lines. 2 3 use std::ops::{Mul, Range}; 4 5 use arrayvec::ArrayVec; 6 7 use crate::MAX_EXTREMA; 8 use crate::{ 9 Affine, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveCurvature, ParamCurveDeriv, 10 ParamCurveExtrema, ParamCurveNearest, PathEl, Point, Rect, Shape, 11 }; 12 13 /// A single line. 14 #[derive(Clone, Copy, Debug, PartialEq)] 15 pub struct Line { 16 /// The line's start point. 17 pub p0: Point, 18 /// The line's end point. 19 pub p1: Point, 20 } 21 22 impl Line { 23 /// Create a new line. 24 #[inline] new(p0: impl Into<Point>, p1: impl Into<Point>) -> Line25 pub fn new(p0: impl Into<Point>, p1: impl Into<Point>) -> Line { 26 Line { 27 p0: p0.into(), 28 p1: p1.into(), 29 } 30 } 31 } 32 33 impl ParamCurve for Line { 34 #[inline] eval(&self, t: f64) -> Point35 fn eval(&self, t: f64) -> Point { 36 self.p0.lerp(self.p1, t) 37 } 38 39 #[inline] start(&self) -> Point40 fn start(&self) -> Point { 41 self.p0 42 } 43 44 #[inline] end(&self) -> Point45 fn end(&self) -> Point { 46 self.p1 47 } 48 49 #[inline] subsegment(&self, range: Range<f64>) -> Line50 fn subsegment(&self, range: Range<f64>) -> Line { 51 Line { 52 p0: self.eval(range.start), 53 p1: self.eval(range.end), 54 } 55 } 56 } 57 58 impl ParamCurveDeriv for Line { 59 type DerivResult = ConstPoint; 60 61 #[inline] deriv(&self) -> ConstPoint62 fn deriv(&self) -> ConstPoint { 63 ConstPoint((self.p1 - self.p0).to_point()) 64 } 65 } 66 67 impl ParamCurveArclen for Line { 68 #[inline] arclen(&self, _accuracy: f64) -> f6469 fn arclen(&self, _accuracy: f64) -> f64 { 70 (self.p1 - self.p0).hypot() 71 } 72 } 73 74 impl ParamCurveArea for Line { 75 #[inline] signed_area(&self) -> f6476 fn signed_area(&self) -> f64 { 77 self.p0.to_vec2().cross(self.p1.to_vec2()) * 0.5 78 } 79 } 80 81 impl ParamCurveNearest for Line { nearest(&self, p: Point, _accuracy: f64) -> (f64, f64)82 fn nearest(&self, p: Point, _accuracy: f64) -> (f64, f64) { 83 let d = self.p1 - self.p0; 84 let dotp = d.dot(p - self.p0); 85 let d_squared = d.dot(d); 86 if dotp <= 0.0 { 87 (0.0, (p - self.p0).hypot2()) 88 } else if dotp >= d_squared { 89 (1.0, (p - self.p1).hypot2()) 90 } else { 91 let t = dotp / d_squared; 92 let dist = (p - self.eval(t)).hypot2(); 93 (t, dist) 94 } 95 } 96 } 97 98 impl ParamCurveCurvature for Line { 99 #[inline] curvature(&self, _t: f64) -> f64100 fn curvature(&self, _t: f64) -> f64 { 101 0.0 102 } 103 } 104 105 impl ParamCurveExtrema for Line { 106 #[inline] extrema(&self) -> ArrayVec<[f64; MAX_EXTREMA]>107 fn extrema(&self) -> ArrayVec<[f64; MAX_EXTREMA]> { 108 ArrayVec::new() 109 } 110 } 111 112 /// A trivial "curve" that is just a constant. 113 #[derive(Clone, Copy, Debug)] 114 pub struct ConstPoint(Point); 115 116 impl ParamCurve for ConstPoint { 117 #[inline] eval(&self, _t: f64) -> Point118 fn eval(&self, _t: f64) -> Point { 119 self.0 120 } 121 122 #[inline] subsegment(&self, _range: Range<f64>) -> ConstPoint123 fn subsegment(&self, _range: Range<f64>) -> ConstPoint { 124 *self 125 } 126 } 127 128 impl ParamCurveDeriv for ConstPoint { 129 type DerivResult = ConstPoint; 130 131 #[inline] deriv(&self) -> ConstPoint132 fn deriv(&self) -> ConstPoint { 133 ConstPoint(Point::new(0.0, 0.0)) 134 } 135 } 136 137 impl ParamCurveArclen for ConstPoint { 138 #[inline] arclen(&self, _accuracy: f64) -> f64139 fn arclen(&self, _accuracy: f64) -> f64 { 140 0.0 141 } 142 } 143 144 impl Mul<Line> for Affine { 145 type Output = Line; 146 147 #[inline] mul(self, other: Line) -> Line148 fn mul(self, other: Line) -> Line { 149 Line { 150 p0: self * other.p0, 151 p1: self * other.p1, 152 } 153 } 154 } 155 156 /// An iterator yielding the path for a single line. 157 #[doc(hidden)] 158 pub struct LinePathIter { 159 line: Line, 160 ix: usize, 161 } 162 163 impl Shape for Line { 164 type BezPathIter = LinePathIter; 165 166 #[inline] to_bez_path(&self, _tolerance: f64) -> LinePathIter167 fn to_bez_path(&self, _tolerance: f64) -> LinePathIter { 168 LinePathIter { line: *self, ix: 0 } 169 } 170 171 /// Returning zero here is consistent with the contract (area is 172 /// only meaningful for closed shapes), but an argument can be made 173 /// that the contract should be tightened to include the Green's 174 /// theorem contribution. area(&self) -> f64175 fn area(&self) -> f64 { 176 0.0 177 } 178 179 #[inline] perimeter(&self, _accuracy: f64) -> f64180 fn perimeter(&self, _accuracy: f64) -> f64 { 181 (self.p1 - self.p0).hypot() 182 } 183 184 /// Same consideration as `area`. winding(&self, _pt: Point) -> i32185 fn winding(&self, _pt: Point) -> i32 { 186 0 187 } 188 189 #[inline] bounding_box(&self) -> Rect190 fn bounding_box(&self) -> Rect { 191 Rect::from_points(self.p0, self.p1) 192 } 193 194 #[inline] as_line(&self) -> Option<Line>195 fn as_line(&self) -> Option<Line> { 196 Some(*self) 197 } 198 } 199 200 impl Iterator for LinePathIter { 201 type Item = PathEl; 202 next(&mut self) -> Option<PathEl>203 fn next(&mut self) -> Option<PathEl> { 204 self.ix += 1; 205 match self.ix { 206 1 => Some(PathEl::MoveTo(self.line.p0)), 207 2 => Some(PathEl::LineTo(self.line.p1)), 208 _ => None, 209 } 210 } 211 } 212 213 #[cfg(test)] 214 mod tests { 215 use crate::{Line, ParamCurveArclen}; 216 217 #[test] line_arclen()218 fn line_arclen() { 219 let l = Line::new((0.0, 0.0), (1.0, 1.0)); 220 let true_len = 2.0f64.sqrt(); 221 let epsilon = 1e-9; 222 assert!(l.arclen(epsilon) - true_len < epsilon); 223 224 let t = l.inv_arclen(true_len / 3.0, epsilon); 225 assert!((t - 1.0 / 3.0).abs() < epsilon); 226 } 227 } 228