lyon_geom-0.15.0/Cargo.toml.orig010064400017500001750000000011471360117372500147050ustar0000000000000000[package] name = "lyon_geom" version = "0.15.0" description = "2D quadratic and cubic bézier arcs and line segment math on top of euclid." authors = ["Nicolas Silva "] repository = "https://github.com/nical/lyon" documentation = "https://docs.rs/lyon_geom/" keywords = ["2d", "graphics", "bezier", "geometry"] license = "MIT/Apache-2.0" workspace = ".." edition = "2018" [lib] name = "lyon_geom" [features] serialization = ["serde", "euclid/serde"] [dependencies] euclid = "0.20.0" arrayvec = "0.5" num-traits = "0.2" serde = {version = "1.0", optional = true, features = ["serde_derive"] } lyon_geom-0.15.0/Cargo.toml0000644000000022431360117435400112100ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "lyon_geom" version = "0.15.0" authors = ["Nicolas Silva "] description = "2D quadratic and cubic bézier arcs and line segment math on top of euclid." documentation = "https://docs.rs/lyon_geom/" keywords = ["2d", "graphics", "bezier", "geometry"] license = "MIT/Apache-2.0" repository = "https://github.com/nical/lyon" [lib] name = "lyon_geom" [dependencies.arrayvec] version = "0.5" [dependencies.euclid] version = "0.20.0" [dependencies.num-traits] version = "0.2" [dependencies.serde] version = "1.0" features = ["serde_derive"] optional = true [features] serialization = ["serde", "euclid/serde"] lyon_geom-0.15.0/src/arc.rs010064400017500001750000000670321360117331400137170ustar0000000000000000//! Elliptic arc related maths and tools. use std::ops::Range; use std::mem::swap; use crate::Line; use crate::scalar::{Scalar, Float, cast}; use crate::generic_math::{Point, point, Vector, vector, Rotation, Transform, Angle, Rect}; use crate::segment::{Segment, FlatteningStep, BoundingRect}; use crate::segment; use crate::QuadraticBezierSegment; use crate::CubicBezierSegment; /// A flattening iterator for arc segments. pub type Flattened = segment::Flattened>; /// An elliptic arc curve segment using the SVG's end-point notation. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct SvgArc { pub from: Point, pub to: Point, pub radii: Vector, pub x_rotation: Angle, pub flags: ArcFlags, } /// An elliptic arc curve segment. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct Arc { pub center: Point, pub radii: Vector, pub start_angle: Angle, pub sweep_angle: Angle, pub x_rotation: Angle, } impl Arc { /// Create simple circle. pub fn circle(center: Point, radius: S) -> Self { Arc { center, radii: vector(radius, radius), start_angle: Angle::zero(), sweep_angle: Angle::two_pi(), x_rotation: Angle::zero(), } } /// Convert from the SVG arc notation. pub fn from_svg_arc(arc: &SvgArc) -> Arc { debug_assert!(!arc.from.x.is_nan()); debug_assert!(!arc.from.y.is_nan()); debug_assert!(!arc.to.x.is_nan()); debug_assert!(!arc.to.y.is_nan()); debug_assert!(!arc.radii.x.is_nan()); debug_assert!(!arc.radii.y.is_nan()); debug_assert!(!arc.x_rotation.get().is_nan()); // The SVG spec specifies what we should do if one of the two // radii is zero and not the other, but it's better to handle // this out of arc code and generate a line_to instead of an arc. assert!(!arc.is_straight_line()); let mut rx = S::abs(arc.radii.x); let mut ry = S::abs(arc.radii.y); let xr = arc.x_rotation.get() % (S::TWO * S::PI()); let cos_phi = Float::cos(xr); let sin_phi = Float::sin(xr); let hd_x = (arc.from.x - arc.to.x) / S::TWO; let hd_y = (arc.from.y - arc.to.y) / S::TWO; let hs_x = (arc.from.x + arc.to.x) / S::TWO; let hs_y = (arc.from.y + arc.to.y) / S::TWO; // F6.5.1 let p = Point::new( cos_phi * hd_x + sin_phi * hd_y, -sin_phi * hd_x + cos_phi * hd_y, ); // Sanitize the radii. // If rf > 1 it means the radii are too small for the arc to // possibly connect the end points. In this situation we scale // them up according to the formula provided by the SVG spec. // F6.6.2 let rf = p.x * p.x / (rx * rx) + p.y * p.y / (ry * ry); if rf > S::ONE { let scale = S::sqrt(rf); rx *= scale; ry *= scale; } let rxry = rx * ry; let rxpy = rx * p.y; let rypx = ry * p.x; let sum_of_sq = rxpy * rxpy + rypx * rypx; debug_assert_ne!(sum_of_sq, S::ZERO); // F6.5.2 let sign_coe = if arc.flags.large_arc == arc.flags.sweep {-S::ONE } else { S::ONE }; let coe = sign_coe * S::sqrt(S::abs((rxry * rxry - sum_of_sq) / sum_of_sq)); let transformed_cx = coe * rxpy / ry; let transformed_cy = -coe * rypx / rx; // F6.5.3 let center = point( cos_phi * transformed_cx - sin_phi * transformed_cy + hs_x, sin_phi * transformed_cx + cos_phi * transformed_cy + hs_y ); let start_v: Vector = vector( (p.x - transformed_cx) / rx, (p.y - transformed_cy) / ry, ); let end_v: Vector = vector( (-p.x - transformed_cx) / rx, (-p.y - transformed_cy) / ry, ); let two_pi = S::TWO * S::PI(); let start_angle = start_v.angle_from_x_axis(); let mut sweep_angle = (end_v.angle_from_x_axis() - start_angle).radians % two_pi; if arc.flags.sweep && sweep_angle < S::ZERO { sweep_angle += two_pi; } else if !arc.flags.sweep && sweep_angle > S::ZERO { sweep_angle -= two_pi; } Arc { center, radii: vector(rx, ry), start_angle, sweep_angle: Angle::radians(sweep_angle), x_rotation: arc.x_rotation } } /// Convert to the SVG arc notation. pub fn to_svg_arc(&self) -> SvgArc { let from = self.sample(S::ZERO); let to = self.sample(S::ONE); let flags = ArcFlags { sweep: S::abs(self.sweep_angle.get()) >= S::PI(), large_arc: self.sweep_angle.get() >= S::ZERO, }; SvgArc { from, to, radii: self.radii, x_rotation: self.x_rotation, flags, } } /// Approximate the arc with a sequence of quadratic bézier curves. #[inline] pub fn for_each_quadratic_bezier(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment) { arc_to_quadratic_beziers(self, cb); } /// Approximate the arc with a sequence of cubic bézier curves. #[inline] pub fn for_each_cubic_bezier(&self, cb: &mut F) where F: FnMut(&CubicBezierSegment) { arc_to_cubic_beziers(self, cb); } /// Sample the curve at t (expecting t between 0 and 1). #[inline] pub fn sample(&self, t: S) -> Point { let angle = self.get_angle(t); self.center + sample_ellipse(self.radii, self.x_rotation, angle).to_vector() } #[inline] pub fn x(&self, t: S) -> S { self.sample(t).x } #[inline] pub fn y(&self, t: S) -> S { self.sample(t).y } /// Sample the curve's tangent at t (expecting t between 0 and 1). #[inline] pub fn sample_tangent(&self, t: S) -> Vector { self.tangent_at_angle(self.get_angle(t)) } /// Sample the curve's angle at t (expecting t between 0 and 1). #[inline] pub fn get_angle(&self, t: S) -> Angle { self.start_angle + Angle::radians(self.sweep_angle.get() * t) } #[inline] pub fn end_angle(&self) -> Angle { self.start_angle + self.sweep_angle } #[inline] pub fn from(&self) -> Point { self.sample(S::ZERO) } #[inline] pub fn to(&self) -> Point { self.sample(S::ONE) } /// Return the sub-curve inside a given range of t. /// /// This is equivalent splitting at the range's end points. pub fn split_range(&self, t_range: Range) -> Self { let angle_1 = Angle::radians(self.sweep_angle.get() * t_range.start); let angle_2 = Angle::radians(self.sweep_angle.get() * t_range.end); Arc { center: self.center, radii: self.radii, start_angle: self.start_angle + angle_1, sweep_angle: angle_2 - angle_1, x_rotation: self.x_rotation, } } /// Split this curve into two sub-curves. pub fn split(&self, t: S) -> (Arc, Arc) { let split_angle = Angle::radians(self.sweep_angle.get() * t); ( Arc { center: self.center, radii: self.radii, start_angle: self.start_angle, sweep_angle: split_angle, x_rotation: self.x_rotation, }, Arc { center: self.center, radii: self.radii, start_angle: self.start_angle + split_angle, sweep_angle: self.sweep_angle - split_angle, x_rotation: self.x_rotation, }, ) } /// Return the curve before the split point. pub fn before_split(&self, t: S) -> Arc { let split_angle = Angle::radians(self.sweep_angle.get() * t); Arc { center: self.center, radii: self.radii, start_angle: self.start_angle, sweep_angle: split_angle, x_rotation: self.x_rotation, } } /// Return the curve after the split point. pub fn after_split(&self, t: S) -> Arc { let split_angle = Angle::radians(self.sweep_angle.get() * t); Arc { center: self.center, radii: self.radii, start_angle: self.start_angle + split_angle, sweep_angle: self.sweep_angle - split_angle, x_rotation: self.x_rotation, } } /// Swap the direction of the segment. pub fn flip(&self) -> Self { let mut arc = *self; arc.start_angle = arc.start_angle + self.sweep_angle; arc.sweep_angle = -self.sweep_angle; arc } /// Approximates the arc with a sequence of line segments. pub fn for_each_flattened(&self, tolerance: S, call_back: &mut F) where F: FnMut(Point) { crate::segment::for_each_flattened(self, tolerance, call_back); } /// Iterates through the curve invoking a callback at each point. pub fn for_each_flattened_with_t(&self, tolerance: S, call_back: &mut F) where F: FnMut(Point, S) { crate::segment::for_each_flattened_with_t(self, tolerance, call_back); } /// Finds the interval of the beginning of the curve that can be approximated with a /// line segment. pub fn flattening_step(&self, tolerance: S) -> S { // cos(theta) = (r - tolerance) / r // angle = 2 * theta // s = angle / sweep // Here we make the approximation that for small tolerance values we consider // the radius to be constant over each approximated segment. let r = (self.from() - self.center).length(); let a = S::TWO * S::acos((r - tolerance) / r); let result = S::min(a / self.sweep_angle.radians, S::ONE); if result < S::EPSILON { return S::ONE; } result } /// Returns the flattened representation of the curve as an iterator, starting *after* the /// current point. pub fn flattened(&self, tolerance: S) -> Flattened { Flattened::new(*self, tolerance) } /// Returns a conservative rectangle that contains the curve. pub fn fast_bounding_rect(&self) -> Rect { Transform::create_rotation(self.x_rotation).transform_rect( &Rect::new( self.center - self.radii, self.radii.to_size() * S::TWO ) ) } /// Returns a conservative rectangle that contains the curve. pub fn bounding_rect(&self) -> Rect { let from = self.from(); let to = self.to(); let mut min = Point::min(from, to); let mut max = Point::max(from, to); self.for_each_local_x_extremum_t(&mut|t| { let p = self.sample(t); min.x = S::min(min.x, p.x); max.x = S::max(max.x, p.x); }); self.for_each_local_y_extremum_t(&mut|t| { let p = self.sample(t); min.y = S::min(min.y, p.y); max.y = S::max(max.y, p.y); }); Rect { origin: min, size: (max - min).to_size(), } } pub fn for_each_local_x_extremum_t(&self, cb: &mut F) where F: FnMut(S) { let rx = self.radii.x; let ry = self.radii.y; let a1 = Angle::radians(-S::atan(ry * Float::tan(self.x_rotation.radians) / rx)); let a2 = Angle::pi() + a1; self.for_each_extremum_inner(a1, a2, cb); } pub fn for_each_local_y_extremum_t(&self, cb: &mut F) where F: FnMut(S) { let rx = self.radii.x; let ry = self.radii.y; let a1 = Angle::radians(S::atan(ry / (Float::tan(self.x_rotation.radians) * rx))); let a2 = Angle::pi() + a1; self.for_each_extremum_inner(a1, a2, cb); } fn for_each_extremum_inner(&self, a1: Angle, a2: Angle, cb: &mut F) where F: FnMut(S) { let sweep = self.sweep_angle.radians; let abs_sweep = S::abs(sweep); let sign = S::signum(sweep); let mut a1 = (a1 - self.start_angle).positive().radians; let mut a2 = (a2 - self.start_angle).positive().radians; if a1 * sign > a2 * sign { swap(&mut a1, &mut a2); } let two_pi = S::TWO * S::PI(); if sweep >= S::ZERO { if a1 < abs_sweep { cb(a1 / abs_sweep); } if a2 < abs_sweep { cb(a2 / abs_sweep); } } else { if a1 > two_pi - abs_sweep { cb(a1 / abs_sweep); } if a2 > two_pi - abs_sweep { cb(a2 / abs_sweep); } } } pub fn bounding_range_x(&self) -> (S, S) { let r = self.bounding_rect(); (r.min_x(), r.max_x()) } pub fn bounding_range_y(&self) -> (S, S) { let r = self.bounding_rect(); (r.min_y(), r.max_y()) } pub fn fast_bounding_range_x(&self) -> (S, S) { let r = self.fast_bounding_rect(); (r.min_x(), r.max_x()) } pub fn fast_bounding_range_y(&self) -> (S, S) { let r = self.fast_bounding_rect(); (r.min_y(), r.max_y()) } pub fn approximate_length(&self, tolerance: S) -> S { let mut from = self.from(); let mut len = S::ZERO; self.for_each_flattened(tolerance, &mut|to| { len = len + (to - from).length(); from = to; }); len } #[inline] fn tangent_at_angle(&self, angle: Angle) -> Vector { let a = angle.get(); Rotation::new(self.x_rotation).transform_vector( vector(-self.radii.x * Float::sin(a), self.radii.y * Float::cos(a)) ) } } impl Into> for SvgArc { fn into(self) -> Arc { self.to_arc() } } impl SvgArc { /// Converts this arc from endpoints to center notation. pub fn to_arc(&self) -> Arc { Arc::from_svg_arc(self) } /// Per SVG spec, this arc should be rendered as a line_to segment. /// /// Do not convert an `SvgArc` into an `arc` if this returns true. pub fn is_straight_line(&self) -> bool { S::abs(self.radii.x) <= S::EPSILON || S::abs(self.radii.y) <= S::EPSILON || self.from == self.to } /// Approximates the arc with a sequence of quadratic bézier segments. pub fn for_each_quadratic_bezier(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment) { if self.is_straight_line() { cb(&QuadraticBezierSegment{ from: self.from, ctrl: self.from, to: self.to, }); return; } Arc::from_svg_arc(self).for_each_quadratic_bezier(cb); } /// Approximates the arc with a sequence of cubic bézier segments. pub fn for_each_cubic_bezier(&self, cb: &mut F) where F: FnMut(&CubicBezierSegment) { if self.is_straight_line() { cb(&CubicBezierSegment { from: self.from, ctrl1: self.from, ctrl2: self.to, to: self.to, }); return; } Arc::from_svg_arc(self).for_each_cubic_bezier(cb); } /// Approximates the arc with a sequence of line segments. pub fn for_each_flattened)>(&self, tolerance: S, cb: &mut F) { if self.is_straight_line() { cb(self.to); return; } Arc::from_svg_arc(self).for_each_flattened(tolerance, cb); } } /// Flag parameters for arcs as described by the SVG specification. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct ArcFlags { pub large_arc: bool, pub sweep: bool, } impl Default for ArcFlags { fn default() -> Self { ArcFlags { large_arc: false, sweep: false, } } } fn arc_to_quadratic_beziers( arc: &Arc, callback: &mut F, ) where S: Scalar, F: FnMut(&QuadraticBezierSegment) { let sign = arc.sweep_angle.get().signum(); let sweep_angle = S::abs(arc.sweep_angle.get()).min(S::PI() * S::TWO); let n_steps = S::ceil(sweep_angle / S::FRAC_PI_4()); let step = Angle::radians(sweep_angle / n_steps * sign); for i in 0..cast::(n_steps).unwrap() { let a1 = arc.start_angle + step * cast(i).unwrap(); let a2 = arc.start_angle + step * cast(i+1).unwrap(); let v1 = sample_ellipse(arc.radii, arc.x_rotation, a1).to_vector(); let v2 = sample_ellipse(arc.radii, arc.x_rotation, a2).to_vector(); let from = arc.center + v1; let to = arc.center + v2; let l1 = Line { point: from, vector: arc.tangent_at_angle(a1) }; let l2 = Line { point: to, vector: arc.tangent_at_angle(a2) }; let ctrl = l2.intersection(&l1).unwrap_or(from); callback(&QuadraticBezierSegment { from , ctrl, to }); } } fn arc_to_cubic_beziers( arc: &Arc, callback: &mut F, ) where S: Scalar, F: FnMut(&CubicBezierSegment) { let sign = arc.sweep_angle.get().signum(); let sweep_angle = S::abs(arc.sweep_angle.get()).min(S::PI() * S::TWO); let n_steps = S::ceil(sweep_angle / S::FRAC_PI_2()); let step = Angle::radians(sweep_angle / n_steps * sign); for i in 0..cast::(n_steps).unwrap() { let a1 = arc.start_angle + step * cast(i).unwrap(); let a2 = arc.start_angle + step * cast(i+1).unwrap(); let v1 = sample_ellipse(arc.radii, arc.x_rotation, a1).to_vector(); let v2 = sample_ellipse(arc.radii, arc.x_rotation, a2).to_vector(); let from = arc.center + v1; let to = arc.center + v2; // From http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf // Note that the parameterization used by Arc (see sample_ellipse for // example) is the same as the eta-parameterization used at the link. let delta_a = a2 - a1; let tan_da = Float::tan(delta_a.get() * S::HALF); let alpha_sqrt = S::sqrt(S::FOUR + S::THREE * tan_da * tan_da); let alpha = Float::sin(delta_a.get()) * (alpha_sqrt - S::ONE) / S::THREE; let ctrl1 = from + arc.tangent_at_angle(a1) * alpha; let ctrl2 = to - arc.tangent_at_angle(a2) * alpha; callback(&CubicBezierSegment { from , ctrl1, ctrl2, to }); } } fn sample_ellipse(radii: Vector, x_rotation: Angle, angle: Angle) -> Point { Rotation::new(x_rotation).transform_point( point(radii.x * Float::cos(angle.get()), radii.y * Float::sin(angle.get())) ) } impl Segment for Arc { type Scalar = S; fn from(&self) -> Point { self.from() } fn to(&self) -> Point { self.to() } fn sample(&self, t: S) -> Point { self.sample(t) } fn x(&self, t: S) -> S { self.x(t) } fn y(&self, t: S) -> S { self.y(t) } fn derivative(&self, t: S) -> Vector { self.sample_tangent(t) } fn split_range(&self, t_range: Range) -> Self { self.split_range(t_range) } fn split(&self, t: S) -> (Self, Self) { self.split(t) } fn before_split(&self, t: S) -> Self { self.before_split(t) } fn after_split(&self, t: S) -> Self { self.after_split(t) } fn flip(&self) -> Self { self.flip() } fn approximate_length(&self, tolerance: S) -> S { self.approximate_length(tolerance) } } impl BoundingRect for Arc { type Scalar = S; fn bounding_rect(&self) -> Rect { self.bounding_rect() } fn fast_bounding_rect(&self) -> Rect { self.fast_bounding_rect() } fn bounding_range_x(&self) -> (S, S) { self.bounding_range_x() } fn bounding_range_y(&self) -> (S, S) { self.bounding_range_y() } fn fast_bounding_range_x(&self) -> (S, S) { self.fast_bounding_range_x() } fn fast_bounding_range_y(&self) -> (S, S) { self.fast_bounding_range_y() } } impl FlatteningStep for Arc { fn flattening_step(&self, tolerance: S) -> S { self.flattening_step(tolerance) } } #[test] fn test_from_svg_arc() { use euclid::approxeq::ApproxEq; use crate::math::vector; let flags = ArcFlags { large_arc: false, sweep: false }; test_endpoints(&SvgArc { from: point(0.0, -10.0), to: point(10.0, 0.0), radii: vector(10.0, 10.0), x_rotation: Angle::radians(0.0), flags, }); test_endpoints(&SvgArc { from: point(0.0, -10.0), to: point(10.0, 0.0), radii: vector(100.0, 10.0), x_rotation: Angle::radians(0.0), flags, }); test_endpoints(&SvgArc { from: point(0.0, -10.0), to: point(10.0, 0.0), radii: vector(10.0, 30.0), x_rotation: Angle::radians(1.0), flags, }); test_endpoints(&SvgArc { from: point(5.0, -10.0), to: point(5.0, 5.0), radii: vector(10.0, 30.0), x_rotation: Angle::radians(-2.0), flags, }); // This arc has invalid radii (too small to connect the two endpoints), // but the conversion needs to be able to cope with that. test_endpoints(&SvgArc { from: point(0.0, 0.0), to: point(80.0, 60.0), radii: vector(40.0, 40.0), x_rotation: Angle::radians(0.0), flags, }); fn test_endpoints(svg_arc: &SvgArc) { do_test_endpoints(&SvgArc { flags: ArcFlags { large_arc: false, sweep: false, }, ..svg_arc.clone() }); do_test_endpoints(&SvgArc { flags: ArcFlags { large_arc: true, sweep: false, }, ..svg_arc.clone() }); do_test_endpoints(&SvgArc { flags: ArcFlags { large_arc: false, sweep: true, }, ..svg_arc.clone() }); do_test_endpoints(&SvgArc { flags: ArcFlags { large_arc: true, sweep: true, }, ..svg_arc.clone() }); } fn do_test_endpoints(svg_arc: &SvgArc) { let eps = point(0.01, 0.01); let arc = svg_arc.to_arc(); assert!(arc.from().approx_eq_eps(&svg_arc.from, &eps), "unexpected arc.from: {:?} == {:?}, flags: {:?}", arc.from(), svg_arc.from, svg_arc.flags, ); assert!(arc.to().approx_eq_eps(&svg_arc.to, &eps), "unexpected arc.from: {:?} == {:?}, flags: {:?}", arc.to(), svg_arc.to, svg_arc.flags, ); } } #[test] fn test_to_quadratics_and_cubics() { use euclid::approxeq::ApproxEq; fn do_test(arc: &Arc, expected_quadratic_count: u32, expected_cubic_count: u32) { let last = arc.to(); { let mut prev = arc.from(); let mut count = 0; arc.for_each_quadratic_bezier(&mut |c| { assert!(c.from.approx_eq(&prev)); prev = c.to; count += 1; }); assert!(prev.approx_eq(&last)); assert_eq!(count, expected_quadratic_count); } { let mut prev = arc.from(); let mut count = 0; arc.for_each_cubic_bezier(&mut|c| { assert!(c.from.approx_eq(&prev)); prev = c.to; count += 1; }); assert!(prev.approx_eq(&last)); assert_eq!(count, expected_cubic_count); } } do_test( &Arc { center: point(2.0, 3.0), radii: vector(10.0, 3.0), start_angle: Angle::radians(0.1), sweep_angle: Angle::radians(3.0), x_rotation: Angle::radians(0.5), }, 4, 2 ); do_test( &Arc { center: point(4.0, 5.0), radii: vector(3.0, 5.0), start_angle: Angle::radians(2.0), sweep_angle: Angle::radians(-3.0), x_rotation: Angle::radians(1.3), }, 4, 2 ); do_test( &Arc { center: point(0.0, 0.0), radii: vector(100.0, 0.01), start_angle: Angle::radians(-1.0), sweep_angle: Angle::radians(0.1), x_rotation: Angle::radians(0.3), }, 1, 1 ); do_test( &Arc { center: point(0.0, 0.0), radii: vector(1.0, 1.0), start_angle: Angle::radians(3.0), sweep_angle: Angle::radians(-0.1), x_rotation: Angle::radians(-0.3), }, 1, 1 ); } #[test] fn test_bounding_rect() { use euclid::approxeq::ApproxEq; use crate::math::rect; fn approx_eq(r1: Rect, r2: Rect) -> bool { if !r1.min_x().approx_eq(&r2.min_x()) || !r1.max_x().approx_eq(&r2.max_x()) || !r1.min_y().approx_eq(&r2.min_y()) || !r1.max_y().approx_eq(&r2.max_y()) { println!("\n left: {:?}\n right: {:?}", r1, r2); return false; } true } let r = Arc { center: point(0.0, 0.0), radii: vector(1.0, 1.0), start_angle: Angle::radians(0.0), sweep_angle: Angle::pi(), x_rotation: Angle::zero(), }.bounding_rect(); assert!(approx_eq(r, rect(-1.0, 0.0, 2.0, 1.0))); let r = Arc { center: point(0.0, 0.0), radii: vector(1.0, 1.0), start_angle: Angle::radians(0.0), sweep_angle: Angle::pi(), x_rotation: Angle::pi(), }.bounding_rect(); assert!(approx_eq(r, rect(-1.0, -1.0, 2.0, 1.0))); let r = Arc { center: point(0.0, 0.0), radii: vector(2.0, 1.0), start_angle: Angle::radians(0.0), sweep_angle: Angle::pi(), x_rotation: Angle::pi() * 0.5, }.bounding_rect(); assert!(approx_eq(r, rect(-1.0, -2.0, 1.0, 4.0))); let r = Arc { center: point(1.0, 1.0), radii: vector(1.0, 1.0), start_angle: Angle::pi(), sweep_angle: Angle::pi(), x_rotation: -Angle::pi() * 0.25, }.bounding_rect(); assert!(approx_eq(r, rect(0.0, 0.0, 1.707107, 1.707107))); let mut angle = Angle::zero(); for _ in 0..10 { println!("angle: {:?}", angle); let r = Arc { center: point(0.0, 0.0), radii: vector(4.0, 4.0), start_angle: angle, sweep_angle: Angle::pi() * 2.0, x_rotation: Angle::pi() * 0.25, }.bounding_rect(); assert!(approx_eq(r, rect(-4.0, -4.0, 8.0, 8.0))); angle += Angle::pi() * 2.0 / 10.0; } let mut angle = Angle::zero(); for _ in 0..10 { println!("angle: {:?}", angle); let r = Arc { center: point(0.0, 0.0), radii: vector(4.0, 4.0), start_angle: Angle::zero(), sweep_angle: Angle::pi() * 2.0, x_rotation: angle, }.bounding_rect(); assert!(approx_eq(r, rect(-4.0, -4.0, 8.0, 8.0))); angle += Angle::pi() * 2.0 / 10.0; } } #[test] fn negative_flattening_step() { // These parameters were running into a precision issue which led the // flattening step to never converge towards 1 and cause an infinite loop. let arc = Arc { center: point(-100.0, -150.0), radii: vector(50.0, 50.0), start_angle: Angle::radians(0.982944787), sweep_angle: Angle::radians(-898.0), x_rotation: Angle::zero(), }; arc.for_each_flattened(0.100000001, &mut|_|{}); } lyon_geom-0.15.0/src/cubic_bezier.rs010064400017500001750000001176401360117331400156000ustar0000000000000000pub use crate::flatten_cubic::Flattened; use crate::{Line, LineSegment, LineEquation, QuadraticBezierSegment}; use crate::scalar::Scalar; use crate::generic_math::{Point, Vector, Rect, rect}; use crate::flatten_cubic::{flatten_cubic_bezier, flatten_cubic_bezier_with_t, find_cubic_bezier_inflection_points}; use crate::cubic_to_quadratic::*; use crate::cubic_bezier_intersections::cubic_bezier_intersections_t; use crate::monotonic::Monotonic; use crate::utils::{min_max, cubic_polynomial_roots}; use crate::segment::{Segment, BoundingRect}; use crate::traits::Transformation; use arrayvec::ArrayVec; use std::ops::Range; use std::cmp::Ordering::{Less, Equal, Greater}; /// A 2d curve segment defined by four points: the beginning of the segment, two control /// points and the end of the segment. /// /// The curve is defined by equation:² /// ```∀ t ∈ [0..1], P(t) = (1 - t)³ * from + 3 * (1 - t)² * t * ctrl1 + 3 * t² * (1 - t) * ctrl2 + t³ * to``` #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct CubicBezierSegment { pub from: Point, pub ctrl1: Point, pub ctrl2: Point, pub to: Point, } impl CubicBezierSegment { /// Sample the curve at t (expecting t between 0 and 1). pub fn sample(&self, t: S) -> Point { let t2 = t * t; let t3 = t2 * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; let one_t3 = one_t2 * one_t; return self.from * one_t3 + self.ctrl1.to_vector() * S::THREE * one_t2 * t + self.ctrl2.to_vector() * S::THREE * one_t * t2 + self.to.to_vector() * t3; } /// Sample the x coordinate of the curve at t (expecting t between 0 and 1). pub fn x(&self, t: S) -> S { let t2 = t * t; let t3 = t2 * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; let one_t3 = one_t2 * one_t; return self.from.x * one_t3 + self.ctrl1.x * S::THREE * one_t2 * t + self.ctrl2.x * S::THREE * one_t * t2 + self.to.x * t3; } /// Sample the y coordinate of the curve at t (expecting t between 0 and 1). pub fn y(&self, t: S) -> S { let t2 = t * t; let t3 = t2 * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; let one_t3 = one_t2 * one_t; return self.from.y * one_t3 + self.ctrl1.y * S::THREE * one_t2 * t + self.ctrl2.y * S::THREE * one_t * t2 + self.to.y * t3; } /// Return the parameter values corresponding to a given x coordinate. /// See also solve_t_for_x for monotonic curves. pub fn solve_t_for_x(&self, x: S) -> ArrayVec<[S; 3]> { if self.is_a_point(S::ZERO) || (self.non_point_is_linear(S::ZERO) && self.from.x == self.to.x) { return ArrayVec::new(); } self.parameters_for_xy_value(x, self.from.x, self.ctrl1.x, self.ctrl2.x, self.to.x) } /// Return the parameter values corresponding to a given y coordinate. /// See also solve_t_for_y for monotonic curves. pub fn solve_t_for_y(&self, y: S) -> ArrayVec<[S; 3]> { if self.is_a_point(S::ZERO) || (self.non_point_is_linear(S::ZERO) && self.from.y == self.to.y) { return ArrayVec::new(); } self.parameters_for_xy_value(y, self.from.y, self.ctrl1.y, self.ctrl2.y, self.to.y) } fn parameters_for_xy_value( &self, value: S, from: S, ctrl1: S, ctrl2: S, to: S, ) -> ArrayVec<[S; 3]> { let mut result = ArrayVec::new(); let a = -from + S::THREE * ctrl1 - S::THREE * ctrl2 + to; let b = S::THREE * from - S::SIX * ctrl1 + S::THREE * ctrl2; let c = -S::THREE * from + S::THREE * ctrl1; let d = from - value; let roots = cubic_polynomial_roots(a, b, c, d); for root in roots { if root > S::ZERO && root < S::ONE { result.push(root); } } result } #[inline] fn derivative_coefficients(&self, t: S) -> (S, S, S, S) { let t2 = t*t; ( - S::THREE * t2 + S::SIX * t - S::THREE, S::NINE * t2 - S::value(12.0) * t + S::THREE, - S::NINE * t2 + S::SIX * t, S::THREE * t2 ) } /// Sample the curve's derivative at t (expecting t between 0 and 1). pub fn derivative(&self, t: S) -> Vector { let (c0, c1, c2, c3) = self.derivative_coefficients(t); self.from.to_vector() * c0 + self.ctrl1.to_vector() * c1 + self.ctrl2.to_vector() * c2 + self.to.to_vector() * c3 } /// Sample the x coordinate of the curve's derivative at t (expecting t between 0 and 1). pub fn dx(&self, t: S) -> S { let (c0, c1, c2, c3) = self.derivative_coefficients(t); self.from.x * c0 + self.ctrl1.x * c1 + self.ctrl2.x * c2 + self.to.x * c3 } /// Sample the y coordinate of the curve's derivative at t (expecting t between 0 and 1). pub fn dy(&self, t: S) -> S { let (c0, c1, c2, c3) = self.derivative_coefficients(t); self.from.y * c0 + self.ctrl1.y * c1 + self.ctrl2.y * c2 + self.to.y * c3 } /// Return the sub-curve inside a given range of t. /// /// This is equivalent to splitting at the range's end points. pub fn split_range(&self, t_range: Range) -> Self { let (t0, t1) = (t_range.start, t_range.end); let from = self.sample(t0); let to = self.sample(t1); let d = QuadraticBezierSegment { from: (self.ctrl1 - self.from).to_point(), ctrl: (self.ctrl2 - self.ctrl1).to_point(), to: (self.to - self.ctrl2).to_point(), }; let dt = t1 - t0; let ctrl1 = from + d.sample(t0).to_vector() * dt; let ctrl2 = to - d.sample(t1).to_vector() * dt; CubicBezierSegment { from, ctrl1, ctrl2, to } } /// Split this curve into two sub-curves. pub fn split(&self, t: S) -> (CubicBezierSegment, CubicBezierSegment) { let ctrl1a = self.from + (self.ctrl1 - self.from) * t; let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; let ctrl1aaa = ctrl1aa + (ctrl2aa - ctrl1aa) * t; let to = self.to; return (CubicBezierSegment { from: self.from, ctrl1: ctrl1a, ctrl2: ctrl1aa, to: ctrl1aaa, }, CubicBezierSegment { from: ctrl1aaa, ctrl1: ctrl2aa, ctrl2: ctrl3a, to: to, }); } /// Return the curve before the split point. pub fn before_split(&self, t: S) -> CubicBezierSegment { let ctrl1a = self.from + (self.ctrl1 - self.from) * t; let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; let ctrl1aaa = ctrl1aa + (ctrl2aa - ctrl1aa) * t; return CubicBezierSegment { from: self.from, ctrl1: ctrl1a, ctrl2: ctrl1aa, to: ctrl1aaa, }; } /// Return the curve after the split point. pub fn after_split(&self, t: S) -> CubicBezierSegment { let ctrl1a = self.from + (self.ctrl1 - self.from) * t; let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; return CubicBezierSegment { from: ctrl1aa + (ctrl2aa - ctrl1aa) * t, ctrl1: ctrl2a + (ctrl3a - ctrl2a) * t, ctrl2: ctrl3a, to: self.to, }; } #[inline] pub fn baseline(&self) -> LineSegment { LineSegment { from: self.from, to: self.to } } pub fn is_linear(&self, tolerance: S) -> bool { let epsilon = S::EPSILON; if (self.from - self.to).square_length() < epsilon { return false; } self.non_point_is_linear(tolerance) } #[inline] fn non_point_is_linear(&self, tolerance: S) -> bool { let line = self.baseline().to_line().equation(); line.distance_to_point(&self.ctrl1) <= tolerance && line.distance_to_point(&self.ctrl2) <= tolerance } pub(crate) fn is_a_point(&self, tolerance: S) -> bool { let tolerance_squared = tolerance * tolerance; // Use <= so that tolerance can be zero. (self.from - self.to).square_length() <= tolerance_squared && (self.from - self.ctrl1).square_length() <= tolerance_squared && (self.to - self.ctrl2).square_length() <= tolerance_squared } /// Computes the signed distances (min <= 0 and max >= 0) from the baseline of this /// curve to its two "fat line" boundary lines. /// /// A fat line is two conservative lines between which the segment /// is fully contained. pub(crate) fn fat_line_min_max(&self) -> (S, S) { let baseline = self.baseline().to_line().equation(); let (d1, d2) = min_max( baseline.signed_distance_to_point(&self.ctrl1), baseline.signed_distance_to_point(&self.ctrl2), ); let factor = if (d1 * d2) > S::ZERO { S::THREE / S::FOUR } else { S::FOUR / S::NINE }; let d_min = factor * S::min(d1, S::ZERO); let d_max = factor * S::max(d2, S::ZERO); (d_min, d_max) } /// Computes a "fat line" of this segment. /// /// A fat line is two conservative lines between which the segment /// is fully contained. pub fn fat_line(&self) -> (LineEquation, LineEquation) { let baseline = self.baseline().to_line().equation(); let (d1, d2) = self.fat_line_min_max(); (baseline.offset(d1), baseline.offset(d2)) } /// Applies the transform to this curve and returns the results. #[inline] pub fn transformed>(&self, transform: &T) -> Self { CubicBezierSegment { from: transform.transform_point(self.from), ctrl1: transform.transform_point(self.ctrl1), ctrl2: transform.transform_point(self.ctrl2), to: transform.transform_point(self.to) } } /// Swap the beginning and the end of the segment. pub fn flip(&self) -> Self { CubicBezierSegment { from: self.to, ctrl1: self.ctrl2, ctrl2: self.ctrl1, to: self.from, } } /// Returns the flattened representation of the curve as an iterator, starting *after* the /// current point. pub fn flattened(&self, tolerance: S) -> Flattened { Flattened::new(*self, tolerance) } /// Invokes a callback between each monotonic part of the segment. pub fn for_each_monotonic_t(&self, mut cb: F) where F: FnMut(S), { let mut x_extrema: ArrayVec<[S; 3]> = ArrayVec::new(); self.for_each_local_x_extremum_t(&mut|t| { x_extrema.push(t) }); let mut y_extrema: ArrayVec<[S; 3]> = ArrayVec::new(); self.for_each_local_y_extremum_t(&mut|t| { y_extrema.push(t) }); let mut it_x = x_extrema.iter().cloned(); let mut it_y = y_extrema.iter().cloned(); let mut tx = it_x.next(); let mut ty = it_y.next(); loop { let next = match (tx, ty) { (Some(a), Some(b)) => { if a < b { tx = it_x.next(); a } else { ty = it_y.next(); b } } (Some(a), None) => { tx = it_x.next(); a } (None, Some(b)) => { ty = it_y.next(); b } (None, None) => { return } }; if next > S::ZERO && next < S::ONE { cb(next); } } } /// Invokes a callback for each monotonic part of the segment.. pub fn for_each_monotonic_range(&self, mut cb: F) where F: FnMut(Range), { let mut t0 = S::ZERO; self.for_each_monotonic_t(|t| { cb(t0..t); t0 = t; }); cb(t0..S::ONE); } /// Approximates the cubic bézier curve with sequence of quadratic ones, /// invoking a callback at each step. pub fn for_each_quadratic_bezier(&self, tolerance: S, cb: &mut F) where F: FnMut(&QuadraticBezierSegment) { cubic_to_quadratics(self, tolerance, cb); } /// Approximates the cubic bézier curve with sequence of monotonic quadratic /// ones, invoking a callback at each step. pub fn for_each_monotonic_quadratic(&self, tolerance: S, cb: &mut F) where F: FnMut(&Monotonic>) { cubic_to_monotonic_quadratics(self, tolerance, cb); } /// Iterates through the curve invoking a callback at each point. pub fn for_each_flattened)>(&self, tolerance: S, call_back: &mut F) { flatten_cubic_bezier(*self, tolerance, call_back); } /// Iterates through the curve invoking a callback at each point. pub fn for_each_flattened_with_t, S)>(&self, tolerance: S, call_back: &mut F) { flatten_cubic_bezier_with_t(*self, tolerance, call_back); } /// Compute the length of the segment using a flattened approximation. pub fn approximate_length(&self, tolerance: S) -> S { let mut from = self.from; let mut len = S::ZERO; self.for_each_flattened(tolerance, &mut|to| { len = len + (to - from).length(); from = to; }); len } pub fn for_each_inflection_t(&self, cb: &mut F) where F: FnMut(S) { find_cubic_bezier_inflection_points(self, cb); } /// Return local x extrema or None if this curve is monotonic. /// /// This returns the advancements along the curve, not the actual x position. pub fn for_each_local_x_extremum_t(&self, cb: &mut F) where F: FnMut(S) { Self::for_each_local_extremum(self.from.x, self.ctrl1.x, self.ctrl2.x, self.to.x, cb) } /// Return local y extrema or None if this curve is monotonic. /// /// This returns the advancements along the curve, not the actual y position. pub fn for_each_local_y_extremum_t(&self, cb: &mut F) where F: FnMut(S) { Self::for_each_local_extremum(self.from.y, self.ctrl1.y, self.ctrl2.y, self.to.y, cb) } fn for_each_local_extremum(p0: S, p1: S, p2: S, p3: S, cb: &mut F) where F: FnMut(S) { // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation // The derivative of a cubic bezier curve is a curve representing a second degree polynomial function // f(x) = a * x² + b * x + c such as : let a = S::THREE * (p3 + S::THREE * (p1 - p2) - p0); let b = S::SIX * (p2 - S::TWO * p1 + p0); let c = S::THREE * (p1 - p0); fn in_range(t: S) -> bool { t > S::ZERO && t < S::ONE } // If the derivative is a linear function if a == S::ZERO { if b != S::ZERO { let t = -c / b; if in_range(t) { cb(t); } } return; } let discriminant = b * b - S::FOUR * a * c; // There is no Real solution for the equation if discriminant < S::ZERO { return; } // There is one Real solution for the equation if discriminant == S::ZERO { let t = -b / (S::TWO * a); if in_range(t) { cb(t); } return; } // There are two Real solutions for the equation let discriminant_sqrt = discriminant.sqrt(); let first_extremum = (-b - discriminant_sqrt) / (S::TWO * a); let second_extremum = (-b + discriminant_sqrt) / (S::TWO * a); if in_range(first_extremum) { cb(first_extremum); } if in_range(second_extremum) { cb(second_extremum); } } /// Find the advancement of the y-most position in the curve. /// /// This returns the advancement along the curve, not the actual y position. pub fn y_maximum_t(&self) -> S { let mut max_t = S::ZERO; let mut max_y = self.from.y; if self.to.y > max_y { max_t = S::ONE; max_y = self.to.y; } self.for_each_local_y_extremum_t(&mut|t| { let y = self.y(t); if y > max_y { max_t = t; max_y = y; } }); return max_t; } /// Find the advancement of the y-least position in the curve. /// /// This returns the advancement along the curve, not the actual y position. pub fn y_minimum_t(&self) -> S { let mut min_t = S::ZERO; let mut min_y = self.from.y; if self.to.y < min_y { min_t = S::ONE; min_y = self.to.y; } self.for_each_local_y_extremum_t(&mut |t| { let y = self.y(t); if y < min_y { min_t = t; min_y = y; } }); return min_t; } /// Find the advancement of the x-most position in the curve. /// /// This returns the advancement along the curve, not the actual x position. pub fn x_maximum_t(&self) -> S { let mut max_t = S::ZERO; let mut max_x = self.from.x; if self.to.x > max_x { max_t = S::ONE; max_x = self.to.x; } self.for_each_local_x_extremum_t(&mut |t| { let x = self.x(t); if x > max_x { max_t = t; max_x = x; } }); return max_t; } /// Find the x-least position in the curve. pub fn x_minimum_t(&self) -> S { let mut min_t = S::ZERO; let mut min_x = self.from.x; if self.to.x < min_x { min_t = S::ONE; min_x = self.to.x; } self.for_each_local_x_extremum_t(&mut |t| { let x = self.x(t); if x < min_x { min_t = t; min_x = x; } }); return min_t; } /// Returns a conservative rectangle the curve is contained in. /// /// This method is faster than `bounding_rect` but more conservative. pub fn fast_bounding_rect(&self) -> Rect { let (min_x, max_x) = self.fast_bounding_range_x(); let (min_y, max_y) = self.fast_bounding_range_y(); return rect(min_x, min_y, max_x - min_x, max_y - min_y); } /// Returns a conservative range of x this curve is contained in. #[inline] pub fn fast_bounding_range_x(&self) -> (S, S) { let min_x = self.from.x.min(self.ctrl1.x).min(self.ctrl2.x).min(self.to.x); let max_x = self.from.x.max(self.ctrl1.x).max(self.ctrl2.x).max(self.to.x); (min_x, max_x) } /// Returns a conservative range of y this curve is contained in. #[inline] pub fn fast_bounding_range_y(&self) -> (S, S) { let min_y = self.from.y.min(self.ctrl1.y).min(self.ctrl2.y).min(self.to.y); let max_y = self.from.y.max(self.ctrl1.y).max(self.ctrl2.y).max(self.to.y); (min_y, max_y) } /// Returns the smallest rectangle the curve is contained in pub fn bounding_rect(&self) -> Rect { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); return rect(min_x, min_y, max_x - min_x, max_y - min_y); } /// Returns the smallest range of x this curve is contained in. #[inline] pub fn bounding_range_x(&self) -> (S, S) { let min_x = self.x(self.x_minimum_t()); let max_x = self.x(self.x_maximum_t()); (min_x, max_x) } /// Returns the smallest range of y this curve is contained in. #[inline] pub fn bounding_range_y(&self) -> (S, S) { let min_y = self.y(self.y_minimum_t()); let max_y = self.y(self.y_maximum_t()); (min_y, max_y) } /// Cast this curve into a monotonic curve without checking that the monotonicity /// assumption is correct. pub fn assume_monotonic(&self) -> MonotonicCubicBezierSegment { MonotonicCubicBezierSegment { segment: *self } } /// Returns whether this segment is monotonic on the x axis. pub fn is_x_monotonic(&self) -> bool { let mut found = false; self.for_each_local_x_extremum_t(&mut |_|{ found = true; }); !found } /// Returns whether this segment is monotonic on the y axis. pub fn is_y_monotonic(&self) -> bool { let mut found = false; self.for_each_local_y_extremum_t(&mut |_|{ found = true; }); !found } /// Returns whether this segment is fully monotonic. pub fn is_monotonic(&self) -> bool { self.is_x_monotonic() && self.is_y_monotonic() } /// Computes the intersections (if any) between this segment and another one. /// /// The result is provided in the form of the `t` parameters of each point along the curves. To /// get the intersection points, sample the curves at the corresponding values. /// /// Returns endpoint intersections where an endpoint intersects the interior of the other curve, /// but not endpoint/endpoint intersections. /// /// Returns no intersections if either curve is a point. pub fn cubic_intersections_t(&self, curve: &CubicBezierSegment) -> ArrayVec<[(S, S); 9]> { cubic_bezier_intersections_t(self, curve) } /// Computes the intersection points (if any) between this segment and another one. pub fn cubic_intersections(&self, curve: &CubicBezierSegment) -> ArrayVec<[Point; 9]> { let intersections = self.cubic_intersections_t(curve); let mut result_with_repeats = ArrayVec::<[_; 9]>::new(); for (t, _) in intersections { result_with_repeats.push(self.sample(t)); } // We can have up to nine "repeated" values here (for example: two lines, each of which // overlaps itself 3 times, intersecting in their 3-fold overlaps). We make an effort to // dedupe the results, but that's hindered by not having predictable control over how far // the repeated intersections can be from each other (and then by the fact that true // intersections can be arbitrarily close), so the results will never be perfect. let pair_cmp = |s: &Point, t: &Point| { if s.x < t.x || (s.x == t.x && s.y < t.y) { Less } else if s.x == t.x && s.y == t.y { Equal } else { Greater } }; result_with_repeats.sort_unstable_by(pair_cmp); if result_with_repeats.len() <= 1 { return result_with_repeats; } #[inline] fn dist_sq(p1: &Point, p2: &Point) -> S { (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) } let epsilon_squared = S::EPSILON * S::EPSILON; let mut result = ArrayVec::new(); let mut reference_intersection = &result_with_repeats[0]; result.push(*reference_intersection); for i in 1..result_with_repeats.len() { let intersection = &result_with_repeats[i]; if dist_sq(reference_intersection, intersection) < epsilon_squared { continue; } else { result.push(*intersection); reference_intersection = intersection; } } result } /// Computes the intersections (if any) between this segment a quadratic bézier segment. /// /// The result is provided in the form of the `t` parameters of each point along the curves. To /// get the intersection points, sample the curves at the corresponding values. /// /// Returns endpoint intersections where an endpoint intersects the interior of the other curve, /// but not endpoint/endpoint intersections. /// /// Returns no intersections if either curve is a point. pub fn quadratic_intersections_t(&self, curve: &QuadraticBezierSegment) -> ArrayVec<[(S, S); 9]> { self.cubic_intersections_t(&curve.to_cubic()) } /// Computes the intersection points (if any) between this segment and a quadratic bézier segment. pub fn quadratic_intersections(&self, curve: &QuadraticBezierSegment) -> ArrayVec<[Point; 9]> { self.cubic_intersections(&curve.to_cubic()) } /// Computes the intersections (if any) between this segment and a line. /// /// The result is provided in the form of the `t` parameters of each /// point along curve. To get the intersection points, sample the curve /// at the corresponding values. pub fn line_intersections_t(&self, line: &Line) -> ArrayVec<[S; 3]> { if line.vector.square_length() < S::EPSILON { return ArrayVec::new(); } let from = self.from.to_vector(); let ctrl1 = self.ctrl1.to_vector(); let ctrl2 = self.ctrl2.to_vector(); let to = self.to.to_vector(); let p1 = to - from + (ctrl1 - ctrl2) * S::THREE; let p2 = from * S::THREE + (ctrl2 - ctrl1 * S::TWO) * S::THREE; let p3 = (ctrl1 - from) * S::THREE; let p4 = from; let c = line.point.y * line.vector.x - line.point.x * line.vector.y; let roots = cubic_polynomial_roots( line.vector.y * p1.x - line.vector.x * p1.y, line.vector.y * p2.x - line.vector.x * p2.y, line.vector.y * p3.x - line.vector.x * p3.y, line.vector.y * p4.x - line.vector.x * p4.y + c, ); let mut result = ArrayVec::new(); for root in roots { if root > S::ZERO && root < S::ONE { result.push(root); } } return result; } /// Computes the intersection points (if any) between this segment and a line. pub fn line_intersections(&self, line: &Line) -> ArrayVec<[Point; 3]> { let intersections = self.line_intersections_t(&line); let mut result = ArrayVec::new(); for t in intersections { result.push(self.sample(t)); } return result; } /// Computes the intersections (if any) between this segment and a line segment. /// /// The result is provided in the form of the `t` parameters of each /// point along curve and segment. To get the intersection points, sample /// the segments at the corresponding values. pub fn line_segment_intersections_t(&self, segment: &LineSegment) -> ArrayVec<[(S, S); 3]> { if !self.fast_bounding_rect().intersects(&segment.bounding_rect()) { return ArrayVec::new(); } let intersections = self.line_intersections_t(&segment.to_line()); let mut result = ArrayVec::new(); if intersections.len() == 0 { return result; } let seg_is_mostly_vertical = S::abs(segment.from.y - segment.to.y) >= S::abs(segment.from.x - segment.to.x); let (seg_long_axis_min, seg_long_axis_max) = if seg_is_mostly_vertical { segment.bounding_range_y() } else { segment.bounding_range_x() }; for t in intersections { let intersection_xy = if seg_is_mostly_vertical { self.y(t) } else { self.x(t) }; if intersection_xy >= seg_long_axis_min && intersection_xy <= seg_long_axis_max { let t2 = (self.sample(t) - segment.from).length() / segment.length(); result.push((t, t2)); } } result } #[inline] pub fn from(&self) -> Point { self.from } #[inline] pub fn to(&self) -> Point { self.to } pub fn line_segment_intersections(&self, segment: &LineSegment) -> ArrayVec<[Point; 3]> { let intersections = self.line_segment_intersections_t(&segment); let mut result = ArrayVec::new(); for (t, _) in intersections { result.push(self.sample(t)); } return result; } } impl Segment for CubicBezierSegment { impl_segment!(S); } impl BoundingRect for CubicBezierSegment { type Scalar = S; fn bounding_rect(&self) -> Rect { self.bounding_rect() } fn fast_bounding_rect(&self) -> Rect { self.fast_bounding_rect() } fn bounding_range_x(&self) -> (S, S) { self.bounding_range_x() } fn bounding_range_y(&self) -> (S, S) { self.bounding_range_y() } fn fast_bounding_range_x(&self) -> (S, S) { self.fast_bounding_range_x() } fn fast_bounding_range_y(&self) -> (S, S) { self.fast_bounding_range_y() } } /// A monotonically increasing in x and y quadratic bézier curve segment pub type MonotonicCubicBezierSegment = Monotonic>; #[test] fn fast_bounding_rect_for_cubic_bezier_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 1.0), ctrl2: Point::new(1.5, -1.0), to: Point::new(2.0, 0.0), }; let expected_bounding_rect = rect(0.0, -1.0, 2.0, 2.0); let actual_bounding_rect = a.fast_bounding_rect(); assert!(expected_bounding_rect == actual_bounding_rect) } #[test] fn minimum_bounding_rect_for_cubic_bezier_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 2.0), ctrl2: Point::new(1.5, -2.0), to: Point::new(2.0, 0.0), }; let expected_bigger_bounding_rect: Rect = rect(0.0, -0.6, 2.0, 1.2); let expected_smaller_bounding_rect: Rect = rect(0.1, -0.5, 1.9, 1.0); let actual_minimum_bounding_rect: Rect = a.bounding_rect(); assert!(expected_bigger_bounding_rect.contains_rect(&actual_minimum_bounding_rect)); assert!(actual_minimum_bounding_rect.contains_rect(&expected_smaller_bounding_rect)); } #[test] fn y_maximum_t_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 1.0), ctrl2: Point::new(1.5, 1.0), to: Point::new(2.0, 2.0), }; let expected_y_maximum = 1.0; let actual_y_maximum = a.y_maximum_t(); assert!(expected_y_maximum == actual_y_maximum) } #[test] fn y_minimum_t_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 1.0), ctrl2: Point::new(1.5, 1.0), to: Point::new(2.0, 0.0), }; let expected_y_minimum = 0.0; let actual_y_minimum = a.y_minimum_t(); assert!(expected_y_minimum == actual_y_minimum) } #[test] fn y_extrema_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(1.0, 2.0), ctrl2: Point::new(2.0, 2.0), to: Point::new(3.0, 0.0), }; let mut n: u32 = 0; a.for_each_local_y_extremum_t(&mut |t| { assert_eq!(t, 0.5); n += 1; }); assert_eq!(n, 1); } #[test] fn x_extrema_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(1.0, 2.0), ctrl2: Point::new(1.0, 2.0), to: Point::new(0.0, 0.0), }; let mut n: u32 = 0; a.for_each_local_x_extremum_t(&mut |t| { assert_eq!(t, 0.5); n += 1; }); assert_eq!(n, 1); } #[test] fn x_maximum_t_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 1.0), ctrl2: Point::new(1.5, 1.0), to: Point::new(2.0, 0.0), }; let expected_x_maximum = 1.0; let actual_x_maximum = a.x_maximum_t(); assert!(expected_x_maximum == actual_x_maximum) } #[test] fn x_minimum_t_for_simple_cubic_segment() { let a = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.5, 1.0), ctrl2: Point::new(1.5, 1.0), to: Point::new(2.0, 0.0), }; let expected_x_minimum = 0.0; let actual_x_minimum = a.x_minimum_t(); assert!(expected_x_minimum == actual_x_minimum) } #[test] fn derivatives() { let c1 = CubicBezierSegment { from: Point::new(1.0, 1.0,), ctrl1: Point::new(1.0, 2.0,), ctrl2: Point::new(2.0, 1.0,), to: Point::new(2.0, 2.0,), }; assert_eq!(c1.dx(0.0), 0.0); assert_eq!(c1.dx(1.0), 0.0); assert_eq!(c1.dy(0.5), 0.0); } #[test] fn monotonic_solve_t_for_x() { let c1 = CubicBezierSegment { from: Point::new(1.0, 1.0), ctrl1: Point::new(1.0, 2.0), ctrl2: Point::new(2.0, 1.0), to: Point::new(2.0, 2.0), }; let tolerance = 0.0001; for i in 0..10u32 { let t = i as f32 / 10.0; let p = c1.sample(t); let t2 = c1.assume_monotonic().solve_t_for_x(p.x, 0.0..1.0, tolerance); // t should be pretty close to t2 but the only guarantee we have and can test // against is that x(t) - x(t2) is within the specified tolerance threshold. let x_diff = c1.x(t) - c1.x(t2); assert!(f32::abs(x_diff) <= tolerance); } } #[test] fn fat_line() { use crate::math::point; let c1 = CubicBezierSegment { from: point(1.0f32, 2.0), ctrl1: point(1.0, 3.0), ctrl2: point(11.0, 11.0), to: point(11.0, 12.0), }; let (l1, l2) = c1.fat_line(); for i in 0..100 { let t = i as f32 / 99.0; assert!(l1.signed_distance_to_point(&c1.sample(t)) >= -0.000001); assert!(l2.signed_distance_to_point(&c1.sample(t)) <= 0.000001); } let c2 = CubicBezierSegment { from: point(1.0f32, 2.0), ctrl1: point(1.0, 3.0), ctrl2: point(11.0, 14.0), to: point(11.0, 12.0), }; let (l1, l2) = c2.fat_line(); for i in 0..100 { let t = i as f32 / 99.0; assert!(l1.signed_distance_to_point(&c2.sample(t)) >= -0.000001); assert!(l2.signed_distance_to_point(&c2.sample(t)) <= 0.000001); } let c3 = CubicBezierSegment { from: point(0.0f32, 1.0), ctrl1: point(0.5, 0.0), ctrl2: point(0.5, 0.0), to: point(1.0, 1.0), }; let (l1, l2) = c3.fat_line(); for i in 0..100 { let t = i as f32 / 99.0; assert!(l1.signed_distance_to_point(&c3.sample(t)) >= -0.000001); assert!(l2.signed_distance_to_point(&c3.sample(t)) <= 0.000001); } } #[test] fn is_linear() { let mut angle = 0.0; let center = Point::new(1000.0, -700.0); for _ in 0..100 { for i in 0..10 { for j in 0..10 { let (sin, cos) = f64::sin_cos(angle); let endpoint = Vector::new(cos * 100.0, sin * 100.0); let curve = CubicBezierSegment { from: center - endpoint, ctrl1: center + endpoint.lerp(-endpoint, i as f64 / 9.0), ctrl2: center + endpoint.lerp(-endpoint, j as f64 / 9.0), to: center + endpoint, }; assert!(curve.is_linear(1e-10)); } } angle += 0.001; } } #[test] fn test_monotonic() { use crate::math::point; let curve = CubicBezierSegment { from: point(1.0, 1.0), ctrl1: point(10.0, 2.0), ctrl2: point(1.0, 3.0), to: point(10.0, 4.0), }; curve.for_each_monotonic_range(&mut|range| { let sub_curve = curve.split_range(range); assert!(sub_curve.is_monotonic()); }); } #[test] fn test_line_segment_intersections() { use crate::math::point; fn assert_approx_eq(a: ArrayVec<[(f32, f32); 3]>, b: &[(f32, f32)], epsilon: f32) { for i in 0..a.len() { if f32::abs(a[i].0 - b[i].0) > epsilon || f32::abs(a[i].1 - b[i].1) > epsilon { println!("{:?} != {:?}", a, b); } assert!((a[i].0 - b[i].0).abs() <= epsilon && (a[i].1 - b[i].1).abs() <= epsilon); } assert_eq!(a.len(), b.len()); } let epsilon = 0.0001; // Make sure we find intersections with horizontal and vertical lines. let curve1 = CubicBezierSegment { from: point(-1.0, -1.0), ctrl1: point(0.0, 4.0), ctrl2: point(10.0, -4.0), to: point(11.0, 1.0), }; let seg1 = LineSegment { from: point(0.0, 0.0), to: point(10.0, 0.0) }; assert_approx_eq(curve1.line_segment_intersections_t(&seg1), &[(0.5, 0.5)], epsilon); let curve2 = CubicBezierSegment { from: point(-1.0, 0.0), ctrl1: point(0.0, 5.0), ctrl2: point(0.0, 5.0), to: point(1.0, 0.0), }; let seg2 = LineSegment { from: point(0.0, 0.0), to: point(0.0, 5.0) }; assert_approx_eq(curve2.line_segment_intersections_t(&seg2), &[(0.5, 0.75)], epsilon); } #[test] fn test_parameters_for_value() { use crate::math::point; fn assert_approx_eq(a: ArrayVec<[f32; 3]>, b: &[f32], epsilon: f32) { for i in 0..a.len() { if f32::abs(a[i] - b[i]) > epsilon { println!("{:?} != {:?}", a, b); } assert!((a[i] - b[i]).abs() <= epsilon); } assert_eq!(a.len(), b.len()); } { let curve = CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 8.0), ctrl2: point(10.0, 8.0), to: point(10.0, 0.0) }; let epsilon = 1e-4; assert_approx_eq(curve.solve_t_for_x(5.0), &[0.5], epsilon); assert_approx_eq(curve.solve_t_for_y(6.0), &[0.5], epsilon); } { let curve = CubicBezierSegment { from: point(0.0, 10.0), ctrl1: point(0.0, 10.0), ctrl2: point(10.0, 10.0), to: point(10.0, 10.0) }; assert_approx_eq(curve.solve_t_for_y(10.0), &[], 0.0); } } #[test] fn test_cubic_intersection_deduping() { use crate::math::point; let epsilon = 0.0001; // Two "line segments" with 3-fold overlaps, intersecting in their overlaps for a total of nine // parameter intersections. let line1 = CubicBezierSegment { from: point(-1_000_000.0, 0.0), ctrl1: point(2_000_000.0, 3_000_000.0), ctrl2: point(-2_000_000.0, -1_000_000.0), to: point(1_000_000.0, 2_000_000.0), }; let line2 = CubicBezierSegment { from: point(-1_000_000.0, 2_000_000.0), ctrl1: point(2_000_000.0, -1_000_000.0), ctrl2: point(-2_000_000.0, 3_000_000.0), to: point(1_000_000.0, 0.0), }; let intersections = line1.cubic_intersections(&line2); // (If you increase the coordinates above to 10s of millions, you get two returned intersection // points; i.e. the test fails.) assert_eq!(intersections.len(), 1); assert!(f64::abs(intersections[0].x) < epsilon); assert!(f64::abs(intersections[0].y - 1_000_000.0) < epsilon); // Two self-intersecting curves that intersect in their self-intersections, for a total of four // parameter intersections. let curve1 = CubicBezierSegment { from: point(-10.0, -13.636363636363636), ctrl1: point(15.0, 11.363636363636363), ctrl2: point(-15.0, 11.363636363636363), to: point(10.0, -13.636363636363636), }; let curve2 = CubicBezierSegment { from: point(13.636363636363636, -10.0), ctrl1: point(-11.363636363636363, 15.0), ctrl2: point(-11.363636363636363, -15.0), to: point(13.636363636363636, 10.0), }; let intersections = curve1.cubic_intersections(&curve2); assert_eq!(intersections.len(), 1); assert!(f64::abs(intersections[0].x) < epsilon); assert!(f64::abs(intersections[0].y) < epsilon); } lyon_geom-0.15.0/src/cubic_bezier_intersections.rs010064400017500001750000001270041357773770300205700ustar0000000000000000///! Computes intersection parameters for two cubic bézier curves using bézier clipping, also known ///! as fat line clipping. ///! ///! The implementation here was originally ported from that of paper.js: ///! https://github.com/paperjs/paper.js/blob/0deddebb2c83ea2a0c848f7c8ba5e22fa7562a4e/src/path/Curve.js#L2008 ///! See "Bézier Clipping method" in ///! https://scholarsarchive.byu.edu/facpub/1/ ///! for motivation and details of how the process works. use crate::generic_math::{point, Point, Rect}; use crate::scalar::Scalar; use crate::CubicBezierSegment; use arrayvec::ArrayVec; use std::ops::Range; // Computes the intersections (if any) between two cubic bézier curves in the form of the `t` // parameters of each intersection point along the curves. // // Returns endpoint intersections where an endpoint intersects the interior of the other curve, // but not endpoint/endpoint intersections. // // Returns no intersections if either curve is a point or if the curves are parallel lines. pub fn cubic_bezier_intersections_t( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, ) -> ArrayVec<[(S, S); 9]> { if !curve1.fast_bounding_rect().intersects(&curve2.fast_bounding_rect()) || curve1 == curve2 || (curve1.from == curve2.to && curve1.ctrl1 == curve2.ctrl2 && curve1.ctrl2 == curve2.ctrl1 && curve1.to == curve2.from) { return ArrayVec::new(); } let mut result = ArrayVec::new(); #[inline] fn midpoint(point1: &Point, point2: &Point) -> Point { point((point1.x + point2.x) * S::HALF, (point1.y + point2.y) * S::HALF) } let curve1_is_a_point = curve1.is_a_point(S::EPSILON); let curve2_is_a_point = curve2.is_a_point(S::EPSILON); if curve1_is_a_point && !curve2_is_a_point { let point1 = midpoint(&curve1.from, &curve1.to); let curve_params = point_curve_intersections(&point1, &curve2, S::EPSILON); for t in curve_params { if t > S::EPSILON && t < S::ONE - S::EPSILON { result.push((S::ZERO, t)); } } } else if !curve1_is_a_point && curve2_is_a_point { let point2 = midpoint(&curve2.from, &curve2.to); let curve_params = point_curve_intersections(&point2, &curve1, S::EPSILON); for t in curve_params { if t > S::EPSILON && t < S::ONE - S::EPSILON { result.push((t, S::ZERO)); } } } if curve1_is_a_point || curve2_is_a_point { // Caller is always responsible for checking endpoints and overlaps, in the case that both // curves were points. return result; } let linear1 = curve1.is_linear(S::EPSILON); let linear2 = curve2.is_linear(S::EPSILON); if linear1 && !linear2 { result = line_curve_intersections(curve1, curve2, /* flip */ false); } else if !linear1 && linear2 { result = line_curve_intersections(curve2, curve1, /* flip */ true); } else if linear1 && linear2 { result = line_line_intersections(curve1, curve2); } else { add_curve_intersections( &curve1, &curve2, &(S::ZERO..S::ONE), &(S::ZERO..S::ONE), &mut result, /* flip */ false, /* recursion_count */ 0, /* call_count */ 0, /* original curve1 */ &curve1, /* original curve2 */ &curve2, ); } result } fn point_curve_intersections( pt: &Point, curve: &CubicBezierSegment, epsilon: S, ) -> ArrayVec<[S; 9]> { let mut result = ArrayVec::new(); // (If both endpoints are epsilon close, we only return S::ZERO.) if (*pt - curve.from).square_length() < epsilon { result.push(S::ZERO); return result; } if (*pt - curve.to).square_length() < epsilon { result.push(S::ONE); return result; } let curve_x_t_params = curve.solve_t_for_x(pt.x); let curve_y_t_params = curve.solve_t_for_y(pt.y); // We want to coalesce parameters representing the same intersection from the x and y // directions, but the parameter calculations aren't very accurate, so give a little more // leeway there (TODO: this isn't perfect, as you might expect - the dupes that pass here are // currently being detected in add_intersection). let param_eps = S::TEN * epsilon; for params in [curve_x_t_params, curve_y_t_params].iter() { for t in params { let t = *t; if (*pt - curve.sample(t)).square_length() > epsilon { continue; } let mut already_found_t = false; for u in &result { if S::abs(t - *u) < param_eps { already_found_t = true; break; } } if !already_found_t { result.push(t); } } } if result.len() > 0 { return result; } // The remaining case is if pt is within epsilon of an interior point of curve, but not within // the x-range or y-range of the curve (which we already checked) - for example if curve is a // horizontal line that extends beyond its endpoints, and pt is just outside an end of the line; // or if the curve has a cusp in one of the corners of its convex hull and pt is // diagonally just outside the hull. This is a rare case (could we even ignore it?). #[inline] fn maybe_add(t: S, pt: &Point, curve: &CubicBezierSegment, epsilon: S, result: &mut ArrayVec<[S; 9]>) -> bool { if (curve.sample(t) - *pt).square_length() < epsilon { result.push(t); return true; } false } let _ = maybe_add(curve.x_minimum_t(), pt, curve, epsilon, &mut result) || maybe_add(curve.x_maximum_t(), pt, curve, epsilon, &mut result) || maybe_add(curve.y_minimum_t(), pt, curve, epsilon, &mut result) || maybe_add(curve.y_maximum_t(), pt, curve, epsilon, &mut result); result } fn line_curve_intersections( line_as_curve: &CubicBezierSegment, curve: &CubicBezierSegment, flip: bool, ) -> ArrayVec<[(S, S); 9]> { let mut result = ArrayVec::new(); let baseline = line_as_curve.baseline(); let curve_intersections = curve.line_intersections_t(&baseline.to_line()); let line_is_mostly_vertical = S::abs(baseline.from.y - baseline.to.y) >= S::abs(baseline.from.x - baseline.to.x); for curve_t in curve_intersections { let line_intersections = if line_is_mostly_vertical { let intersection_y = curve.y(curve_t); line_as_curve.solve_t_for_y(intersection_y) } else { let intersection_x = curve.x(curve_t); line_as_curve.solve_t_for_x(intersection_x) }; for line_t in line_intersections { add_intersection(line_t, line_as_curve, curve_t, curve, flip, &mut result); } } result } fn line_line_intersections( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, ) -> ArrayVec<[(S, S); 9]> { let mut result = ArrayVec::new(); let intersection = curve1.baseline().to_line().intersection(&curve2.baseline().to_line()); if intersection.is_none() { return result; } let intersection = intersection.unwrap(); #[inline] fn parameters_for_line_point( curve: &CubicBezierSegment, pt: &Point, ) -> ArrayVec<[S; 3]> { let line_is_mostly_vertical = S::abs(curve.from.y - curve.to.y) >= S::abs(curve.from.x - curve.to.x); if line_is_mostly_vertical { curve.solve_t_for_y(pt.y) } else { curve.solve_t_for_x(pt.x) } } let line1_params = parameters_for_line_point(&curve1, &intersection); if line1_params.len() == 0 { return result; } let line2_params = parameters_for_line_point(&curve2, &intersection); if line2_params.len() == 0 { return result; } for t1 in &line1_params { for t2 in &line2_params { // It could be argued that an endpoint intersection located in the interior of one // or both curves should be returned here; we currently don't. add_intersection(*t1, curve1, *t2, curve2, /* flip */ false, &mut result); } } result } // This function implements the main bézier clipping algorithm by recursively subdividing curve1 and // curve2 in to smaller and smaller portions of the original curves with the property that one of // the curves intersects the fat line of the other curve at each stage. // // curve1 and curve2 at each stage are sub-bézier curves of the original curves; flip tells us // whether curve1 at a given stage is a subcurve of the original curve1 or the original curve2; // similarly for curve2. domain1 and domain2 shrink (or stay the same) at each stage and describe // which subdomain of an original curve the current curve1 and curve2 correspond to. (The domains of // curve1 and curve2 are 0..1 at every stage.) fn add_curve_intersections( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, domain1: &Range, domain2: &Range, intersections: &mut ArrayVec<[(S, S); 9]>, flip: bool, mut recursion_count: u32, mut call_count: u32, orig_curve1: &CubicBezierSegment, orig_curve2: &CubicBezierSegment, ) -> u32 { call_count += 1; recursion_count += 1; if call_count >= 4096 || recursion_count >= 60 { return call_count; } let epsilon = if inputs_are_f32::() { S::value(5e-6) } else { S::value(1e-9) }; if domain2.start == domain2.end || curve2.is_a_point(S::ZERO) { add_point_curve_intersection( &curve2, /* point is curve1 */ false, curve1, domain2, domain1, intersections, flip, ); return call_count; } else if curve2.from == curve2.to { // There's no curve2 baseline to fat-line against (and we'll (debug) crash if we try with // the current implementation), so split curve2 and try again. let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF); let domain2_mid = (domain2.start + domain2.end) * S::HALF; call_count = add_curve_intersections( curve1, &new_2_curves.0, domain1, &(domain2.start..domain2_mid), intersections, flip, recursion_count, call_count, orig_curve1, orig_curve2, ); call_count = add_curve_intersections( curve1, &new_2_curves.1, domain1, &(domain2_mid..domain2.end), intersections, flip, recursion_count, call_count, orig_curve1, orig_curve2, ); return call_count; } // (Don't call this before checking for point curves: points are inexact and can lead to false // negatives here.) if !rectangles_overlap(&curve1.fast_bounding_rect(), &curve2.fast_bounding_rect()) { return call_count; } let (t_min_clip, t_max_clip) = match restrict_curve_to_fat_line(curve1, curve2) { Some((min, max)) => (min, max), None => return call_count, }; // t_min_clip and t_max_clip are (0, 1)-based, so project them back to get the new restricted // range: let new_domain1 = &(domain_value_at_t(&domain1, t_min_clip)..domain_value_at_t(&domain1, t_max_clip)); if S::max(domain2.end - domain2.start, new_domain1.end - new_domain1.start) < epsilon { let t1 = (new_domain1.start + new_domain1.end) * S::HALF; let t2 = (domain2.start + domain2.end) * S::HALF; if inputs_are_f32::() { // There's an unfortunate tendency for curve2 endpoints that end near (but not all // that near) to the interior of curve1 to register as intersections, so try to avoid // that. (We could be discarding a legitimate intersection here.) let end_eps = S::value(1e-3); if (t2 < end_eps || t2 > S::ONE - end_eps) && (orig_curve1.sample(t1) - orig_curve2.sample(t2)).length() > S::FIVE { return call_count; } } add_intersection(t1, orig_curve1, t2, orig_curve2, flip, intersections); return call_count; } // Reduce curve1 to the part that might intersect curve2. let curve1 = &orig_curve1.split_range(new_domain1.clone()); // (Note: it's possible for new_domain1 to have become a point, even if // t_min_clip < t_max_clip. It's also possible for curve1 to not be a point even if new_domain1 // is a point (but then curve1 will be very small).) if new_domain1.start == new_domain1.end || curve1.is_a_point(S::ZERO) { add_point_curve_intersection( &curve1, /* point is curve1 */ true, curve2, new_domain1, domain2, intersections, flip, ); return call_count; } // If the new range is still 80% or more of the old range, subdivide and try again. if t_max_clip - t_min_clip > S::EIGHT / S::TEN { // Subdivide the curve which has converged the least. if new_domain1.end - new_domain1.start > domain2.end - domain2.start { let new_1_curves = curve1.split(S::HALF); let new_domain1_mid = (new_domain1.start + new_domain1.end) * S::HALF; call_count = add_curve_intersections( curve2, &new_1_curves.0, domain2, &(new_domain1.start..new_domain1_mid), intersections, !flip, recursion_count, call_count, orig_curve2, orig_curve1, ); call_count = add_curve_intersections( curve2, &new_1_curves.1, domain2, &(new_domain1_mid..new_domain1.end), intersections, !flip, recursion_count, call_count, orig_curve2, orig_curve1, ); } else { let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF); let domain2_mid = (domain2.start + domain2.end) * S::HALF; call_count = add_curve_intersections( &new_2_curves.0, curve1, &(domain2.start..domain2_mid), new_domain1, intersections, !flip, recursion_count, call_count, orig_curve2, orig_curve1, ); call_count = add_curve_intersections( &new_2_curves.1, curve1, &(domain2_mid..domain2.end), new_domain1, intersections, !flip, recursion_count, call_count, orig_curve2, orig_curve1, ); } } else { // Iterate. if domain2.end - domain2.start >= epsilon { call_count = add_curve_intersections( curve2, curve1, domain2, new_domain1, intersections, !flip, recursion_count, call_count, orig_curve2, orig_curve1, ); } else { // The interval on curve2 is already tight enough, so just continue iterating on curve1. call_count = add_curve_intersections( curve1, curve2, new_domain1, domain2, intersections, flip, recursion_count, call_count, orig_curve1, orig_curve2, ); } } call_count } fn add_point_curve_intersection( pt_curve: &CubicBezierSegment, pt_curve_is_curve1: bool, curve: &CubicBezierSegment, pt_domain: &Range, curve_domain: &Range, intersections: &mut ArrayVec<[(S, S); 9]>, flip: bool, ) { let pt = pt_curve.from; // We assume pt is curve1 when we add intersections below. let flip = if pt_curve_is_curve1 { flip } else { !flip }; // Generally speaking |curve| will be quite small at this point, so see if we can get away with // just sampling here. let epsilon = epsilon_for_point(&pt); let pt_t = (pt_domain.start + pt_domain.end) * S::HALF; let curve_t = { let mut t_for_min = S::ZERO; let mut min_dist_sq = epsilon; let tenths = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; for &t in tenths.iter() { let t = S::value(t); let d = (pt - curve.sample(t)).square_length(); if d < min_dist_sq { t_for_min = t; min_dist_sq = d; } } if min_dist_sq == epsilon { -S::ONE } else { let curve_t = domain_value_at_t(curve_domain, t_for_min); curve_t } }; if curve_t != -S::ONE { add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections); return; } // If sampling didn't work, try a different approach. let results = point_curve_intersections(&pt, curve, epsilon); for t in results { let curve_t = domain_value_at_t(curve_domain, t); add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections); } } // If we're comparing distances between samples of curves, our epsilon should depend on how big the // points we're comparing are. This function returns an epsilon appropriate for the size of pt. fn epsilon_for_point(pt: &Point) -> S { let max = S::max(S::abs(pt.x), S::abs(pt.y)); let epsilon = if inputs_are_f32::() { match max.to_i32().unwrap() { 0 ..= 9 => S::value(0.001), 10 ..= 99 => S::value(0.01), 100 ..= 999 => S::value(0.1), 1_000 ..= 9_999 => S::value(0.25), 10_000 ..= 999_999 => S::HALF, _ => S::ONE, } } else { match max.to_i64().unwrap() { 0 ..= 99_999 => S::EPSILON, 100_000 ..= 99_999_999 => S::value(1e-5), 100_000_000 ..= 9_999_999_999 => S::value(1e-3), _ => S::value(1e-1), } }; epsilon } fn add_intersection( t1: S, orig_curve1: &CubicBezierSegment, t2: S, orig_curve2: &CubicBezierSegment, flip: bool, intersections: &mut ArrayVec<[(S, S); 9]>, ) { let (t1, t2) = if flip { (t2, t1) } else { (t1, t2) }; // (This should probably depend in some way on how large our input coefficients are.) let epsilon = if inputs_are_f32::() { S::value(1e-3) } else { S::EPSILON }; // Discard endpoint/endpoint intersections. let t1_is_an_endpoint = t1 < epsilon || t1 > S::ONE - epsilon; let t2_is_an_endpoint = t2 < epsilon || t2 > S::ONE - epsilon; if t1_is_an_endpoint && t2_is_an_endpoint { return; } // We can get repeated intersections when we split a curve at an intersection point, or when // two curves intersect at a point where the curves are very close together, or when the fat // line process breaks down. for i in 0..intersections.len() { let (old_t1, old_t2) = intersections[i]; // f32 errors can be particularly bad (over a hundred) if we wind up keeping the "wrong" // duplicate intersection, so always keep the one that minimizes sample distance. if S::abs(t1 - old_t1) < epsilon && S::abs(t2 - old_t2) < epsilon { let cur_dist = (orig_curve1.sample(old_t1) - orig_curve2.sample(old_t2)).square_length(); let new_dist = (orig_curve1.sample(t1) - orig_curve2.sample(t2)).square_length(); if new_dist < cur_dist { intersections[i] = (t1, t2); } return; } } if intersections.len() < 9 { intersections.push((t1, t2)); } } // Returns an interval (t_min, t_max) with the property that for parameter values outside that // interval, curve1 is guaranteed to not intersect curve2; uses the fat line of curve2 as its basis // for the guarantee. (See the Sederberg document for what's going on here.) fn restrict_curve_to_fat_line( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, ) -> Option<(S, S)> { // TODO: Consider clipping against the perpendicular fat line as well (recommended by // Sederberg). // TODO: The current algorithm doesn't handle the (rare) case where curve1 and curve2 are // overlapping lines. let baseline2 = curve2.baseline().to_line().equation(); let d_0 = baseline2.signed_distance_to_point(&curve1.from); let d_1 = baseline2.signed_distance_to_point(&curve1.ctrl1); let d_2 = baseline2.signed_distance_to_point(&curve1.ctrl2); let d_3 = baseline2.signed_distance_to_point(&curve1.to); let (mut top, mut bottom) = convex_hull_of_distance_curve(d_0, d_1, d_2, d_3); let (d_min, d_max) = curve2.fat_line_min_max(); clip_convex_hull_to_fat_line(&mut top, &mut bottom, d_min, d_max) } // Returns the convex hull of the curve that's the graph of the function // t -> d(curve1(t), baseline(curve2)). The convex hull is described as a top and a bottom, where // each of top and bottom is described by the list of its vertices from left to right (the number of // vertices for each is variable). fn convex_hull_of_distance_curve( d0: S, d1: S, d2: S, d3: S, ) -> (Vec>, Vec>) { let p0 = point(S::ZERO, d0); let p1 = point(S::ONE / S::THREE, d1); let p2 = point(S::TWO / S::THREE, d2); let p3 = point(S::ONE, d3); // Compute the vertical signed distance of p1 and p2 from [p0, p3]. let dist1 = d1 - (S::TWO * d0 + d3) / S::THREE; let dist2 = d2 - (d0 + S::TWO * d3) / S::THREE; // Compute the hull assuming p1 is on top - we'll switch later if needed. let mut hull = if dist1 * dist2 < S::ZERO { // p1 and p2 lie on opposite sides of [p0, p3], so the hull is a quadrilateral: (vec![p0, p1, p3], vec![p0, p2, p3]) } else { // p1 and p2 lie on the same side of [p0, p3]. The hull can be a triangle or a // quadrilateral, and [p0, p3] is part of the hull. The hull is a triangle if the vertical // distance of one of the middle points p1, p2 is <= half the vertical distance of the // other middle point. let dist1 = S::abs(dist1); let dist2 = S::abs(dist2); if dist1 >= S::TWO * dist2 { (vec![p0, p1, p3], vec![p0, p3]) } else if dist2 >= S::TWO * dist1 { (vec![p0, p2, p3], vec![p0, p3]) } else { (vec![p0, p1, p2, p3], vec![p0, p3]) } }; // Flip the hull if needed: if dist1 < S::ZERO || (dist1 == S::ZERO && dist2 < S::ZERO) { hull = (hull.1, hull.0); } hull } // Returns the min and max values at which the convex hull enters the fat line min/max offset lines. fn clip_convex_hull_to_fat_line( hull_top: &mut Vec>, hull_bottom: &mut Vec>, d_min: S, d_max: S, ) -> Option<(S, S)> { // Walk from the left corner of the convex hull until we enter the fat line limits: let t_clip_min = walk_convex_hull_start_to_fat_line(&hull_top, &hull_bottom, d_min, d_max); if let None = t_clip_min { return None; } // Now walk from the right corner of the convex hull until we enter the fat line limits - to // walk right to left we just reverse the order of the hull vertices, so that hull_top and // hull_bottom start at the right corner now: hull_top.reverse(); hull_bottom.reverse(); let t_clip_max = walk_convex_hull_start_to_fat_line(&hull_top, &hull_bottom, d_min, d_max); if let None = t_clip_max { return None; } Some((t_clip_min.unwrap(), t_clip_max.unwrap())) } // Walk the edges of the convex hull until you hit a fat line offset value, starting from the // (first vertex in hull_top_vertices == first vertex in hull_bottom_vertices). fn walk_convex_hull_start_to_fat_line( hull_top_vertices: &Vec>, hull_bottom_vertices: &Vec>, d_min: S, d_max: S, ) -> Option { let start_corner = hull_top_vertices[0]; if start_corner.y < d_min { return walk_convex_hull_edges_to_fat_line(hull_top_vertices, true, d_min); } else if start_corner.y > d_max { return walk_convex_hull_edges_to_fat_line(hull_bottom_vertices, false, d_max); } else { return Some(start_corner.x); } } // Do the actual walking, starting from the first vertex of hull_vertices. fn walk_convex_hull_edges_to_fat_line( hull_vertices: &Vec>, vertices_are_for_top: bool, threshold: S, ) -> Option { for i in 0..hull_vertices.len() - 1 { let p = hull_vertices[i]; let q = hull_vertices[i + 1]; if (vertices_are_for_top && q.y >= threshold) || (!vertices_are_for_top && q.y <= threshold) { if q.y == threshold { return Some(q.x); } else { return Some(p.x + (threshold - p.y) * (q.x - p.x) / (q.y - p.y)); } } } // All points of the hull are outside the threshold: None } #[inline] fn inputs_are_f32() -> bool { S::EPSILON > S::value(1e-6) } #[inline] // Return the point of domain corresponding to the point t, 0 <= t <= 1. fn domain_value_at_t(domain: &Range, t: S) -> S { domain.start + (domain.end - domain.start) * t } #[inline] // Rect.intersects doesn't count edge/corner intersections, this version does. fn rectangles_overlap(r1: &Rect, r2: &Rect) -> bool { r1.origin.x <= r2.origin.x + r2.size.width && r2.origin.x <= r1.origin.x + r1.size.width && r1.origin.y <= r2.origin.y + r2.size.height && r2.origin.y <= r1.origin.y + r1.size.height } #[cfg(test)] fn do_test( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, intersection_count: i32, ) { do_test_once(curve1, curve2, intersection_count); do_test_once(curve2, curve1, intersection_count); } #[cfg(test)] fn do_test_once( curve1: &CubicBezierSegment, curve2: &CubicBezierSegment, intersection_count: i32, ) { let intersections = cubic_bezier_intersections_t(&curve1, &curve2); for intersection in &intersections { let p1 = curve1.sample(intersection.0); let p2 = curve2.sample(intersection.1); check_dist(&p1, &p2); } assert_eq!(intersections.len() as i32, intersection_count); } #[cfg(test)] fn check_dist(p1: &Point, p2: &Point) { let dist = S::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); if dist > S::HALF { assert!(false, "Intersection points too far apart."); } } #[test] fn test_cubic_curve_curve_intersections() { do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 1.0), ctrl2: point(0.0, 1.0), to: point(1.0, 1.0), }, &CubicBezierSegment { from: point(0.0, 1.0), ctrl1: point(1.0, 1.0), ctrl2: point(1.0, 1.0), to: point(1.0, 0.0), }, 1, ); do_test( &CubicBezierSegment { from: point(48.0f32, 84.0), ctrl1: point(104.0, 176.0), ctrl2: point(190.0, 37.0), to: point(121.0, 75.0), }, &CubicBezierSegment { from: point(68.0, 145.0), ctrl1: point(74.0, 6.0), ctrl2: point(143.0, 197.0), to: point(138.0, 55.0), }, 4, ); do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.5, 1.0), ctrl2: point(0.5, 1.0), to: point(1.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 1.0), ctrl1: point(0.5, 0.0), ctrl2: point(0.5, 0.0), to: point(1.0, 1.0), }, 2, ); do_test( &CubicBezierSegment { from: point(0.2, 0.0), ctrl1: point(0.5, 3.0), ctrl2: point(0.5, -2.0), to: point(0.8, 1.0), }, &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(2.5, 0.5), ctrl2: point(-1.5, 0.5), to: point(1.0, 0.0), }, 9, ); // (A previous version of the code was returning two practically identical // intersection points here.) do_test( &CubicBezierSegment { from: point(718133.1363092018, 673674.987999388), ctrl1: point(-53014.13135835016, 286988.87959900266), ctrl2: point(-900630.1880107201, -7527.6889376943), to: point(417822.48349384824, -149039.14932848653), }, &CubicBezierSegment { from: point(924715.3309247112, 719414.5221912428), ctrl1: point(965365.9679664494, -563421.3040676294), ctrl2: point(273552.85484064696, 643090.0890117711), to: point(-113963.134524995, 732017.9466050486), }, 1, ); // On these curves the algorithm runs to a state at which the new clipped domain1 becomes a // point even though t_min_clip < t_max_clip (because domain1 was small enough to begin with // relative to the small distance between t_min_clip and t_max_clip), and the new curve1 is not // a point (it's split off the old curve1 using t_min_clip < t_max_clip). do_test( &CubicBezierSegment { from: point(423394.5967598548, -91342.7434613118), ctrl1: point(333212.450870987, 225564.45711810607), ctrl2: point(668108.668469816, -626100.8367380127), to: point(-481885.0610437216, 893767.5320803947), }, &CubicBezierSegment { from: point(-484505.2601961801, -222621.44229855016), ctrl1: point(22432.829984141514, -944727.7102144773), ctrl2: point(-433294.66549074976, -168018.60431004688), to: point(567688.5977972192, 13975.09633399453), }, 3, ); } #[test] fn test_cubic_control_point_touching() { // After splitting the curve2 loop in half, curve1.ctrl1 (and only that // point) touches the curve2 fat line - make sure we don't accept that as an // intersection. [We're really only interested in the right half of the loop - the rest of the // loop is there just to get past an initial fast_bounding_rect check.] do_test( &CubicBezierSegment { from: point(-1.0, 0.0), ctrl1: point(0.0, 0.0), ctrl2: point(-1.0, -0.1), to: point(-1.0, -0.1), }, &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(5.0, -5.0), ctrl2: point(-5.0, -5.0), to: point(0.0, 0.0), }, 0, ); } #[test] fn test_cubic_self_intersections() { // Two self-intersecting curves intersecting at their self-intersections (the origin). do_test( &CubicBezierSegment { from: point(-10.0, -13.636363636363636), ctrl1: point(15.0, 11.363636363636363), ctrl2: point(-15.0, 11.363636363636363), to: point(10.0, -13.636363636363636), }, &CubicBezierSegment { from: point(13.636363636363636, -10.0), ctrl1: point(-11.363636363636363, 15.0), ctrl2: point(-11.363636363636363, -15.0), to: point(13.636363636363636, 10.0), }, 4, ); } #[test] fn test_cubic_loops() { // This gets up to a recursion count of 53 trying to find (0.0, 0.0) and (1.0, 1.0) (which // aren't actually needed) - with the curves in the opposite order it gets up to 81! do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(-10.0, 10.0), ctrl2: point(10.0, 10.0), to: point(0.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(-1.0, 1.0), ctrl2: point(1.0, 1.0), to: point(0.0, 0.0), }, 0, ); do_test( &CubicBezierSegment { from: point(0.0f32, 0.0), ctrl1: point(-100.0, 0.0), ctrl2: point(-500.0, 500.0), to: point(0.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(-800.0, 100.0), ctrl2: point(500.0, 500.0), to: point(0.0, 0.0), }, 3, ); } #[test] fn test_cubic_line_curve_intersections() { do_test( &CubicBezierSegment { /* line */ from: point(1.0, 2.0), ctrl1: point(20.0, 1.0), ctrl2: point(1.0, 2.0), to: point(20.0, 1.0), }, &CubicBezierSegment { from: point(1.0, 0.0), ctrl1: point(1.0, 5.0), ctrl2: point(20.0, 25.0), to: point(20.0, 0.0), }, 2, ); do_test( &CubicBezierSegment { /* line */ from: point(0.0f32, 0.0), ctrl1: point(-10.0, 0.0), ctrl2: point(20.0, 0.0), to: point(10.0, 0.0), }, &CubicBezierSegment { from: point(-1.0, -1.0), ctrl1: point(0.0, 4.0), ctrl2: point(10.0, -4.0), to: point(11.0, 1.0), }, 5, ); do_test( &CubicBezierSegment { from: point(-1.0, -2.0), ctrl1: point(-1.0, 8.0), ctrl2: point(1.0, -8.0), to: point(1.0, 2.0), }, &CubicBezierSegment { /* line */ from: point(-10.0, -10.0), ctrl1: point(20.0, 20.0), ctrl2: point(-20.0, -20.0), to: point(10.0, 10.0), }, 9, ); } #[test] fn test_cubic_line_line_intersections() { do_test( &CubicBezierSegment { from: point(-10.0, -10.0), ctrl1: point(20.0, 20.0), ctrl2: point(-20.0, -20.0), to: point(10.0, 10.0), }, &CubicBezierSegment { from: point(-10.0, 10.0), ctrl1: point(20.0, -20.0), ctrl2: point(-20.0, 20.0), to: point(10.0, -10.0), }, 9, ); // Overlapping line segments should return 0 intersections. do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 0.0), ctrl2: point(10.0, 0.0), to: point(10.0, 0.0), }, &CubicBezierSegment { from: point(5.0, 0.0), ctrl1: point(5.0, 0.0), ctrl2: point(15.0, 0.0), to: point(15.0, 0.0), }, 0, ); } #[test] // (This test used to fail on a previous version of the algorithm by returning an intersection close // to (1.0, 0.0), but not close enough to consider it the same as (1.0, 0.0) - the curves are quite // close at that endpoint.) fn test_cubic_similar_loops() { do_test( &CubicBezierSegment { from: point(-0.281604145719379, -0.3129629924180608), ctrl1: point(-0.04393998118946163, 0.13714701102906668), ctrl2: point(0.4472584256288119, 0.2876115686206546), to: point(-0.281604145719379, -0.3129629924180608), }, &CubicBezierSegment { from: point(-0.281604145719379, -0.3129629924180608), ctrl1: point(-0.1560415754252551, -0.22924729391648402), ctrl2: point(-0.9224550447067958, 0.19110227764357646), to: point(-0.281604145719379, -0.3129629924180608), }, 2, ); } #[test] // (A previous version of the algorithm returned an intersection close to (0.5, 0.5), but not close // enough to be considered the same as (0.5, 0.5).) fn test_cubic_no_duplicated_root() { do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(-10.0, 1.0), ctrl2: point(10.0, 1.0), to: point(0.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(-1.0, 1.0), ctrl2: point(1.0, 1.0), to: point(0.0, 0.0), }, 1, ); } #[test] fn test_cubic_glancing_intersection() { use std::panic; // The f64 version currently fails on a very close fat line miss after 57 recursions. let result = panic::catch_unwind(|| { do_test( &CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 8.0), ctrl2: point(10.0, 8.0), to: point(10.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 12.0), ctrl1: point(0.0, 4.0), ctrl2: point(10.0, 4.0), to: point(10.0, 12.0), }, 1, ); }); assert!(result.is_err()); let result = panic::catch_unwind(|| { do_test( &CubicBezierSegment { from: point(0.0f32, 0.0), ctrl1: point(0.0, 8.0), ctrl2: point(10.0, 8.0), to: point(10.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 12.0), ctrl1: point(0.0, 4.0), ctrl2: point(10.0, 4.0), to: point(10.0, 12.0), }, 1, ); }); assert!(result.is_err()); } #[test] fn test_cubic_duplicated_intersections() { use std::panic; let result = panic::catch_unwind(|| { // This finds an extra intersection (0.49530116, 0.74361485) - the actual, also found, is // (0.49633604, 0.74361396). Their difference is (−0.00103488, 0.00000089) - we consider // the two to be the same if both difference values are < 1e-3. do_test( &CubicBezierSegment { from: point(-33307.36f32, -1804.0625), ctrl1: point(-59259.727, 70098.31), ctrl2: point(98661.78, 48235.703), to: point(28422.234, 31845.219), }, &CubicBezierSegment { from: point(-21501.133, 51935.344), ctrl1: point(-95301.96, 95031.45), ctrl2: point(-25882.242, -12896.75), to: point(94618.97, 94288.66), }, 2, ); }); assert!(result.is_err()); } #[test] fn test_cubic_endpoint_not_an_intersection() { // f32 curves seem to be somewhat prone to picking up not-an-intersections where an endpoint of // one curve is close to and points into the interior of the other curve, and both curves are // "mostly linear" on some level. use std::panic; let result = panic::catch_unwind(|| { do_test( &CubicBezierSegment { from: point(76868.875f32, 47679.28), ctrl1: point(65326.86, 856.21094), ctrl2: point(-85621.64, -80823.375), to: point(-56517.53, 28062.227), }, &CubicBezierSegment { from: point(-67977.72, 77673.53), ctrl1: point(-59829.57, -41917.87), ctrl2: point(57.4375, 52822.97), to: point(51075.86, 85772.84), }, 0, ); }); assert!(result.is_err()); } #[test] // The endpoints of curve2 intersect the interior of curve1. fn test_cubic_interior_endpoint() { do_test( &CubicBezierSegment { // Has its apex at 6.0. from: point(-5.0, 0.0), ctrl1: point(-5.0, 8.0), ctrl2: point(5.0, 8.0), to: point(5.0, 0.0), }, &CubicBezierSegment { from: point(0.0, 6.0), ctrl1: point(-5.0, 0.0), ctrl2: point(5.0, 0.0), to: point(0.0, 6.0), }, 2, ); } #[test] fn test_cubic_point_curve_intersections() { let epsilon = 1e-5; { let curve1 = CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 1.0), ctrl2: point(0.0, 1.0), to: point(1.0, 1.0), }; let sample_t = 0.123456789; let pt = curve1.sample(sample_t); let curve2 = CubicBezierSegment { from: pt, ctrl1: pt, ctrl2: pt, to: pt, }; let intersections = cubic_bezier_intersections_t(&curve1, &curve2); assert_eq!(intersections.len(), 1); let intersection_t = intersections[0].0; assert!(f64::abs(intersection_t - sample_t) < epsilon); } { let curve1 = CubicBezierSegment { from: point(-10.0, -13.636363636363636), ctrl1: point(15.0, 11.363636363636363), ctrl2: point(-15.0, 11.363636363636363), to: point(10.0, -13.636363636363636), }; // curve1 has a self intersection at the following parameter values: let parameter1 = 0.7611164839335472; let parameter2 = 0.23888351606645375; let pt = curve1.sample(parameter1); let curve2 = CubicBezierSegment { from: pt, ctrl1: pt, ctrl2: pt, to: pt, }; let intersections = cubic_bezier_intersections_t(&curve1, &curve2); assert_eq!(intersections.len(), 2); let intersection_t1 = intersections[0].0; let intersection_t2 = intersections[1].0; assert!(f64::abs(intersection_t1 - parameter1) < epsilon); assert!(f64::abs(intersection_t2 - parameter2) < epsilon); } { let epsilon = epsilon as f32; let curve1 = CubicBezierSegment { from: point(0.0f32, 0.0), ctrl1: point(50.0, 50.0), ctrl2: point(-50.0, -50.0), to: point(10.0, 10.0), }; // curve1 is a line that passes through (5.0, 5.0) three times: let parameter1 = 0.96984464; let parameter2 = 0.037427425; let parameter3 = 0.44434106; let pt = curve1.sample(parameter1); let curve2 = CubicBezierSegment { from: pt, ctrl1: pt, ctrl2: pt, to: pt, }; let intersections = cubic_bezier_intersections_t(&curve1, &curve2); assert_eq!(intersections.len(), 3); let intersection_t1 = intersections[0].0; let intersection_t2 = intersections[1].0; let intersection_t3 = intersections[2].0; assert!(f32::abs(intersection_t1 - parameter1) < epsilon); assert!(f32::abs(intersection_t2 - parameter2) < epsilon); assert!(f32::abs(intersection_t3 - parameter3) < epsilon); } } #[test] fn test_cubic_subcurve_intersections() { let curve1 = CubicBezierSegment { from: point(0.0, 0.0), ctrl1: point(0.0, 1.0), ctrl2: point(0.0, 1.0), to: point(1.0, 1.0), }; let curve2 = curve1.split_range(0.25..0.75); // The algorithm will find as many intersections as you let it, basically - make sure we're // not allowing too many intersections to be added, and not crashing on out of resources. do_test(&curve1, &curve2, 9); } #[test] fn test_cubic_result_distance() { // In a previous version this used to return an intersection pair (0.17933762, 0.23783168), // close to an actual intersection, where the sampled intersection points on respective curves // were at distance 160.08488. The point here is that the old extra intersection was the result // of an anomalous fat line calculation, in other words an actual error, not just a "not quite // computationally close enough" error. do_test( &CubicBezierSegment { from: point(5893.133f32, -51377.152), ctrl1: point(-94403.984, 37668.156), ctrl2: point(-58914.684, 30339.195), to: point(4895.875, 83473.3), }, &CubicBezierSegment { from: point(-51523.734, 75047.05), ctrl1: point(-58162.76, -91093.875), ctrl2: point(82137.516, -59844.35), to: point(46856.406, 40479.64), }, 3, ); } lyon_geom-0.15.0/src/cubic_to_quadratic.rs010064400017500001750000000135201357773320200170010ustar0000000000000000use crate::scalar::Scalar; use crate::{QuadraticBezierSegment, CubicBezierSegment}; use crate::monotonic::Monotonic; use crate::math::point; /// Approximates a cubic bézier segment with a sequence of quadratic béziers. pub fn cubic_to_quadratics( curve: &CubicBezierSegment, tolerance: S, cb: &mut F ) where F: FnMut(&QuadraticBezierSegment) { debug_assert!(tolerance >= S::EPSILON); let mut sub_curve = curve.clone(); let mut range = S::ZERO..S::ONE; loop { if single_curve_approximation_test(&sub_curve, tolerance) { cb(&single_curve_approximation(&sub_curve)); if range.end >= S::ONE { return; } range.start = range.end; range.end = S::ONE; } else { range.end = (range.start + range.end) * S::HALF; } sub_curve = curve.split_range(range.clone()); } } /// This is terrible as a general approximation but works if the cubic /// curve does not have inflection points and is "flat" enough. Typically /// usables after subdiving the curve a few times. pub fn single_curve_approximation(cubic: &CubicBezierSegment) -> QuadraticBezierSegment { let c1 = (cubic.ctrl1 * S::THREE - cubic.from) * S::HALF; let c2 = (cubic.ctrl2 * S::THREE - cubic.to) * S::HALF; QuadraticBezierSegment { from: cubic.from, ctrl: ((c1 + c2) * S::HALF).to_point(), to: cubic.to, } } /// Evaluates an upper bound on the maximum distance between the curve /// and its quadratic approximation obtained using the single curve approximation. pub fn single_curve_approximation_error(curve: &CubicBezierSegment) -> S { // See http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html S::sqrt(S::THREE) / S::value(36.0) * ((curve.to - curve.ctrl2 * S::THREE) + (curve.ctrl1 * S::THREE - curve.from)).length() } // Similar to single_curve_approximation_error avoiding the square root. fn single_curve_approximation_test(curve: &CubicBezierSegment, tolerance: S) -> bool { S::THREE / S::value(1296.0) * ((curve.to - curve.ctrl2 * S::THREE) + (curve.ctrl1 * S::THREE - curve.from)).square_length() <= tolerance * tolerance } pub fn cubic_to_monotonic_quadratics( curve: &CubicBezierSegment, tolerance: S, cb: &mut F ) where F: FnMut(&Monotonic>), { curve.for_each_monotonic_range(|range| { cubic_to_quadratics( &curve.split_range(range), tolerance, &mut|c| { cb(&make_monotonic(c)) } ); }); } // Unfortunately the single curve approximation can turn a monotonic cubic // curve into an almost-but-exactly monotonic quadratic segment, so we may // need to nudge the control point slightly to not break downstream code // that rely on the monotonicity. fn make_monotonic(curve: &QuadraticBezierSegment) -> Monotonic>{ Monotonic { segment: QuadraticBezierSegment { from: curve.from, ctrl: point( S::min(S::max(curve.from.x, curve.ctrl.x), curve.to.x), S::min(S::max(curve.from.y, curve.ctrl.y), curve.to.y), ), to: curve.to, } } } /* pub struct MonotonicQuadraticBezierSegments { curve: CubicBezierSegment, splits: ArrayVec<[S; 4]>, t0: S, idx: u8, } impl MonotonicQuadraticBezierSegments { pub fn new(curve: &CubicBezierSegment) -> Self { let mut splits = ArrayVec::new(); curve.for_each_monotonic_t(|t| { splits.push(t); }); MonotonicQuadraticBezierSegments { curve: *curve, splits, t0: S::ZERO, idx: 0, } } } impl Iterator for MonotonicQuadraticBezierSegments { type Item = QuadraticBezierSegment; fn next(&mut self) -> Option> { let i = self.idx as usize; if i < self.splits.len() { let a = self.t0; let b = self.splits[i]; self.t0 = b; self.idx += 1; return Some(single_curve_approximation(&self.curve.split_range(a..b))); } None } } */ #[test] fn test_cubic_to_quadratics() { use euclid::approxeq::ApproxEq; let quadratic = QuadraticBezierSegment { from: point(1.0, 2.0), ctrl: point(10.0, 5.0), to: point(0.0, 1.0), }; let mut count = 0; cubic_to_quadratics(&quadratic.to_cubic(), 0.0001, &mut|c| { assert!(count == 0); assert!(c.from.approx_eq(&quadratic.from)); assert!(c.ctrl.approx_eq(&quadratic.ctrl)); assert!(c.to.approx_eq(&quadratic.to)); count += 1; }); let cubic = CubicBezierSegment { from: point(1.0, 1.0), ctrl1: point(10.0, 2.0), ctrl2: point(1.0, 3.0), to: point(10.0, 4.0), }; let mut prev = cubic.from; let mut count = 0; cubic_to_quadratics(&cubic, 0.01, &mut|c| { assert!(c.from.approx_eq(&prev)); prev = c.to; count += 1; }); assert!(prev.approx_eq(&cubic.to)); assert!(count < 10); assert!(count > 4); } #[test] fn test_cubic_to_monotonic_quadratics() { use euclid::approxeq::ApproxEq; let cubic = CubicBezierSegment { from: point(1.0, 1.0), ctrl1: point(10.0, 2.0), ctrl2: point(1.0, 3.0), to: point(10.0, 4.0), }; let mut prev = cubic.from; let mut count = 0; cubic_to_monotonic_quadratics(&cubic, 0.01, &mut|c| { assert!(c.segment().from.approx_eq(&prev)); prev = c.segment().to; assert!(c.segment().is_monotonic()); count += 1; }); assert!(prev.approx_eq(&cubic.to)); assert!(count < 10); assert!(count > 4); } lyon_geom-0.15.0/src/flatten_cubic.rs010064400017500001750000000422261360010744200157500ustar0000000000000000///! Utilities to flatten cubic bezier curve segments, implmeneted both with callback and ///! iterator based APIs. ///! ///! The algorithm implemented here is based on: ///! http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf ///! It produces a better approximations than the usual recursive subdivision approach (or ///! in other words, it generates less points for a given tolerance threshold). use crate::CubicBezierSegment; use crate::scalar::Scalar; use crate::generic_math::Point; use arrayvec::ArrayVec; /// An iterator over a cubic bezier segment that yields line segments approximating the /// curve for a given approximation threshold. /// /// The iterator starts at the first point *after* the origin of the curve and ends at the /// destination. pub struct Flattened { remaining_curve: CubicBezierSegment, // current portion of the curve, does not have inflections. current_curve: Option>, next_inflection: Option, following_inflection: Option, tolerance: S, check_inflection: bool, } impl Flattened { /// Creates an iterator that yields points along a cubic bezier segment, useful to build a /// flattened approximation of the curve given a certain tolerance. pub fn new(bezier: CubicBezierSegment, tolerance: S) -> Self { let mut inflections: ArrayVec<[S; 2]> = ArrayVec::new(); find_cubic_bezier_inflection_points(&bezier, &mut|t| { inflections.push(t); }); let mut iter = Flattened { remaining_curve: bezier, current_curve: None, next_inflection: inflections.get(0).cloned(), following_inflection: inflections.get(1).cloned(), tolerance: tolerance, check_inflection: false, }; if let Some(&t1) = inflections.get(0) { let (before, after) = bezier.split(t1); iter.current_curve = Some(before); iter.remaining_curve = after; if let Some(&t2) = inflections.get(1) { // Adjust the second inflection since we removed the part before the // first inflection from the bezier curve. let t2 = (t2 - t1) / (S::ONE - t1); iter.following_inflection = Some(t2) } return iter; } iter.current_curve = Some(bezier); return iter; } } impl Iterator for Flattened { type Item = Point; fn next(&mut self) -> Option> { if self.current_curve.is_none() && self.next_inflection.is_some() { if let Some(t2) = self.following_inflection { // No need to re-map t2 in the curve because we already did iter_points // in the iterator's new function. let (before, after) = self.remaining_curve.split(t2); self.current_curve = Some(before); self.remaining_curve = after; } else { // The last chunk doesn't have inflection points, use it. self.current_curve = Some(self.remaining_curve); } // Pop the inflection stack. self.next_inflection = self.following_inflection; self.following_inflection = None; self.check_inflection = true; } if let Some(sub_curve) = self.current_curve { if sub_curve.is_a_point(self.tolerance) { self.current_curve = None; return self.next(); } if self.check_inflection { self.check_inflection = false; if let Some(tf) = inflection_approximation_range(&sub_curve, self.tolerance) { let next = sub_curve.after_split(tf); self.current_curve = Some(next); return Some(next.from); } } // We are iterating over a sub-curve that does not have inflections. let t = no_inflection_flattening_step(&sub_curve, self.tolerance); if t >= S::ONE { let to = sub_curve.to; self.current_curve = None; return Some(to); } let next_curve = sub_curve.after_split(t); self.current_curve = Some(next_curve); return Some(next_curve.from); } return None; } } pub fn flatten_cubic_bezier)>( mut bezier: CubicBezierSegment, tolerance: S, call_back: &mut F, ) { let mut inflections: ArrayVec<[S; 2]> = ArrayVec::new(); find_cubic_bezier_inflection_points(&bezier, &mut|t| { inflections.push(t); }); if let Some(&t1) = inflections.get(0) { bezier = flatten_including_inflection(&bezier, t1, tolerance, call_back); if let Some(&t2) = inflections.get(1) { // Adjust the second inflection since we removed the part before the // first inflection from the bezier curve. let t2 = (t2 - t1) / (S::ONE - t1); bezier = flatten_including_inflection(&bezier, t2, tolerance, call_back); } } flatten_cubic_no_inflection(bezier, tolerance, call_back); } pub fn flatten_cubic_bezier_with_t, S)>( mut bezier: CubicBezierSegment, tolerance: S, call_back: &mut F, ) { let mut inflections: ArrayVec<[S; 2]> = ArrayVec::new(); find_cubic_bezier_inflection_points(&bezier, &mut|t| { inflections.push(t); }); let mut t = S::ZERO; for t_inflection in inflections { let (before, mut after) = bezier.split(t_inflection); // Flatten up to the inflection point. flatten_cubic_no_inflection_with_t(before, t, t_inflection, tolerance, call_back); t = t_inflection; // Approximate the inflection with a segment if need be. if let Some(tf) = inflection_approximation_range(&after, tolerance) { after = after.after_split(tf); t += tf * (S::ONE - t); call_back(after.from, t); } bezier = after; } // Do the rest of the curve. flatten_cubic_no_inflection_with_t(bezier, t, S::ONE, tolerance, call_back); } // Flatten the curve up to the the inflection point and its approximation range included. fn flatten_including_inflection)>( bezier: &CubicBezierSegment, up_to_t: S, tolerance: S, call_back: &mut F, ) -> CubicBezierSegment { let (before, mut after) = bezier.split(up_to_t); flatten_cubic_no_inflection(before, tolerance, call_back); if let Some(tf) = inflection_approximation_range(&after, tolerance) { after = after.after_split(tf); call_back(after.from); } after } // The algorithm implemented here is based on: // http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf // // The basic premise is that for a small t the third order term in the // equation of a cubic bezier curve is insignificantly small. This can // then be approximated by a quadratic equation for which the maximum // difference from a linear approximation can be much more easily determined. fn flatten_cubic_no_inflection)>( mut curve: CubicBezierSegment, tolerance: S, call_back: &mut F, ) { let end = curve.to; loop { let step = no_inflection_flattening_step(&curve, tolerance); if step >= S::ONE { if !curve.is_a_point(S::ZERO) { call_back(end); } break; } curve = curve.after_split(step); call_back(curve.from); } } fn flatten_cubic_no_inflection_with_t, S)>( mut curve: CubicBezierSegment, mut t0: S, t1: S, tolerance: S, call_back: &mut F, ) { let end = curve.to; loop { let step = no_inflection_flattening_step(&curve, tolerance); if step >= S::ONE { if t0 < t1 { call_back(end, t1); } break; } curve = curve.after_split(step); t0 += step * (t1 - t0); call_back(curve.from, t0); } } fn no_inflection_flattening_step(bezier: &CubicBezierSegment, tolerance: S) -> S { let v1 = bezier.ctrl1 - bezier.from; let v2 = bezier.ctrl2 - bezier.from; // This function assumes that the bézier segment is not starting at an inflection point, // otherwise the following cross product may result in very small numbers which will hit // floating point precision issues. // To remove divisions and check for divide-by-zero, this is optimized from: // s2 = (v2.x * v1.y - v2.y * v1.x) / hypot(v1.x, v1.y); // t = 2 * sqrt(tolerance / (3. * abs(s2))); let v2_cross_v1 = v2.cross(v1); if v2_cross_v1 == S::ZERO { return S::ONE; } let s2inv = v1.x.hypot(v1.y) / v2_cross_v1; let t = S::TWO * S::sqrt(tolerance * S::abs(s2inv) / S::THREE); // TODO: We start having floating point precision issues if this constant // is closer to 1.0 with a small enough tolerance threshold. if t >= S::value(0.995) || t == S::ZERO { return S::ONE; } return t; } // Find the inflection points of a cubic bezier curve. pub(crate) fn find_cubic_bezier_inflection_points( bezier: &CubicBezierSegment, cb: &mut F, ) where S: Scalar, F: FnMut(S) { // Find inflection points. // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation // of this approach. let pa = bezier.ctrl1 - bezier.from; let pb = bezier.ctrl2.to_vector() - (bezier.ctrl1.to_vector() * S::TWO) + bezier.from.to_vector(); let pc = bezier.to.to_vector() - (bezier.ctrl2.to_vector() * S::THREE) + (bezier.ctrl1.to_vector() * S::THREE) - bezier.from.to_vector(); let a = pb.cross(pc); let b = pa.cross(pc); let c = pa.cross(pb); if S::abs(a) < S::EPSILON { // Not a quadratic equation. if S::abs(b) < S::EPSILON { // Instead of a linear acceleration change we have a constant // acceleration change. This means the equation has no solution // and there are no inflection points, unless the constant is 0. // In that case the curve is a straight line, essentially that means // the easiest way to deal with is is by saying there's an inflection // point at t == 0. The inflection point approximation range found will // automatically extend into infinity. if S::abs(c) < S::EPSILON { cb(S::ZERO); } } else { let t = -c / b; if in_range(t) { cb(t); } } return; } fn in_range(t: S) -> bool { t >= S::ZERO && t < S::ONE } let discriminant = b * b - S::FOUR * a * c; if discriminant < S::ZERO { return; } if discriminant < S::EPSILON { let t = -b / (S::TWO * a); if in_range(t) { cb(t); } return; } // This code is derived from https://www2.units.it/ipl/students_area/imm2/files/Numerical_Recipes.pdf page 184. // Computing the roots this way avoids precision issues when a, c or both are small. let discriminant_sqrt = S::sqrt(discriminant); let sign_b = if b >= S::ZERO { S::ONE } else { -S::ONE }; let q = -S::HALF * (b + sign_b * discriminant_sqrt); let mut first_inflection = q / a; let mut second_inflection = c / q; if first_inflection > second_inflection { std::mem::swap(&mut first_inflection, &mut second_inflection); } if in_range(first_inflection) { cb(first_inflection); } if in_range(second_inflection) { cb(second_inflection); } } // Find the range around the start of the curve where the curve can locally be approximated // with a line segment, given a tolerance threshold. fn inflection_approximation_range( bezier: &CubicBezierSegment, tolerance: S, ) -> Option { // Transform the curve such that it starts at the origin. let p1 = bezier.ctrl1 - bezier.from; let p2 = bezier.ctrl2 - bezier.from; let p3 = bezier.to - bezier.from; // Thus, curve(t) = t^3 * (3*p1 - 3*p2 + p3) + t^2 * (-6*p1 + 3*p2) + t * (3*p1). // Since curve(0) is an inflection point, cross(p1, p2) = 0, i.e. p1 and p2 are parallel. // Let s(t) = s3 * t^3 be the (signed) perpendicular distance of curve(t) from a line that will be determined below. let s3; if S::abs(p1.x) < S::EPSILON && S::abs(p1.y) < S::EPSILON { // Assume p1 = 0. if S::abs(p2.x) < S::EPSILON && S::abs(p2.y) < S::EPSILON { // Assume p2 = 0. // The curve itself is a line or a point. return None; } else { // In this case p2 is away from zero. // Choose the line in direction p2. s3 = p2.cross(p3) / p2.length(); } } else { // In this case p1 is away from zero. // Choose the line in direction p1 and use that p1 and p2 are parallel. s3 = p1.cross(p3) / p1.length(); } // Calculate the maximal t value such that the (absolute) distance is within the tolerance. let tf = S::abs(tolerance / s3).powf(S::ONE / S::THREE); return if tf < S::ONE { Some(tf) } else { None }; } #[cfg(test)] fn print_arrays(a: &[Point], b: &[Point]) { println!("left: {:?}", a); println!("right: {:?}", b); } #[cfg(test)] fn assert_approx_eq(a: &[Point], b: &[Point]) { if a.len() != b.len() { print_arrays(a, b); panic!("Lenths differ ({} != {})", a.len(), b.len()); } for i in 0..a.len() { if f32::abs(a[i].x - b[i].x) > 0.0000001 || f32::abs(a[i].y - b[i].y) > 0.0000001 { print_arrays(a, b); panic!("The arrays are not equal"); } } } #[test] fn test_iterator_builder_1() { let tolerance = 0.01; let c1 = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(1.0, 0.0), ctrl2: Point::new(1.0, 1.0), to: Point::new(0.0, 1.0), }; let iter_points: Vec> = c1.flattened(tolerance).collect(); let mut builder_points = Vec::new(); c1.for_each_flattened(tolerance, &mut |p| { builder_points.push(p); }); assert!(iter_points.len() > 2); assert_approx_eq(&iter_points[..], &builder_points[..]); } #[test] fn test_iterator_builder_2() { let tolerance = 0.01; let c1 = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(1.0, 0.0), ctrl2: Point::new(0.0, 1.0), to: Point::new(1.0, 1.0), }; let iter_points: Vec> = c1.flattened(tolerance).collect(); let mut builder_points = Vec::new(); c1.for_each_flattened(tolerance, &mut |p| { builder_points.push(p); }); assert!(iter_points.len() > 2); assert_approx_eq(&iter_points[..], &builder_points[..]); } #[test] fn test_iterator_builder_3() { let tolerance = 0.01; let c1 = CubicBezierSegment { from: Point::new(141.0, 135.0), ctrl1: Point::new(141.0, 130.0), ctrl2: Point::new(140.0, 130.0), to: Point::new(131.0, 130.0), }; let iter_points: Vec> = c1.flattened(tolerance).collect(); let mut builder_points = Vec::new(); c1.for_each_flattened(tolerance, &mut |p| { builder_points.push(p); }); assert!(iter_points.len() > 2); assert_approx_eq(&iter_points[..], &builder_points[..]); } #[test] fn test_issue_19() { let tolerance = 0.15; let c1 = CubicBezierSegment { from: Point::new(11.71726, 9.07143), ctrl1: Point::new(1.889879, 13.22917), ctrl2: Point::new(18.142855, 19.27679), to: Point::new(18.142855, 19.27679), }; let iter_points: Vec> = c1.flattened(tolerance).collect(); let mut builder_points = Vec::new(); c1.for_each_flattened(tolerance, &mut |p| { builder_points.push(p); }); assert_approx_eq(&iter_points[..], &builder_points[..]); assert!(iter_points.len() > 1); } #[test] fn test_issue_194() { let segment = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.0, 0.0), ctrl2: Point::new(50.0, 70.0), to: Point::new(100.0, 100.0), }; let mut points = Vec::new(); segment.for_each_flattened(0.1, &mut |p| { points.push(p); }); assert!(points.len() > 2); } #[test] fn flatten_with_t() { let segment = CubicBezierSegment { from: Point::new(0.0f32, 0.0), ctrl1: Point::new(0.0, 0.0), ctrl2: Point::new(50.0, 70.0), to: Point::new(100.0, 100.0), }; for tolerance in &[0.1, 0.01, 0.001, 0.0001] { let tolerance = *tolerance; let mut a = Vec::new(); segment.for_each_flattened(tolerance, &mut|p| { a.push(p); }); let mut b = Vec::new(); let mut ts = Vec::new(); segment.for_each_flattened_with_t(tolerance, &mut|p, t| { b.push(p); ts.push(t); }); assert_eq!(a, b); for i in 0..b.len() { let sampled = segment.sample(ts[i]); let point = b[i]; let dist = (sampled - point).length(); assert!(dist <= tolerance); } } } lyon_geom-0.15.0/src/lib.rs010064400017500001750000000327211360117331400137150ustar0000000000000000#![doc(html_logo_url = "https://nical.github.io/lyon-doc/lyon-logo.svg")] #![deny(bare_trait_objects)] //! Simple 2D geometric primitives on top of euclid. //! //! This crate is reexported in [lyon](https://docs.rs/lyon/). //! //! # Overview. //! //! This crate implements some of the maths to work with: //! //! - lines and line segments, //! - quadratic and cubic bézier curves, //! - elliptic arcs, //! - triangles. //! //! # Flattening //! //! Flattening is the action of approximating a curve with a succession of line segments. //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! //! The flattening algorithm implemented in this crate is based on the paper //! [Fast, Precise Flattening of Cubic Bézier Segment Offset Curves](http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf). //! It tends to produce a better approximations than the usual recursive subdivision approach (or //! in other words, it generates less segments for a given tolerance threshold). //! //! The tolerance threshold taken as input by the flattening algorithms corresponds //! to the maximum distance between the curve and its linear approximation. //! The smaller the tolerance is, the more precise the approximation and the more segments //! are generated. This value is typically chosen in function of the zoom level. //! //! //! //! //! //! //! //! //! //! //! //! //! The figure above shows a close up on a curve (the dotted line) and its linear //! approximation (the black segments). The tolerance threshold is represented by //! the light green area and the orange arrow. //! //#![allow(needless_return)] // clippy // Reexport dependencies. pub use arrayvec; pub use euclid; #[cfg(feature = "serialization")] #[macro_use] pub extern crate serde; #[macro_use] mod segment; pub mod quadratic_bezier; pub mod cubic_bezier; pub mod arc; pub mod utils; pub mod cubic_to_quadratic; mod cubic_bezier_intersections; mod flatten_cubic; mod triangle; mod line; mod monotonic; #[doc(inline)] pub use crate::quadratic_bezier::QuadraticBezierSegment; #[doc(inline)] pub use crate::cubic_bezier::CubicBezierSegment; #[doc(inline)] pub use crate::triangle::{Triangle}; #[doc(inline)] pub use crate::line::{LineSegment, Line, LineEquation}; #[doc(inline)] pub use crate::arc::{Arc, SvgArc, ArcFlags}; #[doc(inline)] pub use crate::segment::{Segment, BezierSegment}; #[doc(inline)] pub use crate::monotonic::Monotonic; pub use crate::scalar::Scalar; mod scalar { pub(crate) use num_traits::{Float, FloatConst, NumCast}; pub(crate) use num_traits::One; pub(crate) use num_traits::cast::cast; pub(crate) use euclid::Trig; use std::fmt::{Display, Debug}; use std::ops::{AddAssign, SubAssign, MulAssign, DivAssign}; pub trait Scalar : Float + NumCast + FloatConst + Sized + Display + Debug + Trig + AddAssign + SubAssign + MulAssign + DivAssign { const HALF: Self; const ZERO: Self; const ONE: Self; const TWO: Self; const THREE: Self; const FOUR: Self; const FIVE: Self; const SIX: Self; const SEVEN: Self; const EIGHT: Self; const NINE: Self; const TEN: Self; const EPSILON: Self; fn value(v: f32) -> Self; } impl Scalar for f32 { const HALF: Self = 0.5; const ZERO: Self = 0.0; const ONE: Self = 1.0; const TWO: Self = 2.0; const THREE: Self = 3.0; const FOUR: Self = 4.0; const FIVE: Self = 5.0; const SIX: Self = 6.0; const SEVEN: Self = 7.0; const EIGHT: Self = 8.0; const NINE: Self = 9.0; const TEN: Self = 10.0; const EPSILON: Self = 1e-5; #[inline] fn value(v: f32) -> Self { v } } impl Scalar for f64 { const HALF: Self = 0.5; const ZERO: Self = 0.0; const ONE: Self = 1.0; const TWO: Self = 2.0; const THREE: Self = 3.0; const FOUR: Self = 4.0; const FIVE: Self = 5.0; const SIX: Self = 6.0; const SEVEN: Self = 7.0; const EIGHT: Self = 8.0; const NINE: Self = 9.0; const TEN: Self = 10.0; const EPSILON: Self = 1e-8; #[inline] fn value(v: f32) -> Self { v as f64 } } } mod generic_math { /// Alias for `euclid::default::Point2D`. pub use euclid::default::Point2D as Point; /// Alias for `euclid::default::Vector2D`. pub use euclid::default::Vector2D as Vector; /// Alias for `euclid::default::Size2D`. pub use euclid::default::Size2D as Size; /// Alias for `euclid::default::Rect` pub use euclid::default::Rect; /// Alias for `euclid::default::Transform2D` pub type Transform = euclid::default::Transform2D; /// Alias for `euclid::default::Rotation2D` pub type Rotation = euclid::default::Rotation2D; /// Alias for `euclid::default::Translation2D` pub type Translation = euclid::Translation2D; /// Alias for `euclid::default::Scale` pub use euclid::default::Scale; /// An angle in radians. pub use euclid::Angle; /// Shorthand for `Rect::new(Point::new(x, y), Size::new(w, h))`. pub use euclid::rect; /// Shorthand for `Vector::new(x, y)`. pub use euclid::vec2 as vector; /// Shorthand for `Point::new(x, y)`. pub use euclid::point2 as point; /// Shorthand for `Size::new(x, y)`. pub use euclid::size2 as size; } pub mod math { //! Basic types that are used everywhere. Most other lyon crates //! reexport them. use euclid; /// Alias for ```euclid::default::Point2D```. pub type Point = euclid::default::Point2D; /// Alias for ```euclid::default::Point2D```. pub type F64Point = euclid::default::Point2D; /// Alias for ```euclid::default::Point2D```. pub type Vector = euclid::default::Vector2D; /// Alias for ```euclid::default::Size2D```. pub type Size = euclid::default::Size2D; /// Alias for ```euclid::default::Rect``` pub type Rect = euclid::default::Rect; /// Alias for ```euclid::default::Transform2D``` pub type Transform = euclid::default::Transform2D; /// Alias for ```euclid::default::Rotation2D``` pub type Rotation = euclid::default::Rotation2D; /// Alias for ```euclid::default::Translation2D``` pub type Translation = euclid::Translation2D; /// Alias for ```euclid::default::Scale``` pub type Scale = euclid::default::Scale; /// An angle in radians (f32). pub type Angle = euclid::Angle; /// Shorthand for `Rect::new(Point::new(x, y), Size::new(w, h))`. pub use euclid::rect; /// Shorthand for `Vector::new(x, y)`. pub use euclid::vec2 as vector; /// Shorthand for `Point::new(x, y)`. pub use euclid::point2 as point; /// Shorthand for `Size::new(x, y)`. pub use euclid::size2 as size; } pub mod traits { pub use crate::segment::{Segment, FlatteningStep}; //pub use monotonic::MonotonicSegment; use crate::generic_math::{Point, Vector, Transform, Rotation, Translation, Scale}; use crate::Scalar; pub trait Transformation { fn transform_point(&self, p: Point) -> Point; fn transform_vector(&self, v: Vector) -> Vector; } impl Transformation for Transform { fn transform_point(&self, p: Point) -> Point { self.transform_point(p) } fn transform_vector(&self, v: Vector) -> Vector { self.transform_vector(v) } } impl Transformation for Rotation { fn transform_point(&self, p: Point) -> Point { self.transform_point(p) } fn transform_vector(&self, v: Vector) -> Vector { self.transform_vector(v) } } impl Transformation for Translation { fn transform_point(&self, p: Point) -> Point { self.transform_point(p) } fn transform_vector(&self, v: Vector) -> Vector { v } } impl Transformation for Scale { fn transform_point(&self, p: Point) -> Point { self.transform_point(p) } fn transform_vector(&self, v: Vector) -> Vector { self.transform_vector(v) } } } lyon_geom-0.15.0/src/line.rs010064400017500001750000000604611360117331400141000ustar0000000000000000use crate::scalar::Scalar; use crate::generic_math::{Point, point, Vector, vector, Rect, Size}; use crate::segment::{Segment, FlatteningStep, BoundingRect}; use crate::monotonic::MonotonicSegment; use crate::utils::min_max; use crate::traits::Transformation; use std::mem::swap; use std::ops::Range; /// A linear segment. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct LineSegment { pub from: Point, pub to: Point, } impl LineSegment { /// Sample the segment at t (expecting t between 0 and 1). #[inline] pub fn sample(&self, t: S) -> Point { self.from.lerp(self.to, t) } /// Sample the x coordinate of the segment at t (expecting t between 0 and 1). #[inline] pub fn x(&self, t: S) -> S { self.from.x * (S::ONE - t) + self.to.x * t } /// Sample the y coordinate of the segment at t (expecting t between 0 and 1). #[inline] pub fn y(&self, t: S) -> S { self.from.y * (S::ONE - t) + self.to.y * t } #[inline] pub fn from(&self) -> Point { self.from } #[inline] pub fn to(&self) -> Point { self.to } pub fn solve_t_for_x(&self, x: S) -> S { let dx = self.to.x - self.from.x; if dx == S::ZERO { return S::ZERO; } (x - self.from.x) / dx } pub fn solve_t_for_y(&self, y: S) -> S { let dy = self.to.y - self.from.y; if dy == S::ZERO { return S::ZERO } (y - self.from.y) / dy } pub fn solve_y_for_x(&self, x: S) -> S { self.y(self.solve_t_for_x(x)) } pub fn solve_x_for_y(&self, y: S) -> S { self.x(self.solve_t_for_y(y)) } /// Returns an inverted version of this segment where the beginning and the end /// points are swapped. #[inline] pub fn flip(&self) -> Self { LineSegment { from: self.to, to: self.from } } /// Return the sub-segment inside a given range of t. /// /// This is equivalent splitting at the range's end points. pub fn split_range(&self, t_range: Range) -> Self { LineSegment { from: self.from.lerp(self.to, t_range.start), to: self.from.lerp(self.to, t_range.end), } } /// Split this curve into two sub-segments. #[inline] pub fn split(&self, t: S) -> (Self, Self) { let split_point = self.sample(t); return ( LineSegment { from: self.from, to: split_point }, LineSegment { from: split_point, to: self.to }, ); } /// Return the segment before the split point. #[inline] pub fn before_split(&self, t: S) -> Self { LineSegment { from: self.from, to: self.sample(t) } } /// Return the segment after the split point. #[inline] pub fn after_split(&self, t: S) -> Self { LineSegment { from: self.sample(t), to: self.to } } pub fn split_at_x(&self, x: S) -> (Self, Self) { self.split(self.solve_t_for_x(x)) } /// Return the minimum bounding rectangle #[inline] pub fn bounding_rect(&self) -> Rect { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); let width = max_x - min_x; let height = max_y - min_y; Rect::new(Point::new(min_x, min_y), Size::new(width, height)) } #[inline] fn bounding_range_x(&self) -> (S, S) { min_max(self.from.x, self.to.x) } #[inline] fn bounding_range_y(&self) -> (S, S) { min_max(self.from.y, self.to.y) } /// Returns the vector between this segment's `from` and `to` points. #[inline] pub fn to_vector(&self) -> Vector { self.to - self.from } /// Returns the line containing this segment. #[inline] pub fn to_line(&self) -> Line { Line { point: self.from, vector: self.to - self.from, } } /// Computes the length of this segment. #[inline] pub fn length(&self) -> S { self.to_vector().length() } /// Changes the segment's length, moving destination point. pub fn set_length(&mut self, new_length: S) { let v = self.to_vector(); let old_length = v.length(); self.to = self.from + v * (new_length / old_length); } #[inline] pub fn translate(&mut self, by: Vector) -> Self { LineSegment { from: self.from + by, to: self.to + by, } } /// Applies the transform to this segment and returns the results. #[inline] pub fn transformed>(&self, transform: &T) -> Self { LineSegment { from: transform.transform_point(self.from), to: transform.transform_point(self.to), } } /// Computes the intersection (if any) between this segment and another one. /// /// The result is provided in the form of the `t` parameter of each /// segment. To get the intersection point, sample one of the segments /// at the corresponding value. pub fn intersection_t(&self, other: &Self) -> Option<(S, S)> { let (min1, max1) = self.bounding_range_x(); let (min2, max2) = other.bounding_range_x(); if min1 > max2 || max1 < min2 { return None; } if self.to == other.to || self.from == other.from || self.from == other.to || self.to == other.from { return None; } let v1 = self.to_vector(); let v2 = other.to_vector(); let v1_cross_v2 = v1.cross(v2); if v1_cross_v2 == S::ZERO { // The segments are parallel return None; } let sign_v1_cross_v2 = S::signum(v1_cross_v2); let abs_v1_cross_v2 = S::abs(v1_cross_v2); let v3 = other.from - self.from; // t and u should be divided by v1_cross_v2, but we postpone that to not lose precision. // We have to respect the sign of v1_cross_v2 (and therefore t and u) so we apply it now and // will use the absolute value of v1_cross_v2 afterwards. let t = v3.cross(v2) * sign_v1_cross_v2; let u = v3.cross(v1) * sign_v1_cross_v2; if t < S::ZERO || t > abs_v1_cross_v2 || u < S::ZERO || u > abs_v1_cross_v2 { return None; } Some(( t / abs_v1_cross_v2, u / abs_v1_cross_v2, )) } #[inline] pub fn intersection(&self, other: &Self) -> Option> { self.intersection_t(other).map(|(t, _)| self.sample(t)) } pub fn line_intersection_t(&self, line: &Line) -> Option { let v1 = self.to_vector(); let v2 = line.vector; let v1_cross_v2 = v1.cross(v2); if v1_cross_v2 == S::ZERO { // The segments are parallel return None; } let sign_v1_cross_v2 = S::signum(v1_cross_v2); let abs_v1_cross_v2 = S::abs(v1_cross_v2); let v3 = line.point - self.from; let t = v3.cross(v2) * sign_v1_cross_v2; if t < S::ZERO || t > abs_v1_cross_v2 { return None; } Some(t / abs_v1_cross_v2) } #[inline] pub fn line_intersection(&self, line: &Line) -> Option> { self.line_intersection_t(line).map(|t| self.sample(t)) } pub fn horizontal_line_intersection_t(&self, y: S) -> Option { Self::axis_aligned_intersection_1d(self.from.y, self.to.y, y) } pub fn vertical_line_intersection_t(&self, x: S) -> Option { Self::axis_aligned_intersection_1d(self.from.x, self.to.x, x) } #[inline] pub fn horizontal_line_intersection(&self, y: S) -> Option> { self.horizontal_line_intersection_t(y).map(|t| self.sample(t)) } #[inline] pub fn vertical_line_intersection(&self, x: S) -> Option> { self.vertical_line_intersection_t(x).map(|t| self.sample(t)) } fn axis_aligned_intersection_1d(mut a: S, mut b: S, v: S) -> Option { let swap = a > b; if swap { std::mem::swap(&mut a, &mut b); } let d = b - a; if d == S::ZERO { return None; } let t = (v - a) / d; if t < S::ZERO || t > S::ONE { return None; } Some(if swap { S::ONE - t } else { t }) } #[inline] pub fn intersects(&self, other: &Self) -> bool { self.intersection_t(other).is_some() } #[inline] pub fn intersects_line(&self, line: &Line) -> bool { self.line_intersection_t(line).is_some() } pub fn overlaps_line(&self, line: &Line) -> bool { let v1 = self.to_vector(); let v2 = line.vector; let v3 = line.point - self.from; v1.cross(v2) == S::ZERO && v1.cross(v3) == S::ZERO } pub fn overlaps_segment(&self, other: &LineSegment) -> bool { if !self.overlaps_line(&other.to_line()) { return false; } let v1 = self.to - self.from; let v2 = other.from - self.from; let v3 = other.to - self.from; let mut a = S::ZERO; let mut b = v1.dot(v1); let mut c = v1.dot(v2); let mut d = v1.dot(v3); if a > b { swap(&mut a, &mut b); } if c > d { swap(&mut d, &mut c); } (c > a && c < b) || (d > a && d < b) || (a > c && a < d) || (b > c && b < d) || (a == c && b == d) } pub fn contains_segment(&self, other: &LineSegment) -> bool { if !self.overlaps_line(&other.to_line()) { return false; } let v1 = self.to - self.from; let v2 = other.from - self.from; let v3 = other.to - self.from; let mut a = S::ZERO; let mut b = v1.dot(v1); let mut c = v1.dot(v2); let mut d = v1.dot(v3); if a > b { swap(&mut a, &mut b); } if c > d { swap(&mut d, &mut c); } c >= a && c <= b && d >= a && d <= b } } impl Segment for LineSegment { type Scalar = S; fn from(&self) -> Point { self.from } fn to(&self) -> Point { self.to } fn sample(&self, t: S) -> Point { self.sample(t) } fn x(&self, t: S) -> S { self.x(t) } fn y(&self, t: S) -> S { self.y(t) } fn derivative(&self, _t: S) -> Vector { self.to_vector() } fn dx(&self, _t: S) -> S { self.to.x - self.from.x } fn dy(&self, _t: S) -> S { self.to.y - self.from.y } fn split_range(&self, t_range: Range) -> Self { self.split_range(t_range) } fn split(&self, t: S) -> (Self, Self) { self.split(t) } fn before_split(&self, t: S) -> Self { self.before_split(t) } fn after_split(&self, t: S) -> Self { self.after_split(t) } fn flip(&self) -> Self { self.flip() } fn approximate_length(&self, _tolerance: S) -> S { self.length() } } impl BoundingRect for LineSegment { type Scalar = S; fn bounding_rect(&self) -> Rect { self.bounding_rect() } fn fast_bounding_rect(&self) -> Rect { self.bounding_rect() } fn bounding_range_x(&self) -> (S, S) { self.bounding_range_x() } fn bounding_range_y(&self) -> (S, S) { self.bounding_range_y() } fn fast_bounding_range_x(&self) -> (S, S) { self.bounding_range_x() } fn fast_bounding_range_y(&self) -> (S, S) { self.bounding_range_y() } } impl MonotonicSegment for LineSegment { type Scalar = S; fn solve_t_for_x(&self, x: S, _t_range: Range, _tolerance: S) -> S { self.solve_t_for_x(x) } } impl FlatteningStep for LineSegment { fn flattening_step(&self, _tolerance: S) -> S { S::ONE } } /// An infinite line defined by a point and a vector. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct Line { pub point: Point, pub vector: Vector, } impl Line { pub fn intersection(&self, other: &Self) -> Option> { let det = self.vector.cross(other.vector); if S::abs(det) <= S::EPSILON { // The lines are very close to parallel return None; } let inv_det = S::ONE / det; let self_p2 = self.point + self.vector; let other_p2 = other.point + other.vector; let a = self.point.to_vector().cross(self_p2.to_vector()); let b = other.point.to_vector().cross(other_p2.to_vector()); return Some( point( (b * self.vector.x - a * other.vector.x) * inv_det, (b * self.vector.y - a * other.vector.y) * inv_det, ) ); } pub fn signed_distance_to_point(&self, p: &Point) -> S { let v1 = self.point.to_vector(); let v2 = v1 + self.vector; (self.vector.cross(p.to_vector()) + v1.cross(v2)) / self.vector.length() } pub fn distance_to_point(&self, p: &Point) -> S { S::abs(self.signed_distance_to_point(p)) } pub fn equation(&self) -> LineEquation { let a = -self.vector.y; let b = self.vector.x; let c = -(a * self.point.x + b * self.point.y); LineEquation::new(a, b, c) } } /// A line defined by the equation /// `a * x + b * y + c = 0; a * a + b * b = 1`. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct LineEquation { a: S, b: S, c: S, } impl LineEquation { pub fn new(a: S, b: S, c: S) -> Self { debug_assert!(a != S::ZERO || b != S::ZERO); let div = S::ONE / S::sqrt(a * a + b * b); LineEquation { a: a * div, b: b * div, c: c * div } } #[inline] pub fn a(&self) -> S { self.a } #[inline] pub fn b(&self) -> S { self.b } #[inline] pub fn c(&self) -> S { self.c } pub fn project_point(&self, p: &Point) -> Point { point( self.b * (self.b * p.x - self.a * p.y) - self.a * self.c, self.a * (self.a * p.y - self.b * p.x) - self.b * self.c, ) } #[inline] pub fn signed_distance_to_point(&self, p: &Point) -> S { self.a * p.x + self.b * p.y + self.c } #[inline] pub fn distance_to_point(&self, p: &Point) -> S { S::abs(self.signed_distance_to_point(p)) } #[inline] pub fn invert(&self) -> Self { LineEquation { a: -self.a, b: -self.b, c: -self.c } } #[inline] pub fn parallel_line(&self, p: &Point) -> Self { let c = -(self.a * p.x + self.b * p.y); LineEquation { a: self.a, b: self.b, c } } #[inline] pub fn offset(&self, d: S) -> Self { LineEquation { a: self.a, b: self.b, c: self.c - d } } #[inline] pub fn tangent(&self) -> Vector { vector(self.b, -self.a) } #[inline] pub fn normal(&self) -> Vector { vector(self.a, self.b) } #[inline] pub fn solve_y_for_x(&self, x: S) -> Option { if self.b == S::ZERO { return None; } Some((self.a * x + self.c) / -self.b) } #[inline] pub fn solve_x_for_y(&self, y: S) -> Option { if self.a == S::ZERO { return None; } Some((self.b * y + self.c) / -self.a) } #[inline] pub fn is_horizontal(&self) -> bool { self.a == S::ZERO } #[inline] pub fn is_vertical(&self) -> bool { self.b == S::ZERO } } #[cfg(test)] fn fuzzy_eq_f32(a: f32, b: f32, epsilon: f32) -> bool { return f32::abs(a - b) <= epsilon; } #[cfg(test)] fn fuzzy_eq_vector(a: Vector, b: Vector, epsilon: f32) -> bool { fuzzy_eq_f32(a.x, b.x, epsilon) && fuzzy_eq_f32(a.y, b.y, epsilon) } #[cfg(test)] fn fuzzy_eq_point(a: Point, b: Point, epsilon: f32) -> bool { fuzzy_eq_vector(a.to_vector(), b.to_vector(), epsilon) } #[test] fn intersection_rotated() { use std::f32::consts::PI; let epsilon = 0.0001; let count: u32 = 100; for i in 0..count { for j in 0..count { if i % (count / 2) == j % (count / 2) { // avoid the colinear case. continue; } let angle1 = i as f32 / (count as f32) * 2.0 * PI; let angle2 = j as f32 / (count as f32) * 2.0 * PI; let l1 = LineSegment { from: point(10.0 * angle1.cos(), 10.0 * angle1.sin()), to: point(-10.0 * angle1.cos(), -10.0 * angle1.sin()), }; let l2 = LineSegment { from: point(10.0 * angle2.cos(), 10.0 * angle2.sin()), to: point(-10.0 * angle2.cos(), -10.0 * angle2.sin()), }; assert!(l1.intersects(&l2)); assert!( fuzzy_eq_point( l1.sample(l1.intersection_t(&l2).unwrap().0), point(0.0, 0.0), epsilon ) ); assert!( fuzzy_eq_point( l2.sample(l1.intersection_t(&l2).unwrap().1), point(0.0, 0.0), epsilon ) ); } } } #[test] fn intersection_touching() { let l1 = LineSegment { from: point(0.0, 0.0), to: point(10.0, 10.0), }; let l2 = LineSegment { from: point(10.0, 10.0), to: point(10.0, 0.0), }; assert!(!l1.intersects(&l2)); assert!(l1.intersection(&l2).is_none()); } #[test] fn intersection_overlap() { // It's hard to define the intersection points of two segments that overlap, // (would be a region rather than a point) and more importanly, in practice // the algorithms in lyon don't need to consider this special case as an intersection, // so we choose to treat overlapping segments as not intersecting. let l1 = LineSegment { from: point(0.0, 0.0), to: point(10.0, 0.0), }; let l2 = LineSegment { from: point(5.0, 00.0), to: point(15.0, 0.0), }; assert!(!l1.intersects(&l2)); assert!(l1.intersection(&l2).is_none()); } #[cfg(test)] use euclid::rect; #[cfg(test)] use euclid::approxeq::ApproxEq; #[test] fn bounding_rect() { let l1 = LineSegment { from: point(1., 5.), to: point(5., 7.), }; let r1 = rect(1., 5., 4., 2.); let l2 = LineSegment { from: point(5., 5.), to: point(1., 1.), }; let r2 = rect(1., 1., 4., 4.); let l3 = LineSegment { from: point(3., 3.), to: point(1., 5.), }; let r3 = rect(1., 3., 2., 2.); let cases = vec![(l1, r1), (l2, r2), (l3, r3)]; for &(ls, r) in &cases { assert_eq!(ls.bounding_rect(), r); } } #[test] fn distance_to_point() { use crate::math::vector; let l1 = Line { point: point(2.0f32, 3.0), vector: vector(-1.5, 0.0), }; let l2 = Line { point: point(3.0f32, 3.0), vector: vector(1.5, 1.5), }; assert!(l1.signed_distance_to_point(&point(1.1, 4.0)).approx_eq(&-1.0)); assert!(l1.signed_distance_to_point(&point(2.3, 2.0)).approx_eq(&1.0)); assert!(l2.signed_distance_to_point(&point(1.0, 0.0)).approx_eq(&(-f32::sqrt(2.0)/2.0))); assert!(l2.signed_distance_to_point(&point(0.0, 1.0)).approx_eq(&(f32::sqrt(2.0)/2.0))); assert!(l1.equation().distance_to_point(&point(1.1, 4.0)).approx_eq(&1.0)); assert!(l1.equation().distance_to_point(&point(2.3, 2.0)).approx_eq(&1.0)); assert!(l2.equation().distance_to_point(&point(1.0, 0.0)).approx_eq(&(f32::sqrt(2.0)/2.0))); assert!(l2.equation().distance_to_point(&point(0.0, 1.0)).approx_eq(&(f32::sqrt(2.0)/2.0))); assert!(l1.equation().signed_distance_to_point(&point(1.1, 4.0)).approx_eq(&l1.signed_distance_to_point(&point(1.1, 4.0)))); assert!(l1.equation().signed_distance_to_point(&point(2.3, 2.0)).approx_eq(&l1.signed_distance_to_point(&point(2.3, 2.0)))); assert!(l2.equation().signed_distance_to_point(&point(1.0, 0.0)).approx_eq(&l2.signed_distance_to_point(&point(1.0, 0.0)))); assert!(l2.equation().signed_distance_to_point(&point(0.0, 1.0)).approx_eq(&l2.signed_distance_to_point(&point(0.0, 1.0)))); } #[test] fn solve_y_for_x() { let line = Line { point: Point::new(1.0, 1.0), vector: Vector::new(2.0, 4.0), }; let eqn = line.equation(); if let Some(y) = eqn.solve_y_for_x(line.point.x) { println!("{:?} != {:?}", y, line.point.y); assert!(f64::abs(y - line.point.y) < 0.000001) } if let Some(x) = eqn.solve_x_for_y(line.point.y) { assert!(f64::abs(x - line.point.x) < 0.000001) } let mut angle = 0.1; for _ in 0..100 { let (sin, cos) = f64::sin_cos(angle); let line = Line { point: Point::new(-1000.0, 600.0), vector: Vector::new(cos * 100.0, sin * 100.0), }; let eqn = line.equation(); if let Some(y) = eqn.solve_y_for_x(line.point.x) { println!("{:?} != {:?}", y, line.point.y); assert!(f64::abs(y - line.point.y) < 0.000001) } if let Some(x) = eqn.solve_x_for_y(line.point.y) { assert!(f64::abs(x - line.point.x) < 0.000001) } angle += 0.001; } } #[test] fn offset() { let l1 = LineEquation::new(2.0, 3.0, 1.0); let p = Point::new(10.0, 3.0); let d = l1.signed_distance_to_point(&p); let l2 = l1.offset(d); assert!(l2.distance_to_point(&p) < 0.0000001f64); } #[test] fn set_length() { let mut a = LineSegment { from: point(10.0, 1.0), to: point(100.0, -15.0), }; a.set_length(1.0); assert!(a.length().approx_eq(&1.0)); a.set_length(1.5); assert!(a.length().approx_eq(&1.5)); a.set_length(100.0); assert!(a.length().approx_eq(&100.0)); a.set_length(-1.0); assert!(a.length().approx_eq(&1.0)); } #[test] fn overlap() { assert!( LineSegment { from: point(0.0, 0.0), to: point(-1.0, 0.0), }.overlaps_line( &Line { point: point(100.0, 0.0), vector: vector(10.0, 0.0), } ) ); assert!( LineSegment { from: point(0.0, 0.0), to: point(1.0, 0.0), }.overlaps_line( &Line { point: point(0.0, 0.0), vector: vector(1.0, 0.0), } ) ); assert!( LineSegment { from: point(0.0, 0.0), to: point(1.0, 0.0), }.overlaps_segment( &LineSegment { from: point(0.0, 0.0), to: point(1.0, 0.0), } ) ); assert!( !LineSegment { from: point(0.0, 0.0), to: point(1.0, 0.0), }.overlaps_line( &Line { point: point(0.0, 1.0), vector: vector(1.0, 1.0), } ) ); } #[test] fn contains_segment() { assert!( LineSegment { from: point(-1.0, 1.0), to: point(4.0, 1.0), }.contains_segment( &LineSegment { from: point(2.0, 1.0), to: point(1.0, 1.0), } ) ); } #[test] fn horizontal_line_intersection() { let segment = LineSegment { from: point(1.0, 2.0), to: point(2.0, 3.0), }; assert_eq!(segment.horizontal_line_intersection_t(2.0), Some(0.0)); assert_eq!(segment.horizontal_line_intersection_t(2.25), Some(0.25)); assert_eq!(segment.horizontal_line_intersection_t(2.5), Some(0.5)); assert_eq!(segment.horizontal_line_intersection_t(2.75), Some(0.75)); assert_eq!(segment.horizontal_line_intersection_t(3.0), Some(1.0)); assert_eq!(segment.horizontal_line_intersection_t(1.5), None); assert_eq!(segment.horizontal_line_intersection_t(3.5), None); let segment = LineSegment { from: point(2.0, 3.0), to: point(1.0, 2.0), }; assert_eq!(segment.horizontal_line_intersection_t(2.0), Some(1.0)); assert_eq!(segment.horizontal_line_intersection_t(2.25), Some(0.75)); assert_eq!(segment.horizontal_line_intersection_t(2.5), Some(0.5)); assert_eq!(segment.horizontal_line_intersection_t(2.75), Some(0.25)); assert_eq!(segment.horizontal_line_intersection_t(3.0), Some(0.0)); assert_eq!(segment.horizontal_line_intersection_t(1.5), None); assert_eq!(segment.horizontal_line_intersection_t(3.5), None); } lyon_geom-0.15.0/src/monotonic.rs010064400017500001750000000302761357773320200151710ustar0000000000000000use crate::segment::{Segment, BoundingRect}; use crate::scalar::{Scalar, NumCast}; use crate::generic_math::{Point, Vector, Rect}; use crate::{QuadraticBezierSegment, CubicBezierSegment}; use std::ops::Range; use arrayvec::ArrayVec; use std::f64; pub(crate) trait MonotonicSegment { type Scalar: Scalar; fn solve_t_for_x(&self, x: Self::Scalar, t_range: Range, tolerance: Self::Scalar) -> Self::Scalar; } /// A x and y monotonic curve segment, for example `Monotonic`. #[derive(Copy, Clone, Debug)] pub struct Monotonic { pub(crate) segment: T, } impl Monotonic { #[inline] pub fn segment(&self) -> &T { &self.segment } #[inline] pub fn from(&self) -> Point { self.segment.from() } #[inline] pub fn to(&self) -> Point { self.segment.to() } #[inline] pub fn sample(&self, t: T::Scalar) -> Point { self.segment.sample(t) } #[inline] pub fn x(&self, t: T::Scalar) -> T::Scalar { self.segment.x(t) } #[inline] pub fn y(&self, t: T::Scalar) -> T::Scalar { self.segment.y(t) } #[inline] pub fn derivative(&self, t: T::Scalar) -> Vector { self.segment.derivative(t) } #[inline] pub fn dx(&self, t: T::Scalar) -> T::Scalar { self.segment.dx(t) } #[inline] pub fn dy(&self, t: T::Scalar) -> T::Scalar { self.segment.dy(t) } #[inline] pub fn split_range(&self, t_range: Range) -> Self { Self { segment: self.segment.split_range(t_range) } } #[inline] pub fn split(&self, t: T::Scalar) -> (Self, Self) { let (a, b) = self.segment.split(t); (Self { segment: a }, Self { segment: b }) } #[inline] pub fn before_split(&self, t: T::Scalar) -> Self { Self { segment: self.segment.before_split(t) } } #[inline] pub fn after_split(&self, t: T::Scalar) -> Self { Self { segment: self.segment.after_split(t) } } #[inline] pub fn flip(&self) -> Self { Self { segment: self.segment.flip() } } #[inline] pub fn approximate_length(&self, tolerance: T::Scalar) -> T::Scalar { self.segment.approximate_length(tolerance) } } impl Segment for Monotonic { impl_segment!(T::Scalar); } impl BoundingRect for Monotonic { type Scalar = T::Scalar; fn bounding_rect(&self) -> Rect { // For monotonic segments the fast bounding rect approximation // is exact. self.segment.fast_bounding_rect() } fn fast_bounding_rect(&self) -> Rect { self.segment.fast_bounding_rect() } fn bounding_range_x(&self) -> (T::Scalar, T::Scalar) { self.segment.bounding_range_x() } fn bounding_range_y(&self) -> (T::Scalar, T::Scalar) { self.segment.bounding_range_y() } fn fast_bounding_range_x(&self) -> (T::Scalar, T::Scalar) { self.segment.fast_bounding_range_x() } fn fast_bounding_range_y(&self) -> (T::Scalar, T::Scalar) { self.segment.fast_bounding_range_y() } } impl Monotonic> { pub fn solve_t_for_x(&self, x: S) -> S { Self::solve_t( NumCast::from(self.segment.from.x).unwrap(), NumCast::from(self.segment.ctrl.x).unwrap(), NumCast::from(self.segment.to.x).unwrap(), NumCast::from(x).unwrap(), ) } pub fn solve_t_for_y(&self, y: S) -> S { Self::solve_t( NumCast::from(self.segment.from.y).unwrap(), NumCast::from(self.segment.ctrl.y).unwrap(), NumCast::from(self.segment.to.y).unwrap(), NumCast::from(y).unwrap(), ) } fn solve_t(from: f64, ctrl: f64, to: f64, x: f64) -> S { let a = from - 2.0 * ctrl + to; let b = -2.0 * from + 2.0 * ctrl; let c = from - x; let t = 2.0 * c / (-b - f64::sqrt(b * b - 4.0 * a * c)); NumCast::from(t.max(0.0).min(1.0)).unwrap() } #[inline] pub fn split_at_x(&self, x: S) -> (Self, Self) { self.split(self.solve_t_for_x(x)) } pub fn intersections_t( &self, self_t_range: Range, other: &Self, other_t_range: Range, tolerance: S, ) -> ArrayVec<[(S, S);2]> { monotonic_segment_intersecions( self, self_t_range, other, other_t_range, tolerance ) } pub fn intersections( &self, self_t_range: Range, other: &Self, other_t_range: Range, tolerance: S, ) -> ArrayVec<[Point;2]> { let intersections = monotonic_segment_intersecions( self, self_t_range, other, other_t_range, tolerance ); let mut result = ArrayVec::new(); for (t, _) in intersections { result.push(self.sample(t)); } result } pub fn first_intersection_t( &self, self_t_range: Range, other: &Self, other_t_range: Range, tolerance: S, ) -> Option<(S, S)> { first_monotonic_segment_intersecion( self, self_t_range, other, other_t_range, tolerance ) } pub fn first_intersection( &self, self_t_range: Range, other: &Self, other_t_range: Range, tolerance: S, ) -> Option> { first_monotonic_segment_intersecion( self, self_t_range, other, other_t_range, tolerance ).map(|(t, _)|{ self.sample(t) }) } } impl MonotonicSegment for Monotonic> { type Scalar = S; fn solve_t_for_x(&self, x: S, _t_range: Range, _tolerance: S) -> S { self.solve_t_for_x(x) } } impl Monotonic> { pub fn solve_t_for_x(&self, x: S, t_range: Range, tolerance: S) -> S { debug_assert!(t_range.start <= t_range.end); let from = self.x(t_range.start); let to = self.x(t_range.end); if x <= from { return t_range.start; } if x >= to { return t_range.end; } // Newton's method. let mut t = x - from / (to - from); for _ in 0..8 { let x2 = self.x(t); if S::abs(x2 - x) <= tolerance { return t } let dx = self.dx(t); if dx <= S::EPSILON { break } t = t - (x2 - x) / dx; } // Fall back to binary search. let mut min = t_range.start; let mut max = t_range.end; let mut t = S::HALF; while min < max { let x2 = self.x(t); if S::abs(x2 - x) < tolerance { return t; } if x > x2 { min = t; } else { max = t; } t = (max - min) * S::HALF + min; } return t; } #[inline] pub fn split_at_x(&self, x: S) -> (Self, Self) { // TODO tolerance param. self.split(self.solve_t_for_x(x, S::ZERO..S::ONE, S::value(0.001))) } } impl MonotonicSegment for Monotonic> { type Scalar = S; fn solve_t_for_x(&self, x: S, t_range: Range, tolerance: S) -> S { self.solve_t_for_x(x, t_range, tolerance) } } /// Return the first intersection point (if any) of two monotonic curve /// segments. /// /// Both segments must be monotonically increasing in x. pub(crate) fn first_monotonic_segment_intersecion( a: &A, a_t_range: Range, b: &B, b_t_range: Range, tolerance: S, ) -> Option<(S, S)> where A: Segment + MonotonicSegment + BoundingRect, B: Segment + MonotonicSegment + BoundingRect, { debug_assert!(a.from().x <= a.to().x); debug_assert!(b.from().x <= b.to().x); // We need to have a stricter tolerance in solve_t_for_x otherwise // the error accumulation becomes pretty bad. let tx_tolerance = tolerance / S::TEN; let (a_min, a_max) = a.split_range(a_t_range).fast_bounding_range_x(); let (b_min, b_max) = b.split_range(b_t_range).fast_bounding_range_x(); if a_min > b_max || a_max < b_min { return None; } let mut min_x = S::max(a_min, b_min); let mut max_x = S::min(a_max, b_max); let mut t_min_a = a.solve_t_for_x(min_x, S::ZERO..S::ONE, tx_tolerance); let mut t_max_a = a.solve_t_for_x(max_x, t_min_a..S::ONE, tx_tolerance); let mut t_min_b = b.solve_t_for_x(min_x, S::ZERO..S::ONE, tx_tolerance); let mut t_max_b = b.solve_t_for_x(max_x, t_min_b..S::ONE, tx_tolerance); const MAX_ITERATIONS: u32 = 32; for _ in 0..MAX_ITERATIONS { let y_max_a = a.y(t_max_a); let y_max_b = b.y(t_max_b); // It would seem more sensible to use the mid point instead of // the max point, but using the mid point means we don't know whether // the approximation will be slightly before or slightly after the // point. // Using the max point ensures that the we return an approximation // that is always slightly after the real intersection, which // means that if we search for intersections after the one we // found, we are not going to converge towards it again. if S::abs(y_max_a - y_max_b) < tolerance { return Some((t_max_a, t_max_b)); } let mid_x = (min_x + max_x) * S::HALF; let t_mid_a = a.solve_t_for_x(mid_x, t_min_a..t_max_a, tx_tolerance); let t_mid_b = b.solve_t_for_x(mid_x, t_min_b..t_max_b, tx_tolerance); let y_mid_a = a.y(t_mid_a); let y_min_a = a.y(t_min_a); let y_mid_b = b.y(t_mid_b); let y_min_b = b.y(t_min_b); let min_sign = S::signum(y_min_a - y_min_b); let mid_sign = S::signum(y_mid_a - y_mid_b); let max_sign = S::signum(y_max_a - y_max_b); if min_sign != mid_sign { max_x = mid_x; t_max_a = t_mid_a; t_max_b = t_mid_b; } else if max_sign != mid_sign { min_x = mid_x; t_min_a = t_mid_a; t_min_b = t_mid_b; } else { // TODO: This is not always correct: if the min, max and mid // points are all on the same side, we consider that there is // no intersection, but there could be a pair of intersections // between the min/max and the mid point. break; } } None } /// Return the intersection points (if any) of two monotonic curve /// segments. /// /// Both segments must be monotonically increasing in x. pub(crate) fn monotonic_segment_intersecions( a: &A, a_t_range: Range, b: &B, b_t_range: Range, tolerance: S, ) -> ArrayVec<[(S, S); 2]> where A: Segment + MonotonicSegment + BoundingRect, B: Segment + MonotonicSegment + BoundingRect, { let (t1, t2) = match first_monotonic_segment_intersecion( a, a_t_range.clone(), b, b_t_range.clone(), tolerance ) { Some(intersection) => { intersection } None => { return ArrayVec::new(); } }; let mut result = ArrayVec::new(); result.push((t1, t2)); match first_monotonic_segment_intersecion( a, t1..a_t_range.end, b, t2..b_t_range.end, tolerance ) { Some(intersection) => { result.push(intersection); } None => {} } result } #[test] fn two_intersections() { use crate::QuadraticBezierSegment; use crate::math::point; let c1 = QuadraticBezierSegment { from: point(10.0, 0.0), ctrl: point(10.0, 90.0), to: point(100.0, 90.0), }.assume_monotonic(); let c2 = QuadraticBezierSegment { from: point(0.0, 10.0), ctrl: point(90.0, 10.0), to: point(90.0, 100.0), }.assume_monotonic(); let intersections = monotonic_segment_intersecions( &c1, 0.0..1.0, &c2, 0.0..1.0, 0.001, ); assert_eq!(intersections.len(), 2); assert!(intersections[0].0 < 0.1, "{:?} < 0.1", intersections[0].0); assert!(intersections[1].1 > 0.9, "{:?} > 0.9", intersections[0].1); } lyon_geom-0.15.0/src/quadratic_bezier.rs010064400017500001750000000611641360117331400164670ustar0000000000000000use crate::{CubicBezierSegment, Triangle, Line, LineSegment, LineEquation}; use crate::scalar::Scalar; use crate::generic_math::{Point, Vector, Rect, rect}; use crate::monotonic::Monotonic; use crate::segment::{Segment, FlatteningStep, BoundingRect}; use crate::segment; use crate::traits::Transformation; use arrayvec::ArrayVec; use std::ops::Range; use std::mem; /// A flattening iterator for quadratic bézier segments. pub type Flattened = segment::Flattened>; /// A 2d curve segment defined by three points: the beginning of the segment, a control /// point and the end of the segment. /// /// The curve is defined by equation: /// ```∀ t ∈ [0..1], P(t) = (1 - t)² * from + 2 * (1 - t) * t * ctrl + 2 * t² * to``` #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct QuadraticBezierSegment { pub from: Point, pub ctrl: Point, pub to: Point, } impl QuadraticBezierSegment { /// Sample the curve at t (expecting t between 0 and 1). pub fn sample(&self, t: S) -> Point { let t2 = t * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; return self.from * one_t2 + self.ctrl.to_vector() * S::TWO * one_t * t + self.to.to_vector() * t2; } /// Sample the x coordinate of the curve at t (expecting t between 0 and 1). pub fn x(&self, t: S) -> S { let t2 = t * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; return self.from.x * one_t2 + self.ctrl.x * S::TWO * one_t * t + self.to.x * t2; } /// Sample the y coordinate of the curve at t (expecting t between 0 and 1). pub fn y(&self, t: S) -> S { let t2 = t * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; return self.from.y * one_t2 + self.ctrl.y * S::TWO * one_t * t + self.to.y * t2; } #[inline] fn derivative_coefficients(&self, t: S) -> (S, S, S) { (S::TWO * t - S::TWO, -S::FOUR * t + S::TWO, S::TWO * t) } /// Sample the curve's derivative at t (expecting t between 0 and 1). pub fn derivative(&self, t: S) -> Vector { let (c0, c1, c2) = self.derivative_coefficients(t); self.from.to_vector() * c0 + self.ctrl.to_vector() * c1 + self.to.to_vector() * c2 } /// Sample the x coordinate of the curve's derivative at t (expecting t between 0 and 1). pub fn dx(&self, t: S) -> S { let (c0, c1, c2) = self.derivative_coefficients(t); self.from.x * c0 + self.ctrl.x * c1 + self.to.x * c2 } /// Sample the y coordinate of the curve's derivative at t (expecting t between 0 and 1). pub fn dy(&self, t: S) -> S { let (c0, c1, c2) = self.derivative_coefficients(t); self.from.y * c0 + self.ctrl.y * c1 + self.to.y * c2 } /// Swap the beginning and the end of the segment. pub fn flip(&self) -> Self { QuadraticBezierSegment { from: self.to, ctrl: self.ctrl, to: self.from, } } /// Find the advancement of the y-most position in the curve. /// /// This returns the advancement along the curve, not the actual y position. pub fn y_maximum_t(&self) -> S { if let Some(t) = self.local_y_extremum_t() { let y = self.y(t); if y > self.from.y && y > self.to.y { return t; } } return if self.from.y > self.to.y { S::ZERO } else { S::ONE }; } /// Find the advancement of the y-least position in the curve. /// /// This returns the advancement along the curve, not the actual y position. pub fn y_minimum_t(&self) -> S { if let Some(t) = self.local_y_extremum_t() { let y = self.y(t); if y < self.from.y && y < self.to.y { return t; } } return if self.from.y < self.to.y { S::ZERO } else { S::ONE }; } /// Return the y inflection point or None if this curve is y-monotonic. pub fn local_y_extremum_t(&self) -> Option { let div = self.from.y - S::TWO * self.ctrl.y + self.to.y; if div == S::ZERO { return None; } let t = (self.from.y - self.ctrl.y) / div; if t > S::ZERO && t < S::ONE { return Some(t); } return None; } /// Find the advancement of the x-most position in the curve. /// /// This returns the advancement along the curve, not the actual x position. pub fn x_maximum_t(&self) -> S { if let Some(t) = self.local_x_extremum_t() { let x = self.x(t); if x > self.from.x && x > self.to.x { return t; } } return if self.from.x > self.to.x { S::ZERO } else { S::ONE }; } /// Find the advancement of the x-least position in the curve. /// /// This returns the advancement along the curve, not the actual x position. pub fn x_minimum_t(&self) -> S { if let Some(t) = self.local_x_extremum_t() { let x = self.x(t); if x < self.from.x && x < self.to.x { return t; } } return if self.from.x < self.to.x { S::ZERO } else { S::ONE }; } /// Return the x inflection point or None if this curve is x-monotonic. pub fn local_x_extremum_t(&self) -> Option { let div = self.from.x - S::TWO * self.ctrl.x + self.to.x; if div == S::ZERO { return None; } let t = (self.from.x - self.ctrl.x) / div; if t > S::ZERO && t < S::ONE { return Some(t); } return None; } /// Return the sub-curve inside a given range of t. /// /// This is equivalent splitting at the range's end points. pub fn split_range(&self, t_range: Range) -> Self { let t0 = t_range.start; let t1 = t_range.end; let from = self.sample(t0); let to = self.sample(t1); let ctrl = from + (self.ctrl - self.from).lerp(self.to - self.ctrl, t0) * (t1 - t0); QuadraticBezierSegment { from, ctrl, to } } /// Split this curve into two sub-curves. pub fn split(&self, t: S) -> (QuadraticBezierSegment, QuadraticBezierSegment) { let split_point = self.sample(t); return (QuadraticBezierSegment { from: self.from, ctrl: self.from.lerp(self.ctrl, t), to: split_point, }, QuadraticBezierSegment { from: split_point, ctrl: self.ctrl.lerp(self.to, t), to: self.to, }); } /// Return the curve before the split point. pub fn before_split(&self, t: S) -> QuadraticBezierSegment { return QuadraticBezierSegment { from: self.from, ctrl: self.from.lerp(self.ctrl, t), to: self.sample(t), }; } /// Return the curve after the split point. pub fn after_split(&self, t: S) -> QuadraticBezierSegment { return QuadraticBezierSegment { from: self.sample(t), ctrl: self.ctrl.lerp(self.to, t), to: self.to, }; } /// Elevate this curve to a third order bézier. pub fn to_cubic(&self) -> CubicBezierSegment { CubicBezierSegment { from: self.from, ctrl1: (self.from + self.ctrl.to_vector() * S::TWO) / S::THREE, ctrl2: (self.to + self.ctrl.to_vector() * S::TWO) / S::THREE, to: self.to, } } #[inline] pub fn baseline(&self) -> LineSegment { LineSegment { from: self.from, to: self.to } } pub fn is_linear(&self, tolerance: S) -> bool { let epsilon = S::EPSILON; if (self.from - self.to).square_length() < epsilon { return false; } let line = self.baseline().to_line().equation(); line.distance_to_point(&self.ctrl) < tolerance } /// Computes a "fat line" of this segment. /// /// A fat line is two convervative lines between which the segment /// is fully contained. pub fn fat_line(&self) -> (LineEquation, LineEquation) { let l1 = self.baseline().to_line().equation(); let d = S::HALF * l1.signed_distance_to_point(&self.ctrl); let l2 = l1.offset(d); if d >= S::ZERO { (l1, l2) } else { (l2, l1) } } /// Applies the transform to this curve and returns the results. #[inline] pub fn transformed>(&self, transform: &T) -> Self { QuadraticBezierSegment { from: transform.transform_point(self.from), ctrl: transform.transform_point(self.ctrl), to: transform.transform_point(self.to) } } /// Find the interval of the begining of the curve that can be approximated with a /// line segment. pub fn flattening_step(&self, tolerance: S) -> S { let v1 = self.ctrl - self.from; let v2 = self.to - self.from; let v1_cross_v2 = v2.x * v1.y - v2.y * v1.x; let h = v1.x.hypot(v1.y); if S::abs(v1_cross_v2 * h) <= S::EPSILON { return S::ONE; } let s2inv = h / v1_cross_v2; let t = S::TWO * S::sqrt(tolerance * S::abs(s2inv) / S::THREE); if t > S::ONE { return S::ONE; } return t; } /// Iterates through the curve invoking a callback at each point. pub fn for_each_flattened(&self, tolerance: S, call_back: &mut F) where F: FnMut(Point) { crate::segment::for_each_flattened(self, tolerance, call_back); } /// Iterates through the curve invoking a callback at each point. pub fn for_each_flattened_with_t(&self, tolerance: S, call_back: &mut F) where F: FnMut(Point, S) { crate::segment::for_each_flattened_with_t(self, tolerance, call_back); } /// Returns the flattened representation of the curve as an iterator, starting *after* the /// current point. pub fn flattened(&self, tolerance: S) -> Flattened { Flattened::new(*self, tolerance) } /// Invokes a callback between each monotonic part of the segment. pub fn for_each_monotonic_t(&self, mut cb: F) where F: FnMut(S), { let mut t0 = self.local_x_extremum_t(); let mut t1 = self.local_x_extremum_t(); let swap = match (t0, t1) { (None, Some(_)) => { true } (Some(tx), Some(ty)) => { tx > ty } _ => { false } }; if swap { mem::swap(&mut t0, &mut t1); } if let Some(t) = t0 { if t < S::ONE { cb(t); } } if let Some(t) = t1 { if t < S::ONE { cb(t); } } } /// Invokes a callback for each monotonic part of the segment.. pub fn for_each_monotonic_range(&self, mut cb: F) where F: FnMut(Range), { let mut t0 = S::ZERO; self.for_each_monotonic_t(|t| { cb(t0..t); t0 = t; }); cb(t0..S::ONE); } pub fn for_each_monotonic(&self, cb: &mut F) where F: FnMut(&Monotonic>) { self.for_each_monotonic_range(|range| { cb(&self.split_range(range).assume_monotonic()) }); } /// Compute the length of the segment using a flattened approximation. pub fn approximate_length(&self, tolerance: S) -> S { let mut from = self.from; let mut len = S::ZERO; self.for_each_flattened(tolerance, &mut|to| { len = len + (to - from).length(); from = to; }); len } /// Returns a triangle containing this curve segment. pub fn bounding_triangle(&self) -> Triangle { Triangle { a: self.from, b: self.ctrl, c: self.to, } } /// Returns a conservative rectangle that contains the curve. pub fn fast_bounding_rect(&self) -> Rect { let (min_x, max_x) = self.fast_bounding_range_x(); let (min_y, max_y) = self.fast_bounding_range_y(); rect(min_x, min_y, max_x - min_x, max_y - min_y) } /// Returns a conservative range of x this curve is contained in. pub fn fast_bounding_range_x(&self) -> (S, S) { let min_x = self.from.x.min(self.ctrl.x).min(self.to.x); let max_x = self.from.x.max(self.ctrl.x).max(self.to.x); (min_x, max_x) } /// Returns a conservative range of y this curve is contained in. pub fn fast_bounding_range_y(&self) -> (S, S) { let min_y = self.from.y.min(self.ctrl.y).min(self.to.y); let max_y = self.from.y.max(self.ctrl.y).max(self.to.y); (min_y, max_y) } /// Returns the smallest rectangle the curve is contained in pub fn bounding_rect(&self) -> Rect { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); rect(min_x, min_y, max_x - min_x, max_y - min_y) } /// Returns the smallest range of x this curve is contained in. pub fn bounding_range_x(&self) -> (S, S) { let min_x = self.x(self.x_minimum_t()); let max_x = self.x(self.x_maximum_t()); (min_x, max_x) } /// Returns the smallest range of y this curve is contained in. pub fn bounding_range_y(&self) -> (S, S) { let min_y = self.y(self.y_minimum_t()); let max_y = self.y(self.y_maximum_t()); (min_y, max_y) } /// Cast this curve into a monotonic curve without checking that the monotonicity /// assumption is correct. pub fn assume_monotonic(&self) -> MonotonicQuadraticBezierSegment { MonotonicQuadraticBezierSegment { segment: *self } } /// Returns whether this segment is monotonic on the x axis. pub fn is_x_monotonic(&self) -> bool { self.local_x_extremum_t().is_none() } /// Returns whether this segment is monotonic on the y axis. pub fn is_y_monotonic(&self) -> bool { self.local_y_extremum_t().is_none() } /// Returns whether this segment is fully monotonic. pub fn is_monotonic(&self) -> bool { self.is_x_monotonic() && self.is_y_monotonic() } /// Computes the intersections (if any) between this segment a line. /// /// The result is provided in the form of the `t` parameters of each /// point along curve. To get the intersection points, sample the curve /// at the corresponding values. pub fn line_intersections_t(&self, line: &Line) -> ArrayVec<[S; 2]> { // TODO: a specific quadratic bézier vs line intersection function // would allow for better performance. let intersections = self.to_cubic().line_intersections_t(line); let mut result = ArrayVec::new(); for t in intersections { result.push(t); } return result; } /// Computes the intersection points (if any) between this segment a line. pub fn line_intersections(&self, line: &Line) -> ArrayVec<[Point;2]> { let intersections = self.to_cubic().line_intersections_t(line); let mut result = ArrayVec::new(); for t in intersections { result.push(self.sample(t)); } return result; } /// Computes the intersections (if any) between this segment a line segment. /// /// The result is provided in the form of the `t` parameters of each /// point along curve and segment. To get the intersection points, sample /// the segments at the corresponding values. pub fn line_segment_intersections_t(&self, segment: &LineSegment) -> ArrayVec<[(S, S); 2]> { // TODO: a specific quadratic bézier vs line intersection function // would allow for better performance. let intersections = self.to_cubic().line_segment_intersections_t(&segment); assert!(intersections.len() <= 2); let mut result = ArrayVec::new(); for t in intersections { result.push(t); } return result; } #[inline] pub fn from(&self) -> Point { self.from } #[inline] pub fn to(&self) -> Point { self.to } /// Computes the intersection points (if any) between this segment a line segment. pub fn line_segment_intersections(&self, segment: &LineSegment) -> ArrayVec<[Point; 2]> { let intersections = self.to_cubic().line_segment_intersections_t(&segment); assert!(intersections.len() <= 2); let mut result = ArrayVec::new(); for (t, _) in intersections { result.push(self.sample(t)); } return result; } } impl Segment for QuadraticBezierSegment { impl_segment!(S); } impl BoundingRect for QuadraticBezierSegment { type Scalar = S; fn bounding_rect(&self) -> Rect { self.bounding_rect() } fn fast_bounding_rect(&self) -> Rect { self.fast_bounding_rect() } fn bounding_range_x(&self) -> (S, S) { self.bounding_range_x() } fn bounding_range_y(&self) -> (S, S) { self.bounding_range_y() } fn fast_bounding_range_x(&self) -> (S, S) { self.fast_bounding_range_x() } fn fast_bounding_range_y(&self) -> (S, S) { self.fast_bounding_range_y() } } impl FlatteningStep for QuadraticBezierSegment { fn flattening_step(&self, tolerance: S) -> S { self.flattening_step(tolerance) } } /// A monotonically increasing in x and y quadratic bézier curve segment pub type MonotonicQuadraticBezierSegment = Monotonic>; #[test] fn bounding_rect_for_monotonic_quadratic_bezier_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(0.0, 0.0), to: Point::new(2.0, 0.0), }; let expected_bounding_rect = rect(0.0, 0.0, 2.0, 0.0); let actual_bounding_rect = a.bounding_rect(); assert!(expected_bounding_rect == actual_bounding_rect) } #[test] fn fast_bounding_rect_for_quadratic_bezier_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(2.0, 0.0), }; let expected_bounding_rect = rect(0.0, 0.0, 2.0, 1.0); let actual_bounding_rect = a.fast_bounding_rect(); assert!(expected_bounding_rect == actual_bounding_rect) } #[test] fn minimum_bounding_rect_for_quadratic_bezier_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(2.0, 0.0), }; let expected_bounding_rect = rect(0.0, 0.0, 2.0, 0.5); let actual_bounding_rect = a.bounding_rect(); assert!(expected_bounding_rect == actual_bounding_rect) } #[test] fn y_maximum_t_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(2.0, 0.0), }; let expected_y_maximum = 0.5; let actual_y_maximum = a.y_maximum_t(); assert!(expected_y_maximum == actual_y_maximum) } #[test] fn local_y_extremum_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(2.0, 0.0), }; let expected_y_inflection = 0.5; match a.local_y_extremum_t() { Some(actual_y_inflection) => assert!(expected_y_inflection == actual_y_inflection), None => panic!(), } } #[test] fn y_minimum_t_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, -1.0), to: Point::new(2.0, 0.0), }; let expected_y_minimum = 0.5; let actual_y_minimum = a.y_minimum_t(); assert!(expected_y_minimum == actual_y_minimum) } #[test] fn x_maximum_t_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(0.0, 2.0), }; let expected_x_maximum = 0.5; let actual_x_maximum = a.x_maximum_t(); assert!(expected_x_maximum == actual_x_maximum) } #[test] fn local_x_extremum_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(0.0, 2.0), }; let expected_x_inflection = 0.5; match a.local_x_extremum_t() { Some(actual_x_inflection) => assert!(expected_x_inflection == actual_x_inflection), None => panic!(), } } #[test] fn x_minimum_t_for_simple_segment() { let a = QuadraticBezierSegment { from: Point::new(2.0, 0.0), ctrl: Point::new(1.0, 1.0), to: Point::new(2.0, 2.0), }; let expected_x_minimum = 0.5; let actual_x_minimum = a.x_minimum_t(); assert!(expected_x_minimum == actual_x_minimum) } #[test] fn length_straight_line() { // Sanity check: aligned points so both these curves are straight lines // that go form (0.0, 0.0) to (2.0, 0.0). let len = QuadraticBezierSegment { from: Point::new(0.0, 0.0), ctrl: Point::new(1.0, 0.0), to: Point::new(2.0, 0.0), }.approximate_length(0.01); assert_eq!(len, 2.0); let len = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(1.0, 0.0), ctrl2: Point::new(1.0, 0.0), to: Point::new(2.0, 0.0), }.approximate_length(0.01); assert_eq!(len, 2.0); } #[test] fn derivatives() { let c1 = QuadraticBezierSegment { from: Point::new(1.0, 1.0), ctrl: Point::new(2.0, 1.0), to: Point::new(2.0, 2.0), }; assert_eq!(c1.dy(0.0), 0.0); assert_eq!(c1.dx(1.0), 0.0); assert_eq!(c1.dy(0.5), c1.dx(0.5)); } #[test] fn monotonic_solve_t_for_x() { let curve = QuadraticBezierSegment { from: Point::new(1.0, 1.0), ctrl: Point::new(5.0, 5.0), to: Point::new(10.0, 2.0), }; let tolerance = 0.0001; for i in 0..10u32 { let t = i as f32 / 10.0; let p = curve.sample(t); let t2 = curve.assume_monotonic().solve_t_for_x(p.x); // t should be pretty close to t2 but the only guarantee we have and can test // against is that x(t) - x(t2) is within the specified tolerance threshold. let x_diff = curve.x(t) - curve.x(t2); assert!(f32::abs(x_diff) <= tolerance); } } #[test] fn fat_line() { use crate::math::point; let c1 = QuadraticBezierSegment { from: point(1.0f32, 2.0), ctrl: point(1.0, 3.0), to: point(11.0, 12.0), }; let (l1, l2) = c1.fat_line(); for i in 0..100 { let t = i as f32 / 99.0; assert!(l1.signed_distance_to_point(&c1.sample(t)) >= -0.000001); assert!(l2.signed_distance_to_point(&c1.sample(t)) <= 0.000001); } } #[test] fn is_linear() { let mut angle = 0.0; let center = Point::new(1000.0, -700.0); for _ in 0..100 { for i in 0..10 { let (sin, cos) = f64::sin_cos(angle); let endpoint = Vector::new(cos * 100.0, sin * 100.0); let curve = QuadraticBezierSegment { from: center - endpoint, ctrl: center + endpoint.lerp(-endpoint, i as f64 / 9.0), to: center + endpoint, }; assert!(curve.is_linear(1e-10)); } angle += 0.001; } } #[test] fn test_flattening() { use crate::generic_math::point; let c1 = QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(5.0, 0.0), to: point(5.0, 5.0), }; let c2 = QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(50.0, 0.0), to: point(50.0, 50.0), }; let c3 = QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(100.0, 100.0), to: point(5.0, 0.0), }; fn check_tolerance(curve: &QuadraticBezierSegment, tolerance: f64) { let mut c = curve.clone(); loop { let t = c.flattening_step(tolerance); if t >= 1.0 { break; } let (before, after) = c.split(t); let mid_point = before.sample(0.5); let distance = before.baseline().to_line().equation().distance_to_point(&mid_point); assert!(distance <= tolerance); c = after; } } check_tolerance(&c1, 1.0); check_tolerance(&c1, 0.1); check_tolerance(&c1, 0.01); check_tolerance(&c1, 0.001); check_tolerance(&c1, 0.0001); check_tolerance(&c2, 1.0); check_tolerance(&c2, 0.1); check_tolerance(&c2, 0.01); check_tolerance(&c2, 0.001); check_tolerance(&c2, 0.0001); check_tolerance(&c3, 1.0); check_tolerance(&c3, 0.1); check_tolerance(&c3, 0.01); check_tolerance(&c3, 0.001); check_tolerance(&c3, 0.0001); } lyon_geom-0.15.0/src/segment.rs010064400017500001750000000214031360010744200146020ustar0000000000000000use crate::scalar::{Scalar, One}; use crate::generic_math::{Point, Vector, Rect}; use crate::{LineSegment, QuadraticBezierSegment, CubicBezierSegment}; use std::ops::Range; /// Common APIs to segment types. pub trait Segment: Copy + Sized { type Scalar: Scalar; /// Start of the curve. fn from(&self) -> Point; /// End of the curve. fn to(&self) -> Point; /// Sample the curve at t (expecting t between 0 and 1). fn sample(&self, t: Self::Scalar) -> Point; /// Sample x at t (expecting t between 0 and 1). fn x(&self, t: Self::Scalar) -> Self::Scalar { self.sample(t).x } /// Sample y at t (expecting t between 0 and 1). fn y(&self, t: Self::Scalar) -> Self::Scalar { self.sample(t).y } /// Sample the derivative at t (expecting t between 0 and 1). fn derivative(&self, t: Self::Scalar) -> Vector; /// Sample x derivative at t (expecting t between 0 and 1). fn dx(&self, t: Self::Scalar) -> Self::Scalar { self.derivative(t).x } /// Sample y derivative at t (expecting t between 0 and 1). fn dy(&self, t: Self::Scalar) -> Self::Scalar { self.derivative(t).y } /// Split this curve into two sub-curves. fn split(&self, t: Self::Scalar) -> (Self, Self); /// Return the curve before the split point. fn before_split(&self, t: Self::Scalar) -> Self; /// Return the curve after the split point. fn after_split(&self, t: Self::Scalar) -> Self; /// Return the curve inside a given range of t. /// /// This is equivalent splitting at the range's end points. fn split_range(&self, t_range: Range) -> Self; /// Swap the direction of the segment. fn flip(&self) -> Self; /// Compute the length of the segment using a flattened approximation. fn approximate_length(&self, tolerance: Self::Scalar) -> Self::Scalar; } pub trait BoundingRect { type Scalar: Scalar; /// Returns a rectangle that contains the curve. fn bounding_rect(&self) -> Rect; /// Returns a rectangle that contains the curve. /// /// This does not necessarily return the smallest possible bounding rectangle. fn fast_bounding_rect(&self) -> Rect { self.bounding_rect() } /// Returns a range of x values that contains the curve. fn bounding_range_x(&self) -> (Self::Scalar, Self::Scalar); /// Returns a range of y values that contains the curve. fn bounding_range_y(&self) -> (Self::Scalar, Self::Scalar); /// Returns a range of x values that contains the curve. fn fast_bounding_range_x(&self) -> (Self::Scalar, Self::Scalar); /// Returns a range of y values that contains the curve. fn fast_bounding_range_y(&self) -> (Self::Scalar, Self::Scalar); } /// Types that implement local flattening approximation at the start of the curve. pub trait FlatteningStep: Segment { /// Find the interval of the begining of the curve that can be approximated with a /// line segment. fn flattening_step(&self, tolerance: Self::Scalar) -> Self::Scalar; } pub(crate) fn for_each_flattened(curve: &T, tolerance: T::Scalar, call_back: &mut F) where T: FlatteningStep, F: FnMut(Point) { let mut iter = curve.clone(); loop { let t = iter.flattening_step(tolerance); if t >= T::Scalar::one() { call_back(iter.to()); break; } iter = iter.after_split(t); call_back(iter.from()); } } pub(crate) fn for_each_flattened_with_t(curve: &T, tolerance: T::Scalar, call_back: &mut F) where T: FlatteningStep, F: FnMut(Point, T::Scalar) { let end = curve.to(); let mut curve = curve.clone(); let mut t0 = T::Scalar::ZERO; loop { let step = curve.flattening_step(tolerance); if step >= T::Scalar::ONE { break; } curve = curve.after_split(step); t0 += step * (T::Scalar::ONE - t0); call_back(curve.from(), t0); } call_back(end, T::Scalar::ONE); } /// An iterator over a generic curve segment that yields line segments approximating the /// curve for a given approximation threshold. /// /// The iterator starts at the first point *after* the origin of the curve and ends at the /// destination. pub struct Flattened { curve: T, tolerance: S, done: bool, } impl Flattened { pub fn new(curve: T, tolerance: S) -> Self { assert!(tolerance > S::ZERO); Flattened { curve: curve, tolerance: tolerance, done: false, } } } impl> Iterator for Flattened { type Item = Point; fn next(&mut self) -> Option> { if self.done { return None; } let t = self.curve.flattening_step(self.tolerance); if t >= S::ONE { self.done = true; return Some(self.curve.to()); } self.curve = self.curve.after_split(t); return Some(self.curve.from()); } } macro_rules! impl_segment { ($S:ty) => ( type Scalar = $S; fn from(&self) -> Point<$S> { self.from() } fn to(&self) -> Point<$S> { self.to() } fn sample(&self, t: $S) -> Point<$S> { self.sample(t) } fn x(&self, t: $S) -> $S { self.x(t) } fn y(&self, t: $S) -> $S { self.y(t) } fn derivative(&self, t: $S) -> Vector<$S> { self.derivative(t) } fn dx(&self, t: $S) -> $S { self.dx(t) } fn dy(&self, t: $S) -> $S { self.dy(t) } fn split(&self, t: $S) -> (Self, Self) { self.split(t) } fn before_split(&self, t: $S) -> Self { self.before_split(t) } fn after_split(&self, t: $S) -> Self { self.after_split(t) } fn split_range(&self, t_range: Range<$S>) -> Self { self.split_range(t_range) } fn flip(&self) -> Self { self.flip() } fn approximate_length(&self, tolerance: $S) -> $S { self.approximate_length(tolerance) } ) } /// Either a cubic, quadratic or linear bézier segment. #[derive(Copy, Clone, Debug, PartialEq)] pub enum BezierSegment { Linear(LineSegment), Quadratic(QuadraticBezierSegment), Cubic(CubicBezierSegment), } impl BezierSegment { #[inline] pub fn sample(&self, t: S) -> Point { match self { BezierSegment::Linear(segment) => segment.sample(t), BezierSegment::Quadratic(segment) => segment.sample(t), BezierSegment::Cubic(segment) => segment.sample(t), } } #[inline] pub fn from(&self) -> Point { match self { BezierSegment::Linear(segment) => segment.from, BezierSegment::Quadratic(segment) => segment.from, BezierSegment::Cubic(segment) => segment.from, } } #[inline] pub fn to(&self) -> Point { match self { BezierSegment::Linear(segment) => segment.to, BezierSegment::Quadratic(segment) => segment.to, BezierSegment::Cubic(segment) => segment.to, } } #[inline] pub fn is_linear(&self, tolerance: S) -> bool { match self { BezierSegment::Linear(..) => true, BezierSegment::Quadratic(segment) => segment.is_linear(tolerance), BezierSegment::Cubic(segment) => segment.is_linear(tolerance), } } #[inline] pub fn baseline(&self) -> LineSegment { match self { BezierSegment::Linear(segment) => *segment, BezierSegment::Quadratic(segment) => segment.baseline(), BezierSegment::Cubic(segment) => segment.baseline(), } } /// Split this segment into two sub-segments. pub fn split(&self, t: S) -> (BezierSegment, BezierSegment) { match self { BezierSegment::Linear(segment) => { let (a, b) = segment.split(t); (BezierSegment::Linear(a), BezierSegment::Linear(b)) } BezierSegment::Quadratic(segment) => { let (a, b) = segment.split(t); (BezierSegment::Quadratic(a), BezierSegment::Quadratic(b)) } BezierSegment::Cubic(segment) => { let (a, b) = segment.split(t); (BezierSegment::Cubic(a), BezierSegment::Cubic(b)) } } } } impl From> for BezierSegment { fn from(s: LineSegment) -> Self { BezierSegment::Linear(s) } } impl From> for BezierSegment { fn from(s: QuadraticBezierSegment) -> Self { BezierSegment::Quadratic(s) } } impl From> for BezierSegment { fn from(s: CubicBezierSegment) -> Self { BezierSegment::Cubic(s) } } lyon_geom-0.15.0/src/triangle.rs010064400017500001750000000164361360117331400147610ustar0000000000000000use crate::scalar::Scalar; use crate::generic_math::{Point, Rect, Size}; use crate::traits::Transformation; use crate::LineSegment; /// A 2D triangle defined by three points `a`, `b` and `c`. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct Triangle { pub a: Point, pub b: Point, pub c: Point, } impl Triangle { #[inline] fn get_barycentric_coords_for_point(&self, point: Point) -> (S, S, S) { let v0 = self.b - self.a; let v1 = self.c - self.a; let v2 = point - self.a; let inv = S::ONE / v0.cross(v1); let a = v0.cross(v2) * inv; let b = v2.cross(v1) * inv; let c = S::ONE - a - b; (a, b, c) } pub fn contains_point(&self, point: Point) -> bool { let coords = self.get_barycentric_coords_for_point(point); coords.0 > S::ZERO && coords.1 > S::ZERO && coords.2 > S::ZERO } /// Return the minimum bounding rectangle. #[inline] pub fn bounding_rect(&self) -> Rect { let max_x = self.a.x.max(self.b.x).max(self.c.x); let min_x = self.a.x.min(self.b.x).min(self.c.x); let max_y = self.a.y.max(self.b.y).max(self.c.y); let min_y = self.a.y.min(self.b.y).min(self.c.y); let width = max_x - min_x; let height = max_y - min_y; Rect::new(Point::new(min_x, min_y), Size::new(width, height)) } #[inline] pub fn ab(&self) -> LineSegment { LineSegment { from: self.a, to: self.b } } #[inline] pub fn ba(&self) -> LineSegment { LineSegment { from: self.b, to: self.a } } #[inline] pub fn bc(&self) -> LineSegment { LineSegment { from: self.b, to: self.c } } #[inline] pub fn cb(&self) -> LineSegment { LineSegment { from: self.c, to: self.b } } #[inline] pub fn ca(&self) -> LineSegment { LineSegment { from: self.c, to: self.a } } #[inline] pub fn ac(&self) -> LineSegment { LineSegment { from: self.a, to: self.c } } /// [Not implemented] Applies the transform to this triangle and returns the results. #[inline] pub fn transform>(&self, transform: &T) -> Self { Triangle { a: transform.transform_point(self.a), b: transform.transform_point(self.b), c: transform.transform_point(self.c) } } /// Test for triangle-triangle intersection. pub fn intersects(&self, other: &Self) -> bool { // TODO: This should be optimized. // A bounding rect check should speed this up dramatically. // Inlining and reusing intermediate computation of the intersections // functions below and using SIMD would help too. return self.ab().intersects(&other.ab()) || self.ab().intersects(&other.bc()) || self.ab().intersects(&other.ac()) || self.bc().intersects(&other.ab()) || self.bc().intersects(&other.bc()) || self.bc().intersects(&other.ac()) || self.ac().intersects(&other.ab()) || self.ac().intersects(&other.bc()) || self.ac().intersects(&other.ac()) || self.contains_point(other.a) || other.contains_point(self.a) || *self == *other; } /// Test for triangle-segment intersection. #[inline] pub fn intersects_line_segment(&self, segment: &LineSegment) -> bool { return self.ab().intersects(segment) || self.bc().intersects(segment) || self.ac().intersects(segment) || self.contains_point(segment.from); } } #[cfg(test)] use crate::math::point; #[test] fn test_triangle_contains() { assert!( Triangle { a: point(0.0, 0.0), b: point(1.0, 0.0), c: point(0.0, 1.0), }.contains_point(point(0.2, 0.2)) ); assert!( !Triangle { a: point(0.0, 0.0), b: point(1.0, 0.0), c: point(0.0, 1.0), }.contains_point(point(1.2, 0.2)) ); // Triangle vertex winding should not matter assert!( Triangle { a: point(1.0, 0.0), b: point(0.0, 0.0), c: point(0.0, 1.0), }.contains_point(point(0.2, 0.2)) ); // Point exactly on the edge counts as outside the triangle. assert!( !Triangle { a: point(0.0, 0.0), b: point(1.0, 0.0), c: point(0.0, 1.0), }.contains_point(point(0.0, 0.0)) ); } #[test] fn test_segments() { let t = Triangle { a: point(1.0, 2.0), b: point(3.0, 4.0), c: point(5.0, 6.0), }; assert!(t.ab() == t.ba().flip()); assert!(t.ac() == t.ca().flip()); assert!(t.bc() == t.cb().flip()); } #[test] fn test_triangle_intersections() { let t1 = Triangle { a: point(1.0, 1.0), b: point(6.0, 1.0), c: point(3.0, 6.0), }; let t2 = Triangle { a: point(2.0, 2.0), b: point(0.0, 3.0), c: point(1.0, 6.0), }; assert!(t1.intersects(&t2)); assert!(t2.intersects(&t1)); // t3 and t1 have an overlapping edge, they are "touching" but not intersecting. let t3 = Triangle { a: point(6.0, 5.0), b: point(6.0, 1.0), c: point(3.0, 6.0), }; assert!(!t1.intersects(&t3)); assert!(!t3.intersects(&t1)); // t4 is entirely inside t1. let t4 = Triangle { a: point(2.0, 2.0), b: point(5.0, 2.0), c: point(3.0, 4.0), }; assert!(t1.intersects(&t4)); assert!(t4.intersects(&t1)); // Triangles intersect themselves. assert!(t1.intersects(&t1)); assert!(t2.intersects(&t2)); assert!(t3.intersects(&t3)); assert!(t4.intersects(&t4)); } #[test] fn test_segment_intersection() { let tri = Triangle { a: point(1.0, 1.0), b: point(6.0, 1.0), c: point(3.0, 6.0), }; let l1 = LineSegment { from: point(2.0, 0.0), to: point(3.0, 4.0), }; assert!(tri.intersects_line_segment(&l1)); let l2 = LineSegment { from: point(1.0, 3.0), to: point(0.0, 4.0), }; assert!(!tri.intersects_line_segment(&l2)); // The segement is entirely inside the triangle. let inside = LineSegment { from: point(2.0, 2.0), to: point(5.0, 2.0), }; assert!(tri.intersects_line_segment(&inside)); // A triangle does not intersect its own segments. assert!(!tri.intersects_line_segment(&tri.ab())); assert!(!tri.intersects_line_segment(&tri.bc())); assert!(!tri.intersects_line_segment(&tri.ac())); } #[cfg(test)] use euclid::rect; #[test] fn test_bounding_rect() { let t1 = Triangle { a: point(10., 20.), b: point(35., 40.), c: point(50., 10.), }; let r1 = rect(10., 10., 40., 30.); let t2 = Triangle { a: point(5., 30.), b: point(25., 10.), c: point(35., 40.), }; let r2 = rect(5., 10., 30., 30.); let t3 = Triangle { a: point(1., 1.), b: point(2., 5.), c: point(0., 4.), }; let r3 = rect(0., 1., 2., 4.); let cases = vec![(t1, r1), (t2, r2), (t3, r3)]; for &(tri, r) in &cases { assert_eq!(tri.bounding_rect(), r); } } lyon_geom-0.15.0/src/utils.rs010064400017500001750000000110561357773320200143170ustar0000000000000000use crate::scalar::{Scalar, Float}; use crate::generic_math::{Point, Vector, vector}; use arrayvec::ArrayVec; #[inline] pub fn min_max(a: S, b: S) -> (S, S) { if a < b { (a, b) } else { (b, a) } } #[inline] pub fn tangent(v: Vector) -> Vector { vector(-v.y, v.x) } #[inline] pub fn normalized_tangent(v: Vector) -> Vector { tangent(v).normalize() } /// Angle between vectors v1 and v2 (oriented clockwise assyming y points downwards). /// The result is a number between `0` and `2 * PI`. /// /// ex: `directed_angle([0,1], [1,0]) = 3/2 Pi rad` /// /// ```text /// x __ /// 0--> / \ /// y| | x--> v2 /// v \ |v1 /// v /// ``` /// /// Or, assuming y points upwards: /// `directed_angle([0,-1], [1,0]) = 1/2 Pi rad` /// /// ```text /// ^ v2 /// y| x--> /// 0--> v1 | / /// x v- /// ``` /// #[inline] pub fn directed_angle(v1: Vector, v2: Vector) -> S { let angle = S::fast_atan2(v2.y, v2.x) - S::fast_atan2(v1.y, v1.x); return if angle < S::ZERO { angle + S::TWO * S::PI() } else { angle }; } pub fn directed_angle2(center: Point, a: Point, b: Point) -> S { directed_angle(a - center, b - center) } pub fn cubic_polynomial_roots(a: S, b: S, c: S, d: S) -> ArrayVec<[S; 3]> { let mut result = ArrayVec::new(); if S::abs(a) < S::EPSILON { if S::abs(b) < S::EPSILON { if S::abs(c) < S::EPSILON { return result; } // linear equation result.push(-d / c); return result; } // quadratic equation let delta = c * c - S::FOUR * b * d; if delta > S::ZERO { let sqrt_delta = S::sqrt(delta); result.push((-c - sqrt_delta) / (S::TWO * b)); result.push((-c + sqrt_delta) / (S::TWO * b)); } else if S::abs(delta) < S::EPSILON { result.push(-c / (S::TWO * b)); } return result; } let frac_1_3 = S::ONE / S::THREE; let bn = b / a; let cn = c / a; let dn = d / a; let delta0 = (S::THREE * cn - bn * bn) / S::NINE; let delta1 = (S::NINE * bn * cn - S::value(27.0) * dn - S::TWO * bn * bn * bn) / S::value(54.0); let delta_01 = delta0 * delta0 * delta0 + delta1 * delta1; if delta_01 >= S::ZERO { let delta_p_sqrt = delta1 + S::sqrt(delta_01); let delta_m_sqrt = delta1 - S::sqrt(delta_01); let s = delta_p_sqrt.signum() * S::abs(delta_p_sqrt).powf(frac_1_3); let t = delta_m_sqrt.signum() * S::abs(delta_m_sqrt).powf(frac_1_3); result.push(-bn * frac_1_3 + (s + t)); // Don't add the repeated root when s + t == 0. if S::abs(s - t) < S::EPSILON && S::abs(s + t) >= S::EPSILON { result.push(-bn * frac_1_3 - (s + t) / S::TWO); } } else { let theta = S::acos(delta1 / S::sqrt(-delta0 * delta0 * delta0)); let two_sqrt_delta0 = S::TWO * S::sqrt(-delta0); result.push(two_sqrt_delta0 * Float::cos(theta * frac_1_3) - bn * frac_1_3); result.push(two_sqrt_delta0 * Float::cos((theta + S::TWO * S::PI()) * frac_1_3) - bn * frac_1_3); result.push(two_sqrt_delta0 * Float::cos((theta + S::FOUR * S::PI()) * frac_1_3) - bn * frac_1_3); } //result.sort(); return result; } #[test] fn cubic_polynomial() { fn assert_approx_eq(a: ArrayVec<[f32; 3]>, b: &[f32], epsilon: f32) { for i in 0..a.len() { if f32::abs(a[i] - b[i]) > epsilon { println!("{:?} != {:?}", a, b); } assert!((a[i] - b[i]).abs() <= epsilon); } assert_eq!(a.len(), b.len()); } assert_approx_eq(cubic_polynomial_roots(2.0, -4.0, 2.0, 0.0), &[0.0, 1.0], 0.0000001); assert_approx_eq(cubic_polynomial_roots(-1.0, 1.0, -1.0, 1.0), &[1.0], 0.000001); assert_approx_eq(cubic_polynomial_roots(-2.0, 2.0, -1.0, 10.0), &[2.0], 0.00005); // (x - 1)^3, with a triple root, should only return one root. assert_approx_eq(cubic_polynomial_roots(1.0, -3.0, 3.0, -1.0), &[1.0], 0.00005); // Quadratics. assert_approx_eq(cubic_polynomial_roots(0.0, 1.0, -5.0, -14.0), &[-2.0, 7.0], 0.00005); // (x - 3)^2, with a double root, should only return one root. assert_approx_eq(cubic_polynomial_roots(0.0, 1.0, -6.0, 9.0), &[3.0], 0.00005); // Linear. assert_approx_eq(cubic_polynomial_roots(0.0, 0.0, 2.0, 1.0), &[-0.5], 0.00005); // Constant. assert_approx_eq(cubic_polynomial_roots(0.0, 0.0, 0.0, 0.0), &[], 0.00005); } lyon_geom-0.15.0/.cargo_vcs_info.json0000644000000001121360117435400132030ustar00{ "git": { "sha1": "292a95ea67d0821b505a1d4000672013f30f5aec" } }