lyon_geom-1.0.5/.cargo_vcs_info.json0000644000000001510000000000100130240ustar { "git": { "sha1": "a7423e3cf0052164eb6c00cc2e6ee3ee9b09738a" }, "path_in_vcs": "crates/geom" }lyon_geom-1.0.5/Cargo.toml0000644000000025760000000000100110370ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "lyon_geom" version = "1.0.5" 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/" readme = "README.md" keywords = [ "2d", "graphics", "bezier", "geometry", ] license = "MIT OR Apache-2.0" repository = "https://github.com/nical/lyon" resolver = "2" [lib] name = "lyon_geom" [dependencies.arrayvec] version = "0.7" default-features = false [dependencies.euclid] version = "0.22.4" default-features = false [dependencies.num-traits] version = "0.2" features = ["libm"] default-features = false [dependencies.serde] version = "1.0" features = ["serde_derive"] optional = true default-features = false [features] default = ["std"] serialization = [ "serde", "euclid/serde", ] std = [ "arrayvec/std", "euclid/std", "num-traits/std", ] lyon_geom-1.0.5/Cargo.toml.orig000064400000000000000000000015351046102023000145120ustar 00000000000000[package] name = "lyon_geom" version = "1.0.5" 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 OR Apache-2.0" workspace = "../.." edition = "2018" [lib] name = "lyon_geom" [features] default = ["std"] serialization = ["serde", "euclid/serde"] std = ["arrayvec/std", "euclid/std", "num-traits/std"] [dependencies] euclid = { version = "0.22.4", default-features = false } arrayvec = { version = "0.7", default-features = false } num-traits = { version = "0.2", features = ["libm"], default-features = false } serde = { version = "1.0", optional = true, features = ["serde_derive"], default-features = false } lyon_geom-1.0.5/README.md000064400000000000000000000007431046102023000131020ustar 00000000000000# lyon::geom 2D geometric primitives on top of [euclid](https://docs.rs/euclid/).

crates.io documentation

