├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── THANKS.md ├── benches ├── known_run.md ├── nearest_point.rs ├── rasterize.rs ├── space.rs ├── sweep.rs └── vectorize.rs ├── demos ├── Cargo.toml ├── arithmetic │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── brush │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── curve_fit │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── curve_intersections │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── distort │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── offset │ ├── Cargo.toml │ └── src │ │ └── main.rs └── walk │ ├── Cargo.toml │ └── src │ └── main.rs ├── logo-small.png ├── logo.png ├── src ├── arc │ ├── circle.rs │ └── mod.rs ├── bezier │ ├── basis.rs │ ├── bounds.rs │ ├── characteristics.rs │ ├── curve.rs │ ├── deform.rs │ ├── derivative.rs │ ├── distort.rs │ ├── fit.rs │ ├── intersection │ │ ├── curve_curve_clip.rs │ │ ├── curve_line.rs │ │ ├── fat_line.rs │ │ ├── mod.rs │ │ └── self_intersection.rs │ ├── length.rs │ ├── mod.rs │ ├── nearest_point.rs │ ├── normal.rs │ ├── offset.rs │ ├── offset_lms.rs │ ├── offset_scaling.rs │ ├── offset_subdivision_lms.rs │ ├── overlaps.rs │ ├── path │ │ ├── algorithms │ │ │ ├── fill_concave.rs │ │ │ ├── fill_convex.rs │ │ │ ├── fill_settings.rs │ │ │ └── mod.rs │ │ ├── arithmetic │ │ │ ├── add.rs │ │ │ ├── chain.rs │ │ │ ├── chain_add.rs │ │ │ ├── cut.rs │ │ │ ├── full_intersect.rs │ │ │ ├── intersect.rs │ │ │ ├── mod.rs │ │ │ ├── ray_cast.rs │ │ │ └── sub.rs │ │ ├── bounds.rs │ │ ├── graph_path │ │ │ ├── edge.rs │ │ │ ├── edge_ref.rs │ │ │ ├── mod.rs │ │ │ ├── path_collision.rs │ │ │ ├── ray_collision.rs │ │ │ └── test.rs │ │ ├── intersection.rs │ │ ├── is_clockwise.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ ├── path_builder.rs │ │ ├── point.rs │ │ ├── ray.rs │ │ ├── stroke.rs │ │ └── to_curves.rs │ ├── rasterize │ │ ├── create_distance_field.rs │ │ ├── marching_parabola_distance_field.rs │ │ ├── marching_parabolas.rs │ │ ├── mod.rs │ │ ├── path_contour.rs │ │ ├── path_distance_field.rs │ │ └── ray_cast_contour.rs │ ├── roots │ │ ├── find_roots.rs │ │ ├── mod.rs │ │ ├── nearest_point_bezier_root_finder.rs │ │ └── polynomial_to_bezier.rs │ ├── search.rs │ ├── section.rs │ ├── solve.rs │ ├── subdivide.rs │ ├── tangent.rs │ ├── vectorize │ │ ├── brush_stroke.rs │ │ ├── circular_brush.rs │ │ ├── circular_distance_field.rs │ │ ├── column_sampled_contour.rs │ │ ├── contour_edges_by_scanline.rs │ │ ├── daub_brush_distance_field.rs │ │ ├── distance_field.rs │ │ ├── intercept_scan_edge_iterator.rs │ │ ├── marching_squares.rs │ │ ├── mod.rs │ │ ├── sampled_contour.rs │ │ ├── scaled_brush.rs │ │ ├── scaled_contour.rs │ │ └── scaled_distance_field.rs │ └── walk.rs ├── consts.rs ├── debug │ ├── graph_path_debug.rs │ ├── mod.rs │ └── path_to_string.rs ├── geo │ ├── bounding_box.rs │ ├── coord1.rs │ ├── coord2.rs │ ├── coord3.rs │ ├── coordinate.rs │ ├── coordinate_ext.rs │ ├── geo.rs │ ├── has_bounds.rs │ ├── mod.rs │ ├── space1.rs │ └── sweep.rs ├── lib.rs ├── line │ ├── coefficients.rs │ ├── intersection.rs │ ├── line.rs │ ├── mod.rs │ └── to_curve.rs └── test_assert.rs └── tests ├── bez.rs ├── bezier ├── algorithms │ ├── fill_concave.rs │ ├── fill_convex.rs │ └── mod.rs ├── basis.rs ├── bounds.rs ├── characteristics.rs ├── curve_intersection_clip_tests.rs ├── deform.rs ├── derivative.rs ├── distort.rs ├── flatness_tests.rs ├── intersection.rs ├── length.rs ├── mod.rs ├── nearest_point_tests.rs ├── normal.rs ├── offset.rs ├── overlaps_tests.rs ├── path │ ├── arithmetic_add.rs │ ├── arithmetic_chain_add.rs │ ├── arithmetic_complicated_paths.rs │ ├── arithmetic_cut.rs │ ├── arithmetic_intersect.rs │ ├── arithmetic_sub.rs │ ├── bounds.rs │ ├── checks.rs │ ├── graph_path_tests.rs │ ├── intersection.rs │ ├── is_clockwise.rs │ ├── mod.rs │ ├── path.rs │ ├── permute.rs │ ├── point.rs │ ├── rays.rs │ ├── stroke_tests.rs │ ├── svg.rs │ └── to_curves.rs ├── rasterize │ ├── marching_parabolas_tests.rs │ ├── mod.rs │ ├── path_contour_tests.rs │ ├── path_distance_field_tests.rs │ └── ray_cast_contour_tests.rs ├── search.rs ├── section.rs ├── self_intersection.rs ├── solve.rs ├── subdivide.rs ├── tangent.rs ├── vectorize │ ├── brush_stroke_tests.rs │ ├── circular_distance_field_tests.rs │ ├── daub_brush_distance_field_tests.rs │ ├── marching_squares_tests.rs │ ├── mod.rs │ ├── sampled_contour_tests.rs │ └── scaled_distance_field_tests.rs └── walk.rs ├── bounds.rs ├── coordinates.rs ├── line ├── angle.rs ├── coefficients.rs ├── intersection.rs ├── mod.rs ├── nearest.rs └── to_curve.rs ├── readme.rs └── sweep.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Cargo.lock 3 | target 4 | etc/** 5 | papers/** 6 | scratchpad/** 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_curves" 3 | version = "0.8.1" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | repository = "https://github.com/Logicalshift/flo_curves" 7 | description = "Library for manipulating Bezier curves" 8 | documentation = "http://docs.rs/flo_curves/" 9 | readme = "README.md" 10 | edition = "2018" 11 | include = [ "Cargo.toml", "LICENSE", "src/**/*", "demos/src/**/*.rs", "demos/*.toml", "demos/*.md", "logo*.png", "README.md", "THANKS.md" ] 12 | 13 | keywords = ["bezier", "geometry", "graphics"] 14 | categories = ["algorithms","rendering"] 15 | 16 | [features] 17 | extra_checks = [] 18 | 19 | [dependencies] 20 | itertools = "0.14" 21 | roots = "0.0.8" 22 | smallvec = { version = "1.10", features = ["const_generics"] } 23 | ouroboros = "0.18" 24 | 25 | [dev-dependencies] 26 | rand = "0.9" 27 | criterion = "0.5" 28 | 29 | [[bench]] 30 | name = "rasterize" 31 | path = "benches/rasterize.rs" 32 | harness = false 33 | 34 | [[bench]] 35 | name = "vectorize" 36 | path = "benches/vectorize.rs" 37 | harness = false 38 | 39 | [[bench]] 40 | name = "sweep" 41 | path = "benches/sweep.rs" 42 | harness = false 43 | 44 | [[bench]] 45 | name = "nearest_point" 46 | path = "benches/nearest_point.rs" 47 | harness = false 48 | 49 | [[bench]] 50 | name = "space" 51 | path = "benches/space.rs" 52 | harness = false 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```toml 2 | flo_curves = "0.8" 3 | ``` 4 | 5 | flo_curves 6 | ========== 7 | 8 | `flo_curves` is a library of routines for inspecting and manipulating curves, with a focus on cubic Bézier curves. In 9 | this library, you'll find routines for computing points on curves, performing collision detection between curves and 10 | lines or other curves, all the way up to routines for combining paths made up of multiple curves. 11 | 12 | Anyone doing any work with Bézier curves will likely find something in this library that is of use, but its range of 13 | functions makes it particularly useful for collision detection or performing path arithmetic. 14 | 15 | A set of curve and coordinate types are provided by the library, as well as a set of traits that can be implemented 16 | on any types with suitable properties. Implementing these traits makes it possible to add the extra features of this 17 | library to any existing code that has its own way of representing coordinates, curves or paths. 18 | 19 | Examples 20 | ======== 21 | 22 | Creating a curve: 23 | 24 | ```Rust 25 | use flo_curves::*; 26 | use flo_curves::bezier; 27 | 28 | let curve = bezier::Curve::from_points(Coord2(1.0, 2.0), (Coord2(2.0, 0.0), Coord2(3.0, 5.0)), Coord2(4.0, 2.0)); 29 | ``` 30 | 31 | Finding a point on a curve: 32 | 33 | ```Rust 34 | use flo_curves::bezier; 35 | 36 | let pos = curve.point_at_pos(0.5); 37 | ``` 38 | 39 | Intersections: 40 | 41 | ```Rust 42 | use flo_curves::bezier; 43 | 44 | for (t1, t2) in bezier::curve_intersects_curve_clip(curve1, curve2, 0.01) { 45 | let pos = curve1.point_at_pos(t1); 46 | println!("Intersection, curve1 t: {}, curve2 t: {}, position: {}, {}", t1, t2, pos.x(), pos.y()); 47 | } 48 | ``` 49 | 50 | Creating a path: 51 | 52 | ```Rust 53 | use flo_curves::bezier; 54 | use flo_curves::bezier::path::*; 55 | 56 | let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 57 | .line_to(Coord2(5.0, 1.0)) 58 | .line_to(Coord2(5.0, 5.0)) 59 | .line_to(Coord2(1.0, 5.0)) 60 | .line_to(Coord2(1.0, 1.0)) 61 | .build(); 62 | ``` 63 | 64 | Path arithmetic: 65 | 66 | ```Rust 67 | use flo_curves::bezier::path::*; 68 | 69 | let rectangle_with_hole = path_sub::(&vec![rectangle], &vec![circle], 0.01); 70 | ``` 71 | 72 | --- 73 | 74 | ![flo_curves logo](./logo-small.png) 75 | -------------------------------------------------------------------------------- /THANKS.md: -------------------------------------------------------------------------------- 1 | Thankyou to everyone who has contributed to this library and to the authors of 2 | the crates that flo_curves depends on. 3 | 4 | Matthew Blanchard [https://github.com/MatthewBlanchard]: bug reports and suggestions 5 | Mikhail Vorotilov (https://github.com/vorot): the roots crate 6 | "bluss" (https://github.com/bluss): the itertools crate 7 | "shi-yan" (https://https://github.com/shi-yan): suggestions 8 | "andrewvarga" (https://github.com/andrewvarga): bug reports 9 | 10 | -------------------------------------------------------------------------------- /benches/nearest_point.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use flo_curves::*; 4 | use flo_curves::bezier::*; 5 | 6 | fn criterion_benchmark(c: &mut Criterion) { 7 | let 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)); 8 | let point = Coord2(100.0, 130.0); 9 | 10 | c.bench_function("default_algorithm", |b| b.iter(|| nearest_point_on_curve(&curve, &point))); 11 | c.bench_function("newton_raphson", |b| b.iter(|| nearest_point_on_curve_newton_raphson(&curve, &point))); 12 | } 13 | 14 | criterion_group!(benches, criterion_benchmark); 15 | criterion_main!(benches); 16 | -------------------------------------------------------------------------------- /benches/rasterize.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use flo_curves::arc::*; 4 | use flo_curves::bezier::*; 5 | use flo_curves::bezier::path::*; 6 | use flo_curves::bezier::rasterize::*; 7 | use flo_curves::bezier::vectorize::*; 8 | 9 | use smallvec::*; 10 | 11 | use std::ops::{Range}; 12 | 13 | fn scan_convert_path(path: &Vec) -> Vec; 4]>> { 14 | let scan_converter = PathContour::from_path(path.clone(), ContourSize(1000, 1000)); 15 | (0..1000).map(|y| scan_converter.rounded_intercepts_on_line(y as f64)).collect() 16 | } 17 | 18 | fn criterion_benchmark(c: &mut Criterion) { 19 | let radius = 300.0; 20 | let center = Coord2(500.0, 500.0); 21 | let circle_path = Circle::new(center, radius).to_path::(); 22 | 23 | c.bench_function("scan_convert_circle", |b| b.iter(|| scan_convert_path(&vec![circle_path.clone()]))); 24 | } 25 | 26 | criterion_group!(benches, criterion_benchmark); 27 | criterion_main!(benches); 28 | -------------------------------------------------------------------------------- /benches/sweep.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use flo_curves::geo::*; 4 | use flo_curves::bezier::path::*; 5 | 6 | use rand::prelude::*; 7 | use std::cmp::{Ordering}; 8 | 9 | fn sweep(n: usize) { 10 | 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]); 11 | let mut bounds = (0..n).into_iter() 12 | .map(|_| { 13 | let x = rng.gen::() * 900.0; 14 | let y = rng.gen::() * 900.0; 15 | let w = rng.gen::() * 400.0; 16 | let h = rng.gen::() * 400.0; 17 | 18 | Bounds::from_min_max(Coord2(x, y), Coord2(x+w, y+h)) 19 | }) 20 | .collect::>(); 21 | bounds.sort_by(|b1, b2| b1.min().x().partial_cmp(&b2.min().x()).unwrap_or(Ordering::Equal)); 22 | 23 | let _ = sweep_self(bounds.iter()).collect::>(); 24 | } 25 | 26 | fn sweep_slow(n: usize) { 27 | 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]); 28 | let bounds = (0..n).into_iter() 29 | .map(|_| { 30 | let x = rng.gen::() * 900.0; 31 | let y = rng.gen::() * 900.0; 32 | let w = rng.gen::() * 400.0; 33 | let h = rng.gen::() * 400.0; 34 | 35 | Bounds::from_min_max(Coord2(x, y), Coord2(x+w, y+h)) 36 | }) 37 | .collect::>(); 38 | 39 | let mut slow_collisions = vec![]; 40 | 41 | for i1 in 0..bounds.len() { 42 | for i2 in 0..i1 { 43 | if i1 == i2 { continue; } 44 | 45 | if bounds[i1].overlaps(&bounds[i2]) { 46 | slow_collisions.push((&bounds[i1], &bounds[i2])); 47 | } 48 | } 49 | } 50 | } 51 | 52 | fn create_graph_path(rng: &mut StdRng, n: usize) -> GraphPath { 53 | let mut x = 100.0; 54 | let mut y = 100.0; 55 | let mut path_builder = BezierPathBuilder::::start(Coord2(x, y)); 56 | 57 | for _ in 0..n { 58 | let xo = rng.gen::() * 50.0; 59 | let yo = rng.gen::() * 50.0; 60 | 61 | x += xo; 62 | y += yo; 63 | 64 | path_builder = path_builder.line_to(Coord2(x, y)); 65 | } 66 | 67 | let path = path_builder.build(); 68 | let graph_path = GraphPath::from_path(&path, ()); 69 | 70 | graph_path 71 | } 72 | 73 | fn detect_collisions(mut graph_path: GraphPath) { 74 | graph_path.self_collide(0.1); 75 | } 76 | 77 | fn merge_paths(path1: GraphPath, path2: GraphPath) { 78 | path1.collide(path2, 0.1); 79 | } 80 | 81 | fn criterion_benchmark(c: &mut Criterion) { 82 | 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]); 83 | let graph_path = create_graph_path(&mut rng, 1000); 84 | let merge_path = create_graph_path(&mut rng, 500); 85 | 86 | c.bench_function("detect_collisions 1000", |b| b.iter(|| detect_collisions(black_box(graph_path.clone())))); 87 | c.bench_function("merge_paths 1000", |b| b.iter(|| merge_paths(black_box(graph_path.clone()), black_box(merge_path.clone())))); 88 | 89 | c.bench_function("sweep 10", |b| b.iter(|| sweep(black_box(10)))); 90 | c.bench_function("sweep_slow 10", |b| b.iter(|| sweep_slow(black_box(10)))); 91 | 92 | c.bench_function("sweep 100", |b| b.iter(|| sweep(black_box(100)))); 93 | c.bench_function("sweep_slow 100", |b| b.iter(|| sweep_slow(black_box(100)))); 94 | 95 | c.bench_function("sweep 1000", |b| b.iter(|| sweep(black_box(1000)))); 96 | c.bench_function("sweep_slow 1000", |b| b.iter(|| sweep_slow(black_box(1000)))); 97 | } 98 | 99 | criterion_group!(benches, criterion_benchmark); 100 | criterion_main!(benches); 101 | -------------------------------------------------------------------------------- /demos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "offset", 4 | "walk", 5 | "distort", 6 | "arithmetic", 7 | "curve_intersections", 8 | "brush", 9 | "curve_fit", 10 | ] 11 | 12 | [patch.crates-io] 13 | flo_curves = { path = ".." } 14 | flo_draw = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 15 | flo_canvas = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 16 | flo_canvas_events = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 17 | flo_render = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 18 | flo_render_canvas = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 19 | flo_render_gl_offscreen = { git = "https://github.com/Logicalshift/flo_draw", branch = "v0.4" } 20 | 21 | flo_binding = { git = "https://github.com/Logicalshift/flo_binding", branch = "v3.0" } 22 | flo_scene = { git = "https://github.com/Logicalshift/flo_scene", branch = "v0.2" } 23 | desync = { git = "https://github.com/Logicalshift/desync", branch = "v0.9" } 24 | -------------------------------------------------------------------------------- /demos/arithmetic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arithmetic_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | description = "Demonstrates path arithmetic in flo_curves" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/arithmetic/src/main.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::arc::*; 3 | use flo_curves::bezier::path::*; 4 | use flo_draw::*; 5 | use flo_draw::canvas::*; 6 | 7 | use std::f64; 8 | use std::thread; 9 | use std::time::{Duration, Instant}; 10 | 11 | fn main() { 12 | with_2d_graphics(|| { 13 | let canvas = create_canvas_window("Path arithmetic demonstration"); 14 | 15 | let start_time = Instant::now(); 16 | 17 | loop { 18 | // Wait for the next frame 19 | thread::sleep(Duration::from_nanos(1_000_000_000 / 60)); 20 | 21 | // Decide on an amplitude that determines where the paths are relative to each other 22 | let since_start = Instant::now().duration_since(start_time); 23 | let since_start = since_start.as_nanos() as f64; 24 | let amplitude = (since_start / (f64::consts::PI * 1_000_000_000.0)).cos() * 200.0; 25 | 26 | // Create some circles 27 | let path1 = Circle::new(Coord2(500.0 + amplitude, 500.0), 100.0).to_path::(); 28 | let path2 = Circle::new(Coord2(500.0 - amplitude, 500.0), 100.0).to_path::(); 29 | let path3 = Circle::new(Coord2(500.0, 500.0 - amplitude), 60.0).to_path::(); 30 | 31 | // Add and subtract them to generate the final path 32 | let path = path_add::(&vec![path1.clone()], &vec![path2.clone()], 0.1); 33 | let path = path_sub::(&path, &vec![path3.clone()], 0.1); 34 | 35 | canvas.draw(|gc| { 36 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 37 | 38 | gc.canvas_height(1000.0); 39 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 40 | 41 | // Render the subpaths 42 | gc.line_width(1.0); 43 | 44 | gc.stroke_color(Color::Rgba(0.4, 0.8, 0.0, 0.5)); 45 | for p in vec![&path1, &path2, &path3] { 46 | gc.new_path(); 47 | gc.bezier_path(p); 48 | gc.stroke(); 49 | } 50 | 51 | // Render the combined path 52 | gc.line_width(3.0); 53 | 54 | gc.stroke_color(Color::Rgba(0.8, 0.5, 0.0, 1.0)); 55 | gc.fill_color(Color::Rgba(0.3, 0.6, 0.8, 0.8)); 56 | gc.winding_rule(WindingRule::EvenOdd); 57 | 58 | gc.new_path(); 59 | path.iter().for_each(|path| { 60 | gc.bezier_path(path); 61 | }); 62 | gc.fill(); 63 | gc.stroke(); 64 | 65 | // Render the path points 66 | gc.line_width(1.0); 67 | 68 | for subpath in path.iter() { 69 | for (_, _, point) in subpath.1.iter() { 70 | gc.new_path(); 71 | gc.circle(point.x() as _, point.y() as _, 5.0); 72 | gc.stroke(); 73 | } 74 | } 75 | }); 76 | } 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /demos/brush/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brush_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | edition = "2018" 6 | description = "Demonstrates brush strokes in flo_curve" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/curve_fit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "curve_fit_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | description = "Demonstrates path arithmetic in flo_curves" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/curve_intersections/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "curve_intersections_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | description = "Demonstrates curve intersections in flo_curves" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/curve_intersections/src/main.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::arc::*; 3 | use flo_curves::bezier::*; 4 | use flo_curves::bezier::path::*; 5 | use flo_draw::*; 6 | use flo_draw::canvas::*; 7 | 8 | fn main() { 9 | with_2d_graphics(|| { 10 | let canvas = create_canvas_window("Curve intersection demonstration"); 11 | 12 | // Two paths to find intersections in 13 | let path1 = Circle::new(Coord2(496.9997044935593, 500.0), 300.0).to_path::(); 14 | let path2 = Circle::new(Coord2(503.0002955064407, 500.0), 300.0).to_path::(); 15 | 16 | // Find the intersections between these two paths 17 | let mut intersections = vec![]; 18 | for curve1 in path1.to_curves::>() { 19 | for curve2 in path2.to_curves::>() { 20 | intersections.extend(curve_intersects_curve_clip(&curve1, &curve2, 0.01).into_iter() 21 | .map(|(t1, _t2)| curve1.point_at_pos(t1))); 22 | } 23 | } 24 | 25 | println!("{:?}", intersections.len()); 26 | 27 | // Draw the circles and the intersections 28 | canvas.draw(|gc| { 29 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 30 | 31 | gc.canvas_height(1000.0); 32 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 33 | 34 | // Render the subpaths 35 | gc.line_width(1.0); 36 | 37 | gc.stroke_color(Color::Rgba(0.4, 0.8, 0.0, 1.0)); 38 | for p in vec![&path1, &path2] { 39 | gc.new_path(); 40 | gc.bezier_path(p); 41 | gc.stroke(); 42 | } 43 | 44 | // Render the intersection points 45 | gc.stroke_color(Color::Rgba(0.8, 0.4, 0.0, 1.0)); 46 | 47 | for Coord2(x, y) in intersections { 48 | gc.new_path(); 49 | gc.circle(x as _, y as _, 5.0); 50 | gc.stroke(); 51 | } 52 | }) 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /demos/distort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "distort_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | description = "Demonstrates path distortion in flo_curves" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/distort/src/main.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier; 3 | use flo_curves::bezier::path::*; 4 | use flo_draw::*; 5 | use flo_draw::canvas::*; 6 | 7 | use std::f64; 8 | use std::thread; 9 | use std::time::{Duration, Instant}; 10 | 11 | fn main() { 12 | with_2d_graphics(|| { 13 | let canvas = create_canvas_window("Curve and path distortion demonstration"); 14 | 15 | // Simple rectangle to use as a source path 16 | let source_path = BezierPathBuilder::::start(Coord2(300.0, 500.0)) 17 | .line_to(Coord2(700.0, 500.0)) 18 | .line_to(Coord2(700.0, 900.0)) 19 | .line_to(Coord2(300.0, 900.0)) 20 | .line_to(Coord2(300.0, 500.0)) 21 | .build(); 22 | 23 | // Line to use as a source curve 24 | let source_curve = bezier::Curve::from_points(Coord2(300.0, 200.0), (Coord2(300.0, 300.0), Coord2(700.0, 100.0)), Coord2(700.0, 200.0)); 25 | 26 | // We'll change the amount of distortion over time 27 | let start_time = Instant::now(); 28 | 29 | loop { 30 | // Wait for the next frame 31 | thread::sleep(Duration::from_nanos(1_000_000_000 / 60)); 32 | 33 | // Generate a distortion of the source path 34 | let since_start = Instant::now().duration_since(start_time); 35 | let since_start = since_start.as_nanos() as f64; 36 | let amplitude = (since_start / (f64::consts::PI * 500_000_000.0)).sin() * 50.0; 37 | 38 | let distorted_path = bezier::distort_path::<_, _, SimpleBezierPath>(&source_path, |point, _curve, _t| { 39 | let distance = point.magnitude(); 40 | let ripple = (since_start / (f64::consts::PI * 500_000_000.0)) * 10.0; 41 | 42 | let offset_x = (distance / (f64::consts::PI*5.0) + ripple).sin() * amplitude * 0.5; 43 | let offset_y = (distance / (f64::consts::PI*4.0) + ripple).cos() * amplitude * 0.5; 44 | 45 | Coord2(point.x() + offset_x, point.y() + offset_y) 46 | }, 1.0, 0.1).unwrap(); 47 | 48 | let distorted_curve = bezier::distort_curve::<_, _, bezier::Curve>(&source_curve, |point, _t| { 49 | let offset_x = (point.x() / (f64::consts::PI*25.0)*(amplitude/50.0)).sin() * amplitude * 2.0; 50 | let offset_y = (point.x() / (f64::consts::PI*12.0)).cos() * amplitude * 2.0; 51 | 52 | Coord2(point.x() + offset_x, point.y() + offset_y) 53 | }, 1.0, 0.1).unwrap(); 54 | 55 | canvas.draw(|gc| { 56 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 57 | 58 | gc.canvas_height(1000.0); 59 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 60 | 61 | gc.line_width(2.0); 62 | 63 | // Render the distorted curve and the original 64 | gc.stroke_color(Color::Rgba(0.8, 0.6, 0.0, 1.0)); 65 | gc.new_path(); 66 | gc.move_to(source_curve.start_point().x() as _, source_curve.start_point.y() as _); 67 | gc.bezier_curve(&source_curve); 68 | gc.stroke(); 69 | 70 | gc.stroke_color(Color::Rgba(0.0, 0.6, 0.8, 1.0)); 71 | gc.new_path(); 72 | gc.move_to(distorted_curve[0].start_point().x() as _, distorted_curve[0].start_point.y() as _); 73 | for curve in distorted_curve.iter() { 74 | gc.bezier_curve(curve); 75 | } 76 | gc.stroke(); 77 | 78 | // Render the distorted path and the original 79 | gc.stroke_color(Color::Rgba(0.8, 0.6, 0.0, 1.0)); 80 | gc.new_path(); 81 | gc.bezier_path(&source_path); 82 | gc.stroke(); 83 | 84 | gc.stroke_color(Color::Rgba(0.0, 0.6, 0.8, 1.0)); 85 | gc.new_path(); 86 | gc.bezier_path(&distorted_path); 87 | gc.stroke(); 88 | 89 | if (since_start % 10_000_000_000.0) > 5_000_000_000.0 { 90 | // Render the path points 91 | gc.line_width(1.0); 92 | 93 | for (_, _, point) in distorted_path.1.iter() { 94 | gc.new_path(); 95 | gc.circle(point.x() as _, point.y() as _, 5.0); 96 | gc.stroke(); 97 | } 98 | } 99 | }); 100 | } 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /demos/offset/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "offset_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | edition = "2018" 6 | description = "Demonstrates bezier curve offsets in flo_curve" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/walk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "walk_demo" 3 | version = "0.1.0" 4 | authors = ["Andrew Hunter "] 5 | description = "Demonstrates curve walking in flo_curves" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | flo_curves = "0.8" 10 | flo_draw = "0.4" 11 | -------------------------------------------------------------------------------- /demos/walk/src/main.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::*; 3 | use flo_draw::*; 4 | use flo_draw::canvas::*; 5 | 6 | fn main() { 7 | with_2d_graphics(|| { 8 | let canvas = create_canvas_window("Bezier curve walking demo"); 9 | let curve_1 = Curve::from_points(Coord2(100.0, 500.0), (Coord2(400.0, 2000.0), Coord2(1500.5, 1200.0)), Coord2(1400.0, 900.0)); 10 | let curve_2 = Curve::from_points(Coord2(100.0, 100.0), (Coord2(400.0, 1600.0), Coord2(1500.5, 800.0)), Coord2(1400.0, 500.0)); 11 | 12 | canvas.draw(|gc| { 13 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 14 | 15 | gc.canvas_height(1500.0); 16 | gc.center_region(0.0, 0.0, 1500.0, 1500.0); 17 | 18 | gc.line_width(2.0); 19 | 20 | gc.new_path(); 21 | gc.move_to(curve_1.start_point().x() as _, curve_1.start_point().y() as _); 22 | gc.bezier_curve(&curve_1); 23 | gc.stroke_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 24 | gc.stroke(); 25 | 26 | gc.new_path(); 27 | gc.move_to(curve_2.start_point().x() as _, curve_2.start_point().y() as _); 28 | gc.bezier_curve(&curve_2); 29 | gc.stroke_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 30 | gc.stroke(); 31 | 32 | gc.stroke_color(Color::Rgba(1.0, 0.6, 0.0, 1.0)); 33 | gc.fill_color(Color::Rgba(1.0, 0.6, 0.0, 1.0)); 34 | for section in walk_curve_unevenly(&curve_1, 20) { 35 | let (_t_min, t_max) = section.original_curve_t_values(); 36 | let pos = curve_1.point_at_pos(t_max); 37 | let unit_normal = curve_1.normal_at_pos(t_max).to_unit_vector(); 38 | 39 | gc.new_path(); 40 | gc.move_to((pos.x() + unit_normal.x()*12.0) as _, (pos.y() + unit_normal.y()*12.0) as _); 41 | gc.line_to((pos.x() - unit_normal.x()*12.0) as _, (pos.y() - unit_normal.y()*12.0) as _); 42 | gc.stroke(); 43 | 44 | gc.new_path(); 45 | gc.circle(pos.x() as _, pos.y() as _, 6.0); 46 | gc.fill(); 47 | } 48 | 49 | for section in walk_curve_evenly(&curve_2, curve_length(&curve_2, 0.1)/20.0, 0.1) { 50 | let (_t_min, t_max) = section.original_curve_t_values(); 51 | let pos = curve_2.point_at_pos(t_max); 52 | let unit_normal = curve_2.normal_at_pos(t_max).to_unit_vector(); 53 | 54 | gc.new_path(); 55 | gc.move_to((pos.x() + unit_normal.x()*12.0) as _, (pos.y() + unit_normal.y()*12.0) as _); 56 | gc.line_to((pos.x() - unit_normal.x()*12.0) as _, (pos.y() - unit_normal.y()*12.0) as _); 57 | gc.stroke(); 58 | 59 | gc.new_path(); 60 | gc.circle(pos.x() as _, pos.y() as _, 6.0); 61 | gc.fill(); 62 | } 63 | }); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_curves/762aba80ad2aeb6c443a432219212b0629712b33/logo-small.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_curves/762aba80ad2aeb6c443a432219212b0629712b33/logo.png -------------------------------------------------------------------------------- /src/arc/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Describing circular arcs 3 | //! 4 | //! The `arc` module provides routines for describing circular arcs and converting them to bezier 5 | //! curves. 6 | //! 7 | 8 | mod circle; 9 | 10 | pub use self::circle::*; 11 | 12 | // TODO: represent arcs in more than 2 dimensions 13 | -------------------------------------------------------------------------------- /src/bezier/basis.rs: -------------------------------------------------------------------------------- 1 | use crate::geo::*; 2 | use smallvec::*; 3 | 4 | /// 5 | /// Computes the bezier coefficients (A, B, C, D) for a bezier curve 6 | /// 7 | pub fn bezier_coefficients(dimension: usize, w1: &Point, w2: &Point, w3: &Point, w4: &Point) -> (f64, f64, f64, f64) { 8 | let w1 = w1.get(dimension); 9 | let w2 = w2.get(dimension); 10 | let w3 = w3.get(dimension); 11 | let w4 = w4.get(dimension); 12 | 13 | ( 14 | w4-(3.0*w3)+(3.0*w2)-w1, 15 | (3.0*w3)-(6.0*w2)+3.0*w1, 16 | 3.0*w2-3.0*w1, 17 | w1 18 | ) 19 | } 20 | 21 | /// 22 | /// The cubic bezier weighted basis function 23 | /// 24 | #[inline] 25 | pub fn basis(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> Point { 26 | let t_squared = t*t; 27 | let t_cubed = t_squared*t; 28 | 29 | let one_minus_t = 1.0-t; 30 | let one_minus_t_squared = one_minus_t*one_minus_t; 31 | let one_minus_t_cubed = one_minus_t_squared*one_minus_t; 32 | 33 | w1*one_minus_t_cubed 34 | + w2*3.0*one_minus_t_squared*t 35 | + w3*3.0*one_minus_t*t_squared 36 | + w4*t_cubed 37 | } 38 | 39 | /// 40 | /// de Casteljau's algorithm for bezier curves of arbitrary degree 41 | /// 42 | #[inline] 43 | pub fn de_casteljau_n(t: f64, points: SmallVec<[Point; N]>) -> Point { 44 | let mut points = points; 45 | 46 | while points.len() > 1 { 47 | let mut next_points = smallvec![]; 48 | 49 | for idx in 0..(points.len()-1) { 50 | next_points.push(points[idx]*(1.0-t) + points[idx+1]*t); 51 | } 52 | 53 | points = next_points; 54 | } 55 | 56 | return points[0]; 57 | } 58 | 59 | /// 60 | /// de Casteljau's algorithm for cubic bezier curves 61 | /// 62 | #[inline] 63 | pub fn de_casteljau4(t: f64, w1: Point, w2: Point, w3: Point, w4: Point) -> Point { 64 | let wn1 = w1*(1.0-t) + w2*t; 65 | let wn2 = w2*(1.0-t) + w3*t; 66 | let wn3 = w3*(1.0-t) + w4*t; 67 | 68 | de_casteljau3(t, wn1, wn2, wn3) 69 | } 70 | 71 | /// 72 | /// de Casteljau's algorithm for quadratic bezier curves 73 | /// 74 | #[inline] 75 | pub fn de_casteljau3(t: f64, w1: Point, w2: Point, w3: Point) -> Point { 76 | let wn1 = w1*(1.0-t) + w2*t; 77 | let wn2 = w2*(1.0-t) + w3*t; 78 | 79 | de_casteljau2(t, wn1, wn2) 80 | } 81 | 82 | /// 83 | /// de Casteljau's algorithm for lines 84 | /// 85 | #[inline] 86 | pub fn de_casteljau2(t: f64, w1: Point, w2: Point) -> Point { 87 | w1*(1.0-t) + w2*t 88 | } 89 | -------------------------------------------------------------------------------- /src/bezier/bounds.rs: -------------------------------------------------------------------------------- 1 | use super::basis::*; 2 | use super::super::geo::*; 3 | 4 | /// 5 | /// Finds the t values of the extremities of a curve (these are the points at which 6 | /// the x or y value is at a minimum or maximum) 7 | /// 8 | pub fn find_extremities(w1: Point, w2: Point, w3: Point, w4: Point) -> Vec { 9 | // The 't' values where this curve has extremities we need to examine 10 | let mut t_extremes = vec![1.0]; 11 | 12 | // The derivative is a quadratic function, so we can compute the locations of these (t values) by solving the quadratic formula for them 13 | for component_index in 0..Point::len() { 14 | // Fetch the parameters for this component 15 | let p1 = w1.get(component_index); 16 | let p2 = w2.get(component_index); 17 | let p3 = w3.get(component_index); 18 | let p4 = w4.get(component_index); 19 | 20 | // Compute the bezier coefficients 21 | let a = (-p1 + p2*3.0 - p3*3.0 + p4)*3.0; 22 | let b = (p1 - p2*2.0 + p3)*6.0; 23 | let c = (p2 - p1)*3.0; 24 | 25 | // Extremities are points at which the curve has a 0 gradient (in any of its dimensions) 26 | let root1 = (-b + f64::sqrt(b*b - a*c*4.0)) / (a*2.0); 27 | let root2 = (-b - f64::sqrt(b*b - a*c*4.0)) / (a*2.0); 28 | 29 | if root1 > 0.0 && root1 < 1.0 { t_extremes.push(root1); } 30 | if root2 > 0.0 && root2 < 1.0 { t_extremes.push(root2); } 31 | 32 | // We also solve for the second derivative 33 | let aa = 2.0*(b-a); 34 | let bb = 2.0*(c-a); 35 | 36 | // Solve for a'*t+b = 0 (0-b/a') 37 | if aa != 0.0 { 38 | let root3 = -bb/aa; 39 | if root3 > 0.0 && root3 < 1.0 { 40 | t_extremes.push(root3); 41 | } 42 | } 43 | } 44 | 45 | t_extremes 46 | } 47 | 48 | /// 49 | /// Finds the upper and lower points in a cubic curve's bounding box 50 | /// 51 | pub fn bounding_box4>(w1: Point, w2: Point, w3: Point, w4: Point) -> Bounds { 52 | // The 't' values where this curve has extremities we need to examine 53 | let t_extremes = find_extremities(w1, w2, w3, w4); 54 | 55 | // Start with the point at 0,0 as the minimum position 56 | let mut min_pos = de_casteljau4(0.0, w1, w2, w3, w4); 57 | let mut max_pos = min_pos; 58 | 59 | for t in t_extremes { 60 | let point = de_casteljau4(t, w1, w2, w3, w4); 61 | 62 | min_pos = Point::from_smallest_components(min_pos, point); 63 | max_pos = Point::from_biggest_components(max_pos, point); 64 | } 65 | 66 | Bounds::from_min_max(min_pos, max_pos) 67 | } 68 | -------------------------------------------------------------------------------- /src/bezier/deform.rs: -------------------------------------------------------------------------------- 1 | use super::curve::*; 2 | 3 | /// 4 | /// Moves the point at 't' on the curve by the offset vector 5 | /// 6 | /// This recomputes the control points such that the point at t on the original curve 7 | /// is moved by the vector specified by `offset`. 8 | /// 9 | pub fn move_point(curve: &impl BezierCurve, t: f64, offset: &CurveOut::Point) -> CurveOut { 10 | // Fetch the points from the curve 11 | let w1 = curve.start_point(); 12 | let w4 = curve.end_point(); 13 | let (w2, w3) = curve.control_points(); 14 | 15 | let one_minus_t = 1.0-t; 16 | let one_minus_t_cubed = one_minus_t*one_minus_t*one_minus_t; 17 | let t_cubed = t*t*t; 18 | 19 | // Point 'C' is fixed for the transformation and is along the line w1-w4 20 | let u = one_minus_t_cubed / (t_cubed + one_minus_t_cubed); 21 | let c = w1*u + w4*(1.0-u); 22 | 23 | // Construct the de Casteljau points for the point we're moving 24 | let wn1 = w1*(1.0-t) + w2*t; 25 | let wn2 = w2*(1.0-t) + w3*t; 26 | let wn3 = w3*(1.0-t) + w4*t; 27 | 28 | let wnn1 = wn1*(1.0-t) + wn2*t; 29 | let wnn2 = wn2*(1.0-t) + wn3*t; 30 | 31 | let p = wnn1*(1.0-t) + wnn2*t; 32 | 33 | // Translating wnn1 and wnn2 by the offset will give us a new p that is also translated by the offset 34 | let pb = p + *offset; 35 | 36 | let wnn1b = wnn1 + *offset; 37 | let wnn2b = wnn2 + *offset; 38 | 39 | // The line c->pb->wn2b has the same ratios as the line c->p->wn2, so we can compute wn2b 40 | // 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) 41 | let ratio = ((t_cubed+one_minus_t_cubed)/(t_cubed + one_minus_t_cubed-1.0)).abs(); 42 | let wn2b = ((pb-c)*ratio) + pb; 43 | 44 | // We can now calculate wn1b and wn3b 45 | let inverse_t = 1.0/t; 46 | let inverse_tminus1 = 1.0/(t-1.0); 47 | 48 | let wn1b = (wn2b*t - wnn1b)*inverse_tminus1; 49 | let wn3b = (wn2b*-1.0 + wn2b*t + wnn2b)*inverse_t; 50 | 51 | // ... and the new control points 52 | let w2b = (w1*-1.0 + w1*t + wn1b)*inverse_t; 53 | let w3b = (w4*t-wn3b)*inverse_tminus1; 54 | 55 | // Use the values to construct the curve with the moved point 56 | CurveOut::from_points(w1, (w2b, w3b), w4) 57 | } -------------------------------------------------------------------------------- /src/bezier/derivative.rs: -------------------------------------------------------------------------------- 1 | use crate::geo::*; 2 | 3 | use smallvec::*; 4 | 5 | /// 6 | /// Returns the derivative of a bezier curve of arbitrary degree 7 | /// 8 | /// (Resolves to a smallvec as Rust can't currently return a slice with a definition like [Point; N-1]) 9 | /// 10 | pub fn derivative_n(points: SmallVec<[Point; N]>) -> SmallVec<[Point; N]> { 11 | let n = points.len(); 12 | let multiplier = (n-1) as f64; 13 | 14 | let mut derivative = smallvec![]; 15 | for idx in 0..(n-1) { 16 | derivative.push((points[idx+1]-points[idx])*multiplier); 17 | } 18 | 19 | derivative 20 | } 21 | 22 | /// 23 | /// Returns the 1st derivative of a cubic bezier curve 24 | /// 25 | pub fn derivative4(w1: Point, w2: Point, w3: Point, w4: Point) -> (Point, Point, Point) { 26 | ((w2-w1)*3.0, (w3-w2)*3.0, (w4-w3)*3.0) 27 | } 28 | 29 | /// 30 | /// Returns the 1st derivative of a quadratic bezier curve (or the 2nd derivative of a cubic curve) 31 | /// 32 | pub fn derivative3(wn1: Point, wn2: Point, wn3: Point) -> (Point, Point) { 33 | ((wn2-wn1)*2.0, (wn3-wn2)*2.0) 34 | } 35 | 36 | /// 37 | /// Returns the 3rd derivative of a cubic bezier curve (2nd of a quadratic) 38 | /// 39 | pub fn derivative2(wnn1: Point, wnn2: Point) -> Point { 40 | wnn2-wnn1 41 | } 42 | -------------------------------------------------------------------------------- /src/bezier/distort.rs: -------------------------------------------------------------------------------- 1 | use super::fit::*; 2 | use super::walk::*; 3 | use super::path::*; 4 | use super::curve::*; 5 | use super::normal::*; 6 | use crate::geo::*; 7 | 8 | use std::iter; 9 | 10 | /// 11 | /// Distorts a curve using an arbitrary function 12 | /// 13 | pub fn distort_curve(curve: &CurveIn, distort_fn: DistortFn, step_len: f64, max_error: f64) -> Option> 14 | where 15 | CurveIn: BezierCurve, 16 | CurveIn::Point: Normalize+Coordinate2D, 17 | CurveOut: BezierCurveFactory, 18 | DistortFn: Fn(CurveIn::Point, f64) -> CurveOut::Point, 19 | { 20 | // Walk the curve at roughly step_len increments 21 | let sections = walk_curve_evenly(curve, step_len, step_len / 4.0); 22 | 23 | // Generate the points to fit to using the distortion function 24 | let fit_points = sections.map(|section| { 25 | let (t, _) = section.original_curve_t_values(); 26 | let pos = curve.point_at_pos(t); 27 | 28 | (pos, t) 29 | }) 30 | .chain(iter::once((curve.point_at_pos(1.0), 1.0))) 31 | .map(|(point, t)| distort_fn(point, t)) 32 | .collect::>(); 33 | 34 | // Fit the points to generate the final curve 35 | fit_curve(&fit_points, max_error) 36 | } 37 | 38 | /// 39 | /// Distorts a path using an arbitrary function 40 | /// 41 | pub fn distort_path(path: &PathIn, distort_fn: DistortFn, step_len: f64, max_error: f64) -> Option 42 | where 43 | PathIn: BezierPath, 44 | PathOut: BezierPathFactory, 45 | DistortFn: Fn(PathIn::Point, &Curve, f64) -> PathOut::Point, 46 | { 47 | // The initial point is derived from the first curve 48 | let start_point = path.start_point(); 49 | let mut path_points = path.points(); 50 | let mut current_point = path_points.next()?; 51 | let mut current_curve = Curve::from_points(start_point, (current_point.0, current_point.1), current_point.2); 52 | let start_point = distort_fn(start_point, ¤t_curve, 0.0); 53 | 54 | // Process the remaining points to generate the new path 55 | let mut new_points = vec![]; 56 | 57 | loop { 58 | // Distort the current curve 59 | let sections = walk_curve_evenly(¤t_curve, step_len, step_len / 4.0); 60 | 61 | let fit_points = sections.map(|section| { 62 | let (t, _) = section.original_curve_t_values(); 63 | let pos = current_curve.point_at_pos(t); 64 | 65 | (pos, t) 66 | }) 67 | .chain(iter::once((current_curve.point_at_pos(1.0), 1.0))) 68 | .map(|(point, t)| distort_fn(point, ¤t_curve, t)) 69 | .collect::>(); 70 | 71 | // Fit the points to generate the new curves 72 | let new_curves = fit_curve::>(&fit_points, max_error)?; 73 | new_points.extend(new_curves.into_iter().map(|curve| { 74 | let (cp1, cp2) = curve.control_points(); 75 | (cp1, cp2, curve.end_point()) 76 | })); 77 | 78 | // Move to the next curve (stopping once we reach the end of the list of the points) 79 | let next_start_point = current_curve.end_point(); 80 | current_point = if let Some(point) = path_points.next() { point } else { break; }; 81 | current_curve = Curve::from_points(next_start_point, (current_point.0, current_point.1), current_point.2); 82 | } 83 | 84 | // Create the new path from the result 85 | Some(PathOut::from_points(start_point, new_points)) 86 | } 87 | -------------------------------------------------------------------------------- /src/bezier/intersection/mod.rs: -------------------------------------------------------------------------------- 1 | mod curve_line; 2 | mod curve_curve_clip; 3 | mod fat_line; 4 | mod self_intersection; 5 | 6 | pub use self::curve_line::*; 7 | pub use self::curve_curve_clip::*; 8 | pub use self::self_intersection::*; 9 | -------------------------------------------------------------------------------- /src/bezier/intersection/self_intersection.rs: -------------------------------------------------------------------------------- 1 | use super::curve_curve_clip::*; 2 | use super::super::curve::*; 3 | use super::super::section::*; 4 | use super::super::characteristics::*; 5 | use super::super::super::geo::*; 6 | 7 | /// 8 | /// If a cubic curve contains a loop, finds the t values where the curve self-intersects 9 | /// 10 | pub fn find_self_intersection_point(curve: &C, accuracy: f64) -> Option<(f64, f64)> 11 | where 12 | C::Point: Coordinate+Coordinate2D, 13 | { 14 | let curve_type = curve.characteristics(); 15 | 16 | if curve_type == CurveCategory::Loop { 17 | let full_curve = CurveSection::new(curve, 0.0, 1.0); 18 | find_intersection_point_in_loop(full_curve, accuracy) 19 | } else { 20 | None 21 | } 22 | } 23 | 24 | /// 25 | /// Given a curve known to have a loop in it, subdivides it in order to determine where the intersection lies 26 | /// 27 | fn find_intersection_point_in_loop(curve: CurveSection, accuracy: f64) -> Option<(f64, f64)> 28 | where 29 | C::Point: Coordinate+Coordinate2D, 30 | { 31 | use self::CurveCategory::*; 32 | 33 | // 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 34 | // If we split in the middle of the loop, we use the bezier clipping algorithm to find where the two sides intersect 35 | let (left, right) = (curve.subsection(0.0, 0.5), curve.subsection(0.5, 1.0)); 36 | let (left_type, right_type) = (left.characteristics(), right.characteristics()); 37 | 38 | match (left_type, right_type) { 39 | (Loop, Loop) => { 40 | // If both sides are loops then we've split the original curve at the intersection point 41 | unimplemented!("Need support for a loop where we hit the intersection point") 42 | } 43 | 44 | (Loop, _) => { 45 | // Loop is in the left side 46 | find_intersection_point_in_loop(left, accuracy) 47 | }, 48 | 49 | (_, Loop) => { 50 | // Loop is in the right side 51 | find_intersection_point_in_loop(right, accuracy) 52 | } 53 | 54 | (_, _) => { 55 | // Can find the intersection by using the clipping algorithm 56 | let intersections = curve_intersects_curve_clip(&left, &right, accuracy); 57 | 58 | if intersections.is_empty() && left.start_point().is_near_to(&right.end_point(), accuracy) { 59 | // Didn't find an intersection but the left and right curves start and end at the same position 60 | return Some((left.t_for_t(0.0), right.t_for_t(1.0))); 61 | } 62 | 63 | test_assert!(intersections.len() != 0); 64 | 65 | if intersections.len() == 1 { 66 | // Only found a single intersection 67 | intersections.into_iter().next() 68 | .map(|(t1, t2)| (left.t_for_t(t1), right.t_for_t(t2))) 69 | } else { 70 | // 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) 71 | intersections.into_iter() 72 | .find(|(t1, t2)| *t1 < 1.0 && *t2 > 0.0) 73 | .map(|(t1, t2)| (left.t_for_t(t1), right.t_for_t(t2))) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/bezier/length.rs: -------------------------------------------------------------------------------- 1 | use super::curve::*; 2 | use super::section::*; 3 | use crate::geo::*; 4 | 5 | /// 6 | /// Returns the length of the control polygon for a bezier curve 7 | /// 8 | pub fn control_polygon_length(curve: &impl BezierCurve) -> f64 { 9 | let p1 = curve.start_point(); 10 | let (p2, p3) = curve.control_points(); 11 | let p4 = curve.end_point(); 12 | 13 | p1.distance_to(&p2) 14 | + p2.distance_to(&p3) 15 | + p3.distance_to(&p4) 16 | } 17 | 18 | /// 19 | /// Returns the length of the chord of a bezier curve 20 | /// 21 | pub fn chord_length(curve: &impl BezierCurve) -> f64 { 22 | let p1 = curve.start_point(); 23 | let p2 = curve.end_point(); 24 | 25 | p1.distance_to(&p2) 26 | } 27 | 28 | /// 29 | /// Estimates the length of a bezier curve within a particular error tolerance 30 | /// 31 | pub fn curve_length(curve: &impl BezierCurve, max_error: f64) -> f64 { 32 | section_length(curve.section(0.0, 1.0), max_error) 33 | } 34 | 35 | /// 36 | /// Computes the length of a section of a bezier curve 37 | /// 38 | fn section_length(section: CurveSection<'_, impl BezierCurve>, max_error: f64) -> f64 { 39 | // This algorithm is described in Graphics Gems V IV.7 40 | 41 | // The MIN_ERROR guards against cases where the length of a section fails to converge for some reason 42 | const MIN_ERROR: f64 = 1e-12; 43 | 44 | // Algorithm is recursive, but we use a vec as a stack to avoid overflowing (and to make the number of iterations easy to count) 45 | let mut waiting = vec![(section, max_error)]; 46 | let mut total_length = 0.0; 47 | 48 | while let Some((section, max_error)) = waiting.pop() { 49 | // Estimate the error for the length of the curve 50 | let polygon_length = control_polygon_length(§ion); 51 | let chord_length = chord_length(§ion); 52 | 53 | let error = (polygon_length - chord_length) * (polygon_length - chord_length); 54 | 55 | // If the error is low enough, return the estimated length 56 | if error < max_error || max_error <= MIN_ERROR { 57 | total_length += (2.0*chord_length + 2.0*polygon_length)/4.0; 58 | } else { 59 | // Subdivide the curve (each half has half the error tolerance) 60 | let left = section.subsection(0.0, 0.5); 61 | let right = section.subsection(0.5, 1.0); 62 | let subsection_error = max_error / 2.0; 63 | 64 | waiting.push((left, subsection_error)); 65 | waiting.push((right, subsection_error)); 66 | } 67 | } 68 | 69 | total_length 70 | } 71 | 72 | /// 73 | /// Returns true if a curve is so small it could be considered to represent an individual point 74 | /// 75 | /// 'Tiny' in this instance is a curve with a maximum arc length of 1e-6 76 | /// 77 | #[inline] 78 | pub fn curve_is_tiny(curve: &impl BezierCurve) -> bool { 79 | // Distance that we consider 'tiny' 80 | const MAX_DISTANCE: f64 = 1e-6; 81 | 82 | let (sp, (cp1, cp2), ep) = curve.all_points(); 83 | 84 | if sp.is_near_to(&ep, MAX_DISTANCE) { 85 | // Start point and end point are close together (curve could be a long loop or a short section) 86 | 87 | // Calculate the length of the control polygon 88 | let total_length = sp.distance_to(&cp1) + cp1.distance_to(&cp2) + cp2.distance_to(&ep); 89 | 90 | // If the control polygon is shorter than MAX_DISTANCE then the whole curve 91 | total_length <= MAX_DISTANCE 92 | } else { 93 | // Start and end point are far apart 94 | false 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/bezier/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Routines for describing, querying and manipulating Bezier curves 3 | //! 4 | //! ``` 5 | //! # use flo_curves::*; 6 | //! # use flo_curves::bezier::*; 7 | //! # 8 | //! let curve = Curve::from_points(Coord2(1.0, 2.0), (Coord2(2.0, 0.0), Coord2(3.0, 5.0)), Coord2(4.0, 2.0)); 9 | //! 10 | //! let mid_point = curve.point_at_pos(0.5); 11 | //! let all_points = walk_curve_evenly(&curve, 1.0, 0.01).map(|section| section.point_at_pos(0.5)).collect::>(); 12 | //! let fitted_curve = fit_curve::>(&all_points, 0.1); 13 | //! let intersections = curve_intersects_ray(&curve, &(Coord2(1.0, 1.0), Coord2(2.0, 2.0))); 14 | //! let offset_curve = offset(&curve, 2.0, 2.0); 15 | //! ``` 16 | //! 17 | //! Anything that implements the `BezierCurve` trait can be manipulated by the functions in this crate. The `Curve` type 18 | //! is provided as a basic implementation for defining bezier curves, but the trait can be defined on any type that 19 | //! represents a bezier curve. 20 | //! 21 | //! The `BezierCurveFactory` trait extends the `BezierCurve` trait for use with functions that can build/return new curves. 22 | //! 23 | //! For routines that deal with paths made up of bezier curves, see the `path` namespace. 24 | //! 25 | 26 | mod curve; 27 | mod section; 28 | mod basis; 29 | mod subdivide; 30 | mod derivative; 31 | mod tangent; 32 | mod normal; 33 | mod bounds; 34 | mod deform; 35 | mod fit; 36 | mod offset; 37 | mod offset_lms; 38 | mod offset_scaling; 39 | mod offset_subdivision_lms; 40 | mod search; 41 | mod solve; 42 | mod overlaps; 43 | mod intersection; 44 | mod characteristics; 45 | mod length; 46 | mod walk; 47 | mod distort; 48 | mod nearest_point; 49 | 50 | pub mod path; 51 | pub mod vectorize; 52 | pub mod rasterize; 53 | pub mod roots; 54 | 55 | pub use curve::*; 56 | pub use section::*; 57 | pub use basis::*; 58 | pub use subdivide::*; 59 | pub use derivative::*; 60 | pub use tangent::*; 61 | pub use normal::*; 62 | pub use bounds::*; 63 | pub use deform::*; 64 | pub use fit::*; 65 | pub use offset::*; 66 | pub use offset_lms::*; 67 | pub use offset_scaling::*; 68 | pub use offset_subdivision_lms::*; 69 | pub use search::*; 70 | pub use solve::*; 71 | pub use overlaps::*; 72 | pub use intersection::*; 73 | pub use characteristics::*; 74 | pub use length::*; 75 | pub use walk::*; 76 | pub use distort::*; 77 | pub use nearest_point::*; 78 | 79 | pub use super::geo::*; 80 | -------------------------------------------------------------------------------- /src/bezier/offset.rs: -------------------------------------------------------------------------------- 1 | use super::curve::*; 2 | use super::normal::*; 3 | use super::offset_lms::*; 4 | use super::super::geo::*; 5 | 6 | /// 7 | /// Computes a series of curves that approximate an offset curve from the specified origin curve. 8 | /// 9 | pub fn offset(curve: &Curve, initial_offset: f64, final_offset: f64) -> Vec 10 | where 11 | Curve: BezierCurveFactory+NormalCurve, 12 | Curve::Point: Normalize+Coordinate2D, 13 | { 14 | offset_lms_sampling(curve, move |t| (final_offset - initial_offset) * t + initial_offset, |_| 0.0, 32, 0.1) 15 | .unwrap_or_else(|| vec![]) 16 | } 17 | -------------------------------------------------------------------------------- /src/bezier/offset_lms.rs: -------------------------------------------------------------------------------- 1 | use super::fit::*; 2 | use super::curve::*; 3 | use super::normal::*; 4 | use super::characteristics::*; 5 | use crate::geo::*; 6 | 7 | use smallvec::*; 8 | use std::iter; 9 | 10 | /// 11 | /// Produces an offset curve by performing a least-mean-square curve fit against the output of a function 12 | /// 13 | /// This is about 5x slower than the scaling algorithm with 10 subdivisions (which is a number that seems to 14 | /// produce good results). Too few subdivisions can result in flat sections in the curve, and sharp bends in 15 | /// the curve may require more samples to produce a good result. See `offset_lms_subdivisions` for a version 16 | /// designed to work more effectively with curves with sharp bends in them. 17 | /// 18 | /// Samples are output at the normal of every point on the curve. This produces good results when the offsets 19 | /// do not change much over the length of the curve, but can produce points that are too close to the curve 20 | /// in concave sections if the offsets at the start and end are very different. 21 | /// 22 | /// See also the `DaubBrushDistanceField` struct for an even more general way to generate 'thick' lines, which 23 | /// is not prone to these limitations. 24 | /// 25 | pub fn offset_lms_sampling(curve: &Curve, normal_offset_for_t: NormalOffsetFn, tangent_offset_for_t: TangentOffsetFn, subdivisions: u32, max_error: f64) -> Option> 26 | where 27 | Curve: BezierCurveFactory+NormalCurve, 28 | Curve::Point: Normalize+Coordinate2D, 29 | NormalOffsetFn: Fn(f64) -> f64, 30 | TangentOffsetFn: Fn(f64) -> f64, 31 | { 32 | if subdivisions < 2 { return None; } 33 | 34 | // Subdivide the curve by its major features 35 | let sections: SmallVec<[_; 4]> = match features_for_curve(curve, 0.01) { 36 | CurveFeatures::DoubleInflectionPoint(t1, t2) => { 37 | let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; 38 | let t2 = if t2 > 0.9999 { 1.0 } else if t2 < 0.0001 { 0.0 } else { t2 }; 39 | 40 | if t2 > t1 { 41 | smallvec![(0.0, t1), (t1, t2), (t2, 1.0)] 42 | } else { 43 | smallvec![(0.0, t2), (t2, t1), (t1, 1.0)] 44 | } 45 | } 46 | 47 | CurveFeatures::Loop(t1, t3) => { 48 | let t1 = if t1 > 0.9999 { 1.0 } else if t1 < 0.0001 { 0.0 } else { t1 }; 49 | let t3 = if t3 > 0.9999 { 1.0 } else if t3 < 0.0001 { 0.0 } else { t3 }; 50 | let t2 = (t1+t3)/2.0; 51 | 52 | if t3 > t1 { 53 | smallvec![(0.0, t1), (t1, t2), (t2, t3), (t3, 1.0)] 54 | } else { 55 | smallvec![(0.0, t3), (t3, t2), (t2, t1), (t1, 1.0)] 56 | } 57 | } 58 | 59 | CurveFeatures::SingleInflectionPoint(t) => { 60 | if t > 0.0001 && t < 0.9999 { 61 | smallvec![(0.0, t), (t, 1.0)] 62 | } else { 63 | smallvec![(0.0, 1.0)] 64 | } 65 | } 66 | 67 | _ => { smallvec![(0.0, 1.0)] } 68 | }; 69 | 70 | // Each section is subdivided in turn subdivisions times to produce a set of sample points to fit against 71 | let sections = sections.into_iter() 72 | .filter(|(t1, t2)| t1 != t2) 73 | .flat_map(|(t1, t2)| { 74 | let step = (t2-t1)/(subdivisions as f64); 75 | (0..subdivisions).into_iter().map(move |x| t1 + step * (x as f64)) 76 | }) 77 | .chain(iter::once(1.0)); 78 | 79 | // Take a sample at each point 80 | let sample_points = sections 81 | .map(|t| { 82 | let original_point = curve.point_at_pos(t); 83 | let unit_tangent = curve.tangent_at_pos(t).to_unit_vector(); 84 | let unit_normal = Curve::Point::to_normal(&original_point, &unit_tangent); 85 | let unit_normal = Curve::Point::from_components(&unit_normal); 86 | let normal_offset = normal_offset_for_t(t); 87 | let tangent_offset = tangent_offset_for_t(t); 88 | 89 | original_point + (unit_normal * normal_offset) + (unit_tangent * tangent_offset) 90 | }) 91 | .collect::>(); 92 | 93 | // Generate a curve using the sample points 94 | let start_tangent = curve.tangent_at_pos(0.0).to_unit_vector(); 95 | let end_tangent = curve.tangent_at_pos(1.0).to_unit_vector() * -1.0; 96 | Some(fit_curve_cubic(&sample_points, &start_tangent, &end_tangent, max_error)) 97 | } 98 | -------------------------------------------------------------------------------- /src/bezier/path/algorithms/fill_settings.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Options that affect the fill algorithm 3 | /// 4 | /// The default options are created using `FillOptions::default()`. These can be used to tweak 5 | /// settings like this step size. 6 | /// 7 | #[derive(Clone, Copy, PartialEq, Debug)] 8 | pub struct FillSettings { 9 | /// The distance between one ray and the next 10 | pub (crate) step: f64, 11 | 12 | /// The maximum error to allow when performing curve fitting 13 | pub (crate) fit_error: f64, 14 | 15 | /// For concave fills, the minimum gap size that a fill can escape through 16 | pub (crate) min_gap: Option 17 | } 18 | 19 | impl FillSettings { 20 | /// 21 | /// Creates a new fill options from this one by setting the step 22 | /// 23 | /// The step size defines how accurately the flood-filled region reflects the area defined by the 24 | /// ray-casting function. Higher steps will result in a faster but less accurate result. 25 | /// 26 | pub fn with_step(self, new_step: f64) -> FillSettings { 27 | let mut new_options = self; 28 | new_options.step = new_step; 29 | new_options 30 | } 31 | 32 | /// 33 | /// Creates a new fill options from this one by setting the curve fitting error 34 | /// 35 | /// The curve fitting error indicates how precisely the generated curve fits against the points 36 | /// returned by the ray casting algorithm. Increasing this value reduces the precision of the 37 | /// fit, which may produce a simpler (and smoother) resulting path but which will not necessarily 38 | /// fit the points as well. 39 | /// 40 | pub fn with_fit_error(self, new_fit_error: f64) -> FillSettings { 41 | let mut new_options = self; 42 | new_options.fit_error = new_fit_error; 43 | new_options 44 | } 45 | 46 | /// 47 | /// Sets the minimum gap size that a fill can 'escape' through when moving between regions 48 | /// 49 | /// This makes it possible to fill regions that are not perfectly enclosed 50 | /// 51 | pub fn with_min_gap(self, new_min_gap: Option) -> FillSettings { 52 | let mut new_options = self; 53 | new_options.min_gap = new_min_gap; 54 | new_options 55 | } 56 | } 57 | 58 | impl Default for FillSettings { 59 | /// 60 | /// Creates the default set of fill options 61 | /// 62 | fn default() -> FillSettings { 63 | FillSettings { 64 | step: 2.0, 65 | fit_error: 0.5, 66 | min_gap: Some(5.0) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/bezier/path/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | mod fill_convex; 2 | mod fill_concave; 3 | mod fill_settings; 4 | 5 | pub use self::fill_convex::*; 6 | pub use self::fill_concave::*; 7 | pub use self::fill_settings::*; 8 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/chain.rs: -------------------------------------------------------------------------------- 1 | use super::add::*; 2 | use super::sub::*; 3 | use super::chain_add::*; 4 | use super::intersect::*; 5 | use super::super::path::*; 6 | use super::super::super::super::geo::*; 7 | 8 | /// 9 | /// Description of an arithmetic operation to perform on a bezier path 10 | /// 11 | /// A series of `PathCombine` operations can be used to describe how to build up a path by adding and 12 | /// subtracting solid components. 13 | /// 14 | #[derive(Clone, Debug)] 15 | pub enum PathCombine 16 | where 17 | P::Point : Coordinate+Coordinate2D, 18 | { 19 | /// Sets the result to a particular path 20 | Path(Vec

), 21 | 22 | /// Sets the result to a path with its interior points removed. 23 | /// 24 | /// Everything within the outermost boundary of the path will be removed: the path will be left with no 25 | /// 'holes' in it. This is useful for cleaning up things like paths generated by brush strokes that may 26 | /// self-overlap. See also `path_remove_overlapped_points()` for a way to clean up paths where every edge 27 | /// is intended to be an exterior edge. 28 | /// 29 | /// This can be considered to be a slightly stricter version of the non-zero winding rule: the intention is 30 | /// that `PathCombine::Subtract` is used to cut holes in an existing path with this used to clean up input 31 | /// paths that might be self-overlapping. 32 | RemoveInteriorPoints(Vec

), 33 | 34 | /// Adds a series of paths 35 | Add(Vec>), 36 | 37 | /// Subtracts a series of paths (from the first path) 38 | Subtract(Vec>), 39 | 40 | /// Intersects a series a paths (with the first path) 41 | Intersect(Vec>) 42 | } 43 | 44 | /// 45 | /// Performs a series of path combining operations to generate an output path 46 | /// 47 | pub fn path_combine(operation: PathCombine

, accuracy: f64) -> Vec

48 | where 49 | P::Point: Coordinate+Coordinate2D, 50 | { 51 | // 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 52 | 53 | match operation { 54 | PathCombine::Path(result) => result, 55 | PathCombine::RemoveInteriorPoints(path) => path_remove_interior_points(&path, accuracy), 56 | PathCombine::Add(paths) => path_add_chain(&paths.into_iter().map(|path| path_combine(path, accuracy)).collect(), accuracy), 57 | 58 | PathCombine::Subtract(paths) => { 59 | let mut path_iter = paths.into_iter(); 60 | let result = path_iter.next().unwrap_or_else(|| PathCombine::Path(vec![])); 61 | let mut result = path_combine(result, accuracy); 62 | 63 | for to_subtract in path_iter { 64 | let to_subtract = path_combine(to_subtract, accuracy); 65 | result = path_sub(&result, &to_subtract, accuracy); 66 | } 67 | 68 | result 69 | } 70 | 71 | PathCombine::Intersect(paths) => { 72 | let mut path_iter = paths.into_iter(); 73 | let result = path_iter.next().unwrap_or_else(|| PathCombine::Path(vec![])); 74 | let mut result = path_combine(result, accuracy); 75 | 76 | for to_intersect in path_iter { 77 | let to_intersect = path_combine(to_intersect, accuracy); 78 | result = path_intersect(&result, &to_intersect, accuracy); 79 | } 80 | 81 | result 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/chain_add.rs: -------------------------------------------------------------------------------- 1 | use super::ray_cast::*; 2 | use super::super::path::*; 3 | use super::super::graph_path::*; 4 | use super::super::super::super::geo::*; 5 | 6 | /// 7 | /// Adds multiple paths in a single operation 8 | /// 9 | pub fn path_add_chain(paths: &Vec>>, accuracy: f64) -> Vec 10 | where 11 | POut: BezierPathFactory, 12 | POut::Point: Coordinate+Coordinate2D, 13 | { 14 | // Build up the graph path from the supplied list 15 | let mut merged_path = GraphPath::new(); 16 | 17 | for (path_idx, path) in paths.iter().enumerate() { 18 | let path_idx = path_idx as u32; 19 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path.iter().map(|path| (path, PathLabel(path_idx)))), accuracy); 20 | } 21 | 22 | merged_path.round(accuracy); 23 | 24 | // Set the exterior edges using the 'add' algorithm (all edges are considered 'external' here) 25 | merged_path.set_edge_kinds_by_ray_casting(|path_crossings| { 26 | for count in path_crossings.iter() { 27 | if (count&1) != 0 { 28 | return true; 29 | } 30 | } 31 | 32 | false 33 | }); 34 | merged_path.heal_exterior_gaps(); 35 | 36 | // Produce the final result 37 | merged_path.exterior_paths() 38 | } 39 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/cut.rs: -------------------------------------------------------------------------------- 1 | use super::ray_cast::*; 2 | use super::super::path::*; 3 | use super::super::graph_path::*; 4 | use super::super::super::super::geo::*; 5 | 6 | /// 7 | /// The result of a path cut operation 8 | /// 9 | pub struct PathCut { 10 | /// The path that was inside the 'cut' path 11 | pub interior_path: Vec

, 12 | 13 | /// The path that was outside of the 'cut' path 14 | pub exterior_path: Vec

15 | } 16 | 17 | /// 18 | /// Cuts a path (`path1`) into two along another path (`path2`), returning the part of `path1` that was interior to `path2` and 19 | /// the part that was exterior in one operation 20 | /// 21 | pub fn path_cut(path1: &Vec>, path2: &Vec>, accuracy: f64) -> PathCut 22 | where 23 | POut: BezierPathFactory, 24 | POut::Point: Coordinate+Coordinate2D, 25 | { 26 | // If path1 is empty, then there are no points in the result. If path2 is empty, then all points are exterior 27 | if path1.is_empty() { 28 | return PathCut { 29 | interior_path: vec![], 30 | exterior_path: vec![] 31 | }; 32 | } else if path2.is_empty() { 33 | return PathCut { 34 | interior_path: vec![], 35 | exterior_path: path1.iter().map(|path| POut::from_path(path)).collect() 36 | }; 37 | } 38 | 39 | // Create the graph path from the source side 40 | let mut merged_path = GraphPath::new(); 41 | merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(0))))); 42 | 43 | // Collide with the target side to generate a full path 44 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(1)))), accuracy); 45 | merged_path.round(accuracy); 46 | 47 | // The interior edges are those found by intersecting the second path with the first 48 | merged_path.set_exterior_by_intersecting(); 49 | merged_path.heal_exterior_gaps(); 50 | 51 | // Fetch the interior path 52 | let interior_path = merged_path.exterior_paths(); 53 | 54 | // TODO: we can use the same raycasting operation to detect the interior and exterior points simultaneously but the current design 55 | // doesn't allow us to represent this in the data for the edges (this would speed up the 'cut' operation as only half the ray-casting 56 | // operations would be required, though note that the merge and collide operation is likely to be more expensive than this overall) 57 | 58 | // The exterior edges are those found by subtracting the second path from the first 59 | merged_path.reset_edge_kinds(); 60 | merged_path.set_exterior_by_subtracting(); 61 | merged_path.heal_exterior_gaps(); 62 | 63 | // Fetch the exterior path 64 | let exterior_path = merged_path.exterior_paths(); 65 | 66 | PathCut { 67 | interior_path, 68 | exterior_path 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/full_intersect.rs: -------------------------------------------------------------------------------- 1 | use super::ray_cast::*; 2 | use super::super::path::*; 3 | use super::super::graph_path::*; 4 | use super::super::super::super::geo::*; 5 | 6 | /// 7 | /// The result of a path cut operation 8 | /// 9 | #[derive(Clone, Debug)] 10 | pub struct PathIntersection { 11 | /// The path that was intersecting between the two paths 12 | pub intersecting_path: Vec

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

; 2] 16 | } 17 | 18 | /// 19 | /// Intersects two paths, returning both the path that is the intersection and the paths that are outside 20 | /// 21 | /// Each of the two paths passed into this function is assumed not to overlap themselves. IE, this does not perform self-intersection 22 | /// on either `path1` or `path2`. This provides both a performance optimisation and finer control over how self-intersecting paths are 23 | /// handled. See `path_remove_interior_points()` and `path_remove_overlapped_points()` for a way to eliminate overlaps. 24 | /// 25 | pub fn path_full_intersect(path1: &Vec>, path2: &Vec>, accuracy: f64) -> PathIntersection 26 | where 27 | POut: BezierPathFactory, 28 | POut::Point: Coordinate+Coordinate2D 29 | { 30 | // If path1 is empty, then there are no points in the result. If path2 is empty, then all points are exterior 31 | if path1.is_empty() { 32 | return PathIntersection { 33 | intersecting_path: vec![], 34 | exterior_paths: [vec![], path2.iter().map(|path| POut::from_path(path)).collect()] 35 | }; 36 | } else if path2.is_empty() { 37 | return PathIntersection { 38 | intersecting_path: vec![], 39 | exterior_paths: [path1.iter().map(|path| POut::from_path(path)).collect(), vec![]] 40 | }; 41 | } 42 | 43 | // Create the graph path from the source side 44 | let mut merged_path = GraphPath::new(); 45 | merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(0))))); 46 | 47 | // Collide with the target side to generate a full path 48 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(1)))), accuracy); 49 | merged_path.round(accuracy); 50 | 51 | // The interior edges are those found by intersecting the second path with the first 52 | merged_path.set_exterior_by_intersecting(); 53 | merged_path.heal_exterior_gaps(); 54 | 55 | // Fetch the interior path 56 | let intersecting_path = merged_path.exterior_paths(); 57 | 58 | // TODO: we can use the same raycasting operation to detect the interior and exterior points simultaneously but the current design 59 | // doesn't allow us to represent this in the data for the edges (this would speed up the 'cut' operation as only half the ray-casting 60 | // operations would be required, though note that the merge and collide operation is likely to be more expensive than this overall) 61 | 62 | // The exterior edges are those found by subtracting the second path from the first 63 | merged_path.reset_edge_kinds(); 64 | merged_path.set_exterior_by_subtracting(); 65 | merged_path.heal_exterior_gaps(); 66 | 67 | // This will be the part of path 1 that excludes path 2 68 | let exterior_from_path_1 = merged_path.exterior_paths(); 69 | 70 | // Invert the subtraction operation 71 | // TODO: it would be faster to re-use the existing merged paths here, but this will fail to properly generate a subtracted paths 72 | // in the case where edges of the two paths overlap. 73 | let mut merged_path = GraphPath::new(); 74 | merged_path = merged_path.merge(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(0))))); 75 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(1)))), accuracy); 76 | merged_path.round(accuracy); 77 | 78 | merged_path.set_exterior_by_subtracting(); 79 | merged_path.heal_exterior_gaps(); 80 | 81 | // This will be the part of path 2 that excludes path1 82 | let exterior_from_path_2 = merged_path.exterior_paths(); 83 | 84 | PathIntersection { 85 | intersecting_path: intersecting_path, 86 | exterior_paths: [exterior_from_path_1, exterior_from_path_2] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/intersect.rs: -------------------------------------------------------------------------------- 1 | use super::ray_cast::*; 2 | use super::super::path::*; 3 | use super::super::graph_path::*; 4 | use super::super::super::super::geo::*; 5 | 6 | impl GraphPath { 7 | /// 8 | /// Given a labelled graph path, marks exterior edges by intersecting `PathSource::Path1` and `PathSource::Path2` 9 | /// 10 | pub fn set_exterior_by_intersecting(&mut self) { 11 | // Use an even-odd winding rule (all edges are considered 'external') 12 | self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 && (path_crossings[1]&1) != 0); 13 | } 14 | } 15 | 16 | /// 17 | /// Generates the path formed by intersecting two sets of paths 18 | /// 19 | /// Each of the two paths passed into this function is assumed not to overlap themselves. IE, this does not perform self-intersection 20 | /// on either `path1` or `path2`. This provides both a performance optimisation and finer control over how self-intersecting paths are 21 | /// handled. See `path_remove_interior_points()` and `path_remove_overlapped_points()` for a way to eliminate overlaps. 22 | /// 23 | /// 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 24 | /// effectively represents a path intended to be rendered with an even-odd winding rule) 25 | /// 26 | pub fn path_intersect(path1: &Vec>, path2: &Vec>, accuracy: f64) -> Vec 27 | where 28 | POut: BezierPathFactory, 29 | POut::Point: Coordinate+Coordinate2D 30 | { 31 | // If either path is empty, short-circuit by returning the other 32 | if path1.is_empty() { 33 | return path2.iter() 34 | .map(|path| POut::from_path(path)) 35 | .collect(); 36 | } else if path2.is_empty() { 37 | return path1.iter() 38 | .map(|path| POut::from_path(path)) 39 | .collect(); 40 | } 41 | 42 | // Create the graph path from the source side 43 | let mut merged_path = GraphPath::new(); 44 | merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(0))))); 45 | 46 | // Collide with the target side to generate a full path 47 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(1)))), accuracy); 48 | merged_path.round(accuracy); 49 | 50 | // Set the exterior edges using the 'intersect' algorithm 51 | merged_path.set_exterior_by_intersecting(); 52 | merged_path.heal_exterior_gaps(); 53 | 54 | // Produce the final result 55 | merged_path.exterior_paths() 56 | } 57 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/mod.rs: -------------------------------------------------------------------------------- 1 | mod ray_cast; 2 | mod intersect; 3 | mod add; 4 | mod chain_add; 5 | mod sub; 6 | mod chain; 7 | mod cut; 8 | mod full_intersect; 9 | 10 | pub use self::ray_cast::*; 11 | pub use self::intersect::*; 12 | pub use self::add::*; 13 | pub use self::sub::*; 14 | pub use self::chain::*; 15 | pub use self::chain_add::*; 16 | pub use self::cut::*; 17 | pub use self::full_intersect::*; 18 | -------------------------------------------------------------------------------- /src/bezier/path/arithmetic/sub.rs: -------------------------------------------------------------------------------- 1 | use super::ray_cast::*; 2 | use super::super::path::*; 3 | use super::super::graph_path::*; 4 | use super::super::super::super::geo::*; 5 | 6 | impl GraphPath { 7 | /// 8 | /// Given a labelled graph path, marks exterior edges by subtracting `PathSource::Path2` from `PathSource::Path1` 9 | /// 10 | pub fn set_exterior_by_subtracting(&mut self) { 11 | // Use an even-odd winding rule (all edges are considered 'external') 12 | self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 && (path_crossings[1]&1) == 0); 13 | } 14 | } 15 | 16 | /// 17 | /// Generates the path formed by subtracting two sets of paths 18 | /// 19 | /// Each of the two paths passed into this function is assumed not to overlap themselves. IE, this does not perform self-intersection 20 | /// on either `path1` or `path2`. This provides both a performance optimisation and finer control over how self-intersecting paths are 21 | /// handled. See `path_remove_interior_points()` and `path_remove_overlapped_points()` for a way to eliminate overlaps. 22 | /// 23 | /// 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 24 | /// effectively represents a path intended to be rendered with an even-odd winding rule) 25 | /// 26 | pub fn path_sub(path1: &Vec>, path2: &Vec>, accuracy: f64) -> Vec 27 | where 28 | POut: BezierPathFactory, 29 | POut::Point: Coordinate+Coordinate2D, 30 | { 31 | // If either path is empty, short-circuit by returning the other 32 | if path1.is_empty() { 33 | return path2.iter() 34 | .map(|path| POut::from_path(path)) 35 | .collect(); 36 | } else if path2.is_empty() { 37 | return path1.iter() 38 | .map(|path| POut::from_path(path)) 39 | .collect(); 40 | } 41 | 42 | // Create the graph path from the source side 43 | let mut merged_path = GraphPath::new(); 44 | merged_path = merged_path.merge(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(0))))); 45 | 46 | // Collide with the target side to generate a full path 47 | merged_path = merged_path.collide(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(1)))), accuracy); 48 | merged_path.round(accuracy); 49 | 50 | // Set the exterior edges using the 'subtract' algorithm 51 | merged_path.set_exterior_by_subtracting(); 52 | merged_path.heal_exterior_gaps(); 53 | 54 | // Produce the final result 55 | merged_path.exterior_paths() 56 | } 57 | -------------------------------------------------------------------------------- /src/bezier/path/bounds.rs: -------------------------------------------------------------------------------- 1 | use super::path::*; 2 | use super::to_curves::*; 3 | use super::super::curve::*; 4 | use super::super::super::geo::*; 5 | 6 | /// 7 | /// Finds the bounds of a path 8 | /// 9 | pub fn path_bounding_box>(path: &P) -> Bounds { 10 | path_to_curves(path) 11 | .map(|curve: Curve| curve.bounding_box()) 12 | .reduce(|first: Bounds, second| first.union_bounds(second)) 13 | .unwrap_or_else(|| Bounds::from_min_max(P::Point::origin(), P::Point::origin())) 14 | } 15 | 16 | /// 17 | /// Finds the bounds of a path using the looser 'fast' algorithm 18 | /// 19 | pub fn path_fast_bounding_box>(path: &P) -> Bounds { 20 | path_to_curves(path) 21 | .map(|curve: Curve| curve.fast_bounding_box()) 22 | .reduce(|first: Bounds, second| first.union_bounds(second)) 23 | .unwrap_or_else(|| Bounds::from_min_max(P::Point::origin(), P::Point::origin())) 24 | } 25 | -------------------------------------------------------------------------------- /src/bezier/path/graph_path/edge_ref.rs: -------------------------------------------------------------------------------- 1 | use super::{GraphPath, GraphEdge, GraphEdgeRef}; 2 | use crate::geo::*; 3 | 4 | impl GraphEdgeRef { 5 | /// 6 | /// Creates a reversed version of this edge ref 7 | /// 8 | pub fn reversed(mut self) -> GraphEdgeRef { 9 | self.reverse = !self.reverse; 10 | self 11 | } 12 | } 13 | 14 | /// 15 | /// A GraphEdgeRef can be created from a GraphEdge in order to release the borrow 16 | /// 17 | impl<'a, Point: 'a+Coordinate, Label: 'a+Copy> From> for GraphEdgeRef { 18 | fn from(edge: GraphEdge<'a, Point, Label>) -> GraphEdgeRef { 19 | edge.edge 20 | } 21 | } 22 | 23 | /// 24 | /// A GraphEdgeRef can be created from a GraphEdge in order to release the borrow 25 | /// 26 | impl<'a, 'b, Point: 'a+Coordinate, Label: 'a+Copy> From<&'b GraphEdge<'a, Point, Label>> for GraphEdgeRef { 27 | fn from(edge: &'b GraphEdge<'a, Point, Label>) -> GraphEdgeRef { 28 | edge.edge 29 | } 30 | } 31 | 32 | impl GraphPath { 33 | /// 34 | /// Given an edge ref, returns the edge ref that follows it 35 | /// 36 | #[inline] 37 | pub fn following_edge_ref(&self, edge_ref: GraphEdgeRef) -> GraphEdgeRef { 38 | if edge_ref.reverse { 39 | // Need to search in reverse for the edge 40 | for connected_from in self.points[edge_ref.start_idx].connected_from.iter() { 41 | for (edge_idx, edge) in self.points[*connected_from].forward_edges.iter().enumerate() { 42 | if edge.end_idx == edge_ref.start_idx { 43 | return GraphEdgeRef { 44 | start_idx: *connected_from, 45 | edge_idx: edge_idx, 46 | reverse: true 47 | } 48 | } 49 | } 50 | } 51 | 52 | panic!("Reverse edge could not be found") 53 | } else { 54 | // Can just use the following edge 55 | let edge = &self.points[edge_ref.start_idx].forward_edges[edge_ref.edge_idx]; 56 | 57 | GraphEdgeRef { 58 | start_idx: edge.end_idx, 59 | edge_idx: edge.following_edge_idx, 60 | reverse: false 61 | } 62 | } 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/bezier/path/graph_path/ray_collision.rs: -------------------------------------------------------------------------------- 1 | use super::{GraphPath,GraphEdge,GraphEdgeRef}; 2 | use crate::bezier::path::ray::*; 3 | use crate::geo::*; 4 | use crate::line::*; 5 | 6 | use smallvec::*; 7 | 8 | /// 9 | /// Represents a collision between a ray and a GraphPath 10 | /// 11 | #[derive(Clone, Copy, Debug, PartialEq)] 12 | pub enum GraphRayCollision { 13 | /// Collision against a single edge 14 | SingleEdge(GraphEdgeRef), 15 | 16 | /// Collision against an intersection point 17 | Intersection(GraphEdgeRef) 18 | } 19 | 20 | impl GraphPath { 21 | /// 22 | /// Finds all collisions between a ray and this path 23 | /// 24 | /// The return value is a tuple of (collision, curve_t, line_t, position) 25 | /// 26 | pub fn ray_collisions>(&self, ray: &L) -> Vec<(GraphRayCollision, f64, f64, Point)> { 27 | ray_collisions(&self, ray) 28 | } 29 | } 30 | 31 | impl GraphRayCollision { 32 | /// 33 | /// Returns true if this collision is at an intersection 34 | /// 35 | #[inline] 36 | pub fn is_intersection(&self) -> bool { 37 | match self { 38 | GraphRayCollision::SingleEdge(_) => false, 39 | GraphRayCollision::Intersection(_edges) => true 40 | } 41 | } 42 | 43 | /// 44 | /// Returns the edge this collision is for 45 | /// 46 | #[inline] 47 | pub fn edge(&self) -> GraphEdgeRef { 48 | match self { 49 | GraphRayCollision::SingleEdge(edge) => *edge, 50 | GraphRayCollision::Intersection(edge) => *edge, 51 | } 52 | } 53 | } 54 | 55 | impl<'a, Point, Label> RayPath for &'a GraphPath 56 | where 57 | Point: Coordinate+Coordinate2D, 58 | Label: Copy 59 | { 60 | type Point = Point; 61 | type Curve = GraphEdge<'a, Point, Label>; 62 | 63 | #[inline] fn num_points(&self) -> usize { self.points.len() } 64 | 65 | #[inline] fn num_edges(&self, point_idx: usize) -> usize { self.points[point_idx].forward_edges.len() } 66 | 67 | #[inline] fn edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { 68 | let num_edges = self.points[point_idx].forward_edges.len(); 69 | (0..num_edges).into_iter() 70 | .map(move |edge_idx| GraphEdgeRef { start_idx: point_idx, edge_idx: edge_idx, reverse: false }) 71 | .collect() 72 | } 73 | 74 | #[inline] fn reverse_edges_for_point(&self, point_idx: usize) -> SmallVec<[GraphEdgeRef; 8]> { 75 | self.points[point_idx].connected_from.iter() 76 | .flat_map(|connected_point_idx| { 77 | let num_edges = self.points[*connected_point_idx].forward_edges.len(); 78 | 79 | (0..num_edges).into_iter() 80 | .filter(move |edge_idx| self.points[*connected_point_idx].forward_edges[*edge_idx].end_idx == point_idx) 81 | .map(move |edge_idx| GraphEdgeRef { start_idx: *connected_point_idx, edge_idx: edge_idx, reverse: true }) 82 | }) 83 | .collect() 84 | } 85 | 86 | #[inline] fn get_edge(&self, edge: GraphEdgeRef) -> Self::Curve { 87 | GraphEdge { graph: *self, edge: edge } 88 | } 89 | 90 | #[inline] fn get_next_edge(&self, edge: GraphEdgeRef) -> (GraphEdgeRef, Self::Curve) { 91 | let next_point_idx = self.edge_end_point_idx(edge); 92 | let next_edge_idx = self.edge_following_edge_idx(edge); 93 | 94 | let next_edge_ref = GraphEdgeRef { start_idx: next_point_idx, edge_idx: next_edge_idx, reverse: edge.reverse }; 95 | 96 | (next_edge_ref, self.get_edge(next_edge_ref)) 97 | } 98 | 99 | #[inline] fn point_position(&self, point: usize) -> Self::Point { 100 | self.points[point].position 101 | } 102 | 103 | #[inline] fn edge_start_point_idx(&self, edge: GraphEdgeRef) -> usize { 104 | if edge.reverse { 105 | self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx 106 | } else { 107 | edge.start_idx 108 | } 109 | } 110 | 111 | #[inline] fn edge_end_point_idx(&self, edge: GraphEdgeRef) -> usize { 112 | if edge.reverse { 113 | edge.start_idx 114 | } else { 115 | self.points[edge.start_idx].forward_edges[edge.edge_idx].end_idx 116 | } 117 | } 118 | 119 | #[inline] fn edge_following_edge_idx(&self, edge: GraphEdgeRef) -> usize { 120 | if edge.reverse { 121 | unimplemented!("Finding the following edge for a reversed reference not implemented yet") 122 | } else { 123 | self.points[edge.start_idx].forward_edges[edge.edge_idx].following_edge_idx 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/bezier/path/intersection.rs: -------------------------------------------------------------------------------- 1 | use super::path::*; 2 | use super::to_curves::*; 3 | use super::super::curve::*; 4 | use super::super::intersection::*; 5 | use super::super::super::geo::*; 6 | use super::super::super::line::*; 7 | 8 | /// 9 | /// Determines the intersections of a path and a line 10 | /// 11 | /// Intersections are returned as the path section index, the 't' parameter along that curve and the 't' value along the line: 12 | /// ie: `(path_point_idx, curve_t, line_t)`. 13 | /// 14 | pub fn path_intersects_line<'a, Path: BezierPath, L: Line>(path: &'a Path, line: &'a L) -> impl 'a+Iterator 15 | where 16 | Path::Point: 'a+Coordinate2D, 17 | { 18 | path_to_curves::<_, Curve<_>>(path) 19 | .enumerate() 20 | .flat_map(move |(section_id, curve)| curve_intersects_line(&curve, line).into_iter().map(move |(t, s, _pos)| (section_id, t, s))) 21 | } 22 | 23 | /// 24 | /// Determines the intersections of a path and a ray. 25 | /// 26 | /// Return value is `(path_point_idx, curve_t, line_t)`. Ray intersections differ from line intersections 27 | /// 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 28 | /// returned). 29 | /// 30 | /// It's possible to filter for matches that occur after the start of the line by looking for results with an `s` value >= 0 31 | /// 32 | pub fn path_intersects_ray<'a, Path: BezierPath, L: Line>(path: &'a Path, line: &'a L) -> impl 'a+Iterator 33 | where 34 | Path::Point: 'a+Coordinate2D, 35 | { 36 | path_to_curves::<_, Curve<_>>(path) 37 | .enumerate() 38 | .flat_map(move |(section_id, curve)| curve_intersects_line(&curve, line).into_iter().map(move |(t, s, _pos)| (section_id, t, s))) 39 | } 40 | 41 | /// 42 | /// Finds the points where a path intersects another path 43 | /// 44 | /// Intersections are returned as (segment index, t-value), in pairs indicating the position on the first path 45 | /// and the position on the second path. Intersections are unordered by default. 46 | /// 47 | /// The accuracy value indicates the maximum errors that's permitted for an intersection: the bezier curve 48 | /// intersection algorithm is approximate. 49 | /// 50 | pub fn path_intersects_path<'a, Path: BezierPath>(path1: &'a Path, path2: &'a Path, accuracy: f64) -> Vec<((usize, f64), (usize, f64))> 51 | where 52 | Path::Point: 'a+Coordinate2D, 53 | { 54 | // Convert both paths to sections: also compute the bounding boxes for quick rejection of sections with no intersections 55 | let path1_sections = path_to_curves::<_, Curve<_>>(path1) 56 | .enumerate() 57 | .map(|(section_id, curve)| (section_id, curve, curve.bounding_box::>())); 58 | 59 | let path2_sections = path_to_curves::<_, Curve<_>>(path2) 60 | .enumerate() 61 | .map(|(section_id, curve)| (section_id, curve, curve.bounding_box::>())) 62 | .collect::>(); 63 | 64 | // Start generating the result 65 | let mut result = vec![]; 66 | 67 | // Compare the sections in path1 to the sections in path2 68 | // We iterate over path1 once... 69 | for (p1_section_id, p1_curve, p1_curve_bounds) in path1_sections { 70 | // But repeatedly interate over path2 71 | for (p2_section_id, p2_curve, p2_curve_bounds) in path2_sections.iter() { 72 | // Only search for intersections if these two sections have overlapping bounding boxes 73 | if p1_curve_bounds.overlaps(p2_curve_bounds) { 74 | // Determine the intersections (if any) between these two curves 75 | let intersections = curve_intersects_curve_clip(&p1_curve, p2_curve, accuracy); 76 | 77 | // Combine with the section IDs to generate the results 78 | result.extend(intersections.into_iter().map(|(t1, t2)| ((p1_section_id, t1), (*p2_section_id, t2)) )); 79 | } 80 | } 81 | } 82 | 83 | result 84 | } 85 | -------------------------------------------------------------------------------- /src/bezier/path/is_clockwise.rs: -------------------------------------------------------------------------------- 1 | use super::path::*; 2 | use super::super::super::geo::*; 3 | 4 | use itertools::*; 5 | 6 | /// 7 | /// Determines if a set of points are in a clockwise ordering (assuming that a positive y value indicates an upwards direction) 8 | /// 9 | pub fn points_are_clockwise>(mut points: PointIter) -> bool { 10 | // Technique suggested in https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order 11 | let mut total = 0.0; 12 | 13 | // The first point needs to be repeated at the end of the sequence 14 | let first_point = points.next(); 15 | if let Some(first_point) = first_point { 16 | let points = vec![first_point].into_iter().chain(points).chain(vec![first_point].into_iter()); 17 | 18 | // Sum over the edges to determine if the points are clockwise 19 | for (start, end) in points.tuple_windows() { 20 | total += (end.x()-start.x()) * (end.y()+start.y()); 21 | } 22 | } 23 | 24 | total >= 0.0 25 | } 26 | 27 | /// 28 | /// Trait implemented by paths that can determine if their points are in a clockwise ordering or not 29 | /// 30 | pub trait PathWithIsClockwise { 31 | /// 32 | /// Determines if this path is ordered in a clockwise direction 33 | /// 34 | fn is_clockwise(&self) -> bool; 35 | } 36 | 37 | impl PathWithIsClockwise for P 38 | where 39 | P::Point: Coordinate+Coordinate2D, 40 | { 41 | #[inline] 42 | fn is_clockwise(&self) -> bool { 43 | points_are_clockwise(self.points().map(|(_cp1, _cp2, p)| p)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/bezier/path/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Manipulates multiple Bezier curves joined into a path 3 | //! 4 | //! ``` 5 | //! # use flo_curves::*; 6 | //! # use flo_curves::arc::*; 7 | //! # use flo_curves::bezier; 8 | //! # use flo_curves::bezier::path::*; 9 | //! # 10 | //! let rectangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 11 | //! .line_to(Coord2(5.0, 1.0)) 12 | //! .line_to(Coord2(5.0, 5.0)) 13 | //! .line_to(Coord2(1.0, 5.0)) 14 | //! .line_to(Coord2(1.0, 1.0)) 15 | //! .build(); 16 | //! let circle = Circle::new(Coord2(3.0, 3.0), 1.0).to_path::(); 17 | //! 18 | //! let rectangle_with_hole = path_sub::(&vec![rectangle], &vec![circle], 0.01); 19 | //! ``` 20 | //! 21 | //! Anything that implements the `BezierPath` trait can be treated as a path. The `SimpleBezierPath` type is provided 22 | //! as a convenient default implementation of this trait. These paths represent a single perimeter of a region. 23 | //! 24 | //! The arithmetic operations such as `path_sub()`, `path_add()`, `path_intersect()` all work with collections of these 25 | //! perimeters, stored in a `Vec`. A path with a hole in the middle will have two perimeters, for example. 26 | //! 27 | //! These perimeters must not be self-intersecting: `flo_curves` doesn't use a winding rule as such but instead considers 28 | //! all edges to be exterior edges (which is very similar to an even-odd winding rule). A couple of methods are provided 29 | //! for fixing paths with self-intersections: `path_remove_interior_points()` will find the outermost perimeter of a shape - 30 | //! which is useful for tidying up the subpaths. `path_remove_overlapped_points()` will combine subpaths so that 31 | //! there are no overlapping edges. These two functions provide much finer control than is possible through the traditional 32 | //! idea of the winding rule. 33 | //! 34 | //! There are a few more advanced algorithms: for example, the `flood_fill_concave()` function provides a vector 35 | //! implementation of the flood fill algorithm, returning a path that fills a space defined by a ray-casting function. 36 | //! 37 | 38 | mod path; 39 | mod to_curves; 40 | mod ray; 41 | mod point; 42 | mod bounds; 43 | mod intersection; 44 | mod path_builder; 45 | mod graph_path; 46 | mod is_clockwise; 47 | mod arithmetic; 48 | mod stroke; 49 | pub mod algorithms; 50 | 51 | pub use self::path::*; 52 | pub use self::to_curves::*; 53 | pub use self::point::*; 54 | pub use self::bounds::*; 55 | pub use self::intersection::*; 56 | pub use self::path_builder::*; 57 | pub use self::graph_path::*; 58 | pub use self::is_clockwise::*; 59 | pub use self::arithmetic::*; 60 | pub use self::stroke::*; 61 | -------------------------------------------------------------------------------- /src/bezier/path/path_builder.rs: -------------------------------------------------------------------------------- 1 | use super::path::*; 2 | 3 | /// 4 | /// Used to build a bezier path 5 | /// 6 | pub struct BezierPathBuilder { 7 | /// Where the path starts 8 | start_point: P::Point, 9 | 10 | /// The points in the path 11 | points: Vec<(P::Point, P::Point, P::Point)> 12 | } 13 | 14 | impl BezierPathBuilder

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

{ 19 | BezierPathBuilder { 20 | start_point: start, 21 | points: vec![] 22 | } 23 | } 24 | 25 | /// 26 | /// Builds the path for this builder 27 | /// 28 | pub fn build(self) -> P { 29 | P::from_points(self.start_point, self.points) 30 | } 31 | 32 | /// 33 | /// Adds a line to the specified point 34 | /// 35 | pub fn line_to(mut self, point: P::Point) -> Self { 36 | // Get the vector from the last point to the new point 37 | let distance = if self.points.is_empty() { 38 | point - self.start_point 39 | } else { 40 | point - self.points[self.points.len()-1].2 41 | }; 42 | 43 | // A line puts control points at 33% and 66% of the distance 44 | let cp1 = point - (distance*0.6666); 45 | let cp2 = point - (distance*0.3333); 46 | 47 | self.points.push((cp1, cp2, point)); 48 | 49 | self 50 | } 51 | 52 | /// 53 | /// Adds a curve to a particular point 54 | /// 55 | pub fn curve_to(mut self, (cp1, cp2): (P::Point, P::Point), end_point: P::Point) -> Self { 56 | self.points.push((cp1, cp2, end_point)); 57 | 58 | self 59 | } 60 | } -------------------------------------------------------------------------------- /src/bezier/path/to_curves.rs: -------------------------------------------------------------------------------- 1 | use super::path::*; 2 | use super::super::curve::*; 3 | 4 | use itertools::*; 5 | 6 | /// 7 | /// Converts a path to a series of bezier curves 8 | /// 9 | pub fn path_to_curves>(path: &Path) -> impl Iterator { 10 | let just_start_point = vec![(path.start_point(), path.start_point(), path.start_point())].into_iter(); 11 | let points = path.points(); 12 | 13 | just_start_point.chain(points) 14 | .tuple_windows() 15 | .map(|((_, _, start_point), (cp1, cp2, end_point))| { 16 | Curve::from_points(start_point, (cp1, cp2), end_point) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/bezier/rasterize/create_distance_field.rs: -------------------------------------------------------------------------------- 1 | use crate::bezier::vectorize::*; 2 | 3 | /// 4 | /// Creates a distance field from a function providing a distance at a point 5 | /// 6 | /// This is the slowest way to create a distance field in most instances 7 | /// 8 | pub fn create_distance_field(signed_distance_at_point: impl Fn(f64, f64) -> f64, size: ContourSize) -> F64SampledDistanceField { 9 | let width = size.width(); 10 | let height = size.height(); 11 | 12 | let samples = (0..width*height) 13 | .map(|pixel| { 14 | let x = pixel % width; 15 | let y = pixel / width; 16 | 17 | signed_distance_at_point(x as _, y as _) 18 | }) 19 | .collect(); 20 | 21 | F64SampledDistanceField(size, samples) 22 | } 23 | -------------------------------------------------------------------------------- /src/bezier/rasterize/mod.rs: -------------------------------------------------------------------------------- 1 | mod path_contour; 2 | mod path_distance_field; 3 | mod ray_cast_contour; 4 | mod create_distance_field; 5 | mod marching_parabolas; 6 | mod marching_parabola_distance_field; 7 | 8 | pub use ray_cast_contour::*; 9 | pub use path_contour::*; 10 | pub use path_distance_field::*; 11 | pub use create_distance_field::*; 12 | pub use marching_parabola_distance_field::*; 13 | -------------------------------------------------------------------------------- /src/bezier/rasterize/path_distance_field.rs: -------------------------------------------------------------------------------- 1 | use super::path_contour::*; 2 | use super::marching_parabola_distance_field::*; 3 | use crate::geo::*; 4 | use crate::bezier::path::*; 5 | use crate::bezier::vectorize::*; 6 | 7 | use std::sync::*; 8 | 9 | /// 10 | /// Approximates a distance field generated from a path 11 | /// 12 | #[derive(Clone)] 13 | pub struct PathDistanceField { 14 | path_contour: Arc, 15 | distance_field: Arc, 16 | } 17 | 18 | impl PathDistanceField { 19 | /// 20 | /// Creates a (approximated) distance field from a bezier path 21 | /// 22 | pub fn from_path(path: Vec, size: ContourSize) -> Self 23 | where 24 | TPath: 'static + BezierPath, 25 | TPath::Point: Coordinate + Coordinate2D, 26 | { 27 | // The path contour can be used both as the actual path contour and as a way to determine if a point is inside the path 28 | let path_contour = PathContour::from_path(path, size); 29 | let path_contour = Arc::new(path_contour); 30 | 31 | // Compute the distance field using the marching parabolas algorithm 32 | let marching_parabolas = MarchingParabolaDistanceField::from_intercepts(size.0, size.1, 33 | |x| path_contour.intercepts_on_column(x), 34 | |y| path_contour.intercepts_on_line(y)); 35 | let distance_field = Arc::new(marching_parabolas); 36 | 37 | PathDistanceField { path_contour, distance_field } 38 | } 39 | 40 | /// 41 | /// Creates a distance field that has the specified path at the center 42 | /// 43 | /// The coordinate returned is the offset of the resulting distance field (add to the coordinates to get the coordinates on the original path) 44 | /// 45 | pub fn center_path(path: Vec, border: usize) -> (Self, TPath::Point) 46 | where 47 | TPath: 'static + BezierPath + BezierPathFactory, 48 | TPath::Point: Coordinate + Coordinate2D, 49 | { 50 | // Figure out the bounding box of the path 51 | let bounds = path.iter() 52 | .map(|subpath| subpath.bounding_box::>()) 53 | .reduce(|a, b| a.union_bounds(b)) 54 | .unwrap_or_else(|| Bounds::empty()); 55 | 56 | // Offset is the lower-left corner of the bounding box 57 | let border = TPath::Point::from_components(&[border as f64, border as f64]); 58 | let offset = bounds.min() - border; 59 | let size = bounds.max() - bounds.min(); 60 | let size = size + (border * 2.0); 61 | 62 | // Allow a 1px border around the path 63 | let offset = offset - TPath::Point::from_components(&[1.0, 1.0]); 64 | 65 | // Move the path so that its lower bound is at 1,1 66 | let mut path = path; 67 | path.iter_mut().for_each(|subpath| { 68 | let new_subpath = subpath.map_points(|p| p - offset); 69 | *subpath = new_subpath; 70 | }); 71 | 72 | // The size of the distance field is the size of the path with a 2px border 73 | let width = size.x().ceil() + 2.0; 74 | let height = size.y().ceil() + 2.0; 75 | let size = ContourSize(width as _, height as _); 76 | 77 | // Create the distance field 78 | let distance_field = Self::from_path(path, size); 79 | 80 | (distance_field, offset) 81 | } 82 | } 83 | 84 | impl SampledSignedDistanceField for PathDistanceField { 85 | type Contour = PathContour; 86 | 87 | #[inline] 88 | fn field_size(&self) -> ContourSize { 89 | self.path_contour.contour_size() 90 | } 91 | 92 | #[inline] 93 | fn distance_at_point(&self, pos: ContourPosition) -> f64 { 94 | self.distance_field.distance_at_point(pos) 95 | } 96 | 97 | #[inline] 98 | fn as_contour<'a>(&'a self) -> &'a Self::Contour { 99 | &self.path_contour 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/bezier/rasterize/ray_cast_contour.rs: -------------------------------------------------------------------------------- 1 | use crate::bezier::vectorize::*; 2 | 3 | use smallvec::*; 4 | 5 | use std::ops::{Range}; 6 | 7 | /// 8 | /// Provides an implementation of `SampledContour` derived from a ray-casting function that 9 | /// provides the intercepts along any y position 10 | /// 11 | pub struct RayCastContour 12 | where 13 | TFn: Fn(f64) -> SmallVec<[Range; 4]> 14 | { 15 | /// Function that maps a y position to the intercepts on the x axis 16 | intercept_fn: TFn, 17 | 18 | /// Size of the contour to return from this function 19 | size: ContourSize, 20 | 21 | /// Scale factor used to convert y positions into positions to pass to the intercept function 22 | scale_factor: f64, 23 | } 24 | 25 | impl RayCastContour 26 | where 27 | TFn: Fn(f64) -> SmallVec<[Range; 4]>, 28 | { 29 | /// 30 | /// Creates a new ray-cast contour 31 | /// 32 | /// The `intercept_fn` defines where the edges of the contour are by finding the ranges that are inside the contour at a 33 | /// given y position. The size indicates the range of x and y positions that can be generated by the contour. 34 | /// 35 | /// The function must return the intercepts in ascending x order. 36 | /// 37 | #[inline] 38 | pub fn new(intercept_fn: TFn, size: ContourSize) -> Self { 39 | RayCastContour { 40 | intercept_fn: intercept_fn, 41 | size: size, 42 | scale_factor: 1.0 43 | } 44 | } 45 | 46 | /// 47 | /// Sets the scale factor for the contour 48 | /// 49 | #[inline] 50 | pub fn with_scale(self, scale_factor: f64) -> Self { 51 | RayCastContour { 52 | intercept_fn: self.intercept_fn, 53 | size: self.size, 54 | scale_factor: scale_factor 55 | } 56 | } 57 | } 58 | 59 | impl Clone for RayCastContour 60 | where 61 | TFn: Clone + Fn(f64) -> SmallVec<[Range; 4]>, 62 | { 63 | #[inline] 64 | fn clone(&self) -> Self { 65 | RayCastContour { 66 | intercept_fn: self.intercept_fn.clone(), 67 | size: self.size, 68 | scale_factor: self.scale_factor, 69 | } 70 | } 71 | } 72 | 73 | impl SampledContour for RayCastContour 74 | where 75 | TFn: Fn(f64) -> SmallVec<[Range; 4]>, 76 | { 77 | /// 78 | /// The size of this contour 79 | /// 80 | #[inline] 81 | fn contour_size(&self) -> ContourSize { 82 | self.size 83 | } 84 | 85 | /// 86 | /// Given a y coordinate returns ranges indicating the filled pixels on that line 87 | /// 88 | /// The ranges must be provided in ascending order, and must also not overlap. 89 | /// 90 | fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range; 4]> { 91 | raycast_intercepts_on_line(&self.intercept_fn, y, self.scale_factor, self.size.width()) 92 | } 93 | } 94 | 95 | /// 96 | /// Implementation of the `intercepts_on_line` trait function from `SampledContour`, implemented in terms of a function that 97 | /// returns where the intercepts are 98 | /// 99 | #[inline] 100 | pub (crate) fn raycast_intercepts_on_line(intercept_fn: &TFn, y: f64, scale_factor: f64, width: usize) -> SmallVec<[Range; 4]> 101 | where 102 | TFn: Fn(f64) -> SmallVec<[Range; 4]>, 103 | { 104 | // Convert the y position to a coordinate 105 | let y = y as f64; 106 | let y = y * scale_factor; 107 | let width = width as f64; 108 | 109 | // Find the intercepts on this line 110 | let intercepts = (intercept_fn)(y); 111 | 112 | // Process them to create the final result: remove intercepts outside of the width of the cell, clip the remaining intercepts, round to usizes and then remove any 0-width intercepts 113 | intercepts.into_iter() 114 | .filter(|intercept| intercept.end >= 0.0 && intercept.start < width) 115 | .map(|intercept| { 116 | let start = if intercept.start < 0.0 { 0.0 } else { intercept.start }; 117 | let end = if intercept.end >= width { width } else { intercept.end }; 118 | start..end 119 | }) 120 | .filter(|intercept| intercept.start < intercept.end) 121 | .collect() 122 | } 123 | 124 | /// Ray cast contour with a dynamic intercept function 125 | pub type DynRayCastContour = RayCastContour SmallVec<[Range; 4]>>>; 126 | -------------------------------------------------------------------------------- /src/bezier/roots/mod.rs: -------------------------------------------------------------------------------- 1 | // See "A bezier curve-based root-finder", Philip J Schneider, Graphics Gems 2 | 3 | mod polynomial_to_bezier; 4 | mod find_roots; 5 | mod nearest_point_bezier_root_finder; 6 | 7 | pub use polynomial_to_bezier::*; 8 | pub use find_roots::*; 9 | pub use nearest_point_bezier_root_finder::*; 10 | -------------------------------------------------------------------------------- /src/bezier/roots/nearest_point_bezier_root_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::geo::*; 2 | use crate::bezier::*; 3 | use super::find_roots::*; 4 | 5 | use itertools::*; 6 | use smallvec::*; 7 | 8 | use std::iter; 9 | 10 | /// 11 | /// Creates a 5th degree bezier curve that describes the dot product of the curve's tangent and the line connecting to 12 | /// the point at every point on the curve. This is 0 when the point is perpendicular to the curve (ie, where the curve 13 | /// is neither moving away from or towards the point) 14 | /// 15 | /// The closest points must be either one that is perpendicular or the start or end point of the curve. 16 | /// 17 | fn distance_in_bezier_form(curve: &C, point: &C::Point) -> [Coord2; 6] 18 | where 19 | C: BezierCurve + BezierCurve2D, 20 | C::Point: Coordinate2D, 21 | { 22 | // Precomputed 'z' factor for cubic curves 23 | const Z: [[f64; 4]; 3] = [ 24 | [1.0, 0.6, 0.3, 0.1], 25 | [0.4, 0.6, 0.6, 0.4], 26 | [0.1, 0.3, 0.6, 1.0], 27 | ]; 28 | 29 | // Fetch the control points of the curve 30 | let start_point = curve.start_point(); 31 | let end_point = curve.end_point(); 32 | let (cp1, cp2) = curve.control_points(); 33 | let curve_points = [start_point, cp1, cp2, end_point]; 34 | 35 | // Convert to Coord2s 36 | let point = Coord2(point.x(), point.y()); 37 | let curve_points = curve_points.iter().map(|p| Coord2(p.x(), p.y())).collect::>(); 38 | 39 | // Get the vectors from each control point to the control points, and from each control point to the next 40 | let control_point_to_point = curve_points.iter() 41 | .map(|control_point| *control_point - point) 42 | .collect::>(); 43 | let control_point_to_next = curve_points.iter().tuple_windows() 44 | .map(|(cp1, cp2)| (*cp2-*cp1) * 3.0) 45 | .collect::>(); 46 | 47 | // Create a table of dot products of the points in each of the two lists we just made 48 | let cp_dot_products = control_point_to_next.into_iter() 49 | .map(|to_next_cp| { 50 | control_point_to_point.iter() 51 | .map(|to_point| to_next_cp.dot(to_point)) 52 | .collect::>() 53 | }).collect::>(); 54 | 55 | // Apply the 'z' factors to create the final curve 56 | let mut curve = [Coord2(0.0/5.0, 0.0), Coord2(1.0/5.0, 0.0), Coord2(2.0/5.0, 0.0), Coord2(3.0/5.0, 0.0), Coord2(4.0/5.0, 0.0), Coord2(5.0/5.0, 0.0)]; 57 | 58 | for k in 0..=5i32 { 59 | let lower = 0.max(k-2); 60 | let upper = k.min(3); 61 | 62 | for i in lower..=upper { 63 | let j = k - i; 64 | 65 | curve[(i+j) as usize].1 += cp_dot_products[j as usize][i as usize] * Z[j as usize][i as usize]; 66 | } 67 | } 68 | 69 | curve 70 | } 71 | 72 | /// 73 | /// Uses the root-finding algorithm described in Graphics Gems to find the nearest points on the 74 | /// bezier curve. 75 | /// 76 | pub fn nearest_point_on_curve_bezier_root_finder(curve: &C, point: &C::Point) -> f64 77 | where 78 | C: BezierCurve + BezierCurve2D, 79 | C::Point: Coordinate + Coordinate2D, 80 | { 81 | // See "Solving the Nearest-Point-On-Curve Problem", Philip J Schneider, Graphics Gems 82 | 83 | // Create a curve of order 5 to find the points that are perpendicular to the curve 84 | let tangent_curve = distance_in_bezier_form(curve, point); 85 | 86 | // Solve to find the roots of this curve 87 | let perpendicular_t_values = find_bezier_roots(tangent_curve); 88 | 89 | // Need to find the closest roots, or the start or end points can be closer 90 | let mut min_t_value = 0.0; 91 | let offset = curve.point_at_pos(0.0) - *point; 92 | let mut min_distance_sq = offset.dot(&offset); 93 | 94 | for t in perpendicular_t_values.into_iter().filter(|t| *t > 0.0 && *t < 1.0).chain(iter::once(1.0)) { 95 | let offset = curve.point_at_pos(t) - *point; 96 | let distance_sq = offset.dot(&offset); 97 | 98 | if distance_sq <= min_distance_sq { 99 | min_t_value = t; 100 | min_distance_sq = distance_sq; 101 | } 102 | } 103 | 104 | // Closest point on the curve should be min_t_value 105 | min_t_value 106 | } 107 | -------------------------------------------------------------------------------- /src/bezier/roots/polynomial_to_bezier.rs: -------------------------------------------------------------------------------- 1 | use crate::geo::*; 2 | 3 | use std::convert::{TryInto}; 4 | 5 | /// 6 | /// Generates the control polygon corresponding to a polynomial 7 | /// 8 | /// The polynomial has the form `c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 ...` where `c` is the list of coefficints 9 | /// 10 | pub fn polynomial_to_bezier(coefficients: [f64; N]) -> [TPoint; N] 11 | where 12 | TPoint: Coordinate + Coordinate2D, 13 | { 14 | // See "A bezier curve-based root-finder", Philip J Schneider, Graphics Gems 15 | let mut coefficients = coefficients; 16 | 17 | for j in 1..N { 18 | let c = 1.0 / (N as f64 - j as f64); 19 | let mut d = 1.0; 20 | let mut e = c; 21 | 22 | for i in (j..N).into_iter().rev() { 23 | coefficients[i] = d * coefficients[i] + e * coefficients[i-1]; 24 | d = d - c; 25 | e = e + c; 26 | } 27 | } 28 | 29 | // Convert to points (range is 0..1) 30 | let coefficients = coefficients.iter().enumerate().map(|(x, y)| { 31 | let x = (x as f64) / ((N-1) as f64); 32 | TPoint::from_components(&[x, *y]) 33 | }).collect::>().try_into(); 34 | 35 | if let Ok(coefficients) = coefficients { 36 | coefficients 37 | } else { 38 | unreachable!() 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod test { 44 | use super::*; 45 | use crate::bezier::*; 46 | 47 | #[test] 48 | fn simple_polynomial() { 49 | // (x-0.5)(x-0.4)(x-0.3)(x-0.2)(x-0.1) 50 | // == -0.0012 + 0.0274x - 0.225x^2 + 0.85x^3 - 1.5x^4 + x^5 51 | let bezier = polynomial_to_bezier::([-0.0012, 0.0274, -0.225, 0.85, -1.5, 1.0]); 52 | let point5 = de_casteljau_n(0.5, bezier.clone().into()); 53 | let point4 = de_casteljau_n(0.4, bezier.clone().into()); 54 | let point3 = de_casteljau_n(0.3, bezier.clone().into()); 55 | let point2 = de_casteljau_n(0.2, bezier.clone().into()); 56 | let point1 = de_casteljau_n(0.1, bezier.clone().into()); 57 | 58 | assert!(point1.y().abs() < 0.1, "{:?} {:?} {:?} {:?} {:?}", point1, point2, point3, point4, point5); 59 | assert!(point2.y().abs() < 0.1, "{:?} {:?} {:?} {:?} {:?}", point1, point2, point3, point4, point5); 60 | assert!(point3.y().abs() < 0.1, "{:?} {:?} {:?} {:?} {:?}", point1, point2, point3, point4, point5); 61 | assert!(point4.y().abs() < 0.1, "{:?} {:?} {:?} {:?} {:?}", point1, point2, point3, point4, point5); 62 | assert!(point5.y().abs() < 0.1, "{:?} {:?} {:?} {:?} {:?}", point1, point2, point3, point4, point5); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/bezier/search.rs: -------------------------------------------------------------------------------- 1 | use super::bounds::*; 2 | use super::subdivide::*; 3 | use super::super::geo::*; 4 | 5 | /// 6 | /// Performs a subdivision search on a curve for a point matching a function 7 | /// 8 | /// This searches for a point using a matching function that determines whether or not the point 9 | /// is within a particular bounding box. The return value is a list of t values for the curve 10 | /// described by the w values where the bounding box was shrunk to the size specified by min_size. 11 | /// 12 | /// A limitation of this algorithm is that if the target point lies very close to a subdivision point, 13 | /// it may produce multiple matches (as it will find a nearby point on either side of the subdivision) 14 | /// 15 | pub fn search_bounds4(min_size: f64, w1: Point, w2: Point, w3: Point, w4: Point, match_fn: MatchFn) -> Vec 16 | where 17 | Point: Coordinate, 18 | MatchFn: Fn(Point, Point) -> bool, 19 | { 20 | // Helper function to determine if a bounding box is below the minimum size 21 | let min_size_squared = min_size * min_size; 22 | let is_valid_match = |p1: Point, p2: Point| { 23 | let diff = p1-p2; 24 | let size_squared = diff.dot(&diff); 25 | 26 | size_squared <= min_size_squared 27 | }; 28 | 29 | // Push the initial curve as one to check 30 | let mut pending = vec![]; 31 | let mut result = vec![]; 32 | 33 | // Each point is the list of w values and the min/max t values remaining to search 34 | pending.push((w1, w2, w3, w4, 0.0, 1.0)); 35 | 36 | // Iterate while there are still curve sections to search 37 | while let Some((w1, w2, w3, w4, min_t, max_t)) = pending.pop() { 38 | // Subdivide at the midpoint 39 | let midpoint = (min_t + max_t)/2.0; 40 | let ((aw1, aw2, aw3, aw4), (bw1, bw2, bw3, bw4)) = subdivide4(0.5, w1, w2, w3, w4); 41 | 42 | // Compute the bounds of either side 43 | let (amin, amax) = bounding_box4(aw1, aw2, aw3, aw4); 44 | let (bmin, bmax) = bounding_box4(bw1, bw2, bw3, bw4); 45 | 46 | // Process the 'earlier' side of the curve 47 | if match_fn(amin, amax) { 48 | if is_valid_match(amin, amax) { 49 | // Bounds are small enough this counts as a match: push the midpoint 50 | result.push((min_t+midpoint)/2.0); 51 | } else { 52 | // Continue processing this half of the curve 53 | pending.push((aw1, aw2, aw3, aw4, min_t, midpoint)); 54 | } 55 | } 56 | 57 | // Process the 'later' side of the curve 58 | if match_fn(bmin, bmax) { 59 | if is_valid_match(bmin, bmax) { 60 | // Bounds are small enough this counts as a match: push the midpoint 61 | result.push((midpoint+max_t)/2.0); 62 | } else { 63 | // Continue processing this half of the curve 64 | pending.push((bw1, bw2, bw3, bw4, midpoint, max_t)); 65 | } 66 | } 67 | } 68 | 69 | result 70 | } 71 | -------------------------------------------------------------------------------- /src/bezier/solve.rs: -------------------------------------------------------------------------------- 1 | use super::curve::*; 2 | use super::super::geo::*; 3 | use super::super::consts::*; 4 | 5 | use roots::{find_roots_quadratic, find_roots_cubic, Roots}; 6 | use smallvec::*; 7 | 8 | pub (crate) const CLOSE_ENOUGH: f64 = SMALL_DISTANCE * 50.0; 9 | 10 | /// 11 | /// Solves for t in a single dimension for a bezier curve (finds the point(s) where the basis 12 | /// function evaluates to p) 13 | /// 14 | pub fn solve_basis_for_t(w1: f64, w2: f64, w3: f64, w4: f64, p: f64) -> SmallVec<[f64; 4]> { 15 | const TINY_T: f64 = 1e-6; 16 | 17 | // Compute the coefficients for the cubic bezier function 18 | let d = w1-p; 19 | let c = 3.0*(w2-w1); 20 | let b = 3.0*(w3-w2)-c; 21 | let a = w4-w1-c-b; 22 | 23 | // Solve for p 24 | let roots = if a.abs() < 0.00000001 { find_roots_quadratic(b, c, d) } else { find_roots_cubic(a, b, c, d) }; 25 | let mut roots = match roots { 26 | Roots::No(_) => smallvec![], 27 | Roots::One([a]) => smallvec![a], 28 | Roots::Two([a, b]) => smallvec![a, b], 29 | Roots::Three([a, b, c]) => smallvec![a, b, c], 30 | Roots::Four([a, b, c, d]) => smallvec![a, b, c, d] 31 | }; 32 | 33 | // Remove any roots outside the range of the function 34 | roots.retain(|r| *r > 0.0 && *r < 1.0); 35 | 36 | // Add 0.0 and 1.0 if they are an exact match 37 | if w1 == p { 38 | roots.retain(|r| *r > TINY_T); 39 | roots.insert(0, 0.0); 40 | } 41 | 42 | if w4 == p { 43 | roots.retain(|r| *r < 1.0-TINY_T); 44 | roots.push(1.0); 45 | } 46 | 47 | // Return the roots 48 | roots 49 | } 50 | 51 | /// 52 | /// Searches along the x or y axis for a point within `accuracy` units of the curve, returning the `t` value of that point 53 | /// 54 | /// This is best used for points that are known to either be on the curve or which are very close to it. There are a couple of 55 | /// other options for finding points on a curve: `nearest_point_on_curve()` will return the true closest point on a curve rather 56 | /// than just the closest point along a particular axis, and the ray casting function `curve_intersects_ray()` can be used to 57 | /// search for the first point encountered along any direction instead of just searching the x or y axes. 58 | /// 59 | /// For interactive use, `curve_intersects_ray()` might be more useful than eitehr this function or the `nearest_point_on_curve()` 60 | /// function as the 'true' nearest point may move in an odd manner as the point it's closest to changes. 61 | /// 62 | pub fn solve_curve_for_t_along_axis(curve: &C, point: &C::Point, accuracy: f64) -> Option { 63 | let p1 = curve.start_point(); 64 | let (p2, p3) = curve.control_points(); 65 | let p4 = curve.end_point(); 66 | 67 | // 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) 68 | for dimension in 0..(C::Point::len()) { 69 | // Solve for this dimension 70 | let (w1, w2, w3, w4) = (p1.get(dimension), p2.get(dimension), p3.get(dimension), p4.get(dimension)); 71 | let possible_t_values = solve_basis_for_t(w1, w2, w3, w4, point.get(dimension)); 72 | 73 | for possible_t in possible_t_values { 74 | // Ignore values outside the range of the curve 75 | if !(-0.001..=1.001).contains(&possible_t) { 76 | continue; 77 | } 78 | 79 | // If this is an accurate enough solution, return this as the t value 80 | let point_at_t = curve.point_at_pos(possible_t); 81 | if point_at_t.is_near_to(point, accuracy) { 82 | return Some(possible_t); 83 | } 84 | } 85 | } 86 | 87 | // No solution: result is None 88 | None 89 | } 90 | -------------------------------------------------------------------------------- /src/bezier/tangent.rs: -------------------------------------------------------------------------------- 1 | use super::curve::*; 2 | use super::basis::*; 3 | use super::derivative::*; 4 | 5 | /// 6 | /// A structure that can be used to compute the tangent of a bezier curve 7 | /// 8 | pub struct Tangent { 9 | /// The derivative of the curve 10 | derivative: (Curve::Point, Curve::Point, Curve::Point) 11 | } 12 | 13 | impl<'a, Curve: BezierCurve> From<&'a Curve> for Tangent { 14 | /// 15 | /// Creates a structure that can computes the tangents for a bezier curve 16 | /// 17 | fn from(curve: &'a Curve) -> Tangent { 18 | let control_points = curve.control_points(); 19 | 20 | Tangent { 21 | derivative: derivative4(curve.start_point(), control_points.0, control_points.1, curve.end_point()) 22 | } 23 | } 24 | } 25 | 26 | impl Tangent { 27 | /// 28 | /// Calculates the tangent at a particular point 29 | /// 30 | pub fn tangent(&self, t: f64) -> Curve::Point { 31 | de_casteljau3(t, self.derivative.0, self.derivative.1, self.derivative.2) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/bezier/vectorize/circular_brush.rs: -------------------------------------------------------------------------------- 1 | use super::circular_distance_field::*; 2 | use super::brush_stroke::*; 3 | use super::sampled_contour::*; 4 | use crate::geo::*; 5 | 6 | /// 7 | /// A brush distance field that can be used to create brush strokes made up of variable-radius circles 8 | /// 9 | pub struct CircularBrush; 10 | 11 | impl DaubBrush for CircularBrush { 12 | type DaubDistanceField = CircularDistanceField; 13 | 14 | #[inline] 15 | fn create_daub(&self, centered_at: impl Coordinate + Coordinate2D, radius: f64) -> Option<(CircularDistanceField, ContourPosition)> { 16 | CircularDistanceField::centered_at_position(centered_at, radius) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/bezier/vectorize/circular_distance_field.rs: -------------------------------------------------------------------------------- 1 | use super::distance_field::*; 2 | use super::sampled_contour::*; 3 | use super::column_sampled_contour::*; 4 | use crate::geo::*; 5 | 6 | use smallvec::*; 7 | 8 | use std::ops::{Range}; 9 | 10 | /// 11 | /// A distance field to a circle with a particular radius 12 | /// 13 | #[derive(Clone, Copy, PartialEq)] 14 | pub struct CircularDistanceField { 15 | radius: f64, 16 | center_x: f64, 17 | center_y: f64, 18 | diameter: usize, 19 | } 20 | 21 | impl CircularDistanceField { 22 | /// 23 | /// Creates a new sampled distance field for a circle with the specified radius 24 | /// 25 | #[inline] 26 | pub fn with_radius(radius: f64) -> Self { 27 | let radius = if radius < 0.0 { 0.0 } else { radius }; 28 | let center = radius.ceil() + 1.0; 29 | let diameter = (center as usize) * 2 + 1; 30 | 31 | CircularDistanceField { 32 | radius: radius, 33 | center_x: center, 34 | center_y: center, 35 | diameter: diameter, 36 | } 37 | } 38 | 39 | /// 40 | /// Gives the circle a non-linear offset, from between 0.0 to 1.0 41 | /// 42 | #[inline] 43 | pub fn with_center_offset(self, x: f64, y: f64) -> Self { 44 | let center_x = self.center_x + x; 45 | let center_y = self.center_y + y; 46 | 47 | CircularDistanceField { 48 | radius: self.radius, 49 | center_x: center_x, 50 | center_y: center_y, 51 | diameter: ((center_x.max(center_y)).floor() as usize) * 2 + 1, 52 | } 53 | } 54 | 55 | /// 56 | /// Returns a circular distance field and an offset that will create a circle centered at the specified position 57 | /// 58 | /// All of the points within the resulting circle must be at positive coordinates (ie, `x-radius` and `y-radius` must 59 | /// be positive values). This is intended to be used as input to the `DaubBrushDistanceField` type to create brush 60 | /// strokes out of many circle. 61 | /// 62 | pub fn centered_at_position(pos: impl Coordinate + Coordinate2D, radius: f64) -> Option<(CircularDistanceField, ContourPosition)> { 63 | if radius <= 0.0 { return None; } 64 | 65 | let circle = CircularDistanceField::with_radius(radius); 66 | 67 | let x = pos.x() - circle.center_x - 1.0; 68 | let y = pos.y() - circle.center_y - 1.0; 69 | 70 | debug_assert!(x >= 0.0, "x {}-{}-1 < 0.0 ({})", pos.x(), circle.center_x, x); 71 | debug_assert!(y >= 0.0, "y {}-{}-1 < 0.0 ({})", pos.y(), circle.center_y, y); 72 | 73 | if x < 0.0 || y < 0.0 { return None; } 74 | 75 | let offset_x = x - x.floor(); 76 | let offset_y = y - y.floor(); 77 | 78 | let circle = circle.with_center_offset(offset_x, offset_y); 79 | let position = ContourPosition(x.floor() as usize, y.floor() as usize); 80 | 81 | Some((circle, position)) 82 | } 83 | } 84 | 85 | impl SampledContour for CircularDistanceField { 86 | #[inline] 87 | fn contour_size(&self) -> ContourSize { 88 | ContourSize(self.diameter, self.diameter) 89 | } 90 | 91 | #[inline] 92 | fn intercepts_on_line(&self, ypos: f64) -> SmallVec<[Range; 4]> { 93 | let y = ypos - self.center_y; 94 | 95 | if y.abs() <= self.radius { 96 | let intercept = ((self.radius*self.radius) - (y*y)).sqrt(); 97 | let min_x = self.center_x - intercept; 98 | let max_x = self.center_x + intercept; 99 | 100 | smallvec![min_x..max_x] 101 | } else { 102 | smallvec![] 103 | } 104 | } 105 | } 106 | 107 | impl ColumnSampledContour for CircularDistanceField { 108 | #[inline] 109 | fn intercepts_on_column(&self, xpos: f64) -> SmallVec<[Range; 4]> { 110 | let x = xpos - self.center_x; 111 | 112 | if x.abs() <= self.radius { 113 | let intercept = ((self.radius*self.radius) - (x*x)).sqrt(); 114 | let min_y = self.center_y - intercept; 115 | let max_y = self.center_y + intercept; 116 | 117 | smallvec![min_y..max_y] 118 | } else { 119 | smallvec![] 120 | } 121 | } 122 | } 123 | 124 | impl SampledSignedDistanceField for CircularDistanceField { 125 | type Contour = CircularDistanceField; 126 | 127 | #[inline] 128 | fn field_size(&self) -> ContourSize { 129 | ContourSize(self.diameter, self.diameter) 130 | } 131 | 132 | fn distance_at_point(&self, pos: ContourPosition) -> f64 { 133 | let pos_x = pos.0 as f64; 134 | let pos_y = pos.1 as f64; 135 | let offset_x = pos_x - self.center_x; 136 | let offset_y = pos_y - self.center_y; 137 | 138 | (offset_x*offset_x + offset_y*offset_y).sqrt() - self.radius 139 | } 140 | 141 | #[inline] 142 | fn as_contour<'a>(&'a self) -> &'a Self::Contour { self } 143 | } 144 | -------------------------------------------------------------------------------- /src/bezier/vectorize/column_sampled_contour.rs: -------------------------------------------------------------------------------- 1 | use super::sampled_contour::*; 2 | 3 | use smallvec::*; 4 | 5 | use std::ops::{Range}; 6 | 7 | /// 8 | /// A `SampledContour` that can return the intercepts along columns as well as lines 9 | /// 10 | /// This can be used as an alternative to a distance field to produce a more accurate tracing of a shape. 11 | /// 12 | pub trait ColumnSampledContour : SampledContour { 13 | /// 14 | /// Given an x coordinate, returns ranges indicating the filled pixels on that column 15 | /// 16 | /// The ranges must be returned in ascending order and must not overlap 17 | /// 18 | fn intercepts_on_column(&self, x: f64) -> SmallVec<[Range; 4]>; 19 | 20 | /// 21 | /// Retrieves the intercepts on a column, rounded to pixel positions 22 | /// 23 | #[inline] 24 | fn rounded_intercepts_on_column(&self, x: f64) -> SmallVec<[Range; 4]> { 25 | let intercepts = self.intercepts_on_column(x) 26 | .into_iter() 27 | .map(|intercept| { 28 | let min_y_ceil = intercept.start.ceil(); 29 | let max_y_ceil = intercept.end.ceil(); 30 | 31 | let min_y = min_y_ceil as usize; 32 | let max_y = max_y_ceil as usize; 33 | 34 | min_y..max_y 35 | }) 36 | .filter(|intercept| intercept.start != intercept.end) 37 | .collect::>(); 38 | 39 | if intercepts.len() <= 1 { 40 | intercepts 41 | } else { 42 | merge_overlapping_intercepts(intercepts) 43 | } 44 | } 45 | } 46 | 47 | impl<'a, T> ColumnSampledContour for &'a T 48 | where 49 | T: ColumnSampledContour, 50 | { 51 | #[inline] fn intercepts_on_column(&self, x: f64) -> SmallVec<[Range; 4]> { (*self).intercepts_on_column(x) } 52 | } 53 | -------------------------------------------------------------------------------- /src/bezier/vectorize/contour_edges_by_scanline.rs: -------------------------------------------------------------------------------- 1 | use super::sampled_contour::*; 2 | 3 | use smallvec::*; 4 | 5 | use std::mem; 6 | 7 | /// 8 | /// Iterator that takes a set of contour edges ordered by y position and groups them by their y position 9 | /// 10 | /// The iterators returned from `SampledContour::edge_cell_iterator()` should be in a suitable ordering to be 11 | /// transformed by this iterator. 12 | /// 13 | pub struct EdgesByScanlineIterator 14 | where 15 | TIterator: Iterator, 16 | { 17 | // The contour iterator 18 | iterator: TIterator, 19 | 20 | /// The current scanline identifier (None if all of the edges have been generated) 21 | current_scanline: Option, 22 | 23 | /// The cells in the current scanline 24 | scanline_cells: SmallVec<[(usize, ContourCell); 4]>, 25 | } 26 | 27 | impl From for EdgesByScanlineIterator 28 | where 29 | TIterator: Iterator, 30 | { 31 | fn from(iterator: TIterator) -> EdgesByScanlineIterator { 32 | let mut iterator = iterator; 33 | 34 | if let Some((first_pos, first_cell)) = iterator.next() { 35 | EdgesByScanlineIterator { 36 | iterator: iterator, 37 | current_scanline: Some(first_pos.1), 38 | scanline_cells: smallvec![(first_pos.0, first_cell)], 39 | } 40 | } else { 41 | EdgesByScanlineIterator { 42 | iterator: iterator, 43 | current_scanline: None, 44 | scanline_cells: smallvec![], 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl Iterator for EdgesByScanlineIterator 51 | where 52 | TIterator: Iterator, 53 | { 54 | type Item = (usize, SmallVec<[(usize, ContourCell); 4]>); 55 | 56 | fn next(&mut self) -> Option { 57 | if let Some(current_scanline) = self.current_scanline { 58 | // Fetch the cells in the current scanline 59 | let mut cells = smallvec![]; 60 | mem::swap(&mut self.scanline_cells, &mut cells); 61 | 62 | // Read cells until we reach the end of the current scanline 63 | loop { 64 | if let Some((next_pos, next_cell)) = self.iterator.next() { 65 | // Store the current value in the next scanline and return once we run out of cells on the old scanline 66 | if next_pos.1 != current_scanline { 67 | self.current_scanline = Some(next_pos.1); 68 | self.scanline_cells.push((next_pos.0, next_cell)); 69 | 70 | return Some((current_scanline, cells)); 71 | } else { 72 | // Add to the current scanline and continue reading 73 | cells.push((next_pos.0, next_cell)); 74 | } 75 | } else { 76 | // Won't return any more entries 77 | self.current_scanline = None; 78 | 79 | // Return the last scanline (there should always be at least one cell per scaline) 80 | return Some((current_scanline, cells)); 81 | } 82 | } 83 | } else { 84 | // No more items to return 85 | None 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/bezier/vectorize/mod.rs: -------------------------------------------------------------------------------- 1 | mod sampled_contour; 2 | mod column_sampled_contour; 3 | mod distance_field; 4 | mod circular_distance_field; 5 | mod circular_brush; 6 | mod daub_brush_distance_field; 7 | mod marching_squares; 8 | mod contour_edges_by_scanline; 9 | mod brush_stroke; 10 | mod intercept_scan_edge_iterator; 11 | mod scaled_contour; 12 | mod scaled_distance_field; 13 | mod scaled_brush; 14 | 15 | pub use sampled_contour::*; 16 | pub use column_sampled_contour::*; 17 | pub use distance_field::*; 18 | pub use circular_distance_field::*; 19 | pub use circular_brush::*; 20 | pub use daub_brush_distance_field::*; 21 | pub use marching_squares::*; 22 | pub use contour_edges_by_scanline::*; 23 | pub use brush_stroke::*; 24 | pub use intercept_scan_edge_iterator::*; 25 | pub use scaled_contour::*; 26 | pub use scaled_distance_field::*; 27 | pub use scaled_brush::*; 28 | -------------------------------------------------------------------------------- /src/bezier/vectorize/scaled_brush.rs: -------------------------------------------------------------------------------- 1 | use super::brush_stroke::*; 2 | use super::distance_field::*; 3 | use super::sampled_contour::*; 4 | use super::scaled_distance_field::*; 5 | 6 | /// 7 | /// Brush that returns a scaled version of a distance field for each daub 8 | /// 9 | pub struct ScaledBrush { 10 | /// The base distance field for the brush 11 | distance_field: TDistanceField, 12 | 13 | /// The x offset to the center of the distance field (point we scale around) 14 | center_x: f64, 15 | 16 | /// The y offset to the center of the distance field (point we scale around) 17 | center_y: f64, 18 | 19 | /// The scale factor to apply to the radius in the daubs 20 | radius_scale: f64, 21 | } 22 | 23 | impl ScaledBrush 24 | where 25 | TDistanceField: SampledSignedDistanceField, 26 | { 27 | /// 28 | /// Creates a new scaled brush that will produce scaled versions of the supplied distance field 29 | /// 30 | pub fn from_distance_field(distance_field: TDistanceField) -> Self { 31 | // Scale around the center of the distance field 32 | let size = distance_field.field_size(); 33 | let ContourSize(width, height) = size; 34 | 35 | let center_x = (width as f64) / 2.0; 36 | let center_y = (height as f64) / 2.0; 37 | 38 | // Scale to the largest of the width/height 39 | let radius = width.max(height); 40 | let radius_scale = 1.0 / (radius as f64); 41 | 42 | ScaledBrush { 43 | distance_field, center_x, center_y, radius_scale 44 | } 45 | } 46 | } 47 | 48 | impl<'a, TDistanceField> DaubBrush for &'a ScaledBrush 49 | where 50 | TDistanceField: SampledSignedDistanceField, 51 | { 52 | type DaubDistanceField = ScaledDistanceField<&'a TDistanceField>; 53 | 54 | #[inline] 55 | fn create_daub(&self, pos: impl crate::Coordinate + crate::Coordinate2D, radius: f64) -> Option<(Self::DaubDistanceField, ContourPosition)> { 56 | if radius > 0.0 { 57 | let scale = radius * self.radius_scale; 58 | 59 | let x = pos.x() - (self.center_x * scale) - 1.0; 60 | let y = pos.y() - (self.center_y * scale) - 1.0; 61 | 62 | if x < 0.0 || y < 0.0 { return None; } 63 | 64 | let offset_x = x - x.floor(); 65 | let offset_y = y - y.floor(); 66 | 67 | let distance_field = ScaledDistanceField::from_distance_field(&self.distance_field, scale, (offset_x, offset_y)); 68 | let position = ContourPosition(x.floor() as usize, y.floor() as usize); 69 | 70 | Some((distance_field, position)) 71 | } else { 72 | None 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/bezier/vectorize/scaled_contour.rs: -------------------------------------------------------------------------------- 1 | use super::sampled_contour::*; 2 | use super::column_sampled_contour::*; 3 | 4 | use smallvec::*; 5 | 6 | use std::ops::{Range}; 7 | 8 | /// 9 | /// A sampled contour whose result is scaled by the specified scale factor. The results of scaling a contour depends on how its 10 | /// implementation of `intercepts_on_line` works: for many contour types (such as path contours), this will produce accurate 11 | /// results on non-integer positions, so will scale smoothly. 12 | /// 13 | #[derive(Clone)] 14 | pub struct ScaledContour { 15 | /// The contour whose contents will be scaled 16 | contour: TContour, 17 | 18 | /// The scale factor 19 | scale_factor: f64, 20 | 21 | /// X offset to apply to the result 22 | offset_x: f64, 23 | 24 | /// Y offset to apply to the result 25 | offset_y: f64, 26 | 27 | /// The size of 28 | size: ContourSize, 29 | } 30 | 31 | impl ScaledContour 32 | where 33 | TContour: SampledContour, 34 | { 35 | /// 36 | /// Creates scaled version of another contour 37 | /// 38 | #[inline] 39 | pub fn from_contour(contour: TContour, scale_factor: f64, offset: (f64, f64)) -> Self { 40 | // Multiply the original size by the scale factor to get the new size 41 | let ContourSize(width, height) = contour.contour_size(); 42 | 43 | let width = (width as f64) * scale_factor + offset.0; 44 | let height = (height as f64) * scale_factor + offset.1; 45 | let width = width.ceil(); 46 | let height = height.ceil(); 47 | 48 | let size = ContourSize(width as _, height as _); 49 | 50 | // The offset is added to the position to allow for aligning the distance field to non-integer grids (eg, when this is used as a brush) 51 | let (offset_x, offset_y) = offset; 52 | 53 | ScaledContour { contour, scale_factor, size, offset_x, offset_y } 54 | } 55 | } 56 | 57 | impl SampledContour for ScaledContour 58 | where 59 | TContour: SampledContour, 60 | { 61 | #[inline] 62 | fn contour_size(&self) -> ContourSize { 63 | self.size 64 | } 65 | 66 | #[inline] 67 | fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range; 4]> { 68 | let y = (y - self.offset_y) / self.scale_factor; 69 | 70 | self.contour.intercepts_on_line(y) 71 | .into_iter() 72 | .map(|range| { 73 | (range.start * self.scale_factor + self.offset_x)..(range.end * self.scale_factor + self.offset_x) 74 | }) 75 | .collect() 76 | } 77 | } 78 | 79 | impl ColumnSampledContour for ScaledContour 80 | where 81 | TContour: ColumnSampledContour, 82 | { 83 | #[inline] 84 | fn intercepts_on_column(&self, x: f64) -> SmallVec<[Range; 4]> { 85 | let x = (x - self.offset_x) / self.scale_factor; 86 | 87 | self.contour.intercepts_on_column(x) 88 | .into_iter() 89 | .map(|range| { 90 | (range.start * self.scale_factor + self.offset_y)..(range.end * self.scale_factor + self.offset_y) 91 | }) 92 | .collect() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/bezier/vectorize/scaled_distance_field.rs: -------------------------------------------------------------------------------- 1 | use super::column_sampled_contour::*; 2 | use super::distance_field::*; 3 | use super::sampled_contour::*; 4 | use super::scaled_contour::*; 5 | 6 | use smallvec::*; 7 | 8 | use std::ops::{Range}; 9 | 10 | /// 11 | /// A distance field that uses bilinear filtering in order to adjust its size by a scale factor 12 | /// 13 | pub struct ScaledDistanceField { 14 | /// The distance field that is being scaled 15 | distance_field: TDistanceField, 16 | 17 | /// The scale factor to apply to the source distance field 18 | scale_factor: f64, 19 | 20 | /// X offset to apply to the result 21 | offset_x: f64, 22 | 23 | /// Y offset to apply to the result 24 | offset_y: f64, 25 | 26 | /// The size of 27 | size: ContourSize, 28 | } 29 | 30 | impl ScaledDistanceField 31 | where 32 | TDistanceField: SampledSignedDistanceField, 33 | { 34 | /// 35 | /// Creates scaled version of another distance field 36 | /// 37 | #[inline] 38 | pub fn from_distance_field(distance_field: TDistanceField, scale_factor: f64, offset: (f64, f64)) -> Self { 39 | // Multiply the original size by the scale factor to get the new size 40 | let ContourSize(width, height) = distance_field.field_size(); 41 | 42 | let width = (width as f64) * scale_factor + offset.0; 43 | let height = (height as f64) * scale_factor + offset.1; 44 | let width = width.ceil(); 45 | let height = height.ceil(); 46 | 47 | let size = ContourSize(width as _, height as _); 48 | 49 | // The offset is added to the position to allow for aligning the distance field to non-integer grids (eg, when this is used as a brush) 50 | let (offset_x, offset_y) = offset; 51 | 52 | ScaledDistanceField { distance_field, scale_factor, size, offset_x, offset_y } 53 | } 54 | } 55 | 56 | impl SampledSignedDistanceField for ScaledDistanceField 57 | where 58 | TDistanceField: SampledSignedDistanceField, 59 | { 60 | type Contour = ScaledDistanceField; 61 | 62 | #[inline] 63 | fn field_size(&self) -> ContourSize { 64 | self.size 65 | } 66 | 67 | fn distance_at_point(&self, pos: super::ContourPosition) -> f64 { 68 | let ContourPosition(x, y) = pos; 69 | 70 | // Offset the x & y positions 71 | let x = x as f64 - self.offset_x; 72 | let y = y as f64 - self.offset_y; 73 | 74 | let distance_field = &self.distance_field; 75 | 76 | // Scale the x & y positions 77 | let x = x / self.scale_factor; 78 | let y = y / self.scale_factor; 79 | 80 | let low_x = x.floor(); 81 | let low_y = y.floor(); 82 | 83 | // We want to read the distance between the low and high positions 84 | let high_x = low_x + 1.0; 85 | let high_y = low_y + 1.0; 86 | 87 | // Read the distances at the 4 corners 88 | let distances = [ 89 | [distance_field.distance_at_point(ContourPosition(low_x as _, low_y as _)), distance_field.distance_at_point(ContourPosition(low_x as _, high_y as _))], 90 | [distance_field.distance_at_point(ContourPosition(high_x as _, low_y as _)), distance_field.distance_at_point(ContourPosition(high_x as _, high_y as _))] 91 | ]; 92 | 93 | // Interpolate the distances 94 | let distance_x1 = ((high_x - x)/(high_x - low_x)) * distances[0][0] + ((x - low_x)/(high_x - low_x)) * distances[1][0]; 95 | let distance_x2 = ((high_x - x)/(high_x - low_x)) * distances[0][1] + ((x - low_x)/(high_x - low_x)) * distances[1][1]; 96 | let distance = ((high_y - y)/(high_y - low_y)) * distance_x1 + ((y - low_y)/(high_y - low_y)) * distance_x2; 97 | 98 | distance * self.scale_factor 99 | } 100 | 101 | #[inline] 102 | fn as_contour<'a>(&'a self) -> &'a Self::Contour { 103 | self 104 | } 105 | } 106 | 107 | impl SampledContour for ScaledDistanceField 108 | where 109 | TDistanceField: SampledSignedDistanceField, 110 | { 111 | #[inline] 112 | fn contour_size(&self) -> ContourSize { 113 | self.field_size() 114 | } 115 | 116 | #[inline] 117 | fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range; 4]> { 118 | ScaledContour::from_contour(self.distance_field.as_contour(), self.scale_factor, (self.offset_x, self.offset_y)).intercepts_on_line(y) 119 | } 120 | } 121 | 122 | impl ColumnSampledContour for ScaledDistanceField 123 | where 124 | TDistanceField: SampledSignedDistanceField, 125 | TDistanceField::Contour: ColumnSampledContour, 126 | { 127 | #[inline] 128 | fn intercepts_on_column(&self, x: f64) -> SmallVec<[Range; 4]> { 129 | ScaledContour::from_contour(self.distance_field.as_contour(), self.scale_factor, (self.offset_x, self.offset_y)).intercepts_on_column(x) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | /// Length we consider a small distance (points closer than this far apart are considered to be the same) 2 | pub const SMALL_DISTANCE: f64 = 0.001; 3 | 4 | /// Length we consider a 'close' distance (we may round to this precision or cut out points that are closer than this) 5 | pub const CLOSE_DISTANCE: f64 = 0.01; 6 | 7 | /// Difference between 't' values on a bezier curve for values considered the same 8 | pub const SMALL_T_DISTANCE: f64 = 0.000001; 9 | -------------------------------------------------------------------------------- /src/debug/mod.rs: -------------------------------------------------------------------------------- 1 | mod path_to_string; 2 | mod graph_path_debug; 3 | 4 | pub use self::path_to_string::*; 5 | pub use self::graph_path_debug::*; 6 | -------------------------------------------------------------------------------- /src/debug/path_to_string.rs: -------------------------------------------------------------------------------- 1 | use super::super::geo::*; 2 | use super::super::bezier::path::*; 3 | 4 | use std::fmt::*; 5 | 6 | /// 7 | /// Writes out a path as a Rust simple bezier path definition 8 | /// 9 | /// This can be used to generate code for a test when a path definition fails to perform as expected 10 | /// 11 | pub fn bezier_path_to_rust_definition>(path: &P) -> String { 12 | let mut rust_code = String::new(); 13 | 14 | let start = path.start_point(); 15 | write!(&mut rust_code, "BezierPathBuilder::::start(Coord2({}, {}))", start.x(), start.y()).unwrap(); 16 | 17 | for (cp1, cp2, endpoint) in path.points() { 18 | write!(&mut rust_code, "\n .curve_to((Coord2({}, {}), Coord2({}, {})), Coord2({}, {}))", cp1.x(), cp1.y(), cp2.x(), cp2.y(), endpoint.x(), endpoint.y()).unwrap(); 19 | } 20 | write!(&mut rust_code, "\n .build()").unwrap(); 21 | 22 | rust_code 23 | } 24 | -------------------------------------------------------------------------------- /src/geo/bounding_box.rs: -------------------------------------------------------------------------------- 1 | use super::geo::*; 2 | use super::has_bounds::*; 3 | use super::coordinate::*; 4 | 5 | /// 6 | /// Trait implemented by things representing axis-aligned bounding boxes 7 | /// 8 | pub trait BoundingBox : Geo+Sized { 9 | /// 10 | /// Returns a bounding box with the specified minimum and maximum coordinates 11 | /// 12 | fn from_min_max(min: Self::Point, max: Self::Point) -> Self; 13 | 14 | /// 15 | /// Returns a bounding box containing the specified points 16 | /// 17 | fn bounds_for_points>(points: PointIter) -> Self { 18 | let mut points = points.into_iter(); 19 | 20 | // Initialise the bounding box with the first point 21 | let first_point = points.next(); 22 | if let Some(first_point) = first_point { 23 | // min, max is just the first point initially 24 | let (mut min, mut max) = (first_point, first_point); 25 | 26 | // Update with the remainder of the points 27 | for point in points { 28 | min = Self::Point::from_smallest_components(min, point); 29 | max = Self::Point::from_biggest_components(max, point); 30 | } 31 | 32 | Self::from_min_max(min, max) 33 | } else { 34 | // If there are no points, then the result is the empty bounding box 35 | Self::empty() 36 | } 37 | } 38 | 39 | /// 40 | /// Returns the minimum point of this bounding box 41 | /// 42 | fn min(&self) -> Self::Point; 43 | 44 | /// 45 | /// Returns the maximum point of this bounding box 46 | /// 47 | fn max(&self) -> Self::Point; 48 | 49 | /// 50 | /// Returns an empty bounding box 51 | /// 52 | fn empty() -> Self { 53 | Self::from_min_max(Self::Point::origin(), Self::Point::origin()) 54 | } 55 | 56 | /// 57 | /// True if this bounding box is empty 58 | /// 59 | #[inline] 60 | fn is_empty(&self) -> bool { 61 | self.min() == self.max() 62 | } 63 | 64 | /// 65 | /// Creates the union of this and another bounding box 66 | /// 67 | fn union_bounds(self, target: Self) -> Self { 68 | if self.is_empty() { 69 | target 70 | } else if target.is_empty() { 71 | self 72 | } else { 73 | Self::from_min_max(Self::Point::from_smallest_components(self.min(), target.min()), Self::Point::from_biggest_components(self.max(), target.max())) 74 | } 75 | } 76 | 77 | /// 78 | /// Returns true if this bounding box overlaps another 79 | /// 80 | fn overlaps(&self, target: &Self) -> bool { 81 | let (min1, max1) = (self.min(), self.max()); 82 | let (min2, max2) = (target.min(), target.max()); 83 | 84 | for p_index in 0..Self::Point::len() { 85 | if min1.get(p_index) > max2.get(p_index) { return false; } 86 | if min2.get(p_index) > max1.get(p_index) { return false; } 87 | } 88 | 89 | true 90 | } 91 | } 92 | 93 | /// 94 | /// Type representing a bounding box 95 | /// 96 | /// (Unlike a normal point tuple this always represents its bounds in minimum/maximum order) 97 | /// 98 | #[derive(Debug, Clone, Copy, PartialEq)] 99 | pub struct Bounds(pub Point, pub Point); 100 | 101 | impl BoundingBox for (Point, Point) { 102 | #[inline] 103 | fn from_min_max(min: Self::Point, max: Self::Point) -> Self { 104 | (min, max) 105 | } 106 | 107 | #[inline] 108 | fn min(&self) -> Self::Point { 109 | Point::from_smallest_components(self.0, self.1) 110 | } 111 | 112 | #[inline] 113 | fn max(&self) -> Self::Point { 114 | Point::from_biggest_components(self.0, self.1) 115 | } 116 | } 117 | 118 | impl HasBoundingBox for Bounds { 119 | fn get_bounding_box>(&self) -> Bounds { 120 | Bounds::from_min_max(self.min(), self.max()) 121 | } 122 | } 123 | 124 | impl Geo for Bounds { 125 | type Point=Point; 126 | } 127 | 128 | impl BoundingBox for Bounds { 129 | #[inline] 130 | fn from_min_max(min: Self::Point, max: Self::Point) -> Self { 131 | Bounds(min, max) 132 | } 133 | 134 | #[inline] 135 | fn min(&self) -> Self::Point { 136 | self.0 137 | } 138 | 139 | #[inline] 140 | fn max(&self) -> Self::Point { 141 | self.1 142 | } 143 | } -------------------------------------------------------------------------------- /src/geo/coord1.rs: -------------------------------------------------------------------------------- 1 | use super::coordinate::*; 2 | 3 | impl Coordinate for f64 { 4 | fn from_components(components: &[f64]) -> f64 { 5 | components[0] 6 | } 7 | 8 | #[inline] fn origin() -> f64 { 0.0 } 9 | #[inline] fn len() -> usize { 1 } 10 | #[inline] fn get(&self, _index: usize) -> f64 { *self } 11 | 12 | #[inline] 13 | fn from_biggest_components(p1: f64, p2: f64) -> f64 { 14 | if p1 > p2 { 15 | p1 16 | } else { 17 | p2 18 | } 19 | } 20 | 21 | #[inline] 22 | fn from_smallest_components(p1: f64, p2: f64) -> f64 { 23 | if p1 < p2 { 24 | p1 25 | } else { 26 | p2 27 | } 28 | } 29 | 30 | #[inline] 31 | fn distance_to(&self, target: &f64) -> f64 { 32 | f64::abs(self-target) 33 | } 34 | 35 | fn dot(&self, target: &f64) -> f64 { 36 | self * target 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/geo/coord2.rs: -------------------------------------------------------------------------------- 1 | use super::coordinate::*; 2 | 3 | use std::ops::*; 4 | 5 | /// Represents a 2D point 6 | #[derive(Copy, Clone, PartialEq, Debug)] 7 | pub struct Coord2(pub f64, pub f64); 8 | 9 | impl Coord2 { 10 | /// 11 | /// Creates a Coord2 from any other implementation of Coordinate2D 12 | /// 13 | #[inline] 14 | pub fn from_coordinate(coord: impl Coordinate2D) -> Coord2 { 15 | Coord2(coord.x(), coord.y()) 16 | } 17 | } 18 | 19 | impl Coordinate2D for Coord2 { 20 | /// 21 | /// X component of this coordinate 22 | /// 23 | #[inline] 24 | fn x(&self) -> f64 { 25 | self.0 26 | } 27 | 28 | /// 29 | /// Y component of this coordinate 30 | /// 31 | #[inline] 32 | fn y(&self) -> f64 { 33 | self.1 34 | } 35 | } 36 | 37 | impl Add for Coord2 { 38 | type Output=Coord2; 39 | 40 | #[inline] 41 | fn add(self, rhs: Coord2) -> Coord2 { 42 | Coord2(self.0 + rhs.0, self.1 + rhs.1) 43 | } 44 | } 45 | 46 | impl Sub for Coord2 { 47 | type Output=Coord2; 48 | 49 | #[inline] 50 | fn sub(self, rhs: Coord2) -> Coord2 { 51 | Coord2(self.0 - rhs.0, self.1 - rhs.1) 52 | } 53 | } 54 | 55 | impl Mul for Coord2 { 56 | type Output=Coord2; 57 | 58 | #[inline] 59 | fn mul(self, rhs: f64) -> Coord2 { 60 | Coord2(self.0 * rhs, self.1 * rhs) 61 | } 62 | } 63 | 64 | impl From<(f64, f64)> for Coord2 { 65 | fn from((x, y): (f64, f64)) -> Coord2 { 66 | Coord2(x, y) 67 | } 68 | } 69 | 70 | impl Into<(f64, f64)> for Coord2 { 71 | fn into(self) -> (f64, f64) { 72 | (self.0, self.1) 73 | } 74 | } 75 | 76 | impl From<(f32, f32)> for Coord2 { 77 | fn from((x, y): (f32, f32)) -> Coord2 { 78 | Coord2(x as _, y as _) 79 | } 80 | } 81 | 82 | impl Into<(f32, f32)> for Coord2 { 83 | fn into(self) -> (f32, f32) { 84 | (self.0 as _, self.1 as _) 85 | } 86 | } 87 | 88 | impl Coordinate for Coord2 { 89 | #[inline] 90 | fn from_components(components: &[f64]) -> Coord2 { 91 | Coord2(components[0], components[1]) 92 | } 93 | 94 | #[inline] 95 | fn origin() -> Coord2 { 96 | Coord2(0.0, 0.0) 97 | } 98 | 99 | #[inline] 100 | fn len() -> usize { 2 } 101 | 102 | #[inline] 103 | fn get(&self, index: usize) -> f64 { 104 | match index { 105 | 0 => self.0, 106 | 1 => self.1, 107 | _ => panic!("Coord2 only has two components") 108 | } 109 | } 110 | 111 | fn from_biggest_components(p1: Coord2, p2: Coord2) -> Coord2 { 112 | Coord2(f64::from_biggest_components(p1.0, p2.0), f64::from_biggest_components(p1.1, p2.1)) 113 | } 114 | 115 | fn from_smallest_components(p1: Coord2, p2: Coord2) -> Coord2 { 116 | Coord2(f64::from_smallest_components(p1.0, p2.0), f64::from_smallest_components(p1.1, p2.1)) 117 | } 118 | 119 | #[inline] 120 | fn distance_to(&self, target: &Coord2) -> f64 { 121 | let dist_x = target.0-self.0; 122 | let dist_y = target.1-self.1; 123 | 124 | f64::sqrt(dist_x*dist_x + dist_y*dist_y) 125 | } 126 | 127 | #[inline] 128 | fn dot(&self, target: &Self) -> f64 { 129 | self.0*target.0 + self.1*target.1 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/geo/coord3.rs: -------------------------------------------------------------------------------- 1 | use super::coordinate::*; 2 | use super::coord2::*; 3 | 4 | use std::ops::*; 5 | 6 | /// Represents a 2D point 7 | #[derive(Copy, Clone, PartialEq, Debug)] 8 | pub struct Coord3(pub f64, pub f64, pub f64); 9 | 10 | impl Coordinate3D for Coord3 { 11 | /// 12 | /// X component of this coordinate 13 | /// 14 | #[inline] 15 | fn x(&self) -> f64 { 16 | self.0 17 | } 18 | 19 | /// 20 | /// Y component of this coordinate 21 | /// 22 | #[inline] 23 | fn y(&self) -> f64 { 24 | self.1 25 | } 26 | 27 | /// 28 | /// Z component of this coordinate 29 | /// 30 | #[inline] 31 | fn z(&self) -> f64 { 32 | self.2 33 | } 34 | } 35 | 36 | impl Add for Coord3 { 37 | type Output=Coord3; 38 | 39 | #[inline] 40 | fn add(self, rhs: Coord3) -> Coord3 { 41 | Coord3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) 42 | } 43 | } 44 | 45 | impl Sub for Coord3 { 46 | type Output=Coord3; 47 | 48 | #[inline] 49 | fn sub(self, rhs: Coord3) -> Coord3 { 50 | Coord3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) 51 | } 52 | } 53 | 54 | impl Mul for Coord3 { 55 | type Output=Coord3; 56 | 57 | #[inline] 58 | fn mul(self, rhs: f64) -> Coord3 { 59 | Coord3(self.0 * rhs, self.1 * rhs, self.2 * rhs) 60 | } 61 | } 62 | 63 | impl From<(f64, f64, f64)> for Coord3 { 64 | fn from((x, y, z): (f64, f64, f64)) -> Coord3 { 65 | Coord3(x, y, z) 66 | } 67 | } 68 | 69 | impl From<(Coord2, f64)> for Coord3 { 70 | fn from((pos, z): (Coord2, f64)) -> Coord3 { 71 | Coord3(pos.0, pos.1, z) 72 | } 73 | } 74 | 75 | impl Into<(f64, f64, f64)> for Coord3 { 76 | fn into(self) -> (f64, f64, f64) { 77 | (self.0, self.1, self.2) 78 | } 79 | } 80 | 81 | impl From<(f32, f32, f32)> for Coord3 { 82 | fn from((x, y, z): (f32, f32, f32)) -> Coord3 { 83 | Coord3(x as _, y as _, z as _) 84 | } 85 | } 86 | 87 | impl Into<(f32, f32, f32)> for Coord3 { 88 | fn into(self) -> (f32, f32, f32) { 89 | (self.0 as _, self.1 as _, self.2 as _) 90 | } 91 | } 92 | 93 | impl Coordinate for Coord3 { 94 | #[inline] 95 | fn from_components(components: &[f64]) -> Coord3 { 96 | Coord3(components[0], components[1], components[2]) 97 | } 98 | 99 | #[inline] 100 | fn origin() -> Coord3 { 101 | Coord3(0.0, 0.0, 0.0) 102 | } 103 | 104 | #[inline] 105 | fn len() -> usize { 3 } 106 | 107 | #[inline] 108 | fn get(&self, index: usize) -> f64 { 109 | match index { 110 | 0 => self.0, 111 | 1 => self.1, 112 | 2 => self.2, 113 | _ => panic!("Coord3 only has three components") 114 | } 115 | } 116 | 117 | fn from_biggest_components(p1: Coord3, p2: Coord3) -> Coord3 { 118 | Coord3(f64::from_biggest_components(p1.0, p2.0), f64::from_biggest_components(p1.1, p2.1), f64::from_biggest_components(p1.2, p2.2)) 119 | } 120 | 121 | fn from_smallest_components(p1: Coord3, p2: Coord3) -> Coord3 { 122 | Coord3(f64::from_smallest_components(p1.0, p2.0), f64::from_smallest_components(p1.1, p2.1), f64::from_smallest_components(p1.2, p2.2)) 123 | } 124 | 125 | #[inline] 126 | fn distance_to(&self, target: &Coord3) -> f64 { 127 | let dist_x = target.0-self.0; 128 | let dist_y = target.1-self.1; 129 | let dist_z = target.2-self.2; 130 | 131 | f64::sqrt(dist_x*dist_x + dist_y*dist_y + dist_z*dist_z) 132 | } 133 | 134 | #[inline] 135 | fn dot(&self, target: &Self) -> f64 { 136 | self.0*target.0 + self.1*target.1 + self.2*target.2 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/geo/coordinate_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::geo::coordinate::*; 2 | 3 | /// 4 | /// Extra functions provided for coordinate types 5 | /// 6 | pub trait CoordinateExt { 7 | /// 8 | /// Creates a unit vector along the x axis 9 | /// 10 | fn unit_vector() -> Self; 11 | } 12 | 13 | /// 14 | /// Extra functions introduced for 2D coordinate types 15 | /// 16 | pub trait Coordinate2DExt { 17 | /// 18 | /// Creates a unit vector at an angle in radians measured from the x-axis 19 | /// 20 | fn unit_vector_at_angle(radians: impl Into) -> Self; 21 | } 22 | 23 | impl CoordinateExt for T 24 | where 25 | T: Coordinate, 26 | { 27 | fn unit_vector() -> Self { 28 | let mut components = vec![0.0; Self::len()]; 29 | components[0] = 1.0; 30 | 31 | Self::from_components(&components) 32 | } 33 | } 34 | 35 | impl Coordinate2DExt for T 36 | where 37 | T: Coordinate+Coordinate2D, 38 | { 39 | fn unit_vector_at_angle(radians: impl Into) -> Self { 40 | let radians = radians.into(); 41 | 42 | Self::from_components(&[radians.cos(), radians.sin()]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/geo/geo.rs: -------------------------------------------------------------------------------- 1 | use super::coordinate::*; 2 | 3 | /// 4 | /// Simple base trait implemented by things representing geometry 5 | /// 6 | pub trait Geo { 7 | /// The type of a point in this geometry 8 | type Point: Coordinate; 9 | } 10 | -------------------------------------------------------------------------------- /src/geo/has_bounds.rs: -------------------------------------------------------------------------------- 1 | use super::geo::*; 2 | use super::bounding_box::*; 3 | 4 | /// 5 | /// Trait implemented by types that have a bounding box associated with them 6 | /// 7 | pub trait HasBoundingBox : Geo { 8 | /// 9 | /// Returns the bounding box that encloses this item 10 | /// 11 | fn get_bounding_box>(&self) -> Bounds; 12 | } 13 | -------------------------------------------------------------------------------- /src/geo/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Traits for basic geometric definitions 3 | //! 4 | //! This provides some basic geometric definitions. The `Geo` trait can be implemented by any type that has 5 | //! a particular type of coordinate - for example, implementations of `BezierCurve` need to implement `Geo` 6 | //! in order to describe what type they use for coordinates. 7 | //! 8 | //! `BoundingBox` provides a way to describe axis-aligned bounding boxes. It too is a trait, making it 9 | //! possible to request bounding boxes in types other than the default `Bounds` type supplied by the 10 | //! library. 11 | //! 12 | 13 | mod geo; 14 | mod sweep; 15 | mod has_bounds; 16 | mod coordinate; 17 | mod coord1; 18 | mod coord2; 19 | mod coord3; 20 | mod coordinate_ext; 21 | mod bounding_box; 22 | mod space1; 23 | 24 | pub use self::geo::*; 25 | pub use self::sweep::*; 26 | // pub use self::coord1::*; 27 | pub use self::coord2::*; 28 | pub use self::coord3::*; 29 | pub use self::has_bounds::*; 30 | pub use self::coordinate::*; 31 | pub use self::bounding_box::*; 32 | pub use self::coordinate_ext::*; 33 | pub use self::space1::*; 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # flo_curves 3 | //! 4 | //! `flo_curves` is a library of routines for inspecting and manipulating curves, with a focus on cubic Bézier curves. In 5 | //! this library, you'll find routines for computing points on curves, performing collision detection between curves and 6 | //! lines or other curves, all the way up to routines for combining paths made up of multiple curves. 7 | //! 8 | //! Anyone doing any work with Bézier curves will likely find something in this library that is of use, but its range of 9 | //! functions makes it particularly useful for collision detection or performing path arithmetic. 10 | //! 11 | //! A set of curve and coordinate types are provided by the library, as well as a set of traits that can be implemented 12 | //! on any types with suitable properties. Implementing these traits makes it possible to add the extra features of this 13 | //! library to any existing code that has its own way of representing coordinates, curves or paths. 14 | //! 15 | //! `flo_curves` was built as a support library for `flowbetween`, an animation tool I'm working on. 16 | //! 17 | //! ## Examples 18 | //! 19 | //! Creating a curve: 20 | //! 21 | //! ``` 22 | //! use flo_curves::*; 23 | //! use flo_curves::bezier; 24 | //! 25 | //! let curve = bezier::Curve::from_points(Coord2(1.0, 2.0), (Coord2(2.0, 0.0), Coord2(3.0, 5.0)), Coord2(4.0, 2.0)); 26 | //! ``` 27 | //! 28 | //! Finding a point on a curve: 29 | //! 30 | //! ``` 31 | //! # use flo_curves::*; 32 | //! # use flo_curves::bezier; 33 | //! # 34 | //! # let curve = bezier::Curve::from_points(Coord2(1.0, 2.0), (Coord2(2.0, 0.0), Coord2(3.0, 5.0)), Coord2(4.0, 2.0)); 35 | //! # 36 | //! let pos = curve.point_at_pos(0.5); 37 | //! ``` 38 | //! 39 | //! Intersections: 40 | //! 41 | //! ``` 42 | //! use flo_curves::*; 43 | //! use flo_curves::bezier; 44 | //! # 45 | //! # let curve1 = bezier::Curve::from_points(Coord2(1.0, 2.0), (Coord2(2.0, 0.0), Coord2(3.0, 5.0)), Coord2(4.0, 2.0)); 46 | //! # let curve2 = bezier::Curve::from_points(Coord2(2.0, 1.0), (Coord2(0.0, 2.0), Coord2(5.0, 3.0)), Coord2(2.0, 4.0)); 47 | //! 48 | //! for (t1, t2) in bezier::curve_intersects_curve_clip(&curve1, &curve2, 0.01) { 49 | //! let pos = curve1.point_at_pos(t1); 50 | //! println!("Intersection, curve1 t: {}, curve2 t: {}, position: {}, {}", t1, t2, pos.x(), pos.y()); 51 | //! } 52 | //! ``` 53 | //! 54 | //! Creating a path: 55 | //! 56 | //! ``` 57 | //! use flo_curves::*; 58 | //! use flo_curves::bezier; 59 | //! use flo_curves::bezier::path::*; 60 | //! 61 | //! let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 62 | //! .line_to(Coord2(5.0, 1.0)) 63 | //! .line_to(Coord2(5.0, 5.0)) 64 | //! .line_to(Coord2(1.0, 5.0)) 65 | //! .line_to(Coord2(1.0, 1.0)) 66 | //! .build(); 67 | //! ``` 68 | //! 69 | //! Path arithmetic: 70 | //! 71 | //! ``` 72 | //! use flo_curves::*; 73 | //! use flo_curves::arc::*; 74 | //! use flo_curves::bezier::path::*; 75 | //! 76 | //! let rectangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 77 | //! .line_to(Coord2(5.0, 1.0)) 78 | //! .line_to(Coord2(5.0, 5.0)) 79 | //! .line_to(Coord2(1.0, 5.0)) 80 | //! .line_to(Coord2(1.0, 1.0)) 81 | //! .build(); 82 | //! let circle = Circle::new(Coord2(3.0, 3.0), 1.0).to_path::(); 83 | //! 84 | //! let rectangle_with_hole = path_sub::(&vec![rectangle], &vec![circle], 0.01); 85 | //! ``` 86 | //! 87 | 88 | #![warn(bare_trait_objects)] 89 | 90 | // Breaks the exported API if auto-fixed (can remove these with a version bump) 91 | #![allow(clippy::ptr_arg)] 92 | #![allow(clippy::from_over_into)] 93 | #![allow(clippy::new_without_default)] 94 | #![allow(clippy::type_complexity)] 95 | 96 | // Breaks stylistic choices/algorithm readability 97 | #![allow(clippy::redundant_field_names)] // Used for consistency when initialising some types 98 | #![allow(clippy::collapsible_if)] // Often used to clarify algorithm structure (rewrites need to be at least as clear) 99 | #![allow(clippy::collapsible_else_if)] // Often used to clarify algorithm structure 100 | #![allow(clippy::if_same_then_else)] // Often used to clarify algorithm structure 101 | #![allow(clippy::module_inception)] // The 'line' module has a 'Line' type in it, for example. Makes sense the file is called 'line'... 102 | #![allow(clippy::let_and_return)] // Often we want to say what the return value means (eg: calling something 'tangent' instead of just 'de_cateljau3' is much more clear) 103 | 104 | #[macro_use] mod test_assert; 105 | mod consts; 106 | pub mod bezier; 107 | pub mod line; 108 | pub mod arc; 109 | pub mod debug; 110 | 111 | pub mod geo; 112 | pub use self::geo::*; 113 | 114 | pub use self::bezier::BezierCurveFactory; 115 | pub use self::bezier::BezierCurve; 116 | pub use self::line::Line; 117 | -------------------------------------------------------------------------------- /src/line/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Manipulating and describing lines 3 | //! 4 | //! While `flo_curves` deals mostly with curves, it also supplies a small library of functions for manipulating 5 | //! lines. The `Line` trait can be implemented on other types that define lines, enabling them to be used anywhere 6 | //! the library needs to perform an operation on a line. 7 | //! 8 | //! The basic line type is simply a tuple of two points (that is, any tuple of two values of the same type that 9 | //! implements `Coordinate`). 10 | //! 11 | 12 | mod line; 13 | mod to_curve; 14 | mod intersection; 15 | mod coefficients; 16 | 17 | pub use self::line::*; 18 | pub use self::to_curve::*; 19 | pub use self::coefficients::*; 20 | pub use self::intersection::*; 21 | 22 | pub use super::geo::*; 23 | -------------------------------------------------------------------------------- /src/line/to_curve.rs: -------------------------------------------------------------------------------- 1 | use super::line::*; 2 | use super::super::bezier::*; 3 | 4 | /// 5 | /// Changes a line to a bezier curve 6 | /// 7 | pub fn line_to_bezier(line: &impl Line) -> Curve { 8 | let points = line.points(); 9 | let point_distance = points.1 - points.0; 10 | let (cp1, cp2) = (points.0 + point_distance*0.3333, points.0 + point_distance*0.6666); 11 | 12 | Curve::from_points(points.0, (cp1, cp2), points.1) 13 | } 14 | -------------------------------------------------------------------------------- /src/test_assert.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(not(any(test, feature="extra_checks")))] 3 | macro_rules! test_assert { 4 | ($cond:expr) => ({ }); 5 | ($cond:expr,) => ({ }); 6 | ($cond:expr, $($arg:tt)+) => ({ }); 7 | } 8 | 9 | #[cfg(any(test, feature="extra_checks"))] 10 | macro_rules! test_assert { 11 | ($cond:expr) => ({ assert!($cond); }); 12 | ($cond:expr,) => ({ assert!($cond); }); 13 | ($cond:expr, $($arg:tt)+) => ({ assert!($cond, $($arg)*); }); 14 | } 15 | -------------------------------------------------------------------------------- /tests/bez.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | extern crate flo_curves; 4 | mod bezier; 5 | mod line; 6 | -------------------------------------------------------------------------------- /tests/bezier/algorithms/mod.rs: -------------------------------------------------------------------------------- 1 | mod fill_convex; 2 | mod fill_concave; 3 | -------------------------------------------------------------------------------- /tests/bezier/basis.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use flo_curves::bezier; 3 | 4 | #[test] 5 | fn basis_at_t0_is_w1() { 6 | assert!(bezier::basis(0.0, 2.0, 3.0, 4.0, 5.0) == 2.0); 7 | } 8 | 9 | #[test] 10 | fn basis_at_t1_is_w4() { 11 | assert!(bezier::basis(1.0, 2.0, 3.0, 4.0, 5.0) == 5.0); 12 | } 13 | 14 | #[test] 15 | fn basis_agrees_with_de_casteljau() { 16 | for x in 0..100 { 17 | let t = (x as f64)/100.0; 18 | 19 | let basis = bezier::basis(t, 2.0, 3.0, 4.0, 5.0); 20 | let de_casteljau = bezier::de_casteljau4(t, 2.0, 3.0, 4.0, 5.0); 21 | 22 | assert!(approx_equal(basis, de_casteljau)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/bezier/bounds.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::bezier::{BezierCurve, BezierCurveFactory}; 2 | use flo_curves::bezier; 3 | use flo_curves::geo::Coord2; 4 | use flo_curves::geo::Coordinate; 5 | 6 | #[test] 7 | fn get_straight_line_bounds() { 8 | 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)); 9 | 10 | let bounds: (Coord2, Coord2) = straight_line.bounding_box(); 11 | 12 | assert!(bounds == (Coord2(0.0, 1.0), Coord2(2.0, 3.0))); 13 | } 14 | 15 | #[test] 16 | fn get_curved_line_bounds() { 17 | 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)); 18 | 19 | let bounds: (Coord2, Coord2) = curved_line.bounding_box(); 20 | 21 | assert!(bounds.0.distance_to(&Coord2(-0.3, 1.0)) < 0.0001); 22 | assert!(bounds.1.distance_to(&Coord2(2.0, 3.0)) < 0.0001); 23 | } 24 | -------------------------------------------------------------------------------- /tests/bezier/characteristics.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::*; 3 | 4 | #[test] 5 | fn detect_loop_1() { 6 | 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)); 7 | assert!(curve.characteristics() == bezier::CurveCategory::Loop); 8 | } 9 | 10 | #[test] 11 | fn detect_loop_2() { 12 | 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)); 13 | assert!(characterize_curve(&curve) == bezier::CurveCategory::Loop); 14 | } 15 | 16 | #[test] 17 | fn detect_loop_2_features() { 18 | 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)); 19 | 20 | match features_for_curve(&curve, 0.01) { 21 | CurveFeatures::Loop(t1, t2) => { 22 | let (p1, p2) = (curve.point_at_pos(t1), curve.point_at_pos(t2)); 23 | assert!(p1.is_near_to(&p2, 0.01)); 24 | }, 25 | _ => assert!(false) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/bezier/deform.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier; 3 | 4 | #[test] 5 | fn deform_line_upwards() { 6 | 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)); 7 | let deformed = bezier::move_point::>(&curve, 0.5, &Coord2(0.0, 4.0)); 8 | 9 | let original_point = curve.point_at_pos(0.5); 10 | let new_point = deformed.point_at_pos(0.5); 11 | 12 | let offset = new_point - original_point; 13 | 14 | assert!((offset.x()-0.0).abs() < 0.01); 15 | assert!((offset.y()-4.0).abs() < 0.01); 16 | 17 | assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); 18 | assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); 19 | } 20 | 21 | #[test] 22 | fn deform_curve_at_halfway_point() { 23 | 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)); 24 | let deformed = bezier::move_point::>(&curve, 0.5, &Coord2(3.0, 4.0)); 25 | 26 | let original_point = curve.point_at_pos(0.5); 27 | let new_point = deformed.point_at_pos(0.5); 28 | 29 | let offset = new_point - original_point; 30 | 31 | assert!((offset.x()-3.0).abs() < 0.01); 32 | assert!((offset.y()-4.0).abs() < 0.01); 33 | 34 | assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); 35 | assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); 36 | } 37 | 38 | #[test] 39 | fn deform_curve_at_other_point() { 40 | let t = 0.32; 41 | 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)); 42 | let deformed = bezier::move_point::>(&curve, t, &Coord2(3.0, 4.0)); 43 | 44 | let original_point = curve.point_at_pos(t); 45 | let new_point = deformed.point_at_pos(t); 46 | 47 | let offset = new_point - original_point; 48 | 49 | assert!((offset.x()-3.0).abs() < 0.01); 50 | assert!((offset.y()-4.0).abs() < 0.01); 51 | 52 | assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); 53 | assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); 54 | } 55 | 56 | #[test] 57 | fn deform_curve_at_many_other_points() { 58 | for t in 0..100 { 59 | // Won't work at 0 or 1 as these are the start and end points and don't move 60 | let t = (t as f64)/100.0; 61 | let t = (0.9*t)+0.05; 62 | 63 | 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)); 64 | let deformed = bezier::move_point::>(&curve, t, &Coord2(6.0, -4.0)); 65 | 66 | let original_point = curve.point_at_pos(t); 67 | let new_point = deformed.point_at_pos(t); 68 | 69 | let offset = new_point - original_point; 70 | 71 | assert!((offset.x()-6.0).abs() < 0.01); 72 | assert!((offset.y()- -4.0).abs() < 0.01); 73 | 74 | assert!(curve.point_at_pos(0.0).distance_to(&deformed.point_at_pos(0.0)) < 0.01); 75 | assert!(curve.point_at_pos(1.0).distance_to(&deformed.point_at_pos(1.0)) < 0.01); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/bezier/derivative.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::bezier; 2 | 3 | #[test] 4 | fn take_first_derivative() { 5 | assert!(bezier::derivative4(1.0, 2.0, 3.0, 4.0) == (3.0, 3.0, 3.0)); 6 | } 7 | -------------------------------------------------------------------------------- /tests/bezier/distort.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::geo::*; 2 | use flo_curves::bezier::*; 3 | 4 | #[test] 5 | fn line_to_sine_wave() { 6 | 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)); 7 | 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"); 8 | 9 | for curve in distorted.into_iter() { 10 | println!("{:?}", curve); 11 | 12 | for section in walk_curve_evenly(&curve, 1.0, 0.1) { 13 | let (t_min, t_max) = section.original_curve_t_values(); 14 | let t_mid = (t_min+t_max)/2.0; 15 | let pos = section.point_at_pos(t_mid); 16 | 17 | let expected_y = 100.0 + (pos.x()*20.0).sin(); 18 | let actual_y = pos.y(); 19 | 20 | println!(" {:?} {:?} {:?} {:?}", t_mid, expected_y, actual_y, (expected_y-actual_y).abs()); 21 | 22 | assert!((expected_y-actual_y).abs() < 4.0); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/bezier/flatness_tests.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::geo::*; 2 | use flo_curves::bezier::*; 3 | use flo_curves::line::line_to_bezier; 4 | 5 | #[test] 6 | fn line_is_flat_1() { 7 | let line = (Coord2(100.0, 100.0), Coord2(1234.0, 5678.0)); 8 | let line = line_to_bezier::>(&line); 9 | let flatness = line.flatness(); 10 | 11 | assert!((flatness-0.0).abs() < 1e-6, "Line is not flat"); 12 | } 13 | 14 | #[test] 15 | fn line_is_flat_2() { 16 | let line = Curve::from_points(Coord2(100.0, 100.0), (Coord2(100.0, 100.0), Coord2(1234.0, 5678.0)), Coord2(1234.0, 5678.0)); 17 | let flatness = line.flatness(); 18 | 19 | assert!((flatness-0.0).abs() < 1e-6, "Line is not flat"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/bezier/length.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::bezier::*; 2 | 3 | /// 4 | /// Estimates a curve's length by subdividing it a lot 5 | /// 6 | fn subdivide_length(curve: &Curve) -> f64 { 7 | let mut length = 0.0; 8 | 9 | for subsection in walk_curve_unevenly(curve, 1000) { 10 | length += chord_length(&subsection); 11 | } 12 | 13 | length 14 | } 15 | 16 | #[test] 17 | fn measure_point_length() { 18 | 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)); 19 | let by_subdivision = subdivide_length(&c); 20 | let by_measuring = curve_length(&c, 0.5); 21 | 22 | assert!((by_measuring - by_subdivision).abs() < 1.0); 23 | assert!(by_measuring.abs() < 0.1); 24 | } 25 | 26 | #[test] 27 | fn measure_length_1() { 28 | 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)); 29 | let by_subdivision = subdivide_length(&c); 30 | let by_measuring = curve_length(&c, 0.5); 31 | 32 | assert!((by_measuring - by_subdivision).abs() < 1.0); 33 | } 34 | 35 | #[test] 36 | fn measure_length_2() { 37 | 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)); 38 | let by_subdivision = subdivide_length(&c); 39 | let by_measuring = curve_length(&c, 0.5); 40 | 41 | assert!((by_measuring - by_subdivision).abs() < 1.0); 42 | } 43 | 44 | #[test] 45 | fn measure_length_3() { 46 | 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)); 47 | let by_subdivision = subdivide_length(&c); 48 | let by_measuring = curve_length(&c, 0.5); 49 | 50 | assert!((by_measuring - by_subdivision).abs() < 1.0); 51 | } 52 | 53 | #[test] 54 | fn measure_length_4() { 55 | 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)); 56 | let by_subdivision = subdivide_length(&c); 57 | let by_measuring = curve_length(&c, 0.5); 58 | 59 | assert!((by_measuring - by_subdivision).abs() < 1.0); 60 | } 61 | -------------------------------------------------------------------------------- /tests/bezier/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | use flo_curves::*; 4 | use flo_curves::bezier; 5 | 6 | mod path; 7 | mod algorithms; 8 | mod vectorize; 9 | mod rasterize; 10 | 11 | mod basis; 12 | mod section; 13 | mod subdivide; 14 | mod derivative; 15 | mod tangent; 16 | mod normal; 17 | mod bounds; 18 | mod deform; 19 | mod search; 20 | mod solve; 21 | mod offset; 22 | mod overlaps_tests; 23 | mod intersection; 24 | mod characteristics; 25 | mod self_intersection; 26 | mod curve_intersection_clip_tests; 27 | mod length; 28 | mod walk; 29 | mod distort; 30 | mod nearest_point_tests; 31 | mod flatness_tests; 32 | 33 | pub fn approx_equal(a: f64, b: f64) -> bool { 34 | f64::floor(f64::abs(a-b)*10000.0) == 0.0 35 | } 36 | 37 | #[test] 38 | fn read_curve_control_points() { 39 | 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)); 40 | 41 | assert!(curve.start_point() == Coord2(1.0, 1.0)); 42 | assert!(curve.end_point() == Coord2(2.0, 2.0)); 43 | assert!(curve.control_points() == (Coord2(3.0, 3.0), Coord2(4.0, 4.0))); 44 | } 45 | 46 | #[test] 47 | fn read_curve_points() { 48 | 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)); 49 | 50 | for x in 0..100 { 51 | let t = (x as f64)/100.0; 52 | 53 | let point = curve.point_at_pos(t); 54 | 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)); 55 | 56 | assert!(point.distance_to(&another_point) < 0.001); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/bezier/path/arithmetic_cut.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::path::*; 3 | 4 | #[test] 5 | fn cut_square() { 6 | let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) 7 | .line_to(Coord2(10.0, 5.0)) 8 | .line_to(Coord2(10.0, 10.0)) 9 | .line_to(Coord2(5.0, 10.0)) 10 | .line_to(Coord2(5.0, 5.0)) 11 | .build(); 12 | 13 | let square_2 = BezierPathBuilder::::start(Coord2(7.5, 7.5)) 14 | .line_to(Coord2(15.0, 7.5)) 15 | .line_to(Coord2(15.0, 15.0)) 16 | .line_to(Coord2(7.5, 15.0)) 17 | .line_to(Coord2(7.5, 7.5)) 18 | .build(); 19 | 20 | let cut_square = path_cut::(&vec![square_1], &vec![square_2], 0.01); 21 | 22 | assert!(cut_square.exterior_path.len() == 1); 23 | assert!(cut_square.interior_path.len() == 1); 24 | 25 | assert!(cut_square.interior_path[0].points().len() == 4); 26 | assert!(cut_square.exterior_path[0].points().len() == 6); 27 | } 28 | 29 | #[test] 30 | fn cut_square_entirely_interior() { 31 | let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) 32 | .line_to(Coord2(10.0, 5.0)) 33 | .line_to(Coord2(10.0, 10.0)) 34 | .line_to(Coord2(5.0, 10.0)) 35 | .line_to(Coord2(5.0, 5.0)) 36 | .build(); 37 | 38 | let square_2 = BezierPathBuilder::::start(Coord2(2.0, 2.0)) 39 | .line_to(Coord2(15.0, 2.0)) 40 | .line_to(Coord2(15.0, 15.0)) 41 | .line_to(Coord2(2.0, 15.0)) 42 | .line_to(Coord2(2.0, 2.0)) 43 | .build(); 44 | 45 | let cut_square = path_cut::(&vec![square_1], &vec![square_2], 0.01); 46 | 47 | assert!(cut_square.exterior_path.len() == 0); 48 | assert!(cut_square.interior_path.len() == 1); 49 | 50 | assert!(cut_square.interior_path[0].points().len() == 4); 51 | } 52 | 53 | 54 | #[test] 55 | fn cut_square_entirely_exterior() { 56 | let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) 57 | .line_to(Coord2(10.0, 5.0)) 58 | .line_to(Coord2(10.0, 10.0)) 59 | .line_to(Coord2(5.0, 10.0)) 60 | .line_to(Coord2(5.0, 5.0)) 61 | .build(); 62 | 63 | let square_2 = BezierPathBuilder::::start(Coord2(20.0, 20.0)) 64 | .line_to(Coord2(15.0, 20.0)) 65 | .line_to(Coord2(15.0, 15.0)) 66 | .line_to(Coord2(20.0, 15.0)) 67 | .line_to(Coord2(20.0, 20.0)) 68 | .build(); 69 | 70 | let cut_square = path_cut::(&vec![square_1], &vec![square_2], 0.01); 71 | 72 | assert!(cut_square.exterior_path.len() == 1); 73 | assert!(cut_square.interior_path.len() == 0); 74 | 75 | assert!(cut_square.exterior_path[0].points().len() == 4); 76 | } 77 | 78 | #[test] 79 | fn cut_square_center() { 80 | let square_1 = BezierPathBuilder::::start(Coord2(5.0, 5.0)) 81 | .line_to(Coord2(10.0, 5.0)) 82 | .line_to(Coord2(10.0, 10.0)) 83 | .line_to(Coord2(5.0, 10.0)) 84 | .line_to(Coord2(5.0, 5.0)) 85 | .build(); 86 | 87 | let square_2 = BezierPathBuilder::::start(Coord2(6.0, 6.0)) 88 | .line_to(Coord2(9.0, 6.0)) 89 | .line_to(Coord2(9.0, 9.0)) 90 | .line_to(Coord2(6.0, 9.0)) 91 | .line_to(Coord2(6.0, 6.0)) 92 | .build(); 93 | 94 | let cut_square = path_cut::(&vec![square_1], &vec![square_2], 0.01); 95 | 96 | assert!(cut_square.exterior_path.len() == 2); 97 | assert!(cut_square.interior_path.len() == 1); 98 | 99 | assert!(cut_square.interior_path[0].points().len() == 4); 100 | assert!(cut_square.exterior_path[0].points().len() == 4); 101 | assert!(cut_square.exterior_path[1].points().len() == 4); 102 | } 103 | -------------------------------------------------------------------------------- /tests/bezier/path/bounds.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::arc::*; 3 | use flo_curves::bezier::path::*; 4 | 5 | #[test] 6 | fn circle_path_bounds() { 7 | let center = Coord2(5.0, 5.0); 8 | let radius = 4.0; 9 | 10 | // Create a path from a circle 11 | let circle: SimpleBezierPath = Circle::new(center, radius).to_path(); 12 | 13 | let bounds: (Coord2, Coord2) = circle.bounding_box(); 14 | 15 | assert!(bounds.0.distance_to(&Coord2(1.0, 1.0)) < 0.1); 16 | assert!(bounds.1.distance_to(&Coord2(9.0, 9.0)) < 0.1); 17 | } 18 | 19 | #[test] 20 | fn circle_path_fast_bounds() { 21 | let center = Coord2(5.0, 5.0); 22 | let radius = 4.0; 23 | 24 | // Create a path from a circle 25 | let circle: SimpleBezierPath = Circle::new(center, radius).to_path(); 26 | 27 | let bounds: (Coord2, Coord2) = circle.fast_bounding_box(); 28 | 29 | assert!(bounds.0.x() <= 1.0); 30 | assert!(bounds.0.y() <= 1.0); 31 | assert!(bounds.1.x() >= 9.0); 32 | assert!(bounds.1.y() >= 9.0); 33 | } 34 | -------------------------------------------------------------------------------- /tests/bezier/path/is_clockwise.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::path::*; 3 | 4 | #[test] 5 | pub fn rectangle_is_clockwise() { 6 | let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 7 | .line_to(Coord2(1.0, 1.0)) 8 | .line_to(Coord2(1.0, 5.0)) 9 | .line_to(Coord2(5.0, 5.0)) 10 | .line_to(Coord2(5.0, 1.0)) 11 | .build(); 12 | 13 | assert!(rectangle1.is_clockwise()); 14 | } 15 | 16 | #[test] 17 | pub fn rectangle_is_anticlockwise() { 18 | let rectangle1 = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 19 | .line_to(Coord2(5.0, 1.0)) 20 | .line_to(Coord2(5.0, 5.0)) 21 | .line_to(Coord2(1.0, 5.0)) 22 | .line_to(Coord2(1.0, 1.0)) 23 | .build(); 24 | 25 | assert!(!rectangle1.is_clockwise()); 26 | } 27 | -------------------------------------------------------------------------------- /tests/bezier/path/mod.rs: -------------------------------------------------------------------------------- 1 | mod svg; 2 | mod checks; 3 | mod permute; 4 | mod to_curves; 5 | mod point; 6 | mod path; 7 | mod intersection; 8 | mod bounds; 9 | mod graph_path_tests; 10 | mod is_clockwise; 11 | mod arithmetic_add; 12 | mod arithmetic_chain_add; 13 | mod arithmetic_sub; 14 | mod arithmetic_cut; 15 | mod arithmetic_intersect; 16 | mod arithmetic_complicated_paths; 17 | mod rays; 18 | mod stroke_tests; 19 | -------------------------------------------------------------------------------- /tests/bezier/path/path.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::path::*; 3 | 4 | #[test] 5 | fn reverse_rectangle() { 6 | let rectangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 7 | .line_to(Coord2(1.0, 5.0)) 8 | .line_to(Coord2(5.0, 5.0)) 9 | .line_to(Coord2(5.0, 1.0)) 10 | .line_to(Coord2(1.0, 1.0)) 11 | .build(); 12 | 13 | let reversed = rectangle.reversed::(); 14 | 15 | assert!(reversed.start_point() == Coord2(1.0, 1.0)); 16 | 17 | let points = reversed.points().collect::>(); 18 | assert!(points.len() == 4); 19 | assert!(points[0].2 == Coord2(5.0, 1.0)); 20 | assert!(points[1].2 == Coord2(5.0, 5.0)); 21 | assert!(points[2].2 == Coord2(1.0, 5.0)); 22 | assert!(points[3].2 == Coord2(1.0, 1.0)); 23 | } 24 | 25 | #[test] 26 | fn reverse_unclosed_rectangle() { 27 | let rectangle = BezierPathBuilder::::start(Coord2(1.0, 1.0)) 28 | .line_to(Coord2(1.0, 5.0)) 29 | .line_to(Coord2(5.0, 5.0)) 30 | .line_to(Coord2(5.0, 1.0)) 31 | .build(); 32 | 33 | let reversed = rectangle.reversed::(); 34 | 35 | assert!(reversed.start_point() == Coord2(5.0, 1.0)); 36 | 37 | let points = reversed.points().collect::>(); 38 | assert!(points.len() == 3); 39 | assert!(points[0].2 == Coord2(5.0, 5.0)); 40 | assert!(points[1].2 == Coord2(1.0, 5.0)); 41 | assert!(points[2].2 == Coord2(1.0, 1.0)); 42 | } 43 | -------------------------------------------------------------------------------- /tests/bezier/path/permute.rs: -------------------------------------------------------------------------------- 1 | use super::checks::*; 2 | 3 | use flo_curves::geo::*; 4 | use flo_curves::bezier::path::*; 5 | 6 | /// 7 | /// Creates a permutation of a path, by rotating and optionally reversing the points 8 | /// 9 | pub fn path_permutation(path: Vec, start_offset: usize, forward: bool) -> SimpleBezierPath { 10 | let mut result = BezierPathBuilder::start(path[start_offset]); 11 | 12 | for idx in 1..path.len() { 13 | let pos = if forward { 14 | (start_offset + idx) % path.len() 15 | } else { 16 | let idx = (path.len()) - idx; 17 | (start_offset + idx) % path.len() 18 | }; 19 | 20 | result = result.line_to(path[pos]); 21 | } 22 | 23 | result = result.line_to(path[start_offset]); 24 | 25 | result.build() 26 | } 27 | 28 | #[test] 29 | fn permutation_matches_original() { 30 | let path = vec![Coord2(206.0, 391.0), Coord2(206.0, 63.0), Coord2(281.0, 66.0), Coord2(281.0, 320.0), Coord2(649.0, 320.0), Coord2(649.0, 63.0), Coord2(734.0, 63.0), Coord2(734.0, 391.0)]; 31 | let base_path = path_permutation(path.clone(), 0, true); 32 | 33 | for forward in [true, false] { 34 | for permutation in 0..path.len() { 35 | println!("Forward: {:?}, permutation: {}/{}", forward, permutation, path.len()); 36 | 37 | let permuted = path_permutation(path.clone(), permutation, forward); 38 | 39 | assert!(path_has_points_in_order(permuted, base_path.1.iter().cloned().collect(), 0.1)); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /tests/bezier/path/stroke_tests.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::geo::*; 2 | use flo_curves::bezier::*; 3 | use flo_curves::bezier::path::*; 4 | 5 | #[test] 6 | fn stroke_closes_path_1() { 7 | let source_path = BezierPathBuilder::::start(Coord2(-0.4241647720336914, -0.5655260682106018)) 8 | .curve_to((Coord2(-0.42449551820755005, -0.5624192953109741), Coord2(-0.42483407258987427, -0.5598203539848328)), Coord2(-0.42421942949295044, -0.5567578077316284)) 9 | .curve_to((Coord2(-0.4213106632232666, -0.5422917008399963), Coord2(-0.4013028144836426, -0.541242241859436)), Coord2(-0.3897610902786255, -0.5445390939712524)) 10 | .curve_to((Coord2(-0.38367515802383423, -0.5462786555290222), Coord2(-0.37594079971313477, -0.5489739775657654)), Coord2(-0.37083661556243896, -0.5525599122047424)) 11 | .curve_to((Coord2(-0.3617064952850342, -0.5589792132377625), Coord2(-0.3560839891433716, -0.566072940826416)), Coord2(-0.3501152992248535, -0.5752266049385071)) 12 | .curve_to((Coord2(-0.3474694490432739, -0.5792812705039978), Coord2(-0.3451542854309082, -0.5823802351951599)), Coord2(-0.3430267572402954, -0.5867734551429749)) 13 | .curve_to((Coord2(-0.3401283025741577, -0.5927551984786987), Coord2(-0.3379720449447632, -0.5992813110351563)), Coord2(-0.3365788459777832, -0.6057526469230652)) 14 | .curve_to((Coord2(-0.33605802059173584, -0.6081719398498535), Coord2(-0.33473503589630127, -0.6122525930404663)), Coord2(-0.33617258071899414, -0.6146484613418579)) 15 | .curve_to((Coord2(-0.33695387840270996, -0.6159505844116211), Coord2(-0.3390554189682007, -0.6160208582878113)), Coord2(-0.340242862701416, -0.61677086353302)) 16 | .curve_to((Coord2(-0.34509706497192383, -0.6198411583900452), Coord2(-0.36623769998550415, -0.6199765801429749)), Coord2(-0.37046170234680176, -0.6194036602973938)) 17 | .curve_to((Coord2(-0.38461530208587646, -0.6174792051315308), Coord2(-0.39963871240615845, -0.611023485660553)), Coord2(-0.40813350677490234, -0.599109411239624)) 18 | .curve_to((Coord2(-0.413180410861969, -0.5920312404632568), Coord2(-0.41695642471313477, -0.5837708711624146)), Coord2(-0.42010748386383057, -0.5756927728652954)) 19 | .curve_to((Coord2(-0.4211726188659668, -0.5729583501815796), Coord2(-0.422487735748291, -0.5704115033149719)), Coord2(-0.4234902858734131, -0.5676692724227905)) 20 | .curve_to((Coord2(-0.42384183406829834, -0.5667083263397217), Coord2(-0.4238600730895996, -0.5657370090484619)), Coord2(-0.4242611527442932, -0.56480473279953)) 21 | .curve_to((Coord2(-0.4243835210800171, -0.5645208358764648), Coord2(-0.4241647720336914, -0.5653906464576721)), Coord2(-0.4241647720336914, -0.5655260682106018)) 22 | .build(); 23 | let width = 0.0026041667442768812; 24 | let options = StrokeOptions::default() 25 | .with_accuracy(0.002) 26 | .with_min_sample_distance(0.001) 27 | .with_join(LineJoin::Bevel) 28 | .with_start_cap(LineCap::Butt) 29 | .with_end_cap(LineCap::Butt); 30 | 31 | let stroked_path = stroke_path::(&source_path, width, &options); 32 | 33 | assert!(stroked_path.len() == 1, "Should be 1 subpath, found {}", stroked_path.len()); 34 | 35 | let stroked_path = &stroked_path[0]; 36 | let curves = stroked_path.to_curves::>(); 37 | 38 | assert!(curves.len() > 0, "Subpath should have at least one curve"); 39 | 40 | let start_point = curves[0].start_point(); 41 | let end_point = curves.last().unwrap().end_point(); 42 | assert!(end_point == start_point, "Path should be closed ({:?} != {:?}), curves are {:?}", start_point, end_point, curves); 43 | } 44 | -------------------------------------------------------------------------------- /tests/bezier/path/svg.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::geo::*; 2 | use flo_curves::bezier::path::*; 3 | 4 | use std::fmt::Write; 5 | 6 | pub fn svg_path_string(path: &Path) -> String 7 | where Path::Point: Coordinate2D { 8 | let mut svg = String::new(); 9 | 10 | write!(&mut svg, "M {} {}", path.start_point().x(), path.start_point().y()).unwrap(); 11 | for (cp1, cp2, end) in path.points() { 12 | write!(&mut svg, " C {} {}, {} {}, {} {}", cp1.x(), cp1.y(), cp2.x(), cp2.y(), end.x(), end.y()).unwrap(); 13 | } 14 | 15 | svg 16 | } 17 | -------------------------------------------------------------------------------- /tests/bezier/path/to_curves.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::*; 3 | use flo_curves::bezier::path::*; 4 | 5 | #[test] 6 | pub fn convert_path_to_bezier_curves() { 7 | 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))]); 8 | let curve = path_to_curves::<_, Curve<_>>(&path); 9 | let curve: Vec<_> = curve.collect(); 10 | 11 | assert!(curve.len() == 2); 12 | assert!(curve[0] == Curve { 13 | start_point: Coord2(10.0, 11.0), 14 | end_point: Coord2(19.0, 20.0), 15 | control_points: (Coord2(15.0, 16.0), Coord2(17.0, 18.0)) 16 | }); 17 | assert!(curve[1] == Curve { 18 | start_point: Coord2(19.0, 20.0), 19 | end_point: Coord2(25.0, 26.0), 20 | control_points: (Coord2(21.0, 22.0), Coord2(23.0, 24.0)) 21 | }); 22 | } 23 | 24 | #[test] 25 | pub fn no_points_means_no_curve() { 26 | let path = (Coord2(10.0, 11.0), vec![]); 27 | let curve = path_to_curves::<_, Curve<_>>(&path); 28 | let curve: Vec<_> = curve.collect(); 29 | 30 | assert!(curve.len() == 0); 31 | } 32 | -------------------------------------------------------------------------------- /tests/bezier/rasterize/mod.rs: -------------------------------------------------------------------------------- 1 | mod ray_cast_contour_tests; 2 | mod path_contour_tests; 3 | mod path_distance_field_tests; 4 | mod marching_parabolas_tests; 5 | -------------------------------------------------------------------------------- /tests/bezier/search.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::bezier; 2 | 3 | #[test] 4 | fn search_for_x_coordinate() { 5 | // Initial curve 6 | let (w1, w2, w3, w4) = (1.0, -2.0, 3.0, 4.0); 7 | 8 | // Search for the t value for a particular X coord 9 | let x_coord = 1.5; 10 | let matching_values = bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); 11 | 12 | // Should be only 1 coordinate with this curve 13 | assert!(matching_values.len() == 1); 14 | 15 | // Basis function should be within 0.01 16 | let actual_val = bezier::basis(matching_values[0], w1, w2, w3, w4); 17 | assert!((actual_val-x_coord).abs() < 0.01); 18 | } 19 | 20 | #[test] 21 | fn coordinate_outside_curve_produces_no_results() { 22 | // Initial curve 23 | let (w1, w2, w3, w4) = (1.0, -2.0, 3.0, 4.0); 24 | 25 | // Search for the t value for a particular X coord, which is outside the curve 26 | let x_coord = 5.0; 27 | let matching_values = bezier::search_bounds4(0.01, w1, w2, w3, w4, |p1, p2| p1 < x_coord && p2 > x_coord); 28 | 29 | // No points on the curve match this coordinate 30 | assert!(matching_values.len() == 0); 31 | } 32 | -------------------------------------------------------------------------------- /tests/bezier/section.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::*; 3 | 4 | #[test] 5 | fn section_points_match() { 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)); 7 | let mid_section = original_curve.section(0.25, 0.75); 8 | 9 | for t in 0..=10 { 10 | let t = (t as f64)/10.0; 11 | let t2 = t*0.5 + 0.25; 12 | 13 | let p1 = mid_section.point_at_pos(t); 14 | let p2 = original_curve.point_at_pos(t2); 15 | 16 | assert!(p1.distance_to(&p2) < 0.0001); 17 | } 18 | } 19 | 20 | #[test] 21 | fn generate_curve_from_section() { 22 | 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)); 23 | let mid_section = Curve::from_curve(&original_curve.section(0.2, 0.6)); 24 | 25 | for t in 0..=10 { 26 | let t = (t as f64)/10.0; 27 | let t2 = t*0.4 + 0.2; 28 | 29 | let p1 = mid_section.point_at_pos(t); 30 | let p2 = original_curve.point_at_pos(t2); 31 | 32 | assert!(p1.distance_to(&p2) < 0.0001); 33 | } 34 | } 35 | 36 | #[test] 37 | fn section_of_section() { 38 | 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)); 39 | let mut mid_section = original_curve.section(0.25, 0.75); 40 | mid_section = mid_section.subsection(0.25, 0.75); 41 | 42 | for t in 0..=10 { 43 | let t = (t as f64)/10.0; 44 | let t2 = t*0.25 + 0.375; 45 | 46 | let p1 = mid_section.point_at_pos(t); 47 | let p2 = original_curve.point_at_pos(t2); 48 | 49 | assert!(p1.distance_to(&p2) < 0.0001); 50 | } 51 | } 52 | 53 | #[test] 54 | fn recover_original_t_values() { 55 | 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)); 56 | let mid_section = original_curve.section(0.2, 0.6); 57 | 58 | assert!(mid_section.original_curve_t_values() == (0.2, 0.6)); 59 | } 60 | 61 | #[test] 62 | fn map_t_values_back_to_section() { 63 | 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)); 64 | let mid_section = original_curve.section(0.2, 0.6); 65 | 66 | assert!((mid_section.section_t_for_original_t(0.2)-0.0).abs() < 0.01); 67 | assert!((mid_section.section_t_for_original_t(0.4)-0.5).abs() < 0.01); 68 | assert!((mid_section.section_t_for_original_t(0.6)-1.0).abs() < 0.01); 69 | } 70 | 71 | #[test] 72 | fn recover_original_t_values_from_subsection() { 73 | 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)); 74 | let mid_section = original_curve.section(0.25, 0.75); 75 | let sub_section = mid_section.subsection(0.25, 0.75); 76 | 77 | assert!(sub_section.original_curve_t_values() == (0.375, 0.625)); 78 | } 79 | -------------------------------------------------------------------------------- /tests/bezier/self_intersection.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier::*; 3 | 4 | #[test] 5 | fn find_simple_self_intersection() { 6 | 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)); 7 | let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); 8 | 9 | assert!(intersection_point.is_some()); 10 | 11 | let (t1, t2) = intersection_point.unwrap(); 12 | let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); 13 | 14 | assert!(p1.is_near_to(&p2, 0.01)); 15 | } 16 | 17 | #[test] 18 | fn whole_curve_is_a_loop() { 19 | 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)); 20 | let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); 21 | 22 | assert!(intersection_point.is_some()); 23 | 24 | let (t1, t2) = intersection_point.unwrap(); 25 | let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); 26 | 27 | assert!(p1.is_near_to(&p2, 0.01)); 28 | assert!(t1 <= 0.0); 29 | assert!(t2 >= 1.0); 30 | } 31 | 32 | #[test] 33 | fn narrow_loop() { 34 | 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)); 35 | let intersection_point = find_self_intersection_point(&curve_with_loop, 0.01); 36 | 37 | assert!(intersection_point.is_some()); 38 | 39 | let (t1, t2) = intersection_point.unwrap(); 40 | let (p1, p2) = (curve_with_loop.point_at_pos(t1), curve_with_loop.point_at_pos(t2)); 41 | 42 | assert!(p1.is_near_to(&p2, 0.01)); 43 | } 44 | -------------------------------------------------------------------------------- /tests/bezier/solve.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::bezier::*; 2 | 3 | #[test] 4 | fn basis_solve_middle() { 5 | assert!((solve_basis_for_t(0.0, 0.33, 0.66, 1.0, 0.5)[0]-0.5).abs() < 0.01); 6 | assert!((solve_basis_for_t(0.0, 1.0, 2.0, 3.0, 1.5)[0]-0.5).abs() < 0.01); 7 | } 8 | 9 | #[test] 10 | fn basis_solve_many() { 11 | fn test_for(w1: f64, w2: f64, w3: f64, w4: f64) { 12 | for p in 0..=16 { 13 | // Pick a point between w1 and w4 14 | let p = ((p as f64)/16.0)*(w4-w1) + w1; 15 | 16 | // Solve for t values 17 | let t_values = solve_basis_for_t(w1, w2, w3, w4, p); 18 | 19 | // Computing the points for these values should result in a valid curve 20 | let pos_for_t = t_values.iter() 21 | .map(|t| basis(*t, w1, w2, w3, w4)) 22 | .collect::>(); 23 | 24 | // Should all evaluate to positions on the curve 25 | pos_for_t.iter().for_each(|pos| assert!((pos-p).abs() < 0.01)); 26 | } 27 | } 28 | 29 | test_for(0.0, 0.33, 0.66, 1.0); 30 | test_for(2.0, 3.0, 4.0, 5.0); 31 | test_for(2.0, -1.0, 5.0, 3.0); 32 | } 33 | 34 | #[test] 35 | fn solve_t_for_pos() { 36 | 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)); 37 | 38 | let point_at_one_third = curve1.point_at_pos(0.3333); 39 | let solved = curve1.t_for_point(&point_at_one_third); 40 | 41 | assert!(solved.is_some()); 42 | assert!((solved.unwrap()-0.3333).abs() < 0.001); 43 | } 44 | 45 | #[test] 46 | fn solve_t_for_start() { 47 | 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)); 48 | 49 | let solved = curve1.t_for_point(&Coord2(10.0, 100.0)); 50 | 51 | assert!(solved.is_some()); 52 | assert!((solved.unwrap()-0.0).abs() < 0.001); 53 | } 54 | 55 | #[test] 56 | fn solve_t_for_end() { 57 | 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)); 58 | 59 | let solved = curve1.t_for_point(&Coord2(220.0, 220.0)); 60 | 61 | assert!(solved.is_some()); 62 | assert!((solved.unwrap()-1.0).abs() < 0.001); 63 | } 64 | 65 | #[test] 66 | fn solve_t_for_many_positions() { 67 | 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)); 68 | 69 | for p in 0..10 { 70 | let p = (p as f64)/10.0; 71 | let point = curve1.point_at_pos(p); 72 | let solved = curve1.t_for_point(&point); 73 | 74 | assert!(solved.is_some()); 75 | assert!((solved.unwrap()-p).abs() < 0.001); 76 | } 77 | } 78 | 79 | #[test] 80 | fn solve_t_for_out_of_bounds() { 81 | 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)); 82 | 83 | let solved = curve1.t_for_point(&Coord2(45.0, 23.0)); 84 | assert!(solved.is_none()); 85 | } 86 | -------------------------------------------------------------------------------- /tests/bezier/subdivide.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use flo_curves::bezier; 4 | 5 | #[test] 6 | fn subdivide_1() { 7 | // Initial curve 8 | let (w1, w2, w3, w4) = (1.0, 2.0, 3.0, 4.0); 9 | 10 | // Subdivide at 33%, creating two curves 11 | let ((wa1, wa2, wa3, wa4), (_wb1, _wb2, _wb3, _wb4)) = bezier::subdivide4(0.33, w1, w2, w3, w4); 12 | 13 | // Check that the original curve corresponds to the basis function for wa 14 | for x in 0..100 { 15 | let t = (x as f64)/100.0; 16 | 17 | let original = bezier::basis(t*0.33, w1, w2, w3, w4); 18 | let subdivision = bezier::basis(t, wa1, wa2, wa3, wa4); 19 | 20 | assert!(approx_equal(original, subdivision)); 21 | } 22 | } 23 | 24 | #[test] 25 | fn subdivide_2() { 26 | // Initial curve 27 | let (w1, w2, w3, w4) = (1.0, 2.0, 3.0, 4.0); 28 | 29 | // Subdivide at 33%, creating two curves 30 | let ((_wa1, _wa2, _wa3, _wa4), (wb1, wb2, wb3, wb4)) = bezier::subdivide4(0.33, w1, w2, w3, w4); 31 | 32 | // Check that the original curve corresponds to the basis function for wb 33 | for x in 0..100 { 34 | let t = (x as f64)/100.0; 35 | 36 | let original = bezier::basis(0.33+(t*(1.0-0.33)), w1, w2, w3, w4); 37 | let subdivision = bezier::basis(t, wb1, wb2, wb3, wb4); 38 | 39 | assert!(approx_equal(original, subdivision)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/bezier/tangent.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::bezier; 3 | 4 | #[test] 5 | fn calculate_tangent_for_straight_line() { 6 | 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)); 7 | let tangent = bezier::Tangent::from(&straight_line); 8 | 9 | assert!(tangent.tangent(0.5) == Coord2(2.25, 2.25)); 10 | 11 | assert!(tangent.tangent(0.0).x() == tangent.tangent(0.0).y()); 12 | assert!(tangent.tangent(0.5).x() == tangent.tangent(0.5).y()); 13 | assert!(tangent.tangent(0.7).x() == tangent.tangent(0.7).y()); 14 | assert!(tangent.tangent(1.0).x() == tangent.tangent(1.0).y()); 15 | } 16 | -------------------------------------------------------------------------------- /tests/bezier/vectorize/mod.rs: -------------------------------------------------------------------------------- 1 | mod sampled_contour_tests; 2 | mod marching_squares_tests; 3 | mod circular_distance_field_tests; 4 | mod daub_brush_distance_field_tests; 5 | mod brush_stroke_tests; 6 | mod scaled_distance_field_tests; 7 | -------------------------------------------------------------------------------- /tests/bounds.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | extern crate flo_curves; 4 | 5 | use flo_curves::*; 6 | 7 | #[test] 8 | fn overlapping_rects() { 9 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 40.0)); 10 | let r2 = (Coord2(20.0, 25.0), Coord2(35.0, 35.0)); 11 | 12 | assert!(r1.overlaps(&r2)); 13 | } 14 | 15 | #[test] 16 | fn non_overlapping_rects() { 17 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 40.0)); 18 | let r2 = (Coord2(20.0, 25.0), Coord2(9.0, 10.0)); 19 | 20 | assert!(!r1.overlaps(&r2)); 21 | } 22 | 23 | #[test] 24 | fn same_rects() { 25 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 40.0)); 26 | 27 | assert!(r1.overlaps(&r1)); 28 | } 29 | 30 | #[test] 31 | fn touching_rects() { 32 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 40.0)); 33 | let r2 = (Coord2(20.0, 25.0), Coord2(30.0, 30.0)); 34 | 35 | assert!(r1.overlaps(&r2)); 36 | } 37 | 38 | #[test] 39 | fn overlap_interior_rect() { 40 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 50.0)); 41 | let r2 = (Coord2(35.0, 35.0), Coord2(55.0, 45.0)); 42 | 43 | assert!(r1.overlaps(&r2)); 44 | } 45 | 46 | #[test] 47 | fn overlap_exterior_rect() { 48 | let r1 = (Coord2(30.0, 30.0), Coord2(60.0, 40.0)); 49 | let r2 = (Coord2(20.0, 20.0), Coord2(70.0, 50.0)); 50 | 51 | assert!(r1.overlaps(&r2)); 52 | } 53 | 54 | #[test] 55 | fn from_points() { 56 | let r = Bounds::::bounds_for_points(vec![ 57 | Coord2(30.0, 30.0), 58 | Coord2(60.0, 40.0), 59 | Coord2(45.0, 70.0), 60 | Coord2(10.0, 35.0) 61 | ]); 62 | 63 | assert!(r.min() == Coord2(10.0, 30.0)); 64 | assert!(r.max() == Coord2(60.0, 70.00)); 65 | } 66 | -------------------------------------------------------------------------------- /tests/coordinates.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | extern crate flo_curves; 4 | 5 | use flo_curves::*; 6 | 7 | use std::f64; 8 | 9 | #[test] 10 | fn can_get_distance_between_points() { 11 | assert!(Coord2(1.0, 1.0).distance_to(&Coord2(1.0, 8.0)) == 7.0); 12 | } 13 | 14 | #[test] 15 | fn can_find_unit_vector() { 16 | assert!(Coord2(0.0, 1.0).to_unit_vector() == Coord2(0.0, 1.0)); 17 | assert!(Coord2(0.0, 2.0).to_unit_vector() == Coord2(0.0, 1.0)); 18 | 19 | assert!(f64::abs(Coord2(4.0, 2.0).to_unit_vector().distance_to(&Coord2(0.0, 0.0))-1.0) < 0.01); 20 | } 21 | 22 | #[test] 23 | fn unit_vector_of_0_0_is_0_0() { 24 | assert!(Coord2(0.0, 0.0).to_unit_vector() == Coord2(0.0, 0.0)); 25 | } 26 | 27 | #[test] 28 | fn can_get_dot_product() { 29 | assert!(Coord2(2.0,1.0).dot(&Coord2(3.0, 4.0)) == 10.0); 30 | } 31 | 32 | #[test] 33 | fn round_to_hundredths() { 34 | assert!(Coord2(1.1111, 2.2222).round(0.01) == Coord2(1.11, 2.22)); 35 | } 36 | 37 | #[test] 38 | fn round_to_units() { 39 | assert!(Coord2(1.1111, 2.2222).round(1.0) == Coord2(1.0, 2.0)); 40 | } 41 | 42 | #[test] 43 | fn round_up_to_units() { 44 | assert!(Coord2(1.1111, 2.5555).round(1.0) == Coord2(1.0, 3.0)); 45 | } 46 | 47 | #[test] 48 | fn unit_vector_0_degrees() { 49 | assert!(Coord2::unit_vector_at_angle(0.0).distance_to(&Coord2(1.0, 0.0)) < 0.001); 50 | } 51 | 52 | #[test] 53 | fn unit_vector_90_degrees() { 54 | assert!(Coord2::unit_vector_at_angle(f64::consts::PI / 2.0).distance_to(&Coord2(0.0, 1.0)) < 0.001); 55 | } 56 | -------------------------------------------------------------------------------- /tests/line/angle.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::line::*; 2 | 3 | use std::f64; 4 | 5 | #[test] 6 | fn angle_on_x_axis_is_0() { 7 | let angle = (Coord2(1.0, 1.0), Coord2(2.0, 1.0)).angle(); 8 | 9 | assert!((angle-0.0).abs() < 0.01, "Angle should be 0.0 but is {}", angle); 10 | } 11 | 12 | #[test] 13 | fn angle_on_x_axis_reversed_is_pi() { 14 | let angle = (Coord2(2.0, 1.0), Coord2(1.0, 1.0)).angle(); 15 | 16 | assert!((angle-f64::consts::PI).abs() < 0.01, "Angle should be 0.0 but is {}", angle); 17 | } 18 | 19 | #[test] 20 | fn angle_on_y_axis_is_half_pi() { 21 | let angle = (Coord2(1.0, 1.0), Coord2(1.0, 2.0)).angle(); 22 | 23 | assert!((angle-f64::consts::PI/2.0).abs() < 0.01, "Angle should be pi/2 but is {}", angle); 24 | } 25 | 26 | #[test] 27 | fn angle_on_y_axis_reversed_is_three_halfs_pi() { 28 | let angle = (Coord2(1.0, 2.0), Coord2(1.0, 1.0)).angle(); 29 | 30 | assert!((angle-3.0*f64::consts::PI/2.0).abs() < 0.01, "Angle should be 3*pi/2 but is {}", angle); 31 | } 32 | 33 | #[test] 34 | fn angle_between_45_degree_lines_1() { 35 | let line1 = (Coord2(1.0, 1.0), Coord2(0.0, 2.0)); 36 | let line2 = (Coord2(1.0, 1.0), Coord2(2.0, 2.0)); 37 | 38 | let angle_between = line1.angle_to(&line2); 39 | 40 | assert!((angle_between-f64::consts::PI/2.0).abs() < 0.01, "Angle should be pi/2 but is {}", angle_between); 41 | } 42 | 43 | #[test] 44 | fn angle_between_45_degree_lines_2() { 45 | let line1 = (Coord2(1.0, 1.0), Coord2(2.0, 2.0)); 46 | let line2 = (Coord2(1.0, 1.0), Coord2(0.0, 2.0)); 47 | 48 | let angle_between = line1.angle_to(&line2); 49 | 50 | assert!((angle_between-3.0*f64::consts::PI/2.0).abs() < 0.01, "Angle should be 3*pi/2 but is {}", angle_between); 51 | } 52 | -------------------------------------------------------------------------------- /tests/line/intersection.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::line::*; 3 | 4 | #[test] 5 | fn intersection_at_0_0() { 6 | 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); 7 | } 8 | 9 | #[test] 10 | fn intersection_at_other_point() { 11 | 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); 12 | } 13 | 14 | #[test] 15 | fn ray_intersects_line() { 16 | 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); 17 | } 18 | 19 | #[test] 20 | fn two_rays_intersect() { 21 | 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); 22 | } 23 | 24 | #[test] 25 | fn no_intersection() { 26 | 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); 27 | } 28 | 29 | #[test] 30 | fn line_in_bounds() { 31 | let line = (Coord2(5.0, 3.0), Coord2(7.0, 9.0)); 32 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 33 | let clipped = line_clip_to_bounds(&line, &bounds); 34 | 35 | assert!(clipped.is_some()); 36 | 37 | let clipped = clipped.unwrap(); 38 | assert!(clipped.0.distance_to(&Coord2(5.0, 3.0)) < 0.01); 39 | assert!(clipped.1.distance_to(&Coord2(7.0, 9.0)) < 0.01); 40 | } 41 | 42 | #[test] 43 | fn horizontal_clipped_line() { 44 | let line = (Coord2(-10.0, 4.0), Coord2(20.0, 4.0)); 45 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 46 | let clipped = line_clip_to_bounds(&line, &bounds); 47 | 48 | assert!(clipped.is_some()); 49 | 50 | let clipped = clipped.unwrap(); 51 | assert!(clipped.0.distance_to(&Coord2(1.0, 4.0)) < 0.01); 52 | assert!(clipped.1.distance_to(&Coord2(10.0, 4.0)) < 0.01); 53 | } 54 | 55 | #[test] 56 | fn horizontal_clipped_line_inside_to_outside() { 57 | let line = (Coord2(5.0, 4.0), Coord2(20.0, 4.0)); 58 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 59 | let clipped = line_clip_to_bounds(&line, &bounds); 60 | 61 | assert!(clipped.is_some()); 62 | 63 | let clipped = clipped.unwrap(); 64 | assert!(clipped.0.distance_to(&Coord2(5.0, 4.0)) < 0.01); 65 | assert!(clipped.1.distance_to(&Coord2(10.0, 4.0)) < 0.01); 66 | } 67 | 68 | #[test] 69 | fn horizontal_clipped_line_inside_to_outside_reverse() { 70 | let line = (Coord2(20.0, 4.0), Coord2(5.0, 4.0)); 71 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 72 | let clipped = line_clip_to_bounds(&line, &bounds); 73 | 74 | assert!(clipped.is_some()); 75 | 76 | let clipped = clipped.unwrap(); 77 | assert!(clipped.0.distance_to(&Coord2(10.0, 4.0)) < 0.01); 78 | assert!(clipped.1.distance_to(&Coord2(5.0, 4.0)) < 0.01); 79 | } 80 | 81 | #[test] 82 | fn vertical_clipped_line_inside_to_outside() { 83 | let line = (Coord2(5.0, 4.0), Coord2(5.0, 20.0)); 84 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 85 | let clipped = line_clip_to_bounds(&line, &bounds); 86 | 87 | assert!(clipped.is_some()); 88 | 89 | let clipped = clipped.unwrap(); 90 | assert!(clipped.0.distance_to(&Coord2(5.0, 4.0)) < 0.01); 91 | assert!(clipped.1.distance_to(&Coord2(5.0, 10.0)) < 0.01); 92 | } 93 | 94 | #[test] 95 | fn line_out_of_bounds_right() { 96 | let line = (Coord2(11.0, 9.5), Coord2(20.0, 9.0)); 97 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 98 | let clipped = line_clip_to_bounds(&line, &bounds); 99 | 100 | assert!(clipped.is_none()); 101 | } 102 | 103 | #[test] 104 | fn line_out_of_bounds_left() { 105 | let line = (Coord2(-11.0, 9.5), Coord2(-20.0, 9.0)); 106 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 107 | let clipped = line_clip_to_bounds(&line, &bounds); 108 | 109 | assert!(clipped.is_none()); 110 | } 111 | 112 | #[test] 113 | fn line_out_of_bounds_crossing() { 114 | let line = (Coord2(9.0, 0.0), Coord2(20.0, 9.0)); 115 | let bounds = (Coord2(1.0, 1.0), Coord2(10.0, 10.0)); 116 | let clipped = line_clip_to_bounds(&line, &bounds); 117 | 118 | assert!(clipped.is_none()); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /tests/line/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | mod to_curve; 4 | mod intersection; 5 | mod coefficients; 6 | mod nearest; 7 | mod angle; 8 | -------------------------------------------------------------------------------- /tests/line/to_curve.rs: -------------------------------------------------------------------------------- 1 | use flo_curves::*; 2 | use flo_curves::line::*; 3 | use flo_curves::bezier::*; 4 | 5 | #[test] 6 | fn convert_line_to_bezier_curve() { 7 | let line = (Coord2(10.0, 20.0), Coord2(40.0, 30.0)); 8 | let curve = line_to_bezier::>(&line); 9 | 10 | assert!(curve.start_point == Coord2(10.0, 20.0)); 11 | assert!(curve.end_point == Coord2(40.0, 30.0)); 12 | assert!(curve.control_points.0.distance_to(&Coord2(20.0, 23.33)) < 0.1); 13 | assert!(curve.control_points.1.distance_to(&Coord2(30.0, 26.66)) < 0.1); 14 | } 15 | -------------------------------------------------------------------------------- /tests/readme.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] // Tests are lower priority to fix 2 | 3 | use std::str; 4 | 5 | /// 6 | /// Reads the README file for the crate 7 | /// 8 | fn readme() -> &'static str { 9 | let readme_bytes = include_bytes!("../README.md"); 10 | let readme_str = str::from_utf8(readme_bytes); 11 | 12 | readme_str.expect("Could not decode README.md") 13 | } 14 | 15 | #[test] 16 | fn starts_with_version_number_toml() { 17 | let major_version = env!("CARGO_PKG_VERSION_MAJOR"); 18 | let minor_version = env!("CARGO_PKG_VERSION_MINOR"); 19 | 20 | let expected = format!("```toml 21 | flo_curves = \"{}.{}\" 22 | ```", major_version, minor_version); 23 | 24 | println!("{}", expected); 25 | 26 | let readme = if !readme().starts_with(&expected) { 27 | readme().to_string().replace("\r\n", "\n") 28 | } else { 29 | readme().into() 30 | }; 31 | 32 | assert!(readme.starts_with(&expected)); 33 | } 34 | --------------------------------------------------------------------------------