diff --git a/benches/sweep.rs b/benches/sweep.rs index c030aded..33f720f4 100644 --- a/benches/sweep.rs +++ b/benches/sweep.rs @@ -1,38 +1,53 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use flo_curves::geo::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{BezierPathBuilder, BezierPathFactory, GraphPath, SimpleBezierPath}; +use flo_curves::geo::{ + sweep_self, BoundingBox, Bounds, Coord2, Coordinate, Coordinate2D, Coordinate3D, +}; use rand::prelude::*; -use std::cmp::{Ordering}; +use std::cmp::Ordering; fn sweep(n: usize) { - let mut rng = StdRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); - let mut bounds = (0..n).into_iter() + let mut rng = StdRng::from_seed([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, + ]); + let mut bounds = (0..n) + .into_iter() .map(|_| { let x = rng.gen::() * 900.0; let y = rng.gen::() * 900.0; let w = rng.gen::() * 400.0; let h = rng.gen::() * 400.0; - Bounds::from_min_max(Coord2(x, y), Coord2(x+w, y+h)) + Bounds::from_min_max(Coord2(x, y), Coord2(x + w, y + h)) }) .collect::>(); - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); let _ = sweep_self(bounds.iter()).collect::>(); } fn sweep_slow(n: usize) { - let mut rng = StdRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); - let bounds = (0..n).into_iter() + let mut rng = StdRng::from_seed([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, + ]); + let bounds = (0..n) + .into_iter() .map(|_| { let x = rng.gen::() * 900.0; let y = rng.gen::() * 900.0; let w = rng.gen::() * 400.0; let h = rng.gen::() * 400.0; - Bounds::from_min_max(Coord2(x, y), Coord2(x+w, y+h)) + Bounds::from_min_max(Coord2(x, y), Coord2(x + w, y + h)) }) .collect::>(); @@ -40,7 +55,9 @@ fn sweep_slow(n: usize) { for i1 in 0..bounds.len() { for i2 in 0..i1 { - if i1 == i2 { continue; } + if i1 == i2 { + continue; + } if bounds[i1].overlaps(&bounds[i2]) { slow_collisions.push((&bounds[i1], &bounds[i2])); @@ -50,9 +67,9 @@ fn sweep_slow(n: usize) { } fn create_graph_path(rng: &mut StdRng, n: usize) -> GraphPath { - let mut x = 100.0; - let mut y = 100.0; - let mut path_builder = BezierPathBuilder::::start(Coord2(x, y)); + let mut x = 100.0; + let mut y = 100.0; + let mut path_builder = BezierPathBuilder::::start(Coord2(x, y)); for _ in 0..n { let xo = rng.gen::() * 50.0; @@ -64,10 +81,9 @@ fn create_graph_path(rng: &mut StdRng, n: usize) -> GraphPath { path_builder = path_builder.line_to(Coord2(x, y)); } - let path = path_builder.build(); - let graph_path = GraphPath::from_path(&path, ()); + let path = path_builder.build(); - graph_path + GraphPath::from_path(&path, ()) } fn detect_collisions(mut graph_path: GraphPath) { @@ -79,12 +95,19 @@ fn merge_paths(path1: GraphPath, path2: GraphPath) { } fn criterion_benchmark(c: &mut Criterion) { - let mut rng = StdRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); - let graph_path = create_graph_path(&mut rng, 1000); - let merge_path = create_graph_path(&mut rng, 500); - - c.bench_function("detect_collisions 1000", |b| b.iter(|| detect_collisions(black_box(graph_path.clone())))); - c.bench_function("merge_paths 1000", |b| b.iter(|| merge_paths(black_box(graph_path.clone()), black_box(merge_path.clone())))); + let mut rng = StdRng::from_seed([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, + ]); + let graph_path = create_graph_path(&mut rng, 1000); + let merge_path = create_graph_path(&mut rng, 500); + + c.bench_function("detect_collisions 1000", |b| { + b.iter(|| detect_collisions(black_box(graph_path.clone()))) + }); + c.bench_function("merge_paths 1000", |b| { + b.iter(|| merge_paths(black_box(graph_path.clone()), black_box(merge_path.clone()))) + }); c.bench_function("sweep 10", |b| b.iter(|| sweep(black_box(10)))); c.bench_function("sweep_slow 10", |b| b.iter(|| sweep_slow(black_box(10)))); @@ -93,7 +116,9 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("sweep_slow 100", |b| b.iter(|| sweep_slow(black_box(100)))); c.bench_function("sweep 1000", |b| b.iter(|| sweep(black_box(1000)))); - c.bench_function("sweep_slow 1000", |b| b.iter(|| sweep_slow(black_box(1000)))); + c.bench_function("sweep_slow 1000", |b| { + b.iter(|| sweep_slow(black_box(1000))) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/src/arc/circle.rs b/src/arc/circle.rs index b3dcabcd..b41e49ed 100644 --- a/src/arc/circle.rs +++ b/src/arc/circle.rs @@ -1,5 +1,5 @@ -use super::super::bezier::*; -use super::super::bezier::path::*; +use super::super::bezier::path::BezierPathFactory; +use super::super::bezier::{BezierCurve, BezierCurveFactory, Coordinate, Coordinate2D, Curve}; use std::f64; @@ -8,21 +8,21 @@ use std::f64; /// /// Represents a circle in 2 dimensions -/// +/// #[derive(Clone, Copy)] -pub struct Circle { +pub struct Circle { /// The center of this circle pub center: Coord, /// The radius of this circle - pub radius: f64 + pub radius: f64, } /// /// Represents an arc of a circle in 2 dimensions -/// +/// #[derive(Clone, Copy)] -pub struct CircularArc<'a, Coord: 'a+Coordinate2D+Coordinate> { +pub struct CircularArc<'a, Coord: 'a + Coordinate2D + Coordinate> { /// The circle that this is an arc of circle: &'a Circle, @@ -30,89 +30,94 @@ pub struct CircularArc<'a, Coord: 'a+Coordinate2D+Coordinate> { start_radians: f64, /// The end point of this arc, in radians - end_radians: f64 + end_radians: f64, } -impl Circle { +impl Circle { /// /// Creates a new circle with a center and a radius - /// - pub fn new(center: Coord, radius: f64) -> Circle { - Circle { - center: center, - radius: radius - } + /// + pub fn new(center: Coord, radius: f64) -> Self { + Self { center, radius } } /// /// Returns an object representing an arc from this circle - /// - pub fn arc<'a>(&'a self, start_radians: f64, end_radians: f64) -> CircularArc<'a, Coord> { + /// + pub fn arc(&self, start_radians: f64, end_radians: f64) -> CircularArc { CircularArc { - circle: self, - start_radians: start_radians, - end_radians: end_radians + circle: self, + start_radians, + end_radians, } } /// /// Returns a set of bezier curves that approximate this circle - /// - pub fn to_curves>(&self) -> Vec { + /// + pub fn to_curves>(&self) -> Vec { // Angles to put the curves at (we need 4 curves for a decent approximation of a circle) - let start_angle = f64::consts::PI/4.0; - let section_angle = f64::consts::PI/2.0; - let angles = [ - start_angle, - start_angle + section_angle, - start_angle + section_angle*2.0, - start_angle + section_angle*3.0]; - + let start_angle = f64::consts::PI / 4.0; + let section_angle = f64::consts::PI / 2.0; + let angles = [ + start_angle, + start_angle + section_angle, + start_angle + section_angle * 2.0, + start_angle + section_angle * 3.0, + ]; + // Convert the angles into curves - angles.iter() - .map(|angle| self.arc(*angle, angle+section_angle).to_bezier_curve()) + angles + .iter() + .map(|angle| self.arc(*angle, angle + section_angle).to_bezier_curve()) .collect() } /// /// Returns a path that approximates this circle - /// - pub fn to_path>(&self) -> P { + /// + pub fn to_path>(&self) -> P { let curves = self.to_curves::>(); - P::from_points(curves[0].start_point(), curves.into_iter().map(|curve| { - let (cp1, cp2) = curve.control_points(); - let end_point = curve.end_point(); + P::from_points( + curves[0].start_point(), + curves.into_iter().map(|curve| { + let (cp1, cp2) = curve.control_points(); + let end_point = curve.end_point(); - (cp1, cp2, end_point) - })) + (cp1, cp2, end_point) + }), + ) } } -impl<'a, Coord: Coordinate2D+Coordinate> CircularArc<'a, Coord> { +impl<'a, Coord: Coordinate2D + Coordinate> CircularArc<'a, Coord> { /// /// Converts this arc to a bezier curve - /// + /// /// If this arc covers an angle > 90 degrees, the curve will /// be very inaccurate. - /// - pub fn to_bezier_curve>(&self) -> Curve { + /// + pub fn to_bezier_curve>(&self) -> Curve { // Algorithm described here: https://www.tinaja.com/glib/bezcirc2.pdf // Curve for the unit arc with its center at (1,0) - let theta = self.end_radians - self.start_radians; - let (x0, y0) = ((theta/2.0).cos(), (theta/2.0).sin()); - let (x1, y1) = ((4.0-x0)/3.0, ((1.0-x0)*(3.0-x0)/(3.0*y0))); - let (x2, y2) = (x1, -y1); - let (x3, y3) = (x0, -y0); + let theta = self.end_radians - self.start_radians; + let (x0, y0) = ((theta / 2.0).cos(), (theta / 2.0).sin()); + let (x1, y1) = ((4.0 - x0) / 3.0, ((1.0 - x0) * (3.0 - x0) / (3.0 * y0))); + let (x2, y2) = (x1, -y1); + let (x3, y3) = (x0, -y0); // Rotate so the curve starts at start_radians fn rotate(x: f64, y: f64, theta: f64) -> (f64, f64) { let (cos_theta, sin_theta) = (theta.cos(), theta.sin()); - (x*cos_theta + y*sin_theta, x*-sin_theta + y*cos_theta) + ( + x * cos_theta + y * sin_theta, + x * -sin_theta + y * cos_theta, + ) } - let angle = -(f64::consts::PI/2.0-(theta/2.0)); + let angle = -(f64::consts::PI / 2.0 - (theta / 2.0)); let angle = angle + self.start_radians; let (x0, y0) = rotate(x0, y0, angle); @@ -122,17 +127,17 @@ impl<'a, Coord: Coordinate2D+Coordinate> CircularArc<'a, Coord> { // Scale by radius let radius = self.circle.radius; - let (x0, y0) = (x0*radius, y0*radius); - let (x1, y1) = (x1*radius, y1*radius); - let (x2, y2) = (x2*radius, y2*radius); - let (x3, y3) = (x3*radius, y3*radius); + let (x0, y0) = (x0 * radius, y0 * radius); + let (x1, y1) = (x1 * radius, y1 * radius); + let (x2, y2) = (x2 * radius, y2 * radius); + let (x3, y3) = (x3 * radius, y3 * radius); // Translate by center let center = &self.circle.center; - let (x0, y0) = (x0+center.x(), y0+center.y()); - let (x1, y1) = (x1+center.x(), y1+center.y()); - let (x2, y2) = (x2+center.x(), y2+center.y()); - let (x3, y3) = (x3+center.x(), y3+center.y()); + let (x0, y0) = (x0 + center.x(), y0 + center.y()); + let (x1, y1) = (x1 + center.x(), y1 + center.y()); + let (x2, y2) = (x2 + center.x(), y2 + center.y()); + let (x3, y3) = (x3 + center.x(), y3 + center.y()); // Create the curve let p0 = Coord::from_components(&[x0, y0]); @@ -147,12 +152,15 @@ impl<'a, Coord: Coordinate2D+Coordinate> CircularArc<'a, Coord> { #[cfg(test)] mod test { use super::*; - use std::f64; + use crate::{ + bezier::path::{path_to_curves, SimpleBezierPath}, + Coord2, + }; #[test] fn can_convert_unit_arc() { - let circle = Circle::new(Coord2(0.0, 0.0), 1.0); - let arc = circle.arc(0.0, f64::consts::PI/2.0); + let circle = Circle::new(Coord2(0.0, 0.0), 1.0); + let arc = circle.arc(0.0, f64::consts::PI / 2.0); let curve: Curve<_> = arc.to_bezier_curve(); assert!(curve.start_point().distance_to(&Coord2(0.0, 1.0)) < 0.01); @@ -165,9 +173,9 @@ mod test { for curve in circle.to_curves::>() { for t in 0..=10 { - let t = (t as f64)/10.0; + let t = (t as f64) / 10.0; let p = curve.point_at_pos(t); - assert!((p.distance_to(&Coord2(0.0, 0.0))-1.0).abs() < 0.01); + assert!((p.distance_to(&Coord2(0.0, 0.0)) - 1.0).abs() < 0.01); } } } @@ -178,10 +186,10 @@ mod test { for curve in path_to_curves::<_, Curve<_>>(&circle.to_path::()) { for t in 0..=10 { - let t = (t as f64)/10.0; + let t = (t as f64) / 10.0; let p = curve.point_at_pos(t); - assert!((p.distance_to(&Coord2(5.0, 5.0))-4.0).abs() < 0.01); + assert!((p.distance_to(&Coord2(5.0, 5.0)) - 4.0).abs() < 0.01); } } } -} \ No newline at end of file +} diff --git a/src/arc/mod.rs b/src/arc/mod.rs index 3d8be2ad..0f222ff2 100644 --- a/src/arc/mod.rs +++ b/src/arc/mod.rs @@ -1,6 +1,6 @@ //! //! # Describing circular arcs -//! +//! //! The `arc` module provides routines for describing circular arcs and converting them to bezier //! curves. //! diff --git a/src/bezier/basis.rs b/src/bezier/basis.rs index e4248afd..aa874f2c 100644 --- a/src/bezier/basis.rs +++ b/src/bezier/basis.rs @@ -1,68 +1,79 @@ -use super::super::geo::*; +use super::super::geo::Coordinate; /// /// Computes the bezier coefficients (A, B, C, D) for a bezier curve -/// -pub fn bezier_coefficients(dimension: usize, w1: &Point, w2: &Point, w3: &Point, w4: &Point) -> (f64, f64, f64, f64) { +/// +pub fn bezier_coefficients( + dimension: usize, + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, +) -> (f64, f64, f64, f64) { let w1 = w1.get(dimension); let w2 = w2.get(dimension); let w3 = w3.get(dimension); let w4 = w4.get(dimension); ( - w4-(3.0*w3)+(3.0*w2)-w1, - (3.0*w3)-(6.0*w2)+3.0*w1, - 3.0*w2-3.0*w1, - w1 + w4 - (3.0 * w3) + (3.0 * w2) - w1, + (3.0 * w3) - (6.0 * w2) + 3.0 * w1, + 3.0 * w2 - 3.0 * w1, + w1, ) } /// /// The cubic bezier weighted basis function -/// +/// #[inline] pub fn basis(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> Point { - let t_squared = t*t; - let t_cubed = t_squared*t; + let t_squared = t * t; + let t_cubed = t_squared * t; - let one_minus_t = 1.0-t; - let one_minus_t_squared = one_minus_t*one_minus_t; - let one_minus_t_cubed = one_minus_t_squared*one_minus_t; + let one_minus_t = 1.0 - t; + let one_minus_t_squared = one_minus_t * one_minus_t; + let one_minus_t_cubed = one_minus_t_squared * one_minus_t; - w1*one_minus_t_cubed - + w2*3.0*one_minus_t_squared*t - + w3*3.0*one_minus_t*t_squared - + w4*t_cubed + w1 * one_minus_t_cubed + + w2 * 3.0 * one_minus_t_squared * t + + w3 * 3.0 * one_minus_t * t_squared + + w4 * t_cubed } - /// /// de Casteljau's algorithm for cubic bezier curves -/// +/// #[inline] -pub fn de_casteljau4(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> Point { - let wn1 = w1*(1.0-t) + w2*t; - let wn2 = w2*(1.0-t) + w3*t; - let wn3 = w3*(1.0-t) + w4*t; +pub fn de_casteljau4( + t: f64, + w1: Point, + w2: Point, + w3: Point, + w4: Point, +) -> Point { + let wn1 = w1 * (1.0 - t) + w2 * t; + let wn2 = w2 * (1.0 - t) + w3 * t; + let wn3 = w3 * (1.0 - t) + w4 * t; de_casteljau3(t, wn1, wn2, wn3) } /// /// de Casteljau's algorithm for quadratic bezier curves -/// +/// #[inline] pub fn de_casteljau3(t: f64, w1: Point, w2: Point, w3: Point) -> Point { - let wn1 = w1*(1.0-t) + w2*t; - let wn2 = w2*(1.0-t) + w3*t; + let wn1 = w1 * (1.0 - t) + w2 * t; + let wn2 = w2 * (1.0 - t) + w3 * t; de_casteljau2(t, wn1, wn2) } /// /// de Casteljau's algorithm for lines -/// +/// #[inline] pub fn de_casteljau2(t: f64, w1: Point, w2: Point) -> Point { - w1*(1.0-t) + w2*t + w1 * (1.0 - t) + w2 * t } diff --git a/src/bezier/bounds.rs b/src/bezier/bounds.rs index f8b1e5a4..ed867322 100644 --- a/src/bezier/bounds.rs +++ b/src/bezier/bounds.rs @@ -1,10 +1,10 @@ -use super::basis::*; -use super::super::geo::*; +use super::super::geo::{BoundingBox, Coordinate}; +use super::basis::de_casteljau4; /// /// Finds the t values of the extremities of a curve (these are the points at which /// the x or y value is at a minimum or maximum) -/// +/// pub fn find_extremities(w1: Point, w2: Point, w3: Point, w4: Point) -> Vec { // The 't' values where this curve has extremities we need to examine let mut t_extremes = vec![1.0]; @@ -18,24 +18,28 @@ pub fn find_extremities(w1: Point, w2: Point, w3: Point, w4: let p4 = w4.get(component_index); // Compute the bezier coefficients - let a = (-p1 + p2*3.0 - p3*3.0 + p4)*3.0; - let b = (p1 - p2*2.0 + p3)*6.0; - let c = (p2 - p1)*3.0; + let a = (-p1 + p2 * 3.0 - p3 * 3.0 + p4) * 3.0; + let b = (p1 - p2 * 2.0 + p3) * 6.0; + let c = (p2 - p1) * 3.0; // Extremities are points at which the curve has a 0 gradient (in any of its dimensions) - let root1 = (-b + f64::sqrt(b*b - a*c*4.0)) / (a*2.0); - let root2 = (-b - f64::sqrt(b*b - a*c*4.0)) / (a*2.0); + let root1 = (-b + f64::sqrt(b * b - a * c * 4.0)) / (a * 2.0); + let root2 = (-b - f64::sqrt(b * b - a * c * 4.0)) / (a * 2.0); - if root1 > 0.0 && root1 < 1.0 { t_extremes.push(root1); } - if root2 > 0.0 && root2 < 1.0 { t_extremes.push(root2); } + if root1 > 0.0 && root1 < 1.0 { + t_extremes.push(root1); + } + if root2 > 0.0 && root2 < 1.0 { + t_extremes.push(root2); + } // We also solve for the second derivative - let aa = 2.0*(b-a); - let bb = 2.0*(c-a); + let aa = 2.0 * (b - a); + let bb = 2.0 * (c - a); // Solve for a'*t+b = 0 (0-b/a') if aa != 0.0 { - let root3 = -bb/aa; + let root3 = -bb / aa; if root3 > 0.0 && root3 < 1.0 { t_extremes.push(root3); } @@ -47,8 +51,13 @@ pub fn find_extremities(w1: Point, w2: Point, w3: Point, w4: /// /// Finds the upper and lower points in a cubic curve's bounding box -/// -pub fn bounding_box4>(w1: Point, w2: Point, w3: Point, w4: Point) -> Bounds { +/// +pub fn bounding_box4>( + w1: Point, + w2: Point, + w3: Point, + w4: Point, +) -> Bounds { // The 't' values where this curve has extremities we need to examine let t_extremes = find_extremities(w1, w2, w3, w4); diff --git a/src/bezier/characteristics.rs b/src/bezier/characteristics.rs index 762b6ec1..8047e258 100644 --- a/src/bezier/characteristics.rs +++ b/src/bezier/characteristics.rs @@ -1,8 +1,8 @@ -use super::curve::*; -use super::intersection::*; -use super::super::geo::*; -use super::super::line::*; -use super::super::consts::*; +use super::super::consts::SMALL_DISTANCE; +use super::super::geo::{Coordinate, Coordinate2D}; +use super::super::line::line_coefficients_2d; +use super::curve::{BezierCurve, BezierCurveFactory, Curve}; +use super::intersection::find_self_intersection_point; use std::f64; @@ -39,7 +39,7 @@ pub enum CurveCategory { Cusp, /// A curve containing a loop - Loop + Loop, } /// @@ -69,38 +69,42 @@ pub enum CurveFeatures { Cusp, /// A curve containing a loop and the two t values where it self-intersects - Loop(f64, f64) + Loop(f64, f64), } /// /// Computes an affine transform that translates from an arbitrary bezier curve to one that has the first three control points /// fixed at w1 = (0,0), w2 = (0, 1) and w3 = (1, 1). -/// +/// /// Bezier curves maintain their properties when transformed so this provides a curve with equivalent properties to the input /// curve but only a single free point (w4). This will return 'None' for the degenerate cases: where two points overlap or /// where the points are collinear. /// -fn canonical_curve_transform(w1: &Point, w2: &Point, w3: &Point) -> Option<(f64, f64, f64, f64, f64, f64)> { +fn canonical_curve_transform( + w1: &Point, + w2: &Point, + w3: &Point, +) -> Option<(f64, f64, f64, f64, f64, f64)> { // Fetch the coordinates let (x0, y0) = (w1.x(), w1.y()); let (x1, y1) = (w2.x(), w2.y()); let (x2, y2) = (w3.x(), w3.y()); - let a_divisor = (y2-y1)*(x0-x1)-(x2-x1)*(y0-y1); + let a_divisor = (y2 - y1) * (x0 - x1) - (x2 - x1) * (y0 - y1); if a_divisor.abs() > SMALL_DIVISOR { // Transform is: - // + // // [ a, b, c ] [ x ] // [ d, e, f ] . [ y ] // [ 0, 0, 1 ] [ 1 ] - // + // // This will move w1 to 0,0, w2 to 0, 1 and w3 to 1, 1, which will form our canonical curve that we use for the classification algorithm - let a = (-(y0-y1)) / a_divisor; - let b = (-(x0-x1)) / ((x2-x1)*(y0-y1)-(y2-y1)*(x0-x1)); - let c = -a*x0 - b*y0; - let d = (y1-y2) / ((x0-x1)*(y2-y1) - (x2-x1)*(y0-y1)); - let e = (x1-x2) / ((y0-y1)*(x2-x1) - (y2-y1)*(x0-x1)); - let f = -d*x0 - e*y0; + let a = (-(y0 - y1)) / a_divisor; + let b = (-(x0 - x1)) / ((x2 - x1) * (y0 - y1) - (y2 - y1) * (x0 - x1)); + let c = -a * x0 - b * y0; + let d = (y1 - y2) / ((x0 - x1) * (y2 - y1) - (x2 - x1) * (y0 - y1)); + let e = (x1 - x2) / ((y0 - y1) * (x2 - x1) - (y2 - y1) * (x0 - x1)); + let f = -d * x0 - e * y0; Some((a, b, c, d, e, f)) } else { @@ -111,19 +115,24 @@ fn canonical_curve_transform(w1: &Point, w2: &Po /// /// Converts a set of points to a 'canonical' curve -/// +/// /// This is the curve such that w1 = (0.0), w2 = (1, 0) and w3 = (1, 1), if such a curve exists. The return value is the point w4 /// for this curve. /// -fn to_canonical_curve(w1: &Point, w2: &Point, w3: &Point, w4: &Point) -> Option { +fn to_canonical_curve( + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, +) -> Option { // Retrieve the affine transform for the curve if let Some((a, b, c, d, e, f)) = canonical_curve_transform(w1, w2, w3) { // Calculate the free point w4 based on the transform - let x4 = w4.x(); - let y4 = w4.y(); + let x4 = w4.x(); + let y4 = w4.y(); - let x = a*x4 + b*y4 + c; - let y = d*x4 + e*y4 + f; + let x = a * x4 + b * y4 + c; + let y = d * x4 + e * y4 + f; Some(Point::from_components(&[x, y])) } else { @@ -137,8 +146,8 @@ fn to_canonical_curve(w1: &Point, w2: &Point, w3 #[inline] fn characterize_from_canonical_point(b4: (f64, f64)) -> CurveCategory { // These coefficients can be used to characterise the curve - let (x, y) = b4; - let delta = x*x - 2.0*x + 4.0*y - 3.0; + let (x, y) = b4; + let delta = x * x - 2.0 * x + 4.0 * y - 3.0; if delta.abs() <= f64::EPSILON { // Curve has a cusp (but we don't know if it's in the range 0<=t<=1) @@ -158,8 +167,8 @@ fn characterize_from_canonical_point(b4: (f64, f64)) -> CurveCategory { } else { CurveCategory::Arch } - } else if x*x - 3.0*x + 3.0*y >= 0.0 { - if x*x + y*y + x*y - 3.0*x >= 0.0 { + } else if x * x - 3.0 * x + 3.0 * y >= 0.0 { + if x * x + y * y + x * y - 3.0 * x >= 0.0 { // Curve lies within the loop region CurveCategory::Loop } else { @@ -170,18 +179,14 @@ fn characterize_from_canonical_point(b4: (f64, f64)) -> CurveCategory { // Loop is outside of 0<=t<=1 (double point is t > 1) CurveCategory::Arch } + } else if y >= 1.0 { + CurveCategory::SingleInflectionPoint + } else if x <= 0.0 { + CurveCategory::DoubleInflectionPoint + } else if (x - 3.0).abs() <= f64::EPSILON && (y - 0.0).abs() <= f64::EPSILON { + CurveCategory::Parabolic } else { - if y >= 1.0 { - CurveCategory::SingleInflectionPoint - } else if x <= 0.0 { - CurveCategory::DoubleInflectionPoint - } else { - if (x-3.0).abs() <= f64::EPSILON && (y-0.0).abs() <= f64::EPSILON { - CurveCategory::Parabolic - } else { - CurveCategory::Arch - } - } + CurveCategory::Arch } } @@ -189,13 +194,18 @@ fn characterize_from_canonical_point(b4: (f64, f64)) -> CurveCategory { /// Determines the characteristics of a particular bezier curve: whether or not it is an arch, or changes directions /// (has inflection points), or self-intersects (has a loop) /// -pub fn characterize_cubic_bezier(w1: &Point, w2: &Point, w3: &Point, w4: &Point) -> CurveCategory { - // b4 is the end point of an equivalent curve with the other control points fixed at (0, 0), (0, 1) and (1, 1) - let b4 = to_canonical_curve(w1, w2, w3, w4); +pub fn characterize_cubic_bezier( + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, +) -> CurveCategory { + // b4 is the end point of an equivalent curve with the other control points fixed at (0, 0), (0, 1) and (1, 1) + let b4 = to_canonical_curve(w1, w2, w3, w4); if let Some(b4) = b4 { - let x = b4.x(); - let y = b4.y(); + let x = b4.x(); + let y = b4.y(); characterize_from_canonical_point((x, y)) } else { @@ -214,10 +224,10 @@ pub fn characterize_cubic_bezier(w1: &Point, w2: CurveCategory::Linear } else { // w2 and w3 are the same. If w1, w2, w3 and w4 are collinear then we have a straight line, otherwise we have a curve with an inflection point. - let line = (w1.clone(), w3.clone()); - let (a, b, c) = line_coefficients_2d(&line); + let line = (*w1, *w3); + let (a, b, c) = line_coefficients_2d(&line); - let distance = a*w4.x() + b*w4.y() + c; + let distance = a * w4.x() + b * w4.y() + c; if distance.abs() < SMALL_DISTANCE { // w1, w3 and w4 are collinear (and w2 is the same as w3) CurveCategory::Linear @@ -234,8 +244,8 @@ pub fn characterize_cubic_bezier(w1: &Point, w2: if let Some(b1) = b1 { // w4 is not co-linear with w1, w2, w3 - let x = b1.x(); - let y = b1.y(); + let x = b1.x(); + let y = b1.y(); characterize_from_canonical_point((x, y)) } else { @@ -252,7 +262,7 @@ pub fn characterize_cubic_bezier(w1: &Point, w2: enum InflectionPoints { Zero, One(f64), - Two(f64, f64) + Two(f64, f64), } /// @@ -260,45 +270,43 @@ enum InflectionPoints { /// fn find_inflection_points(b4: (f64, f64)) -> InflectionPoints { // Compute coefficients - let (x4, y4) = b4; - let a = -3.0+x4+y4; - let b = 3.0-x4; + let (x4, y4) = b4; + let a = -3.0 + x4 + y4; + let b = 3.0 - x4; if a.abs() <= f64::EPSILON { // No solution InflectionPoints::Zero } else { // Solve the quadratic for this curve - let lhs = (-b)/(2.0*a); - let rhs = (4.0*a + b*b).sqrt()/(2.0*a); + let lhs = (-b) / (2.0 * a); + let rhs = (4.0 * a + b * b).sqrt() / (2.0 * a); - let t1 = lhs - rhs; - let t2 = lhs + rhs; + let t1 = lhs - rhs; + let t2 = lhs + rhs; // Want points between 0 and 1 - if t1 < 0.0 || t1 > 1.0 { - if t2 < 0.0 || t2 > 1.0 { + if !(0.0..=1.0).contains(&t1) { + if !(0.0..=1.0).contains(&t2) { InflectionPoints::Zero } else { InflectionPoints::One(t2) } + } else if !(0.0..=1.0).contains(&t2) { + InflectionPoints::One(t1) } else { - if t2 < 0.0 || t2 > 1.0 { - InflectionPoints::One(t1) - } else { - InflectionPoints::Two(t1, t2) - } + InflectionPoints::Two(t1, t2) } } } -impl Into for InflectionPoints { +impl From for CurveFeatures { #[inline] - fn into(self) -> CurveFeatures { - match self { - InflectionPoints::Zero => CurveFeatures::Arch, - InflectionPoints::One(t) => CurveFeatures::SingleInflectionPoint(t), - InflectionPoints::Two(t1, t2) => CurveFeatures::DoubleInflectionPoint(t1, t2) + fn from(ip: InflectionPoints) -> Self { + match ip { + InflectionPoints::Zero => Self::Arch, + InflectionPoints::One(t) => Self::SingleInflectionPoint(t), + InflectionPoints::Two(t1, t2) => Self::DoubleInflectionPoint(t1, t2), } } } @@ -306,21 +314,31 @@ impl Into for InflectionPoints { /// /// Returns the features from a curve where we have discovered the canonical point /// -fn features_from_canonical_point(x: f64, y: f64, w1: &Point, w2: &Point, w3: &Point, w4: &Point, accuracy: f64) -> CurveFeatures { +fn features_from_canonical_point( + x: f64, + y: f64, + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, + accuracy: f64, +) -> CurveFeatures { match characterize_from_canonical_point((x, y)) { - CurveCategory::Arch => CurveFeatures::Arch, - CurveCategory::Linear => CurveFeatures::Linear, - CurveCategory::Cusp => CurveFeatures::Cusp, - CurveCategory::Parabolic => CurveFeatures::Parabolic, - CurveCategory::Point => CurveFeatures::Point, - CurveCategory::DoubleInflectionPoint | - CurveCategory::SingleInflectionPoint => find_inflection_points((x, y)).into(), - CurveCategory::Loop => { - let curve = Curve::from_points(w1.clone(), (w2.clone(), w3.clone()), w4.clone()); - let loop_pos = find_self_intersection_point(&curve, accuracy); + CurveCategory::Arch => CurveFeatures::Arch, + CurveCategory::Linear => CurveFeatures::Linear, + CurveCategory::Cusp => CurveFeatures::Cusp, + CurveCategory::Parabolic => CurveFeatures::Parabolic, + CurveCategory::Point => CurveFeatures::Point, + CurveCategory::DoubleInflectionPoint | CurveCategory::SingleInflectionPoint => { + find_inflection_points((x, y)).into() + } + CurveCategory::Loop => { + let curve = Curve::from_points(*w1, (*w2, *w3), *w4); + let loop_pos = find_self_intersection_point(&curve, accuracy); // TODO: if we can't find the loop_pos, we could probably find a cusp position instead - loop_pos.map(|(t1, t2)| CurveFeatures::Loop(t1, t2)) + loop_pos + .map(|(t1, t2)| CurveFeatures::Loop(t1, t2)) .unwrap_or(CurveFeatures::Arch) } } @@ -330,15 +348,21 @@ fn features_from_canonical_point(x: f64, y: f64, /// Determines the characteristics of a paritcular bezier curve: whether or not it is an arch, or changes directions /// (has inflection points), or self-intersects (has a loop) /// -pub fn features_for_cubic_bezier(w1: &Point, w2: &Point, w3: &Point, w4: &Point, accuracy: f64) -> CurveFeatures { - // b4 is the end point of an equivalent curve with the other control points fixed at (0, 0), (0, 1) and (1, 1) - let b4 = to_canonical_curve(w1, w2, w3, w4); +pub fn features_for_cubic_bezier( + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, + accuracy: f64, +) -> CurveFeatures { + // b4 is the end point of an equivalent curve with the other control points fixed at (0, 0), (0, 1) and (1, 1) + let b4 = to_canonical_curve(w1, w2, w3, w4); if let Some(b4) = b4 { // For the inflection points, we rely on the fact that the canonical curve is generated by an affine transform of the original // (and the features are invariant in such a situation) - let x = b4.x(); - let y = b4.y(); + let x = b4.x(); + let y = b4.y(); features_from_canonical_point(x, y, w1, w2, w3, w4, accuracy) } else { @@ -357,10 +381,10 @@ pub fn features_for_cubic_bezier(w1: &Point, w2: CurveFeatures::Linear } else { // w2 and w3 are the same. If w1, w2, w3 and w4 are collinear then we have a straight line, otherwise we have a curve with an inflection point. - let line = (w1.clone(), w3.clone()); - let (a, b, c) = line_coefficients_2d(&line); + let line = (*w1, *w3); + let (a, b, c) = line_coefficients_2d(&line); - let distance = a*w4.x() + b*w4.y() + c; + let distance = a * w4.x() + b * w4.y() + c; if distance.abs() < SMALL_DISTANCE { // w1, w3 and w4 are collinear (and w2 is the same as w3) CurveFeatures::Linear @@ -372,19 +396,23 @@ pub fn features_for_cubic_bezier(w1: &Point, w2: } else { // w1, w2, w3 must be collinear (w2 and w3 are known not to overlap) // w4 may or may not be co-linear: determine the features of the curve when reversed - let b1 = to_canonical_curve(w4, w3, w2, w1); + let b1 = to_canonical_curve(w4, w3, w2, w1); if let Some(b1) = b1 { // w4 is not co-linear with w2 and w3 - let x = b1.x(); - let y = b1.y(); + let x = b1.x(); + let y = b1.y(); // Reverse the curve coordinates for the features match features_from_canonical_point(x, y, w1, w2, w3, w4, accuracy) { - CurveFeatures::SingleInflectionPoint(t) => CurveFeatures::SingleInflectionPoint(1.0-t), - CurveFeatures::DoubleInflectionPoint(t1, t2) => CurveFeatures::DoubleInflectionPoint(1.0-t1, 1.0-t2), - CurveFeatures::Loop(t1, t2) => CurveFeatures::Loop(1.0-t1, 1.0-t2), - other => other + CurveFeatures::SingleInflectionPoint(t) => { + CurveFeatures::SingleInflectionPoint(1.0 - t) + } + CurveFeatures::DoubleInflectionPoint(t1, t2) => { + CurveFeatures::DoubleInflectionPoint(1.0 - t1, 1.0 - t2) + } + CurveFeatures::Loop(t1, t2) => CurveFeatures::Loop(1.0 - t1, 1.0 - t2), + other => other, } } else { // w1, w2 and w3 are co-linear and w2, w3 and w4 are co-linear, so all of w1, w2, w3 and w4 must be along the same line @@ -396,14 +424,16 @@ pub fn features_for_cubic_bezier(w1: &Point, w2: /// /// Discovers the 'character' of a particular bezier curve, returning a value indicating what kinds of features -/// it has (for example, whether it has a loop or a cusp) +/// it has (for example, whether it has a loop or a cusp) /// #[inline] pub fn characterize_curve(curve: &C) -> CurveCategory -where C::Point: Coordinate+Coordinate2D { +where + C::Point: Coordinate + Coordinate2D, +{ let start_point = curve.start_point(); - let (cp1, cp2) = curve.control_points(); - let end_point = curve.end_point(); + let (cp1, cp2) = curve.control_points(); + let end_point = curve.end_point(); characterize_cubic_bezier(&start_point, &cp1, &cp2, &end_point) } @@ -413,10 +443,12 @@ where C::Point: Coordinate+Coordinate2D { /// #[inline] pub fn features_for_curve(curve: &C, accuracy: f64) -> CurveFeatures -where C::Point: Coordinate+Coordinate2D { +where + C::Point: Coordinate + Coordinate2D, +{ let start_point = curve.start_point(); - let (cp1, cp2) = curve.control_points(); - let end_point = curve.end_point(); + let (cp1, cp2) = curve.control_points(); + let end_point = curve.end_point(); features_for_cubic_bezier(&start_point, &cp1, &cp2, &end_point, accuracy) } @@ -424,31 +456,32 @@ where C::Point: Coordinate+Coordinate2D { #[cfg(test)] mod test { use super::*; + use crate::Coord2; #[test] fn canonical_curve_coeffs_are_valid_1() { // Mapping the three control points via the affine transform should leave them at (0,0), (0,1) and (1,1) - let w1 = Coord2(1.0, 1.0); - let w2 = Coord2(2.0, 3.0); - let w3 = Coord2(5.0, 2.0); + let w1 = Coord2(1.0, 1.0); + let w2 = Coord2(2.0, 3.0); + let w3 = Coord2(5.0, 2.0); - let (a, b, c, d, e, f) = canonical_curve_transform(&w1, &w2, &w3).unwrap(); + let (a, b, c, d, e, f) = canonical_curve_transform(&w1, &w2, &w3).unwrap(); - let w1_new_x = w1.x()*a + w1.y()*b + c; - let w1_new_y = w1.x()*d + w1.y()*e + f; - let w2_new_x = w2.x()*a + w2.y()*b + c; - let w2_new_y = w2.x()*d + w2.y()*e + f; - let w3_new_x = w3.x()*a + w3.y()*b + c; - let w3_new_y = w3.x()*d + w3.y()*e + f; + let w1_new_x = w1.x() * a + w1.y() * b + c; + let w1_new_y = w1.x() * d + w1.y() * e + f; + let w2_new_x = w2.x() * a + w2.y() * b + c; + let w2_new_y = w2.x() * d + w2.y() * e + f; + let w3_new_x = w3.x() * a + w3.y() * b + c; + let w3_new_y = w3.x() * d + w3.y() * e + f; - assert!((w1_new_x-0.0).abs() < 0.0001); - assert!((w1_new_y-0.0).abs() < 0.0001); + assert!((w1_new_x - 0.0).abs() < 0.0001); + assert!((w1_new_y - 0.0).abs() < 0.0001); - assert!((w2_new_x-0.0).abs() < 0.0001); - assert!((w2_new_y-1.0).abs() < 0.0001); + assert!((w2_new_x - 0.0).abs() < 0.0001); + assert!((w2_new_y - 1.0).abs() < 0.0001); - assert!((w3_new_x-1.0).abs() < 0.0001); - assert!((w3_new_y-1.0).abs() < 0.0001); + assert!((w3_new_x - 1.0).abs() < 0.0001); + assert!((w3_new_y - 1.0).abs() < 0.0001); } #[test] @@ -471,9 +504,11 @@ mod test { match features_for_cubic_bezier(&w1, &w2, &w3, &w4, 0.01) { CurveFeatures::Loop(t1, t2) => { let curve = Curve::from_points(w1, (w2, w3), w4); - assert!(curve.point_at_pos(t1).is_near_to(&curve.point_at_pos(t2), 0.01)); - }, - _ => assert!(false) + assert!(curve + .point_at_pos(t1) + .is_near_to(&curve.point_at_pos(t2), 0.01)); + } + _ => assert!(false), } } @@ -564,10 +599,18 @@ mod test { let w3 = Coord2(73.0, 221.0); let w4 = Coord2(249.0, 136.0); - assert!(characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::SingleInflectionPoint); + assert!( + characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::SingleInflectionPoint + ); } - fn is_inflection_point(w1: &Point, w2: &Point, w3: &Point, w4: &Point, t: f64) -> bool { + fn is_inflection_point( + w1: &Point, + w2: &Point, + w3: &Point, + w4: &Point, + t: f64, + ) -> bool { let a = 3.0 * (w2.x() - w1.x()); let b = 3.0 * (w3.x() - w2.x()); let c = 3.0 * (w4.x() - w3.x()); @@ -580,12 +623,12 @@ mod test { let w = 2.0 * (e - d); let z = 2.0 * (f - e); - let bx1 = a * (1.0-t)*(1.0-t) + 2.0 * b * (1.0-t)*t + c * t*t; - let bx2 = u * (1.0-t) + v*t; - let by1 = d * (1.0-t)*(1.0-t) + 2.0 * e * (1.0-t)*t + f * t*t; - let by2 = w * (1.0-t) + z*t; + let bx1 = a * (1.0 - t) * (1.0 - t) + 2.0 * b * (1.0 - t) * t + c * t * t; + let bx2 = u * (1.0 - t) + v * t; + let by1 = d * (1.0 - t) * (1.0 - t) + 2.0 * e * (1.0 - t) * t + f * t * t; + let by2 = w * (1.0 - t) + z * t; - let curvature = bx1*by2 - by1*bx2; + let curvature = bx1 * by2 - by1 * bx2; curvature.abs() < 0.0001 } @@ -599,8 +642,8 @@ mod test { match features_for_cubic_bezier(&w1, &w2, &w3, &w4, 0.01) { CurveFeatures::SingleInflectionPoint(t) => { assert!(is_inflection_point(&w1, &w2, &w3, &w4, t)); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -631,7 +674,9 @@ mod test { let w3 = Coord2(108.0, 233.0); let w4 = Coord2(329.0, 129.0); - assert!(characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::DoubleInflectionPoint); + assert!( + characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::DoubleInflectionPoint + ); } #[test] @@ -645,8 +690,8 @@ mod test { CurveFeatures::DoubleInflectionPoint(t1, t2) => { assert!(is_inflection_point(&w1, &w2, &w3, &w4, t1)); assert!(is_inflection_point(&w1, &w2, &w3, &w4, t2)); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -717,7 +762,9 @@ mod test { let w3 = Coord2(72.0, 172.0); let w4 = Coord2(128.0, 162.0); - assert!(characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::DoubleInflectionPoint); + assert!( + characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::DoubleInflectionPoint + ); } #[test] @@ -731,8 +778,8 @@ mod test { CurveFeatures::DoubleInflectionPoint(t1, t2) => { assert!(is_inflection_point(&w1, &w2, &w3, &w4, t1)); assert!(is_inflection_point(&w1, &w2, &w3, &w4, t2)); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -743,6 +790,8 @@ mod test { let w3 = Coord2(290.0, 200.0); let w4 = Coord2(290.0, 95.0); - assert!(characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::SingleInflectionPoint); + assert!( + characterize_cubic_bezier(&w1, &w2, &w3, &w4) == CurveCategory::SingleInflectionPoint + ); } } diff --git a/src/bezier/curve.rs b/src/bezier/curve.rs index a9d51253..a1add4d4 100644 --- a/src/bezier/curve.rs +++ b/src/bezier/curve.rs @@ -1,35 +1,45 @@ -use super::fit::*; -use super::basis::*; -use super::solve::*; -use super::length::*; -use super::search::*; -use super::bounds::*; -use super::section::*; -use super::subdivide::*; -use super::characteristics::*; - -use crate::geo::*; +use super::basis::basis; +use super::bounds::{bounding_box4, find_extremities}; +use super::characteristics::{ + characterize_cubic_bezier, features_for_cubic_bezier, CurveCategory, CurveFeatures, +}; +use super::fit::fit_curve; +use super::length::curve_length; +use super::search::search_bounds4; +use super::section::CurveSection; +use super::solve::solve_curve_for_t; +use super::subdivide::subdivide4; + +use crate::geo::{BoundingBox, Coordinate, Coordinate2D, Geo, HasBoundingBox}; /// /// Trait implemented by bezier curves that can create new versions of themselves -/// +/// pub trait BezierCurveFactory: BezierCurve { /// /// Creates a new bezier curve of the same type from some points - /// - fn from_points(start: Self::Point, control_points: (Self::Point, Self::Point), end: Self::Point) -> Self; + /// + fn from_points( + start: Self::Point, + control_points: (Self::Point, Self::Point), + end: Self::Point, + ) -> Self; /// /// Creates a new bezier curve of this type from an equivalent curve of another type - /// + /// #[inline] - fn from_curve>(curve: &Curve) -> Self { - Self::from_points(curve.start_point(), curve.control_points(), curve.end_point()) + fn from_curve>(curve: &Curve) -> Self { + Self::from_points( + curve.start_point(), + curve.control_points(), + curve.end_point(), + ) } /// /// Generates a curve by attempting to find a best fit against a set of points - /// + /// #[inline] fn fit_from_points(points: &[Self::Point], max_error: f64) -> Option> { fit_curve(points, max_error) @@ -38,38 +48,44 @@ pub trait BezierCurveFactory: BezierCurve { /// /// Trait implemented by things representing a cubic bezier curve -/// -pub trait BezierCurve: Geo+Clone+Sized { +/// +pub trait BezierCurve: Geo + Clone + Sized { /// /// The start point of this curve - /// + /// fn start_point(&self) -> Self::Point; /// /// The end point of this curve - /// + /// fn end_point(&self) -> Self::Point; /// /// The control points in this curve - /// + /// fn control_points(&self) -> (Self::Point, Self::Point); /// /// Reverses the direction of this curve - /// - fn reverse>(self) -> Curve { + /// + fn reverse>(self) -> Curve { let (cp1, cp2) = self.control_points(); Curve::from_points(self.end_point(), (cp2, cp1), self.start_point()) } /// /// Given a value t from 0 to 1, returns a point on this curve - /// + /// #[inline] fn point_at_pos(&self, t: f64) -> Self::Point { let control_points = self.control_points(); - basis(t, self.start_point(), control_points.0, control_points.1, self.end_point()) + basis( + t, + self.start_point(), + control_points.0, + control_points.1, + self.end_point(), + ) } /// @@ -83,46 +99,58 @@ pub trait BezierCurve: Geo+Clone+Sized { /// /// Given a value t from 0 to 1, finds a point on this curve and subdivides it, returning the two resulting curves - /// + /// #[inline] - fn subdivide>(&self, t: f64) -> (Curve, Curve) { - let control_points = self.control_points(); - let (first_curve, second_curve) = subdivide4(t, self.start_point(), control_points.0, control_points.1, self.end_point()); - - (Curve::from_points(first_curve.0, (first_curve.1, first_curve.2), first_curve.3), - Curve::from_points(second_curve.0, (second_curve.1, second_curve.2), second_curve.3)) + fn subdivide>(&self, t: f64) -> (Curve, Curve) { + let control_points = self.control_points(); + let (first_curve, second_curve) = subdivide4( + t, + self.start_point(), + control_points.0, + control_points.1, + self.end_point(), + ); + + ( + Curve::from_points(first_curve.0, (first_curve.1, first_curve.2), first_curve.3), + Curve::from_points( + second_curve.0, + (second_curve.1, second_curve.2), + second_curve.3, + ), + ) } /// /// Computes the bounds of this bezier curve - /// - fn bounding_box>(&self) -> Bounds { + /// + fn bounding_box>(&self) -> Bounds { // Fetch the various points and the derivative of this curve - let start = self.start_point(); - let end = self.end_point(); - let (cp1, cp2) = self.control_points(); + let start = self.start_point(); + let end = self.end_point(); + let (cp1, cp2) = self.control_points(); bounding_box4(start, cp1, cp2, end) } - + /// /// Faster but less accurate bounding box for a curve - /// + /// /// This will produce a bounding box that contains the curve but which may be larger than necessary - /// + /// #[inline] - fn fast_bounding_box>(&self) -> Bounds { - let start = self.start_point(); - let end = self.end_point(); - let control_points = self.control_points(); + fn fast_bounding_box>(&self) -> Bounds { + let start = self.start_point(); + let end = self.end_point(); + let control_points = self.control_points(); - let min = Self::Point::from_smallest_components(start, end); - let min = Self::Point::from_smallest_components(min, control_points.0); - let min = Self::Point::from_smallest_components(min, control_points.1); + let min = Self::Point::from_smallest_components(start, end); + let min = Self::Point::from_smallest_components(min, control_points.0); + let min = Self::Point::from_smallest_components(min, control_points.1); - let max = Self::Point::from_biggest_components(start, end); - let max = Self::Point::from_biggest_components(max, control_points.0); - let max = Self::Point::from_biggest_components(max, control_points.1); + let max = Self::Point::from_biggest_components(start, end); + let max = Self::Point::from_biggest_components(max, control_points.0); + let max = Self::Point::from_biggest_components(max, control_points.1); Bounds::from_min_max(min, max) } @@ -130,12 +158,16 @@ pub trait BezierCurve: Geo+Clone+Sized { /// /// Given a function that determines if a searched-for point is within a bounding box, searches the /// curve for the t values for the corresponding points - /// - fn search_with_bounds bool>(&self, max_error: f64, match_fn: MatchFn) -> Vec { + /// + fn search_with_bounds bool>( + &self, + max_error: f64, + match_fn: MatchFn, + ) -> Vec { // Fetch the various points and the derivative of this curve - let start = self.start_point(); - let end = self.end_point(); - let (cp1, cp2) = self.control_points(); + let start = self.start_point(); + let end = self.end_point(); + let (cp1, cp2) = self.control_points(); // Perform the search search_bounds4(max_error, start, cp1, cp2, end, match_fn) @@ -143,19 +175,19 @@ pub trait BezierCurve: Geo+Clone+Sized { /// /// Finds the t values where this curve has extremities - /// + /// #[inline] fn find_extremities(&self) -> Vec { - let start = self.start_point(); - let end = self.end_point(); - let (cp1, cp2) = self.control_points(); + let start = self.start_point(); + let end = self.end_point(); + let (cp1, cp2) = self.control_points(); find_extremities(start, cp1, cp2, end) } /// /// Attempts to estimate the length of this curve - /// + /// fn estimate_length(&self) -> f64 { curve_length(self, 0.01) } @@ -163,20 +195,20 @@ pub trait BezierCurve: Geo+Clone+Sized { /// /// Create a section from this curve. Consider calling `subsection` for curves /// that are already `CurveSections`. - /// - fn section<'a>(&'a self, t_min: f64, t_max: f64) -> CurveSection<'a, Self> { + /// + fn section(&self, t_min: f64, t_max: f64) -> CurveSection { CurveSection::new(self, t_min, t_max) } } /// /// Represents a Bezier curve -/// +/// #[derive(Clone, Copy, Debug, PartialEq)] pub struct Curve { - pub start_point: Coord, - pub end_point: Coord, - pub control_points: (Coord, Coord) + pub start_point: Coord, + pub end_point: Coord, + pub control_points: (Coord, Coord), } impl Geo for Curve { @@ -184,11 +216,15 @@ impl Geo for Curve { } impl BezierCurveFactory for Curve { - fn from_points(start: Coord, (control_point1, control_point2): (Coord, Coord), end: Coord) -> Self { - Curve { - start_point: start, + fn from_points( + start: Coord, + (control_point1, control_point2): (Coord, Coord), + end: Coord, + ) -> Self { + Self { + start_point: start, control_points: (control_point1, control_point2), - end_point: end + end_point: end, } } } @@ -213,8 +249,8 @@ impl BezierCurve for Curve { impl HasBoundingBox for Curve { /// /// Computes the bounds of this bezier curve - /// - fn get_bounding_box>(&self) -> Bounds { + /// + fn get_bounding_box>(&self) -> Bounds { self.bounding_box() } } @@ -235,12 +271,14 @@ pub trait BezierCurve2D: BezierCurve { } impl BezierCurve2D for T -where T::Point: Coordinate+Coordinate2D { +where + T::Point: Coordinate + Coordinate2D, +{ #[inline] fn characteristics(&self) -> CurveCategory { let start_point = self.start_point(); - let end_point = self.end_point(); - let (cp1, cp2) = self.control_points(); + let end_point = self.end_point(); + let (cp1, cp2) = self.control_points(); characterize_cubic_bezier(&start_point, &cp1, &cp2, &end_point) } @@ -248,8 +286,8 @@ where T::Point: Coordinate+Coordinate2D { #[inline] fn features(&self, accuracy: f64) -> CurveFeatures { let start_point = self.start_point(); - let end_point = self.end_point(); - let (cp1, cp2) = self.control_points(); + let end_point = self.end_point(); + let (cp1, cp2) = self.control_points(); features_for_cubic_bezier(&start_point, &cp1, &cp2, &end_point, accuracy) } diff --git a/src/bezier/deform.rs b/src/bezier/deform.rs index 1b3d82af..269315fd 100644 --- a/src/bezier/deform.rs +++ b/src/bezier/deform.rs @@ -1,35 +1,43 @@ -use super::curve::*; -use super::super::geo::*; +use super::super::geo::Coordinate; +use super::curve::{BezierCurve, BezierCurveFactory}; /// /// Moves the point at 't' on the curve by the offset vector -/// +/// /// This recomputes the control points such that the point at t on the original curve /// is moved by the vector specified by `offset`. -/// -pub fn move_point, CurveOut: BezierCurveFactory>(curve: &CurveIn, t: f64, offset: &P) -> CurveOut { +/// +pub fn move_point< + P: Coordinate, + CurveIn: BezierCurve, + CurveOut: BezierCurveFactory, +>( + curve: &CurveIn, + t: f64, + offset: &P, +) -> CurveOut { // Fetch the points from the curve - let w1 = curve.start_point(); - let w4 = curve.end_point(); - let (w2, w3) = curve.control_points(); + let w1 = curve.start_point(); + let w4 = curve.end_point(); + let (w2, w3) = curve.control_points(); - let one_minus_t = 1.0-t; - let one_minus_t_cubed = one_minus_t*one_minus_t*one_minus_t; - let t_cubed = t*t*t; + let one_minus_t = 1.0 - t; + let one_minus_t_cubed = one_minus_t * one_minus_t * one_minus_t; + let t_cubed = t * t * t; // Point 'C' is fixed for the transformation and is along the line w1-w4 let u = one_minus_t_cubed / (t_cubed + one_minus_t_cubed); - let c = w1*u + w4*(1.0-u); + let c = w1 * u + w4 * (1.0 - u); // Construct the de Casteljau points for the point we're moving - let wn1 = w1*(1.0-t) + w2*t; - let wn2 = w2*(1.0-t) + w3*t; - let wn3 = w3*(1.0-t) + w4*t; + let wn1 = w1 * (1.0 - t) + w2 * t; + let wn2 = w2 * (1.0 - t) + w3 * t; + let wn3 = w3 * (1.0 - t) + w4 * t; - let wnn1 = wn1*(1.0-t) + wn2*t; - let wnn2 = wn2*(1.0-t) + wn3*t; + let wnn1 = wn1 * (1.0 - t) + wn2 * t; + let wnn2 = wn2 * (1.0 - t) + wn3 * t; - let p = wnn1*(1.0-t) + wnn2*t; + let p = wnn1 * (1.0 - t) + wnn2 * t; // Translating wnn1 and wnn2 by the offset will give us a new p that is also translated by the offset let pb = p + *offset; @@ -39,20 +47,20 @@ pub fn move_point, CurveOut: Bezier // The line c->pb->wn2b has the same ratios as the line c->p->wn2, so we can compute wn2b // There's a trick to calculating this for cubic curves (which is handy as it means this will work with straight lines as well as curves) - let ratio = ((t_cubed+one_minus_t_cubed)/(t_cubed + one_minus_t_cubed-1.0)).abs(); - let wn2b = ((pb-c)*ratio) + pb; + let ratio = ((t_cubed + one_minus_t_cubed) / (t_cubed + one_minus_t_cubed - 1.0)).abs(); + let wn2b = ((pb - c) * ratio) + pb; // We can now calculate wn1b and wn3b - let inverse_t = 1.0/t; - let inverse_tminus1 = 1.0/(t-1.0); - - let wn1b = (wn2b*t - wnn1b)*inverse_tminus1; - let wn3b = (wn2b*-1.0 + wn2b*t + wnn2b)*inverse_t; + let inverse_t = 1.0 / t; + let inverse_tminus1 = 1.0 / (t - 1.0); + + let wn1b = (wn2b * t - wnn1b) * inverse_tminus1; + let wn3b = (wn2b * -1.0 + wn2b * t + wnn2b) * inverse_t; // ... and the new control points - let w2b = (w1*-1.0 + w1*t + wn1b)*inverse_t; - let w3b = (w4*t-wn3b)*inverse_tminus1; + let w2b = (w1 * -1.0 + w1 * t + wn1b) * inverse_t; + let w3b = (w4 * t - wn3b) * inverse_tminus1; // Use the values to construct the curve with the moved point CurveOut::from_points(w1, (w2b, w3b), w4) -} \ No newline at end of file +} diff --git a/src/bezier/derivative.rs b/src/bezier/derivative.rs index dfc4e04e..0ae86d88 100644 --- a/src/bezier/derivative.rs +++ b/src/bezier/derivative.rs @@ -1,22 +1,27 @@ -use super::super::geo::*; +use super::super::geo::Coordinate; /// /// Returns the 1st derivative of a cubic bezier curve -/// -pub fn derivative4(w1: Point, w2: Point, w3: Point, w4: Point) -> (Point, Point, Point) { - ((w2-w1)*3.0, (w3-w2)*3.0, (w4-w3)*3.0) +/// +pub fn derivative4( + w1: Point, + w2: Point, + w3: Point, + w4: Point, +) -> (Point, Point, Point) { + ((w2 - w1) * 3.0, (w3 - w2) * 3.0, (w4 - w3) * 3.0) } /// /// Returns the 1st derivative of a quadratic bezier curve (or the 2nd derivative of a cubic curve) -/// +/// pub fn derivative3(wn1: Point, wn2: Point, wn3: Point) -> (Point, Point) { - ((wn2-wn1)*2.0, (wn3-wn2)*2.0) + ((wn2 - wn1) * 2.0, (wn3 - wn2) * 2.0) } /// /// Returns the 3rd derivative of a cubic bezier curve (2nd of a quadratic) -/// +/// pub fn derivative2(wnn1: Point, wnn2: Point) -> Point { - wnn2-wnn1 + wnn2 - wnn1 } diff --git a/src/bezier/distort.rs b/src/bezier/distort.rs index 46bdc577..b97244a5 100644 --- a/src/bezier/distort.rs +++ b/src/bezier/distort.rs @@ -1,28 +1,35 @@ -use super::fit::*; -use super::walk::*; -use super::path::*; -use super::curve::*; -use super::normal::*; -use crate::geo::*; +use super::curve::{BezierCurve, BezierCurveFactory, Curve}; +use super::fit::fit_curve; +use super::normal::Normalize; +use super::path::{BezierPath, BezierPathFactory}; +use super::walk::walk_curve_evenly; +use crate::geo::Coordinate2D; use std::iter; /// /// Distorts a curve using an arbitrary function /// -pub fn distort_curve(curve: &CurveIn, distort_fn: DistortFn, step_len: f64, max_error: f64) -> Option> +pub fn distort_curve( + curve: &CurveIn, + distort_fn: DistortFn, + step_len: f64, + max_error: f64, +) -> Option> where -CurveIn: BezierCurve, -CurveIn::Point: Normalize+Coordinate2D, -CurveOut: BezierCurveFactory, -DistortFn: Fn(CurveIn::Point, f64) -> CurveOut::Point { + CurveIn: BezierCurve, + CurveIn::Point: Normalize + Coordinate2D, + CurveOut: BezierCurveFactory, + DistortFn: Fn(CurveIn::Point, f64) -> CurveOut::Point, +{ // Walk the curve at roughly step_len increments - let sections = walk_curve_evenly(curve, step_len, step_len / 4.0); + let sections = walk_curve_evenly(curve, step_len, step_len / 4.0); // Generate the points to fit to using the distortion function - let fit_points = sections.map(|section| { - let (t, _) = section.original_curve_t_values(); - let pos = curve.point_at_pos(t); + let fit_points = sections + .map(|section| { + let (t, _) = section.original_curve_t_values(); + let pos = curve.point_at_pos(t); (pos, t) }) @@ -37,28 +44,39 @@ DistortFn: Fn(CurveIn::Point, f64) -> CurveOut::Point { /// /// Distorts a path using an arbitrary function /// -pub fn distort_path(path: &PathIn, distort_fn: DistortFn, step_len: f64, max_error: f64) -> Option +pub fn distort_path( + path: &PathIn, + distort_fn: DistortFn, + step_len: f64, + max_error: f64, +) -> Option where -PathIn: BezierPath, -PathOut: BezierPathFactory, -DistortFn: Fn(PathIn::Point, &Curve, f64) -> PathOut::Point { + PathIn: BezierPath, + PathOut: BezierPathFactory, + DistortFn: Fn(PathIn::Point, &Curve, f64) -> PathOut::Point, +{ // The initial point is derived from the first curve - let start_point = path.start_point(); - let mut path_points = path.points(); - let mut current_point = path_points.next()?; - let mut current_curve = Curve::from_points(start_point, (current_point.0, current_point.1), current_point.2); - let start_point = distort_fn(start_point, ¤t_curve, 0.0); + let start_point = path.start_point(); + let mut path_points = path.points(); + let mut current_point = path_points.next()?; + let mut current_curve = Curve::from_points( + start_point, + (current_point.0, current_point.1), + current_point.2, + ); + let start_point = distort_fn(start_point, ¤t_curve, 0.0); // Process the remaining points to generate the new path - let mut new_points = vec![]; + let mut new_points = vec![]; loop { // Distort the current curve - let sections = walk_curve_evenly(¤t_curve, step_len, step_len / 4.0); + let sections = walk_curve_evenly(¤t_curve, step_len, step_len / 4.0); - let fit_points = sections.map(|section| { - let (t, _) = section.original_curve_t_values(); - let pos = current_curve.point_at_pos(t); + let fit_points = sections + .map(|section| { + let (t, _) = section.original_curve_t_values(); + let pos = current_curve.point_at_pos(t); (pos, t) }) @@ -67,16 +85,24 @@ DistortFn: Fn(PathIn::Point, &Curve, f64) -> PathOut::Point { .collect::>(); // Fit the points to generate the new curves - let new_curves = fit_curve::>(&fit_points, max_error)?; + let new_curves = fit_curve::>(&fit_points, max_error)?; new_points.extend(new_curves.into_iter().map(|curve| { let (cp1, cp2) = curve.control_points(); (cp1, cp2, curve.end_point()) })); // Move to the next curve (stopping once we reach the end of the list of the points) - let next_start_point = current_curve.end_point(); - current_point = if let Some(point) = path_points.next() { point } else { break; }; - current_curve = Curve::from_points(next_start_point, (current_point.0, current_point.1), current_point.2); + let next_start_point = current_curve.end_point(); + current_point = if let Some(point) = path_points.next() { + point + } else { + break; + }; + current_curve = Curve::from_points( + next_start_point, + (current_point.0, current_point.1), + current_point.2, + ); } // Create the new path from the result diff --git a/src/bezier/fit.rs b/src/bezier/fit.rs index 5a3b37e1..6a0c2485 100644 --- a/src/bezier/fit.rs +++ b/src/bezier/fit.rs @@ -1,6 +1,6 @@ -use super::curve::*; -use super::basis::*; -use crate::geo::*; +use super::basis::{de_casteljau2, de_casteljau3}; +use super::curve::{BezierCurve, BezierCurveFactory}; +use crate::geo::Coordinate; /// Maximum number of iterations to perform when trying to improve the curve fit const MAX_ITERATIONS: usize = 4; @@ -13,17 +13,20 @@ const MAX_POINTS_TO_FIT: usize = 100; /// /// Creates a bezier curve that fits a set of points with a particular error -/// +/// /// Algorithm from Philip J. Schneider, Graphics Gems -/// +/// /// There are a few modifications from the original algorithm: -/// -/// * The 'small' error used to determine if we should use Newton-Raphson is now +/// +/// * The 'small' error used to determine if we should use Newton-Raphson is now /// just a multiplier of the max error /// * We only try to fit a certain number of points at once as the algorithm runs /// in quadratic time otherwise -/// -pub fn fit_curve(points: &[Curve::Point], max_error: f64) -> Option> { +/// +pub fn fit_curve( + points: &[Curve::Point], + max_error: f64, +) -> Option> { // Need at least 2 points to fit anything if points.len() < 2 { // Insufficient points for this curve @@ -32,28 +35,30 @@ pub fn fit_curve(points: &[Curve::Point], let mut curves = vec![]; // Divide up the points into blocks containing MAX_POINTS_TO_FIT items - let num_blocks = ((points.len()-1) / MAX_POINTS_TO_FIT)+1; + let num_blocks = ((points.len() - 1) / MAX_POINTS_TO_FIT) + 1; for point_block in 0..num_blocks { // Pick the set of points that will be in this block - let start_point = point_block * MAX_POINTS_TO_FIT; - let mut num_points = MAX_POINTS_TO_FIT; + let start_point = point_block * MAX_POINTS_TO_FIT; + let mut num_points = MAX_POINTS_TO_FIT; - if start_point+num_points > points.len() { + if start_point + num_points > points.len() { num_points = points.len() - start_point; } // Edge case: one point outside of a block (we ignore these blocks) - if num_points < 2 { continue; } + if num_points < 2 { + continue; + } // Need the start and end tangents so we know how the curve continues - let block_points = &points[start_point..start_point+num_points]; + let block_points = &points[start_point..start_point + num_points]; - let start_tangent = start_tangent(block_points); - let end_tangent = if start_point+num_points < points.len() { - end_tangent(&points[start_point..start_point+num_points+1]) - } else { - end_tangent(block_points) + let start_tangent = start_tangent(block_points); + let end_tangent = if start_point + num_points < points.len() { + end_tangent(&points[start_point..start_point + num_points + 1]) + } else { + end_tangent(block_points) }; let fit = fit_curve_cubic(block_points, &start_tangent, &end_tangent, max_error); @@ -82,35 +87,40 @@ pub fn fit_curve(points: &[Curve::Point], /// The algorithm here is to attempt to fit a single bezier curve against the points, estimate the point which has the highest error, and if too high /// subdivide at that point and try again. /// -pub fn fit_curve_cubic(points: &[Curve::Point], start_tangent: &Curve::Point, end_tangent: &Curve::Point, max_error: f64) -> Vec { +pub fn fit_curve_cubic( + points: &[Curve::Point], + start_tangent: &Curve::Point, + end_tangent: &Curve::Point, + max_error: f64, +) -> Vec { if points.len() <= 2 { // 2 points is a line (less than 2 points is an error here) fit_line(&points[0], &points[1]) } else { // Perform an initial estimate of the 't' values corresponding to the chords of the curve - let mut chords = chords_for_points(points); + let mut chords = chords_for_points(points); // Use the least-squares method to fit against the initial set of chords - let mut curve = generate_bezier(points, &chords, start_tangent, end_tangent); + let mut curve = generate_bezier(points, &chords, start_tangent, end_tangent); // Reparameterise the chords (which will probably be quite a bad estimate initially) - chords = reparameterize(points, &chords, &curve); - curve = generate_bezier(points, &chords, start_tangent, end_tangent); + chords = reparameterize(points, &chords, &curve); + curve = generate_bezier(points, &chords, start_tangent, end_tangent); // Estimate the error after the reparameterization - let (mut error, mut split_pos) = max_error_for_curve(points, &chords, &curve); + let (mut error, mut split_pos) = max_error_for_curve(points, &chords, &curve); // Try iterating to improve the fit if we're not too far out - if error > max_error && error < max_error*FIT_ATTEMPT_RATIO { + if error > max_error && error < max_error * FIT_ATTEMPT_RATIO { for _iteration in 1..MAX_ITERATIONS { // Recompute the chords and the curve chords = reparameterize(points, &chords, &curve); - curve = generate_bezier(points, &chords, start_tangent, end_tangent); + curve = generate_bezier(points, &chords, start_tangent, end_tangent); // Recompute the error let (new_error, new_split_pos) = max_error_for_curve(points, &chords, &curve); - error = new_error; - split_pos = new_split_pos; + error = new_error; + split_pos = new_split_pos; if error <= max_error { break; @@ -123,11 +133,25 @@ pub fn fit_curve_cubic(points: &[Curve::P vec![curve] } else { // If error still too large, split the points and create two curves - let center_tangent = tangent_between(&points[split_pos-1], &points[split_pos], &points[split_pos+1]); + let center_tangent = tangent_between( + &points[split_pos - 1], + &points[split_pos], + &points[split_pos + 1], + ); // Fit the two sides - let lhs = fit_curve_cubic(&points[0..split_pos+1], start_tangent, ¢er_tangent, max_error); - let rhs = fit_curve_cubic(&points[split_pos..points.len()], &(center_tangent*-1.0), end_tangent, max_error); + let lhs = fit_curve_cubic( + &points[0..split_pos + 1], + start_tangent, + ¢er_tangent, + max_error, + ); + let rhs = fit_curve_cubic( + &points[split_pos..points.len()], + &(center_tangent * -1.0), + end_tangent, + max_error, + ); // Collect the result lhs.into_iter().chain(rhs.into_iter()).collect() @@ -137,35 +161,35 @@ pub fn fit_curve_cubic(points: &[Curve::P /// /// Creates a curve representing a line between two points -/// +/// fn fit_line(p1: &Curve::Point, p2: &Curve::Point) -> Vec { // Any bezier curve where the control points line up forms a straight line; we use points around 1/3rd of the way along in our generation here - let direction = *p2 - *p1; - let cp1 = *p1 + (direction * 0.33); - let cp2 = *p1 + (direction * 0.66); + let direction = *p2 - *p1; + let cp1 = *p1 + (direction * 0.33); + let cp2 = *p1 + (direction * 0.66); vec![Curve::from_points(*p1, (cp1, cp2), *p2)] } /// /// Chord-length parameterizes a set of points -/// +/// /// This is an estimate of the 't' value for these points on the final curve. -/// +/// fn chords_for_points(points: &[Point]) -> Vec { - let mut distances = vec![]; - let mut total_distance = 0.0; + let mut distances = vec![]; + let mut total_distance = 0.0; // Compute the distances for each point distances.push(total_distance); - for p in 1..points.len() { - total_distance += points[p-1].distance_to(&points[p]); + for ps in points.windows(2) { + total_distance += ps[0].distance_to(&ps[1]); distances.push(total_distance); } // Normalize to the range 0..1 - for p in 0..points.len() { - distances[p] /= total_distance; + for distance in &mut distances { + *distance /= total_distance; } distances @@ -173,23 +197,31 @@ fn chords_for_points(points: &[Point]) -> Vec { /// /// Generates a bezier curve using the least-squares method -/// -fn generate_bezier(points: &[Curve::Point], chords: &[f64], start_tangent: &Curve::Point, end_tangent: &Curve::Point) -> Curve { +/// +fn generate_bezier( + points: &[Curve::Point], + chords: &[f64], + start_tangent: &Curve::Point, + end_tangent: &Curve::Point, +) -> Curve { // Precompute the RHS as 'a' - let a: Vec<_> = chords.iter().map(|chord| { - let inverse_chord = 1.0 - chord; + let a: Vec<_> = chords + .iter() + .map(|chord| { + let inverse_chord = 1.0 - chord; - let b1 = 3.0 * chord * (inverse_chord*inverse_chord); - let b2 = 3.0 * chord * chord * inverse_chord; + let b1 = 3.0 * chord * (inverse_chord * inverse_chord); + let b2 = 3.0 * chord * chord * inverse_chord; - (*start_tangent*b1, *end_tangent*b2) - }).collect(); + (*start_tangent * b1, *end_tangent * b2) + }) + .collect(); // Create the 'C' and 'X' matrices - let mut c = [[ 0.0, 0.0 ], [ 0.0, 0.0 ]]; + let mut c = [[0.0, 0.0], [0.0, 0.0]]; let mut x = [0.0, 0.0]; - let last_point = points[points.len()-1]; + let last_point = points[points.len() - 1]; for point in 0..points.len() { c[0][0] += a[point].0.dot(&a[point].0); @@ -197,140 +229,175 @@ fn generate_bezier(points: &[Curve::Point], chords: & c[1][0] = c[0][1]; c[1][1] += a[point].1.dot(&a[point].1); - let chord = chords[point]; - let inverse_chord = 1.0 - chord; - let b0 = inverse_chord*inverse_chord*inverse_chord; - let b1 = 3.0 * chord * (inverse_chord*inverse_chord); - let b2 = 3.0 * chord * chord * inverse_chord; - let b3 = chord*chord*chord; + let chord = chords[point]; + let inverse_chord = 1.0 - chord; + let b0 = inverse_chord * inverse_chord * inverse_chord; + let b1 = 3.0 * chord * (inverse_chord * inverse_chord); + let b2 = 3.0 * chord * chord * inverse_chord; + let b3 = chord * chord * chord; - let tmp = points[point] - - ((points[0] * b0) + (points[0] * b1) + (last_point*b2) + (last_point*b3)); + let tmp = points[point] + - ((points[0] * b0) + (points[0] * b1) + (last_point * b2) + (last_point * b3)); x[0] += a[point].0.dot(&tmp); x[1] += a[point].1.dot(&tmp); } // Compute their determinants - let det_c0_c1 = c[0][0]*c[1][1] - c[1][0]*c[0][1]; - let det_c0_x = c[0][0]*x[1] - c[1][0]*x[0]; - let det_x_c1 = x[0]*c[1][1] - x[1]*c[0][1]; + let det_c0_c1 = c[0][0] * c[1][1] - c[1][0] * c[0][1]; + let det_c0_x = c[0][0] * x[1] - c[1][0] * x[0]; + let det_x_c1 = x[0] * c[1][1] - x[1] * c[0][1]; // Derive alpha values - let alpha_l = if f64::abs(det_c0_c1)<1.0e-4 { 0.0 } else { det_x_c1/det_c0_c1 }; - let alpha_r = if f64::abs(det_c0_c1)<1.0e-4 { 0.0 } else { det_c0_x/det_c0_c1 }; + let alpha_l = if f64::abs(det_c0_c1) < 1.0e-4 { + 0.0 + } else { + det_x_c1 / det_c0_c1 + }; + let alpha_r = if f64::abs(det_c0_c1) < 1.0e-4 { + 0.0 + } else { + det_c0_x / det_c0_c1 + }; // Use the Wu/Barsky heuristic if alpha-negative - let seg_length = points[0].distance_to(&last_point); - let epsilon = 1.0e-6*seg_length; + let seg_length = points[0].distance_to(&last_point); + let epsilon = 1.0e-6 * seg_length; if alpha_l < epsilon || alpha_r < epsilon { // Much less accurate means of estimating a curve - let dist = seg_length/3.0; - Curve::from_points(points[0], (points[0]+(*start_tangent*dist), last_point+(*end_tangent*dist)), last_point) + let dist = seg_length / 3.0; + Curve::from_points( + points[0], + ( + points[0] + (*start_tangent * dist), + last_point + (*end_tangent * dist), + ), + last_point, + ) } else { // The control points are positioned an alpha distance out along the tangent vectors - Curve::from_points(points[0], (points[0]+(*start_tangent*alpha_l), last_point+(*end_tangent*alpha_r)), last_point) + Curve::from_points( + points[0], + ( + points[0] + (*start_tangent * alpha_l), + last_point + (*end_tangent * alpha_r), + ), + last_point, + ) } } /// /// Computes the maximum error for a curve fit against a given set of points -/// +/// /// The chords indicate the estimated t-values corresponding to the points. -/// +/// /// Returns the maximum error and the index of the point with that error. -/// -fn max_error_for_curve(points: &[Curve::Point], chords: &[f64], curve: &Curve) -> (f64, usize) { - let errors = points.iter().zip(chords.iter()) - .map(|(point, chord)| { - // Get the actual position of this point and the offset - let actual = curve.point_at_pos(*chord); - let offset = *point - actual; - - // The dot product of an item with itself is the square of the distance - offset.dot(&offset) - }); - +/// +fn max_error_for_curve( + points: &[Curve::Point], + chords: &[f64], + curve: &Curve, +) -> (f64, usize) { + let errors = points.iter().zip(chords.iter()).map(|(point, chord)| { + // Get the actual position of this point and the offset + let actual = curve.point_at_pos(*chord); + let offset = *point - actual; + + // The dot product of an item with itself is the square of the distance + offset.dot(&offset) + }); + // Search the errors for the biggest one let mut biggest_error_squared = 0.0; - let mut biggest_error_offset = 0; + let mut biggest_error_offset = 0; for (current_point, error_squared) in errors.enumerate() { if error_squared > biggest_error_squared { biggest_error_squared = error_squared; - biggest_error_offset = current_point; + biggest_error_offset = current_point; } } - - // Indicate the biggest error and where it was + + // Indicate the biggest error and where it was (f64::sqrt(biggest_error_squared), biggest_error_offset) } /// /// Returns the unit tangent at the start of the curve -/// +/// fn start_tangent(points: &[Point]) -> Point { - (points[1]-points[0]).to_unit_vector() + (points[1] - points[0]).to_unit_vector() } /// /// Returns the unit tangent at the end of the curve -/// +/// fn end_tangent(points: &[Point]) -> Point { - (points[points.len()-2]-points[points.len()-1]).to_unit_vector() + (points[points.len() - 2] - points[points.len() - 1]).to_unit_vector() } /// -/// Estimates the tangent between three points +/// Estimates the tangent between three points /// fn tangent_between(p1: &Point, p2: &Point, p3: &Point) -> Point { let v1 = *p1 - *p2; let v2 = *p2 - *p3; - ((v1+v2)*0.5).to_unit_vector() + ((v1 + v2) * 0.5).to_unit_vector() } /// /// Applies the newton-raphson method in order to improve the t values of a curve -/// -fn reparameterize(points: &[Curve::Point], chords: &[f64], curve: &Curve) -> Vec { - points.iter().zip(chords.iter()) +/// +fn reparameterize( + points: &[Curve::Point], + chords: &[f64], + curve: &Curve, +) -> Vec { + points + .iter() + .zip(chords.iter()) .map(|(point, chord)| newton_raphson_root_find(curve, point, *chord)) .collect() } /// /// Uses newton-raphson to find a root for a curve -/// -fn newton_raphson_root_find(curve: &Curve, point: &Curve::Point, estimated_t: f64) -> f64 { - let start = curve.start_point(); - let end = curve.end_point(); - let (cp1, cp2) = curve.control_points(); +/// +fn newton_raphson_root_find( + curve: &Curve, + point: &Curve::Point, + estimated_t: f64, +) -> f64 { + let start = curve.start_point(); + let end = curve.end_point(); + let (cp1, cp2) = curve.control_points(); // Compute Q(t) (where Q is our curve) - let qt = curve.point_at_pos(estimated_t); - + let qt = curve.point_at_pos(estimated_t); + // Generate control vertices - let qn1 = (cp1-start)*3.0; - let qn2 = (cp2-cp1)*3.0; - let qn3 = (end-cp2)*3.0; + let qn1 = (cp1 - start) * 3.0; + let qn2 = (cp2 - cp1) * 3.0; + let qn3 = (end - cp2) * 3.0; - let qnn1 = (qn2-qn1)*2.0; - let qnn2 = (qn3-qn2)*2.0; + let qnn1 = (qn2 - qn1) * 2.0; + let qnn2 = (qn3 - qn2) * 2.0; // Compute Q'(t) and Q''(t) - let qnt = de_casteljau3(estimated_t, qn1, qn2, qn3); - let qnnt = de_casteljau2(estimated_t, qnn1, qnn2); + let qnt = de_casteljau3(estimated_t, qn1, qn2, qn3); + let qnnt = de_casteljau2(estimated_t, qnn1, qnn2); // Compute f(u)/f'(u) - let numerator = (qt-*point).dot(&qnt); - let denominator = qnt.dot(&qnt) + (qt-*point).dot(&qnnt); + let numerator = (qt - *point).dot(&qnt); + let denominator = qnt.dot(&qnt) + (qt - *point).dot(&qnnt); // u = u - f(u)/f'(u) if denominator == 0.0 { estimated_t } else { - estimated_t - (numerator/denominator) + estimated_t - (numerator / denominator) } } diff --git a/src/bezier/intersection/curve_curve_clip.rs b/src/bezier/intersection/curve_curve_clip.rs index cac23e0b..069e0c1a 100644 --- a/src/bezier/intersection/curve_curve_clip.rs +++ b/src/bezier/intersection/curve_curve_clip.rs @@ -1,25 +1,25 @@ -use super::fat_line::*; -use super::curve_line::*; -use crate::geo::*; -use crate::bezier::*; -use crate::bezier::solve::*; +use super::curve_line::curve_intersects_ray; +use super::fat_line::FatLine; +use crate::bezier::solve::{solve_curve_for_t, CLOSE_ENOUGH}; +use crate::bezier::{overlapping_region, BezierCurve, CurveSection}; +use crate::geo::{BoundingBox, Bounds, Coordinate, Coordinate2D}; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; /// /// Determines the length of a curve's hull as a sum of squares -/// -fn curve_hull_length_sq<'a, C: BezierCurve>(curve: &CurveSection<'a, C>) -> f64 { +/// +fn curve_hull_length_sq(curve: &CurveSection) -> f64 { if curve.is_tiny() { 0.0 } else { - let start = curve.start_point(); - let end = curve.end_point(); - let (cp1, cp2) = curve.control_points(); + let start = curve.start_point(); + let end = curve.end_point(); + let (cp1, cp2) = curve.control_points(); - let offset1 = cp1-start; - let offset2 = cp2-cp1; - let offset3 = cp2-end; + let offset1 = cp1 - start; + let offset2 = cp2 - cp1; + let offset3 = cp2 - end; offset1.dot(&offset1) + offset2.dot(&offset2) + offset3.dot(&offset3) } @@ -28,14 +28,20 @@ fn curve_hull_length_sq<'a, C: BezierCurve>(curve: &CurveSection<'a, C>) -> f64 /// /// Given a line representing a linear section of a curve, finds the intersection with a curved section and returns the t values /// -fn intersections_with_linear_section<'a, C: BezierCurve>(linear_section: &CurveSection<'a, C>, curved_section: &CurveSection<'a, C>) -> SmallVec<[(f64, f64); 4]> -where C::Point: 'a+Coordinate2D { +fn intersections_with_linear_section<'a, C: BezierCurve>( + linear_section: &CurveSection<'a, C>, + curved_section: &CurveSection<'a, C>, +) -> SmallVec<[(f64, f64); 4]> +where + C::Point: 'a + Coordinate2D, +{ // Treat the linear section as a ray based on the start and the end point and find where on the curved section the ray intersects the linear section - let ray = (linear_section.start_point(), linear_section.end_point()); - let ray_intersections = curve_intersects_ray(curved_section, &ray); + let ray = (linear_section.start_point(), linear_section.end_point()); + let ray_intersections = curve_intersects_ray(curved_section, &ray); // Attempt to find where the 't' value is for each ray intersection against the linear section - let curve_intersections = ray_intersections.iter() + let curve_intersections = ray_intersections + .iter() .filter_map(|(curved_t, _ray_t, pos)| { let linear_t = solve_curve_for_t(linear_section, pos); @@ -44,19 +50,23 @@ where C::Point: 'a+Coordinate2D { .collect::>(); // Rarely: the linear section might be very short and the solver might miss that it's essentially a point - if curve_intersections.len() == 0 && ray_intersections.len() != 0 { + if curve_intersections.is_empty() && !ray_intersections.is_empty() { // If the linear section seems short - if linear_section.point_at_pos(0.0).is_near_to(&linear_section.point_at_pos(1.0), 0.1) { - let midpoint = linear_section.point_at_pos(0.5); - let curve_intersections = ray_intersections.iter() - .filter_map(|(curved_t, _ray_t, pos)| { - if pos.is_near_to(&midpoint, CLOSE_ENOUGH) { - Some((0.5, *curved_t)) - } else { - None - } - }) - .collect::>(); + if linear_section + .point_at_pos(0.0) + .is_near_to(&linear_section.point_at_pos(1.0), 0.1) + { + let midpoint = linear_section.point_at_pos(0.5); + let curve_intersections = ray_intersections + .iter() + .filter_map(|(curved_t, _ray_t, pos)| { + if pos.is_near_to(&midpoint, CLOSE_ENOUGH) { + Some((0.5, *curved_t)) + } else { + None + } + }) + .collect::>(); return curve_intersections; } @@ -72,18 +82,23 @@ where C::Point: 'a+Coordinate2D { enum ClipResult { None, Some((f64, f64)), - SecondCurveIsLinear + SecondCurveIsLinear, } /// /// Performs the fat-line clipping algorithm on two curves, returning the t values if they overlap -/// +/// #[inline] -fn clip<'a, C: BezierCurve>(curve_to_clip: &CurveSection<'a, C>, curve_to_clip_against: &CurveSection<'a, C>) -> ClipResult -where C::Point: 'a+Coordinate2D { +fn clip<'a, C: BezierCurve>( + curve_to_clip: &CurveSection<'a, C>, + curve_to_clip_against: &CurveSection<'a, C>, +) -> ClipResult +where + C::Point: 'a + Coordinate2D, +{ // Clip against the fat line - let fat_line = FatLine::from_curve(curve_to_clip_against); - let clip_t = fat_line.clip_t(curve_to_clip); + let fat_line = FatLine::from_curve(curve_to_clip_against); + let clip_t = fat_line.clip_t(curve_to_clip); if fat_line.is_flat() { return ClipResult::SecondCurveIsLinear; @@ -91,8 +106,8 @@ where C::Point: 'a+Coordinate2D { let clip_t = if let Some(clip_t) = clip_t { // Also try clipping against the perpendicular line - let perpendicular_line = FatLine::from_curve_perpendicular(curve_to_clip_against); - let clip_t_perpendicular = perpendicular_line.clip_t(curve_to_clip); + let perpendicular_line = FatLine::from_curve_perpendicular(curve_to_clip_against); + let clip_t_perpendicular = perpendicular_line.clip_t(curve_to_clip); // Use the perpendicular version if better if let Some(clip_t_perpendicular) = clip_t_perpendicular { @@ -116,40 +131,53 @@ where C::Point: 'a+Coordinate2D { // t1 and t2 must not match (exact matches produce an invalid curve) match clip_t { - ClipResult::Some((t1, t2)) => if t1 == t2 { ClipResult::Some(((t1-0.005).max(0.0), (t2+0.005).min(1.0))) } else { ClipResult::Some((t1, t2)) } - other => other + ClipResult::Some((t1, t2)) => { + if t1 == t2 { + ClipResult::Some(((t1 - 0.005).max(0.0), (t2 + 0.005).min(1.0))) + } else { + ClipResult::Some((t1, t2)) + } + } + other => other, } } /// /// Given a set of intersections found on a left and right curve, joins them in a way that eliminates duplicates -/// -fn join_subsections<'a, C: BezierCurve>(curve1: &CurveSection<'a, C>, left: SmallVec<[(f64, f64); 8]>, right: SmallVec<[(f64, f64); 8]>, accuracy_squared: f64) -> SmallVec<[(f64, f64); 8]> -where C::Point: Coordinate2D { - if left.len() == 0 { +/// +fn join_subsections( + curve1: &CurveSection, + left: SmallVec<[(f64, f64); 8]>, + right: SmallVec<[(f64, f64); 8]>, + accuracy_squared: f64, +) -> SmallVec<[(f64, f64); 8]> +where + C::Point: Coordinate2D, +{ + if left.is_empty() { // No further work to do right - } else if right.len() == 0 { + } else if right.is_empty() { // No further work to do left } else { // The last intersection in left might be the same as the first in right - let (left_t1, _left_t2) = left[left.len()-1]; - let (right_t1, _right_t2) = right[0]; + let (left_t1, _left_t2) = left[left.len() - 1]; + let (right_t1, _right_t2) = right[0]; // We use t1 and curve1 to determine this - let left_t1 = curve1.section_t_for_original_t(left_t1); - let right_t1 = curve1.section_t_for_original_t(right_t1); + let left_t1 = curve1.section_t_for_original_t(left_t1); + let right_t1 = curve1.section_t_for_original_t(right_t1); - if (right_t1-left_t1).abs() < 0.1 { + if (right_t1 - left_t1).abs() < 0.1 { // Could be the same point let p1 = curve1.point_at_pos(left_t1); let p2 = curve1.point_at_pos(right_t1); - let offset = p2-p1; - let distance_squared = offset.dot(&offset); + let offset = p2 - p1; + let distance_squared = offset.dot(&offset); - if distance_squared <= (accuracy_squared*2.0) { + if distance_squared <= (accuracy_squared * 2.0) { // First and last points are the same: only use the version of the LHS let mut combined = left; combined.extend(right.into_iter().skip(1)); @@ -171,9 +199,15 @@ where C::Point: Coordinate2D { /// /// Determines the points at which two curves intersect using the Bezier clipping algorithm -/// -fn curve_intersects_curve_clip_inner<'a, C: BezierCurve>(curve1: CurveSection<'a, C>, curve2: CurveSection<'a, C>, accuracy_squared: f64) -> SmallVec<[(f64, f64); 8]> -where C::Point: 'a+Coordinate2D { +/// +fn curve_intersects_curve_clip_inner<'a, C: BezierCurve>( + curve1: CurveSection<'a, C>, + curve2: CurveSection<'a, C>, + accuracy_squared: f64, +) -> SmallVec<[(f64, f64); 8]> +where + C::Point: 'a + Coordinate2D, +{ // Overlapping curves should be treated separately (the clipping algorithm will just match all of the points) let overlaps = overlapping_region(&curve1, &curve2); if let Some(((c1_t1, c1_t2), (c2_t1, c2_t2))) = overlaps { @@ -201,22 +235,28 @@ where C::Point: 'a+Coordinate2D { let mut curve2_last_len = curve_hull_length_sq(&curve2); // Edge case: 0-length curves have no match - if curve1_last_len == 0.0 { return smallvec![]; } - if curve2_last_len == 0.0 { return smallvec![]; } + if curve1_last_len == 0.0 { + return smallvec![]; + } + if curve2_last_len == 0.0 { + return smallvec![]; + } // Iterate to refine the match loop { let curve2_len = if curve2_last_len > accuracy_squared { // Clip curve2 against curve1 - let clip_t = clip(&curve2, &curve1); - let clip_t = match clip_t { - ClipResult::None => { return smallvec![]; }, - ClipResult::Some(clip_t) => clip_t, - ClipResult::SecondCurveIsLinear => { + let clip_t = clip(&curve2, &curve1); + let clip_t = match clip_t { + ClipResult::None => { + return smallvec![]; + } + ClipResult::Some(clip_t) => clip_t, + ClipResult::SecondCurveIsLinear => { return intersections_with_linear_section(&curve1, &curve2) .into_iter() .map(|(t1, t2)| (curve1.t_for_t(t1), curve2.t_for_t(t2))) - .collect(); + .collect(); } }; @@ -224,21 +264,23 @@ where C::Point: 'a+Coordinate2D { // Work out the length of the new curve curve_hull_length_sq(&curve2) - } else { + } else { curve2_last_len }; let curve1_len = if curve1_last_len > accuracy_squared { // Clip curve1 against curve2 - let clip_t = clip(&curve1, &curve2); - let clip_t = match clip_t { - ClipResult::None => { return smallvec![]; }, - ClipResult::Some(clip_t) => clip_t, - ClipResult::SecondCurveIsLinear => { + let clip_t = clip(&curve1, &curve2); + let clip_t = match clip_t { + ClipResult::None => { + return smallvec![]; + } + ClipResult::Some(clip_t) => clip_t, + ClipResult::SecondCurveIsLinear => { return intersections_with_linear_section(&curve2, &curve1) .into_iter() .map(|(t2, t1)| (curve1.t_for_t(t1), curve2.t_for_t(t2))) - .collect(); + .collect(); } }; @@ -252,31 +294,37 @@ where C::Point: 'a+Coordinate2D { if curve1_len <= accuracy_squared && curve2_len <= accuracy_squared { // Found a point to the required accuracy: return it, in coordinates relative to the original curve - if curve1.fast_bounding_box::>().overlaps(&curve2.fast_bounding_box::>()) { + if curve1 + .fast_bounding_box::>() + .overlaps(&curve2.fast_bounding_box::>()) + { let (t_min1, t_max1) = curve1.original_curve_t_values(); let (t_min2, t_max2) = curve2.original_curve_t_values(); - return smallvec![((t_min1+t_max1)*0.5, (t_min2+t_max2)*0.5)]; + return smallvec![((t_min1 + t_max1) * 0.5, (t_min2 + t_max2) * 0.5)]; } else { // Clipping algorithm found a point, but the two curves do not actually overlap, so reject them return smallvec![]; } } - if (curve1_last_len*0.8) <= curve1_len && (curve2_last_len*0.8) <= curve2_len { + if (curve1_last_len * 0.8) <= curve1_len && (curve2_last_len * 0.8) <= curve2_len { // If neither curve shrunk by 20%, then subdivide the one that shrunk the least - if curve1_len/curve1_last_len > curve2_len/curve2_last_len { + if curve1_len / curve1_last_len > curve2_len / curve2_last_len { // Curve1 shrunk less than curve2 - let (left, right) = (curve1.subsection(0.0, 0.5), curve1.subsection(0.5, 1.0)); - let left = curve_intersects_curve_clip_inner(left, curve2.clone(), accuracy_squared); - let right = curve_intersects_curve_clip_inner(right, curve2, accuracy_squared); + let (left, right) = (curve1.subsection(0.0, 0.5), curve1.subsection(0.5, 1.0)); + let left = + curve_intersects_curve_clip_inner(left, curve2.clone(), accuracy_squared); + let right = curve_intersects_curve_clip_inner(right, curve2, accuracy_squared); return join_subsections(&curve1, left, right, accuracy_squared); } else { // Curve2 shrunk less than curve1 - let (left, right) = (curve2.subsection(0.0, 0.5), curve2.subsection(0.5, 1.0)); - let left = curve_intersects_curve_clip_inner(curve1.clone(), left, accuracy_squared); - let right = curve_intersects_curve_clip_inner(curve1.clone(), right, accuracy_squared); + let (left, right) = (curve2.subsection(0.0, 0.5), curve2.subsection(0.5, 1.0)); + let left = + curve_intersects_curve_clip_inner(curve1.clone(), left, accuracy_squared); + let right = + curve_intersects_curve_clip_inner(curve1.clone(), right, accuracy_squared); return join_subsections(&curve1, left, right, accuracy_squared); } @@ -291,13 +339,19 @@ where C::Point: 'a+Coordinate2D { /// /// Determines the points at which two curves intersect using the Bezier clipping /// algorihtm -/// -pub fn curve_intersects_curve_clip<'a, C: BezierCurve>(curve1: &'a C, curve2: &'a C, accuracy: f64) -> SmallVec<[(f64, f64); 8]> -where C::Point: 'a+Coordinate2D { +/// +pub fn curve_intersects_curve_clip<'a, C: BezierCurve>( + curve1: &'a C, + curve2: &'a C, + accuracy: f64, +) -> SmallVec<[(f64, f64); 8]> +where + C::Point: 'a + Coordinate2D, +{ // Start with the entire span of both curves let curve1 = curve1.section(0.0, 1.0); let curve2 = curve2.section(0.0, 1.0); // Perform the clipping algorithm on these curves - curve_intersects_curve_clip_inner(curve1, curve2, accuracy*accuracy) + curve_intersects_curve_clip_inner(curve1, curve2, accuracy * accuracy) } diff --git a/src/bezier/intersection/curve_line.rs b/src/bezier/intersection/curve_line.rs index d9ccf960..6cb9b6b4 100644 --- a/src/bezier/intersection/curve_line.rs +++ b/src/bezier/intersection/curve_line.rs @@ -1,11 +1,11 @@ -use super::super::curve::*; -use super::super::basis::*; -use crate::geo::*; -use crate::line::*; -use crate::consts::*; +use super::super::basis::{bezier_coefficients, de_casteljau4}; +use super::super::curve::BezierCurve; +use crate::consts::SMALL_DISTANCE; +use crate::geo::Coordinate2D; +use crate::line::Line; -use smallvec::*; use roots::{find_roots_cubic, find_roots_quadratic, Roots}; +use smallvec::{smallvec, SmallVec}; /// /// Solves the roots for a set of cubic coefficients @@ -36,84 +36,90 @@ fn solve_roots(p: (f64, f64, f64, f64)) -> Roots { /// /// Return value is a vector of (curve_t, line_t, intersection_point) values. The `line_t` value can be outside the /// original line, so this will return all the points on the curve that lie on a line of infinite length. -/// -pub fn curve_intersects_ray>(curve: &C, line: &L) -> SmallVec<[(f64, f64, C::Point); 4]> -where C::Point: Coordinate2D { +/// +pub fn curve_intersects_ray>( + curve: &C, + line: &L, +) -> SmallVec<[(f64, f64, C::Point); 4]> +where + C::Point: Coordinate2D, +{ // Based upon https://www.particleincell.com/2013/cubic-line-intersection/ // Line coefficients - let (p1, p2) = line.points(); - let a = p2.y()-p1.y(); - let b = p1.x()-p2.x(); - let c = p1.x()*(p1.y()-p2.y()) + p1.y()*(p2.x()-p1.x()); + let (p1, p2) = line.points(); + let a = p2.y() - p1.y(); + let b = p1.x() - p2.x(); + let c = p1.x() * (p1.y() - p2.y()) + p1.y() * (p2.x() - p1.x()); if a == 0.0 && b == 0.0 { return smallvec![]; } // Bezier coefficients - let (w2, w3) = curve.control_points(); - let (w1, w4) = (curve.start_point(), curve.end_point()); - let bx = bezier_coefficients(0, &w1, &w2, &w3, &w4); - let by = bezier_coefficients(1, &w1, &w2, &w3, &w4); - - let p = ( - a*bx.0+b*by.0, - a*bx.1+b*by.1, - a*bx.2+b*by.2, - a*bx.3+b*by.3+c + let (w2, w3) = curve.control_points(); + let (w1, w4) = (curve.start_point(), curve.end_point()); + let bx = bezier_coefficients(0, &w1, &w2, &w3, &w4); + let by = bezier_coefficients(1, &w1, &w2, &w3, &w4); + + let p = ( + a * bx.0 + b * by.0, + a * bx.1 + b * by.1, + a * bx.2 + b * by.2, + a * bx.3 + b * by.3 + c, ); - let roots = solve_roots(p); - let roots: SmallVec<[f64; 4]> = match roots { - Roots::No(_) => smallvec![], - Roots::One(r) => SmallVec::from_slice(&r), - Roots::Two(r) => SmallVec::from_slice(&r), + let roots = solve_roots(p); + let roots: SmallVec<[f64; 4]> = match roots { + Roots::No(_) => smallvec![], + Roots::One(r) => SmallVec::from_slice(&r), + Roots::Two(r) => SmallVec::from_slice(&r), Roots::Three(r) => SmallVec::from_slice(&r), - Roots::Four(r) => SmallVec::from_buf(r) + Roots::Four(r) => SmallVec::from_buf(r), }; let mut result = smallvec![]; for t in roots.into_iter() { // Allow a small amount of 'slop' for items at the start/end as the root finding is not exact - let t = - if t < 0.0 && t > -0.01 { - // If the line passes close enough to the start of the curve, set t to 0 - let factor = (a*a + b*b).sqrt(); - let (a, b, c) = (a/factor, b/factor, c/factor); - let start_point = &w1; - - if (start_point.x()*a + start_point.y()*b + c).abs() < SMALL_DISTANCE { - 0.0 - } else { - t - } - } else if t > 1.0 && t < 1.01 { - // If the line passes close enough to the end of the curve, set t to 1 - let factor = (a*a + b*b).sqrt(); - let (a, b, c) = (a/factor, b/factor, c/factor); - let end_point = &w4; - - if (end_point.x()*a + end_point.y()*b + c).abs() < SMALL_DISTANCE { - 1.0 - } else { - t - } - } else { t }; - - if t >= 0.0 && t <= 1.0 { + let t = if t < 0.0 && t > -0.01 { + // If the line passes close enough to the start of the curve, set t to 0 + let factor = (a * a + b * b).sqrt(); + let (a, b, c) = (a / factor, b / factor, c / factor); + let start_point = &w1; + + if (start_point.x() * a + start_point.y() * b + c).abs() < SMALL_DISTANCE { + 0.0 + } else { + t + } + } else if t > 1.0 && t < 1.01 { + // If the line passes close enough to the end of the curve, set t to 1 + let factor = (a * a + b * b).sqrt(); + let (a, b, c) = (a / factor, b / factor, c / factor); + let end_point = &w4; + + if (end_point.x() * a + end_point.y() * b + c).abs() < SMALL_DISTANCE { + 1.0 + } else { + t + } + } else { + t + }; + + if (0.0..=1.0).contains(&t) { // Calculate the position on the curve let pos = de_casteljau4(t, w1, w2, w3, w4); // Coordinates on the curve - let x = pos.x(); - let y = pos.y(); + let x = pos.x(); + let y = pos.y(); // Solve for the position on the line let s = if b.abs() > a.abs() { - (x-p1.x())/(p2.x()-p1.x()) + (x - p1.x()) / (p2.x() - p1.x()) } else { - (y-p1.y())/(p2.y()-p1.y()) + (y - p1.y()) / (p2.y() - p1.y()) }; test_assert!(!s.is_nan()); @@ -130,11 +136,16 @@ where C::Point: Coordinate2D { /// Find the t values where a curve intersects a line /// /// Return value is a vector of (curve_t, line_t, intersection_point) values -/// -pub fn curve_intersects_line>(curve: &C, line: &L) -> SmallVec<[(f64, f64, C::Point); 4]> -where C::Point: Coordinate2D { +/// +pub fn curve_intersects_line>( + curve: &C, + line: &L, +) -> SmallVec<[(f64, f64, C::Point); 4]> +where + C::Point: Coordinate2D, +{ let mut ray_intersections = curve_intersects_ray(curve, line); - ray_intersections.retain(|(_t, s, _pos)| s >= &mut 0.0 && s <= &mut 1.0); + ray_intersections.retain(|(_t, s, _pos)| (&mut 0.0..=&mut 1.0).contains(&s)); ray_intersections } diff --git a/src/bezier/intersection/fat_line.rs b/src/bezier/intersection/fat_line.rs index ce18edca..c0d14723 100644 --- a/src/bezier/intersection/fat_line.rs +++ b/src/bezier/intersection/fat_line.rs @@ -1,14 +1,14 @@ -use super::super::curve::*; -use super::super::super::geo::*; -use super::super::super::line::*; -use super::super::super::consts::*; +use super::super::super::consts::SMALL_DISTANCE; +use super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::super::line::{line_coefficients_2d, Line}; +use super::super::curve::{BezierCurve, BezierCurveFactory, Curve}; use std::f64; /// /// A 'fat line' is a line with a width. It's used in bezier intersection algorithms, /// in particular the clipping algorithm described by Sederberg and Nishita -/// +/// #[derive(Debug)] pub struct FatLine { /// The distance from the line to the upper part of the 'fat line' @@ -18,68 +18,78 @@ pub struct FatLine { d_max: f64, /// The coefficients (a, b, c) in the equation ax+bx+c (where a^2+b^2 = 0) - coeff: (f64, f64, f64) + coeff: (f64, f64, f64), } impl FatLine { /// /// Returns the distance between the point and the central line - /// + /// #[inline] pub fn distance(&self, point: &Point) -> f64 { let (a, b, c) = self.coeff; - a*point.x() + b*point.y() + c + a * point.x() + b * point.y() + c } /// /// Given a bezier curve, returns another curve whose X axis is the distance /// from the central line and the Y axis varies from 0 to 1, with a uniform /// distribution of t values. - /// + /// /// This is used in the bezier clipping algorithm to discover where a bezier /// curve clips against this line. - /// - pub fn distance_curve>(&self, curve: &FromCurve) -> ToCurve - where FromCurve::Point: Coordinate2D { - let (cp1, cp2) = curve.control_points(); - - let start = FromCurve::Point::from_components(&[self.distance(&curve.start_point()), 0.0]); - let end = FromCurve::Point::from_components(&[self.distance(&curve.end_point()), 1.0]); - let cp1 = FromCurve::Point::from_components(&[self.distance(&cp1), 1.0/3.0]); - let cp2 = FromCurve::Point::from_components(&[self.distance(&cp2), 2.0/3.0]); + /// + pub fn distance_curve< + FromCurve: BezierCurve, + ToCurve: BezierCurveFactory, + >( + &self, + curve: &FromCurve, + ) -> ToCurve + where + FromCurve::Point: Coordinate2D, + { + let (cp1, cp2) = curve.control_points(); + + let start = FromCurve::Point::from_components(&[self.distance(&curve.start_point()), 0.0]); + let end = FromCurve::Point::from_components(&[self.distance(&curve.end_point()), 1.0]); + let cp1 = FromCurve::Point::from_components(&[self.distance(&cp1), 1.0 / 3.0]); + let cp2 = FromCurve::Point::from_components(&[self.distance(&cp2), 2.0 / 3.0]); ToCurve::from_points(start, (cp1, cp2), end) } /// /// Returns the convex hull of a curve returned by distance_curve - /// + /// /// We can use some of the properties of the distance_curve to simplify how this /// is worked out (specifically, we know the points are sorted vertically already /// so we only need to know if the two control points are on the same side or not) - /// - fn distance_curve_convex_hull(distance_curve: &C) -> Vec - where C::Point: Coordinate2D { + /// + fn distance_curve_convex_hull(distance_curve: &C) -> Vec + where + C::Point: Coordinate2D, + { // Read the points from the curve - let start = distance_curve.start_point(); - let (cp1, cp2) = distance_curve.control_points(); - let end = distance_curve.end_point(); + let start = distance_curve.start_point(); + let (cp1, cp2) = distance_curve.control_points(); + let end = distance_curve.end_point(); // Compute the x component of the distances of cp1 and cp2 from the central line defined by start->end // These are the m and c values for y=mx+c assuming that start.y() = 0 and end.y() = 1 which is true for the distance curve - let m = end.x()-start.x(); + let m = end.x() - start.x(); let c = start.x(); - let dx1 = cp1.x() - (m*(1.0/3.0)+c); - let dx2 = cp2.x() - (m*(2.0/3.0)+c); + let dx1 = cp1.x() - (m * (1.0 / 3.0) + c); + let dx2 = cp2.x() - (m * (2.0 / 3.0) + c); // If they have the same sign, they're on the same side - let on_same_side = dx1*dx2 >= 0.0; + let on_same_side = dx1 * dx2 >= 0.0; // Ordering on the convex hull depends only on if cp1 and cp2 are on the same side or not if on_same_side { // cp1 or cp2 might be inside the hull - let dist_ratio = dx1/dx2; + let dist_ratio = dx1 / dx2; if dist_ratio >= 2.0 { // cp2 is in the hull (between the line cp1->end and start->end) @@ -99,7 +109,7 @@ impl FatLine { /// /// Rounds values very close to 0 or 1 to 0 or 1 - /// + /// #[inline] fn round_y_value(y: f64) -> f64 { if y < 0.00001 && y > -0.001 { @@ -113,17 +123,28 @@ impl FatLine { /// /// Given an x pos on a line, solves for the y point - /// + /// #[inline] - fn solve_line_y((x1, x2): (f64, f64), (p1, p2): (&Point, &Point)) -> (Option, Option) { + fn solve_line_y( + (x1, x2): (f64, f64), + (p1, p2): (&Point, &Point), + ) -> (Option, Option) { let min_x = p1.x().min(p2.x()); let max_x = p1.x().max(p2.x()); - let m = (p2.y()-p1.y())/(p2.x()-p1.x()); + let m = (p2.y() - p1.y()) / (p2.x() - p1.x()); let c = p1.y() - m * p1.x(); - let y1 = if x1 >= min_x && x1 <= max_x { Some(Self::round_y_value(m*x1 + c)) } else { None }; - let y2 = if x2 >= min_x && x2 <= max_x { Some(Self::round_y_value(m*x2 + c)) } else { None }; + let y1 = if x1 >= min_x && x1 <= max_x { + Some(Self::round_y_value(m * x1 + c)) + } else { + None + }; + let y2 = if x2 >= min_x && x2 <= max_x { + Some(Self::round_y_value(m * x2 + c)) + } else { + None + }; (y1, y2) } @@ -131,52 +152,63 @@ impl FatLine { /// /// Clips the curve against this fat line. This attempts to find two new t values such /// that values outside that range are guaranteed not to lie within this fat line. - /// + /// /// (This doesn't guarantee that the t values lie precisely within the line, though it's /// usually possible to iterate to improve the precision of the match) - /// - pub fn clip_t(&self, curve: &C) -> Option<(f64, f64)> - where C::Point: Coordinate2D { + /// + pub fn clip_t(&self, curve: &C) -> Option<(f64, f64)> + where + C::Point: Coordinate2D, + { // The 'distance' curve is a bezier curve where 'x' is the distance to the central line from the curve and 'y' is the t value where that distance occurs - let distance_curve = self.distance_curve::<_, Curve>(curve); + let distance_curve = self.distance_curve::<_, Curve>(curve); // The convex hull encloses the distance curve, and can be used to find the y values where it's between d_min and d_max // As y=t due to how we construct the distance curve these are also the t values // We make use of the fact that the hull always has the start point at the start - let distance_convex_hull = Self::distance_curve_convex_hull(&distance_curve); + let distance_convex_hull = Self::distance_curve_convex_hull(&distance_curve); // To solve for t, we need to find where the two edge lines cross d_min and d_max - let num_points = distance_convex_hull.len(); - let mut t1 = f64::MAX; - let mut t2 = f64::MIN; - let d_min = self.d_min; - let d_max = self.d_max; + let num_points = distance_convex_hull.len(); + let mut t1 = f64::MAX; + let mut t2 = f64::MIN; + let d_min = self.d_min; + let d_max = self.d_max; for idx in 0..num_points { // Solve where this part of the convex hull crosses this line - let (p1, p2) = (&distance_convex_hull[idx], &distance_convex_hull[(idx+1)%num_points]); - let hull_line = (p1, p2); - let (t1a, t2a) = Self::solve_line_y((d_min, d_max), hull_line); + let (p1, p2) = ( + &distance_convex_hull[idx], + &distance_convex_hull[(idx + 1) % num_points], + ); + let hull_line = (p1, p2); + let (t1a, t2a) = Self::solve_line_y((d_min, d_max), hull_line); // The y axis indicates where the hull crosses from inside to outside the fat line if let Some(t1a) = t1a { // Line crossed d_min - if t1a >= 0.0 && t1a <= 1.0 { + if (0.0..=1.0).contains(&t1a) { t1 = t1.min(t1a); t2 = t2.max(t1a); } } - + if let Some(t2a) = t2a { // Line crossed d_max - if t2a >= 0.0 && t2a <= 1.0 { + if (0.0..=1.0).contains(&t2a) { t1 = t1.min(t2a); t2 = t2.max(t2a); } } // If the start or end point is inside the fat line then it is also within the clipping area - if p1.x() <= d_max && p1.x() >= d_min { t1 = t1.min(p1.y()); t2 = t2.max(p1.y()); } - if p2.x() <= d_max && p2.x() >= d_min { t1 = t1.min(p2.y()); t2 = t2.max(p2.y()); } + if p1.x() <= d_max && p1.x() >= d_min { + t1 = t1.min(p1.y()); + t2 = t2.max(p1.y()); + } + if p2.x() <= d_max && p2.x() >= d_min { + t1 = t1.min(p2.y()); + t2 = t2.max(p2.y()); + } } if t1 > t2 { @@ -186,9 +218,12 @@ impl FatLine { Some((0.0, t2)) } else { // No part of the hull crossed the line (either entirely inside or outside) - let hull_x = distance_convex_hull.into_iter().map(|p| p.x()).collect::>(); - let hull_min_x = hull_x.iter().cloned().fold(f64::INFINITY, f64::min); - let hull_max_x = hull_x.iter().cloned().fold(f64::NEG_INFINITY, f64::max); + let hull_x = distance_convex_hull + .into_iter() + .map(|p| p.x()) + .collect::>(); + let hull_min_x = hull_x.iter().cloned().fold(f64::INFINITY, f64::min); + let hull_max_x = hull_x.iter().cloned().fold(f64::NEG_INFINITY, f64::max); if self.d_min > hull_max_x || self.d_max < hull_min_x { // Convex hull is outside the line @@ -224,61 +259,61 @@ impl FatLine { /// Returns true if this line is flat (indicating the source curve is a straight line) /// pub fn is_flat(&self) -> bool { - if self.d_min.abs() < SMALL_DISTANCE && self.d_max.abs() < SMALL_DISTANCE { - true - } else { - false - } + self.d_min.abs() < SMALL_DISTANCE && self.d_max.abs() < SMALL_DISTANCE } } impl FatLine { /// /// Creates a new fatline from a central line and two points representing its outer edges - /// - fn from_line_and_points(line: L, p1: L::Point, p2: L::Point) -> FatLine - where L::Point: Coordinate+Coordinate2D { + /// + fn from_line_and_points(line: L, p1: L::Point, p2: L::Point) -> Self + where + L::Point: Coordinate + Coordinate2D, + { // Coefficients for the line - let (a, b, c) = line_coefficients_2d(&line); + let (a, b, c) = line_coefficients_2d(&line); // Compute the distances to the control points - let d1 = a*p1.x() + b*p1.y() + c; - let d2 = a*p2.x() + b*p2.y() + c; + let d1 = a * p1.x() + b * p1.y() + c; + let d2 = a * p2.x() + b * p2.y() + c; // This is the 'estimated fit' shortcut suggested by Sederberg/Nishta in their paper rather than the tighest fitting line - let (d_min, d_max) = if d1*d2 > 0.0 { + let (d_min, d_max) = if d1 * d2 > 0.0 { // Both control points on the same side of the line ( - (3.0/4.0) * (d1.min(d2).min(0.0)), - (3.0/4.0) * (d1.max(d2).max(0.0)) + (3.0 / 4.0) * (d1.min(d2).min(0.0)), + (3.0 / 4.0) * (d1.max(d2).max(0.0)), ) } else { // Control points on opposite sides of the line ( - (4.0/9.0) * (d1.min(d2).min(0.0)), - (4.0/9.0) * (d1.max(d2).max(0.0)) + (4.0 / 9.0) * (d1.min(d2).min(0.0)), + (4.0 / 9.0) * (d1.max(d2).max(0.0)), ) }; - FatLine { - d_min: d_min, - d_max: d_max, - coeff: (a, b, c) + Self { + d_min, + d_max, + coeff: (a, b, c), } } /// /// Creates a new fatline from a curve - /// - pub fn from_curve(curve: &C) -> FatLine - where C::Point: Coordinate+Coordinate2D { + /// + pub fn from_curve(curve: &C) -> Self + where + C::Point: Coordinate + Coordinate2D, + { // Line between the start and end points of the curve - let line = (curve.start_point(), curve.end_point()); - let (cp1, cp2) = curve.control_points(); + let line = (curve.start_point(), curve.end_point()); + let (cp1, cp2) = curve.control_points(); // If the start point and the end point are at the same location, use the gap between the control points to set the line direction instead let line = if line.0.is_near_to(&line.1, 0.0000001) { - (curve.start_point(), curve.start_point() + (cp2-cp1)) + (curve.start_point(), curve.start_point() + (cp2 - cp1)) } else { line }; @@ -288,70 +323,76 @@ impl FatLine { /// /// Creates a perpendicular fatline from a curve - /// - pub fn from_curve_perpendicular(curve: &C) -> FatLine - where C::Point: Coordinate+Coordinate2D { + /// + pub fn from_curve_perpendicular(curve: &C) -> Self + where + C::Point: Coordinate + Coordinate2D, + { let (start_point, end_point) = (curve.start_point(), curve.end_point()); // If the start point and the end point are at the same location, use the gap between the control points to set the line direction instead let end_point = if start_point.is_near_to(&end_point, 0.0000001) { let (cp1, cp2) = curve.control_points(); - start_point + (cp2-cp1) + start_point + (cp2 - cp1) } else { end_point }; // Line between the start and end points of the curve - let line = (start_point, end_point); + let line = (start_point, end_point); // Mid-point of the line - let mid_point = line.point_at_pos(0.5); + let mid_point = line.point_at_pos(0.5); // Target point to generate a perpendicular line - let offset = mid_point - start_point; - let offset = C::Point::from_components(&[offset.y(), offset.x()]); - let target_point = mid_point + offset; + let offset = mid_point - start_point; + let offset = C::Point::from_components(&[offset.y(), offset.x()]); + let target_point = mid_point + offset; // Perpendicular line - let line = (mid_point, target_point); + let line = (mid_point, target_point); // Compute the distances to all of the points - let (cp1, cp2) = curve.control_points(); - let (a, b, c) = line_coefficients_2d(&line); + let (cp1, cp2) = curve.control_points(); + let (a, b, c) = line_coefficients_2d(&line); - let d1 = a*start_point.x() + b*start_point.y() + c; - let d2 = a*cp1.x() + b*cp1.y() + c; - let d3 = a*cp2.x() + b*cp2.y() + c; - let d4 = a*end_point.x() + b*end_point.y() + c; + let d1 = a * start_point.x() + b * start_point.y() + c; + let d2 = a * cp1.x() + b * cp1.y() + c; + let d3 = a * cp2.x() + b * cp2.y() + c; + let d4 = a * end_point.x() + b * end_point.y() + c; // No approximation to improve the line fit here - let d_min = d1.min(d2).min(d3).min(d4); - let d_max = d1.max(d2).max(d3).max(d4); + let d_min = d1.min(d2).min(d3).min(d4); + let d_max = d1.max(d2).max(d3).max(d4); - FatLine { - coeff: (a, b, c), - d_min: d_min, - d_max: d_max + Self { + coeff: (a, b, c), + d_min, + d_max, } } } #[cfg(test)] mod test { + use crate::{line::line_to_bezier, Coord2}; + use super::*; impl FatLine { /// /// Creates a new fat line - /// - pub fn new(line: L, d_min: f64, d_max: f64) -> FatLine - where L::Point: Coordinate2D { - let (a, b, c) = line_coefficients_2d(&line); - - FatLine { - d_min: d_min, - d_max: d_max, - coeff: (a, b, c) + /// + pub fn new(line: L, d_min: f64, d_max: f64) -> Self + where + L::Point: Coordinate2D, + { + let (a, b, c) = line_coefficients_2d(&line); + + Self { + d_min, + d_max, + coeff: (a, b, c), } } @@ -359,12 +400,17 @@ mod test { /// Clips a bezier curve against this fat line. This returns a new bezier curve where /// parts that are outside the line have been clipped away, but does not necessarily /// guarantee that it's just the portion within the line. - /// + /// /// This call can be iterated to improve the fit in many cases, and will return none /// in the case where the curve is not within the line. - /// - pub fn clip>(&self, curve: &FromCurve) -> Option - where FromCurve::Point: Coordinate2D { + /// + pub fn clip>( + &self, + curve: &FromCurve, + ) -> Option + where + FromCurve::Point: Coordinate2D, + { if let Some((t1, t2)) = self.clip_t(curve) { Some(ToCurve::from_curve(&curve.section(t1, t2))) } else { @@ -375,120 +421,152 @@ mod test { #[test] fn distance_to_horizontal_line() { - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - assert!((fat_line.distance(&Coord2(0.0, 8.0))-4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(0.0, 0.0))- -4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(0.0, 8.0)) - 4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(0.0, 0.0)) - -4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(3.0, 8.0))-4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(3.0, 0.0))- -4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(3.0, 8.0)) - 4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(3.0, 0.0)) - -4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(5.0, 8.0))-4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(5.0, 0.0))- -4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(5.0, 8.0)) - 4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(5.0, 0.0)) - -4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(200.0, 8.0))-4.0).abs() < 0.0001); - assert!((fat_line.distance(&Coord2(200.0, 0.0))- -4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(200.0, 8.0)) - 4.0).abs() < 0.0001); + assert!((fat_line.distance(&Coord2(200.0, 0.0)) - -4.0).abs() < 0.0001); } #[test] fn convex_hull_basic() { - let hull_curve = Curve::from_points(Coord2(1.0, 0.0), (Coord2(5.0, 1.0/3.0), Coord2(6.0, 2.0/3.0)), Coord2(4.0, 1.0)); - let hull = FatLine::distance_curve_convex_hull(&hull_curve); + let hull_curve = Curve::from_points( + Coord2(1.0, 0.0), + (Coord2(5.0, 1.0 / 3.0), Coord2(6.0, 2.0 / 3.0)), + Coord2(4.0, 1.0), + ); + let hull = FatLine::distance_curve_convex_hull(&hull_curve); println!("{:?}", hull); - assert!(hull.len()==4); + assert!(hull.len() == 4); assert!(hull[0].distance_to(&Coord2(1.0, 0.0)) < 0.001); - assert!(hull[1].distance_to(&Coord2(5.0, 1.0/3.0)) < 0.001); - assert!(hull[2].distance_to(&Coord2(6.0, 2.0/3.0)) < 0.001); + assert!(hull[1].distance_to(&Coord2(5.0, 1.0 / 3.0)) < 0.001); + assert!(hull[2].distance_to(&Coord2(6.0, 2.0 / 3.0)) < 0.001); assert!(hull[3].distance_to(&Coord2(4.0, 1.0)) < 0.001); } #[test] fn convex_hull_concave_cp2() { - let hull_curve = Curve::from_points(Coord2(1.0, 0.0), (Coord2(4.0, 1.0/3.0), Coord2(3.0, 2.0/3.0)), Coord2(4.0, 1.0)); - let hull = FatLine::distance_curve_convex_hull(&hull_curve); + let hull_curve = Curve::from_points( + Coord2(1.0, 0.0), + (Coord2(4.0, 1.0 / 3.0), Coord2(3.0, 2.0 / 3.0)), + Coord2(4.0, 1.0), + ); + let hull = FatLine::distance_curve_convex_hull(&hull_curve); println!("{:?}", hull); - assert!(hull.len()==3); + assert!(hull.len() == 3); assert!(hull[0].distance_to(&Coord2(1.0, 0.0)) < 0.001); - assert!(hull[1].distance_to(&Coord2(4.0, 1.0/3.0)) < 0.001); + assert!(hull[1].distance_to(&Coord2(4.0, 1.0 / 3.0)) < 0.001); assert!(hull[2].distance_to(&Coord2(4.0, 1.0)) < 0.001); } #[test] fn convex_hull_concave_cp1() { - let hull_curve = Curve::from_points(Coord2(1.0, 0.0), (Coord2(4.0, 1.0/3.0), Coord2(8.0, 2.0/3.0)), Coord2(4.0, 1.0)); - let hull = FatLine::distance_curve_convex_hull(&hull_curve); + let hull_curve = Curve::from_points( + Coord2(1.0, 0.0), + (Coord2(4.0, 1.0 / 3.0), Coord2(8.0, 2.0 / 3.0)), + Coord2(4.0, 1.0), + ); + let hull = FatLine::distance_curve_convex_hull(&hull_curve); println!("{:?}", hull); - assert!(hull.len()==3); + assert!(hull.len() == 3); assert!(hull[0].distance_to(&Coord2(1.0, 0.0)) < 0.001); - assert!(hull[1].distance_to(&Coord2(8.0, 2.0/3.0)) < 0.001); + assert!(hull[1].distance_to(&Coord2(8.0, 2.0 / 3.0)) < 0.001); assert!(hull[2].distance_to(&Coord2(4.0, 1.0)) < 0.001); } #[test] fn convex_hull_opposite_sides() { - let hull_curve = Curve::from_points(Coord2(1.0, 0.0), (Coord2(4.0, 1.0/3.0), Coord2(1.0, 2.0/3.0)), Coord2(4.0, 1.0)); - let hull = FatLine::distance_curve_convex_hull(&hull_curve); + let hull_curve = Curve::from_points( + Coord2(1.0, 0.0), + (Coord2(4.0, 1.0 / 3.0), Coord2(1.0, 2.0 / 3.0)), + Coord2(4.0, 1.0), + ); + let hull = FatLine::distance_curve_convex_hull(&hull_curve); println!("{:?}", hull); - assert!(hull.len()==4); + assert!(hull.len() == 4); assert!(hull[0].distance_to(&Coord2(1.0, 0.0)) < 0.001); - assert!(hull[1].distance_to(&Coord2(4.0, 1.0/3.0)) < 0.001); + assert!(hull[1].distance_to(&Coord2(4.0, 1.0 / 3.0)) < 0.001); assert!(hull[2].distance_to(&Coord2(4.0, 1.0)) < 0.001); - assert!(hull[3].distance_to(&Coord2(1.0, 2.0/3.0)) < 0.001); + assert!(hull[3].distance_to(&Coord2(1.0, 2.0 / 3.0)) < 0.001); } #[test] fn distance_curve_1() { // Horizontal line, with a y range of 2.0 to 7.0 - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - let clip_curve = line_to_bezier::<_, Curve<_>>(&(Coord2(0.0, 0.0), Coord2(5.0, 8.0))); - let distance_curve = fat_line.distance_curve::<_, Curve>(&clip_curve); - - println!("{:?} {:?}", distance_curve.point_at_pos(0.0), distance_curve.point_at_pos(1.0)); - - assert!((distance_curve.point_at_pos(0.0).x()- -4.0).abs() < 0.0001); - assert!((distance_curve.point_at_pos(1.0).x()-4.0).abs() < 0.0001); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let clip_curve = line_to_bezier::<_, Curve<_>>(&(Coord2(0.0, 0.0), Coord2(5.0, 8.0))); + let distance_curve = fat_line.distance_curve::<_, Curve>(&clip_curve); + + println!( + "{:?} {:?}", + distance_curve.point_at_pos(0.0), + distance_curve.point_at_pos(1.0) + ); + + assert!((distance_curve.point_at_pos(0.0).x() - -4.0).abs() < 0.0001); + assert!((distance_curve.point_at_pos(1.0).x() - 4.0).abs() < 0.0001); } #[test] fn clip_line_1() { // Horizontal line, with a y range of 2.0 to 7.0 - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - let clip_curve = line_to_bezier::<_, Curve<_>>(&(Coord2(0.0, 0.0), Coord2(5.0, 8.0))); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let clip_curve = line_to_bezier::<_, Curve<_>>(&(Coord2(0.0, 0.0), Coord2(5.0, 8.0))); - let clipped = fat_line.clip::<_, Curve>(&clip_curve).unwrap(); - let clipped = fat_line.clip::<_, Curve>(&clipped).unwrap(); + let clipped = fat_line.clip::<_, Curve>(&clip_curve).unwrap(); + let clipped = fat_line.clip::<_, Curve>(&clipped).unwrap(); let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); - assert!((start_point.y()-2.0).abs() < 0.0001); - assert!((end_point.y()-7.0).abs() < 0.0001); + assert!((start_point.y() - 2.0).abs() < 0.0001); + assert!((end_point.y() - 7.0).abs() < 0.0001); } #[test] fn clip_t_1() { // Horizontal line, with a y range of 2.0 to 7.0 - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); - let distance_curve = fat_line.distance_curve::<_, Curve>(&clip_curve); - - let (t1, t2) = fat_line.clip_t(&clip_curve).unwrap(); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); + let distance_curve = fat_line.distance_curve::<_, Curve>(&clip_curve); + + let (t1, t2) = fat_line.clip_t(&clip_curve).unwrap(); let start_point = clip_curve.point_at_pos(t1); - let end_point = clip_curve.point_at_pos(t2); + let end_point = clip_curve.point_at_pos(t2); println!("Points on curve: {:?} {:?}", start_point, end_point); - println!("Distance-x: {:?} {:?}", distance_curve.point_at_pos(t1).x(), distance_curve.point_at_pos(t2).x()); - println!("Distance-y: {:?} {:?}", distance_curve.point_at_pos(t1).y(), distance_curve.point_at_pos(t2).y()); + println!( + "Distance-x: {:?} {:?}", + distance_curve.point_at_pos(t1).x(), + distance_curve.point_at_pos(t2).x() + ); + println!( + "Distance-y: {:?} {:?}", + distance_curve.point_at_pos(t1).y(), + distance_curve.point_at_pos(t2).y() + ); println!("T: {:?}", fat_line.clip_t(&clip_curve)); assert!(start_point.y() <= 2.0); @@ -498,10 +576,14 @@ mod test { #[test] fn clip_curve_1() { // Horizontal line, with a y range of 2.0 to 7.0 - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); - let mut clipped = clip_curve.clone(); + let mut clipped = clip_curve; // Should be able to iteratively refine to a curve clipped to the fat line for _x in 0..5 { @@ -510,88 +592,104 @@ mod test { } let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); - assert!((start_point.y()-2.0).abs() < 0.0001); - assert!((end_point.y()-7.0).abs() < 0.0001); + assert!((start_point.y() - 2.0).abs() < 0.0001); + assert!((end_point.y() - 7.0).abs() < 0.0001); } #[test] fn clip_curve_in_line() { - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -16.0, 16.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -16.0, 16.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); let clipped = fat_line.clip::<_, Curve>(&clip_curve); assert!(clipped.is_some()); let clipped = clipped.unwrap(); let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); - assert!((start_point.x()-0.0).abs() < 0.0001); - assert!((end_point.x()-5.0).abs() < 0.0001); + assert!((start_point.x() - 0.0).abs() < 0.0001); + assert!((end_point.x() - 5.0).abs() < 0.0001); - assert!((start_point.y()-0.0).abs() < 0.0001); - assert!((end_point.y()-8.0).abs() < 0.0001); + assert!((start_point.y() - 0.0).abs() < 0.0001); + assert!((end_point.y() - 8.0).abs() < 0.0001); } #[test] fn clip_curve_start_in_line() { // If the start point is inside the fat line, we should only clip the end point - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -16.0, 3.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -16.0, 3.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); let clipped = fat_line.clip::<_, Curve>(&clip_curve); assert!(clipped.is_some()); let clipped = clipped.unwrap(); let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); - assert!((start_point.x()-0.0).abs() < 0.0001); + assert!((start_point.x() - 0.0).abs() < 0.0001); assert!(end_point.x() <= 5.0); - assert!((start_point.y()-0.0).abs() < 0.0001); + assert!((start_point.y() - 0.0).abs() < 0.0001); assert!(end_point.y() <= 8.0); } #[test] fn clip_curve_end_in_line() { // If the end point is inside the fat line, we should only clip the start point - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 16.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 16.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); let clipped = fat_line.clip::<_, Curve>(&clip_curve); assert!(clipped.is_some()); let clipped = clipped.unwrap(); let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); assert!(start_point.x() >= 0.0); - assert!((end_point.x()-5.0).abs() < 0.0001); + assert!((end_point.x() - 5.0).abs() < 0.0001); assert!(start_point.y() >= 0.0); - assert!((end_point.y()-8.0).abs() < 0.0001); + assert!((end_point.y() - 8.0).abs() < 0.0001); } #[test] fn clip_curve_outside_line() { // If the curve is entirely outside the line, we should return None - let fat_line = FatLine::new((Coord2(0.0, 20.0), Coord2(5.0, 20.0)), -2.0, 2.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 20.0), Coord2(5.0, 20.0)), -2.0, 2.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); let clipped = fat_line.clip::<_, Curve>(&clip_curve); assert!(clipped.is_none()); @@ -600,10 +698,14 @@ mod test { #[test] fn can_always_refine() { // Horizontal line, with a y range of 2.0 to 7.0 - let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); - let clip_curve = Curve::from_points(Coord2(0.0, 0.0), (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), Coord2(5.0, 8.0)); + let fat_line = FatLine::new((Coord2(0.0, 4.0), Coord2(5.0, 4.0)), -2.0, 3.0); + let clip_curve = Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 5.0), Coord2(5.0, 4.0)), + Coord2(5.0, 8.0), + ); - let mut clipped = clip_curve.clone(); + let mut clipped = clip_curve; for _x in 0..100 { let next_clipped = fat_line.clip(&clipped); @@ -612,20 +714,28 @@ mod test { } let start_point = clipped.point_at_pos(0.0); - let end_point = clipped.point_at_pos(1.0); + let end_point = clipped.point_at_pos(1.0); println!("{:?} {:?}", start_point, end_point); println!("{:?}", fat_line.clip_t(&clip_curve)); - assert!((start_point.y()-2.0).abs() < 0.0001); - assert!((end_point.y()-7.0).abs() < 0.0001); + assert!((start_point.y() - 2.0).abs() < 0.0001); + assert!((end_point.y() - 7.0).abs() < 0.0001); } #[test] fn clip_curves_1() { // Two curves that clipped incorrectly from the clip intersection test - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); - let curve2 = Curve::from_points(Coord2(67.25, 113.48), (Coord2(146.18, 85.98), Coord2(109.35, 211.01)), Coord2(181.38, 199.44)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); + let curve2 = Curve::from_points( + Coord2(67.25, 113.48), + (Coord2(146.18, 85.98), Coord2(109.35, 211.01)), + Coord2(181.38, 199.44), + ); // Clip curve1 against curve2 let fat_line = FatLine::from_curve(&curve2); @@ -652,7 +762,11 @@ mod test { println!("{} pos {:?}, dist {:?}, actual {:?}", t, p1, d1, d2); } - println!("{:?} {:?}", (t1, t2), (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y())); + println!( + "{:?} {:?}", + (t1, t2), + (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y()) + ); assert!(curve1.point_at_pos(t1).x() < 81.79); assert!(curve1.point_at_pos(t2).x() > 179.86); @@ -667,8 +781,16 @@ mod test { // Coord2(179.87, 199.67) // Two curves that clipped incorrectly from the clip intersection test - let curve1 = Curve::from_points(Coord2(67.25, 113.48), (Coord2(155.03, 82.90), Coord2(99.65, 240.93)), Coord2(210.0, 190.0)); - let curve2 = Curve::from_points(Coord2(77.5, 103.75), (Coord2(97.5, 132.5), Coord2(130.0, 180.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(67.25, 113.48), + (Coord2(155.03, 82.90), Coord2(99.65, 240.93)), + Coord2(210.0, 190.0), + ); + let curve2 = Curve::from_points( + Coord2(77.5, 103.75), + (Coord2(97.5, 132.5), Coord2(130.0, 180.0)), + Coord2(220.0, 220.0), + ); // Clip curve1 against curve2 let fat_line = FatLine::from_curve(&curve2); @@ -689,7 +811,11 @@ mod test { println!("{} pos {:?}, dist {:?}, actual {:?}", t, p1, d1, d2); } - println!("{:?} {:?}", (t1, t2), (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y())); + println!( + "{:?} {:?}", + (t1, t2), + (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y()) + ); assert!(curve1.point_at_pos(t1).x() < 81.79); assert!(curve1.point_at_pos(t2).x() > 179.86); @@ -703,8 +829,16 @@ mod test { // Curve1 here forms a line that intercepts close to the start of Curve2, which seems to cause some accuracy issues - let curve1 = Curve::from_points(Coord2(80.317, 107.796), (Coord2(82.851, 111.424), Coord2(85.591, 115.301)), Coord2(88.615, 119.383)); - let curve2 = Curve::from_points(Coord2(81.248, 109.971), (Coord2(118.038, 104.934), Coord2(122.245, 142.970)), Coord2(134.936, 171.219)); + let curve1 = Curve::from_points( + Coord2(80.317, 107.796), + (Coord2(82.851, 111.424), Coord2(85.591, 115.301)), + Coord2(88.615, 119.383), + ); + let curve2 = Curve::from_points( + Coord2(81.248, 109.971), + (Coord2(118.038, 104.934), Coord2(122.245, 142.970)), + Coord2(134.936, 171.219), + ); // Clip curve1 against curve2 let fat_line = FatLine::from_curve(&curve2); @@ -725,7 +859,11 @@ mod test { println!("{} pos {:?}, dist {:?}, actual {:?}", t, p1, d1, d2); } - println!("{:?} {:?}", (t1, t2), (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y())); + println!( + "{:?} {:?}", + (t1, t2), + (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y()) + ); assert!(curve1.point_at_pos(t1).x() < 81.79); assert!(curve1.point_at_pos(t2).x() > 81.78); @@ -737,8 +875,16 @@ mod test { // // Coord2(81.78, 109.88) - let curve2 = Curve::from_points(Coord2(80.317, 107.796), (Coord2(82.851, 111.424), Coord2(85.591, 115.301)), Coord2(88.615, 119.383)); - let curve1 = Curve::from_points(Coord2(81.248, 109.971), (Coord2(118.038, 104.934), Coord2(122.245, 142.970)), Coord2(134.936, 171.219)); + let curve2 = Curve::from_points( + Coord2(80.317, 107.796), + (Coord2(82.851, 111.424), Coord2(85.591, 115.301)), + Coord2(88.615, 119.383), + ); + let curve1 = Curve::from_points( + Coord2(81.248, 109.971), + (Coord2(118.038, 104.934), Coord2(122.245, 142.970)), + Coord2(134.936, 171.219), + ); // Clip curve1 against curve2 let fat_line = FatLine::from_curve(&curve2); @@ -759,7 +905,11 @@ mod test { println!("{} pos {:?}, dist {:?}, actual {:?}", t, p1, d1, d2); } - println!("{:?} {:?}", (t1, t2), (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y())); + println!( + "{:?} {:?}", + (t1, t2), + (curve1.point_at_pos(t2).x(), curve1.point_at_pos(t2).y()) + ); assert!(curve1.point_at_pos(t1).x() < 81.79); assert!(curve1.point_at_pos(t2).x() > 81.78); diff --git a/src/bezier/intersection/mod.rs b/src/bezier/intersection/mod.rs index f6c845f3..f62c1024 100644 --- a/src/bezier/intersection/mod.rs +++ b/src/bezier/intersection/mod.rs @@ -1,8 +1,8 @@ -mod curve_line; mod curve_curve_clip; +mod curve_line; mod fat_line; mod self_intersection; -pub use self::curve_line::*; pub use self::curve_curve_clip::*; +pub use self::curve_line::*; pub use self::self_intersection::*; diff --git a/src/bezier/intersection/self_intersection.rs b/src/bezier/intersection/self_intersection.rs index 5dd09c74..cec21539 100644 --- a/src/bezier/intersection/self_intersection.rs +++ b/src/bezier/intersection/self_intersection.rs @@ -1,14 +1,16 @@ -use super::curve_curve_clip::*; -use super::super::curve::*; -use super::super::section::*; -use super::super::characteristics::*; -use super::super::super::geo::*; +use super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::characteristics::CurveCategory; +use super::super::curve::{BezierCurve, BezierCurve2D}; +use super::super::section::CurveSection; +use super::curve_curve_clip::curve_intersects_curve_clip; /// /// If a cubic curve contains a loop, finds the t values where the curve self-intersects /// pub fn find_self_intersection_point(curve: &C, accuracy: f64) -> Option<(f64, f64)> -where C::Point: Coordinate+Coordinate2D { +where + C::Point: Coordinate + Coordinate2D, +{ let curve_type = curve.characteristics(); if curve_type == CurveCategory::Loop { @@ -22,13 +24,18 @@ where C::Point: Coordinate+Coordinate2D { /// /// Given a curve known to have a loop in it, subdivides it in order to determine where the intersection lies /// -fn find_intersection_point_in_loop(curve: CurveSection, accuracy: f64) -> Option<(f64, f64)> -where C::Point: Coordinate+Coordinate2D { +fn find_intersection_point_in_loop( + curve: CurveSection, + accuracy: f64, +) -> Option<(f64, f64)> +where + C::Point: Coordinate + Coordinate2D, +{ use self::CurveCategory::*; // The algorithm here is to divide the curve into two. We'll either find a smaller curve with a loop or split the curve in the middle of the loop // If we split in the middle of the loop, we use the bezier clipping algorithm to find where the two sides intersect - let (left, right) = (curve.subsection(0.0, 0.5), curve.subsection(0.5, 1.0)); + let (left, right) = (curve.subsection(0.0, 0.5), curve.subsection(0.5, 1.0)); let (left_type, right_type) = (left.characteristics(), right.characteristics()); match (left_type, right_type) { @@ -40,7 +47,7 @@ where C::Point: Coordinate+Coordinate2D { (Loop, _) => { // Loop is in the left side find_intersection_point_in_loop(left, accuracy) - }, + } (_, Loop) => { // Loop is in the right side @@ -51,22 +58,26 @@ where C::Point: Coordinate+Coordinate2D { // Can find the intersection by using the clipping algorithm let intersections = curve_intersects_curve_clip(&left, &right, accuracy); - if intersections.len() == 0 && left.start_point().is_near_to(&right.end_point(), accuracy) { + if intersections.is_empty() + && left.start_point().is_near_to(&right.end_point(), accuracy) + { // Didn't find an intersection but the left and right curves start and end at the same position return Some((left.t_for_t(0.0), right.t_for_t(1.0))); } - test_assert!(intersections.len() != 0); + test_assert!(!intersections.is_empty()); if intersections.len() == 1 { // Only found a single intersection - intersections.into_iter().nth(0) + intersections + .into_iter() + .next() .map(|(t1, t2)| (left.t_for_t(t1), right.t_for_t(t2))) } else { // Intersection may include the point between the left and right curves (ignore any point that's at t=1 on the left or t=0 on the right) - intersections.into_iter() - .filter(|(t1, t2)| *t1 < 1.0 && *t2 > 0.0) - .nth(0) + intersections + .into_iter() + .find(|(t1, t2)| *t1 < 1.0 && *t2 > 0.0) .map(|(t1, t2)| (left.t_for_t(t1), right.t_for_t(t2))) } } diff --git a/src/bezier/length.rs b/src/bezier/length.rs index 7d58e166..caa3cacf 100644 --- a/src/bezier/length.rs +++ b/src/bezier/length.rs @@ -1,26 +1,24 @@ -use super::curve::*; -use super::section::*; -use crate::geo::*; +use super::curve::BezierCurve; +use super::section::CurveSection; +use crate::geo::Coordinate; /// /// Returns the length of the control polygon for a bezier curve /// pub fn control_polygon_length(curve: &Curve) -> f64 { - let p1 = curve.start_point(); - let (p2, p3) = curve.control_points(); - let p4 = curve.end_point(); + let p1 = curve.start_point(); + let (p2, p3) = curve.control_points(); + let p4 = curve.end_point(); - p1.distance_to(&p2) - + p2.distance_to(&p3) - + p3.distance_to(&p4) + p1.distance_to(&p2) + p2.distance_to(&p3) + p3.distance_to(&p4) } /// /// Returns the length of the chord of a bezier curve /// pub fn chord_length(curve: &Curve) -> f64 { - let p1 = curve.start_point(); - let p2 = curve.end_point(); + let p1 = curve.start_point(); + let p2 = curve.end_point(); p1.distance_to(&p2) } @@ -35,33 +33,34 @@ pub fn curve_length(curve: &Curve, max_error: f64) -> f64 { /// /// Computes the length of a section of a bezier curve /// -fn section_length<'a, Curve>(section: CurveSection<'a, Curve>, max_error: f64) -> f64 +fn section_length(section: CurveSection, max_error: f64) -> f64 where -Curve: BezierCurve { + Curve: BezierCurve, +{ // This algorithm is described in Graphics Gems V IV.7 // The MIN_ERROR guards against cases where the length of a section fails to converge for some reason const MIN_ERROR: f64 = 1e-12; // Algorithm is recursive, but we use a vec as a stack to avoid overflowing (and to make the number of iterations easy to count) - let mut waiting = vec![(section, max_error)]; - let mut total_length = 0.0; + let mut waiting = vec![(section, max_error)]; + let mut total_length = 0.0; while let Some((section, max_error)) = waiting.pop() { // Estimate the error for the length of the curve - let polygon_length = control_polygon_length(§ion); - let chord_length = chord_length(§ion); + let polygon_length = control_polygon_length(§ion); + let chord_length = chord_length(§ion); - let error = (polygon_length - chord_length) * (polygon_length - chord_length); + let error = (polygon_length - chord_length) * (polygon_length - chord_length); // If the error is low enough, return the estimated length if error < max_error || max_error <= MIN_ERROR { - total_length += (2.0*chord_length + 2.0*polygon_length)/4.0; + total_length += (2.0 * chord_length + 2.0 * polygon_length) / 4.0; } else { // Subdivide the curve (each half has half the error tolerance) - let left = section.subsection(0.0, 0.5); - let right = section.subsection(0.5, 1.0); - let subsection_error = max_error / 2.0; + let left = section.subsection(0.0, 0.5); + let right = section.subsection(0.5, 1.0); + let subsection_error = max_error / 2.0; waiting.push((left, subsection_error)); waiting.push((right, subsection_error)); diff --git a/src/bezier/mod.rs b/src/bezier/mod.rs index 2de04d26..fbb98250 100644 --- a/src/bezier/mod.rs +++ b/src/bezier/mod.rs @@ -1,65 +1,65 @@ //! //! # Routines for describing, querying and manipulating Bezier curves -//! +//! //! Bezier curves are described by types that implement the `BezierCurve` trait, as a start point, an end point //! and two control points. The `Curve` type is provided as a base implementation but as with the other traits, //! the primary trait can be implemented on any suitable data structure. `BezierCurveFactory` is provided for //! types that can create new instances of themselves. -//! +//! //! Even for types that don't support the factory method, the `section()` method can be used to represent curve //! subsections efficiently. -//! +//! //! The `fit_curve()` function provides a way to fit a series of Bezier curves to one or more points using a //! least-mean-squared algorithm. -//! +//! //! The various `curve_intersects_X()` functions provide ways to determine where a curve meets another kind //! of object. //! -mod curve; -mod section; mod basis; -mod subdivide; -mod derivative; -mod tangent; -mod normal; mod bounds; +mod characteristics; +mod curve; mod deform; +mod derivative; +mod distort; mod fit; +mod intersection; +mod length; +mod normal; mod offset; mod offset_lms; mod offset_scaling; +mod overlaps; mod search; +mod section; mod solve; -mod overlaps; -mod intersection; -mod characteristics; -mod length; +mod subdivide; +mod tangent; mod walk; -mod distort; pub mod path; -pub use self::curve::*; -pub use self::section::*; pub use self::basis::*; -pub use self::subdivide::*; -pub use self::derivative::*; -pub use self::tangent::*; -pub use self::normal::*; pub use self::bounds::*; +pub use self::characteristics::*; +pub use self::curve::*; pub use self::deform::*; +pub use self::derivative::*; +pub use self::distort::*; pub use self::fit::*; +pub use self::intersection::*; +pub use self::length::*; +pub use self::normal::*; pub use self::offset::*; pub use self::offset_lms::*; pub use self::offset_scaling::*; +pub use self::overlaps::*; pub use self::search::*; +pub use self::section::*; pub use self::solve::*; -pub use self::overlaps::*; -pub use self::intersection::*; -pub use self::characteristics::*; -pub use self::length::*; +pub use self::subdivide::*; +pub use self::tangent::*; pub use self::walk::*; -pub use self::distort::*; pub use super::geo::*; diff --git a/src/bezier/normal.rs b/src/bezier/normal.rs index 966047c1..7bfd2fb0 100644 --- a/src/bezier/normal.rs +++ b/src/bezier/normal.rs @@ -1,13 +1,13 @@ -use super::curve::*; -use super::basis::*; -use super::derivative::*; -use super::super::geo::*; +use super::super::geo::{Coordinate, Coordinate2D}; +use super::basis::{de_casteljau3, de_casteljau4}; +use super::curve::BezierCurve; +use super::derivative::derivative4; // TODO: normalize should be a trait associated with coordinate rather than bezier curves (move outwards) /// /// Changes a point and its tangent into a normal -/// +/// pub trait Normalize { /// Computes the normal at a point, given its tangent fn to_normal(point: &Self, tangent: &Self) -> Vec; @@ -54,8 +54,8 @@ impl Normalize for Coordinate3D { /// /// Trait implemented by bezier curves where we can compute the normal -/// -pub trait NormalCurve : BezierCurve { +/// +pub trait NormalCurve: BezierCurve { /// /// Computes the tangent vector to the curve at the specified t value /// @@ -78,47 +78,47 @@ pub trait NormalCurve : BezierCurve { } impl NormalCurve for Curve -where Curve::Point: Normalize { +where + Curve::Point: Normalize, +{ fn tangent_at_pos(&self, t: f64) -> Curve::Point { // Extract the points that make up this curve - let w1 = self.start_point(); - let (w2, w3) = self.control_points(); - let w4 = self.end_point(); + let w1 = self.start_point(); + let (w2, w3) = self.control_points(); + let w4 = self.end_point(); - // If w1 == w2 or w3 == w4 there will be an anomaly at t=0.0 and t=1.0 + // If w1 == w2 or w3 == w4 there will be an anomaly at t=0.0 and t=1.0 // (it's probably mathematically correct to say there's no tangent at these points but the result is surprising and probably useless in a practical sense) - let t = if t == 0.0 { f64::EPSILON } else { t }; - let t = if t == 1.0 { 1.0-f64::EPSILON } else { t }; + let t = if t == 0.0 { f64::EPSILON } else { t }; + let t = if t == 1.0 { 1.0 - f64::EPSILON } else { t }; // Get the deriviative let (d1, d2, d3) = derivative4(w1, w2, w3, w4); // Get the tangent and the point at the specified t value - let tangent = de_casteljau3(t, d1, d2, d3); - - tangent + de_casteljau3(t, d1, d2, d3) } fn normal_at_pos(&self, t: f64) -> Curve::Point { // Extract the points that make up this curve - let w1 = self.start_point(); - let (w2, w3) = self.control_points(); - let w4 = self.end_point(); + let w1 = self.start_point(); + let (w2, w3) = self.control_points(); + let w4 = self.end_point(); - // If w1 == w2 or w3 == w4 there will be an anomaly at t=0.0 and t=1.0 + // If w1 == w2 or w3 == w4 there will be an anomaly at t=0.0 and t=1.0 // (it's probably mathematically correct to say there's no normal at these points but the result is surprising and probably useless in a practical sense) - let t = if t == 0.0 { f64::EPSILON } else { t }; - let t = if t == 1.0 { 1.0-f64::EPSILON } else { t }; + let t = if t == 0.0 { f64::EPSILON } else { t }; + let t = if t == 1.0 { 1.0 - f64::EPSILON } else { t }; // Get the deriviative let (d1, d2, d3) = derivative4(w1, w2, w3, w4); // Get the tangent and the point at the specified t value - let point = de_casteljau4(t, w1, w2, w3, w4); - let tangent = de_casteljau3(t, d1, d2, d3); + let point = de_casteljau4(t, w1, w2, w3, w4); + let tangent = de_casteljau3(t, d1, d2, d3); // Compute the normal - let normal = Curve::Point::to_normal(&point, &tangent); + let normal = Curve::Point::to_normal(&point, &tangent); Curve::Point::from_components(&normal) } diff --git a/src/bezier/offset.rs b/src/bezier/offset.rs index a98db123..1ea9d560 100644 --- a/src/bezier/offset.rs +++ b/src/bezier/offset.rs @@ -1,13 +1,15 @@ -use super::curve::*; -use super::normal::*; -use super::offset_scaling::*; -use super::super::geo::*; +use super::super::geo::Coordinate2D; +use super::curve::BezierCurveFactory; +use super::normal::{NormalCurve, Normalize}; +use super::offset_scaling::offset_scaling; /// /// Computes a series of curves that approximate an offset curve from the specified origin curve. /// pub fn offset(curve: &Curve, initial_offset: f64, final_offset: f64) -> Vec -where Curve: BezierCurveFactory+NormalCurve, - Curve::Point: Normalize+Coordinate2D { +where + Curve: BezierCurveFactory + NormalCurve, + Curve::Point: Normalize + Coordinate2D, +{ offset_scaling(curve, initial_offset, final_offset) } diff --git a/src/bezier/offset_lms.rs b/src/bezier/offset_lms.rs index 65f75e5d..fa3c8693 100644 --- a/src/bezier/offset_lms.rs +++ b/src/bezier/offset_lms.rs @@ -1,10 +1,10 @@ -use super::fit::*; -use super::curve::*; -use super::normal::*; -use super::characteristics::*; -use crate::geo::*; +use super::characteristics::{features_for_curve, CurveFeatures}; +use super::curve::BezierCurveFactory; +use super::fit::fit_curve_cubic; +use super::normal::{NormalCurve, Normalize}; +use crate::geo::{Coordinate, Coordinate2D}; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; use std::iter; /// @@ -14,18 +14,40 @@ use std::iter; /// produce good results). Too few subdivisions can result in flat sections in the curve, and too many can /// result in artifacts caused by overfitting. /// -pub fn offset_lms_sampling(curve: &Curve, normal_offset_for_t: NormalOffsetFn, tangent_offset_for_t: TangentOffsetFn, subdivisions: u32, max_error: f64) -> Option> -where Curve: BezierCurveFactory+NormalCurve, - Curve::Point: Normalize+Coordinate2D, - NormalOffsetFn: Fn(f64) -> f64, - TangentOffsetFn: Fn(f64) -> f64 { - if subdivisions < 2 { return None; } +pub fn offset_lms_sampling( + curve: &Curve, + normal_offset_for_t: NormalOffsetFn, + tangent_offset_for_t: TangentOffsetFn, + subdivisions: u32, + max_error: f64, +) -> Option> +where + Curve: BezierCurveFactory + NormalCurve, + Curve::Point: Normalize + Coordinate2D, + NormalOffsetFn: Fn(f64) -> f64, + TangentOffsetFn: Fn(f64) -> f64, +{ + if subdivisions < 2 { + return None; + } // Subdivide the curve by its major features - let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) { - CurveFeatures::DoubleInflectionPoint(t1, t2) => { - let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; - let t2 = if t2 > 0.9999 { 1.0 } else if t2 < 0.0001 { 0.0 } else { t2 }; + let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) { + CurveFeatures::DoubleInflectionPoint(t1, t2) => { + let t1 = if t1 > 0.9999 { + 1.0 + } else if t1 < 0.0001 { + 0.0 + } else { + t1 + }; + let t2 = if t2 > 0.9999 { + 1.0 + } else if t2 < 0.0001 { + 0.0 + } else { + t2 + }; if t2 > t1 { smallvec![(0.0, t1), (t1, t2), (t2, 1.0)] @@ -35,9 +57,21 @@ where Curve: BezierCurveFactory+NormalCurve, } CurveFeatures::Loop(t1, t3) => { - let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; - let t3 = if t3 > 0.9999 { 1.0 } else if t3 < 0.0001 { 0.0 } else { t3 }; - let t2 = (t1+t3)/2.0; + let t1 = if t1 > 0.9999 { + 1.0 + } else if t1 < 0.0001 { + 0.0 + } else { + t1 + }; + let t3 = if t3 > 0.9999 { + 1.0 + } else if t3 < 0.0001 { + 0.0 + } else { + t3 + }; + let t2 = (t1 + t3) / 2.0; if t3 > t1 { smallvec![(0.0, t1), (t1, t2), (t2, t3), (t3, 1.0)] @@ -54,34 +88,44 @@ where Curve: BezierCurveFactory+NormalCurve, } } - _ => { smallvec![(0.0, 1.0)] } + _ => { + smallvec![(0.0, 1.0)] + } }; // Each section is subdivided in turn subdivisions times to produce a set of sample points to fit against - let sections = sections.into_iter() + let sections = sections + .into_iter() .filter(|(t1, t2)| t1 != t2) .flat_map(|(t1, t2)| { - let step = (t2-t1)/(subdivisions as f64); - (0..subdivisions).into_iter().map(move |x| t1 + step * (x as f64)) + let step = (t2 - t1) / (subdivisions as f64); + (0..subdivisions) + .into_iter() + .map(move |x| t1 + step * (x as f64)) }) .chain(iter::once(1.0)); // Take a sample at each point - let sample_points = sections + let sample_points = sections .map(|t| { - let original_point = curve.point_at_pos(t); - let unit_tangent = curve.tangent_at_pos(t).to_unit_vector(); - let unit_normal = Curve::Point::to_normal(&original_point, &unit_tangent); - let unit_normal = Curve::Point::from_components(&unit_normal); - let normal_offset = normal_offset_for_t(t); - let tangent_offset = tangent_offset_for_t(t); + let original_point = curve.point_at_pos(t); + let unit_tangent = curve.tangent_at_pos(t).to_unit_vector(); + let unit_normal = Curve::Point::to_normal(&original_point, &unit_tangent); + let unit_normal = Curve::Point::from_components(&unit_normal); + let normal_offset = normal_offset_for_t(t); + let tangent_offset = tangent_offset_for_t(t); original_point + (unit_normal * normal_offset) + (unit_tangent * tangent_offset) }) .collect::>(); // Generate a curve using the sample points - let start_tangent = curve.tangent_at_pos(0.0).to_unit_vector(); - let end_tangent = curve.tangent_at_pos(1.0).to_unit_vector() * -1.0; - Some(fit_curve_cubic(&sample_points, &start_tangent, &end_tangent, max_error)) + let start_tangent = curve.tangent_at_pos(0.0).to_unit_vector(); + let end_tangent = curve.tangent_at_pos(1.0).to_unit_vector() * -1.0; + Some(fit_curve_cubic( + &sample_points, + &start_tangent, + &end_tangent, + max_error, + )) } diff --git a/src/bezier/offset_scaling.rs b/src/bezier/offset_scaling.rs index 8753e9a6..7068fdf8 100644 --- a/src/bezier/offset_scaling.rs +++ b/src/bezier/offset_scaling.rs @@ -1,30 +1,32 @@ -use super::curve::*; -use super::normal::*; -use super::characteristics::*; -use crate::geo::*; -use crate::line::*; -use crate::bezier::{CurveSection}; - -use smallvec::*; -use itertools::*; +use super::characteristics::{ + characterize_curve, features_for_curve, CurveCategory, CurveFeatures, +}; +use super::curve::{BezierCurve, BezierCurveFactory}; +use super::normal::{NormalCurve, Normalize}; +use crate::bezier::CurveSection; +use crate::geo::{Coordinate, Coordinate2D}; +use crate::line::ray_intersects_ray; + +use itertools::Itertools; +use smallvec::{smallvec, SmallVec}; // This is loosely based on the algorithm described at: https://pomax.github.io/bezierinfo/#offsetting, // with numerous changes to allow for variable-width offsets and consistent behaviour (in particular, // a much more reliable method of subdividing the curve) -// +// // This algorithm works by subdividing the original curve into arches. We use the characteristics of the // curve to do this: by subdividing a curve at its inflection point, we turn it into a series of arches. -// Arches have a focal point that the normal vectors along the curve roughly converge to, so we can -// scale around this point to generate an approximate offset curve (every point of the curve will move +// Arches have a focal point that the normal vectors along the curve roughly converge to, so we can +// scale around this point to generate an approximate offset curve (every point of the curve will move // away from the focal point along its normal axis). // // As the focal point is approximate, using the start and end points to compute its location ensures that -// the offset is exact at the start and end of the curve. +// the offset is exact at the start and end of the curve. // // Edge cases: curves with inflection points at the start or end, arches where the normal vectors at the // start and end are in parallel. // -// Not all arches have normal vectors that converge (close to) a focal point. We can spot these quickly +// Not all arches have normal vectors that converge (close to) a focal point. We can spot these quickly // because the focal point of any two points is in general not equidistant from those two points: this // also results in uneven scaling of the start and end points. // @@ -41,13 +43,27 @@ use itertools::*; /// errors, especially if the initial and final offsets are very different from one another. /// pub fn offset_scaling(curve: &Curve, initial_offset: f64, final_offset: f64) -> Vec -where Curve: BezierCurveFactory+NormalCurve, - Curve::Point: Normalize+Coordinate2D { +where + Curve: BezierCurveFactory + NormalCurve, + Curve::Point: Normalize + Coordinate2D, +{ // Split at the location of any features the curve might have - let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) { - CurveFeatures::DoubleInflectionPoint(t1, t2) => { - let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; - let t2 = if t2 > 0.9999 { 1.0 } else if t2 < 0.0001 { 0.0 } else { t2 }; + let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) { + CurveFeatures::DoubleInflectionPoint(t1, t2) => { + let t1 = if t1 > 0.9999 { + 1.0 + } else if t1 < 0.0001 { + 0.0 + } else { + t1 + }; + let t2 = if t2 > 0.9999 { + 1.0 + } else if t2 < 0.0001 { + 0.0 + } else { + t2 + }; if t2 > t1 { smallvec![(0.0, t1), (t1, t2), (t2, 1.0)] @@ -57,9 +73,21 @@ where Curve: BezierCurveFactory+NormalCurve, } CurveFeatures::Loop(t1, t3) => { - let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; - let t3 = if t3 > 0.9999 { 1.0 } else if t3 < 0.0001 { 0.0 } else { t3 }; - let t2 = (t1+t3)/2.0; + let t1 = if t1 > 0.9999 { + 1.0 + } else if t1 < 0.0001 { + 0.0 + } else { + t1 + }; + let t3 = if t3 > 0.9999 { + 1.0 + } else if t3 < 0.0001 { + 0.0 + } else { + t3 + }; + let t2 = (t1 + t3) / 2.0; if t3 > t1 { smallvec![(0.0, t1), (t1, t2), (t2, t3), (t3, 1.0)] @@ -76,21 +104,28 @@ where Curve: BezierCurveFactory+NormalCurve, } } - _ => { smallvec![(0.0, 1.0)] } + _ => { + smallvec![(0.0, 1.0)] + } }; - let sections = sections.into_iter() + let sections = sections + .into_iter() .filter(|(t1, t2)| t1 != t2) .map(|(t1, t2)| curve.section(t1, t2)) .collect::>(); // Offset the set of curves that we retrieved - let offset_distance = final_offset-initial_offset; + let offset_distance = final_offset - initial_offset; - sections.into_iter() + sections + .into_iter() .flat_map(|section| { // Compute the offsets for this section (TODO: use the curve length, not the t values) - let (t1, t2) = section.original_curve_t_values(); - let (offset1, offset2) = (t1*offset_distance+initial_offset, t2*offset_distance+initial_offset); + let (t1, t2) = section.original_curve_t_values(); + let (offset1, offset2) = ( + t1 * offset_distance + initial_offset, + t2 * offset_distance + initial_offset, + ); subdivide_offset(§ion, offset1, offset2, 0) }) @@ -100,71 +135,78 @@ where Curve: BezierCurveFactory+NormalCurve, /// /// Attempts a simple offset of a curve, and subdivides it if the midpoint is too far away from the expected distance /// -fn subdivide_offset<'a, CurveIn, CurveOut>(curve: &CurveSection<'a, CurveIn>, initial_offset: f64, final_offset: f64, depth: usize) -> SmallVec<[CurveOut; 2]> -where CurveIn: NormalCurve+BezierCurve, - CurveOut: BezierCurveFactory, - CurveIn::Point: Coordinate2D+Normalize { +fn subdivide_offset( + curve: &CurveSection, + initial_offset: f64, + final_offset: f64, + depth: usize, +) -> SmallVec<[CurveOut; 2]> +where + CurveIn: NormalCurve + BezierCurve, + CurveOut: BezierCurveFactory, + CurveIn::Point: Coordinate2D + Normalize, +{ const MAX_DEPTH: usize = 5; // Fetch the original points - let start = curve.start_point(); - let end = curve.end_point(); + let start = curve.start_point(); + let end = curve.end_point(); // The normals at the start and end of the curve define the direction we should move in - let normal_start = curve.normal_at_pos(0.0); - let normal_end = curve.normal_at_pos(1.0); - let normal_start = normal_start.to_unit_vector(); - let normal_end = normal_end.to_unit_vector(); + let normal_start = curve.normal_at_pos(0.0); + let normal_end = curve.normal_at_pos(1.0); + let normal_start = normal_start.to_unit_vector(); + let normal_end = normal_end.to_unit_vector(); // If we can we want to scale the control points around the intersection of the normals - let intersect_point = ray_intersects_ray(&(start, start+normal_start), &(end, end+normal_end)); + let intersect_point = + ray_intersects_ray(&(start, start + normal_start), &(end, end + normal_end)); - if intersect_point.is_none() { - if characterize_curve(curve) != CurveCategory::Linear && depth < MAX_DEPTH { - // Collinear normals - let divide_point = 0.5; + if intersect_point.is_none() + && characterize_curve(curve) != CurveCategory::Linear + && depth < MAX_DEPTH + { + // Collinear normals + let divide_point = 0.5; - let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point; - let left_curve = curve.subsection(0.0, divide_point); - let right_curve = curve.subsection(divide_point, 1.0); + let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point; + let left_curve = curve.subsection(0.0, divide_point); + let right_curve = curve.subsection(divide_point, 1.0); - let left_offset = subdivide_offset(&left_curve, initial_offset, mid_offset, depth+1); - let right_offset = subdivide_offset(&right_curve, mid_offset, final_offset, depth+1); + let left_offset = subdivide_offset(&left_curve, initial_offset, mid_offset, depth + 1); + let right_offset = subdivide_offset(&right_curve, mid_offset, final_offset, depth + 1); - return left_offset.into_iter() - .chain(right_offset) - .collect(); - } + return left_offset.into_iter().chain(right_offset).collect(); } if let Some(intersect_point) = intersect_point { // Subdivide again if the intersection point is too close to one or other of the normals - let start_distance = intersect_point.distance_to(&start); - let end_distance = intersect_point.distance_to(&end); - let distance_ratio = start_distance.min(end_distance) / start_distance.max(end_distance); + let start_distance = intersect_point.distance_to(&start); + let end_distance = intersect_point.distance_to(&end); + let distance_ratio = start_distance.min(end_distance) / start_distance.max(end_distance); // TODO: the closer to 1 this value is, the better the quality of the offset (0.99 produces good results) // but the number of subdivisions tends to be too high: we need to find either a way to generate a better offset // curve for an arch with a non-centered intersection point, or a better way to pick the subdivision point if distance_ratio < 0.995 && depth < MAX_DEPTH { // Try to subdivide at the curve's extremeties - let mut extremeties = curve.find_extremities(); + let mut extremeties = curve.find_extremities(); extremeties.retain(|item| item > &0.01 && item < &0.99); - if extremeties.len() == 0 || true { + if extremeties.is_empty() || true { // No extremeties (or they're all too close to the edges) - let divide_point = 0.5; + let divide_point = 0.5; - let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point; - let left_curve = curve.subsection(0.0, divide_point); - let right_curve = curve.subsection(divide_point, 1.0); + let mid_offset = initial_offset + (final_offset - initial_offset) * divide_point; + let left_curve = curve.subsection(0.0, divide_point); + let right_curve = curve.subsection(divide_point, 1.0); - let left_offset = subdivide_offset(&left_curve, initial_offset, mid_offset, depth+1); - let right_offset = subdivide_offset(&right_curve, mid_offset, final_offset, depth+1); + let left_offset = + subdivide_offset(&left_curve, initial_offset, mid_offset, depth + 1); + let right_offset = + subdivide_offset(&right_curve, mid_offset, final_offset, depth + 1); - left_offset.into_iter() - .chain(right_offset) - .collect() + left_offset.into_iter().chain(right_offset).collect() } else { let mut extremeties = extremeties; extremeties.insert(0, 0.0); @@ -174,22 +216,33 @@ where CurveIn: NormalCurve+BezierCurve, .into_iter() .tuple_windows() .flat_map(|(t1, t2)| { - let subsection = curve.subsection(t1, t2); - let offset1 = initial_offset + (final_offset - initial_offset) * t1; - let offset2 = initial_offset + (final_offset - initial_offset) * t2; - let res = subdivide_offset(&subsection, offset1, offset2, depth+1); - res + let subsection = curve.subsection(t1, t2); + let offset1 = initial_offset + (final_offset - initial_offset) * t1; + let offset2 = initial_offset + (final_offset - initial_offset) * t2; + subdivide_offset(&subsection, offset1, offset2, depth + 1) }) .collect() } } else { // Event intersection point - smallvec![offset_by_scaling(curve, initial_offset, final_offset, intersect_point, normal_start, normal_end)] + smallvec![offset_by_scaling( + curve, + initial_offset, + final_offset, + intersect_point, + normal_start, + normal_end + )] } - } else { // No intersection point - smallvec![offset_by_moving(curve, initial_offset, final_offset, normal_start, normal_end)] + smallvec![offset_by_moving( + curve, + initial_offset, + final_offset, + normal_start, + normal_end + )] } } @@ -197,27 +250,37 @@ where CurveIn: NormalCurve+BezierCurve, /// Offsets a curve by scaling around a central point /// #[inline] -fn offset_by_scaling(curve: &CurveIn, initial_offset: f64, final_offset: f64, intersect_point: CurveIn::Point, unit_normal_start: CurveIn::Point, unit_normal_end: CurveIn::Point) -> CurveOut -where CurveIn: NormalCurve+BezierCurve, - CurveOut: BezierCurveFactory, - CurveIn::Point: Coordinate2D+Normalize { - let start = curve.start_point(); - let end = curve.end_point(); - let (cp1, cp2) = curve.control_points(); +fn offset_by_scaling( + curve: &CurveIn, + initial_offset: f64, + final_offset: f64, + intersect_point: CurveIn::Point, + unit_normal_start: CurveIn::Point, + unit_normal_end: CurveIn::Point, +) -> CurveOut +where + CurveIn: NormalCurve + BezierCurve, + CurveOut: BezierCurveFactory, + CurveIn::Point: Coordinate2D + Normalize, +{ + let start = curve.start_point(); + let end = curve.end_point(); + let (cp1, cp2) = curve.control_points(); // The control points point at an intersection point. We want to scale around this point so that start and end wind up at the appropriate offsets - let new_start = start + (unit_normal_start * initial_offset); - let new_end = end + (unit_normal_end * final_offset); + let new_start = start + (unit_normal_start * initial_offset); + let new_end = end + (unit_normal_end * final_offset); - let start_scale = (intersect_point.distance_to(&new_start))/(intersect_point.distance_to(&start)); - let end_scale = (intersect_point.distance_to(&new_end))/(intersect_point.distance_to(&end)); + let start_scale = + (intersect_point.distance_to(&new_start)) / (intersect_point.distance_to(&start)); + let end_scale = (intersect_point.distance_to(&new_end)) / (intersect_point.distance_to(&end)); // When the scale is changing, the control points are effectively 1/3rd and 2/3rds of the way along the curve - let cp1_scale = (end_scale - start_scale) * (1.0/3.0) + start_scale; - let cp2_scale = (end_scale - start_scale) * (2.0/3.0) + start_scale; + let cp1_scale = (end_scale - start_scale) * (1.0 / 3.0) + start_scale; + let cp2_scale = (end_scale - start_scale) * (2.0 / 3.0) + start_scale; - let new_cp1 = ((cp1-intersect_point) * cp1_scale) + intersect_point; - let new_cp2 = ((cp2-intersect_point) * cp2_scale) + intersect_point; + let new_cp1 = ((cp1 - intersect_point) * cp1_scale) + intersect_point; + let new_cp2 = ((cp2 - intersect_point) * cp2_scale) + intersect_point; CurveOut::from_points(new_start, (new_cp1, new_cp2), new_end) } @@ -226,19 +289,27 @@ where CurveIn: NormalCurve+BezierCurve, /// Given a curve where the start and end normals do not intersect at a point, calculates the offset (by moving the start and end points along the normal) /// #[inline] -fn offset_by_moving(curve: &CurveIn, initial_offset: f64, final_offset: f64, unit_normal_start: CurveIn::Point, unit_normal_end: CurveIn::Point) -> CurveOut -where CurveIn: NormalCurve+BezierCurve, - CurveOut: BezierCurveFactory, - CurveIn::Point: Coordinate2D+Normalize { - let start = curve.start_point(); - let end = curve.end_point(); - let (cp1, cp2) = curve.control_points(); +fn offset_by_moving( + curve: &CurveIn, + initial_offset: f64, + final_offset: f64, + unit_normal_start: CurveIn::Point, + unit_normal_end: CurveIn::Point, +) -> CurveOut +where + CurveIn: NormalCurve + BezierCurve, + CurveOut: BezierCurveFactory, + CurveIn::Point: Coordinate2D + Normalize, +{ + let start = curve.start_point(); + let end = curve.end_point(); + let (cp1, cp2) = curve.control_points(); // Offset start & end by the specified amounts to create the first approximation of a curve - let new_start = start + (unit_normal_start * initial_offset); - let new_cp1 = cp1 + (unit_normal_start * initial_offset); - let new_cp2 = cp2 + (unit_normal_end * final_offset); - let new_end = end + (unit_normal_end * final_offset); + let new_start = start + (unit_normal_start * initial_offset); + let new_cp1 = cp1 + (unit_normal_start * initial_offset); + let new_cp2 = cp2 + (unit_normal_end * final_offset); + let new_end = end + (unit_normal_end * final_offset); CurveOut::from_points(new_start, (new_cp1, new_cp2), new_end) } diff --git a/src/bezier/overlaps.rs b/src/bezier/overlaps.rs index 96ab6955..243be870 100644 --- a/src/bezier/overlaps.rs +++ b/src/bezier/overlaps.rs @@ -1,20 +1,25 @@ -use super::curve::*; -use super::super::geo::*; -use super::super::line::*; -use super::super::consts::*; +use super::super::consts::{SMALL_DISTANCE, SMALL_T_DISTANCE}; +use super::super::geo::{Coordinate, Coordinate2D}; +use super::super::line::Line2D; +use super::curve::BezierCurve; /// /// If `curve2` overlaps `curve1`, returns two sets of `t` values (those for `curve1` and those for `curve2`) /// -pub fn overlapping_region(curve1: &C1, curve2: &C2) -> Option<((f64, f64), (f64, f64))> -where C1::Point: Coordinate+Coordinate2D, - C2: BezierCurve { +pub fn overlapping_region( + curve1: &C1, + curve2: &C2, +) -> Option<((f64, f64), (f64, f64))> +where + C1::Point: Coordinate + Coordinate2D, + C2: BezierCurve, +{ let mut c2_t1 = 0.0; let mut c2_t2 = 1.0; // The start and end points of curve1 should be on curve2 - let c2_start = curve2.start_point(); - let c2_end = curve2.end_point(); + let c2_start = curve2.start_point(); + let c2_end = curve2.end_point(); let c1_t1 = if let Some(t) = curve1.t_for_point(&c2_start) { // Start point is on the curve @@ -41,21 +46,24 @@ where C1::Point: Coordinate+Coordinate2D, }; // If we just found one point where the curve overlaps, then say that they didn't - if (c1_t1-c1_t2).abs() < SMALL_T_DISTANCE || (c2_t1-c2_t2).abs() < SMALL_T_DISTANCE { + if (c1_t1 - c1_t2).abs() < SMALL_T_DISTANCE || (c2_t1 - c2_t2).abs() < SMALL_T_DISTANCE { return None; } // If curve1 and curve2 are collinear - two overlapping lines - we've already got the results (and the control points will differ anyway) #[inline] fn is_collinear(p: &P, &(a, b, c): &(f64, f64, f64)) -> bool { - (a*p.x() + b*p.y() + c).abs() < SMALL_DISTANCE + (a * p.x() + b * p.y() + c).abs() < SMALL_DISTANCE } - let coeff = (curve1.start_point(), curve1.end_point()).coefficients(); - let (c1_cp1, c1_cp2) = curve1.control_points(); + let coeff = (curve1.start_point(), curve1.end_point()).coefficients(); + let (c1_cp1, c1_cp2) = curve1.control_points(); - if is_collinear(&c1_cp1, &coeff) && is_collinear(&c1_cp2, &coeff) - && is_collinear(&curve2.start_point(), &coeff) && is_collinear(&curve2.end_point(), &coeff) { + if is_collinear(&c1_cp1, &coeff) + && is_collinear(&c1_cp2, &coeff) + && is_collinear(&curve2.start_point(), &coeff) + && is_collinear(&curve2.end_point(), &coeff) + { let (c2_cp1, c2_cp2) = curve2.control_points(); if is_collinear(&c2_cp1, &coeff) && is_collinear(&c2_cp2, &coeff) { @@ -66,13 +74,15 @@ where C1::Point: Coordinate+Coordinate2D, // Start and end points match at t1, t2 #[inline] fn close_enough(p1: &P, p2: &P) -> bool { - p1.is_near_to(&p2, SMALL_DISTANCE) + p1.is_near_to(p2, SMALL_DISTANCE) } // Get the control points for the two curves #[inline] fn control_points(curve: &C, t1: f64, t2: f64) -> (C::Point, C::Point) - where C::Point: Coordinate+Coordinate2D, { + where + C::Point: Coordinate + Coordinate2D, + { if t2 < t1 { let (cp2, cp1) = curve.section(t2, t1).control_points(); (cp1, cp2) diff --git a/src/bezier/path/algorithms/fill_concave.rs b/src/bezier/path/algorithms/fill_concave.rs index f4b8e28a..c07e73f5 100644 --- a/src/bezier/path/algorithms/fill_concave.rs +++ b/src/bezier/path/algorithms/fill_concave.rs @@ -1,10 +1,10 @@ -use super::fill_convex::*; -use super::fill_settings::*; +use super::fill_convex::{trace_outline_convex, trace_outline_convex_partial, RayCollision}; +use super::fill_settings::FillSettings; -use crate::geo::*; -use crate::line::*; -use crate::bezier::*; -use crate::bezier::path::*; +use crate::bezier::path::{path_remove_interior_points, BezierPathFactory}; +use crate::bezier::{fit_curve, BezierCurve, Curve}; +use crate::geo::{Coordinate, Coordinate2D}; +use crate::line::{line_intersects_ray, Line}; use std::f64; @@ -17,14 +17,14 @@ enum ConcaveItem { Edge(Item), /// Intersection with an edge detected in an earlier raycasting operation - SelfIntersection(usize) + SelfIntersection(usize), } -impl Into> for ConcaveItem { - fn into(self) -> Option { - match self { - ConcaveItem::Edge(item) => Some(item), - ConcaveItem::SelfIntersection(_) => None +impl From> for Option { + fn from(ci: ConcaveItem) -> Self { + match ci { + ConcaveItem::Edge(item) => Some(item), + ConcaveItem::SelfIntersection(_) => None, } } } @@ -33,37 +33,42 @@ impl Into> for ConcaveItem { /// Represents a long edge that we want to raycast from /// struct LongEdge { - start: Coord, - end: Coord, - edge_index: (usize, usize), - ray_collided: bool + start: Coord, + end: Coord, + edge_index: (usize, usize), + ray_collided: bool, } /// /// Retrieves the 'long' edges from a set of edges returned by a raycast tracing operation /// -fn find_long_edges(edges: &[RayCollision], edge_min_len_squared: f64) -> Vec> -where Coord: Coordinate+Coordinate2D { +fn find_long_edges( + edges: &[RayCollision], + edge_min_len_squared: f64, +) -> Vec> +where + Coord: Coordinate + Coordinate2D, +{ // Find the edges where we need to cast extra rays - let mut long_edges = vec![]; + let mut long_edges = vec![]; for edge_num in 0..edges.len() { // Get the length of this edge - let last_edge = if edge_num == 0 { + let last_edge = if edge_num == 0 { edges.len() - 1 } else { - edge_num-1 + edge_num - 1 }; - let edge_offset = edges[last_edge].position - edges[edge_num].position; - let edge_distance_squared = edge_offset.dot(&edge_offset); + let edge_offset = edges[last_edge].position - edges[edge_num].position; + let edge_distance_squared = edge_offset.dot(&edge_offset); // Add to the list of long edges if it's long enough to need further ray-casting if edge_distance_squared >= edge_min_len_squared { - long_edges.push(LongEdge { - start: edges[last_edge].position.clone(), - end: edges[edge_num].position.clone(), - edge_index: (last_edge, edge_num), - ray_collided: false + long_edges.push(LongEdge { + start: edges[last_edge].position, + end: edges[edge_num].position, + edge_index: (last_edge, edge_num), + ray_collided: false, }) } } @@ -75,9 +80,11 @@ where Coord: Coordinate+Coordinate2D { /// Determines if the 'to' position is further away from the 'center' position than the 'from' position /// fn ray_is_moving_outwards(center: &Coord, from: &Coord, to: &Coord) -> bool -where Coord: Coordinate+Coordinate2D { +where + Coord: Coordinate + Coordinate2D, +{ // Determine where the 'to' point is along this ray - let ray = (center.clone(), from.clone()); + let ray = (*center, *from); let pos = ray.pos_for_point(to); // Position will be > 1.0 if the 'to' position is further away that 'from' @@ -92,10 +99,16 @@ where Coord: Coordinate+Coordinate2D { /// If they're closer than the minimum size, we can remove the edge by moving all the points that were found on the other /// side into a line. /// -fn remove_small_gaps(center: &Coord, edges: &mut Vec>, long_edges: &mut Vec>, min_gap_size: f64) -where Coord: Coordinate+Coordinate2D { +fn remove_small_gaps( + center: &Coord, + edges: &mut [RayCollision], + long_edges: &mut Vec>, + min_gap_size: f64, +) where + Coord: Coordinate + Coordinate2D, +{ // To avoid calculating a lot of square roots, square the min gap size - let min_gap_sq = min_gap_size * min_gap_size; + let min_gap_sq = min_gap_size * min_gap_size; // List of long edges to remove after we've edited the points let mut long_edges_to_remove = vec![]; @@ -103,24 +116,30 @@ where Coord: Coordinate+Coordinate2D { // Inspect the 'long edges' as pairs (they need to be in order for this to work) for edge1_idx in 0..long_edges.len() { // Going to measure the distance between this edge and the following one - let edge2_idx = if edge1_idx < long_edges.len()-1 { edge1_idx + 1 } else { 0 }; - let edge1 = &long_edges[edge1_idx]; - let edge2 = &long_edges[edge2_idx]; + let edge2_idx = if edge1_idx < long_edges.len() - 1 { + edge1_idx + 1 + } else { + 0 + }; + let edge1 = &long_edges[edge1_idx]; + let edge2 = &long_edges[edge2_idx]; // Edge1 must be moving out from the center - if ray_is_moving_outwards(center, &edge1.start, &edge1.end) && !ray_is_moving_outwards(center, &edge2.start, &edge2.end) { + if ray_is_moving_outwards(center, &edge1.start, &edge1.end) + && !ray_is_moving_outwards(center, &edge2.start, &edge2.end) + { // Work out the gap between the start and the end of this gap - let start_pos = &edge1.start; - let end_pos = &edge2.end; - let offset = *end_pos - *start_pos; + let start_pos = &edge1.start; + let end_pos = &edge2.end; + let offset = *end_pos - *start_pos; let distance_sq = offset.dot(&offset); // If it's less than the min gap size, add it to the list of edges to remove if distance_sq <= min_gap_sq { // Move all the points between the two 'long' edges onto a line between the start and end point // Alternatively: could remove the points here to produce a smoother shape later on - let gap_line = (edge1.start.clone(), edge2.end.clone()); - let mut edge_num = edge1.edge_index.1; + let gap_line = (edge1.start, edge2.end); + let mut edge_num = edge1.edge_index.1; loop { // Stop once we reach the end of the final edge @@ -129,13 +148,16 @@ where Coord: Coordinate+Coordinate2D { } // Map this edge to the gap line - let edge = &mut edges[edge_num]; - let edge_ray = (center.clone(), edge.position.clone()); - edge.position = line_intersects_ray(&edge_ray, &gap_line).unwrap_or_else(|| edge.position.clone()); + let edge = &mut edges[edge_num]; + let edge_ray = (*center, edge.position); + edge.position = + line_intersects_ray(&edge_ray, &gap_line).unwrap_or(edge.position); // Move to the next edge edge_num += 1; - if edge_num >= edges.len() { edge_num = 0; } + if edge_num >= edges.len() { + edge_num = 0; + } } // Remove these edges from consideration for future raycast operations @@ -146,8 +168,8 @@ where Coord: Coordinate+Coordinate2D { } // Remove any long edges that were affected by the gap removal operation - if long_edges_to_remove.len() > 0 { - long_edges_to_remove.sort(); + if !long_edges_to_remove.is_empty() { + long_edges_to_remove.sort_unstable(); for long_edge_num in long_edges_to_remove.into_iter().rev() { long_edges.remove(long_edge_num); } @@ -168,29 +190,35 @@ where Coord: Coordinate+Coordinate2D { /// Collisions generated internally will have 'None' set for the `what` field of the ray collision (this is why the field is made /// optional by this call) /// -pub fn trace_outline_concave(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Vec>> -where Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { +pub fn trace_outline_concave( + center: Coord, + options: &FillSettings, + cast_ray: RayFn, +) -> Vec>> +where + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ // Modify the raycasting function to return concave items (so we can distinguish between edges we introduced and ones matched by the original raycasting algorithm) // TODO: this just ensures we return optional items - let cast_ray = &cast_ray; - let cast_ray = &|from, to| { - cast_ray(from, to).into_iter().map(|collision| { - RayCollision { - position: collision.position, - what: ConcaveItem::Edge(collision.what) - } - }) + let cast_ray = &cast_ray; + let cast_ray = &|from, to| { + cast_ray(from, to) + .into_iter() + .map(|collision| RayCollision { + position: collision.position, + what: ConcaveItem::Edge(collision.what), + }) }; // The edge min length is the length of edge we need to see before we'll 'look around' a corner - let edge_min_len = options.step * 4.0; - let edge_min_len_squared = edge_min_len * edge_min_len; + let edge_min_len = options.step * 4.0; + let edge_min_len_squared = edge_min_len * edge_min_len; // Distance to move past a self-intersection (so we fully close the path). This can be reasonably large (as we'll use the // edge from the ray casting function if it's nearer) - let self_intersection_distance = options.step; + let self_intersection_distance = options.step; // Perform the initial convex ray-casting let mut edges = trace_outline_convex(center, options, cast_ray); @@ -201,7 +229,7 @@ where Coord: Coordinate+Coordinate2D, } // Find the edges where we need to cast extra rays - let mut long_edges = find_long_edges(&edges, edge_min_len_squared); + let mut long_edges = find_long_edges(&edges, edge_min_len_squared); // Remove any gaps that are too small for the rays to escape through if let Some(min_gap) = options.min_gap { @@ -218,36 +246,39 @@ where Coord: Coordinate+Coordinate2D, if !next_edge.ray_collided { // Pick the center point let center_point = (next_edge.start + next_edge.end) * 0.5; - let offset = next_edge.start - next_edge.end; + let offset = next_edge.start - next_edge.end; // Find the angle of the next edge - let line_angle = offset.x().atan2(offset.y()); + let line_angle = offset.x().atan2(offset.y()); // Generate a version of the raycasting function that inspects the existing list of long edges - let cast_ray_to_edges = |from: Coord, to: Coord| { + let cast_ray_to_edges = |from: Coord, to: Coord| { // Generate the edge collisions from the main raycasting function - let edge_collisions = cast_ray(from.clone(), to.clone()); - let ray_line = (from.clone(), to.clone()); + let edge_collisions = cast_ray(from, to); + let ray_line = (from, to); // Generate the collisions with the 'long edges' where we'll be considering casting more rays later on - let extra_collisions = long_edges.iter() + let extra_collisions = long_edges + .iter() .enumerate() .filter(|(edge_index, _edge)| *edge_index != long_edge_index) .filter_map(move |(edge_index, edge)| { // Create lines from the ray and the lines - let edge_line = (edge.start.clone(), edge.end.clone()); + let edge_line = (edge.start, edge.end); // Detect where they intersect - if let Some(intersection_point) = line_intersects_ray(&edge_line, &ray_line) { + if let Some(intersection_point) = line_intersects_ray(&edge_line, &ray_line) + { // Move the intersection point slightly inside the shape along the direction of the ray (so we can add the final result up properly) - let length = to.distance_to(&from); - let direction = (to-from) * (4.0/length); - let intersection_point = intersection_point + (direction * self_intersection_distance); + let length = to.distance_to(&from); + let direction = (to - from) * (4.0 / length); + let intersection_point = + intersection_point + (direction * self_intersection_distance); // Generate a colision at this point Some(RayCollision { - position: intersection_point, - what: ConcaveItem::SelfIntersection(edge_index) + position: intersection_point, + what: ConcaveItem::SelfIntersection(edge_index), }) } else { None @@ -259,7 +290,12 @@ where Coord: Coordinate+Coordinate2D, }; // Perform raycasting over a 180 degree angle to get the next set of edges - let mut new_edges = trace_outline_convex_partial(center_point, options, line_angle..(line_angle+f64::consts::PI), cast_ray_to_edges); + let mut new_edges = trace_outline_convex_partial( + center_point, + options, + line_angle..(line_angle + f64::consts::PI), + cast_ray_to_edges, + ); if new_edges.len() > 2 { // We ignore the first point as it will be the point along the existing edge (ie, will be the start point we already know) @@ -274,7 +310,8 @@ where Coord: Coordinate+Coordinate2D, } // Find new long edges in the new edges - let mut new_long_edges = find_long_edges(&new_edges[0..(new_edges.len())], edge_min_len_squared); + let mut new_long_edges = + find_long_edges(&new_edges[0..(new_edges.len())], edge_min_len_squared); // Don't count the edge ending at point 0 (that's the edge we just came from) new_long_edges.retain(|edge| edge.edge_index.1 != 0); @@ -284,20 +321,22 @@ where Coord: Coordinate+Coordinate2D, remove_small_gaps(¢er, &mut new_edges, &mut new_long_edges, min_gap); } - // Insert the new edges into the existing edge list (except the first which will be a duplicate) - let edge_index = next_edge_index; - let num_new_edges = new_edges.len()-1; - edges.splice(edge_index..edge_index, new_edges.into_iter().take(num_new_edges)); + let edge_index = next_edge_index; + let num_new_edges = new_edges.len() - 1; + edges.splice( + edge_index..edge_index, + new_edges.into_iter().take(num_new_edges), + ); // Update the remaining long edge indexes - for update_idx in long_edge_index..long_edges.len() { - if long_edges[update_idx].edge_index.0 >= edge_index { - long_edges[update_idx].edge_index.0 += num_new_edges; + for long_edge in long_edges.iter_mut().skip(long_edge_index) { + if long_edge.edge_index.0 >= edge_index { + long_edge.edge_index.0 += num_new_edges; } - if long_edges[update_idx].edge_index.1 >= edge_index { - long_edges[update_idx].edge_index.1 += num_new_edges; + if long_edge.edge_index.1 >= edge_index { + long_edge.edge_index.1 += num_new_edges; } } @@ -307,7 +346,7 @@ where Coord: Coordinate+Coordinate2D, edge.edge_index.1 += edge_index; } - long_edges.splice((long_edge_index+1)..(long_edge_index+1), new_long_edges); + long_edges.splice((long_edge_index + 1)..(long_edge_index + 1), new_long_edges); } } @@ -316,48 +355,64 @@ where Coord: Coordinate+Coordinate2D, } // The edges we retrieved are the result - edges.into_iter() - .map(|collision| RayCollision { - position: collision.position, - what: collision.what.into() + edges + .into_iter() + .map(|collision| RayCollision { + position: collision.position, + what: collision.what.into(), }) .collect() } /// /// Creates a Bezier path by flood-filling a convex area whose bounds can be determined by ray-casting. -/// +/// /// This won't fill areas that cannot be directly reached by a straight line from the center point. If the /// area is not entirely closed (from the point of view of the ray-casting function), then a line will be /// generated between the gaps. /// -pub fn flood_fill_concave(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Option> -where Path: BezierPathFactory, - Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { +pub fn flood_fill_concave( + center: Coord, + options: &FillSettings, + cast_ray: RayFn, +) -> Option> +where + Path: BezierPathFactory, + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ // Trace where the ray casting algorithm indicates collisions with the specified center let collisions = trace_outline_concave(center, options, cast_ray); // Build a path using the LMS algorithm - let curves = fit_curve::>(&collisions.iter().map(|collision| collision.position.clone()).collect::>(), options.fit_error); + let curves = fit_curve::>( + &collisions + .iter() + .map(|collision| collision.position) + .collect::>(), + options.fit_error, + ); if let Some(curves) = curves { - if curves.len() > 0 { + if !curves.is_empty() { // Convert the curves into a path - let initial_point = curves[0].start_point(); - let overlapped_path = Path::from_points(initial_point, curves.into_iter().map(|curve| { - let (cp1, cp2) = curve.control_points(); - let end_point = curve.end_point(); - (cp1, cp2, end_point) - })); - + let initial_point = curves[0].start_point(); + let overlapped_path = Path::from_points( + initial_point, + curves.into_iter().map(|curve| { + let (cp1, cp2) = curve.control_points(); + let end_point = curve.end_point(); + (cp1, cp2, end_point) + }), + ); + // Remove any interior points that the path might have (this happens when the fill path overlaps itself) - Some(path_remove_interior_points(&vec![overlapped_path], 0.01)) + Some(path_remove_interior_points(&[overlapped_path], 0.01)) } else { // No curves in the path None - } + } } else { // Failed to fit a curve to these points None diff --git a/src/bezier/path/algorithms/fill_convex.rs b/src/bezier/path/algorithms/fill_convex.rs index e6151c1f..7f0de194 100644 --- a/src/bezier/path/algorithms/fill_convex.rs +++ b/src/bezier/path/algorithms/fill_convex.rs @@ -1,73 +1,91 @@ -use super::fill_settings::*; -use super::super::*; -use super::super::super::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::super::{fit_curve, BezierCurve, Curve}; +use super::super::BezierPathFactory; +use super::fill_settings::FillSettings; use std::f64; -use std::ops::{Range}; +use std::ops::Range; /// /// Represents a collision between a ray and an object /// #[derive(Clone)] pub struct RayCollision -where Coord: Coordinate+Coordinate2D { +where + Coord: Coordinate + Coordinate2D, +{ /// Where this collision occurred pub position: Coord, /// The object that this ray colided with - pub what: Item + pub what: Item, } impl RayCollision -where Coord: Coordinate+Coordinate2D { +where + Coord: Coordinate + Coordinate2D, +{ /// /// Creates a new collision at a specific point /// - pub fn new(position: Coord, what: Item) -> RayCollision { - RayCollision { position, what } + pub fn new(position: Coord, what: Item) -> Self { + Self { position, what } } } /// -/// Given a ray-casting function, traces the outline of a shape containing the specified center point -/// -/// `center` is a point known to be contained in the shape (it's the origin of the region to be filled) -/// +/// Given a ray-casting function, traces the outline of a shape containing the specified center point +/// +/// `center` is a point known to be contained in the shape (it's the origin of the region to be filled) +/// /// The ray-casting function has the type `Fn(Coord, Coord) -> RayList`, where the two coordinates /// that are passed in represents the direction of the ray. It should return at least one intersection /// along this ray. If there is an intersection, the returned list should always include the closest /// intersection in the direction of the ray defined by the two coordinates. /// -pub fn trace_outline_convex(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Vec> -where Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { - trace_outline_convex_partial(center, options, (0.0)..(2.0*f64::consts::PI), cast_ray) +pub fn trace_outline_convex( + center: Coord, + options: &FillSettings, + cast_ray: RayFn, +) -> Vec> +where + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ + trace_outline_convex_partial(center, options, (0.0)..(2.0 * f64::consts::PI), cast_ray) } /// /// Finds the nearest collision and the square of its distance from the center from the results of a ray-casting operation /// -fn find_nearest_collision(candidates: RayList, center: Coord, ray_vector: Coord) -> Option<(RayCollision, f64)> -where Coord: Coordinate+Coordinate2D, - RayList: IntoIterator> { +fn find_nearest_collision( + candidates: RayList, + center: Coord, + ray_vector: Coord, +) -> Option<(RayCollision, f64)> +where + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, +{ // Pick the first positive collision in the direction of the ray - let mut nearest_collision = None; - let mut nearest_distance_squared = f64::MAX; + let mut nearest_collision = None; + let mut nearest_distance_squared = f64::MAX; for ray_collision in candidates { - let collision_vector = ray_collision.position - center; + let collision_vector = ray_collision.position - center; // Ignore collisions in the opposite direction of our ray - let direction = collision_vector.dot(&ray_vector); - if direction < 0.0 { continue; } + let direction = collision_vector.dot(&ray_vector); + if direction < 0.0 { + continue; + } // If this collision is closer to the center than before, then it becomes the nearest collision - let distance = collision_vector.dot(&collision_vector); + let distance = collision_vector.dot(&collision_vector); if distance < nearest_distance_squared { - nearest_collision = Some(ray_collision); - nearest_distance_squared = distance; + nearest_collision = Some(ray_collision); + nearest_distance_squared = distance; } } @@ -77,17 +95,23 @@ where Coord: Coordinate+Coordinate2D, /// /// Performs a raycast from a center point at a particular angle /// -fn perform_ray_cast(center: Coord, theta: f64, cast_ray: RayFn) -> Option<(RayCollision, f64)> -where Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { +fn perform_ray_cast( + center: Coord, + theta: f64, + cast_ray: RayFn, +) -> Option<(RayCollision, f64)> +where + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ // Work out the direction of the ray - let ray_vector = [1.0 * theta.sin(), 1.0 * theta.cos()]; - let ray_vector = Coord::from_components(&ray_vector); - let ray_target = center + ray_vector; + let ray_vector = [1.0 * theta.sin(), 1.0 * theta.cos()]; + let ray_vector = Coord::from_components(&ray_vector); + let ray_target = center + ray_vector; // Cast this ray and get the list of collisions - let ray_collisions = cast_ray(center, ray_target); + let ray_collisions = cast_ray(center, ray_target); // Pick the first positive collision in the direction of the ray find_nearest_collision(ray_collisions, center, ray_vector) @@ -96,73 +120,82 @@ where Coord: Coordinate+Coordinate2D, /// /// Ray traces around a specified range of angles to find the shape of the outline. Angles are in radians /// -pub (super) fn trace_outline_convex_partial(center: Coord, options: &FillSettings, angles: Range, cast_ray: RayFn) -> Vec> -where Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { +pub(super) fn trace_outline_convex_partial( + center: Coord, + options: &FillSettings, + angles: Range, + cast_ray: RayFn, +) -> Vec> +where + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ // The minimum number of radians to move forward when a ray does not find a collision - let min_step = 0.02; + let min_step = 0.02; // The number of pixels to put between points when tracing the outline - let step_size = options.step; - let max_step = step_size * 2.0; - let max_step_squared = max_step * max_step; + let step_size = options.step; + let max_step = step_size * 2.0; + let max_step_squared = max_step * max_step; // Collisions we're including in the result - let mut collisions = vec![]; + let mut collisions = vec![]; // Create a stack to track the state - let mut stack = vec![]; - struct StackEntry { - angle: Range, - start_pos: Option<(RayCollision, f64)>, - end_pos: Option + let mut stack = vec![]; + struct StackEntry { + angle: Range, + start_pos: Option<(RayCollision, f64)>, + end_pos: Option, } // Ray cast a few points to get the initial stack of points to check for check_point in 0..4 { - let check_point = (3-check_point) as f64; - let theta = angles.start + (angles.end-angles.start)/4.0 * check_point; - let end_theta = theta + (angles.end-angles.start)/4.0; + let check_point = (3 - check_point) as f64; + let theta = angles.start + (angles.end - angles.start) / 4.0 * check_point; + let end_theta = theta + (angles.end - angles.start) / 4.0; - let start_pos = perform_ray_cast(center, theta, &cast_ray); - let end_pos = perform_ray_cast(center, end_theta, &cast_ray); + let start_pos = perform_ray_cast(center, theta, &cast_ray); + let end_pos = perform_ray_cast(center, end_theta, &cast_ray); stack.push(StackEntry { - angle: theta..end_theta, - start_pos: start_pos, - end_pos: end_pos.map(|(end_pos, _distance)| end_pos.position) + angle: theta..end_theta, + start_pos, + end_pos: end_pos.map(|(end_pos, _distance)| end_pos.position), }); } // Divide up the check points until the gap between them becomes small enough that it's less than the maximum gap size while let Some(entry) = stack.pop() { - if let (Some((start_pos, _start_distance_squared)), Some(end_pos)) = (entry.start_pos.as_ref(), entry.end_pos) { + if let (Some((start_pos, _start_distance_squared)), Some(end_pos)) = + (entry.start_pos.as_ref(), entry.end_pos) + { // Check the distance between the start and the end - let offset = end_pos - start_pos.position; - let distance_squared = offset.dot(&offset); + let offset = end_pos - start_pos.position; + let distance_squared = offset.dot(&offset); if distance_squared < max_step_squared { // This point is close enough to its following point to be added to the result collisions.push(entry.start_pos.unwrap().0); } else { // Divide the entry into two by casting a ray between the two points - let mid_point = (entry.angle.start + entry.angle.end) / 2.0; - let mid_ray = perform_ray_cast(center, mid_point, &cast_ray); - let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position.clone()); + let mid_point = (entry.angle.start + entry.angle.end) / 2.0; + let mid_ray = perform_ray_cast(center, mid_point, &cast_ray); + let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position); // If there's a discontinuity (eg, a corner we can't see around), we'll see that the mid point is very close to the end point and far from the start point if let Some(mid_ray_pos) = mid_ray_pos { // Compute the distance from the start to the mid-point and the mid-point to the end - let mid_to_end = end_pos - mid_ray_pos; - let mid_to_end_sq = mid_to_end.dot(&mid_to_end); + let mid_to_end = end_pos - mid_ray_pos; + let mid_to_end_sq = mid_to_end.dot(&mid_to_end); // If the end is very close to the mid-point ... if mid_to_end_sq < (step_size * step_size) { // ... and is over 3/4 from the start point ... - let three_quarters_sq = (9.0*distance_squared)/16.0; - let start_to_mid = mid_ray_pos - start_pos.position; - let start_to_mid_sq = start_to_mid.dot(&start_to_mid); + let three_quarters_sq = (9.0 * distance_squared) / 16.0; + let start_to_mid = mid_ray_pos - start_pos.position; + let start_to_mid_sq = start_to_mid.dot(&start_to_mid); if start_to_mid_sq >= three_quarters_sq { // ... we've hit an edge and won't be able to find a point closer to the start position @@ -174,36 +207,34 @@ where Coord: Coordinate+Coordinate2D, // Divide into two pairs of ranges (process the earlier one first) stack.push(StackEntry { - angle: mid_point..entry.angle.end, - start_pos: mid_ray, - end_pos: entry.end_pos + angle: mid_point..entry.angle.end, + start_pos: mid_ray, + end_pos: entry.end_pos, }); stack.push(StackEntry { - angle: entry.angle.start..mid_point, - start_pos: entry.start_pos, - end_pos: mid_ray_pos + angle: entry.angle.start..mid_point, + start_pos: entry.start_pos, + end_pos: mid_ray_pos, }) } - } else { - // One or both of the rays did not find a collision if entry.angle.end - entry.angle.start > min_step { // Cast a ray between the two points - let mid_point = (entry.angle.start + entry.angle.end) / 2.0; - let mid_ray = perform_ray_cast(center, mid_point, &cast_ray); - let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position.clone()); + let mid_point = (entry.angle.start + entry.angle.end) / 2.0; + let mid_ray = perform_ray_cast(center, mid_point, &cast_ray); + let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position); // Divide into two pairs of ranges (process the earlier one first) stack.push(StackEntry { - angle: mid_point..entry.angle.end, - start_pos: mid_ray, - end_pos: entry.end_pos + angle: mid_point..entry.angle.end, + start_pos: mid_ray, + end_pos: entry.end_pos, }); stack.push(StackEntry { - angle: entry.angle.start..mid_point, - start_pos: entry.start_pos, - end_pos: mid_ray_pos + angle: entry.angle.start..mid_point, + start_pos: entry.start_pos, + end_pos: mid_ray_pos, }) } } @@ -212,38 +243,52 @@ where Coord: Coordinate+Coordinate2D, collisions } - /// /// Creates a Bezier path by flood-filling a convex area whose bounds can be determined by ray-casting. -/// +/// /// This won't fill areas that cannot be directly reached by a straight line from the center point. If the /// area is not entirely closed (from the point of view of the ray-casting function), then a line will be /// generated between the gaps. /// -pub fn flood_fill_convex(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Option -where Path: BezierPathFactory, - Coord: Coordinate+Coordinate2D, - RayList: IntoIterator>, - RayFn: Fn(Coord, Coord) -> RayList { +pub fn flood_fill_convex( + center: Coord, + options: &FillSettings, + cast_ray: RayFn, +) -> Option +where + Path: BezierPathFactory, + Coord: Coordinate + Coordinate2D, + RayList: IntoIterator>, + RayFn: Fn(Coord, Coord) -> RayList, +{ // Trace where the ray casting algorithm indicates collisions with the specified center let collisions = trace_outline_convex(center, options, cast_ray); // Build a path using the LMS algorithm - let curves = fit_curve::>(&collisions.iter().map(|collision| collision.position.clone()).collect::>(), options.fit_error); + let curves = fit_curve::>( + &collisions + .iter() + .map(|collision| collision.position) + .collect::>(), + options.fit_error, + ); if let Some(curves) = curves { - if curves.len() > 0 { + if !curves.is_empty() { // Convert the curves into a path let initial_point = curves[0].start_point(); - Some(Path::from_points(initial_point, curves.into_iter().map(|curve| { - let (cp1, cp2) = curve.control_points(); - let end_point = curve.end_point(); - (cp1, cp2, end_point) - }))) + Some(Path::from_points( + initial_point, + curves.into_iter().map(|curve| { + let (cp1, cp2) = curve.control_points(); + let end_point = curve.end_point(); + (cp1, cp2, end_point) + }), + )) } else { // No curves in the path None - } + } } else { // Failed to fit a curve to these points None diff --git a/src/bezier/path/algorithms/fill_settings.rs b/src/bezier/path/algorithms/fill_settings.rs index 01f98ebe..b9299d75 100644 --- a/src/bezier/path/algorithms/fill_settings.rs +++ b/src/bezier/path/algorithms/fill_settings.rs @@ -1,29 +1,29 @@ /// /// Options that affect the fill algorithm -/// +/// /// The default options are created using `FillOptions::default()`. These can be used to tweak /// settings like this step size. /// #[derive(Clone, Copy, PartialEq, Debug)] pub struct FillSettings { /// The distance between one ray and the next - pub (crate) step: f64, + pub(crate) step: f64, /// The maximum error to allow when performing curve fitting - pub (crate) fit_error: f64, + pub(crate) fit_error: f64, /// For concave fills, the minimum gap size that a fill can escape through - pub (crate) min_gap: Option + pub(crate) min_gap: Option, } impl FillSettings { /// /// Creates a new fill options from this one by setting the step - /// + /// /// The step size defines how accurately the flood-filled region reflects the area defined by the /// ray-casting function. Higher steps will result in a faster but less accurate result. /// - pub fn with_step(self, new_step: f64) -> FillSettings { + pub fn with_step(self, new_step: f64) -> Self { let mut new_options = self; new_options.step = new_step; new_options @@ -31,13 +31,13 @@ impl FillSettings { /// /// Creates a new fill options from this one by setting the curve fitting error - /// + /// /// The curve fitting error indicates how precisely the generated curve fits against the points /// returned by the ray casting algorithm. Increasing this value reduces the precision of the /// fit, which may produce a simpler (and smoother) resulting path but which will not necessarily /// fit the points as well. /// - pub fn with_fit_error(self, new_fit_error: f64) -> FillSettings { + pub fn with_fit_error(self, new_fit_error: f64) -> Self { let mut new_options = self; new_options.fit_error = new_fit_error; new_options @@ -48,7 +48,7 @@ impl FillSettings { /// /// This makes it possible to fill regions that are not perfectly enclosed /// - pub fn with_min_gap(self, new_min_gap: Option) -> FillSettings { + pub fn with_min_gap(self, new_min_gap: Option) -> Self { let mut new_options = self; new_options.min_gap = new_min_gap; new_options @@ -59,11 +59,11 @@ impl Default for FillSettings { /// /// Creates the default set of fill options /// - fn default() -> FillSettings { - FillSettings { - step: 2.0, - fit_error: 0.5, - min_gap: Some(5.0) + fn default() -> Self { + Self { + step: 2.0, + fit_error: 0.5, + min_gap: Some(5.0), } - } + } } diff --git a/src/bezier/path/algorithms/mod.rs b/src/bezier/path/algorithms/mod.rs index 5d530999..df520362 100644 --- a/src/bezier/path/algorithms/mod.rs +++ b/src/bezier/path/algorithms/mod.rs @@ -1,7 +1,7 @@ -mod fill_convex; mod fill_concave; +mod fill_convex; mod fill_settings; -pub use self::fill_convex::*; pub use self::fill_concave::*; +pub use self::fill_convex::*; pub use self::fill_settings::*; diff --git a/src/bezier/path/arithmetic/add.rs b/src/bezier/path/arithmetic/add.rs index 70b21996..fb1ba10a 100644 --- a/src/bezier/path/arithmetic/add.rs +++ b/src/bezier/path/arithmetic/add.rs @@ -1,22 +1,24 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; // // This uses a simple ray casting algorithm to perform the addition -// +// // Basic idea is to cast a ray at an edge which is currently uncategorised, and mark the edges it crosses as interior or // exterior depending on whether or not we consider it as crossing into or out of the final shape. // -impl GraphPath { +impl GraphPath { /// /// Given a labelled graph path, marks exterior edges by adding `PathSource::Path1` and `PathSource::Path2` /// pub fn set_exterior_by_adding(&mut self) { // Use an even-odd winding rule (all edges are considered 'external') - self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 || (path_crossings[1]&1) != 0); + self.set_edge_kinds_by_ray_casting(|path_crossings| { + (path_crossings[0] & 1) != 0 || (path_crossings[1] & 1) != 0 + }); } /// @@ -24,37 +26,52 @@ impl GraphPath { /// pub fn set_exterior_by_removing_interior_points(&mut self) { // All points inside the path are considered 'interior' (non-zero winding rule) - self.set_edge_kinds_by_ray_casting(|path_crossings| path_crossings[0] != 0 || path_crossings[1] != 0); + self.set_edge_kinds_by_ray_casting(|path_crossings| { + path_crossings[0] != 0 || path_crossings[1] != 0 + }); } } /// /// Generates the path formed by adding two sets of paths -/// +/// /// The input vectors represent the external edges of the path to add (a single BezierPath cannot have any holes in it, so a set of them /// effectively represents a path intended to be rendered with an even-odd winding rule) /// -pub fn path_add(path1: &Vec, path2: &Vec, accuracy: f64) -> Vec -where P1::Point: Coordinate+Coordinate2D, - P2: BezierPath, - POut: BezierPathFactory { +pub fn path_add( + path1: &[P1], + path2: &[P2], + accuracy: f64, +) -> Vec +where + P1::Point: Coordinate + Coordinate2D, + P2: BezierPath, + POut: BezierPathFactory, +{ // If either path is empty, short-circuit by returning the other - if path1.len() == 0 { - return path2.iter() - .map(|path| POut::from_path(path)) - .collect(); - } else if path2.len() == 0 { - return path1.iter() - .map(|path| POut::from_path(path)) - .collect(); + if path1.is_empty() { + return path2.iter().map(|path| POut::from_path(path)).collect(); + } else if path2.is_empty() { + return path1.iter().map(|path| POut::from_path(path)).collect(); } // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide with the target side to generate a full path - merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); // Set the exterior edges using the 'add' algorithm @@ -66,21 +83,29 @@ where P1::Point: Coordinate+Coordinate2D, } /// -/// Generates the path formed by removing any interior points from an existing path. This considers only the outermost edges of the +/// Generates the path formed by removing any interior points from an existing path. This considers only the outermost edges of the /// path to be the true edges, so if there are sub-paths inside an outer path, they will be removed. /// /// This is a strict version of the 'non-zero' winding rule. It's useful for things like a path that was generated from a brush stroke -/// and might self-overlap: this can be passed a drawing of a loop made by overlapping the ends and it will output two non-overlapping +/// and might self-overlap: this can be passed a drawing of a loop made by overlapping the ends and it will output two non-overlapping /// subpaths. /// /// See `path_remove_overlapped_points()` for a version that considers all edges within the path to be exterior edges. /// -pub fn path_remove_interior_points(path: &Vec, accuracy: f64) -> Vec -where P1::Point: Coordinate+Coordinate2D, - POut: BezierPathFactory { +pub fn path_remove_interior_points( + path: &[P1], + accuracy: f64, +) -> Vec +where + P1::Point: Coordinate + Coordinate2D, + POut: BezierPathFactory, +{ // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path.iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide the path with itself to find the intersections merged_path.self_collide(accuracy); @@ -92,7 +117,7 @@ where P1::Point: Coordinate+Coordinate2D, // Produce the final result let result = merged_path.exterior_paths(); - test_assert!(result.len() != 0 || path.len() == 0); + test_assert!(!result.is_empty() || path.is_empty()); result } @@ -110,12 +135,20 @@ where P1::Point: Coordinate+Coordinate2D, /// Note that calling 'subtract' is a more reliable way to cut a hole in an existing path than relying on a winding rule, as /// winding rules presuppose you can tell if a subpath is inside or outside of an existing path. /// -pub fn path_remove_overlapped_points(path: &Vec, accuracy: f64) -> Vec -where P1::Point: Coordinate+Coordinate2D, - POut: BezierPathFactory { +pub fn path_remove_overlapped_points( + path: &[P1], + accuracy: f64, +) -> Vec +where + P1::Point: Coordinate + Coordinate2D, + POut: BezierPathFactory, +{ // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path.iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide the path with itself to find the intersections merged_path.self_collide(accuracy); @@ -127,7 +160,7 @@ where P1::Point: Coordinate+Coordinate2D, // Produce the final result let result = merged_path.exterior_paths(); - test_assert!(result.len() != 0 || path.len() == 0); + test_assert!(!result.is_empty() || path.is_empty()); result } diff --git a/src/bezier/path/arithmetic/chain.rs b/src/bezier/path/arithmetic/chain.rs index adcb6438..5ece28e2 100644 --- a/src/bezier/path/arithmetic/chain.rs +++ b/src/bezier/path/arithmetic/chain.rs @@ -1,9 +1,9 @@ -use super::add::*; -use super::sub::*; -use super::chain_add::*; -use super::intersect::*; -use super::super::path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::add::path_remove_interior_points; +use super::chain_add::path_add_chain; +use super::intersect::path_intersect; +use super::sub::path_sub; /// /// Description of an arithmetic operation to perform on a bezier path @@ -13,7 +13,9 @@ use super::super::super::super::geo::*; /// #[derive(Clone, Debug)] pub enum PathCombine -where P::Point : Coordinate+Coordinate2D { +where + P::Point: Coordinate + Coordinate2D, +{ /// Sets the result to a particular path Path(Vec

), @@ -21,7 +23,7 @@ where P::Point : Coordinate+Coordinate2D { /// /// Everything within the outermost boundary of the path will be removed: the path will be left with no /// 'holes' in it. This is useful for cleaning up things like paths generated by brush strokes that may - /// self-overlap. See also `path_remove_overlapped_points()` for a way to clean up paths where every edge + /// self-overlap. See also `path_remove_overlapped_points()` for a way to clean up paths where every edge /// is intended to be an exterior edge. /// /// This can be considered to be a slightly stricter version of the non-zero winding rule: the intention is @@ -36,42 +38,55 @@ where P::Point : Coordinate+Coordinate2D { Subtract(Vec>), /// Intersects a series a paths (with the first path) - Intersect(Vec>) + Intersect(Vec>), } /// /// Performs a series of path combining operations to generate an output path /// pub fn path_combine(operation: PathCombine

, accuracy: f64) -> Vec

-where P::Point: Coordinate+Coordinate2D { +where + P::Point: Coordinate + Coordinate2D, +{ // TODO: it's probably possible to combine add, subtract and intersect into a single ray-casting operation using a similar technique to how path_add_chain works match operation { - PathCombine::Path(result) => result, + PathCombine::Path(result) => result, PathCombine::RemoveInteriorPoints(path) => path_remove_interior_points(&path, accuracy), - PathCombine::Add(paths) => path_add_chain(&paths.into_iter().map(|path| path_combine(path, accuracy)).collect(), accuracy), + PathCombine::Add(paths) => path_add_chain( + paths + .into_iter() + .map(|path| path_combine(path, accuracy)) + .collect::>() + .as_slice(), + accuracy, + ), - PathCombine::Subtract(paths) => { - let mut path_iter = paths.into_iter(); - let result = path_iter.next().unwrap_or_else(|| PathCombine::Path(vec![])); - let mut result = path_combine(result, accuracy); + PathCombine::Subtract(paths) => { + let mut path_iter = paths.into_iter(); + let result = path_iter + .next() + .unwrap_or_else(|| PathCombine::Path(vec![])); + let mut result = path_combine(result, accuracy); - while let Some(to_subtract) = path_iter.next() { + for to_subtract in path_iter { let to_subtract = path_combine(to_subtract, accuracy); - result = path_sub(&result, &to_subtract, accuracy); + result = path_sub(&result, &to_subtract, accuracy); } result } - PathCombine::Intersect(paths) => { - let mut path_iter = paths.into_iter(); - let result = path_iter.next().unwrap_or_else(|| PathCombine::Path(vec![])); - let mut result = path_combine(result, accuracy); + PathCombine::Intersect(paths) => { + let mut path_iter = paths.into_iter(); + let result = path_iter + .next() + .unwrap_or_else(|| PathCombine::Path(vec![])); + let mut result = path_combine(result, accuracy); - while let Some(to_intersect) = path_iter.next() { - let to_intersect = path_combine(to_intersect, accuracy); - result = path_intersect(&result, &to_intersect, accuracy); + for to_intersect in path_iter { + let to_intersect = path_combine(to_intersect, accuracy); + result = path_intersect(&result, &to_intersect, accuracy); } result diff --git a/src/bezier/path/arithmetic/chain_add.rs b/src/bezier/path/arithmetic/chain_add.rs index a8cb9dc2..f82128fd 100644 --- a/src/bezier/path/arithmetic/chain_add.rs +++ b/src/bezier/path/arithmetic/chain_add.rs @@ -1,20 +1,31 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; /// /// Adds multiple paths in a single operation /// -pub fn path_add_chain(paths: &Vec>, accuracy: f64) -> Vec -where P::Point: Coordinate+Coordinate2D, - POut: BezierPathFactory { +pub fn path_add_chain( + paths: &[Vec

], + accuracy: f64, +) -> Vec +where + P::Point: Coordinate + Coordinate2D, + POut: BezierPathFactory, +{ // Build up the graph path from the supplied list let mut merged_path = GraphPath::new(); for (path_idx, path) in paths.iter().enumerate() { - let path_idx = path_idx as u32; - merged_path = merged_path.collide(GraphPath::from_merged_paths(path.into_iter().map(|path| (path, PathLabel(path_idx, PathDirection::from(path))))), accuracy); + let path_idx = path_idx as u32; + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path.iter() + .map(|path| (path, PathLabel(path_idx, PathDirection::from(path)))), + ), + accuracy, + ); } merged_path.round(accuracy); @@ -22,11 +33,11 @@ where P::Point: Coordinate+Coordinate2D, // Set the exterior edges using the 'add' algorithm (all edges are considered 'external' here) merged_path.set_edge_kinds_by_ray_casting(|path_crossings| { for count in path_crossings.iter() { - if (count&1) != 0 { - return true; - } + if (count & 1) != 0 { + return true; + } } - return false; + false }); merged_path.heal_exterior_gaps(); diff --git a/src/bezier/path/arithmetic/cut.rs b/src/bezier/path/arithmetic/cut.rs index 467eab79..f322b770 100644 --- a/src/bezier/path/arithmetic/cut.rs +++ b/src/bezier/path/arithmetic/cut.rs @@ -1,7 +1,7 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; /// /// The result of a path cut operation @@ -11,36 +11,53 @@ pub struct PathCut { pub interior_path: Vec

, /// The path that was outside of the 'cut' path - pub exterior_path: Vec

+ pub exterior_path: Vec

, } /// /// Cuts a path (`path1`) into two along another path (`path2`), returning the part of `path1` that was interior to `path2` and /// the part that was exterior in one operation /// -pub fn path_cut(path1: &Vec, path2: &Vec, accuracy: f64) -> PathCut -where P1::Point: Coordinate+Coordinate2D, - P2: BezierPath, - POut: BezierPathFactory { +pub fn path_cut( + path1: &[P1], + path2: &[P2], + accuracy: f64, +) -> PathCut +where + P1::Point: Coordinate + Coordinate2D, + P2: BezierPath, + POut: BezierPathFactory, +{ // If path1 is empty, then there are no points in the result. If path2 is empty, then all points are exterior - if path1.len() == 0 { - return PathCut { - interior_path: vec![], - exterior_path: vec![] + if path1.is_empty() { + return PathCut { + interior_path: vec![], + exterior_path: vec![], }; - } else if path2.len() == 0 { - return PathCut { - interior_path: vec![], - exterior_path: path1.iter().map(|path| POut::from_path(path)).collect() + } else if path2.is_empty() { + return PathCut { + interior_path: vec![], + exterior_path: path1.iter().map(|path| POut::from_path(path)).collect(), }; } // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide with the target side to generate a full path - merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); // The interior edges are those found by intersecting the second path with the first @@ -64,6 +81,6 @@ where P1::Point: Coordinate+Coordinate2D, PathCut { interior_path, - exterior_path + exterior_path, } } diff --git a/src/bezier/path/arithmetic/full_intersect.rs b/src/bezier/path/arithmetic/full_intersect.rs index 47377299..67a49a66 100644 --- a/src/bezier/path/arithmetic/full_intersect.rs +++ b/src/bezier/path/arithmetic/full_intersect.rs @@ -1,7 +1,7 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; /// /// The result of a path cut operation @@ -12,35 +12,58 @@ pub struct PathIntersection { pub intersecting_path: Vec

, /// The path that was outside of the 'cut' path for the two input paths - pub exterior_paths: [Vec

; 2] + pub exterior_paths: [Vec

; 2], } /// /// Intersects two paths, returning both the path that is the intersection and the paths that are outside /// -pub fn path_full_intersect(path1: &Vec, path2: &Vec, accuracy: f64) -> PathIntersection -where P1::Point: Coordinate+Coordinate2D, - P2: BezierPath, - POut: BezierPathFactory { +pub fn path_full_intersect( + path1: &[P1], + path2: &[P2], + accuracy: f64, +) -> PathIntersection +where + P1::Point: Coordinate + Coordinate2D, + P2: BezierPath, + POut: BezierPathFactory, +{ // If path1 is empty, then there are no points in the result. If path2 is empty, then all points are exterior - if path1.len() == 0 { - return PathIntersection { - intersecting_path: vec![], - exterior_paths: [vec![], path2.iter().map(|path| POut::from_path(path)).collect()] + if path1.is_empty() { + return PathIntersection { + intersecting_path: vec![], + exterior_paths: [ + vec![], + path2.iter().map(|path| POut::from_path(path)).collect(), + ], }; - } else if path2.len() == 0 { - return PathIntersection { - intersecting_path: vec![], - exterior_paths: [path1.iter().map(|path| POut::from_path(path)).collect(), vec![]] + } else if path2.is_empty() { + return PathIntersection { + intersecting_path: vec![], + exterior_paths: [ + path1.iter().map(|path| POut::from_path(path)).collect(), + vec![], + ], }; } // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide with the target side to generate a full path - merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); // The interior edges are those found by intersecting the second path with the first @@ -66,8 +89,19 @@ where P1::Point: Coordinate+Coordinate2D, // TODO: it would be faster to re-use the existing merged paths here, but this will fail to properly generate a subtracted paths // in the case where edges of the two paths overlap. let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); - merged_path = merged_path.collide(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); merged_path.set_exterior_by_subtracting(); @@ -77,7 +111,7 @@ where P1::Point: Coordinate+Coordinate2D, let exterior_from_path_2 = merged_path.exterior_paths(); PathIntersection { - intersecting_path: intersecting_path, - exterior_paths: [exterior_from_path_1, exterior_from_path_2] + intersecting_path, + exterior_paths: [exterior_from_path_1, exterior_from_path_2], } } diff --git a/src/bezier/path/arithmetic/intersect.rs b/src/bezier/path/arithmetic/intersect.rs index ce6ce7ee..17c77d23 100644 --- a/src/bezier/path/arithmetic/intersect.rs +++ b/src/bezier/path/arithmetic/intersect.rs @@ -1,45 +1,60 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; -impl GraphPath { +impl GraphPath { /// /// Given a labelled graph path, marks exterior edges by intersecting `PathSource::Path1` and `PathSource::Path2` /// pub fn set_exterior_by_intersecting(&mut self) { // Use an even-odd winding rule (all edges are considered 'external') - self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 && (path_crossings[1]&1) != 0); + self.set_edge_kinds_by_ray_casting(|path_crossings| { + (path_crossings[0] & 1) != 0 && (path_crossings[1] & 1) != 0 + }); } } /// /// Generates the path formed by intersecting two sets of paths -/// +/// /// The input vectors represent the external edges of the path to intersect (a single BezierPath cannot have any holes in it, so a set of them /// effectively represents a path intended to be rendered with an even-odd winding rule) /// -pub fn path_intersect(path1: &Vec, path2: &Vec, accuracy: f64) -> Vec -where P1::Point: Coordinate+Coordinate2D, - P2: BezierPath, - POut: BezierPathFactory { +pub fn path_intersect( + path1: &[P1], + path2: &[P2], + accuracy: f64, +) -> Vec +where + P1::Point: Coordinate + Coordinate2D, + P2: BezierPath, + POut: BezierPathFactory, +{ // If either path is empty, short-circuit by returning the other - if path1.len() == 0 { - return path2.iter() - .map(|path| POut::from_path(path)) - .collect(); - } else if path2.len() == 0 { - return path1.iter() - .map(|path| POut::from_path(path)) - .collect(); + if path1.is_empty() { + return path2.iter().map(|path| POut::from_path(path)).collect(); + } else if path2.is_empty() { + return path1.iter().map(|path| POut::from_path(path)).collect(); } // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide with the target side to generate a full path - merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); // Set the exterior edges using the 'intersect' algorithm diff --git a/src/bezier/path/arithmetic/mod.rs b/src/bezier/path/arithmetic/mod.rs index 82970f2c..6d6715bd 100644 --- a/src/bezier/path/arithmetic/mod.rs +++ b/src/bezier/path/arithmetic/mod.rs @@ -1,17 +1,17 @@ -mod ray_cast; -mod intersect; mod add; -mod chain_add; -mod sub; mod chain; +mod chain_add; mod cut; mod full_intersect; +mod intersect; +mod ray_cast; +mod sub; -pub use self::ray_cast::*; -pub use self::intersect::*; pub use self::add::*; -pub use self::sub::*; pub use self::chain::*; pub use self::chain_add::*; pub use self::cut::*; pub use self::full_intersect::*; +pub use self::intersect::*; +pub use self::ray_cast::*; +pub use self::sub::*; diff --git a/src/bezier/path/arithmetic/ray_cast.rs b/src/bezier/path/arithmetic/ray_cast.rs index a56105c6..fb9963f0 100644 --- a/src/bezier/path/arithmetic/ray_cast.rs +++ b/src/bezier/path/arithmetic/ray_cast.rs @@ -1,12 +1,12 @@ -use crate::bezier::path::path::*; -use crate::bezier::path::graph_path::*; -use crate::bezier::path::is_clockwise::*; -use crate::bezier::curve::*; -use crate::bezier::normal::*; -use crate::geo::*; -use crate::line::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::super::curve::BezierCurve; +use super::super::super::normal::NormalCurve; +use super::super::graph_path::{GraphPath, GraphPathEdgeKind, GraphRayCollision}; +use super::super::is_clockwise::PathWithIsClockwise; +use super::super::path::BezierPath; +use crate::line::Line; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; /// /// Winding direction of a particular path @@ -14,46 +14,51 @@ use smallvec::*; #[derive(Copy, Clone, PartialEq, Debug)] pub enum PathDirection { Clockwise, - Anticlockwise + Anticlockwise, } impl<'a, P: BezierPath> From<&'a P> for PathDirection -where P::Point: Coordinate2D { +where + P::Point: Coordinate2D, +{ #[inline] - fn from(path: &'a P) -> PathDirection { + fn from(path: &'a P) -> Self { if path.is_clockwise() { - PathDirection::Clockwise + Self::Clockwise } else { - PathDirection::Anticlockwise + Self::Anticlockwise } } } /// /// Label attached to a path used for arithmetic -/// +/// /// The parameters are the path number (counting from 0) and the winding direction of the path -/// +/// #[derive(Clone, Copy, Debug)] pub struct PathLabel(pub u32, pub PathDirection); -impl GraphPath { +impl GraphPath { /// /// Returns the ray collisions with an ordering algorithm applied so that the rays enters and exits sets of overlapping edges /// in a consistent order. /// - pub fn ordered_ray_collisions>(&self, ray: &L) -> Vec<(GraphRayCollision, f64, f64, Point)> { - let mut collisions = self.ray_collisions(ray); + pub fn ordered_ray_collisions>( + &self, + ray: &L, + ) -> Vec<(GraphRayCollision, f64, f64, Point)> { + let mut collisions = self.ray_collisions(ray); // There should always be an even number of collisions on a particular ray cast through a closed shape - test_assert!((collisions.len()&1) == 0); + test_assert!((collisions.len() & 1) == 0); // For collisions that overlap, ensure that the first shape is outermost so that subtractions work (swap based on the direction) // This interacts with the ordering chosen in ray_collisions: if that ordering changes this may no longer be correct - if collisions.len() > 0 { - for collision_idx in 0..(collisions.len()-1) { - let (collision_a, _curve_t, line_t_a, _pos) = &collisions[collision_idx+0]; - let (collision_b, _curve_t, line_t_b, _pos) = &collisions[collision_idx+1]; + if !collisions.is_empty() { + for collision_idx in 0..(collisions.len() - 1) { + let (collision_a, _curve_t, line_t_a, _pos) = &collisions[collision_idx]; + let (collision_b, _curve_t, line_t_b, _pos) = &collisions[collision_idx + 1]; if line_t_a == line_t_b { let edge_a = collision_a.edge(); @@ -61,11 +66,15 @@ impl GraphPath { // Swap if the earlier of the two edges is moving in the appropriate direction if edge_a.start_idx == edge_b.start_idx { - let earlier_edge = if edge_a.edge_idx < edge_b.edge_idx { edge_a } else { edge_b }; - let PathLabel(_, edge_direction) = self.edge_label(earlier_edge); + let earlier_edge = if edge_a.edge_idx < edge_b.edge_idx { + edge_a + } else { + edge_b + }; + let PathLabel(_, edge_direction) = self.edge_label(earlier_edge); if edge_direction == PathDirection::Anticlockwise { - collisions.swap(collision_idx, collision_idx+1); + collisions.swap(collision_idx, collision_idx + 1); } } } @@ -77,12 +86,15 @@ impl GraphPath { /// /// Sets the edge kinds by performing ray casting - /// + /// /// The function passed in to this method takes two parameters: these are the number of times edges have been crossed in /// path 1 and path 2. It should return true if this number of crossings represents a point inside the final shape, or false /// if it represents a point outside of the shape. /// - pub fn set_edge_kinds_by_ray_casting) -> bool>(&mut self, is_inside: FnIsInside) { + pub fn set_edge_kinds_by_ray_casting) -> bool>( + &mut self, + is_inside: FnIsInside, + ) { for point_idx in 0..self.num_points() { for next_edge in self.edge_refs_for_point(point_idx) { // Only process edges that have not yet been categorised @@ -91,8 +103,8 @@ impl GraphPath { } // Cast a ray at this edge - let real_edge = self.get_edge(next_edge); - let next_point = real_edge.point_at_pos(0.5); + let real_edge = self.get_edge(next_edge); + let next_point = real_edge.point_at_pos(0.5); let next_normal = real_edge.normal_at_pos(0.5); // Mark the next edge as visited (this prevents an infinite loop in the event the edge we're aiming at has a length of 0 and thus will always be an intersection) @@ -103,28 +115,62 @@ impl GraphPath { let mut path_crossings: SmallVec<[i32; 8]> = smallvec![0, 0]; // Cast a ray at the target edge - let ray = (next_point - next_normal, next_point); - let ray_direction = ray.1 - ray.0; - let collisions = self.ordered_ray_collisions(&ray); + let ray = (next_point - next_normal, next_point); + let ray_direction = ray.1 - ray.0; + let mut collisions = self.ray_collisions(&ray); + + // There should always be an even number of collisions on a particular ray cast through a closed shape + test_assert!((collisions.len() & 1) == 0); + + // For collisions that overlap, ensure that the first shape is outermost so that subtractions work (swap based on the direction) + // This interacts with the ordering chosen in ray_collisions: if that ordering changes this may no longer be correct + if !collisions.is_empty() { + for collision_idx in 0..(collisions.len() - 1) { + let (collision_a, _curve_t, line_t_a, _pos) = &collisions[collision_idx]; + let (collision_b, _curve_t, line_t_b, _pos) = + &collisions[collision_idx + 1]; + + if line_t_a == line_t_b { + let edge_a = collision_a.edge(); + let edge_b = collision_b.edge(); + + // Swap if the earlier of the two edges is moving in the appropriate direction + if edge_a.start_idx == edge_b.start_idx { + let earlier_edge = if edge_a.edge_idx < edge_b.edge_idx { + edge_a + } else { + edge_b + }; + let PathLabel(_, edge_direction) = self.edge_label(earlier_edge); + + if edge_direction == PathDirection::Anticlockwise { + collisions.swap(collision_idx, collision_idx + 1); + } + } + } + } + } // Work out which edges are interior or exterior for every edge the ray has crossed for (collision, curve_t, _line_t, _pos) in collisions { let is_intersection = collision.is_intersection(); - let edge = collision.edge(); + let edge = collision.edge(); let PathLabel(path_number, direction) = self.edge_label(edge); // The relative direction of the tangent to the ray indicates the direction we're crossing in - let normal = self.get_edge(edge).normal_at_pos(curve_t); + let normal = self.get_edge(edge).normal_at_pos(curve_t); - let side = ray_direction.dot(&normal).signum() as i32; - let side = match direction { - PathDirection::Clockwise => { side }, - PathDirection::Anticlockwise => { -side } + let side = ray_direction.dot(&normal).signum() as i32; + let side = match direction { + PathDirection::Clockwise => side, + PathDirection::Anticlockwise => -side, }; - // Extend the path_crossings vector to accomodate all of the paths included by this ray - while path_crossings.len() <= path_number as usize { path_crossings.push(0); } + // Extend the path_crossings vector + while path_crossings.len() <= path_number as usize { + path_crossings.push(0); + } let was_inside = is_inside(&path_crossings); if side < 0 { @@ -139,7 +185,10 @@ impl GraphPath { // If this isn't an intersection, set whether or not the edge is exterior let edge_kind = self.edge_kind(edge); - if !is_intersection && (edge_kind == GraphPathEdgeKind::Uncategorised || edge_kind == GraphPathEdgeKind::Visited) { + if !is_intersection + && (edge_kind == GraphPathEdgeKind::Uncategorised + || edge_kind == GraphPathEdgeKind::Visited) + { // Exterior edges move from inside to outside or vice-versa if curve_t > 0.1 && curve_t < 0.9 { if was_inside ^ is_inside { @@ -167,7 +216,9 @@ impl GraphPath { } // The ray should exit and enter the path an even number of times - test_assert!(path_crossings.into_iter().all(|crossing_count| crossing_count == 0)); + test_assert!(path_crossings + .into_iter() + .all(|crossing_count| crossing_count == 0)); } } } diff --git a/src/bezier/path/arithmetic/sub.rs b/src/bezier/path/arithmetic/sub.rs index 176f1c8f..1708df55 100644 --- a/src/bezier/path/arithmetic/sub.rs +++ b/src/bezier/path/arithmetic/sub.rs @@ -1,45 +1,60 @@ -use super::ray_cast::*; -use super::super::path::*; -use super::super::graph_path::*; -use super::super::super::super::geo::*; +use super::super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::graph_path::GraphPath; +use super::super::path::{BezierPath, BezierPathFactory}; +use super::ray_cast::{PathDirection, PathLabel}; -impl GraphPath { +impl GraphPath { /// /// Given a labelled graph path, marks exterior edges by subtracting `PathSource::Path2` from `PathSource::Path1` /// pub fn set_exterior_by_subtracting(&mut self) { // Use an even-odd winding rule (all edges are considered 'external') - self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 && (path_crossings[1]&1) == 0); + self.set_edge_kinds_by_ray_casting(|path_crossings| { + (path_crossings[0] & 1) != 0 && (path_crossings[1] & 1) == 0 + }); } } /// /// Generates the path formed by subtracting two sets of paths -/// +/// /// The input vectors represent the external edges of the path to subtract (a single BezierPath cannot have any holes in it, so a set of them /// effectively represents a path intended to be rendered with an even-odd winding rule) /// -pub fn path_sub(path1: &Vec, path2: &Vec, accuracy: f64) -> Vec -where P1::Point: Coordinate+Coordinate2D, - P2: BezierPath, - POut: BezierPathFactory { +pub fn path_sub( + path1: &[P1], + path2: &[P2], + accuracy: f64, +) -> Vec +where + P1::Point: Coordinate + Coordinate2D, + P2: BezierPath, + POut: BezierPathFactory, +{ // If either path is empty, short-circuit by returning the other - if path1.len() == 0 { - return path2.iter() - .map(|path| POut::from_path(path)) - .collect(); - } else if path2.len() == 0 { - return path1.iter() - .map(|path| POut::from_path(path)) - .collect(); + if path1.is_empty() { + return path2.iter().map(|path| POut::from_path(path)).collect(); + } else if path2.is_empty() { + return path1.iter().map(|path| POut::from_path(path)).collect(); } // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path1 + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide with the target side to generate a full path - merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.into_iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), accuracy); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + path2 + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + accuracy, + ); merged_path.round(accuracy); // Set the exterior edges using the 'subtract' algorithm diff --git a/src/bezier/path/bounds.rs b/src/bezier/path/bounds.rs index 5777a000..20e9044f 100644 --- a/src/bezier/path/bounds.rs +++ b/src/bezier/path/bounds.rs @@ -1,12 +1,12 @@ -use super::path::*; -use super::to_curves::*; -use super::super::curve::*; -use super::super::super::geo::*; +use super::super::super::geo::{BoundingBox, Coordinate}; +use super::super::curve::{BezierCurve, Curve}; +use super::path::BezierPath; +use super::to_curves::path_to_curves; /// /// Finds the bounds of a path -/// -pub fn path_bounding_box>(path: &P) -> Bounds { +/// +pub fn path_bounding_box>(path: &P) -> Bounds { path_to_curves(path) .map(|curve: Curve| curve.bounding_box()) .reduce(|first: Bounds, second| first.union_bounds(second)) @@ -15,8 +15,10 @@ pub fn path_bounding_box>(pat /// /// Finds the bounds of a path using the looser 'fast' algorithm -/// -pub fn path_fast_bounding_box>(path: &P) -> Bounds { +/// +pub fn path_fast_bounding_box>( + path: &P, +) -> Bounds { path_to_curves(path) .map(|curve: Curve| curve.fast_bounding_box()) .reduce(|first: Bounds, second| first.union_bounds(second)) diff --git a/src/bezier/path/graph_path/edge.rs b/src/bezier/path/graph_path/edge.rs index b55112aa..d4572471 100644 --- a/src/bezier/path/graph_path/edge.rs +++ b/src/bezier/path/graph_path/edge.rs @@ -1,19 +1,31 @@ -use super::{GraphPath, GraphEdgeRef, GraphEdge, GraphPathEdge, GraphPathEdgeKind}; -use crate::bezier::curve::*; -use crate::bezier::bounds::*; -use crate::geo::*; +use super::{GraphEdge, GraphEdgeRef, GraphPath, GraphPathEdge, GraphPathEdgeKind}; +use crate::bezier::bounds::bounding_box4; +use crate::bezier::curve::BezierCurve; +use crate::geo::{BoundingBox, Coordinate, Geo, HasBoundingBox}; +use std::cell::RefCell; use std::fmt; -use std::cell::*; impl GraphPathEdge { /// /// Creates a new graph path edge - /// + /// #[inline] - pub (crate) fn new(kind: GraphPathEdgeKind, (cp1, cp2): (Point, Point), end_idx: usize, label: Label, following_edge_idx: usize) -> GraphPathEdge { - GraphPathEdge { - label, kind, cp1, cp2, end_idx, following_edge_idx, bbox: RefCell::new(None) + pub(crate) fn new( + kind: GraphPathEdgeKind, + (cp1, cp2): (Point, Point), + end_idx: usize, + label: Label, + following_edge_idx: usize, + ) -> Self { + Self { + label, + kind, + cp1, + cp2, + end_idx, + following_edge_idx, + bbox: RefCell::new(None), } } @@ -21,24 +33,24 @@ impl GraphPathEdge { /// Invalidates the cache for this edge /// #[inline] - pub (crate) fn invalidate_cache(&self) { + pub(crate) fn invalidate_cache(&self) { (*self.bbox.borrow_mut()) = None; } } -impl<'a, Point: 'a, Label: 'a+Copy> GraphEdge<'a, Point, Label> { +impl<'a, Point: 'a, Label: 'a + Copy> GraphEdge<'a, Point, Label> { /// /// Creates a new graph edge (with an edge kind of 'exterior') - /// + /// #[inline] - pub (crate) fn new(graph: &'a GraphPath, edge: GraphEdgeRef) -> GraphEdge<'a, Point, Label> { + pub(crate) fn new( + graph: &'a GraphPath, + edge: GraphEdgeRef, + ) -> GraphEdge<'a, Point, Label> { test_assert!(edge.start_idx < graph.points.len()); test_assert!(edge.edge_idx < graph.points[edge.start_idx].forward_edges.len()); - GraphEdge { - graph: graph, - edge: edge - } + GraphEdge { graph, edge } } /// @@ -53,20 +65,20 @@ impl<'a, Point: 'a, Label: 'a+Copy> GraphEdge<'a, Point, Label> { /// Retrieves a reference to the edge in the graph /// #[inline] - fn edge<'b>(&'b self) -> &'b GraphPathEdge { + fn edge(&self) -> &GraphPathEdge { &self.graph.points[self.edge.start_idx].forward_edges[self.edge.edge_idx] } /// /// Returns if this is an interior or an exterior edge in the path - /// + /// pub fn kind(&self) -> GraphPathEdgeKind { self.edge().kind } /// /// Returns the index of the start point of this edge - /// + /// #[inline] pub fn start_point_index(&self) -> usize { if self.edge.reverse { @@ -78,7 +90,7 @@ impl<'a, Point: 'a, Label: 'a+Copy> GraphEdge<'a, Point, Label> { /// /// Returns the index of the end point of this edge - /// + /// #[inline] pub fn end_point_index(&self) -> usize { if self.edge.reverse { @@ -97,68 +109,68 @@ impl<'a, Point: 'a, Label: 'a+Copy> GraphEdge<'a, Point, Label> { } } -impl<'a, Point: 'a+Coordinate, Label: 'a> Geo for GraphEdge<'a, Point, Label> { +impl<'a, Point: 'a + Coordinate, Label: 'a> Geo for GraphEdge<'a, Point, Label> { type Point = Point; } -impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> BezierCurve for GraphEdge<'a, Point, Label> { +impl<'a, Point: 'a + Coordinate, Label: 'a + Copy> BezierCurve for GraphEdge<'a, Point, Label> { /// /// The start point of this curve - /// + /// #[inline] fn start_point(&self) -> Self::Point { - self.graph.points[self.start_point_index()].position.clone() + self.graph.points[self.start_point_index()].position } /// /// The end point of this curve - /// + /// #[inline] fn end_point(&self) -> Self::Point { - self.graph.points[self.end_point_index()].position.clone() + self.graph.points[self.end_point_index()].position } /// /// The control points in this curve - /// + /// #[inline] fn control_points(&self) -> (Self::Point, Self::Point) { let edge = self.edge(); if self.edge.reverse { - (edge.cp2.clone(), edge.cp1.clone()) + (edge.cp2, edge.cp1) } else { - (edge.cp1.clone(), edge.cp2.clone()) + (edge.cp1, edge.cp2) } } - + /// /// Faster but less accurate bounding box for a curve - /// + /// /// This will produce a bounding box that contains the curve but which may be larger than necessary - /// + /// #[inline] - fn fast_bounding_box>(&self) -> Bounds { - let edge = self.edge(); + fn fast_bounding_box>(&self) -> Bounds { + let edge = self.edge(); - let mut bbox = edge.bbox.borrow_mut(); + let mut bbox = edge.bbox.borrow_mut(); if let Some((ref min, ref max)) = *bbox { Bounds::from_min_max(*min, *max) } else { - let start = self.graph.points[self.edge.start_idx].position; - let end = self.graph.points[edge.end_idx].position; - let control_points = (edge.cp1, edge.cp2); + let start = self.graph.points[self.edge.start_idx].position; + let end = self.graph.points[edge.end_idx].position; + let control_points = (edge.cp1, edge.cp2); - let min = Self::Point::from_smallest_components(start, end); - let min = Self::Point::from_smallest_components(min, control_points.0); - let min = Self::Point::from_smallest_components(min, control_points.1); + let min = Self::Point::from_smallest_components(start, end); + let min = Self::Point::from_smallest_components(min, control_points.0); + let min = Self::Point::from_smallest_components(min, control_points.1); - let max = Self::Point::from_biggest_components(start, end); - let max = Self::Point::from_biggest_components(max, control_points.0); - let max = Self::Point::from_biggest_components(max, control_points.1); + let max = Self::Point::from_biggest_components(start, end); + let max = Self::Point::from_biggest_components(max, control_points.0); + let max = Self::Point::from_biggest_components(max, control_points.1); - let bounds = Bounds::from_min_max(min, max); + let bounds = Bounds::from_min_max(min, max); *bbox = Some((bounds.min(), bounds.max())); bounds @@ -167,14 +179,14 @@ impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> BezierCurve for GraphEdge<'a, Poi /// /// Computes the bounds of this bezier curve - /// + /// #[inline] - fn bounding_box>(&self) -> Bounds { - let edge = self.edge(); + fn bounding_box>(&self) -> Bounds { + let edge = self.edge(); - let start = self.graph.points[self.edge.start_idx].position; - let end = self.graph.points[edge.end_idx].position; - let (cp1, cp2) = (edge.cp1, edge.cp2); + let start = self.graph.points[self.edge.start_idx].position; + let end = self.graph.points[edge.end_idx].position; + let (cp1, cp2) = (edge.cp1, edge.cp2); let bounds: Bounds = bounding_box4(start, cp1, cp2, end); @@ -182,15 +194,25 @@ impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> BezierCurve for GraphEdge<'a, Poi } } -impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> HasBoundingBox for GraphEdge<'a, Point, Label> { +impl<'a, Point: 'a + Coordinate, Label: 'a + Copy> HasBoundingBox for GraphEdge<'a, Point, Label> { #[inline] - fn get_bounding_box>(&self) -> Bounds { + fn get_bounding_box>(&self) -> Bounds { self.fast_bounding_box() } } -impl<'a, Point: fmt::Debug, Label: 'a+Copy> fmt::Debug for GraphEdge<'a, Point, Label> { +impl<'a, Point: fmt::Debug, Label: 'a + Copy> fmt::Debug for GraphEdge<'a, Point, Label> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}: {:?} -> {:?} ({:?} -> {:?} ({:?}, {:?}))", self.kind(), self.edge.start_idx, self.edge().end_idx, self.graph.points[self.edge.start_idx].position, self.graph.points[self.edge().end_idx].position, self.edge().cp1, self.edge().cp2) + write!( + f, + "{:?}: {:?} -> {:?} ({:?} -> {:?} ({:?}, {:?}))", + self.kind(), + self.edge.start_idx, + self.edge().end_idx, + self.graph.points[self.edge.start_idx].position, + self.graph.points[self.edge().end_idx].position, + self.edge().cp1, + self.edge().cp2 + ) } } diff --git a/src/bezier/path/graph_path/edge_ref.rs b/src/bezier/path/graph_path/edge_ref.rs index fd63d1a0..30661c2d 100644 --- a/src/bezier/path/graph_path/edge_ref.rs +++ b/src/bezier/path/graph_path/edge_ref.rs @@ -1,11 +1,11 @@ -use super::{GraphPath, GraphEdge, GraphEdgeRef}; -use crate::geo::*; +use super::{GraphEdge, GraphEdgeRef, GraphPath}; +use crate::geo::{Coordinate, Coordinate2D}; impl GraphEdgeRef { /// /// Creates a reversed version of this edge ref /// - pub fn reversed(mut self) -> GraphEdgeRef { + pub fn reversed(mut self) -> Self { self.reverse = !self.reverse; self } @@ -14,8 +14,10 @@ impl GraphEdgeRef { /// /// A GraphEdgeRef can be created from a GraphEdge in order to release the borrow /// -impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> From> for GraphEdgeRef { - fn from(edge: GraphEdge<'a, Point, Label>) -> GraphEdgeRef { +impl<'a, Point: 'a + Coordinate, Label: 'a + Copy> From> + for GraphEdgeRef +{ + fn from(edge: GraphEdge<'a, Point, Label>) -> Self { edge.edge } } @@ -23,13 +25,15 @@ impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> From> /// /// A GraphEdgeRef can be created from a GraphEdge in order to release the borrow /// -impl<'a, 'b, Point: 'a+Coordinate, Label: 'a+Copy> From<&'b GraphEdge<'a, Point, Label>> for GraphEdgeRef { - fn from(edge: &'b GraphEdge<'a, Point, Label>) -> GraphEdgeRef { +impl<'a, 'b, Point: 'a + Coordinate, Label: 'a + Copy> From<&'b GraphEdge<'a, Point, Label>> + for GraphEdgeRef +{ + fn from(edge: &'b GraphEdge<'a, Point, Label>) -> Self { edge.edge } } -impl GraphPath { +impl GraphPath { /// /// Given an edge ref, returns the edge ref that follows it /// @@ -38,13 +42,17 @@ impl GraphPath { if edge_ref.reverse { // Need to search in reverse for the edge for connected_from in self.points[edge_ref.start_idx].connected_from.iter() { - for (edge_idx, edge) in self.points[*connected_from].forward_edges.iter().enumerate() { + for (edge_idx, edge) in self.points[*connected_from] + .forward_edges + .iter() + .enumerate() + { if edge.end_idx == edge_ref.start_idx { return GraphEdgeRef { - start_idx: *connected_from, - edge_idx: edge_idx, - reverse: true - } + start_idx: *connected_from, + edge_idx, + reverse: true, + }; } } } @@ -55,11 +63,10 @@ impl GraphPath { let edge = &self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx]; GraphEdgeRef { - start_idx: edge.end_idx, - edge_idx: edge.following_edge_idx, - reverse: false + start_idx: edge.end_idx, + edge_idx: edge.following_edge_idx, + reverse: false, } } } } - diff --git a/src/bezier/path/graph_path/mod.rs b/src/bezier/path/graph_path/mod.rs index 690e1667..6055deb8 100644 --- a/src/bezier/path/graph_path/mod.rs +++ b/src/bezier/path/graph_path/mod.rs @@ -1,31 +1,32 @@ -use super::path::*; -use crate::bezier::curve::*; -use crate::geo::*; -use crate::consts::*; +use super::path::{BezierPath, BezierPathFactory}; +use crate::bezier::curve::BezierCurve; +use crate::consts::CLOSE_DISTANCE; +use crate::geo::{Coordinate, Coordinate2D, Geo}; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; +use std::cell::RefCell; use std::fmt; -use std::cell::*; mod edge; mod edge_ref; -mod ray_collision; mod path_collision; +mod ray_collision; -#[cfg(test)] pub (crate) mod test; +#[cfg(test)] +pub(crate) mod test; pub use self::edge::*; pub use self::edge_ref::*; -pub use self::ray_collision::*; pub use self::path_collision::*; +pub use self::ray_collision::*; /// Maximum number of edges to traverse when 'healing' gaps found in an external path const MAX_HEAL_DEPTH: usize = 3; /// /// Kind of a graph path edge -/// +/// #[derive(Clone, Copy, Debug, PartialEq)] pub enum GraphPathEdgeKind { /// An edge that hasn't been categorised yet @@ -35,14 +36,14 @@ pub enum GraphPathEdgeKind { Visited, /// An exterior edge - /// + /// /// These edges represent a transition between the inside and the outside of the path - Exterior, + Exterior, /// An interior edge - /// + /// /// These edges are on the inside of the path - Interior + Interior, } /// @@ -51,18 +52,18 @@ pub enum GraphPathEdgeKind { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GraphEdgeRef { /// The index of the point this edge starts from - pub (crate) start_idx: usize, + pub(crate) start_idx: usize, /// The index of the edge within the point - pub (crate) edge_idx: usize, + pub(crate) edge_idx: usize, /// True if this reference is for the reverse of this edge - pub (crate) reverse: bool + pub(crate) reverse: bool, } /// /// Enum representing an edge in a graph path -/// +/// #[derive(Clone, Debug)] struct GraphPathEdge { /// The label attached to this edge @@ -84,7 +85,7 @@ struct GraphPathEdge { end_idx: usize, /// The bounding box of this edge, if it has been calculated - bbox: RefCell> + bbox: RefCell>, } /// @@ -99,15 +100,23 @@ struct GraphPathPoint { forward_edges: SmallVec<[GraphPathEdge; 2]>, /// The points with edges connecting to this point - connected_from: SmallVec<[usize; 2]> + connected_from: SmallVec<[usize; 2]>, } impl GraphPathPoint { /// /// Creates a new graph path point /// - fn new(position: Point, forward_edges: SmallVec<[GraphPathEdge; 2]>, connected_from: SmallVec<[usize; 2]>) -> GraphPathPoint { - GraphPathPoint { position, forward_edges, connected_from } + fn new( + position: Point, + forward_edges: SmallVec<[GraphPathEdge; 2]>, + connected_from: SmallVec<[usize; 2]>, + ) -> Self { + Self { + position, + forward_edges, + connected_from, + } } } @@ -115,14 +124,14 @@ impl GraphPathPoint { /// A graph path is a path where each point can have more than one connected edge. Edges are categorized /// into interior and exterior edges depending on if they are on the outside or the inside of the combined /// shape. -/// +/// #[derive(Clone)] pub struct GraphPath { /// The points in this graph and their edges. Each 'point' here consists of two control points and an end point points: Vec>, /// The index to assign to the next path added to this path - next_path_index: usize + next_path_index: usize, } /// @@ -134,28 +143,28 @@ pub enum CollidedGraphPath { Collided(GraphPath), /// None of the edges has collisions in them - Merged(GraphPath) + Merged(GraphPath), } impl Geo for GraphPath { type Point = Point; } -impl GraphPath { +impl GraphPath { /// /// Creates a new graph path with no points /// - pub fn new() -> GraphPath { - GraphPath { - points: vec![], - next_path_index: 0 + pub fn new() -> Self { + Self { + points: vec![], + next_path_index: 0, } } /// /// Creates a graph path from a bezier path - /// - pub fn from_path>(path: &P, label: Label) -> GraphPath { + /// + pub fn from_path>(path: &P, label: Label) -> Self { // All edges are exterior for a single path let mut points = vec![]; @@ -164,29 +173,38 @@ impl GraphPath { points.push(GraphPathPoint::new(start_point, smallvec![], smallvec![])); // We'll add edges to the previous point - let mut last_point_pos = start_point; - let mut last_point_idx = 0; - let mut next_point_idx = 1; + let mut last_point_pos = start_point; + let mut last_point_idx = 0; + let mut next_point_idx = 1; // Iterate through the points in the path for (cp1, cp2, end_point) in path.points() { // Ignore points that are too close to the last point - if end_point.is_near_to(&last_point_pos, CLOSE_DISTANCE) { - if cp1.is_near_to(&last_point_pos, CLOSE_DISTANCE) && cp2.is_near_to(&cp1, CLOSE_DISTANCE) { - continue; - } + if end_point.is_near_to(&last_point_pos, CLOSE_DISTANCE) + && cp1.is_near_to(&last_point_pos, CLOSE_DISTANCE) + && cp2.is_near_to(&cp1, CLOSE_DISTANCE) + { + continue; } // Push the points points.push(GraphPathPoint::new(end_point, smallvec![], smallvec![])); // Add an edge from the last point to the next point - points[last_point_idx].forward_edges.push(GraphPathEdge::new(GraphPathEdgeKind::Uncategorised, (cp1, cp2), next_point_idx, label, 0)); + points[last_point_idx] + .forward_edges + .push(GraphPathEdge::new( + GraphPathEdgeKind::Uncategorised, + (cp1, cp2), + next_point_idx, + label, + 0, + )); // Update the last/next pooints - last_point_idx += 1; - next_point_idx += 1; - last_point_pos = end_point; + last_point_idx += 1; + next_point_idx += 1; + last_point_pos = end_point; } // Close the path @@ -201,11 +219,19 @@ impl GraphPath { points[last_point_idx].forward_edges[0].end_idx = 0; } else { // Need to draw a line to the last point (as there is always a single following edge, the following edge index is always 0 here) - let close_vector = points[last_point_idx].position - start_point; - let cp1 = close_vector * 0.33 + start_point; - let cp2 = close_vector * 0.66 + start_point; - - points[last_point_idx].forward_edges.push(GraphPathEdge::new(GraphPathEdgeKind::Uncategorised, (cp1, cp2), 0, label, 0)); + let close_vector = points[last_point_idx].position - start_point; + let cp1 = close_vector * 0.33 + start_point; + let cp2 = close_vector * 0.66 + start_point; + + points[last_point_idx] + .forward_edges + .push(GraphPathEdge::new( + GraphPathEdgeKind::Uncategorised, + (cp1, cp2), + 0, + label, + 0, + )); } } else { // Just a start point and no edges: remove the start point as it doesn't really make sense @@ -213,9 +239,9 @@ impl GraphPath { } // Create the graph path from the points - let mut path = GraphPath { - points: points, - next_path_index: 1 + let mut path = Self { + points, + next_path_index: 1, }; path.recalculate_reverse_connections(); path @@ -224,13 +250,19 @@ impl GraphPath { /// /// Creates a new graph path by merging (not colliding) a set of paths with their labels /// - pub fn from_merged_paths<'a, P: 'a+BezierPath, PathIter: IntoIterator>(paths: PathIter) -> GraphPath { + pub fn from_merged_paths< + 'a, + P: 'a + BezierPath, + PathIter: IntoIterator, + >( + paths: PathIter, + ) -> Self { // Create an empty path - let mut merged_path = GraphPath::new(); + let mut merged_path = Self::new(); // Merge each path in turn for (path, label) in paths { - let path = GraphPath::from_path(path, label); + let path = Self::from_path(path, label); merged_path = merged_path.merge(path); } @@ -256,14 +288,14 @@ impl GraphPath { // Sort and deduplicate them for point_idx in 0..(self.points.len()) { - self.points[point_idx].connected_from.sort(); + self.points[point_idx].connected_from.sort_unstable(); self.points[point_idx].connected_from.dedup(); } } /// /// Returns the number of points in this graph. Points are numbered from 0 to this value. - /// + /// #[inline] pub fn num_points(&self) -> usize { self.points.len() @@ -273,7 +305,7 @@ impl GraphPath { /// Returns an iterator of all edges in this graph /// #[inline] - pub fn all_edges<'a>(&'a self) -> impl 'a+Iterator> { + pub fn all_edges(&self) -> impl Iterator> { (0..(self.points.len())) .into_iter() .flat_map(move |point_num| self.edges_for_point(point_num)) @@ -283,37 +315,55 @@ impl GraphPath { /// Returns an iterator of all the edges in this graph, as references /// #[inline] - pub fn all_edge_refs<'a>(&'a self) -> impl 'a+Iterator { + pub fn all_edge_refs(&self) -> impl Iterator + '_ { (0..(self.points.len())) .into_iter() - .flat_map(move |point_idx| (0..(self.points[point_idx].forward_edges.len())) - .into_iter() - .map(move |edge_idx| GraphEdgeRef { - start_idx: point_idx, - edge_idx: edge_idx, - reverse: false - })) + .flat_map(move |point_idx| { + (0..(self.points[point_idx].forward_edges.len())) + .into_iter() + .map(move |edge_idx| GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }) + }) } /// /// Returns an iterator of the edges that leave a particular point - /// + /// /// Edges are directional: this will provide the edges that leave the supplied point /// #[inline] - pub fn edges_for_point<'a>(&'a self, point_num: usize) -> impl 'a+Iterator> { + pub fn edges_for_point( + &self, + point_num: usize, + ) -> impl Iterator> { (0..(self.points[point_num].forward_edges.len())) .into_iter() - .map(move |edge_idx| GraphEdge::new(self, GraphEdgeRef { start_idx: point_num, edge_idx: edge_idx, reverse: false })) + .map(move |edge_idx| { + GraphEdge::new( + self, + GraphEdgeRef { + start_idx: point_num, + edge_idx, + reverse: false, + }, + ) + }) } /// /// Returns the edge refs for a particular point /// - pub fn edge_refs_for_point(&self, point_num: usize) -> impl Iterator { + pub fn edge_refs_for_point(&self, point_num: usize) -> impl Iterator { (0..(self.points[point_num].forward_edges.len())) .into_iter() - .map(move |edge_idx| GraphEdgeRef { start_idx: point_num, edge_idx: edge_idx, reverse: false }) + .map(move |edge_idx| GraphEdgeRef { + start_idx: point_num, + edge_idx, + reverse: false, + }) } /// @@ -321,17 +371,21 @@ impl GraphPath { /// #[inline] pub fn point_position(&self, point_num: usize) -> Point { - self.points[point_num].position.clone() + self.points[point_num].position } /// /// Returns an iterator of the edges that arrive at a particular point - /// + /// /// Edges are directional: this will provide the edges that connect to the supplied point /// - pub fn reverse_edges_for_point<'a>(&'a self, point_num: usize) -> impl 'a+Iterator> { + pub fn reverse_edges_for_point( + &self, + point_num: usize, + ) -> impl Iterator> { // Fetch the points that connect to this point - self.points[point_num].connected_from + self.points[point_num] + .connected_from .iter() .flat_map(move |connected_from| { let connected_from = *connected_from; @@ -340,8 +394,13 @@ impl GraphPath { (0..(self.points[connected_from].forward_edges.len())) .into_iter() .filter_map(move |edge_idx| { - if self.points[connected_from].forward_edges[edge_idx].end_idx == point_num { - Some(GraphEdgeRef { start_idx: connected_from, edge_idx: edge_idx, reverse: true }) + if self.points[connected_from].forward_edges[edge_idx].end_idx == point_num + { + Some(GraphEdgeRef { + start_idx: connected_from, + edge_idx, + reverse: true, + }) } else { None } @@ -352,35 +411,34 @@ impl GraphPath { /// /// Merges in another path - /// - /// This adds the edges in the new path to this path without considering if they are internal or external /// - pub fn merge(self, merge_path: GraphPath) -> GraphPath { + /// This adds the edges in the new path to this path without considering if they are internal or external + /// + pub fn merge(self, merge_path: Self) -> Self { // Copy the points from this graph - let mut new_points = self.points; - let next_path_idx = self.next_path_index; + let mut new_points = self.points; + let next_path_idx = self.next_path_index; // Add in points from the merge path - let offset = new_points.len(); - new_points.extend(merge_path.points.into_iter() - .map(|mut point| { - // Update the offsets in the edges - for mut edge in &mut point.forward_edges { - edge.end_idx += offset; - } + let offset = new_points.len(); + new_points.extend(merge_path.points.into_iter().map(|mut point| { + // Update the offsets in the edges + for mut edge in &mut point.forward_edges { + edge.end_idx += offset; + } - for previous_point in &mut point.connected_from { - *previous_point += offset; - } + for previous_point in &mut point.connected_from { + *previous_point += offset; + } - // Generate the new edge - point - })); + // Generate the new edge + point + })); // Combined path - GraphPath { - points: new_points, - next_path_index: next_path_idx + merge_path.next_path_index + Self { + points: new_points, + next_path_index: next_path_idx + merge_path.next_path_index, } } @@ -388,14 +446,14 @@ impl GraphPath { /// Returns true if the specified edge is very short (starts and ends at the same point and does not cover a significant amount of ground) /// fn edge_is_very_short(&self, edge_ref: GraphEdgeRef) -> bool { - let edge = &self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx]; + let edge = &self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx]; if edge_ref.start_idx == edge.end_idx { // Find the points on this edge let start_point = &self.points[edge_ref.start_idx].position; - let cp1 = &edge.cp1; - let cp2 = &edge.cp2; - let end_point = &self.points[edge.end_idx].position; + let cp1 = &edge.cp1; + let cp2 = &edge.cp2; + let end_point = &self.points[edge.end_idx].position; // If all the points are close to each other, then this is a short edge start_point.is_near_to(end_point, CLOSE_DISTANCE) @@ -409,7 +467,7 @@ impl GraphPath { /// /// Removes an edge by updating the previous edge to point at its next edge - /// + /// /// Control points are not updated so the shape will be distorted if the removed edge is very long /// fn remove_edge(&mut self, edge_ref: GraphEdgeRef) { @@ -417,47 +475,77 @@ impl GraphPath { self.check_following_edge_consistency(); // Find the next edge - let next_point_idx = self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx].end_idx; - let next_edge_idx = self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx].following_edge_idx; + let next_point_idx = + self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx].end_idx; + let next_edge_idx = + self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx].following_edge_idx; // Edge shouldn't just loop around to itself test_assert!(next_point_idx != edge_ref.start_idx || next_edge_idx != edge_ref.edge_idx); // ... and the preceding edge (by searching all of the connected points) - let previous_edge_ref = self.points[edge_ref.start_idx].connected_from + let previous_edge_ref = self.points[edge_ref.start_idx] + .connected_from .iter() - .map(|point_idx| { let point_idx = *point_idx; self.points[point_idx].forward_edges.iter().enumerate().map(move |(edge_idx, edge)| (point_idx, edge_idx, edge)) }) - .flatten() + .flat_map(|point_idx| { + let point_idx = *point_idx; + self.points[point_idx] + .forward_edges + .iter() + .enumerate() + .map(move |(edge_idx, edge)| (point_idx, edge_idx, edge)) + }) .filter_map(|(point_idx, edge_idx, edge)| { - if edge.end_idx == edge_ref.start_idx && edge.following_edge_idx == edge_ref.edge_idx { - Some(GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }) + if edge.end_idx == edge_ref.start_idx + && edge.following_edge_idx == edge_ref.edge_idx + { + Some(GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }) } else { None } }) - .nth(0); + .next(); test_assert!(previous_edge_ref.is_some()); if let Some(previous_edge_ref) = previous_edge_ref { - test_assert!(self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx].end_idx == edge_ref.start_idx); - test_assert!(self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx].following_edge_idx == edge_ref.edge_idx); + test_assert!( + self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx] + .end_idx + == edge_ref.start_idx + ); + test_assert!( + self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx] + .following_edge_idx + == edge_ref.edge_idx + ); // Reconnect the previous edge to the next edge - self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx].end_idx = next_point_idx; - self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx].following_edge_idx = next_edge_idx; + self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx] + .end_idx = next_point_idx; + self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx] + .following_edge_idx = next_edge_idx; // Remove the old edge from the list - self.points[edge_ref.start_idx].forward_edges.remove(edge_ref.edge_idx); + self.points[edge_ref.start_idx] + .forward_edges + .remove(edge_ref.edge_idx); // For all the connected points, update the following edge refs let mut still_connected = false; - self.points[edge_ref.start_idx].connected_from.sort(); + self.points[edge_ref.start_idx] + .connected_from + .sort_unstable(); self.points[edge_ref.start_idx].connected_from.dedup(); for connected_point_idx in self.points[edge_ref.start_idx].connected_from.clone() { for edge_idx in 0..(self.points[connected_point_idx].forward_edges.len()) { - let connected_edge = &mut self.points[connected_point_idx].forward_edges[edge_idx]; + let connected_edge = + &mut self.points[connected_point_idx].forward_edges[edge_idx]; // Only interested in edges on the point we just changed if connected_edge.end_idx != edge_ref.start_idx { @@ -481,7 +569,9 @@ impl GraphPath { // If the two points are not still connected, remove the previous point from the connected list if !still_connected { - self.points[edge_ref.start_idx].connected_from.retain(|point_idx| *point_idx != edge_ref.start_idx); + self.points[edge_ref.start_idx] + .connected_from + .retain(|point_idx| *point_idx != edge_ref.start_idx); } // Edges should be consistent again @@ -491,7 +581,7 @@ impl GraphPath { /// /// Removes any edges that appear to be 'very short' from this graph - /// + /// /// 'Very short' edges are edges that start and end at the same point and have control points very close to the start position /// fn remove_all_very_short_edges(&mut self) { @@ -499,7 +589,11 @@ impl GraphPath { let mut edge_idx = 0; while edge_idx < self.points[point_idx].forward_edges.len() { // Remove this edge if it's very short - let edge_ref = GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }; + let edge_ref = GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }; if self.edge_is_very_short(edge_ref) { self.remove_edge(edge_ref); } else { @@ -512,24 +606,32 @@ impl GraphPath { /// /// Collides this path against another, generating a merged path - /// + /// /// Anywhere this graph intersects the second graph, a point with two edges will be generated. All edges will be left as /// interior or exterior depending on how they're set on the graph they originate from. - /// + /// /// Working out the collision points is the first step to performing path arithmetic: the resulting graph can be altered /// to specify edge types - knowing if an edge is an interior or exterior edge makes it possible to tell the difference /// between a hole cut into a shape and an intersection. - /// + /// /// Unlike collide(), this will indicate if any collisions were detected or if the two paths merged without collisions - /// - pub fn collide_or_merge(mut self, collide_path: GraphPath, accuracy: f64) -> CollidedGraphPath { + /// + pub fn collide_or_merge( + mut self, + collide_path: Self, + accuracy: f64, + ) -> CollidedGraphPath { // Generate a merged path with all of the edges - let collision_offset = self.points.len(); - self = self.merge(collide_path); + let collision_offset = self.points.len(); + self = self.merge(collide_path); // Search for collisions between our original path and the new one let total_points = self.points.len(); - if self.detect_collisions(0..collision_offset, collision_offset..total_points, accuracy) { + if self.detect_collisions( + 0..collision_offset, + collision_offset..total_points, + accuracy, + ) { CollidedGraphPath::Collided(self) } else { CollidedGraphPath::Merged(self) @@ -538,22 +640,26 @@ impl GraphPath { /// /// Collides this path against another, generating a merged path - /// + /// /// Anywhere this graph intersects the second graph, a point with two edges will be generated. All edges will be left as /// interior or exterior depending on how they're set on the graph they originate from. - /// + /// /// Working out the collision points is the first step to performing path arithmetic: the resulting graph can be altered /// to specify edge types - knowing if an edge is an interior or exterior edge makes it possible to tell the difference /// between a hole cut into a shape and an intersection. - /// - pub fn collide(mut self, collide_path: GraphPath, accuracy: f64) -> GraphPath { + /// + pub fn collide(mut self, collide_path: Self, accuracy: f64) -> Self { // Generate a merged path with all of the edges - let collision_offset = self.points.len(); - self = self.merge(collide_path); + let collision_offset = self.points.len(); + self = self.merge(collide_path); // Search for collisions between our original path and the new one let total_points = self.points.len(); - self.detect_collisions(0..collision_offset, collision_offset..total_points, accuracy); + self.detect_collisions( + 0..collision_offset, + collision_offset..total_points, + accuracy, + ); // Return the result self @@ -567,8 +673,12 @@ impl GraphPath { self.points[point_idx].position.round(accuracy); for edge_idx in 0..(self.points[point_idx].forward_edges.len()) { - self.points[point_idx].forward_edges[edge_idx].cp1.round(accuracy); - self.points[point_idx].forward_edges[edge_idx].cp2.round(accuracy); + self.points[point_idx].forward_edges[edge_idx] + .cp1 + .round(accuracy); + self.points[point_idx].forward_edges[edge_idx] + .cp2 + .round(accuracy); } } } @@ -585,7 +695,7 @@ impl GraphPath { /// Returns the GraphEdge for an edgeref /// #[inline] - pub fn get_edge<'a>(&'a self, edge: GraphEdgeRef) -> GraphEdge<'a, Point, Label> { + pub fn get_edge(&self, edge: GraphEdgeRef) -> GraphEdge { GraphEdge::new(self, edge) } @@ -620,7 +730,7 @@ impl GraphPath { pub fn edge_label(&self, edge: GraphEdgeRef) -> Label { self.points[edge.start_idx].forward_edges[edge.edge_idx].label } - + /// /// Resets the edge kinds in this path by setting them all to uncategorised /// @@ -636,8 +746,8 @@ impl GraphPath { /// Sets the kind of an edge and any connected edge where there are no intersections (only one edge) /// pub fn set_edge_kind_connected(&mut self, edge: GraphEdgeRef, kind: GraphPathEdgeKind) { - let mut current_edge = edge; - let mut visited = vec![false; self.points.len()]; + let mut current_edge = edge; + let mut visited = vec![false; self.points.len()]; // Move forward loop { @@ -646,8 +756,9 @@ impl GraphPath { visited[current_edge.start_idx] = true; // Pick the next edge - let end_idx = self.points[current_edge.start_idx].forward_edges[current_edge.edge_idx].end_idx; - let edges = &self.points[end_idx].forward_edges; + let end_idx = + self.points[current_edge.start_idx].forward_edges[current_edge.edge_idx].end_idx; + let edges = &self.points[end_idx].forward_edges; if edges.len() != 1 { // At an intersection @@ -655,9 +766,9 @@ impl GraphPath { } else { // Move on current_edge = GraphEdgeRef { - start_idx: end_idx, - edge_idx: 0, - reverse: false + start_idx: end_idx, + edge_idx: 0, + reverse: false, } } @@ -678,15 +789,18 @@ impl GraphPath { break; } else { // There's a single preceding point (but maybe more than one edge) - let current_point_idx = current_edge.start_idx; - let previous_point_idx = self.points[current_edge.start_idx].connected_from[0]; + let current_point_idx = current_edge.start_idx; + let previous_point_idx = self.points[current_edge.start_idx].connected_from[0]; // Find the index of the preceding edge - let mut previous_edges = (0..(self.points[previous_point_idx].forward_edges.len())) + let mut previous_edges = (0..(self.points[previous_point_idx].forward_edges.len())) .into_iter() - .filter(|edge_idx| self.points[previous_point_idx].forward_edges[*edge_idx].end_idx == current_point_idx); + .filter(|edge_idx| { + self.points[previous_point_idx].forward_edges[*edge_idx].end_idx + == current_point_idx + }); - let previous_edge_idx = previous_edges.next().expect("Previous edge"); + let previous_edge_idx = previous_edges.next().expect("Previous edge"); if previous_edges.next().is_some() { // There is more than one edge connecting these two points break; @@ -694,9 +808,9 @@ impl GraphPath { // Move on to the next edge current_edge = GraphEdgeRef { - start_idx: previous_point_idx, - edge_idx: previous_edge_idx, - reverse: false + start_idx: previous_point_idx, + edge_idx: previous_edge_idx, + reverse: false, }; // Change its kind @@ -717,7 +831,8 @@ impl GraphPath { self.edges_for_point(point_idx) .chain(self.reverse_edges_for_point(point_idx)) .filter(|edge| edge.kind() == GraphPathEdgeKind::Exterior) - .count() == 1 + .count() + == 1 } /// @@ -725,18 +840,27 @@ impl GraphPath { /// fn edge_has_gap(&self, edge: GraphEdgeRef) -> bool { // Interior edges have no gaps - if self.points[edge.start_idx].forward_edges[edge.edge_idx].kind != GraphPathEdgeKind::Exterior { + if self.points[edge.start_idx].forward_edges[edge.edge_idx].kind + != GraphPathEdgeKind::Exterior + { false } else { // Get the end point index for this edge let (start_idx, end_idx) = if edge.reverse { - (self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx, edge.start_idx) + ( + self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx, + edge.start_idx, + ) } else { - (edge.start_idx, self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx) + ( + edge.start_idx, + self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx, + ) }; // Result is true if there is no edge attached to the end point that is marked exterior (other than the edge leading back to the initial point) - !self.edges_for_point(end_idx) + !self + .edges_for_point(end_idx) .chain(self.reverse_edges_for_point(end_idx)) .filter(|following_edge| following_edge.end_point_index() != start_idx) .any(|following_edge| following_edge.kind() == GraphPathEdgeKind::Exterior) @@ -752,10 +876,10 @@ impl GraphPath { let end_point_idx = self.points[point_idx].forward_edges[edge_idx].end_idx; // State of the algorithm - let mut preceding_edge = vec![None; self.points.len()]; - let mut points_to_process = vec![(point_idx, end_point_idx)]; - let mut current_depth = 0; - let mut target_point_idx = None; + let mut preceding_edge = vec![None; self.points.len()]; + let mut points_to_process = vec![(point_idx, end_point_idx)]; + let mut current_depth = 0; + let mut target_point_idx = None; // Iterate until we hit the maximum depth while current_depth < max_depth && target_point_idx.is_none() { @@ -765,24 +889,36 @@ impl GraphPath { // Process all the points found in the previous pass for (from_point_idx, next_point_idx) in points_to_process { // Stop once we find a point - if target_point_idx.is_some() { break; } + if target_point_idx.is_some() { + break; + } // Process all edges connected to this point - for next_edge in self.edges_for_point(next_point_idx) /*.chain(self.reverse_edges_for_point(next_point_idx)) */ { - let edge_end_point_idx = next_edge.end_point_index(); - let next_edge_ref = GraphEdgeRef::from(&next_edge); - let edge_start_idx = next_edge.start_point_index(); + for next_edge in self.edges_for_point(next_point_idx) + /*.chain(self.reverse_edges_for_point(next_point_idx)) */ + { + let edge_end_point_idx = next_edge.end_point_index(); + let next_edge_ref = GraphEdgeRef::from(&next_edge); + let edge_start_idx = next_edge.start_point_index(); // Don't go back the way we came - if edge_end_point_idx == from_point_idx { continue; } + if edge_end_point_idx == from_point_idx { + continue; + } // Don't revisit points we already have a trail for - if preceding_edge[edge_end_point_idx].is_some() { continue; } + if preceding_edge[edge_end_point_idx].is_some() { + continue; + } // Ignore exterior edges (except exterior edges where edge_has_gap is true, which indicate we've crossed our gap) let mut reversed_edge_ref = next_edge_ref; reversed_edge_ref.reverse = !reversed_edge_ref.reverse; - if next_edge.kind() == GraphPathEdgeKind::Exterior && !self.edge_has_gap(reversed_edge_ref) { continue; } + if next_edge.kind() == GraphPathEdgeKind::Exterior + && !self.edge_has_gap(reversed_edge_ref) + { + continue; + } // Add this as a preceding edge preceding_edge[edge_end_point_idx] = Some(next_edge_ref); @@ -807,14 +943,17 @@ impl GraphPath { } if let Some(target_point_idx) = target_point_idx { - // Target_point represents the final point in the + // Target_point represents the final point in the let mut current_point_idx = target_point_idx; while current_point_idx != end_point_idx { - let previous_edge_ref = preceding_edge[current_point_idx].expect("Previous point during gap healing"); + let previous_edge_ref = + preceding_edge[current_point_idx].expect("Previous point during gap healing"); // Mark this edge as exterior - self.points[previous_edge_ref.start_idx].forward_edges[previous_edge_ref.edge_idx].kind = GraphPathEdgeKind::Exterior; + self.points[previous_edge_ref.start_idx].forward_edges + [previous_edge_ref.edge_idx] + .kind = GraphPathEdgeKind::Exterior; // Move to the previous point let previous_edge = self.get_edge(previous_edge_ref); @@ -831,7 +970,7 @@ impl GraphPath { /// /// Finds any gaps in the edges marked as exterior and attempts to 'heal' them by finding a route to another /// part of the path with a missing edge - /// + /// /// Returns true if all the gaps that were found were 'healed' /// pub fn heal_exterior_gaps(&mut self) -> bool { @@ -841,7 +980,11 @@ impl GraphPath { for point_idx in 0..(self.points.len()) { for edge_idx in 0..(self.points[point_idx].forward_edges.len()) { // If this edge has a gap... - if self.edge_has_gap(GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }) { + if self.edge_has_gap(GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }) { // ... try to heal it if !self.heal_edge_with_gap(point_idx, edge_idx, MAX_HEAL_DEPTH) { all_healed = false; @@ -856,15 +999,15 @@ impl GraphPath { /// /// Finds the exterior edges and turns them into a series of paths /// - pub fn exterior_paths>(&self) -> Vec { + pub fn exterior_paths>(&self) -> Vec { // List of paths returned by this function let mut exterior_paths = vec![]; // Array of points visited on a path that we've added to the result let mut visited = vec![false; self.points.len()]; - let mut previous_point = vec![None; self.points.len()]; - let mut points_to_check: SmallVec<[_; 16]> = smallvec![]; + let mut previous_point = vec![None; self.points.len()]; + let mut points_to_check: SmallVec<[_; 16]> = smallvec![]; for point_idx in 0..(self.points.len()) { // Ignore this point if we've already visited it as part of a path @@ -885,7 +1028,7 @@ impl GraphPath { // Loop until we find a previous point for the initial point (indicating we've got a loop of points) while previous_point[point_idx].is_none() { - if points_to_check.len() == 0 { + if points_to_check.is_empty() { // Ran out of points to check to find a loop (there is no loop for this point) break; } @@ -896,7 +1039,8 @@ impl GraphPath { for (previous_point_idx, current_point_idx) in points_to_check { let mut edges = if current_point_idx == point_idx { // For the first point, only search forward - self.reverse_edges_for_point(current_point_idx).collect::>() + self.reverse_edges_for_point(current_point_idx) + .collect::>() } else { // For all other points, search all edges self.edges_for_point(current_point_idx) @@ -905,7 +1049,12 @@ impl GraphPath { }; // Only follow exterior edges... - if current_point_idx == point_idx || edges.iter().any(|edge| edge.kind() == GraphPathEdgeKind::Exterior && edge.end_point_index() != previous_point_idx) { + if current_point_idx == point_idx + || edges.iter().any(|edge| { + edge.kind() == GraphPathEdgeKind::Exterior + && edge.end_point_index() != previous_point_idx + }) + { // ... unless the only exterior edge is the one we arrived on, in which case we'll follow interior edges to try to bridge gaps as a backup measure edges.retain(|edge| edge.kind() == GraphPathEdgeKind::Exterior); } else { @@ -942,12 +1091,12 @@ impl GraphPath { // If we found a loop, generate a path if previous_point[point_idx].is_some() { - let mut path_points = vec![]; - let mut cur_point_idx = point_idx; + let mut path_points = vec![]; + let mut cur_point_idx = point_idx; while let Some((last_point_idx, ref edge)) = previous_point[cur_point_idx] { // Push to the path points (we're following the edges in reverse, so points are in reverse order) - let (cp1, cp2) = edge.control_points(); + let (cp1, cp2) = edge.control_points(); let start_point = edge.start_point(); path_points.push((cp2, cp1, start_point)); @@ -965,9 +1114,9 @@ impl GraphPath { } // Start point of the path is the initial point we checked - let start_point = self.points[point_idx].position.clone(); + let start_point = self.points[point_idx].position; - let new_path = POut::from_points(start_point, path_points); + let new_path = POut::from_points(start_point, path_points); exterior_paths.push(new_path); } } @@ -979,17 +1128,19 @@ impl GraphPath { /// /// Represents an edge in a graph path -/// +/// #[derive(Clone)] pub struct GraphEdge<'a, Point: 'a, Label: 'a> { /// The graph that this point is for graph: &'a GraphPath, /// A reference to the edge this point is for - edge: GraphEdgeRef + edge: GraphEdgeRef, } -impl fmt::Debug for GraphPath { +impl fmt::Debug + for GraphPath +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for point_idx in 0..(self.points.len()) { write!(f, "\nPoint {:?}:", point_idx)?; diff --git a/src/bezier/path/graph_path/path_collision.rs b/src/bezier/path/graph_path/path_collision.rs index a70a5f58..247c336b 100644 --- a/src/bezier/path/graph_path/path_collision.rs +++ b/src/bezier/path/graph_path/path_collision.rs @@ -1,14 +1,16 @@ -use super::{GraphPath, GraphEdge, GraphEdgeRef, GraphPathPoint, GraphPathEdge}; -use crate::bezier::curve::*; -use crate::bezier::intersection::*; -use crate::geo::*; -use crate::consts::*; +use super::{GraphEdge, GraphEdgeRef, GraphPath, GraphPathEdge, GraphPathPoint}; +use crate::bezier::curve::{BezierCurve, BezierCurveFactory, Curve}; +use crate::bezier::intersection::{curve_intersects_curve_clip, find_self_intersection_point}; +use crate::consts::{CLOSE_DISTANCE, SMALL_T_DISTANCE}; +use crate::geo::{ + sweep_against, sweep_self, BoundingBox, Bounds, Coordinate, Coordinate2D, Geo, HasBoundingBox, +}; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; +use std::cmp::Ordering; use std::mem; use std::ops::Range; -use std::cmp::Ordering; /// /// Struct describing a collision between two edges @@ -25,29 +27,42 @@ struct Collision { edge_1_t: f64, /// The location on edge2 of the collision - edge_2_t: f64 + edge_2_t: f64, } -impl GraphPath { - /// +impl GraphPath { + /// /// True if the t value is effectively at the start of the curve - /// + /// #[inline] - fn t_is_zero(t: f64) -> bool { t <= 0.0 } + fn t_is_zero(t: f64) -> bool { + t <= 0.0 + } /// /// True if the t value is effective at the end of the curve - /// + /// #[inline] - fn t_is_one(t: f64) -> bool { t >= 1.0 } + fn t_is_one(t: f64) -> bool { + t >= 1.0 + } /// /// Retrieves the ordered graph edges for a range of points /// - fn get_ordered_edges<'a>(&'a self, points: Range) -> Vec> { - let mut ordered_edges = points.into_iter() - .flat_map(|point_idx| (0..self.points[point_idx].forward_edges.len()).into_iter().map(move |edge_idx| (point_idx, edge_idx))) - .map(|(point_idx, edge_idx)| GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }) + fn get_ordered_edges(&self, points: Range) -> Vec> { + let mut ordered_edges = points + .into_iter() + .flat_map(|point_idx| { + (0..self.points[point_idx].forward_edges.len()) + .into_iter() + .map(move |edge_idx| (point_idx, edge_idx)) + }) + .map(|(point_idx, edge_idx)| GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }) .map(|edge_ref| GraphEdge::new(self, edge_ref)) .collect::>(); @@ -55,7 +70,10 @@ impl GraphPath { let bb1 = edge1.get_bounding_box::>(); let bb2 = edge2.get_bounding_box::>(); - bb1.min().x().partial_cmp(&bb2.min().x()).unwrap_or(Ordering::Equal) + bb1.min() + .x() + .partial_cmp(&bb2.min().x()) + .unwrap_or(Ordering::Equal) }); ordered_edges @@ -66,7 +84,7 @@ impl GraphPath { /// #[inline] fn snap_points(p1: &Point, p2: &Point) -> Point { - Point::from_components(&[(p1.x() + p2.x())/2.0, (p1.y() + p2.y())/2.0]) + Point::from_components(&[(p1.x() + p2.x()) / 2.0, (p1.y() + p2.y()) / 2.0]) } /// @@ -74,8 +92,8 @@ impl GraphPath { /// #[inline] fn point_is_near(p1: &Point, p2: &Point, max_distance_squared: f64) -> bool { - let offset = *p1 - *p2; - let squared_distance = offset.dot(&offset); + let offset = *p1 - *p2; + let squared_distance = offset.dot(&offset); squared_distance <= max_distance_squared } @@ -93,33 +111,38 @@ impl GraphPath { for (src_curve, tgt_curve) in sweep_self(ordered_edges.iter()) { // Find any collisions between the two edges (to the required accuracy) let mut edge_collisions = curve_intersects_curve_clip(src_curve, tgt_curve, accuracy); - if edge_collisions.len() == 0 { continue; } + if edge_collisions.is_empty() { + continue; + } // Remove any pairs of collisions that are too close together remove_and_round_close_collisions(&mut edge_collisions, src_curve, tgt_curve); // Turn into collisions, filtering out the collisions that occur at the ends (where one edge joins another). // For cases where we get a collision at the end of an edge, wait for the one at the beginning of the next one - let edge_collisions = edge_collisions.into_iter() - .filter(|(src_t, tgt_t)| !(Self::t_is_one(*src_t) || Self::t_is_one(*tgt_t) || (Self::t_is_zero(*src_t) && Self::t_is_zero(*tgt_t)))) - .map(|(src_t, tgt_t)| { - Collision { - edge_1: src_curve.edge, - edge_2: tgt_curve.edge, - edge_1_t: src_t, - edge_2_t: tgt_t - } + let edge_collisions = edge_collisions + .into_iter() + .filter(|(src_t, tgt_t)| { + !(Self::t_is_one(*src_t) + || Self::t_is_one(*tgt_t) + || (Self::t_is_zero(*src_t) && Self::t_is_zero(*tgt_t))) + }) + .map(|(src_t, tgt_t)| Collision { + edge_1: src_curve.edge, + edge_2: tgt_curve.edge, + edge_1_t: src_t, + edge_2_t: tgt_t, }) .map(|mut collision| { // If the collision is at the end of the edge, move it to the start of the following edge if Self::t_is_one(collision.edge_1_t) { - collision.edge_1 = self.following_edge_ref(collision.edge_1); - collision.edge_1_t = 0.0; + collision.edge_1 = self.following_edge_ref(collision.edge_1); + collision.edge_1_t = 0.0; } if Self::t_is_one(collision.edge_2_t) { - collision.edge_2 = self.following_edge_ref(collision.edge_2); - collision.edge_2_t = 0.0; + collision.edge_2 = self.following_edge_ref(collision.edge_2); + collision.edge_2_t = 0.0; } collision @@ -133,12 +156,12 @@ impl GraphPath { for edge in ordered_edges { // Colliding edge against itself if let Some((t1, t2)) = find_self_intersection_point(&edge, accuracy) { - if !(t1 <= 0.0 && t2 >= 1.0) && !(t1 >= 1.0 && t2 <= 0.0) { + if !(t1 <= 0.0 && t2 >= 1.0 || t1 >= 1.0 && t2 <= 0.0) { collisions.push(Collision { - edge_1: edge.edge, - edge_2: edge.edge, - edge_1_t: t1, - edge_2_t: t2 + edge_1: edge.edge, + edge_2: edge.edge, + edge_1_t: t1, + edge_2_t: t2, }); } } @@ -150,7 +173,12 @@ impl GraphPath { /// /// Finds any collisions that might exist between two ranges of points /// - fn find_collisions(&self, collide_from: Range, collide_to: Range, accuracy: f64) -> Vec { + fn find_collisions( + &self, + collide_from: Range, + collide_to: Range, + accuracy: f64, + ) -> Vec { if collide_from == collide_to { return self.find_self_collisions(collide_from, accuracy); } @@ -164,34 +192,39 @@ impl GraphPath { for (src_curve, tgt_curve) in sweep_against(collide_src.iter(), collide_tgt.iter()) { // Find any collisions between the two edges (to the required accuracy) - let mut edge_collisions = curve_intersects_curve_clip(src_curve, tgt_curve, accuracy); - if edge_collisions.len() == 0 { continue; } + let mut edge_collisions = curve_intersects_curve_clip(src_curve, tgt_curve, accuracy); + if edge_collisions.is_empty() { + continue; + } // Remove any pairs of collisions that are too close together remove_and_round_close_collisions(&mut edge_collisions, src_curve, tgt_curve); // Turn into collisions, filtering out the collisions that occur at the ends (where one edge joins another). // For cases where we get a collision at the end of an edge, wait for the one at the beginning of the next one - let edge_collisions = edge_collisions.into_iter() - .filter(|(src_t, tgt_t)| !(Self::t_is_one(*src_t) || Self::t_is_one(*tgt_t) || (Self::t_is_zero(*src_t) && Self::t_is_zero(*tgt_t)))) - .map(|(src_t, tgt_t)| { - Collision { - edge_1: src_curve.edge, - edge_2: tgt_curve.edge, - edge_1_t: src_t, - edge_2_t: tgt_t - } + let edge_collisions = edge_collisions + .into_iter() + .filter(|(src_t, tgt_t)| { + !(Self::t_is_one(*src_t) + || Self::t_is_one(*tgt_t) + || (Self::t_is_zero(*src_t) && Self::t_is_zero(*tgt_t))) + }) + .map(|(src_t, tgt_t)| Collision { + edge_1: src_curve.edge, + edge_2: tgt_curve.edge, + edge_1_t: src_t, + edge_2_t: tgt_t, }) .map(|mut collision| { // If the collision is at the end of the edge, move it to the start of the following edge if Self::t_is_one(collision.edge_1_t) { - collision.edge_1 = self.following_edge_ref(collision.edge_1); - collision.edge_1_t = 0.0; + collision.edge_1 = self.following_edge_ref(collision.edge_1); + collision.edge_1_t = 0.0; } if Self::t_is_one(collision.edge_2_t) { - collision.edge_2 = self.following_edge_ref(collision.edge_2); - collision.edge_2_t = 0.0; + collision.edge_2 = self.following_edge_ref(collision.edge_2); + collision.edge_2_t = 0.0; } collision @@ -222,14 +255,14 @@ impl GraphPath { collision.edge_2.start_idx } else { // Create a new point - let edge = self.get_edge(collision.edge_1); - let new_point_pos = edge.point_at_pos(collision.edge_1_t); - let new_point_idx = self.points.len(); + let edge = self.get_edge(collision.edge_1); + let new_point_pos = edge.point_at_pos(collision.edge_1_t); + let new_point_idx = self.points.len(); self.points.push(GraphPathPoint { - position: new_point_pos, - forward_edges: smallvec![], - connected_from: smallvec![] + position: new_point_pos, + forward_edges: smallvec![], + connected_from: smallvec![], }); new_point_idx @@ -244,25 +277,29 @@ impl GraphPath { /// /// Given a list of collisions and the point where they end, organizes them by edge - /// + /// /// Return type is a vector of edges for each point, where each edge is a list of collisions, as 't' value on the edge and the /// index of the end point /// - fn organize_collisions_by_edge(&self, collisions: Vec<(Collision, usize)>) -> Vec; 2]>>> { + fn organize_collisions_by_edge( + &self, + collisions: Vec<(Collision, usize)>, + ) -> Vec; 2]>>> { // Initially there are no collisions for any point - let mut points: Vec; 2]>>> = vec![None; self.num_points()]; + let mut points: Vec; 2]>>> = + vec![None; self.num_points()]; // Iterate through the collisions and store them per edge. Every collision affects two edges for (collision, end_point_idx) in collisions.iter() { // First edge let point = points[collision.edge_1.start_idx].get_or_insert_with(|| smallvec![smallvec![]; self.points[collision.edge_1.start_idx].forward_edges.len()]); - let edge = &mut point[collision.edge_1.edge_idx]; + let edge = &mut point[collision.edge_1.edge_idx]; edge.push((collision.edge_1_t, *end_point_idx)); // Second edge let point = points[collision.edge_2.start_idx].get_or_insert_with(|| smallvec![smallvec![]; self.points[collision.edge_2.start_idx].forward_edges.len()]); - let edge = &mut point[collision.edge_2.edge_idx]; + let edge = &mut point[collision.edge_2.edge_idx]; edge.push((collision.edge_2_t, *end_point_idx)); } @@ -273,44 +310,59 @@ impl GraphPath { /// /// Searches two ranges of points in this object and detects collisions between them, subdividing the edges /// and creating branch points at the appropriate places. - /// + /// /// collide_from must indicate indices lower than collide_to - /// + /// /// Returns true if any collisions were found - /// - pub (crate) fn detect_collisions(&mut self, collide_from: Range, collide_to: Range, accuracy: f64) -> bool { + /// + pub(crate) fn detect_collisions( + &mut self, + collide_from: Range, + collide_to: Range, + accuracy: f64, + ) -> bool { // Find all of the collision points - let all_collisions = self.find_collisions(collide_from, collide_to, accuracy); - if all_collisions.len() == 0 { + let all_collisions = self.find_collisions(collide_from, collide_to, accuracy); + if all_collisions.is_empty() { let collided_at_point = self.combine_overlapping_points(accuracy); self.remove_all_very_short_edges(); return collided_at_point; } // Add in any extra points that are required by the collisions we found - let all_collisions = self.create_collision_points(all_collisions); + let all_collisions = self.create_collision_points(all_collisions); // Organize the collisions by edge - let collisions_by_edge = self.organize_collisions_by_edge(all_collisions); + let collisions_by_edge = self.organize_collisions_by_edge(all_collisions); // Limit to just points with collisions - let collisions_by_point = collisions_by_edge.into_iter() - .enumerate() - .filter_map(|(point_idx, collisions)| collisions.map(|collisions| (point_idx, collisions))); + let collisions_by_point = + collisions_by_edge + .into_iter() + .enumerate() + .filter_map(|(point_idx, collisions)| { + collisions.map(|collisions| (point_idx, collisions)) + }); // Actually divide the edges by collision for (point_idx, edge_collisions) in collisions_by_point { for (edge_idx, mut collisions) in edge_collisions.into_iter().enumerate() { // Skip edges with no collisions - if collisions.len() == 0 { continue; } + if collisions.is_empty() { + continue; + } self.check_following_edge_consistency(); // Create a copy of the edge. Our future edges will all have the same kind and label as the edge that's being divided - let edge = self.get_edge(GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }); - let kind = edge.kind(); - let label = edge.label(); - let edge = Curve::from_curve(&edge); + let edge = self.get_edge(GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }); + let kind = edge.kind(); + let label = edge.label(); + let edge = Curve::from_curve(&edge); // Sort collisions by t value collisions.sort_by(|(t1, _end_point_idx1), (t2, _end_point_idx2)| { @@ -324,87 +376,111 @@ impl GraphPath { }); // We'll progressively split bits from the edge - let mut remaining_edge = edge; - let mut remaining_t = 1.0; - let final_point_idx = self.points[point_idx].forward_edges[edge_idx].end_idx; - let final_following_edge_idx = self.points[point_idx].forward_edges[edge_idx].following_edge_idx; - let mut last_point_idx = point_idx; - let mut previous_edge = None; - let mut found_collisions = false; + let mut remaining_edge = edge; + let mut remaining_t = 1.0; + let final_point_idx = self.points[point_idx].forward_edges[edge_idx].end_idx; + let final_following_edge_idx = + self.points[point_idx].forward_edges[edge_idx].following_edge_idx; + let mut last_point_idx = point_idx; + let mut previous_edge = None; + let mut found_collisions = false; // Iterate through the collisions (skipping any at t=0) - let mut collisions = collisions.into_iter() - .filter(|(t, _)| !Self::t_is_zero(*t)); + let mut collisions = collisions.into_iter().filter(|(t, _)| !Self::t_is_zero(*t)); // First collision is special as we need to edit the existing edge instead of adding a new one if let Some((t, end_point_idx)) = collisions.next() { // Subdivide the edge let (next_edge, new_remaining_edge) = remaining_edge.subdivide::>(t); - let following_edge_idx = self.points[end_point_idx].forward_edges.len(); - let (cp1, cp2) = next_edge.control_points(); + let following_edge_idx = self.points[end_point_idx].forward_edges.len(); + let (cp1, cp2) = next_edge.control_points(); - test_assert!(next_edge.start_point().is_near_to(&self.points[point_idx].position, 0.1)); - test_assert!(next_edge.end_point().is_near_to(&self.points[end_point_idx].position, 0.1)); + test_assert!(next_edge + .start_point() + .is_near_to(&self.points[point_idx].position, 0.1)); + test_assert!(next_edge + .end_point() + .is_near_to(&self.points[end_point_idx].position, 0.1)); // Update the control points and end point index - let old_edge = &mut self.points[point_idx].forward_edges[edge_idx]; + let old_edge = &mut self.points[point_idx].forward_edges[edge_idx]; - old_edge.cp1 = cp1; - old_edge.cp2 = cp2; - old_edge.end_idx = end_point_idx; + old_edge.cp1 = cp1; + old_edge.cp2 = cp2; + old_edge.end_idx = end_point_idx; old_edge.following_edge_idx = following_edge_idx; old_edge.invalidate_cache(); // Move on to the next edge - previous_edge = Some((point_idx, edge_idx)); - remaining_t = 1.0-t; - remaining_edge = new_remaining_edge; - last_point_idx = end_point_idx; - found_collisions = true; + previous_edge = Some((point_idx, edge_idx)); + remaining_t = 1.0 - t; + remaining_edge = new_remaining_edge; + last_point_idx = end_point_idx; + found_collisions = true; } // Deal with the rest of the collisions for (t, end_point_idx) in collisions { // Point the previous edge at the new edge we're adding let new_edge_idx = self.points[last_point_idx].forward_edges.len(); - previous_edge.map(|(point_idx, edge_idx)| self.points[point_idx].forward_edges[edge_idx].following_edge_idx = new_edge_idx); + if let Some((point_idx, edge_idx)) = previous_edge { + self.points[point_idx].forward_edges[edge_idx].following_edge_idx = + new_edge_idx + } // Subdivide the remaining edge - let t2 = (t - (1.0-remaining_t))/remaining_t; + let t2 = (t - (1.0 - remaining_t)) / remaining_t; let (next_edge, new_remaining_edge) = remaining_edge.subdivide::>(t2); - let (cp1, cp2) = next_edge.control_points(); + let (cp1, cp2) = next_edge.control_points(); - test_assert!(next_edge.start_point().is_near_to(&self.points[last_point_idx].position, 0.1)); - test_assert!(next_edge.end_point().is_near_to(&self.points[end_point_idx].position, 0.1)); + test_assert!(next_edge + .start_point() + .is_near_to(&self.points[last_point_idx].position, 0.1)); + test_assert!(next_edge + .end_point() + .is_near_to(&self.points[end_point_idx].position, 0.1)); // Add the new edge to the previous point - let new_edge = GraphPathEdge::new(kind, (cp1, cp2), end_point_idx, label, 0); + let new_edge = GraphPathEdge::new(kind, (cp1, cp2), end_point_idx, label, 0); self.points[last_point_idx].forward_edges.push(new_edge); // Move on to the next edge - previous_edge = Some((last_point_idx, new_edge_idx)); - remaining_t = 1.0-t; - remaining_edge = new_remaining_edge; - last_point_idx = end_point_idx; - found_collisions = true; + previous_edge = Some((last_point_idx, new_edge_idx)); + remaining_t = 1.0 - t; + remaining_edge = new_remaining_edge; + last_point_idx = end_point_idx; + found_collisions = true; } // Provided there was at least one collision (ie, not just one at t=0), add the final edge if found_collisions { // Point the previous edge at the new edge we're adding let new_edge_idx = self.points[last_point_idx].forward_edges.len(); - previous_edge.map(|(point_idx, edge_idx)| self.points[point_idx].forward_edges[edge_idx].following_edge_idx = new_edge_idx); + if let Some((point_idx, edge_idx)) = previous_edge { + self.points[point_idx].forward_edges[edge_idx].following_edge_idx = + new_edge_idx + } // This edge ends where the original edge ended - let end_point_idx = final_point_idx; - let following_edge_idx = final_following_edge_idx; - let (cp1, cp2) = remaining_edge.control_points(); + let end_point_idx = final_point_idx; + let following_edge_idx = final_following_edge_idx; + let (cp1, cp2) = remaining_edge.control_points(); - test_assert!(remaining_edge.start_point().is_near_to(&self.points[last_point_idx].position, 0.1)); - test_assert!(remaining_edge.end_point().is_near_to(&self.points[end_point_idx].position, 0.1)); + test_assert!(remaining_edge + .start_point() + .is_near_to(&self.points[last_point_idx].position, 0.1)); + test_assert!(remaining_edge + .end_point() + .is_near_to(&self.points[end_point_idx].position, 0.1)); // Add to the final point - let final_edge = GraphPathEdge::new(kind, (cp1, cp2), end_point_idx, label, following_edge_idx); + let final_edge = GraphPathEdge::new( + kind, + (cp1, cp2), + end_point_idx, + label, + following_edge_idx, + ); self.points[last_point_idx].forward_edges.push(final_edge); } } @@ -427,11 +503,11 @@ impl GraphPath { /// /// Return value is a list of nearby points /// - fn sweep_for_nearby_points(&mut self, accuracy: f64) -> impl Iterator { + fn sweep_for_nearby_points(&mut self, accuracy: f64) -> impl Iterator { // Structure to attach a bounding box to a point within this graph: this limits us as to the maximum distance we can use as it's used for sweeping struct PointArea<'a, Point, Label>(&'a GraphPath, usize); - impl<'a, Point: Coordinate+Coordinate2D, Label> PointArea<'a, Point, Label> { + impl<'a, Point: Coordinate + Coordinate2D, Label> PointArea<'a, Point, Label> { #[inline] fn pos(&self) -> &Point { let PointArea(graph, point_idx) = self; @@ -447,24 +523,27 @@ impl GraphPath { } } - impl<'a, Point: Coordinate+Coordinate2D, Label> Geo for PointArea<'a, Point, Label> { + impl<'a, Point: Coordinate + Coordinate2D, Label> Geo for PointArea<'a, Point, Label> { type Point = Point; } - impl<'a, Point: Coordinate+Coordinate2D, Label> HasBoundingBox for PointArea<'a, Point, Label> { - fn get_bounding_box>(&self) -> Bounds { + impl<'a, Point: Coordinate + Coordinate2D, Label> HasBoundingBox for PointArea<'a, Point, Label> { + fn get_bounding_box>(&self) -> Bounds { let PointArea(graph, point_idx) = self; - let point = &graph.points[*point_idx]; - let lower = Point::from_components(&[point.position.x()-1.0, point.position.y()-1.0]); - let upper = Point::from_components(&[point.position.x()+1.0, point.position.y()+1.0]); + let point = &graph.points[*point_idx]; + let lower = + Point::from_components(&[point.position.x() - 1.0, point.position.y() - 1.0]); + let upper = + Point::from_components(&[point.position.x() + 1.0, point.position.y() + 1.0]); Bounds::from_min_max(lower, upper) } } // Collect all of the points in the graph, and order them by min_x - let mut all_points = (0..self.points.len()).into_iter() + let mut all_points = (0..self.points.len()) + .into_iter() .map(|idx| PointArea(self, idx)) .collect::>(); all_points.sort_by(|point1, point2| { @@ -475,39 +554,36 @@ impl GraphPath { }); // Sweep to find the points that might be colliding - let min_distance_squared = accuracy * accuracy; - let colliding_points = sweep_self(all_points.iter()) - .filter(|(point1, point2)| { - if point1.idx() == point2.idx() { - // A point cannot overlap itself - false - } else { - // Work out the distances between the points and - let p1 = point1.pos(); - let p2 = point2.pos(); + let min_distance_squared = accuracy * accuracy; + let colliding_points = sweep_self(all_points.iter()).filter(|(point1, point2)| { + if point1.idx() == point2.idx() { + // A point cannot overlap itself + false + } else { + // Work out the distances between the points and + let p1 = point1.pos(); + let p2 = point2.pos(); - let (x1, y1) = (p1.x(), p1.y()); - let (x2, y2) = (p2.x(), p2.y()); - let (dx, dy) = (x2-x1, y2-y1); + let (x1, y1) = (p1.x(), p1.y()); + let (x2, y2) = (p2.x(), p2.y()); + let (dx, dy) = (x2 - x1, y2 - y1); - let distance_squared = dx*dx + dy*dy; + let distance_squared = dx * dx + dy * dy; - distance_squared < min_distance_squared - } - }); + distance_squared < min_distance_squared + } + }); // Result is the indexes of the points that are 'close enough' to collide colliding_points - .map(|(point1, point2)| { - (point1.idx(), point2.idx()) - }) + .map(|(point1, point2)| (point1.idx(), point2.idx())) .collect::>() .into_iter() } /// /// Finds any points that have approximately the same coordinates and combines them - /// + /// /// Accuracy indicates the maximum difference in the x or y coordinate for two points to be considered the same. /// #[inline(never)] @@ -515,17 +591,17 @@ impl GraphPath { // Move any points that are connected by an edge and very close to each other on top of each other for point_idx in 0..self.points.len() { for edge_idx in 0..(self.points[point_idx].forward_edges.len()) { - let end_point_idx = self.points[point_idx].forward_edges[edge_idx].end_idx; + let end_point_idx = self.points[point_idx].forward_edges[edge_idx].end_idx; if end_point_idx == point_idx { // A point is always close to itself, so we don't want to try to move it in this case continue; } - let start_point = &self.points[point_idx].position; - let end_point = &self.points[end_point_idx].position; + let start_point = &self.points[point_idx].position; + let end_point = &self.points[end_point_idx].position; if start_point.is_near_to(end_point, accuracy) { - self.points[end_point_idx].position = self.points[point_idx].position.clone(); + self.points[end_point_idx].position = self.points[point_idx].position; } } } @@ -535,34 +611,42 @@ impl GraphPath { if let Some(nearby_point) = nearby_points.next() { // Remap points according to whatever is nearest - let min_distance_squared = accuracy * accuracy; - let mut remapped_points = (0..self.points.len()) + let min_distance_squared = accuracy * accuracy; + let mut remapped_points = (0..self.points.len()) .into_iter() - .map(|idx| (idx, None)) // Target index (= point index if not remapped and new position, or None if unmoved) + .map(|idx| (idx, None)) // Target index (= point index if not remapped and new position, or None if unmoved) .collect::)>>(); - let mut nearby_point = nearby_point; + let mut nearby_point = nearby_point; loop { // Index is of two points that are close enough to overlap let (p1_orig_idx, p2_orig_idx) = nearby_point; - debug_assert!(p1_orig_idx != p2_orig_idx); // Guaranteed by the implementation of sweep_for_nearby_points() + debug_assert!(p1_orig_idx != p2_orig_idx); // Guaranteed by the implementation of sweep_for_nearby_points() // Point may be remapped - let (p1_idx, p1_pos) = &remapped_points[p1_orig_idx]; - let (p2_idx, p2_pos) = &remapped_points[p2_orig_idx]; + let (p1_idx, p1_pos) = &remapped_points[p1_orig_idx]; + let (p2_idx, p2_pos) = &remapped_points[p2_orig_idx]; // To prevent averaging a whole bunch of points down to the same point because they're all close together, we re-check the distance if the one of the two close points has already been remapped - let moved = p1_pos.is_some() || p2_pos.is_some(); + let moved = p1_pos.is_some() || p2_pos.is_some(); - let p1_pos = if let Some(pos) = p1_pos { pos.clone() } else { self.points[*p1_idx].position.clone() }; - let p2_pos = if let Some(pos) = p2_pos { pos.clone() } else { self.points[*p2_idx].position.clone() }; + let p1_pos = if let Some(pos) = p1_pos { + *pos + } else { + self.points[*p1_idx].position + }; + let p2_pos = if let Some(pos) = p2_pos { + *pos + } else { + self.points[*p2_idx].position + }; if !moved || Self::point_is_near(&p1_pos, &p2_pos, min_distance_squared) { // Remap both points to a common target position - let pos = Self::snap_points(&p1_pos, &p2_pos); - let remap_idx = usize::min(*p1_idx, *p2_idx); + let pos = Self::snap_points(&p1_pos, &p2_pos); + let remap_idx = usize::min(*p1_idx, *p2_idx); - remapped_points[p1_orig_idx] = (remap_idx, Some(pos.clone())); + remapped_points[p1_orig_idx] = (remap_idx, Some(pos)); remapped_points[p2_orig_idx] = (remap_idx, Some(pos)); } @@ -580,16 +664,18 @@ impl GraphPath { for original_idx in 0..self.points.len() { if let (new_idx, Some(new_pos)) = &remapped_points[original_idx] { // This point has been moved - self.points[original_idx].position = new_pos.clone(); + self.points[original_idx].position = *new_pos; // If this is the target point, then don't move any edges - if *new_idx == original_idx { continue; } + if *new_idx == original_idx { + continue; + } // Trace the new index to its final point (which is the point still mapped to itself: this should always exist because we always prefer the lowest point) let mut new_idx = *new_idx; loop { - let (next_idx, _) = &remapped_points[new_idx]; - let next_idx = *next_idx; + let (next_idx, _) = &remapped_points[new_idx]; + let next_idx = *next_idx; if next_idx == new_idx { break; @@ -600,13 +686,18 @@ impl GraphPath { } // Move the edges into the new index - let forward_edges = mem::take(&mut self.points[original_idx].forward_edges); - let connected_from = mem::take(&mut self.points[original_idx].connected_from); - - following_edge_idx_offset[original_idx] = self.points[new_idx].forward_edges.len(); - - self.points[new_idx].forward_edges.extend(forward_edges.into_iter()); - self.points[new_idx].connected_from.extend(connected_from.into_iter()); + let forward_edges = mem::take(&mut self.points[original_idx].forward_edges); + let connected_from = mem::take(&mut self.points[original_idx].connected_from); + + following_edge_idx_offset[original_idx] = + self.points[new_idx].forward_edges.len(); + + self.points[new_idx] + .forward_edges + .extend(forward_edges.into_iter()); + self.points[new_idx] + .connected_from + .extend(connected_from.into_iter()); } } @@ -617,10 +708,10 @@ impl GraphPath { let new_end_idx = remapped_points[edge.end_idx].0; if new_end_idx != edge.end_idx { - let following_edge_idx_offset = following_edge_idx_offset[edge.end_idx]; + let following_edge_idx_offset = following_edge_idx_offset[edge.end_idx]; - edge.end_idx = new_end_idx; - edge.following_edge_idx += following_edge_idx_offset; + edge.end_idx = new_end_idx; + edge.following_edge_idx += following_edge_idx_offset; } } @@ -631,13 +722,13 @@ impl GraphPath { if new_connected_from_idx != *connected_from_idx { *connected_from_idx = new_connected_from_idx; - remapped = true; + remapped = true; } } // If we introduced duplicates, remove them if remapped { - point.connected_from.sort(); + point.connected_from.sort_unstable(); point.connected_from.dedup(); } } @@ -653,7 +744,7 @@ impl GraphPath { /// Checks that the following edges are consistent /// #[cfg(any(test, extra_checks))] - pub (crate) fn check_following_edge_consistency(&self) { + pub(crate) fn check_following_edge_consistency(&self) { let mut used_edges = vec![vec![]; self.points.len()]; for point_idx in 0..(self.points.len()) { @@ -663,7 +754,9 @@ impl GraphPath { let edge = &point.forward_edges[edge_idx]; test_assert!(edge.end_idx < self.points.len()); - test_assert!(edge.following_edge_idx < self.points[edge.end_idx].forward_edges.len()); + test_assert!( + edge.following_edge_idx < self.points[edge.end_idx].forward_edges.len() + ); test_assert!(!used_edges[edge.end_idx].contains(&edge.following_edge_idx)); used_edges[edge.end_idx].push(edge.following_edge_idx); @@ -672,38 +765,49 @@ impl GraphPath { } #[cfg(not(any(test, extra_checks)))] - pub (crate) fn check_following_edge_consistency(&self) { - - } + pub(crate) fn check_following_edge_consistency(&self) {} } /// -/// Removes any pairs of collisions that are closer than `CLOSE_DISTANCE` apart, and also rounds the +/// Removes any pairs of collisions that are closer than `CLOSE_DISTANCE` apart, and also rounds the /// first and last collisions to 0.0 and 1.0 -/// -/// When colliding two bezier curves we want to avoid subdividing excessively to produce very small +/// +/// When colliding two bezier curves we want to avoid subdividing excessively to produce very small /// sections as they have a tendency to produce extra collisions due to floating point or root finding /// errors. /// -fn remove_and_round_close_collisions(collisions: &mut SmallVec<[(f64, f64); 8]>, src: &C, tgt: &C) -where C::Point: Coordinate+Coordinate2D { +fn remove_and_round_close_collisions( + collisions: &mut SmallVec<[(f64, f64); 8]>, + src: &C, + tgt: &C, +) where + C::Point: Coordinate + Coordinate2D, +{ // Nothing to do if there are no collisions - if collisions.len() == 0 { + if collisions.is_empty() { return; } // Work out the positions of each point - let mut positions = collisions.iter().map(|(t1, _t2)| src.point_at_pos(*t1)).collect::>(); + let mut positions = collisions + .iter() + .map(|(t1, _t2)| src.point_at_pos(*t1)) + .collect::>(); // Find any pairs of points that are too close together let mut collision_idx = 0; - while collision_idx+1 < collisions.len() { + while collision_idx + 1 < collisions.len() { // Just remove both of these if they are too close together (as each collision crosses the curve once, removing collisions in pairs means that there'll still be at least one collision left if the curves actually end up crossing over) - if positions[collision_idx].is_near_to(&positions[collision_idx+1], CLOSE_DISTANCE) { - if (collisions[collision_idx].0 - collisions[collision_idx+1].0).abs() < SMALL_T_DISTANCE - && (collisions[collision_idx].1 - collisions[collision_idx+1].1).abs() < SMALL_T_DISTANCE { - collisions.remove(collision_idx); positions.remove(collision_idx); - collisions.remove(collision_idx); positions.remove(collision_idx); + if positions[collision_idx].is_near_to(&positions[collision_idx + 1], CLOSE_DISTANCE) { + if (collisions[collision_idx].0 - collisions[collision_idx + 1].0).abs() + < SMALL_T_DISTANCE + && (collisions[collision_idx].1 - collisions[collision_idx + 1].1).abs() + < SMALL_T_DISTANCE + { + collisions.remove(collision_idx); + positions.remove(collision_idx); + collisions.remove(collision_idx); + positions.remove(collision_idx); } else { collision_idx += 1; } @@ -711,35 +815,44 @@ where C::Point: Coordinate+Coordinate2D { collision_idx += 1; } } - + // If the first point or the last point is close to the end of the source or target curve, clip to 0 or 1 - if collisions.len() > 0 { + if !collisions.is_empty() { // Get the start/end points of the source and target - let src_start = src.start_point(); - let src_end = src.end_point(); - let tgt_start = tgt.start_point(); - let tgt_end = tgt.end_point(); + let src_start = src.start_point(); + let src_end = src.end_point(); + let tgt_start = tgt.start_point(); + let tgt_end = tgt.end_point(); // Snap collisions to 0.0 or 1.0 if they're very close to the start or end of either curve for collision_idx in 0..collisions.len() { // Snap the source side if collisions[collision_idx].0 > 0.0 && collisions[collision_idx].0 < 1.0 { - if src_start.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) && collisions[collision_idx].0 < SMALL_T_DISTANCE { + if src_start.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) + && collisions[collision_idx].0 < SMALL_T_DISTANCE + { collisions[collision_idx].0 = 0.0; } - if src_end.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) && collisions[collision_idx].0 > 1.0-SMALL_T_DISTANCE { + if src_end.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) + && collisions[collision_idx].0 > 1.0 - SMALL_T_DISTANCE + { collisions[collision_idx].0 = 1.0; } } // Snap the target side - if collisions[collision_idx].1 > 0.0 && collisions[collision_idx].1 < 1.0 && collisions[collision_idx].1 < SMALL_T_DISTANCE { + if collisions[collision_idx].1 > 0.0 + && collisions[collision_idx].1 < 1.0 + && collisions[collision_idx].1 < SMALL_T_DISTANCE + { if tgt_start.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) { collisions[collision_idx].1 = 0.0; } - if tgt_end.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) && collisions[collision_idx].1 > 1.0-SMALL_T_DISTANCE { + if tgt_end.is_near_to(&positions[collision_idx], CLOSE_DISTANCE) + && collisions[collision_idx].1 > 1.0 - SMALL_T_DISTANCE + { collisions[collision_idx].1 = 1.0; } } diff --git a/src/bezier/path/graph_path/ray_collision.rs b/src/bezier/path/graph_path/ray_collision.rs index 760012f8..1848b589 100644 --- a/src/bezier/path/graph_path/ray_collision.rs +++ b/src/bezier/path/graph_path/ray_collision.rs @@ -1,9 +1,9 @@ -use super::{GraphPath,GraphEdge,GraphEdgeRef}; -use crate::bezier::path::ray::*; -use crate::geo::*; -use crate::line::*; +use super::{GraphEdge, GraphEdgeRef, GraphPath}; +use crate::bezier::path::ray::{ray_collisions, RayPath}; +use crate::geo::{Coordinate, Coordinate2D}; +use crate::line::Line; -use smallvec::*; +use smallvec::SmallVec; /// /// Represents a collision between a ray and a GraphPath @@ -14,16 +14,19 @@ pub enum GraphRayCollision { SingleEdge(GraphEdgeRef), /// Collision against an intersection point - Intersection(GraphEdgeRef) + Intersection(GraphEdgeRef), } -impl GraphPath { +impl GraphPath { /// /// Finds all collisions between a ray and this path - /// + /// /// The return value is a tuple of (collision, curve_t, line_t, position) - /// - pub fn ray_collisions>(&self, ray: &L) -> Vec<(GraphRayCollision, f64, f64, Point)> { + /// + pub fn ray_collisions>( + &self, + ray: &L, + ) -> Vec<(GraphRayCollision, f64, f64, Point)> { ray_collisions(&self, ray) } } @@ -35,8 +38,8 @@ impl GraphRayCollision { #[inline] pub fn is_intersection(&self) -> bool { match self { - GraphRayCollision::SingleEdge(_) => false, - GraphRayCollision::Intersection(_edges) => true + GraphRayCollision::SingleEdge(_) => false, + GraphRayCollision::Intersection(_edges) => true, } } @@ -46,59 +49,92 @@ impl GraphRayCollision { #[inline] pub fn edge(&self) -> GraphEdgeRef { match self { - GraphRayCollision::SingleEdge(edge) => *edge, - GraphRayCollision::Intersection(edge) => *edge, + GraphRayCollision::SingleEdge(edge) => *edge, + GraphRayCollision::Intersection(edge) => *edge, } } } -impl<'a, Point, Label> RayPath for &'a GraphPath -where Point: Coordinate+Coordinate2D, - Label: Copy { +impl<'a, Point, Label> RayPath for &'a GraphPath +where + Point: Coordinate + Coordinate2D, + Label: Copy, +{ type Point = Point; type Curve = GraphEdge<'a, Point, Label>; - #[inline] fn num_points(&self) -> usize { self.points.len() } + #[inline] + fn num_points(&self) -> usize { + self.points.len() + } - #[inline] fn num_edges(&self, point_idx: usize) -> usize { self.points[point_idx].forward_edges.len() } + #[inline] + fn num_edges(&self, point_idx: usize) -> usize { + self.points[point_idx].forward_edges.len() + } - #[inline] fn edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { + #[inline] + fn edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { let num_edges = self.points[point_idx].forward_edges.len(); - (0..num_edges).into_iter() - .map(move |edge_idx| GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }) + (0..num_edges) + .into_iter() + .map(move |edge_idx| GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }) .collect() } - #[inline] fn reverse_edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { - self.points[point_idx].connected_from.iter() + #[inline] + fn reverse_edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { + self.points[point_idx] + .connected_from + .iter() .flat_map(|connected_point_idx| { let num_edges = self.points[*connected_point_idx].forward_edges.len(); - (0..num_edges).into_iter() - .filter(move |edge_idx| self.points[*connected_point_idx].forward_edges[*edge_idx].end_idx == point_idx) - .map(move |edge_idx| GraphEdgeRef { start_idx: *connected_point_idx, edge_idx: edge_idx, reverse: true }) + (0..num_edges) + .into_iter() + .filter(move |edge_idx| { + self.points[*connected_point_idx].forward_edges[*edge_idx].end_idx + == point_idx + }) + .map(move |edge_idx| GraphEdgeRef { + start_idx: *connected_point_idx, + edge_idx, + reverse: true, + }) }) .collect() } - #[inline] fn get_edge(&self, edge: GraphEdgeRef) -> Self::Curve { - GraphEdge { graph: *self, edge: edge } + #[inline] + fn get_edge(&self, edge: GraphEdgeRef) -> Self::Curve { + GraphEdge { graph: *self, edge } } - #[inline] fn get_next_edge(&self, edge: GraphEdgeRef) -> (GraphEdgeRef, Self::Curve) { - let next_point_idx = self.edge_end_point_idx(edge); - let next_edge_idx = self.edge_following_edge_idx(edge); + #[inline] + fn get_next_edge(&self, edge: GraphEdgeRef) -> (GraphEdgeRef, Self::Curve) { + let next_point_idx = self.edge_end_point_idx(edge); + let next_edge_idx = self.edge_following_edge_idx(edge); - let next_edge_ref = GraphEdgeRef { start_idx: next_point_idx, edge_idx: next_edge_idx, reverse: edge.reverse }; + let next_edge_ref = GraphEdgeRef { + start_idx: next_point_idx, + edge_idx: next_edge_idx, + reverse: edge.reverse, + }; (next_edge_ref, self.get_edge(next_edge_ref)) } - #[inline] fn point_position(&self, point: usize) -> Self::Point { + #[inline] + fn point_position(&self, point: usize) -> Self::Point { self.points[point].position } - #[inline] fn edge_start_point_idx(&self, edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_start_point_idx(&self, edge: GraphEdgeRef) -> usize { if edge.reverse { self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx } else { @@ -106,7 +142,8 @@ where Point: Coordinate+Coordinate2D, } } - #[inline] fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize { if edge.reverse { edge.start_idx } else { @@ -114,9 +151,12 @@ where Point: Coordinate+Coordinate2D, } } - #[inline] fn edge_following_edge_idx(&self, edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_following_edge_idx(&self, edge: GraphEdgeRef) -> usize { if edge.reverse { - unimplemented!("Finding the following edge for a reversed reference not implemented yet") + unimplemented!( + "Finding the following edge for a reversed reference not implemented yet" + ) } else { self.points[edge.start_idx].forward_edges[edge.edge_idx].following_edge_idx } diff --git a/src/bezier/path/graph_path/test.rs b/src/bezier/path/graph_path/test.rs index d7e19ac9..c29720d7 100644 --- a/src/bezier/path/graph_path/test.rs +++ b/src/bezier/path/graph_path/test.rs @@ -1,70 +1,209 @@ use super::*; -use crate::bezier::path::*; -use crate::bezier::normal::*; use crate::arc::*; +use crate::bezier::normal::*; +use crate::bezier::path::*; +use crate::Coord2; -pub (crate) fn donut() -> GraphPath { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); +pub(crate) fn donut() -> GraphPath { + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); - let mut circle1 = GraphPath::from_path(&circle1, ()); - circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); - let mut circle2 = GraphPath::from_path(&circle2, ()); - circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); + let mut circle1 = GraphPath::from_path(&circle1, ()); + circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); + let mut circle2 = GraphPath::from_path(&circle2, ()); + circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); circle1.collide(circle2, 0.1) } pub fn tricky_path1() -> SimpleBezierPath { BezierPathBuilder::::start(Coord2(266.4305, 634.9583)) - .curve_to((Coord2(267.89352, 634.96545), Coord2(276.2691, 647.3115)), Coord2(283.95255, 660.0379)) - .curve_to((Coord2(287.94046, 666.35474), Coord2(291.91766, 672.60645)), Coord2(295.15033, 677.43414)) - .curve_to((Coord2(296.7672, 679.91516), Coord2(298.1211, 681.9124)), Coord2(299.32123, 683.47577)) - .curve_to((Coord2(299.95978, 684.32623), Coord2(300.40076, 684.9176)), Coord2(300.98044, 685.51074)) - .curve_to((Coord2(301.33307, 685.8545), Coord2(301.51462, 686.0718)), Coord2(301.92783, 686.3648)) - .curve_to((Coord2(302.63144, 686.6535), Coord2(302.6845, 686.9835)), Coord2(303.79065, 687.13)) - .curve_to((Coord2(308.23322, 698.75146), Coord2(314.235, 706.79364)), Coord2(320.5527, 711.571)) - .curve_to((Coord2(323.84628, 713.9084), Coord2(326.7522, 715.38696)), Coord2(329.93036, 715.9504)) - .curve_to((Coord2(333.10065, 716.4182), Coord2(336.06982, 716.2095)), Coord2(338.80997, 715.17615)) - .curve_to((Coord2(344.1068, 713.1569), Coord2(348.558, 708.8886)), Coord2(352.09903, 704.2416)) - .curve_to((Coord2(355.6339, 699.64606), Coord2(358.63943, 694.3838)), Coord2(361.0284, 690.2511)) - .curve_to((Coord2(352.29608, 691.48425), Coord2(348.7531, 697.58563)), Coord2(344.9467, 702.02875)) - .curve_to((Coord2(343.1644, 704.2118), Coord2(340.9616, 706.1748)), Coord2(338.98895, 707.4077)) - .curve_to((Coord2(337.17404, 708.7338), Coord2(334.93362, 709.2896)), Coord2(332.94815, 709.3193)) - .curve_to((Coord2(338.20477, 716.0944), Coord2(342.99326, 713.658)), Coord2(346.69864, 710.2048)) - .curve_to((Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)) - .curve_to((Coord2(358.8071, 690.86554), Coord2(368.403, 680.78076)), Coord2(364.57346, 683.4333)) - .curve_to((Coord2(370.74402, 683.10126), Coord2(380.93408, 677.46747)), Coord2(391.3346, 669.7194)) - .curve_to((Coord2(401.82745, 661.6356), Coord2(411.92975, 652.304)), Coord2(416.44824, 642.7813)) - .curve_to((Coord2(421.56387, 630.7548), Coord2(419.29, 605.44073)), Coord2(418.97845, 598.63885)) - .curve_to((Coord2(416.0324, 600.9351), Coord2(416.06793, 605.21173)), Coord2(415.80798, 610.2456)) - .curve_to((Coord2(418.3617, 603.8127), Coord2(419.7235, 595.5345)), Coord2(417.99966, 597.9464)) - .curve_to((Coord2(417.83536, 597.29565), Coord2(417.6163, 596.428)), Coord2(417.452, 595.7772)) - .curve_to((Coord2(415.13226, 598.33954), Coord2(417.1024, 601.5625)), Coord2(415.80798, 610.2456)) - .curve_to((Coord2(419.39615, 605.133), Coord2(419.15756, 600.892)), Coord2(418.97845, 598.63885)) - .curve_to((Coord2(415.9, 605.6454), Coord2(416.15115, 630.697)), Coord2(410.98987, 640.1752)) - .curve_to((Coord2(407.398, 647.65436), Coord2(397.31293, 657.55756)), Coord2(387.45657, 664.45013)) - .curve_to((Coord2(377.50784, 671.67847), Coord2(367.18683, 676.76263)), Coord2(364.60056, 676.3969)) - .curve_to((Coord2(356.0477, 679.03125), Coord2(358.2825, 685.37573)), Coord2(350.3949, 694.47205)) - .curve_to((Coord2(347.86517, 698.46545), Coord2(345.09418, 702.3025)), Coord2(342.02982, 705.0691)) - .curve_to((Coord2(338.955, 707.7797), Coord2(336.14987, 709.45294)), Coord2(332.94815, 709.3193)) - .curve_to((Coord2(336.5865, 716.2577), Coord2(339.58755, 714.99677)), Coord2(342.64694, 713.29364)) - .curve_to((Coord2(345.54865, 711.4972), Coord2(347.85297, 709.2183)), Coord2(350.22574, 706.551)) - .curve_to((Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), Coord2(361.0284, 690.2511)) - .curve_to((Coord2(352.55414, 690.95703), Coord2(349.8117, 695.7842)), Coord2(346.5798, 700.0057)) - .curve_to((Coord2(343.354, 704.1756), Coord2(340.01953, 707.4518)), Coord2(336.43625, 708.6749)) - .curve_to((Coord2(334.73633, 709.2627), Coord2(332.9918, 709.5996)), Coord2(331.1653, 709.1589)) - .curve_to((Coord2(329.34668, 708.8136), Coord2(326.97275, 707.9294)), Coord2(324.69394, 706.071)) - .curve_to((Coord2(319.86685, 702.45667), Coord2(313.55374, 694.77545)), Coord2(307.1513, 682.14154)) - .curve_to((Coord2(305.31448, 680.437), Coord2(305.08902, 680.6507)), Coord2(305.46603, 680.73413)) - .curve_to((Coord2(305.55258, 680.8219), Coord2(305.35938, 680.745)), Coord2(305.29236, 680.7117)) - .curve_to((Coord2(305.03268, 680.5507), Coord2(304.45453, 680.05615)), Coord2(303.91962, 679.53674)) - .curve_to((Coord2(302.7728, 678.36035), Coord2(301.16226, 676.48175)), Coord2(299.40033, 674.3327)) - .curve_to((Coord2(295.8753, 669.90015), Coord2(291.43716, 663.8746)), Coord2(286.9764, 657.9508)) - .curve_to((Coord2(277.76248, 646.196), Coord2(269.10742, 634.2079)), Coord2(266.40128, 634.45917)) - .curve_to((Coord2(266.42087, 634.7936), Coord2(266.41122, 634.6289)), Coord2(266.4305, 634.9583)) + .curve_to( + (Coord2(267.89352, 634.96545), Coord2(276.2691, 647.3115)), + Coord2(283.95255, 660.0379), + ) + .curve_to( + (Coord2(287.94046, 666.35474), Coord2(291.91766, 672.60645)), + Coord2(295.15033, 677.43414), + ) + .curve_to( + (Coord2(296.7672, 679.91516), Coord2(298.1211, 681.9124)), + Coord2(299.32123, 683.47577), + ) + .curve_to( + (Coord2(299.95978, 684.32623), Coord2(300.40076, 684.9176)), + Coord2(300.98044, 685.51074), + ) + .curve_to( + (Coord2(301.33307, 685.8545), Coord2(301.51462, 686.0718)), + Coord2(301.92783, 686.3648), + ) + .curve_to( + (Coord2(302.63144, 686.6535), Coord2(302.6845, 686.9835)), + Coord2(303.79065, 687.13), + ) + .curve_to( + (Coord2(308.23322, 698.75146), Coord2(314.235, 706.79364)), + Coord2(320.5527, 711.571), + ) + .curve_to( + (Coord2(323.84628, 713.9084), Coord2(326.7522, 715.38696)), + Coord2(329.93036, 715.9504), + ) + .curve_to( + (Coord2(333.10065, 716.4182), Coord2(336.06982, 716.2095)), + Coord2(338.80997, 715.17615), + ) + .curve_to( + (Coord2(344.1068, 713.1569), Coord2(348.558, 708.8886)), + Coord2(352.09903, 704.2416), + ) + .curve_to( + (Coord2(355.6339, 699.64606), Coord2(358.63943, 694.3838)), + Coord2(361.0284, 690.2511), + ) + .curve_to( + (Coord2(352.29608, 691.48425), Coord2(348.7531, 697.58563)), + Coord2(344.9467, 702.02875), + ) + .curve_to( + (Coord2(343.1644, 704.2118), Coord2(340.9616, 706.1748)), + Coord2(338.98895, 707.4077), + ) + .curve_to( + (Coord2(337.17404, 708.7338), Coord2(334.93362, 709.2896)), + Coord2(332.94815, 709.3193), + ) + .curve_to( + (Coord2(338.20477, 716.0944), Coord2(342.99326, 713.658)), + Coord2(346.69864, 710.2048), + ) + .curve_to( + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ) + .curve_to( + (Coord2(358.8071, 690.86554), Coord2(368.403, 680.78076)), + Coord2(364.57346, 683.4333), + ) + .curve_to( + (Coord2(370.74402, 683.10126), Coord2(380.93408, 677.46747)), + Coord2(391.3346, 669.7194), + ) + .curve_to( + (Coord2(401.82745, 661.6356), Coord2(411.92975, 652.304)), + Coord2(416.44824, 642.7813), + ) + .curve_to( + (Coord2(421.56387, 630.7548), Coord2(419.29, 605.44073)), + Coord2(418.97845, 598.63885), + ) + .curve_to( + (Coord2(416.0324, 600.9351), Coord2(416.06793, 605.21173)), + Coord2(415.80798, 610.2456), + ) + .curve_to( + (Coord2(418.3617, 603.8127), Coord2(419.7235, 595.5345)), + Coord2(417.99966, 597.9464), + ) + .curve_to( + (Coord2(417.83536, 597.29565), Coord2(417.6163, 596.428)), + Coord2(417.452, 595.7772), + ) + .curve_to( + (Coord2(415.13226, 598.33954), Coord2(417.1024, 601.5625)), + Coord2(415.80798, 610.2456), + ) + .curve_to( + (Coord2(419.39615, 605.133), Coord2(419.15756, 600.892)), + Coord2(418.97845, 598.63885), + ) + .curve_to( + (Coord2(415.9, 605.6454), Coord2(416.15115, 630.697)), + Coord2(410.98987, 640.1752), + ) + .curve_to( + (Coord2(407.398, 647.65436), Coord2(397.31293, 657.55756)), + Coord2(387.45657, 664.45013), + ) + .curve_to( + (Coord2(377.50784, 671.67847), Coord2(367.18683, 676.76263)), + Coord2(364.60056, 676.3969), + ) + .curve_to( + (Coord2(356.0477, 679.03125), Coord2(358.2825, 685.37573)), + Coord2(350.3949, 694.47205), + ) + .curve_to( + (Coord2(347.86517, 698.46545), Coord2(345.09418, 702.3025)), + Coord2(342.02982, 705.0691), + ) + .curve_to( + (Coord2(338.955, 707.7797), Coord2(336.14987, 709.45294)), + Coord2(332.94815, 709.3193), + ) + .curve_to( + (Coord2(336.5865, 716.2577), Coord2(339.58755, 714.99677)), + Coord2(342.64694, 713.29364), + ) + .curve_to( + (Coord2(345.54865, 711.4972), Coord2(347.85297, 709.2183)), + Coord2(350.22574, 706.551), + ) + .curve_to( + (Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), + Coord2(361.0284, 690.2511), + ) + .curve_to( + (Coord2(352.55414, 690.95703), Coord2(349.8117, 695.7842)), + Coord2(346.5798, 700.0057), + ) + .curve_to( + (Coord2(343.354, 704.1756), Coord2(340.01953, 707.4518)), + Coord2(336.43625, 708.6749), + ) + .curve_to( + (Coord2(334.73633, 709.2627), Coord2(332.9918, 709.5996)), + Coord2(331.1653, 709.1589), + ) + .curve_to( + (Coord2(329.34668, 708.8136), Coord2(326.97275, 707.9294)), + Coord2(324.69394, 706.071), + ) + .curve_to( + (Coord2(319.86685, 702.45667), Coord2(313.55374, 694.77545)), + Coord2(307.1513, 682.14154), + ) + .curve_to( + (Coord2(305.31448, 680.437), Coord2(305.08902, 680.6507)), + Coord2(305.46603, 680.73413), + ) + .curve_to( + (Coord2(305.55258, 680.8219), Coord2(305.35938, 680.745)), + Coord2(305.29236, 680.7117), + ) + .curve_to( + (Coord2(305.03268, 680.5507), Coord2(304.45453, 680.05615)), + Coord2(303.91962, 679.53674), + ) + .curve_to( + (Coord2(302.7728, 678.36035), Coord2(301.16226, 676.48175)), + Coord2(299.40033, 674.3327), + ) + .curve_to( + (Coord2(295.8753, 669.90015), Coord2(291.43716, 663.8746)), + Coord2(286.9764, 657.9508), + ) + .curve_to( + (Coord2(277.76248, 646.196), Coord2(269.10742, 634.2079)), + Coord2(266.40128, 634.45917), + ) + .curve_to( + (Coord2(266.42087, 634.7936), Coord2(266.41122, 634.6289)), + Coord2(266.4305, 634.9583), + ) .build() } @@ -85,24 +224,20 @@ fn overlapping_rectangle() -> SimpleBezierPath { fn looped_rectangle() -> SimpleBezierPath { BezierPathBuilder::::start(Coord2(1.0, 1.0)) - //.line_to(Coord2(2.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 5.0)) .line_to(Coord2(2.0, 5.0)) .line_to(Coord2(2.0, 1.0)) .line_to(Coord2(3.0, 1.0)) - .line_to(Coord2(5.0, 1.0)) .line_to(Coord2(5.0, 5.0)) - //.line_to(Coord2(3.0, 5.0)) .line_to(Coord2(2.0, 5.0)) .line_to(Coord2(2.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 5.0)) .line_to(Coord2(2.0, 5.0)) - .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(1.0, 1.0)) .build() @@ -110,98 +245,105 @@ fn looped_rectangle() -> SimpleBezierPath { #[test] fn ray_cast_with_tricky_path_after_self_collide() { - let tricky = tricky_path1(); - let mut tricky = GraphPath::from_path(&tricky, ()); + let tricky = tricky_path1(); + let mut tricky = GraphPath::from_path(&tricky, ()); tricky.self_collide(0.01); for edge in tricky.all_edges() { - let target = edge.point_at_pos(0.5); - let normal = edge.normal_at_pos(0.5); - let ray = (target, target+normal); + let target = edge.point_at_pos(0.5); + let normal = edge.normal_at_pos(0.5); + let ray = (target, target + normal); let collisions = tricky.ray_collisions(&ray); // Should be an even number of collisions - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); } } #[test] fn single_difficult_ray_cast_with_tricky_path_before_self_collide() { - let tricky = tricky_path1(); - let tricky = GraphPath::from_path(&tricky, ()); + let tricky = tricky_path1(); + let tricky = GraphPath::from_path(&tricky, ()); - let ray = (Coord2(344.7127586558301, 702.311674360346), Coord2(344.6914625870749, 702.2935114955856)); - let collisions = tricky.ray_collisions(&ray); + let ray = ( + Coord2(344.7127586558301, 702.311674360346), + Coord2(344.6914625870749, 702.2935114955856), + ); + let collisions = tricky.ray_collisions(&ray); println!("{:?}", tricky); println!("{:?}", collisions); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); } #[test] fn single_difficult_ray_cast_with_tricky_path_after_self_collide() { - let tricky = tricky_path1(); - let mut tricky = GraphPath::from_path(&tricky, ()); + let tricky = tricky_path1(); + let mut tricky = GraphPath::from_path(&tricky, ()); tricky.self_collide(0.01); - let ray = (Coord2(344.7127586558301, 702.311674360346), Coord2(344.6914625870749, 702.2935114955856)); - let collisions = tricky.ray_collisions(&ray); + let ray = ( + Coord2(344.7127586558301, 702.311674360346), + Coord2(344.6914625870749, 702.2935114955856), + ); + let collisions = tricky.ray_collisions(&ray); println!("{:?}", tricky); println!("{:?}", collisions); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); } #[test] fn overlapping_rectangle_ray_cast_after_self_collide() { - let overlapping = overlapping_rectangle(); + let overlapping = overlapping_rectangle(); let mut overlapping = GraphPath::from_path(&overlapping, ()); overlapping.self_collide(0.01); - let ray = (Coord2(3.0, 0.0), Coord2(3.0, 5.0)); - let collisions = overlapping.ray_collisions(&ray); + let ray = (Coord2(3.0, 0.0), Coord2(3.0, 5.0)); + let collisions = overlapping.ray_collisions(&ray); println!("{:?}", overlapping); println!("{:?}", collisions); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); } #[test] fn looped_rectangle_ray_cast_after_self_collide() { - let looped = looped_rectangle(); + let looped = looped_rectangle(); let mut looped = GraphPath::from_path(&looped, ()); looped.self_collide(0.01); println!("{:?}", looped); for edge in looped.all_edges() { - let target = edge.point_at_pos(0.5); - let normal = edge.normal_at_pos(0.5); - let ray = (target, target+normal); + let target = edge.point_at_pos(0.5); + let normal = edge.normal_at_pos(0.5); + let ray = (target, target + normal); let collisions = looped.ray_collisions(&ray); // Should be an even number of collisions - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); } } #[test] fn find_gaps() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(5.0, 5.0)) .line_to(Coord2(5.0, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); - let edges = (0..4).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).nth(0).unwrap().into()) + let mut graph_path = GraphPath::from_path(&path, ()); + let edges = (0..4) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).next().unwrap().into()) .collect::>(); graph_path.set_edge_kind(edges[0], GraphPathEdgeKind::Exterior); @@ -209,11 +351,23 @@ fn find_gaps() { graph_path.set_edge_kind(edges[3], GraphPathEdgeKind::Exterior); // Edge 0,0 is followed by a gap - assert!(graph_path.edge_has_gap(GraphEdgeRef { start_idx: 0, edge_idx: 0, reverse: false })); + assert!(graph_path.edge_has_gap(GraphEdgeRef { + start_idx: 0, + edge_idx: 0, + reverse: false + })); // Edge 1,0 is the gap - assert!(!graph_path.edge_has_gap(GraphEdgeRef { start_idx: 1, edge_idx: 0, reverse: false })); + assert!(!graph_path.edge_has_gap(GraphEdgeRef { + start_idx: 1, + edge_idx: 0, + reverse: false + })); // Edge 2,0 is preceded by the gap - assert!(graph_path.edge_has_gap(GraphEdgeRef { start_idx: 2, edge_idx: 0, reverse: true })); + assert!(graph_path.edge_has_gap(GraphEdgeRef { + start_idx: 2, + edge_idx: 0, + reverse: true + })); } diff --git a/src/bezier/path/intersection.rs b/src/bezier/path/intersection.rs index 529e82a8..8e6348c5 100644 --- a/src/bezier/path/intersection.rs +++ b/src/bezier/path/intersection.rs @@ -1,55 +1,79 @@ -use super::path::*; -use super::to_curves::*; -use super::super::curve::*; -use super::super::intersection::*; -use super::super::super::geo::*; -use super::super::super::line::*; +use super::super::super::geo::{BoundingBox, Bounds, Coordinate2D}; +use super::super::super::line::Line; +use super::super::curve::{BezierCurve, Curve}; +use super::super::intersection::{curve_intersects_curve_clip, curve_intersects_line}; +use super::path::BezierPath; +use super::to_curves::path_to_curves; /// /// Determines the intersections of a path and a line -/// +/// /// Intersections are returned as the path section index, the 't' parameter along that curve and the 't' value along the line: -/// ie: `(path_point_idx, curve_t, line_t)`. -/// -pub fn path_intersects_line<'a, Path: BezierPath, L: Line>(path: &'a Path, line: &'a L) -> impl 'a+Iterator -where Path::Point: 'a+Coordinate2D { +/// ie: `(path_point_idx, curve_t, line_t)`. +/// +pub fn path_intersects_line<'a, Path: BezierPath, L: Line>( + path: &'a Path, + line: &'a L, +) -> impl 'a + Iterator +where + Path::Point: 'a + Coordinate2D, +{ path_to_curves::<_, Curve<_>>(path) .enumerate() - .flat_map(move |(section_id, curve)| curve_intersects_line(&curve, line).into_iter().map(move |(t, s, _pos)| (section_id, t, s))) + .flat_map(move |(section_id, curve)| { + curve_intersects_line(&curve, line) + .into_iter() + .map(move |(t, s, _pos)| (section_id, t, s)) + }) } /// /// Determines the intersections of a path and a ray. -/// +/// /// Return value is `(path_point_idx, curve_t, line_t)`. Ray intersections differ from line intersections /// in that there's no requirement for the result to be within the bounds of the supplied line (so any match in the direction of the line is /// returned). -/// +/// /// It's possible to filter for matches that occur after the start of the line by looking for results with an `s` value >= 0 -/// -pub fn path_intersects_ray<'a, Path: BezierPath, L: Line>(path: &'a Path, line: &'a L) -> impl 'a+Iterator -where Path::Point: 'a+Coordinate2D { +/// +pub fn path_intersects_ray<'a, Path: BezierPath, L: Line>( + path: &'a Path, + line: &'a L, +) -> impl 'a + Iterator +where + Path::Point: 'a + Coordinate2D, +{ path_to_curves::<_, Curve<_>>(path) .enumerate() - .flat_map(move |(section_id, curve)| curve_intersects_line(&curve, line).into_iter().map(move |(t, s, _pos)| (section_id, t, s))) + .flat_map(move |(section_id, curve)| { + curve_intersects_line(&curve, line) + .into_iter() + .map(move |(t, s, _pos)| (section_id, t, s)) + }) } /// /// Finds the points where a path intersects another path -/// +/// /// Intersections are returned as (segment index, t-value), in pairs indicating the position on the first path /// and the position on the second path. Intersections are unordered by default. -/// +/// /// The accuracy value indicates the maximum errors that's permitted for an intersection: the bezier curve /// intersection algorithm is approximate. -/// -pub fn path_intersects_path<'a, Path: BezierPath>(path1: &'a Path, path2: &'a Path, accuracy: f64) -> Vec<((usize, f64), (usize, f64))> -where Path::Point: 'a+Coordinate2D { +/// +pub fn path_intersects_path<'a, Path: BezierPath>( + path1: &'a Path, + path2: &'a Path, + accuracy: f64, +) -> Vec<((usize, f64), (usize, f64))> +where + Path::Point: 'a + Coordinate2D, +{ // Convert both paths to sections: also compute the bounding boxes for quick rejection of sections with no intersections let path1_sections = path_to_curves::<_, Curve<_>>(path1) .enumerate() .map(|(section_id, curve)| (section_id, curve, curve.bounding_box::>())); - + let path2_sections = path_to_curves::<_, Curve<_>>(path2) .enumerate() .map(|(section_id, curve)| (section_id, curve, curve.bounding_box::>())) @@ -66,10 +90,14 @@ where Path::Point: 'a+Coordinate2D { // Only search for intersections if these two sections have overlapping bounding boxes if p1_curve_bounds.overlaps(p2_curve_bounds) { // Determine the intersections (if any) between these two curves - let intersections = curve_intersects_curve_clip(&p1_curve, &p2_curve, accuracy); + let intersections = curve_intersects_curve_clip(&p1_curve, p2_curve, accuracy); // Combine with the section IDs to generate the results - result.extend(intersections.into_iter().map(|(t1, t2)| ((p1_section_id, t1), (*p2_section_id, t2)) )); + result.extend( + intersections + .into_iter() + .map(|(t1, t2)| ((p1_section_id, t1), (*p2_section_id, t2))), + ); } } } diff --git a/src/bezier/path/is_clockwise.rs b/src/bezier/path/is_clockwise.rs index 9a8b3d9c..a554e6bb 100644 --- a/src/bezier/path/is_clockwise.rs +++ b/src/bezier/path/is_clockwise.rs @@ -1,23 +1,28 @@ -use super::path::*; -use super::super::super::geo::*; +use super::super::super::geo::{Coordinate, Coordinate2D}; +use super::path::BezierPath; -use itertools::*; +use itertools::Itertools; /// /// Determines if a set of points are in a clockwise ordering (assuming that a positive y value indicates an upwards direction) /// -pub fn points_are_clockwise>(mut points: PointIter) -> bool { +pub fn points_are_clockwise>( + mut points: PointIter, +) -> bool { // Technique suggested in https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order let mut total = 0.0; // The first point needs to be repeated at the end of the sequence let first_point = points.next(); if let Some(first_point) = first_point { - let points = vec![first_point.clone()].into_iter().chain(points).chain(vec![first_point].into_iter()); + let points = vec![first_point] + .into_iter() + .chain(points) + .chain(vec![first_point].into_iter()); // Sum over the edges to determine if the points are clockwise for (start, end) in points.tuple_windows() { - total += (end.x()-start.x()) * (end.y()+start.y()); + total += (end.x() - start.x()) * (end.y() + start.y()); } } @@ -34,7 +39,10 @@ pub trait PathWithIsClockwise { fn is_clockwise(&self) -> bool; } -impl PathWithIsClockwise for P where P::Point: Coordinate+Coordinate2D { +impl PathWithIsClockwise for P +where + P::Point: Coordinate + Coordinate2D, +{ #[inline] fn is_clockwise(&self) -> bool { points_are_clockwise(self.points().map(|(_cp1, _cp2, p)| p)) diff --git a/src/bezier/path/mod.rs b/src/bezier/path/mod.rs index d2bcdac7..75e5acbf 100644 --- a/src/bezier/path/mod.rs +++ b/src/bezier/path/mod.rs @@ -1,38 +1,38 @@ //! //! # Manipulates multiple Bezier curves joined into a path -//! -//! The `BezierPath` trait provides a way to represent a bezier path. `flo_curves` considers a path to be a single -//! closed loop, unlike many libraries which allow for open paths and paths with subpaths. Instead, a path with +//! +//! The `BezierPath` trait provides a way to represent a bezier path. `flo_curves` considers a path to be a single +//! closed loop, unlike many libraries which allow for open paths and paths with subpaths. Instead, a path with //! multiple subpaths is represented as a collection - ie `Vec`. This reduces the number of edge cases //! the library has to deal with. -//! +//! //! The `path_add()`, `path_sub()` and `path_intersect()` functions can be used to perform path arithmetic: combining //! multiple paths into a single result. The `GraphPath` type is used to implement these functions: it can represent //! paths where points can have more than one following edge attached to them and provides functions for implementing //! similar operations. -//! +//! //! `BezierPathBuilder` provides a way to quickly build paths from any type implementing the factory trait without //! needing to generate all of the primitives manually. //! -mod path; -mod to_curves; -mod ray; -mod point; +pub mod algorithms; +mod arithmetic; mod bounds; -mod intersection; -mod path_builder; mod graph_path; +mod intersection; mod is_clockwise; -mod arithmetic; -pub mod algorithms; +mod path; +mod path_builder; +mod point; +mod ray; +mod to_curves; -pub use self::path::*; -pub use self::to_curves::*; -pub use self::point::*; +pub use self::arithmetic::*; pub use self::bounds::*; -pub use self::intersection::*; -pub use self::path_builder::*; pub use self::graph_path::*; +pub use self::intersection::*; pub use self::is_clockwise::*; -pub use self::arithmetic::*; +pub use self::path::*; +pub use self::path_builder::*; +pub use self::point::*; +pub use self::to_curves::*; diff --git a/src/bezier/path/path.rs b/src/bezier/path/path.rs index 49db2e02..a1a84f2b 100644 --- a/src/bezier/path/path.rs +++ b/src/bezier/path/path.rs @@ -1,120 +1,131 @@ -use super::bounds::*; -use super::to_curves::*; -use super::super::curve::*; -use super::super::super::geo::*; +use super::super::super::geo::{BoundingBox, Coord2, Coordinate, Geo}; +use super::super::curve::BezierCurveFactory; +use super::bounds::{path_bounding_box, path_fast_bounding_box}; +use super::to_curves::path_to_curves; -use itertools::*; -use std::vec; +use itertools::Itertools; use std::iter; +use std::vec; /// /// Trait representing a path made out of bezier sections -/// -pub trait BezierPath : Geo+Clone+Sized { +/// +pub trait BezierPath: Geo + Clone + Sized { /// Type of an iterator over the points in this curve. This tuple contains the points ordered as a hull: ie, two control points followed by a point on the curve - type PointIter: Iterator; + type PointIter: Iterator; /// /// Retrieves the initial point of this path - /// + /// fn start_point(&self) -> Self::Point; /// /// Retrieves an iterator over the points in this path - /// + /// fn points(&self) -> Self::PointIter; /// /// Finds the bounds of this path - /// + /// #[inline] - fn bounding_box>(&self) -> Bounds { + fn bounding_box>(&self) -> Bounds { path_bounding_box(self) } /// /// Finds a loose bounding box for this path (more quickly than bounding_box) - /// + /// /// This will contain the path but might not be tightly aligned to the curves /// - fn fast_bounding_box>(&self) -> Bounds { + fn fast_bounding_box>(&self) -> Bounds { path_fast_bounding_box(self) } /// /// Changes this path into a set of bezier curves - /// + /// #[inline] - fn to_curves>(&self) -> Vec { + fn to_curves>(&self) -> Vec { path_to_curves(self).collect() } /// /// Creates a reversed version of this path /// - fn reversed>(&self) -> POut { + fn reversed>(&self) -> POut { // Add in the first point (control points don't matter) - let fake_first_point = (Self::Point::origin(), Self::Point::origin(), self.start_point()); - let points = self.points(); - let points = iter::once(fake_first_point).chain(points); + let fake_first_point = ( + Self::Point::origin(), + Self::Point::origin(), + self.start_point(), + ); + let points = self.points(); + let points = iter::once(fake_first_point).chain(points); // Reverse the direction of the path - let reversed_points = points + let mut reversed_points = points .tuple_windows() .map(|((_, _, start_point), (cp1, cp2, _))| (cp2, cp1, start_point)) - .collect::>(); + .collect_vec(); + reversed_points.reverse(); - POut::from_points(self.start_point(), reversed_points.into_iter().rev()) + POut::from_points(self.start_point(), reversed_points.into_iter()) } } /// /// Trait implemented by types that can construct new bezier paths /// -pub trait BezierPathFactory : BezierPath { +pub trait BezierPathFactory: BezierPath { /// /// Creates a new instance of this path from a set of points - /// - fn from_points>(start_point: Self::Point, points: FromIter) -> Self; + /// + fn from_points>( + start_point: Self::Point, + points: FromIter, + ) -> Self; /// /// Creates a new instance of this path from the points in another path /// - fn from_path>(path: &FromPath) -> Self { + fn from_path>(path: &FromPath) -> Self { Self::from_points(path.start_point(), path.points()) } } -impl Geo for (Point, Vec<(Point, Point, Point)>) { +impl Geo for (Point, Vec<(Point, Point, Point)>) { type Point = Point; } /// /// The type (start_point, Vec<(Point, Point, Point)>) is the simplest bezier path type -/// -impl BezierPath for (Point, Vec<(Point, Point, Point)>) { - type PointIter = vec::IntoIter<(Point, Point, Point)>; +/// +impl BezierPath for (Point, Vec<(Point, Point, Point)>) { + type PointIter = vec::IntoIter<(Point, Point, Point)>; /// /// Retrieves the initial point of this path - /// + /// fn start_point(&self) -> Self::Point { - self.0.clone() + self.0 } /// /// Retrieves an iterator over the points in this path - /// + /// fn points(&self) -> Self::PointIter { self.1.clone().into_iter() } } -impl BezierPathFactory for (Point, Vec<(Point, Point, Point)>) { +impl BezierPathFactory for (Point, Vec<(Point, Point, Point)>) { /// /// Creates a new instance of this path from a set of points - /// - fn from_points>(start_point: Self::Point, points: FromIter) -> Self { + /// + fn from_points>( + start_point: Self::Point, + points: FromIter, + ) -> Self { (start_point, points.into_iter().collect()) } } diff --git a/src/bezier/path/path_builder.rs b/src/bezier/path/path_builder.rs index 62c0bb59..c38f3120 100644 --- a/src/bezier/path/path_builder.rs +++ b/src/bezier/path/path_builder.rs @@ -1,48 +1,48 @@ -use super::path::*; +use super::path::{BezierPath, BezierPathFactory}; /// /// Used to build a bezier path -/// +/// pub struct BezierPathBuilder { /// Where the path starts start_point: P::Point, /// The points in the path - points: Vec<(P::Point, P::Point, P::Point)> + points: Vec<(P::Point, P::Point, P::Point)>, } impl BezierPathBuilder

{ /// /// Creates a new bezier path builder with the specified start point - /// - pub fn start(start: P::Point) -> BezierPathBuilder

{ - BezierPathBuilder { - start_point: start, - points: vec![] + /// + pub fn start(start: P::Point) -> Self { + Self { + start_point: start, + points: vec![], } } /// /// Builds the path for this builder - /// + /// pub fn build(self) -> P { P::from_points(self.start_point, self.points) } /// /// Adds a line to the specified point - /// + /// pub fn line_to(mut self, point: P::Point) -> Self { // Get the vector from the last point to the new point - let distance = if self.points.len() == 0 { + let distance = if self.points.is_empty() { point - self.start_point } else { - point - self.points[self.points.len()-1].2 + point - self.points[self.points.len() - 1].2 }; // A line puts control points at 33% and 66% of the distance - let cp1 = point - (distance*0.6666); - let cp2 = point - (distance*0.3333); + let cp1 = point - (distance * 0.6666); + let cp2 = point - (distance * 0.3333); self.points.push((cp1, cp2, point)); @@ -51,10 +51,10 @@ impl BezierPathBuilder

{ /// /// Adds a curve to a particular point - /// + /// pub fn curve_to(mut self, (cp1, cp2): (P::Point, P::Point), end_point: P::Point) -> Self { self.points.push((cp1, cp2, end_point)); self } -} \ No newline at end of file +} diff --git a/src/bezier/path/point.rs b/src/bezier/path/point.rs index 584b2e69..b9e7104e 100644 --- a/src/bezier/path/point.rs +++ b/src/bezier/path/point.rs @@ -1,48 +1,48 @@ -use super::ray::*; -use super::path::*; -use super::to_curves::*; -use super::graph_path::*; -use super::super::curve::*; -use super::super::normal::*; -use super::super::super::geo::*; +use super::super::super::geo::{Coordinate, Coordinate2D, Geo}; +use super::super::curve::{BezierCurve, Curve}; +use super::super::normal::NormalCurve; +use super::graph_path::GraphEdgeRef; +use super::path::BezierPath; +use super::ray::{ray_collisions, RayPath}; +use super::to_curves::path_to_curves; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; /// /// Represents a curve that can be represented either forwards or backwards /// #[derive(Clone)] -pub (crate) enum ReversableCurve { +pub(crate) enum ReversableCurve { Forward(Curve), - Reversed(Curve) + Reversed(Curve), } impl Geo for ReversableCurve { - type Point=Curve::Point; + type Point = Curve::Point; } impl BezierCurve for ReversableCurve { #[inline] - fn start_point(&self) -> Curve::Point { + fn start_point(&self) -> Curve::Point { match self { - ReversableCurve::Forward(curve) => curve.start_point(), - ReversableCurve::Reversed(curve) => curve.end_point() + ReversableCurve::Forward(curve) => curve.start_point(), + ReversableCurve::Reversed(curve) => curve.end_point(), } } #[inline] - fn end_point(&self) -> Curve::Point { + fn end_point(&self) -> Curve::Point { match self { - ReversableCurve::Forward(curve) => curve.end_point(), - ReversableCurve::Reversed(curve) => curve.start_point() + ReversableCurve::Forward(curve) => curve.end_point(), + ReversableCurve::Reversed(curve) => curve.start_point(), } } #[inline] fn control_points(&self) -> (Curve::Point, Curve::Point) { match self { - ReversableCurve::Forward(curve) => curve.control_points(), - ReversableCurve::Reversed(curve) => { + ReversableCurve::Forward(curve) => curve.control_points(), + ReversableCurve::Reversed(curve) => { let (cp1, cp2) = curve.control_points(); (cp2, cp1) } @@ -50,32 +50,51 @@ impl BezierCurve for ReversableCurve { } } -impl RayPath for Vec -where Curve::Point: Coordinate2D { +impl RayPath for Vec +where + Curve::Point: Coordinate2D, +{ type Curve = ReversableCurve; type Point = Curve::Point; - #[inline] fn num_points(&self) -> usize { + #[inline] + fn num_points(&self) -> usize { self.len() } - #[inline] fn num_edges(&self, _point_idx: usize) -> usize { + #[inline] + fn num_edges(&self, _point_idx: usize) -> usize { 1 } - #[inline] fn reverse_edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { + #[inline] + fn reverse_edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { if point_idx == 0 { - smallvec![GraphEdgeRef { start_idx: self.len()-1, edge_idx: 0, reverse: true }] + smallvec![GraphEdgeRef { + start_idx: self.len() - 1, + edge_idx: 0, + reverse: true + }] } else { - smallvec![GraphEdgeRef { start_idx: point_idx-1, edge_idx: 0, reverse: true }] + smallvec![GraphEdgeRef { + start_idx: point_idx - 1, + edge_idx: 0, + reverse: true + }] } } - #[inline] fn edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { - smallvec![GraphEdgeRef { start_idx: point_idx, edge_idx: 0, reverse: false }] + #[inline] + fn edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { + smallvec![GraphEdgeRef { + start_idx: point_idx, + edge_idx: 0, + reverse: false + }] } - #[inline] fn get_edge(&self, edge: GraphEdgeRef) -> Self::Curve { + #[inline] + fn get_edge(&self, edge: GraphEdgeRef) -> Self::Curve { if edge.reverse { ReversableCurve::Reversed(self[edge.start_idx].clone()) } else { @@ -83,16 +102,23 @@ where Curve::Point: Coordinate2D { } } - #[inline] fn get_next_edge(&self, edge: GraphEdgeRef) -> (GraphEdgeRef, Self::Curve) { - let next_ref = GraphEdgeRef { start_idx: self.edge_end_point_idx(edge), edge_idx: 0, reverse: edge.reverse }; + #[inline] + fn get_next_edge(&self, edge: GraphEdgeRef) -> (GraphEdgeRef, Self::Curve) { + let next_ref = GraphEdgeRef { + start_idx: self.edge_end_point_idx(edge), + edge_idx: 0, + reverse: edge.reverse, + }; (next_ref, self.get_edge(next_ref)) } - #[inline] fn point_position(&self, point: usize) -> Self::Point { + #[inline] + fn point_position(&self, point: usize) -> Self::Point { self[point].start_point() } - #[inline] fn edge_start_point_idx(&self, edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_start_point_idx(&self, edge: GraphEdgeRef) -> usize { if edge.reverse { unimplemented!() } else { @@ -100,56 +126,67 @@ where Curve::Point: Coordinate2D { } } - #[inline] fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize { if edge.reverse { unimplemented!() + } else if edge.start_idx + 1 == self.len() { + 0 } else { - if edge.start_idx+1 == self.len() { - 0 - } else { - edge.start_idx+1 - } + edge.start_idx + 1 } } - #[inline] fn edge_following_edge_idx(&self, _edge: GraphEdgeRef) -> usize { + #[inline] + fn edge_following_edge_idx(&self, _edge: GraphEdgeRef) -> usize { 0 } } /// /// Returns true if a particular point is within a bezier path -/// +/// pub fn path_contains_point(path: &P, point: &P::Point) -> bool -where P::Point: Coordinate2D { +where + P::Point: Coordinate2D, +{ // We want to cast a ray from the outer edge of the bounds to our point let (min_bounds, max_bounds) = path.bounding_box(); - if min_bounds.x() > point.x() || max_bounds.x() < point.x() || min_bounds.y() > point.y() || max_bounds.y() < point.y() { + if min_bounds.x() > point.x() + || max_bounds.x() < point.x() + || min_bounds.y() > point.y() + || max_bounds.y() < point.y() + { // Point is outside the bounds of the path false } else { // Ray is from the top of the bounds to our point - let ray = (max_bounds + P::Point::from_components(&[0.01, 0.01]), *point); - let ray_direction = ray.1 - ray.0; + let ray = ( + max_bounds + P::Point::from_components(&[0.01, 0.01]), + *point, + ); + let ray_direction = ray.1 - ray.0; // Call through to ray_collisions to get the collisions - let curves = path_to_curves::<_, Curve<_>>(path).collect::>(); - let collisions = ray_collisions(&curves, &ray); + let curves = path_to_curves::<_, Curve<_>>(path).collect::>(); + let collisions = ray_collisions(&curves, &ray); // The total of all of the ray directions - let mut total_direction = 0; + let mut total_direction = 0; for (collision, curve_t, line_t, _pos) in collisions { // Stop once the ray reaches the desired point - if line_t > 1.0 { break; } + if line_t > 1.0 { + break; + } // Curve this collision was is just the start index of the edge - let curve_idx = collision.edge().start_idx; + let curve_idx = collision.edge().start_idx; // Use the normal at this point to determine the direction relative to the ray - let normal = curves[curve_idx].normal_at_pos(curve_t); - let direction = ray_direction.dot(&normal).signum() as i32; + let normal = curves[curve_idx].normal_at_pos(curve_t); + let direction = ray_direction.dot(&normal).signum() as i32; // Add to the total direction total_direction += direction; diff --git a/src/bezier/path/ray.rs b/src/bezier/path/ray.rs index 95598727..908bea8c 100644 --- a/src/bezier/path/ray.rs +++ b/src/bezier/path/ray.rs @@ -1,20 +1,20 @@ -use super::graph_path::*; -use super::super::curve::*; -use super::super::normal::*; -use super::super::intersection::*; -use super::super::super::geo::*; -use super::super::super::line::*; -use super::super::super::consts::*; - -use smallvec::*; +use super::super::super::consts::{CLOSE_DISTANCE, SMALL_DISTANCE}; +use super::super::super::geo::{Coordinate, Coordinate2D}; +use super::super::super::line::{Line, Line2D}; +use super::super::curve::BezierCurve; +use super::super::intersection::curve_intersects_ray; +use super::super::normal::NormalCurve; +use super::graph_path::{GraphEdgeRef, GraphRayCollision}; + +use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; /// /// Represents a path that can be accessed by the ray collision algorithm /// -pub (crate) trait RayPath { - type Point: Coordinate+Coordinate2D; - type Curve: BezierCurve; +pub(crate) trait RayPath { + type Point: Coordinate + Coordinate2D; + type Curve: BezierCurve; /// /// Returns the number of points in this RayPath @@ -62,7 +62,7 @@ pub (crate) trait RayPath { fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize; /// - /// Retrieves the index of the edge following the specified edge + /// Retrieves the index of the edge following the specified edge /// (the edge start from the end point index that continues the path the edge is a part of) /// fn edge_following_edge_idx(&self, edge: GraphEdgeRef) -> usize; @@ -73,53 +73,60 @@ pub (crate) trait RayPath { /// #[inline] fn curve_is_collinear(edge: &Edge, (a, b, c): (f64, f64, f64)) -> bool -where Edge::Point: Coordinate+Coordinate2D { +where + Edge::Point: Coordinate + Coordinate2D, +{ // Fetch the points of the curve let start_point = edge.start_point(); - let end_point = edge.end_point(); - let (cp1, cp2) = edge.control_points(); + let end_point = edge.end_point(); + let (cp1, cp2) = edge.control_points(); // The curve is collinear if all of the points lie on the ray - if (start_point.x()*a + start_point.y()*b + c).abs() < SMALL_DISTANCE - && (end_point.x()*a + end_point.y()*b + c).abs() < SMALL_DISTANCE - && (cp1.x()*a + cp1.y()*b + c).abs() < SMALL_DISTANCE - && (cp2.x()*a + cp2.y()*b + c).abs() < SMALL_DISTANCE { - true - } else { - false - } + (start_point.x() * a + start_point.y() * b + c).abs() < SMALL_DISTANCE + && (end_point.x() * a + end_point.y() * b + c).abs() < SMALL_DISTANCE + && (cp1.x() * a + cp1.y() * b + c).abs() < SMALL_DISTANCE + && (cp2.x() * a + cp2.y() * b + c).abs() < SMALL_DISTANCE } #[derive(PartialEq)] enum RayCanIntersect { WrongSide, Collinear, - CrossesRay + CrossesRay, } /// /// Given the coefficients of a ray, returns whether or not an edge can intersect it /// fn ray_can_intersect(edge: &Edge, (a, b, c): (f64, f64, f64)) -> RayCanIntersect -where Edge::Point: Coordinate+Coordinate2D { +where + Edge::Point: Coordinate + Coordinate2D, +{ // Fetch the points of the curve let start_point = edge.start_point(); - let end_point = edge.end_point(); - let (cp1, cp2) = edge.control_points(); + let end_point = edge.end_point(); + let (cp1, cp2) = edge.control_points(); // Calculate distances to each of the points - let start_distance = a*start_point.x() + b*start_point.y() + c; - let cp1_distance = a*cp1.x() + b*cp1.y() + c; - let cp2_distance = a*cp2.x() + b*cp2.y() + c; - let end_distance = a*end_point.x()+ b*end_point.y() + c; + let start_distance = a * start_point.x() + b * start_point.y() + c; + let cp1_distance = a * cp1.x() + b * cp1.y() + c; + let cp2_distance = a * cp2.x() + b * cp2.y() + c; + let end_distance = a * end_point.x() + b * end_point.y() + c; // The sign of the distances indicate which side they're on - let side = start_distance.signum() + end_distance.signum() + cp1_distance.signum() + cp2_distance.signum(); - - if start_distance.abs() < SMALL_DISTANCE && end_distance.abs() < SMALL_DISTANCE && cp1_distance.abs() < SMALL_DISTANCE && cp2_distance.abs() < SMALL_DISTANCE { + let side = start_distance.signum() + + end_distance.signum() + + cp1_distance.signum() + + cp2_distance.signum(); + + if start_distance.abs() < SMALL_DISTANCE + && end_distance.abs() < SMALL_DISTANCE + && cp1_distance.abs() < SMALL_DISTANCE + && cp2_distance.abs() < SMALL_DISTANCE + { // If all the distances are small enough, this section is collinear RayCanIntersect::Collinear - } else if side < -3.99 || side > 3.99 { + } else if !(-3.99..=3.99).contains(&side) { // If the side sums to 4, all points are on the same side RayCanIntersect::WrongSide } else { @@ -131,14 +138,18 @@ where Edge::Point: Coordinate+Coordinate2D { /// /// Given a list of points, returns the edges that cross the line given by the specified set of coefficients /// -fn crossing_edges(path: &Path, (a, b, c): (f64, f64, f64), points: Vec) -> Vec { +fn crossing_edges( + path: &Path, + (a, b, c): (f64, f64, f64), + points: Vec, +) -> Vec { let mut crossing_edges = vec![]; for point_idx in points.into_iter() { for incoming_ref in path.reverse_edges_for_point(point_idx) { // Get the incoming edge going in the right direction - let incoming_ref = incoming_ref.reversed(); - let incoming = path.get_edge(incoming_ref); + let incoming_ref = incoming_ref.reversed(); + let incoming = path.get_edge(incoming_ref); // Ignore collinear incoming edges if curve_is_collinear(&incoming, (a, b, c)) { @@ -146,16 +157,20 @@ fn crossing_edges(path: &Path, (a, b, c): (f64, f64, f64), points } // Fetch the leaving edge for the incoming edge - let following_ref = path.edge_following_edge_idx(incoming_ref); - let mut leaving_ref = GraphEdgeRef { start_idx: point_idx, edge_idx: following_ref, reverse: false }; - let mut leaving = path.get_edge(leaving_ref); + let following_ref = path.edge_following_edge_idx(incoming_ref); + let mut leaving_ref = GraphEdgeRef { + start_idx: point_idx, + edge_idx: following_ref, + reverse: false, + }; + let mut leaving = path.get_edge(leaving_ref); // Follow the path until we complete a loop or find a leaving edge that's not collinear while curve_is_collinear(&leaving, (a, b, c)) { let (next_ref, next_edge) = path.get_next_edge(leaving_ref); leaving_ref = next_ref; - leaving = next_edge; + leaving = next_edge; if path.edge_start_point_idx(leaving_ref) == point_idx { // Found a loop that was entirely collinear @@ -166,11 +181,11 @@ fn crossing_edges(path: &Path, (a, b, c): (f64, f64, f64), points // If it's not colinear, add to the set of crossing edges if !curve_is_collinear(&leaving, (a, b, c)) { - let incoming_cp2 = incoming.control_points().1; - let leaving_cp1 = leaving.control_points().0; + let incoming_cp2 = incoming.control_points().1; + let leaving_cp1 = leaving.control_points().0; - let incoming_side = a*incoming_cp2.x() + b*incoming_cp2.y() + c; - let leaving_side = a*leaving_cp1.x() + b*leaving_cp1.y() + c; + let incoming_side = a * incoming_cp2.x() + b * incoming_cp2.y() + c; + let leaving_side = a * leaving_cp1.x() + b * leaving_cp1.y() + c; if incoming_side.signum() != leaving_side.signum() { // Control points are on different sides of the line, so this is a crossing edge @@ -185,27 +200,39 @@ fn crossing_edges(path: &Path, (a, b, c): (f64, f64, f64), points /// /// Performs a basic search for collisions, returning them grouped into two sets. -/// +/// /// The first set is crossing collisions. These are places where the ray met and edge at an angle and crossed it. /// The second set is collinear collisions. These occur on straight edges that follow the same path as the ray. /// #[inline(never)] -fn crossing_and_collinear_collisions(path: &Path, ray: &L) -> (SmallVec<[(GraphEdgeRef, f64, f64, Path::Point); 32]>, SmallVec<[(GraphEdgeRef, f64, f64, Path::Point); 8]>) -where Path::Point: Coordinate+Coordinate2D, - L: Line { - let mut raw_collisions = smallvec![]; +fn crossing_and_collinear_collisions( + path: &Path, + ray: &L, +) -> ( + SmallVec<[(GraphEdgeRef, f64, f64, Path::Point); 32]>, + SmallVec<[(GraphEdgeRef, f64, f64, Path::Point); 8]>, +) +where + Path::Point: Coordinate + Coordinate2D, + L: Line, +{ + let mut raw_collisions = smallvec![]; // If there are multiple collinear sections grouped together, these give them each a common identifier - let mut section_with_point: Option>> = None; - let mut collinear_sections: SmallVec<[Vec<_>; 8]> = smallvec![]; + let mut section_with_point: Option>> = None; + let mut collinear_sections: SmallVec<[Vec<_>; 8]> = smallvec![]; // The coefficients are used to determine if a particular edge can collide with the curve and if it's collinear or not let ray_coeffs = ray.coefficients(); for point_idx in 0..(path.num_points()) { for edge_idx in 0..(path.num_edges(point_idx)) { - let edge_ref = GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }; - let edge = path.get_edge(edge_ref); + let edge_ref = GraphEdgeRef { + start_idx: point_idx, + edge_idx, + reverse: false, + }; + let edge = path.get_edge(edge_ref); let intersection_type = ray_can_intersect(&edge, ray_coeffs); @@ -220,11 +247,12 @@ where Path::Point: Coordinate+Coordinate2D, RayCanIntersect::Collinear => { // There are usually no collinear collisions, so only allocate our array if we find some - let section_with_point = section_with_point.get_or_insert_with(|| vec![None; path.num_points()]); + let section_with_point = + section_with_point.get_or_insert_with(|| vec![None; path.num_points()]); // This edge is collinear with the ray - let start_idx = path.edge_start_point_idx(edge_ref); - let end_idx = path.edge_end_point_idx(edge_ref); + let start_idx = path.edge_start_point_idx(edge_ref); + let end_idx = path.edge_end_point_idx(edge_ref); if let Some(start_section) = section_with_point[start_idx] { if let Some(_end_section) = section_with_point[end_idx] { @@ -240,12 +268,12 @@ where Path::Point: Coordinate+Coordinate2D, // New section let new_section = collinear_sections.len(); collinear_sections.push(vec![start_idx, end_idx]); - section_with_point[start_idx] = Some(new_section); - section_with_point[end_idx] = Some(new_section); + section_with_point[start_idx] = Some(new_section); + section_with_point[end_idx] = Some(new_section); } } - RayCanIntersect::WrongSide => { + RayCanIntersect::WrongSide => { // Ray does not intersect the curve } } @@ -255,15 +283,17 @@ where Path::Point: Coordinate+Coordinate2D, // Collect any collinear collisions into a vec let collinear_collisions = collinear_sections .into_iter() - .flat_map(move |colinear_edge_points| crossing_edges(path, ray_coeffs, colinear_edge_points) + .flat_map(move |colinear_edge_points| { + crossing_edges(path, ray_coeffs, colinear_edge_points) .into_iter() .map(move |crossing_edge| { - let point = path.edge_start_point_idx(crossing_edge); - let point = path.point_position(point); - let line_t = ray.pos_for_point(&point); + let point = path.edge_start_point_idx(crossing_edge); + let point = path.point_position(point); + let line_t = ray.pos_for_point(&point); (crossing_edge, 0.0, line_t, point) - })) + }) + }) .collect(); (raw_collisions, collinear_collisions) @@ -273,34 +303,47 @@ where Path::Point: Coordinate+Coordinate2D, /// Given a list of collisions, removes any that are at the end just before a collinear section /// #[inline] -fn remove_collisions_before_or_after_collinear_section<'a, Path: RayPath, L: Line, Collisions: 'a+IntoIterator>(path: &'a Path, ray: &L, collisions: Collisions) -> impl 'a+Iterator -where Path::Point: Coordinate+Coordinate2D, - L: Line { +fn remove_collisions_before_or_after_collinear_section< + 'a, + Path: RayPath, + L: Line, + Collisions: 'a + IntoIterator, +>( + path: &'a Path, + ray: &L, + collisions: Collisions, +) -> impl 'a + Iterator +where + Path::Point: Coordinate + Coordinate2D, + L: Line, +{ let ray_coeffs = ray.coefficients(); - collisions.into_iter() + collisions + .into_iter() .filter(move |(collision, curve_t, _line_t, position)| { if *curve_t > 0.9 { - let end_point_idx = path.edge_end_point_idx(*collision); - let end_point = path.point_position(end_point_idx); + let end_point_idx = path.edge_end_point_idx(*collision); + let end_point = path.point_position(end_point_idx); // If any following edge is collinear, remove this collision - if position.is_near_to(&end_point, CLOSE_DISTANCE) && path.edges_for_point(end_point_idx).into_iter().map(|edge| path.get_edge(edge)).any(|next| curve_is_collinear(&next, ray_coeffs)) { - false - } else { - true - } + !(position.is_near_to(&end_point, CLOSE_DISTANCE) + && path + .edges_for_point(end_point_idx) + .into_iter() + .map(|edge| path.get_edge(edge)) + .any(|next| curve_is_collinear(&next, ray_coeffs))) } else if *curve_t < 0.1 { let start_point_idx = path.edge_start_point_idx(*collision); - let start_point = path.point_position(start_point_idx); + let start_point = path.point_position(start_point_idx); // If any preceding edge is collinear, remove this collision - if position.is_near_to(&start_point, CLOSE_DISTANCE) && path.reverse_edges_for_point(start_point_idx).into_iter().map(|edge| path.get_edge(edge)).any(|previous| curve_is_collinear(&previous, ray_coeffs)) { - // Collisions crossing collinear sections are taken care of during the collinear collision phase - false - } else { - true - } + !(position.is_near_to(&start_point, CLOSE_DISTANCE) + && path + .reverse_edges_for_point(start_point_idx) + .into_iter() + .map(|edge| path.get_edge(edge)) + .any(|previous| curve_is_collinear(&previous, ray_coeffs))) } else { // Not at the end of a curve true @@ -310,28 +353,40 @@ where Path::Point: Coordinate+Coordinate2D, /// /// Given a list of collisions, finds any that are on a collinear line and moves them to the end of the collinear section -/// +/// /// Collinear edges have the property that a ray collides with them on all points. We treat these as a collision at the start /// of the following section, as this is the point where the line could enter or leave a shape. /// #[inline] -fn move_collinear_collisions_to_end<'a, Path: RayPath, L: Line, Collisions: 'a+IntoIterator>(path: &'a Path, ray: &L, collisions: Collisions) -> impl 'a+Iterator -where Path::Point: Coordinate+Coordinate2D, - L: Line { +fn move_collinear_collisions_to_end< + 'a, + Path: RayPath, + L: Line, + Collisions: 'a + IntoIterator, +>( + path: &'a Path, + ray: &L, + collisions: Collisions, +) -> impl 'a + Iterator +where + Path::Point: Coordinate + Coordinate2D, + L: Line, +{ let ray_coeffs = ray.coefficients(); - collisions.into_iter() + collisions + .into_iter() .map(move |(collision, curve_t, line_t, position)| { let edge = path.get_edge(collision); if curve_is_collinear(&edge, ray_coeffs) { - let mut edge_ref = collision; + let mut edge_ref = collision; let mut edge; // Skip over collinear sections (they have 0 width from the point of view of the ray) loop { let (next_edge_ref, next_edge) = path.get_next_edge(edge_ref); - edge_ref = next_edge_ref; - edge = next_edge; + edge_ref = next_edge_ref; + edge = next_edge; if !curve_is_collinear(&edge, ray_coeffs) { break; } @@ -349,7 +404,12 @@ where Path::Point: Coordinate+Coordinate2D, /// Returns true if the collision is at the start of the specified edge /// #[inline] -fn collision_is_at_start(path: &Path, edge: &GraphEdgeRef, curve_t: f64, position: &Path::Point) -> bool { +fn collision_is_at_start( + path: &Path, + edge: &GraphEdgeRef, + curve_t: f64, + position: &Path::Point, +) -> bool { if curve_t > 0.1 { false } else { @@ -362,35 +422,53 @@ fn collision_is_at_start(path: &Path, edge: &GraphEdgeRef, curve_ /// Returns true if the collision is at the end of the specified edge /// #[inline] -fn collision_is_at_end(path: &Path, edge: &GraphEdgeRef, curve_t: f64, position: &Path::Point) -> bool { +fn collision_is_at_end( + path: &Path, + edge: &GraphEdgeRef, + curve_t: f64, + position: &Path::Point, +) -> bool { if curve_t < 0.9 { false } else { - let next_point_idx = path.edge_end_point_idx(*edge); - let end_point = path.point_position(next_point_idx); + let next_point_idx = path.edge_end_point_idx(*edge); + let end_point = path.point_position(next_point_idx); end_point.is_near_to(position, SMALL_DISTANCE) } } /// -/// Returns true if the +/// Returns true if the /// #[inline] -fn edges_are_glancing(path: &Path, ray: (f64, f64, f64), previous_edge: &GraphEdgeRef, following_edge: &GraphEdgeRef) -> bool { +fn edges_are_glancing( + path: &Path, + ray: (f64, f64, f64), + previous_edge: &GraphEdgeRef, + following_edge: &GraphEdgeRef, +) -> bool { // Fetch the actual edges and take the ray apart - let following_edge = path.get_edge(*following_edge); - let previous_edge = path.get_edge(*previous_edge); - let (a, b, c) = ray; + let following_edge = path.get_edge(*following_edge); + let previous_edge = path.get_edge(*previous_edge); + let (a, b, c) = ray; // A glancing collision has control points on the same side of the ray - let cp_in = previous_edge.control_points().1; - let cp_out = following_edge.control_points().0; + let cp_in = previous_edge.control_points().1; + let cp_out = following_edge.control_points().0; - let side_in = cp_in.x()*a + cp_in.y()*b + c; - let side_out = cp_out.x()*a + cp_out.y()*b + c; + let side_in = cp_in.x() * a + cp_in.y() * b + c; + let side_out = cp_out.x() * a + cp_out.y() * b + c; - let side_in = if side_in.abs() < 0.001 { 0.0 } else { side_in.signum() }; - let side_out = if side_out.abs() < 0.001 { 0.0 } else { side_out.signum() }; + let side_in = if side_in.abs() < 0.001 { + 0.0 + } else { + side_in.signum() + }; + let side_out = if side_out.abs() < 0.001 { + 0.0 + } else { + side_out.signum() + }; // A glancing collision has both edges on the same side of the ray side_in == side_out @@ -398,47 +476,61 @@ fn edges_are_glancing(path: &Path, ray: (f64, f64, f64), previous /// /// Removes extra collisions found near vertices -/// -/// When a ray crosses at a vertex it will generate a collision at the end of one edge and the beginning +/// +/// When a ray crosses at a vertex it will generate a collision at the end of one edge and the beginning /// of another. If this occurs, we should remove one of those collisions. As we use numerical methods to /// solve for the collision point, it's possible to see only the 'end' or the 'start' collision. -/// -/// It's possible for a ray to hit a vertex and not actually enter the shape. These are 'glancing' -/// collisions. A glancing collision is one that generates exactly one collision at a corner without +/// +/// It's possible for a ray to hit a vertex and not actually enter the shape. These are 'glancing' +/// collisions. A glancing collision is one that generates exactly one collision at a corner without /// actually crossing into the shape (or which happens to hit a curve exactly on a tangent). -/// -/// Corner collisions are found by looking for collisions at the start of an edge (we assume that the +/// +/// Corner collisions are found by looking for collisions at the start of an edge (we assume that the /// filtering in `move_collisions_at_end_to_beginning` has been applied) and checking if the following edge /// crosses the array. If it does not, it's probably a glancing collision. -/// +/// /// Tangent collisions are found just by looking at the tangent of the curve at the point of collision: if /// it's collinear with the ray, then the ray presumably does not cross the edge. -/// +/// /// As we use numerical methods to find line/curve collisions, it's possible for errors to result in a ray /// that hits a corner and the other edge, so we finish up by filtering for this condition. -/// +/// /// We need to filter these both in the same place as the choice of filter depends on whether or not a /// particular collision is a glancing collision or a crossing collision. /// -fn filter_collisions_near_vertices<'a, Path: RayPath, L: Line, Collisions: 'a+IntoIterator>(path: &'a Path, ray: &'a L, collisions: Collisions) -> impl 'a+Iterator -where L: Line { - let (a, b, c) = ray.coefficients(); - let mut visited_start = None; +fn filter_collisions_near_vertices< + 'a, + Path: RayPath, + L: Line, + Collisions: 'a + IntoIterator, +>( + path: &'a Path, + ray: &'a L, + collisions: Collisions, +) -> impl 'a + Iterator +where + L: Line, +{ + let (a, b, c) = ray.coefficients(); + let mut visited_start = None; - collisions.into_iter() + collisions + .into_iter() .filter_map(move |(edge, curve_t, line_t, position)| { // This only applies to collisions at the end or start of an edge let is_at_start = collision_is_at_start(path, &edge, curve_t, &position); - let is_at_end = !is_at_start && collision_is_at_end(path, &edge, curve_t, &position); + let is_at_end = !is_at_start && collision_is_at_end(path, &edge, curve_t, &position); if is_at_start || is_at_end { // Collision might be crossing or glancing: get the two edges on the collision let (preceding_edge, following_edge) = if is_at_start { - let previous_edge = path.reverse_edges_for_point(edge.start_idx) + let previous_edge = path + .reverse_edges_for_point(edge.start_idx) .into_iter() .map(|previous_edge| previous_edge.reversed()) - .filter(|previous_edge| path.edge_following_edge_idx(*previous_edge) == edge.edge_idx) - .nth(0) + .find(|previous_edge| { + path.edge_following_edge_idx(*previous_edge) == edge.edge_idx + }) .expect("Previous edge for a collision at start"); (previous_edge, edge) @@ -449,21 +541,24 @@ where L: Line { }; if edges_are_glancing(path, (a, b, c), &preceding_edge, &following_edge) { - // Ray hits close to a vertex between two edges that both face away from it (ie, may be a glancing collision) // There must also be a glancing collision on the 'other' edge (we can afford this expensive check as glancing collisions are rare) let both_glancing = if is_at_start { // Must be a point close to the end of the preceding edge too - let edge = path.get_edge(preceding_edge); - let collisions = curve_intersects_ray(&edge, ray); + let edge = path.get_edge(preceding_edge); + let collisions = curve_intersects_ray(&edge, ray); - collisions.into_iter().any(|(curve_t, _line_t, position)| collision_is_at_end(path, &preceding_edge, curve_t, &position)) + collisions.into_iter().any(|(curve_t, _line_t, position)| { + collision_is_at_end(path, &preceding_edge, curve_t, &position) + }) } else { // Must be a point close to the start of the following edge too - let edge = path.get_edge(following_edge); - let collisions = curve_intersects_ray(&edge, ray); + let edge = path.get_edge(following_edge); + let collisions = curve_intersects_ray(&edge, ray); - collisions.into_iter().any(|(curve_t, _line_t, position)| collision_is_at_start(path, &following_edge, curve_t, &position)) + collisions.into_iter().any(|(curve_t, _line_t, position)| { + collision_is_at_start(path, &following_edge, curve_t, &position) + }) }; if both_glancing { @@ -473,20 +568,23 @@ where L: Line { // Ray only gets close to the end of one of the edges: must be a crossing collision Some((edge, curve_t, line_t, position)) } - } else { - // Ray crosses exactly on the vertex: report it exactly once (as the beginning of the curve) - let visited_start = visited_start.get_or_insert_with(|| vec![None; path.num_points()]); + let visited_start = + visited_start.get_or_insert_with(|| vec![None; path.num_points()]); // At the start of the curve let was_visited = visited_start[following_edge.start_idx] .as_ref() - .map(|collisions: &SmallVec<[_; 2]>| collisions.contains(&following_edge.edge_idx)) + .map(|collisions: &SmallVec<[_; 2]>| { + collisions.contains(&following_edge.edge_idx) + }) .unwrap_or(false); if !was_visited { - visited_start[following_edge.start_idx].get_or_insert_with(|| smallvec![]).push(following_edge.edge_idx); + visited_start[following_edge.start_idx] + .get_or_insert_with(|| smallvec![]) + .push(following_edge.edge_idx); } if !was_visited { @@ -494,7 +592,6 @@ where L: Line { } else { None } - } } else { // In the middle: not glancing or crossing @@ -507,28 +604,36 @@ where L: Line { /// Removes any collision that manages to hit an edge exactly on a tangent /// #[inline] -fn remove_tangent_collisions<'a, Path: RayPath, L: Line, Collisions: 'a+IntoIterator>(path: &'a Path, ray: &'a L, collisions: Collisions) -> impl 'a+Iterator -where L: Line { - let ray_vector = (ray.point_at_pos(1.0) - ray.point_at_pos(0.0)).to_unit_vector(); +fn remove_tangent_collisions< + 'a, + Path: RayPath, + L: Line, + Collisions: 'a + IntoIterator, +>( + path: &'a Path, + ray: &'a L, + collisions: Collisions, +) -> impl 'a + Iterator +where + L: Line, +{ + let ray_vector = (ray.point_at_pos(1.0) - ray.point_at_pos(0.0)).to_unit_vector(); - collisions.into_iter() + collisions + .into_iter() .filter(move |(edge, curve_t, _line_t, _position)| { // Check if we've hit a tangent - let edge = path.get_edge(*edge); + let edge = path.get_edge(*edge); // Get the curve tangent at this position - let tangent = edge.tangent_at_pos(*curve_t).to_unit_vector(); + let tangent = edge.tangent_at_pos(*curve_t).to_unit_vector(); // Test if it's going the same way as the ray - let dot_product = ray_vector.dot(&tangent); + let dot_product = ray_vector.dot(&tangent); let dot_product_mag = dot_product.abs() - 1.0; // Dot product of two unit vectors will be 1.0 or -1.0 for a tangent collision - if dot_product_mag > -0.00000001 && dot_product_mag < 0.00000001 { - false - } else { - true - } + !(dot_product_mag > -0.00000001 && dot_product_mag < 0.00000001) }) } @@ -536,8 +641,17 @@ where L: Line { /// Finds any collision that occurred too close to an intersection and flags it as such /// #[inline] -fn flag_collisions_at_intersections<'a, Path: RayPath, Collisions: 'a+IntoIterator>(path: &'a Path, collisions: Collisions) -> impl 'a+Iterator -where Path::Point: Coordinate+Coordinate2D { +fn flag_collisions_at_intersections< + 'a, + Path: RayPath, + Collisions: 'a + IntoIterator, +>( + path: &'a Path, + collisions: Collisions, +) -> impl 'a + Iterator +where + Path::Point: Coordinate + Coordinate2D, +{ collisions .into_iter() .map(move |(collision, curve_t, line_t, position)| { @@ -545,32 +659,53 @@ where Path::Point: Coordinate+Coordinate2D { // Might be at an intersection (close to the start of the curve) if path.num_edges(collision.start_idx) > 1 { // Intersection - (GraphRayCollision::Intersection(collision), curve_t, line_t, position) + ( + GraphRayCollision::Intersection(collision), + curve_t, + line_t, + position, + ) } else { // Edge with only a single following point - (GraphRayCollision::SingleEdge(collision), curve_t, line_t, position) + ( + GraphRayCollision::SingleEdge(collision), + curve_t, + line_t, + position, + ) } } else { // Not at an intersection - (GraphRayCollision::SingleEdge(collision), curve_t, line_t, position) + ( + GraphRayCollision::SingleEdge(collision), + curve_t, + line_t, + position, + ) } }) } /// /// Finds all collisions between a ray and this path -/// -pub (crate) fn ray_collisions(path: &Path, ray: &L) -> Vec<(GraphRayCollision, f64, f64, Path::Point)> -where Path::Point: Coordinate+Coordinate2D, - L: Line { - let (p1, p2) = ray.points(); - let ray_direction = p2 - p1; +/// +pub(crate) fn ray_collisions( + path: &Path, + ray: &L, +) -> Vec<(GraphRayCollision, f64, f64, Path::Point)> +where + Path::Point: Coordinate + Coordinate2D, + L: Line, +{ + let (p1, p2) = ray.points(); + let ray_direction = p2 - p1; // Raw collisions let (crossing_collisions, collinear_collisions) = crossing_and_collinear_collisions(path, ray); - let collinear_collisions = collinear_collisions.into_iter(); - let crossing_collisions = crossing_collisions.into_iter(); - let crossing_collisions = remove_collisions_before_or_after_collinear_section(path, ray, crossing_collisions); + let collinear_collisions = collinear_collisions.into_iter(); + let crossing_collisions = crossing_collisions.into_iter(); + let crossing_collisions = + remove_collisions_before_or_after_collinear_section(path, ray, crossing_collisions); // Chain them together let collisions = collinear_collisions.chain(crossing_collisions); @@ -584,65 +719,79 @@ where Path::Point: Coordinate+Coordinate2D, // Convert to a vec and sort by ray position let mut collisions = collisions.collect::>(); - collisions.sort_by(|(edge_a, curve_t_a, line_t_a, pos_a), (edge_b, curve_t_b, line_t_b, pos_b)| { - // If the collision occurs at the same point on the line (within SMALL_DISTANCE), we need to order by edge priority. Otherwise, order by where collisions occur along the ray - let dx = pos_a.x() - pos_b.x(); - let dy = pos_a.y() - pos_b.y(); - - if dx.abs() > SMALL_DISTANCE || dy.abs() > SMALL_DISTANCE { - // Order by position on the ray - line_t_a.partial_cmp(line_t_b).unwrap_or(Ordering::Equal) - } else { - // Position on the line is the same (stabilise ordering by checking the edges) - let edge_a = edge_a.edge(); - let edge_b = edge_b.edge(); - - let result = edge_a.start_idx.cmp(&edge_b.start_idx); - if result != Ordering::Equal { - // Different start points - result - } else { - // Check if these are the same edge or not - let edge_order = edge_a.edge_idx.cmp(&edge_b.edge_idx); - - // Ordering is reversed depending on the direction of the edge relative to the line - // To produce a consistent ordering, we rely on edges from newer paths being added later in the list (having a higher edge_idx) - // The ordering here is used with `set_edge_kinds_by_ray_casting()` to generate consistent results when paths overlap - // TODO: it would be better to use label ordering here to get a more consistent result. This relies on the order that edges are added to the list - let (earlier_edge, edge_t) = match edge_order { - Ordering::Greater => (edge_b, *curve_t_b), - Ordering::Less => (edge_a, *curve_t_a), - Ordering::Equal => { return Ordering::Equal; } - }; - let earlier_edge = path.get_edge(earlier_edge); - let earlier_normal = earlier_edge.normal_at_pos(edge_t); - let earlier_direction = ray_direction.dot(&earlier_normal); + collisions.sort_by( + |(edge_a, curve_t_a, line_t_a, pos_a), (edge_b, curve_t_b, line_t_b, pos_b)| { + // If the collision occurs at the same point on the line (within SMALL_DISTANCE), we need to order by edge priority. Otherwise, order by where collisions occur along the ray + let dx = pos_a.x() - pos_b.x(); + let dy = pos_a.y() - pos_b.y(); - if earlier_direction < 0.0 { - edge_order.reverse() + if dx.abs() > SMALL_DISTANCE || dy.abs() > SMALL_DISTANCE { + // Order by position on the ray + line_t_a.partial_cmp(line_t_b).unwrap_or(Ordering::Equal) + } else { + // Position on the line is the same (stabilise ordering by checking the edges) + let edge_a = edge_a.edge(); + let edge_b = edge_b.edge(); + + let result = edge_a.start_idx.cmp(&edge_b.start_idx); + if result != Ordering::Equal { + // Different start points + result } else { - edge_order + // Check if these are the same edge or not + let edge_order = edge_a.edge_idx.cmp(&edge_b.edge_idx); + + // Ordering is reversed depending on the direction of the edge relative to the line + // To produce a consistent ordering, we rely on edges from newer paths being added later in the list (having a higher edge_idx) + // The ordering here is used with `set_edge_kinds_by_ray_casting()` to generate consistent results when paths overlap + // TODO: it would be better to use label ordering here to get a more consistent result. This relies on the order that edges are added to the list + let (earlier_edge, edge_t) = match edge_order { + Ordering::Greater => (edge_b, *curve_t_b), + Ordering::Less => (edge_a, *curve_t_a), + Ordering::Equal => { + return Ordering::Equal; + } + }; + let earlier_edge = path.get_edge(earlier_edge); + let earlier_normal = earlier_edge.normal_at_pos(edge_t); + let earlier_direction = ray_direction.dot(&earlier_normal); + + if earlier_direction < 0.0 { + edge_order.reverse() + } else { + edge_order + } } } - } - }); + }, + ); collisions } #[cfg(test)] mod test { - use super::*; + use super::super::graph_path::test::*; use super::super::path::*; use super::super::path_builder::*; - use super::super::graph_path::test::*; + use super::*; + use crate::bezier::path::GraphPath; + use crate::Coord2; #[test] fn raw_donut_collisions() { let donut = donut(); let donut = &donut; - let raw_collisions = crossing_and_collinear_collisions(&donut, &(Coord2(7.000584357101389, 8.342524209216537), Coord2(6.941479643691172, 8.441210096108172))).0.into_iter(); + let raw_collisions = crossing_and_collinear_collisions( + &donut, + &( + Coord2(7.000584357101389, 8.342524209216537), + Coord2(6.941479643691172, 8.441210096108172), + ), + ) + .0 + .into_iter(); println!("{:?}", raw_collisions.collect::>()); // assert!(false); @@ -662,8 +811,9 @@ mod test { let gp = GraphPath::from_path(&rectangle1, ()); let gp = &gp; - let collisions = crossing_and_collinear_collisions(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0))).1; - assert!(collisions.len() == 0); + let collisions = + crossing_and_collinear_collisions(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0))).1; + assert!(collisions.is_empty()); } #[test] @@ -680,11 +830,18 @@ mod test { let gp = GraphPath::from_path(&rectangle1, ()); let gp = &gp; - let collisions = crossing_and_collinear_collisions(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0))).0.into_iter(); - let collisions = remove_collisions_before_or_after_collinear_section(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0)), collisions); + let collisions = + crossing_and_collinear_collisions(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0))) + .0 + .into_iter(); + let collisions = remove_collisions_before_or_after_collinear_section( + &gp, + &(Coord2(5.0, 0.0), Coord2(5.0, 5.0)), + collisions, + ); let collisions = collisions.collect::>(); - assert!(collisions.len() == 0); + assert!(collisions.is_empty()); } #[test] @@ -699,8 +856,8 @@ mod test { .build(); // Collide along the vertical seam of this graph - let gp = GraphPath::from_path(&concave_shape, ()); - let gp = &gp; + let gp = GraphPath::from_path(&concave_shape, ()); + let gp = &gp; let ray = (Coord2(5.0, 0.0), Coord2(5.0, 5.0)); let collisions = crossing_and_collinear_collisions(&gp, &ray).1; @@ -720,12 +877,16 @@ mod test { .build(); // Collide along the vertical seam of this graph - let gp = GraphPath::from_path(&concave_shape, ()); - let gp = &gp; + let gp = GraphPath::from_path(&concave_shape, ()); + let gp = &gp; let ray = (Coord2(5.0, 0.0), Coord2(5.0, 5.0)); let collisions = crossing_and_collinear_collisions(&gp, &ray).0.into_iter(); - let collisions = remove_collisions_before_or_after_collinear_section(&gp, &(Coord2(5.0, 0.0), Coord2(5.0, 5.0)), collisions); + let collisions = remove_collisions_before_or_after_collinear_section( + &gp, + &(Coord2(5.0, 0.0), Coord2(5.0, 5.0)), + collisions, + ); let collisions = collisions.collect::>(); assert!(collisions.len() == 1); @@ -743,23 +904,30 @@ mod test { .build(); // Collide along the vertical seam of this graph - let gp = GraphPath::from_path(&concave_shape, ()); - let gp = &gp; + let gp = GraphPath::from_path(&concave_shape, ()); + let gp = &gp; let ray = (Coord2(5.0, 0.0), Coord2(5.0, 5.0)); // Raw collisions - let (normal_collisions, collinear_collisions) = crossing_and_collinear_collisions(&gp, &ray); - let normal_collisions = remove_collisions_before_or_after_collinear_section(&gp, &ray, normal_collisions).collect::>(); + let (normal_collisions, collinear_collisions) = + crossing_and_collinear_collisions(&gp, &ray); + let normal_collisions = + remove_collisions_before_or_after_collinear_section(&gp, &ray, normal_collisions) + .collect::>(); assert!(collinear_collisions.len() == 1); assert!(normal_collisions.len() == 1); // Chain them together - let collisions = collinear_collisions.into_iter().chain(normal_collisions.into_iter()).collect::>(); + let collisions = collinear_collisions + .into_iter() + .chain(normal_collisions.into_iter()) + .collect::>(); assert!(collisions.len() == 2); // Filter for accuracy - let collisions = move_collinear_collisions_to_end(&gp, &ray, collisions).collect::>(); + let collisions = + move_collinear_collisions_to_end(&gp, &ray, collisions).collect::>(); assert!(collisions.len() == 2); let collisions = filter_collisions_near_vertices(&gp, &ray, collisions).collect::>(); assert!(collisions.len() == 2); @@ -780,10 +948,10 @@ mod test { let mut with_interior_point = GraphPath::from_path(&with_interior_point, ()); with_interior_point.self_collide(0.01); - let with_interior_point = &with_interior_point; + let with_interior_point = &with_interior_point; - let ray = (Coord2(0.0, 3.0), Coord2(1.0, 3.0)); - let collisions = crossing_and_collinear_collisions(&with_interior_point, &ray).0; + let ray = (Coord2(0.0, 3.0), Coord2(1.0, 3.0)); + let collisions = crossing_and_collinear_collisions(&with_interior_point, &ray).0; println!("{:?}", with_interior_point); println!("{:?}", collisions); @@ -791,12 +959,15 @@ mod test { assert!(collisions.len() == 4); // Filter for accuracy - let collisions = move_collinear_collisions_to_end(&with_interior_point, &ray, collisions).collect::>(); + let collisions = move_collinear_collisions_to_end(&with_interior_point, &ray, collisions) + .collect::>(); assert!(collisions.len() == 4); - let collisions = filter_collisions_near_vertices(&with_interior_point, &ray, collisions).collect::>(); + let collisions = filter_collisions_near_vertices(&with_interior_point, &ray, collisions) + .collect::>(); println!("{:?}", collisions); assert!(collisions.len() == 4); - let collisions = flag_collisions_at_intersections(&with_interior_point, collisions).collect::>(); + let collisions = + flag_collisions_at_intersections(&with_interior_point, collisions).collect::>(); assert!(collisions.len() == 4); } } diff --git a/src/bezier/path/to_curves.rs b/src/bezier/path/to_curves.rs index e10fabbe..5cb24d74 100644 --- a/src/bezier/path/to_curves.rs +++ b/src/bezier/path/to_curves.rs @@ -1,18 +1,21 @@ -use super::path::*; -use super::super::curve::*; +use super::super::curve::BezierCurveFactory; +use super::path::BezierPath; -use itertools::*; +use itertools::Itertools; /// /// Converts a path to a series of bezier curves -/// -pub fn path_to_curves>(path: &Path) -> impl Iterator { - let just_start_point = vec![(path.start_point(), path.start_point(), path.start_point())].into_iter(); - let points = path.points(); +/// +pub fn path_to_curves>( + path: &Path, +) -> impl Iterator { + let just_start_point = + vec![(path.start_point(), path.start_point(), path.start_point())].into_iter(); + let points = path.points(); - just_start_point.chain(points) - .tuple_windows() - .map(|((_, _, start_point), (cp1, cp2, end_point))| { + just_start_point.chain(points).tuple_windows().map( + |((_, _, start_point), (cp1, cp2, end_point))| { Curve::from_points(start_point, (cp1, cp2), end_point) - }) + }, + ) } diff --git a/src/bezier/search.rs b/src/bezier/search.rs index 2fa7f25d..d0a39d60 100644 --- a/src/bezier/search.rs +++ b/src/bezier/search.rs @@ -1,32 +1,41 @@ -use super::bounds::*; -use super::subdivide::*; -use super::super::geo::*; +use super::super::geo::Coordinate; +use super::bounds::bounding_box4; +use super::subdivide::subdivide4; /// /// Performs a subdivision search on a curve for a point matching a function -/// +/// /// This searches for a point using a matching function that determines whether or not the point /// is within a particular bounding box. The return value is a list of t values for the curve /// described by the w values where the bounding box was shrunk to the size specified by min_size. -/// +/// /// A limitation of this algorithm is that if the target point lies very close to a subdivision point, /// it may produce multiple matches (as it will find a nearby point on either side of the subdivision) -/// -pub fn search_bounds4(min_size: f64, w1: Point, w2: Point, w3: Point, w4: Point, match_fn: MatchFn) -> Vec -where Point: Coordinate, - MatchFn: Fn(Point, Point) -> bool { +/// +pub fn search_bounds4( + min_size: f64, + w1: Point, + w2: Point, + w3: Point, + w4: Point, + match_fn: MatchFn, +) -> Vec +where + Point: Coordinate, + MatchFn: Fn(Point, Point) -> bool, +{ // Helper function to determine if a bounding box is below the minimum size - let min_size_squared = min_size * min_size; - let is_valid_match = |p1: Point, p2: Point| { - let diff = p1-p2; - let size_squared = diff.dot(&diff); + let min_size_squared = min_size * min_size; + let is_valid_match = |p1: Point, p2: Point| { + let diff = p1 - p2; + let size_squared = diff.dot(&diff); size_squared <= min_size_squared }; // Push the initial curve as one to check let mut pending = vec![]; - let mut result = vec![]; + let mut result = vec![]; // Each point is the list of w values and the min/max t values remaining to search pending.push((w1, w2, w3, w4, 0.0, 1.0)); @@ -34,9 +43,9 @@ where Point: Coordinate, // Iterate while there are still curve sections to search while let Some((w1, w2, w3, w4, min_t, max_t)) = pending.pop() { // Subdivide at the midpoint - let midpoint = (min_t + max_t)/2.0; + let midpoint = (min_t + max_t) / 2.0; let ((aw1, aw2, aw3, aw4), (bw1, bw2, bw3, bw4)) = subdivide4(0.5, w1, w2, w3, w4); - + // Compute the bounds of either side let (amin, amax) = bounding_box4(aw1, aw2, aw3, aw4); let (bmin, bmax) = bounding_box4(bw1, bw2, bw3, bw4); @@ -45,7 +54,7 @@ where Point: Coordinate, if match_fn(amin, amax) { if is_valid_match(amin, amax) { // Bounds are small enough this counts as a match: push the midpoint - result.push((min_t+midpoint)/2.0); + result.push((min_t + midpoint) / 2.0); } else { // Continue processing this half of the curve pending.push((aw1, aw2, aw3, aw4, min_t, midpoint)); @@ -56,7 +65,7 @@ where Point: Coordinate, if match_fn(bmin, bmax) { if is_valid_match(bmin, bmax) { // Bounds are small enough this counts as a match: push the midpoint - result.push((midpoint+max_t)/2.0); + result.push((midpoint + max_t) / 2.0); } else { // Continue processing this half of the curve pending.push((bw1, bw2, bw3, bw4, midpoint, max_t)); diff --git a/src/bezier/section.rs b/src/bezier/section.rs index e91c78d0..78ec261b 100644 --- a/src/bezier/section.rs +++ b/src/bezier/section.rs @@ -1,15 +1,15 @@ -use super::curve::*; -use super::basis::*; -use super::super::geo::*; -use super::super::consts::*; +use super::super::consts::SMALL_DISTANCE; +use super::super::geo::Geo; +use super::basis::de_casteljau2; +use super::curve::BezierCurve; -use std::cell::*; +use std::cell::RefCell; /// /// Represents a subsection of a bezier curve -/// +/// #[derive(Clone)] -pub struct CurveSection<'a, C: 'a+BezierCurve> { +pub struct CurveSection<'a, C: 'a + BezierCurve> { /// Full curve curve: &'a C, @@ -20,31 +20,31 @@ pub struct CurveSection<'a, C: 'a+BezierCurve> { t_m: f64, /// Cached version of the control points for this curve section - cached_control_points: RefCell> + cached_control_points: RefCell>, } -impl<'a, C: 'a+BezierCurve> CurveSection<'a, C> { +impl<'a, C: 'a + BezierCurve> CurveSection<'a, C> { /// /// Creates a new curve section from a region of another bezier curve - /// + /// pub fn new(curve: &'a C, t_min: f64, t_max: f64) -> CurveSection<'a, C> { let t_c = t_min; let t_m = t_max - t_c; CurveSection { - curve: curve, - t_m: t_m, - t_c: t_c, - cached_control_points: RefCell::new(None) + curve, + t_m, + t_c, + cached_control_points: RefCell::new(None), } } /// /// Returns the t value on the full curve for a t value on the section - /// + /// #[inline] pub fn t_for_t(&self, t: f64) -> f64 { - t*self.t_m + self.t_c + t * self.t_m + self.t_c } /// @@ -55,46 +55,46 @@ impl<'a, C: 'a+BezierCurve> CurveSection<'a, C> { let t_min = self.t_c; let t_max = self.t_for_t(1.0); - (t_max-t_min).abs() < SMALL_DISTANCE + (t_max - t_min).abs() < SMALL_DISTANCE } /// /// Creates a sub-section from this curve section (dividing it further) - /// + /// /// Unlike calling `section`, this keeps the same type and avoids the need /// for recursive recalculation for things like the control points. This means /// that `original_curve_t_values` will return the coordinates for the same /// original curve as the curve that this subsection was created from. - /// + /// pub fn subsection(&self, t_min: f64, t_max: f64) -> CurveSection<'a, C> { CurveSection::new(self.curve, self.t_for_t(t_min), self.t_for_t(t_max)) } /// /// Returns the original t values (t_min, t_max) that this section was created from - /// + /// #[inline] pub fn original_curve_t_values(&self) -> (f64, f64) { - (self.t_c, self.t_m+self.t_c) + (self.t_c, self.t_m + self.t_c) } /// /// Given a 't' value on the original curve, returns the equivalent value on this section - /// + /// #[inline] pub fn section_t_for_original_t(&self, t: f64) -> f64 { - (t-self.t_c)/self.t_m + (t - self.t_c) / self.t_m } } -impl<'a, C: 'a+BezierCurve> Geo for CurveSection<'a, C> { +impl<'a, C: 'a + BezierCurve> Geo for CurveSection<'a, C> { type Point = C::Point; } -impl<'a, C: 'a+BezierCurve> BezierCurve for CurveSection<'a, C> { +impl<'a, C: 'a + BezierCurve> BezierCurve for CurveSection<'a, C> { /// /// The start point of this curve - /// + /// #[inline] fn start_point(&self) -> Self::Point { self.curve.point_at_pos(self.t_for_t(0.0)) @@ -102,7 +102,7 @@ impl<'a, C: 'a+BezierCurve> BezierCurve for CurveSection<'a, C> { /// /// The end point of this curve - /// + /// #[inline] fn end_point(&self) -> Self::Point { self.curve.point_at_pos(self.t_for_t(1.0)) @@ -110,54 +110,55 @@ impl<'a, C: 'a+BezierCurve> BezierCurve for CurveSection<'a, C> { /// /// The control points in this curve - /// + /// fn control_points(&self) -> (Self::Point, Self::Point) { - self.cached_control_points.borrow_mut() + *self + .cached_control_points + .borrow_mut() .get_or_insert_with(move || { // This is the de-casteljau subdivision algorithm (ran twice to cut out a section of the curve) let t_min = self.t_c; // Get the weights from the curve - let w1 = self.curve.start_point(); - let (w2, w3) = self.curve.control_points(); - let w4 = self.curve.end_point(); + let w1 = self.curve.start_point(); + let (w2, w3) = self.curve.control_points(); + let w4 = self.curve.end_point(); // Weights (from de casteljau) - let wn1 = w1*(1.0-t_min) + w2*t_min; - let wn2 = w2*(1.0-t_min) + w3*t_min; - let wn3 = w3*(1.0-t_min) + w4*t_min; + let wn1 = w1 * (1.0 - t_min) + w2 * t_min; + let wn2 = w2 * (1.0 - t_min) + w3 * t_min; + let wn3 = w3 * (1.0 - t_min) + w4 * t_min; // Further refine the weights - let wnn1 = wn1*(1.0-t_min) + wn2*t_min; - let wnn2 = wn2*(1.0-t_min) + wn3*t_min; + let wnn1 = wn1 * (1.0 - t_min) + wn2 * t_min; + let wnn2 = wn2 * (1.0 - t_min) + wn3 * t_min; // Get the point at which the two curves join let p = de_casteljau2(t_min, wnn1, wnn2); - + // Curve from t_min to 1 is in (p, wnn2, wn3, w4), we need to subdivide again // Ie, we've removed the section of the curve from 0-t_min here and now need to remove t_max to 1. We're subdividing the curve t_min to 1, so the t_max value is relative to that curve rather than the source. - let (w1, w2, w3) = (p, wnn2, wn3); - let t_max = self.t_m/(1.0-self.t_c); + let (w1, w2, w3) = (p, wnn2, wn3); + let t_max = self.t_m / (1.0 - self.t_c); // Weights (from de casteljau) - let wn1 = w1*(1.0-t_max) + w2*t_max; - let wn2 = w2*(1.0-t_max) + w3*t_max; + let wn1 = w1 * (1.0 - t_max) + w2 * t_max; + let wn2 = w2 * (1.0 - t_max) + w3 * t_max; // let wn3 = w3*(1.0-t_max) + w4*t_max; // Further refine the weights - let wnn1 = wn1*(1.0-t_max) + wn2*t_max; + let wnn1 = wn1 * (1.0 - t_max) + wn2 * t_max; // let wnn2 = wn2*(1.0-t_max) + wn3*t_max; // let p = de_casteljau2(t_min, wnn1, wnn2); // Curve is (w1, wn1, wnn1, p) so control points are wn1 and wnn1 (wn1, wnn1) }) - .clone() } /// /// Given a value t from 0 to 1, returns a point on this curve - /// + /// #[inline] fn point_at_pos(&self, t: f64) -> Self::Point { self.curve.point_at_pos(self.t_for_t(t)) diff --git a/src/bezier/solve.rs b/src/bezier/solve.rs index 1b0d63d1..6c261f78 100644 --- a/src/bezier/solve.rs +++ b/src/bezier/solve.rs @@ -1,37 +1,45 @@ -use super::curve::*; -use super::super::geo::*; -use super::super::consts::*; +use super::super::consts::SMALL_DISTANCE; +use super::super::geo::Coordinate; +use super::curve::BezierCurve; -use roots::{find_roots_quadratic, find_roots_cubic, Roots}; -use smallvec::*; +use roots::{find_roots_cubic, find_roots_quadratic, Roots}; +use smallvec::{smallvec, SmallVec}; -pub (crate) const CLOSE_ENOUGH: f64 = SMALL_DISTANCE * 50.0; +pub(crate) const CLOSE_ENOUGH: f64 = SMALL_DISTANCE * 50.0; /// /// Solves for t in a single dimension for a bezier curve (finds the point(s) where the basis /// function evaluates to p) -/// +/// pub fn solve_basis_for_t(w1: f64, w2: f64, w3: f64, w4: f64, p: f64) -> SmallVec<[f64; 4]> { // Compute the coefficients for the cubic bezier function - let d = w1-p; - let c = 3.0*(w2-w1); - let b = 3.0*(w3-w2)-c; - let a = w4-w1-c-b; + let d = w1 - p; + let c = 3.0 * (w2 - w1); + let b = 3.0 * (w3 - w2) - c; + let a = w4 - w1 - c - b; // Solve for p - let roots = if a.abs() < 0.00000001 { find_roots_quadratic(b, c, d) } else { find_roots_cubic(a, b, c, d) }; + let roots = if a.abs() < 0.00000001 { + find_roots_quadratic(b, c, d) + } else { + find_roots_cubic(a, b, c, d) + }; let mut roots = match roots { - Roots::No(_) => smallvec![], - Roots::One([a]) => smallvec![a], - Roots::Two([a, b]) => smallvec![a, b], - Roots::Three([a, b, c]) => smallvec![a, b, c], - Roots::Four([a, b, c, d]) => smallvec![a, b, c, d] + Roots::No(_) => smallvec![], + Roots::One([a]) => smallvec![a], + Roots::Two([a, b]) => smallvec![a, b], + Roots::Three([a, b, c]) => smallvec![a, b, c], + Roots::Four([a, b, c, d]) => smallvec![a, b, c, d], }; // Clip to 0/1 for small ranges outside for root in roots.iter_mut() { - if *root < 0.0 && *root > -0.001 { *root = 0.0 } - if *root > 1.0 && *root < 1.001 { *root = 1.0 } + if *root < 0.0 && *root > -0.001 { + *root = 0.0 + } + if *root > 1.0 && *root < 1.001 { + *root = 1.0 + } } // Remove any roots outside the range of the function @@ -58,37 +66,46 @@ pub fn solve_curve_for_t(curve: &C, point: &C::Point) -> Option< /// is best used with accuracy values that are small compared to the length of the curve and with points far away from /// inflection points or cusps. /// -/// This is often 'good enough' to find a point close to where a user has clicked along a curve, for example, but as it -/// effectively ray-casts along the x and y axes to do so is not suitable as a general-purpose way of finding the closest point +/// This is often 'good enough' to find a point close to where a user has clicked along a curve, for example, but as it +/// effectively ray-casts along the x and y axes to do so is not suitable as a general-purpose way of finding the closest point /// on a curve to another point. /// /// Note that `curve_intersects_ray()` can be used to find points on a curve along any direction rather than solely along the axis. /// -pub fn solve_curve_for_t_along_axis(curve: &C, point: &C::Point, accuracy: f64) -> Option { - let p1 = curve.start_point(); - let (p2, p3) = curve.control_points(); - let p4 = curve.end_point(); +pub fn solve_curve_for_t_along_axis( + curve: &C, + point: &C::Point, + accuracy: f64, +) -> Option { + let p1 = curve.start_point(); + let (p2, p3) = curve.control_points(); + let p4 = curve.end_point(); // Solve the basis function for each of the point's dimensions and pick the first that appears close enough (and within the range 0-1) for dimension in 0..(C::Point::len()) { // Solve for this dimension - let (w1, w2, w3, w4) = (p1.get(dimension), p2.get(dimension), p3.get(dimension), p4.get(dimension)); - let possible_t_values = solve_basis_for_t(w1, w2, w3, w4, point.get(dimension)); + let (w1, w2, w3, w4) = ( + p1.get(dimension), + p2.get(dimension), + p3.get(dimension), + p4.get(dimension), + ); + let possible_t_values = solve_basis_for_t(w1, w2, w3, w4, point.get(dimension)); for possible_t in possible_t_values { // Ignore values outside the range of the curve - if possible_t < -0.001 || possible_t > 1.001 { + if !(-0.001..=1.001).contains(&possible_t) { continue; } // If this is an accurate enough solution, return this as the t value - let point_at_t = curve.point_at_pos(possible_t); + let point_at_t = curve.point_at_pos(possible_t); if point_at_t.is_near_to(point, accuracy) { return Some(possible_t); } } } - + // No solution: result is None None } diff --git a/src/bezier/subdivide.rs b/src/bezier/subdivide.rs index c4f760e2..d5c8913a 100644 --- a/src/bezier/subdivide.rs +++ b/src/bezier/subdivide.rs @@ -1,20 +1,25 @@ -use super::basis::*; -use super::super::geo::*; +use super::super::geo::Coordinate; +use super::basis::de_casteljau2; /// /// Subdivides a cubic bezier curve at a particular point, returning the weights of /// the two component curves -/// -pub fn subdivide4(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> - ((Point, Point, Point, Point), (Point, Point, Point, Point)) { +/// +pub fn subdivide4( + t: f64, + w1: Point, + w2: Point, + w3: Point, + w4: Point, +) -> ((Point, Point, Point, Point), (Point, Point, Point, Point)) { // Weights (from de casteljau) - let wn1 = w1*(1.0-t) + w2*t; - let wn2 = w2*(1.0-t) + w3*t; - let wn3 = w3*(1.0-t) + w4*t; + let wn1 = w1 * (1.0 - t) + w2 * t; + let wn2 = w2 * (1.0 - t) + w3 * t; + let wn3 = w3 * (1.0 - t) + w4 * t; // Further refine the weights - let wnn1 = wn1*(1.0-t) + wn2*t; - let wnn2 = wn2*(1.0-t) + wn3*t; + let wnn1 = wn1 * (1.0 - t) + wn2 * t; + let wnn2 = wn2 * (1.0 - t) + wn3 * t; // Get the point at which the two curves join let p = de_casteljau2(t, wnn1, wnn2); diff --git a/src/bezier/tangent.rs b/src/bezier/tangent.rs index 38b56dbf..099d9869 100644 --- a/src/bezier/tangent.rs +++ b/src/bezier/tangent.rs @@ -1,24 +1,29 @@ -use super::curve::*; -use super::basis::*; -use super::derivative::*; +use super::basis::de_casteljau3; +use super::curve::BezierCurve; +use super::derivative::derivative4; /// /// A structure that can be used to compute the tangent of a bezier curve -/// +/// pub struct Tangent { /// The derivative of the curve - derivative: (Curve::Point, Curve::Point, Curve::Point) + derivative: (Curve::Point, Curve::Point, Curve::Point), } impl<'a, Curve: BezierCurve> From<&'a Curve> for Tangent { /// /// Creates a structure that can computes the tangents for a bezier curve - /// - fn from(curve: &'a Curve) -> Tangent { + /// + fn from(curve: &'a Curve) -> Self { let control_points = curve.control_points(); - Tangent { - derivative: derivative4(curve.start_point(), control_points.0, control_points.1, curve.end_point()) + Self { + derivative: derivative4( + curve.start_point(), + control_points.0, + control_points.1, + curve.end_point(), + ), } } } @@ -26,7 +31,7 @@ impl<'a, Curve: BezierCurve> From<&'a Curve> for Tangent { impl Tangent { /// /// Calculates the tangent at a particular point - /// + /// pub fn tangent(&self, t: f64) -> Curve::Point { de_casteljau3(t, self.derivative.0, self.derivative.1, self.derivative.2) } diff --git a/src/bezier/walk.rs b/src/bezier/walk.rs index 9658cf5d..48300a0e 100644 --- a/src/bezier/walk.rs +++ b/src/bezier/walk.rs @@ -1,31 +1,34 @@ -use super::basis::*; -use super::curve::*; -use super::section::*; -use super::derivative::*; +use super::basis::de_casteljau3; +use super::curve::BezierCurve; +use super::derivative::derivative4; +use super::section::CurveSection; -use crate::geo::*; +use crate::geo::Coordinate; /// /// Walks a bezier curve by dividing it into a number of sections /// /// These sections are uneven in length: they all advance equally by 't' value but the points will -/// be spaced according to the shape of the curve (will have an uneven distance between them) +/// be spaced according to the shape of the curve (will have an uneven distance between them) /// #[inline] -pub fn walk_curve_unevenly<'a, Curve: BezierCurve>(curve: &'a Curve, num_subdivisions: usize) -> impl 'a+Iterator> { +pub fn walk_curve_unevenly( + curve: &Curve, + num_subdivisions: usize, +) -> impl Iterator> { if num_subdivisions > 0 { UnevenWalkIterator { - curve: curve, - step: (1.0)/(num_subdivisions as f64), - num_subdivisions: num_subdivisions, - last_subdivision: 0 + curve, + step: (1.0) / (num_subdivisions as f64), + num_subdivisions, + last_subdivision: 0, } } else { UnevenWalkIterator { - curve: curve, - step: 0.0, - num_subdivisions: 0, - last_subdivision: 0 + curve, + step: 0.0, + num_subdivisions: 0, + last_subdivision: 0, } } } @@ -36,20 +39,28 @@ pub fn walk_curve_unevenly<'a, Curve: BezierCurve>(curve: &'a Curve, num_subdivi /// This walks evenly using the curve's chord length rather than the arc length: each section returned will have a `chord_length()` /// of `distance`. The call `vary_by()` can be used on the result to vary the step size at each point. /// -pub fn walk_curve_evenly<'a, Curve: BezierCurve>(curve: &'a Curve, distance: f64, max_error: f64) -> EvenWalkIterator<'a, Curve> { +pub fn walk_curve_evenly( + curve: &Curve, + distance: f64, + max_error: f64, +) -> EvenWalkIterator { const INITIAL_INCREMENT: f64 = 0.01; // Too small or negative values might produce bad effects due to floating point inprecision - let max_error = if max_error < 1e-10 { 1e-10 } else { max_error }; - let distance = if distance < 1e-10 { 1e-10 } else { distance }; + let max_error = if max_error < 1e-10 { 1e-10 } else { max_error }; + let distance = if distance < 1e-10 { 1e-10 } else { distance }; // Compute the derivative of the curve - let (cp1, cp2) = curve.control_points(); + let (cp1, cp2) = curve.control_points(); let (wn1, wn2, wn3) = derivative4(curve.start_point(), cp1, cp2, curve.end_point()); // We can calculate the initial speed from close to the first point of the curve - let initial_speed = de_casteljau3(0.001, wn1, wn2, wn3).magnitude(); - let initial_speed = if distance/(initial_speed.abs()) > 0.25 { de_casteljau3(0.01, wn1, wn2, wn3).magnitude() } else { initial_speed }; + let initial_speed = de_casteljau3(0.001, wn1, wn2, wn3).magnitude(); + let initial_speed = if distance / (initial_speed.abs()) > 0.25 { + de_casteljau3(0.01, wn1, wn2, wn3).magnitude() + } else { + initial_speed + }; let initial_increment = if initial_speed.abs() < 0.00000001 { INITIAL_INCREMENT @@ -57,16 +68,20 @@ pub fn walk_curve_evenly<'a, Curve: BezierCurve>(curve: &'a Curve, distance: f64 distance / initial_speed }; - let initial_increment = if initial_increment > 0.25 { INITIAL_INCREMENT } else { initial_increment }; + let initial_increment = if initial_increment > 0.25 { + INITIAL_INCREMENT + } else { + initial_increment + }; EvenWalkIterator { - curve: curve, - derivative: (wn1, wn2, wn3), - last_t: 0.0, - last_point: curve.start_point(), + curve, + derivative: (wn1, wn2, wn3), + last_t: 0.0, + last_point: curve.start_point(), last_increment: initial_increment, - distance: distance, - max_error: max_error + distance, + max_error, } } @@ -75,16 +90,16 @@ pub fn walk_curve_evenly<'a, Curve: BezierCurve>(curve: &'a Curve, distance: f64 /// struct UnevenWalkIterator<'a, Curve: BezierCurve> { /// The curve that this is iterating over - curve: &'a Curve, + curve: &'a Curve, /// The distance between t-values - step: f64, + step: f64, /// The total number of subdivisions to return - num_subdivisions: usize, + num_subdivisions: usize, /// The number of the most recently returned subdivision - last_subdivision: usize + last_subdivision: usize, } impl<'a, Curve: BezierCurve> Iterator for UnevenWalkIterator<'a, Curve> { @@ -112,37 +127,37 @@ impl<'a, Curve: BezierCurve> Iterator for UnevenWalkIterator<'a, Curve> { /// pub struct EvenWalkIterator<'a, Curve: BezierCurve> { /// The curve that is being walked - curve: &'a Curve, + curve: &'a Curve, /// The wn1, wn2, wn3 of the derivative of the curve - derivative: (Curve::Point, Curve::Point, Curve::Point), + derivative: (Curve::Point, Curve::Point, Curve::Point), /// The last 't' value where a coordinate was generated - last_t: f64, + last_t: f64, /// The point generated at the last 't' value - last_point: Curve::Point, + last_point: Curve::Point, /// The last increment last_increment: f64, /// The target distance between points (as the chord length) - distance: f64, + distance: f64, /// The maximum error in distance for the points that are generated by this iterator - max_error: f64 + max_error: f64, } /// /// Iterator that modifies the behaviour of EvenWalkIterator so that it varies the distance between /// each step /// -struct VaryingWalkIterator<'a, Curve: BezierCurve, DistanceIter: 'a+Iterator> { +struct VaryingWalkIterator<'a, Curve: BezierCurve, DistanceIter: 'a + Iterator> { /// The even walk iterator even_iterator: EvenWalkIterator<'a, Curve>, /// Iterator that returns the distance for each step (or None if the distance is fixed for the remaining distance) - distance_iterator: Option + distance_iterator: Option, } impl<'a, Curve: BezierCurve> EvenWalkIterator<'a, Curve> { @@ -153,10 +168,13 @@ impl<'a, Curve: BezierCurve> EvenWalkIterator<'a, Curve> { /// would generate a cycle of distances (say, by calling `cycle()`), but if it does end, the last distance will be used /// until the iteration over the curve is completed /// - pub fn vary_by>(self, distance: DistanceIter) -> impl 'a+Iterator> { + pub fn vary_by>( + self, + distance: DistanceIter, + ) -> impl 'a + Iterator> { VaryingWalkIterator { - even_iterator: self, - distance_iterator: Some(distance) + even_iterator: self, + distance_iterator: Some(distance), } } } @@ -168,14 +186,14 @@ impl<'a, Curve: BezierCurve> Iterator for EvenWalkIterator<'a, Curve> { const MAX_ITERATIONS: usize = 32; // Gather values - let curve = self.curve; + let curve = self.curve; let (wn1, wn2, wn3) = self.derivative; - let distance = self.distance; - let max_error = self.max_error; + let distance = self.distance; + let max_error = self.max_error; let mut t_increment = self.last_increment; - let last_t = self.last_t; - let mut next_t = last_t + t_increment; - let last_point = self.last_point.clone(); + let last_t = self.last_t; + let mut next_t = last_t + t_increment; + let last_point = self.last_point; let mut next_point; // If the next point appears to be after the end of the curve, and the end of the curve is further away than the closest distance, return None @@ -183,13 +201,11 @@ impl<'a, Curve: BezierCurve> Iterator for EvenWalkIterator<'a, Curve> { return None; } - if next_t >= 1.0 { - if last_point.distance_to(&curve.point_at_pos(1.0)) < distance { - // End point is closer than the target distance - let last_section = curve.section(last_t, 1.0); - self.last_t = 1.0; - return Some(last_section); - } + if next_t >= 1.0 && last_point.distance_to(&curve.point_at_pos(1.0)) < distance { + // End point is closer than the target distance + let last_section = curve.section(last_t, 1.0); + self.last_t = 1.0; + return Some(last_section); } let mut count = 0; @@ -197,11 +213,11 @@ impl<'a, Curve: BezierCurve> Iterator for EvenWalkIterator<'a, Curve> { debug_assert!(!t_increment.is_nan()); // next_point contains the initial estimate of the position of the point at distance 't' from the current point - next_point = curve.point_at_pos(next_t); + next_point = curve.point_at_pos(next_t); // Compute the distance to the guess and the error - let next_distance = last_point.distance_to(&next_point); - let error = distance - next_distance; + let next_distance = last_point.distance_to(&next_point); + let error = distance - next_distance; // We've found the next point if the error drops low enough if error.abs() < max_error { @@ -209,39 +225,39 @@ impl<'a, Curve: BezierCurve> Iterator for EvenWalkIterator<'a, Curve> { } // Use the slope of the curve at this position to work out the next point to try - let tangent = de_casteljau3(next_t, wn1, wn2, wn3); - let speed = tangent.magnitude(); + let tangent = de_casteljau3(next_t, wn1, wn2, wn3); + let speed = tangent.magnitude(); if speed.abs() < 0.00000001 { // Very rarely, the speed can be 0 (at t=0 or t=1 when the control points overlap, for the easiest example to construct) // Use the error to adjust the t position we're testing if it's larger than max_error - let error_ratio = distance / next_distance; - t_increment = if error_ratio < 0.5 { + let error_ratio = distance / next_distance; + t_increment = if error_ratio < 0.5 { t_increment * 0.5 - } else if error_ratio > 1.5 { + } else if error_ratio > 1.5 { t_increment * 1.5 } else { t_increment * error_ratio }; } else { // Use the current speed to work out the adjustment for t_increment - let error = next_distance - distance; - let adjustment = error / speed; + let error = next_distance - distance; + let adjustment = error / speed; if adjustment >= t_increment { t_increment *= 0.3333333; } else { - t_increment = t_increment - adjustment; + t_increment -= adjustment; } } - next_t = last_t + t_increment; + next_t = last_t + t_increment; // Sharp changes in direction can sometimes cause the distance to fail to converge: we limit the maximum number of iterations to avoid this - // (It's possible for there to be multiple points 'distance' away or two equidistant points around the target point, + // (It's possible for there to be multiple points 'distance' away or two equidistant points around the target point, // and for this algorithm to fail to converge as a result) - count += 1; + count += 1; if count >= MAX_ITERATIONS { break; } @@ -255,16 +271,18 @@ impl<'a, Curve: BezierCurve> Iterator for EvenWalkIterator<'a, Curve> { } // Update the coordinates - self.last_point = next_point; + self.last_point = next_point; self.last_increment = t_increment; - self.last_t = next_t; + self.last_t = next_t; // Return the section that we found Some(self.curve.section(last_t, next_t)) } } -impl<'a, Curve: BezierCurve, DistanceIter: 'a+Iterator> Iterator for VaryingWalkIterator<'a, Curve, DistanceIter> { +impl<'a, Curve: BezierCurve, DistanceIter: 'a + Iterator> Iterator + for VaryingWalkIterator<'a, Curve, DistanceIter> +{ type Item = CurveSection<'a, Curve>; fn next(&mut self) -> Option { @@ -272,9 +290,9 @@ impl<'a, Curve: BezierCurve, DistanceIter: 'a+Iterator> Iterator for V if let Some(distance_iterator) = &mut self.distance_iterator { if let Some(distance) = distance_iterator.next() { // Update the distance in the 'even' iterator - let ratio = distance / self.even_iterator.distance; - self.even_iterator.distance = distance; - self.even_iterator.last_increment *= ratio; + let ratio = distance / self.even_iterator.distance; + self.even_iterator.distance = distance; + self.even_iterator.last_increment *= ratio; } else { // No more distance changes self.distance_iterator = None; diff --git a/src/debug/graph_path_debug.rs b/src/debug/graph_path_debug.rs index 3570f99c..03093888 100644 --- a/src/debug/graph_path_debug.rs +++ b/src/debug/graph_path_debug.rs @@ -1,129 +1,186 @@ -use super::super::bezier::*; -use super::super::bezier::path::*; +use super::super::bezier::path::{GraphPath, GraphPathEdgeKind, PathDirection, PathLabel}; +use super::super::bezier::{ + BezierCurve, BoundingBox, Bounds, Coordinate, Coordinate2D, NormalCurve, +}; use std::fmt::Write; /// /// Writes out the graph path as an SVG string /// -pub fn graph_path_svg_string(path: &GraphPath, rays: Vec<(P, P)>) -> String { +pub fn graph_path_svg_string( + path: &GraphPath, + rays: Vec<(P, P)>, +) -> String { let mut result = String::new(); - let bounds = path.all_edges().fold(Bounds::empty(), |a, b| a.union_bounds(b.bounding_box())); - let offset = bounds.min(); - let scale = 1000.0/(bounds.max() - bounds.min()).x(); - - let mut index = 0; - - for kinds in vec![ vec![ GraphPathEdgeKind::Uncategorised, GraphPathEdgeKind::Visited, GraphPathEdgeKind::Interior ], vec![ GraphPathEdgeKind::Exterior ] ] { + let bounds = path + .all_edges() + .fold(Bounds::empty(), |a, b| a.union_bounds(b.bounding_box())); + let offset = bounds.min(); + let scale = 1000.0 / (bounds.max() - bounds.min()).x(); + + let mut index = 0; + + for kinds in vec![ + vec![ + GraphPathEdgeKind::Uncategorised, + GraphPathEdgeKind::Visited, + GraphPathEdgeKind::Interior, + ], + vec![GraphPathEdgeKind::Exterior], + ] { for edge in path.all_edges() { if !kinds.contains(&edge.kind()) { continue; } let start_point = edge.start_point(); - let end_point = edge.end_point(); - let (cp1, cp2) = edge.control_points(); + let end_point = edge.end_point(); + let (cp1, cp2) = edge.control_points(); - write!(result, "\n", - index, + writeln!(result, "", + index, start_point.x(), start_point.y(), cp1.x(), cp1.y(), cp2.x(), cp2.y(), end_point.x(), end_point.y()).unwrap(); - let start_point = (start_point - offset)*scale; - let end_point = (end_point - offset)*scale; - let cp1 = (cp1 - offset)*scale; - let cp2 = (cp2 - offset)*scale; + let start_point = (start_point - offset) * scale; + let end_point = (end_point - offset) * scale; + let cp1 = (cp1 - offset) * scale; + let cp2 = (cp2 - offset) * scale; - let kind = match edge.kind() { - GraphPathEdgeKind::Uncategorised => "yellow", - GraphPathEdgeKind::Visited => "red", - GraphPathEdgeKind::Exterior => "blue", - GraphPathEdgeKind::Interior => "green" + let kind = match edge.kind() { + GraphPathEdgeKind::Uncategorised => "yellow", + GraphPathEdgeKind::Visited => "red", + GraphPathEdgeKind::Exterior => "blue", + GraphPathEdgeKind::Interior => "green", }; - write!(result, "\n", + writeln!(result, "", start_point.x(), start_point.y(), cp1.x(), cp1.y(), cp2.x(), cp2.y(), end_point.x(), end_point.y(), kind).unwrap(); - write!(result, "\n", end_point.x(), end_point.y()).unwrap(); - - write!(result, "{} <- {} - {}\n", end_point.x()+4.0, end_point.y()+8.0, edge.end_point_index(), edge.start_point_index(), index).unwrap(); + writeln!( + result, + "", + end_point.x(), + end_point.y() + ) + .unwrap(); + + writeln!( + result, + "{} <- {} - {}", + end_point.x() + 4.0, + end_point.y() + 8.0, + edge.end_point_index(), + edge.start_point_index(), + index + ) + .unwrap(); index += 1; } } for (p1, p2) in rays { - write!(result, "\n", p1.x(), p1.y(), p2.x(), p2.y()).unwrap(); + writeln!( + result, + "", + p1.x(), + p1.y(), + p2.x(), + p2.y() + ) + .unwrap(); let collisions = path.ray_collisions(&(p1, p2)); write!(result, "", collisions.len()).unwrap(); let p1 = (p1 - offset) * scale; let p2 = (p2 - offset) * scale; - let point_offset = p2-p1; + let point_offset = p2 - p1; let p1 = p1 - (point_offset * 1000.0); let p2 = p2 + (point_offset * 1000.0); - write!(result, "\n", - p1.x(), p1.y(), - p2.x(), p2.y()).unwrap(); - - let ray_direction = p2-p1; + writeln!( + result, + "", + p1.x(), + p1.y(), + p2.x(), + p2.y() + ) + .unwrap(); + + let ray_direction = p2 - p1; let mut collision_count = 0; - let mut collision_num = 0; + let mut collision_num = 0; for (collision, curve_t, _line_t, pos) in collisions { // Determine which direction the ray is crossing - let edge = collision.edge(); - let PathLabel(path_number, direction) = path.edge_label(edge); - let normal = path.get_edge(edge).normal_at_pos(curve_t); - - let side = ray_direction.dot(&normal).signum() as i32; - let side = match direction { - PathDirection::Clockwise => { side }, - PathDirection::Anticlockwise => { -side } + let edge = collision.edge(); + let PathLabel(path_number, direction) = path.edge_label(edge); + let normal = path.get_edge(edge).normal_at_pos(curve_t); + + let side = ray_direction.dot(&normal).signum() as i32; + let side = match direction { + PathDirection::Clockwise => side, + PathDirection::Anticlockwise => -side, }; // Update the collision count collision_count += side; - collision_num += 1; + collision_num += 1; - let pos = (pos - offset)*scale; + let pos = (pos - offset) * scale; - let edge = path.get_edge(edge); + let edge = path.get_edge(edge); let start_point = edge.start_point(); - let end_point = edge.end_point(); - let (cp1, cp2) = edge.control_points(); + let end_point = edge.end_point(); + let (cp1, cp2) = edge.control_points(); - write!(result, "\n", + writeln!(result, "", collision_num, path_number, start_point.x(), start_point.y(), cp1.x(), cp1.y(), cp2.x(), cp2.y(), end_point.x(), end_point.y()).unwrap(); - let start_point = (start_point - offset)*scale; - let end_point = (end_point - offset)*scale; - let cp1 = (cp1 - offset)*scale; - let cp2 = (cp2 - offset)*scale; + let start_point = (start_point - offset) * scale; + let end_point = (end_point - offset) * scale; + let cp1 = (cp1 - offset) * scale; + let cp2 = (cp2 - offset) * scale; - write!(result, "\n", + writeln!(result, "", start_point.x(), start_point.y(), cp1.x(), cp1.y(), cp2.x(), cp2.y(), - end_point.x(), end_point.y(), - "cyan").unwrap(); + end_point.x(), end_point.y()).unwrap(); - write!(result, "\n", pos.x(), pos.y()).unwrap(); - write!(result, "{}: C{} ({})\n", pos.x() + 2.0, pos.y()+3.0, collision_num, collision_count, side).unwrap(); + writeln!( + result, + "", + pos.x(), + pos.y() + ) + .unwrap(); + writeln!( + result, + "{}: C{} ({})", + pos.x() + 2.0, + pos.y() + 3.0, + collision_num, + collision_count, + side + ) + .unwrap(); } } result -} \ No newline at end of file +} diff --git a/src/debug/mod.rs b/src/debug/mod.rs index 564cbe2b..9a6386e0 100644 --- a/src/debug/mod.rs +++ b/src/debug/mod.rs @@ -1,5 +1,5 @@ -mod path_to_string; mod graph_path_debug; +mod path_to_string; -pub use self::path_to_string::*; pub use self::graph_path_debug::*; +pub use self::path_to_string::*; diff --git a/src/debug/path_to_string.rs b/src/debug/path_to_string.rs index 1dcb92cb..59f56809 100644 --- a/src/debug/path_to_string.rs +++ b/src/debug/path_to_string.rs @@ -1,23 +1,41 @@ -use super::super::geo::*; -use super::super::bezier::path::*; +use super::super::bezier::path::BezierPath; +use super::super::geo::{Coordinate, Coordinate2D}; -use std::fmt::*; +use std::fmt::Write; /// /// Writes out a path as a Rust simple bezier path definition -/// +/// /// This can be used to generate code for a test when a path definition fails to perform as expected /// -pub fn bezier_path_to_rust_definition>(path: &P) -> String { +pub fn bezier_path_to_rust_definition>( + path: &P, +) -> String { let mut rust_code = String::new(); let start = path.start_point(); - write!(&mut rust_code, "BezierPathBuilder::::start(Coord2({}, {}))", start.x(), start.y()).unwrap(); + write!( + rust_code, + "BezierPathBuilder::::start(Coord2({}, {}))", + start.x(), + start.y() + ) + .unwrap(); for (cp1, cp2, endpoint) in path.points() { - write!(&mut rust_code, "\n .curve_to((Coord2({}, {}), Coord2({}, {})), Coord2({}, {}))", cp1.x(), cp1.y(), cp2.x(), cp2.y(), endpoint.x(), endpoint.y()).unwrap(); + write!( + rust_code, + "\n .curve_to((Coord2({}, {}), Coord2({}, {})), Coord2({}, {}))", + cp1.x(), + cp1.y(), + cp2.x(), + cp2.y(), + endpoint.x(), + endpoint.y() + ) + .unwrap(); } - write!(&mut rust_code, "\n .build()").unwrap(); + write!(rust_code, "\n .build()").unwrap(); rust_code } diff --git a/src/geo/bounding_box.rs b/src/geo/bounding_box.rs index 316ab896..c356b522 100644 --- a/src/geo/bounding_box.rs +++ b/src/geo/bounding_box.rs @@ -1,27 +1,27 @@ -use super::geo::*; -use super::has_bounds::*; -use super::coordinate::*; +use super::coordinate::Coordinate; +use super::geo::Geo; +use super::has_bounds::HasBoundingBox; /// /// Trait implemented by things representing axis-aligned bounding boxes -/// -pub trait BoundingBox : Geo+Sized { +/// +pub trait BoundingBox: Geo + Sized { /// /// Returns a bounding box with the specified minimum and maximum coordinates - /// + /// fn from_min_max(min: Self::Point, max: Self::Point) -> Self; /// /// Returns a bounding box containing the specified points - /// - fn bounds_for_points>(points: PointIter) -> Self { + /// + fn bounds_for_points>(points: PointIter) -> Self { let mut points = points.into_iter(); // Initialise the bounding box with the first point let first_point = points.next(); if let Some(first_point) = first_point { // min, max is just the first point initially - let (mut min, mut max) = (first_point.clone(), first_point); + let (mut min, mut max) = (first_point, first_point); // Update with the remainder of the points for point in points { @@ -43,19 +43,19 @@ pub trait BoundingBox : Geo+Sized { /// /// Returns the maximum point of this bounding box - /// + /// fn max(&self) -> Self::Point; /// /// Returns an empty bounding box - /// + /// fn empty() -> Self { Self::from_min_max(Self::Point::origin(), Self::Point::origin()) } /// /// True if this bounding box is empty - /// + /// #[inline] fn is_empty(&self) -> bool { self.min() == self.max() @@ -63,27 +63,34 @@ pub trait BoundingBox : Geo+Sized { /// /// Creates the union of this and another bounding box - /// + /// fn union_bounds(self, target: Self) -> Self { if self.is_empty() { target } else if target.is_empty() { self } else { - Self::from_min_max(Self::Point::from_smallest_components(self.min(), target.min()), Self::Point::from_biggest_components(self.max(), target.max())) + Self::from_min_max( + Self::Point::from_smallest_components(self.min(), target.min()), + Self::Point::from_biggest_components(self.max(), target.max()), + ) } } /// /// Returns true if this bounding box overlaps another - /// + /// fn overlaps(&self, target: &Self) -> bool { let (min1, max1) = (self.min(), self.max()); let (min2, max2) = (target.min(), target.max()); for p_index in 0..Self::Point::len() { - if min1.get(p_index) > max2.get(p_index) { return false; } - if min2.get(p_index) > max1.get(p_index) { return false; } + if min1.get(p_index) > max2.get(p_index) { + return false; + } + if min2.get(p_index) > max1.get(p_index) { + return false; + } } true @@ -92,9 +99,9 @@ pub trait BoundingBox : Geo+Sized { /// /// Type representing a bounding box -/// +/// /// (Unlike a normal point tuple this always represents its bounds in minimum/maximum order) -/// +/// #[derive(Debug, Clone, Copy, PartialEq)] pub struct Bounds(Point, Point); @@ -116,19 +123,19 @@ impl BoundingBox for (Point, Point) { } impl HasBoundingBox for Bounds { - fn get_bounding_box>(&self) -> Bounds { + fn get_bounding_box>(&self) -> Bounds { Bounds::from_min_max(self.min(), self.max()) } } impl Geo for Bounds { - type Point=Point; + type Point = Point; } impl BoundingBox for Bounds { #[inline] fn from_min_max(min: Self::Point, max: Self::Point) -> Self { - Bounds(min, max) + Self(min, max) } #[inline] @@ -140,4 +147,4 @@ impl BoundingBox for Bounds { fn max(&self) -> Self::Point { self.1 } -} \ No newline at end of file +} diff --git a/src/geo/coordinate.rs b/src/geo/coordinate.rs index 4af42b66..51d43ea0 100644 --- a/src/geo/coordinate.rs +++ b/src/geo/coordinate.rs @@ -3,65 +3,72 @@ //! //! The `Coordinate` trait provides a way to represent coordinates in arbitary numbers of dimensions. Most of the //! types in `flo_curves` support arbitrary coordinate types through this trait. -//! +//! //! `Coordinate2D` coordinates are a special case of coordinates with only two dimensions. Some operations are //! only defined for two dimensions: for example, taking the normal of a Bezier curve. The `Coord2` type is //! supplied as a generic implementation of a 2-dimensional coordinate, though these operations will work on //! any type for which the `Coordinate2D` trait is defined. //! -use smallvec::*; +use smallvec::{smallvec, SmallVec}; -use std::ops::*; +use std::ops::{Add, Mul, Sub}; /// /// Represents a value that can be used as a coordinate in a bezier curve -/// -pub trait Coordinate : Sized+Copy+Add+Mul+Sub+PartialEq { +/// +pub trait Coordinate: + Sized + + Copy + + Add + + Mul + + Sub + + PartialEq +{ /// /// Creates a new coordinate from the specified set of components - /// + /// fn from_components(components: &[f64]) -> Self; /// /// Returns the origin coordinate - /// + /// fn origin() -> Self; /// /// The number of components in this coordinate - /// + /// fn len() -> usize; /// /// Retrieves the component at the specified index - /// + /// fn get(&self, index: usize) -> f64; /// /// Returns a point made up of the biggest components of the two points - /// + /// fn from_biggest_components(p1: Self, p2: Self) -> Self; /// /// Returns a point made up of the smallest components of the two points - /// + /// fn from_smallest_components(p1: Self, p2: Self) -> Self; /// /// Computes the distance between this coordinate and another of the same type - /// + /// #[inline] fn distance_to(&self, target: &Self) -> f64 { - let offset = *self - *target; - let squared_distance = offset.dot(&offset); + let offset = *self - *target; + let squared_distance = offset.dot(&offset); f64::sqrt(squared_distance) } /// /// Computes the dot product for this vector along with another vector - /// + /// #[inline] fn dot(&self, target: &Self) -> f64 { let mut dot_product = 0.0; @@ -75,7 +82,7 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S /// /// Computes the magnitude of this vector - /// + /// #[inline] fn magnitude(&self) -> f64 { f64::sqrt(self.dot(self)) @@ -83,14 +90,14 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S /// /// Treating this as a vector, returns a unit vector in the same direction - /// + /// #[inline] fn to_unit_vector(&self) -> Self { let magnitude = self.magnitude(); if magnitude == 0.0 { Self::origin() } else { - *self * (1.0/magnitude) + *self * (1.0 / magnitude) } } @@ -105,7 +112,7 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S } } - return false; + false } /// @@ -117,7 +124,7 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S for component in 0..Self::len() { let unrounded_value = self.get(component); - let rounded_value = (unrounded_value/accuracy).round() * accuracy; + let rounded_value = (unrounded_value / accuracy).round() * accuracy; new_components.push(rounded_value); } @@ -127,41 +134,41 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S /// /// True if this point is within max_distance of another point - /// + /// #[inline] fn is_near_to(&self, other: &Self, max_distance: f64) -> bool { - let offset = *self - *other; - let squared_distance = offset.dot(&offset); + let offset = *self - *other; + let squared_distance = offset.dot(&offset); - squared_distance <= (max_distance*max_distance) + squared_distance <= (max_distance * max_distance) } /// /// Generates a smoothed version of a set of coordinates, using the specified weights /// (weights should add up to 1.0). - /// + /// /// A suggested set of weights might be '[0.25, 0.5, 0.25]', which will slightly /// adjust each point according to its neighbours (the central weight is what's /// applied to the 'current' point) - /// + /// fn smooth(points: &[Self], weights: &[f64]) -> Vec { - let mut smoothed = vec![]; - let points_len = points.len() as i32; - let weight_len = weights.len() as i32; - let weight_offset = weight_len/2; - + let mut smoothed = vec![]; + let points_len = points.len() as i32; + let weight_len = weights.len() as i32; + let weight_offset = weight_len / 2; + for index in 0..points_len { - let mut res = Self::origin(); + let mut res = Self::origin(); let initial_pos = index - weight_offset; for weight_pos in 0..weight_len { - let weight = weights[weight_pos as usize]; - let source_pos = initial_pos + weight_pos; + let weight = weights[weight_pos as usize]; + let source_pos = initial_pos + weight_pos; - let source_val = if source_pos < 0 { + let source_val = if source_pos < 0 { &points[0] } else if source_pos >= points_len { - &points[(points_len-1) as usize] + &points[(points_len - 1) as usize] } else { &points[source_pos as usize] }; @@ -178,18 +185,20 @@ pub trait Coordinate : Sized+Copy+Add+Mul+S /// /// Represents a coordinate with a 2D position -/// +/// pub trait Coordinate2D { fn x(&self) -> f64; fn y(&self) -> f64; #[inline] - fn coords(&self) -> (f64, f64) { (self.x(), self.y()) } + fn coords(&self) -> (f64, f64) { + (self.x(), self.y()) + } } /// /// Represents a coordinate with a 3D position -/// +/// pub trait Coordinate3D { fn x(&self) -> f64; fn y(&self) -> f64; @@ -197,16 +206,25 @@ pub trait Coordinate3D { } impl Coordinate for f64 { - fn from_components(components: &[f64]) -> f64 { + fn from_components(components: &[f64]) -> Self { components[0] } - #[inline] fn origin() -> f64 { 0.0 } - #[inline] fn len() -> usize { 1 } - #[inline] fn get(&self, _index: usize) -> f64 { *self } + #[inline] + fn origin() -> Self { + 0.0 + } + #[inline] + fn len() -> usize { + 1 + } + #[inline] + fn get(&self, _index: usize) -> f64 { + *self + } #[inline] - fn from_biggest_components(p1: f64, p2: f64) -> f64 { + fn from_biggest_components(p1: Self, p2: Self) -> Self { if p1 > p2 { p1 } else { @@ -215,7 +233,7 @@ impl Coordinate for f64 { } #[inline] - fn from_smallest_components(p1: f64, p2: f64) -> f64 { + fn from_smallest_components(p1: Self, p2: Self) -> Self { if p1 < p2 { p1 } else { @@ -224,11 +242,11 @@ impl Coordinate for f64 { } #[inline] - fn distance_to(&self, target: &f64) -> f64 { - f64::abs(self-target) + fn distance_to(&self, target: &Self) -> f64 { + Self::abs(self - target) } - fn dot(&self, target: &f64) -> f64 { + fn dot(&self, target: &Self) -> f64 { self * target } } @@ -240,7 +258,7 @@ pub struct Coord2(pub f64, pub f64); impl Coordinate2D for Coord2 { /// /// X component of this coordinate - /// + /// #[inline] fn x(&self) -> f64 { self.0 @@ -248,105 +266,113 @@ impl Coordinate2D for Coord2 { /// /// Y component of this coordinate - /// + /// #[inline] fn y(&self) -> f64 { self.1 } } -impl Add for Coord2 { - type Output=Coord2; +impl Add for Coord2 { + type Output = Self; #[inline] - fn add(self, rhs: Coord2) -> Coord2 { - Coord2(self.0 + rhs.0, self.1 + rhs.1) + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0, self.1 + rhs.1) } } -impl Sub for Coord2 { - type Output=Coord2; +impl Sub for Coord2 { + type Output = Self; #[inline] - fn sub(self, rhs: Coord2) -> Coord2 { - Coord2(self.0 - rhs.0, self.1 - rhs.1) + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0, self.1 - rhs.1) } } impl Mul for Coord2 { - type Output=Coord2; + type Output = Self; #[inline] - fn mul(self, rhs: f64) -> Coord2 { - Coord2(self.0 * rhs, self.1 * rhs) + fn mul(self, rhs: f64) -> Self { + Self(self.0 * rhs, self.1 * rhs) } } impl From<(f64, f64)> for Coord2 { - fn from((x, y): (f64, f64)) -> Coord2 { - Coord2(x, y) + fn from((x, y): (f64, f64)) -> Self { + Self(x, y) } } -impl Into<(f64, f64)> for Coord2 { - fn into(self) -> (f64, f64) { - (self.0, self.1) +impl From for (f64, f64) { + fn from(c: Coord2) -> (f64, f64) { + (c.0, c.1) } } impl From<(f32, f32)> for Coord2 { - fn from((x, y): (f32, f32)) -> Coord2 { - Coord2(x as _, y as _) + fn from((x, y): (f32, f32)) -> Self { + Self(x as _, y as _) } } -impl Into<(f32, f32)> for Coord2 { - fn into(self) -> (f32, f32) { - (self.0 as _, self.1 as _) +impl From for (f32, f32) { + fn from(c: Coord2) -> (f32, f32) { + (c.0 as _, c.1 as _) } } impl Coordinate for Coord2 { #[inline] - fn from_components(components: &[f64]) -> Coord2 { - Coord2(components[0], components[1]) + fn from_components(components: &[f64]) -> Self { + Self(components[0], components[1]) } #[inline] - fn origin() -> Coord2 { - Coord2(0.0, 0.0) + fn origin() -> Self { + Self(0.0, 0.0) } #[inline] - fn len() -> usize { 2 } + fn len() -> usize { + 2 + } #[inline] - fn get(&self, index: usize) -> f64 { + fn get(&self, index: usize) -> f64 { match index { 0 => self.0, 1 => self.1, - _ => panic!("Coord2 only has two components") + _ => panic!("Coord2 only has two components"), } } - fn from_biggest_components(p1: Coord2, p2: Coord2) -> Coord2 { - Coord2(f64::from_biggest_components(p1.0, p2.0), f64::from_biggest_components(p1.1, p2.1)) + fn from_biggest_components(p1: Self, p2: Self) -> Self { + Self( + f64::from_biggest_components(p1.0, p2.0), + f64::from_biggest_components(p1.1, p2.1), + ) } - fn from_smallest_components(p1: Coord2, p2: Coord2) -> Coord2 { - Coord2(f64::from_smallest_components(p1.0, p2.0), f64::from_smallest_components(p1.1, p2.1)) + fn from_smallest_components(p1: Self, p2: Self) -> Self { + Self( + f64::from_smallest_components(p1.0, p2.0), + f64::from_smallest_components(p1.1, p2.1), + ) } #[inline] - fn distance_to(&self, target: &Coord2) -> f64 { - let dist_x = target.0-self.0; - let dist_y = target.1-self.1; + fn distance_to(&self, target: &Self) -> f64 { + let dist_x = target.0 - self.0; + let dist_y = target.1 - self.1; - f64::sqrt(dist_x*dist_x + dist_y*dist_y) + f64::sqrt(dist_x * dist_x + dist_y * dist_y) } #[inline] fn dot(&self, target: &Self) -> f64 { - self.0*target.0 + self.1*target.1 + self.0 * target.0 + self.1 * target.1 } } diff --git a/src/geo/coordinate_ext.rs b/src/geo/coordinate_ext.rs index 63b7b24e..a9648866 100644 --- a/src/geo/coordinate_ext.rs +++ b/src/geo/coordinate_ext.rs @@ -1,4 +1,4 @@ -use crate::geo::coordinate::*; +use crate::geo::coordinate::{Coordinate, Coordinate2D}; /// /// Extra functions provided for coordinate types @@ -20,18 +20,22 @@ pub trait Coordinate2DExt { fn unit_vector_at_angle(radians: impl Into) -> Self; } -impl CoordinateExt for T -where T: Coordinate { +impl CoordinateExt for T +where + T: Coordinate, +{ fn unit_vector() -> Self { - let mut components = vec![0.0; Self::len()]; - components[0] = 1.0; + let mut components = vec![0.0; Self::len()]; + components[0] = 1.0; Self::from_components(&components) } } impl Coordinate2DExt for T -where T: Coordinate+Coordinate2D { +where + T: Coordinate + Coordinate2D, +{ fn unit_vector_at_angle(radians: impl Into) -> Self { let radians = radians.into(); diff --git a/src/geo/geo.rs b/src/geo/geo.rs index 941656eb..1d62af9d 100644 --- a/src/geo/geo.rs +++ b/src/geo/geo.rs @@ -1,8 +1,8 @@ -use super::coordinate::*; +use super::coordinate::Coordinate; /// /// Simple base trait implemented by things representing geometry -/// +/// pub trait Geo { /// The type of a point in this geometry type Point: Coordinate; diff --git a/src/geo/has_bounds.rs b/src/geo/has_bounds.rs index a0aae708..b78c2c90 100644 --- a/src/geo/has_bounds.rs +++ b/src/geo/has_bounds.rs @@ -1,12 +1,12 @@ -use super::geo::*; -use super::bounding_box::*; +use super::bounding_box::BoundingBox; +use super::geo::Geo; /// /// Trait implemented by types that have a bounding box associated with them /// -pub trait HasBoundingBox : Geo { +pub trait HasBoundingBox: Geo { /// /// Returns the bounding box that encloses this item /// - fn get_bounding_box>(&self) -> Bounds; + fn get_bounding_box>(&self) -> Bounds; } diff --git a/src/geo/mod.rs b/src/geo/mod.rs index 0e8ff129..c7737fbc 100644 --- a/src/geo/mod.rs +++ b/src/geo/mod.rs @@ -1,26 +1,25 @@ //! //! # Traits for basic geometric definitions -//! +//! //! This provides some basic geometric definitions. The `Geo` trait can be implemented by any type that has //! a particular type of coordinate - for example, implementations of `BezierCurve` need to implement `Geo` //! in order to describe what type they use for coordinates. -//! +//! //! `BoundingBox` provides a way to describe axis-aligned bounding boxes. It too is a trait, making it //! possible to request bounding boxes in types other than the default `Bounds` type supplied by the //! library. //! -mod geo; -mod sweep; -mod has_bounds; +mod bounding_box; mod coordinate; mod coordinate_ext; -mod bounding_box; +mod geo; +mod has_bounds; +mod sweep; -pub use self::geo::*; -pub use self::sweep::*; -pub use self::has_bounds::*; -pub use self::coordinate::*; pub use self::bounding_box::*; +pub use self::coordinate::*; pub use self::coordinate_ext::*; - +pub use self::geo::*; +pub use self::has_bounds::*; +pub use self::sweep::*; diff --git a/src/geo/sweep.rs b/src/geo/sweep.rs index 845856ff..cc9539c0 100644 --- a/src/geo/sweep.rs +++ b/src/geo/sweep.rs @@ -1,23 +1,26 @@ -use crate::geo::*; +use crate::geo::{BoundingBox, Bounds, Coordinate2D, HasBoundingBox}; -use smallvec::*; +use smallvec::{smallvec, SmallVec}; -use std::cmp::{Ordering}; +use std::cmp::Ordering; /// /// Sweeps a set of objects with bounding boxes to find the potential collisions between them /// /// The objects must be sorted into order by their min-x position, with the lowest first /// -pub fn sweep_self<'a, TItem, BoundsIter>(ordered_items: BoundsIter) -> impl 'a+Iterator +pub fn sweep_self<'a, TItem, BoundsIter>( + ordered_items: BoundsIter, +) -> impl 'a + Iterator where -BoundsIter: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + BoundsIter: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ SweepSelfIterator { - bounds_iterator: ordered_items, - pending: smallvec![], - by_max_x: Vec::new() + bounds_iterator: ordered_items, + pending: smallvec![], + by_max_x: Vec::new(), } } @@ -27,18 +30,22 @@ TItem::Point: Coordinate2D { /// This will only collide between objects in src and objects in tgt. Both must be sorted into order by /// their min-x position, with the lowest first /// -pub fn sweep_against<'a, TItem, SrcBoundsIter, TgtBoundsIter>(src: SrcBoundsIter, tgt: TgtBoundsIter) -> impl 'a+Iterator +pub fn sweep_against<'a, TItem, SrcBoundsIter, TgtBoundsIter>( + src: SrcBoundsIter, + tgt: TgtBoundsIter, +) -> impl 'a + Iterator where -SrcBoundsIter: 'a+Iterator, -TgtBoundsIter: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + SrcBoundsIter: 'a + Iterator, + TgtBoundsIter: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ SweepAgainstIterator { - src_iterator: Some(src), - tgt_iterator: tgt, - pending: smallvec![], - src_by_max_x: Vec::new(), - src_last_min_x: f64::MIN + src_iterator: Some(src), + tgt_iterator: tgt, + pending: smallvec![], + src_by_max_x: Vec::new(), + src_last_min_x: f64::MIN, } } @@ -47,9 +54,10 @@ TItem::Point: Coordinate2D { /// struct SweepSelfIterator<'a, TItem, BoundsIter> where -BoundsIter: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + BoundsIter: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ /// Iterator, ordered by minimum X position, that returns the items to be checked for overlaps bounds_iterator: BoundsIter, @@ -58,14 +66,15 @@ TItem::Point: Coordinate2D { /// Items currently under consideration for collisions, reverse ordered by their maximum X coordinate /// (reverse ordered so we can remove the earliest items by popping them) - by_max_x: Vec<(Bounds, &'a TItem)> + by_max_x: Vec<(Bounds, &'a TItem)>, } impl<'a, TItem, BoundsIter> Iterator for SweepSelfIterator<'a, TItem, BoundsIter> where -BoundsIter: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + BoundsIter: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ type Item = (&'a TItem, &'a TItem); fn next(&mut self) -> Option { @@ -77,19 +86,14 @@ TItem::Point: Coordinate2D { // Attempt to fill the pending queue by reading from the bounds iterator loop { // Read the next item and retrieve its bounding box - let next_item = if let Some(next_item) = self.bounds_iterator.next() { - next_item - } else { - // No more items to read, and the pending queue is empty - return None; - }; + let next_item = self.bounds_iterator.next()?; // Fetch the bounding box let next_bounds = next_item.get_bounding_box::>(); // Remove elements from the front of the by_max_x list until the closest ends after where this item begins // As the bounds_iterator is ordered by the min_x, we'll never see anything that's before this point again here - let min_x = next_bounds.min().x(); + let min_x = next_bounds.min().x(); while let Some((earliest_x, _item)) = self.by_max_x.last() { if earliest_x.max().x() >= min_x { break; @@ -108,18 +112,21 @@ TItem::Point: Coordinate2D { // Insert the new item into the 'by_max_x' list // TODO: possible that something like a btree is much more efficient here when there are a lot of items to process - let max_x = next_bounds.max().x(); - let index = self.by_max_x.binary_search_by(|(bounds, _item)| { - let item_max_x = bounds.max().x(); - - if item_max_x > max_x { - Ordering::Less - } else if item_max_x == max_x { - Ordering::Equal - } else { - Ordering::Greater - } - }).unwrap_or_else(|idx| idx); + let max_x = next_bounds.max().x(); + let index = self + .by_max_x + .binary_search_by(|(bounds, _item)| { + let item_max_x = bounds.max().x(); + + if item_max_x > max_x { + Ordering::Less + } else if item_max_x == max_x { + Ordering::Equal + } else { + Ordering::Greater + } + }) + .unwrap_or_else(|idx| idx); self.by_max_x.insert(index, (next_bounds, next_item)); @@ -136,10 +143,11 @@ TItem::Point: Coordinate2D { /// struct SweepAgainstIterator<'a, TItem, SrcIterator, TgtIterator> where -SrcIterator: 'a+Iterator, -TgtIterator: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + SrcIterator: 'a + Iterator, + TgtIterator: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ /// Iterator, ordered by minimum X position src_iterator: Option, @@ -156,12 +164,14 @@ TItem::Point: Coordinate2D { src_by_max_x: Vec<(Bounds, &'a TItem)>, } -impl<'a, TItem, SrcIterator, TgtIterator> Iterator for SweepAgainstIterator<'a, TItem, SrcIterator, TgtIterator> +impl<'a, TItem, SrcIterator, TgtIterator> Iterator + for SweepAgainstIterator<'a, TItem, SrcIterator, TgtIterator> where -SrcIterator: 'a+Iterator, -TgtIterator: 'a+Iterator, -TItem: 'a+HasBoundingBox, -TItem::Point: Coordinate2D { + SrcIterator: 'a + Iterator, + TgtIterator: 'a + Iterator, + TItem: 'a + HasBoundingBox, + TItem::Point: Coordinate2D, +{ type Item = (&'a TItem, &'a TItem); fn next(&mut self) -> Option { @@ -172,14 +182,14 @@ TItem::Point: Coordinate2D { } // Read a new target item. Target items determine the sweep position (we read things in order such that there'll be no collisions before this point) - let next_tgt = self.tgt_iterator.next(); - let next_tgt = if let Some(next_tgt) = next_tgt { next_tgt } else { return None }; + let next_tgt = self.tgt_iterator.next(); + let next_tgt = next_tgt?; let next_tgt_bounds = next_tgt.get_bounding_box::>(); // Sweep the source and target items - let tgt_min_x = next_tgt_bounds.min().x(); - let tgt_max_x = next_tgt_bounds.max().x(); + let tgt_min_x = next_tgt_bounds.min().x(); + let tgt_max_x = next_tgt_bounds.max().x(); while let Some((earliest_x, _item)) = self.src_by_max_x.last() { if earliest_x.max().x() >= tgt_min_x { @@ -192,32 +202,39 @@ TItem::Point: Coordinate2D { // Read source items and add them to the src list until we find one after the existing target loop { // Stop reading if we get a source item that can't overlap the current target item - if self.src_last_min_x > tgt_max_x { break; } + if self.src_last_min_x > tgt_max_x { + break; + } // Try to read the next source item - let next_src = if let Some(next_src) = self.src_iterator.as_mut().and_then(|iter| iter.next()) { + let next_src = if let Some(next_src) = + self.src_iterator.as_mut().and_then(|iter| iter.next()) + { next_src } else { self.src_iterator = None; - break; + break; }; // Add to the list of source items - let src_bounds = next_src.get_bounding_box::>(); - let src_min_x = src_bounds.min().x(); - let src_max_x = src_bounds.max().x(); - - let index = self.src_by_max_x.binary_search_by(|(bounds, _item)| { - let item_max_x = bounds.max().x(); - - if item_max_x > src_max_x { - Ordering::Less - } else if item_max_x == src_max_x { - Ordering::Equal - } else { - Ordering::Greater - } - }).unwrap_or_else(|idx| idx); + let src_bounds = next_src.get_bounding_box::>(); + let src_min_x = src_bounds.min().x(); + let src_max_x = src_bounds.max().x(); + + let index = self + .src_by_max_x + .binary_search_by(|(bounds, _item)| { + let item_max_x = bounds.max().x(); + + if item_max_x > src_max_x { + Ordering::Less + } else if item_max_x == src_max_x { + Ordering::Equal + } else { + Ordering::Greater + } + }) + .unwrap_or_else(|idx| idx); self.src_by_max_x.insert(index, (src_bounds, next_src)); diff --git a/src/lib.rs b/src/lib.rs index 45c73fe7..881babea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,33 +1,34 @@ -//! +//! //! flo_curves //! ========== //! -//! `flo_curves` is a library of routines for inspecting and manipulating curves, with a focus on cubic Bézier curves. In -//! this library, you'll find routines for computing points on curves, performing collision detection between curves and +//! `flo_curves` is a library of routines for inspecting and manipulating curves, with a focus on cubic Bézier curves. In +//! this library, you'll find routines for computing points on curves, performing collision detection between curves and //! lines or other curves, all the way up to routines for combining paths made up of multiple curves. -//! +//! //! Anyone doing any work with Bézier curves will likely find something in this library that is of use, but its range of //! functions makes it particularly useful for collision detection or performing path arithmetic. -//! +//! //! A set of curve and coordinate types are provided by the library, as well as a set of traits that can be implemented //! on any types with suitable properties. Implementing these traits makes it possible to add the extra features of this //! library to any existing code that has its own way of representing coordinates, curves or paths. -//! +//! //! `flo_curves` was built as a support library for `flowbetween`, an animation tool I'm working on. //! #![warn(bare_trait_objects)] -#[macro_use] mod test_assert; -mod consts; -pub mod bezier; -pub mod line; +#[macro_use] +mod test_assert; pub mod arc; +pub mod bezier; +mod consts; pub mod debug; +pub mod line; pub mod geo; pub use self::geo::*; -pub use self::bezier::BezierCurveFactory; pub use self::bezier::BezierCurve; +pub use self::bezier::BezierCurveFactory; pub use self::line::Line; diff --git a/src/line/coefficients.rs b/src/line/coefficients.rs index dd366526..76e2a0f6 100644 --- a/src/line/coefficients.rs +++ b/src/line/coefficients.rs @@ -1,27 +1,29 @@ -use super::line::*; -use super::super::geo::*; +use super::super::geo::{Coordinate, Coordinate2D}; +use super::line::Line; /// /// For a two-dimensional line, computes the coefficients of the line equation ax+by+c=0 -/// These coefficients are not normalized, which is slightly more efficient than computing the normalized form. -/// +/// These coefficients are not normalized, which is slightly more efficient than computing the normalized form. +/// /// This will return (0,0,0) for a line where the start and end point are the same. -/// -pub fn line_coefficients_2d_unnormalized(line: &L) -> (f64, f64, f64) -where L::Point: Coordinate+Coordinate2D { - // Compute the offset - let (from, to) = line.points(); - let offset = to - from; +/// +pub fn line_coefficients_2d_unnormalized(line: &L) -> (f64, f64, f64) +where + L::Point: Coordinate + Coordinate2D, +{ + // Compute the offset + let (from, to) = line.points(); + let offset = to - from; // Compute values for a, b, c - let (a, b, c) = if offset.x() == 0.0 && offset.y() == 0.0 { + let (a, b, c) = if offset.x() == 0.0 && offset.y() == 0.0 { // This is a point rather than a line return (0.0, 0.0, 0.0); } else if offset.x().abs() > offset.y().abs() { // Derive a, b, c from y = ax+c let a = offset.y() / offset.x(); let b = -1.0; - let c = -(a*from.x() + b*from.y()); + let c = -(a * from.x() + b * from.y()); if offset.x() > 0.0 { (-a, -b, -c) @@ -32,7 +34,7 @@ where L::Point: Coordinate+Coordinate2D { // Derive a, b, c from x = by+c let a = -1.0; let b = offset.x() / offset.y(); - let c = -(a*from.x() + b*from.y()); + let c = -(a * from.x() + b * from.y()); if offset.y() > 0.0 { (-a, -b, -c) @@ -45,19 +47,21 @@ where L::Point: Coordinate+Coordinate2D { } /// -/// For a two-dimensional line, computes the coefficients of the line equation ax+by+c=0, such that +/// For a two-dimensional line, computes the coefficients of the line equation ax+by+c=0, such that /// a^2+b^2 = 1. This normalized form means that `a*x + b*y + c` will return the distance that the /// point `x`, `y` is from the line. -/// +/// /// This will return (0,0,0) for a line where the start and end point are the same. -/// -pub fn line_coefficients_2d(line: &L) -> (f64, f64, f64) -where L::Point: Coordinate+Coordinate2D { +/// +pub fn line_coefficients_2d(line: &L) -> (f64, f64, f64) +where + L::Point: Coordinate + Coordinate2D, +{ let (a, b, c) = line_coefficients_2d_unnormalized(line); // Normalise so that a^2+b^2 = 1 - let factor = (a*a + b*b).sqrt(); - let (a, b, c) = (a/factor, b/factor, c/factor); + let factor = (a * a + b * b).sqrt(); + let (a, b, c) = (a / factor, b / factor, c / factor); (a, b, c) } diff --git a/src/line/intersection.rs b/src/line/intersection.rs index ef2c9d7c..a071ffb5 100644 --- a/src/line/intersection.rs +++ b/src/line/intersection.rs @@ -1,5 +1,5 @@ -use super::line::*; -use super::super::geo::*; +use super::super::geo::{Coordinate, Coordinate2D}; +use super::line::Line; /// Smallest divisor magnitude to use in ray_intersects_ray (the closer the divisor is to 0, the more close to parallel the lines are), so this /// determines the shallowest angle allowed between two lines before we consider them to be parallel. @@ -8,25 +8,29 @@ const RAY_DIVISOR_SMALLEST_VALUE: f64 = 2e-12; /// /// Returns the point at which two lines intersect (if they intersect) -/// +/// /// Only the 2-dimensional form is supported at the moment (lines are much less likely to intersect /// in higher dimensions) -/// -pub fn line_intersects_line(line1: &L, line2: &L) -> Option -where L::Point: Coordinate2D { +/// +pub fn line_intersects_line(line1: &L, line2: &L) -> Option +where + L::Point: Coordinate2D, +{ let line1_points = line1.points(); let line2_points = line2.points(); let ((x1, y1), (x2, y2)) = (line1_points.0.coords(), line1_points.1.coords()); let ((x3, y3), (x4, y4)) = (line2_points.0.coords(), line2_points.1.coords()); - let ua = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); - let ub = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); + let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) + / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); + let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) + / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); - if ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0 { + if (0.0..=1.0).contains(&ua) && ub >= 0.0 && ub <= 1.0 { Some(L::Point::from_components(&[ - x1+(ua*(x2-x1)), - y1+(ua*(y2-y1)) + x1 + (ua * (x2 - x1)), + y1 + (ua * (y2 - y1)), ])) } else { None @@ -36,24 +40,27 @@ where L::Point: Coordinate2D { /// /// Returns the point at which a line and a ray intersect (if they intersect). The ray is assumed to be /// infinitely long, but the line is not. -/// +/// /// Only the 2-dimensional form is supported at the moment (lines are much less likely to intersect /// in higher dimensions) -/// -pub fn line_intersects_ray(line: &L, ray: &L) -> Option -where L::Point: Coordinate2D { +/// +pub fn line_intersects_ray(line: &L, ray: &L) -> Option +where + L::Point: Coordinate2D, +{ let line_points = line.points(); - let ray_points = ray.points(); + let ray_points = ray.points(); let ((x1, y1), (x2, y2)) = (line_points.0.coords(), line_points.1.coords()); let ((x3, y3), (x4, y4)) = (ray_points.0.coords(), ray_points.1.coords()); - let ua = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); + let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) + / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); - if ua >= 0.0 && ua <= 1.0 { + if (0.0..=1.0).contains(&ua) { Some(L::Point::from_components(&[ - x1+(ua*(x2-x1)), - y1+(ua*(y2-y1)) + x1 + (ua * (x2 - x1)), + y1 + (ua * (y2 - y1)), ])) } else { None @@ -62,26 +69,28 @@ where L::Point: Coordinate2D { /// /// Returns the point at which two rays intersect (if they intersect). Rays are infinitely long. -/// +/// /// Only the 2-dimensional form is supported at the moment (lines are much less likely to intersect /// in higher dimensions) -/// -pub fn ray_intersects_ray(line: &L, ray: &L) -> Option -where L::Point: Coordinate2D { +/// +pub fn ray_intersects_ray(line: &L, ray: &L) -> Option +where + L::Point: Coordinate2D, +{ let line_points = line.points(); - let ray_points = ray.points(); + let ray_points = ray.points(); - let ((x1, y1), (x2, y2)) = (line_points.0.coords(), line_points.1.coords()); - let ((x3, y3), (x4, y4)) = (ray_points.0.coords(), ray_points.1.coords()); + let ((x1, y1), (x2, y2)) = (line_points.0.coords(), line_points.1.coords()); + let ((x3, y3), (x4, y4)) = (ray_points.0.coords(), ray_points.1.coords()); - let divisor = (y4-y3)*(x2-x1) - (x4-x3)*(y2-y1); + let divisor = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if divisor.abs() > RAY_DIVISOR_SMALLEST_VALUE { - let ua = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / divisor; + let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / divisor; Some(L::Point::from_components(&[ - x1+(ua*(x2-x1)), - y1+(ua*(y2-y1)) + x1 + (ua * (x2 - x1)), + y1 + (ua * (y2 - y1)), ])) } else { None @@ -90,26 +99,34 @@ where L::Point: Coordinate2D { /// /// Determines if a 2D line has intersected a bounding box (and returns the intersection if it exists) -/// +/// pub fn line_clip_to_bounds(line: &L, bounds: &(L::Point, L::Point)) -> Option -where L::Point: Coordinate2D { +where + L::Point: Coordinate2D, +{ // Fetch the points for the line - let line_points = line.points(); - let ((x1, y1), (x2, y2)) = (line_points.0.coords(), line_points.1.coords()); - let (dx, dy) = (x2-x1, y2-y1); + let line_points = line.points(); + let ((x1, y1), (x2, y2)) = (line_points.0.coords(), line_points.1.coords()); + let (dx, dy) = (x2 - x1, y2 - y1); // ... and the points for the bounding rectangle - let (xmin, ymin) = (bounds.0.x().min(bounds.1.x()), bounds.0.y().min(bounds.1.y())); - let (xmax, ymax) = (bounds.0.x().max(bounds.1.x()), bounds.0.y().max(bounds.1.y())); + let (xmin, ymin) = ( + bounds.0.x().min(bounds.1.x()), + bounds.0.y().min(bounds.1.y()), + ); + let (xmax, ymax) = ( + bounds.0.x().max(bounds.1.x()), + bounds.0.y().max(bounds.1.y()), + ); // Our line can be described as '(x1+t*dx, y1+t*dy)' where 0 <= t <= 1 // We want to solve for the edges, eg (xmin=x1+tmin*dx => txmin=(xmin-x1)/dx) - let delta = [-dx, dx, -dy, dy]; - let edge = [x1-xmin, xmax-x1, y1-ymin, ymax-y1]; + let delta = [-dx, dx, -dy, dy]; + let edge = [x1 - xmin, xmax - x1, y1 - ymin, ymax - y1]; // t1 and t2 are the points on the line. Initially they describe the entire line (t=0 -> t=1) - let mut t1 = 0.0; - let mut t2 = 1.0; + let mut t1 = 0.0; + let mut t2 = 1.0; // Clip against each of the 4 edges in turn for (delta, edge) in delta.iter().zip(edge.iter()) { @@ -121,7 +138,7 @@ where L::Point: Coordinate2D { } } else { // Compute the 't' value where the line intersects this edge - let t = edge/delta; + let t = edge / delta; if delta < &0.0 && t1 < t { // Start of the line is clipped against this edge (if the delta value is <0 the start is closer to this edge) @@ -138,8 +155,8 @@ where L::Point: Coordinate2D { None } else { // Line intersects the rectangle. t1 and t2 indicate where - let p1 = L::Point::from_components(&[x1 + t1*dx, y1 + t1*dy]); - let p2 = L::Point::from_components(&[x1 + t2*dx, y1 + t2*dy]); + let p1 = L::Point::from_components(&[x1 + t1 * dx, y1 + t1 * dy]); + let p2 = L::Point::from_components(&[x1 + t2 * dx, y1 + t2 * dy]); Some(L::from_points(p1, p2)) } diff --git a/src/line/line.rs b/src/line/line.rs index d7eb0b52..d2d55b30 100644 --- a/src/line/line.rs +++ b/src/line/line.rs @@ -1,47 +1,47 @@ -use super::coefficients::*; -use super::super::geo::*; +use super::super::geo::{Coordinate, Coordinate2D, Geo}; +use super::coefficients::line_coefficients_2d; /// /// Represents a straight line -/// -pub trait Line : Geo { +/// +pub trait Line: Geo { /// /// Creates a new line from points - /// + /// fn from_points(p1: Self::Point, p2: Self::Point) -> Self; /// /// Returns the two points that mark the start and end of this line - /// + /// fn points(&self) -> (Self::Point, Self::Point); /// /// Given a value 't' from 0 to 1, returns the point at that position along the line - /// + /// fn point_at_pos(&self, t: f64) -> Self::Point { - let (p1, p2) = self.points(); - let delta = p2-p1; + let (p1, p2) = self.points(); + let delta = p2 - p1; - p1 + delta*t + p1 + delta * t } /// /// Given a point (assumed to be on the line), returns the 't' value on the line - /// + /// /// If the point is not on the line, this will return a t value where at least one of the components of the point matches with /// the point on the line. /// fn pos_for_point(&self, point: &Self::Point) -> f64 { - let (p1, p2) = self.points(); - let delta_line = p2-p1; - let delta_point = *point-p1; + let (p1, p2) = self.points(); + let delta_line = p2 - p1; + let delta_point = *point - p1; for component_idx in 0..Self::Point::len() { - let line_component = delta_line.get(component_idx); + let line_component = delta_line.get(component_idx); let point_component = delta_point.get(component_idx); if line_component.abs() > 0.000001 && point_component.abs() > 0.000001 { - return point_component/line_component; + return point_component / line_component; } } @@ -52,14 +52,14 @@ pub trait Line : Geo { /// /// Trait implemented by a 2D line -/// +/// pub trait Line2D { - type Point: Coordinate+Coordinate2D; + type Point: Coordinate + Coordinate2D; /// /// Returns the coefficients (a, b, c) for this line, such that ax+by+c = 0 for /// any point on the line and also such that a^2 + b^2 = 1 - /// + /// fn coefficients(&self) -> (f64, f64, f64); /// @@ -68,7 +68,7 @@ pub trait Line2D { /// Note that this will project the line to infinity so this can return a distance to a point outside of the start or end point /// of the line. To determine if this has occurred, the `pos_for_point()` call can be used to determine the `t` value for the /// closest point: it will return a value in the range `0.0..1.0` if the closest point is within the line. - /// + /// fn distance_to(&self, p: &Self::Point) -> f64; /// @@ -77,14 +77,14 @@ pub trait Line2D { fn which_side(&self, p: &Self::Point) -> i8; } -impl Geo for (Point, Point) { +impl Geo for (Point, Point) { type Point = Point; } /// /// Simplest line is just a tuple of two points -/// -impl Line for (Point, Point) { +/// +impl Line for (Point, Point) { /// /// Creates a new line from points /// @@ -95,20 +95,20 @@ impl Line for (Point, Point) { /// /// Returns the two points that mark the start and end of this line - /// + /// #[inline] fn points(&self) -> (Self::Point, Self::Point) { - self.clone() + *self } } -impl> Line2D for L { - type Point=Point; +impl> Line2D for L { + type Point = Point; /// /// Returns the coefficients (a, b, c) for this line, such that ax+by+c = 0 for /// any point on the line and also such that a^2 + b^2 = 1 - /// + /// #[inline] fn coefficients(&self) -> (f64, f64, f64) { line_coefficients_2d(self) @@ -116,12 +116,12 @@ impl> Line2D for L { /// /// Returns the distance from a point to this line - /// + /// #[inline] fn distance_to(&self, p: &Self::Point) -> f64 { let (a, b, c) = self.coefficients(); - a*p.x() + b*p.y() + c + a * p.x() + b * p.y() + c } /// @@ -131,7 +131,9 @@ impl> Line2D for L { fn which_side(&self, p: &Self::Point) -> i8 { let (start, end) = self.points(); - let side = ((p.x()-start.x())*(end.y()-start.y()) - (p.y()-start.y())*(end.x()-start.x())).signum(); + let side = ((p.x() - start.x()) * (end.y() - start.y()) + - (p.y() - start.y()) * (end.x() - start.x())) + .signum(); if side < 0.0 { -1 diff --git a/src/line/mod.rs b/src/line/mod.rs index 7b276444..9863f07f 100644 --- a/src/line/mod.rs +++ b/src/line/mod.rs @@ -1,22 +1,22 @@ //! //! # Manipulating and describing lines -//! +//! //! While `flo_curves` deals mostly with curves, it also supplies a small library of functions for manipulating //! lines. The `Line` trait can be implemented on other types that define lines, enabling them to be used anywhere //! the library needs to perform an operation on a line. -//! +//! //! The basic line type is simply a tuple of two points (that is, any tuple of two values of the same type that //! implements `Coordinate`). //! +mod coefficients; +mod intersection; mod line; mod to_curve; -mod intersection; -mod coefficients; -pub use self::line::*; -pub use self::to_curve::*; pub use self::coefficients::*; pub use self::intersection::*; +pub use self::line::*; +pub use self::to_curve::*; pub use super::geo::*; diff --git a/src/line/to_curve.rs b/src/line/to_curve.rs index 92b01bbd..ea036524 100644 --- a/src/line/to_curve.rs +++ b/src/line/to_curve.rs @@ -1,13 +1,16 @@ -use super::line::*; -use super::super::bezier::*; +use super::super::bezier::BezierCurveFactory; +use super::line::Line; /// /// Changes a line to a bezier curve -/// -pub fn line_to_bezier>(line: &L) -> Curve { - let points = line.points(); - let point_distance = points.1 - points.0; - let (cp1, cp2) = (points.0 + point_distance*0.3333, points.0 + point_distance*0.6666); +/// +pub fn line_to_bezier>(line: &L) -> Curve { + let points = line.points(); + let point_distance = points.1 - points.0; + let (cp1, cp2) = ( + points.0 + point_distance * 0.3333, + points.0 + point_distance * 0.6666, + ); Curve::from_points(points.0, (cp1, cp2), points.1) } diff --git a/src/test_assert.rs b/src/test_assert.rs index 5346ba99..d7198866 100644 --- a/src/test_assert.rs +++ b/src/test_assert.rs @@ -1,9 +1,8 @@ - #[cfg(not(any(test, extra_checks)))] macro_rules! test_assert { - ($cond:expr) => ({ }); - ($cond:expr,) => ({ }); - ($cond:expr, $($arg:tt)+) => ({ }); + ($cond:expr) => {{}}; + ($cond:expr,) => {{}}; + ($cond:expr, $($arg:tt)+) => {{}}; } #[cfg(any(test, extra_checks))] diff --git a/tests/bezier/algorithms/fill_concave.rs b/tests/bezier/algorithms/fill_concave.rs index 40952dc1..73fe394d 100644 --- a/tests/bezier/algorithms/fill_concave.rs +++ b/tests/bezier/algorithms/fill_concave.rs @@ -1,114 +1,125 @@ -use flo_curves::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; -use flo_curves::bezier::path::algorithms::*; - -fn circle_ray_cast(circle_center: Coord2, radius: f64) -> impl Fn(Coord2, Coord2) -> Vec> { +use flo_curves::bezier::path::algorithms::{flood_fill_concave, FillSettings, RayCollision}; +use flo_curves::bezier::path::{BezierPath, SimpleBezierPath}; +use flo_curves::bezier::{ + BezierCurve, BoundingBox, Coord2, Coordinate, Coordinate2D, Coordinate3D, Curve, +}; + +fn circle_ray_cast( + circle_center: Coord2, + radius: f64, +) -> impl Fn(Coord2, Coord2) -> Vec> { move |from: Coord2, to: Coord2| { - let from = from - circle_center; - let to = to - circle_center; + let from = from - circle_center; + let to = to - circle_center; - let x1 = from.x(); - let y1 = from.y(); - let x2 = to.x(); - let y2 = to.y(); + let x1 = from.x(); + let y1 = from.y(); + let x2 = to.x(); + let y2 = to.y(); - let dx = x2-x1; - let dy = y2-y1; - let dr = (dx*dx + dy*dy).sqrt(); + let dx = x2 - x1; + let dy = y2 - y1; + let dr = (dx * dx + dy * dy).sqrt(); - let d = x1*y2 - x2*y1; + let d = x1 * y2 - x2 * y1; - let xc1 = (d*dy + (dy.signum()*dx*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let xc2 = (d*dy - (dy.signum()*dx*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let yc1 = (-d*dx + (dy.abs()*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let yc2 = (-d*dx - (dy.abs()*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); + let xc1 = (d * dy + (dy.signum() * dx * ((radius * radius * dr * dr - d * d).sqrt()))) + / (dr * dr); + let xc2 = (d * dy - (dy.signum() * dx * ((radius * radius * dr * dr - d * d).sqrt()))) + / (dr * dr); + let yc1 = (-d * dx + (dy.abs() * ((radius * radius * dr * dr - d * d).sqrt()))) / (dr * dr); + let yc2 = (-d * dx - (dy.abs() * ((radius * radius * dr * dr - d * d).sqrt()))) / (dr * dr); vec![ - RayCollision::new(Coord2(xc1, yc1)+circle_center, ()), RayCollision::new(Coord2(xc2, yc2)+circle_center, ()) + RayCollision::new(Coord2(xc1, yc1) + circle_center, ()), + RayCollision::new(Coord2(xc2, yc2) + circle_center, ()), ] } } #[test] fn ray_cast_to_circle_at_origin() { - let ray_cast = circle_ray_cast(Coord2(0.0, 0.0), 5.0); + let ray_cast = circle_ray_cast(Coord2(0.0, 0.0), 5.0); let from_center = ray_cast(Coord2(0.0, 0.0), Coord2(1.0, 1.0)); assert!(from_center.len() == 2); - assert!((from_center[0].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); - assert!((from_center[1].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); + assert!((from_center[0].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); + assert!((from_center[1].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); assert!(from_center[0].position.distance_to(&Coord2(3.54, 3.54)) < 0.1); assert!(from_center[1].position.distance_to(&Coord2(-3.54, -3.54)) < 0.1); let offset = ray_cast(Coord2(1.0, 1.0), Coord2(2.0, 2.0)); assert!(offset.len() == 2); - assert!((offset[0].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); - assert!((offset[1].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); + assert!((offset[0].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); + assert!((offset[1].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); assert!(offset[0].position.distance_to(&Coord2(3.54, 3.54)) < 0.1); assert!(offset[1].position.distance_to(&Coord2(-3.54, -3.54)) < 0.1); let offset2 = ray_cast(Coord2(1.0, 1.0), Coord2(1.0, 2.0)); assert!(offset2.len() == 2); - assert!((offset2[0].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); - assert!((offset2[1].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); + assert!((offset2[0].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); + assert!((offset2[1].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); let offset3 = ray_cast(Coord2(1.0, 1.0), Coord2(2.0, 1.0)); assert!(offset3.len() == 2); - assert!((offset3[0].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); - assert!((offset3[1].position.distance_to(&Coord2(0.0, 0.0))-5.0).abs() < 0.1); + assert!((offset3[0].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); + assert!((offset3[1].position.distance_to(&Coord2(0.0, 0.0)) - 5.0).abs() < 0.1); } #[test] fn ray_cast_to_circle() { - let ray_cast = circle_ray_cast(Coord2(10.0, 10.0), 5.0); + let ray_cast = circle_ray_cast(Coord2(10.0, 10.0), 5.0); let from_center = ray_cast(Coord2(10.0, 10.0), Coord2(11.0, 11.0)); assert!(from_center.len() == 2); - assert!((from_center[0].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); - assert!((from_center[1].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); + assert!((from_center[0].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); + assert!((from_center[1].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); assert!(from_center[0].position.distance_to(&Coord2(13.54, 13.54)) < 0.1); assert!(from_center[1].position.distance_to(&Coord2(6.46, 6.46)) < 0.1); let offset = ray_cast(Coord2(11.0, 11.0), Coord2(12.0, 12.0)); assert!(offset.len() == 2); - assert!((offset[0].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); - assert!((offset[1].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); + assert!((offset[0].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); + assert!((offset[1].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); assert!(offset[0].position.distance_to(&Coord2(13.54, 13.54)) < 0.1); assert!(offset[1].position.distance_to(&Coord2(6.46, 6.46)) < 0.1); let offset2 = ray_cast(Coord2(11.0, 11.0), Coord2(12.0, 11.0)); assert!(offset2.len() == 2); - assert!((offset2[0].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); - assert!((offset2[1].position.distance_to(&Coord2(10.0, 10.0))-5.0).abs() < 0.1); + assert!((offset2[0].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); + assert!((offset2[1].position.distance_to(&Coord2(10.0, 10.0)) - 5.0).abs() < 0.1); } #[test] fn fill_concave_circle() { // Simple circle ray-casting algorithm - let circle_center = Coord2(10.0, 10.0); - let radius = 50.0; + let circle_center = Coord2(10.0, 10.0); + let radius = 50.0; let circle_ray_cast = circle_ray_cast(circle_center, radius); // Flood-fill this curve - let path = flood_fill_concave::(circle_center, &FillSettings::default(), circle_ray_cast); + let path = flood_fill_concave::( + circle_center, + &FillSettings::default(), + circle_ray_cast, + ); assert!(path.is_some()); assert!(path.as_ref().unwrap().len() == 1); for curve in path.unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-radius).abs() < 1.0); + assert!((distance - radius).abs() < 1.0); } } } @@ -116,22 +127,26 @@ fn fill_concave_circle() { #[test] fn fill_concave_circle_offset() { // Simple circle ray-casting algorithm - let circle_center = Coord2(10.0, 10.0); - let radius = 50.0; + let circle_center = Coord2(10.0, 10.0); + let radius = 50.0; let circle_ray_cast = circle_ray_cast(circle_center, radius); // Flood-fill this curve - let path = flood_fill_concave::(circle_center + Coord2(1.0, 0.0), &FillSettings::default(), circle_ray_cast); + let path = flood_fill_concave::( + circle_center + Coord2(1.0, 0.0), + &FillSettings::default(), + circle_ray_cast, + ); assert!(path.is_some()); assert!(path.as_ref().unwrap().len() == 1); for curve in path.unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-radius).abs() < 1.0); + assert!((distance - radius).abs() < 1.0); } } } @@ -139,234 +154,250 @@ fn fill_concave_circle_offset() { #[test] fn fill_concave_doughnut() { // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled - let circle_center = Coord2(10.0, 10.0); - let outer_radius = 100.0; - let inner_radius = 50.0; - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let inner_circle = circle_ray_cast(circle_center, inner_radius); - let doughnut = |from: Coord2, to: Coord2| { - outer_circle(from.clone(), to.clone()).into_iter() + let circle_center = Coord2(10.0, 10.0); + let outer_radius = 100.0; + let inner_radius = 50.0; + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let inner_circle = circle_ray_cast(circle_center, inner_radius); + let doughnut = |from: Coord2, to: Coord2| { + outer_circle(from, to) + .into_iter() .chain(inner_circle(from, to)) }; // Flood-fill this curve - let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); - let path = flood_fill_concave::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); + let path = flood_fill_concave::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); - assert!(path.as_ref().unwrap().len() != 0); + assert!(!path.as_ref().unwrap().is_empty()); assert!(path.as_ref().unwrap().len() != 1); assert!(path.as_ref().unwrap().len() == 2); for curve in path.as_ref().unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-outer_radius).abs() < 2.0); + assert!((distance - outer_radius).abs() < 2.0); } } for curve in path.unwrap()[1].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-inner_radius).abs() < 2.0); + assert!((distance - inner_radius).abs() < 2.0); } } } #[test] fn fill_doughnut_with_extra_holes() { - // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled - let circle_center = Coord2(10.0, 10.0); - let outer_radius = 100.0; - let inner_radius = 50.0; - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let inner_circle = circle_ray_cast(circle_center, inner_radius); - let doughnut = |from: Coord2, to: Coord2| { - let inner_collisions = inner_circle(from.clone(), to.clone()); - let outer_collisions = outer_circle(from.clone(), to.clone()); - - let ray = to-from; - if (ray.x()/ray.y()).abs() < 0.1 || (ray.y()/ray.x()) < 0.1 { + // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled + let circle_center = Coord2(10.0, 10.0); + let outer_radius = 100.0; + let inner_radius = 50.0; + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let inner_circle = circle_ray_cast(circle_center, inner_radius); + let doughnut = |from: Coord2, to: Coord2| { + let inner_collisions = inner_circle(from, to); + let outer_collisions = outer_circle(from, to); + + let ray = to - from; + if (ray.x() / ray.y()).abs() < 0.1 || (ray.y() / ray.x()) < 0.1 { // Just the inner collisions (leave holes in the collision list) - inner_collisions.into_iter() - .chain(vec![]) + inner_collisions.into_iter().chain(vec![]) } else { // All the collisions - inner_collisions.into_iter() - .chain(outer_collisions) + inner_collisions.into_iter().chain(outer_collisions) } }; // Flood-fill this curve - let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); - let path = flood_fill_concave::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); + let path = flood_fill_concave::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); - assert!(path.as_ref().unwrap().len() != 0); + assert!(!path.as_ref().unwrap().is_empty()); assert!(path.as_ref().unwrap().len() != 1); assert!(path.as_ref().unwrap().len() == 2); for curve in path.as_ref().unwrap()[1].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-outer_radius).abs() < 5.0); + assert!((distance - outer_radius).abs() < 5.0); } } for curve in path.unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-inner_radius).abs() < 2.0); + assert!((distance - inner_radius).abs() < 2.0); } } } #[test] fn fill_circle_without_escaping_gaps() { - // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled - let circle_center = Coord2(10.0, 10.0); - let enclosing_radius = 200.0; - let outer_radius = 100.0; - let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let doughnut = |from: Coord2, to: Coord2| { - let enclosing_collisions = enclosing_circle(from.clone(), to.clone()); - let outer_collisions = outer_circle(from.clone(), to.clone()); - - let ray = to-from; - if (ray.x()/ray.y()).abs() < 0.01 { + // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled + let circle_center = Coord2(10.0, 10.0); + let enclosing_radius = 200.0; + let outer_radius = 100.0; + let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let doughnut = |from: Coord2, to: Coord2| { + let enclosing_collisions = enclosing_circle(from, to); + let outer_collisions = outer_circle(from, to); + + let ray = to - from; + if (ray.x() / ray.y()).abs() < 0.01 { // Just the inner collisions (leave holes in the collision list) - enclosing_collisions.into_iter() - .chain(vec![]) + enclosing_collisions.into_iter().chain(vec![]) } else { // All the collisions - enclosing_collisions.into_iter() - .chain(outer_collisions) + enclosing_collisions.into_iter().chain(outer_collisions) } }; // Flood-fill this curve - let start_point = circle_center; - let path = flood_fill_concave::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center; + let path = flood_fill_concave::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); - assert!(path.as_ref().unwrap().len() != 0); + assert!(!path.as_ref().unwrap().is_empty()); assert!(path.as_ref().unwrap().len() == 1); for curve in path.as_ref().unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-outer_radius).abs() < 5.0); + assert!((distance - outer_radius).abs() < 5.0); } } } - #[test] fn fill_circle_without_escaping_gaps_offset() { - // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled - let circle_center = Coord2(10.0, 10.0); - let enclosing_radius = 200.0; - let outer_radius = 100.0; - let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let doughnut = |from: Coord2, to: Coord2| { - let enclosing_collisions = enclosing_circle(from.clone(), to.clone()); - let outer_collisions = outer_circle(from.clone(), to.clone()); - - let ray = to-from; - if (ray.x()/ray.y()).abs() < 0.01 { + // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled + let circle_center = Coord2(10.0, 10.0); + let enclosing_radius = 200.0; + let outer_radius = 100.0; + let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let doughnut = |from: Coord2, to: Coord2| { + let enclosing_collisions = enclosing_circle(from, to); + let outer_collisions = outer_circle(from, to); + + let ray = to - from; + if (ray.x() / ray.y()).abs() < 0.01 { // Just the inner collisions (leave holes in the collision list) - enclosing_collisions.into_iter() - .chain(vec![]) + enclosing_collisions.into_iter().chain(vec![]) } else { // All the collisions - enclosing_collisions.into_iter() - .chain(outer_collisions) + enclosing_collisions.into_iter().chain(outer_collisions) } }; // Flood-fill this curve - let start_point = circle_center + Coord2(50.0, 70.0); - let path = flood_fill_concave::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(50.0, 70.0); + let path = flood_fill_concave::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); - assert!(path.as_ref().unwrap().len() != 0); + assert!(!path.as_ref().unwrap().is_empty()); assert!(path.as_ref().unwrap().len() == 1); for curve in path.as_ref().unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-outer_radius).abs() < 5.0); + assert!((distance - outer_radius).abs() < 5.0); } } } #[test] fn fill_doughnut_without_escaping_gaps() { - // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled - let circle_center = Coord2(10.0, 10.0); - let enclosing_radius = 200.0; - let outer_radius = 100.0; - let inner_radius = 50.0; - let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let inner_circle = circle_ray_cast(circle_center, inner_radius); - let doughnut = |from: Coord2, to: Coord2| { - let enclosing_collisions = enclosing_circle(from.clone(), to.clone()); - let inner_collisions = inner_circle(from.clone(), to.clone()); - let outer_collisions = outer_circle(from.clone(), to.clone()); - - let ray = to-from; - if (ray.x()/ray.y()).abs() < 0.01 { + // A 'doughnut' shape is one of the harder shapes to fill in this manner as eventually we'll have to raycast over areas we've already filled + let circle_center = Coord2(10.0, 10.0); + let enclosing_radius = 200.0; + let outer_radius = 100.0; + let inner_radius = 50.0; + let enclosing_circle = circle_ray_cast(circle_center, enclosing_radius); + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let inner_circle = circle_ray_cast(circle_center, inner_radius); + let doughnut = |from: Coord2, to: Coord2| { + let enclosing_collisions = enclosing_circle(from, to); + let inner_collisions = inner_circle(from, to); + let outer_collisions = outer_circle(from, to); + + let ray = to - from; + if (ray.x() / ray.y()).abs() < 0.01 { // Just the inner collisions (leave holes in the collision list) - inner_collisions.into_iter() + inner_collisions + .into_iter() .chain(enclosing_collisions) .chain(vec![]) } else { // All the collisions - inner_collisions.into_iter() + inner_collisions + .into_iter() .chain(enclosing_collisions) .chain(outer_collisions) } }; // Flood-fill this curve - let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); - let path = flood_fill_concave::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); + let path = flood_fill_concave::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); - assert!(path.as_ref().unwrap().len() != 0); + assert!(!path.as_ref().unwrap().is_empty()); assert!(path.as_ref().unwrap().len() != 1); assert!(path.as_ref().unwrap().len() == 2); for curve in path.as_ref().unwrap()[0].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-outer_radius).abs() < 5.0); + assert!((distance - outer_radius).abs() < 5.0); } } for curve in path.unwrap()[1].to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-inner_radius).abs() < 2.0); + assert!((distance - inner_radius).abs() < 2.0); } } } diff --git a/tests/bezier/algorithms/fill_convex.rs b/tests/bezier/algorithms/fill_convex.rs index 190d002c..1ef02808 100644 --- a/tests/bezier/algorithms/fill_convex.rs +++ b/tests/bezier/algorithms/fill_convex.rs @@ -1,31 +1,40 @@ -use flo_curves::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; -use flo_curves::bezier::path::algorithms::*; - -fn circle_ray_cast(circle_center: Coord2, radius: f64) -> impl Fn(Coord2, Coord2) -> Vec> { +use flo_curves::bezier::path::algorithms::{ + flood_fill_convex, trace_outline_convex, FillSettings, RayCollision, +}; +use flo_curves::bezier::path::{BezierPath, SimpleBezierPath}; +use flo_curves::bezier::{ + BezierCurve, BoundingBox, Coord2, Coordinate, Coordinate2D, Coordinate3D, Curve, +}; + +fn circle_ray_cast( + circle_center: Coord2, + radius: f64, +) -> impl Fn(Coord2, Coord2) -> Vec> { move |from: Coord2, to: Coord2| { - let from = from - circle_center; - let to = to - circle_center; + let from = from - circle_center; + let to = to - circle_center; - let x1 = from.x(); - let y1 = from.y(); - let x2 = to.x(); - let y2 = to.y(); + let x1 = from.x(); + let y1 = from.y(); + let x2 = to.x(); + let y2 = to.y(); - let dx = x2-x1; - let dy = y2-y1; - let dr = (dx*dx + dy*dy).sqrt(); + let dx = x2 - x1; + let dy = y2 - y1; + let dr = (dx * dx + dy * dy).sqrt(); - let d = x1*y2 - x2*y1; + let d = x1 * y2 - x2 * y1; - let xc1 = (d*dy + (dy.signum()*dx*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let xc2 = (d*dy - (dy.signum()*dx*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let yc1 = (-d*dx + (dy.abs()*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); - let yc2 = (-d*dx - (dy.abs()*((radius*radius*dr*dr - d*d).sqrt())))/(dr*dr); + let xc1 = (d * dy + (dy.signum() * dx * ((radius * radius * dr * dr - d * d).sqrt()))) + / (dr * dr); + let xc2 = (d * dy - (dy.signum() * dx * ((radius * radius * dr * dr - d * d).sqrt()))) + / (dr * dr); + let yc1 = (-d * dx + (dy.abs() * ((radius * radius * dr * dr - d * d).sqrt()))) / (dr * dr); + let yc2 = (-d * dx - (dy.abs() * ((radius * radius * dr * dr - d * d).sqrt()))) / (dr * dr); vec![ - RayCollision::new(Coord2(xc1, yc1)+circle_center, ()), RayCollision::new(Coord2(xc2, yc2)+circle_center, ()) + RayCollision::new(Coord2(xc1, yc1) + circle_center, ()), + RayCollision::new(Coord2(xc2, yc2) + circle_center, ()), ] } } @@ -33,23 +42,27 @@ fn circle_ray_cast(circle_center: Coord2, radius: f64) -> impl Fn(Coord2, Coord2 #[test] fn trace_convex_circle() { // Simple circle ray-casting algorithm - let circle_center = Coord2(10.0, 10.0); - let radius = 100.0; + let circle_center = Coord2(10.0, 10.0); + let radius = 100.0; let circle_ray_cast = circle_ray_cast(circle_center, radius); // Trace the outline let outline = trace_outline_convex(circle_center, &FillSettings::default(), circle_ray_cast); // Should be at least one point - assert!(outline.len() > 0); + assert!(!outline.is_empty()); // Points should be no more that 4.0 pixels apart and should be the correct distance from the circle for point_idx in 0..outline.len() { - let next_point_idx = if point_idx+1 >= outline.len() { 0 } else { point_idx+1 }; - let point = &outline[point_idx]; - let next_point = &outline[next_point_idx]; - - assert!((point.position.distance_to(&circle_center)-radius).abs() < 1.0); + let next_point_idx = if point_idx + 1 >= outline.len() { + 0 + } else { + point_idx + 1 + }; + let point = &outline[point_idx]; + let next_point = &outline[next_point_idx]; + + assert!((point.position.distance_to(&circle_center) - radius).abs() < 1.0); assert!(point.position.distance_to(&next_point.position) <= 4.0); } @@ -59,21 +72,22 @@ fn trace_convex_circle() { #[test] fn fill_convex_circle() { // Simple circle ray-casting algorithm - let circle_center = Coord2(10.0, 10.0); - let radius = 100.0; + let circle_center = Coord2(10.0, 10.0); + let radius = 100.0; let circle_ray_cast = circle_ray_cast(circle_center, radius); // Flood-fill this curve - let path: Option = flood_fill_convex(circle_center, &FillSettings::default(), circle_ray_cast); + let path: Option = + flood_fill_convex(circle_center, &FillSettings::default(), circle_ray_cast); assert!(path.is_some()); for curve in path.unwrap().to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); - assert!((distance-radius).abs() < 1.0); + assert!((distance - radius).abs() < 1.0); } } } @@ -81,28 +95,31 @@ fn fill_convex_circle() { #[test] fn trace_convex_doughnut() { // With a convex fill, a 'doughnut' shape will only fill those points that are immediately reachable from the origin point - let circle_center = Coord2(10.0, 10.0); - let outer_radius = 100.0; - let inner_radius = 50.0; - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let inner_circle = circle_ray_cast(circle_center, inner_radius); - let doughnut = |from: Coord2, to: Coord2| { - outer_circle(from.clone(), to.clone()).into_iter() + let circle_center = Coord2(10.0, 10.0); + let outer_radius = 100.0; + let inner_radius = 50.0; + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let inner_circle = circle_ray_cast(circle_center, inner_radius); + let doughnut = |from: Coord2, to: Coord2| { + outer_circle(from, to) + .into_iter() .chain(inner_circle(from, to)) }; // Trace the outline - let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); - let outline = trace_outline_convex(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); + let outline = trace_outline_convex(start_point, &FillSettings::default(), doughnut); // Should be at least one point - assert!(outline.len() > 0); + assert!(!outline.is_empty()); for point_idx in 0..outline.len() { - let point = &outline[point_idx]; + let point = &outline[point_idx]; - assert!((point.position.distance_to(&circle_center)-outer_radius).abs() < 1.0 - || (point.position.distance_to(&circle_center)-inner_radius).abs() < 1.0); + assert!( + (point.position.distance_to(&circle_center) - outer_radius).abs() < 1.0 + || (point.position.distance_to(&circle_center) - inner_radius).abs() < 1.0 + ); } assert!(outline.len() > 8); @@ -111,26 +128,31 @@ fn trace_convex_doughnut() { #[test] fn fill_convex_doughnut() { // With a convex fill, a 'doughnut' shape will only fill those points that are immediately reachable from the origin point - let circle_center = Coord2(10.0, 10.0); - let outer_radius = 100.0; - let inner_radius = 50.0; - let outer_circle = circle_ray_cast(circle_center, outer_radius); - let inner_circle = circle_ray_cast(circle_center, inner_radius); - let doughnut = |from: Coord2, to: Coord2| { - outer_circle(from.clone(), to.clone()).into_iter() + let circle_center = Coord2(10.0, 10.0); + let outer_radius = 100.0; + let inner_radius = 50.0; + let outer_circle = circle_ray_cast(circle_center, outer_radius); + let inner_circle = circle_ray_cast(circle_center, inner_radius); + let doughnut = |from: Coord2, to: Coord2| { + outer_circle(from, to) + .into_iter() .chain(inner_circle(from, to)) }; // Flood-fill this curve - let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); - let path = flood_fill_convex::(start_point, &FillSettings::default(), doughnut); + let start_point = circle_center + Coord2(inner_radius + 10.0, 0.0); + let path = flood_fill_convex::( + start_point, + &FillSettings::default(), + doughnut, + ); assert!(path.is_some()); for curve in path.as_ref().unwrap().to_curves::>() { for t in 0..100 { - let t = (t as f64)/100.0; - let distance = circle_center.distance_to(&curve.point_at_pos(t)); + let t = (t as f64) / 100.0; + let distance = circle_center.distance_to(&curve.point_at_pos(t)); assert!(distance >= inner_radius - 2.0 && distance <= outer_radius + 2.0) } diff --git a/tests/bezier/algorithms/mod.rs b/tests/bezier/algorithms/mod.rs index 00e29f4e..a9a1a994 100644 --- a/tests/bezier/algorithms/mod.rs +++ b/tests/bezier/algorithms/mod.rs @@ -1,2 +1,2 @@ -mod fill_convex; mod fill_concave; +mod fill_convex; diff --git a/tests/bezier/basis.rs b/tests/bezier/basis.rs index a6fc4dad..d68a17fa 100644 --- a/tests/bezier/basis.rs +++ b/tests/bezier/basis.rs @@ -1,4 +1,4 @@ -use super::*; +use super::approx_equal; use flo_curves::bezier; #[test] @@ -14,10 +14,10 @@ fn basis_at_t1_is_w4() { #[test] fn basis_agrees_with_de_casteljau() { for x in 0..100 { - let t = (x as f64)/100.0; + let t = (x as f64) / 100.0; - let basis = bezier::basis(t, 2.0, 3.0, 4.0, 5.0); - let de_casteljau = bezier::de_casteljau4(t, 2.0, 3.0, 4.0, 5.0); + let basis = bezier::basis(t, 2.0, 3.0, 4.0, 5.0); + let de_casteljau = bezier::de_casteljau4(t, 2.0, 3.0, 4.0, 5.0); assert!(approx_equal(basis, de_casteljau)); } diff --git a/tests/bezier/bounds.rs b/tests/bezier/bounds.rs index b0fe91cd..53639f7b 100644 --- a/tests/bezier/bounds.rs +++ b/tests/bezier/bounds.rs @@ -1,11 +1,15 @@ -use flo_curves::bezier::{BezierCurve, BezierCurveFactory}; use flo_curves::bezier; +use flo_curves::bezier::{BezierCurve, BezierCurveFactory}; use flo_curves::geo::Coord2; use flo_curves::geo::Coordinate; #[test] fn get_straight_line_bounds() { - let straight_line = bezier::Curve::from_points(Coord2(0.0, 1.0), (Coord2(0.5, 1.5), Coord2(1.5, 2.5)), Coord2(2.0, 3.0)); + let straight_line = bezier::Curve::from_points( + Coord2(0.0, 1.0), + (Coord2(0.5, 1.5), Coord2(1.5, 2.5)), + Coord2(2.0, 3.0), + ); let bounds: (Coord2, Coord2) = straight_line.bounding_box(); @@ -14,7 +18,11 @@ fn get_straight_line_bounds() { #[test] fn get_curved_line_bounds() { - let curved_line = bezier::Curve::from_points(Coord2(0.0, 1.0), (Coord2(-1.1875291, 1.5), Coord2(1.5, 2.5)), Coord2(2.0, 3.0)); + let curved_line = bezier::Curve::from_points( + Coord2(0.0, 1.0), + (Coord2(-1.1875291, 1.5), Coord2(1.5, 2.5)), + Coord2(2.0, 3.0), + ); let bounds: (Coord2, Coord2) = curved_line.bounding_box(); diff --git a/tests/bezier/characteristics.rs b/tests/bezier/characteristics.rs index afea983c..b40ffe83 100644 --- a/tests/bezier/characteristics.rs +++ b/tests/bezier/characteristics.rs @@ -1,27 +1,48 @@ -use flo_curves::*; -use flo_curves::bezier::*; +use flo_curves::bezier::{ + characterize_curve, features_for_curve, BezierCurve, BezierCurve2D, BezierCurveFactory, Coord2, + Coordinate, Curve, CurveFeatures, +}; +use flo_curves::{bezier, Line}; #[test] fn detect_loop_1() { - let curve = Curve::from_points(Coord2(110.0, 150.0), (Coord2(287.0, 227.0), Coord2(70.0, 205.0)), Coord2(205.0, 159.0)); + let curve = Curve::from_points( + Coord2(110.0, 150.0), + (Coord2(287.0, 227.0), Coord2(70.0, 205.0)), + Coord2(205.0, 159.0), + ); assert!(curve.characteristics() == bezier::CurveCategory::Loop); } #[test] fn detect_loop_2() { - let curve = Curve::from_points(Coord2(549.2899780273438, 889.4202270507813), (Coord2(553.4288330078125, 893.8638305664063), Coord2(542.5203247070313, 889.04931640625)), Coord2(548.051025390625, 891.1853637695313)); + let curve = Curve::from_points( + Coord2(549.2899780273438, 889.4202270507813), + ( + Coord2(553.4288330078125, 893.8638305664063), + Coord2(542.5203247070313, 889.04931640625), + ), + Coord2(548.051025390625, 891.1853637695313), + ); assert!(characterize_curve(&curve) == bezier::CurveCategory::Loop); } #[test] fn detect_loop_2_features() { - let curve = Curve::from_points(Coord2(549.2899780273438, 889.4202270507813), (Coord2(553.4288330078125, 893.8638305664063), Coord2(542.5203247070313, 889.04931640625)), Coord2(548.051025390625, 891.1853637695313)); + let curve = Curve::from_points( + Coord2(549.2899780273438, 889.4202270507813), + ( + Coord2(553.4288330078125, 893.8638305664063), + Coord2(542.5203247070313, 889.04931640625), + ), + Coord2(548.051025390625, 891.1853637695313), + ); match features_for_curve(&curve, 0.01) { - CurveFeatures::Loop(t1, t2) => { + CurveFeatures::Loop(t1, t2) => { let (p1, p2) = (curve.point_at_pos(t1), curve.point_at_pos(t2)); assert!(p1.is_near_to(&p2, 0.01)); - }, - _ => assert!(false) + } + _ => assert!(false), } } diff --git a/tests/bezier/curve_intersection_clip.rs b/tests/bezier/curve_intersection_clip.rs index c3511333..d9017160 100644 --- a/tests/bezier/curve_intersection_clip.rs +++ b/tests/bezier/curve_intersection_clip.rs @@ -1,16 +1,25 @@ -use flo_curves::*; -use flo_curves::line; use flo_curves::bezier; +use flo_curves::line; +use flo_curves::{BezierCurve, BezierCurveFactory, BoundingBox, Coord2, Coordinate, Line}; #[test] fn find_intersection_on_straight_line_not_middle() { // Cross that intersects at (5.0, 5.0) - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 0.0), Coord2(13.0, 13.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(9.0, 1.0), Coord2(0.0, 10.0))); - - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - println!("{:?} {:?}", intersections, intersections.iter().map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))).collect::>()); - assert!(intersections.len() != 0); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 0.0), Coord2(13.0, 13.0))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(9.0, 1.0), Coord2(0.0, 10.0))); + + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + println!( + "{:?} {:?}", + intersections, + intersections + .iter() + .map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))) + .collect::>() + ); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersect_point.distance_to(&Coord2(5.0, 5.0)) < 0.1); @@ -24,12 +33,21 @@ fn find_intersection_on_straight_line_not_middle() { #[test] fn find_intersection_on_straight_line_middle() { // Cross that intersects at (5.0, 5.0) - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 0.0), Coord2(10.0, 10.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - println!("{:?} {:?}", intersections, intersections.iter().map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))).collect::>()); - assert!(intersections.len() != 0); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 0.0), Coord2(10.0, 10.0))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + println!( + "{:?} {:?}", + intersections, + intersections + .iter() + .map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))) + .collect::>() + ); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersect_point.distance_to(&Coord2(5.0, 5.0)) < 0.1); @@ -43,11 +61,13 @@ fn find_intersection_on_straight_line_middle() { #[test] fn find_intersection_on_straight_line_start() { // Intersection at the start of two curves - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(10.0, 10.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(0.0, 10.0))); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(10.0, 10.0))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersections[0].0 < 0.01); @@ -63,11 +83,13 @@ fn find_intersection_on_straight_line_start() { #[test] fn find_intersection_on_straight_line_end_1() { // Intersection at the start of two curves - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(5.0, 5.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 10.0), Coord2(5.0, 5.0))); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(5.0, 5.0))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 10.0), Coord2(5.0, 5.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersections[0].0 > 0.99); @@ -83,11 +105,13 @@ fn find_intersection_on_straight_line_end_1() { #[test] fn find_intersection_on_straight_line_end_to_start_1() { // Intersection at the start of two curves - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(5.0, 5.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(0.0, 10.0))); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(5.0, 5.0))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersections[0].0 > 0.99); @@ -103,11 +127,11 @@ fn find_intersection_on_straight_line_end_to_start_1() { #[test] fn find_intersection_on_line_end_to_end_2() { // Intersection that should be found in self_collide_removes_shared_point_2 in the graph_path tests - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(3.0, 3.0))); + let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 5.0), Coord2(3.0, 3.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersect_point.distance_to(&Coord2(3.0, 3.0)) < 0.1); @@ -123,11 +147,11 @@ fn find_intersection_on_line_end_to_end_3() { // TODO: this fails at the moment (the issue is that as the ray is collinear we get the wrong t values for the intersection point) // Intersection that should be found in self_collide_removes_shared_point_1 in the graph_path tests - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 1.0), Coord2(3.0, 3.0))); + let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 1.0), Coord2(3.0, 3.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersect_point.distance_to(&Coord2(3.0, 3.0)) < 0.1); @@ -140,7 +164,7 @@ fn find_intersection_on_line_end_to_end_3() { #[test] fn solve_for_end_1() { - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); + let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); let end_pos = bezier::solve_curve_for_t(&curve1, &Coord2(3.0, 3.0)); assert!(end_pos.is_some()); @@ -149,7 +173,7 @@ fn solve_for_end_1() { #[test] fn solve_for_end_2() { - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 1.0), Coord2(3.0, 3.0))); + let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(5.0, 1.0), Coord2(3.0, 3.0))); let end_pos = bezier::solve_curve_for_t(&curve1, &Coord2(3.0, 3.0)); assert!(end_pos.is_some()); @@ -159,11 +183,11 @@ fn solve_for_end_2() { #[test] fn find_intersection_on_line_end_to_start_2() { // Reverse of the intersection that should be found in self_collide_removes_shared_point_2 in the graph_path tests - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(3.0, 3.0), Coord2(5.0, 5.0))); + let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(1.0, 5.0), Coord2(3.0, 3.0))); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(3.0, 3.0), Coord2(5.0, 5.0))); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + assert!(!intersections.is_empty()); let intersect_point = curve1.point_at_pos(intersections[0].0); assert!(intersect_point.distance_to(&Coord2(3.0, 3.0)) < 0.1); @@ -177,13 +201,22 @@ fn find_intersection_on_line_end_to_start_2() { #[test] fn find_intersection_on_straight_line_near_end() { // Intersection at the start of two curves - let curve1 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(4.9, 5.1))); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 10.0), Coord2(5.1, 4.9))); - - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); - println!("{:?} {:?}", intersections, intersections.iter().map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))).collect::>()); + let curve1 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 10.0), Coord2(4.9, 5.1))); + let curve2 = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(0.0, 10.0), Coord2(5.1, 4.9))); - assert!(intersections.len() != 0); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + println!( + "{:?} {:?}", + intersections, + intersections + .iter() + .map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))) + .collect::>() + ); + + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } @@ -198,11 +231,26 @@ fn find_intersections_on_curve() { // Coord2(133.16, 167.13) // Coord2(179.87, 199.67) // - let curve1 = bezier::Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); - let curve2 = bezier::Curve::from_points(Coord2(5.0, 150.0), (Coord2(180.0, 20.0), Coord2(80.0, 250.0)), Coord2(210.0, 190.0)); - - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - println!("{:?} {:?}", intersections, intersections.iter().map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))).collect::>()); + let curve1 = bezier::Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); + let curve2 = bezier::Curve::from_points( + Coord2(5.0, 150.0), + (Coord2(180.0, 20.0), Coord2(80.0, 250.0)), + Coord2(210.0, 190.0), + ); + + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + println!( + "{:?} {:?}", + intersections, + intersections + .iter() + .map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))) + .collect::>() + ); // All intersections should be approximately the same location for intersect in intersections.iter() { @@ -219,10 +267,18 @@ fn find_intersections_on_curve() { #[test] fn intersections_with_overlapping_curves_1() { - let curve1 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); - let curve2 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); + let curve1 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); + let curve2 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); @@ -231,11 +287,19 @@ fn intersections_with_overlapping_curves_1() { #[test] fn intersections_with_overlapping_curves_2() { - let curve1 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); - let curve2 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); + let curve1 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); + let curve2 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); let curve2 = bezier::Curve::from_curve(&curve2.section(0.2, 0.6)); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); @@ -244,11 +308,19 @@ fn intersections_with_overlapping_curves_2() { #[test] fn intersections_with_overlapping_curves_3() { - let curve1 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); - let curve2 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); + let curve1 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); + let curve2 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); let curve1 = bezier::Curve::from_curve(&curve1.section(0.2, 0.6)); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); @@ -257,10 +329,18 @@ fn intersections_with_overlapping_curves_3() { #[test] fn intersections_with_nearby_curves_1() { - let curve1 = bezier::Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); - let curve2 = bezier::Curve::from_points(Coord2(350.22574, 706.551), (Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), Coord2(361.0284, 690.2511)); + let curve1 = bezier::Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); + let curve2 = bezier::Curve::from_points( + Coord2(350.22574, 706.551), + (Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), + Coord2(361.0284, 690.2511), + ); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); @@ -269,10 +349,24 @@ fn intersections_with_nearby_curves_1() { #[test] fn intersections_with_nearby_curves_2() { - let curve1 = bezier::Curve::from_points(Coord2(305.86907958984375, 882.2529296875), (Coord2(305.41015625, 880.7345581054688), Coord2(303.0707092285156, 879.744140625)), Coord2(298.0640869140625, 875.537353515625)); - let curve2 = bezier::Curve::from_points(Coord2(302.7962341308594, 879.1681518554688), (Coord2(299.5769348144531, 876.8582763671875), Coord2(297.1976318359375, 874.7939453125)), Coord2(301.4282531738281, 878.26220703125)); + let curve1 = bezier::Curve::from_points( + Coord2(305.86907958984375, 882.2529296875), + ( + Coord2(305.41015625, 880.7345581054688), + Coord2(303.0707092285156, 879.744140625), + ), + Coord2(298.0640869140625, 875.537353515625), + ); + let curve2 = bezier::Curve::from_points( + Coord2(302.7962341308594, 879.1681518554688), + ( + Coord2(299.5769348144531, 876.8582763671875), + Coord2(297.1976318359375, 874.7939453125), + ), + Coord2(301.4282531738281, 878.26220703125), + ); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); assert!(intersections.len() <= 9); @@ -280,10 +374,24 @@ fn intersections_with_nearby_curves_2() { #[test] fn intersections_with_nearby_curves_3() { - let curve1 = bezier::Curve::from_points(Coord2(304.6919250488281, 880.6288452148438), (Coord2(304.2330017089844, 879.1104736328125), Coord2(301.8935546875, 878.1200561523438)), Coord2(296.8869323730469, 873.9132690429688)); - let curve2 = bezier::Curve::from_points(Coord2(301.61907958984375, 877.5440673828125), (Coord2(300.2510986328125, 876.6381225585938), Coord2(298.3997802734375, 875.2341918945313)), Coord2(296.0204772949219, 873.1698608398438)); + let curve1 = bezier::Curve::from_points( + Coord2(304.6919250488281, 880.6288452148438), + ( + Coord2(304.2330017089844, 879.1104736328125), + Coord2(301.8935546875, 878.1200561523438), + ), + Coord2(296.8869323730469, 873.9132690429688), + ); + let curve2 = bezier::Curve::from_points( + Coord2(301.61907958984375, 877.5440673828125), + ( + Coord2(300.2510986328125, 876.6381225585938), + Coord2(298.3997802734375, 875.2341918945313), + ), + Coord2(296.0204772949219, 873.1698608398438), + ); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); // assert!(intersections.len() <= 9); @@ -291,10 +399,24 @@ fn intersections_with_nearby_curves_3() { #[test] fn intersections_with_nearby_curves_4() { - let curve1 = bezier::Curve::from_points(Coord2(436.15716552734375, 869.3236083984375), (Coord2(444.5263671875, 869.2921752929688), Coord2(480.9628601074219, 854.3709106445313)), Coord2(490.6786804199219, 849.5614624023438)); - let curve2 = bezier::Curve::from_points(Coord2(462.5539855957031, 861.322021484375), (Coord2(462.4580078125, 861.4293823242188), Coord2(462.3710021972656, 861.5908813476563)), Coord2(462.3448486328125, 861.8137817382813)); + let curve1 = bezier::Curve::from_points( + Coord2(436.15716552734375, 869.3236083984375), + ( + Coord2(444.5263671875, 869.2921752929688), + Coord2(480.9628601074219, 854.3709106445313), + ), + Coord2(490.6786804199219, 849.5614624023438), + ); + let curve2 = bezier::Curve::from_points( + Coord2(462.5539855957031, 861.322021484375), + ( + Coord2(462.4580078125, 861.4293823242188), + Coord2(462.3710021972656, 861.5908813476563), + ), + Coord2(462.3448486328125, 861.8137817382813), + ); - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); assert!(intersections.len() <= 9); @@ -302,123 +424,277 @@ fn intersections_with_nearby_curves_4() { #[test] fn intersection_curve_1() { - let curve1 = bezier::Curve::from_points(Coord2(252.08901977539063, 676.4180908203125), (Coord2(244.0195770263672, 679.6658935546875), Coord2(244.11508178710938, 682.8816528320313)), Coord2(244.31190490722656, 686.1041259765625)); - let curve2 = bezier::Curve::from_points(Coord2(244.31190490722656, 686.1041259765625), (Coord2(250.65411376953125, 661.4817504882813), Coord2(255.51109313964844, 635.5418701171875)), Coord2(265.2398376464844, 618.4223022460938)); + let curve1 = bezier::Curve::from_points( + Coord2(252.08901977539063, 676.4180908203125), + ( + Coord2(244.0195770263672, 679.6658935546875), + Coord2(244.11508178710938, 682.8816528320313), + ), + Coord2(244.31190490722656, 686.1041259765625), + ); + let curve2 = bezier::Curve::from_points( + Coord2(244.31190490722656, 686.1041259765625), + ( + Coord2(250.65411376953125, 661.4817504882813), + Coord2(255.51109313964844, 635.5418701171875), + ), + Coord2(265.2398376464844, 618.4223022460938), + ); let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() != 1); assert!(intersections.len() == 2); - assert!(curve1.point_at_pos(intersections[0].0).distance_to(&curve2.point_at_pos(intersections[0].1)) < 0.01); - assert!(curve1.point_at_pos(intersections[1].0).distance_to(&curve2.point_at_pos(intersections[1].1)) < 0.01); + assert!( + curve1 + .point_at_pos(intersections[0].0) + .distance_to(&curve2.point_at_pos(intersections[0].1)) + < 0.01 + ); + assert!( + curve1 + .point_at_pos(intersections[1].0) + .distance_to(&curve2.point_at_pos(intersections[1].1)) + < 0.01 + ); let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 2); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&curve1.point_at_pos(intersections[0].1)) < 0.01); - assert!(curve2.point_at_pos(intersections[1].0).distance_to(&curve1.point_at_pos(intersections[1].1)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&curve1.point_at_pos(intersections[0].1)) + < 0.01 + ); + assert!( + curve2 + .point_at_pos(intersections[1].0) + .distance_to(&curve1.point_at_pos(intersections[1].1)) + < 0.01 + ); } #[test] fn intersection_curve_2() { - let curve1 = bezier::Curve::from_points(Coord2(248.42221069335938, 678.5138549804688), (Coord2(240.33773803710938, 703.49462890625), Coord2(246.20928955078125, 728.5226440429688)), Coord2(258.2634582519531, 745.7745361328125)); - let curve2 = bezier::Curve::from_points(Coord2(240.6450958251953, 688.1998901367188), (Coord2(248.51101684570313, 684.6644897460938), Coord2(248.41787719726563, 681.5728759765625)), Coord2(248.42221069335938, 678.5138549804688)); + let curve1 = bezier::Curve::from_points( + Coord2(248.42221069335938, 678.5138549804688), + ( + Coord2(240.33773803710938, 703.49462890625), + Coord2(246.20928955078125, 728.5226440429688), + ), + Coord2(258.2634582519531, 745.7745361328125), + ); + let curve2 = bezier::Curve::from_points( + Coord2(240.6450958251953, 688.1998901367188), + ( + Coord2(248.51101684570313, 684.6644897460938), + Coord2(248.41787719726563, 681.5728759765625), + ), + Coord2(248.42221069335938, 678.5138549804688), + ); let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() != 1); assert!(intersections.len() == 2); - assert!(curve1.point_at_pos(intersections[0].0).distance_to(&curve2.point_at_pos(intersections[0].1)) < 0.01); - assert!(curve1.point_at_pos(intersections[1].0).distance_to(&curve2.point_at_pos(intersections[1].1)) < 0.01); + assert!( + curve1 + .point_at_pos(intersections[0].0) + .distance_to(&curve2.point_at_pos(intersections[0].1)) + < 0.01 + ); + assert!( + curve1 + .point_at_pos(intersections[1].0) + .distance_to(&curve2.point_at_pos(intersections[1].1)) + < 0.01 + ); let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 2); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&curve1.point_at_pos(intersections[0].1)) < 0.01); - assert!(curve2.point_at_pos(intersections[1].0).distance_to(&curve1.point_at_pos(intersections[1].1)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&curve1.point_at_pos(intersections[0].1)) + < 0.01 + ); + assert!( + curve2 + .point_at_pos(intersections[1].0) + .distance_to(&curve1.point_at_pos(intersections[1].1)) + < 0.01 + ); } #[test] fn intersection_curve_3() { - let curve1 = bezier::Curve::from_points(Coord2(377.8294677734375, 495.076904296875), (Coord2(380.0453796386719, 492.69927978515625), Coord2(381.98138427734375, 489.805419921875)), Coord2(383.61865234375, 486.40106201171875)); - let curve2 = bezier::Curve::from_points(Coord2(379.064697265625, 493.7556457519531), (Coord2(371.90069580078125, 491.9415588378906), Coord2(368.96783447265625, 493.451171875)), Coord2(366.3587951660156, 494.5915832519531)); + let curve1 = bezier::Curve::from_points( + Coord2(377.8294677734375, 495.076904296875), + ( + Coord2(380.0453796386719, 492.69927978515625), + Coord2(381.98138427734375, 489.805419921875), + ), + Coord2(383.61865234375, 486.40106201171875), + ); + let curve2 = bezier::Curve::from_points( + Coord2(379.064697265625, 493.7556457519531), + ( + Coord2(371.90069580078125, 491.9415588378906), + Coord2(368.96783447265625, 493.451171875), + ), + Coord2(366.3587951660156, 494.5915832519531), + ); let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve1.point_at_pos(intersections[0].0).distance_to(&curve2.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve1 + .point_at_pos(intersections[0].0) + .distance_to(&curve2.point_at_pos(intersections[0].1)) + < 0.01 + ); let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&curve1.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&curve1.point_at_pos(intersections[0].1)) + < 0.01 + ); } #[test] fn intersection_curve_4() { - let curve1 = bezier::Curve::from_points(Coord2(377.8294677734375, 495.076904296875), (Coord2(380.0453796386719, 492.69927978515625), Coord2(381.98138427734375, 489.805419921875)), Coord2(383.61865234375, 486.40106201171875)); - let curve2 = bezier::Curve::from_points(Coord2(379.064697265625, 493.7556457519531), (Coord2(371.3619079589844, 493.8326110839844), Coord2(366.50872802734375, 495.2229919433594)), Coord2(362.0657958984375, 496.14581298828125)); + let curve1 = bezier::Curve::from_points( + Coord2(377.8294677734375, 495.076904296875), + ( + Coord2(380.0453796386719, 492.69927978515625), + Coord2(381.98138427734375, 489.805419921875), + ), + Coord2(383.61865234375, 486.40106201171875), + ); + let curve2 = bezier::Curve::from_points( + Coord2(379.064697265625, 493.7556457519531), + ( + Coord2(371.3619079589844, 493.8326110839844), + Coord2(366.50872802734375, 495.2229919433594), + ), + Coord2(362.0657958984375, 496.14581298828125), + ); let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve1.point_at_pos(intersections[0].0).distance_to(&curve2.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve1 + .point_at_pos(intersections[0].0) + .distance_to(&curve2.point_at_pos(intersections[0].1)) + < 0.01 + ); let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&curve1.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&curve1.point_at_pos(intersections[0].1)) + < 0.01 + ); } #[test] fn intersection_curve_5() { - let curve1 = bezier::Curve::from_points(Coord2(379.064697265625, 493.7556457519531), (Coord2(371.90069580078125, 491.9415588378906), Coord2(368.96783447265625, 493.451171875)), Coord2(366.3587951660156, 494.5915832519531)); - let curve2 = bezier::Curve::from_points(Coord2(379.064697265625, 493.7556457519531), (Coord2(371.3619079589844, 493.8326110839844), Coord2(366.50872802734375, 495.2229919433594)), Coord2(362.0657958984375, 496.14581298828125)); - + let curve1 = bezier::Curve::from_points( + Coord2(379.064697265625, 493.7556457519531), + ( + Coord2(371.90069580078125, 491.9415588378906), + Coord2(368.96783447265625, 493.451171875), + ), + Coord2(366.3587951660156, 494.5915832519531), + ); + let curve2 = bezier::Curve::from_points( + Coord2(379.064697265625, 493.7556457519531), + ( + Coord2(371.3619079589844, 493.8326110839844), + Coord2(366.50872802734375, 495.2229919433594), + ), + Coord2(362.0657958984375, 496.14581298828125), + ); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve1.point_at_pos(intersections[0].0).distance_to(&curve2.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve1 + .point_at_pos(intersections[0].0) + .distance_to(&curve2.point_at_pos(intersections[0].1)) + < 0.01 + ); let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&curve1.point_at_pos(intersections[0].1)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&curve1.point_at_pos(intersections[0].1)) + < 0.01 + ); } #[test] fn intersection_curve_6() { - let curve1 = bezier::Curve::from_points(Coord2(608.7642211914063, 855.5934448242188), (Coord2(608.6810302734375, 855.288330078125), Coord2(608.5828857421875, 855.0850830078125)), Coord2(608.47265625, 855.011962890625)); - let curve2 = bezier::Curve::from_points(Coord2(608.81689453125, 855.5904541015625), (Coord2(608.7009887695313, 855.386474609375), Coord2(608.5858154296875, 855.193115234375)), Coord2(608.47265625, 855.011962890625)); - + let curve1 = bezier::Curve::from_points( + Coord2(608.7642211914063, 855.5934448242188), + ( + Coord2(608.6810302734375, 855.288330078125), + Coord2(608.5828857421875, 855.0850830078125), + ), + Coord2(608.47265625, 855.011962890625), + ); + let curve2 = bezier::Curve::from_points( + Coord2(608.81689453125, 855.5904541015625), + ( + Coord2(608.7009887695313, 855.386474609375), + Coord2(608.5858154296875, 855.193115234375), + ), + Coord2(608.47265625, 855.011962890625), + ); + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); - + let intersections = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 2); } @@ -426,15 +702,39 @@ fn intersection_curve_6() { fn intersection_curve_7() { // Three curves that should intersect in two places, seems to be generating a missed collision when doing a self-collide // See `remove_interior_points_complex_1`: one of these curves seems to be producing only one collision, at least for the case that prompted that test - let curve1 = bezier::Curve::from_points(Coord2(602.1428833007813, 859.0895385742188), (Coord2(607.4638061523438, 858.4710693359375), Coord2(614.4444580078125, 855.14404296875)), Coord2(608.3931884765625, 855.6187133789063)); - let curve2 = bezier::Curve::from_points(Coord2(608.3871459960938, 864.779541015625), (Coord2(609.6311645507813, 860.09716796875), Coord2(608.9767456054688, 852.512939453125)), Coord2(607.8642578125, 855.7709350585938)); - let curve3 = bezier::Curve::from_points(Coord2(611.2799682617188, 862.292236328125), (Coord2(610.607666015625, 857.74365234375), Coord2(606.7666625976563, 851.5074462890625)), Coord2(606.7361450195313, 853.9833984375)); + let curve1 = bezier::Curve::from_points( + Coord2(602.1428833007813, 859.0895385742188), + ( + Coord2(607.4638061523438, 858.4710693359375), + Coord2(614.4444580078125, 855.14404296875), + ), + Coord2(608.3931884765625, 855.6187133789063), + ); + let curve2 = bezier::Curve::from_points( + Coord2(608.3871459960938, 864.779541015625), + ( + Coord2(609.6311645507813, 860.09716796875), + Coord2(608.9767456054688, 852.512939453125), + ), + Coord2(607.8642578125, 855.7709350585938), + ); + let curve3 = bezier::Curve::from_points( + Coord2(611.2799682617188, 862.292236328125), + ( + Coord2(610.607666015625, 857.74365234375), + Coord2(606.7666625976563, 851.5074462890625), + ), + Coord2(606.7361450195313, 853.9833984375), + ); let intersections1 = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); let intersections2 = bezier::curve_intersects_curve_clip(&curve2, &curve3, 0.01); let intersections3 = bezier::curve_intersects_curve_clip(&curve1, &curve3, 0.01); - println!("{:?}\n{:?}\n{:?}\n", intersections1, intersections2, intersections3); + println!( + "{:?}\n{:?}\n{:?}\n", + intersections1, intersections2, intersections3 + ); assert!(intersections1.len() == 2); assert!(intersections2.len() == 2); @@ -444,7 +744,10 @@ fn intersection_curve_7() { let intersections2 = bezier::curve_intersects_curve_clip(&curve3, &curve2, 0.01); let intersections3 = bezier::curve_intersects_curve_clip(&curve3, &curve1, 0.01); - println!("{:?}\n{:?}\n{:?}\n", intersections1, intersections2, intersections3); + println!( + "{:?}\n{:?}\n{:?}\n", + intersections1, intersections2, intersections3 + ); assert!(intersections1.len() == 2); assert!(intersections2.len() == 2); @@ -454,8 +757,22 @@ fn intersection_curve_7() { #[test] fn intersection_curve_8() { // This curve starts and ends at the same position - let loop_curve = bezier::Curve::from_points(Coord2(534.170654296875, 832.8574829101563), (Coord2(534.3781127929688, 832.078369140625), Coord2(534.73828125, 832.8485107421875)), Coord2(534.170654296875, 832.8574829101563)); - let curve2 = bezier::Curve::from_points(Coord2(534.3034057617188, 832.695068359375), (Coord2(534.2012536621094, 832.3760168457031), Coord2(534.2515673828125, 832.5331616210938)), Coord2(534.1509399414063, 832.2188720703125)); + let loop_curve = bezier::Curve::from_points( + Coord2(534.170654296875, 832.8574829101563), + ( + Coord2(534.3781127929688, 832.078369140625), + Coord2(534.73828125, 832.8485107421875), + ), + Coord2(534.170654296875, 832.8574829101563), + ); + let curve2 = bezier::Curve::from_points( + Coord2(534.3034057617188, 832.695068359375), + ( + Coord2(534.2012536621094, 832.3760168457031), + Coord2(534.2515673828125, 832.5331616210938), + ), + Coord2(534.1509399414063, 832.2188720703125), + ); let intersections1 = bezier::curve_intersects_curve_clip(&loop_curve, &curve2, 0.01); let intersections2 = bezier::curve_intersects_curve_clip(&curve2, &loop_curve, 0.01); @@ -463,8 +780,8 @@ fn intersection_curve_8() { println!("{:?}", intersections1); println!("{:?}", intersections2); - assert!(intersections1.len() > 0); - assert!(intersections2.len() > 0); + assert!(!intersections1.is_empty()); + assert!(!intersections2.is_empty()); assert!(intersections1.len() == 1); assert!(intersections2.len() == 1); @@ -475,17 +792,31 @@ fn intersection_curve_8() { #[test] fn intersection_curve_9() { - let curve1 = bezier::Curve::from_points(Coord2(576.0272827148438, 854.4729614257813), (Coord2(575.8976440429688, 855.9894409179688), Coord2(576.928466796875, 870.5877685546875)), Coord2(577.4629516601563, 873.9253540039063)); - let curve2 = bezier::Curve::from_points(Coord2(576.0455932617188, 854.9674072265625), (Coord2(574.2247924804688, 855.3988037109375), Coord2(577.3884887695313, 863.1025390625)), Coord2(580.003662109375, 863.5904541015625)); - + let curve1 = bezier::Curve::from_points( + Coord2(576.0272827148438, 854.4729614257813), + ( + Coord2(575.8976440429688, 855.9894409179688), + Coord2(576.928466796875, 870.5877685546875), + ), + Coord2(577.4629516601563, 873.9253540039063), + ); + let curve2 = bezier::Curve::from_points( + Coord2(576.0455932617188, 854.9674072265625), + ( + Coord2(574.2247924804688, 855.3988037109375), + Coord2(577.3884887695313, 863.1025390625), + ), + Coord2(580.003662109375, 863.5904541015625), + ); + let intersections1 = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); let intersections2 = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections1); println!("{:?}", intersections2); - assert!(intersections1.len() > 0); - assert!(intersections2.len() > 0); + assert!(!intersections1.is_empty()); + assert!(!intersections2.is_empty()); assert!(intersections1.len() == 2); assert!(intersections2.len() == 2); @@ -493,27 +824,33 @@ fn intersection_curve_9() { #[test] fn intersection_curve_10() { - let curve1 = bezier::Curve { - start_point: Coord2(284.86767013759504, 712.1320343559642), - end_point: Coord2(709.1317388495236, 712.1320343559643), - control_points: (Coord2(402.02495766297596, 829.2893218813454), Coord2(591.9744513241425, 829.2893218813454)) + let curve1 = bezier::Curve { + start_point: Coord2(284.86767013759504, 712.1320343559642), + end_point: Coord2(709.1317388495236, 712.1320343559643), + control_points: ( + Coord2(402.02495766297596, 829.2893218813454), + Coord2(591.9744513241425, 829.2893218813454), + ), }; - let curve2 = bezier::Curve { - start_point: Coord2(290.8682611504764, 712.1320343559642), - end_point: Coord2(715.132329862405, 712.1320343559643), - control_points: (Coord2(408.02554867585735, 829.2893218813454), Coord2(597.9750423370239, 829.2893218813454)) + let curve2 = bezier::Curve { + start_point: Coord2(290.8682611504764, 712.1320343559642), + end_point: Coord2(715.132329862405, 712.1320343559643), + control_points: ( + Coord2(408.02554867585735, 829.2893218813454), + Coord2(597.9750423370239, 829.2893218813454), + ), }; let intersections1 = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); - assert!(intersections1.len() > 0); + assert!(!intersections1.is_empty()); let intersections2 = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections1); println!("{:?}", intersections2); - assert!(intersections1.len() > 0); - assert!(intersections2.len() > 0); + assert!(!intersections1.is_empty()); + assert!(!intersections2.is_empty()); assert!(intersections1.len() == 1); assert!(intersections2.len() == 1); @@ -527,30 +864,36 @@ fn intersection_curve_10() { fn intersection_curve_11() { // Tries to eliminate the subdivisions from intersection_curve_10 so will more reliably fail if other changes are made to // the intersection algorithm - let curve1 = bezier::Curve { - start_point: Coord2(284.86767013759504, 712.1320343559642), - end_point: Coord2(709.1317388495236, 712.1320343559643), - control_points: (Coord2(402.02495766297596, 829.2893218813454), Coord2(591.9744513241425, 829.2893218813454)) + let curve1 = bezier::Curve { + start_point: Coord2(284.86767013759504, 712.1320343559642), + end_point: Coord2(709.1317388495236, 712.1320343559643), + control_points: ( + Coord2(402.02495766297596, 829.2893218813454), + Coord2(591.9744513241425, 829.2893218813454), + ), }; - let curve2 = bezier::Curve { - start_point: Coord2(290.8682611504764, 712.1320343559642), - end_point: Coord2(715.132329862405, 712.1320343559643), - control_points: (Coord2(408.02554867585735, 829.2893218813454), Coord2(597.9750423370239, 829.2893218813454)) + let curve2 = bezier::Curve { + start_point: Coord2(290.8682611504764, 712.1320343559642), + end_point: Coord2(715.132329862405, 712.1320343559643), + control_points: ( + Coord2(408.02554867585735, 829.2893218813454), + Coord2(597.9750423370239, 829.2893218813454), + ), }; let curve1 = curve1.section(0.49236699497857783, 0.5065132298924669); let curve2 = curve2.section(0.47428429377321385, 0.4934869656848157); let intersections1 = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01); - assert!(intersections1.len() > 0); + assert!(!intersections1.is_empty()); let intersections2 = bezier::curve_intersects_curve_clip(&curve2, &curve1, 0.01); println!("{:?}", intersections1); println!("{:?}", intersections2); - assert!(intersections1.len() > 0); - assert!(intersections2.len() > 0); + assert!(!intersections1.is_empty()); + assert!(!intersections2.is_empty()); assert!(intersections1.len() == 1); assert!(intersections2.len() == 1); @@ -578,8 +921,22 @@ fn intersection_curve_11() { fn intersection_very_close_to_start_1() { // One section here is a line, so we can find that the start point of the 'remaining' section is very close to that line (enough that it should produce an intersection) // Source of this is a case where the 'remaining' section is slightly rounded due to a conversion to f32 and back to f64 - let fragment = bezier::Curve { start_point: Coord2(503.12144515225805, 515.6925644864517), end_point: Coord2(558.5270966048384, 794.2355841209692), control_points: (Coord2(521.5881487814031, 608.5309529306364), Coord2(540.0548524105482, 701.369341374821)) }; - let remaining = bezier::Curve { start_point: Coord2(522.6328735351563, 613.7830200195313), end_point: Coord2(582.0244140625, 582.0244140625), control_points: (Coord2(544.3945922851563, 609.47509765625), Coord2(565.159912109375, 598.8888549804688)) }; + let fragment = bezier::Curve { + start_point: Coord2(503.12144515225805, 515.6925644864517), + end_point: Coord2(558.5270966048384, 794.2355841209692), + control_points: ( + Coord2(521.5881487814031, 608.5309529306364), + Coord2(540.0548524105482, 701.369341374821), + ), + }; + let remaining = bezier::Curve { + start_point: Coord2(522.6328735351563, 613.7830200195313), + end_point: Coord2(582.0244140625, 582.0244140625), + control_points: ( + Coord2(544.3945922851563, 609.47509765625), + Coord2(565.159912109375, 598.8888549804688), + ), + }; let intersections1 = bezier::curve_intersects_curve_clip(&fragment, &remaining, 0.01); let intersections2 = bezier::curve_intersects_curve_clip(&remaining, &fragment, 0.01); @@ -587,8 +944,8 @@ fn intersection_very_close_to_start_1() { println!("{:?}", intersections1); println!("{:?}", intersections2); - assert!(intersections1.len() > 0); - assert!(intersections2.len() > 0); + assert!(!intersections1.is_empty()); + assert!(!intersections2.is_empty()); assert!(intersections1.len() == 1); assert!(intersections2.len() == 1); @@ -612,11 +969,25 @@ fn intersection_very_close_to_start_1() { #[test] fn solve_t_close_to_start() { - use flo_curves::bezier::*; + use flo_curves::bezier::{BezierCurve, BezierCurve2D, Coord2, Coordinate, CurveCategory}; // Same curve as above, but we try to solve the closest point for one curve against another - let fragment = bezier::Curve { start_point: Coord2(503.12144515225805, 515.6925644864517), end_point: Coord2(558.5270966048384, 794.2355841209692), control_points: (Coord2(521.5881487814031, 608.5309529306364), Coord2(540.0548524105482, 701.369341374821)) }; - let remaining = bezier::Curve { start_point: Coord2(522.6328735351563, 613.7830200195313), end_point: Coord2(582.0244140625, 582.0244140625), control_points: (Coord2(544.3945922851563, 609.47509765625), Coord2(565.159912109375, 598.8888549804688)) }; + let fragment = bezier::Curve { + start_point: Coord2(503.12144515225805, 515.6925644864517), + end_point: Coord2(558.5270966048384, 794.2355841209692), + control_points: ( + Coord2(521.5881487814031, 608.5309529306364), + Coord2(540.0548524105482, 701.369341374821), + ), + }; + let remaining = bezier::Curve { + start_point: Coord2(522.6328735351563, 613.7830200195313), + end_point: Coord2(582.0244140625, 582.0244140625), + control_points: ( + Coord2(544.3945922851563, 609.47509765625), + Coord2(565.159912109375, 598.8888549804688), + ), + }; // In this case, the fragment is linear (however, as we're solving for a point close to a curve, this shouldn't be necessary always) assert!(fragment.characteristics() == CurveCategory::Linear); @@ -625,9 +996,11 @@ fn solve_t_close_to_start() { let t_remaining = 0.0; // Should be able to solve for this point on the remaining curve - let t_fragment = fragment.t_for_point(&remaining.start_point).expect("t value"); - let t_point = fragment.point_at_pos(t_fragment); - let t_distance = t_point.distance_to(&remaining.point_at_pos(t_remaining)); + let t_fragment = fragment + .t_for_point(&remaining.start_point) + .expect("t value"); + let t_point = fragment.point_at_pos(t_fragment); + let t_distance = t_point.distance_to(&remaining.point_at_pos(t_remaining)); // The above test should be able to solve this value to at least this precision level (t_remaining = 0.0, t_fragment = as above) assert!(t_distance < 0.02); @@ -636,13 +1009,24 @@ fn solve_t_close_to_start() { #[test] fn intersections_on_curve_sections() { // Two sections from the same larger curve, no shared points but overlapping in the middle - let larger_curve = bezier::Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); - - let curve1 = larger_curve.section(0.1, 0.5); - let curve2 = larger_curve.section(0.4, 0.8); - - let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); - println!("{:?} {:?}", intersections, intersections.iter().map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))).collect::>()); + let larger_curve = bezier::Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); + + let curve1 = larger_curve.section(0.1, 0.5); + let curve2 = larger_curve.section(0.4, 0.8); + + let intersections = bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.1); + println!( + "{:?} {:?}", + intersections, + intersections + .iter() + .map(|(t1, t2)| (curve1.point_at_pos(*t1), curve2.point_at_pos(*t2))) + .collect::>() + ); // All intersections should be approximately the same location for intersect in intersections.iter() { diff --git a/tests/bezier/deform.rs b/tests/bezier/deform.rs index 4e42368d..7489db09 100644 --- a/tests/bezier/deform.rs +++ b/tests/bezier/deform.rs @@ -1,77 +1,135 @@ -use flo_curves::*; use flo_curves::bezier; +use flo_curves::{ + BezierCurve, BezierCurveFactory, Coord2, Coordinate, Coordinate2D, Coordinate3D, Line, +}; #[test] fn deform_line_upwards() { - let curve = bezier::Curve::from_points(Coord2(0.0, 0.0), (Coord2(2.5, 0.0), Coord2(7.5, 0.0)), Coord2(10.0, 0.0)); - let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, 0.5, &Coord2(0.0, 4.0)); - - let original_point = curve.point_at_pos(0.5); - let new_point = deformed.point_at_pos(0.5); - - let offset = new_point - original_point; - - assert!((offset.x()-0.0).abs() < 0.01); - assert!((offset.y()-4.0).abs() < 0.01); - - assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); - assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); + let curve = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(2.5, 0.0), Coord2(7.5, 0.0)), + Coord2(10.0, 0.0), + ); + let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, 0.5, &Coord2(0.0, 4.0)); + + let original_point = curve.point_at_pos(0.5); + let new_point = deformed.point_at_pos(0.5); + + let offset = new_point - original_point; + + assert!((offset.x() - 0.0).abs() < 0.01); + assert!((offset.y() - 4.0).abs() < 0.01); + + assert!( + curve + .point_at_pos(0.0) + .distance_to(&deformed.point_at_pos(0.0)) + < 0.01 + ); + assert!( + curve + .point_at_pos(1.0) + .distance_to(&deformed.point_at_pos(1.0)) + < 0.01 + ); } #[test] fn deform_curve_at_halfway_point() { - let curve = bezier::Curve::from_points(Coord2(10.0, 20.0), (Coord2(0.0, 15.0), Coord2(16.0, 30.0)), Coord2(20.0, 15.0)); - let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, 0.5, &Coord2(3.0, 4.0)); - - let original_point = curve.point_at_pos(0.5); - let new_point = deformed.point_at_pos(0.5); - - let offset = new_point - original_point; - - assert!((offset.x()-3.0).abs() < 0.01); - assert!((offset.y()-4.0).abs() < 0.01); - - assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); - assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); + let curve = bezier::Curve::from_points( + Coord2(10.0, 20.0), + (Coord2(0.0, 15.0), Coord2(16.0, 30.0)), + Coord2(20.0, 15.0), + ); + let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, 0.5, &Coord2(3.0, 4.0)); + + let original_point = curve.point_at_pos(0.5); + let new_point = deformed.point_at_pos(0.5); + + let offset = new_point - original_point; + + assert!((offset.x() - 3.0).abs() < 0.01); + assert!((offset.y() - 4.0).abs() < 0.01); + + assert!( + curve + .point_at_pos(0.0) + .distance_to(&deformed.point_at_pos(0.0)) + < 0.01 + ); + assert!( + curve + .point_at_pos(1.0) + .distance_to(&deformed.point_at_pos(1.0)) + < 0.01 + ); } #[test] fn deform_curve_at_other_point() { - let t = 0.32; - let curve = bezier::Curve::from_points(Coord2(10.0, 20.0), (Coord2(0.0, 15.0), Coord2(16.0, 30.0)), Coord2(20.0, 15.0)); - let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, t, &Coord2(3.0, 4.0)); - - let original_point = curve.point_at_pos(t); - let new_point = deformed.point_at_pos(t); - - let offset = new_point - original_point; - - assert!((offset.x()-3.0).abs() < 0.01); - assert!((offset.y()-4.0).abs() < 0.01); - - assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); - assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); + let t = 0.32; + let curve = bezier::Curve::from_points( + Coord2(10.0, 20.0), + (Coord2(0.0, 15.0), Coord2(16.0, 30.0)), + Coord2(20.0, 15.0), + ); + let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, t, &Coord2(3.0, 4.0)); + + let original_point = curve.point_at_pos(t); + let new_point = deformed.point_at_pos(t); + + let offset = new_point - original_point; + + assert!((offset.x() - 3.0).abs() < 0.01); + assert!((offset.y() - 4.0).abs() < 0.01); + + assert!( + curve + .point_at_pos(0.0) + .distance_to(&deformed.point_at_pos(0.0)) + < 0.01 + ); + assert!( + curve + .point_at_pos(1.0) + .distance_to(&deformed.point_at_pos(1.0)) + < 0.01 + ); } #[test] fn deform_curve_at_many_other_points() { for t in 0..100 { // Won't work at 0 or 1 as these are the start and end points and don't move - let t = (t as f64)/100.0; - let t = (0.9*t)+0.05; - - let curve = bezier::Curve::from_points(Coord2(5.0, 23.0), (Coord2(-10.0, 15.0), Coord2(26.0, 30.0)), Coord2(22.0, 17.0)); - let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, t, &Coord2(6.0, -4.0)); - - let original_point = curve.point_at_pos(t); - let new_point = deformed.point_at_pos(t); - - let offset = new_point - original_point; - - assert!((offset.x()-6.0).abs() < 0.01); - assert!((offset.y()- -4.0).abs() < 0.01); - - assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); - assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); + let t = (t as f64) / 100.0; + let t = (0.9 * t) + 0.05; + + let curve = bezier::Curve::from_points( + Coord2(5.0, 23.0), + (Coord2(-10.0, 15.0), Coord2(26.0, 30.0)), + Coord2(22.0, 17.0), + ); + let deformed = bezier::move_point::<_, _, bezier::Curve<_>>(&curve, t, &Coord2(6.0, -4.0)); + + let original_point = curve.point_at_pos(t); + let new_point = deformed.point_at_pos(t); + + let offset = new_point - original_point; + + assert!((offset.x() - 6.0).abs() < 0.01); + assert!((offset.y() - -4.0).abs() < 0.01); + + assert!( + curve + .point_at_pos(0.0) + .distance_to(&deformed.point_at_pos(0.0)) + < 0.01 + ); + assert!( + curve + .point_at_pos(1.0) + .distance_to(&deformed.point_at_pos(1.0)) + < 0.01 + ); } } diff --git a/tests/bezier/distort.rs b/tests/bezier/distort.rs index 11633540..833a9fb0 100644 --- a/tests/bezier/distort.rs +++ b/tests/bezier/distort.rs @@ -1,25 +1,43 @@ -use flo_curves::geo::*; -use flo_curves::bezier::*; +use flo_curves::bezier::{ + distort_curve, walk_curve_evenly, BezierCurve, BezierCurveFactory, Coord2, Coordinate2D, + Coordinate3D, Curve, +}; #[test] fn line_to_sine_wave() { - let line = Curve::from_points(Coord2(100.0, 100.0), (Coord2(100.0, 100.0), Coord2(400.0, 100.0)), Coord2(400.0, 100.0)); - let distorted = distort_curve::<_, _, Curve<_>>(&line, |pos, _t| Coord2(pos.x(), pos.y() + (pos.x()*20.0).sin()), 1.0, 1.0).expect("Fit curve"); + let line = Curve::from_points( + Coord2(100.0, 100.0), + (Coord2(100.0, 100.0), Coord2(400.0, 100.0)), + Coord2(400.0, 100.0), + ); + let distorted = distort_curve::<_, _, Curve<_>>( + &line, + |pos, _t| Coord2(pos.x(), pos.y() + (pos.x() * 20.0).sin()), + 1.0, + 1.0, + ) + .expect("Fit curve"); for curve in distorted.into_iter() { println!("{:?}", curve); for section in walk_curve_evenly(&curve, 1.0, 0.1) { - let (t_min, t_max) = section.original_curve_t_values(); - let t_mid = (t_min+t_max)/2.0; - let pos = section.point_at_pos(t_mid); + let (t_min, t_max) = section.original_curve_t_values(); + let t_mid = (t_min + t_max) / 2.0; + let pos = section.point_at_pos(t_mid); - let expected_y = 100.0 + (pos.x()*20.0).sin(); - let actual_y = pos.y(); + let expected_y = 100.0 + (pos.x() * 20.0).sin(); + let actual_y = pos.y(); - println!(" {:?} {:?} {:?} {:?}", t_mid, expected_y, actual_y, (expected_y-actual_y).abs()); + println!( + " {:?} {:?} {:?} {:?}", + t_mid, + expected_y, + actual_y, + (expected_y - actual_y).abs() + ); - assert!((expected_y-actual_y).abs() < 4.0); + assert!((expected_y - actual_y).abs() < 4.0); } } } diff --git a/tests/bezier/intersection.rs b/tests/bezier/intersection.rs index c4832d24..b7659da8 100644 --- a/tests/bezier/intersection.rs +++ b/tests/bezier/intersection.rs @@ -1,14 +1,15 @@ -use flo_curves::*; use flo_curves::bezier; use flo_curves::line; +use flo_curves::{BezierCurve, BezierCurveFactory, BoundingBox, Coord2, Coordinate, Line}; #[test] fn find_intersection_on_straight_line() { // Cross that intersects at (5.0, 5.0) - let line = (Coord2(0.0, 0.0), Coord2(10.0, 10.0)); - let curve = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + let line = (Coord2(0.0, 0.0), Coord2(10.0, 10.0)); + let curve = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); assert!(intersections.len() == 1); let intersect_point = curve.point_at_pos(intersections[0].0); @@ -18,10 +19,11 @@ fn find_intersection_on_straight_line() { #[test] fn find_intersection_with_vertical_ray() { // Cross that intersects at (5.0, 5.0) - let line = (Coord2(5.0, 0.0), Coord2(5.0, 10.0)); - let curve = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + let line = (Coord2(5.0, 0.0), Coord2(5.0, 10.0)); + let curve = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); assert!(intersections.len() == 1); let intersect_point = curve.point_at_pos(intersections[0].0); @@ -31,10 +33,11 @@ fn find_intersection_with_vertical_ray() { #[test] fn find_intersection_with_horizontal_ray() { // Cross that intersects at (5.0, 5.0) - let line = (Coord2(0.0, 5.0), Coord2(10.0, 5.0)); - let curve = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + let line = (Coord2(0.0, 5.0), Coord2(10.0, 5.0)); + let curve = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); assert!(intersections.len() == 1); let intersect_point = curve.point_at_pos(intersections[0].0); @@ -44,20 +47,22 @@ fn find_intersection_with_horizontal_ray() { #[test] fn no_intersection_if_line_does_not_cross_curve() { // Line moves away from the curve - let line = (Coord2(0.0, 0.0), Coord2(-10.0, -10.0)); - let curve = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + let line = (Coord2(0.0, 0.0), Coord2(-10.0, -10.0)); + let curve = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_line(&curve, &line); - assert!(intersections.len() == 0); + let intersections = bezier::curve_intersects_line(&curve, &line); + assert!(intersections.is_empty()); } #[test] fn find_intersection_on_straight_line_against_ray() { // Line moves away from the curve so it doesn't intersect. When we use intersects_ray(), however, we find intersections anywhere along the line - let line = (Coord2(0.0, 0.0), Coord2(-10.0, -10.0)); - let curve = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); + let line = (Coord2(0.0, 0.0), Coord2(-10.0, -10.0)); + let curve = + line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(10.0, 0.0), Coord2(0.0, 10.0))); - let intersections = bezier::curve_intersects_ray(&curve, &line); + let intersections = bezier::curve_intersects_ray(&curve, &line); assert!(intersections.len() == 1); let intersect_point = curve.point_at_pos(intersections[0].0); @@ -66,223 +71,327 @@ fn find_intersection_on_straight_line_against_ray() { #[test] fn find_intersection_on_curve() { - let line = (Coord2(0.0, 6.0), Coord2(10.0, 4.0)); - let curve = bezier::Curve { - start_point: Coord2(0.0, 2.0), - end_point: Coord2(10.0, 8.0), - control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)) + let line = (Coord2(0.0, 6.0), Coord2(10.0, 4.0)); + let curve = bezier::Curve { + start_point: Coord2(0.0, 2.0), + end_point: Coord2(10.0, 8.0), + control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)), }; // Find the intersections - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); // Should be 3 intersections assert!(intersections.len() == 3); // Curve is symmetrical so the mid-point should be at 5,5 - assert!(curve.point_at_pos(intersections[1].0).distance_to(&Coord2(5.0, 5.0)) < 0.01); + assert!( + curve + .point_at_pos(intersections[1].0) + .distance_to(&Coord2(5.0, 5.0)) + < 0.01 + ); // Other points are a bit less precise - assert!(curve.point_at_pos(intersections[0].0).distance_to(&Coord2(0.260, 5.948)) < 0.01); - assert!(curve.point_at_pos(intersections[2].0).distance_to(&Coord2(9.740, 4.052)) < 0.01); + assert!( + curve + .point_at_pos(intersections[0].0) + .distance_to(&Coord2(0.260, 5.948)) + < 0.01 + ); + assert!( + curve + .point_at_pos(intersections[2].0) + .distance_to(&Coord2(9.740, 4.052)) + < 0.01 + ); } #[test] fn find_intersection_on_curve_short_line() { - let line = (Coord2(0.0, 6.0), Coord2(8.0, 4.4)); - let curve = bezier::Curve { - start_point: Coord2(0.0, 2.0), - end_point: Coord2(10.0, 8.0), - control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)) + let line = (Coord2(0.0, 6.0), Coord2(8.0, 4.4)); + let curve = bezier::Curve { + start_point: Coord2(0.0, 2.0), + end_point: Coord2(10.0, 8.0), + control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)), }; // Find the intersections - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); // Should be 2 intersections assert!(intersections.len() == 2); - assert!(curve.point_at_pos(intersections[1].0).distance_to(&Coord2(5.0, 5.0)) < 0.01); - assert!(curve.point_at_pos(intersections[0].0).distance_to(&Coord2(0.260, 5.948)) < 0.01); + assert!( + curve + .point_at_pos(intersections[1].0) + .distance_to(&Coord2(5.0, 5.0)) + < 0.01 + ); + assert!( + curve + .point_at_pos(intersections[0].0) + .distance_to(&Coord2(0.260, 5.948)) + < 0.01 + ); } #[test] fn dot_intersects_nothing() { // Line with 0 length - let line = (Coord2(4.0, 4.0), Coord2(4.0, 4.0)); - let curve = bezier::Curve { - start_point: Coord2(0.0, 2.0), - end_point: Coord2(10.0, 8.0), - control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)) + let line = (Coord2(4.0, 4.0), Coord2(4.0, 4.0)); + let curve = bezier::Curve { + start_point: Coord2(0.0, 2.0), + end_point: Coord2(10.0, 8.0), + control_points: (Coord2(0.0, 20.0), Coord2(10.0, -10.0)), }; // Find the intersections - let intersections = bezier::curve_intersects_line(&curve, &line); + let intersections = bezier::curve_intersects_line(&curve, &line); // Should be no intersections - assert!(intersections.len() == 0); + assert!(intersections.is_empty()); } #[test] fn lines_intersect_at_start() { - let line1 = (Coord2(4.0, 4.0), Coord2(5.0, 8.0)); - let line2 = (Coord2(4.0, 4.0), Coord2(8.0, 5.0)); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); + let line1 = (Coord2(4.0, 4.0), Coord2(5.0, 8.0)); + let line2 = (Coord2(4.0, 4.0), Coord2(8.0, 5.0)); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); let intersections = bezier::curve_intersects_line(&curve2, &line1); assert!(intersections.len() == 1); assert!(intersections[0].0 < 0.01); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&Coord2(4.0, 4.0)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&Coord2(4.0, 4.0)) + < 0.01 + ); } #[test] fn lines_intersect_at_end() { - let line1 = (Coord2(5.0, 8.0), Coord2(4.0, 4.0)); - let line2 = (Coord2(8.0, 5.0), Coord2(4.0, 4.0)); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); + let line1 = (Coord2(5.0, 8.0), Coord2(4.0, 4.0)); + let line2 = (Coord2(8.0, 5.0), Coord2(4.0, 4.0)); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); let intersections = bezier::curve_intersects_line(&curve2, &line1); assert!(intersections.len() == 1); assert!(intersections[0].0 > 0.99); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&Coord2(4.0, 4.0)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&Coord2(4.0, 4.0)) + < 0.01 + ); } #[test] fn lines_intersect_start_to_end() { - let line1 = (Coord2(4.0, 4.0), Coord2(5.0, 8.0)); - let line2 = (Coord2(8.0, 5.0), Coord2(4.0, 4.0)); - let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); + let line1 = (Coord2(4.0, 4.0), Coord2(5.0, 8.0)); + let line2 = (Coord2(8.0, 5.0), Coord2(4.0, 4.0)); + let curve2 = line::line_to_bezier::<_, bezier::Curve<_>>(&line2); let intersections = bezier::curve_intersects_line(&curve2, &line1); assert!(intersections.len() == 1); assert!(intersections[0].0 > 0.99); - assert!(curve2.point_at_pos(intersections[0].0).distance_to(&Coord2(4.0, 4.0)) < 0.01); + assert!( + curve2 + .point_at_pos(intersections[0].0) + .distance_to(&Coord2(4.0, 4.0)) + < 0.01 + ); } #[test] fn ray_intersects_collinear_line_1() { // Ray intersecting a collinear line edge-on - let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); - let line = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(4.0, 2.0), Coord2(8.0, 4.0))); + let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); + let line = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(4.0, 2.0), Coord2(8.0, 4.0))); let intersections = bezier::curve_intersects_ray(&line, &ray); assert!(intersections.len() == 2); assert!(intersections[0].0 < 0.001); assert!(intersections[0].2.distance_to(&Coord2(4.0, 2.0)) < 0.01); - assert!((intersections[1].0-1.0).abs() < 0.001); + assert!((intersections[1].0 - 1.0).abs() < 0.001); assert!(intersections[1].2.distance_to(&Coord2(8.0, 4.0)) < 0.01); } #[test] fn ray_intersects_collinear_line_2() { // Intersecting a collinear line which has a point closer to the start of the ray than the start of the line - let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); - let line = bezier::Curve::from_points(Coord2(4.0, 2.0), (Coord2(2.0, 1.0), Coord2(10.0, 5.0)), Coord2(8.0, 4.0)); // line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(4.0, 2.0), Coord2(8.0, 4.0))); + let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); + let line = bezier::Curve::from_points( + Coord2(4.0, 2.0), + (Coord2(2.0, 1.0), Coord2(10.0, 5.0)), + Coord2(8.0, 4.0), + ); // line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(4.0, 2.0), Coord2(8.0, 4.0))); let intersections = bezier::curve_intersects_ray(&line, &ray); assert!(intersections.len() == 2); assert!(intersections[0].0 < 0.001); assert!(intersections[0].2.distance_to(&Coord2(4.0, 2.0)) < 0.01); - assert!((intersections[1].0-1.0).abs() < 0.001); + assert!((intersections[1].0 - 1.0).abs() < 0.001); assert!(intersections[1].2.distance_to(&Coord2(8.0, 4.0)) < 0.01); } #[test] fn ray_intersects_collinear_line_3() { // Line moving towards the start of the ray instead of away from it - let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); - let line = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(8.0, 4.0), Coord2(4.0, 2.0))); + let ray = (Coord2(0.0, 0.0), Coord2(2.0, 1.0)); + let line = line::line_to_bezier::<_, bezier::Curve<_>>(&(Coord2(8.0, 4.0), Coord2(4.0, 2.0))); let intersections = bezier::curve_intersects_ray(&line, &ray); assert!(intersections.len() == 2); assert!(intersections[0].0.abs() < 0.001); assert!(intersections[0].2.distance_to(&Coord2(8.0, 4.0)) < 0.01); - assert!((intersections[1].0-1.0).abs() < 0.001); + assert!((intersections[1].0 - 1.0).abs() < 0.001); assert!(intersections[1].2.distance_to(&Coord2(4.0, 2.0)) < 0.01); } #[test] fn ray_intersects_curve_1() { // Failed intersection in ring_with_offset_crossbar_ray_casting_issue - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.3853378796624052, 0.7560017173290998), Coord2(0.385337879662404, 1.0999999999999999)); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.3853378796624052, 0.7560017173290998), + Coord2(0.385337879662404, 1.0999999999999999), + ); let intersections = bezier::curve_intersects_ray(&curve, &ray); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn ray_intersects_curve_1a() { // As above but the ray is less vertical - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.3854378796624052, 0.7560017173290998), Coord2(0.385337879662404, 1.0999999999999999)); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.3854378796624052, 0.7560017173290998), + Coord2(0.385337879662404, 1.0999999999999999), + ); let intersections = bezier::curve_intersects_ray(&curve, &ray); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn ray_intersects_curve_1b() { // Failed intersection in ring_with_offset_crossbar_ray_casting_issue (different ray: all vertical rays seem to be an issue) - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.395337879662404, 0.7560017173290998), Coord2(0.395337879662404, 1.0999999999999999)); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.395337879662404, 0.7560017173290998), + Coord2(0.395337879662404, 1.0999999999999999), + ); let intersections = bezier::curve_intersects_ray(&curve, &ray); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn ray_intersects_curve_1c() { // Horizontal ray to the collision point in 1a - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.7560017173290998, 0.8192109187049827), Coord2(1.0999999999999999, 0.8192109187049827)); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.7560017173290998, 0.8192109187049827), + Coord2(1.0999999999999999, 0.8192109187049827), + ); let intersections = bezier::curve_intersects_ray(&curve, &ray); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn ray_intersects_curve_1d() { // Failed intersection in ring_with_offset_crossbar_ray_casting_issue (vertical ray to the collision point from 1a) - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.38541950989400653, 0.7560017173290998), Coord2(0.38541950989400653, 1.0999999999999999)); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.38541950989400653, 0.7560017173290998), + Coord2(0.38541950989400653, 1.0999999999999999), + ); let intersections = bezier::curve_intersects_ray(&curve, &ray); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn ray_intersects_curve_1e() { // Same intersection but using the clipping algorithm (fails the same way as we eventually try to find via the root finder) - let curve = bezier::Curve::from_points(Coord2(0.5857864376269051, 0.5857864376269049), (Coord2(0.488017920077567, 0.683554955176243), Coord2(0.40248767198507907, 0.7889273585090868)), Coord2(0.3291956933494412, 0.899999999999999)); - let ray = (Coord2(0.3853378796624052, 0.0), Coord2(0.385337879662404, 10.0)); - let ray = line::line_to_bezier(&ray); + let curve = bezier::Curve::from_points( + Coord2(0.5857864376269051, 0.5857864376269049), + ( + Coord2(0.488017920077567, 0.683554955176243), + Coord2(0.40248767198507907, 0.7889273585090868), + ), + Coord2(0.3291956933494412, 0.899999999999999), + ); + let ray = ( + Coord2(0.3853378796624052, 0.0), + Coord2(0.385337879662404, 10.0), + ); + let ray = line::line_to_bezier(&ray); let intersections = bezier::curve_intersects_curve_clip(&curve, &ray, 0.01); - assert!(intersections.len() != 0); + assert!(!intersections.is_empty()); assert!(intersections.len() == 1); } #[test] fn roots_library_does_not_have_missing_root_bug() { - use roots::*; + use roots::{find_roots_cubic, FloatType, Roots}; // Known root of a set of coefficients (which happen to be the coefficients from the failing tests above) let a = -0.000000000000000040410628481035; @@ -293,26 +402,26 @@ fn roots_library_does_not_have_missing_root_bug() { let x = 0.754710877053; // Demonstrate that this is a root - assert!((a*x*x*x + b*x*x + c*x + d).abs() < 0.001); + assert!((a * x * x * x + b * x * x + c * x + d).abs() < 0.001); // Try to find this root let roots = find_roots_cubic(a, b, c, d); let roots = match roots { - Roots::No(_) => vec![], - Roots::One(r) => r.to_vec(), - Roots::Two(r) => r.to_vec(), + Roots::No(_) => vec![], + Roots::One(r) => r.to_vec(), + Roots::Two(r) => r.to_vec(), Roots::Three(r) => r.to_vec(), - Roots::Four(r) => r.to_vec() + Roots::Four(r) => r.to_vec(), }; // Should exist a root that's close to the value above println!("{:?}", roots); - assert!(roots.into_iter().any(|r| (r-x).abs() < 0.01)); + assert!(roots.into_iter().any(|r| (r - x).abs() < 0.01)); } #[test] fn ray_missing_root_2() { - use roots::*; + use roots::{find_roots_cubic, FloatType, Roots}; // As above but with the slightly weird coefficent a set to 0.0 let a = -0.0; @@ -323,26 +432,26 @@ fn ray_missing_root_2() { let x = 0.754710877053; // Demonstrate that this is a root - assert!((a*x*x*x + b*x*x + c*x + d).abs() < 0.001); + assert!((a * x * x * x + b * x * x + c * x + d).abs() < 0.001); // Try to find this root let roots = find_roots_cubic(a, b, c, d); let roots = match roots { - Roots::No(_) => vec![], - Roots::One(r) => r.to_vec(), - Roots::Two(r) => r.to_vec(), + Roots::No(_) => vec![], + Roots::One(r) => r.to_vec(), + Roots::Two(r) => r.to_vec(), Roots::Three(r) => r.to_vec(), - Roots::Four(r) => r.to_vec() + Roots::Four(r) => r.to_vec(), }; // Should exist a root that's close to the value above println!("{:?}", roots); - assert!(roots.into_iter().any(|r| (r-x).abs() < 0.01)); + assert!(roots.into_iter().any(|r| (r - x).abs() < 0.01)); } #[test] fn ray_missing_root_3() { - use roots::*; + use roots::{find_roots_cubic, FloatType, Roots}; // Again, but with the smallest value of a that we get a sensible answer for let a = -0.0000000002; @@ -353,19 +462,19 @@ fn ray_missing_root_3() { let x = 0.754710877053; // Demonstrate that this is a root - assert!((a*x*x*x + b*x*x + c*x + d).abs() < 0.001); + assert!((a * x * x * x + b * x * x + c * x + d).abs() < 0.001); // Try to find this root let roots = find_roots_cubic(a, b, c, d); let roots = match roots { - Roots::No(_) => vec![], - Roots::One(r) => r.to_vec(), - Roots::Two(r) => r.to_vec(), + Roots::No(_) => vec![], + Roots::One(r) => r.to_vec(), + Roots::Two(r) => r.to_vec(), Roots::Three(r) => r.to_vec(), - Roots::Four(r) => r.to_vec() + Roots::Four(r) => r.to_vec(), }; // Should exist a root that's close to the value above println!("{:?}", roots); - assert!(roots.into_iter().any(|r| (r-x).abs() < 0.01)); + assert!(roots.into_iter().any(|r| (r - x).abs() < 0.01)); } diff --git a/tests/bezier/length.rs b/tests/bezier/length.rs index 5a33c496..32b013f1 100644 --- a/tests/bezier/length.rs +++ b/tests/bezier/length.rs @@ -1,4 +1,6 @@ -use flo_curves::bezier::*; +use flo_curves::bezier::{ + chord_length, curve_length, walk_curve_unevenly, BezierCurve, BezierCurveFactory, Coord2, Curve, +}; /// /// Estimates a curve's length by subdividing it a lot @@ -7,7 +9,7 @@ fn subdivide_length(curve: &Curve) -> f64 { let mut length = 0.0; for subsection in walk_curve_unevenly(curve, 1000) { - length += chord_length(&subsection); + length += chord_length(&subsection); } length @@ -15,9 +17,13 @@ fn subdivide_length(curve: &Curve) -> f64 { #[test] fn measure_point_length() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(412.0, 500.0)), Coord2(412.0, 500.0)); - let by_subdivision = subdivide_length(&c); - let by_measuring = curve_length(&c, 0.5); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(412.0, 500.0)), + Coord2(412.0, 500.0), + ); + let by_subdivision = subdivide_length(&c); + let by_measuring = curve_length(&c, 0.5); assert!((by_measuring - by_subdivision).abs() < 1.0); assert!(by_measuring.abs() < 0.1); @@ -25,36 +31,52 @@ fn measure_point_length() { #[test] fn measure_length_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let by_subdivision = subdivide_length(&c); - let by_measuring = curve_length(&c, 0.5); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let by_subdivision = subdivide_length(&c); + let by_measuring = curve_length(&c, 0.5); assert!((by_measuring - by_subdivision).abs() < 1.0); } #[test] fn measure_length_2() { - let c = Curve::from_points(Coord2(987.7637, 993.9645), (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), Coord2(1064.9473, 994.277)); - let by_subdivision = subdivide_length(&c); - let by_measuring = curve_length(&c, 0.5); + let c = Curve::from_points( + Coord2(987.7637, 993.9645), + (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), + Coord2(1064.9473, 994.277), + ); + let by_subdivision = subdivide_length(&c); + let by_measuring = curve_length(&c, 0.5); assert!((by_measuring - by_subdivision).abs() < 1.0); } #[test] fn measure_length_3() { - let c = Curve::from_points(Coord2(170.83203, 534.28906), (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), Coord2(262.95313, 533.2656)); - let by_subdivision = subdivide_length(&c); - let by_measuring = curve_length(&c, 0.5); + let c = Curve::from_points( + Coord2(170.83203, 534.28906), + (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), + Coord2(262.95313, 533.2656), + ); + let by_subdivision = subdivide_length(&c); + let by_measuring = curve_length(&c, 0.5); assert!((by_measuring - by_subdivision).abs() < 1.0); } #[test] fn measure_length_4() { - let c = Curve::from_points(Coord2(170.83203, 534.28906), (Coord2(35.15625, 502.65625), Coord2(0.52734375, 478.67188)), Coord2(262.95313, 533.2656)); - let by_subdivision = subdivide_length(&c); - let by_measuring = curve_length(&c, 0.5); + let c = Curve::from_points( + Coord2(170.83203, 534.28906), + (Coord2(35.15625, 502.65625), Coord2(0.52734375, 478.67188)), + Coord2(262.95313, 533.2656), + ); + let by_subdivision = subdivide_length(&c); + let by_measuring = curve_length(&c, 0.5); assert!((by_measuring - by_subdivision).abs() < 1.0); } diff --git a/tests/bezier/mod.rs b/tests/bezier/mod.rs index f398a8b6..1f1a24c6 100644 --- a/tests/bezier/mod.rs +++ b/tests/bezier/mod.rs @@ -1,36 +1,40 @@ -use flo_curves::*; use flo_curves::bezier; +use flo_curves::{BezierCurve, BezierCurveFactory, Coord2, Coordinate, Line}; -mod path; mod algorithms; +mod path; mod basis; -mod section; -mod subdivide; -mod derivative; -mod tangent; -mod normal; mod bounds; +mod characteristics; +mod curve_intersection_clip; mod deform; -mod search; -mod solve; +mod derivative; +mod distort; +mod intersection; +mod length; +mod normal; mod offset; mod overlaps; -mod intersection; -mod characteristics; +mod search; +mod section; mod self_intersection; -mod curve_intersection_clip; -mod length; +mod solve; +mod subdivide; +mod tangent; mod walk; -mod distort; pub fn approx_equal(a: f64, b: f64) -> bool { - f64::floor(f64::abs(a-b)*10000.0) == 0.0 + f64::floor(f64::abs(a - b) * 10000.0) == 0.0 } #[test] fn read_curve_control_points() { - let curve = bezier::Curve::from_points(Coord2(1.0, 1.0), (Coord2(3.0, 3.0), Coord2(4.0, 4.0)), Coord2(2.0, 2.0)); + let curve = bezier::Curve::from_points( + Coord2(1.0, 1.0), + (Coord2(3.0, 3.0), Coord2(4.0, 4.0)), + Coord2(2.0, 2.0), + ); assert!(curve.start_point() == Coord2(1.0, 1.0)); assert!(curve.end_point() == Coord2(2.0, 2.0)); @@ -39,13 +43,23 @@ fn read_curve_control_points() { #[test] fn read_curve_points() { - let curve = bezier::Curve::from_points(Coord2(1.0, 1.0), (Coord2(3.0, 3.0), Coord2(4.0, 4.0)), Coord2(2.0, 2.0)); + let curve = bezier::Curve::from_points( + Coord2(1.0, 1.0), + (Coord2(3.0, 3.0), Coord2(4.0, 4.0)), + Coord2(2.0, 2.0), + ); for x in 0..100 { - let t = (x as f64)/100.0; + let t = (x as f64) / 100.0; - let point = curve.point_at_pos(t); - let another_point = bezier::de_casteljau4(t, Coord2(1.0, 1.0), Coord2(3.0, 3.0), Coord2(4.0, 4.0), Coord2(2.0, 2.0)); + let point = curve.point_at_pos(t); + let another_point = bezier::de_casteljau4( + t, + Coord2(1.0, 1.0), + Coord2(3.0, 3.0), + Coord2(4.0, 4.0), + Coord2(2.0, 2.0), + ); assert!(point.distance_to(&another_point) < 0.001); } diff --git a/tests/bezier/normal.rs b/tests/bezier/normal.rs index 358526a6..e523239f 100644 --- a/tests/bezier/normal.rs +++ b/tests/bezier/normal.rs @@ -1,11 +1,15 @@ -use flo_curves::*; use flo_curves::bezier; -use flo_curves::bezier::{NormalCurve}; +use flo_curves::bezier::NormalCurve; +use flo_curves::{BezierCurveFactory, Coord2, Coordinate, Coordinate2D, Coordinate3D, Line}; #[test] fn normal_for_line_is_straight_up() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(3.0, 0.0), Coord2(7.0, 0.0)), Coord2(10.0, 0.0)); - let normal = line.normal_at_pos(0.5); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(3.0, 0.0), Coord2(7.0, 0.0)), + Coord2(10.0, 0.0), + ); + let normal = line.normal_at_pos(0.5); // Normal should be a line facing up assert!(normal.x().abs() < 0.01); @@ -14,11 +18,15 @@ fn normal_for_line_is_straight_up() { #[test] fn normal_for_short_line_is_straight_up_1() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000003, 0.0), Coord2(0.0000000007, 0.0)), Coord2(0.0000000010, 0.0)); - let normal = line.normal_at_pos(0.5); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0000000003, 0.0), Coord2(0.0000000007, 0.0)), + Coord2(0.0000000010, 0.0), + ); + let normal = line.normal_at_pos(0.5); // Normals usually aren't unit vectors, but will produce very small values for very short lines - let normal = normal.to_unit_vector(); + let normal = normal.to_unit_vector(); // Normal should be a line facing up assert!(normal.x().abs() < 0.01); @@ -29,8 +37,12 @@ fn normal_for_short_line_is_straight_up_1() { fn normal_for_short_line_is_straight_up_2() { // IEEE floating point has extra precision for numbers very close to 0, so we also try with a short line 'far away' from 0 // (Will break down eventually when the line is far enough away as it will get represented as a point due to how floating point works) - let line = bezier::Curve::from_points(Coord2(10.0, 10.0), (Coord2(10.0000000003, 10.0), Coord2(10.0000000007, 10.0)), Coord2(10.0000000010, 10.0)); - let normal = line.normal_at_pos(0.5); + let line = bezier::Curve::from_points( + Coord2(10.0, 10.0), + (Coord2(10.0000000003, 10.0), Coord2(10.0000000007, 10.0)), + Coord2(10.0000000010, 10.0), + ); + let normal = line.normal_at_pos(0.5); // Normals usually aren't unit vectors, but will produce very small values for very short lines let normal = normal.to_unit_vector(); @@ -44,8 +56,12 @@ fn normal_for_short_line_is_straight_up_2() { fn normal_for_short_line_is_straight_up_2_t_0() { // IEEE floating point has extra precision for numbers very close to 0, so we also try with a short line 'far away' from 0 // (Will break down eventually when the line is far enough away as it will get represented as a point due to how floating point works) - let line = bezier::Curve::from_points(Coord2(10.0, 10.0), (Coord2(10.0000000003, 10.0), Coord2(10.0000000007, 10.0)), Coord2(10.0000000010, 10.0)); - let normal = line.normal_at_pos(0.0); + let line = bezier::Curve::from_points( + Coord2(10.0, 10.0), + (Coord2(10.0000000003, 10.0), Coord2(10.0000000007, 10.0)), + Coord2(10.0000000010, 10.0), + ); + let normal = line.normal_at_pos(0.0); // Normals usually aren't unit vectors, but will produce very small values for very short lines let normal = normal.to_unit_vector(); @@ -57,14 +73,18 @@ fn normal_for_short_line_is_straight_up_2_t_0() { #[test] fn normal_for_short_line_is_straight_up_overlapping_control_points_inside_line() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), Coord2(0.0000000010, 0.0)); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), + Coord2(0.0000000010, 0.0), + ); for t in 1..100 { - let t = (t as f64) / 100.0; - let normal = line.normal_at_pos(t); + let t = (t as f64) / 100.0; + let normal = line.normal_at_pos(t); // Normals usually aren't unit vectors, but will produce very small values for very short lines - let normal = normal.to_unit_vector(); + let normal = normal.to_unit_vector(); // Normal should be a line facing up assert!(normal.x().abs() < 0.01); @@ -74,13 +94,17 @@ fn normal_for_short_line_is_straight_up_overlapping_control_points_inside_line() #[test] fn normal_for_short_line_is_straight_up_overlapping_control_points_t_0() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), Coord2(0.0000000010, 0.0)); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), + Coord2(0.0000000010, 0.0), + ); - let t = 0.0; - let normal = line.normal_at_pos(t); + let t = 0.0; + let normal = line.normal_at_pos(t); // Normals usually aren't unit vectors, but will produce very small values for very short lines - let normal = normal.to_unit_vector(); + let normal = normal.to_unit_vector(); // Normal should be a line facing up assert!(normal.x().abs() < 0.01); @@ -89,13 +113,17 @@ fn normal_for_short_line_is_straight_up_overlapping_control_points_t_0() { #[test] fn normal_for_short_line_is_straight_up_overlapping_control_points_t_1() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), Coord2(0.0000000010, 0.0)); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0000000000, 0.0), Coord2(0.0000000010, 0.0)), + Coord2(0.0000000010, 0.0), + ); - let t = 1.0; - let normal = line.normal_at_pos(t); + let t = 1.0; + let normal = line.normal_at_pos(t); // Normals usually aren't unit vectors, but will produce very small values for very short lines - let normal = normal.to_unit_vector(); + let normal = normal.to_unit_vector(); // Normal should be a line facing up assert!(normal.x().abs() < 0.01); @@ -104,10 +132,17 @@ fn normal_for_short_line_is_straight_up_overlapping_control_points_t_1() { #[test] fn normal_for_short_diagonal_line_is_diagonal() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000003, 0.0000000003), Coord2(0.0000000007, 0.0000000007)), Coord2(0.0000000010, 0.0000000010)); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + ( + Coord2(0.0000000003, 0.0000000003), + Coord2(0.0000000007, 0.0000000007), + ), + Coord2(0.0000000010, 0.0000000010), + ); for t in 0..101 { - let t = (t as f64) / 100.0; - let normal = line.normal_at_pos(t); + let t = (t as f64) / 100.0; + let normal = line.normal_at_pos(t); // Normals usually aren't unit vectors, but will produce very small values for very short lines let normal = normal.to_unit_vector(); @@ -115,16 +150,23 @@ fn normal_for_short_diagonal_line_is_diagonal() { // Normal should be a 45 degree diagonal line assert!(normal.x() < -0.01); assert!(normal.y() > 0.01); - assert!((-normal.x()-normal.y()).abs() < 0.01); + assert!((-normal.x() - normal.y()).abs() < 0.01); } } #[test] fn normal_for_short_diagonal_line_is_diagonal_overlapping_points() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0000000000, 0.0000000000), Coord2(0.0000000010, 0.0000000010)), Coord2(0.0000000010, 0.0000000010)); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + ( + Coord2(0.0000000000, 0.0000000000), + Coord2(0.0000000010, 0.0000000010), + ), + Coord2(0.0000000010, 0.0000000010), + ); for t in 0..101 { - let t = (t as f64) / 100.0; - let normal = line.normal_at_pos(t); + let t = (t as f64) / 100.0; + let normal = line.normal_at_pos(t); // Normals usually aren't unit vectors, but will produce very small values for very short lines let normal = normal.to_unit_vector(); @@ -132,14 +174,18 @@ fn normal_for_short_diagonal_line_is_diagonal_overlapping_points() { // Normal should be a 45 degree diagonal line assert!(normal.x() < -0.01); assert!(normal.y() > 0.01); - assert!((-normal.x()-normal.y()).abs() < 0.01); + assert!((-normal.x() - normal.y()).abs() < 0.01); } } #[test] fn normal_for_point() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0, 0.0), Coord2(0.0, 0.0)), Coord2(0.0, 0.0)); - let normal = line.normal_at_pos(0.5); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 0.0), Coord2(0.0, 0.0)), + Coord2(0.0, 0.0), + ); + let normal = line.normal_at_pos(0.5); // Normal should be the (0,0) vector (points don't have normals) assert!(normal.x().abs() < 0.0001); @@ -148,8 +194,12 @@ fn normal_for_point() { #[test] fn normal_at_start_of_curve_matches_control_points() { - let line = bezier::Curve::from_points(Coord2(0.0,0.0), (Coord2(0.0, 1.0), Coord2(7.0, 0.0)), Coord2(10.0, 0.0)); - let normal = line.normal_at_pos(0.0); + let line = bezier::Curve::from_points( + Coord2(0.0, 0.0), + (Coord2(0.0, 1.0), Coord2(7.0, 0.0)), + Coord2(10.0, 0.0), + ); + let normal = line.normal_at_pos(0.0); // Normal should be a facing left assert!(normal.x() < 0.0); diff --git a/tests/bezier/offset.rs b/tests/bezier/offset.rs index bd5e7a80..e8d7d7e0 100644 --- a/tests/bezier/offset.rs +++ b/tests/bezier/offset.rs @@ -1,8 +1,10 @@ -use flo_curves::*; -use flo_curves::line; -use flo_curves::line::{Line2D}; -use flo_curves::bezier::*; use flo_curves::bezier::NormalCurve; +use flo_curves::bezier::{ + curve_intersects_ray, offset, offset_lms_sampling, BezierCurve, BezierCurveFactory, + BoundingBox, Coord2, Coordinate, Coordinate2D, Coordinate3D, Curve, Normalize, +}; +use flo_curves::line; +use flo_curves::line::Line2D; use std::f64; @@ -13,35 +15,42 @@ use std::f64; /// (When the offsets are different, there are a few choices for distance: we use the 't' value but it would be more /// correct to use curve length) /// -fn max_error(src_curve: &Curve, offset_curve: &Vec, initial_offset: f64, final_offset: f64) -> f64 -where Curve::Point: Coordinate2D+Normalize, -Curve: BezierCurve+NormalCurve { - let mut error = 0.0f64; - let mut last_closest: Option<(f64, Curve::Point)> = None; +fn max_error( + src_curve: &Curve, + offset_curve: &Vec, + initial_offset: f64, + final_offset: f64, +) -> f64 +where + Curve::Point: Coordinate2D + Normalize, + Curve: BezierCurve + NormalCurve, +{ + let mut error = 0.0f64; + let mut last_closest: Option<(f64, Curve::Point)> = None; for offset in offset_curve.iter() { for t in 1..=99 { - let t = (t as f64)/100.0; + let t = (t as f64) / 100.0; - let pos = offset.point_at_pos(t); - let normal = offset.normal_at_pos(t); - let intersect = curve_intersects_ray(src_curve, &(pos, pos+normal)); + let pos = offset.point_at_pos(t); + let normal = offset.normal_at_pos(t); + let intersect = curve_intersects_ray(src_curve, &(pos, pos + normal)); - let mut min_error = f64::MAX; + let mut min_error = f64::MAX; if let Some((last_expected_offset, last_point)) = last_closest { - let distance = last_point.distance_to(&pos); - min_error = min_error.min((distance-last_expected_offset).abs()); + let distance = last_point.distance_to(&pos); + min_error = min_error.min((distance - last_expected_offset).abs()); } for (curve_t, _, intersect_point) in intersect { - let expected_offset = (final_offset-initial_offset) * curve_t + initial_offset; + let expected_offset = (final_offset - initial_offset) * curve_t + initial_offset; - let distance = intersect_point.distance_to(&pos); - let error = (distance-expected_offset).abs(); + let distance = intersect_point.distance_to(&pos); + let error = (distance - expected_offset).abs(); if error < min_error { - min_error = error; - last_closest = Some((expected_offset, intersect_point)); + min_error = error; + last_closest = Some((expected_offset, intersect_point)); } } @@ -61,36 +70,52 @@ Curve: BezierCurve+NormalCurve { #[test] fn offset_overlap_start_point() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 3.5); } #[test] fn offset_overlap_end_point() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(163.0, 589.0), Coord2(308.0, 665.0)), Coord2(308.0, 665.0)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(163.0, 589.0), Coord2(308.0, 665.0)), + Coord2(308.0, 665.0), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 10.0); } #[test] fn simple_offset_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(163.0, 589.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(163.0, 589.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 2.0); } #[test] fn simple_offset_2() { - let c = Curve::from_points(Coord2(110.0, 110.0), (Coord2(110.0,300.0), Coord2(500.0,300.0)), Coord2(500.0,110.0)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(110.0, 110.0), + (Coord2(110.0, 300.0), Coord2(500.0, 300.0)), + Coord2(500.0, 110.0), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 2.0); } @@ -98,9 +123,16 @@ fn simple_offset_2() { #[test] fn simple_offset_3() { // This curve doesn't produce a very satisfying result, so it's interesting it has a low error value - let c = Curve::from_points(Coord2(516.170654296875, 893.27001953125), (Coord2(445.1522921545783, 856.2028149461783), Coord2(447.7831664737134, 878.3276285260063)), Coord2(450.51018453430754, 901.260980294519)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(516.170654296875, 893.27001953125), + ( + Coord2(445.1522921545783, 856.2028149461783), + Coord2(447.7831664737134, 878.3276285260063), + ), + Coord2(450.51018453430754, 901.260980294519), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 2.0); } @@ -108,9 +140,13 @@ fn simple_offset_3() { #[test] fn simple_offset_4() { // This curve seems to produce a huge spike - let c = Curve::from_points(Coord2(987.7637, 993.9645), (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), Coord2(1064.9473, 994.277)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(987.7637, 993.9645), + (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), + Coord2(1064.9473, 994.277), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(error <= 10.0); } @@ -120,55 +156,78 @@ fn simple_offset_5() { // This curve has a point approaching a cusp, so it produces 'strange' values // We bulge out slightly around the cusp so there's a large error - let c = Curve::from_points(Coord2(170.83203, 534.28906), (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), Coord2(262.95313, 533.2656)); - let offset_1 = offset(&c, 10.0, 10.0); - let error_1 = max_error(&c, &offset_1, 10.0, 10.0); + let c = Curve::from_points( + Coord2(170.83203, 534.28906), + (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), + Coord2(262.95313, 533.2656), + ); + let offset_1 = offset(&c, 10.0, 10.0); + let error_1 = max_error(&c, &offset_1, 10.0, 10.0); assert!(error_1 <= 12.0); // Offsetting too much 'inside' the curve starts to produce chaotic behaviour around the cusp with this algorithm - let offset_2 = offset(&c, -2.0, -2.0); - let error_2 = max_error(&c, &offset_2, 2.0, 2.0); + let offset_2 = offset(&c, -2.0, -2.0); + let error_2 = max_error(&c, &offset_2, 2.0, 2.0); assert!(error_2 <= 4.0); } #[test] fn simple_offset_6() { - let c = Curve::from_points(Coord2(170.83203, 534.28906), (Coord2(35.15625, 502.65625), Coord2(0.52734375, 478.67188)), Coord2(262.95313, 533.2656)); + let c = Curve::from_points( + Coord2(170.83203, 534.28906), + (Coord2(35.15625, 502.65625), Coord2(0.52734375, 478.67188)), + Coord2(262.95313, 533.2656), + ); // This is a very tight curve, so there's no good solution in this direction for large offsets (the scaling algorithm produces a very chaotic curve) - let offset_1 = offset(&c, 2.0, 2.0); - let error_1 = max_error(&c, &offset_1, 2.0, 2.0); + let offset_1 = offset(&c, 2.0, 2.0); + let error_1 = max_error(&c, &offset_1, 2.0, 2.0); assert!(error_1 <= 2.0); - let offset_2 = offset(&c, -10.0, -10.0); - let error_2 = max_error(&c, &offset_2, 10.0, 10.0); + let offset_2 = offset(&c, -10.0, -10.0); + let error_2 = max_error(&c, &offset_2, 10.0, 10.0); assert!(error_2 <= 1.0); } #[test] fn resizing_offset_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(163.0, 589.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let offset = offset(&c, 10.0, 40.0); - let error = max_error(&c, &offset, 10.0, 40.0); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(163.0, 589.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let offset = offset(&c, 10.0, 40.0); + let error = max_error(&c, &offset, 10.0, 40.0); assert!(error <= 2.0); } #[test] fn resizing_offset_2() { - let c = Curve::from_points(Coord2(110.0, 110.0), (Coord2(110.0,300.0), Coord2(500.0,300.0)), Coord2(500.0,110.0)); - let offset = offset(&c, 10.0, 40.0); - let error = max_error(&c, &offset, 10.0, 40.0); + let c = Curve::from_points( + Coord2(110.0, 110.0), + (Coord2(110.0, 300.0), Coord2(500.0, 300.0)), + Coord2(500.0, 110.0), + ); + let offset = offset(&c, 10.0, 40.0); + let error = max_error(&c, &offset, 10.0, 40.0); assert!(error <= 6.0); } #[test] fn resize_offset_3() { - let c = Curve::from_points(Coord2(516.170654296875, 893.27001953125), (Coord2(445.1522921545783, 856.2028149461783), Coord2(447.7831664737134, 878.3276285260063)), Coord2(450.51018453430754, 901.260980294519)); - let offset = offset(&c, 10.0, 40.0); - let error = max_error(&c, &offset, 10.0, 40.0); + let c = Curve::from_points( + Coord2(516.170654296875, 893.27001953125), + ( + Coord2(445.1522921545783, 856.2028149461783), + Coord2(447.7831664737134, 878.3276285260063), + ), + Coord2(450.51018453430754, 901.260980294519), + ); + let offset = offset(&c, 10.0, 40.0); + let error = max_error(&c, &offset, 10.0, 40.0); // The error seems to get so high because we're using the 't' value as a ratio for determining width rather than curve length // This also results in this offset curve not being particularly smooth @@ -177,15 +236,19 @@ fn resize_offset_3() { #[test] fn move_offset_1() { - let c = Curve::from_points(Coord2(163.0, 579.0), (Coord2(163.0, 579.0), Coord2(405.0, 684.0)), Coord2(405.0, 684.0)); - let offset = offset(&c, 10.0, 10.0); - let error = max_error(&c, &offset, 10.0, 10.0); + let c = Curve::from_points( + Coord2(163.0, 579.0), + (Coord2(163.0, 579.0), Coord2(405.0, 684.0)), + Coord2(405.0, 684.0), + ); + let offset = offset(&c, 10.0, 10.0); + let error = max_error(&c, &offset, 10.0, 10.0); assert!(offset.len() == 1); - let w1 = offset[0].start_point(); - let (w2, w3) = offset[0].control_points(); - let w4 = offset[0].end_point(); + let w1 = offset[0].start_point(); + let (w2, w3) = offset[0].control_points(); + let w4 = offset[0].end_point(); assert!((w2, w3).distance_to(&w1) < 0.01); assert!((w2, w3).distance_to(&w4) < 0.01); @@ -195,34 +258,40 @@ fn move_offset_1() { #[test] fn normals_for_line_do_not_meet_at_intersection() { // Overlapping control points mean that this curve defines a line - let c = Curve::from_points(Coord2(163.0, 579.0), (Coord2(163.0, 579.0), Coord2(405.0, 684.0)), Coord2(405.0, 684.0)); + let c = Curve::from_points( + Coord2(163.0, 579.0), + (Coord2(163.0, 579.0), Coord2(405.0, 684.0)), + Coord2(405.0, 684.0), + ); // Compute the normal at the start and the end of the line - let start = c.start_point(); - let end = c.end_point(); - let start_normal = c.normal_at_pos(0.0).to_unit_vector(); - let end_normal = c.normal_at_pos(1.0).to_unit_vector(); + let start = c.start_point(); + let end = c.end_point(); + let start_normal = c.normal_at_pos(0.0).to_unit_vector(); + let end_normal = c.normal_at_pos(1.0).to_unit_vector(); // The rays starting from the start and end of this line should not intersect // (This generates a ray divisor of 0.00000000000002603472992745992, because we lose enough precision that the lines appear to be not quite parallel) - let intersection = line::ray_intersects_ray(&(start, start+start_normal), &(end, end+end_normal)); + let intersection = + line::ray_intersects_ray(&(start, start + start_normal), &(end, end + end_normal)); assert!(intersection.is_none()); } #[test] fn offset_lms_sampling_arc_start_tangent() { - use flo_curves::arc::*; + use flo_curves::arc::Circle; // 90 degree circle arc - let circle = Circle::new(Coord2(0.0, 0.0), 100.0); - let arc = circle.arc(0.0, f64::consts::PI / 2.0); - let arc_curve = arc.to_bezier_curve::>(); + let circle = Circle::new(Coord2(0.0, 0.0), 100.0); + let arc = circle.arc(0.0, f64::consts::PI / 2.0); + let arc_curve = arc.to_bezier_curve::>(); // Offset by 10 - let offset_arc = offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); + let offset_arc = + offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); - let start_tangent_original = arc_curve.tangent_at_pos(0.0).to_unit_vector(); - let start_tangent_new = offset_arc[0].tangent_at_pos(0.0).to_unit_vector(); + let start_tangent_original = arc_curve.tangent_at_pos(0.0).to_unit_vector(); + let start_tangent_new = offset_arc[0].tangent_at_pos(0.0).to_unit_vector(); println!("{:?} {:?}", start_tangent_original, start_tangent_new); @@ -231,18 +300,21 @@ fn offset_lms_sampling_arc_start_tangent() { #[test] fn offset_lms_sampling_arc_end_tangent() { - use flo_curves::arc::*; + use flo_curves::arc::Circle; // 90 degree circle arc - let circle = Circle::new(Coord2(0.0, 0.0), 100.0); - let arc = circle.arc(0.0, f64::consts::PI / 2.0); - let arc_curve = arc.to_bezier_curve::>(); + let circle = Circle::new(Coord2(0.0, 0.0), 100.0); + let arc = circle.arc(0.0, f64::consts::PI / 2.0); + let arc_curve = arc.to_bezier_curve::>(); // Offset by 10 - let offset_arc = offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); + let offset_arc = + offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); - let end_tangent_original = arc_curve.tangent_at_pos(1.0).to_unit_vector(); - let end_tangent_new = offset_arc[offset_arc.len()-1].tangent_at_pos(1.0).to_unit_vector(); + let end_tangent_original = arc_curve.tangent_at_pos(1.0).to_unit_vector(); + let end_tangent_new = offset_arc[offset_arc.len() - 1] + .tangent_at_pos(1.0) + .to_unit_vector(); println!("{:?} {:?}", end_tangent_original, end_tangent_new); @@ -251,37 +323,39 @@ fn offset_lms_sampling_arc_end_tangent() { #[test] fn offset_lms_sampling_arc_end_point() { - use flo_curves::arc::*; + use flo_curves::arc::Circle; // 90 degree circle arc - let circle = Circle::new(Coord2(0.0, 0.0), 100.0); - let arc = circle.arc(0.0, f64::consts::PI / 2.0); - let arc_curve = arc.to_bezier_curve::>(); + let circle = Circle::new(Coord2(0.0, 0.0), 100.0); + let arc = circle.arc(0.0, f64::consts::PI / 2.0); + let arc_curve = arc.to_bezier_curve::>(); // Offset by 10 - let offset_arc = offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); + let offset_arc = + offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 0.01).expect("Offset curve"); - let end_point_original = arc_curve.point_at_pos(1.0); - let end_point_new = offset_arc[offset_arc.len()-1].point_at_pos(1.0); - let end_point_expected = Coord2(end_point_original.x() + 10.0, end_point_original.y()); + let end_point_original = arc_curve.point_at_pos(1.0); + let end_point_new = offset_arc[offset_arc.len() - 1].point_at_pos(1.0); + let end_point_expected = Coord2(end_point_original.x() + 10.0, end_point_original.y()); println!("{:?} {:?}", end_point_original, end_point_new); - assert!((end_point_original.distance_to(&end_point_new)-10.0).abs() < 0.01); + assert!((end_point_original.distance_to(&end_point_new) - 10.0).abs() < 0.01); assert!(end_point_expected.distance_to(&end_point_new) < 0.01); } #[test] fn offset_lms_sampling_arc_fit_single_curve() { - use flo_curves::arc::*; + use flo_curves::arc::Circle; // 90 degree circle arc - let circle = Circle::new(Coord2(0.0, 0.0), 100.0); - let arc = circle.arc(0.0, f64::consts::PI / 2.0); - let arc_curve = arc.to_bezier_curve::>(); + let circle = Circle::new(Coord2(0.0, 0.0), 100.0); + let arc = circle.arc(0.0, f64::consts::PI / 2.0); + let arc_curve = arc.to_bezier_curve::>(); // Offset by 10 - let offset_arc = offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 1.0).expect("Offset curve"); + let offset_arc = + offset_lms_sampling(&arc_curve, |_t| 10.0, |_t| 0.0, 20, 1.0).expect("Offset curve"); // We should be able to find a single bezier curve that fits these points assert!(offset_arc.len() == 1); diff --git a/tests/bezier/overlaps.rs b/tests/bezier/overlaps.rs index c6f88adb..6111239d 100644 --- a/tests/bezier/overlaps.rs +++ b/tests/bezier/overlaps.rs @@ -1,8 +1,12 @@ -use flo_curves::bezier::*; +use flo_curves::bezier::{overlapping_region, BezierCurve, BezierCurveFactory, Coord2, Curve}; #[test] fn simple_overlapping_curves() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); let section = curve1.section(0.3333, 0.6666); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -13,7 +17,11 @@ fn simple_overlapping_curves() { #[test] fn simple_overlapping_curves_curve2_larger() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); let section = curve1.section(0.3333, 0.6666); let overlaps = overlapping_region(§ion, &curve1).unwrap(); @@ -27,8 +35,16 @@ fn simple_overlapping_curves_curve2_larger() { #[test] fn simple_overlapping_curves_same() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); - let section = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); + let section = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -38,8 +54,16 @@ fn simple_overlapping_curves_same() { #[test] fn simple_overlapping_curves_reversed() { - let curve1 = Curve::from_points(Coord2(220.0, 220.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(10.0, 100.0)); - let section = Curve::from_points(Coord2(10.0, 100.0), (Coord2(40.0, 140.0), Coord2(90.0, 30.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(220.0, 220.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(10.0, 100.0), + ); + let section = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(40.0, 140.0), Coord2(90.0, 30.0)), + Coord2(220.0, 220.0), + ); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -49,7 +73,11 @@ fn simple_overlapping_curves_reversed() { #[test] fn simple_overlapping_lines() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), Coord2(220.0, 100.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), + Coord2(220.0, 100.0), + ); let section = curve1.section(0.3333, 0.6666); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -60,8 +88,16 @@ fn simple_overlapping_lines() { #[test] fn overlapping_lines_same() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), Coord2(220.0, 100.0)); - let section = Curve::from_points(Coord2(10.0, 100.0), (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), Coord2(220.0, 100.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), + Coord2(220.0, 100.0), + ); + let section = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), + Coord2(220.0, 100.0), + ); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -71,8 +107,16 @@ fn overlapping_lines_same() { #[test] fn overlapping_lines_with_different_t_values() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), Coord2(220.0, 100.0)); - let section = Curve::from_points(Coord2(10.0, 100.0), (Coord2(50.0, 100.0), Coord2(180.0, 100.0)), Coord2(220.0, 100.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(30.0, 100.0), Coord2(200.0, 100.0)), + Coord2(220.0, 100.0), + ); + let section = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(50.0, 100.0), Coord2(180.0, 100.0)), + Coord2(220.0, 100.0), + ); let overlaps = overlapping_region(&curve1, §ion).unwrap(); @@ -83,34 +127,82 @@ fn overlapping_lines_with_different_t_values() { #[test] fn overlaps_with_known_curve_1() { // These curves should overlap - let curve1 = Curve::from_points(Coord2(346.69864, 710.2048), (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), Coord2(356.28525, 698.20306)); - let curve2 = Curve::from_points(Coord2(350.22574, 706.551), (Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), Coord2(361.0284, 690.2511)); + let curve1 = Curve::from_points( + Coord2(346.69864, 710.2048), + (Coord2(350.41446, 706.8076), Coord2(353.61026, 702.4266)), + Coord2(356.28525, 698.20306), + ); + let curve2 = Curve::from_points( + Coord2(350.22574, 706.551), + (Coord2(354.72943, 701.2933), Coord2(358.0882, 695.26)), + Coord2(361.0284, 690.2511), + ); // They currently don't - assert!(curve1.t_for_point(&curve2.start_point()).is_some() || curve2.t_for_point(&curve1.start_point()).is_some()); - assert!(curve1.t_for_point(&curve2.end_point()).is_some() || curve2.t_for_point(&curve1.end_point()).is_some()); - - assert!(!overlapping_region(&curve1, &curve2).is_some()); + assert!( + curve1.t_for_point(&curve2.start_point()).is_some() + || curve2.t_for_point(&curve1.start_point()).is_some() + ); + assert!( + curve1.t_for_point(&curve2.end_point()).is_some() + || curve2.t_for_point(&curve1.end_point()).is_some() + ); + + assert!(overlapping_region(&curve1, &curve2).is_none()); } #[test] fn overlaps_with_known_curve_2() { // These curves should overlap - let curve1 = Curve::from_points(Coord2(305.86907958984375, 882.2529296875), (Coord2(305.41015625, 880.7345581054688), Coord2(303.0707092285156, 879.744140625)), Coord2(298.0640869140625, 875.537353515625)); - let curve2 = Curve::from_points(Coord2(302.7962341308594, 879.1681518554688), (Coord2(299.5769348144531, 876.8582763671875), Coord2(297.1976318359375, 874.7939453125)), Coord2(301.4282531738281, 878.26220703125)); + let curve1 = Curve::from_points( + Coord2(305.86907958984375, 882.2529296875), + ( + Coord2(305.41015625, 880.7345581054688), + Coord2(303.0707092285156, 879.744140625), + ), + Coord2(298.0640869140625, 875.537353515625), + ); + let curve2 = Curve::from_points( + Coord2(302.7962341308594, 879.1681518554688), + ( + Coord2(299.5769348144531, 876.8582763671875), + Coord2(297.1976318359375, 874.7939453125), + ), + Coord2(301.4282531738281, 878.26220703125), + ); // They currently don't - assert!(!curve1.t_for_point(&curve2.start_point()).is_some() || curve2.t_for_point(&curve1.start_point()).is_some()); - assert!(curve1.t_for_point(&curve2.end_point()).is_some() || curve2.t_for_point(&curve1.end_point()).is_some()); - - assert!(!overlapping_region(&curve1, &curve2).is_some()); + assert!( + curve1.t_for_point(&curve2.start_point()).is_none() + || curve2.t_for_point(&curve1.start_point()).is_some() + ); + assert!( + curve1.t_for_point(&curve2.end_point()).is_some() + || curve2.t_for_point(&curve1.end_point()).is_some() + ); + + assert!(overlapping_region(&curve1, &curve2).is_none()); } #[test] fn overlaps_with_known_curve_3() { // These curves should overlap - let curve1 = Curve::from_points(Coord2(510.6888427734375, 684.9293212890625), (Coord2(511.68206787109375, 683.7874145507813), Coord2(512.7827758789063, 682.6954345703125)), Coord2(513.9757080078125, 681.668212890625)); - let curve2 = Curve::from_points(Coord2(510.6888427734375, 684.9293212890625), (Coord2(511.66473388671875, 683.8077392578125), Coord2(512.7447509765625, 682.73388671875)), Coord2(513.9143676757813, 681.7202758789063)); + let curve1 = Curve::from_points( + Coord2(510.6888427734375, 684.9293212890625), + ( + Coord2(511.68206787109375, 683.7874145507813), + Coord2(512.7827758789063, 682.6954345703125), + ), + Coord2(513.9757080078125, 681.668212890625), + ); + let curve2 = Curve::from_points( + Coord2(510.6888427734375, 684.9293212890625), + ( + Coord2(511.66473388671875, 683.8077392578125), + Coord2(512.7447509765625, 682.73388671875), + ), + Coord2(513.9143676757813, 681.7202758789063), + ); assert!(overlapping_region(&curve1, &curve2).is_some()); assert!(overlapping_region(&curve2, &curve1).is_some()); diff --git a/tests/bezier/path/arithmetic_add.rs b/tests/bezier/path/arithmetic_add.rs index 03af4d84..9041151f 100644 --- a/tests/bezier/path/arithmetic_add.rs +++ b/tests/bezier/path/arithmetic_add.rs @@ -1,9 +1,13 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; -use flo_curves::debug::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + path_add, path_combine, path_remove_interior_points, path_remove_overlapped_points, BezierPath, + BezierPathBuilder, BezierPathFactory, GraphPath, PathCombine, PathDirection, PathLabel, + SimpleBezierPath, +}; +use flo_curves::debug::graph_path_svg_string; +use flo_curves::{BezierCurve, BoundingBox, Coord2, Coordinate, Line}; -use super::svg::*; +use super::svg::svg_path_string; #[test] fn add_two_overlapping_circles() { @@ -12,32 +16,46 @@ fn add_two_overlapping_circles() { let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -51,18 +69,21 @@ fn add_two_identical_circles() { let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point).collect::>(); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point) + .collect::>(); for point in points.iter() { - let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); + let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01); + assert!((distance_to_circle1 - 4.0).abs() < 0.01); println!("{:?} {:?}", point, distance_to_circle1); } @@ -77,21 +98,23 @@ fn add_two_very_close_circles() { let circle2 = Circle::new(Coord2(5.01, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); println!("{:?}", combined_circles.len()); - assert!(combined_circles.len() != 0); + assert!(!combined_circles.is_empty()); assert!(combined_circles.len() != 2); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.1); + assert!((distance_to_circle1 - 4.0).abs() < 0.1); println!("{:?} {:?}", point, distance_to_circle1); } @@ -104,20 +127,22 @@ fn add_two_close_circles() { let circle2 = Circle::new(Coord2(503.0002955064407, 5.0), 300.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); - assert!(combined_circles.len() != 0); + assert!(!combined_circles.is_empty()); assert!(combined_circles.len() != 2); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); for point in points { let distance_to_circle1 = Coord2(500.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-300.0).abs() < 10.0); + assert!((distance_to_circle1 - 300.0).abs() < 10.0); println!("{:?} {:?}", point, distance_to_circle1); } @@ -130,32 +155,52 @@ fn add_two_overlapping_circles_via_combination_chain() { let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_combine::(PathCombine::Add(vec![PathCombine::Path(vec![circle1]), PathCombine::Path(vec![circle2])]), 0.01); + let combined_circles = path_combine::( + PathCombine::Add(vec![ + PathCombine::Path(vec![circle1]), + PathCombine::Path(vec![circle2]), + ]), + 0.01, + ); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -165,11 +210,14 @@ fn add_two_overlapping_circles_via_combination_chain() { #[test] fn add_series_of_circles_via_combination_chain() { // Two overlapping circles - let circles = (0..4).into_iter() - .map(|idx| Circle::new(Coord2(5.0 + (idx as f64)*2.0, 4.0), 4.0).to_path::()) + let circles = (0..4) + .into_iter() + .map(|idx| { + Circle::new(Coord2(5.0 + (idx as f64) * 2.0, 4.0), 4.0).to_path::() + }) .map(|circle| PathCombine::Path(vec![circle])); - let combine = PathCombine::Add(circles.collect()); - let combined_circles = path_combine::(combine, 0.01); + let combine = PathCombine::Add(circles.collect()); + let combined_circles = path_combine::(combine, 0.01); assert!(combined_circles.len() == 1); } @@ -181,21 +229,25 @@ fn add_circle_inside_circle() { let circle2 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; + let mut num_points_on_circle1 = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); // Must be on the circle - assert!((distance_to_circle1-4.0).abs() < 0.01); - if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } + assert!((distance_to_circle1 - 4.0).abs() < 0.01); + if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } } assert!(num_points_on_circle1 == 4); @@ -208,32 +260,46 @@ fn add_two_overlapping_circles_further_apart() { let circle2 = Circle::new(Coord2(12.9, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(12.9, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 4); assert!(num_points_on_circle2 == 4); @@ -248,33 +314,47 @@ fn add_two_overlapping_circles_with_one_reversed() { let circle2 = circle2.reversed::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); println!("{:?}", combined_circles); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -288,7 +368,7 @@ fn add_two_non_overlapping_circles() { let circle2 = Circle::new(Coord2(20.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let combined_circles = path_add::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); println!("{:?}", combined_circles); assert!(combined_circles.len() == 2); @@ -297,10 +377,10 @@ fn add_two_non_overlapping_circles() { #[test] fn add_two_doughnuts() { // Two overlapping circles - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); println!("{}", svg_path_string(&circle1)); println!("{}", svg_path_string(&inner_circle1)); @@ -308,18 +388,28 @@ fn add_two_doughnuts() { println!("{}", svg_path_string(&inner_circle2)); // Combine them - let combined_circles = path_add::<_, _, SimpleBezierPath>(&vec![circle1, inner_circle1], &vec![circle2, inner_circle2], 0.09); + let combined_circles = path_add::<_, _, SimpleBezierPath>( + &[circle1, inner_circle1], + &[circle2, inner_circle2], + 0.09, + ); println!("{:?}", combined_circles.len()); println!("{:?}", combined_circles); - println!("{:?}", combined_circles.iter().map(|path| svg_path_string(path)).collect::>()); + println!( + "{:?}", + combined_circles + .iter() + .map(svg_path_string) + .collect::>() + ); assert!(combined_circles.len() == 4); } #[test] fn remove_interior_from_circle_removes_nothing() { - let circle = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![circle.clone()], 0.1); + let circle = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let removed = path_remove_interior_points::<_, SimpleBezierPath>(&[circle.clone()], 0.1); assert!(removed.len() == 1); assert!(removed[0].1.len() == circle.1.len()); @@ -333,10 +423,10 @@ fn remove_interior_from_circle_removes_nothing() { #[test] fn remove_interior_for_ring_removes_center() { - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone()], 0.01); + let removed = path_remove_interior_points::<_, SimpleBezierPath>(&[ring1.clone(), ring2], 0.01); assert!(removed.len() == 1); @@ -349,16 +439,19 @@ fn remove_interior_for_ring_removes_center() { #[test] fn remove_interior_for_ring_with_crossbar_removes_center() { - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) .line_to(Coord2(0.2, 2.1)) .line_to(Coord2(3.8, 2.1)) .line_to(Coord2(3.8, 1.9)) .line_to(Coord2(0.2, 1.9)) .build(); - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone(), crossbar1.clone()], 0.01); + let removed = path_remove_interior_points::<_, SimpleBezierPath>( + &[ring1.clone(), ring2, crossbar1], + 0.01, + ); assert!(removed.len() == 1); @@ -371,9 +464,9 @@ fn remove_interior_for_ring_with_crossbar_removes_center() { #[test] fn remove_interior_for_ring_with_offset_crossbar_removes_center() { - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.7).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 0.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.7).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 0.9)) .line_to(Coord2(0.2, 1.1)) .line_to(Coord2(3.8, 1.1)) .line_to(Coord2(3.8, 0.9)) @@ -383,7 +476,10 @@ fn remove_interior_for_ring_with_offset_crossbar_removes_center() { // Create the graph path from the source side let path = vec![ring1.clone(), ring2.clone(), crossbar1.clone()]; let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path.iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path.iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide the path with itself to find the intersections merged_path.self_collide(0.01); @@ -394,7 +490,8 @@ fn remove_interior_for_ring_with_offset_crossbar_removes_center() { println!("{}", graph_path_svg_string(&merged_path, vec![])); // Try the actual removing operation - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone(), crossbar1.clone()], 0.01); + let removed = + path_remove_interior_points::<_, SimpleBezierPath>(&[ring1, ring2, crossbar1], 0.01); assert!(removed.len() == 1); } @@ -402,43 +499,52 @@ fn remove_interior_for_ring_with_offset_crossbar_removes_center() { #[test] fn ring_with_offset_crossbar_ray_casting_issue() { // Hit a bug where the ray (Coord2(0.3853378796624052, 0.7560017173290998), Coord2(0.385337879662404, 1.0999999999999999)) seems to be missing intersections - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.7).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 0.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.7).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 0.9)) .line_to(Coord2(0.2, 1.1)) .line_to(Coord2(3.8, 1.1)) .line_to(Coord2(3.8, 0.9)) .line_to(Coord2(0.2, 0.9)) .build(); - let path = vec![ring1.clone(), ring2.clone(), crossbar1.clone()]; + let path = vec![ring1, ring2, crossbar1]; let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(path.iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + path.iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); merged_path.self_collide(0.01); merged_path.round(0.01); - let ray_cast = merged_path.ray_collisions(&(Coord2(0.3853378796624052, 0.7560017173290998), Coord2(0.385337879662404, 1.0999999999999999))); + let ray_cast = merged_path.ray_collisions(&( + Coord2(0.3853378796624052, 0.7560017173290998), + Coord2(0.385337879662404, 1.0999999999999999), + )); println!("{}: {:?}", ray_cast.len(), ray_cast); assert!(ray_cast.len() == 6); } #[test] fn remove_interior_for_ring_with_cross_removes_center() { - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) .line_to(Coord2(0.2, 2.1)) .line_to(Coord2(3.8, 2.1)) .line_to(Coord2(3.8, 1.9)) .build(); - let crossbar2 = BezierPathBuilder::::start(Coord2(1.9, 0.2)) + let crossbar2 = BezierPathBuilder::::start(Coord2(1.9, 0.2)) .line_to(Coord2(2.1, 0.2)) .line_to(Coord2(2.1, 3.8)) .line_to(Coord2(1.9, 3.8)) .build(); - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone(), crossbar1.clone(), crossbar2.clone()], 0.01); + let removed = path_remove_interior_points::<_, SimpleBezierPath>( + &[ring1.clone(), ring2, crossbar1, crossbar2], + 0.01, + ); // Check the removed result assert!(removed.len() == 1); @@ -452,10 +558,11 @@ fn remove_interior_for_ring_with_cross_removes_center() { #[test] fn remove_overlapped_for_ring_does_not_remove_center() { - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let removed = path_remove_overlapped_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone()], 0.01); + let removed = + path_remove_overlapped_points::<_, SimpleBezierPath>(&[ring1.clone(), ring2.clone()], 0.01); assert!(removed.len() == 2); @@ -475,16 +582,17 @@ fn remove_overlapped_for_ring_does_not_remove_center() { #[test] fn remove_overlapped_for_ring_with_overlapping_crossbar() { // Crossbar overlaps both the main ring and the center - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(0.2, 1.9)) .line_to(Coord2(0.2, 2.1)) .line_to(Coord2(3.8, 2.1)) .line_to(Coord2(3.8, 1.9)) .line_to(Coord2(0.2, 1.9)) .build(); - let removed = path_remove_overlapped_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone(), crossbar1.clone()], 0.01); + let removed = + path_remove_overlapped_points::<_, SimpleBezierPath>(&[ring1, ring2, crossbar1], 0.01); assert!(removed.len() == 5); } @@ -492,15 +600,16 @@ fn remove_overlapped_for_ring_with_overlapping_crossbar() { #[test] fn remove_overlapped_for_ring_with_crossbar_in_space() { // Crossbar is floating in space here - let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); - let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); - let crossbar1 = BezierPathBuilder::::start(Coord2(1.6, 0.9)) + let ring1 = Circle::new(Coord2(2.0, 2.0), 2.0).to_path::(); + let ring2 = Circle::new(Coord2(2.0, 2.0), 1.5).to_path::(); + let crossbar1 = BezierPathBuilder::::start(Coord2(1.6, 0.9)) .line_to(Coord2(1.6, 1.1)) .line_to(Coord2(2.4, 1.1)) .line_to(Coord2(2.4, 0.9)) .build(); - let removed = path_remove_overlapped_points::<_, SimpleBezierPath>(&vec![ring1.clone(), ring2.clone(), crossbar1.clone()], 0.01); + let removed = + path_remove_overlapped_points::<_, SimpleBezierPath>(&[ring1, ring2, crossbar1], 0.01); assert!(removed.len() == 3); } @@ -516,7 +625,8 @@ fn remove_interior_points_basic() { .line_to(Coord2(1.0, 1.0)) .build(); - let with_points_removed: Vec = path_remove_interior_points(&vec![with_interior_point], 0.1); + let with_points_removed: Vec = + path_remove_interior_points(&[with_interior_point], 0.1); // Should be 5 points in the path with points removed assert!(with_points_removed.len() == 1); @@ -528,12 +638,16 @@ fn remove_interior_points_basic() { Coord2(1.0, 5.0), Coord2(5.0, 5.0), Coord2(5.0, 1.0), - Coord2(3.0, 3.0) + Coord2(3.0, 3.0), ]; - assert!(expected_points.iter().any(|expected| with_points_removed[0].start_point().distance_to(expected) < 0.1)); + assert!(expected_points + .iter() + .any(|expected| with_points_removed[0].start_point().distance_to(expected) < 0.1)); for (_cp1, _cp2, point) in with_points_removed[0].points() { - assert!(expected_points.iter().any(|expected| point.distance_to(expected) < 0.1)); + assert!(expected_points + .iter() + .any(|expected| point.distance_to(expected) < 0.1)); } } @@ -548,11 +662,11 @@ fn self_collide_is_stable() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&with_interior_point, ()); + let mut graph_path = GraphPath::from_path(&with_interior_point, ()); graph_path.self_collide(0.1); - let initial_num_points = graph_path.num_points(); - let initial_num_edges = graph_path.all_edges().count(); + let initial_num_points = graph_path.num_points(); + let initial_num_edges = graph_path.all_edges().count(); graph_path.self_collide(0.1); assert!(graph_path.all_edges().count() == initial_num_edges); @@ -598,12 +712,12 @@ fn rectangle_add() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -635,12 +749,12 @@ fn rectangle_add_with_shared_point() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -672,12 +786,12 @@ fn rectangle_add_with_shared_point_2() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -711,12 +825,12 @@ fn rectangle_add_with_shared_point_3() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -751,17 +865,20 @@ fn rectangle_add_with_shared_point_4() { .reversed::(); // Print out the graph path generated by adding these two points - let mut gp = GraphPath::from_path(&rectangle1, PathLabel(0, PathDirection::Clockwise)).collide(GraphPath::from_path(&rectangle2, PathLabel(1, PathDirection::Clockwise)), 0.01); + let mut gp = GraphPath::from_path(&rectangle1, PathLabel(0, PathDirection::Clockwise)).collide( + GraphPath::from_path(&rectangle2, PathLabel(1, PathDirection::Clockwise)), + 0.01, + ); gp.set_exterior_by_adding(); println!("{:?}", gp); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -796,12 +913,12 @@ fn rectangle_add_with_shared_point_5() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(1.0, 5.0)) < 0.1); @@ -834,12 +951,12 @@ fn rectangle_add_with_shared_point_6() { .build(); // Add them - let shared_point = path_add::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let shared_point = path_add::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(1.0, 5.0)) < 0.1); diff --git a/tests/bezier/path/arithmetic_chain_add.rs b/tests/bezier/path/arithmetic_chain_add.rs index eeeaaac9..4a94dc99 100644 --- a/tests/bezier/path/arithmetic_chain_add.rs +++ b/tests/bezier/path/arithmetic_chain_add.rs @@ -1,8 +1,11 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + path_add_chain, path_remove_interior_points, BezierPath, BezierPathBuilder, BezierPathFactory, + GraphPath, SimpleBezierPath, +}; +use flo_curves::{BezierCurve, Coord2, Coordinate, Line}; -use super::svg::*; +use super::svg::svg_path_string; #[test] fn add_two_overlapping_circles() { @@ -11,32 +14,47 @@ fn add_two_overlapping_circles() { let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); + let combined_circles = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -50,21 +68,26 @@ fn add_circle_inside_circle() { let circle2 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); + let combined_circles = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; + let mut num_points_on_circle1 = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); // Must be on the circle - assert!((distance_to_circle1-4.0).abs() < 0.01); - if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } + assert!((distance_to_circle1 - 4.0).abs() < 0.01); + if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } } assert!(num_points_on_circle1 == 4); @@ -77,32 +100,47 @@ fn add_two_overlapping_circles_further_apart() { let circle2 = Circle::new(Coord2(12.9, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); + let combined_circles = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(12.9, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 4); assert!(num_points_on_circle2 == 4); @@ -117,33 +155,48 @@ fn add_two_overlapping_circles_with_one_reversed() { let circle2 = circle2.reversed::(); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); + let combined_circles = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.01); println!("{:?}", combined_circles); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -157,7 +210,8 @@ fn add_two_non_overlapping_circles() { let circle2 = Circle::new(Coord2(20.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.1); + let combined_circles = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1], vec![circle2]], 0.1); println!("{:?}", combined_circles); assert!(combined_circles.len() == 2); @@ -166,10 +220,10 @@ fn add_two_non_overlapping_circles() { #[test] fn add_two_doughnuts() { // Two overlapping circles - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); println!("{}", svg_path_string(&circle1)); println!("{}", svg_path_string(&inner_circle1)); @@ -177,11 +231,20 @@ fn add_two_doughnuts() { println!("{}", svg_path_string(&inner_circle2)); // Combine them - let combined_circles = path_add_chain::<_, SimpleBezierPath>(&vec![vec![circle1, inner_circle1], vec![circle2, inner_circle2]], 0.09); + let combined_circles = path_add_chain::<_, SimpleBezierPath>( + &vec![vec![circle1, inner_circle1], vec![circle2, inner_circle2]], + 0.09, + ); println!("{:?}", combined_circles.len()); println!("{:?}", combined_circles); - println!("{:?}", combined_circles.iter().map(|path| svg_path_string(path)).collect::>()); + println!( + "{:?}", + combined_circles + .iter() + .map(svg_path_string) + .collect::>() + ); assert!(combined_circles.len() == 4); } @@ -196,7 +259,8 @@ fn remove_interior_points_basic() { .line_to(Coord2(1.0, 1.0)) .build(); - let with_points_removed: Vec = path_remove_interior_points(&vec![with_interior_point], 0.1); + let with_points_removed: Vec = + path_remove_interior_points(&[with_interior_point], 0.1); // Should be 5 points in the path with points removed assert!(with_points_removed.len() == 1); @@ -208,12 +272,16 @@ fn remove_interior_points_basic() { Coord2(1.0, 5.0), Coord2(5.0, 5.0), Coord2(5.0, 1.0), - Coord2(3.0, 3.0) + Coord2(3.0, 3.0), ]; - assert!(expected_points.iter().any(|expected| with_points_removed[0].start_point().distance_to(expected) < 0.1)); + assert!(expected_points + .iter() + .any(|expected| with_points_removed[0].start_point().distance_to(expected) < 0.1)); for (_cp1, _cp2, point) in with_points_removed[0].points() { - assert!(expected_points.iter().any(|expected| point.distance_to(expected) < 0.1)); + assert!(expected_points + .iter() + .any(|expected| point.distance_to(expected) < 0.1)); } } @@ -256,12 +324,13 @@ fn rectangle_add() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -293,12 +362,13 @@ fn rectangle_add_with_shared_point() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -330,12 +400,13 @@ fn rectangle_add_with_shared_point_2() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -369,12 +440,13 @@ fn rectangle_add_with_shared_point_3() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -409,12 +481,13 @@ fn rectangle_add_with_shared_point_5() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(1.0, 5.0)) < 0.1); @@ -447,12 +520,13 @@ fn rectangle_add_with_shared_point_6() { .build(); // Add them - let shared_point = path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); + let shared_point = + path_add_chain::<_, SimpleBezierPath>(&vec![vec![rectangle1], vec![rectangle2]], 0.01); assert!(shared_point.len() == 1); - let shared_point = &shared_point[0]; - let points = shared_point.points().collect::>(); + let shared_point = &shared_point[0]; + let points = shared_point.points().collect::>(); assert!(shared_point.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(1.0, 5.0)) < 0.1); diff --git a/tests/bezier/path/arithmetic_complicated_paths.rs b/tests/bezier/path/arithmetic_complicated_paths.rs index daf72b95..56ec45e7 100644 --- a/tests/bezier/path/arithmetic_complicated_paths.rs +++ b/tests/bezier/path/arithmetic_complicated_paths.rs @@ -1,76 +1,420 @@ -use flo_curves::*; -use flo_curves::debug::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{ + path_remove_interior_points, BezierPathBuilder, BezierPathFactory, GraphPath, + GraphPathEdgeKind, PathDirection, PathLabel, SimpleBezierPath, +}; +use flo_curves::debug::graph_path_svg_string; +use flo_curves::{BoundingBox, Coord2, Coordinate}; -use super::svg::*; +use super::svg::svg_path_string; #[test] fn remove_interior_points_1() { // Complicated curve found in FlowBetween that produces 0 points when interior points are removed - // It appears this has three curves that converge on a single point, which generates two points in the output, + // It appears this has three curves that converge on a single point, which generates two points in the output, // which in turn produces a spurious edge, which prevents us from being able to follow the path all the way around. - let curve = BezierPathBuilder::::start(Coord2(562.0692138671875, 669.944580078125)) - .curve_to((Coord2(562.0692138671875, 669.944580078125), Coord2(562.0692138671875, 669.944580078125)), Coord2(562.0692138671875, 669.944580078125)) - .curve_to((Coord2(562.4200439453125, 669.9562377929688), Coord2(562.6718139648438, 670.0160522460938)), Coord2(562.8291015625, 670.0160522460938)) - .curve_to((Coord2(562.7747802734375, 670.2525634765625), Coord2(563.1968383789063, 669.9431762695313)), Coord2(563.401611328125, 669.6962890625)) - .curve_to((Coord2(563.218505859375, 669.447021484375), Coord2(562.7468872070313, 668.9757690429688)), Coord2(562.6525268554688, 669.1633911132813)) - .curve_to((Coord2(562.3690185546875, 669.1181640625), Coord2(562.02490234375, 668.9761352539063)), Coord2(561.6610107421875, 668.9097290039063)) - .curve_to((Coord2(560.6327514648438, 668.3336181640625), Coord2(560.5078125, 668.680419921875)), Coord2(560.7962036132813, 668.913818359375)) - .curve_to((Coord2(560.7932739257813, 669.053955078125), Coord2(560.795166015625, 668.9855346679688)), Coord2(560.7711791992188, 669.1161499023438)) - .curve_to((Coord2(560.0169067382813, 670.2783813476563), Coord2(561.4442749023438, 669.9208984375)), Coord2(561.7700805664063, 668.3951416015625)) - .curve_to((Coord2(560.5978393554688, 668.2579956054688), Coord2(555.1843872070313, 665.8880615234375)), Coord2(552.6854248046875, 664.9908447265625)) - .curve_to((Coord2(552.62158203125, 664.9489135742188), Coord2(552.4188842773438, 664.8121948242188)), Coord2(552.1951293945313, 664.701171875)) - .curve_to((Coord2(552.0418090820313, 669.23193359375), Coord2(555.0795288085938, 667.7922973632813)), Coord2(555.9325561523438, 664.439697265625)) - .curve_to((Coord2(555.8035278320313, 663.3936767578125), Coord2(543.4547729492188, 664.566162109375)), Coord2(541.8832397460938, 667.4561767578125)) - .curve_to((Coord2(542.26611328125, 672.0006103515625), Coord2(548.4946899414063, 670.5872192382813)), Coord2(547.83984375, 666.5941772460938)) - .curve_to((Coord2(546.2003784179688, 665.4840087890625), Coord2(543.0369262695313, 665.3294677734375)), Coord2(543.1106567382813, 665.9275512695313)) - .curve_to((Coord2(536.3306274414063, 669.7837524414063), Coord2(541.8121337890625, 670.9800415039063)), Coord2(539.4649658203125, 666.6785888671875)) - .curve_to((Coord2(536.6891479492188, 665.93017578125), Coord2(534.8207397460938, 663.9938354492188)), Coord2(533.337890625, 661.9244995117188)) - .curve_to((Coord2(532.1223754882813, 662.1298828125), Coord2(530.9287109375, 662.1915893554688)), Coord2(534.033203125, 663.5484619140625)) - .curve_to((Coord2(539.8789672851563, 669.0048828125), Coord2(535.4338989257813, 664.3715209960938)), Coord2(530.1646118164063, 657.32666015625)) - .curve_to((Coord2(525.6614379882813, 654.2191162109375), Coord2(526.3388671875, 656.8445434570313)), Coord2(530.332275390625, 658.5115356445313)) - .curve_to((Coord2(530.9607543945313, 663.3235473632813), Coord2(535.1883544921875, 667.216552734375)), Coord2(533.1292724609375, 661.65673828125)) - .curve_to((Coord2(526.8078002929688, 654.7847290039063), Coord2(527.2481689453125, 655.82421875)), Coord2(528.5620727539063, 658.5321044921875)) - .curve_to((Coord2(529.048828125, 663.075927734375), Coord2(530.8765869140625, 662.1258544921875)), Coord2(531.5584106445313, 659.6661987304688)) - .curve_to((Coord2(530.1249389648438, 657.940185546875), Coord2(529.1561889648438, 657.2536010742188)), Coord2(528.7389526367188, 655.5059814453125)) - .curve_to((Coord2(527.8021240234375, 654.7122192382813), Coord2(529.899658203125, 656.5814819335938)), Coord2(531.8333740234375, 654.7963256835938)) - .curve_to((Coord2(538.0204467773438, 653.547119140625), Coord2(542.1532592773438, 652.2764892578125)), Coord2(544.957275390625, 652.1034545898438)) - .curve_to((Coord2(545.7479858398438, 652.0574340820313), Coord2(546.3248291015625, 651.8165283203125)), Coord2(546.8508911132813, 651.8157958984375)) - .curve_to((Coord2(548.2747802734375, 652.2127685546875), Coord2(548.1990356445313, 651.2047119140625)), Coord2(547.912109375, 650.8655395507813)) - .curve_to((Coord2(547.7791748046875, 650.193359375), Coord2(549.1414184570313, 650.476806640625)), Coord2(548.0958251953125, 650.5689086914063)) - .curve_to((Coord2(548.0958251953125, 650.7786865234375), Coord2(548.0958251953125, 651.0584716796875)), Coord2(548.0958251953125, 651.2682495117188)) - .curve_to((Coord2(548.9656982421875, 651.643798828125), Coord2(547.8914184570313, 651.3145141601563)), Coord2(549.1207275390625, 650.8655395507813)) - .curve_to((Coord2(548.8338012695313, 650.4108276367188), Coord2(547.700927734375, 649.2344360351563)), Coord2(546.8508911132813, 649.63134765625)) - .curve_to((Coord2(546.3272705078125, 649.630615234375), Coord2(545.5951538085938, 649.4255981445313)), Coord2(544.8019409179688, 649.4730834960938)) - .curve_to((Coord2(542.03857421875, 649.6287841796875), Coord2(537.2066040039063, 649.3989868164063)), Coord2(530.6641845703125, 650.7567138671875)) - .curve_to((Coord2(529.2568359375, 650.2000122070313), Coord2(525.3572998046875, 653.1232299804688)), Coord2(525.4530639648438, 656.3499145507813)) - .curve_to((Coord2(526.0859985351563, 658.6912231445313), Coord2(527.9020385742188, 660.7957763671875)), Coord2(529.1198120117188, 661.9281616210938)) - .curve_to((Coord2(532.0623779296875, 661.90576171875), Coord2(533.4664306640625, 660.0416259765625)), Coord2(529.3554077148438, 657.97998046875)) - .curve_to((Coord2(526.156005859375, 654.2037353515625), Coord2(522.1826782226563, 656.4036254882813)), Coord2(530.3896484375, 664.3438720703125)) - .curve_to((Coord2(536.754150390625, 667.3721923828125), Coord2(535.8456420898438, 660.3375854492188)), Coord2(530.91162109375, 658.0571899414063)) - .curve_to((Coord2(529.3756103515625, 652.6741333007813), Coord2(525.1596069335938, 652.45458984375)), Coord2(527.2052612304688, 659.278076171875)) - .curve_to((Coord2(532.2788696289063, 667.9177856445313), Coord2(540.3832397460938, 669.9564208984375)), Coord2(534.7332763671875, 662.905029296875)) - .curve_to((Coord2(532.432373046875, 658.3805541992188), Coord2(530.3565063476563, 660.47900390625)), Coord2(530.7684326171875, 663.4412841796875)) - .curve_to((Coord2(531.7405395507813, 665.5307006835938), Coord2(535.3882446289063, 669.1942138671875)), Coord2(538.6748046875, 669.9224853515625)) - .curve_to((Coord2(545.757080078125, 667.9179077148438), Coord2(541.6903686523438, 667.3967895507813)), Coord2(543.7351684570313, 669.8466186523438)) - .curve_to((Coord2(545.7384643554688, 670.13720703125), Coord2(545.3059692382813, 669.0546875)), Coord2(544.8681640625, 669.27587890625)) - .curve_to((Coord2(544.5274047851563, 665.6309204101563), Coord2(545.35498046875, 665.0091552734375)), Coord2(545.9674682617188, 668.1023559570313)) - .curve_to((Coord2(544.9426879882813, 667.5361328125), Coord2(553.4862670898438, 669.1529541015625)), Coord2(556.2109375, 667.8751831054688)) - .curve_to((Coord2(557.3668823242188, 664.4981079101563), Coord2(550.9618530273438, 662.6256103515625)), Coord2(549.96337890625, 666.1478271484375)) - .curve_to((Coord2(551.0449829101563, 667.5820922851563), Coord2(551.2767333984375, 667.6608276367188)), Coord2(551.4939575195313, 667.7685546875)) - .curve_to((Coord2(554.0316772460938, 668.719970703125), Coord2(560.2760620117188, 670.0292358398438)), Coord2(561.5628051757813, 670.177978515625)) - .curve_to((Coord2(562.9513549804688, 668.7757568359375), Coord2(560.3701782226563, 666.8861694335938)), Coord2(559.4100952148438, 668.6519775390625)) - .curve_to((Coord2(559.3812255859375, 668.7340698242188), Coord2(559.3759765625, 668.8223876953125)), Coord2(559.3749389648438, 668.913818359375)) - .curve_to((Coord2(559.663330078125, 669.5628662109375), Coord2(561.01806640625, 670.2659912109375)), Coord2(561.4695434570313, 669.9596557617188)) - .curve_to((Coord2(561.845458984375, 670.0281982421875), Coord2(562.2411499023438, 669.9994506835938)), Coord2(562.5125732421875, 670.0426025390625)) - .curve_to((Coord2(562.97314453125, 670.3184814453125), Coord2(562.8713989257813, 669.9105834960938)), Coord2(562.6882934570313, 669.6962890625)) - .curve_to((Coord2(562.89306640625, 669.4701538085938), Coord2(563.1842041015625, 669.1715698242188)), Coord2(562.8291015625, 669.4080810546875)) - .curve_to((Coord2(562.6779174804688, 669.4080810546875), Coord2(562.442626953125, 669.45654296875)), Coord2(562.085693359375, 669.44482421875)) - .build(); + let curve = + BezierPathBuilder::::start(Coord2(562.0692138671875, 669.944580078125)) + .curve_to( + ( + Coord2(562.0692138671875, 669.944580078125), + Coord2(562.0692138671875, 669.944580078125), + ), + Coord2(562.0692138671875, 669.944580078125), + ) + .curve_to( + ( + Coord2(562.4200439453125, 669.9562377929688), + Coord2(562.6718139648438, 670.0160522460938), + ), + Coord2(562.8291015625, 670.0160522460938), + ) + .curve_to( + ( + Coord2(562.7747802734375, 670.2525634765625), + Coord2(563.1968383789063, 669.9431762695313), + ), + Coord2(563.401611328125, 669.6962890625), + ) + .curve_to( + ( + Coord2(563.218505859375, 669.447021484375), + Coord2(562.7468872070313, 668.9757690429688), + ), + Coord2(562.6525268554688, 669.1633911132813), + ) + .curve_to( + ( + Coord2(562.3690185546875, 669.1181640625), + Coord2(562.02490234375, 668.9761352539063), + ), + Coord2(561.6610107421875, 668.9097290039063), + ) + .curve_to( + ( + Coord2(560.6327514648438, 668.3336181640625), + Coord2(560.5078125, 668.680419921875), + ), + Coord2(560.7962036132813, 668.913818359375), + ) + .curve_to( + ( + Coord2(560.7932739257813, 669.053955078125), + Coord2(560.795166015625, 668.9855346679688), + ), + Coord2(560.7711791992188, 669.1161499023438), + ) + .curve_to( + ( + Coord2(560.0169067382813, 670.2783813476563), + Coord2(561.4442749023438, 669.9208984375), + ), + Coord2(561.7700805664063, 668.3951416015625), + ) + .curve_to( + ( + Coord2(560.5978393554688, 668.2579956054688), + Coord2(555.1843872070313, 665.8880615234375), + ), + Coord2(552.6854248046875, 664.9908447265625), + ) + .curve_to( + ( + Coord2(552.62158203125, 664.9489135742188), + Coord2(552.4188842773438, 664.8121948242188), + ), + Coord2(552.1951293945313, 664.701171875), + ) + .curve_to( + ( + Coord2(552.0418090820313, 669.23193359375), + Coord2(555.0795288085938, 667.7922973632813), + ), + Coord2(555.9325561523438, 664.439697265625), + ) + .curve_to( + ( + Coord2(555.8035278320313, 663.3936767578125), + Coord2(543.4547729492188, 664.566162109375), + ), + Coord2(541.8832397460938, 667.4561767578125), + ) + .curve_to( + ( + Coord2(542.26611328125, 672.0006103515625), + Coord2(548.4946899414063, 670.5872192382813), + ), + Coord2(547.83984375, 666.5941772460938), + ) + .curve_to( + ( + Coord2(546.2003784179688, 665.4840087890625), + Coord2(543.0369262695313, 665.3294677734375), + ), + Coord2(543.1106567382813, 665.9275512695313), + ) + .curve_to( + ( + Coord2(536.3306274414063, 669.7837524414063), + Coord2(541.8121337890625, 670.9800415039063), + ), + Coord2(539.4649658203125, 666.6785888671875), + ) + .curve_to( + ( + Coord2(536.6891479492188, 665.93017578125), + Coord2(534.8207397460938, 663.9938354492188), + ), + Coord2(533.337890625, 661.9244995117188), + ) + .curve_to( + ( + Coord2(532.1223754882813, 662.1298828125), + Coord2(530.9287109375, 662.1915893554688), + ), + Coord2(534.033203125, 663.5484619140625), + ) + .curve_to( + ( + Coord2(539.8789672851563, 669.0048828125), + Coord2(535.4338989257813, 664.3715209960938), + ), + Coord2(530.1646118164063, 657.32666015625), + ) + .curve_to( + ( + Coord2(525.6614379882813, 654.2191162109375), + Coord2(526.3388671875, 656.8445434570313), + ), + Coord2(530.332275390625, 658.5115356445313), + ) + .curve_to( + ( + Coord2(530.9607543945313, 663.3235473632813), + Coord2(535.1883544921875, 667.216552734375), + ), + Coord2(533.1292724609375, 661.65673828125), + ) + .curve_to( + ( + Coord2(526.8078002929688, 654.7847290039063), + Coord2(527.2481689453125, 655.82421875), + ), + Coord2(528.5620727539063, 658.5321044921875), + ) + .curve_to( + ( + Coord2(529.048828125, 663.075927734375), + Coord2(530.8765869140625, 662.1258544921875), + ), + Coord2(531.5584106445313, 659.6661987304688), + ) + .curve_to( + ( + Coord2(530.1249389648438, 657.940185546875), + Coord2(529.1561889648438, 657.2536010742188), + ), + Coord2(528.7389526367188, 655.5059814453125), + ) + .curve_to( + ( + Coord2(527.8021240234375, 654.7122192382813), + Coord2(529.899658203125, 656.5814819335938), + ), + Coord2(531.8333740234375, 654.7963256835938), + ) + .curve_to( + ( + Coord2(538.0204467773438, 653.547119140625), + Coord2(542.1532592773438, 652.2764892578125), + ), + Coord2(544.957275390625, 652.1034545898438), + ) + .curve_to( + ( + Coord2(545.7479858398438, 652.0574340820313), + Coord2(546.3248291015625, 651.8165283203125), + ), + Coord2(546.8508911132813, 651.8157958984375), + ) + .curve_to( + ( + Coord2(548.2747802734375, 652.2127685546875), + Coord2(548.1990356445313, 651.2047119140625), + ), + Coord2(547.912109375, 650.8655395507813), + ) + .curve_to( + ( + Coord2(547.7791748046875, 650.193359375), + Coord2(549.1414184570313, 650.476806640625), + ), + Coord2(548.0958251953125, 650.5689086914063), + ) + .curve_to( + ( + Coord2(548.0958251953125, 650.7786865234375), + Coord2(548.0958251953125, 651.0584716796875), + ), + Coord2(548.0958251953125, 651.2682495117188), + ) + .curve_to( + ( + Coord2(548.9656982421875, 651.643798828125), + Coord2(547.8914184570313, 651.3145141601563), + ), + Coord2(549.1207275390625, 650.8655395507813), + ) + .curve_to( + ( + Coord2(548.8338012695313, 650.4108276367188), + Coord2(547.700927734375, 649.2344360351563), + ), + Coord2(546.8508911132813, 649.63134765625), + ) + .curve_to( + ( + Coord2(546.3272705078125, 649.630615234375), + Coord2(545.5951538085938, 649.4255981445313), + ), + Coord2(544.8019409179688, 649.4730834960938), + ) + .curve_to( + ( + Coord2(542.03857421875, 649.6287841796875), + Coord2(537.2066040039063, 649.3989868164063), + ), + Coord2(530.6641845703125, 650.7567138671875), + ) + .curve_to( + ( + Coord2(529.2568359375, 650.2000122070313), + Coord2(525.3572998046875, 653.1232299804688), + ), + Coord2(525.4530639648438, 656.3499145507813), + ) + .curve_to( + ( + Coord2(526.0859985351563, 658.6912231445313), + Coord2(527.9020385742188, 660.7957763671875), + ), + Coord2(529.1198120117188, 661.9281616210938), + ) + .curve_to( + ( + Coord2(532.0623779296875, 661.90576171875), + Coord2(533.4664306640625, 660.0416259765625), + ), + Coord2(529.3554077148438, 657.97998046875), + ) + .curve_to( + ( + Coord2(526.156005859375, 654.2037353515625), + Coord2(522.1826782226563, 656.4036254882813), + ), + Coord2(530.3896484375, 664.3438720703125), + ) + .curve_to( + ( + Coord2(536.754150390625, 667.3721923828125), + Coord2(535.8456420898438, 660.3375854492188), + ), + Coord2(530.91162109375, 658.0571899414063), + ) + .curve_to( + ( + Coord2(529.3756103515625, 652.6741333007813), + Coord2(525.1596069335938, 652.45458984375), + ), + Coord2(527.2052612304688, 659.278076171875), + ) + .curve_to( + ( + Coord2(532.2788696289063, 667.9177856445313), + Coord2(540.3832397460938, 669.9564208984375), + ), + Coord2(534.7332763671875, 662.905029296875), + ) + .curve_to( + ( + Coord2(532.432373046875, 658.3805541992188), + Coord2(530.3565063476563, 660.47900390625), + ), + Coord2(530.7684326171875, 663.4412841796875), + ) + .curve_to( + ( + Coord2(531.7405395507813, 665.5307006835938), + Coord2(535.3882446289063, 669.1942138671875), + ), + Coord2(538.6748046875, 669.9224853515625), + ) + .curve_to( + ( + Coord2(545.757080078125, 667.9179077148438), + Coord2(541.6903686523438, 667.3967895507813), + ), + Coord2(543.7351684570313, 669.8466186523438), + ) + .curve_to( + ( + Coord2(545.7384643554688, 670.13720703125), + Coord2(545.3059692382813, 669.0546875), + ), + Coord2(544.8681640625, 669.27587890625), + ) + .curve_to( + ( + Coord2(544.5274047851563, 665.6309204101563), + Coord2(545.35498046875, 665.0091552734375), + ), + Coord2(545.9674682617188, 668.1023559570313), + ) + .curve_to( + ( + Coord2(544.9426879882813, 667.5361328125), + Coord2(553.4862670898438, 669.1529541015625), + ), + Coord2(556.2109375, 667.8751831054688), + ) + .curve_to( + ( + Coord2(557.3668823242188, 664.4981079101563), + Coord2(550.9618530273438, 662.6256103515625), + ), + Coord2(549.96337890625, 666.1478271484375), + ) + .curve_to( + ( + Coord2(551.0449829101563, 667.5820922851563), + Coord2(551.2767333984375, 667.6608276367188), + ), + Coord2(551.4939575195313, 667.7685546875), + ) + .curve_to( + ( + Coord2(554.0316772460938, 668.719970703125), + Coord2(560.2760620117188, 670.0292358398438), + ), + Coord2(561.5628051757813, 670.177978515625), + ) + .curve_to( + ( + Coord2(562.9513549804688, 668.7757568359375), + Coord2(560.3701782226563, 666.8861694335938), + ), + Coord2(559.4100952148438, 668.6519775390625), + ) + .curve_to( + ( + Coord2(559.3812255859375, 668.7340698242188), + Coord2(559.3759765625, 668.8223876953125), + ), + Coord2(559.3749389648438, 668.913818359375), + ) + .curve_to( + ( + Coord2(559.663330078125, 669.5628662109375), + Coord2(561.01806640625, 670.2659912109375), + ), + Coord2(561.4695434570313, 669.9596557617188), + ) + .curve_to( + ( + Coord2(561.845458984375, 670.0281982421875), + Coord2(562.2411499023438, 669.9994506835938), + ), + Coord2(562.5125732421875, 670.0426025390625), + ) + .curve_to( + ( + Coord2(562.97314453125, 670.3184814453125), + Coord2(562.8713989257813, 669.9105834960938), + ), + Coord2(562.6882934570313, 669.6962890625), + ) + .curve_to( + ( + Coord2(562.89306640625, 669.4701538085938), + Coord2(563.1842041015625, 669.1715698242188), + ), + Coord2(562.8291015625, 669.4080810546875), + ) + .curve_to( + ( + Coord2(562.6779174804688, 669.4080810546875), + Coord2(562.442626953125, 669.45654296875), + ), + Coord2(562.085693359375, 669.44482421875), + ) + .build(); // Create the graph path from the source side let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(vec![&curve].into_iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + vec![&curve] + .into_iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); // Collide the path with itself to find the intersections merged_path.self_collide(0.01); @@ -79,13 +423,17 @@ fn remove_interior_points_1() { println!("{}", graph_path_svg_string(&merged_path, vec![])); println!("{:?}", svg_path_string(&curve)); - let with_points_removed: Vec = path_remove_interior_points(&vec![curve], 0.01); + let with_points_removed: Vec = path_remove_interior_points(&[curve], 0.01); - println!("{:?}", with_points_removed.iter() - .map(|path| svg_path_string(path)) - .collect::>()); + println!( + "{:?}", + with_points_removed + .iter() + .map(svg_path_string) + .collect::>() + ); - assert!(with_points_removed.len() > 0); + assert!(!with_points_removed.is_empty()); } #[test] @@ -151,132 +499,797 @@ fn remove_interior_points_1_without_failing_section() { .build(); println!("{:?}", svg_path_string(&curve)); - let with_points_removed: Vec = path_remove_interior_points(&vec![curve], 0.01); + let with_points_removed: Vec = path_remove_interior_points(&[curve], 0.01); - println!("{:?}", with_points_removed.iter() - .map(|path| svg_path_string(path)) - .collect::>()); + println!( + "{:?}", + with_points_removed + .iter() + .map(svg_path_string) + .collect::>() + ); - assert!(with_points_removed.len() > 0); + assert!(!with_points_removed.is_empty()); } #[test] fn remove_interior_points_complex_2() { - let path = BezierPathBuilder::::start(Coord2(589.8298950195313, 841.699951171875)) - .curve_to((Coord2(589.8298950195313, 841.699951171875), Coord2(589.8298950195313, 841.699951171875)), Coord2(589.8298950195313, 841.699951171875)) - .curve_to((Coord2(585.0781860351563, 841.545166015625), Coord2(588.116943359375, 846.1569213867188)), Coord2(589.9508056640625, 846.92041015625)) - .curve_to((Coord2(593.9074096679688, 850.3338623046875), Coord2(596.3680419921875, 855.8639526367188)), Coord2(600.2550048828125, 860.024169921875)) - .curve_to((Coord2(602.3019409179688, 864.72900390625), Coord2(603.487060546875, 861.721435546875)), Coord2(602.1428833007813, 859.0895385742188)) - .curve_to((Coord2(607.4638061523438, 858.4710693359375), Coord2(614.4444580078125, 855.14404296875)), Coord2(608.3931884765625, 855.6187133789063)) - .curve_to((Coord2(604.7843627929688, 851.9526977539063), Coord2(601.4735107421875, 847.9655151367188)), Coord2(597.78515625, 843.8760986328125)) - .curve_to((Coord2(601.0536499023438, 837.7391357421875), Coord2(590.90966796875, 841.439453125)), Coord2(587.8450927734375, 847.3414916992188)) - .curve_to((Coord2(592.2240600585938, 850.6311645507813), Coord2(595.8001098632813, 856.1324462890625)), Coord2(599.6971435546875, 861.4691772460938)) - .curve_to((Coord2(599.6600952148438, 866.1685546875), Coord2(601.5029907226563, 861.010498046875)), Coord2(601.408447265625, 857.6356811523438)) - .curve_to((Coord2(605.051025390625, 858.197509765625), Coord2(608.0866088867188, 854.1636352539063)), Coord2(597.3378295898438, 846.8604125976563)) - .curve_to((Coord2(597.2238159179688, 836.9576416015625), Coord2(590.7571411132813, 843.5430297851563)), Coord2(587.1199340820313, 848.599365234375)) - .curve_to((Coord2(588.7532348632813, 853.0540161132813), Coord2(591.633544921875, 856.119873046875)), Coord2(594.626708984375, 853.6188354492188)) - .curve_to((Coord2(596.7156982421875, 852.8362426757813), Coord2(595.0059814453125, 845.878662109375)), Coord2(591.52490234375, 845.5113525390625)) - .curve_to((Coord2(585.76171875, 847.6647338867188), Coord2(580.7750244140625, 855.853759765625)), Coord2(586.7627563476563, 853.3876342773438)) - .curve_to((Coord2(588.5208129882813, 859.3195190429688), Coord2(594.2566528320313, 860.6160278320313)), Coord2(592.3621826171875, 860.9254760742188)) - .curve_to((Coord2(594.9733276367188, 864.4375), Coord2(593.3421020507813, 848.7232055664063)), Coord2(586.76220703125, 847.8418579101563)) - .curve_to((Coord2(589.7845458984375, 841.6835327148438), Coord2(583.6079711914063, 848.498046875)), Coord2(580.9037475585938, 853.9146118164063)) - .curve_to((Coord2(580.701904296875, 853.186767578125), Coord2(578.50439453125, 857.2315063476563)), Coord2(581.5901489257813, 860.4940795898438)) - .curve_to((Coord2(585.6346435546875, 863.285400390625), Coord2(589.900146484375, 854.3807373046875)), Coord2(584.1525268554688, 856.2511596679688)) - .curve_to((Coord2(590.3831787109375, 852.05712890625), Coord2(578.9157104492188, 850.2012329101563)), Coord2(574.5430297851563, 856.5203247070313)) - .curve_to((Coord2(573.6943969726563, 863.1355590820313), Coord2(580.0052490234375, 871.26220703125)), Coord2(575.3004760742188, 871.1060791015625)) - .curve_to((Coord2(576.81103515625, 870.624267578125), Coord2(572.30712890625, 859.2913818359375)), Coord2(570.9198608398438, 861.718994140625)) - .curve_to((Coord2(572.5287475585938, 864.7382202148438), Coord2(581.41259765625, 882.9050903320313)), Coord2(580.4722900390625, 881.7498779296875)) - .curve_to((Coord2(580.0606689453125, 880.2344970703125), Coord2(575.6553955078125, 869.0311889648438)), Coord2(573.716552734375, 868.6065673828125)) - .curve_to((Coord2(570.4192504882813, 866.5391845703125), Coord2(572.1432495117188, 889.7837524414063)), Coord2(575.9349365234375, 889.2540893554688)) - .curve_to((Coord2(579.9112548828125, 889.1182250976563), Coord2(573.3362426757813, 870.1537475585938)), Coord2(570.325439453125, 872.933349609375)) - .curve_to((Coord2(566.7039184570313, 872.4866333007813), Coord2(575.889892578125, 896.3516845703125)), Coord2(580.193359375, 885.1004028320313)) - .curve_to((Coord2(578.9361572265625, 882.8379516601563), Coord2(578.29638671875, 880.9623413085938)), Coord2(577.2049560546875, 878.0570678710938)) - .curve_to((Coord2(576.3244018554688, 875.5227661132813), Coord2(575.8396606445313, 874.0106811523438)), Coord2(575.3523559570313, 871.5857543945313)) - .curve_to((Coord2(567.6146240234375, 879.8153076171875), Coord2(569.26904296875, 890.168212890625)), Coord2(572.8831176757813, 890.166259765625)) - .curve_to((Coord2(580.7759399414063, 887.835693359375), Coord2(580.0247802734375, 885.56103515625)), Coord2(572.6173095703125, 889.1515502929688)) - .curve_to((Coord2(572.6390991210938, 889.1546020507813), Coord2(572.2571411132813, 889.167724609375)), Coord2(572.2820434570313, 889.1630249023438)) - .curve_to((Coord2(570.7896728515625, 887.8728637695313), Coord2(567.4065551757813, 888.0462036132813)), Coord2(572.1813354492188, 892.5457763671875)) - .curve_to((Coord2(570.9942016601563, 894.4725341796875), Coord2(577.9598999023438, 900.7188720703125)), Coord2(582.4383544921875, 902.0015258789063)) - .curve_to((Coord2(582.8182373046875, 902.308349609375), Coord2(586.3283081054688, 901.2371826171875)), Coord2(586.35205078125, 900.798583984375)) - .curve_to((Coord2(588.947998046875, 898.2053833007813), Coord2(592.195068359375, 891.016845703125)), Coord2(591.5047607421875, 889.0786743164063)) - .curve_to((Coord2(592.836669921875, 884.303955078125), Coord2(592.759033203125, 882.3919677734375)), Coord2(593.544921875, 881.51806640625)) - .curve_to((Coord2(594.1064453125, 880.7155151367188), Coord2(593.8582153320313, 881.4864501953125)), Coord2(596.4064331054688, 879.8722534179688)) - .curve_to((Coord2(597.3624877929688, 879.4691162109375), Coord2(597.849365234375, 879.2901611328125)), Coord2(598.5863037109375, 879.035400390625)) - .curve_to((Coord2(598.9070434570313, 878.928466796875), Coord2(599.0929565429688, 878.8623657226563)), Coord2(599.3098754882813, 878.7935180664063)) - .curve_to((Coord2(596.8707275390625, 882.4271240234375), Coord2(601.0760498046875, 876.7950439453125)), Coord2(603.7096557617188, 873.0940551757813)) - .curve_to((Coord2(603.7099609375, 873.09423828125), Coord2(603.7090454101563, 872.9913940429688)), Coord2(603.708740234375, 872.9912109375)) - .curve_to((Coord2(602.2408447265625, 869.050048828125), Coord2(594.5162353515625, 860.6947021484375)), Coord2(590.5521850585938, 859.4874267578125)) - .curve_to((Coord2(584.2527465820313, 852.785888671875), Coord2(581.061279296875, 852.8796997070313)), Coord2(581.9452514648438, 853.1057739257813)) - .curve_to((Coord2(582.593017578125, 852.9660034179688), Coord2(580.7717895507813, 856.9833984375)), Coord2(580.3909301757813, 856.538818359375)) - .curve_to((Coord2(580.433837890625, 856.8338623046875), Coord2(577.17236328125, 858.1321411132813)), Coord2(578.2269897460938, 857.4826049804688)) - .curve_to((Coord2(579.6019897460938, 857.3278198242188), Coord2(581.242431640625, 858.7636108398438)), Coord2(586.4614868164063, 860.9908447265625)) - .curve_to((Coord2(589.0213012695313, 862.7692260742188), Coord2(595.4768676757813, 865.4718017578125)), Coord2(598.6329345703125, 865.7658081054688)) - .curve_to((Coord2(598.567138671875, 866.820556640625), Coord2(603.420166015625, 864.4375610351563)), Coord2(603.9707641601563, 863.19189453125)) - .curve_to((Coord2(604.5771484375, 862.9888916015625), Coord2(605.8325805664063, 859.209716796875)), Coord2(605.4430541992188, 858.7909545898438)) - .curve_to((Coord2(604.993408203125, 855.49951171875), Coord2(601.7562866210938, 849.7847900390625)), Coord2(600.6087036132813, 850.0838623046875)) - .curve_to((Coord2(598.4024047851563, 846.3101196289063), Coord2(598.4458618164063, 848.7549438476563)), Coord2(599.054931640625, 849.7504272460938)) - .curve_to((Coord2(599.3753051757813, 849.5570678710938), Coord2(598.5122680664063, 852.48095703125)), Coord2(598.3043212890625, 852.2940063476563)) - .curve_to((Coord2(594.026123046875, 853.1077270507813), Coord2(590.7466430664063, 857.636474609375)), Coord2(597.5106811523438, 857.97314453125)) - .curve_to((Coord2(599.0369262695313, 859.3222045898438), Coord2(599.9699096679688, 856.8018798828125)), Coord2(600.7046508789063, 857.8336181640625)) - .curve_to((Coord2(602.2219848632813, 857.0648803710938), Coord2(604.2450561523438, 856.0399780273438)), Coord2(605.7623901367188, 855.271240234375)) - .curve_to((Coord2(606.0952758789063, 855.509765625), Coord2(607.6958618164063, 852.7109985351563)), Coord2(605.1021118164063, 850.0916748046875)) - .curve_to((Coord2(607.8820190429688, 846.5908203125), Coord2(593.875244140625, 843.7130737304688)), Coord2(588.6094360351563, 846.0635986328125)) - .curve_to((Coord2(588.1766357421875, 846.2265625), Coord2(587.21044921875, 849.5330200195313)), Coord2(587.5308227539063, 849.7504272460938)) - .curve_to((Coord2(588.139892578125, 853.0325317382813), Coord2(591.3778076171875, 858.6402587890625)), Coord2(592.365966796875, 858.1364135742188)) - .curve_to((Coord2(594.4129028320313, 861.7054443359375), Coord2(594.3702392578125, 859.3676147460938)), Coord2(593.9205932617188, 858.7909545898438)) - .curve_to((Coord2(593.531005859375, 859.0397338867188), Coord2(594.5933837890625, 855.8880004882813)), Coord2(594.7660522460938, 856.260986328125)) - .curve_to((Coord2(595.0291137695313, 855.397216796875), Coord2(599.3807983398438, 853.1875)), Coord2(598.6329345703125, 854.2422485351563)) - .curve_to((Coord2(598.4532470703125, 854.5361938476563), Coord2(597.2518920898438, 853.0940551757813)), Coord2(591.7156982421875, 850.7276611328125)) - .curve_to((Coord2(588.8387451171875, 848.8101196289063), Coord2(581.9440307617188, 846.1011962890625)), Coord2(578.2269897460938, 845.9464111328125)) - .curve_to((Coord2(577.8850708007813, 845.296875), Coord2(573.4860229492188, 846.9068603515625)), Coord2(572.7304077148438, 847.910888671875)) - .curve_to((Coord2(571.8158569335938, 847.940185546875), Coord2(569.74658203125, 852.5507202148438)), Coord2(570.394287109375, 853.1057739257813)) - .curve_to((Coord2(571.2783203125, 857.921142578125), Coord2(578.909423828125, 867.0386962890625)), Coord2(583.4408569335938, 868.6987915039063)) - .curve_to((Coord2(590.3077392578125, 875.8531494140625), Coord2(593.4223022460938, 875.197265625)), Coord2(591.9874877929688, 873.194091796875)) - .curve_to((Coord2(591.9872436523438, 873.1967163085938), Coord2(591.986328125, 873.0939331054688)), Coord2(591.9866333007813, 873.0940551757813)) - .curve_to((Coord2(594.6202392578125, 869.4052734375), Coord2(598.6082153320313, 863.8417358398438)), Coord2(595.7800903320313, 867.597900390625)) - .curve_to((Coord2(595.6080322265625, 867.6517333984375), Coord2(595.2333374023438, 867.7623901367188)), Coord2(594.86767578125, 867.8843994140625)) - .curve_to((Coord2(594.2318115234375, 868.0874633789063), Coord2(592.8426513671875, 868.5746459960938)), Coord2(591.7855224609375, 869.0294189453125)) - .curve_to((Coord2(590.3074340820313, 869.1311645507813), Coord2(585.4846801757813, 872.3850708007813)), Coord2(583.8530883789063, 874.7000122070313)) - .curve_to((Coord2(582.0348510742188, 877.52783203125), Coord2(580.2197875976563, 883.7698364257813)), Coord2(579.9202880859375, 886.5905151367188)) - .curve_to((Coord2(577.5986328125, 892.2477416992188), Coord2(579.3203735351563, 892.0960083007813)), Coord2(579.736328125, 890.97021484375)) - .curve_to((Coord2(579.3685302734375, 890.795166015625), Coord2(582.47412109375, 889.8472900390625)), Coord2(582.4383544921875, 890.154052734375)) - .curve_to((Coord2(583.9168090820313, 891.4367065429688), Coord2(587.2955322265625, 891.2642211914063)), Coord2(582.5258178710938, 886.7721557617188)) - .curve_to((Coord2(583.718017578125, 884.8529663085938), Coord2(576.7567138671875, 878.6074829101563)), Coord2(572.2820434570313, 877.3173217773438)) - .curve_to((Coord2(572.260009765625, 877.3126220703125), Coord2(571.8314208984375, 877.3272705078125)), Coord2(571.8067016601563, 877.3335571289063)) - .curve_to((Coord2(560.010498046875, 881.22509765625), Coord2(569.1023559570313, 904.441650390625)), Coord2(580.4130249023438, 899.290283203125)) - .curve_to((Coord2(587.358642578125, 896.5389404296875), Coord2(577.4598388671875, 865.2567138671875)), Coord2(569.1909790039063, 866.8143920898438)) - .curve_to((Coord2(564.2433471679688, 876.4852905273438), Coord2(565.2119750976563, 879.7418823242188)), Coord2(566.0805053710938, 882.063720703125)) - .curve_to((Coord2(566.91650390625, 884.5101928710938), Coord2(568.3486328125, 888.1217651367188)), Coord2(569.8612060546875, 890.8428344726563)) - .curve_to((Coord2(587.60546875, 903.775146484375), Coord2(589.3789672851563, 862.3728637695313)), Coord2(570.9526977539063, 861.138671875)) - .curve_to((Coord2(555.2870483398438, 863.2453002929688), Coord2(565.8951416015625, 904.8345336914063)), Coord2(580.59521484375, 900.0840454101563)) - .curve_to((Coord2(594.0852661132813, 895.3810424804688), Coord2(579.9393920898438, 852.4515380859375)), Coord2(565.9549560546875, 859.7515869140625)) - .curve_to((Coord2(557.6361083984375, 864.9191284179688), Coord2(571.5972900390625, 896.5897216796875)), Coord2(582.2666625976563, 893.362060546875)) - .curve_to((Coord2(596.2720947265625, 889.8972778320313), Coord2(586.6115112304688, 848.4343872070313)), Coord2(571.9376220703125, 850.0352172851563)) - .curve_to((Coord2(556.0201416015625, 851.1971435546875), Coord2(561.6185302734375, 885.7030029296875)), Coord2(577.4126586914063, 882.59521484375)) - .curve_to((Coord2(589.552978515625, 879.34228515625), Coord2(591.1780395507813, 853.8557739257813)), Coord2(584.5911254882813, 850.6495971679688)) - .curve_to((Coord2(573.9320678710938, 846.2090454101563), Coord2(564.6778564453125, 858.427490234375)), Coord2(573.91796875, 861.5853881835938)) - .curve_to((Coord2(572.7061767578125, 870.7952880859375), Coord2(589.696533203125, 873.0230712890625)), Coord2(593.0504760742188, 859.9937133789063)) - .curve_to((Coord2(595.9219970703125, 858.3511352539063), Coord2(586.99267578125, 843.552978515625)), Coord2(581.2372436523438, 842.6605834960938)) - .curve_to((Coord2(576.9470825195313, 848.0301513671875), Coord2(573.3963623046875, 858.2985229492188)), Coord2(577.3682861328125, 853.6705322265625)) - .curve_to((Coord2(573.3412475585938, 856.9037475585938), Coord2(593.6327514648438, 872.11083984375)), Coord2(601.6283569335938, 866.2468872070313)) - .curve_to((Coord2(603.8673706054688, 859.3587036132813), Coord2(597.4412231445313, 846.4760131835938)), Coord2(595.3387451171875, 847.1231689453125)) - .curve_to((Coord2(600.8814697265625, 844.0478515625), Coord2(586.74169921875, 842.0896606445313)), Coord2(580.974609375, 845.4783935546875)) - .curve_to((Coord2(577.481201171875, 849.10498046875), Coord2(595.04345703125, 864.8500366210938)), Coord2(599.9298095703125, 862.3732299804688)) - .curve_to((Coord2(603.3001708984375, 859.6436767578125), Coord2(598.7106323242188, 838.4157104492188)), Coord2(590.7146606445313, 839.1911010742188)) - .curve_to((Coord2(586.1773071289063, 843.9035034179688), Coord2(581.3427124023438, 853.1783447265625)), Coord2(589.6311645507813, 853.3121948242188)) - .curve_to((Coord2(591.4857788085938, 861.0636596679688), Coord2(603.1357421875, 865.7894287109375)), Coord2(608.3871459960938, 864.779541015625)) - .curve_to((Coord2(609.6311645507813, 860.09716796875), Coord2(608.9767456054688, 852.512939453125)), Coord2(607.8642578125, 855.7709350585938)) - .curve_to((Coord2(604.8263549804688, 851.1680908203125), Coord2(599.0707397460938, 843.4915161132813)), Coord2(593.83251953125, 839.4678955078125)) - .curve_to((Coord2(588.12841796875, 843.3626708984375), Coord2(584.9580688476563, 854.13720703125)), Coord2(590.4581909179688, 850.477294921875)) - .curve_to((Coord2(593.902099609375, 854.3041381835938), Coord2(597.5442504882813, 858.5637817382813)), Coord2(601.3976440429688, 862.492431640625)) - .curve_to((Coord2(596.497314453125, 864.138427734375), Coord2(605.6493530273438, 863.7943115234375)), Coord2(611.2799682617188, 862.292236328125)) - .curve_to((Coord2(610.607666015625, 857.74365234375), Coord2(606.7666625976563, 851.5074462890625)), Coord2(606.7361450195313, 853.9833984375)) - .curve_to((Coord2(602.8890380859375, 849.8455200195313), Coord2(597.0514526367188, 846.7515869140625)), Coord2(593.0549926757813, 843.3157958984375)) - .curve_to((Coord2(591.688232421875, 841.3230590820313), Coord2(585.3775024414063, 841.3017578125)), Coord2(589.6620483398438, 842.685791015625)) - .build(); + let path = + BezierPathBuilder::::start(Coord2(589.8298950195313, 841.699951171875)) + .curve_to( + ( + Coord2(589.8298950195313, 841.699951171875), + Coord2(589.8298950195313, 841.699951171875), + ), + Coord2(589.8298950195313, 841.699951171875), + ) + .curve_to( + ( + Coord2(585.0781860351563, 841.545166015625), + Coord2(588.116943359375, 846.1569213867188), + ), + Coord2(589.9508056640625, 846.92041015625), + ) + .curve_to( + ( + Coord2(593.9074096679688, 850.3338623046875), + Coord2(596.3680419921875, 855.8639526367188), + ), + Coord2(600.2550048828125, 860.024169921875), + ) + .curve_to( + ( + Coord2(602.3019409179688, 864.72900390625), + Coord2(603.487060546875, 861.721435546875), + ), + Coord2(602.1428833007813, 859.0895385742188), + ) + .curve_to( + ( + Coord2(607.4638061523438, 858.4710693359375), + Coord2(614.4444580078125, 855.14404296875), + ), + Coord2(608.3931884765625, 855.6187133789063), + ) + .curve_to( + ( + Coord2(604.7843627929688, 851.9526977539063), + Coord2(601.4735107421875, 847.9655151367188), + ), + Coord2(597.78515625, 843.8760986328125), + ) + .curve_to( + ( + Coord2(601.0536499023438, 837.7391357421875), + Coord2(590.90966796875, 841.439453125), + ), + Coord2(587.8450927734375, 847.3414916992188), + ) + .curve_to( + ( + Coord2(592.2240600585938, 850.6311645507813), + Coord2(595.8001098632813, 856.1324462890625), + ), + Coord2(599.6971435546875, 861.4691772460938), + ) + .curve_to( + ( + Coord2(599.6600952148438, 866.1685546875), + Coord2(601.5029907226563, 861.010498046875), + ), + Coord2(601.408447265625, 857.6356811523438), + ) + .curve_to( + ( + Coord2(605.051025390625, 858.197509765625), + Coord2(608.0866088867188, 854.1636352539063), + ), + Coord2(597.3378295898438, 846.8604125976563), + ) + .curve_to( + ( + Coord2(597.2238159179688, 836.9576416015625), + Coord2(590.7571411132813, 843.5430297851563), + ), + Coord2(587.1199340820313, 848.599365234375), + ) + .curve_to( + ( + Coord2(588.7532348632813, 853.0540161132813), + Coord2(591.633544921875, 856.119873046875), + ), + Coord2(594.626708984375, 853.6188354492188), + ) + .curve_to( + ( + Coord2(596.7156982421875, 852.8362426757813), + Coord2(595.0059814453125, 845.878662109375), + ), + Coord2(591.52490234375, 845.5113525390625), + ) + .curve_to( + ( + Coord2(585.76171875, 847.6647338867188), + Coord2(580.7750244140625, 855.853759765625), + ), + Coord2(586.7627563476563, 853.3876342773438), + ) + .curve_to( + ( + Coord2(588.5208129882813, 859.3195190429688), + Coord2(594.2566528320313, 860.6160278320313), + ), + Coord2(592.3621826171875, 860.9254760742188), + ) + .curve_to( + ( + Coord2(594.9733276367188, 864.4375), + Coord2(593.3421020507813, 848.7232055664063), + ), + Coord2(586.76220703125, 847.8418579101563), + ) + .curve_to( + ( + Coord2(589.7845458984375, 841.6835327148438), + Coord2(583.6079711914063, 848.498046875), + ), + Coord2(580.9037475585938, 853.9146118164063), + ) + .curve_to( + ( + Coord2(580.701904296875, 853.186767578125), + Coord2(578.50439453125, 857.2315063476563), + ), + Coord2(581.5901489257813, 860.4940795898438), + ) + .curve_to( + ( + Coord2(585.6346435546875, 863.285400390625), + Coord2(589.900146484375, 854.3807373046875), + ), + Coord2(584.1525268554688, 856.2511596679688), + ) + .curve_to( + ( + Coord2(590.3831787109375, 852.05712890625), + Coord2(578.9157104492188, 850.2012329101563), + ), + Coord2(574.5430297851563, 856.5203247070313), + ) + .curve_to( + ( + Coord2(573.6943969726563, 863.1355590820313), + Coord2(580.0052490234375, 871.26220703125), + ), + Coord2(575.3004760742188, 871.1060791015625), + ) + .curve_to( + ( + Coord2(576.81103515625, 870.624267578125), + Coord2(572.30712890625, 859.2913818359375), + ), + Coord2(570.9198608398438, 861.718994140625), + ) + .curve_to( + ( + Coord2(572.5287475585938, 864.7382202148438), + Coord2(581.41259765625, 882.9050903320313), + ), + Coord2(580.4722900390625, 881.7498779296875), + ) + .curve_to( + ( + Coord2(580.0606689453125, 880.2344970703125), + Coord2(575.6553955078125, 869.0311889648438), + ), + Coord2(573.716552734375, 868.6065673828125), + ) + .curve_to( + ( + Coord2(570.4192504882813, 866.5391845703125), + Coord2(572.1432495117188, 889.7837524414063), + ), + Coord2(575.9349365234375, 889.2540893554688), + ) + .curve_to( + ( + Coord2(579.9112548828125, 889.1182250976563), + Coord2(573.3362426757813, 870.1537475585938), + ), + Coord2(570.325439453125, 872.933349609375), + ) + .curve_to( + ( + Coord2(566.7039184570313, 872.4866333007813), + Coord2(575.889892578125, 896.3516845703125), + ), + Coord2(580.193359375, 885.1004028320313), + ) + .curve_to( + ( + Coord2(578.9361572265625, 882.8379516601563), + Coord2(578.29638671875, 880.9623413085938), + ), + Coord2(577.2049560546875, 878.0570678710938), + ) + .curve_to( + ( + Coord2(576.3244018554688, 875.5227661132813), + Coord2(575.8396606445313, 874.0106811523438), + ), + Coord2(575.3523559570313, 871.5857543945313), + ) + .curve_to( + ( + Coord2(567.6146240234375, 879.8153076171875), + Coord2(569.26904296875, 890.168212890625), + ), + Coord2(572.8831176757813, 890.166259765625), + ) + .curve_to( + ( + Coord2(580.7759399414063, 887.835693359375), + Coord2(580.0247802734375, 885.56103515625), + ), + Coord2(572.6173095703125, 889.1515502929688), + ) + .curve_to( + ( + Coord2(572.6390991210938, 889.1546020507813), + Coord2(572.2571411132813, 889.167724609375), + ), + Coord2(572.2820434570313, 889.1630249023438), + ) + .curve_to( + ( + Coord2(570.7896728515625, 887.8728637695313), + Coord2(567.4065551757813, 888.0462036132813), + ), + Coord2(572.1813354492188, 892.5457763671875), + ) + .curve_to( + ( + Coord2(570.9942016601563, 894.4725341796875), + Coord2(577.9598999023438, 900.7188720703125), + ), + Coord2(582.4383544921875, 902.0015258789063), + ) + .curve_to( + ( + Coord2(582.8182373046875, 902.308349609375), + Coord2(586.3283081054688, 901.2371826171875), + ), + Coord2(586.35205078125, 900.798583984375), + ) + .curve_to( + ( + Coord2(588.947998046875, 898.2053833007813), + Coord2(592.195068359375, 891.016845703125), + ), + Coord2(591.5047607421875, 889.0786743164063), + ) + .curve_to( + ( + Coord2(592.836669921875, 884.303955078125), + Coord2(592.759033203125, 882.3919677734375), + ), + Coord2(593.544921875, 881.51806640625), + ) + .curve_to( + ( + Coord2(594.1064453125, 880.7155151367188), + Coord2(593.8582153320313, 881.4864501953125), + ), + Coord2(596.4064331054688, 879.8722534179688), + ) + .curve_to( + ( + Coord2(597.3624877929688, 879.4691162109375), + Coord2(597.849365234375, 879.2901611328125), + ), + Coord2(598.5863037109375, 879.035400390625), + ) + .curve_to( + ( + Coord2(598.9070434570313, 878.928466796875), + Coord2(599.0929565429688, 878.8623657226563), + ), + Coord2(599.3098754882813, 878.7935180664063), + ) + .curve_to( + ( + Coord2(596.8707275390625, 882.4271240234375), + Coord2(601.0760498046875, 876.7950439453125), + ), + Coord2(603.7096557617188, 873.0940551757813), + ) + .curve_to( + ( + Coord2(603.7099609375, 873.09423828125), + Coord2(603.7090454101563, 872.9913940429688), + ), + Coord2(603.708740234375, 872.9912109375), + ) + .curve_to( + ( + Coord2(602.2408447265625, 869.050048828125), + Coord2(594.5162353515625, 860.6947021484375), + ), + Coord2(590.5521850585938, 859.4874267578125), + ) + .curve_to( + ( + Coord2(584.2527465820313, 852.785888671875), + Coord2(581.061279296875, 852.8796997070313), + ), + Coord2(581.9452514648438, 853.1057739257813), + ) + .curve_to( + ( + Coord2(582.593017578125, 852.9660034179688), + Coord2(580.7717895507813, 856.9833984375), + ), + Coord2(580.3909301757813, 856.538818359375), + ) + .curve_to( + ( + Coord2(580.433837890625, 856.8338623046875), + Coord2(577.17236328125, 858.1321411132813), + ), + Coord2(578.2269897460938, 857.4826049804688), + ) + .curve_to( + ( + Coord2(579.6019897460938, 857.3278198242188), + Coord2(581.242431640625, 858.7636108398438), + ), + Coord2(586.4614868164063, 860.9908447265625), + ) + .curve_to( + ( + Coord2(589.0213012695313, 862.7692260742188), + Coord2(595.4768676757813, 865.4718017578125), + ), + Coord2(598.6329345703125, 865.7658081054688), + ) + .curve_to( + ( + Coord2(598.567138671875, 866.820556640625), + Coord2(603.420166015625, 864.4375610351563), + ), + Coord2(603.9707641601563, 863.19189453125), + ) + .curve_to( + ( + Coord2(604.5771484375, 862.9888916015625), + Coord2(605.8325805664063, 859.209716796875), + ), + Coord2(605.4430541992188, 858.7909545898438), + ) + .curve_to( + ( + Coord2(604.993408203125, 855.49951171875), + Coord2(601.7562866210938, 849.7847900390625), + ), + Coord2(600.6087036132813, 850.0838623046875), + ) + .curve_to( + ( + Coord2(598.4024047851563, 846.3101196289063), + Coord2(598.4458618164063, 848.7549438476563), + ), + Coord2(599.054931640625, 849.7504272460938), + ) + .curve_to( + ( + Coord2(599.3753051757813, 849.5570678710938), + Coord2(598.5122680664063, 852.48095703125), + ), + Coord2(598.3043212890625, 852.2940063476563), + ) + .curve_to( + ( + Coord2(594.026123046875, 853.1077270507813), + Coord2(590.7466430664063, 857.636474609375), + ), + Coord2(597.5106811523438, 857.97314453125), + ) + .curve_to( + ( + Coord2(599.0369262695313, 859.3222045898438), + Coord2(599.9699096679688, 856.8018798828125), + ), + Coord2(600.7046508789063, 857.8336181640625), + ) + .curve_to( + ( + Coord2(602.2219848632813, 857.0648803710938), + Coord2(604.2450561523438, 856.0399780273438), + ), + Coord2(605.7623901367188, 855.271240234375), + ) + .curve_to( + ( + Coord2(606.0952758789063, 855.509765625), + Coord2(607.6958618164063, 852.7109985351563), + ), + Coord2(605.1021118164063, 850.0916748046875), + ) + .curve_to( + ( + Coord2(607.8820190429688, 846.5908203125), + Coord2(593.875244140625, 843.7130737304688), + ), + Coord2(588.6094360351563, 846.0635986328125), + ) + .curve_to( + ( + Coord2(588.1766357421875, 846.2265625), + Coord2(587.21044921875, 849.5330200195313), + ), + Coord2(587.5308227539063, 849.7504272460938), + ) + .curve_to( + ( + Coord2(588.139892578125, 853.0325317382813), + Coord2(591.3778076171875, 858.6402587890625), + ), + Coord2(592.365966796875, 858.1364135742188), + ) + .curve_to( + ( + Coord2(594.4129028320313, 861.7054443359375), + Coord2(594.3702392578125, 859.3676147460938), + ), + Coord2(593.9205932617188, 858.7909545898438), + ) + .curve_to( + ( + Coord2(593.531005859375, 859.0397338867188), + Coord2(594.5933837890625, 855.8880004882813), + ), + Coord2(594.7660522460938, 856.260986328125), + ) + .curve_to( + ( + Coord2(595.0291137695313, 855.397216796875), + Coord2(599.3807983398438, 853.1875), + ), + Coord2(598.6329345703125, 854.2422485351563), + ) + .curve_to( + ( + Coord2(598.4532470703125, 854.5361938476563), + Coord2(597.2518920898438, 853.0940551757813), + ), + Coord2(591.7156982421875, 850.7276611328125), + ) + .curve_to( + ( + Coord2(588.8387451171875, 848.8101196289063), + Coord2(581.9440307617188, 846.1011962890625), + ), + Coord2(578.2269897460938, 845.9464111328125), + ) + .curve_to( + ( + Coord2(577.8850708007813, 845.296875), + Coord2(573.4860229492188, 846.9068603515625), + ), + Coord2(572.7304077148438, 847.910888671875), + ) + .curve_to( + ( + Coord2(571.8158569335938, 847.940185546875), + Coord2(569.74658203125, 852.5507202148438), + ), + Coord2(570.394287109375, 853.1057739257813), + ) + .curve_to( + ( + Coord2(571.2783203125, 857.921142578125), + Coord2(578.909423828125, 867.0386962890625), + ), + Coord2(583.4408569335938, 868.6987915039063), + ) + .curve_to( + ( + Coord2(590.3077392578125, 875.8531494140625), + Coord2(593.4223022460938, 875.197265625), + ), + Coord2(591.9874877929688, 873.194091796875), + ) + .curve_to( + ( + Coord2(591.9872436523438, 873.1967163085938), + Coord2(591.986328125, 873.0939331054688), + ), + Coord2(591.9866333007813, 873.0940551757813), + ) + .curve_to( + ( + Coord2(594.6202392578125, 869.4052734375), + Coord2(598.6082153320313, 863.8417358398438), + ), + Coord2(595.7800903320313, 867.597900390625), + ) + .curve_to( + ( + Coord2(595.6080322265625, 867.6517333984375), + Coord2(595.2333374023438, 867.7623901367188), + ), + Coord2(594.86767578125, 867.8843994140625), + ) + .curve_to( + ( + Coord2(594.2318115234375, 868.0874633789063), + Coord2(592.8426513671875, 868.5746459960938), + ), + Coord2(591.7855224609375, 869.0294189453125), + ) + .curve_to( + ( + Coord2(590.3074340820313, 869.1311645507813), + Coord2(585.4846801757813, 872.3850708007813), + ), + Coord2(583.8530883789063, 874.7000122070313), + ) + .curve_to( + ( + Coord2(582.0348510742188, 877.52783203125), + Coord2(580.2197875976563, 883.7698364257813), + ), + Coord2(579.9202880859375, 886.5905151367188), + ) + .curve_to( + ( + Coord2(577.5986328125, 892.2477416992188), + Coord2(579.3203735351563, 892.0960083007813), + ), + Coord2(579.736328125, 890.97021484375), + ) + .curve_to( + ( + Coord2(579.3685302734375, 890.795166015625), + Coord2(582.47412109375, 889.8472900390625), + ), + Coord2(582.4383544921875, 890.154052734375), + ) + .curve_to( + ( + Coord2(583.9168090820313, 891.4367065429688), + Coord2(587.2955322265625, 891.2642211914063), + ), + Coord2(582.5258178710938, 886.7721557617188), + ) + .curve_to( + ( + Coord2(583.718017578125, 884.8529663085938), + Coord2(576.7567138671875, 878.6074829101563), + ), + Coord2(572.2820434570313, 877.3173217773438), + ) + .curve_to( + ( + Coord2(572.260009765625, 877.3126220703125), + Coord2(571.8314208984375, 877.3272705078125), + ), + Coord2(571.8067016601563, 877.3335571289063), + ) + .curve_to( + ( + Coord2(560.010498046875, 881.22509765625), + Coord2(569.1023559570313, 904.441650390625), + ), + Coord2(580.4130249023438, 899.290283203125), + ) + .curve_to( + ( + Coord2(587.358642578125, 896.5389404296875), + Coord2(577.4598388671875, 865.2567138671875), + ), + Coord2(569.1909790039063, 866.8143920898438), + ) + .curve_to( + ( + Coord2(564.2433471679688, 876.4852905273438), + Coord2(565.2119750976563, 879.7418823242188), + ), + Coord2(566.0805053710938, 882.063720703125), + ) + .curve_to( + ( + Coord2(566.91650390625, 884.5101928710938), + Coord2(568.3486328125, 888.1217651367188), + ), + Coord2(569.8612060546875, 890.8428344726563), + ) + .curve_to( + ( + Coord2(587.60546875, 903.775146484375), + Coord2(589.3789672851563, 862.3728637695313), + ), + Coord2(570.9526977539063, 861.138671875), + ) + .curve_to( + ( + Coord2(555.2870483398438, 863.2453002929688), + Coord2(565.8951416015625, 904.8345336914063), + ), + Coord2(580.59521484375, 900.0840454101563), + ) + .curve_to( + ( + Coord2(594.0852661132813, 895.3810424804688), + Coord2(579.9393920898438, 852.4515380859375), + ), + Coord2(565.9549560546875, 859.7515869140625), + ) + .curve_to( + ( + Coord2(557.6361083984375, 864.9191284179688), + Coord2(571.5972900390625, 896.5897216796875), + ), + Coord2(582.2666625976563, 893.362060546875), + ) + .curve_to( + ( + Coord2(596.2720947265625, 889.8972778320313), + Coord2(586.6115112304688, 848.4343872070313), + ), + Coord2(571.9376220703125, 850.0352172851563), + ) + .curve_to( + ( + Coord2(556.0201416015625, 851.1971435546875), + Coord2(561.6185302734375, 885.7030029296875), + ), + Coord2(577.4126586914063, 882.59521484375), + ) + .curve_to( + ( + Coord2(589.552978515625, 879.34228515625), + Coord2(591.1780395507813, 853.8557739257813), + ), + Coord2(584.5911254882813, 850.6495971679688), + ) + .curve_to( + ( + Coord2(573.9320678710938, 846.2090454101563), + Coord2(564.6778564453125, 858.427490234375), + ), + Coord2(573.91796875, 861.5853881835938), + ) + .curve_to( + ( + Coord2(572.7061767578125, 870.7952880859375), + Coord2(589.696533203125, 873.0230712890625), + ), + Coord2(593.0504760742188, 859.9937133789063), + ) + .curve_to( + ( + Coord2(595.9219970703125, 858.3511352539063), + Coord2(586.99267578125, 843.552978515625), + ), + Coord2(581.2372436523438, 842.6605834960938), + ) + .curve_to( + ( + Coord2(576.9470825195313, 848.0301513671875), + Coord2(573.3963623046875, 858.2985229492188), + ), + Coord2(577.3682861328125, 853.6705322265625), + ) + .curve_to( + ( + Coord2(573.3412475585938, 856.9037475585938), + Coord2(593.6327514648438, 872.11083984375), + ), + Coord2(601.6283569335938, 866.2468872070313), + ) + .curve_to( + ( + Coord2(603.8673706054688, 859.3587036132813), + Coord2(597.4412231445313, 846.4760131835938), + ), + Coord2(595.3387451171875, 847.1231689453125), + ) + .curve_to( + ( + Coord2(600.8814697265625, 844.0478515625), + Coord2(586.74169921875, 842.0896606445313), + ), + Coord2(580.974609375, 845.4783935546875), + ) + .curve_to( + ( + Coord2(577.481201171875, 849.10498046875), + Coord2(595.04345703125, 864.8500366210938), + ), + Coord2(599.9298095703125, 862.3732299804688), + ) + .curve_to( + ( + Coord2(603.3001708984375, 859.6436767578125), + Coord2(598.7106323242188, 838.4157104492188), + ), + Coord2(590.7146606445313, 839.1911010742188), + ) + .curve_to( + ( + Coord2(586.1773071289063, 843.9035034179688), + Coord2(581.3427124023438, 853.1783447265625), + ), + Coord2(589.6311645507813, 853.3121948242188), + ) + .curve_to( + ( + Coord2(591.4857788085938, 861.0636596679688), + Coord2(603.1357421875, 865.7894287109375), + ), + Coord2(608.3871459960938, 864.779541015625), + ) + .curve_to( + ( + Coord2(609.6311645507813, 860.09716796875), + Coord2(608.9767456054688, 852.512939453125), + ), + Coord2(607.8642578125, 855.7709350585938), + ) + .curve_to( + ( + Coord2(604.8263549804688, 851.1680908203125), + Coord2(599.0707397460938, 843.4915161132813), + ), + Coord2(593.83251953125, 839.4678955078125), + ) + .curve_to( + ( + Coord2(588.12841796875, 843.3626708984375), + Coord2(584.9580688476563, 854.13720703125), + ), + Coord2(590.4581909179688, 850.477294921875), + ) + .curve_to( + ( + Coord2(593.902099609375, 854.3041381835938), + Coord2(597.5442504882813, 858.5637817382813), + ), + Coord2(601.3976440429688, 862.492431640625), + ) + .curve_to( + ( + Coord2(596.497314453125, 864.138427734375), + Coord2(605.6493530273438, 863.7943115234375), + ), + Coord2(611.2799682617188, 862.292236328125), + ) + .curve_to( + ( + Coord2(610.607666015625, 857.74365234375), + Coord2(606.7666625976563, 851.5074462890625), + ), + Coord2(606.7361450195313, 853.9833984375), + ) + .curve_to( + ( + Coord2(602.8890380859375, 849.8455200195313), + Coord2(597.0514526367188, 846.7515869140625), + ), + Coord2(593.0549926757813, 843.3157958984375), + ) + .curve_to( + ( + Coord2(591.688232421875, 841.3230590820313), + Coord2(585.3775024414063, 841.3017578125), + ), + Coord2(589.6620483398438, 842.685791015625), + ) + .build(); // This path has generated an error that indicates that no result path was generated (unfortunately it seems this version does not produce the error) - let without_interior_points = path_remove_interior_points::<_, SimpleBezierPath>(&vec![path.clone()], 0.01); + let without_interior_points = path_remove_interior_points::<_, SimpleBezierPath>(&[path], 0.01); /* // Bug appears to be that not all collisions are generated (so two self-collides in a row will generate more points) @@ -299,139 +1312,802 @@ fn remove_interior_points_complex_2() { assert!(graph_path.num_points() == initial_num_points); */ - assert!(without_interior_points.len() != 0); + assert!(!without_interior_points.is_empty()); assert!(without_interior_points.len() == 1); } #[test] fn remove_interior_points_complex_2_without_healing() { - let path = BezierPathBuilder::::start(Coord2(589.8298950195313, 841.699951171875)) - .curve_to((Coord2(589.8298950195313, 841.699951171875), Coord2(589.8298950195313, 841.699951171875)), Coord2(589.8298950195313, 841.699951171875)) - .curve_to((Coord2(585.0781860351563, 841.545166015625), Coord2(588.116943359375, 846.1569213867188)), Coord2(589.9508056640625, 846.92041015625)) - .curve_to((Coord2(593.9074096679688, 850.3338623046875), Coord2(596.3680419921875, 855.8639526367188)), Coord2(600.2550048828125, 860.024169921875)) - .curve_to((Coord2(602.3019409179688, 864.72900390625), Coord2(603.487060546875, 861.721435546875)), Coord2(602.1428833007813, 859.0895385742188)) - .curve_to((Coord2(607.4638061523438, 858.4710693359375), Coord2(614.4444580078125, 855.14404296875)), Coord2(608.3931884765625, 855.6187133789063)) - .curve_to((Coord2(604.7843627929688, 851.9526977539063), Coord2(601.4735107421875, 847.9655151367188)), Coord2(597.78515625, 843.8760986328125)) - .curve_to((Coord2(601.0536499023438, 837.7391357421875), Coord2(590.90966796875, 841.439453125)), Coord2(587.8450927734375, 847.3414916992188)) - .curve_to((Coord2(592.2240600585938, 850.6311645507813), Coord2(595.8001098632813, 856.1324462890625)), Coord2(599.6971435546875, 861.4691772460938)) - .curve_to((Coord2(599.6600952148438, 866.1685546875), Coord2(601.5029907226563, 861.010498046875)), Coord2(601.408447265625, 857.6356811523438)) - .curve_to((Coord2(605.051025390625, 858.197509765625), Coord2(608.0866088867188, 854.1636352539063)), Coord2(597.3378295898438, 846.8604125976563)) - .curve_to((Coord2(597.2238159179688, 836.9576416015625), Coord2(590.7571411132813, 843.5430297851563)), Coord2(587.1199340820313, 848.599365234375)) - .curve_to((Coord2(588.7532348632813, 853.0540161132813), Coord2(591.633544921875, 856.119873046875)), Coord2(594.626708984375, 853.6188354492188)) - .curve_to((Coord2(596.7156982421875, 852.8362426757813), Coord2(595.0059814453125, 845.878662109375)), Coord2(591.52490234375, 845.5113525390625)) - .curve_to((Coord2(585.76171875, 847.6647338867188), Coord2(580.7750244140625, 855.853759765625)), Coord2(586.7627563476563, 853.3876342773438)) - .curve_to((Coord2(588.5208129882813, 859.3195190429688), Coord2(594.2566528320313, 860.6160278320313)), Coord2(592.3621826171875, 860.9254760742188)) - .curve_to((Coord2(594.9733276367188, 864.4375), Coord2(593.3421020507813, 848.7232055664063)), Coord2(586.76220703125, 847.8418579101563)) - .curve_to((Coord2(589.7845458984375, 841.6835327148438), Coord2(583.6079711914063, 848.498046875)), Coord2(580.9037475585938, 853.9146118164063)) - .curve_to((Coord2(580.701904296875, 853.186767578125), Coord2(578.50439453125, 857.2315063476563)), Coord2(581.5901489257813, 860.4940795898438)) - .curve_to((Coord2(585.6346435546875, 863.285400390625), Coord2(589.900146484375, 854.3807373046875)), Coord2(584.1525268554688, 856.2511596679688)) - .curve_to((Coord2(590.3831787109375, 852.05712890625), Coord2(578.9157104492188, 850.2012329101563)), Coord2(574.5430297851563, 856.5203247070313)) - .curve_to((Coord2(573.6943969726563, 863.1355590820313), Coord2(580.0052490234375, 871.26220703125)), Coord2(575.3004760742188, 871.1060791015625)) - .curve_to((Coord2(576.81103515625, 870.624267578125), Coord2(572.30712890625, 859.2913818359375)), Coord2(570.9198608398438, 861.718994140625)) - .curve_to((Coord2(572.5287475585938, 864.7382202148438), Coord2(581.41259765625, 882.9050903320313)), Coord2(580.4722900390625, 881.7498779296875)) - .curve_to((Coord2(580.0606689453125, 880.2344970703125), Coord2(575.6553955078125, 869.0311889648438)), Coord2(573.716552734375, 868.6065673828125)) - .curve_to((Coord2(570.4192504882813, 866.5391845703125), Coord2(572.1432495117188, 889.7837524414063)), Coord2(575.9349365234375, 889.2540893554688)) - .curve_to((Coord2(579.9112548828125, 889.1182250976563), Coord2(573.3362426757813, 870.1537475585938)), Coord2(570.325439453125, 872.933349609375)) - .curve_to((Coord2(566.7039184570313, 872.4866333007813), Coord2(575.889892578125, 896.3516845703125)), Coord2(580.193359375, 885.1004028320313)) - .curve_to((Coord2(578.9361572265625, 882.8379516601563), Coord2(578.29638671875, 880.9623413085938)), Coord2(577.2049560546875, 878.0570678710938)) - .curve_to((Coord2(576.3244018554688, 875.5227661132813), Coord2(575.8396606445313, 874.0106811523438)), Coord2(575.3523559570313, 871.5857543945313)) - .curve_to((Coord2(567.6146240234375, 879.8153076171875), Coord2(569.26904296875, 890.168212890625)), Coord2(572.8831176757813, 890.166259765625)) - .curve_to((Coord2(580.7759399414063, 887.835693359375), Coord2(580.0247802734375, 885.56103515625)), Coord2(572.6173095703125, 889.1515502929688)) - .curve_to((Coord2(572.6390991210938, 889.1546020507813), Coord2(572.2571411132813, 889.167724609375)), Coord2(572.2820434570313, 889.1630249023438)) - .curve_to((Coord2(570.7896728515625, 887.8728637695313), Coord2(567.4065551757813, 888.0462036132813)), Coord2(572.1813354492188, 892.5457763671875)) - .curve_to((Coord2(570.9942016601563, 894.4725341796875), Coord2(577.9598999023438, 900.7188720703125)), Coord2(582.4383544921875, 902.0015258789063)) - .curve_to((Coord2(582.8182373046875, 902.308349609375), Coord2(586.3283081054688, 901.2371826171875)), Coord2(586.35205078125, 900.798583984375)) - .curve_to((Coord2(588.947998046875, 898.2053833007813), Coord2(592.195068359375, 891.016845703125)), Coord2(591.5047607421875, 889.0786743164063)) - .curve_to((Coord2(592.836669921875, 884.303955078125), Coord2(592.759033203125, 882.3919677734375)), Coord2(593.544921875, 881.51806640625)) - .curve_to((Coord2(594.1064453125, 880.7155151367188), Coord2(593.8582153320313, 881.4864501953125)), Coord2(596.4064331054688, 879.8722534179688)) - .curve_to((Coord2(597.3624877929688, 879.4691162109375), Coord2(597.849365234375, 879.2901611328125)), Coord2(598.5863037109375, 879.035400390625)) - .curve_to((Coord2(598.9070434570313, 878.928466796875), Coord2(599.0929565429688, 878.8623657226563)), Coord2(599.3098754882813, 878.7935180664063)) - .curve_to((Coord2(596.8707275390625, 882.4271240234375), Coord2(601.0760498046875, 876.7950439453125)), Coord2(603.7096557617188, 873.0940551757813)) - .curve_to((Coord2(603.7099609375, 873.09423828125), Coord2(603.7090454101563, 872.9913940429688)), Coord2(603.708740234375, 872.9912109375)) - .curve_to((Coord2(602.2408447265625, 869.050048828125), Coord2(594.5162353515625, 860.6947021484375)), Coord2(590.5521850585938, 859.4874267578125)) - .curve_to((Coord2(584.2527465820313, 852.785888671875), Coord2(581.061279296875, 852.8796997070313)), Coord2(581.9452514648438, 853.1057739257813)) - .curve_to((Coord2(582.593017578125, 852.9660034179688), Coord2(580.7717895507813, 856.9833984375)), Coord2(580.3909301757813, 856.538818359375)) - .curve_to((Coord2(580.433837890625, 856.8338623046875), Coord2(577.17236328125, 858.1321411132813)), Coord2(578.2269897460938, 857.4826049804688)) - .curve_to((Coord2(579.6019897460938, 857.3278198242188), Coord2(581.242431640625, 858.7636108398438)), Coord2(586.4614868164063, 860.9908447265625)) - .curve_to((Coord2(589.0213012695313, 862.7692260742188), Coord2(595.4768676757813, 865.4718017578125)), Coord2(598.6329345703125, 865.7658081054688)) - .curve_to((Coord2(598.567138671875, 866.820556640625), Coord2(603.420166015625, 864.4375610351563)), Coord2(603.9707641601563, 863.19189453125)) - .curve_to((Coord2(604.5771484375, 862.9888916015625), Coord2(605.8325805664063, 859.209716796875)), Coord2(605.4430541992188, 858.7909545898438)) - .curve_to((Coord2(604.993408203125, 855.49951171875), Coord2(601.7562866210938, 849.7847900390625)), Coord2(600.6087036132813, 850.0838623046875)) - .curve_to((Coord2(598.4024047851563, 846.3101196289063), Coord2(598.4458618164063, 848.7549438476563)), Coord2(599.054931640625, 849.7504272460938)) - .curve_to((Coord2(599.3753051757813, 849.5570678710938), Coord2(598.5122680664063, 852.48095703125)), Coord2(598.3043212890625, 852.2940063476563)) - .curve_to((Coord2(594.026123046875, 853.1077270507813), Coord2(590.7466430664063, 857.636474609375)), Coord2(597.5106811523438, 857.97314453125)) - .curve_to((Coord2(599.0369262695313, 859.3222045898438), Coord2(599.9699096679688, 856.8018798828125)), Coord2(600.7046508789063, 857.8336181640625)) - .curve_to((Coord2(602.2219848632813, 857.0648803710938), Coord2(604.2450561523438, 856.0399780273438)), Coord2(605.7623901367188, 855.271240234375)) - .curve_to((Coord2(606.0952758789063, 855.509765625), Coord2(607.6958618164063, 852.7109985351563)), Coord2(605.1021118164063, 850.0916748046875)) - .curve_to((Coord2(607.8820190429688, 846.5908203125), Coord2(593.875244140625, 843.7130737304688)), Coord2(588.6094360351563, 846.0635986328125)) - .curve_to((Coord2(588.1766357421875, 846.2265625), Coord2(587.21044921875, 849.5330200195313)), Coord2(587.5308227539063, 849.7504272460938)) - .curve_to((Coord2(588.139892578125, 853.0325317382813), Coord2(591.3778076171875, 858.6402587890625)), Coord2(592.365966796875, 858.1364135742188)) - .curve_to((Coord2(594.4129028320313, 861.7054443359375), Coord2(594.3702392578125, 859.3676147460938)), Coord2(593.9205932617188, 858.7909545898438)) - .curve_to((Coord2(593.531005859375, 859.0397338867188), Coord2(594.5933837890625, 855.8880004882813)), Coord2(594.7660522460938, 856.260986328125)) - .curve_to((Coord2(595.0291137695313, 855.397216796875), Coord2(599.3807983398438, 853.1875)), Coord2(598.6329345703125, 854.2422485351563)) - .curve_to((Coord2(598.4532470703125, 854.5361938476563), Coord2(597.2518920898438, 853.0940551757813)), Coord2(591.7156982421875, 850.7276611328125)) - .curve_to((Coord2(588.8387451171875, 848.8101196289063), Coord2(581.9440307617188, 846.1011962890625)), Coord2(578.2269897460938, 845.9464111328125)) - .curve_to((Coord2(577.8850708007813, 845.296875), Coord2(573.4860229492188, 846.9068603515625)), Coord2(572.7304077148438, 847.910888671875)) - .curve_to((Coord2(571.8158569335938, 847.940185546875), Coord2(569.74658203125, 852.5507202148438)), Coord2(570.394287109375, 853.1057739257813)) - .curve_to((Coord2(571.2783203125, 857.921142578125), Coord2(578.909423828125, 867.0386962890625)), Coord2(583.4408569335938, 868.6987915039063)) - .curve_to((Coord2(590.3077392578125, 875.8531494140625), Coord2(593.4223022460938, 875.197265625)), Coord2(591.9874877929688, 873.194091796875)) - .curve_to((Coord2(591.9872436523438, 873.1967163085938), Coord2(591.986328125, 873.0939331054688)), Coord2(591.9866333007813, 873.0940551757813)) - .curve_to((Coord2(594.6202392578125, 869.4052734375), Coord2(598.6082153320313, 863.8417358398438)), Coord2(595.7800903320313, 867.597900390625)) - .curve_to((Coord2(595.6080322265625, 867.6517333984375), Coord2(595.2333374023438, 867.7623901367188)), Coord2(594.86767578125, 867.8843994140625)) - .curve_to((Coord2(594.2318115234375, 868.0874633789063), Coord2(592.8426513671875, 868.5746459960938)), Coord2(591.7855224609375, 869.0294189453125)) - .curve_to((Coord2(590.3074340820313, 869.1311645507813), Coord2(585.4846801757813, 872.3850708007813)), Coord2(583.8530883789063, 874.7000122070313)) - .curve_to((Coord2(582.0348510742188, 877.52783203125), Coord2(580.2197875976563, 883.7698364257813)), Coord2(579.9202880859375, 886.5905151367188)) - .curve_to((Coord2(577.5986328125, 892.2477416992188), Coord2(579.3203735351563, 892.0960083007813)), Coord2(579.736328125, 890.97021484375)) - .curve_to((Coord2(579.3685302734375, 890.795166015625), Coord2(582.47412109375, 889.8472900390625)), Coord2(582.4383544921875, 890.154052734375)) - .curve_to((Coord2(583.9168090820313, 891.4367065429688), Coord2(587.2955322265625, 891.2642211914063)), Coord2(582.5258178710938, 886.7721557617188)) - .curve_to((Coord2(583.718017578125, 884.8529663085938), Coord2(576.7567138671875, 878.6074829101563)), Coord2(572.2820434570313, 877.3173217773438)) - .curve_to((Coord2(572.260009765625, 877.3126220703125), Coord2(571.8314208984375, 877.3272705078125)), Coord2(571.8067016601563, 877.3335571289063)) - .curve_to((Coord2(560.010498046875, 881.22509765625), Coord2(569.1023559570313, 904.441650390625)), Coord2(580.4130249023438, 899.290283203125)) - .curve_to((Coord2(587.358642578125, 896.5389404296875), Coord2(577.4598388671875, 865.2567138671875)), Coord2(569.1909790039063, 866.8143920898438)) - .curve_to((Coord2(564.2433471679688, 876.4852905273438), Coord2(565.2119750976563, 879.7418823242188)), Coord2(566.0805053710938, 882.063720703125)) - .curve_to((Coord2(566.91650390625, 884.5101928710938), Coord2(568.3486328125, 888.1217651367188)), Coord2(569.8612060546875, 890.8428344726563)) - .curve_to((Coord2(587.60546875, 903.775146484375), Coord2(589.3789672851563, 862.3728637695313)), Coord2(570.9526977539063, 861.138671875)) - .curve_to((Coord2(555.2870483398438, 863.2453002929688), Coord2(565.8951416015625, 904.8345336914063)), Coord2(580.59521484375, 900.0840454101563)) - .curve_to((Coord2(594.0852661132813, 895.3810424804688), Coord2(579.9393920898438, 852.4515380859375)), Coord2(565.9549560546875, 859.7515869140625)) - .curve_to((Coord2(557.6361083984375, 864.9191284179688), Coord2(571.5972900390625, 896.5897216796875)), Coord2(582.2666625976563, 893.362060546875)) - .curve_to((Coord2(596.2720947265625, 889.8972778320313), Coord2(586.6115112304688, 848.4343872070313)), Coord2(571.9376220703125, 850.0352172851563)) - .curve_to((Coord2(556.0201416015625, 851.1971435546875), Coord2(561.6185302734375, 885.7030029296875)), Coord2(577.4126586914063, 882.59521484375)) - .curve_to((Coord2(589.552978515625, 879.34228515625), Coord2(591.1780395507813, 853.8557739257813)), Coord2(584.5911254882813, 850.6495971679688)) - .curve_to((Coord2(573.9320678710938, 846.2090454101563), Coord2(564.6778564453125, 858.427490234375)), Coord2(573.91796875, 861.5853881835938)) - .curve_to((Coord2(572.7061767578125, 870.7952880859375), Coord2(589.696533203125, 873.0230712890625)), Coord2(593.0504760742188, 859.9937133789063)) - .curve_to((Coord2(595.9219970703125, 858.3511352539063), Coord2(586.99267578125, 843.552978515625)), Coord2(581.2372436523438, 842.6605834960938)) - .curve_to((Coord2(576.9470825195313, 848.0301513671875), Coord2(573.3963623046875, 858.2985229492188)), Coord2(577.3682861328125, 853.6705322265625)) - .curve_to((Coord2(573.3412475585938, 856.9037475585938), Coord2(593.6327514648438, 872.11083984375)), Coord2(601.6283569335938, 866.2468872070313)) - .curve_to((Coord2(603.8673706054688, 859.3587036132813), Coord2(597.4412231445313, 846.4760131835938)), Coord2(595.3387451171875, 847.1231689453125)) - .curve_to((Coord2(600.8814697265625, 844.0478515625), Coord2(586.74169921875, 842.0896606445313)), Coord2(580.974609375, 845.4783935546875)) - .curve_to((Coord2(577.481201171875, 849.10498046875), Coord2(595.04345703125, 864.8500366210938)), Coord2(599.9298095703125, 862.3732299804688)) - .curve_to((Coord2(603.3001708984375, 859.6436767578125), Coord2(598.7106323242188, 838.4157104492188)), Coord2(590.7146606445313, 839.1911010742188)) - .curve_to((Coord2(586.1773071289063, 843.9035034179688), Coord2(581.3427124023438, 853.1783447265625)), Coord2(589.6311645507813, 853.3121948242188)) - .curve_to((Coord2(591.4857788085938, 861.0636596679688), Coord2(603.1357421875, 865.7894287109375)), Coord2(608.3871459960938, 864.779541015625)) - .curve_to((Coord2(609.6311645507813, 860.09716796875), Coord2(608.9767456054688, 852.512939453125)), Coord2(607.8642578125, 855.7709350585938)) - .curve_to((Coord2(604.8263549804688, 851.1680908203125), Coord2(599.0707397460938, 843.4915161132813)), Coord2(593.83251953125, 839.4678955078125)) - .curve_to((Coord2(588.12841796875, 843.3626708984375), Coord2(584.9580688476563, 854.13720703125)), Coord2(590.4581909179688, 850.477294921875)) - .curve_to((Coord2(593.902099609375, 854.3041381835938), Coord2(597.5442504882813, 858.5637817382813)), Coord2(601.3976440429688, 862.492431640625)) - .curve_to((Coord2(596.497314453125, 864.138427734375), Coord2(605.6493530273438, 863.7943115234375)), Coord2(611.2799682617188, 862.292236328125)) - .curve_to((Coord2(610.607666015625, 857.74365234375), Coord2(606.7666625976563, 851.5074462890625)), Coord2(606.7361450195313, 853.9833984375)) - .curve_to((Coord2(602.8890380859375, 849.8455200195313), Coord2(597.0514526367188, 846.7515869140625)), Coord2(593.0549926757813, 843.3157958984375)) - .curve_to((Coord2(591.688232421875, 841.3230590820313), Coord2(585.3775024414063, 841.3017578125)), Coord2(589.6620483398438, 842.685791015625)) - .build(); + let path = + BezierPathBuilder::::start(Coord2(589.8298950195313, 841.699951171875)) + .curve_to( + ( + Coord2(589.8298950195313, 841.699951171875), + Coord2(589.8298950195313, 841.699951171875), + ), + Coord2(589.8298950195313, 841.699951171875), + ) + .curve_to( + ( + Coord2(585.0781860351563, 841.545166015625), + Coord2(588.116943359375, 846.1569213867188), + ), + Coord2(589.9508056640625, 846.92041015625), + ) + .curve_to( + ( + Coord2(593.9074096679688, 850.3338623046875), + Coord2(596.3680419921875, 855.8639526367188), + ), + Coord2(600.2550048828125, 860.024169921875), + ) + .curve_to( + ( + Coord2(602.3019409179688, 864.72900390625), + Coord2(603.487060546875, 861.721435546875), + ), + Coord2(602.1428833007813, 859.0895385742188), + ) + .curve_to( + ( + Coord2(607.4638061523438, 858.4710693359375), + Coord2(614.4444580078125, 855.14404296875), + ), + Coord2(608.3931884765625, 855.6187133789063), + ) + .curve_to( + ( + Coord2(604.7843627929688, 851.9526977539063), + Coord2(601.4735107421875, 847.9655151367188), + ), + Coord2(597.78515625, 843.8760986328125), + ) + .curve_to( + ( + Coord2(601.0536499023438, 837.7391357421875), + Coord2(590.90966796875, 841.439453125), + ), + Coord2(587.8450927734375, 847.3414916992188), + ) + .curve_to( + ( + Coord2(592.2240600585938, 850.6311645507813), + Coord2(595.8001098632813, 856.1324462890625), + ), + Coord2(599.6971435546875, 861.4691772460938), + ) + .curve_to( + ( + Coord2(599.6600952148438, 866.1685546875), + Coord2(601.5029907226563, 861.010498046875), + ), + Coord2(601.408447265625, 857.6356811523438), + ) + .curve_to( + ( + Coord2(605.051025390625, 858.197509765625), + Coord2(608.0866088867188, 854.1636352539063), + ), + Coord2(597.3378295898438, 846.8604125976563), + ) + .curve_to( + ( + Coord2(597.2238159179688, 836.9576416015625), + Coord2(590.7571411132813, 843.5430297851563), + ), + Coord2(587.1199340820313, 848.599365234375), + ) + .curve_to( + ( + Coord2(588.7532348632813, 853.0540161132813), + Coord2(591.633544921875, 856.119873046875), + ), + Coord2(594.626708984375, 853.6188354492188), + ) + .curve_to( + ( + Coord2(596.7156982421875, 852.8362426757813), + Coord2(595.0059814453125, 845.878662109375), + ), + Coord2(591.52490234375, 845.5113525390625), + ) + .curve_to( + ( + Coord2(585.76171875, 847.6647338867188), + Coord2(580.7750244140625, 855.853759765625), + ), + Coord2(586.7627563476563, 853.3876342773438), + ) + .curve_to( + ( + Coord2(588.5208129882813, 859.3195190429688), + Coord2(594.2566528320313, 860.6160278320313), + ), + Coord2(592.3621826171875, 860.9254760742188), + ) + .curve_to( + ( + Coord2(594.9733276367188, 864.4375), + Coord2(593.3421020507813, 848.7232055664063), + ), + Coord2(586.76220703125, 847.8418579101563), + ) + .curve_to( + ( + Coord2(589.7845458984375, 841.6835327148438), + Coord2(583.6079711914063, 848.498046875), + ), + Coord2(580.9037475585938, 853.9146118164063), + ) + .curve_to( + ( + Coord2(580.701904296875, 853.186767578125), + Coord2(578.50439453125, 857.2315063476563), + ), + Coord2(581.5901489257813, 860.4940795898438), + ) + .curve_to( + ( + Coord2(585.6346435546875, 863.285400390625), + Coord2(589.900146484375, 854.3807373046875), + ), + Coord2(584.1525268554688, 856.2511596679688), + ) + .curve_to( + ( + Coord2(590.3831787109375, 852.05712890625), + Coord2(578.9157104492188, 850.2012329101563), + ), + Coord2(574.5430297851563, 856.5203247070313), + ) + .curve_to( + ( + Coord2(573.6943969726563, 863.1355590820313), + Coord2(580.0052490234375, 871.26220703125), + ), + Coord2(575.3004760742188, 871.1060791015625), + ) + .curve_to( + ( + Coord2(576.81103515625, 870.624267578125), + Coord2(572.30712890625, 859.2913818359375), + ), + Coord2(570.9198608398438, 861.718994140625), + ) + .curve_to( + ( + Coord2(572.5287475585938, 864.7382202148438), + Coord2(581.41259765625, 882.9050903320313), + ), + Coord2(580.4722900390625, 881.7498779296875), + ) + .curve_to( + ( + Coord2(580.0606689453125, 880.2344970703125), + Coord2(575.6553955078125, 869.0311889648438), + ), + Coord2(573.716552734375, 868.6065673828125), + ) + .curve_to( + ( + Coord2(570.4192504882813, 866.5391845703125), + Coord2(572.1432495117188, 889.7837524414063), + ), + Coord2(575.9349365234375, 889.2540893554688), + ) + .curve_to( + ( + Coord2(579.9112548828125, 889.1182250976563), + Coord2(573.3362426757813, 870.1537475585938), + ), + Coord2(570.325439453125, 872.933349609375), + ) + .curve_to( + ( + Coord2(566.7039184570313, 872.4866333007813), + Coord2(575.889892578125, 896.3516845703125), + ), + Coord2(580.193359375, 885.1004028320313), + ) + .curve_to( + ( + Coord2(578.9361572265625, 882.8379516601563), + Coord2(578.29638671875, 880.9623413085938), + ), + Coord2(577.2049560546875, 878.0570678710938), + ) + .curve_to( + ( + Coord2(576.3244018554688, 875.5227661132813), + Coord2(575.8396606445313, 874.0106811523438), + ), + Coord2(575.3523559570313, 871.5857543945313), + ) + .curve_to( + ( + Coord2(567.6146240234375, 879.8153076171875), + Coord2(569.26904296875, 890.168212890625), + ), + Coord2(572.8831176757813, 890.166259765625), + ) + .curve_to( + ( + Coord2(580.7759399414063, 887.835693359375), + Coord2(580.0247802734375, 885.56103515625), + ), + Coord2(572.6173095703125, 889.1515502929688), + ) + .curve_to( + ( + Coord2(572.6390991210938, 889.1546020507813), + Coord2(572.2571411132813, 889.167724609375), + ), + Coord2(572.2820434570313, 889.1630249023438), + ) + .curve_to( + ( + Coord2(570.7896728515625, 887.8728637695313), + Coord2(567.4065551757813, 888.0462036132813), + ), + Coord2(572.1813354492188, 892.5457763671875), + ) + .curve_to( + ( + Coord2(570.9942016601563, 894.4725341796875), + Coord2(577.9598999023438, 900.7188720703125), + ), + Coord2(582.4383544921875, 902.0015258789063), + ) + .curve_to( + ( + Coord2(582.8182373046875, 902.308349609375), + Coord2(586.3283081054688, 901.2371826171875), + ), + Coord2(586.35205078125, 900.798583984375), + ) + .curve_to( + ( + Coord2(588.947998046875, 898.2053833007813), + Coord2(592.195068359375, 891.016845703125), + ), + Coord2(591.5047607421875, 889.0786743164063), + ) + .curve_to( + ( + Coord2(592.836669921875, 884.303955078125), + Coord2(592.759033203125, 882.3919677734375), + ), + Coord2(593.544921875, 881.51806640625), + ) + .curve_to( + ( + Coord2(594.1064453125, 880.7155151367188), + Coord2(593.8582153320313, 881.4864501953125), + ), + Coord2(596.4064331054688, 879.8722534179688), + ) + .curve_to( + ( + Coord2(597.3624877929688, 879.4691162109375), + Coord2(597.849365234375, 879.2901611328125), + ), + Coord2(598.5863037109375, 879.035400390625), + ) + .curve_to( + ( + Coord2(598.9070434570313, 878.928466796875), + Coord2(599.0929565429688, 878.8623657226563), + ), + Coord2(599.3098754882813, 878.7935180664063), + ) + .curve_to( + ( + Coord2(596.8707275390625, 882.4271240234375), + Coord2(601.0760498046875, 876.7950439453125), + ), + Coord2(603.7096557617188, 873.0940551757813), + ) + .curve_to( + ( + Coord2(603.7099609375, 873.09423828125), + Coord2(603.7090454101563, 872.9913940429688), + ), + Coord2(603.708740234375, 872.9912109375), + ) + .curve_to( + ( + Coord2(602.2408447265625, 869.050048828125), + Coord2(594.5162353515625, 860.6947021484375), + ), + Coord2(590.5521850585938, 859.4874267578125), + ) + .curve_to( + ( + Coord2(584.2527465820313, 852.785888671875), + Coord2(581.061279296875, 852.8796997070313), + ), + Coord2(581.9452514648438, 853.1057739257813), + ) + .curve_to( + ( + Coord2(582.593017578125, 852.9660034179688), + Coord2(580.7717895507813, 856.9833984375), + ), + Coord2(580.3909301757813, 856.538818359375), + ) + .curve_to( + ( + Coord2(580.433837890625, 856.8338623046875), + Coord2(577.17236328125, 858.1321411132813), + ), + Coord2(578.2269897460938, 857.4826049804688), + ) + .curve_to( + ( + Coord2(579.6019897460938, 857.3278198242188), + Coord2(581.242431640625, 858.7636108398438), + ), + Coord2(586.4614868164063, 860.9908447265625), + ) + .curve_to( + ( + Coord2(589.0213012695313, 862.7692260742188), + Coord2(595.4768676757813, 865.4718017578125), + ), + Coord2(598.6329345703125, 865.7658081054688), + ) + .curve_to( + ( + Coord2(598.567138671875, 866.820556640625), + Coord2(603.420166015625, 864.4375610351563), + ), + Coord2(603.9707641601563, 863.19189453125), + ) + .curve_to( + ( + Coord2(604.5771484375, 862.9888916015625), + Coord2(605.8325805664063, 859.209716796875), + ), + Coord2(605.4430541992188, 858.7909545898438), + ) + .curve_to( + ( + Coord2(604.993408203125, 855.49951171875), + Coord2(601.7562866210938, 849.7847900390625), + ), + Coord2(600.6087036132813, 850.0838623046875), + ) + .curve_to( + ( + Coord2(598.4024047851563, 846.3101196289063), + Coord2(598.4458618164063, 848.7549438476563), + ), + Coord2(599.054931640625, 849.7504272460938), + ) + .curve_to( + ( + Coord2(599.3753051757813, 849.5570678710938), + Coord2(598.5122680664063, 852.48095703125), + ), + Coord2(598.3043212890625, 852.2940063476563), + ) + .curve_to( + ( + Coord2(594.026123046875, 853.1077270507813), + Coord2(590.7466430664063, 857.636474609375), + ), + Coord2(597.5106811523438, 857.97314453125), + ) + .curve_to( + ( + Coord2(599.0369262695313, 859.3222045898438), + Coord2(599.9699096679688, 856.8018798828125), + ), + Coord2(600.7046508789063, 857.8336181640625), + ) + .curve_to( + ( + Coord2(602.2219848632813, 857.0648803710938), + Coord2(604.2450561523438, 856.0399780273438), + ), + Coord2(605.7623901367188, 855.271240234375), + ) + .curve_to( + ( + Coord2(606.0952758789063, 855.509765625), + Coord2(607.6958618164063, 852.7109985351563), + ), + Coord2(605.1021118164063, 850.0916748046875), + ) + .curve_to( + ( + Coord2(607.8820190429688, 846.5908203125), + Coord2(593.875244140625, 843.7130737304688), + ), + Coord2(588.6094360351563, 846.0635986328125), + ) + .curve_to( + ( + Coord2(588.1766357421875, 846.2265625), + Coord2(587.21044921875, 849.5330200195313), + ), + Coord2(587.5308227539063, 849.7504272460938), + ) + .curve_to( + ( + Coord2(588.139892578125, 853.0325317382813), + Coord2(591.3778076171875, 858.6402587890625), + ), + Coord2(592.365966796875, 858.1364135742188), + ) + .curve_to( + ( + Coord2(594.4129028320313, 861.7054443359375), + Coord2(594.3702392578125, 859.3676147460938), + ), + Coord2(593.9205932617188, 858.7909545898438), + ) + .curve_to( + ( + Coord2(593.531005859375, 859.0397338867188), + Coord2(594.5933837890625, 855.8880004882813), + ), + Coord2(594.7660522460938, 856.260986328125), + ) + .curve_to( + ( + Coord2(595.0291137695313, 855.397216796875), + Coord2(599.3807983398438, 853.1875), + ), + Coord2(598.6329345703125, 854.2422485351563), + ) + .curve_to( + ( + Coord2(598.4532470703125, 854.5361938476563), + Coord2(597.2518920898438, 853.0940551757813), + ), + Coord2(591.7156982421875, 850.7276611328125), + ) + .curve_to( + ( + Coord2(588.8387451171875, 848.8101196289063), + Coord2(581.9440307617188, 846.1011962890625), + ), + Coord2(578.2269897460938, 845.9464111328125), + ) + .curve_to( + ( + Coord2(577.8850708007813, 845.296875), + Coord2(573.4860229492188, 846.9068603515625), + ), + Coord2(572.7304077148438, 847.910888671875), + ) + .curve_to( + ( + Coord2(571.8158569335938, 847.940185546875), + Coord2(569.74658203125, 852.5507202148438), + ), + Coord2(570.394287109375, 853.1057739257813), + ) + .curve_to( + ( + Coord2(571.2783203125, 857.921142578125), + Coord2(578.909423828125, 867.0386962890625), + ), + Coord2(583.4408569335938, 868.6987915039063), + ) + .curve_to( + ( + Coord2(590.3077392578125, 875.8531494140625), + Coord2(593.4223022460938, 875.197265625), + ), + Coord2(591.9874877929688, 873.194091796875), + ) + .curve_to( + ( + Coord2(591.9872436523438, 873.1967163085938), + Coord2(591.986328125, 873.0939331054688), + ), + Coord2(591.9866333007813, 873.0940551757813), + ) + .curve_to( + ( + Coord2(594.6202392578125, 869.4052734375), + Coord2(598.6082153320313, 863.8417358398438), + ), + Coord2(595.7800903320313, 867.597900390625), + ) + .curve_to( + ( + Coord2(595.6080322265625, 867.6517333984375), + Coord2(595.2333374023438, 867.7623901367188), + ), + Coord2(594.86767578125, 867.8843994140625), + ) + .curve_to( + ( + Coord2(594.2318115234375, 868.0874633789063), + Coord2(592.8426513671875, 868.5746459960938), + ), + Coord2(591.7855224609375, 869.0294189453125), + ) + .curve_to( + ( + Coord2(590.3074340820313, 869.1311645507813), + Coord2(585.4846801757813, 872.3850708007813), + ), + Coord2(583.8530883789063, 874.7000122070313), + ) + .curve_to( + ( + Coord2(582.0348510742188, 877.52783203125), + Coord2(580.2197875976563, 883.7698364257813), + ), + Coord2(579.9202880859375, 886.5905151367188), + ) + .curve_to( + ( + Coord2(577.5986328125, 892.2477416992188), + Coord2(579.3203735351563, 892.0960083007813), + ), + Coord2(579.736328125, 890.97021484375), + ) + .curve_to( + ( + Coord2(579.3685302734375, 890.795166015625), + Coord2(582.47412109375, 889.8472900390625), + ), + Coord2(582.4383544921875, 890.154052734375), + ) + .curve_to( + ( + Coord2(583.9168090820313, 891.4367065429688), + Coord2(587.2955322265625, 891.2642211914063), + ), + Coord2(582.5258178710938, 886.7721557617188), + ) + .curve_to( + ( + Coord2(583.718017578125, 884.8529663085938), + Coord2(576.7567138671875, 878.6074829101563), + ), + Coord2(572.2820434570313, 877.3173217773438), + ) + .curve_to( + ( + Coord2(572.260009765625, 877.3126220703125), + Coord2(571.8314208984375, 877.3272705078125), + ), + Coord2(571.8067016601563, 877.3335571289063), + ) + .curve_to( + ( + Coord2(560.010498046875, 881.22509765625), + Coord2(569.1023559570313, 904.441650390625), + ), + Coord2(580.4130249023438, 899.290283203125), + ) + .curve_to( + ( + Coord2(587.358642578125, 896.5389404296875), + Coord2(577.4598388671875, 865.2567138671875), + ), + Coord2(569.1909790039063, 866.8143920898438), + ) + .curve_to( + ( + Coord2(564.2433471679688, 876.4852905273438), + Coord2(565.2119750976563, 879.7418823242188), + ), + Coord2(566.0805053710938, 882.063720703125), + ) + .curve_to( + ( + Coord2(566.91650390625, 884.5101928710938), + Coord2(568.3486328125, 888.1217651367188), + ), + Coord2(569.8612060546875, 890.8428344726563), + ) + .curve_to( + ( + Coord2(587.60546875, 903.775146484375), + Coord2(589.3789672851563, 862.3728637695313), + ), + Coord2(570.9526977539063, 861.138671875), + ) + .curve_to( + ( + Coord2(555.2870483398438, 863.2453002929688), + Coord2(565.8951416015625, 904.8345336914063), + ), + Coord2(580.59521484375, 900.0840454101563), + ) + .curve_to( + ( + Coord2(594.0852661132813, 895.3810424804688), + Coord2(579.9393920898438, 852.4515380859375), + ), + Coord2(565.9549560546875, 859.7515869140625), + ) + .curve_to( + ( + Coord2(557.6361083984375, 864.9191284179688), + Coord2(571.5972900390625, 896.5897216796875), + ), + Coord2(582.2666625976563, 893.362060546875), + ) + .curve_to( + ( + Coord2(596.2720947265625, 889.8972778320313), + Coord2(586.6115112304688, 848.4343872070313), + ), + Coord2(571.9376220703125, 850.0352172851563), + ) + .curve_to( + ( + Coord2(556.0201416015625, 851.1971435546875), + Coord2(561.6185302734375, 885.7030029296875), + ), + Coord2(577.4126586914063, 882.59521484375), + ) + .curve_to( + ( + Coord2(589.552978515625, 879.34228515625), + Coord2(591.1780395507813, 853.8557739257813), + ), + Coord2(584.5911254882813, 850.6495971679688), + ) + .curve_to( + ( + Coord2(573.9320678710938, 846.2090454101563), + Coord2(564.6778564453125, 858.427490234375), + ), + Coord2(573.91796875, 861.5853881835938), + ) + .curve_to( + ( + Coord2(572.7061767578125, 870.7952880859375), + Coord2(589.696533203125, 873.0230712890625), + ), + Coord2(593.0504760742188, 859.9937133789063), + ) + .curve_to( + ( + Coord2(595.9219970703125, 858.3511352539063), + Coord2(586.99267578125, 843.552978515625), + ), + Coord2(581.2372436523438, 842.6605834960938), + ) + .curve_to( + ( + Coord2(576.9470825195313, 848.0301513671875), + Coord2(573.3963623046875, 858.2985229492188), + ), + Coord2(577.3682861328125, 853.6705322265625), + ) + .curve_to( + ( + Coord2(573.3412475585938, 856.9037475585938), + Coord2(593.6327514648438, 872.11083984375), + ), + Coord2(601.6283569335938, 866.2468872070313), + ) + .curve_to( + ( + Coord2(603.8673706054688, 859.3587036132813), + Coord2(597.4412231445313, 846.4760131835938), + ), + Coord2(595.3387451171875, 847.1231689453125), + ) + .curve_to( + ( + Coord2(600.8814697265625, 844.0478515625), + Coord2(586.74169921875, 842.0896606445313), + ), + Coord2(580.974609375, 845.4783935546875), + ) + .curve_to( + ( + Coord2(577.481201171875, 849.10498046875), + Coord2(595.04345703125, 864.8500366210938), + ), + Coord2(599.9298095703125, 862.3732299804688), + ) + .curve_to( + ( + Coord2(603.3001708984375, 859.6436767578125), + Coord2(598.7106323242188, 838.4157104492188), + ), + Coord2(590.7146606445313, 839.1911010742188), + ) + .curve_to( + ( + Coord2(586.1773071289063, 843.9035034179688), + Coord2(581.3427124023438, 853.1783447265625), + ), + Coord2(589.6311645507813, 853.3121948242188), + ) + .curve_to( + ( + Coord2(591.4857788085938, 861.0636596679688), + Coord2(603.1357421875, 865.7894287109375), + ), + Coord2(608.3871459960938, 864.779541015625), + ) + .curve_to( + ( + Coord2(609.6311645507813, 860.09716796875), + Coord2(608.9767456054688, 852.512939453125), + ), + Coord2(607.8642578125, 855.7709350585938), + ) + .curve_to( + ( + Coord2(604.8263549804688, 851.1680908203125), + Coord2(599.0707397460938, 843.4915161132813), + ), + Coord2(593.83251953125, 839.4678955078125), + ) + .curve_to( + ( + Coord2(588.12841796875, 843.3626708984375), + Coord2(584.9580688476563, 854.13720703125), + ), + Coord2(590.4581909179688, 850.477294921875), + ) + .curve_to( + ( + Coord2(593.902099609375, 854.3041381835938), + Coord2(597.5442504882813, 858.5637817382813), + ), + Coord2(601.3976440429688, 862.492431640625), + ) + .curve_to( + ( + Coord2(596.497314453125, 864.138427734375), + Coord2(605.6493530273438, 863.7943115234375), + ), + Coord2(611.2799682617188, 862.292236328125), + ) + .curve_to( + ( + Coord2(610.607666015625, 857.74365234375), + Coord2(606.7666625976563, 851.5074462890625), + ), + Coord2(606.7361450195313, 853.9833984375), + ) + .curve_to( + ( + Coord2(602.8890380859375, 849.8455200195313), + Coord2(597.0514526367188, 846.7515869140625), + ), + Coord2(593.0549926757813, 843.3157958984375), + ) + .curve_to( + ( + Coord2(591.688232421875, 841.3230590820313), + Coord2(585.3775024414063, 841.3017578125), + ), + Coord2(589.6620483398438, 842.685791015625), + ) + .build(); let mut graph_path = GraphPath::from_path(&path, PathLabel(0, PathDirection::Clockwise)); graph_path.self_collide(0.01); graph_path.set_exterior_by_removing_interior_points(); let paths = graph_path.exterior_paths::(); - assert!(paths.len() != 0); + assert!(!paths.is_empty()); assert!(paths.len() == 1); // Must always be a following edge that's an exterior one for edge in graph_path.all_edges() { - let end_point_idx = edge.end_point_index(); - let edge_ref = edge.into(); + let end_point_idx = edge.end_point_index(); + let edge_ref = edge.into(); if graph_path.edge_kind(edge_ref) == GraphPathEdgeKind::Exterior { - assert!(graph_path.edge_refs_for_point(end_point_idx).any(|edge| graph_path.edge_kind(edge) == GraphPathEdgeKind::Exterior)); + assert!(graph_path + .edge_refs_for_point(end_point_idx) + .any(|edge| graph_path.edge_kind(edge) == GraphPathEdgeKind::Exterior)); } } } @@ -440,163 +2116,917 @@ fn remove_interior_points_complex_2_without_healing() { fn remove_interior_points_3() { // This path produces a failure due to missing collisions in FlowBetween // ... it works *absolutely fine* here (collisions intact) - let path = BezierPathBuilder::::start(Coord2(632.2468872070313, 700.9489135742188)) - .curve_to((Coord2(632.2468872070313, 700.9489135742188), Coord2(632.2468872070313, 700.9489135742188)), Coord2(632.2468872070313, 700.9489135742188)) - .curve_to((Coord2(635.8739624023438, 713.1276245117188), Coord2(634.984375, 724.8035888671875)), Coord2(636.5083618164063, 736.8372192382813)) - .curve_to((Coord2(636.55078125, 737.1727905273438), Coord2(636.52880859375, 737.5175170898438)), Coord2(636.5714721679688, 737.8543090820313)) - .curve_to((Coord2(637.0103759765625, 741.3486328125), Coord2(637.1661376953125, 746.5886840820313)), Coord2(637.3720092773438, 750.5798950195313)) - .curve_to((Coord2(637.3945922851563, 751.0081176757813), Coord2(637.3880615234375, 751.6552124023438)), Coord2(637.397705078125, 752.5184936523438)) - .curve_to((Coord2(633.1192016601563, 754.1141357421875), Coord2(639.5689086914063, 756.6727905273438)), Coord2(644.0848999023438, 755.830810546875)) - .curve_to((Coord2(644.1641235351563, 754.98046875), Coord2(644.1864013671875, 754.6741333007813)), Coord2(644.1986694335938, 754.3605346679688)) - .curve_to((Coord2(644.2012329101563, 754.29443359375), Coord2(644.2059936523438, 754.22802734375)), Coord2(644.2086181640625, 754.1612548828125)) - .curve_to((Coord2(644.2246704101563, 753.1409301757813), Coord2(644.22607421875, 752.155517578125)), Coord2(644.2214965820313, 751.2548217773438)) - .curve_to((Coord2(644.2239990234375, 748.8275756835938), Coord2(644.20458984375, 746.9262084960938)), Coord2(644.4571533203125, 744.779296875)) - .curve_to((Coord2(648.3836059570313, 744.2342529296875), Coord2(643.2928466796875, 738.7249145507813)), Coord2(641.3601684570313, 741.8130493164063)) - .curve_to((Coord2(642.6392822265625, 748.04541015625), Coord2(643.9271240234375, 782.0206298828125)), Coord2(641.6926879882813, 782.131591796875)) - .curve_to((Coord2(642.8214111328125, 783.0655517578125), Coord2(645.96337890625, 783.1430053710938)), Coord2(643.7222900390625, 782.1798095703125)) - .curve_to((Coord2(642.6692504882813, 776.1190795898438), Coord2(643.2499389648438, 766.5458374023438)), Coord2(646.3471069335938, 761.6445922851563)) - .curve_to((Coord2(646.038330078125, 762.0671997070313), Coord2(646.3682250976563, 761.8047485351563)), Coord2(646.708740234375, 761.5353393554688)) - .curve_to((Coord2(644.1248168945313, 761.16552734375), Coord2(641.0634765625, 760.8419189453125)), Coord2(643.1715087890625, 762.1446533203125)) - .curve_to((Coord2(643.171875, 762.147705078125), Coord2(643.178466796875, 762.1910400390625)), Coord2(643.181884765625, 762.213134765625)) - .curve_to((Coord2(643.8663330078125, 769.598388671875), Coord2(642.75048828125, 789.9730834960938)), Coord2(638.2301025390625, 795.2767333984375)) - .curve_to((Coord2(639.1027221679688, 794.624267578125), Coord2(638.9803466796875, 794.6729736328125)), Coord2(638.857666015625, 794.72216796875)) - .curve_to((Coord2(638.6315307617188, 794.8128051757813), Coord2(638.40380859375, 794.9035034179688)), Coord2(638.1716918945313, 794.9965209960938)) - .curve_to((Coord2(638.3427734375, 795.274169921875), Coord2(644.6337280273438, 777.146728515625)), Coord2(645.4585571289063, 767.8198852539063)) - .curve_to((Coord2(645.701171875, 766.77490234375), Coord2(645.90869140625, 765.9208984375)), Coord2(646.0075073242188, 765.452880859375)) - .curve_to((Coord2(646.3751220703125, 763.7075805664063), Coord2(646.7393798828125, 761.952392578125)), Coord2(647.0966186523438, 760.2139892578125)) - .curve_to((Coord2(647.5424194335938, 758.0451049804688), Coord2(647.9866943359375, 755.904052734375)), Coord2(648.4459838867188, 753.752197265625)) - .curve_to((Coord2(652.6451416015625, 750.7460327148438), Coord2(647.7916259765625, 749.9540405273438)), Coord2(644.3892822265625, 749.474365234375)) - .curve_to((Coord2(645.332763671875, 755.0831298828125), Coord2(643.2569580078125, 763.2509155273438)), Coord2(641.4593505859375, 771.6987915039063)) - .curve_to((Coord2(640.708740234375, 774.979736328125), Coord2(639.9559936523438, 778.2863159179688)), Coord2(639.5221557617188, 780.9598388671875)) - .curve_to((Coord2(639.490234375, 781.1691284179688), Coord2(639.4317626953125, 781.6621704101563)), Coord2(639.3948974609375, 782.05224609375)) - .curve_to((Coord2(637.6366577148438, 785.9545288085938), Coord2(639.2575073242188, 789.9832763671875)), Coord2(639.6422119140625, 788.78515625)) - .curve_to((Coord2(640.965576171875, 789.996826171875), Coord2(644.2919921875, 791.590576171875)), Coord2(642.24267578125, 789.4522705078125)) - .curve_to((Coord2(642.2576904296875, 789.2343139648438), Coord2(642.2846069335938, 788.7120361328125)), Coord2(642.2734375, 788.3878173828125)) - .curve_to((Coord2(642.06689453125, 783.2173461914063), Coord2(642.5938110351563, 778.2110595703125)), Coord2(643.05029296875, 772.7711181640625)) - .curve_to((Coord2(643.207275390625, 770.955078125), Coord2(643.3928833007813, 769.0886840820313)), Coord2(643.5234985351563, 767.259765625)) - .curve_to((Coord2(643.5761108398438, 766.5219116210938), Coord2(643.6470947265625, 765.5867309570313)), Coord2(643.7337646484375, 764.4581298828125)) - .curve_to((Coord2(645.1050415039063, 753.1468505859375), Coord2(647.046630859375, 717.7682495117188)), Coord2(641.641845703125, 710.2857055664063)) - .curve_to((Coord2(636.7783203125, 710.5094604492188), Coord2(636.7587280273438, 710.9183349609375)), Coord2(636.489990234375, 711.1563110351563)) - .curve_to((Coord2(635.70361328125, 711.7706298828125), Coord2(635.5377807617188, 713.2534790039063)), Coord2(635.5494995117188, 714.0062255859375)) - .curve_to((Coord2(635.4976806640625, 712.0926513671875), Coord2(635.5260620117188, 713.5062866210938)), Coord2(635.5267333984375, 714.4866943359375)) - .curve_to((Coord2(636.59814453125, 714.7744750976563), Coord2(638.026611328125, 715.1581420898438)), Coord2(639.0980224609375, 715.4459228515625)) - .curve_to((Coord2(639.8369750976563, 713.6776123046875), Coord2(639.678466796875, 715.2540893554688)), Coord2(639.9312133789063, 715.1055908203125)) - .curve_to((Coord2(639.8008422851563, 713.9517211914063), Coord2(639.6350708007813, 715.4345703125)), Coord2(639.9312133789063, 715.1055908203125)) - .curve_to((Coord2(640.1494750976563, 714.919189453125), Coord2(640.8361206054688, 714.7127685546875)), Coord2(641.39453125, 714.1593627929688)) - .curve_to((Coord2(641.607666015625, 719.16650390625), Coord2(638.4246215820313, 750.5006713867188)), Coord2(637.8893432617188, 764.0092163085938)) - .curve_to((Coord2(637.8035888671875, 765.1250610351563), Coord2(637.7284545898438, 766.0917358398438)), Coord2(637.6748657226563, 766.84228515625)) - .curve_to((Coord2(637.5464477539063, 768.6419677734375), Coord2(637.4169921875, 770.42578125)), Coord2(637.2578735351563, 772.2711181640625)) - .curve_to((Coord2(636.795654296875, 777.473388671875), Coord2(636.3956909179688, 783.2040405273438)), Coord2(636.60791015625, 788.612060546875)) - .curve_to((Coord2(636.615234375, 788.7552490234375), Coord2(636.6175537109375, 788.81494140625)), Coord2(636.6064453125, 789.2139282226563)) - .curve_to((Coord2(634.4679565429688, 789.1854248046875), Coord2(637.6878662109375, 793.2979125976563)), Coord2(640.5524291992188, 794.2531127929688)) - .curve_to((Coord2(643.1683959960938, 792.6835327148438), Coord2(646.1170043945313, 783.748046875)), Coord2(644.8746337890625, 782.613525390625)) - .curve_to((Coord2(644.9351196289063, 782.0530395507813), Coord2(644.945068359375, 781.8778686523438)), Coord2(644.9533081054688, 781.839111328125)) - .curve_to((Coord2(645.3638305664063, 779.298095703125), Coord2(646.0396118164063, 776.3313598632813)), Coord2(646.8134155273438, 772.91796875)) - .curve_to((Coord2(648.6615600585938, 765.3552856445313), Coord2(650.780029296875, 755.1043701171875)), Coord2(649.7860107421875, 748.6101684570313)) - .curve_to((Coord2(646.33837890625, 747.8474731445313), Coord2(640.4654541015625, 748.3662719726563)), Coord2(643.1162109375, 752.6146240234375)) - .curve_to((Coord2(642.6539916992188, 754.7799682617188), Coord2(642.2095947265625, 756.9632568359375)), Coord2(641.7667236328125, 759.11865234375)) - .curve_to((Coord2(641.4076538085938, 760.8657836914063), Coord2(641.0556030273438, 762.5961303710938)), Coord2(640.6897583007813, 764.3326416015625)) - .curve_to((Coord2(640.6021728515625, 764.7493896484375), Coord2(640.4375610351563, 765.5008544921875)), Coord2(640.1837768554688, 766.5970458984375)) - .curve_to((Coord2(637.48779296875, 772.45751953125), Coord2(634.00927734375, 796.4724731445313)), Coord2(637.9215087890625, 799.7769775390625)) - .curve_to((Coord2(640.3659057617188, 799.7997436523438), Coord2(640.5935668945313, 799.7078857421875)), Coord2(640.8196411132813, 799.6173095703125)) - .curve_to((Coord2(640.9423217773438, 799.568115234375), Coord2(641.0643920898438, 799.5188598632813)), Coord2(641.1866455078125, 799.4699096679688)) - .curve_to((Coord2(647.5555419921875, 792.8746948242188), Coord2(650.9056396484375, 768.038818359375)), Coord2(649.3676147460938, 761.2432861328125)) - .curve_to((Coord2(649.3682250976563, 761.2474975585938), Coord2(649.37109375, 761.2653198242188)), Coord2(649.3666381835938, 761.2356567382813)) - .curve_to((Coord2(651.3629150390625, 761.7766723632813), Coord2(647.6148681640625, 756.7731323242188)), Coord2(644.4575805664063, 755.9600830078125)) - .curve_to((Coord2(642.4801635742188, 756.8904418945313), Coord2(642.14892578125, 757.1510620117188)), Coord2(641.8184204101563, 757.4124755859375)) - .curve_to((Coord2(637.2826538085938, 763.3823852539063), Coord2(635.7777709960938, 778.0116577148438)), Coord2(637.630126953125, 783.6883544921875)) - .curve_to((Coord2(635.5146484375, 783.2322387695313), Coord2(639.78515625, 787.8326416015625)), Coord2(643.0133056640625, 788.3185424804688)) - .curve_to((Coord2(649.2066650390625, 786.63037109375), Coord2(652.2764892578125, 743.0693359375)), Coord2(647.0385131835938, 738.4141235351563)) - .curve_to((Coord2(641.272216796875, 735.0977172851563), Coord2(634.1425170898438, 743.2423095703125)), Coord2(637.9150390625, 744.0158081054688)) - .curve_to((Coord2(637.6356201171875, 746.4281005859375), Coord2(637.637939453125, 749.1239013671875)), Coord2(637.6622314453125, 751.2858276367188)) - .curve_to((Coord2(637.6663208007813, 752.229248046875), Coord2(637.6764526367188, 753.086669921875)), Coord2(637.6630859375, 753.984619140625)) - .curve_to((Coord2(637.6626586914063, 753.9716186523438), Coord2(637.6622314453125, 754.0381469726563)), Coord2(637.6596069335938, 754.1043090820313)) - .curve_to((Coord2(637.6473388671875, 754.4176025390625), Coord2(637.6455078125, 754.7255859375)), Coord2(637.633544921875, 755.0328369140625)) - .curve_to((Coord2(641.7747802734375, 755.7510986328125), Coord2(648.1893310546875, 754.4743041992188)), Coord2(643.877685546875, 752.4591064453125)) - .curve_to((Coord2(643.8726196289063, 751.7140502929688), Coord2(643.8517456054688, 750.79736328125)), Coord2(643.8246459960938, 750.2509765625)) - .curve_to((Coord2(643.6224365234375, 746.2362670898438), Coord2(642.779296875, 740.5953369140625)), Coord2(642.336669921875, 737.1245727539063)) - .curve_to((Coord2(642.2940673828125, 736.7880249023438), Coord2(642.18701171875, 736.4603881835938)), Coord2(642.14453125, 736.12451171875)) - .curve_to((Coord2(640.6148071289063, 724.0070190429688), Coord2(636.8263549804688, 712.7576293945313)), Coord2(633.2051391601563, 700.6627807617188)) - .build(); + let path = + BezierPathBuilder::::start(Coord2(632.2468872070313, 700.9489135742188)) + .curve_to( + ( + Coord2(632.2468872070313, 700.9489135742188), + Coord2(632.2468872070313, 700.9489135742188), + ), + Coord2(632.2468872070313, 700.9489135742188), + ) + .curve_to( + ( + Coord2(635.8739624023438, 713.1276245117188), + Coord2(634.984375, 724.8035888671875), + ), + Coord2(636.5083618164063, 736.8372192382813), + ) + .curve_to( + ( + Coord2(636.55078125, 737.1727905273438), + Coord2(636.52880859375, 737.5175170898438), + ), + Coord2(636.5714721679688, 737.8543090820313), + ) + .curve_to( + ( + Coord2(637.0103759765625, 741.3486328125), + Coord2(637.1661376953125, 746.5886840820313), + ), + Coord2(637.3720092773438, 750.5798950195313), + ) + .curve_to( + ( + Coord2(637.3945922851563, 751.0081176757813), + Coord2(637.3880615234375, 751.6552124023438), + ), + Coord2(637.397705078125, 752.5184936523438), + ) + .curve_to( + ( + Coord2(633.1192016601563, 754.1141357421875), + Coord2(639.5689086914063, 756.6727905273438), + ), + Coord2(644.0848999023438, 755.830810546875), + ) + .curve_to( + ( + Coord2(644.1641235351563, 754.98046875), + Coord2(644.1864013671875, 754.6741333007813), + ), + Coord2(644.1986694335938, 754.3605346679688), + ) + .curve_to( + ( + Coord2(644.2012329101563, 754.29443359375), + Coord2(644.2059936523438, 754.22802734375), + ), + Coord2(644.2086181640625, 754.1612548828125), + ) + .curve_to( + ( + Coord2(644.2246704101563, 753.1409301757813), + Coord2(644.22607421875, 752.155517578125), + ), + Coord2(644.2214965820313, 751.2548217773438), + ) + .curve_to( + ( + Coord2(644.2239990234375, 748.8275756835938), + Coord2(644.20458984375, 746.9262084960938), + ), + Coord2(644.4571533203125, 744.779296875), + ) + .curve_to( + ( + Coord2(648.3836059570313, 744.2342529296875), + Coord2(643.2928466796875, 738.7249145507813), + ), + Coord2(641.3601684570313, 741.8130493164063), + ) + .curve_to( + ( + Coord2(642.6392822265625, 748.04541015625), + Coord2(643.9271240234375, 782.0206298828125), + ), + Coord2(641.6926879882813, 782.131591796875), + ) + .curve_to( + ( + Coord2(642.8214111328125, 783.0655517578125), + Coord2(645.96337890625, 783.1430053710938), + ), + Coord2(643.7222900390625, 782.1798095703125), + ) + .curve_to( + ( + Coord2(642.6692504882813, 776.1190795898438), + Coord2(643.2499389648438, 766.5458374023438), + ), + Coord2(646.3471069335938, 761.6445922851563), + ) + .curve_to( + ( + Coord2(646.038330078125, 762.0671997070313), + Coord2(646.3682250976563, 761.8047485351563), + ), + Coord2(646.708740234375, 761.5353393554688), + ) + .curve_to( + ( + Coord2(644.1248168945313, 761.16552734375), + Coord2(641.0634765625, 760.8419189453125), + ), + Coord2(643.1715087890625, 762.1446533203125), + ) + .curve_to( + ( + Coord2(643.171875, 762.147705078125), + Coord2(643.178466796875, 762.1910400390625), + ), + Coord2(643.181884765625, 762.213134765625), + ) + .curve_to( + ( + Coord2(643.8663330078125, 769.598388671875), + Coord2(642.75048828125, 789.9730834960938), + ), + Coord2(638.2301025390625, 795.2767333984375), + ) + .curve_to( + ( + Coord2(639.1027221679688, 794.624267578125), + Coord2(638.9803466796875, 794.6729736328125), + ), + Coord2(638.857666015625, 794.72216796875), + ) + .curve_to( + ( + Coord2(638.6315307617188, 794.8128051757813), + Coord2(638.40380859375, 794.9035034179688), + ), + Coord2(638.1716918945313, 794.9965209960938), + ) + .curve_to( + ( + Coord2(638.3427734375, 795.274169921875), + Coord2(644.6337280273438, 777.146728515625), + ), + Coord2(645.4585571289063, 767.8198852539063), + ) + .curve_to( + ( + Coord2(645.701171875, 766.77490234375), + Coord2(645.90869140625, 765.9208984375), + ), + Coord2(646.0075073242188, 765.452880859375), + ) + .curve_to( + ( + Coord2(646.3751220703125, 763.7075805664063), + Coord2(646.7393798828125, 761.952392578125), + ), + Coord2(647.0966186523438, 760.2139892578125), + ) + .curve_to( + ( + Coord2(647.5424194335938, 758.0451049804688), + Coord2(647.9866943359375, 755.904052734375), + ), + Coord2(648.4459838867188, 753.752197265625), + ) + .curve_to( + ( + Coord2(652.6451416015625, 750.7460327148438), + Coord2(647.7916259765625, 749.9540405273438), + ), + Coord2(644.3892822265625, 749.474365234375), + ) + .curve_to( + ( + Coord2(645.332763671875, 755.0831298828125), + Coord2(643.2569580078125, 763.2509155273438), + ), + Coord2(641.4593505859375, 771.6987915039063), + ) + .curve_to( + ( + Coord2(640.708740234375, 774.979736328125), + Coord2(639.9559936523438, 778.2863159179688), + ), + Coord2(639.5221557617188, 780.9598388671875), + ) + .curve_to( + ( + Coord2(639.490234375, 781.1691284179688), + Coord2(639.4317626953125, 781.6621704101563), + ), + Coord2(639.3948974609375, 782.05224609375), + ) + .curve_to( + ( + Coord2(637.6366577148438, 785.9545288085938), + Coord2(639.2575073242188, 789.9832763671875), + ), + Coord2(639.6422119140625, 788.78515625), + ) + .curve_to( + ( + Coord2(640.965576171875, 789.996826171875), + Coord2(644.2919921875, 791.590576171875), + ), + Coord2(642.24267578125, 789.4522705078125), + ) + .curve_to( + ( + Coord2(642.2576904296875, 789.2343139648438), + Coord2(642.2846069335938, 788.7120361328125), + ), + Coord2(642.2734375, 788.3878173828125), + ) + .curve_to( + ( + Coord2(642.06689453125, 783.2173461914063), + Coord2(642.5938110351563, 778.2110595703125), + ), + Coord2(643.05029296875, 772.7711181640625), + ) + .curve_to( + ( + Coord2(643.207275390625, 770.955078125), + Coord2(643.3928833007813, 769.0886840820313), + ), + Coord2(643.5234985351563, 767.259765625), + ) + .curve_to( + ( + Coord2(643.5761108398438, 766.5219116210938), + Coord2(643.6470947265625, 765.5867309570313), + ), + Coord2(643.7337646484375, 764.4581298828125), + ) + .curve_to( + ( + Coord2(645.1050415039063, 753.1468505859375), + Coord2(647.046630859375, 717.7682495117188), + ), + Coord2(641.641845703125, 710.2857055664063), + ) + .curve_to( + ( + Coord2(636.7783203125, 710.5094604492188), + Coord2(636.7587280273438, 710.9183349609375), + ), + Coord2(636.489990234375, 711.1563110351563), + ) + .curve_to( + ( + Coord2(635.70361328125, 711.7706298828125), + Coord2(635.5377807617188, 713.2534790039063), + ), + Coord2(635.5494995117188, 714.0062255859375), + ) + .curve_to( + ( + Coord2(635.4976806640625, 712.0926513671875), + Coord2(635.5260620117188, 713.5062866210938), + ), + Coord2(635.5267333984375, 714.4866943359375), + ) + .curve_to( + ( + Coord2(636.59814453125, 714.7744750976563), + Coord2(638.026611328125, 715.1581420898438), + ), + Coord2(639.0980224609375, 715.4459228515625), + ) + .curve_to( + ( + Coord2(639.8369750976563, 713.6776123046875), + Coord2(639.678466796875, 715.2540893554688), + ), + Coord2(639.9312133789063, 715.1055908203125), + ) + .curve_to( + ( + Coord2(639.8008422851563, 713.9517211914063), + Coord2(639.6350708007813, 715.4345703125), + ), + Coord2(639.9312133789063, 715.1055908203125), + ) + .curve_to( + ( + Coord2(640.1494750976563, 714.919189453125), + Coord2(640.8361206054688, 714.7127685546875), + ), + Coord2(641.39453125, 714.1593627929688), + ) + .curve_to( + ( + Coord2(641.607666015625, 719.16650390625), + Coord2(638.4246215820313, 750.5006713867188), + ), + Coord2(637.8893432617188, 764.0092163085938), + ) + .curve_to( + ( + Coord2(637.8035888671875, 765.1250610351563), + Coord2(637.7284545898438, 766.0917358398438), + ), + Coord2(637.6748657226563, 766.84228515625), + ) + .curve_to( + ( + Coord2(637.5464477539063, 768.6419677734375), + Coord2(637.4169921875, 770.42578125), + ), + Coord2(637.2578735351563, 772.2711181640625), + ) + .curve_to( + ( + Coord2(636.795654296875, 777.473388671875), + Coord2(636.3956909179688, 783.2040405273438), + ), + Coord2(636.60791015625, 788.612060546875), + ) + .curve_to( + ( + Coord2(636.615234375, 788.7552490234375), + Coord2(636.6175537109375, 788.81494140625), + ), + Coord2(636.6064453125, 789.2139282226563), + ) + .curve_to( + ( + Coord2(634.4679565429688, 789.1854248046875), + Coord2(637.6878662109375, 793.2979125976563), + ), + Coord2(640.5524291992188, 794.2531127929688), + ) + .curve_to( + ( + Coord2(643.1683959960938, 792.6835327148438), + Coord2(646.1170043945313, 783.748046875), + ), + Coord2(644.8746337890625, 782.613525390625), + ) + .curve_to( + ( + Coord2(644.9351196289063, 782.0530395507813), + Coord2(644.945068359375, 781.8778686523438), + ), + Coord2(644.9533081054688, 781.839111328125), + ) + .curve_to( + ( + Coord2(645.3638305664063, 779.298095703125), + Coord2(646.0396118164063, 776.3313598632813), + ), + Coord2(646.8134155273438, 772.91796875), + ) + .curve_to( + ( + Coord2(648.6615600585938, 765.3552856445313), + Coord2(650.780029296875, 755.1043701171875), + ), + Coord2(649.7860107421875, 748.6101684570313), + ) + .curve_to( + ( + Coord2(646.33837890625, 747.8474731445313), + Coord2(640.4654541015625, 748.3662719726563), + ), + Coord2(643.1162109375, 752.6146240234375), + ) + .curve_to( + ( + Coord2(642.6539916992188, 754.7799682617188), + Coord2(642.2095947265625, 756.9632568359375), + ), + Coord2(641.7667236328125, 759.11865234375), + ) + .curve_to( + ( + Coord2(641.4076538085938, 760.8657836914063), + Coord2(641.0556030273438, 762.5961303710938), + ), + Coord2(640.6897583007813, 764.3326416015625), + ) + .curve_to( + ( + Coord2(640.6021728515625, 764.7493896484375), + Coord2(640.4375610351563, 765.5008544921875), + ), + Coord2(640.1837768554688, 766.5970458984375), + ) + .curve_to( + ( + Coord2(637.48779296875, 772.45751953125), + Coord2(634.00927734375, 796.4724731445313), + ), + Coord2(637.9215087890625, 799.7769775390625), + ) + .curve_to( + ( + Coord2(640.3659057617188, 799.7997436523438), + Coord2(640.5935668945313, 799.7078857421875), + ), + Coord2(640.8196411132813, 799.6173095703125), + ) + .curve_to( + ( + Coord2(640.9423217773438, 799.568115234375), + Coord2(641.0643920898438, 799.5188598632813), + ), + Coord2(641.1866455078125, 799.4699096679688), + ) + .curve_to( + ( + Coord2(647.5555419921875, 792.8746948242188), + Coord2(650.9056396484375, 768.038818359375), + ), + Coord2(649.3676147460938, 761.2432861328125), + ) + .curve_to( + ( + Coord2(649.3682250976563, 761.2474975585938), + Coord2(649.37109375, 761.2653198242188), + ), + Coord2(649.3666381835938, 761.2356567382813), + ) + .curve_to( + ( + Coord2(651.3629150390625, 761.7766723632813), + Coord2(647.6148681640625, 756.7731323242188), + ), + Coord2(644.4575805664063, 755.9600830078125), + ) + .curve_to( + ( + Coord2(642.4801635742188, 756.8904418945313), + Coord2(642.14892578125, 757.1510620117188), + ), + Coord2(641.8184204101563, 757.4124755859375), + ) + .curve_to( + ( + Coord2(637.2826538085938, 763.3823852539063), + Coord2(635.7777709960938, 778.0116577148438), + ), + Coord2(637.630126953125, 783.6883544921875), + ) + .curve_to( + ( + Coord2(635.5146484375, 783.2322387695313), + Coord2(639.78515625, 787.8326416015625), + ), + Coord2(643.0133056640625, 788.3185424804688), + ) + .curve_to( + ( + Coord2(649.2066650390625, 786.63037109375), + Coord2(652.2764892578125, 743.0693359375), + ), + Coord2(647.0385131835938, 738.4141235351563), + ) + .curve_to( + ( + Coord2(641.272216796875, 735.0977172851563), + Coord2(634.1425170898438, 743.2423095703125), + ), + Coord2(637.9150390625, 744.0158081054688), + ) + .curve_to( + ( + Coord2(637.6356201171875, 746.4281005859375), + Coord2(637.637939453125, 749.1239013671875), + ), + Coord2(637.6622314453125, 751.2858276367188), + ) + .curve_to( + ( + Coord2(637.6663208007813, 752.229248046875), + Coord2(637.6764526367188, 753.086669921875), + ), + Coord2(637.6630859375, 753.984619140625), + ) + .curve_to( + ( + Coord2(637.6626586914063, 753.9716186523438), + Coord2(637.6622314453125, 754.0381469726563), + ), + Coord2(637.6596069335938, 754.1043090820313), + ) + .curve_to( + ( + Coord2(637.6473388671875, 754.4176025390625), + Coord2(637.6455078125, 754.7255859375), + ), + Coord2(637.633544921875, 755.0328369140625), + ) + .curve_to( + ( + Coord2(641.7747802734375, 755.7510986328125), + Coord2(648.1893310546875, 754.4743041992188), + ), + Coord2(643.877685546875, 752.4591064453125), + ) + .curve_to( + ( + Coord2(643.8726196289063, 751.7140502929688), + Coord2(643.8517456054688, 750.79736328125), + ), + Coord2(643.8246459960938, 750.2509765625), + ) + .curve_to( + ( + Coord2(643.6224365234375, 746.2362670898438), + Coord2(642.779296875, 740.5953369140625), + ), + Coord2(642.336669921875, 737.1245727539063), + ) + .curve_to( + ( + Coord2(642.2940673828125, 736.7880249023438), + Coord2(642.18701171875, 736.4603881835938), + ), + Coord2(642.14453125, 736.12451171875), + ) + .curve_to( + ( + Coord2(640.6148071289063, 724.0070190429688), + Coord2(636.8263549804688, 712.7576293945313), + ), + Coord2(633.2051391601563, 700.6627807617188), + ) + .build(); - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![path.clone()], 0.01); - assert!(removed.len() != 0); + let removed = path_remove_interior_points::<_, SimpleBezierPath>(&[path.clone()], 0.01); + assert!(!removed.is_empty()); let mut graph_path = GraphPath::from_path(&path, PathLabel(0, PathDirection::Clockwise)); graph_path.self_collide(0.01); graph_path.set_exterior_by_removing_interior_points(); let paths = graph_path.exterior_paths::(); - assert!(paths.len() != 0); + assert!(!paths.is_empty()); assert!(paths.len() == 3); // Must always be a following edge that's an exterior one for edge in graph_path.all_edges() { - let end_point_idx = edge.end_point_index(); - let edge_ref = edge.into(); + let end_point_idx = edge.end_point_index(); + let edge_ref = edge.into(); if graph_path.edge_kind(edge_ref) == GraphPathEdgeKind::Exterior { - assert!(graph_path.edge_refs_for_point(end_point_idx).any(|edge| graph_path.edge_kind(edge) == GraphPathEdgeKind::Exterior)); + assert!(graph_path + .edge_refs_for_point(end_point_idx) + .any(|edge| graph_path.edge_kind(edge) == GraphPathEdgeKind::Exterior)); } } } #[test] fn remove_interior_points_4() { - let path = BezierPathBuilder::::start(Coord2(579.952392578125, 848.2322998046875)) - .curve_to((Coord2(580.9503784179688, 847.3311157226563), Coord2(579.8958129882813, 847.4636840820313)), Coord2(579.7269897460938, 847.880615234375)) - .curve_to((Coord2(579.593505859375, 848.30517578125), Coord2(579.1264038085938, 849.0411376953125)), Coord2(579.0646362304688, 849.64892578125)) - .curve_to((Coord2(577.8814086914063, 850.4049072265625), Coord2(578.8155517578125, 853.5064697265625)), Coord2(579.9620971679688, 852.8971557617188)) - .curve_to((Coord2(580.7517700195313, 852.6128540039063), Coord2(580.5936889648438, 852.1756591796875)), Coord2(579.82666015625, 852.1787719726563)) - .curve_to((Coord2(578.5782470703125, 852.3163452148438), Coord2(578.22900390625, 859.4969482421875)), Coord2(578.1416625976563, 860.2805786132813)) - .curve_to((Coord2(579.2769775390625, 861.6010131835938), Coord2(581.9407348632813, 857.04541015625)), Coord2(580.010986328125, 854.8056640625)) - .curve_to((Coord2(582.6585083007813, 854.671875), Coord2(578.6830444335938, 854.4993896484375)), Coord2(576.0272827148438, 854.4729614257813)) - .curve_to((Coord2(575.8976440429688, 855.9894409179688), Coord2(576.928466796875, 870.5877685546875)), Coord2(577.4629516601563, 873.9253540039063)) - .curve_to((Coord2(579.9541015625, 874.6844482421875), Coord2(583.33642578125, 873.74853515625)), Coord2(580.8639526367188, 873.3152465820313)) - .curve_to((Coord2(582.581298828125, 871.1629028320313), Coord2(578.4156494140625, 866.2734375)), Coord2(576.1420288085938, 867.1937866210938)) - .curve_to((Coord2(575.8514404296875, 868.4409790039063), Coord2(581.5850219726563, 885.1395263671875)), Coord2(581.817138671875, 885.0364990234375)) - .curve_to((Coord2(585.2017211914063, 884.2693481445313), Coord2(580.9660034179688, 873.0250854492188)), Coord2(577.1375732421875, 874.58740234375)) - .curve_to((Coord2(576.8878173828125, 875.6769409179688), Coord2(584.0165405273438, 892.134521484375)), Coord2(585.7166137695313, 893.5682373046875)) - .curve_to((Coord2(586.0726928710938, 894.0137329101563), Coord2(586.338134765625, 894.3269653320313)), Coord2(586.606201171875, 894.654541015625)) - .curve_to((Coord2(590.0245361328125, 893.7957763671875), Coord2(583.5659790039063, 877.0528564453125)), Coord2(581.8451538085938, 878.669921875)) - .curve_to((Coord2(579.1322021484375, 880.3388671875), Coord2(579.1969604492188, 880.3540649414063)), Coord2(579.22119140625, 880.3956909179688)) - .curve_to((Coord2(579.2321166992188, 880.4144287109375), Coord2(579.2623291015625, 880.4225463867188)), Coord2(579.2734985351563, 880.4417114257813)) - .curve_to((Coord2(581.0283203125, 883.2745971679688), Coord2(584.3287353515625, 887.9052124023438)), Coord2(586.827392578125, 891.2733154296875)) - .curve_to((Coord2(588.0198974609375, 892.8840942382813), Coord2(588.9678955078125, 894.2404174804688)), Coord2(589.553466796875, 895.0764770507813)) - .curve_to((Coord2(591.9968872070313, 894.783935546875), Coord2(591.6580810546875, 890.1458740234375)), Coord2(587.6173706054688, 889.505859375)) - .curve_to((Coord2(586.9524536132813, 886.0717163085938), Coord2(582.7312622070313, 886.6657104492188)), Coord2(582.2239990234375, 889.3804931640625)) - .curve_to((Coord2(583.8734130859375, 891.468994140625), Coord2(595.0052490234375, 898.9844360351563)), Coord2(598.0443115234375, 899.9608764648438)) - .curve_to((Coord2(600.0397338867188, 901.0479736328125), Coord2(601.4949951171875, 899.474853515625)), Coord2(601.6517333984375, 898.8914794921875)) - .curve_to((Coord2(601.4926147460938, 898.7960815429688), Coord2(601.6734008789063, 898.8751220703125)), Coord2(601.414794921875, 898.8958129882813)) - .curve_to((Coord2(601.1598022460937, 898.61005859375), Coord2(600.8198120117188, 898.229052734375)), Coord2(600.5648193359375, 897.9432983398438)) - .curve_to((Coord2(600.4238891601563, 898.099365234375), Coord2(600.2045288085938, 898.0982055664063)), Coord2(600.1848754882813, 898.165283203125)) - .curve_to((Coord2(600.1288452148438, 898.01171875), Coord2(601.2973022460938, 897.0179443359375)), Coord2(599.201171875, 896.6677856445313)) - .curve_to((Coord2(596.1993408203125, 895.5221557617188), Coord2(586.39306640625, 889.2333984375)), Coord2(584.7064208984375, 887.3141479492188)) - .curve_to((Coord2(582.2571411132813, 887.6959838867188), Coord2(581.8307495117188, 891.6290283203125)), Coord2(585.4521484375, 891.966552734375)) - .curve_to((Coord2(586.3348999023438, 895.6588745117188), Coord2(591.5681762695313, 895.923828125)), Coord2(592.2828369140625, 893.1641235351563)) - .curve_to((Coord2(591.6741943359375, 892.2957153320313), Coord2(590.6009521484375, 890.9229736328125)), Coord2(589.4315185546875, 889.3446044921875)) - .curve_to((Coord2(586.8740234375, 885.8859252929688), Coord2(583.55712890625, 881.581787109375)), Coord2(581.8828125, 878.87548828125)) - .curve_to((Coord2(581.8934326171875, 878.8925170898438), Coord2(581.901611328125, 878.8626708984375)), Coord2(581.890625, 878.8438110351563)) - .curve_to((Coord2(581.8668823242188, 878.802978515625), Coord2(581.8843994140625, 878.73681640625)), Coord2(581.8605346679688, 878.69580078125)) - .curve_to((Coord2(577.6611938476563, 882.36572265625), Coord2(588.2183837890625, 893.8323974609375)), Coord2(588.7944946289063, 891.6168823242188)) - .curve_to((Coord2(589.3372802734375, 891.8733520507813), Coord2(589.0824584960938, 891.5507202148438)), Coord2(588.8222045898438, 891.2327270507813)) - .curve_to((Coord2(587.4506225585938, 888.200927734375), Coord2(579.8250732421875, 875.5438842773438)), Coord2(579.8422241210938, 876.18017578125)) - .curve_to((Coord2(576.9286499023438, 876.1892700195313), Coord2(580.5906372070313, 884.2669067382813)), Coord2(583.94775390625, 883.4625854492188)) - .curve_to((Coord2(583.9148559570313, 883.0008544921875), Coord2(579.216064453125, 868.5027465820313)), Coord2(579.3073120117188, 867.8202514648438)) - .curve_to((Coord2(577.37939453125, 866.9944458007813), Coord2(574.9931030273438, 871.8741455078125)), Coord2(577.4772338867188, 873.9320678710938)) - .curve_to((Coord2(575.3856201171875, 875.5897216796875), Coord2(578.8990478515625, 875.3737182617188)), Coord2(580.9719848632813, 873.3901977539063)) - .curve_to((Coord2(580.464111328125, 869.892578125), Coord2(579.9021606445313, 856.1516723632813)), Coord2(580.0052490234375, 854.7948608398438)) - .curve_to((Coord2(577.3517456054688, 854.7406005859375), Coord2(573.3838500976563, 854.7532958984375)), Coord2(576.0455932617188, 854.9674072265625)) - .curve_to((Coord2(574.2247924804688, 855.3988037109375), Coord2(577.3884887695313, 863.1025390625)), Coord2(580.003662109375, 863.5904541015625)) - .curve_to((Coord2(583.629150390625, 862.2855224609375), Coord2(581.4857177734375, 849.1261596679688)), Coord2(579.1959838867188, 849.5098266601563)) - .curve_to((Coord2(577.6004028320313, 849.708740234375), Coord2(577.3341674804688, 855.0685424804688)), Coord2(578.9075927734375, 855.1510620117188)) - .curve_to((Coord2(580.097900390625, 854.562255859375), Coord2(582.2202758789063, 849.3984375)), Coord2(581.0670776367188, 849.8407592773438)) - .curve_to((Coord2(581.1075439453125, 849.38232421875), Coord2(581.0016479492188, 848.8671264648438)), Coord2(581.1563720703125, 848.2933349609375)) - .curve_to((Coord2(581.5310668945313, 846.8276977539063), Coord2(580.760498046875, 845.9767456054688)), Coord2(579.556640625, 847.9266357421875)) - .build(); + let path = + BezierPathBuilder::::start(Coord2(579.952392578125, 848.2322998046875)) + .curve_to( + ( + Coord2(580.9503784179688, 847.3311157226563), + Coord2(579.8958129882813, 847.4636840820313), + ), + Coord2(579.7269897460938, 847.880615234375), + ) + .curve_to( + ( + Coord2(579.593505859375, 848.30517578125), + Coord2(579.1264038085938, 849.0411376953125), + ), + Coord2(579.0646362304688, 849.64892578125), + ) + .curve_to( + ( + Coord2(577.8814086914063, 850.4049072265625), + Coord2(578.8155517578125, 853.5064697265625), + ), + Coord2(579.9620971679688, 852.8971557617188), + ) + .curve_to( + ( + Coord2(580.7517700195313, 852.6128540039063), + Coord2(580.5936889648438, 852.1756591796875), + ), + Coord2(579.82666015625, 852.1787719726563), + ) + .curve_to( + ( + Coord2(578.5782470703125, 852.3163452148438), + Coord2(578.22900390625, 859.4969482421875), + ), + Coord2(578.1416625976563, 860.2805786132813), + ) + .curve_to( + ( + Coord2(579.2769775390625, 861.6010131835938), + Coord2(581.9407348632813, 857.04541015625), + ), + Coord2(580.010986328125, 854.8056640625), + ) + .curve_to( + ( + Coord2(582.6585083007813, 854.671875), + Coord2(578.6830444335938, 854.4993896484375), + ), + Coord2(576.0272827148438, 854.4729614257813), + ) + .curve_to( + ( + Coord2(575.8976440429688, 855.9894409179688), + Coord2(576.928466796875, 870.5877685546875), + ), + Coord2(577.4629516601563, 873.9253540039063), + ) + .curve_to( + ( + Coord2(579.9541015625, 874.6844482421875), + Coord2(583.33642578125, 873.74853515625), + ), + Coord2(580.8639526367188, 873.3152465820313), + ) + .curve_to( + ( + Coord2(582.581298828125, 871.1629028320313), + Coord2(578.4156494140625, 866.2734375), + ), + Coord2(576.1420288085938, 867.1937866210938), + ) + .curve_to( + ( + Coord2(575.8514404296875, 868.4409790039063), + Coord2(581.5850219726563, 885.1395263671875), + ), + Coord2(581.817138671875, 885.0364990234375), + ) + .curve_to( + ( + Coord2(585.2017211914063, 884.2693481445313), + Coord2(580.9660034179688, 873.0250854492188), + ), + Coord2(577.1375732421875, 874.58740234375), + ) + .curve_to( + ( + Coord2(576.8878173828125, 875.6769409179688), + Coord2(584.0165405273438, 892.134521484375), + ), + Coord2(585.7166137695313, 893.5682373046875), + ) + .curve_to( + ( + Coord2(586.0726928710938, 894.0137329101563), + Coord2(586.338134765625, 894.3269653320313), + ), + Coord2(586.606201171875, 894.654541015625), + ) + .curve_to( + ( + Coord2(590.0245361328125, 893.7957763671875), + Coord2(583.5659790039063, 877.0528564453125), + ), + Coord2(581.8451538085938, 878.669921875), + ) + .curve_to( + ( + Coord2(579.1322021484375, 880.3388671875), + Coord2(579.1969604492188, 880.3540649414063), + ), + Coord2(579.22119140625, 880.3956909179688), + ) + .curve_to( + ( + Coord2(579.2321166992188, 880.4144287109375), + Coord2(579.2623291015625, 880.4225463867188), + ), + Coord2(579.2734985351563, 880.4417114257813), + ) + .curve_to( + ( + Coord2(581.0283203125, 883.2745971679688), + Coord2(584.3287353515625, 887.9052124023438), + ), + Coord2(586.827392578125, 891.2733154296875), + ) + .curve_to( + ( + Coord2(588.0198974609375, 892.8840942382813), + Coord2(588.9678955078125, 894.2404174804688), + ), + Coord2(589.553466796875, 895.0764770507813), + ) + .curve_to( + ( + Coord2(591.9968872070313, 894.783935546875), + Coord2(591.6580810546875, 890.1458740234375), + ), + Coord2(587.6173706054688, 889.505859375), + ) + .curve_to( + ( + Coord2(586.9524536132813, 886.0717163085938), + Coord2(582.7312622070313, 886.6657104492188), + ), + Coord2(582.2239990234375, 889.3804931640625), + ) + .curve_to( + ( + Coord2(583.8734130859375, 891.468994140625), + Coord2(595.0052490234375, 898.9844360351563), + ), + Coord2(598.0443115234375, 899.9608764648438), + ) + .curve_to( + ( + Coord2(600.0397338867188, 901.0479736328125), + Coord2(601.4949951171875, 899.474853515625), + ), + Coord2(601.6517333984375, 898.8914794921875), + ) + .curve_to( + ( + Coord2(601.4926147460938, 898.7960815429688), + Coord2(601.6734008789063, 898.8751220703125), + ), + Coord2(601.414794921875, 898.8958129882813), + ) + .curve_to( + ( + Coord2(601.1598022460937, 898.61005859375), + Coord2(600.8198120117188, 898.229052734375), + ), + Coord2(600.5648193359375, 897.9432983398438), + ) + .curve_to( + ( + Coord2(600.4238891601563, 898.099365234375), + Coord2(600.2045288085938, 898.0982055664063), + ), + Coord2(600.1848754882813, 898.165283203125), + ) + .curve_to( + ( + Coord2(600.1288452148438, 898.01171875), + Coord2(601.2973022460938, 897.0179443359375), + ), + Coord2(599.201171875, 896.6677856445313), + ) + .curve_to( + ( + Coord2(596.1993408203125, 895.5221557617188), + Coord2(586.39306640625, 889.2333984375), + ), + Coord2(584.7064208984375, 887.3141479492188), + ) + .curve_to( + ( + Coord2(582.2571411132813, 887.6959838867188), + Coord2(581.8307495117188, 891.6290283203125), + ), + Coord2(585.4521484375, 891.966552734375), + ) + .curve_to( + ( + Coord2(586.3348999023438, 895.6588745117188), + Coord2(591.5681762695313, 895.923828125), + ), + Coord2(592.2828369140625, 893.1641235351563), + ) + .curve_to( + ( + Coord2(591.6741943359375, 892.2957153320313), + Coord2(590.6009521484375, 890.9229736328125), + ), + Coord2(589.4315185546875, 889.3446044921875), + ) + .curve_to( + ( + Coord2(586.8740234375, 885.8859252929688), + Coord2(583.55712890625, 881.581787109375), + ), + Coord2(581.8828125, 878.87548828125), + ) + .curve_to( + ( + Coord2(581.8934326171875, 878.8925170898438), + Coord2(581.901611328125, 878.8626708984375), + ), + Coord2(581.890625, 878.8438110351563), + ) + .curve_to( + ( + Coord2(581.8668823242188, 878.802978515625), + Coord2(581.8843994140625, 878.73681640625), + ), + Coord2(581.8605346679688, 878.69580078125), + ) + .curve_to( + ( + Coord2(577.6611938476563, 882.36572265625), + Coord2(588.2183837890625, 893.8323974609375), + ), + Coord2(588.7944946289063, 891.6168823242188), + ) + .curve_to( + ( + Coord2(589.3372802734375, 891.8733520507813), + Coord2(589.0824584960938, 891.5507202148438), + ), + Coord2(588.8222045898438, 891.2327270507813), + ) + .curve_to( + ( + Coord2(587.4506225585938, 888.200927734375), + Coord2(579.8250732421875, 875.5438842773438), + ), + Coord2(579.8422241210938, 876.18017578125), + ) + .curve_to( + ( + Coord2(576.9286499023438, 876.1892700195313), + Coord2(580.5906372070313, 884.2669067382813), + ), + Coord2(583.94775390625, 883.4625854492188), + ) + .curve_to( + ( + Coord2(583.9148559570313, 883.0008544921875), + Coord2(579.216064453125, 868.5027465820313), + ), + Coord2(579.3073120117188, 867.8202514648438), + ) + .curve_to( + ( + Coord2(577.37939453125, 866.9944458007813), + Coord2(574.9931030273438, 871.8741455078125), + ), + Coord2(577.4772338867188, 873.9320678710938), + ) + .curve_to( + ( + Coord2(575.3856201171875, 875.5897216796875), + Coord2(578.8990478515625, 875.3737182617188), + ), + Coord2(580.9719848632813, 873.3901977539063), + ) + .curve_to( + ( + Coord2(580.464111328125, 869.892578125), + Coord2(579.9021606445313, 856.1516723632813), + ), + Coord2(580.0052490234375, 854.7948608398438), + ) + .curve_to( + ( + Coord2(577.3517456054688, 854.7406005859375), + Coord2(573.3838500976563, 854.7532958984375), + ), + Coord2(576.0455932617188, 854.9674072265625), + ) + .curve_to( + ( + Coord2(574.2247924804688, 855.3988037109375), + Coord2(577.3884887695313, 863.1025390625), + ), + Coord2(580.003662109375, 863.5904541015625), + ) + .curve_to( + ( + Coord2(583.629150390625, 862.2855224609375), + Coord2(581.4857177734375, 849.1261596679688), + ), + Coord2(579.1959838867188, 849.5098266601563), + ) + .curve_to( + ( + Coord2(577.6004028320313, 849.708740234375), + Coord2(577.3341674804688, 855.0685424804688), + ), + Coord2(578.9075927734375, 855.1510620117188), + ) + .curve_to( + ( + Coord2(580.097900390625, 854.562255859375), + Coord2(582.2202758789063, 849.3984375), + ), + Coord2(581.0670776367188, 849.8407592773438), + ) + .curve_to( + ( + Coord2(581.1075439453125, 849.38232421875), + Coord2(581.0016479492188, 848.8671264648438), + ), + Coord2(581.1563720703125, 848.2933349609375), + ) + .curve_to( + ( + Coord2(581.5310668945313, 846.8276977539063), + Coord2(580.760498046875, 845.9767456054688), + ), + Coord2(579.556640625, 847.9266357421875), + ) + .build(); // Fails an internal assertion (cannot determine if a line is in or outside) - let removed = path_remove_interior_points::<_, SimpleBezierPath>(&vec![path.clone()], 0.01); - assert!(removed.len() != 0); + let removed = path_remove_interior_points::<_, SimpleBezierPath>(&[path], 0.01); + assert!(!removed.is_empty()); /* -- we do generate extra points right now, indicating that lines are overlapping -- overlapping is occurring because we move 'close' points together after performing the self-collide diff --git a/tests/bezier/path/arithmetic_cut.rs b/tests/bezier/path/arithmetic_cut.rs index 018968dc..10723738 100644 --- a/tests/bezier/path/arithmetic_cut.rs +++ b/tests/bezier/path/arithmetic_cut.rs @@ -1,23 +1,23 @@ -use flo_curves::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{path_cut, BezierPath, BezierPathBuilder, SimpleBezierPath}; +use flo_curves::{BoundingBox, Coord2, Coordinate, Line}; #[test] fn cut_square() { - let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) + let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) .line_to(Coord2(10.0, 5.0)) .line_to(Coord2(10.0, 10.0)) .line_to(Coord2(5.0, 10.0)) .line_to(Coord2(5.0, 5.0)) .build(); - let square_2 = BezierPathBuilder::::start(Coord2(7.5, 7.5)) + let square_2 = BezierPathBuilder::::start(Coord2(7.5, 7.5)) .line_to(Coord2(15.0, 7.5)) .line_to(Coord2(15.0, 15.0)) .line_to(Coord2(7.5, 15.0)) .line_to(Coord2(7.5, 7.5)) .build(); - let cut_square = path_cut::<_, _, SimpleBezierPath>(&vec![square_1], &vec![square_2], 0.01); + let cut_square = path_cut::<_, _, SimpleBezierPath>(&[square_1], &[square_2], 0.01); assert!(cut_square.exterior_path.len() == 1); assert!(cut_square.interior_path.len() == 1); @@ -28,70 +28,69 @@ fn cut_square() { #[test] fn cut_square_entirely_interior() { - let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) + let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) .line_to(Coord2(10.0, 5.0)) .line_to(Coord2(10.0, 10.0)) .line_to(Coord2(5.0, 10.0)) .line_to(Coord2(5.0, 5.0)) .build(); - let square_2 = BezierPathBuilder::::start(Coord2(2.0, 2.0)) + let square_2 = BezierPathBuilder::::start(Coord2(2.0, 2.0)) .line_to(Coord2(15.0, 2.0)) .line_to(Coord2(15.0, 15.0)) .line_to(Coord2(2.0, 15.0)) .line_to(Coord2(2.0, 2.0)) .build(); - let cut_square = path_cut::<_, _, SimpleBezierPath>(&vec![square_1], &vec![square_2], 0.01); + let cut_square = path_cut::<_, _, SimpleBezierPath>(&[square_1], &[square_2], 0.01); - assert!(cut_square.exterior_path.len() == 0); + assert!(cut_square.exterior_path.is_empty()); assert!(cut_square.interior_path.len() == 1); assert!(cut_square.interior_path[0].points().len() == 4); } - #[test] fn cut_square_entirely_exterior() { - let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) + let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) .line_to(Coord2(10.0, 5.0)) .line_to(Coord2(10.0, 10.0)) .line_to(Coord2(5.0, 10.0)) .line_to(Coord2(5.0, 5.0)) .build(); - let square_2 = BezierPathBuilder::::start(Coord2(20.0, 20.0)) + let square_2 = BezierPathBuilder::::start(Coord2(20.0, 20.0)) .line_to(Coord2(15.0, 20.0)) .line_to(Coord2(15.0, 15.0)) .line_to(Coord2(20.0, 15.0)) .line_to(Coord2(20.0, 20.0)) .build(); - let cut_square = path_cut::<_, _, SimpleBezierPath>(&vec![square_1], &vec![square_2], 0.01); + let cut_square = path_cut::<_, _, SimpleBezierPath>(&[square_1], &[square_2], 0.01); assert!(cut_square.exterior_path.len() == 1); - assert!(cut_square.interior_path.len() == 0); + assert!(cut_square.interior_path.is_empty()); assert!(cut_square.exterior_path[0].points().len() == 4); } #[test] fn cut_square_center() { - let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) + let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) .line_to(Coord2(10.0, 5.0)) .line_to(Coord2(10.0, 10.0)) .line_to(Coord2(5.0, 10.0)) .line_to(Coord2(5.0, 5.0)) .build(); - let square_2 = BezierPathBuilder::::start(Coord2(6.0, 6.0)) + let square_2 = BezierPathBuilder::::start(Coord2(6.0, 6.0)) .line_to(Coord2(9.0, 6.0)) .line_to(Coord2(9.0, 9.0)) .line_to(Coord2(6.0, 9.0)) .line_to(Coord2(6.0, 6.0)) .build(); - let cut_square = path_cut::<_, _, SimpleBezierPath>(&vec![square_1], &vec![square_2], 0.01); + let cut_square = path_cut::<_, _, SimpleBezierPath>(&[square_1], &[square_2], 0.01); assert!(cut_square.exterior_path.len() == 2); assert!(cut_square.interior_path.len() == 1); diff --git a/tests/bezier/path/arithmetic_intersect.rs b/tests/bezier/path/arithmetic_intersect.rs index 50060397..4bf761df 100644 --- a/tests/bezier/path/arithmetic_intersect.rs +++ b/tests/bezier/path/arithmetic_intersect.rs @@ -1,8 +1,13 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::line::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + path_full_intersect, path_intersect, BezierPath, BezierPathBuilder, BezierPathFactory, + GraphPath, PathDirection, PathLabel, SimpleBezierPath, +}; +use flo_curves::bezier::{ + curve_intersects_curve_clip, BezierCurve, BezierCurveFactory, BoundingBox, Coord2, Coordinate, + Coordinate2D, Coordinate3D, Curve, +}; +use flo_curves::line::{line_to_bezier, Line, Line2D}; use std::f64; use std::iter; @@ -10,13 +15,17 @@ use std::iter; #[test] fn intersect_two_doughnuts() { // Two overlapping circles - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); // Combine them - let combined_circles = path_intersect::<_, _, SimpleBezierPath>(&vec![circle1, inner_circle1], &vec![circle2, inner_circle2], 0.1); + let combined_circles = path_intersect::<_, _, SimpleBezierPath>( + &[circle1, inner_circle1], + &[circle2, inner_circle2], + 0.1, + ); println!("{:?}", combined_circles.len()); println!("{:?}", combined_circles); @@ -26,13 +35,17 @@ fn intersect_two_doughnuts() { #[test] fn full_intersect_two_doughnuts() { // Two overlapping circles - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); // Combine them - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1, inner_circle1], &vec![circle2, inner_circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>( + &[circle1, inner_circle1], + &[circle2, inner_circle2], + 0.1, + ); let combined_circles = &intersection.intersecting_path; println!("{:?}", combined_circles.len()); @@ -42,10 +55,10 @@ fn full_intersect_two_doughnuts() { #[test] fn full_intersect_two_partially_overlapping_circles() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); assert!(intersection.intersecting_path.len() == 1); assert!(intersection.exterior_paths[0].len() == 1); @@ -54,87 +67,99 @@ fn full_intersect_two_partially_overlapping_circles() { #[test] fn full_intersect_two_non_overlapping_circles() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let circle2 = Circle::new(Coord2(15.0, 5.0), 4.0).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle2 = Circle::new(Coord2(15.0, 5.0), 4.0).to_path::(); - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); - assert!(intersection.intersecting_path.len() == 0); + assert!(intersection.intersecting_path.is_empty()); assert!(intersection.exterior_paths[0].len() == 1); assert!(intersection.exterior_paths[1].len() == 1); } #[test] fn full_intersect_interior_circles_1() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let circle2 = Circle::new(Coord2(5.0, 5.0), 3.5).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle2 = Circle::new(Coord2(5.0, 5.0), 3.5).to_path::(); - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); assert!(intersection.intersecting_path.len() == 1); assert!(intersection.exterior_paths[0].len() == 2); - assert!(intersection.exterior_paths[1].len() == 0); + assert!(intersection.exterior_paths[1].is_empty()); } #[test] fn full_intersect_interior_circles_2() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 3.5).to_path::(); - let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 3.5).to_path::(); + let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); assert!(intersection.intersecting_path.len() == 1); - assert!(intersection.exterior_paths[0].len() == 0); + assert!(intersection.exterior_paths[0].is_empty()); assert!(intersection.exterior_paths[1].len() == 2); } #[test] fn fintersect_two_fully_overlapping_circles() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let intersection = path_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); assert!(intersection.len() == 1); } #[test] fn full_intersect_two_fully_overlapping_circles() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.1); + let intersection = path_full_intersect::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.1); println!("{:?}", intersection); assert!(intersection.intersecting_path.len() == 1); - assert!(intersection.exterior_paths[0].len() == 0); - assert!(intersection.exterior_paths[1].len() == 0); + assert!(intersection.exterior_paths[0].is_empty()); + assert!(intersection.exterior_paths[1].is_empty()); } #[test] fn repeatedly_full_intersect_circle() { // Start with a circle - let circle = Circle::new(Coord2(500.0, 500.0), 116.0).to_path::(); + let circle = Circle::new(Coord2(500.0, 500.0), 116.0).to_path::(); // Cut 16 triangular slices from it - let mut remaining = vec![circle]; - let mut slices = vec![]; + let mut remaining = vec![circle]; + let mut slices = vec![]; for slice_idx in 0..16 { // Angle in radians of this slice - let middle_angle = f64::consts::PI*2.0 / 16.0 * (slice_idx as f64); - let start_angle = middle_angle - (f64::consts::PI*2.0 / 32.0); - let end_angle = middle_angle + (f64::consts::PI*2.0 / 32.0); + let middle_angle = f64::consts::PI * 2.0 / 16.0 * (slice_idx as f64); + let start_angle = middle_angle - (f64::consts::PI * 2.0 / 32.0); + let end_angle = middle_angle + (f64::consts::PI * 2.0 / 32.0); // Create a triangle slice - let (center_x, center_y) = (500.0, 500.0); - let (x1, y1) = (center_x + (f64::sin(start_angle) * 300.0), center_y + (f64::cos(start_angle) * 300.0)); - let (x2, y2) = (center_x + (f64::sin(end_angle) * 300.0), center_y + (f64::cos(end_angle) * 300.0)); - let (x3, y3) = (center_x + (f64::sin(start_angle) * 16.0), center_y + (f64::cos(start_angle) * 16.0)); - let (x4, y4) = (center_x + (f64::sin(end_angle) * 16.0), center_y + (f64::cos(end_angle) * 16.0)); - - let fragment = BezierPathBuilder::::start(Coord2(x3, y3)) + let (center_x, center_y) = (500.0, 500.0); + let (x1, y1) = ( + center_x + (f64::sin(start_angle) * 300.0), + center_y + (f64::cos(start_angle) * 300.0), + ); + let (x2, y2) = ( + center_x + (f64::sin(end_angle) * 300.0), + center_y + (f64::cos(end_angle) * 300.0), + ); + let (x3, y3) = ( + center_x + (f64::sin(start_angle) * 16.0), + center_y + (f64::cos(start_angle) * 16.0), + ); + let (x4, y4) = ( + center_x + (f64::sin(end_angle) * 16.0), + center_y + (f64::cos(end_angle) * 16.0), + ); + + let fragment = BezierPathBuilder::::start(Coord2(x3, y3)) .line_to(Coord2(x1, y1)) .line_to(Coord2(x2, y2)) .line_to(Coord2(x4, y4)) @@ -142,7 +167,8 @@ fn repeatedly_full_intersect_circle() { .build(); // Cut the circle via the fragment - let cut_circle = path_full_intersect::<_, _, SimpleBezierPath>(&vec![fragment], &remaining, 0.01); + let cut_circle = + path_full_intersect::<_, _, SimpleBezierPath>(&[fragment], &remaining, 0.01); // Add the slice and the remaining part of the circle slices.push(cut_circle.intersecting_path); @@ -154,13 +180,15 @@ fn repeatedly_full_intersect_circle() { assert!(circle_fragment.len() == 1); let start_point = circle_fragment[0].start_point(); - let points = circle_fragment[0].points().map(|(_, _, p)| p); - let all_points = iter::once(start_point).chain(points); + let points = circle_fragment[0].points().map(|(_, _, p)| p); + let all_points = iter::once(start_point).chain(points); for circle_point in all_points { let distance_to_center = circle_point.distance_to(&Coord2(500.0, 500.0)); println!("{:?}", distance_to_center); - assert!((distance_to_center-16.0).abs() < 0.1 || (distance_to_center-116.0).abs() < 1.0); + assert!( + (distance_to_center - 16.0).abs() < 0.1 || (distance_to_center - 116.0).abs() < 1.0 + ); } } @@ -168,26 +196,39 @@ fn repeatedly_full_intersect_circle() { assert!(remaining.len() == 1); let start_point = remaining[0].start_point(); - let points = remaining[0].points().map(|(_, _, p)| p); - let all_points = iter::once(start_point).chain(points); + let points = remaining[0].points().map(|(_, _, p)| p); + let all_points = iter::once(start_point).chain(points); for circle_point in all_points { let distance_to_center = circle_point.distance_to(&Coord2(500.0, 500.0)); println!("{:?}", distance_to_center); - assert!((distance_to_center-0.0).abs() < 0.1 || (distance_to_center-16.0).abs() < 1.0); + assert!((distance_to_center - 0.0).abs() < 0.1 || (distance_to_center - 16.0).abs() < 1.0); } } -fn convert_path_to_f32_and_back((start_point, remaining_points): SimpleBezierPath) -> SimpleBezierPath { - let start_f32 = (start_point.x() as f32, start_point.y() as f32); - let remaining_f32 = remaining_points.into_iter().map(|(cp1, cp2, p)| { - ((cp1.x() as f32, cp1.y() as f32), (cp2.x() as f32, cp2.y() as f32), (p.x() as f32, p.y() as f32)) +fn convert_path_to_f32_and_back( + (start_point, remaining_points): SimpleBezierPath, +) -> SimpleBezierPath { + let start_f32 = (start_point.x() as f32, start_point.y() as f32); + let remaining_f32 = remaining_points.into_iter().map(|(cp1, cp2, p)| { + ( + (cp1.x() as f32, cp1.y() as f32), + (cp2.x() as f32, cp2.y() as f32), + (p.x() as f32, p.y() as f32), + ) }); let path_points = remaining_f32.map(|(cp1, cp2, p)| { - (Coord2(cp1.0 as f64, cp1.1 as f64), Coord2(cp2.0 as f64, cp2.1 as f64), Coord2(p.0 as f64, p.1 as f64)) + ( + Coord2(cp1.0 as f64, cp1.1 as f64), + Coord2(cp2.0 as f64, cp2.1 as f64), + Coord2(p.0 as f64, p.1 as f64), + ) }); - (Coord2(start_f32.0 as _, start_f32.1 as _), path_points.collect()) + ( + Coord2(start_f32.0 as _, start_f32.1 as _), + path_points.collect(), + ) } #[test] @@ -195,26 +236,38 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { // Converting the remaining curve to f32 and back again results in a failed intersection due to slightly different line positions, which causes the cut to fail on some slices // Start with a circle - let circle = Circle::new(Coord2(500.0, 500.0), 116.0).to_path::(); + let circle = Circle::new(Coord2(500.0, 500.0), 116.0).to_path::(); // Cut 16 triangular slices from it - let mut remaining = vec![circle]; - let mut slices = vec![]; + let mut remaining = vec![circle]; + let mut slices = vec![]; for slice_idx in 0..16 { // Angle in radians of this slice - let middle_angle = f64::consts::PI*2.0 / 16.0 * (slice_idx as f64); - let start_angle = middle_angle - (f64::consts::PI*2.0 / 32.0); - let end_angle = middle_angle + (f64::consts::PI*2.0 / 32.0); + let middle_angle = f64::consts::PI * 2.0 / 16.0 * (slice_idx as f64); + let start_angle = middle_angle - (f64::consts::PI * 2.0 / 32.0); + let end_angle = middle_angle + (f64::consts::PI * 2.0 / 32.0); // Create a triangle slice - let (center_x, center_y) = (500.0, 500.0); - let (x1, y1) = (center_x + (f64::sin(start_angle) * 300.0), center_y + (f64::cos(start_angle) * 300.0)); - let (x2, y2) = (center_x + (f64::sin(end_angle) * 300.0), center_y + (f64::cos(end_angle) * 300.0)); - let (x3, y3) = (center_x + (f64::sin(start_angle) * 16.0), center_y + (f64::cos(start_angle) * 16.0)); - let (x4, y4) = (center_x + (f64::sin(end_angle) * 16.0), center_y + (f64::cos(end_angle) * 16.0)); - - let fragment = BezierPathBuilder::::start(Coord2(x3, y3)) + let (center_x, center_y) = (500.0, 500.0); + let (x1, y1) = ( + center_x + (f64::sin(start_angle) * 300.0), + center_y + (f64::cos(start_angle) * 300.0), + ); + let (x2, y2) = ( + center_x + (f64::sin(end_angle) * 300.0), + center_y + (f64::cos(end_angle) * 300.0), + ); + let (x3, y3) = ( + center_x + (f64::sin(start_angle) * 16.0), + center_y + (f64::cos(start_angle) * 16.0), + ); + let (x4, y4) = ( + center_x + (f64::sin(end_angle) * 16.0), + center_y + (f64::cos(end_angle) * 16.0), + ); + + let fragment = BezierPathBuilder::::start(Coord2(x3, y3)) .line_to(Coord2(x1, y1)) .line_to(Coord2(x2, y2)) .line_to(Coord2(x4, y4)) @@ -222,31 +275,39 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { .build(); // The edges (x3, y3) -> (x1, y1) and (x2, y2) -> (x4, y4) should both collide with at least one edge in the remaining path - for edge in [(Coord2(x3, y3), Coord2(x1, y1)), (Coord2(x2, y2), Coord2(x4, y4))] { + for edge in [ + (Coord2(x3, y3), Coord2(x1, y1)), + (Coord2(x2, y2), Coord2(x4, y4)), + ] { // Convert the edge to a line - let fragment_edge = line_to_bezier::<_, Curve<_>>(&edge); + let fragment_edge = line_to_bezier::<_, Curve<_>>(&edge); // Iterate through the edges in remaining - let mut first_point = remaining[0].start_point(); - let mut num_collisions = 0; + let mut first_point = remaining[0].start_point(); + let mut num_collisions = 0; for (cp1, cp2, end_point) in remaining[0].points() { // Turn into a curve - let remain_edge = Curve::from_points(first_point, (cp1, cp2), end_point); - let intersections = curve_intersects_curve_clip(&fragment_edge, &remain_edge, 0.01); + let remain_edge = Curve::from_points(first_point, (cp1, cp2), end_point); + let intersections = curve_intersects_curve_clip(&fragment_edge, &remain_edge, 0.01); - num_collisions += intersections.len(); - if intersections.len() > 0 { println!(" {:?}", intersections.len()); } + num_collisions += intersections.len(); + if !intersections.is_empty() { + println!(" {:?}", intersections.len()); + } // There should be at least one collision if the start or end point is near the edge - let start_distance = edge.distance_to(&first_point); - let end_distance = edge.distance_to(&end_point); + let start_distance = edge.distance_to(&first_point); + let end_distance = edge.distance_to(&end_point); - if intersections.len() == 0 { - let start_pos = edge.pos_for_point(&first_point); - let end_pos = edge.pos_for_point(&end_point); + if intersections.is_empty() { + let start_pos = edge.pos_for_point(&first_point); + let end_pos = edge.pos_for_point(&end_point); if start_distance.abs() < 1.0 || end_distance.abs() < 1.0 { - println!(" - {:?} {:?} {:?} {:?}", start_distance, end_distance, start_pos, end_pos); + println!( + " - {:?} {:?} {:?} {:?}", + start_distance, end_distance, start_pos, end_pos + ); println!("Fragment: {:?}", fragment_edge); println!("Remaining: {:?}", remain_edge); @@ -257,7 +318,7 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { } // The end point of this curve is the start point of the next curve - first_point = end_point; + first_point = end_point; } println!("Slice {}: {}, {:?}", slice_idx, num_collisions, edge); @@ -266,26 +327,54 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { // Merge the paths and print out the number of edges let mut merged_path = GraphPath::new(); - let fragment_graph = GraphPath::from_merged_paths(vec![fragment.clone()].iter().map(|path| (path, PathLabel(0, PathDirection::from(path))))); - let remain_graph = GraphPath::from_merged_paths(remaining.iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))); - - println!("Slice {}: {} edges in 'remaining' before colliding with the next fragment", slice_idx, remain_graph.all_edges().count()); - - merged_path = merged_path.merge(fragment_graph); - merged_path = merged_path.collide(remain_graph, 0.01); + let fragment_graph = GraphPath::from_merged_paths( + vec![fragment.clone()] + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + ); + let remain_graph = GraphPath::from_merged_paths( + remaining + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ); + + println!( + "Slice {}: {} edges in 'remaining' before colliding with the next fragment", + slice_idx, + remain_graph.all_edges().count() + ); + + merged_path = merged_path.merge(fragment_graph); + merged_path = merged_path.collide(remain_graph, 0.01); merged_path.round(0.01); - println!("Slice {}: {} edges", slice_idx, merged_path.all_edges().count()); + println!( + "Slice {}: {} edges", + slice_idx, + merged_path.all_edges().count() + ); // Cut the circle via the fragment - let cut_circle = path_full_intersect::<_, _, SimpleBezierPath>(&vec![fragment.clone()], &remaining, 0.01); + let cut_circle = + path_full_intersect::<_, _, SimpleBezierPath>(&[fragment.clone()], &remaining, 0.01); if cut_circle.exterior_paths[1].len() != 1 { - use flo_curves::debug::*; + use flo_curves::debug::graph_path_svg_string; let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(remaining.iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); - merged_path = merged_path.collide(GraphPath::from_merged_paths(vec![fragment.clone()].iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), 0.01); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + remaining + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + vec![fragment.clone()] + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + 0.01, + ); //merged_path.round(0.01); @@ -307,7 +396,10 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { remaining = cut_circle.exterior_paths[1].clone(); // Reduce and re-increase the precision of the remaining path (this happens in FlowBetween: even though the points will be in slightly different positions we should still be able to slice using this curve) - remaining = remaining.into_iter().map(|path| convert_path_to_f32_and_back(path)).collect(); + remaining = remaining + .into_iter() + .map(convert_path_to_f32_and_back) + .collect(); } // Each fragment should consist of points that are either at the origin or on the circle @@ -315,28 +407,30 @@ fn repeatedly_full_intersect_circle_f32_intermediate_representation() { assert!(circle_fragment.len() == 1); let start_point = circle_fragment[0].start_point(); - let points = circle_fragment[0].points().map(|(_, _, p)| p); - let all_points = iter::once(start_point).chain(points); + let points = circle_fragment[0].points().map(|(_, _, p)| p); + let all_points = iter::once(start_point).chain(points); for circle_point in all_points { let distance_to_center = circle_point.distance_to(&Coord2(500.0, 500.0)); println!("- {} {:?}", idx, distance_to_center); - assert!((distance_to_center-16.0).abs() < 0.1 || (distance_to_center-116.0).abs() < 1.0); + assert!( + (distance_to_center - 16.0).abs() < 0.1 || (distance_to_center - 116.0).abs() < 1.0 + ); } } // Should be a 16x16 polygon left over for the circle - assert!(remaining.len() > 0); + assert!(!remaining.is_empty()); println!("{:?}", remaining[0]); - let start_point = remaining[remaining.len()-1].start_point(); - let points = remaining[remaining.len()-1].points().map(|(_, _, p)| p); - let all_points = iter::once(start_point).chain(points); + let start_point = remaining[remaining.len() - 1].start_point(); + let points = remaining[remaining.len() - 1].points().map(|(_, _, p)| p); + let all_points = iter::once(start_point).chain(points); for circle_point in all_points { let distance_to_center = circle_point.distance_to(&Coord2(500.0, 500.0)); println!("{:?}", distance_to_center); - assert!((distance_to_center-0.0).abs() < 0.1 || (distance_to_center-16.0).abs() < 1.0); + assert!((distance_to_center - 0.0).abs() < 0.1 || (distance_to_center - 16.0).abs() < 1.0); } assert!(remaining.len() == 1); diff --git a/tests/bezier/path/arithmetic_sub.rs b/tests/bezier/path/arithmetic_sub.rs index 9339d536..cf8bc97e 100644 --- a/tests/bezier/path/arithmetic_sub.rs +++ b/tests/bezier/path/arithmetic_sub.rs @@ -1,6 +1,9 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + path_sub, BezierPath, BezierPathBuilder, GraphPath, GraphPathEdgeKind, GraphRayCollision, + PathDirection, PathLabel, SimpleBezierPath, +}; +use flo_curves::{BezierCurve, BoundingBox, Coord2, Coordinate, Line}; #[test] fn subtract_circles() { @@ -9,32 +12,46 @@ fn subtract_circles() { let circle2 = Circle::new(Coord2(7.0, 5.0), 4.0).to_path::(); // Combine them - let combined_circles = path_sub::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_sub::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 1); // All points should be on either circle, and two should be on both - let points = combined_circles[0].points().map(|(_, _, end_point)| end_point); + let points = combined_circles[0] + .points() + .map(|(_, _, end_point)| end_point); - let mut num_points_on_circle1 = 0; - let mut num_points_on_circle2 = 0; - let mut num_points_on_both = 0; + let mut num_points_on_circle1 = 0; + let mut num_points_on_circle2 = 0; + let mut num_points_on_both = 0; for point in points { let distance_to_circle1 = Coord2(5.0, 5.0).distance_to(&point); let distance_to_circle2 = Coord2(7.0, 5.0).distance_to(&point); // Must be on either circle - assert!((distance_to_circle1-4.0).abs() < 0.01 || (distance_to_circle2-4.0).abs() < 0.01); - - println!("{:?} {:?} {:?}", point, distance_to_circle1, distance_to_circle2); - - if (distance_to_circle1-4.0).abs() < 0.01 && (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_both += 1 } - else if (distance_to_circle1-4.0).abs() < 0.01 { num_points_on_circle1 += 1 } - else if (distance_to_circle2-4.0).abs() < 0.01 { num_points_on_circle2 += 1 } + assert!( + (distance_to_circle1 - 4.0).abs() < 0.01 || (distance_to_circle2 - 4.0).abs() < 0.01 + ); + + println!( + "{:?} {:?} {:?}", + point, distance_to_circle1, distance_to_circle2 + ); + + if (distance_to_circle1 - 4.0).abs() < 0.01 && (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_both += 1 + } else if (distance_to_circle1 - 4.0).abs() < 0.01 { + num_points_on_circle1 += 1 + } else if (distance_to_circle2 - 4.0).abs() < 0.01 { + num_points_on_circle2 += 1 + } } - println!("{:?} {:?} {:?}", num_points_on_circle1, num_points_on_circle2, num_points_on_both); + println!( + "{:?} {:?} {:?}", + num_points_on_circle1, num_points_on_circle2, num_points_on_both + ); assert!(num_points_on_circle1 == 2); assert!(num_points_on_circle2 == 2); @@ -48,7 +65,7 @@ fn create_doughnut() { let circle2 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); // Create a hole in the larger circle - let combined_circles = path_sub::<_, _, SimpleBezierPath>(&vec![circle1], &vec![circle2], 0.01); + let combined_circles = path_sub::<_, _, SimpleBezierPath>(&[circle1], &[circle2], 0.01); assert!(combined_circles.len() == 2); } @@ -60,9 +77,9 @@ fn erase_all() { let circle2 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); // Create a hole in the larger circle - let combined_circles = path_sub::<_, _, SimpleBezierPath>(&vec![circle2], &vec![circle1], 0.01); + let combined_circles = path_sub::<_, _, SimpleBezierPath>(&[circle2], &[circle1], 0.01); - assert!(combined_circles.len() == 0); + assert!(combined_circles.is_empty()); } #[test] @@ -77,11 +94,12 @@ fn subtract_from_self_rectangles_1() { let rectangle2 = rectangle1.clone(); // Create a hole in the larger circle - let combined_rectangles = path_sub::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let combined_rectangles = + path_sub::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); println!("{:?}", combined_rectangles); assert!(combined_rectangles.len() != 1); - assert!(combined_rectangles.len() == 0); + assert!(combined_rectangles.is_empty()); } #[test] @@ -96,11 +114,12 @@ fn subtract_from_self_rectangles_2() { let rectangle2 = rectangle1.clone(); // Create a hole in the larger circle - let combined_rectangles = path_sub::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let combined_rectangles = + path_sub::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); println!("{:?}", combined_rectangles); assert!(combined_rectangles.len() != 1); - assert!(combined_rectangles.len() == 0); + assert!(combined_rectangles.is_empty()); } #[test] @@ -110,9 +129,9 @@ fn subtract_from_self_circles() { let circle2 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); // Create a hole in the larger circle - let combined_circles = path_sub::<_, _, SimpleBezierPath>(&vec![circle2], &vec![circle1], 0.01); + let combined_circles = path_sub::<_, _, SimpleBezierPath>(&[circle2], &[circle1], 0.01); - assert!(combined_circles.len() == 0); + assert!(combined_circles.is_empty()); } #[test] @@ -132,12 +151,12 @@ fn cut_corners() { .build(); // Subtract them - let cut_corner = path_sub::<_, _, SimpleBezierPath>(&vec![rectangle1], &vec![rectangle2], 0.01); + let cut_corner = path_sub::<_, _, SimpleBezierPath>(&[rectangle1], &[rectangle2], 0.01); assert!(cut_corner.len() == 1); - let cut_corner = &cut_corner[0]; - let points = cut_corner.points().collect::>(); + let cut_corner = &cut_corner[0]; + let points = cut_corner.points().collect::>(); assert!(cut_corner.start_point().distance_to(&Coord2(1.0, 1.0)) < 0.1); assert!(points[0].2.distance_to(&Coord2(5.0, 1.0)) < 0.1); @@ -150,41 +169,173 @@ fn cut_corners() { #[test] fn subtract_triangle_from_partial_circle_graph() { - use flo_curves::debug::*; - use std::collections::{HashMap}; + use flo_curves::debug::graph_path_svg_string; + use std::collections::HashMap; // This regenerates a failing test from arithmetic_intersection: problem seems to be that there are overlapping (or near-overlapping lines) that cause two outer edges when subtracting - let remaining = vec![(Coord2(477.3671569824219, 613.7830200195313), vec![(Coord2(483.87042236328125, 581.0888671875), Coord2(490.3741455078125, 548.3924560546875), Coord2(496.8785400390625, 515.6925659179688)), (Coord2(498.9593200683594, 515.6925659179688), Coord2(501.0400695800781, 515.6925659179688), Coord2(503.1199951171875, 515.6900024414063)), (Coord2(505.0438232421875, 514.8963012695313), Coord2(506.9661865234375, 514.1000366210938), Coord2(508.8900146484375, 513.2999877929688)), (Coord2(510.3604431152344, 511.8321838378906), Coord2(511.8317565917969, 510.3608703613281), Coord2(513.2999877929688, 508.8900146484375)), (Coord2(514.0997924804688, 506.9667663574219), Coord2(514.8960571289063, 505.0444030761719), Coord2(515.6900024414063, 503.1199951171875)), (Coord2(515.6925659179688, 501.0406799316406), Coord2(515.6925659179688, 498.9599304199219), Coord2(515.6900024414063, 496.8800048828125)), (Coord2(514.8963012695313, 494.9561767578125), Coord2(514.1000366210938, 493.0338134765625), Coord2(513.2999877929688, 491.1099853515625)), (Coord2(511.8321838378906, 489.6395568847656), Coord2(510.3608703613281, 488.1682434082031), Coord2(508.8900146484375, 486.70001220703125)), (Coord2(506.9667663574219, 485.90020751953125), Coord2(505.0444030761719, 485.10394287109375), Coord2(503.1199951171875, 484.30999755859375)), (Coord2(501.0406799316406, 484.30743408203125), Coord2(498.9599304199219, 484.30743408203125), Coord2(496.8800048828125, 484.30999755859375)), (Coord2(494.9561767578125, 485.10369873046875), Coord2(493.0338134765625, 485.89996337890625), Coord2(491.1099853515625, 486.70001220703125)), (Coord2(489.6395568847656, 488.1678161621094), Coord2(488.1682434082031, 489.6391296386719), Coord2(486.70001220703125, 491.1099853515625)), (Coord2(485.90020751953125, 493.0332336425781), Coord2(485.10394287109375, 494.9555969238281), Coord2(484.30999755859375, 496.8800048828125)), (Coord2(484.30743408203125, 498.9593200683594), Coord2(484.30743408203125, 501.0400695800781), Coord2(484.30999755859375, 503.1199951171875)), (Coord2(485.10369873046875, 505.0438232421875), Coord2(485.89996337890625, 506.9661865234375), Coord2(486.70001220703125, 508.8900146484375)), (Coord2(488.1678161621094, 510.3604431152344), Coord2(489.6391296386719, 511.8317565917969), Coord2(491.1108703613281, 513.3035278320313)), (Coord2(472.5879821777344, 541.0249633789063), Coord2(454.0650939941406, 568.7463989257813), Coord2(435.5415344238281, 596.4689331054688)), (Coord2(448.4329833984375, 605.102783203125), Coord2(462.67291259765625, 610.8741455078125), Coord2(477.3671569824219, 613.7830200195313))])]; - let fragment = vec![(Coord2(491.1108762716864, 513.3035137968407), vec![(Coord2(438.50637541608546, 592.0317129194746), Coord2(385.91765275510227, 670.736298305119), Coord2(333.3289300941191, 749.4408836907635)), (Coord2(369.38413079268656, 764.375436814194), Coord2(405.428517093924, 779.3055104675816), Coord2(441.4729033951614, 794.2355841209692)), (Coord2(459.9451475894517, 701.369341374821), Coord2(478.41185121859684, 608.5309529306364), Coord2(496.87855484774195, 515.6925644864517)), (Coord2(494.95561081048504, 514.8960549865354), Coord2(493.0332435410857, 514.099784391688), Coord2(491.1108762716864, 513.3035137968407))])]; + let remaining = vec![( + Coord2(477.3671569824219, 613.7830200195313), + vec![ + ( + Coord2(483.87042236328125, 581.0888671875), + Coord2(490.3741455078125, 548.3924560546875), + Coord2(496.8785400390625, 515.6925659179688), + ), + ( + Coord2(498.9593200683594, 515.6925659179688), + Coord2(501.0400695800781, 515.6925659179688), + Coord2(503.1199951171875, 515.6900024414063), + ), + ( + Coord2(505.0438232421875, 514.8963012695313), + Coord2(506.9661865234375, 514.1000366210938), + Coord2(508.8900146484375, 513.2999877929688), + ), + ( + Coord2(510.3604431152344, 511.8321838378906), + Coord2(511.8317565917969, 510.3608703613281), + Coord2(513.2999877929688, 508.8900146484375), + ), + ( + Coord2(514.0997924804688, 506.9667663574219), + Coord2(514.8960571289063, 505.0444030761719), + Coord2(515.6900024414063, 503.1199951171875), + ), + ( + Coord2(515.6925659179688, 501.0406799316406), + Coord2(515.6925659179688, 498.9599304199219), + Coord2(515.6900024414063, 496.8800048828125), + ), + ( + Coord2(514.8963012695313, 494.9561767578125), + Coord2(514.1000366210938, 493.0338134765625), + Coord2(513.2999877929688, 491.1099853515625), + ), + ( + Coord2(511.8321838378906, 489.6395568847656), + Coord2(510.3608703613281, 488.1682434082031), + Coord2(508.8900146484375, 486.70001220703125), + ), + ( + Coord2(506.9667663574219, 485.90020751953125), + Coord2(505.0444030761719, 485.10394287109375), + Coord2(503.1199951171875, 484.30999755859375), + ), + ( + Coord2(501.0406799316406, 484.30743408203125), + Coord2(498.9599304199219, 484.30743408203125), + Coord2(496.8800048828125, 484.30999755859375), + ), + ( + Coord2(494.9561767578125, 485.10369873046875), + Coord2(493.0338134765625, 485.89996337890625), + Coord2(491.1099853515625, 486.70001220703125), + ), + ( + Coord2(489.6395568847656, 488.1678161621094), + Coord2(488.1682434082031, 489.6391296386719), + Coord2(486.70001220703125, 491.1099853515625), + ), + ( + Coord2(485.90020751953125, 493.0332336425781), + Coord2(485.10394287109375, 494.9555969238281), + Coord2(484.30999755859375, 496.8800048828125), + ), + ( + Coord2(484.30743408203125, 498.9593200683594), + Coord2(484.30743408203125, 501.0400695800781), + Coord2(484.30999755859375, 503.1199951171875), + ), + ( + Coord2(485.10369873046875, 505.0438232421875), + Coord2(485.89996337890625, 506.9661865234375), + Coord2(486.70001220703125, 508.8900146484375), + ), + ( + Coord2(488.1678161621094, 510.3604431152344), + Coord2(489.6391296386719, 511.8317565917969), + Coord2(491.1108703613281, 513.3035278320313), + ), + ( + Coord2(472.5879821777344, 541.0249633789063), + Coord2(454.0650939941406, 568.7463989257813), + Coord2(435.5415344238281, 596.4689331054688), + ), + ( + Coord2(448.4329833984375, 605.102783203125), + Coord2(462.67291259765625, 610.8741455078125), + Coord2(477.3671569824219, 613.7830200195313), + ), + ], + )]; + let fragment = vec![( + Coord2(491.1108762716864, 513.3035137968407), + vec![ + ( + Coord2(438.50637541608546, 592.0317129194746), + Coord2(385.91765275510227, 670.736298305119), + Coord2(333.3289300941191, 749.4408836907635), + ), + ( + Coord2(369.38413079268656, 764.375436814194), + Coord2(405.428517093924, 779.3055104675816), + Coord2(441.4729033951614, 794.2355841209692), + ), + ( + Coord2(459.9451475894517, 701.369341374821), + Coord2(478.41185121859684, 608.5309529306364), + Coord2(496.87855484774195, 515.6925644864517), + ), + ( + Coord2(494.95561081048504, 514.8960549865354), + Coord2(493.0332435410857, 514.099784391688), + Coord2(491.1108762716864, 513.3035137968407), + ), + ], + )]; // Contains points that are very close and not the same - for exmaple: // 491.11087 03613281, 513.3035 278320313 // 491.11087 62716864, 513.3035 137968407 // Merge the two paths - let mut merged_path = GraphPath::new(); - merged_path = merged_path.merge(GraphPath::from_merged_paths(remaining.iter().map(|path| (path, PathLabel(0, PathDirection::from(path)))))); - merged_path = merged_path.collide(GraphPath::from_merged_paths(fragment.iter().map(|path| (path, PathLabel(1, PathDirection::from(path))))), 0.01); + let mut merged_path = GraphPath::new(); + merged_path = merged_path.merge(GraphPath::from_merged_paths( + remaining + .iter() + .map(|path| (path, PathLabel(0, PathDirection::from(path)))), + )); + merged_path = merged_path.collide( + GraphPath::from_merged_paths( + fragment + .iter() + .map(|path| (path, PathLabel(1, PathDirection::from(path)))), + ), + 0.01, + ); // Ray cast along the fragment edge - let ypos = 570.0; - let collisions = merged_path.ray_collisions(&(Coord2(0.0, ypos), Coord2(1.0, ypos))); + let ypos = 570.0; + let collisions = merged_path.ray_collisions(&(Coord2(0.0, ypos), Coord2(1.0, ypos))); println!("{:?}", collisions); // Subtract fragment from remaining println!(); merged_path.set_exterior_by_subtracting(); - println!("{}", graph_path_svg_string(&merged_path, vec![(Coord2(0.0, ypos), Coord2(1.0, ypos))])); + println!( + "{}", + graph_path_svg_string(&merged_path, vec![(Coord2(0.0, ypos), Coord2(1.0, ypos))]) + ); merged_path.heal_exterior_gaps(); // No points with any edges leaving or arriving at them should be close to each other let mut point_positions = HashMap::new(); for edge in merged_path.all_edges() { - let start_idx = edge.start_point_index(); - let end_idx = edge.end_point_index(); + let start_idx = edge.start_point_index(); + let end_idx = edge.end_point_index(); - let start_pos = edge.start_point(); - let end_pos = edge.end_point(); + let start_pos = edge.start_point(); + let end_pos = edge.end_point(); point_positions.insert(start_idx, start_pos); point_positions.insert(end_idx, end_pos); @@ -193,7 +344,7 @@ fn subtract_triangle_from_partial_circle_graph() { // All points along the ray should be interior points as they're subtracting from each other (edges very nearly overlap though)\ for (edge_type, _curve_t, _line_t, _pos) in collisions { let edge_ref = match edge_type { - GraphRayCollision::SingleEdge(edge) | GraphRayCollision::Intersection(edge) => edge + GraphRayCollision::SingleEdge(edge) | GraphRayCollision::Intersection(edge) => edge, }; assert!(merged_path.edge_kind(edge_ref) != GraphPathEdgeKind::Exterior); @@ -203,7 +354,9 @@ fn subtract_triangle_from_partial_circle_graph() { println!(); for (idx, pos) in point_positions.iter() { for (cmp_idx, cmp_pos) in point_positions.iter() { - if cmp_idx == idx { continue; } + if cmp_idx == idx { + continue; + } if pos.distance_to(cmp_pos) < 1.0 { println!("Overlapping points: {} {}", idx, cmp_idx); @@ -212,7 +365,7 @@ fn subtract_triangle_from_partial_circle_graph() { } // Extract the resulting path - let subtracted_path = merged_path.exterior_paths::(); + let subtracted_path = merged_path.exterior_paths::(); // This should entirely subtract the triangle from the remaining path assert!(subtracted_path.len() == 1); @@ -221,15 +374,133 @@ fn subtract_triangle_from_partial_circle_graph() { #[test] fn subtract_triangle_from_partial_circle() { // This regenerates a failing test from arithmetic_intersection: problem seems to be that there are overlapping (or near-overlapping lines) that cause two outer edges when subtracting - let remaining = vec![(Coord2(477.3671569824219, 613.7830200195313), vec![(Coord2(483.87042236328125, 581.0888671875), Coord2(490.3741455078125, 548.3924560546875), Coord2(496.8785400390625, 515.6925659179688)), (Coord2(498.9593200683594, 515.6925659179688), Coord2(501.0400695800781, 515.6925659179688), Coord2(503.1199951171875, 515.6900024414063)), (Coord2(505.0438232421875, 514.8963012695313), Coord2(506.9661865234375, 514.1000366210938), Coord2(508.8900146484375, 513.2999877929688)), (Coord2(510.3604431152344, 511.8321838378906), Coord2(511.8317565917969, 510.3608703613281), Coord2(513.2999877929688, 508.8900146484375)), (Coord2(514.0997924804688, 506.9667663574219), Coord2(514.8960571289063, 505.0444030761719), Coord2(515.6900024414063, 503.1199951171875)), (Coord2(515.6925659179688, 501.0406799316406), Coord2(515.6925659179688, 498.9599304199219), Coord2(515.6900024414063, 496.8800048828125)), (Coord2(514.8963012695313, 494.9561767578125), Coord2(514.1000366210938, 493.0338134765625), Coord2(513.2999877929688, 491.1099853515625)), (Coord2(511.8321838378906, 489.6395568847656), Coord2(510.3608703613281, 488.1682434082031), Coord2(508.8900146484375, 486.70001220703125)), (Coord2(506.9667663574219, 485.90020751953125), Coord2(505.0444030761719, 485.10394287109375), Coord2(503.1199951171875, 484.30999755859375)), (Coord2(501.0406799316406, 484.30743408203125), Coord2(498.9599304199219, 484.30743408203125), Coord2(496.8800048828125, 484.30999755859375)), (Coord2(494.9561767578125, 485.10369873046875), Coord2(493.0338134765625, 485.89996337890625), Coord2(491.1099853515625, 486.70001220703125)), (Coord2(489.6395568847656, 488.1678161621094), Coord2(488.1682434082031, 489.6391296386719), Coord2(486.70001220703125, 491.1099853515625)), (Coord2(485.90020751953125, 493.0332336425781), Coord2(485.10394287109375, 494.9555969238281), Coord2(484.30999755859375, 496.8800048828125)), (Coord2(484.30743408203125, 498.9593200683594), Coord2(484.30743408203125, 501.0400695800781), Coord2(484.30999755859375, 503.1199951171875)), (Coord2(485.10369873046875, 505.0438232421875), Coord2(485.89996337890625, 506.9661865234375), Coord2(486.70001220703125, 508.8900146484375)), (Coord2(488.1678161621094, 510.3604431152344), Coord2(489.6391296386719, 511.8317565917969), Coord2(491.1108703613281, 513.3035278320313)), (Coord2(472.5879821777344, 541.0249633789063), Coord2(454.0650939941406, 568.7463989257813), Coord2(435.5415344238281, 596.4689331054688)), (Coord2(448.4329833984375, 605.102783203125), Coord2(462.67291259765625, 610.8741455078125), Coord2(477.3671569824219, 613.7830200195313))])]; - let fragment = vec![(Coord2(491.1108762716864, 513.3035137968407), vec![(Coord2(438.50637541608546, 592.0317129194746), Coord2(385.91765275510227, 670.736298305119), Coord2(333.3289300941191, 749.4408836907635)), (Coord2(369.38413079268656, 764.375436814194), Coord2(405.428517093924, 779.3055104675816), Coord2(441.4729033951614, 794.2355841209692)), (Coord2(459.9451475894517, 701.369341374821), Coord2(478.41185121859684, 608.5309529306364), Coord2(496.87855484774195, 515.6925644864517)), (Coord2(494.95561081048504, 514.8960549865354), Coord2(493.0332435410857, 514.099784391688), Coord2(491.1108762716864, 513.3035137968407))])]; + let remaining = vec![( + Coord2(477.3671569824219, 613.7830200195313), + vec![ + ( + Coord2(483.87042236328125, 581.0888671875), + Coord2(490.3741455078125, 548.3924560546875), + Coord2(496.8785400390625, 515.6925659179688), + ), + ( + Coord2(498.9593200683594, 515.6925659179688), + Coord2(501.0400695800781, 515.6925659179688), + Coord2(503.1199951171875, 515.6900024414063), + ), + ( + Coord2(505.0438232421875, 514.8963012695313), + Coord2(506.9661865234375, 514.1000366210938), + Coord2(508.8900146484375, 513.2999877929688), + ), + ( + Coord2(510.3604431152344, 511.8321838378906), + Coord2(511.8317565917969, 510.3608703613281), + Coord2(513.2999877929688, 508.8900146484375), + ), + ( + Coord2(514.0997924804688, 506.9667663574219), + Coord2(514.8960571289063, 505.0444030761719), + Coord2(515.6900024414063, 503.1199951171875), + ), + ( + Coord2(515.6925659179688, 501.0406799316406), + Coord2(515.6925659179688, 498.9599304199219), + Coord2(515.6900024414063, 496.8800048828125), + ), + ( + Coord2(514.8963012695313, 494.9561767578125), + Coord2(514.1000366210938, 493.0338134765625), + Coord2(513.2999877929688, 491.1099853515625), + ), + ( + Coord2(511.8321838378906, 489.6395568847656), + Coord2(510.3608703613281, 488.1682434082031), + Coord2(508.8900146484375, 486.70001220703125), + ), + ( + Coord2(506.9667663574219, 485.90020751953125), + Coord2(505.0444030761719, 485.10394287109375), + Coord2(503.1199951171875, 484.30999755859375), + ), + ( + Coord2(501.0406799316406, 484.30743408203125), + Coord2(498.9599304199219, 484.30743408203125), + Coord2(496.8800048828125, 484.30999755859375), + ), + ( + Coord2(494.9561767578125, 485.10369873046875), + Coord2(493.0338134765625, 485.89996337890625), + Coord2(491.1099853515625, 486.70001220703125), + ), + ( + Coord2(489.6395568847656, 488.1678161621094), + Coord2(488.1682434082031, 489.6391296386719), + Coord2(486.70001220703125, 491.1099853515625), + ), + ( + Coord2(485.90020751953125, 493.0332336425781), + Coord2(485.10394287109375, 494.9555969238281), + Coord2(484.30999755859375, 496.8800048828125), + ), + ( + Coord2(484.30743408203125, 498.9593200683594), + Coord2(484.30743408203125, 501.0400695800781), + Coord2(484.30999755859375, 503.1199951171875), + ), + ( + Coord2(485.10369873046875, 505.0438232421875), + Coord2(485.89996337890625, 506.9661865234375), + Coord2(486.70001220703125, 508.8900146484375), + ), + ( + Coord2(488.1678161621094, 510.3604431152344), + Coord2(489.6391296386719, 511.8317565917969), + Coord2(491.1108703613281, 513.3035278320313), + ), + ( + Coord2(472.5879821777344, 541.0249633789063), + Coord2(454.0650939941406, 568.7463989257813), + Coord2(435.5415344238281, 596.4689331054688), + ), + ( + Coord2(448.4329833984375, 605.102783203125), + Coord2(462.67291259765625, 610.8741455078125), + Coord2(477.3671569824219, 613.7830200195313), + ), + ], + )]; + let fragment = vec![( + Coord2(491.1108762716864, 513.3035137968407), + vec![ + ( + Coord2(438.50637541608546, 592.0317129194746), + Coord2(385.91765275510227, 670.736298305119), + Coord2(333.3289300941191, 749.4408836907635), + ), + ( + Coord2(369.38413079268656, 764.375436814194), + Coord2(405.428517093924, 779.3055104675816), + Coord2(441.4729033951614, 794.2355841209692), + ), + ( + Coord2(459.9451475894517, 701.369341374821), + Coord2(478.41185121859684, 608.5309529306364), + Coord2(496.87855484774195, 515.6925644864517), + ), + ( + Coord2(494.95561081048504, 514.8960549865354), + Coord2(493.0332435410857, 514.099784391688), + Coord2(491.1108762716864, 513.3035137968407), + ), + ], + )]; // Contains points that are very close and not the same - for exmaple: // 491.11087 03613281, 513.3035 278320313 // 491.11087 62716864, 513.3035 137968407 // Merge the two paths - let subtracted_path = path_sub::<_, _, SimpleBezierPath>(&remaining, &fragment, 0.01); + let subtracted_path = path_sub::<_, _, SimpleBezierPath>(&remaining, &fragment, 0.01); // This should entirely subtract the triangle from the remaining path assert!(subtracted_path.len() == 1); diff --git a/tests/bezier/path/bounds.rs b/tests/bezier/path/bounds.rs index b4ba6276..5424f9f8 100644 --- a/tests/bezier/path/bounds.rs +++ b/tests/bezier/path/bounds.rs @@ -1,6 +1,6 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{BezierPath, SimpleBezierPath}; +use flo_curves::{BezierCurve, Coord2, Coordinate, Coordinate2D, Coordinate3D}; #[test] fn circle_path_bounds() { diff --git a/tests/bezier/path/graph_path.rs b/tests/bezier/path/graph_path.rs index 380fe753..ec978ee1 100644 --- a/tests/bezier/path/graph_path.rs +++ b/tests/bezier/path/graph_path.rs @@ -1,13 +1,22 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + BezierPath, BezierPathBuilder, BezierPathFactory, GraphEdge, GraphPath, GraphPathEdgeKind, + GraphRayCollision, PathDirection, PathLabel, SimpleBezierPath, +}; +use flo_curves::{BezierCurve, BoundingBox, Coord2, Coordinate, Coordinate2D, Coordinate3D, Line}; use std::f64; #[test] pub fn create_and_read_simple_graph_path() { - let path = (Coord2(10.0, 11.0), vec![(Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0))]); - let graph_path = GraphPath::from_path(&path, ()); + let path = ( + Coord2(10.0, 11.0), + vec![ + (Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), + (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0)), + ], + ); + let graph_path = GraphPath::from_path(&path, ()); assert!(graph_path.num_points() == 3); @@ -45,8 +54,14 @@ pub fn create_and_read_simple_graph_path() { #[test] pub fn create_and_read_simple_graph_path_reverse() { - let path = (Coord2(10.0, 11.0), vec![(Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0))]); - let graph_path = GraphPath::from_path(&path, ()); + let path = ( + Coord2(10.0, 11.0), + vec![ + (Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), + (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0)), + ], + ); + let graph_path = GraphPath::from_path(&path, ()); assert!(graph_path.num_points() == 3); @@ -98,7 +113,7 @@ pub fn collide_two_rectangles() { .line_to(Coord2(4.0, 9.0)) .line_to(Coord2(4.0, 4.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, 1); let rectangle2 = GraphPath::from_path(&rectangle2, 2); @@ -115,7 +130,7 @@ pub fn collide_two_rectangles() { let edges = collision.edges_for_point(point_idx).collect::>(); assert!(edges.len() <= 2); - assert!(edges.len() >= 1); + assert!(!edges.is_empty()); assert!(edges[0].kind() == GraphPathEdgeKind::Uncategorised); assert!(edges.len() == 1 || edges[1].kind() == GraphPathEdgeKind::Uncategorised); @@ -158,8 +173,12 @@ pub fn collide_two_rectangles() { check_count += 1; assert!(edges.len() == 2); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 4.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 4.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 5.0)) < 0.1)); assert!(edges.iter().any(|edge| edge.label() == 1)); assert!(edges.iter().any(|edge| edge.label() == 2)); } @@ -168,8 +187,12 @@ pub fn collide_two_rectangles() { check_count += 1; assert!(edges.len() == 2); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(9.0, 4.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(9.0, 4.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); assert!(edges.iter().any(|edge| edge.label() == 1)); assert!(edges.iter().any(|edge| edge.label() == 2)); } @@ -189,7 +212,7 @@ pub fn collide_identical_rectangles() { .line_to(Coord2(1.0, 1.0)) .build(); let rectangle2 = rectangle1.clone(); - + let rectangle1 = GraphPath::from_path(&rectangle1, 1); let rectangle2 = GraphPath::from_path(&rectangle2, 2); @@ -213,7 +236,7 @@ pub fn collide_identical_rectangles() { if point_idx < 4 { assert!(edges.len() == 2); } else { - assert!(edges.len() == 0); + assert!(edges.is_empty()); } } } @@ -233,7 +256,7 @@ fn multiple_collisions_on_one_edge() { .line_to(Coord2(4.0, 0.0)) .line_to(Coord2(2.0, 0.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, ()); let rectangle2 = GraphPath::from_path(&rectangle2, ()); @@ -250,17 +273,33 @@ fn multiple_collisions_on_one_edge() { assert!(edges.len() <= 2); if edges.len() == 2 { if edges[0].start_point().distance_to(&Coord2(2.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 0.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 0.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(2.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 6.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 6.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 1.0)) < 0.1)); } else { // These are the only four intersection points that should exist println!("{:?}", edges[0].start_point()); @@ -285,7 +324,7 @@ fn multiple_collisions_on_one_edge_opposite_direction() { .line_to(Coord2(2.0, 0.0)) .line_to(Coord2(4.0, 0.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, ()); let rectangle2 = GraphPath::from_path(&rectangle2, ()); @@ -301,39 +340,59 @@ fn multiple_collisions_on_one_edge_opposite_direction() { let edges = collision.edges_for_point(point_idx).collect::>(); assert!(edges.len() <= 2); - assert!(edges.len() > 0); + assert!(!edges.is_empty()); if edges.len() == 2 { num_intersects += 1; if edges[0].start_point().distance_to(&Coord2(2.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(2.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); } else { // These are the only four intersection points that should exist println!("{:?}", edges[0].start_point()); assert!(false) } } else if edges.len() == 1 { - let edge = edges.iter().nth(0).unwrap(); + let edge = edges.iter().nth(0).unwrap(); let start_point = edge.start_point(); - assert!((start_point.x()-1.0).abs() < 0.01 || - (start_point.x()-5.0).abs() < 0.01 || - (start_point.x()-2.0).abs() < 0.01 || - (start_point.x()-4.0).abs() < 0.01); - assert!((start_point.y()-1.0).abs() < 0.01 || - (start_point.y()-5.0).abs() < 0.01 || - (start_point.y()-0.0).abs() < 0.01 || - (start_point.y()-6.0).abs() < 0.01); + assert!( + (start_point.x() - 1.0).abs() < 0.01 + || (start_point.x() - 5.0).abs() < 0.01 + || (start_point.x() - 2.0).abs() < 0.01 + || (start_point.x() - 4.0).abs() < 0.01 + ); + assert!( + (start_point.y() - 1.0).abs() < 0.01 + || (start_point.y() - 5.0).abs() < 0.01 + || (start_point.y() - 0.0).abs() < 0.01 + || (start_point.y() - 6.0).abs() < 0.01 + ); } } @@ -357,7 +416,7 @@ fn collision_at_same_point() { .line_to(Coord2(2.0, 0.0)) .line_to(Coord2(4.0, 0.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, ()); let rectangle2 = GraphPath::from_path(&rectangle2, ()); @@ -372,7 +431,9 @@ fn collision_at_same_point() { let mut num_orphaned_points = 0; for point_idx in 0..13 { let edges = collision.edges_for_point(point_idx).collect::>(); - if edges.len() == 0 { num_orphaned_points += 1; } + if edges.is_empty() { + num_orphaned_points += 1; + } } assert!(num_orphaned_points <= 1); @@ -387,34 +448,54 @@ fn collision_at_same_point() { num_intersects += 1; if edges[0].start_point().distance_to(&Coord2(2.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(2.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); } else { // These are the only four intersection points that should exist println!("{:?}", edges[0].start_point()); assert!(false) } } else if edges.len() == 1 { - let edge = edges.iter().nth(0).unwrap(); + let edge = edges.iter().nth(0).unwrap(); let start_point = edge.start_point(); - assert!((start_point.x()-1.0).abs() < 0.01 || - (start_point.x()-5.0).abs() < 0.01 || - (start_point.x()-2.0).abs() < 0.01 || - (start_point.x()-4.0).abs() < 0.01); - assert!((start_point.y()-1.0).abs() < 0.01 || - (start_point.y()-5.0).abs() < 0.01 || - (start_point.y()-0.0).abs() < 0.01 || - (start_point.y()-6.0).abs() < 0.01); + assert!( + (start_point.x() - 1.0).abs() < 0.01 + || (start_point.x() - 5.0).abs() < 0.01 + || (start_point.x() - 2.0).abs() < 0.01 + || (start_point.x() - 4.0).abs() < 0.01 + ); + assert!( + (start_point.y() - 1.0).abs() < 0.01 + || (start_point.y() - 5.0).abs() < 0.01 + || (start_point.y() - 0.0).abs() < 0.01 + || (start_point.y() - 6.0).abs() < 0.01 + ); } else { // Should only be 1 edge (corners) or 2 edges (collision points) println!("{:?}", edges); @@ -441,7 +522,7 @@ fn collision_exactly_on_edge_src() { .line_to(Coord2(2.0, 0.0)) .line_to(Coord2(4.0, 0.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, ()); let rectangle2 = GraphPath::from_path(&rectangle2, ()); @@ -456,7 +537,9 @@ fn collision_exactly_on_edge_src() { let mut num_orphaned_points = 0; for point_idx in 0..13 { let edges = collision.edges_for_point(point_idx).collect::>(); - if edges.len() == 0 { num_orphaned_points += 1; } + if edges.is_empty() { + num_orphaned_points += 1; + } } assert!(num_orphaned_points <= 1); @@ -471,34 +554,54 @@ fn collision_exactly_on_edge_src() { num_intersects += 1; if edges[0].start_point().distance_to(&Coord2(2.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(2.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); } else { // These are the only four intersection points that should exist println!("{:?}", edges[0].start_point()); assert!(false) } } else if edges.len() == 1 { - let edge = edges.iter().nth(0).unwrap(); + let edge = edges.iter().nth(0).unwrap(); let start_point = edge.start_point(); - assert!((start_point.x()-1.0).abs() < 0.01 || - (start_point.x()-5.0).abs() < 0.01 || - (start_point.x()-2.0).abs() < 0.01 || - (start_point.x()-4.0).abs() < 0.01); - assert!((start_point.y()-1.0).abs() < 0.01 || - (start_point.y()-5.0).abs() < 0.01 || - (start_point.y()-0.0).abs() < 0.01 || - (start_point.y()-6.0).abs() < 0.01); + assert!( + (start_point.x() - 1.0).abs() < 0.01 + || (start_point.x() - 5.0).abs() < 0.01 + || (start_point.x() - 2.0).abs() < 0.01 + || (start_point.x() - 4.0).abs() < 0.01 + ); + assert!( + (start_point.y() - 1.0).abs() < 0.01 + || (start_point.y() - 5.0).abs() < 0.01 + || (start_point.y() - 0.0).abs() < 0.01 + || (start_point.y() - 6.0).abs() < 0.01 + ); } else { // Should only be 1 edge (corners) or 2 edges (collision points) println!("{:?}", edges); @@ -525,7 +628,7 @@ fn collision_exactly_on_edge_tgt() { .line_to(Coord2(2.0, 0.0)) .line_to(Coord2(4.0, 0.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle1, ()); let rectangle2 = GraphPath::from_path(&rectangle2, ()); @@ -540,7 +643,9 @@ fn collision_exactly_on_edge_tgt() { let mut num_orphaned_points = 0; for point_idx in 0..13 { let edges = collision.edges_for_point(point_idx).collect::>(); - if edges.len() == 0 { num_orphaned_points += 1; } + if edges.is_empty() { + num_orphaned_points += 1; + } } assert!(num_orphaned_points <= 1); @@ -555,34 +660,54 @@ fn collision_exactly_on_edge_tgt() { num_intersects += 1; if edges[0].start_point().distance_to(&Coord2(2.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 0.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(1.0, 1.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 1.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(2.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(2.0, 1.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 5.0)) < 0.1)); } else if edges[0].start_point().distance_to(&Coord2(4.0, 5.0)) < 0.1 { - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); - assert!(edges.iter().any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(5.0, 5.0)) < 0.1)); + assert!(edges + .iter() + .any(|edge| edge.end_point().distance_to(&Coord2(4.0, 6.0)) < 0.1)); } else { // These are the only four intersection points that should exist println!("{:?}", edges[0].start_point()); assert!(false) } } else if edges.len() == 1 { - let edge = edges.iter().nth(0).unwrap(); + let edge = edges.iter().nth(0).unwrap(); let start_point = edge.start_point(); - assert!((start_point.x()-1.0).abs() < 0.01 || - (start_point.x()-5.0).abs() < 0.01 || - (start_point.x()-2.0).abs() < 0.01 || - (start_point.x()-4.0).abs() < 0.01); - assert!((start_point.y()-1.0).abs() < 0.01 || - (start_point.y()-5.0).abs() < 0.01 || - (start_point.y()-0.0).abs() < 0.01 || - (start_point.y()-6.0).abs() < 0.01); + assert!( + (start_point.x() - 1.0).abs() < 0.01 + || (start_point.x() - 5.0).abs() < 0.01 + || (start_point.x() - 2.0).abs() < 0.01 + || (start_point.x() - 4.0).abs() < 0.01 + ); + assert!( + (start_point.y() - 1.0).abs() < 0.01 + || (start_point.y() - 5.0).abs() < 0.01 + || (start_point.y() - 0.0).abs() < 0.01 + || (start_point.y() - 6.0).abs() < 0.01 + ); } else { // Should only be 1 edge (corners) or 2 edges (collision points) println!("{:?}", edges); @@ -593,10 +718,16 @@ fn collision_exactly_on_edge_tgt() { assert!(num_intersects == 4); } -fn to_collision_with_edges<'a, Point, Label>(collisions: Vec<(GraphRayCollision, f64, f64, Coord2)>, graph_path: &'a GraphPath) -> Vec<(GraphEdge<'a, Point, Label>, f64, f64)> -where Point: Coordinate+Coordinate2D, - Label: Copy { - collisions.into_iter() +fn to_collision_with_edges<'a, Point, Label>( + collisions: Vec<(GraphRayCollision, f64, f64, Coord2)>, + graph_path: &'a GraphPath, +) -> Vec<(GraphEdge<'a, Point, Label>, f64, f64)> +where + Point: Coordinate + Coordinate2D, + Label: Copy, +{ + collisions + .into_iter() .map(move |(collision, curve_t, line_t, _pos)| { let edge = collision.edge(); (graph_path.get_edge(edge), curve_t, line_t) @@ -619,11 +750,11 @@ fn cast_ray_to_rectangle_corner() { let collision = rectangle1.ray_collisions(&(Coord2(0.0, 0.0), Coord2(1.0, 1.0))); let collision = to_collision_with_edges(collision, &rectangle1); - assert!(collision.len() > 0); + assert!(!collision.is_empty()); let collision = &collision[0]; assert!(collision.0.start_point() == Coord2(1.0, 1.0)); - assert!((collision.1-0.0).abs() < 0.01); + assert!((collision.1 - 0.0).abs() < 0.01); } #[test] @@ -644,10 +775,13 @@ fn casting_ray_to_exact_point_produces_one_collision() { let collision = rectangle1.ray_collisions(&(Coord2(0.0, 0.0), Coord2(1.0, 1.0))); let collision = to_collision_with_edges(collision, &rectangle1); - let collisions_with_corner = collision.into_iter() - .filter(|(edge, curve_t, _line_t)| edge.point_at_pos(*curve_t).distance_to(&Coord2(1.0, 1.0)) < 0.1) + let collisions_with_corner = collision + .into_iter() + .filter(|(edge, curve_t, _line_t)| { + edge.point_at_pos(*curve_t).distance_to(&Coord2(1.0, 1.0)) < 0.1 + }) .collect::>(); - assert!(collisions_with_corner.len() != 0); + assert!(!collisions_with_corner.is_empty()); assert!(collisions_with_corner.len() != 2); assert!(collisions_with_corner.len() == 1); } @@ -668,14 +802,14 @@ fn casting_ray_across_corner_produces_no_collision() { let collision = rectangle1.ray_collisions(&(Coord2(0.0, 2.0), Coord2(2.0, 0.0))); assert!(collision.len() != 1); - assert!(collision.len() == 0); + assert!(collision.is_empty()); } #[test] fn casting_ray_to_intersection_point_produces_two_collisions() { // A ray hitting an exact point that is an intersection (has two edges leaving it) should produce two collisions, one on each edge // ... also this case where we have an overlapping line might be weird (but I don't think we'll generate it properly yet): - // + // // +-----+ // | | // | +----+ @@ -683,9 +817,9 @@ fn casting_ray_to_intersection_point_produces_two_collisions() { // | +----+ // | | // +-----+ - // + // // (There's an intersection where there are two edges entering it but only one leaving) - // + // // This test should still be valid if the 'shared' edge is stored in the graph as two edges // Create a rectangle @@ -712,10 +846,13 @@ fn casting_ray_to_intersection_point_produces_two_collisions() { let collision = collided.ray_collisions(&(Coord2(0.0, 0.0), Coord2(5.0, 3.0))); let collision = to_collision_with_edges(collision, &collided); - let collisions_with_corner = collision.into_iter() - .filter(|(edge, curve_t, _line_t)| edge.point_at_pos(*curve_t).distance_to(&Coord2(5.0, 3.0)) < 0.1) + let collisions_with_corner = collision + .into_iter() + .filter(|(edge, curve_t, _line_t)| { + edge.point_at_pos(*curve_t).distance_to(&Coord2(5.0, 3.0)) < 0.1 + }) .collect::>(); - assert!(collisions_with_corner.len() != 0); + assert!(!collisions_with_corner.is_empty()); assert!(collisions_with_corner.len() != 4); assert!(collisions_with_corner.len() == 2); } @@ -735,12 +872,18 @@ fn cast_ray_across_rectangle() { let collision = rectangle1.ray_collisions(&(Coord2(0.0, 3.0), Coord2(6.0, 3.0))); let collision = to_collision_with_edges(collision, &rectangle1); - assert!(collision.len() > 0); + assert!(!collision.is_empty()); let collision = &collision[0]; - assert!(collision.0.point_at_pos(collision.1).distance_to(&Coord2(1.0, 3.0)) < 0.001); + assert!( + collision + .0 + .point_at_pos(collision.1) + .distance_to(&Coord2(1.0, 3.0)) + < 0.001 + ); assert!(collision.0.start_point() == Coord2(1.0, 1.0)); - assert!((collision.1-0.5).abs() < 0.01); + assert!((collision.1 - 0.5).abs() < 0.01); } #[test] @@ -758,11 +901,11 @@ fn cast_ray_to_rectangle_far_corner() { let collision = rectangle1.ray_collisions(&(Coord2(0.0, 0.0), Coord2(6.0, 6.0))); let collision = to_collision_with_edges(collision, &rectangle1); - assert!(collision.len() > 0); + assert!(!collision.is_empty()); let collision = &collision[0]; assert!(collision.0.start_point() == Coord2(1.0, 1.0)); - assert!((collision.1-0.0).abs() < 0.01); + assert!((collision.1 - 0.0).abs() < 0.01); } #[test] @@ -780,11 +923,11 @@ fn cast_ray_to_rectangle_far_corner_backwards() { let collision = rectangle1.ray_collisions(&(Coord2(6.0, 6.0), Coord2(0.0, 0.0))); let collision = to_collision_with_edges(collision, &rectangle1); - assert!(collision.len() > 0); + assert!(!collision.is_empty()); let collision = &collision[0]; assert!(collision.0.start_point().distance_to(&Coord2(5.0, 5.0)) < 0.1); - assert!((collision.1-0.0).abs() < 0.01); + assert!((collision.1 - 0.0).abs() < 0.01); } #[test] @@ -801,7 +944,7 @@ fn cast_ray_to_nowhere() { // Line that entirely misses the rectangle let collision = rectangle1.ray_collisions(&(Coord2(0.0, 0.0), Coord2(0.0, 10.0))); - assert!(collision.len() == 0); + assert!(collision.is_empty()); } #[test] @@ -816,7 +959,7 @@ fn set_simple_path_as_interior() { let mut rectangle1 = GraphPath::from_path(&rectangle1, ()); // Mark everything as an exterior path - let first_edge_ref = rectangle1.all_edges().nth(0).unwrap().into(); + let first_edge_ref = rectangle1.all_edges().next().unwrap().into(); rectangle1.set_edge_kind_connected(first_edge_ref, GraphPathEdgeKind::Interior); // All edges should be exterior @@ -850,7 +993,7 @@ fn set_collision_as_exterior() { let mut collided = rectangle1.collide(rectangle2, 0.01); // Mark everything as an exterior path - let first_edge_ref = collided.edges_for_point(0).nth(0).unwrap().into(); + let first_edge_ref = collided.edges_for_point(0).next().unwrap().into(); collided.set_edge_kind_connected(first_edge_ref, GraphPathEdgeKind::Exterior); // Edges 0 -> 1, 1 -> , -> 2, 2 -> 3 and 3 -> 0 should all be exterior @@ -860,7 +1003,9 @@ fn set_collision_as_exterior() { assert!(edges.len() == 1); assert!(edges[0].kind() == GraphPathEdgeKind::Exterior); - let edges = collided.reverse_edges_for_point(point_idx).collect::>(); + let edges = collided + .reverse_edges_for_point(point_idx) + .collect::>(); assert!(edges.len() == 1); assert!(edges[0].kind() == GraphPathEdgeKind::Exterior); @@ -870,7 +1015,10 @@ fn set_collision_as_exterior() { for point_idx in 4..(collided.num_points()) { let edges = collided.edges_for_point(point_idx).collect::>(); - assert!(edges.into_iter().all(|edge| edge.end_point_index() < 4 || edge.kind() == GraphPathEdgeKind::Uncategorised)); + assert!(edges + .into_iter() + .all(|edge| edge.end_point_index() < 4 + || edge.kind() == GraphPathEdgeKind::Uncategorised)); } } @@ -886,7 +1034,7 @@ fn get_path_from_exterior_lines() { let mut rectangle1 = GraphPath::from_path(&rectangle1, ()); // Mark everything as an exterior path - let first_edge = rectangle1.edges_for_point(0).nth(0).unwrap().into(); + let first_edge = rectangle1.edges_for_point(0).next().unwrap().into(); rectangle1.set_edge_kind_connected(first_edge, GraphPathEdgeKind::Exterior); // Turn back into a path @@ -921,15 +1069,15 @@ fn get_path_from_exterior_lines_multiple_paths() { .line_to(Coord2(15.0, 1.0)) .line_to(Coord2(11.0, 1.0)) .build(); - let rectangle1 = GraphPath::from_path(&rectangle1, ()); - let rectangle2 = GraphPath::from_path(&rectangle2, ()); - let mut rectangle1 = rectangle1.merge(rectangle2); + let rectangle1 = GraphPath::from_path(&rectangle1, ()); + let rectangle2 = GraphPath::from_path(&rectangle2, ()); + let mut rectangle1 = rectangle1.merge(rectangle2); // Mark everything as an exterior path - let first_edge = rectangle1.edges_for_point(0).nth(0).unwrap().into(); + let first_edge = rectangle1.edges_for_point(0).next().unwrap().into(); rectangle1.set_edge_kind_connected(first_edge, GraphPathEdgeKind::Exterior); - let first_edge = rectangle1.edges_for_point(4).nth(0).unwrap().into(); + let first_edge = rectangle1.edges_for_point(4).next().unwrap().into(); rectangle1.set_edge_kind_connected(first_edge, GraphPathEdgeKind::Exterior); // Turn back into a path @@ -975,12 +1123,17 @@ fn collide_circles() { for point_idx in 0..10 { println!("Point {:?}", point_idx); for edge in graph_path.edges_for_point(point_idx) { - println!(" {:?} -> {:?} ({:?})", edge.start_point(), edge.end_point(), edge.end_point_index()); + println!( + " {:?} -> {:?} ({:?})", + edge.start_point(), + edge.end_point(), + edge.end_point_index() + ); } } // First four points should correspond to the four points in circle1 (and should all have one edge) - // Some implementation details depended on here: + // Some implementation details depended on here: // * we preserve at least the points from the first path when colliding assert!(graph_path.edges_for_point(0).collect::>().len() == 1); assert!(graph_path.edges_for_point(1).collect::>().len() == 1); @@ -988,29 +1141,50 @@ fn collide_circles() { assert!(graph_path.edges_for_point(3).collect::>().len() == 1); // Point 1 should lead to the intersection point - let to_intersection = graph_path.edges_for_point(0).nth(0).unwrap(); - let intersection_point = to_intersection.end_point_index(); + let to_intersection = graph_path.edges_for_point(0).next().unwrap(); + let intersection_point = to_intersection.end_point_index(); assert!(intersection_point > 3); // Intersection point should lead to another intersection point - let intersection_edges = graph_path.edges_for_point(intersection_point).collect::>(); + let intersection_edges = graph_path + .edges_for_point(intersection_point) + .collect::>(); assert!(intersection_edges.len() == 2); // Should lead to one point in the second circle, and one other intersection point - let is_intersection = |point_num| { graph_path.edges_for_point(point_num).collect::>().len() > 1 }; - - assert!(intersection_edges.iter().any(|edge| !is_intersection(edge.end_point_index()))); - assert!(intersection_edges.iter().any(|edge| is_intersection(edge.end_point_index()))); + let is_intersection = |point_num| { + graph_path + .edges_for_point(point_num) + .collect::>() + .len() + > 1 + }; + + assert!(intersection_edges + .iter() + .any(|edge| !is_intersection(edge.end_point_index()))); + assert!(intersection_edges + .iter() + .any(|edge| is_intersection(edge.end_point_index()))); // The following intersection point should have one point that leads back into our path - let following_intersection = intersection_edges.iter().filter(|edge| is_intersection(edge.end_point_index())).nth(0).unwrap(); - let second_intersection_edges = graph_path.edges_for_point(following_intersection.end_point_index()).collect::>(); + let following_intersection = intersection_edges + .iter() + .find(|edge| is_intersection(edge.end_point_index())) + .unwrap(); + let second_intersection_edges = graph_path + .edges_for_point(following_intersection.end_point_index()) + .collect::>(); - assert!(second_intersection_edges.iter().any(|edge| edge.end_point_index() <= 3)); + assert!(second_intersection_edges + .iter() + .any(|edge| edge.end_point_index() <= 3)); // It should also have a point that leads back to the first intersection, forming a loop - assert!(second_intersection_edges.iter().any(|edge| edge.end_point_index() == intersection_point)); + assert!(second_intersection_edges + .iter() + .any(|edge| edge.end_point_index() == intersection_point)); } #[test] @@ -1037,7 +1211,8 @@ fn self_collide_simple_path() { assert!(with_interior_point.num_points() == 7); // One intersection - let num_intersections = (0..(with_interior_point.num_points())).into_iter() + let num_intersections = (0..(with_interior_point.num_points())) + .into_iter() .filter(|point_idx| with_interior_point.edges_for_point(*point_idx).count() > 1) .count(); assert!(num_intersections == 1); @@ -1066,14 +1241,35 @@ fn collide_at_shared_point() { let graph = graph.collide(GraphPath::from_path(&rectangle2, ()), 0.01); // Should be two points at 3.0, 5.0 with only one having any edges - let edges_at_shared = graph.all_edges().filter(|edge| edge.start_point().distance_to(&Coord2(3.0, 5.0)) < 0.1).collect::>(); + let edges_at_shared = graph + .all_edges() + .filter(|edge| edge.start_point().distance_to(&Coord2(3.0, 5.0)) < 0.1) + .collect::>(); assert!(edges_at_shared.len() == 2); assert!(edges_at_shared[0].start_point_index() == edges_at_shared[1].start_point_index()); - assert!(edges_at_shared[0].end_point().distance_to(&Coord2(1.0, 5.0)) < 0.1); - assert!(edges_at_shared[1].end_point().distance_to(&Coord2(3.0, 3.0)) < 0.1); - - let points_at_shared = (0..(graph.num_points())).into_iter().filter(|point_idx| graph.point_position(*point_idx).distance_to(&Coord2(3.0, 5.0)) < 0.01).collect::>(); + assert!( + edges_at_shared[0] + .end_point() + .distance_to(&Coord2(1.0, 5.0)) + < 0.1 + ); + assert!( + edges_at_shared[1] + .end_point() + .distance_to(&Coord2(3.0, 3.0)) + < 0.1 + ); + + let points_at_shared = (0..(graph.num_points())) + .into_iter() + .filter(|point_idx| { + graph + .point_position(*point_idx) + .distance_to(&Coord2(3.0, 5.0)) + < 0.01 + }) + .collect::>(); assert!(points_at_shared.len() == 2); } @@ -1086,7 +1282,7 @@ pub fn collide_rectangle_with_self() { .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(1.0, 1.0)) .build(); - + let rectangle1 = GraphPath::from_path(&rectangle, 1); let rectangle2 = GraphPath::from_path(&rectangle, 2); @@ -1103,7 +1299,9 @@ pub fn collide_rectangle_with_self() { let num_edges = collision.edges_for_point(point_idx).count(); assert!(num_edges == 2 || num_edges == 0); - if num_edges != 0 { num_connected_points += 1 } + if num_edges != 0 { + num_connected_points += 1 + } } assert!(num_connected_points == 4); @@ -1122,15 +1320,15 @@ fn ray_collide_along_convex_edge() { // Collide along the vertical seam of this graph let gp = GraphPath::from_path(&rectangle1, PathLabel(0, PathDirection::Clockwise)); - let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); - let collisions_no_seam = gp.ray_collisions(&(Coord2(4.9, 0.0), Coord2(4.9, 5.0))); + let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); + let collisions_no_seam = gp.ray_collisions(&(Coord2(4.9, 0.0), Coord2(4.9, 5.0))); assert!(collisions_no_seam.len() == 2); // As the ray never actually enters the shape along the seam, there should be 0 collisions println!("{:?}", collisions_seam); assert!(collisions_seam.len() != 2); - assert!(collisions_seam.len() == 0); + assert!(collisions_seam.is_empty()); } #[test] @@ -1147,8 +1345,8 @@ fn ray_collide_along_concave_edge() { // Collide along the vertical seam of this graph let gp = GraphPath::from_path(&concave_shape, PathLabel(0, PathDirection::Clockwise)); - let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); - let collisions_no_seam = gp.ray_collisions(&(Coord2(4.9, 0.0), Coord2(4.9, 5.0))); + let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); + let collisions_no_seam = gp.ray_collisions(&(Coord2(4.9, 0.0), Coord2(4.9, 5.0))); assert!(collisions_no_seam.len() == 2); @@ -1175,12 +1373,15 @@ fn ray_collide_along_seam_with_intersection() { .build(); // Collide along the vertical seam of this graph - let gp = GraphPath::from_path(&rectangle1, PathLabel(0, PathDirection::Clockwise)).collide(GraphPath::from_path(&rectangle2, PathLabel(1, PathDirection::Clockwise)), 0.01); + let gp = GraphPath::from_path(&rectangle1, PathLabel(0, PathDirection::Clockwise)).collide( + GraphPath::from_path(&rectangle2, PathLabel(1, PathDirection::Clockwise)), + 0.01, + ); println!("{:?}", gp); - let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); - let collisions_no_seam = gp.ray_collisions(&(Coord2(5.1, 0.0), Coord2(5.1, 5.0))); + let collisions_seam = gp.ray_collisions(&(Coord2(5.0, 0.0), Coord2(5.0, 5.0))); + let collisions_no_seam = gp.ray_collisions(&(Coord2(5.1, 0.0), Coord2(5.1, 5.0))); // Should collide with the line crossing the intersection, and the top line (so two collisions total) assert!(collisions_no_seam.len() == 2); @@ -1188,12 +1389,12 @@ fn ray_collide_along_seam_with_intersection() { assert!(collisions_seam.len() != 4); assert!(collisions_seam.len() != 3); assert!(collisions_seam.len() != 1); - assert!(collisions_seam.len()&1 == 0); + assert!(collisions_seam.len() & 1 == 0); assert!(collisions_seam.len() == 2); let ray = (Coord2(5.0, 0.0), Coord2(5.0, 5.0)); - let first_collision = ray.point_at_pos(collisions_seam[0].2); - let second_collision = ray.point_at_pos(collisions_seam[1].2); + let first_collision = ray.point_at_pos(collisions_seam[0].2); + let second_collision = ray.point_at_pos(collisions_seam[1].2); println!("{:?} {:?}", first_collision, second_collision); @@ -1253,56 +1454,59 @@ fn ray_collide_with_edges_and_convex_point_intersection() { #[test] fn ray_collide_doughnuts_near_intersection() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); - let mut circle1 = GraphPath::from_path(&circle1, ()); - circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); - let mut circle2 = GraphPath::from_path(&circle2, ()); - circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); + let mut circle1 = GraphPath::from_path(&circle1, ()); + circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); + let mut circle2 = GraphPath::from_path(&circle2, ()); + circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); - let graph_path = circle1.collide(circle2, 0.1); + let graph_path = circle1.collide(circle2, 0.1); - let collisions = graph_path.ray_collisions(&(Coord2(7.000584357101389, 8.342524209216537), Coord2(6.941479643691172, 8.441210096108172))); + let collisions = graph_path.ray_collisions(&( + Coord2(7.000584357101389, 8.342524209216537), + Coord2(6.941479643691172, 8.441210096108172), + )); let collision_count = collisions.len(); println!("{:?}", collisions); - assert!((collision_count&1) == 0); + assert!((collision_count & 1) == 0); } #[test] fn ray_collide_doughnuts_many_angles() { - let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); - let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); - let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); - let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); + let circle1 = Circle::new(Coord2(5.0, 5.0), 4.0).to_path::(); + let inner_circle1 = Circle::new(Coord2(5.0, 5.0), 3.9).to_path::(); + let circle2 = Circle::new(Coord2(9.0, 5.0), 4.0).to_path::(); + let inner_circle2 = Circle::new(Coord2(9.0, 5.0), 3.9).to_path::(); - let mut circle1 = GraphPath::from_path(&circle1, ()); - circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); - let mut circle2 = GraphPath::from_path(&circle2, ()); - circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); + let mut circle1 = GraphPath::from_path(&circle1, ()); + circle1 = circle1.merge(GraphPath::from_path(&inner_circle1, ())); + let mut circle2 = GraphPath::from_path(&circle2, ()); + circle2 = circle2.merge(GraphPath::from_path(&inner_circle2, ())); - let graph_path = circle1.collide(circle2, 0.01); + let graph_path = circle1.collide(circle2, 0.01); for angle in 0..3600 { - let angle = (angle as f64)/3600.0; - let angle = (angle/360.0) * 2.0*f64::consts::PI; - let ray_start = Coord2(9.0, 5.0) + Coord2(5.0*angle.sin(), 5.0*angle.cos()); - let ray_end = Coord2(5.0, 5.0) - Coord2(5.0*angle.sin(), 5.0*angle.cos()); + let angle = (angle as f64) / 3600.0; + let angle = (angle / 360.0) * 2.0 * f64::consts::PI; + let ray_start = Coord2(9.0, 5.0) + Coord2(5.0 * angle.sin(), 5.0 * angle.cos()); + let ray_end = Coord2(5.0, 5.0) - Coord2(5.0 * angle.sin(), 5.0 * angle.cos()); - let collisions = graph_path.ray_collisions(&(ray_start, ray_end)); + let collisions = graph_path.ray_collisions(&(ray_start, ray_end)); let collision_count = collisions.len(); - assert!((collision_count&1) == 0); + assert!((collision_count & 1) == 0); } } #[test] fn self_collide_removes_shared_point_1() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(5.0, 5.0)) @@ -1311,7 +1515,7 @@ fn self_collide_removes_shared_point_1() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); + let mut graph_path = GraphPath::from_path(&path, ()); graph_path.self_collide(0.01); let mut edges_ending_at_center = vec![]; @@ -1322,12 +1526,14 @@ fn self_collide_removes_shared_point_1() { } assert!(edges_ending_at_center.len() == 2); - assert!(edges_ending_at_center.iter().all(|edge| edge.end_point_index() == edges_ending_at_center[0].end_point_index())); + assert!(edges_ending_at_center + .iter() + .all(|edge| edge.end_point_index() == edges_ending_at_center[0].end_point_index())); } #[test] fn self_collide_removes_shared_point_2() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(5.0, 1.0)) @@ -1336,7 +1542,7 @@ fn self_collide_removes_shared_point_2() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); + let mut graph_path = GraphPath::from_path(&path, ()); graph_path.self_collide(0.01); let mut edges_ending_at_center = vec![]; @@ -1347,12 +1553,14 @@ fn self_collide_removes_shared_point_2() { } assert!(edges_ending_at_center.len() == 2); - assert!(edges_ending_at_center.iter().all(|edge| edge.end_point_index() == edges_ending_at_center[0].end_point_index())); + assert!(edges_ending_at_center + .iter() + .all(|edge| edge.end_point_index() == edges_ending_at_center[0].end_point_index())); } #[test] fn self_collide_divides_lines_1() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(3.0, 0.0)) .line_to(Coord2(5.0, 5.0)) @@ -1360,7 +1568,7 @@ fn self_collide_divides_lines_1() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); + let mut graph_path = GraphPath::from_path(&path, ()); assert!(graph_path.all_edges().count() == 5); @@ -1371,7 +1579,7 @@ fn self_collide_divides_lines_1() { #[test] fn self_collide_divides_lines_2() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(3.0, 0.985)) .line_to(Coord2(5.0, 5.0)) @@ -1379,7 +1587,7 @@ fn self_collide_divides_lines_2() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); + let mut graph_path = GraphPath::from_path(&path, ()); assert!(graph_path.all_edges().count() == 5); @@ -1391,7 +1599,7 @@ fn self_collide_divides_lines_2() { #[test] fn self_collide_divides_lines_3() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(3.0, 0.999)) .line_to(Coord2(5.0, 5.0)) @@ -1399,7 +1607,7 @@ fn self_collide_divides_lines_3() { .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); + let mut graph_path = GraphPath::from_path(&path, ()); assert!(graph_path.all_edges().count() == 5); @@ -1408,24 +1616,27 @@ fn self_collide_divides_lines_3() { println!("{:?}", graph_path); // So close we should just collide as if the 3.0, 0.999 point is at 3.0, 1.0 - assert!(!graph_path.all_edges().any(|edge| edge.start_point().distance_to(&edge.end_point()) < 0.001)); - assert!(graph_path.all_edges().count() != 9); // Technically valid, indicates a change in the precision of the collision + assert!(!graph_path + .all_edges() + .any(|edge| edge.start_point().distance_to(&edge.end_point()) < 0.001)); + assert!(graph_path.all_edges().count() != 9); // Technically valid, indicates a change in the precision of the collision assert!(graph_path.all_edges().count() != 7); assert!(graph_path.all_edges().count() == 6); } #[test] fn heal_one_line_gap() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(5.0, 5.0)) .line_to(Coord2(5.0, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); - let edges = (0..4).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).nth(0).unwrap().into()) + let mut graph_path = GraphPath::from_path(&path, ()); + let edges = (0..4) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).next().unwrap().into()) .collect::>(); graph_path.set_edge_kind(edges[0], GraphPathEdgeKind::Exterior); @@ -1439,19 +1650,19 @@ fn heal_one_line_gap() { assert!(graph_path.get_edge(edges[1]).kind() == GraphPathEdgeKind::Exterior); } - #[test] fn heal_two_line_gap() { - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(5.0, 5.0)) .line_to(Coord2(5.0, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let mut graph_path = GraphPath::from_path(&path, ()); - let edges = (0..4).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).nth(0).unwrap().into()) + let mut graph_path = GraphPath::from_path(&path, ()); + let edges = (0..4) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).next().unwrap().into()) .collect::>(); graph_path.set_edge_kind(edges[0], GraphPathEdgeKind::Exterior); @@ -1510,9 +1721,10 @@ fn ray_cast_at_tiny_line_2() { // Should be able to cast a ray and hit our line and none of the others for p in 0..10 { - let offset = ((p as f64)/10.0) * 0.01; + let offset = ((p as f64) / 10.0) * 0.01; - let collisions = path.ray_collisions(&(Coord2(2.9945 + offset, 0.0), Coord2(2.9945+offset, 1.0))); + let collisions = + path.ray_collisions(&(Coord2(2.9945 + offset, 0.0), Coord2(2.9945 + offset, 1.0))); println!("{:?}", collisions); assert!(collisions.len() == 2); @@ -1627,18 +1839,39 @@ fn ray_cast_at_tiny_line_6() { // Path with a pair of lines with a known failure on them let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(525.2388916015625, 931.7135009765625)) - .curve_to((Coord2(525.4012451171875, 931.7196044921875), Coord2(525.5686645507813, 931.7201538085938)), Coord2(525.7626342773438, 931.6915893554688)) - .curve_to((Coord2(526.2460327148438, 931.761962890625), Coord2(526.3161010742188, 931.8532104492188)), Coord2(526.6378173828125, 931.9375610351563)) - .curve_to((Coord2(529.997314453125, 935.0886840820313), Coord2(508.8724365234375, 903.5847778320313)), Coord2(508.7933654785156, 901.745849609375)) + .curve_to( + ( + Coord2(525.4012451171875, 931.7196044921875), + Coord2(525.5686645507813, 931.7201538085938), + ), + Coord2(525.7626342773438, 931.6915893554688), + ) + .curve_to( + ( + Coord2(526.2460327148438, 931.761962890625), + Coord2(526.3161010742188, 931.8532104492188), + ), + Coord2(526.6378173828125, 931.9375610351563), + ) + .curve_to( + ( + Coord2(529.997314453125, 935.0886840820313), + Coord2(508.8724365234375, 903.5847778320313), + ), + Coord2(508.7933654785156, 901.745849609375), + ) .line_to(Coord2(700.0, 900.0)) .line_to(Coord2(1.0, 900.0)) .line_to(Coord2(1.0, 1.0)) .build(); let path = GraphPath::from_path(&path, ()); - let collisions = path.ray_collisions(&(Coord2(543.606689453125, 925.3496704101563), Coord2(553.524658203125, 921.505126953125))); + let collisions = path.ray_collisions(&( + Coord2(543.606689453125, 925.3496704101563), + Coord2(553.524658203125, 921.505126953125), + )); println!("{:?}", collisions); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); } @@ -1656,9 +1889,9 @@ fn ray_cast_grazing_circle_produces_0_hits() { let collisions = path.ray_collisions(&(Coord2(24.0, 0.0), Coord2(24.0, 1.0))); // Should not actually hit the circle - assert!(collisions.len() != 2); // 2 collisions would produce no bug + assert!(collisions.len() != 2); // 2 collisions would produce no bug assert!(collisions.len() != 1); - assert!(collisions.len() == 0); + assert!(collisions.is_empty()); } #[test] @@ -1675,7 +1908,7 @@ fn ray_cast_close_to_circle_produces_2_hits() { let collisions = path.ray_collisions(&(Coord2(23.99, 0.0), Coord2(23.99, 1.0))); // Should not actually hit the circle - assert!(collisions.len() != 0); + assert!(!collisions.is_empty()); assert!(collisions.len() != 1); assert!(collisions.len() == 2); } @@ -1683,36 +1916,36 @@ fn ray_cast_close_to_circle_produces_2_hits() { #[test] pub fn ray_cast_identical_rectangles() { // Create the two rectangles - let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(5.0, 1.0)) .line_to(Coord2(5.0, 5.0)) .line_to(Coord2(1.0, 5.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let rectangle2 = rectangle1.clone(); - - let rectangle1 = GraphPath::from_path(&rectangle1, 1); - let rectangle2 = GraphPath::from_path(&rectangle2, 2); + let rectangle2 = rectangle1.clone(); + + let rectangle1 = GraphPath::from_path(&rectangle1, 1); + let rectangle2 = GraphPath::from_path(&rectangle2, 2); // Collide them - let path = rectangle1.collide(rectangle2, 0.1); + let path = rectangle1.collide(rectangle2, 0.1); // The edges are identical, so we need to process them in a consistent order - let collisions = path.ray_collisions(&(Coord2(3.0, 0.0), Coord2(3.0, 10.0))); + let collisions = path.ray_collisions(&(Coord2(3.0, 0.0), Coord2(3.0, 10.0))); // Collides with two edges twice, so four total collisions assert!(collisions.len() == 4); // First two collisions should hit path 1 and path 2 - let edge1 = collisions[0].0.edge(); - let edge2 = collisions[1].0.edge(); - let edge3 = collisions[2].0.edge(); - let edge4 = collisions[3].0.edge(); - - let edge1 = path.get_edge(edge1); - let edge2 = path.get_edge(edge2); - let edge3 = path.get_edge(edge3); - let edge4 = path.get_edge(edge4); + let edge1 = collisions[0].0.edge(); + let edge2 = collisions[1].0.edge(); + let edge3 = collisions[2].0.edge(); + let edge4 = collisions[3].0.edge(); + + let edge1 = path.get_edge(edge1); + let edge2 = path.get_edge(edge2); + let edge3 = path.get_edge(edge3); + let edge4 = path.get_edge(edge4); // edge1, edge2 and edge3, edge4 should all have the same start and end points (ie, be duplicate edges) assert!(edge1.start_point_index() == edge2.start_point_index()); diff --git a/tests/bezier/path/intersection.rs b/tests/bezier/path/intersection.rs index d30a1ba3..60bedf2a 100644 --- a/tests/bezier/path/intersection.rs +++ b/tests/bezier/path/intersection.rs @@ -1,7 +1,8 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{ + path_intersects_line, path_intersects_path, BezierPath, SimpleBezierPath, +}; +use flo_curves::bezier::{BezierCurve, BoundingBox, Coord2, Coordinate, Curve}; use std::f64; @@ -14,22 +15,22 @@ fn awkward_line_intersects_circle() { let circle: SimpleBezierPath = Circle::new(center, radius).to_path(); // Line from the center to the edge - let line = (Coord2(5.0, 5.0), Coord2(5.0, 9.5)); - let intersection = path_intersects_line(&circle, &line).collect::>(); + let line = (Coord2(5.0, 5.0), Coord2(5.0, 9.5)); + let intersection = path_intersects_line(&circle, &line).collect::>(); // This should be an intersection (straight up from the center) assert!(intersection.len() == 1); // Line from the center to the edge - let line = (Coord2(5.0, 5.0), Coord2(5.0, 0.5)); - let intersection = path_intersects_line(&circle, &line).collect::>(); + let line = (Coord2(5.0, 5.0), Coord2(5.0, 0.5)); + let intersection = path_intersects_line(&circle, &line).collect::>(); // This should be an intersection (straight down from the center) assert!(intersection.len() == 1); // Line from the center to the edge - let line = (Coord2(5.0, 5.0), Coord2(4.999999999999999, 9.5)); - let intersection = path_intersects_line(&circle, &line).collect::>(); + let line = (Coord2(5.0, 5.0), Coord2(4.999999999999999, 9.5)); + let intersection = path_intersects_line(&circle, &line).collect::>(); // This should be an intersection (almost straight up from the center) assert!(intersection.len() == 1); @@ -49,22 +50,22 @@ fn line_intersects_circle() { let circle_sections = circle.to_curves::>(); for angle in 0..=20 { - let angle = angle as f64; - let radians = (2.0*f64::consts::PI)*(angle/20.0); + let angle = angle as f64; + let radians = (2.0 * f64::consts::PI) * (angle / 20.0); - let target = Coord2(radians.sin()*length, radians.cos()*length); - let target = target + center; + let target = Coord2(radians.sin() * length, radians.cos() * length); + let target = target + center; - let expected = Coord2(radians.sin()*radius, radians.cos()*radius); - let expected = expected + center; + let expected = Coord2(radians.sin() * radius, radians.cos() * radius); + let expected = expected + center; // Should be one intersection with the circle here - let line = (center, target); - let intersection = path_intersects_line(&circle, &line).collect::>(); + let line = (center, target); + let intersection = path_intersects_line(&circle, &line).collect::>(); assert!(intersection.len() == 1); - if intersection.len() > 0 { - let intersection = intersection[0]; + if !intersection.is_empty() { + let intersection = intersection[0]; let intersect_point = circle_sections[intersection.0].point_at_pos(intersection.1); assert!(expected.distance_to(&intersect_point).abs() < 0.01); @@ -84,16 +85,16 @@ fn line_does_not_intersect_circle() { let length = 3.9999; for angle in 0..=20 { - let angle = angle as f64; - let radians = (2.0*f64::consts::PI)*(angle/20.0); + let angle = angle as f64; + let radians = (2.0 * f64::consts::PI) * (angle / 20.0); - let target = Coord2(radians.sin()*length, radians.cos()*length); - let target = target + center; + let target = Coord2(radians.sin() * length, radians.cos() * length); + let target = target + center; // Should be one intersection with the circle here - let line = (center, target); - let intersection = path_intersects_line(&circle, &line).collect::>(); - assert!(intersection.len() == 0); + let line = (center, target); + let intersection = path_intersects_line(&circle, &line).collect::>(); + assert!(intersection.is_empty()); } } @@ -110,7 +111,7 @@ fn circle_intersects_circle() { let intersections = path_intersects_path(&circle1, &circle2, 0.5); // The circles should intersect at least once - assert!(intersections.len() > 0); + assert!(!intersections.is_empty()); println!("{:?}", intersections); // Convert to curves @@ -122,7 +123,12 @@ fn circle_intersects_circle() { let point1 = curves1[*index1].point_at_pos(*t1); let point2 = curves2[*index2].point_at_pos(*t2); - println!("{:?} {:?} {:?}", point1, point2, point1.distance_to(&point2)); + println!( + "{:?} {:?} {:?}", + point1, + point2, + point1.distance_to(&point2) + ); } for ((index1, t1), (index2, t2)) in intersections.iter() { diff --git a/tests/bezier/path/is_clockwise.rs b/tests/bezier/path/is_clockwise.rs index 62309163..90530a02 100644 --- a/tests/bezier/path/is_clockwise.rs +++ b/tests/bezier/path/is_clockwise.rs @@ -1,5 +1,5 @@ -use flo_curves::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{BezierPathBuilder, PathWithIsClockwise, SimpleBezierPath}; +use flo_curves::Coord2; #[test] pub fn rectangle_is_clockwise() { diff --git a/tests/bezier/path/mod.rs b/tests/bezier/path/mod.rs index a73730f8..9ff6ea6b 100644 --- a/tests/bezier/path/mod.rs +++ b/tests/bezier/path/mod.rs @@ -1,15 +1,15 @@ -mod svg; -mod to_curves; -mod point; -mod path; -mod intersection; -mod bounds; -mod graph_path; -mod is_clockwise; mod arithmetic_add; mod arithmetic_chain_add; -mod arithmetic_sub; +mod arithmetic_complicated_paths; mod arithmetic_cut; mod arithmetic_intersect; -mod arithmetic_complicated_paths; +mod arithmetic_sub; +mod bounds; +mod graph_path; +mod intersection; +mod is_clockwise; +mod path; +mod point; mod rays; +mod svg; +mod to_curves; diff --git a/tests/bezier/path/path.rs b/tests/bezier/path/path.rs index d3a8f98a..480f11ee 100644 --- a/tests/bezier/path/path.rs +++ b/tests/bezier/path/path.rs @@ -1,5 +1,5 @@ -use flo_curves::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{BezierPath, BezierPathBuilder, SimpleBezierPath}; +use flo_curves::{BezierCurve, Coord2, Coordinate, Line}; #[test] fn reverse_rectangle() { diff --git a/tests/bezier/path/point.rs b/tests/bezier/path/point.rs index b01c4675..04cf14af 100644 --- a/tests/bezier/path/point.rs +++ b/tests/bezier/path/point.rs @@ -1,16 +1,19 @@ -use flo_curves::*; -use flo_curves::arc::*; -use flo_curves::bezier::path::*; +use flo_curves::arc::Circle; +use flo_curves::bezier::path::{path_contains_point, SimpleBezierPath}; +use flo_curves::Coord2; #[test] fn simple_path_contains_point() { // Path is a square - let path = (Coord2(1.0, 2.0), vec![ - (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), - (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), - (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), - (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)) - ]); + let path = ( + Coord2(1.0, 2.0), + vec![ + (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), + (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), + (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), + (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)), + ], + ); // Point should be inside assert!(path_contains_point(&path, &Coord2(5.0, 5.0))); @@ -56,12 +59,15 @@ fn circle_edge_is_inside() { #[test] fn point_on_edge_is_not_in_path() { // Path is a square - let path = (Coord2(1.0, 2.0), vec![ - (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), - (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), - (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), - (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)) - ]); + let path = ( + Coord2(1.0, 2.0), + vec![ + (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), + (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), + (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), + (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)), + ], + ); // Points just on the boundary should be outside of the path assert!(!path_contains_point(&path, &Coord2(5.0, 2.0))); @@ -71,12 +77,15 @@ fn point_on_edge_is_not_in_path() { #[test] fn corner_is_in_path() { // Path is a square - let path = (Coord2(1.0, 2.0), vec![ - (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), - (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), - (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), - (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)) - ]); + let path = ( + Coord2(1.0, 2.0), + vec![ + (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), + (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), + (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), + (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)), + ], + ); // Points right on the edge but on the corners are in the path assert!(path_contains_point(&path, &Coord2(1.001, 2.001))); @@ -88,12 +97,15 @@ fn corner_is_in_path() { #[test] fn points_outside_bounds_are_outside_path() { // Path is a square - let path = (Coord2(1.0, 2.0), vec![ - (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), - (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), - (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), - (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)) - ]); + let path = ( + Coord2(1.0, 2.0), + vec![ + (Coord2(3.0, 2.0), Coord2(6.0, 2.0), Coord2(9.0, 2.0)), + (Coord2(9.0, 4.0), Coord2(9.0, 6.0), Coord2(9.0, 8.0)), + (Coord2(6.0, 8.0), Coord2(3.0, 8.0), Coord2(1.0, 8.0)), + (Coord2(1.0, 6.0), Coord2(1.0, 4.0), Coord2(1.0, 2.0)), + ], + ); // Points far outside the path should be outside assert!(!path_contains_point(&path, &Coord2(5.0, 20.0))); diff --git a/tests/bezier/path/rays.rs b/tests/bezier/path/rays.rs index e70fa5b8..1e2d2b22 100644 --- a/tests/bezier/path/rays.rs +++ b/tests/bezier/path/rays.rs @@ -1,6 +1,9 @@ -use flo_curves::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::{ + BezierPath, BezierPathBuilder, BezierPathFactory, GraphPath, SimpleBezierPath, +}; +use flo_curves::bezier::{ + curve_intersects_ray, BezierCurveFactory, BoundingBox, Coord2, Coordinate, Curve, +}; use std::collections::HashMap; @@ -12,22 +15,22 @@ fn crossing_figure_of_8_intersection_from_inside() { // | + | <--- RAY // | / \ | // + + - // + // // This ray hits a corner but it should generate either 0 or 2 collisions at this point, and particularly not 1. - // (0 intersections implies the ray never leaves the shape and 2 intersections indicates it leaves and immediately + // (0 intersections implies the ray never leaves the shape and 2 intersections indicates it leaves and immediately // re-enters) - // + // // (Interestingly, either behaviour is correct: if there are 0 collisions we won't categorise the edges and if there // are 2 we'll mark the edges as exterior when the ray is used to set edge kinds) - // + // // This is interesting because of this case: - // + // // + // | \ // | + <--- RAY // | / // + - // + // // As the 'same' point here should always generate 1 intersection as the ray enters the shape at this point (or leaves // in the reverse direction) // @@ -49,25 +52,33 @@ fn crossing_figure_of_8_intersection_from_inside() { let collisions = graph_path.ray_collisions(&(Coord2(8.0, 2.0), Coord2(7.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); // The intersection point should be an actual intersection - assert!((0..(graph_path.num_points())).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).count()) - .filter(|num_edges_for_point| num_edges_for_point == &2) - .count() == 1); - assert!((0..(graph_path.num_points())).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).count()) - .filter(|num_edges_for_point| num_edges_for_point == &1) - .count() == 4); + assert!( + (0..(graph_path.num_points())) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).count()) + .filter(|num_edges_for_point| num_edges_for_point == &2) + .count() + == 1 + ); + assert!( + (0..(graph_path.num_points())) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).count()) + .filter(|num_edges_for_point| num_edges_for_point == &1) + .count() + == 4 + ); // Also test the ray travelling the other way let collisions = graph_path.ray_collisions(&(Coord2(-2.0, 2.0), Coord2(-1.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); } @@ -80,7 +91,7 @@ fn crossing_figure_of_8_intersection_from_inside_reversed() { // | + | <--- RAY // | / \ | // + + - // + // let left_triangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 3.0)) @@ -99,25 +110,33 @@ fn crossing_figure_of_8_intersection_from_inside_reversed() { let collisions = graph_path.ray_collisions(&(Coord2(8.0, 2.0), Coord2(7.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); // The intersection point should be an actual intersection - assert!((0..(graph_path.num_points())).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).count()) - .filter(|num_edges_for_point| num_edges_for_point == &2) - .count() == 1); - assert!((0..(graph_path.num_points())).into_iter() - .map(|point_idx| graph_path.edges_for_point(point_idx).count()) - .filter(|num_edges_for_point| num_edges_for_point == &1) - .count() == 4); + assert!( + (0..(graph_path.num_points())) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).count()) + .filter(|num_edges_for_point| num_edges_for_point == &2) + .count() + == 1 + ); + assert!( + (0..(graph_path.num_points())) + .into_iter() + .map(|point_idx| graph_path.edges_for_point(point_idx).count()) + .filter(|num_edges_for_point| num_edges_for_point == &1) + .count() + == 4 + ); // Also test the ray travelling the other way let collisions = graph_path.ray_collisions(&(Coord2(-2.0, 2.0), Coord2(-1.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); } @@ -130,7 +149,7 @@ fn crossing_figure_of_8_intersection_from_inside_nearby() { // | + | <--- RAY // | / \ | // + + - // + // let left_triangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 3.0)) @@ -146,11 +165,16 @@ fn crossing_figure_of_8_intersection_from_inside_nearby() { let graph_path = GraphPath::from_path(&left_triangle, ()); let graph_path = graph_path.collide(GraphPath::from_path(&right_triangle, ()), 0.01); - for y in [1.9, 1.99, 1.999, 1.9999, 1.99999, 1.99999, 2.1, 2.01, 2.001, 2.0001, 2.00001, 2.000001, 2.0000001].iter() { + for y in [ + 1.9, 1.99, 1.999, 1.9999, 1.99999, 1.99999, 2.1, 2.01, 2.001, 2.0001, 2.00001, 2.000001, + 2.0000001, + ] + .iter() + { let collisions = graph_path.ray_collisions(&(Coord2(8.0, *y), Coord2(7.0, *y))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4 || collisions.len() == 2); @@ -158,7 +182,7 @@ fn crossing_figure_of_8_intersection_from_inside_nearby() { let collisions = graph_path.ray_collisions(&(Coord2(-2.0, *y), Coord2(-1.0, *y))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 4); } @@ -176,7 +200,7 @@ fn crossing_figure_of_8_intersection_collinear() { // | + | // | / \ | // + + - // + // let left_triangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 3.0)) @@ -195,9 +219,9 @@ fn crossing_figure_of_8_intersection_collinear() { let collisions = graph_path.ray_collisions(&(Coord2(1.0, 1.0), Coord2(3.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); - assert!(collisions.len() == 0); + assert!(collisions.is_empty()); } #[test] @@ -220,9 +244,9 @@ fn crossing_figure_of_8_intersection_from_outside() { let collisions = graph_path.ray_collisions(&(Coord2(3.0, 0.0), Coord2(3.0, 1.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); - assert!(collisions.len() == 0 || collisions.len() == 2); + assert!(collisions.is_empty() || collisions.len() == 2); } #[test] @@ -231,8 +255,8 @@ fn crossing_intersection_with_collinear_edge() { // + + // | \ / \ // | + ----- + <--- RAY - // | / - // + + // | / + // + // let left_triangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) @@ -252,20 +276,20 @@ fn crossing_intersection_with_collinear_edge() { let collisions = graph_path.ray_collisions(&(Coord2(8.0, 2.0), Coord2(7.0, 2.0))); assert!(collisions.len() != 3); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 2); } #[test] fn ray_entering_triangle_through_apex_1() { - // + // // + // | \ // | + <--- RAY // | / // + - // + // // As the 'same' point here should always generate 1 intersection as the ray enters the shape at this point (or leaves // in the reverse direction) // @@ -280,19 +304,19 @@ fn ray_entering_triangle_through_apex_1() { let collisions = graph_path.ray_collisions(&(Coord2(8.0, 2.0), Coord2(7.0, 2.0))); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 2); } #[test] fn ray_entering_triangle_through_apex_2() { - // + // // + // | \ // | + ---> RAY // | / // + - // + // // As the 'same' point here should always generate 1 intersection as the ray enters the shape at this point (or leaves // in the reverse direction) // @@ -307,19 +331,19 @@ fn ray_entering_triangle_through_apex_2() { let collisions = graph_path.ray_collisions(&(Coord2(0.0, 2.0), Coord2(1.0, 2.0))); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 2); } #[test] fn ray_entering_triangle_through_apex_3() { - // + // // + // | \ // | + ---> RAY // | / // + - // + // // As the 'same' point here should always generate 1 intersection as the ray enters the shape at this point (or leaves // in the reverse direction) // @@ -332,10 +356,15 @@ fn ray_entering_triangle_through_apex_3() { let graph_path = GraphPath::from_path(&left_triangle, ()); - for y in [1.9, 1.99, 1.999, 1.9999, 1.99999, 1.99999, 2.1, 2.01, 2.001, 2.0001, 2.00001, 2.000001, 2.0000001].iter() { + for y in [ + 1.9, 1.99, 1.999, 1.9999, 1.99999, 1.99999, 2.1, 2.01, 2.001, 2.0001, 2.00001, 2.000001, + 2.0000001, + ] + .iter() + { let collisions = graph_path.ray_collisions(&(Coord2(0.0, *y), Coord2(1.0, *y))); - assert!((collisions.len()&1) == 0); + assert!((collisions.len() & 1) == 0); assert!(collisions.len() == 2); } } @@ -352,18 +381,42 @@ fn ray_hitting_tangent_at_point() { let collisions = graph_path.ray_collisions(&(Coord2(3.0, 0.0), Coord2(3.0, 1.0))); - assert!((collisions.len()&1) == 0); - assert!(collisions.len() == 0); + assert!((collisions.len() & 1) == 0); + assert!(collisions.is_empty()); } #[test] fn ray_hitting_intersection_bad() { // These three edges form an intersection that has a known bad intersection with the specified ray // edge2 here generates 2 collisions at the intersection for some reason, which seems to be what's causing a bug - let ray = (Coord2(614.1064453125, 904.1033935546875), Coord2(614.3379516601563, 903.910888671875)); - let edge1 = Curve::from_points(Coord2(612.35302734375, 902.1972045898438), (Coord2(611.9544677734375, 904.4937744140625), Coord2(612.1427001953125, 905.798828125)), Coord2(613.4901123046875, 904.6159057617188)); - let edge2 = Curve::from_points(Coord2(613.4901123046875, 904.6159057617188), (Coord2(613.6087646484375, 904.5118408203125), Coord2(613.736328125, 904.388427734375)), Coord2(613.873291015625, 904.2447509765625)); - let edge3 = Curve::from_points(Coord2(613.1998901367188, 904.267822265625), (Coord2(613.2864379882813, 904.4163818359375), Coord2(613.3829956054688, 904.5339965820313)), Coord2(613.4901123046875, 904.6159057617188)); + let ray = ( + Coord2(614.1064453125, 904.1033935546875), + Coord2(614.3379516601563, 903.910888671875), + ); + let edge1 = Curve::from_points( + Coord2(612.35302734375, 902.1972045898438), + ( + Coord2(611.9544677734375, 904.4937744140625), + Coord2(612.1427001953125, 905.798828125), + ), + Coord2(613.4901123046875, 904.6159057617188), + ); + let edge2 = Curve::from_points( + Coord2(613.4901123046875, 904.6159057617188), + ( + Coord2(613.6087646484375, 904.5118408203125), + Coord2(613.736328125, 904.388427734375), + ), + Coord2(613.873291015625, 904.2447509765625), + ); + let edge3 = Curve::from_points( + Coord2(613.1998901367188, 904.267822265625), + ( + Coord2(613.2864379882813, 904.4163818359375), + Coord2(613.3829956054688, 904.5339965820313), + ), + Coord2(613.4901123046875, 904.6159057617188), + ); let ray1 = curve_intersects_ray(&edge1, &ray); let ray2 = curve_intersects_ray(&edge2, &ray); @@ -394,18 +447,18 @@ fn ray_hitting_start_and_end_of_line_1() { // ^ // | // Ray - // - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + // + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(2.0, 3.0)) .line_to(Coord2(2.0, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); assert!(collisions.len() == 2); } @@ -423,78 +476,78 @@ fn ray_hitting_start_and_end_of_line_2() { // ^ // | // Ray - // - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + // + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(2.0, 3.0)) .line_to(Coord2(2.001, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); println!("{:?}", collisions); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); assert!(collisions.len() == 2); } #[test] fn ray_hitting_start_and_end_of_line_3() { // As above but crossing even closer - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(2.0, 3.0)) .line_to(Coord2(2.000999, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); println!("{:?}", collisions); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); assert!(collisions.len() == 2); } #[test] fn ray_hitting_start_and_end_of_line_4() { // Moving one of the other points - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(1.999, 3.0)) .line_to(Coord2(2.0, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); println!("{:?}", collisions); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); assert!(collisions.len() == 2); } #[test] fn ray_hitting_start_and_end_of_curve_1() { // As above, but curve bowing outwards - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(2.0, 3.0)) .curve_to((Coord2(1.0, 3.0), Coord2(1.0, 2.0)), Coord2(2.0, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); assert!(collisions.len() == 2); } @@ -502,17 +555,17 @@ fn ray_hitting_start_and_end_of_curve_1() { #[test] fn ray_hitting_start_and_end_of_curve_2() { // As above, but curve bowing inwards - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(3.0, 1.0)) .line_to(Coord2(3.0, 3.0)) .line_to(Coord2(2.0, 3.0)) .curve_to((Coord2(3.0, 3.0), Coord2(3.0, 2.0)), Coord2(2.0, 2.0)) .line_to(Coord2(1.0, 2.0)) .build(); - let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); - let graph_path = GraphPath::from_path(&path, ()); + let ray = (Coord2(2.0, 0.0), Coord2(2.0, 1.0)); + let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let collisions = graph_path.ray_collisions(&ray); assert!(collisions.len() == 2); } @@ -521,82 +574,147 @@ fn ray_hitting_start_and_end_of_curve_2() { fn ray_crossing_and_glancing() { // This is a ray that scores a crossing collision followed by a glancing collision along a very short curve that is almost but not quite // collinear. We should remove the glancing collision but we do not for some reason - let ray = (Coord2(694.1428833007813, 884.8551025390625), Coord2(692.2153930664063, 883.697509765625)); - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let ray = ( + Coord2(694.1428833007813, 884.8551025390625), + Coord2(692.2153930664063, 883.697509765625), + ); + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(632.3152465820313, 838.1866455078125)) - .curve_to((Coord2(634.3826904296875, 844.4097290039063), Coord2(635.91357421875, 849.9752807617188)), Coord2(635.8788452148438, 849.8710327148438)) - .curve_to((Coord2(635.9103393554688, 849.8935546875), Coord2(635.9293212890625, 849.9025268554688)), Coord2(635.9400634765625, 849.9002075195313)) - .curve_to((Coord2(635.97216796875, 850.158447265625), Coord2(635.9955444335938, 850.357666015625)), Coord2(636.0320434570313, 850.4244384765625)) + .curve_to( + ( + Coord2(634.3826904296875, 844.4097290039063), + Coord2(635.91357421875, 849.9752807617188), + ), + Coord2(635.8788452148438, 849.8710327148438), + ) + .curve_to( + ( + Coord2(635.9103393554688, 849.8935546875), + Coord2(635.9293212890625, 849.9025268554688), + ), + Coord2(635.9400634765625, 849.9002075195313), + ) + .curve_to( + ( + Coord2(635.97216796875, 850.158447265625), + Coord2(635.9955444335938, 850.357666015625), + ), + Coord2(636.0320434570313, 850.4244384765625), + ) .line_to(Coord2(635.97216796875, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let graph_path = GraphPath::from_path(&path, ()); + let collisions = graph_path.ray_collisions(&ray); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); } #[test] fn ray_glancing_1() { - let ray = (Coord2(798.2357788085938, 783.7974853515625), Coord2(798.6553344726563, 782.6351928710938)); - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let ray = ( + Coord2(798.2357788085938, 783.7974853515625), + Coord2(798.6553344726563, 782.6351928710938), + ); + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 677.4369506835938)) .line_to(Coord2(839.3995361328125, 677.4369506835938)) - .curve_to((Coord2(839.6207275390625, 674.3713989257813), Coord2(838.3349609375, 674.1100463867188)), Coord2(837.8158569335938, 674.1475830078125)) - .curve_to((Coord2(838.1270141601563, 674.0364990234375), Coord2(838.5419311523438, 673.8883056640625)), Coord2(838.8530883789063, 673.7772216796875)) + .curve_to( + ( + Coord2(839.6207275390625, 674.3713989257813), + Coord2(838.3349609375, 674.1100463867188), + ), + Coord2(837.8158569335938, 674.1475830078125), + ) + .curve_to( + ( + Coord2(838.1270141601563, 674.0364990234375), + Coord2(838.5419311523438, 673.8883056640625), + ), + Coord2(838.8530883789063, 673.7772216796875), + ) .line_to(Coord2(838.8530883789063, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let graph_path = GraphPath::from_path(&path, ()); + let collisions = graph_path.ray_collisions(&ray); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); } #[test] fn ray_glancing_1_reversed() { - let ray = (Coord2(798.2357788085938, 783.7974853515625), Coord2(798.6553344726563, 782.6351928710938)); - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let ray = ( + Coord2(798.2357788085938, 783.7974853515625), + Coord2(798.6553344726563, 782.6351928710938), + ); + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 677.4369506835938)) .line_to(Coord2(839.3995361328125, 677.4369506835938)) - .curve_to((Coord2(839.6207275390625, 674.3713989257813), Coord2(838.3349609375, 674.1100463867188)), Coord2(837.8158569335938, 674.1475830078125)) - .curve_to((Coord2(838.1270141601563, 674.0364990234375), Coord2(838.5419311523438, 673.8883056640625)), Coord2(838.8530883789063, 673.7772216796875)) + .curve_to( + ( + Coord2(839.6207275390625, 674.3713989257813), + Coord2(838.3349609375, 674.1100463867188), + ), + Coord2(837.8158569335938, 674.1475830078125), + ) + .curve_to( + ( + Coord2(838.1270141601563, 674.0364990234375), + Coord2(838.5419311523438, 673.8883056640625), + ), + Coord2(838.8530883789063, 673.7772216796875), + ) .line_to(Coord2(838.8530883789063, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let path = path.reversed::(); + let path = path.reversed::(); - let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let graph_path = GraphPath::from_path(&path, ()); + let collisions = graph_path.ray_collisions(&ray); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); } #[test] fn ray_glancing_2() { - let ray = (Coord2(576.3092041015625, 854.6082153320313), Coord2(576.0201416015625, 854.5975341796875)); - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let ray = ( + Coord2(576.3092041015625, 854.6082153320313), + Coord2(576.0201416015625, 854.5975341796875), + ); + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 854.7772216796875)) .line_to(Coord2(580.010986328125, 854.7772216796875)) - .curve_to((Coord2(580.0106201171875, 854.7659301757813), Coord2(580.01025390625, 854.7548828125)), Coord2(580.0098266601563, 854.7442016601563)) - .curve_to((Coord2(580.0198974609375, 854.7529296875), Coord2(580.0298461914063, 854.761474609375)), Coord2(580.0394897460938, 854.7695922851563)) + .curve_to( + ( + Coord2(580.0106201171875, 854.7659301757813), + Coord2(580.01025390625, 854.7548828125), + ), + Coord2(580.0098266601563, 854.7442016601563), + ) + .curve_to( + ( + Coord2(580.0198974609375, 854.7529296875), + Coord2(580.0298461914063, 854.761474609375), + ), + Coord2(580.0394897460938, 854.7695922851563), + ) .line_to(Coord2(580.0394897460938, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let graph_path = GraphPath::from_path(&path, ()); + let collisions = graph_path.ray_collisions(&ray); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); let mut edge_collisions = HashMap::new(); for (collision, _curve_t, _line_t, _position) in collisions { - let edge = collision.edge(); + let edge = collision.edge(); - *(edge_collisions.entry(edge) - .or_insert(0)) += 1; + *(edge_collisions.entry(edge).or_insert(0)) += 1; } assert!(edge_collisions.into_iter().all(|(_, count)| count == 1)); @@ -604,28 +722,42 @@ fn ray_glancing_2() { #[test] fn ray_glancing_2_reversed() { - let ray = (Coord2(576.3092041015625, 854.6082153320313), Coord2(576.0201416015625, 854.5975341796875)); - let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) + let ray = ( + Coord2(576.3092041015625, 854.6082153320313), + Coord2(576.0201416015625, 854.5975341796875), + ); + let path = BezierPathBuilder::::start(Coord2(1.0, 1.0)) .line_to(Coord2(1.0, 854.7772216796875)) .line_to(Coord2(580.010986328125, 854.7772216796875)) - .curve_to((Coord2(580.0106201171875, 854.7659301757813), Coord2(580.01025390625, 854.7548828125)), Coord2(580.0098266601563, 854.7442016601563)) - .curve_to((Coord2(580.0198974609375, 854.7529296875), Coord2(580.0298461914063, 854.761474609375)), Coord2(580.0394897460938, 854.7695922851563)) + .curve_to( + ( + Coord2(580.0106201171875, 854.7659301757813), + Coord2(580.01025390625, 854.7548828125), + ), + Coord2(580.0098266601563, 854.7442016601563), + ) + .curve_to( + ( + Coord2(580.0198974609375, 854.7529296875), + Coord2(580.0298461914063, 854.761474609375), + ), + Coord2(580.0394897460938, 854.7695922851563), + ) .line_to(Coord2(580.0394897460938, 1.0)) .line_to(Coord2(1.0, 1.0)) .build(); - let path = path.reversed::(); + let path = path.reversed::(); - let graph_path = GraphPath::from_path(&path, ()); - let collisions = graph_path.ray_collisions(&ray); + let graph_path = GraphPath::from_path(&path, ()); + let collisions = graph_path.ray_collisions(&ray); - assert!(collisions.len()&1 == 0); + assert!(collisions.len() & 1 == 0); let mut edge_collisions = HashMap::new(); for (collision, _curve_t, _line_t, _position) in collisions { - let edge = collision.edge(); + let edge = collision.edge(); - *(edge_collisions.entry(edge) - .or_insert(0)) += 1; + *(edge_collisions.entry(edge).or_insert(0)) += 1; } assert!(edge_collisions.into_iter().all(|(_, count)| count == 1)); diff --git a/tests/bezier/path/svg.rs b/tests/bezier/path/svg.rs index 6c09172a..cbb9ac72 100644 --- a/tests/bezier/path/svg.rs +++ b/tests/bezier/path/svg.rs @@ -1,15 +1,33 @@ -use flo_curves::geo::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::BezierPath; +use flo_curves::geo::{Coordinate2D, Coordinate3D}; use std::fmt::Write; -pub fn svg_path_string(path: &Path) -> String -where Path::Point: Coordinate2D { +pub fn svg_path_string(path: &Path) -> String +where + Path::Point: Coordinate2D, +{ let mut svg = String::new(); - write!(&mut svg, "M {} {}", path.start_point().x(), path.start_point().y()).unwrap(); + write!( + svg, + "M {} {}", + path.start_point().x(), + path.start_point().y() + ) + .unwrap(); for (cp1, cp2, end) in path.points() { - write!(&mut svg, " C {} {}, {} {}, {} {}", cp1.x(), cp1.y(), cp2.x(), cp2.y(), end.x(), end.y()).unwrap(); + write!( + svg, + " C {} {}, {} {}, {} {}", + cp1.x(), + cp1.y(), + cp2.x(), + cp2.y(), + end.x(), + end.y() + ) + .unwrap(); } svg diff --git a/tests/bezier/path/to_curves.rs b/tests/bezier/path/to_curves.rs index ac331270..131307d4 100644 --- a/tests/bezier/path/to_curves.rs +++ b/tests/bezier/path/to_curves.rs @@ -1,31 +1,42 @@ -use flo_curves::*; -use flo_curves::bezier::*; -use flo_curves::bezier::path::*; +use flo_curves::bezier::path::path_to_curves; +use flo_curves::bezier::{BoundingBox, Coord2, Coordinate, Curve}; #[test] pub fn convert_path_to_bezier_curves() { - let path = (Coord2(10.0, 11.0), vec![(Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0))]); - let curve = path_to_curves::<_, Curve<_>>(&path); - let curve: Vec<_> = curve.collect(); + let path = ( + Coord2(10.0, 11.0), + vec![ + (Coord2(15.0, 16.0), Coord2(17.0, 18.0), Coord2(19.0, 20.0)), + (Coord2(21.0, 22.0), Coord2(23.0, 24.0), Coord2(25.0, 26.0)), + ], + ); + let curve = path_to_curves::<_, Curve<_>>(&path); + let curve: Vec<_> = curve.collect(); assert!(curve.len() == 2); - assert!(curve[0] == Curve { - start_point: Coord2(10.0, 11.0), - end_point: Coord2(19.0, 20.0), - control_points: (Coord2(15.0, 16.0), Coord2(17.0, 18.0)) - }); - assert!(curve[1] == Curve { - start_point: Coord2(19.0, 20.0), - end_point: Coord2(25.0, 26.0), - control_points: (Coord2(21.0, 22.0), Coord2(23.0, 24.0)) - }); + assert!( + curve[0] + == Curve { + start_point: Coord2(10.0, 11.0), + end_point: Coord2(19.0, 20.0), + control_points: (Coord2(15.0, 16.0), Coord2(17.0, 18.0)) + } + ); + assert!( + curve[1] + == Curve { + start_point: Coord2(19.0, 20.0), + end_point: Coord2(25.0, 26.0), + control_points: (Coord2(21.0, 22.0), Coord2(23.0, 24.0)) + } + ); } #[test] pub fn no_points_means_no_curve() { - let path = (Coord2(10.0, 11.0), vec![]); - let curve = path_to_curves::<_, Curve<_>>(&path); - let curve: Vec<_> = curve.collect(); + let path = (Coord2(10.0, 11.0), vec![]); + let curve = path_to_curves::<_, Curve<_>>(&path); + let curve: Vec<_> = curve.collect(); - assert!(curve.len() == 0); + assert!(curve.is_empty()); } diff --git a/tests/bezier/search.rs b/tests/bezier/search.rs index 568b7291..a50aed11 100644 --- a/tests/bezier/search.rs +++ b/tests/bezier/search.rs @@ -6,15 +6,16 @@ fn search_for_x_coordinate() { let (w1, w2, w3, w4) = (1.0, -2.0, 3.0, 4.0); // Search for the t value for a particular X coord - let x_coord = 1.5; - let matching_values = bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); + let x_coord = 1.5; + let matching_values = + bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); // Should be only 1 coordinate with this curve assert!(matching_values.len() == 1); // Basis function should be within 0.01 let actual_val = bezier::basis(matching_values[0], w1, w2, w3, w4); - assert!((actual_val-x_coord).abs() < 0.01); + assert!((actual_val - x_coord).abs() < 0.01); } #[test] @@ -23,9 +24,10 @@ fn coordinate_outside_curve_produces_no_results() { let (w1, w2, w3, w4) = (1.0, -2.0, 3.0, 4.0); // Search for the t value for a particular X coord, which is outside the curve - let x_coord = 5.0; - let matching_values = bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); + let x_coord = 5.0; + let matching_values = + bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); // No points on the curve match this coordinate - assert!(matching_values.len() == 0); + assert!(matching_values.is_empty()); } diff --git a/tests/bezier/section.rs b/tests/bezier/section.rs index 4ed94000..1e22a823 100644 --- a/tests/bezier/section.rs +++ b/tests/bezier/section.rs @@ -1,14 +1,17 @@ -use flo_curves::*; -use flo_curves::bezier::*; +use flo_curves::bezier::{BezierCurve, BezierCurveFactory, Coord2, Coordinate, Curve}; #[test] fn section_points_match() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); - let mid_section = original_curve.section(0.25, 0.75); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); + let mid_section = original_curve.section(0.25, 0.75); for t in 0..=10 { - let t = (t as f64)/10.0; - let t2 = t*0.5 + 0.25; + let t = (t as f64) / 10.0; + let t2 = t * 0.5 + 0.25; let p1 = mid_section.point_at_pos(t); let p2 = original_curve.point_at_pos(t2); @@ -19,12 +22,16 @@ fn section_points_match() { #[test] fn generate_curve_from_section() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); - let mid_section = Curve::from_curve(&original_curve.section(0.2, 0.6)); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); + let mid_section = Curve::from_curve(&original_curve.section(0.2, 0.6)); for t in 0..=10 { - let t = (t as f64)/10.0; - let t2 = t*0.4 + 0.2; + let t = (t as f64) / 10.0; + let t2 = t * 0.4 + 0.2; let p1 = mid_section.point_at_pos(t); let p2 = original_curve.point_at_pos(t2); @@ -35,13 +42,17 @@ fn generate_curve_from_section() { #[test] fn section_of_section() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); let mut mid_section = original_curve.section(0.25, 0.75); - mid_section = mid_section.subsection(0.25, 0.75); + mid_section = mid_section.subsection(0.25, 0.75); for t in 0..=10 { - let t = (t as f64)/10.0; - let t2 = t*0.25 + 0.375; + let t = (t as f64) / 10.0; + let t2 = t * 0.25 + 0.375; let p1 = mid_section.point_at_pos(t); let p2 = original_curve.point_at_pos(t2); @@ -52,27 +63,39 @@ fn section_of_section() { #[test] fn recover_original_t_values() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); - let mid_section = original_curve.section(0.2, 0.6); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); + let mid_section = original_curve.section(0.2, 0.6); assert!(mid_section.original_curve_t_values() == (0.2, 0.6)); } #[test] fn map_t_values_back_to_section() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); - let mid_section = original_curve.section(0.2, 0.6); - - assert!((mid_section.section_t_for_original_t(0.2)-0.0).abs() < 0.01); - assert!((mid_section.section_t_for_original_t(0.4)-0.5).abs() < 0.01); - assert!((mid_section.section_t_for_original_t(0.6)-1.0).abs() < 0.01); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); + let mid_section = original_curve.section(0.2, 0.6); + + assert!((mid_section.section_t_for_original_t(0.2) - 0.0).abs() < 0.01); + assert!((mid_section.section_t_for_original_t(0.4) - 0.5).abs() < 0.01); + assert!((mid_section.section_t_for_original_t(0.6) - 1.0).abs() < 0.01); } #[test] fn recover_original_t_values_from_subsection() { - let original_curve = Curve::from_points(Coord2(2.0, 3.0), (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), Coord2(6.0, 2.0)); - let mid_section = original_curve.section(0.25, 0.75); - let sub_section = mid_section.subsection(0.25, 0.75); + let original_curve = Curve::from_points( + Coord2(2.0, 3.0), + (Coord2(4.0, 5.0), Coord2(5.0, 0.0)), + Coord2(6.0, 2.0), + ); + let mid_section = original_curve.section(0.25, 0.75); + let sub_section = mid_section.subsection(0.25, 0.75); assert!(sub_section.original_curve_t_values() == (0.375, 0.625)); } diff --git a/tests/bezier/self_intersection.rs b/tests/bezier/self_intersection.rs index 5d580d44..98cb089d 100644 --- a/tests/bezier/self_intersection.rs +++ b/tests/bezier/self_intersection.rs @@ -1,28 +1,43 @@ -use flo_curves::*; -use flo_curves::bezier::*; +use flo_curves::bezier::{ + find_self_intersection_point, BezierCurve, BezierCurveFactory, Coord2, Coordinate, Curve, +}; #[test] fn find_simple_self_intersection() { - let curve_with_loop = Curve::from_points(Coord2(148.0, 151.0), (Coord2(292.0, 199.0), Coord2(73.0, 221.0)), Coord2(249.0, 136.0)); - let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); + let curve_with_loop = Curve::from_points( + Coord2(148.0, 151.0), + (Coord2(292.0, 199.0), Coord2(73.0, 221.0)), + Coord2(249.0, 136.0), + ); + let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); assert!(intersection_point.is_some()); let (t1, t2) = intersection_point.unwrap(); - let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); + let (p1, p2) = ( + curve_with_loop.point_at_pos(t1), + curve_with_loop.point_at_pos(t2), + ); assert!(p1.is_near_to(&p2, 0.01)); } #[test] fn whole_curve_is_a_loop() { - let curve_with_loop = Curve::from_points(Coord2(205.0, 159.0), (Coord2(81.0, 219.0), Coord2(287.0, 227.0)), Coord2(205.0, 159.0)); - let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); + let curve_with_loop = Curve::from_points( + Coord2(205.0, 159.0), + (Coord2(81.0, 219.0), Coord2(287.0, 227.0)), + Coord2(205.0, 159.0), + ); + let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); assert!(intersection_point.is_some()); let (t1, t2) = intersection_point.unwrap(); - let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); + let (p1, p2) = ( + curve_with_loop.point_at_pos(t1), + curve_with_loop.point_at_pos(t2), + ); assert!(p1.is_near_to(&p2, 0.01)); assert!(t1 <= 0.0); @@ -31,13 +46,23 @@ fn whole_curve_is_a_loop() { #[test] fn narrow_loop() { - let curve_with_loop = Curve::from_points(Coord2(549.2899780273438, 889.4202270507813), (Coord2(553.4288330078125, 893.8638305664063), Coord2(542.5203247070313, 889.04931640625)), Coord2(548.051025390625, 891.1853637695313)); - let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); + let curve_with_loop = Curve::from_points( + Coord2(549.2899780273438, 889.4202270507813), + ( + Coord2(553.4288330078125, 893.8638305664063), + Coord2(542.5203247070313, 889.04931640625), + ), + Coord2(548.051025390625, 891.1853637695313), + ); + let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); assert!(intersection_point.is_some()); let (t1, t2) = intersection_point.unwrap(); - let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); + let (p1, p2) = ( + curve_with_loop.point_at_pos(t1), + curve_with_loop.point_at_pos(t2), + ); assert!(p1.is_near_to(&p2, 0.01)); } diff --git a/tests/bezier/solve.rs b/tests/bezier/solve.rs index 7cc507c8..88d1bc0f 100644 --- a/tests/bezier/solve.rs +++ b/tests/bezier/solve.rs @@ -1,9 +1,11 @@ -use flo_curves::bezier::*; +use flo_curves::bezier::{ + basis, solve_basis_for_t, BezierCurve, BezierCurveFactory, Coord2, Curve, +}; #[test] fn basis_solve_middle() { - assert!((solve_basis_for_t(0.0, 0.33, 0.66, 1.0, 0.5)[0]-0.5).abs() < 0.01); - assert!((solve_basis_for_t(0.0, 1.0, 2.0, 3.0, 1.5)[0]-0.5).abs() < 0.01); + assert!((solve_basis_for_t(0.0, 0.33, 0.66, 1.0, 0.5)[0] - 0.5).abs() < 0.01); + assert!((solve_basis_for_t(0.0, 1.0, 2.0, 3.0, 1.5)[0] - 0.5).abs() < 0.01); } #[test] @@ -11,18 +13,21 @@ fn basis_solve_many() { fn test_for(w1: f64, w2: f64, w3: f64, w4: f64) { for p in 0..=16 { // Pick a point between w1 and w4 - let p = ((p as f64)/16.0)*(w4-w1) + w1; + let p = ((p as f64) / 16.0) * (w4 - w1) + w1; // Solve for t values let t_values = solve_basis_for_t(w1, w2, w3, w4, p); // Computing the points for these values should result in a valid curve - let pos_for_t = t_values.iter() + let pos_for_t = t_values + .iter() .map(|t| basis(*t, w1, w2, w3, w4)) .collect::>(); // Should all evaluate to positions on the curve - pos_for_t.iter().for_each(|pos| assert!((pos-p).abs() < 0.01)); + pos_for_t + .iter() + .for_each(|pos| assert!((pos - p).abs() < 0.01)); } } @@ -33,53 +38,73 @@ fn basis_solve_many() { #[test] fn solve_t_for_pos() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); - let point_at_one_third = curve1.point_at_pos(0.3333); - let solved = curve1.t_for_point(&point_at_one_third); + let point_at_one_third = curve1.point_at_pos(0.3333); + let solved = curve1.t_for_point(&point_at_one_third); assert!(solved.is_some()); - assert!((solved.unwrap()-0.3333).abs() < 0.001); + assert!((solved.unwrap() - 0.3333).abs() < 0.001); } #[test] fn solve_t_for_start() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); - let solved = curve1.t_for_point(&Coord2(10.0, 100.0)); + let solved = curve1.t_for_point(&Coord2(10.0, 100.0)); assert!(solved.is_some()); - assert!((solved.unwrap()-0.0).abs() < 0.001); + assert!((solved.unwrap() - 0.0).abs() < 0.001); } #[test] fn solve_t_for_end() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); - let solved = curve1.t_for_point(&Coord2(220.0, 220.0)); + let solved = curve1.t_for_point(&Coord2(220.0, 220.0)); assert!(solved.is_some()); - assert!((solved.unwrap()-1.0).abs() < 0.001); + assert!((solved.unwrap() - 1.0).abs() < 0.001); } #[test] fn solve_t_for_many_positions() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); for p in 0..10 { - let p = (p as f64)/10.0; - let point = curve1.point_at_pos(p); - let solved = curve1.t_for_point(&point); + let p = (p as f64) / 10.0; + let point = curve1.point_at_pos(p); + let solved = curve1.t_for_point(&point); assert!(solved.is_some()); - assert!((solved.unwrap()-p).abs() < 0.001); + assert!((solved.unwrap() - p).abs() < 0.001); } } #[test] fn solve_t_for_out_of_bounds() { - let curve1 = Curve::from_points(Coord2(10.0, 100.0), (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), Coord2(220.0, 220.0)); + let curve1 = Curve::from_points( + Coord2(10.0, 100.0), + (Coord2(90.0, 30.0), Coord2(40.0, 140.0)), + Coord2(220.0, 220.0), + ); - let solved = curve1.t_for_point(&Coord2(45.0, 23.0)); + let solved = curve1.t_for_point(&Coord2(45.0, 23.0)); assert!(solved.is_none()); } diff --git a/tests/bezier/subdivide.rs b/tests/bezier/subdivide.rs index 50940f5a..705a9b6e 100644 --- a/tests/bezier/subdivide.rs +++ b/tests/bezier/subdivide.rs @@ -1,4 +1,4 @@ -use super::*; +use super::approx_equal; use flo_curves::bezier; @@ -12,9 +12,9 @@ fn subdivide_1() { // Check that the original curve corresponds to the basis function for wa for x in 0..100 { - let t = (x as f64)/100.0; + let t = (x as f64) / 100.0; - let original = bezier::basis(t*0.33, w1, w2, w3, w4); + let original = bezier::basis(t * 0.33, w1, w2, w3, w4); let subdivision = bezier::basis(t, wa1, wa2, wa3, wa4); assert!(approx_equal(original, subdivision)); @@ -31,9 +31,9 @@ fn subdivide_2() { // Check that the original curve corresponds to the basis function for wb for x in 0..100 { - let t = (x as f64)/100.0; + let t = (x as f64) / 100.0; - let original = bezier::basis(0.33+(t*(1.0-0.33)), w1, w2, w3, w4); + let original = bezier::basis(0.33 + (t * (1.0 - 0.33)), w1, w2, w3, w4); let subdivision = bezier::basis(t, wb1, wb2, wb3, wb4); assert!(approx_equal(original, subdivision)); diff --git a/tests/bezier/tangent.rs b/tests/bezier/tangent.rs index e810e64c..32c5d9cb 100644 --- a/tests/bezier/tangent.rs +++ b/tests/bezier/tangent.rs @@ -1,10 +1,14 @@ -use flo_curves::*; use flo_curves::bezier; +use flo_curves::{BezierCurveFactory, Coord2, Coordinate2D, Coordinate3D, Line}; #[test] fn calculate_tangent_for_straight_line() { - let straight_line = bezier::Curve::from_points(Coord2(0.0, 1.0), (Coord2(0.5, 1.5), Coord2(1.5, 2.5)), Coord2(2.0, 3.0)); - let tangent = bezier::Tangent::from(&straight_line); + let straight_line = bezier::Curve::from_points( + Coord2(0.0, 1.0), + (Coord2(0.5, 1.5), Coord2(1.5, 2.5)), + Coord2(2.0, 3.0), + ); + let tangent = bezier::Tangent::from(&straight_line); assert!(tangent.tangent(0.5) == Coord2(2.25, 2.25)); diff --git a/tests/bezier/walk.rs b/tests/bezier/walk.rs index 7ebee6cf..cb03d81b 100644 --- a/tests/bezier/walk.rs +++ b/tests/bezier/walk.rs @@ -1,16 +1,23 @@ -use flo_curves::bezier::*; +use flo_curves::bezier::{ + chord_length, curve_length, walk_curve_evenly, walk_curve_unevenly, BezierCurveFactory, Coord2, + Coordinate, Curve, +}; #[test] fn uneven_walk_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let sections = walk_curve_unevenly(&c, 10).collect::>(); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let sections = walk_curve_unevenly(&c, 10).collect::>(); assert!(sections.len() == 10); assert!(sections[0].original_curve_t_values() == (0.0, 0.1)); for section_num in 0..10 { - let expected_t_min = (section_num as f64)/10.0; - let expected_t_max = (section_num as f64)/10.0 + 0.1; + let expected_t_min = (section_num as f64) / 10.0; + let expected_t_max = (section_num as f64) / 10.0 + 0.1; let (actual_t_min, actual_t_max) = sections[section_num].original_curve_t_values(); @@ -21,178 +28,245 @@ fn uneven_walk_1() { #[test] fn even_walk_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-1.0).abs() <= 0.1 || (t_max >= 1.0 && chord_length(section)-1.0 <= 0.0)); + assert!( + (chord_length(section) - 1.0).abs() <= 0.1 + || (t_max >= 1.0 && chord_length(section) - 1.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } #[test] fn even_walk_2() { - let c = Curve::from_points(Coord2(170.83203, 534.28906), (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), Coord2(262.95313, 533.2656)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(170.83203, 534.28906), + (Coord2(140.99219, 492.1289), Coord2(0.52734375, 478.67188)), + Coord2(262.95313, 533.2656), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-1.0).abs() <= 0.1 || (t_max >= 1.0 && chord_length(section)-1.0 <= 0.0)); + assert!( + (chord_length(section) - 1.0).abs() <= 0.1 + || (t_max >= 1.0 && chord_length(section) - 1.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } #[test] fn even_walk_3() { - let c = Curve::from_points(Coord2(987.7637, 993.9645), (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), Coord2(1064.9473, 994.277)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(987.7637, 993.9645), + (Coord2(991.1699, 994.0231), Coord2(1043.5605, 853.44885)), + Coord2(1064.9473, 994.277), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-1.0).abs() <= 0.1 || (t_max >= 1.0 && chord_length(section)-1.0 <= 0.0)); + assert!( + (chord_length(section) - 1.0).abs() <= 0.1 + || (t_max >= 1.0 && chord_length(section) - 1.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } #[test] fn even_walk_4() { - let c = Curve::from_points(Coord2(222.37538991853827, 99.16540392815092), (Coord2(224.47523575883392, 100.31557953334229), Coord2(223.19303980237945, 101.8075327562316)), Coord2(225.42363518033414, 99.716688142193)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(222.37538991853827, 99.16540392815092), + ( + Coord2(224.47523575883392, 100.31557953334229), + Coord2(223.19303980237945, 101.8075327562316), + ), + Coord2(225.42363518033414, 99.716688142193), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-1.0).abs() <= 0.1 || (t_max >= 1.0 && chord_length(section)-1.0 <= 0.0)); + assert!( + (chord_length(section) - 1.0).abs() <= 0.1 + || (t_max >= 1.0 && chord_length(section) - 1.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } #[test] fn even_walk_5() { - let c = Curve::from_points(Coord2(128.51366414207797, 100.43540868606826), (Coord2(128.8517120419268, 100.53996562501626), Coord2(131.79687993559304, 99.36123524249854)), Coord2(131.8239019605053, 99.36980615298116)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(128.51366414207797, 100.43540868606826), + ( + Coord2(128.8517120419268, 100.53996562501626), + Coord2(131.79687993559304, 99.36123524249854), + ), + Coord2(131.8239019605053, 99.36980615298116), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { - println!("{:?} {:?}", chord_length(section)-1.0, section.original_curve_t_values()); + println!( + "{:?} {:?}", + chord_length(section) - 1.0, + section.original_curve_t_values() + ); let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-1.0).abs() <= 0.1 || (t_max >= 1.0 && chord_length(section)-1.0 <= 0.0)); + assert!( + (chord_length(section) - 1.0).abs() <= 0.1 + || (t_max >= 1.0 && chord_length(section) - 1.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } #[test] fn even_walk_6() { - let c = Curve::from_points(Coord2(771.375, 195.0959930419922), (Coord2(771.375, 195.0959930419922), Coord2(629.2169799804688, 161.80499267578125)), Coord2(622.0430297851563, 160.3459930419922)); - let sections = walk_curve_evenly(&c, 2.0, 0.5).collect::>(); - let actual_length = curve_length(&c, 0.1); - - let mut total_length = 0.0; - let mut last_t = 0.0; + let c = Curve::from_points( + Coord2(771.375, 195.0959930419922), + ( + Coord2(771.375, 195.0959930419922), + Coord2(629.2169799804688, 161.80499267578125), + ), + Coord2(622.0430297851563, 160.3459930419922), + ); + let sections = walk_curve_evenly(&c, 2.0, 0.5).collect::>(); + let actual_length = curve_length(&c, 0.1); + + let mut total_length = 0.0; + let mut last_t = 0.0; for section in sections.iter() { - println!("{:?} {:?}", chord_length(section)-2.0, section.original_curve_t_values()); + println!( + "{:?} {:?}", + chord_length(section) - 2.0, + section.original_curve_t_values() + ); let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; - assert!((chord_length(section)-2.0).abs() <= 0.5 || (t_max >= 1.0 && chord_length(section)-2.0 <= 0.0)); + assert!( + (chord_length(section) - 2.0).abs() <= 0.5 + || (t_max >= 1.0 && chord_length(section) - 2.0 <= 0.0) + ); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 16.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 16.0); } #[test] fn even_walk_point() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(412.0, 500.0)), Coord2(412.0, 500.0)); - let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(412.0, 500.0)), + Coord2(412.0, 500.0), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1).collect::>(); assert!(sections.len() == 1); - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); } #[test] fn varying_walk_1() { - let c = Curve::from_points(Coord2(412.0, 500.0), (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), Coord2(308.0, 665.0)); - let sections = walk_curve_evenly(&c, 1.0, 0.1) + let c = Curve::from_points( + Coord2(412.0, 500.0), + (Coord2(412.0, 500.0), Coord2(163.0, 504.0)), + Coord2(308.0, 665.0), + ); + let sections = walk_curve_evenly(&c, 1.0, 0.1) .vary_by(vec![1.0, 2.0, 3.0].into_iter().cycle()) .collect::>(); - let actual_length = curve_length(&c, 0.1); + let actual_length = curve_length(&c, 0.1); - let mut total_length = 0.0; - let mut last_t = 0.0; + let mut total_length = 0.0; + let mut last_t = 0.0; let mut expected_length = vec![1.0, 2.0, 3.0].into_iter().cycle(); - for section in sections.iter().take(sections.len()-1) { + for section in sections.iter().take(sections.len() - 1) { let (_, t_max) = section.original_curve_t_values(); assert!(t_max > last_t); last_t = t_max; let expected_length = expected_length.next().unwrap(); - assert!((chord_length(section)-expected_length).abs() <= 0.1); + assert!((chord_length(section) - expected_length).abs() <= 0.1); total_length += chord_length(section); } - assert!(sections[sections.len()-1].original_curve_t_values().1 == 1.0); + assert!(sections[sections.len() - 1].original_curve_t_values().1 == 1.0); - println!("{:?}", (total_length-actual_length).abs()); - assert!((total_length-actual_length).abs() < 4.0); + println!("{:?}", (total_length - actual_length).abs()); + assert!((total_length - actual_length).abs() < 4.0); } diff --git a/tests/bounds.rs b/tests/bounds.rs index b65472de..7d6a7960 100644 --- a/tests/bounds.rs +++ b/tests/bounds.rs @@ -1,6 +1,6 @@ extern crate flo_curves; -use flo_curves::*; +use flo_curves::{BoundingBox, Bounds, Coord2}; #[test] fn overlapping_rects() { @@ -55,7 +55,7 @@ fn from_points() { Coord2(30.0, 30.0), Coord2(60.0, 40.0), Coord2(45.0, 70.0), - Coord2(10.0, 35.0) + Coord2(10.0, 35.0), ]); assert!(r.min() == Coord2(10.0, 30.0)); diff --git a/tests/coordinates.rs b/tests/coordinates.rs index b9ca70fb..de4b37bd 100644 --- a/tests/coordinates.rs +++ b/tests/coordinates.rs @@ -1,6 +1,6 @@ extern crate flo_curves; -use flo_curves::*; +use flo_curves::{Coord2, Coordinate, Coordinate2DExt}; use std::f64; @@ -14,7 +14,14 @@ fn can_find_unit_vector() { assert!(Coord2(0.0, 1.0).to_unit_vector() == Coord2(0.0, 1.0)); assert!(Coord2(0.0, 2.0).to_unit_vector() == Coord2(0.0, 1.0)); - assert!(f64::abs(Coord2(4.0, 2.0).to_unit_vector().distance_to(&Coord2(0.0, 0.0))-1.0) < 0.01); + assert!( + f64::abs( + Coord2(4.0, 2.0) + .to_unit_vector() + .distance_to(&Coord2(0.0, 0.0)) + - 1.0 + ) < 0.01 + ); } #[test] @@ -24,7 +31,7 @@ fn unit_vector_of_0_0_is_0_0() { #[test] fn can_get_dot_product() { - assert!(Coord2(2.0,1.0).dot(&Coord2(3.0, 4.0)) == 10.0); + assert!(Coord2(2.0, 1.0).dot(&Coord2(3.0, 4.0)) == 10.0); } #[test] @@ -49,5 +56,7 @@ fn unit_vector_0_degrees() { #[test] fn unit_vector_90_degrees() { - assert!(Coord2::unit_vector_at_angle(f64::consts::PI / 2.0).distance_to(&Coord2(0.0, 1.0)) < 0.001); + assert!( + Coord2::unit_vector_at_angle(f64::consts::PI / 2.0).distance_to(&Coord2(0.0, 1.0)) < 0.001 + ); } diff --git a/tests/line/coefficients.rs b/tests/line/coefficients.rs index 0fac8756..2e882e0a 100644 --- a/tests/line/coefficients.rs +++ b/tests/line/coefficients.rs @@ -1,67 +1,69 @@ -use flo_curves::line::*; +use flo_curves::line::{ + line_coefficients_2d, Coord2, Coordinate, Coordinate2D, Coordinate3D, Line, Line2D, +}; #[test] fn points_on_line_are_on_line_1() { - let line = (Coord2(2.0, 3.0), Coord2(7.0, 6.0)); - let (a, b, c) = line_coefficients_2d(&line); + let line = (Coord2(2.0, 3.0), Coord2(7.0, 6.0)); + let (a, b, c) = line_coefficients_2d(&line); for t in 0..=16 { - let t = (t as f64) / 16.0; - let point = line.point_at_pos(t); + let t = (t as f64) / 16.0; + let point = line.point_at_pos(t); - assert!((a*point.x() + b*point.y() + c).abs() < 0.001); + assert!((a * point.x() + b * point.y() + c).abs() < 0.001); } } #[test] fn points_on_line_are_on_line_2() { - let line = (Coord2(7.0, 6.0), Coord2(2.0, 3.0)); - let (a, b, c) = line_coefficients_2d(&line); + let line = (Coord2(7.0, 6.0), Coord2(2.0, 3.0)); + let (a, b, c) = line_coefficients_2d(&line); for t in 0..=16 { - let t = (t as f64) / 16.0; - let point = line.point_at_pos(t); + let t = (t as f64) / 16.0; + let point = line.point_at_pos(t); - assert!((a*point.x() + b*point.y() + c).abs() < 0.001); + assert!((a * point.x() + b * point.y() + c).abs() < 0.001); } } #[test] fn points_on_line_are_on_line_3() { - let line = (Coord2(2.0, 3.0), Coord2(7.0, 3.0)); - let (a, b, c) = line_coefficients_2d(&line); + let line = (Coord2(2.0, 3.0), Coord2(7.0, 3.0)); + let (a, b, c) = line_coefficients_2d(&line); for t in 0..=16 { - let t = (t as f64) / 16.0; - let point = line.point_at_pos(t); + let t = (t as f64) / 16.0; + let point = line.point_at_pos(t); - assert!((a*point.x() + b*point.y() + c).abs() < 0.001); + assert!((a * point.x() + b * point.y() + c).abs() < 0.001); } } #[test] fn points_on_line_are_on_line_4() { - let line = (Coord2(2.0, 3.0), Coord2(2.0, 6.0)); - let (a, b, c) = line_coefficients_2d(&line); + let line = (Coord2(2.0, 3.0), Coord2(2.0, 6.0)); + let (a, b, c) = line_coefficients_2d(&line); for t in 0..=16 { - let t = (t as f64) / 16.0; - let point = line.point_at_pos(t); + let t = (t as f64) / 16.0; + let point = line.point_at_pos(t); - assert!((a*point.x() + b*point.y() + c).abs() < 0.001); + assert!((a * point.x() + b * point.y() + c).abs() < 0.001); } } #[test] fn points_on_line_are_on_line_5() { - let line = (Coord2(2.0, 3.0), Coord2(2.0, 6.0)); - let (a, b, c) = line.coefficients(); + let line = (Coord2(2.0, 3.0), Coord2(2.0, 6.0)); + let (a, b, c) = line.coefficients(); for t in 0..=16 { - let t = (t as f64) / 16.0; - let point = line.point_at_pos(t); + let t = (t as f64) / 16.0; + let point = line.point_at_pos(t); - assert!((a*point.x() + b*point.y() + c).abs() < 0.001); + assert!((a * point.x() + b * point.y() + c).abs() < 0.001); } } diff --git a/tests/line/intersection.rs b/tests/line/intersection.rs index 6feb2a89..eda0d2b9 100644 --- a/tests/line/intersection.rs +++ b/tests/line/intersection.rs @@ -1,35 +1,74 @@ -use flo_curves::*; -use flo_curves::line::*; +use flo_curves::line::{ + line_clip_to_bounds, line_intersects_line, line_intersects_ray, ray_intersects_ray, Coord2, + Coordinate, Line2D, +}; #[test] fn intersection_at_0_0() { - assert!(line_intersects_line(&(Coord2(-1.0, 0.0), Coord2(1.0, 0.0)), &(Coord2(0.0, 1.0), Coord2(0.0, -1.0))).unwrap().distance_to(&Coord2(0.0, 0.0)) < 0.01); + assert!( + line_intersects_line( + &(Coord2(-1.0, 0.0), Coord2(1.0, 0.0)), + &(Coord2(0.0, 1.0), Coord2(0.0, -1.0)) + ) + .unwrap() + .distance_to(&Coord2(0.0, 0.0)) + < 0.01 + ); } #[test] fn intersection_at_other_point() { - assert!(line_intersects_line(&(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), &(Coord2(10.0, 45.0), Coord2(50.0, 35.0))).unwrap().distance_to(&Coord2(30.0, 40.0)) < 0.01); + assert!( + line_intersects_line( + &(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), + &(Coord2(10.0, 45.0), Coord2(50.0, 35.0)) + ) + .unwrap() + .distance_to(&Coord2(30.0, 40.0)) + < 0.01 + ); } #[test] fn ray_intersects_line() { - assert!(line_intersects_ray(&(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), &(Coord2(10.0, 45.0), Coord2(14.0, 44.0))).unwrap().distance_to(&Coord2(30.0, 40.0)) < 0.01); + assert!( + line_intersects_ray( + &(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), + &(Coord2(10.0, 45.0), Coord2(14.0, 44.0)) + ) + .unwrap() + .distance_to(&Coord2(30.0, 40.0)) + < 0.01 + ); } #[test] fn two_rays_intersect() { - assert!(ray_intersects_ray(&(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), &(Coord2(10.0, 45.0), Coord2(14.0, 44.0))).unwrap().distance_to(&Coord2(30.0, 40.0)) < 0.01); + assert!( + ray_intersects_ray( + &(Coord2(10.0, 20.0), Coord2(50.0, 60.0)), + &(Coord2(10.0, 45.0), Coord2(14.0, 44.0)) + ) + .unwrap() + .distance_to(&Coord2(30.0, 40.0)) + < 0.01 + ); } #[test] fn no_intersection() { - assert!(line_intersects_line(&(Coord2(12.0, 13.0), Coord2(24.0, 30.0)), &(Coord2(1.0, 1.0), Coord2(0.0, -1.0))) == None); + assert!( + line_intersects_line( + &(Coord2(12.0, 13.0), Coord2(24.0, 30.0)), + &(Coord2(1.0, 1.0), Coord2(0.0, -1.0)) + ) == None + ); } #[test] fn line_in_bounds() { - let line = (Coord2(5.0, 3.0), Coord2(7.0, 9.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(5.0, 3.0), Coord2(7.0, 9.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_some()); @@ -41,8 +80,8 @@ fn line_in_bounds() { #[test] fn horizontal_clipped_line() { - let line = (Coord2(-10.0, 4.0), Coord2(20.0, 4.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(-10.0, 4.0), Coord2(20.0, 4.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_some()); @@ -54,8 +93,8 @@ fn horizontal_clipped_line() { #[test] fn horizontal_clipped_line_inside_to_outside() { - let line = (Coord2(5.0, 4.0), Coord2(20.0, 4.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(5.0, 4.0), Coord2(20.0, 4.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_some()); @@ -67,8 +106,8 @@ fn horizontal_clipped_line_inside_to_outside() { #[test] fn horizontal_clipped_line_inside_to_outside_reverse() { - let line = (Coord2(20.0, 4.0), Coord2(5.0, 4.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(20.0, 4.0), Coord2(5.0, 4.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_some()); @@ -80,8 +119,8 @@ fn horizontal_clipped_line_inside_to_outside_reverse() { #[test] fn vertical_clipped_line_inside_to_outside() { - let line = (Coord2(5.0, 4.0), Coord2(5.0, 20.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(5.0, 4.0), Coord2(5.0, 20.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_some()); @@ -93,8 +132,8 @@ fn vertical_clipped_line_inside_to_outside() { #[test] fn line_out_of_bounds_right() { - let line = (Coord2(11.0, 9.5), Coord2(20.0, 9.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(11.0, 9.5), Coord2(20.0, 9.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_none()); @@ -102,8 +141,8 @@ fn line_out_of_bounds_right() { #[test] fn line_out_of_bounds_left() { - let line = (Coord2(-11.0, 9.5), Coord2(-20.0, 9.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(-11.0, 9.5), Coord2(-20.0, 9.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_none()); @@ -111,10 +150,9 @@ fn line_out_of_bounds_left() { #[test] fn line_out_of_bounds_crossing() { - let line = (Coord2(9.0, 0.0), Coord2(20.0, 9.0)); - let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); + let line = (Coord2(9.0, 0.0), Coord2(20.0, 9.0)); + let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); let clipped = line_clip_to_bounds(&line, &bounds); assert!(clipped.is_none()); } - diff --git a/tests/line/mod.rs b/tests/line/mod.rs index 318cef3a..f32f0e15 100644 --- a/tests/line/mod.rs +++ b/tests/line/mod.rs @@ -1,3 +1,3 @@ -mod to_curve; -mod intersection; mod coefficients; +mod intersection; +mod to_curve; diff --git a/tests/line/to_curve.rs b/tests/line/to_curve.rs index 8f30aed2..afa3e552 100644 --- a/tests/line/to_curve.rs +++ b/tests/line/to_curve.rs @@ -1,11 +1,10 @@ -use flo_curves::*; -use flo_curves::line::*; -use flo_curves::bezier::*; +use flo_curves::bezier::{BezierCurve, Coord2, Coordinate, Curve}; +use flo_curves::line::{line_to_bezier, Line2D}; #[test] fn convert_line_to_bezier_curve() { - let line = (Coord2(10.0, 20.0), Coord2(40.0, 30.0)); - let curve = line_to_bezier::<_, Curve<_>>(&line); + let line = (Coord2(10.0, 20.0), Coord2(40.0, 30.0)); + let curve = line_to_bezier::<_, Curve<_>>(&line); assert!(curve.start_point == Coord2(10.0, 20.0)); assert!(curve.end_point == Coord2(40.0, 30.0)); diff --git a/tests/readme.rs b/tests/readme.rs index e5eb19ce..9369043f 100644 --- a/tests/readme.rs +++ b/tests/readme.rs @@ -4,8 +4,8 @@ use std::str; /// Reads the README file for the crate /// fn readme() -> &'static str { - let readme_bytes = include_bytes!("../README.md"); - let readme_str = str::from_utf8(readme_bytes); + let readme_bytes = include_bytes!("../README.md"); + let readme_str = str::from_utf8(readme_bytes); readme_str.expect("Could not decode README.md") } @@ -15,9 +15,12 @@ fn starts_with_version_number_toml() { let major_version = env!("CARGO_PKG_VERSION_MAJOR"); let minor_version = env!("CARGO_PKG_VERSION_MINOR"); - let expected = format!("```toml + let expected = format!( + "```toml flo_curves = \"{}.{}\" -```", major_version, minor_version); +```", + major_version, minor_version + ); println!("{}", expected); assert!(readme().starts_with(&expected)); diff --git a/tests/sweep.rs b/tests/sweep.rs index 3f147d31..9a071c57 100644 --- a/tests/sweep.rs +++ b/tests/sweep.rs @@ -1,7 +1,9 @@ -use flo_curves::geo::*; +use flo_curves::geo::{ + sweep_against, sweep_self, BoundingBox, Bounds, Coord2, Coordinate, Coordinate2D, Coordinate3D, +}; use rand::prelude::*; -use std::cmp::{Ordering}; +use std::cmp::Ordering; #[test] fn sweep_self_single_overlap() { @@ -9,11 +11,16 @@ fn sweep_self_single_overlap() { Bounds::from_min_max(Coord2(100.0, 200.0), Coord2(200.0, 300.0)), Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), ]; - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); - let collisions = sweep_self(bounds.iter()).collect::>(); + let collisions = sweep_self(bounds.iter()); - assert!(collisions.len() == 1); + assert!(collisions.count() == 1); } #[test] @@ -23,11 +30,16 @@ fn sweep_self_double_overlap() { Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), Bounds::from_min_max(Coord2(220.0, 330.0), Coord2(350.0, 450.0)), ]; - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); - let collisions = sweep_self(bounds.iter()).collect::>(); + let collisions = sweep_self(bounds.iter()); - assert!(collisions.len() == 2); + assert!(collisions.count() == 2); } #[test] @@ -37,11 +49,16 @@ fn sweep_self_triple_overlap() { Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), Bounds::from_min_max(Coord2(190.0, 290.0), Coord2(290.0, 390.0)), ]; - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); - let collisions = sweep_self(bounds.iter()).collect::>(); + let collisions = sweep_self(bounds.iter()); - assert!(collisions.len() == 3); + assert!(collisions.count() == 3); } #[test] @@ -52,27 +69,44 @@ fn sweep_self_quad_overlap() { Bounds::from_min_max(Coord2(190.0, 290.0), Coord2(290.0, 390.0)), Bounds::from_min_max(Coord2(0.0, 0.0), Coord2(1000.0, 1000.0)), ]; - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); - let collisions = sweep_self(bounds.iter()).collect::>(); + let collisions = sweep_self(bounds.iter()); - assert!(collisions.len() == 6); + assert!(collisions.count() == 6); } #[test] fn sweep_against_single_overlap() { - let mut bounds1 = vec![ - Bounds::from_min_max(Coord2(100.0, 200.0), Coord2(200.0, 300.0)) - ]; - let mut bounds2 = vec![ - Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), - ]; - bounds1.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - bounds2.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - - let collisions = sweep_against(bounds1.iter(), bounds2.iter()).collect::>(); - - assert!(collisions.len() == 1); + let mut bounds1 = vec![Bounds::from_min_max( + Coord2(100.0, 200.0), + Coord2(200.0, 300.0), + )]; + let mut bounds2 = vec![Bounds::from_min_max( + Coord2(150.0, 250.0), + Coord2(250.0, 350.0), + )]; + bounds1.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + bounds2.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + + let collisions = sweep_against(bounds1.iter(), bounds2.iter()); + + assert!(collisions.count() == 1); } #[test] @@ -81,32 +115,54 @@ fn sweep_against_double_overlap_1() { Bounds::from_min_max(Coord2(100.0, 200.0), Coord2(200.0, 300.0)), Bounds::from_min_max(Coord2(220.0, 330.0), Coord2(350.0, 450.0)), ]; - let mut bounds2 = vec![ - Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), - ]; - bounds1.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - bounds2.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - - let collisions = sweep_against(bounds1.iter(), bounds2.iter()).collect::>(); - - assert!(collisions.len() == 2); + let mut bounds2 = vec![Bounds::from_min_max( + Coord2(150.0, 250.0), + Coord2(250.0, 350.0), + )]; + bounds1.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + bounds2.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + + let collisions = sweep_against(bounds1.iter(), bounds2.iter()); + + assert!(collisions.count() == 2); } #[test] fn sweep_against_double_overlap_2() { - let mut bounds1 = vec![ - Bounds::from_min_max(Coord2(150.0, 250.0), Coord2(250.0, 350.0)), - ]; - let mut bounds2 = vec![ + let mut bounds1 = vec![Bounds::from_min_max( + Coord2(150.0, 250.0), + Coord2(250.0, 350.0), + )]; + let mut bounds2 = vec![ Bounds::from_min_max(Coord2(100.0, 200.0), Coord2(200.0, 300.0)), Bounds::from_min_max(Coord2(220.0, 330.0), Coord2(350.0, 450.0)), ]; - bounds1.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - bounds2.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - - let collisions = sweep_against(bounds1.iter(), bounds2.iter()).collect::>(); - - assert!(collisions.len() == 2); + bounds1.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + bounds2.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + + let collisions = sweep_against(bounds1.iter(), bounds2.iter()); + + assert!(collisions.count() == 2); } #[test] @@ -119,37 +175,58 @@ fn sweep_against_quad_overlap() { Bounds::from_min_max(Coord2(190.0, 290.0), Coord2(290.0, 390.0)), Bounds::from_min_max(Coord2(0.0, 0.0), Coord2(1000.0, 1000.0)), ]; - bounds1.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - bounds2.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); - - let collisions = sweep_against(bounds1.iter(), bounds2.iter()).collect::>(); - - assert!(collisions.len() == 4); + bounds1.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + bounds2.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); + + let collisions = sweep_against(bounds1.iter(), bounds2.iter()); + + assert!(collisions.count() == 4); } #[test] fn sweep_self_1000_random() { - let mut rng = StdRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); - let mut bounds = (0..1000).into_iter() + let mut rng = StdRng::from_seed([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, + ]); + let mut bounds = (0..1000) + .into_iter() .map(|_| { let x = rng.gen::() * 900.0; let y = rng.gen::() * 900.0; let w = rng.gen::() * 400.0; let h = rng.gen::() * 400.0; - Bounds::from_min_max(Coord2(x, y), Coord2(x+w, y+h)) + Bounds::from_min_max(Coord2(x, y), Coord2(x + w, y + h)) }) .collect::>(); - bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); + bounds.sort_by(|b1, b2| { + b1.min() + .x() + .partial_cmp(&b2.min().x()) + .unwrap_or(Ordering::Equal) + }); - let collisions = sweep_self(bounds.iter()).collect::>(); + let collisions = sweep_self(bounds.iter()).collect::>(); // Use the slow approach to detecting the collisions to test against let mut slow_collisions = vec![]; for i1 in 0..bounds.len() { for i2 in 0..i1 { - if i1 == i2 { continue; } + if i1 == i2 { + continue; + } if bounds[i1].overlaps(&bounds[i2]) { slow_collisions.push((&bounds[i1], &bounds[i2]));