`lyon_geom` can be used as a standalone crate or as part of [lyon](https://docs.rs/lyon/) via the `lyon::geom` module. lyon_geom-1.0.5/src/arc.rs000064400000000000000000001036411046102023000135260ustar 00000000000000//! Elliptic arc related maths and tools. use core::mem::swap; use core::ops::Range; use crate::scalar::{cast, Float, Scalar}; use crate::segment::{BoundingBox, Segment}; use crate::{point, vector, Angle, Box2D, Point, Rotation, Transform, Vector}; use crate::{CubicBezierSegment, Line, LineSegment, QuadraticBezierSegment}; /// 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, } /// 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, } 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: self.sweep_angle.get() >= S::ZERO, large_arc: S::abs(self.sweep_angle.get()) >= S::PI(), }; 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_with_t(self, &mut |curve, _| cb(curve)); } /// Approximate the arc with a sequence of quadratic bézier curves. #[inline] pub fn for_each_quadratic_bezier_with_t(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment, Range), { arc_to_quadratic_beziers_with_t(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 += self.sweep_angle; arc.sweep_angle = -self.sweep_angle; arc } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. pub fn for_each_flattened(&self, tolerance: S, callback: &mut F) where F: FnMut(&LineSegment), { let mut from = self.from(); let mut iter = *self; loop { let t = iter.flattening_step(tolerance); if t >= S::ONE { break; } iter = iter.after_split(t); let to = iter.from(); callback(&LineSegment { from, to }); from = to; } callback(&LineSegment { from, to: self.to(), }); } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. pub fn for_each_flattened_with_t(&self, tolerance: S, callback: &mut F) where F: FnMut(&LineSegment, Range), { let mut iter = *self; let mut t0 = S::ZERO; let mut from = self.from(); loop { let step = iter.flattening_step(tolerance); if step >= S::ONE { break; } iter = iter.after_split(step); let t1 = t0 + step * (S::ONE - t0); let to = iter.from(); callback(&LineSegment { from, to }, t0..t1); from = to; t0 = t1; } callback( &LineSegment { from, to: self.to(), }, t0..S::ONE, ); } /// Finds the interval of the beginning of the curve that can be approximated with a /// line segment. 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.abs(), 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_box(&self) -> Box2D { Transform::rotation(self.x_rotation).outer_transformed_box(&Box2D { min: self.center - self.radii, max: self.center + self.radii, }) } /// Returns a conservative rectangle that contains the curve. pub fn bounding_box(&self) -> Box2D { 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); }); Box2D { min, max } } 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_box(); (r.min.x, r.max.x) } pub fn bounding_range_y(&self) -> (S, S) { let r = self.bounding_box(); (r.min.y, r.max.y) } pub fn fast_bounding_range_x(&self) -> (S, S) { let r = self.fast_bounding_box(); (r.min.x, r.max.x) } pub fn fast_bounding_range_y(&self) -> (S, S) { let r = self.fast_bounding_box(); (r.min.y, r.max.y) } pub fn approximate_length(&self, tolerance: S) -> S { let mut len = S::ZERO; self.for_each_flattened(tolerance, &mut |segment| { len += segment.length(); }); 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 From> for Arc { fn from(svg: SvgArc) -> Self { svg.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 quadratic bézier segments. pub fn for_each_quadratic_bezier_with_t(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment, Range), { if self.is_straight_line() { cb( &QuadraticBezierSegment { from: self.from, ctrl: self.from, to: self.to, }, S::ZERO..S::ONE, ); return; } Arc::from_svg_arc(self).for_each_quadratic_bezier_with_t(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 curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. pub fn for_each_flattened)>(&self, tolerance: S, cb: &mut F) { if self.is_straight_line() { cb(&LineSegment { from: self.from, to: self.to, }); return; } Arc::from_svg_arc(self).for_each_flattened(tolerance, cb); } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. pub fn for_each_flattened_with_t, Range)>( &self, tolerance: S, cb: &mut F, ) { if self.is_straight_line() { cb( &LineSegment { from: self.from, to: self.to, }, S::ZERO..S::ONE, ); return; } Arc::from_svg_arc(self).for_each_flattened_with_t(tolerance, cb); } } /// Flag parameters for arcs as described by the SVG specification. /// /// For most situations using the SVG arc notation, there are four different arcs /// (two different ellipses, each with two different arc sweeps) that satisfy the /// arc parameters. The `large_arc` and `sweep` flags indicate which one of the /// four arcs are drawn, as follows: /// /// See more examples in the [SVG specification](https://svgwg.org/specs/paths/) #[derive(Copy, Clone, Debug, PartialEq, Default)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub struct ArcFlags { /// Of the four candidate arc sweeps, two will represent an arc sweep of greater /// than or equal to 180 degrees (the "large-arc"), and two will represent an arc /// sweep of less than or equal to 180 degrees (the "small arc"). If `large_arc` /// is `true`, then one of the two larger arc sweeps will be chosen; otherwise, if /// `large_arc` is `false`, one of the smaller arc sweeps will be chosen. pub large_arc: bool, /// If `sweep` is `true`, then the arc will be drawn in a "positive-angle" direction /// (the ellipse formula `x=cx+rx*cos(theta)` and `y=cy+ry*sin(theta)` is evaluated /// such that theta starts at an angle corresponding to the current point and increases /// positively until the arc reaches the destination position). A value of `false` /// causes the arc to be drawn in a "negative-angle" direction (theta starts at an /// angle value corresponding to the current point and decreases until the arc reaches /// the destination position). pub sweep: bool, } fn arc_to_quadratic_beziers_with_t(arc: &Arc, callback: &mut F) where S: Scalar, F: FnMut(&QuadraticBezierSegment, Range), { 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); let mut t0 = S::ZERO; let dt = S::ONE / n_steps; let n = cast::(n_steps).unwrap(); for i in 0..n { 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); let t1 = if i + 1 == n { S::ONE } else { t0 + dt }; callback(&QuadraticBezierSegment { from, ctrl, to }, t0..t1); t0 = t1; } } 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(&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) -> Self { self.split_range(t_range) } fn flip(&self) -> Self { self.flip() } fn approximate_length(&self, tolerance: S) -> S { self.approximate_length(tolerance) } fn for_each_flattened_with_t( &self, tolerance: Self::Scalar, callback: &mut dyn FnMut(&LineSegment, Range), ) { self.for_each_flattened_with_t(tolerance, &mut |s, t| callback(s, t)); } } impl BoundingBox for Arc { type Scalar = S; 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() } } /// Flattening iterator for arcs. /// /// The iterator starts at the first point *after* the origin of the curve and ends at the /// destination. pub struct Flattened { arc: Arc, tolerance: S, done: bool, } impl Flattened { pub(crate) fn new(arc: Arc, tolerance: S) -> Self { assert!(tolerance > S::ZERO); Flattened { arc, tolerance, done: false, } } } impl Iterator for Flattened { type Item = Point; fn next(&mut self) -> Option> { if self.done { return None; } let t = self.arc.flattening_step(self.tolerance); if t >= S::ONE { self.done = true; return Some(self.arc.to()); } self.arc = self.arc.after_split(t); Some(self.arc.from()) } } #[test] fn test_from_svg_arc() { use crate::vector; use euclid::approxeq::ApproxEq; 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_box() { use euclid::approxeq::ApproxEq; fn approx_eq(r1: Box2D, r2: Box2D) -> 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) { std::println!("\n left: {r1:?}\n right: {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_box(); assert!(approx_eq( r, Box2D { min: point(-1.0, 0.0), max: point(1.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_box(); assert!(approx_eq( r, Box2D { min: point(-1.0, -1.0), max: point(1.0, 0.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_box(); assert!(approx_eq( r, Box2D { min: point(-1.0, -2.0), max: point(0.0, 2.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_box(); assert!(approx_eq( r, Box2D { min: point(0.0, 0.0), max: point(1.707107, 1.707107) } )); let mut angle = Angle::zero(); for _ in 0..10 { std::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_box(); assert!(approx_eq( r, Box2D { min: point(-4.0, -4.0), max: point(4.0, 4.0) } )); angle += Angle::pi() * 2.0 / 10.0; } let mut angle = Angle::zero(); for _ in 0..10 { std::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_box(); assert!(approx_eq( r, Box2D { min: point(-4.0, -4.0), max: point(4.0, 4.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 |_| {}); // There was also an issue with negative sweep_angle leading to a negative step // causing the arc to be approximated with a single line segment. let arc = Arc { center: point(0.0, 0.0), radii: vector(100.0, 10.0), start_angle: Angle::radians(0.2), sweep_angle: Angle::radians(-2.0), x_rotation: Angle::zero(), }; let flattened: std::vec::Vec<_> = arc.flattened(0.1).collect(); assert!(flattened.len() > 1); } lyon_geom-1.0.5/src/cubic_bezier.rs000064400000000000000000002013331046102023000154030ustar 00000000000000use crate::cubic_bezier_intersections::cubic_bezier_intersections_t; use crate::scalar::Scalar; use crate::segment::{BoundingBox, Segment}; use crate::traits::Transformation; use crate::utils::{cubic_polynomial_roots, min_max}; use crate::{point, Box2D, Point, Vector}; use crate::{Line, LineEquation, LineSegment, QuadraticBezierSegment}; use arrayvec::ArrayVec; use core::cmp::Ordering::{Equal, Greater, Less}; use core::ops::Range; #[cfg(test)] use std::vec::Vec; /// 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; 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; 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; 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. pub fn solve_t_for_x(&self, x: S) -> ArrayVec { let (min, max) = self.fast_bounding_range_x(); if min > x || max < 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. pub fn solve_t_for_y(&self, y: S) -> ArrayVec { let (min, max) = self.fast_bounding_range_y(); if min > y || max < 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 { 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; ( CubicBezierSegment { from: self.from, ctrl1: ctrl1a, ctrl2: ctrl1aa, to: ctrl1aaa, }, CubicBezierSegment { from: ctrl1aaa, ctrl1: ctrl2aa, ctrl2: ctrl3a, to: self.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; 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; 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, } } /// Returns true if the curve can be approximated with a single line segment, given /// a tolerance threshold. pub fn is_linear(&self, tolerance: S) -> bool { // Similar to Line::square_distance_to_point, except we keep // the sign of c1 and c2 to compute tighter upper bounds as we // do in fat_line_min_max. let baseline = self.to - self.from; let v1 = self.ctrl1 - self.from; let v2 = self.ctrl2 - self.from; let c1 = baseline.cross(v1); let c2 = baseline.cross(v2); // TODO: it would be faster to multiply the threshold with baseline_len2 // instead of dividing d1 and d2, but it changes the behavior when the // baseline length is zero in ways that breaks some of the cubic intersection // tests. let inv_baseline_len2 = S::ONE / baseline.square_length(); let d1 = (c1 * c1) * inv_baseline_len2; let d2 = (c2 * c2) * inv_baseline_len2; let factor = if (c1 * c2) > S::ZERO { S::THREE / S::FOUR } else { S::FOUR / S::NINE }; let f2 = factor * factor; let threshold = tolerance * tolerance; d1 * f2 <= threshold && d2 * f2 <= threshold } /// Returns whether the curve can be approximated with a single point, given /// a tolerance threshold. 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, } } /// Approximate the curve with a single quadratic bézier segment. /// /// This is terrible as a general approximation but works if the cubic /// curve does not have inflection points and is "flat" enough. Typically /// usable after subdividing the curve a few times. pub fn to_quadratic(&self) -> QuadraticBezierSegment { let c1 = (self.ctrl1 * S::THREE - self.from) * S::HALF; let c2 = (self.ctrl2 * S::THREE - self.to) * S::HALF; QuadraticBezierSegment { from: self.from, ctrl: ((c1 + c2) * S::HALF).to_point(), to: self.to, } } /// Evaluates an upper bound on the maximum distance between the curve /// and its quadratic approximation obtained using `to_quadratic`. pub fn to_quadratic_error(&self) -> S { // See http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html S::sqrt(S::THREE) / S::value(36.0) * ((self.to - self.ctrl2 * S::THREE) + (self.ctrl1 * S::THREE - self.from)).length() } /// Returns true if the curve can be safely approximated with a single quadratic bézier /// segment given the provided tolerance threshold. /// /// Equivalent to comparing `to_quadratic_error` with the tolerance threshold, avoiding /// the cost of two square roots. pub fn is_quadratic(&self, tolerance: S) -> bool { S::THREE / S::value(1296.0) * ((self.to - self.ctrl2 * S::THREE) + (self.ctrl1 * S::THREE - self.from)) .square_length() <= tolerance * tolerance } /// Computes the number of quadratic bézier segments required to approximate this cubic curve /// given a tolerance threshold. /// /// Derived by Raph Levien from section 10.6 of Sedeberg's CAGD notes /// /// and the error metric from the caffein owl blog post pub fn num_quadratics(&self, tolerance: S) -> u32 { self.num_quadratics_impl(tolerance).to_u32().unwrap_or(1) } fn num_quadratics_impl(&self, tolerance: S) -> S { debug_assert!(tolerance > S::ZERO); let x = self.from.x - S::THREE * self.ctrl1.x + S::THREE * self.ctrl2.x - self.to.x; let y = self.from.y - S::THREE * self.ctrl1.y + S::THREE * self.ctrl2.y - self.to.y; let err = x * x + y * y; (err / (S::value(432.0) * tolerance * tolerance)) .powf(S::ONE / S::SIX) .ceil() .max(S::ONE) } /// 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 for each monotonic part of the segment. pub fn for_each_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { let mut extrema: ArrayVec = ArrayVec::new(); self.for_each_local_x_extremum_t(&mut |t| extrema.push(t)); self.for_each_local_y_extremum_t(&mut |t| extrema.push(t)); extrema.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); let mut t0 = S::ZERO; for &t in &extrema { if t != t0 { cb(t0..t); t0 = t; } } cb(t0..S::ONE); } /// Invokes a callback for each monotonic part of the segment. pub fn for_each_monotonic(&self, cb: &mut F) where F: FnMut(&CubicBezierSegment), { self.for_each_monotonic_range(&mut |range| { let mut sub = self.split_range(range); // Due to finite precision the split may actually result in sub-curves // that are almost but not-quite monotonic. Make sure they actually are. let min_x = sub.from.x.min(sub.to.x); let max_x = sub.from.x.max(sub.to.x); let min_y = sub.from.y.min(sub.to.y); let max_y = sub.from.y.max(sub.to.y); sub.ctrl1.x = sub.ctrl1.x.max(min_x).min(max_x); sub.ctrl1.y = sub.ctrl1.y.max(min_y).min(max_y); sub.ctrl2.x = sub.ctrl2.x.max(min_x).min(max_x); sub.ctrl2.y = sub.ctrl2.y.max(min_y).min(max_y); cb(&sub); }); } /// Invokes a callback for each y-monotonic part of the segment. pub fn for_each_y_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { let mut t0 = S::ZERO; self.for_each_local_y_extremum_t(&mut |t| { cb(t0..t); t0 = t; }); cb(t0..S::ONE); } /// Invokes a callback for each y-monotonic part of the segment. pub fn for_each_y_monotonic(&self, cb: &mut F) where F: FnMut(&CubicBezierSegment), { self.for_each_y_monotonic_range(&mut |range| { let mut sub = self.split_range(range); // Due to finite precision the split may actually result in sub-curves // that are almost but not-quite monotonic. Make sure they actually are. let min_y = sub.from.y.min(sub.to.y); let max_y = sub.from.y.max(sub.to.y); sub.ctrl1.y = sub.ctrl1.y.max(min_y).min(max_y); sub.ctrl2.y = sub.ctrl2.y.max(min_y).min(max_y); cb(&sub); }); } /// Invokes a callback for each x-monotonic part of the segment. pub fn for_each_x_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { let mut t0 = S::ZERO; self.for_each_local_x_extremum_t(&mut |t| { cb(t0..t); t0 = t; }); cb(t0..S::ONE); } /// Invokes a callback for each x-monotonic part of the segment. pub fn for_each_x_monotonic(&self, cb: &mut F) where F: FnMut(&CubicBezierSegment), { self.for_each_x_monotonic_range(&mut |range| { let mut sub = self.split_range(range); // Due to finite precision the split may actually result in sub-curves // that are almost but not-quite monotonic. Make sure they actually are. let min_x = sub.from.x.min(sub.to.x); let max_x = sub.from.x.max(sub.to.x); sub.ctrl1.x = sub.ctrl1.x.max(min_x).min(max_x); sub.ctrl2.x = sub.ctrl2.x.max(min_x).min(max_x); cb(&sub); }); } /// 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), { self.for_each_quadratic_bezier_with_t(tolerance, &mut |quad, _range| cb(quad)); } /// Approximates the cubic bézier curve with sequence of quadratic ones, /// invoking a callback at each step. pub fn for_each_quadratic_bezier_with_t(&self, tolerance: S, cb: &mut F) where F: FnMut(&QuadraticBezierSegment, Range), { debug_assert!(tolerance >= S::EPSILON * S::EPSILON); let num_quadratics = self.num_quadratics_impl(tolerance); let step = S::ONE / num_quadratics; let n = num_quadratics.to_u32().unwrap_or(1); let mut t0 = S::ZERO; for _ in 0..(n - 1) { let t1 = t0 + step; let quad = self.split_range(t0..t1).to_quadratic(); cb(&quad, t0..t1); t0 = t1; } // Do the last step manually to make sure we finish at t = 1.0 exactly. let quad = self.split_range(t0..S::ONE).to_quadratic(); cb(&quad, t0..S::ONE) } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. pub fn for_each_flattened)>(&self, tolerance: S, callback: &mut F) { debug_assert!(tolerance >= S::EPSILON * S::EPSILON); let quadratics_tolerance = tolerance * S::value(0.4); let flattening_tolerance = tolerance * S::value(0.8); self.for_each_quadratic_bezier(quadratics_tolerance, &mut |quad| { quad.for_each_flattened(flattening_tolerance, &mut |segment| { callback(segment); }); }); } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. pub fn for_each_flattened_with_t, Range)>( &self, tolerance: S, callback: &mut F, ) { debug_assert!(tolerance >= S::EPSILON * S::EPSILON); let quadratics_tolerance = tolerance * S::value(0.4); let flattening_tolerance = tolerance * S::value(0.8); let mut t_from = S::ZERO; self.for_each_quadratic_bezier_with_t(quadratics_tolerance, &mut |quad, range| { let last_quad = range.end == S::ONE; let range_len = range.end - range.start; quad.for_each_flattened_with_t(flattening_tolerance, &mut |segment, range_sub| { let last_seg = range_sub.end == S::ONE; let t = if last_quad && last_seg { S::ONE } else { range_sub.end * range_len + range.start }; callback(segment, t_from..t); t_from = t; }); }); } /// Compute the length of the segment using a flattened approximation. pub fn approximate_length(&self, tolerance: S) -> S { let mut length = S::ZERO; self.for_each_quadratic_bezier(tolerance, &mut |quad| { length += quad.length(); }); length } /// Invokes a callback at each inflection point if any. pub fn for_each_inflection_t(&self, cb: &mut F) where F: FnMut(S), { // Find inflection points. // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation // of this approach. let pa = self.ctrl1 - self.from; let pb = self.ctrl2.to_vector() - (self.ctrl1.to_vector() * S::TWO) + self.from.to_vector(); let pc = self.to.to_vector() - (self.ctrl2.to_vector() * S::THREE) + (self.ctrl1.to_vector() * S::THREE) - self.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 { core::mem::swap(&mut first_inflection, &mut second_inflection); } if in_range(first_inflection) { cb(first_inflection); } if in_range(second_inflection) { cb(second_inflection); } } /// 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 mut first_extremum = (-b - discriminant_sqrt) / (S::TWO * a); let mut second_extremum = (-b + discriminant_sqrt) / (S::TWO * a); if first_extremum > second_extremum { core::mem::swap(&mut first_extremum, &mut second_extremum); } 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; } }); 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; } }); 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; } }); 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; } }); min_t } /// Returns a conservative rectangle the curve is contained in. /// /// This method is faster than `bounding_box` but more conservative. pub fn fast_bounding_box(&self) -> Box2D { let (min_x, max_x) = self.fast_bounding_range_x(); let (min_y, max_y) = self.fast_bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// Returns a conservative range of x that contains this curve. #[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 that contains this curve. #[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 a conservative rectangle that contains the curve. #[inline] pub fn bounding_box(&self) -> Box2D { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// Returns the smallest range of x that contains this curve. #[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 that contains this curve. #[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) } /// 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, 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, 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 { 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); } } // TODO: sort the intersections? result } /// Computes the intersection points (if any) between this segment and a line. pub fn line_intersections(&self, line: &Line) -> ArrayVec, 3> { let intersections = self.line_intersections_t(line); let mut result = ArrayVec::new(); for t in intersections { result.push(self.sample(t)); } 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_box() .inflate(S::EPSILON, S::EPSILON) .intersects(&segment.bounding_box().inflate(S::EPSILON, S::EPSILON)) { return ArrayVec::new(); } let intersections = self.line_intersections_t(&segment.to_line()); let mut result = ArrayVec::new(); if intersections.is_empty() { 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(); // Don't take intersections that are on endpoints of both curves at the same time. if (t != S::ZERO && t != S::ONE) || (t2 != S::ZERO && t2 != S::ONE) { 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, 3> { let intersections = self.line_segment_intersections_t(segment); let mut result = ArrayVec::new(); for (t, _) in intersections { result.push(self.sample(t)); } result } fn baseline_projection(&self, t: S) -> S { // See https://pomax.github.io/bezierinfo/#abc // We are computing the interpolation factor between // `from` and `to` to get the position of C. let one_t = S::ONE - t; let one_t3 = one_t * one_t * one_t; let t3 = t * t * t; t3 / (t3 + one_t3) } fn abc_ratio(&self, t: S) -> S { // See https://pomax.github.io/bezierinfo/#abc let one_t = S::ONE - t; let one_t3 = one_t * one_t * one_t; let t3 = t * t * t; ((t3 + one_t3 - S::ONE) / (t3 + one_t3)).abs() } // Returns a quadratic bézier curve built by dragging this curve's point at `t` // to a new position, without moving the endpoints. // // The relative effect on control points is chosen to give a similar "feel" to // most vector graphics editors: dragging from near the first endpoint will affect // the first control point more than the second control point, etc. pub fn drag(&self, t: S, new_position: Point) -> Self { // A lot of tweaking could go into making the weight feel as natural as possible. let min = S::value(0.1); let max = S::value(0.9); let weight = if t < min { S::ZERO } else if t > max { S::ONE } else { (t - min) / (max - min) }; self.drag_with_weight(t, new_position, weight) } // Returns a quadratic bézier curve built by dragging this curve's point at `t` // to a new position, without moving the endpoints. // // The provided weight specifies the relative effect on control points. // - with `weight = 0.5`, `ctrl1` and `ctrl2` are equally affected, // - with `weight = 0.0`, only `ctrl1` is affected, // - with `weight = 1.0`, only `ctrl2` is affected, // - etc. pub fn drag_with_weight(&self, t: S, new_position: Point, weight: S) -> Self { // See https://pomax.github.io/bezierinfo/#abc // // From-----------Ctrl1 // | \ d1 \ // C-------P--------A \ d12 // | \d2 \ // | \ \ // To-----------------Ctrl2 // // The ABC relation means we can place the new control points however we like // as long as the ratios CA/CP, d1/d12 and d2/d12 remain constant. // // we use the weight to guide our decisions. A weight of 0.5 would be a uniform // displacement (d1 and d2 do not change and both control points are moved by the // same amount). // The approach is to use the weight interpolate the most constrained control point // between it's old position and the position it would have with uniform displacement. // then we determine the position of the least constrained control point such that // the ratios mentioned earlier remain constant. let c = self.from.lerp(self.to, self.baseline_projection(t)); let cacp_ratio = self.abc_ratio(t); let old_pos = self.sample(t); // Construct A before and after drag using the constance ca/cp ratio let old_a = old_pos + (old_pos - c) / cacp_ratio; let new_a = new_position + (new_position - c) / cacp_ratio; // Sort ctrl1 and ctrl2 such ctrl1 is the least affected (or most constrained). let mut ctrl1 = self.ctrl1; let mut ctrl2 = self.ctrl2; if t < S::HALF { core::mem::swap(&mut ctrl1, &mut ctrl2); } // Move the most constrained control point by a subset of the uniform displacement // depending on the weight. let uniform_displacement = new_a - old_a; let f = if t < S::HALF { S::TWO * weight } else { S::TWO * (S::ONE - weight) }; let mut new_ctrl1 = ctrl1 + uniform_displacement * f; // Now that the most constrained control point is placed there is only one position // for the least constrained control point that satisfies the constant ratios. let d1_pre = (old_a - ctrl1).length(); let d12_pre = (self.ctrl2 - self.ctrl1).length(); let mut new_ctrl2 = new_ctrl1 + (new_a - new_ctrl1) * (d12_pre / d1_pre); if t < S::HALF { core::mem::swap(&mut new_ctrl1, &mut new_ctrl2); } CubicBezierSegment { from: self.from, ctrl1: new_ctrl1, ctrl2: new_ctrl2, to: self.to, } } pub fn to_f32(&self) -> CubicBezierSegment { CubicBezierSegment { from: self.from.to_f32(), ctrl1: self.ctrl1.to_f32(), ctrl2: self.ctrl2.to_f32(), to: self.to.to_f32(), } } pub fn to_f64(&self) -> CubicBezierSegment { CubicBezierSegment { from: self.from.to_f64(), ctrl1: self.ctrl1.to_f64(), ctrl2: self.ctrl2.to_f64(), to: self.to.to_f64(), } } } impl Segment for CubicBezierSegment { impl_segment!(S); fn for_each_flattened_with_t( &self, tolerance: Self::Scalar, callback: &mut dyn FnMut(&LineSegment, Range), ) { self.for_each_flattened_with_t(tolerance, &mut |s, t| callback(s, t)); } } impl BoundingBox for CubicBezierSegment { type Scalar = S; 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() } } use crate::quadratic_bezier::FlattenedT as FlattenedQuadraticSegment; pub struct Flattened { curve: CubicBezierSegment, current_curve: FlattenedQuadraticSegment, remaining_sub_curves: i32, tolerance: S, range_step: S, range_start: S, } impl Flattened { pub(crate) fn new(curve: &CubicBezierSegment, tolerance: S) -> Self { debug_assert!(tolerance >= S::EPSILON * S::EPSILON); let quadratics_tolerance = tolerance * S::value(0.4); let flattening_tolerance = tolerance * S::value(0.8); let num_quadratics = curve.num_quadratics_impl(quadratics_tolerance); let range_step = S::ONE / num_quadratics; let quadratic = curve.split_range(S::ZERO..range_step).to_quadratic(); let current_curve = FlattenedQuadraticSegment::new(&quadratic, flattening_tolerance); Flattened { curve: *curve, current_curve, remaining_sub_curves: num_quadratics.to_i32().unwrap() - 1, tolerance: flattening_tolerance, range_start: S::ZERO, range_step, } } } impl Iterator for Flattened { type Item = Point; fn next(&mut self) -> Option> { if let Some(t_inner) = self.current_curve.next() { let t = self.range_start + t_inner * self.range_step; return Some(self.curve.sample(t)); } if self.remaining_sub_curves <= 0 { return None; } self.range_start += self.range_step; let t0 = self.range_start; let t1 = self.range_start + self.range_step; self.remaining_sub_curves -= 1; let quadratic = self.curve.split_range(t0..t1).to_quadratic(); self.current_curve = FlattenedQuadraticSegment::new(&quadratic, self.tolerance); let t_inner = self.current_curve.next().unwrap_or(S::ONE); let t = t0 + t_inner * self.range_step; Some(self.curve.sample(t)) } fn size_hint(&self) -> (usize, Option) { ( self.remaining_sub_curves as usize * self.current_curve.size_hint().0, None, ) } } #[cfg(test)] fn print_arrays(a: &[Point], b: &[Point]) { std::println!("left: {a:?}"); std::println!("right: {b:?}"); } #[cfg(test)] fn assert_approx_eq(a: &[Point], b: &[Point]) { if a.len() != b.len() { print_arrays(a, b); panic!("Lengths differ ({} != {})", a.len(), b.len()); } for i in 0..a.len() { let threshold = 0.029; let dx = f32::abs(a[i].x - b[i].x); let dy = f32::abs(a[i].y - b[i].y); if dx > threshold || dy > threshold { print_arrays(a, b); std::println!("diff = {dx:?} {dy:?}"); 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 |s| { builder_points.push(s.to); }); 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 |s| { builder_points.push(s.to); }); 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 |s| { builder_points.push(s.to); }); 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 |s| { builder_points.push(s.to); }); 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 |s| { points.push(s.to); }); 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 |s| { a.push(*s); }); let mut b = Vec::new(); let mut ts = Vec::new(); segment.for_each_flattened_with_t(tolerance, &mut |s, t| { b.push(*s); ts.push(t); }); assert_eq!(a, b); for i in 0..b.len() { let sampled = segment.sample(ts[i].start); let point = b[i].from; let dist = (sampled - point).length(); assert!(dist <= tolerance); let sampled = segment.sample(ts[i].end); let point = b[i].to; let dist = (sampled - point).length(); assert!(dist <= tolerance); } } } #[test] fn test_flatten_end() { let segment = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(100.0, 0.0), ctrl2: Point::new(100.0, 100.0), to: Point::new(100.0, 200.0), }; let mut last = segment.from; segment.for_each_flattened(0.0001, &mut |s| { last = s.to; }); assert_eq!(last, segment.to); } #[test] fn test_flatten_point() { let segment = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(0.0, 0.0), ctrl2: Point::new(0.0, 0.0), to: Point::new(0.0, 0.0), }; let mut last = segment.from; segment.for_each_flattened(0.0001, &mut |s| { last = s.to; }); assert_eq!(last, segment.to); } #[test] fn issue_652() { use crate::point; let curve = CubicBezierSegment { from: point(-1061.0, -3327.0), ctrl1: point(-1061.0, -3177.0), ctrl2: point(-1061.0, -3477.0), to: point(-1061.0, -3327.0), }; for _ in curve.flattened(1.0) {} for _ in curve.flattened(0.1) {} for _ in curve.flattened(0.01) {} curve.for_each_flattened(1.0, &mut |_| {}); curve.for_each_flattened(0.1, &mut |_| {}); curve.for_each_flattened(0.01, &mut |_| {}); } #[test] fn fast_bounding_box_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_aabb = Box2D { min: point(0.0, -1.0), max: point(2.0, 1.0), }; let actual_aabb = a.fast_bounding_box(); assert_eq!(expected_aabb, actual_aabb) } #[test] fn minimum_bounding_box_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_aabb: Box2D = Box2D { min: point(0.0, -0.6), max: point(2.0, 0.6), }; let expected_smaller_aabb: Box2D = Box2D { min: point(0.1, -0.5), max: point(2.0, 0.5), }; let actual_minimum_aabb = a.bounding_box(); assert!(expected_bigger_aabb.contains_box(&actual_minimum_aabb)); assert!(actual_minimum_aabb.contains_box(&expected_smaller_aabb)); } #[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_eq!(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_eq!(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_eq!(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_eq!(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 fat_line() { use crate::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::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::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 { std::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::point; fn assert_approx_eq(a: ArrayVec, b: &[f32], epsilon: f32) { for i in 0..a.len() { if f32::abs(a[i] - b[i]) > epsilon { std::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::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); } #[test] fn cubic_line_intersection_on_endpoint() { let l1 = LineSegment { from: Point::new(0.0, -100.0), to: Point::new(0.0, 100.0), }; let cubic = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(20.0, 20.0), ctrl2: Point::new(20.0, 40.0), to: Point::new(0.0, 60.0), }; let intersections = cubic.line_segment_intersections_t(&l1); assert_eq!(intersections.len(), 2); assert_eq!(intersections[0], (1.0, 0.8)); assert_eq!(intersections[1], (0.0, 0.5)); let l2 = LineSegment { from: Point::new(0.0, 0.0), to: Point::new(0.0, 60.0), }; let intersections = cubic.line_segment_intersections_t(&l2); assert!(intersections.is_empty()); let c1 = CubicBezierSegment { from: Point::new(0.0, 0.0), ctrl1: Point::new(20.0, 0.0), ctrl2: Point::new(20.0, 20.0), to: Point::new(0.0, 60.0), }; let c2 = CubicBezierSegment { from: Point::new(0.0, 60.0), ctrl1: Point::new(-40.0, 4.0), ctrl2: Point::new(-20.0, 20.0), to: Point::new(0.0, 00.0), }; let intersections = c1.cubic_intersections_t(&c2); assert!(intersections.is_empty()); } #[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; assert_eq!(quadratic.to_cubic().num_quadratics(0.0001), 1); quadratic .to_cubic() .for_each_quadratic_bezier(0.0001, &mut |c| { assert_eq!(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.0f32, 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.for_each_quadratic_bezier(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); } lyon_geom-1.0.5/src/cubic_bezier_intersections.rs000064400000000000000000001313341046102023000203570ustar 00000000000000use crate::scalar::Scalar; use crate::CubicBezierSegment; ///! 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::{point, Box2D, Point}; use arrayvec::ArrayVec; use core::mem; use core::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_box() .intersects(&curve2.fast_bounding_box()) || 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 { 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.is_empty() { 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, ) -> 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 { 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.is_empty() { return result; } let line2_params = parameters_for_line_point(curve2, &intersection); if line2_params.is_empty() { 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 sub-curve 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.) #[allow(clippy::too_many_arguments)] 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_box(), &curve2.fast_bounding_box()) { 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); } } // TODO: replace with Scalar::epsilon_for? // 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 = ArrayVec::new(); let mut bottom = ArrayVec::new(); convex_hull_of_distance_curve(d_0, d_1, d_2, d_3, &mut top, &mut bottom); 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, top: &mut ArrayVec, 4>, bottom: &mut ArrayVec, 4>, ) { debug_assert!(top.is_empty()); debug_assert!(bottom.is_empty()); 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. if dist1 * dist2 < S::ZERO { // p1 and p2 lie on opposite sides of [p0, p3], so the hull is a quadrilateral: let _ = top.try_extend_from_slice(&[p0, p1, p3]); let _ = bottom.try_extend_from_slice(&[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 { let _ = top.try_extend_from_slice(&[p0, p1, p3]); let _ = bottom.try_extend_from_slice(&[p0, p3]); } else if dist2 >= S::TWO * dist1 { let _ = top.try_extend_from_slice(&[p0, p2, p3]); let _ = bottom.try_extend_from_slice(&[p0, p3]); } else { let _ = top.try_extend_from_slice(&[p0, p1, p2, p3]); let _ = bottom.try_extend_from_slice(&[p0, p3]); } } // Flip the hull if needed: if dist1 < S::ZERO || (dist1 == S::ZERO && dist2 < S::ZERO) { mem::swap(top, bottom); } } // 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 [Point], hull_bottom: &mut [Point], 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)?; // 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)?; Some((t_clip_min, t_clip_max)) } // 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: &[Point], hull_bottom_vertices: &[Point], d_min: S, d_max: S, ) -> Option { let start_corner = hull_top_vertices[0]; if start_corner.y < d_min { walk_convex_hull_edges_to_fat_line(hull_top_vertices, true, d_min) } else if start_corner.y > d_max { walk_convex_hull_edges_to_fat_line(hull_bottom_vertices, false, d_max) } else { 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: &[Point], 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) { return if q.y == threshold { Some(q.x) } else { 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] // Box2D::intersects doesn't count edge/corner intersections, this version does. fn rectangles_overlap(r1: &Box2D, r2: &Box2D) -> bool { r1.min.x <= r2.max.x && r2.min.x <= r1.max.x && r1.min.y <= r2.max.y && r2.min.y <= r1.max.y } #[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_box 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-1.0.5/src/lib.rs000064400000000000000000000331061046102023000135250ustar 00000000000000#![doc(html_logo_url = "https://nical.github.io/lyon-doc/lyon-logo.svg")] #![deny(bare_trait_objects)] #![deny(unconditional_recursion)] #![allow(clippy::excessive_precision)] #![allow(clippy::let_and_return)] #![allow(clippy::many_single_char_names)] #![no_std] //! 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 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 #[cfg(any(test, feature = "std"))] extern crate std; // Reexport dependencies. pub use arrayvec; pub use euclid; #[cfg(feature = "serialization")] #[macro_use] pub extern crate serde; #[macro_use] mod segment; pub mod arc; pub mod cubic_bezier; mod cubic_bezier_intersections; mod line; pub mod quadratic_bezier; mod triangle; pub mod utils; #[doc(inline)] pub use crate::arc::{Arc, ArcFlags, SvgArc}; #[doc(inline)] pub use crate::cubic_bezier::CubicBezierSegment; #[doc(inline)] pub use crate::line::{Line, LineEquation, LineSegment}; #[doc(inline)] pub use crate::quadratic_bezier::QuadraticBezierSegment; #[doc(inline)] pub use crate::segment::Segment; #[doc(inline)] pub use crate::triangle::Triangle; pub use crate::scalar::Scalar; mod scalar { pub(crate) use euclid::Trig; pub(crate) use num_traits::cast::cast; pub(crate) use num_traits::{Float, FloatConst, NumCast}; use core::fmt::{Debug, Display}; use core::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; 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 MIN: Self; const MAX: Self; const EPSILON: Self; const DIV_EPSILON: Self = Self::EPSILON; /// Epsilon constants are usually not a good way to deal with float precision. /// Float precision depends on the magnitude of the values and so should appropriate /// epsilons. fn epsilon_for(_reference: Self) -> Self { Self::EPSILON } 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 MIN: Self = f32::MIN; const MAX: Self = f32::MAX; const EPSILON: Self = 1e-4; fn epsilon_for(reference: Self) -> Self { // The thresholds are chosen by looking at the table at // https://blog.demofox.org/2017/11/21/floating-point-precision/ plus a bit // of trial and error. They might change in the future. // TODO: instead of casting to an integer, could look at the exponent directly. let magnitude = reference.abs() as i32; match magnitude { 0..=7 => 1e-5, 8..=1023 => 1e-3, 1024..=4095 => 1e-2, 5096..=65535 => 1e-1, 65536..=8_388_607 => 0.5, _ => 1.0, } } #[inline] fn value(v: f32) -> Self { v } } // Epsilon constants are usually not a good way to deal with float precision. // Float precision depends on the magnitude of the values and so should appropriate // epsilons. This function addresses this somewhat empirically. 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 MIN: Self = f64::MIN; const MAX: Self = f64::MAX; const EPSILON: Self = 1e-8; fn epsilon_for(reference: Self) -> Self { let magnitude = reference.abs() as i64; match magnitude { 0..=65_535 => 1e-8, 65_536..=8_388_607 => 1e-5, 8_388_608..=4_294_967_295 => 1e-3, _ => 1e-1, } } #[inline] fn value(v: f32) -> Self { v as f64 } } } /// 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::Box2D` pub use euclid::default::Box2D; /// 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 `Vector::new(x, y)`. #[inline] pub fn vector(x: S, y: S) -> Vector { Vector::new(x, y) } /// Shorthand for `Point::new(x, y)`. #[inline] pub fn point(x: S, y: S) -> Point { Point::new(x, y) } /// Shorthand for `Size::new(x, y)`. #[inline] pub fn size(w: S, h: S) -> Size { Size::new(w, h) } pub mod traits { pub use crate::segment::Segment; use crate::{Point, Rotation, Scalar, Scale, Transform, Translation, Vector}; 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) } } // Automatically implement Transformation for all &Transformation. impl<'l, S: Scalar, T: Transformation> Transformation for &'l T { #[inline] fn transform_point(&self, p: Point) -> Point { (*self).transform_point(p) } #[inline] fn transform_vector(&self, v: Vector) -> Vector { (*self).transform_vector(v) } } } lyon_geom-1.0.5/src/line.rs000064400000000000000000001116751046102023000137160ustar 00000000000000use crate::scalar::Scalar; use crate::segment::{BoundingBox, Segment}; use crate::traits::Transformation; use crate::utils::min_max; use crate::{point, vector, Box2D, Point, Vector}; use core::mem::swap; use core::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); ( 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 smallest rectangle containing this segment. #[inline] pub fn bounding_box(&self) -> Box2D { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } #[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() } /// Computes the squared length of this segment. #[inline] pub fn square_length(&self) -> S { self.to_vector().square_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); } /// Computes third mid-point of this segment. pub fn mid_point(&mut self) -> Point { (self.from + self.to.to_vector()) / S::TWO } #[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. #[allow(clippy::suspicious_operation_groupings)] pub fn intersection_t(&self, other: &Self) -> Option<(S, S)> { 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)) } // TODO: Consider only intersecting in the [0, 1[ range instead of [0, 1] 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 { // TODO: is it really useful to swap? let swap = a > b; if swap { core::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 } /// Horizontally clip this segment against a range of the x axis. pub fn clipped_x(&self, clip: Range) -> Option { if (self.from.x < clip.start && self.to.x < clip.start) || (self.from.x > clip.end && self.to.x > clip.end) { return None; } let mut flipped = false; let mut result = *self; if result.from.x > result.to.x { flipped = true; result = result.flip(); } if result.from.x >= clip.start && result.to.x <= clip.end { return Some(*self); } if result.from.x < clip.start { let t = result .vertical_line_intersection_t(clip.start) .unwrap_or(S::ZERO); result.from.x = clip.start; result.from.y = result.y(t); } if result.to.x > clip.end { let t = result .vertical_line_intersection_t(clip.end) .unwrap_or(S::ZERO); result.to.x = clip.end; result.to.y = result.y(t); } if flipped { result = result.flip(); } Some(result) } /// Vertically clip this segment against a range of the y axis. pub fn clipped_y(&self, clip: Range) -> Option { fn transpose(r: &LineSegment) -> LineSegment { LineSegment { from: r.from.yx(), to: r.to.yx(), } } Some(transpose(&transpose(self).clipped_x(clip)?)) } /// Clip this segment against a rectangle. pub fn clipped(&self, clip: &Box2D) -> Option { self.clipped_x(clip.x_range())?.clipped_y(clip.y_range()) } /// Computes the distance between this segment and a point. #[inline] pub fn distance_to_point(&self, p: Point) -> S { self.square_distance_to_point(p).sqrt() } /// Computes the squared distance between this segment and a point. /// /// Can be useful to save a square root and a division when comparing against /// a distance that can be squared. #[inline] pub fn square_distance_to_point(&self, p: Point) -> S { (self.closest_point(p) - p).square_length() } /// Computes the closest point on this segment to `p`. #[inline] pub fn closest_point(&self, p: Point) -> Point { let v1 = self.to - self.from; let v2 = p - self.from; let t = S::min(S::max(v2.dot(v1) / v1.dot(v1), S::ZERO), S::ONE); self.from + v1 * t } #[inline] pub fn to_f32(&self) -> LineSegment { LineSegment { from: self.from.to_f32(), to: self.to.to_f32(), } } #[inline] pub fn to_f64(&self) -> LineSegment { LineSegment { from: self.from.to_f64(), to: self.to.to_f64(), } } } 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(&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) -> Self { self.split_range(t_range) } fn flip(&self) -> Self { self.flip() } fn approximate_length(&self, _tolerance: S) -> S { self.length() } fn for_each_flattened_with_t( &self, _tolerance: Self::Scalar, callback: &mut dyn FnMut(&LineSegment, Range), ) { callback(self, S::ZERO..S::ONE); } } impl BoundingBox for LineSegment { type Scalar = S; 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() } } /// 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()); Some(point( (b * self.vector.x - a * other.vector.x) * inv_det, (b * self.vector.y - a * other.vector.y) * inv_det, )) } pub fn distance_to_point(&self, p: &Point) -> S { S::abs(self.signed_distance_to_point(p)) } pub fn signed_distance_to_point(&self, p: &Point) -> S { let v = *p - self.point; self.vector.cross(v) / self.vector.length() } /// Returned the squared distance to a point. /// /// Can be useful to avoid a square root when comparing against a /// distance that can be squared instead. pub fn square_distance_to_point(&self, p: Point) -> S { let v = p - self.point; let c = self.vector.cross(v); (c * c) / self.vector.square_length() } 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) } pub fn intersects_box(&self, rect: &Box2D) -> bool { let v = self.vector; let diagonal = if (v.y >= S::ZERO) ^ (v.x >= S::ZERO) { LineSegment { from: rect.min, to: rect.max, } } else { LineSegment { from: point(rect.max.x, rect.min.y), to: point(rect.min.x, rect.max.y), } }; diagonal.intersects_line(self) } #[inline] pub fn to_f32(&self) -> Line { Line { point: self.point.to_f32(), vector: self.vector.to_f32(), } } #[inline] pub fn to_f64(&self) -> Line { Line { point: self.point.to_f64(), vector: self.vector.to_f64(), } } } /// 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 { 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 core::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 importantly, 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::approxeq::ApproxEq; #[test] fn bounding_box() { let l1 = LineSegment { from: point(1.0, 5.0), to: point(5.0, 7.0), }; let r1 = Box2D { min: point(1.0, 5.0), max: point(5.0, 7.0), }; let l2 = LineSegment { from: point(5.0, 5.0), to: point(1.0, 1.0), }; let r2 = Box2D { min: point(1.0, 1.0), max: point(5.0, 5.0), }; let l3 = LineSegment { from: point(3.0, 3.0), to: point(1.0, 5.0), }; let r3 = Box2D { min: point(1.0, 3.0), max: point(3.0, 5.0), }; let cases = std::vec![(l1, r1), (l2, r2), (l3, r3)]; for &(ls, r) in &cases { assert_eq!(ls.bounding_box(), r); } } #[test] fn distance_to_point() { use crate::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) { std::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) { std::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); } #[test] fn intersection_on_endpoint() { let l1 = LineSegment { from: point(0.0, 0.0), to: point(0.0, 10.0), }; let l2 = LineSegment { from: point(0.0, 5.0), to: point(10.0, 5.0), }; assert_eq!(l1.intersection_t(&l2), Some((0.5, 0.0))); assert_eq!(l2.intersection_t(&l1), Some((0.0, 0.5))); let l3 = LineSegment { from: point(10.0, 5.0), to: point(0.0, 5.0), }; assert_eq!(l1.intersection_t(&l3), Some((0.5, 1.0))); assert_eq!(l3.intersection_t(&l1), Some((1.0, 0.5))); } #[test] fn intersects_box() { let b = Box2D { min: point(1.0, 2.0), max: point(4.0, 4.0), }; assert!(!Line { point: point(0.0, 0.0), vector: vector(1.0, 0.0) } .intersects_box(&b)); assert!(!Line { point: point(0.0, 0.0), vector: vector(0.0, 1.0) } .intersects_box(&b)); assert!(!Line { point: point(10.0, 0.0), vector: vector(10.0, 10.0) } .intersects_box(&b)); assert!(!Line { point: point(0.0, 10.0), vector: vector(10.0, 10.0) } .intersects_box(&b)); assert!(Line { point: point(1.5, 0.0), vector: vector(1.0, 6.0) } .intersects_box(&b)); assert!(Line { point: point(1.5, 0.0), vector: vector(-1.0, 6.0) } .intersects_box(&b)); assert!(Line { point: point(1.5, 2.5), vector: vector(1.0, 0.5) } .intersects_box(&b)); assert!(Line { point: point(1.5, 2.5), vector: vector(-1.0, -2.0) } .intersects_box(&b)); } #[test] fn clipped() { let b = Box2D { min: point(1.0, 2.0), max: point(3.0, 4.0), }; fn approx_eq(a: LineSegment, b: LineSegment) -> bool { let ok = a.from.approx_eq(&b.from) && a.to.approx_eq(&b.to); if !ok { std::println!("{a:?} != {b:?}"); } ok } assert_eq!( LineSegment { from: point(0.0, 1.0), to: point(4.0, 1.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(0.0, 2.0), to: point(4.0, 2.0) } .clipped(&b), Some(LineSegment { from: point(1.0, 2.0), to: point(3.0, 2.0) }) ); assert_eq!( LineSegment { from: point(0.0, 3.0), to: point(4.0, 3.0) } .clipped(&b), Some(LineSegment { from: point(1.0, 3.0), to: point(3.0, 3.0) }) ); assert_eq!( LineSegment { from: point(0.0, 4.0), to: point(4.0, 4.0) } .clipped(&b), Some(LineSegment { from: point(1.0, 4.0), to: point(3.0, 4.0) }) ); assert_eq!( LineSegment { from: point(0.0, 5.0), to: point(4.0, 5.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(4.0, 1.0), to: point(0.0, 1.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(4.0, 2.0), to: point(0.0, 2.0) } .clipped(&b), Some(LineSegment { from: point(3.0, 2.0), to: point(1.0, 2.0) }) ); assert_eq!( LineSegment { from: point(4.0, 3.0), to: point(0.0, 3.0) } .clipped(&b), Some(LineSegment { from: point(3.0, 3.0), to: point(1.0, 3.0) }) ); assert_eq!( LineSegment { from: point(4.0, 4.0), to: point(0.0, 4.0) } .clipped(&b), Some(LineSegment { from: point(3.0, 4.0), to: point(1.0, 4.0) }) ); assert_eq!( LineSegment { from: point(4.0, 5.0), to: point(0.0, 5.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(0.0, 0.0), to: point(0.0, 5.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(1.0, 0.0), to: point(1.0, 5.0) } .clipped(&b), Some(LineSegment { from: point(1.0, 2.0), to: point(1.0, 4.0) }) ); assert_eq!( LineSegment { from: point(2.0, 0.0), to: point(2.0, 5.0) } .clipped(&b), Some(LineSegment { from: point(2.0, 2.0), to: point(2.0, 4.0) }) ); assert_eq!( LineSegment { from: point(3.0, 0.0), to: point(3.0, 5.0) } .clipped(&b), Some(LineSegment { from: point(3.0, 2.0), to: point(3.0, 4.0) }) ); assert_eq!( LineSegment { from: point(4.0, 0.0), to: point(4.0, 5.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(0.0, 5.0), to: point(0.0, 0.0) } .clipped(&b), None ); assert_eq!( LineSegment { from: point(1.0, 5.0), to: point(1.0, 0.0) } .clipped(&b), Some(LineSegment { from: point(1.0, 4.0), to: point(1.0, 2.0) }) ); assert_eq!( LineSegment { from: point(2.0, 5.0), to: point(2.0, 0.0) } .clipped(&b), Some(LineSegment { from: point(2.0, 4.0), to: point(2.0, 2.0) }) ); assert_eq!( LineSegment { from: point(3.0, 5.0), to: point(3.0, 0.0) } .clipped(&b), Some(LineSegment { from: point(3.0, 4.0), to: point(3.0, 2.0) }) ); assert_eq!( LineSegment { from: point(4.0, 5.0), to: point(4.0, 0.0) } .clipped(&b), None ); assert!(approx_eq( LineSegment { from: point(0.0, 2.0), to: point(4.0, 4.0) } .clipped(&b) .unwrap(), LineSegment { from: point(1.0, 2.5), to: point(3.0, 3.5) } )); assert!(approx_eq( LineSegment { from: point(4.0, 4.0), to: point(0.0, 2.0) } .clipped(&b) .unwrap(), LineSegment { from: point(3.0, 3.5), to: point(1.0, 2.5) } )); let inside = [ LineSegment { from: point(1.0, 2.0), to: point(3.0, 4.0), }, LineSegment { from: point(1.5, 2.0), to: point(1.0, 4.0), }, LineSegment { from: point(1.0, 3.0), to: point(2.0, 3.0), }, ]; for segment in &inside { assert_eq!(segment.clipped(&b), Some(*segment)); assert_eq!(segment.flip().clipped(&b), Some(segment.flip())); } let outside = [ LineSegment { from: point(2.0, 0.0), to: point(5.0, 3.0), }, LineSegment { from: point(-20.0, 0.0), to: point(4.0, 8.0), }, ]; for segment in &outside { assert_eq!(segment.clipped(&b), None); assert_eq!(segment.flip().clipped(&b), None); } } #[test] fn equation() { let lines = [ Line { point: point(100.0f64, 20.0), vector: vector(-1.0, 3.0), }, Line { point: point(-30.0, 150.0), vector: vector(10.0, 2.0), }, Line { point: point(50.0, -10.0), vector: vector(5.0, -1.0), }, ]; for line in &lines { let eqn = line.equation(); use euclid::approxeq::ApproxEq; for t in [-100.0, -50.0, 0.0, 25.0, 325.0] { let p = line.point + line.vector * t; assert!(eqn.solve_y_for_x(p.x).unwrap().approx_eq(&p.y)); assert!(eqn.solve_x_for_y(p.y).unwrap().approx_eq(&p.x)); } } } lyon_geom-1.0.5/src/quadratic_bezier.rs000064400000000000000000001314511046102023000162760ustar 00000000000000use crate::scalar::Scalar; use crate::segment::{BoundingBox, Segment}; use crate::traits::Transformation; use crate::{point, Box2D, Point, Vector}; use crate::{CubicBezierSegment, Line, LineEquation, LineSegment, Triangle}; use arrayvec::ArrayVec; use core::mem; use core::ops::Range; /// 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 + 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; 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; 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; 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; } } 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; } } 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); } 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; } } 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; } } 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); } 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); ( 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 { 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 { 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, } } /// Returns whether the curve can be approximated with a single point, given /// a tolerance threshold. pub fn is_a_point(&self, tolerance: S) -> bool { let tol2 = tolerance * tolerance; (self.from - self.to).square_length() <= tol2 && (self.from - self.ctrl).square_length() <= tol2 } /// Returns true if the curve can be approximated with a single line segment /// given a tolerance threshold. pub fn is_linear(&self, tolerance: S) -> bool { if self.from == self.to { return true; } let d = self .baseline() .to_line() .square_distance_to_point(self.ctrl); d <= (tolerance * tolerance * S::FOUR) } /// 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 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 beginning 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 = S::sqrt(v1.x * v1.x + v1.y * 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; } t } /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// This implements the algorithm described by Raph Levien at /// pub fn for_each_flattened(&self, tolerance: S, callback: &mut F) where F: FnMut(&LineSegment), { self.for_each_flattened_with_t(tolerance, &mut |segment, _| callback(segment)); } /// Compute a flattened approximation of the curve, invoking a callback at /// each step. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. /// /// This implements the algorithm described by Raph Levien at /// pub fn for_each_flattened_with_t(&self, tolerance: S, callback: &mut F) where F: FnMut(&LineSegment, Range), { let params = FlatteningParameters::new(self, tolerance); let mut i = S::ONE; let mut from = self.from; let mut t_from = S::ZERO; for _ in 1..params.count.to_u32().unwrap() { let t = params.t_at_iteration(i); i += S::ONE; let s = LineSegment { from, to: self.sample(t), }; callback(&s, t_from..t); from = s.to; t_from = t; } let s = LineSegment { from, to: self.to }; callback(&s, t_from..S::ONE); } /// 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 the flattened representation of the curve as an iterator, starting *after* the /// current point. pub fn flattened_t(&self, tolerance: S) -> FlattenedT { FlattenedT::new(self, tolerance) } /// Invokes a callback for each monotonic part of the segment. pub fn for_each_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { let mut t0 = self.local_x_extremum_t(); let mut t1 = self.local_y_extremum_t(); let swap = match (t0, t1) { (Some(tx), Some(ty)) => tx > ty, _ => false, }; if swap { mem::swap(&mut t0, &mut t1); } let mut start = S::ZERO; if let Some(t) = t0 { cb(start..t); start = t; } if let Some(t) = t1 { // In extreme cases the same point can be an x and y inflection point. if t != start { cb(start..t); start = t } } cb(start..S::ONE); } /// Invokes a callback for each monotonic part of the segment. pub fn for_each_monotonic(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment), { self.for_each_monotonic_range(&mut |range| { let mut sub = self.split_range(range); // Due to finite precision the split may actually result in sub-curves // that are almost but not-quite monotonic. Make sure they actually are. let min_x = sub.from.x.min(sub.to.x); let max_x = sub.from.x.max(sub.to.x); let min_y = sub.from.y.min(sub.to.y); let max_y = sub.from.y.max(sub.to.y); sub.ctrl.x = sub.ctrl.x.max(min_x).min(max_x); sub.ctrl.y = sub.ctrl.y.max(min_y).min(max_y); cb(&sub); }); } /// Invokes a callback for each y-monotonic part of the segment. pub fn for_each_y_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { match self.local_y_extremum_t() { Some(t) => { cb(S::ZERO..t); cb(t..S::ONE); } None => { cb(S::ZERO..S::ONE); } } } /// Invokes a callback for each y-monotonic part of the segment. pub fn for_each_y_monotonic(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment), { match self.local_y_extremum_t() { Some(t) => { let (a, b) = self.split(t); cb(&a); cb(&b); } None => { cb(self); } } } /// Invokes a callback for each x-monotonic part of the segment. pub fn for_each_x_monotonic_range(&self, cb: &mut F) where F: FnMut(Range), { match self.local_x_extremum_t() { Some(t) => { cb(S::ZERO..t); cb(t..S::ONE); } None => { cb(S::ZERO..S::ONE); } } } /// Invokes a callback for each x-monotonic part of the segment. pub fn for_each_x_monotonic(&self, cb: &mut F) where F: FnMut(&QuadraticBezierSegment), { match self.local_x_extremum_t() { Some(t) => { let (mut a, mut b) = self.split(t); // Due to finite precision the split may actually result in sub-curves // that are almost but not-quite monotonic. Make sure they actually are. let a_min = a.from.x.min(a.to.x); let a_max = a.from.x.max(a.to.x); let b_min = b.from.x.min(b.to.x); let b_max = b.from.x.max(b.to.x); a.ctrl.x = a.ctrl.x.max(a_min).min(a_max); b.ctrl.x = b.ctrl.x.max(b_min).min(b_max); cb(&a); cb(&b); } None => { cb(self); } } } /// 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_box(&self) -> Box2D { let (min_x, max_x) = self.fast_bounding_range_x(); let (min_y, max_y) = self.fast_bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// Returns a conservative range of x that contains this curve. 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 that contains this curve. 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_box(&self) -> Box2D { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// Returns the smallest range of x that contains this curve. 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 that contains this curve. 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) } /// 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 { // take the quadratic bezier formulation and inject it in // the line equation ax + by + c = 0. let eqn = line.equation(); let i = eqn.a() * self.from.x + eqn.b() * self.from.y; let j = eqn.a() * self.ctrl.x + eqn.b() * self.ctrl.y; let k = eqn.a() * self.to.x + eqn.b() * self.to.y; // Solve "(i - 2j + k)t² + (2j - 2i)t + (i + c) = 0" let a = i - j - j + k; let b = j + j - i - i; let c = i + eqn.c(); let mut result = ArrayVec::new(); if a == S::ZERO { // Linear equation bt + c = 0. let t = c / b; if t >= S::ZERO && t <= S::ONE { result.push(t); return result; } } let delta = b * b - S::FOUR * a * c; if delta >= S::ZERO { // To avoid potential float precision issues when b is close to // sqrt_delta, we exploit the fact that given the roots t1 and t2, // t2 = c / (a * t1) and t1 = c / (a * t2). let sqrt_delta = S::sqrt(delta); let s_sqrt_delta = -b.signum() * sqrt_delta; let mut t1 = (-b + s_sqrt_delta) / (S::TWO * a); let mut t2 = c / (a * t1); if t1 > t2 { mem::swap(&mut t1, &mut t2); } if t1 >= S::ZERO && t1 <= S::ONE { result.push(t1); } if t2 >= S::ZERO && t2 <= S::ONE && t1 != t2 { result.push(t2); } } result } /// Computes the intersection points (if any) between this segment a line. pub fn line_intersections(&self, line: &Line) -> ArrayVec, 2> { let intersections = self.line_intersections_t(line); let mut result = ArrayVec::new(); for t in intersections { result.push(self.sample(t)); } 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), 2> { if !self .fast_bounding_box() .inflate(S::EPSILON, S::EPSILON) .intersects(&segment.bounding_box().inflate(S::EPSILON, S::EPSILON)) { return ArrayVec::new(); } let intersections = self.line_intersections_t(&segment.to_line()); let mut result = ArrayVec::new(); if intersections.is_empty() { 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(); // Don't take intersections that are on endpoints of both curves at the same time. if (t != S::ZERO && t != S::ONE) || (t2 != S::ZERO && t2 != S::ONE) { result.push((t, t2)); } } } 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, 2> { let intersections = self.line_segment_intersections_t(segment); let mut result = ArrayVec::new(); for (t, _) in intersections { result.push(self.sample(t)); } result } /// Analytic solution to finding the closest point on the curve to `pos`. pub fn closest_point(&self, pos: Point) -> S { // We are looking for the points in the curve where the line passing through pos // and these points are perpendicular to the curve. let a = self.from - pos; let b = self.ctrl - self.from; let c = self.from + self.to.to_vector() - self.ctrl * S::TWO; // Polynomial coefficients let c0 = c.dot(c); let c1 = b.dot(c) * S::THREE; let c2 = b.dot(b) * S::TWO + a.dot(c); let c3 = a.dot(b); let roots = crate::utils::cubic_polynomial_roots(c0, c1, c2, c3); let mut sq_dist = a.square_length(); let mut t = S::ZERO; let to_dist = (self.to - pos).square_length(); if to_dist < sq_dist { sq_dist = to_dist; t = S::ONE } for root in roots { if root >= S::ZERO && root <= S::ONE { let p = self.sample(root); let d = (pos - p).square_length(); if d < sq_dist { sq_dist = d; t = root; } } } t } /// Returns the shortest distance between this segment and a point. pub fn distance_to_point(&self, pos: Point) -> S { (self.sample(self.closest_point(pos)) - pos).length() } /// Returns the shortest squared distance between this segment and a point. /// /// May be useful to avoid the cost of a square root when comparing against a distance /// that can be squared instead. pub fn square_distance_to_point(&self, pos: Point) -> S { (self.sample(self.closest_point(pos)) - pos).square_length() } // Returns a quadratic bézier curve built by dragging this curve's point at `t` // to a new position without moving the endpoints. pub fn drag(&self, t: S, new_position: Point) -> Self { let t2 = t * t; let one_t = S::ONE - t; let one_t2 = one_t * one_t; let u = t2 / (t2 + one_t2); let c = self.from.lerp(self.to, u); let inv_r = S::abs((t2 + one_t2) / (t2 + one_t2 - S::ONE)); QuadraticBezierSegment { from: self.from, ctrl: new_position + (new_position - c) * inv_r, to: self.to, } } /// Computes the length of this segment. /// /// Implements Raph Levien's analytical approach described in /// https://raphlinus.github.io/curves/2018/12/28/bezier-arclength.html pub fn length(&self) -> S { // This is ported from kurbo's implementation. // https://github.com/linebender/kurbo/blob/d0b956b47f219ba2303b4e2f2d904ea7b946e783/src/quadbez.rs#L239 let d2 = self.from - self.ctrl * S::TWO + self.to.to_vector(); let d1 = self.ctrl - self.from; let a = d2.square_length(); let c = d1.square_length(); if a < S::value(1e-4) * c { // The segment is almost straight. // // Legendre-Gauss quadrature using formula from Behdad // in https://github.com/Pomax/BezierInfo-2/issues/77 let v0 = (self.from.to_vector() * S::value(-0.492943519233745) + self.ctrl.to_vector() * S::value(0.430331482911935) + self.to.to_vector() * S::value(0.0626120363218102)) .length(); let v1 = ((self.to - self.from) * S::value(0.4444444444444444)).length(); let v2 = (self.from.to_vector() * S::value(-0.0626120363218102) + self.ctrl.to_vector() * S::value(-0.430331482911935) + self.to.to_vector() * S::value(0.492943519233745)) .length(); return v0 + v1 + v2; } let b = S::TWO * d2.dot(d1); let sqr_abc = (a + b + c).sqrt(); let a2 = a.powf(-S::HALF); let a32 = a2.powi(3); let c2 = S::TWO * c.sqrt(); let ba_c2 = b * a2 + c2; let v0 = S::HALF * S::HALF * a2 * a2 * b * (S::TWO * sqr_abc - c2) + sqr_abc; if ba_c2 < S::EPSILON { // The curve has a sharp turns. v0 } else { v0 + S::HALF * S::HALF * a32 * (S::FOUR * c * a - b * b) * (((S::TWO * a + b) * a2 + S::TWO * sqr_abc) / ba_c2).ln() } } // This is to conform to the `impl_segment!` macro fn approximate_length(&self, _tolerance: S) -> S { self.length() } pub fn to_f32(&self) -> QuadraticBezierSegment { QuadraticBezierSegment { from: self.from.to_f32(), ctrl: self.ctrl.to_f32(), to: self.to.to_f32(), } } pub fn to_f64(&self) -> QuadraticBezierSegment { QuadraticBezierSegment { from: self.from.to_f64(), ctrl: self.ctrl.to_f64(), to: self.to.to_f64(), } } } pub struct FlatteningParameters { count: S, integral_from: S, integral_step: S, inv_integral_from: S, div_inv_integral_diff: S, } impl FlatteningParameters { // See https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html pub fn new(curve: &QuadraticBezierSegment, tolerance: S) -> Self { // Checking for the single segment approximation is much cheaper than evaluating // the general flattening approximation. if curve.is_linear(tolerance) { return FlatteningParameters { count: S::ZERO, // This are irrelevant as if count is 0. integral_from: S::ZERO, integral_step: S::ZERO, inv_integral_from: S::ZERO, div_inv_integral_diff: S::ZERO, }; } // Map the quadratic bézier segment to y = x^2 parabola. let ddx = S::TWO * curve.ctrl.x - curve.from.x - curve.to.x; let ddy = S::TWO * curve.ctrl.y - curve.from.y - curve.to.y; let cross = (curve.to.x - curve.from.x) * ddy - (curve.to.y - curve.from.y) * ddx; let inv_cross = S::ONE / cross; let parabola_from = ((curve.ctrl.x - curve.from.x) * ddx + (curve.ctrl.y - curve.from.y) * ddy) * inv_cross; let parabola_to = ((curve.to.x - curve.ctrl.x) * ddx + (curve.to.y - curve.ctrl.y) * ddy) * inv_cross; // Note, scale can be NaN, for example with straight lines. When it happens the NaN will // propagate to other parameters. We catch it all by setting the iteration count to zero // and leave the rest as garbage. let scale = cross.abs() / (S::sqrt(ddx * ddx + ddy * ddy) * (parabola_to - parabola_from).abs()); let integral_from = approx_parabola_integral(parabola_from); let integral_to = approx_parabola_integral(parabola_to); let integral_diff = integral_to - integral_from; let inv_integral_from = approx_parabola_inv_integral(integral_from); let inv_integral_to = approx_parabola_inv_integral(integral_to); let div_inv_integral_diff = S::ONE / (inv_integral_to - inv_integral_from); // We could store this as an integer but the generic code makes that awkward and we'll // use it as a scalar again while iterating, so it's kept as a scalar. let mut count = (S::HALF * integral_diff.abs() * (scale / tolerance).sqrt()).ceil(); // If count is NaN the curve can be approximated by a single straight line or a point. if !count.is_finite() { count = S::ZERO; } let integral_step = integral_diff / count; FlatteningParameters { count, integral_from, integral_step, inv_integral_from, div_inv_integral_diff, } } fn t_at_iteration(&self, iteration: S) -> S { let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration); let t = (u - self.inv_integral_from) * self.div_inv_integral_diff; t } } /// Compute an approximation to integral (1 + 4x^2) ^ -0.25 dx used in the flattening code. fn approx_parabola_integral(x: S) -> S { let d = S::value(0.67); let quarter = S::HALF * S::HALF; x / (S::ONE - d + (d.powi(4) + quarter * x * x).sqrt().sqrt()) } /// Approximate the inverse of the function above. fn approx_parabola_inv_integral(x: S) -> S { let b = S::value(0.39); let quarter = S::HALF * S::HALF; x * (S::ONE - b + (b * b + quarter * x * x).sqrt()) } /// A flattening iterator for quadratic bézier segments. /// /// Yields points at each iteration. pub struct Flattened { curve: QuadraticBezierSegment, params: FlatteningParameters, i: S, done: bool, } impl Flattened { #[inline] pub(crate) fn new(curve: &QuadraticBezierSegment, tolerance: S) -> Self { let params = FlatteningParameters::new(curve, tolerance); Flattened { curve: *curve, params, i: S::ONE, done: false, } } } impl Iterator for Flattened { type Item = Point; #[inline] fn next(&mut self) -> Option> { if self.done { return None; } if self.i >= self.params.count - S::EPSILON { self.done = true; return Some(self.curve.to); } let t = self.params.t_at_iteration(self.i); self.i += S::ONE; Some(self.curve.sample(t)) } #[inline] fn size_hint(&self) -> (usize, Option) { let count = (self.params.count + S::ONE - self.i).to_usize().unwrap(); (count, Some(count)) } } /// A flattening iterator for quadratic bézier segments. /// /// Yields the curve parameter at each iteration. pub struct FlattenedT { params: FlatteningParameters, i: S, done: bool, } impl FlattenedT { #[inline] pub(crate) fn new(curve: &QuadraticBezierSegment, tolerance: S) -> Self { let params = FlatteningParameters::new(curve, tolerance); FlattenedT { i: S::ONE, params, done: false, } } } impl Iterator for FlattenedT { type Item = S; #[inline] fn next(&mut self) -> Option { if self.done { return None; } if self.i >= self.params.count - S::EPSILON { self.done = true; return Some(S::ONE); } let t = self.params.t_at_iteration(self.i); self.i += S::ONE; Some(t) } #[inline] fn size_hint(&self) -> (usize, Option) { let count = (self.params.count + S::ONE - self.i).to_usize().unwrap(); (count, Some(count)) } } impl Segment for QuadraticBezierSegment { impl_segment!(S); fn for_each_flattened_with_t( &self, tolerance: Self::Scalar, callback: &mut dyn FnMut(&LineSegment, Range), ) { self.for_each_flattened_with_t(tolerance, &mut |s, t| callback(s, t)); } } impl BoundingBox for QuadraticBezierSegment { type Scalar = S; fn bounding_box(&self) -> Box2D { self.bounding_box() } fn fast_bounding_box(&self) -> Box2D { self.fast_bounding_box() } 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() } } #[test] fn bounding_box_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_aabb = Box2D { min: point(0.0, 0.0), max: point(2.0, 0.0), }; let actual_aabb = a.bounding_box(); assert_eq!(expected_aabb, actual_aabb) } #[test] fn fast_bounding_box_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_aabb = Box2D { min: point(0.0, 0.0), max: point(2.0, 1.0), }; let actual_aabb = a.fast_bounding_box(); assert_eq!(expected_aabb, actual_aabb) } #[test] fn minimum_bounding_box_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_aabb = Box2D { min: point(0.0, 0.0), max: point(2.0, 0.5), }; let actual_aabb = a.bounding_box(); assert_eq!(expected_aabb, actual_aabb) } #[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_eq!(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_eq!(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_eq!(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_eq!(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_eq!(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_eq!(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.0f64, 0.0), ctrl: Point::new(1.0, 0.0), to: Point::new(2.0, 0.0), } .length(); assert!((len - 2.0).abs() < 0.000001); let len = CubicBezierSegment { from: Point::new(0.0f64, 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.0001); assert!((len - 2.0).abs() < 0.000001); } #[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 fat_line() { use crate::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::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); } #[test] fn test_flattening_empty_curve() { use crate::point; let curve = QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(0.0, 0.0), to: point(0.0, 0.0), }; let mut iter = FlattenedT::new(&curve, 0.1); assert_eq!(iter.next(), Some(1.0)); assert_eq!(iter.next(), None); let mut count: u32 = 0; curve.for_each_flattened(0.1, &mut |_| count += 1); assert_eq!(count, 1); } #[test] fn test_flattening_straight_line() { use crate::point; let curve = QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(10.0, 0.0), to: point(20.0, 0.0), }; let mut iter = FlattenedT::new(&curve, 0.1); assert_eq!(iter.next(), Some(1.0)); assert!(iter.next().is_none()); let mut count: u32 = 0; curve.for_each_flattened(0.1, &mut |_| count += 1); assert_eq!(count, 1); } #[test] fn issue_678() { let points = [ [-7768.80859375f32, -35563.80859375], [-38463.125, -10941.41796875], [-21846.12890625, -13518.1953125], [-11727.439453125, -22080.33203125], ]; let quadratic = QuadraticBezierSegment { from: Point::new(points[0][0], points[0][1]), ctrl: Point::new(points[1][0], points[1][1]), to: Point::new(points[2][0], points[2][1]), }; let line = Line { point: Point::new(points[3][0], points[3][1]), vector: Vector::new(-0.5, -0.5).normalize(), }; let intersections = quadratic.line_intersections(&line); std::println!("{intersections:?}"); assert_eq!(intersections.len(), 1); } #[test] fn line_intersections_t() { let curve = QuadraticBezierSegment { from: point(0.0f64, 0.0), ctrl: point(100.0, 0.0), to: point(100.0, 500.0), }; let cubic = curve.to_cubic(); let line = Line { point: point(0.0, -50.0), vector: crate::vector(100.0, 500.0), }; let mut i1 = curve.line_intersections_t(&line); let mut i2 = curve.to_cubic().line_intersections_t(&line); use std::cmp::Ordering::{Equal, Greater, Less}; i1.sort_by(|a, b| { if a == b { Equal } else if a > b { Greater } else { Less } }); i2.sort_by(|a, b| { if a == b { Equal } else if a > b { Greater } else { Less } }); for (t1, t2) in i1.iter().zip(i2.iter()) { use euclid::approxeq::ApproxEq; let p1 = curve.sample(*t1); let p2 = cubic.sample(*t2); assert!(p1.approx_eq(&p2), "{:?} == {:?}", p1, p2); } assert_eq!(i2.len(), 2); assert_eq!(i1.len(), 2); } #[test] fn drag() { let curve = QuadraticBezierSegment { from: point(0.0f32, 0.0), ctrl: point(100.0, 0.0), to: point(100.0, 100.0), }; for t in [0.5, 0.25, 0.1, 0.4, 0.7] { let target = point(0.0, 10.0); let dragged = curve.drag(t, target); use euclid::approxeq::ApproxEq; let p1 = dragged.sample(t); assert!( p1.approx_eq_eps(&target, &point(0.001, 0.001)), "{:?} == {:?}", p1, target ); } } #[test] fn arc_length() { let curves = [ QuadraticBezierSegment { from: point(0.0f64, 0.0), ctrl: point(100.0, 0.0), to: point(0.0, 100.0), }, QuadraticBezierSegment { from: point(0.0, 0.0), ctrl: point(100.0, 0.0), to: point(200.0, 0.0), }, QuadraticBezierSegment { from: point(100.0, 0.0), ctrl: point(0.0, 0.0), to: point(50.0, 1.0), }, ]; for (idx, curve) in curves.iter().enumerate() { let length = curve.length(); let mut accum = 0.0; curve.for_each_flattened(0.00000001, &mut |line| { accum += line.length(); }); assert!( (length - accum).abs() < 0.00001, "curve {:?}, {:?} == {:?}", idx, length, accum ); } } lyon_geom-1.0.5/src/segment.rs000064400000000000000000000115231046102023000144200ustar 00000000000000use crate::scalar::Scalar; use crate::{point, Box2D, LineSegment, Point, Vector}; use core::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; /// Approximates the curve with sequence of line segments. /// /// The `tolerance` parameter defines the maximum distance between the curve and /// its approximation. /// /// The parameter `t` at the final segment is guaranteed to be equal to `1.0`. #[allow(clippy::type_complexity)] fn for_each_flattened_with_t( &self, tolerance: Self::Scalar, callback: &mut dyn FnMut(&LineSegment, Range), ); } pub trait BoundingBox { type Scalar: Scalar; /// Returns the smallest rectangle that contains the curve. fn bounding_box(&self) -> Box2D { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// Returns a conservative rectangle that contains the curve. /// /// This does not necessarily return the smallest possible bounding rectangle. fn fast_bounding_box(&self) -> Box2D { let (min_x, max_x) = self.fast_bounding_range_x(); let (min_y, max_y) = self.fast_bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } /// 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); } 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) } }; } lyon_geom-1.0.5/src/triangle.rs000064400000000000000000000175661046102023000146000ustar 00000000000000use crate::scalar::Scalar; use crate::traits::Transformation; use crate::LineSegment; use crate::{point, Box2D, Point}; /// 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 } /// Returns a conservative range of x that contains this triangle. #[inline] pub fn bounding_range_x(&self) -> (S, S) { let min_x = self.a.x.min(self.b.x).min(self.c.x); let max_x = self.a.x.max(self.b.x).max(self.c.x); (min_x, max_x) } /// Returns a conservative range of y that contains this triangle. #[inline] pub fn bounding_range_y(&self) -> (S, S) { let min_y = self.a.y.min(self.b.y).min(self.c.y); let max_y = self.a.y.max(self.b.y).max(self.c.y); (min_y, max_y) } /// Returns the smallest rectangle that contains this triangle. #[inline] pub fn bounding_box(&self) -> Box2D { let (min_x, max_x) = self.bounding_range_x(); let (min_y, max_y) = self.bounding_range_y(); Box2D { min: point(min_x, min_y), max: point(max_x, max_y), } } #[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. 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 { self.ab().intersects(segment) || self.bc().intersects(segment) || self.ac().intersects(segment) || self.contains_point(segment.from) } } #[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_eq!(t.ab(), t.ba().flip()); assert_eq!(t.ac(), t.ca().flip()); assert_eq!(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 segment 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())); } #[test] fn test_bounding_box() { let t1 = Triangle { a: point(10.0, 20.0), b: point(35.0, 40.0), c: point(50.0, 10.0), }; let r1 = Box2D { min: point(10.0, 10.0), max: point(50.0, 40.0), }; let t2 = Triangle { a: point(5.0, 30.0), b: point(25.0, 10.0), c: point(35.0, 40.0), }; let r2 = Box2D { min: point(5.0, 10.0), max: point(35.0, 40.0), }; let t3 = Triangle { a: point(1.0, 1.0), b: point(2.0, 5.0), c: point(0.0, 4.0), }; let r3 = Box2D { min: point(0.0, 1.0), max: point(2.0, 5.0), }; let cases = std::vec![(t1, r1), (t2, r2), (t3, r3)]; for &(tri, r) in &cases { assert_eq!(tri.bounding_box(), r); } } lyon_geom-1.0.5/src/utils.rs000064400000000000000000000115271046102023000141220ustar 00000000000000use crate::scalar::{Float, Scalar}; use crate::{vector, Point, 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 assuming 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); 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 { let mut result = ArrayVec::new(); let m = a.abs().max(b.abs()).max(c.abs()).max(d.abs()); let epsilon = S::epsilon_for(m); if S::abs(a) < epsilon { if S::abs(b) < epsilon { if S::abs(c) < 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) < 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) < epsilon && S::abs(s + t) >= 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(); result } #[test] fn cubic_polynomial() { fn assert_approx_eq(a: ArrayVec, b: &[f32], epsilon: f32) { for i in 0..a.len() { if f32::abs(a[i] - b[i]) > epsilon { std::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); }