├── .gitignore ├── src ├── areas │ ├── tests │ │ ├── 1_intersection.svg │ │ ├── 3_difference.svg │ │ ├── 1_union.svg │ │ ├── 2_intersection.svg │ │ ├── 2_difference.svg │ │ ├── 1_difference.svg │ │ └── mod.rs │ ├── debug.rs │ └── mod.rs ├── convert_2d_3d.rs ├── angles.rs ├── rough_eq.rs ├── bbox.rs ├── lib.rs ├── band.rs ├── closed_line_path.rs ├── grid.rs ├── segment_grid.rs ├── util.rs ├── intersect.rs ├── embedding.rs ├── edit_arc_line_path.rs ├── arc_line_path.rs ├── line_path.rs ├── segments.rs └── area_embedding.rs ├── Cargo.toml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /src/areas/tests/1_intersection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/areas/tests/3_difference.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/areas/tests/1_union.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/areas/tests/2_intersection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/convert_2d_3d.rs: -------------------------------------------------------------------------------- 1 | use {V2, P2, V3, P3}; 2 | 3 | pub trait Into2d { 4 | type Target; 5 | fn into_2d(self) -> Self::Target; 6 | } 7 | 8 | impl Into2d for V3 { 9 | type Target = V2; 10 | fn into_2d(self) -> V2 { 11 | V2::new(self.x, self.y) 12 | } 13 | } 14 | 15 | impl Into2d for P3 { 16 | type Target = P2; 17 | fn into_2d(self) -> P2 { 18 | P2::new(self.x, self.y) 19 | } 20 | } 21 | 22 | pub trait Into3d { 23 | type Target; 24 | fn into_3d(self) -> Self::Target; 25 | } 26 | 27 | impl Into3d for V2 { 28 | type Target = V3; 29 | fn into_3d(self) -> V3 { 30 | V3::new(self.x, self.y, 0.0) 31 | } 32 | } 33 | 34 | impl Into3d for P2 { 35 | type Target = P3; 36 | fn into_3d(self) -> P3 { 37 | P3::new(self.x, self.y, 0.0) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/areas/tests/2_difference.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "descartes" 3 | version = "0.1.20" 4 | description = "Imprecision-tolerant computational geometry for Rust" 5 | authors = ["Anselm Eickhoff "] 6 | repository = "https://github.com/aeickhoff/descartes" 7 | license = "MIT" 8 | [badges] 9 | maintenance = { status = "experimental" } 10 | 11 | [dependencies] 12 | nalgebra = "0.20" 13 | ordered-float = "0.5.0" 14 | itertools = "0.7.6" 15 | fnv = "1.0.6" 16 | serde = {version = "1", optional = true} 17 | serde_derive = {version = "1", optional = true} 18 | stable-vec = "0.3.1" 19 | smallvec = "0.6.5" 20 | 21 | [dependencies.compact] 22 | version = "0.2.9" 23 | optional = true 24 | 25 | [dependencies.compact_macros] 26 | version = "0.1.0" 27 | optional = true 28 | 29 | [dev-dependencies] 30 | pretty_assertions = "0.5.1" 31 | 32 | [features] 33 | default = [] 34 | compact_containers = ["compact", "compact_macros"] 35 | stdweb = ["nalgebra/stdweb"] 36 | serde-serialization = ["serde", "serde_derive", "nalgebra/serde-serialize"] -------------------------------------------------------------------------------- /src/areas/tests/1_difference.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anselm Eickhoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/angles.rs: -------------------------------------------------------------------------------- 1 | use {V2, N, dot, PI}; 2 | 3 | #[inline] 4 | pub fn angle_to(a: V2, b: V2) -> N { 5 | let theta: N = dot(&a, &b) / (a.norm() * b.norm()); 6 | theta.min(1.0).max(-1.0).acos() 7 | } 8 | 9 | #[inline] 10 | pub fn angle_along_to(a: V2, a_direction: V2, b: V2) -> N { 11 | let simple_angle = angle_to(a, b); 12 | let linear_direction = (b - a).normalize(); 13 | 14 | if a_direction.dot(&linear_direction) >= 0.0 { 15 | simple_angle 16 | } else { 17 | 2.0 * PI - simple_angle 18 | } 19 | } 20 | 21 | #[inline] 22 | pub fn signed_angle_to(a: V2, b: V2) -> N { 23 | // https://stackoverflow.com/a/2150475 24 | let det = a.x * b.y - a.y * b.x; 25 | let dot = a.x * b.x + a.y * b.y; 26 | (det).atan2(dot) 27 | } 28 | 29 | // 30 | // DESCARTES ASSUMES 31 | // A RIGHT HAND COORDINATE SYSTEM 32 | // 33 | // positive angles are counter-clockwise if z axis points out of screen 34 | // 35 | 36 | pub trait WithUniqueOrthogonal: ::std::ops::Neg + Sized { 37 | fn orthogonal_right(&self) -> Self; 38 | fn orthogonal_left(&self) -> Self { 39 | -self.orthogonal_right() 40 | } 41 | } 42 | 43 | impl WithUniqueOrthogonal for V2 { 44 | fn orthogonal_right(&self) -> V2 { 45 | V2::new(self.y, -self.x) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/areas/debug.rs: -------------------------------------------------------------------------------- 1 | use super::{N, P2, LinePath}; 2 | 3 | impl LinePath { 4 | pub fn from_svg(string: &str) -> Option { 5 | let mut tokens = string.split_whitespace(); 6 | let mut points = vec![]; 7 | 8 | while let Some(command) = tokens.next() { 9 | if command == "M" || command == "L" { 10 | let x: N = tokens 11 | .next() 12 | .expect("Expected 1st token after M/L") 13 | .parse() 14 | .expect("Can't parse 1st token after M/L"); 15 | let y: N = tokens 16 | .next() 17 | .expect("Expected 2nd token after M/L") 18 | .parse() 19 | .expect("Can't parse 2nd token after M/L"); 20 | 21 | points.push(P2::new(x, y)); 22 | } else if command == "Z" { 23 | let first_point = points[0]; 24 | points.push(first_point) 25 | } 26 | } 27 | 28 | Self::new(points.into()) 29 | } 30 | 31 | pub fn to_svg(&self) -> String { 32 | format!( 33 | "M {}", 34 | self.points 35 | .iter() 36 | .map(|point| format!("{} {}", point.x, point.y)) 37 | .collect::>() 38 | .join(" L ") 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # descartes 2 | 3 | descartes is a... 4 | 5 | - [X] Error-tolerant 2D geometry engine 6 | - [X] that allows for arbitrary error tolerances 7 | - [X] dealing with both floating-point inaccuracies and much larger user input inaccuracies 8 | 9 | With the following primitives: 10 | 11 | - [X] line 12 | - [X] circle 13 | - [X] line/circle segment 14 | 15 | And the following compound objects: 16 | 17 | - [X] Path (continuous concatenation of line/circle segments) 18 | - [ ] Shape (Path outline with 0..n Path holes) 19 | - [X] Band (a path with a thickness) 20 | 21 | It offers... 22 | 23 | - [ ] Reexported 2D & 3D Point/Vector operations from `nalgebra` 24 | - [X] Projections from and onto lines, circles, segments, paths, bands 25 | - [X] Intersections 26 | - [X] between lines & circles 27 | - [X] between line/circle segments 28 | - [X] between paths 29 | - [X] Axis-aligned bounding boxes for 30 | - [X] line/circle segments 31 | - [X] paths 32 | - [ ] Boolean operations between shapes 33 | - [X] Orthogonal offsets of segments 34 | - [ ] True Orthogonal offsets of paths (without producing self-intersections) 35 | - [X] A `RoughEq` Trait for comparing things within a tolerance, with implementations for `P2, P3` and `V2, P3` 36 | 37 | It internally uses... 38 | 39 | - [X] "Thick" primitives for tolerances 40 | 41 | descartes is named after [René Descartes](https://en.wikipedia.org/wiki/René_Descartes), the father of analytical geometry. -------------------------------------------------------------------------------- /src/rough_eq.rs: -------------------------------------------------------------------------------- 1 | use {N, P2, V2}; 2 | 3 | // Thickness radius 4 | pub const THICKNESS: N = 0.001; 5 | const ROUGH_TOLERANCE: N = 0.000_000_1; 6 | 7 | pub trait RoughEq: Sized { 8 | fn rough_eq(&self, other: Self) -> bool { 9 | self.rough_eq_by(other, ROUGH_TOLERANCE) 10 | } 11 | fn rough_eq_by(&self, other: Self, tolerance: N) -> bool; 12 | } 13 | 14 | impl RoughEq for N { 15 | fn rough_eq_by(&self, other: N, tolerance: N) -> bool { 16 | (self - other).abs() <= tolerance 17 | } 18 | } 19 | 20 | impl RoughEq for P2 { 21 | fn rough_eq_by(&self, other: P2, tolerance: N) -> bool { 22 | (*self - other).norm() <= tolerance 23 | } 24 | } 25 | 26 | impl RoughEq for V2 { 27 | fn rough_eq_by(&self, other: V2, tolerance: N) -> bool { 28 | (*self - other).norm() <= tolerance 29 | } 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! assert_rough_eq_by { 34 | ($left:expr , $right:expr, $tol:expr,) => ({ 35 | assert_eq!($left, $right, $tol) 36 | }); 37 | ($left:expr , $right:expr, $tol:expr) => ({ 38 | match (&($left), &($right)) { 39 | (left_val, right_val) => { 40 | if !((*left_val).rough_eq_by(*right_val, $tol)) { 41 | panic!("assertion failed: `(left ~= right by {})`\ 42 | \n\ 43 | \n{}\ 44 | \n", 45 | $tol, 46 | ::pretty_assertions::Comparison::new(left_val, right_val)) 47 | } 48 | } 49 | } 50 | }); 51 | } -------------------------------------------------------------------------------- /src/bbox.rs: -------------------------------------------------------------------------------- 1 | use {P2, V2, INFINITY, N, NEG_INFINITY}; 2 | 3 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 4 | #[derive(Copy, Clone, Debug)] 5 | pub struct BoundingBox { 6 | pub min: P2, 7 | pub max: P2, 8 | } 9 | 10 | impl BoundingBox { 11 | pub fn infinite() -> Self { 12 | BoundingBox { 13 | min: P2::new(NEG_INFINITY, NEG_INFINITY), 14 | max: P2::new(INFINITY, INFINITY), 15 | } 16 | } 17 | 18 | pub fn overlaps(&self, other: &BoundingBox) -> bool { 19 | self.max.x >= other.min.x 20 | && other.max.x >= self.min.x 21 | && self.max.y >= other.min.y 22 | && other.max.y >= self.min.y 23 | } 24 | 25 | pub fn point(p: P2) -> Self { 26 | BoundingBox { min: p, max: p } 27 | } 28 | 29 | pub fn grown_by(&self, offset: N) -> Self { 30 | BoundingBox { 31 | min: self.min - V2::new(offset, offset), 32 | max: self.max + V2::new(offset, offset), 33 | } 34 | } 35 | 36 | pub fn contains(&self, point: P2) -> bool { 37 | self.min.x <= point.x && self.max.x >= point.x && self.min.y <= point.y && self.max.y >= point.y 38 | } 39 | 40 | pub fn extended_by(&self, other: BoundingBox) -> Self { 41 | BoundingBox { 42 | min: P2::new(self.min.x.min(other.min.x), self.min.y.min(other.min.y)), 43 | max: P2::new(self.max.x.max(other.max.x), self.max.y.max(other.max.y)), 44 | } 45 | } 46 | } 47 | 48 | impl ::std::iter::FromIterator for BoundingBox { 49 | fn from_iter>(collection: T) -> Self { 50 | let mut iter = collection.into_iter(); 51 | let mut bbox = iter.next().expect("Should have at least one bounding box"); 52 | 53 | for next_bbox in iter { 54 | bbox = bbox.extended_by(next_bbox); 55 | } 56 | 57 | bbox 58 | } 59 | } 60 | 61 | pub trait HasBoundingBox { 62 | fn bounding_box(&self) -> BoundingBox; 63 | } 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate nalgebra; 2 | extern crate ordered_float; 3 | extern crate itertools; 4 | 5 | #[cfg(feature = "compact_containers")] 6 | extern crate compact; 7 | 8 | extern crate fnv; 9 | extern crate stable_vec; 10 | extern crate smallvec; 11 | 12 | #[cfg(feature = "compact_containers")] 13 | #[macro_use] 14 | extern crate compact_macros; 15 | 16 | #[cfg(test)] 17 | #[macro_use] 18 | extern crate pretty_assertions; 19 | 20 | #[cfg(feature = "serde-serialization")] 21 | extern crate serde; 22 | #[cfg(feature = "serde-serialization")] 23 | #[macro_use] 24 | extern crate serde_derive; 25 | 26 | use nalgebra::{Vector2, Point2, Rotation2, 27 | Vector3, Point3, Isometry3, Affine3, Perspective3, 28 | Vector4, Matrix4, dot}; 29 | pub use nalgebra::try_inverse; 30 | 31 | #[cfg(feature = "compact_containers")] 32 | pub type VecLike = compact::CVec; 33 | 34 | #[cfg(not(feature = "compact_containers"))] 35 | pub type VecLike = Vec; 36 | 37 | pub type N = f32; 38 | use std::f32::consts::PI; 39 | use std::f32::{INFINITY, NEG_INFINITY}; 40 | 41 | pub type V2 = Vector2; 42 | pub type P2 = Point2; 43 | pub type V3 = Vector3; 44 | pub type V4 = Vector4; 45 | pub type P3 = Point3; 46 | pub type M4 = Matrix4; 47 | pub type Iso3 = Isometry3; 48 | pub type Aff3 = Affine3; 49 | pub type Persp3 = Perspective3; 50 | 51 | #[macro_use] 52 | mod rough_eq; 53 | mod angles; 54 | mod convert_2d_3d; 55 | mod bbox; 56 | mod segments; 57 | mod line_path; 58 | mod closed_line_path; 59 | mod arc_line_path; 60 | mod edit_arc_line_path; 61 | mod intersect; 62 | mod areas; 63 | mod band; 64 | mod grid; 65 | mod segment_grid; 66 | mod embedding; 67 | mod area_embedding; 68 | pub mod util; 69 | 70 | pub use self::rough_eq::*; 71 | pub use self::angles::*; 72 | pub use self::convert_2d_3d::*; 73 | pub use self::bbox::*; 74 | pub use self::segments::*; 75 | pub use self::line_path::*; 76 | pub use self::closed_line_path::*; 77 | pub use self::arc_line_path::*; 78 | pub use self::edit_arc_line_path::*; 79 | pub use self::intersect::*; 80 | pub use self::areas::*; 81 | pub use self::band::*; 82 | pub use self::grid::*; 83 | pub use self::segment_grid::*; 84 | pub use self::embedding::*; 85 | pub use self::area_embedding::*; -------------------------------------------------------------------------------- /src/band.rs: -------------------------------------------------------------------------------- 1 | use N; 2 | use line_path::LinePath; 3 | use closed_line_path::ClosedLinePath; 4 | use areas::Area; 5 | 6 | #[derive(Clone)] 7 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 8 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 9 | pub struct Band { 10 | pub path: LinePath, 11 | pub width_left: N, 12 | pub width_right: N, 13 | } 14 | 15 | impl Band { 16 | pub fn new(path: LinePath, width: N) -> Band { 17 | Band { 18 | path, 19 | width_left: width / 2.0, 20 | width_right: width / 2.0, 21 | } 22 | } 23 | 24 | pub fn new_asymmetric(path: LinePath, width_left: N, width_right: N) -> Band { 25 | Band { 26 | path, 27 | width_left, 28 | width_right, 29 | } 30 | } 31 | 32 | pub fn outline(&self) -> ClosedLinePath { 33 | let left_path = self 34 | .path 35 | .shift_orthogonally(-self.width_left) 36 | .unwrap_or_else(|| self.path.clone()); 37 | let right_path = self 38 | .path 39 | .shift_orthogonally(self.width_right) 40 | .unwrap_or_else(|| self.path.clone()) 41 | .reverse(); 42 | 43 | ClosedLinePath::new( 44 | LinePath::new( 45 | left_path 46 | .points 47 | .iter() 48 | .chain(right_path.points.iter()) 49 | .chain(left_path.points.first()) 50 | .cloned() 51 | .collect(), 52 | ).expect("Band path should always be valid"), 53 | ).expect("Band path should always be closed") 54 | } 55 | 56 | pub fn outline_distance_to_path_distance(&self, distance: N) -> N { 57 | let full_width = self.width_left + self.width_right; 58 | 59 | if let (Some(left_path_length), Some(right_path_length)) = ( 60 | self.path 61 | .shift_orthogonally(-self.width_left) 62 | .map(|p| p.length()), 63 | self.path 64 | .shift_orthogonally(self.width_right) 65 | .map(|p| p.length()), 66 | ) { 67 | if distance > left_path_length + full_width + right_path_length { 68 | // on connector2 69 | 0.0 70 | } else if distance > left_path_length + full_width { 71 | // on right side 72 | (1.0 - (distance - left_path_length - full_width) / right_path_length) 73 | * self.path.length() 74 | } else if distance > left_path_length { 75 | // on connector1 76 | self.path.length() 77 | } else { 78 | // on left side 79 | (distance / left_path_length) * self.path.length() 80 | } 81 | } else { 82 | distance 83 | } 84 | } 85 | 86 | pub fn as_area(&self) -> Area { 87 | Area::new_simple(self.outline()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/closed_line_path.rs: -------------------------------------------------------------------------------- 1 | use {N, P2}; 2 | use segments::Segment; 3 | use line_path::LinePath; 4 | use rough_eq::{RoughEq, THICKNESS}; 5 | 6 | #[derive(Clone, Debug)] 7 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 8 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 9 | pub struct ClosedLinePath(pub LinePath); 10 | 11 | impl ClosedLinePath { 12 | pub fn new(path: LinePath) -> Option { 13 | if path.end().rough_eq_by(path.start(), THICKNESS) { 14 | Some(ClosedLinePath(path)) 15 | } else { 16 | None 17 | } 18 | } 19 | 20 | pub fn try_clone_from(path: &LinePath) -> Option { 21 | if path.end().rough_eq_by(path.start(), THICKNESS) { 22 | Some(ClosedLinePath(path.clone())) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | pub fn path(&self) -> &LinePath { 29 | &self.0 30 | } 31 | 32 | pub fn subsection(&self, start: N, end: N) -> Option { 33 | if start > end + THICKNESS { 34 | let maybe_first_half = self.path().subsection(start, self.path().length()); 35 | let maybe_second_half = self.path().subsection(0.0, end); 36 | 37 | match (maybe_first_half, maybe_second_half) { 38 | (Some(first_half), Some(second_half)) => { 39 | first_half.concat_weld(&second_half, THICKNESS * 2.0).ok() 40 | } 41 | (Some(first_half), None) => Some(first_half), 42 | (None, Some(second_half)) => Some(second_half), 43 | _ => None, 44 | } 45 | } else { 46 | self.path().subsection(start, end) 47 | } 48 | } 49 | 50 | pub fn to_clockwise(self) -> Self { 51 | let sum: f32 = self.path().segments().map(|segment| 52 | (segment.end().x - segment.start().x) * (segment.end().y + segment.start().y) 53 | ).sum(); 54 | 55 | if sum > 0.0 { 56 | self 57 | } else { 58 | Self::new(self.path().reverse()).expect("Reversing a closed line path should always work") 59 | } 60 | } 61 | 62 | pub fn midpoint_between(&self, start: N, end: N) -> P2 { 63 | if start > end + THICKNESS { 64 | let length = self.path().length(); 65 | let start_end_distance = (length - start) + end; 66 | 67 | if start + start_end_distance / 2.0 <= length { 68 | self.path().along(start + start_end_distance / 2.0) 69 | } else { 70 | self.path().along(end - start_end_distance / 2.0) 71 | } 72 | } else { 73 | self.path().along((start + end) / 2.0) 74 | } 75 | } 76 | } 77 | 78 | impl<'a> RoughEq for &'a ClosedLinePath { 79 | fn rough_eq_by(&self, other: Self, tolerance: N) -> bool { 80 | // TODO: is this really equality? 81 | self.path().points.len() == other.path().points.len() 82 | && self.path().segments().all(|self_segment| { 83 | other.path().segments().any(|other_segment| { 84 | self_segment 85 | .start() 86 | .rough_eq_by(other_segment.start(), tolerance) 87 | && self_segment 88 | .end() 89 | .rough_eq_by(other_segment.end(), tolerance) 90 | }) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/grid.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::hash::Hash; 3 | use N; 4 | 5 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 6 | pub struct CellCoords(pub i32, pub i32); 7 | 8 | use fnv::{FnvHashMap}; 9 | use util::SmallSet; 10 | 11 | pub type CellContent = SmallSet<[M; 4]>; 12 | 13 | pub trait GridShape { 14 | fn covered_cell_coords(&self, cell_width: N) -> Vec; 15 | } 16 | 17 | #[derive(Clone)] 18 | pub struct Grid { 19 | pub cell_width: N, 20 | cells: FnvHashMap>, 21 | pub min_bounds: CellCoords, 22 | pub max_bounds: CellCoords, 23 | } 24 | 25 | impl Grid { 26 | pub fn new(cell_width: N) -> Grid { 27 | Grid { 28 | cell_width, 29 | cells: FnvHashMap::default(), 30 | min_bounds: CellCoords(0, 0), 31 | max_bounds: CellCoords(0, 0), 32 | } 33 | } 34 | 35 | pub fn insert_unchecked(&mut self, member: M, shape: &S) { 36 | for coords in shape.covered_cell_coords(self.cell_width) { 37 | match self.cells.entry(coords) { 38 | Entry::Vacant(vacant) => { 39 | let mut set = SmallSet::new(); 40 | set.insert(member.clone()); 41 | vacant.insert(set); 42 | } 43 | Entry::Occupied(occupied) => { 44 | occupied.into_mut().insert(member.clone()); 45 | } 46 | } 47 | self.min_bounds.0 = self.min_bounds.0.min(coords.0); 48 | self.min_bounds.1 = self.min_bounds.0.min(coords.1); 49 | self.max_bounds.0 = self.max_bounds.0.max(coords.0); 50 | self.max_bounds.1 = self.max_bounds.0.max(coords.1); 51 | } 52 | } 53 | 54 | pub fn insert_visiting)>( 55 | &mut self, 56 | member: M, 57 | shape: &S, 58 | mut visitor: F, 59 | ) { 60 | for coords in shape.covered_cell_coords(self.cell_width) { 61 | match self.cells.entry(coords) { 62 | Entry::Vacant(vacant) => { 63 | let mut set = SmallSet::new(); 64 | set.insert(member.clone()); 65 | vacant.insert(set); 66 | } 67 | Entry::Occupied(occupied) => { 68 | visitor(occupied.get()); 69 | occupied.into_mut().insert(member.clone()); 70 | } 71 | } 72 | self.min_bounds.0 = self.min_bounds.0.min(coords.0); 73 | self.min_bounds.1 = self.min_bounds.0.min(coords.1); 74 | self.max_bounds.0 = self.max_bounds.0.max(coords.0); 75 | self.max_bounds.1 = self.max_bounds.0.max(coords.1); 76 | } 77 | } 78 | 79 | pub fn visit)>(&self, shape: &S, mut visitor: F) { 80 | for coords in shape.covered_cell_coords(self.cell_width) { 81 | if let Some(content) = self.cells.get(&coords) { 82 | visitor(content); 83 | } 84 | } 85 | } 86 | 87 | pub fn retain bool>(&mut self, shape: S, mut predicate: F) { 88 | for coords in shape.covered_cell_coords(self.cell_width) { 89 | if let Entry::Occupied(mut occupied) = self.cells.entry(coords) { 90 | let remove = { 91 | let set = occupied.get_mut(); 92 | set.retain(&mut predicate); 93 | set.is_empty() 94 | }; 95 | 96 | if remove { 97 | occupied.remove(); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/segment_grid.rs: -------------------------------------------------------------------------------- 1 | use bbox::{BoundingBox, HasBoundingBox}; 2 | use fnv::FnvHashSet; 3 | use grid::{CellCoords, Grid, GridShape}; 4 | use line_path::LinePath; 5 | use segments::{LineSegment, Segment}; 6 | use N; 7 | 8 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 9 | pub struct PathIdx(u32); 10 | 11 | impl PathIdx { 12 | pub fn as_idx(self) -> usize { 13 | self.0 as usize 14 | } 15 | } 16 | 17 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 18 | pub struct SegmentIdx(u32); 19 | 20 | impl SegmentIdx { 21 | pub fn as_idx(self) -> usize { 22 | self.0 as usize 23 | } 24 | } 25 | 26 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 27 | pub struct PathSegmentIdx { 28 | pub path_idx: PathIdx, 29 | pub segment_idx: SegmentIdx, 30 | } 31 | 32 | pub struct SegmentGrid { 33 | grid: Grid, 34 | next_path_idx: usize, 35 | } 36 | 37 | impl GridShape for LineSegment { 38 | fn covered_cell_coords(&self, cell_width: N) -> Vec { 39 | let length = self.length(); 40 | let step = cell_width / 2.0; 41 | 42 | if length <= step { 43 | bbox_unique_cell_coords(self.bounding_box(), cell_width) 44 | .iter() 45 | .filter_map(|maybe| maybe.clone()) 46 | .collect() 47 | } else { 48 | let start = self.start(); 49 | let direction = self.direction(); 50 | 51 | let mut start_along = 0.0; 52 | let mut end_along = step; 53 | 54 | let start_4_coords = bbox_unique_cell_coords( 55 | LineSegment::new( 56 | start + direction * start_along, 57 | start + direction * end_along, 58 | ).bounding_box(), 59 | cell_width, 60 | ); 61 | let mut coords: Vec<_> = start_4_coords 62 | .iter() 63 | .filter_map(|maybe| maybe.clone()) 64 | .collect(); 65 | 66 | loop { 67 | start_along += step; 68 | end_along = (end_along + step).min(length); 69 | 70 | let mut new_4_coords = bbox_unique_cell_coords( 71 | LineSegment::new( 72 | start + direction * start_along, 73 | start + direction * end_along, 74 | ).bounding_box(), 75 | cell_width, 76 | ); 77 | 78 | { 79 | let only_if_new = |new_coord| { 80 | if coords[coords.len().saturating_sub(4)..coords.len()].contains(&new_coord) 81 | { 82 | None 83 | } else { 84 | Some(new_coord) 85 | } 86 | }; 87 | 88 | new_4_coords[0] = new_4_coords[0].and_then(only_if_new); 89 | new_4_coords[1] = new_4_coords[1].and_then(only_if_new); 90 | new_4_coords[2] = new_4_coords[2].and_then(only_if_new); 91 | new_4_coords[3] = new_4_coords[3].and_then(only_if_new); 92 | } 93 | 94 | coords.extend(new_4_coords.iter().filter_map(|maybe| maybe.clone())); 95 | 96 | if start_along + step >= length { 97 | break; 98 | } 99 | } 100 | 101 | coords 102 | } 103 | } 104 | } 105 | 106 | fn bbox_unique_cell_coords(bbox: BoundingBox, cell_width: N) -> [Option; 4] { 107 | let bottom_left = CellCoords( 108 | (bbox.min.x / cell_width).floor() as i32, 109 | (bbox.min.y / cell_width).floor() as i32, 110 | ); 111 | let top_left = CellCoords( 112 | (bbox.min.x / cell_width).floor() as i32, 113 | (bbox.max.y / cell_width).floor() as i32, 114 | ); 115 | let top_right = CellCoords( 116 | (bbox.max.x / cell_width).floor() as i32, 117 | (bbox.max.y / cell_width).floor() as i32, 118 | ); 119 | let bottom_right = CellCoords( 120 | (bbox.max.x / cell_width).floor() as i32, 121 | (bbox.min.y / cell_width).floor() as i32, 122 | ); 123 | 124 | let mut result = [Some(bottom_left), None, None, None]; 125 | 126 | // compiler should figure out redundant checks 127 | 128 | if top_left != bottom_left { 129 | result[1] = Some(top_left); 130 | } 131 | 132 | if top_right != bottom_left && top_right != top_left { 133 | result[2] = Some(top_right); 134 | } 135 | 136 | if bottom_right != bottom_left && bottom_right != top_left && bottom_right != top_right { 137 | result[3] = Some(bottom_right); 138 | } 139 | 140 | result 141 | } 142 | 143 | #[test] 144 | fn intersection_grid() { 145 | use P2; 146 | assert_eq!( 147 | LineSegment::new(P2::new(0.0, 0.0), P2::new(3.0, 2.0)).covered_cell_coords(0.25), 148 | vec![ 149 | CellCoords(-1, -1), 150 | CellCoords(-1, 0), 151 | CellCoords(0, 0), 152 | CellCoords(0, -1), 153 | CellCoords(1, 0), 154 | CellCoords(1, 1), 155 | CellCoords(2, 1), 156 | CellCoords(2, 2), 157 | CellCoords(3, 2), 158 | CellCoords(3, 1), 159 | CellCoords(4, 2), 160 | CellCoords(4, 3), 161 | CellCoords(5, 3), 162 | CellCoords(5, 4), 163 | CellCoords(6, 4), 164 | CellCoords(6, 3), 165 | CellCoords(7, 4), 166 | CellCoords(7, 5), 167 | CellCoords(8, 5), 168 | CellCoords(8, 6), 169 | CellCoords(9, 6), 170 | CellCoords(9, 5), 171 | CellCoords(10, 6), 172 | CellCoords(10, 7), 173 | CellCoords(11, 7), 174 | CellCoords(11, 8), 175 | CellCoords(12, 8), 176 | CellCoords(12, 7), 177 | ] 178 | ); 179 | } 180 | 181 | impl SegmentGrid { 182 | pub fn new(cell_width: N) -> SegmentGrid { 183 | SegmentGrid { 184 | grid: Grid::new(cell_width), 185 | next_path_idx: 0, 186 | } 187 | } 188 | 189 | pub fn insert( 190 | &mut self, 191 | path: &LinePath, 192 | ) -> (PathIdx, FnvHashSet<(SegmentIdx, PathSegmentIdx)>) { 193 | let path_idx = PathIdx(self.next_path_idx as u32); 194 | self.next_path_idx += 1; 195 | 196 | let mut interactions = FnvHashSet::default(); 197 | 198 | for (segment_idx, segment) in path.segments().enumerate() { 199 | let path_segment_idx = PathSegmentIdx { 200 | path_idx: path_idx, 201 | segment_idx: SegmentIdx(segment_idx as u32), 202 | }; 203 | 204 | self.grid 205 | .insert_visiting(path_segment_idx, &segment, |existing_cell_content| { 206 | for other_path_segment_idx in existing_cell_content.iter() { 207 | // TODO: allow self-intersections 208 | if path_segment_idx.path_idx != other_path_segment_idx.path_idx { 209 | interactions 210 | .insert((SegmentIdx(segment_idx as u32), *other_path_segment_idx)); 211 | } 212 | } 213 | }); 214 | } 215 | 216 | (path_idx, interactions) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/areas/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // TODO: make these tests of embedding instead 2 | 3 | // use super::{LinePath, ClosedLinePath, Area, PrimitiveArea, VecLike}; 4 | 5 | // fn svg_test(file_path: &str) { 6 | // use std::fs; 7 | // use std::io::Read; 8 | // use {THICKNESS, RoughEq}; 9 | 10 | // let mut file = fs::File::open(file_path).unwrap(); 11 | 12 | // let mut contents = String::new(); 13 | // file.read_to_string(&mut contents).unwrap(); 14 | 15 | // let mut clip_area = None; 16 | // let mut subject_area = None; 17 | // let mut expected_result_primitive_areas = VecLike::new(); 18 | 19 | // let path_substrs = contents 20 | // .split(" Result, E>>( 2 | items: &mut Vec, 3 | joiner: F, 4 | ) -> Result<(), E> { 5 | // do-until 6 | while { 7 | let mut could_join = false; 8 | 9 | let mut i = 0; 10 | 11 | while i + 1 < items.len() { 12 | let mut j = i + 1; 13 | 14 | while j < items.len() { 15 | if let Some(joined) = 16 | joiner(&items[i], &items[j])?.or(joiner(&items[j], &items[i])?) 17 | { 18 | items[i] = joined; 19 | items.swap_remove(j); 20 | could_join = true; 21 | } else { 22 | j += 1; 23 | } 24 | } 25 | 26 | i += 1; 27 | } 28 | 29 | could_join 30 | } {} 31 | 32 | Ok(()) 33 | } 34 | 35 | // smallset: a Rust crate for small unordered sets of elements, built on top of 36 | // `smallvec`. 37 | // 38 | // Copyright (c) 2016 Chris Fallin . Released under the MIT license. 39 | // Extendend by Anselm Eickhoff 40 | 41 | use std::fmt; 42 | 43 | use smallvec::{Array, SmallVec}; 44 | 45 | /// A `SmallSet` is an unordered set of elements. It is designed to work best 46 | /// for very small sets (no more than ten or so elements). In order to support 47 | /// small sets very efficiently, it stores elements in a simple unordered array. 48 | /// When the set is smaller than the size of the array `A`, all elements are 49 | /// stored inline, without heap allocation. This is accomplished by using a 50 | /// `smallvec::SmallVec`. 51 | /// 52 | /// The insert, remove, and query methods on `SmallSet` have `O(n)` time 53 | /// complexity in the current set size: they perform a linear scan to determine 54 | /// if the element in question is present. This is inefficient for large sets, 55 | /// but fast and cache-friendly for small sets. 56 | /// 57 | /// Example usage: 58 | /// 59 | /// ``` 60 | /// use descartes::util::SmallSet; 61 | /// 62 | /// // `s` and its elements will be completely stack-allocated in this example. 63 | /// let mut s: SmallSet<[u32; 4]> = SmallSet::new(); 64 | /// s.insert(1); 65 | /// s.insert(2); 66 | /// s.insert(3); 67 | /// assert!(s.len() == 3); 68 | /// assert!(s.contains(&1)); 69 | /// ``` 70 | pub struct SmallSet 71 | where 72 | A::Item: PartialEq + Eq, 73 | { 74 | elements: SmallVec, 75 | } 76 | 77 | impl SmallSet 78 | where 79 | A::Item: PartialEq + Eq, 80 | { 81 | /// Creates a new, empty `SmallSet`. 82 | pub fn new() -> SmallSet { 83 | SmallSet { 84 | elements: SmallVec::new(), 85 | } 86 | } 87 | 88 | /// Inserts `elem` into the set if not yet present. Returns `true` if the 89 | /// set did not have this element present, or `false` if it already had this 90 | /// element present. 91 | pub fn insert(&mut self, elem: A::Item) -> bool { 92 | if !self.contains(&elem) { 93 | self.elements.push(elem); 94 | true 95 | } else { 96 | false 97 | } 98 | } 99 | 100 | /// Removes `elem` from the set. Returns `true` if the element was removed, 101 | /// or `false` if it was not found. 102 | pub fn remove(&mut self, elem: &A::Item) -> bool { 103 | if let Some(pos) = self.elements.iter().position(|e| *e == *elem) { 104 | self.elements.swap_remove(pos); 105 | true 106 | } else { 107 | false 108 | } 109 | } 110 | 111 | /// Clears the set. 112 | pub fn clear(&mut self) { 113 | self.elements.clear(); 114 | } 115 | 116 | pub fn retain bool>(&mut self, predicate: F) { 117 | self.elements.retain(predicate); 118 | } 119 | } 120 | 121 | impl ::std::ops::Deref for SmallSet 122 | where 123 | A::Item: Eq, 124 | { 125 | type Target = [A::Item]; 126 | 127 | fn deref(&self) -> &[A::Item] { 128 | &self.elements 129 | } 130 | } 131 | 132 | impl ::std::ops::DerefMut for SmallSet 133 | where 134 | A::Item: Eq, 135 | { 136 | fn deref_mut(&mut self) -> &mut [A::Item] { 137 | &mut self.elements 138 | } 139 | } 140 | 141 | impl ::std::iter::IntoIterator for SmallSet 142 | where 143 | A::Item: Eq, { 144 | type IntoIter = ::smallvec::IntoIter; 145 | type Item = A::Item; 146 | fn into_iter(self) -> Self::IntoIter { 147 | self.elements.into_iter() 148 | } 149 | } 150 | 151 | impl Clone for SmallSet 152 | where 153 | A::Item: PartialEq + Eq + Clone, 154 | { 155 | fn clone(&self) -> SmallSet { 156 | SmallSet { 157 | elements: self.elements.clone(), 158 | } 159 | } 160 | } 161 | 162 | impl fmt::Debug for SmallSet 163 | where 164 | A::Item: PartialEq + Eq + fmt::Debug, 165 | { 166 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | self.elements.fmt(f) 168 | } 169 | } 170 | 171 | pub struct SmallSortedSet 172 | where 173 | A::Item: PartialEq + Eq, 174 | { 175 | elements: SmallVec, 176 | } 177 | 178 | impl SmallSortedSet 179 | where 180 | A::Item: PartialEq + Eq + Ord, 181 | { 182 | /// Creates a new, empty `SmallSortedSet`. 183 | pub fn new() -> SmallSortedSet { 184 | SmallSortedSet { 185 | elements: SmallVec::new(), 186 | } 187 | } 188 | 189 | /// Inserts `elem` into the set if not yet present. Returns `true` if the 190 | /// set did not have this element present, or `false` if it already had this 191 | /// element present. 192 | pub fn insert(&mut self, elem: A::Item) -> bool { 193 | match self.elements.binary_search(&elem) { 194 | Ok(_pos) => {false}, 195 | Err(insert_pos) => { 196 | self.elements.insert(insert_pos, elem); 197 | true 198 | } 199 | } 200 | } 201 | 202 | /// Removes `elem` from the set. Returns `true` if the element was removed, 203 | /// or `false` if it was not found. 204 | pub fn remove(&mut self, elem: &A::Item) -> bool { 205 | match self.elements.binary_search(&elem) { 206 | Ok(pos) => { 207 | self.elements.remove(pos); 208 | true 209 | }, 210 | Err(_pos) => { 211 | false 212 | } 213 | } 214 | } 215 | 216 | /// Override slice implementation, make use of binary search 217 | pub fn contains(&mut self, elem: &A::Item) -> bool { 218 | self.elements.binary_search(&elem).is_ok() 219 | } 220 | 221 | /// Clears the set. 222 | pub fn clear(&mut self) { 223 | self.elements.clear(); 224 | } 225 | 226 | pub fn retain bool>(&mut self, predicate: F) { 227 | self.elements.retain(predicate); 228 | } 229 | } 230 | 231 | impl ::std::ops::Deref for SmallSortedSet 232 | where 233 | A::Item: Eq, 234 | { 235 | type Target = [A::Item]; 236 | 237 | fn deref(&self) -> &[A::Item] { 238 | &self.elements 239 | } 240 | } 241 | 242 | impl ::std::ops::DerefMut for SmallSortedSet 243 | where 244 | A::Item: Eq, 245 | { 246 | fn deref_mut(&mut self) -> &mut [A::Item] { 247 | &mut self.elements 248 | } 249 | } 250 | 251 | impl ::std::iter::IntoIterator for SmallSortedSet 252 | where 253 | A::Item: Eq, { 254 | type IntoIter = ::smallvec::IntoIter; 255 | type Item = A::Item; 256 | fn into_iter(self) -> Self::IntoIter { 257 | self.elements.into_iter() 258 | } 259 | } 260 | 261 | impl Clone for SmallSortedSet 262 | where 263 | A::Item: PartialEq + Eq + Clone, 264 | { 265 | fn clone(&self) -> SmallSortedSet { 266 | SmallSortedSet { 267 | elements: self.elements.clone(), 268 | } 269 | } 270 | } 271 | 272 | impl fmt::Debug for SmallSortedSet 273 | where 274 | A::Item: PartialEq + Eq + fmt::Debug, 275 | { 276 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 277 | self.elements.fmt(f) 278 | } 279 | } 280 | 281 | #[cfg(test)] 282 | mod test { 283 | use super::*; 284 | use std::fmt::Write; 285 | 286 | #[test] 287 | fn test_basic_set() { 288 | let mut s: SmallSet<[u32; 2]> = SmallSet::new(); 289 | assert!(s.insert(1) == true); 290 | assert!(s.insert(2) == true); 291 | assert!(s.insert(2) == false); 292 | assert!(s.insert(3) == true); 293 | assert!(s.insert(2) == false); 294 | assert!(s.insert(3) == false); 295 | assert!(s.contains(&1)); 296 | assert!(s.contains(&2)); 297 | assert!(s.contains(&3)); 298 | assert!(!s.contains(&4)); 299 | assert!(s.len() == 3); 300 | assert!(s.iter().map(|r| *r).collect::>() == vec![1, 2, 3]); 301 | s.clear(); 302 | assert!(!s.contains(&1)); 303 | } 304 | 305 | #[test] 306 | fn test_remove() { 307 | let mut s: SmallSet<[u32; 2]> = SmallSet::new(); 308 | assert!(s.insert(1) == true); 309 | assert!(s.insert(2) == true); 310 | assert!(s.len() == 2); 311 | assert!(s.contains(&1)); 312 | assert!(s.remove(&1) == true); 313 | assert!(s.remove(&1) == false); 314 | assert!(s.len() == 1); 315 | assert!(!s.contains(&1)); 316 | assert!(s.insert(1) == true); 317 | assert!(s.iter().map(|r| *r).collect::>() == vec![2, 1]); 318 | } 319 | 320 | #[test] 321 | fn test_clone() { 322 | let mut s: SmallSet<[u32; 2]> = SmallSet::new(); 323 | s.insert(1); 324 | s.insert(2); 325 | let c = s.clone(); 326 | assert!(c.contains(&1)); 327 | assert!(c.contains(&2)); 328 | assert!(!c.contains(&3)); 329 | } 330 | 331 | #[test] 332 | fn test_debug() { 333 | let mut s: SmallSet<[u32; 2]> = SmallSet::new(); 334 | s.insert(1); 335 | s.insert(2); 336 | let mut buf = String::new(); 337 | write!(buf, "{:?}", s).unwrap(); 338 | assert!(&buf == "[1, 2]"); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/areas/mod.rs: -------------------------------------------------------------------------------- 1 | use bbox::{BoundingBox, HasBoundingBox}; 2 | use closed_line_path::ClosedLinePath; 3 | use intersect::Intersect; 4 | use line_path::LinePath; 5 | use ordered_float::OrderedFloat; 6 | use segments::Segment; 7 | use util::join_within_vec; 8 | use {P2, RoughEq, VecLike, N, PI, THICKNESS}; 9 | 10 | mod debug; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | #[derive(Debug)] 16 | pub struct UnclosedPathError; 17 | 18 | #[derive(PartialEq)] 19 | pub enum AreaLocation { 20 | Inside, 21 | Boundary, 22 | Outside, 23 | } 24 | 25 | pub trait PointContainer { 26 | fn location_of(&self, point: P2) -> AreaLocation; 27 | 28 | fn contains(&self, point: P2) -> bool { 29 | self.location_of(point) != AreaLocation::Outside 30 | } 31 | } 32 | 33 | /// Represents a filled area bounded by a clockwise boundary. 34 | /// Everything "right of" the boundary is considered "inside" 35 | #[derive(Clone, Debug)] 36 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 37 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 38 | pub struct PrimitiveArea { 39 | pub boundary: ClosedLinePath, 40 | } 41 | 42 | impl PrimitiveArea { 43 | pub fn new(boundary: ClosedLinePath) -> PrimitiveArea { 44 | PrimitiveArea { boundary } 45 | } 46 | 47 | pub fn fully_contains(&self, other: &PrimitiveArea) -> bool { 48 | let n_intersections = (&self.boundary, &other.boundary).intersect().len(); 49 | 50 | n_intersections <= 1 51 | && other 52 | .boundary 53 | .path() 54 | .segments() 55 | .all(|other_segment| self.contains(other_segment.start())) 56 | } 57 | 58 | pub fn add_winding_numbers_batching(&self, points: &[P2], winding_numbers: &mut [f32]) { 59 | for segment in self.boundary.path().segments() { 60 | for (i, point) in points.iter().enumerate() { 61 | winding_numbers[i] += segment.winding_angle(*point) / (2.0 * PI); 62 | } 63 | } 64 | } 65 | 66 | pub fn winding_number(&self, point: P2) -> f32 { 67 | (self 68 | .boundary 69 | .path() 70 | .segments() 71 | .map(|segment| segment.winding_angle(point)) 72 | .sum::() / (2.0 * PI)) 73 | .round() 74 | } 75 | 76 | pub fn area(&self) -> N { 77 | // http://mathworld.wolfram.com/PolygonArea.html 78 | 0.5f32 * self.boundary.path().points.windows(2).map(|pair| { 79 | let (p1, p2) = (pair[0], pair[1]); 80 | p1.x * p2.y - p2.x * p1.y 81 | }).sum::() 82 | } 83 | } 84 | 85 | impl PointContainer for PrimitiveArea { 86 | fn location_of(&self, point: P2) -> AreaLocation { 87 | if self.boundary.path().includes(point) { 88 | AreaLocation::Boundary 89 | } else if self.winding_number(point) == 0.0 { 90 | AreaLocation::Outside 91 | } else { 92 | AreaLocation::Inside 93 | } 94 | } 95 | } 96 | 97 | impl<'a> RoughEq for &'a PrimitiveArea { 98 | fn rough_eq_by(&self, other: Self, tolerance: N) -> bool { 99 | (&self.boundary).rough_eq_by(&other.boundary, tolerance) 100 | } 101 | } 102 | 103 | #[derive(Clone, Debug)] 104 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 105 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 106 | pub struct Area { 107 | pub primitives: VecLike, 108 | pub cached_bbox: Option, 109 | } 110 | 111 | impl Area { 112 | pub fn new(primitives: VecLike) -> Self { 113 | Area { 114 | primitives, 115 | cached_bbox: None 116 | } 117 | } 118 | 119 | pub fn new_simple(boundary: ClosedLinePath) -> Self { 120 | Area { 121 | primitives: Some(PrimitiveArea::new(boundary)).into_iter().collect(), 122 | cached_bbox: None 123 | } 124 | } 125 | 126 | pub fn disjoint(&self) -> Vec { 127 | // TODO: this is not quite correct yet 128 | let mut groups = Vec::>::new(); 129 | 130 | for primitive in self.primitives.iter().cloned() { 131 | if let Some(surrounding_group_i) = groups 132 | .iter() 133 | .position(|group| group[0].fully_contains(&primitive)) 134 | { 135 | groups[surrounding_group_i].push(primitive); 136 | } else if let Some(surrounded_group_i) = groups 137 | .iter() 138 | .position(|group| primitive.fully_contains(&group[0])) 139 | { 140 | groups[surrounded_group_i].insert(0, primitive); 141 | } else { 142 | groups.push(Some(primitive).into_iter().collect()); 143 | } 144 | } 145 | 146 | groups.into_iter().map(Area::new).collect() 147 | } 148 | 149 | fn winding_number(&self, point: P2) -> f32 { 150 | self.primitives 151 | .iter() 152 | .map(|primitive| primitive.winding_number(point)) 153 | .sum() 154 | } 155 | 156 | pub fn add_winding_numbers_batching(&self, points: &[P2], winding_numbers: &mut [f32]) { 157 | for primitive in &self.primitives { 158 | primitive.add_winding_numbers_batching(points, winding_numbers); 159 | } 160 | } 161 | 162 | pub fn area(&self) -> N { 163 | self.primitives.iter().map(PrimitiveArea::area).sum() 164 | } 165 | } 166 | 167 | impl PointContainer for Area { 168 | fn location_of(&self, point: P2) -> AreaLocation { 169 | if self 170 | .primitives 171 | .iter() 172 | .any(|primtive| primtive.boundary.path().includes(point)) 173 | { 174 | AreaLocation::Boundary 175 | } else if self.winding_number(point) == 0.0 { 176 | AreaLocation::Outside 177 | } else { 178 | AreaLocation::Inside 179 | } 180 | } 181 | } 182 | 183 | impl HasBoundingBox for Area { 184 | fn bounding_box(&self) -> BoundingBox { 185 | if let Some(cached) = self.cached_bbox { 186 | cached 187 | } else { 188 | let bbox = self 189 | .primitives 190 | .iter() 191 | .flat_map(|primitive_area| { 192 | primitive_area 193 | .boundary 194 | .path() 195 | .segments() 196 | .map(|segment| segment.bounding_box()) 197 | }) 198 | .collect(); 199 | 200 | unsafe { 201 | // we should use a Cell here, but Compact doesn't support that yet 202 | let self_mut: *mut Area = ::std::mem::transmute(self); 203 | (&mut *self_mut).cached_bbox = Some(bbox); 204 | } 205 | bbox 206 | } 207 | } 208 | } 209 | 210 | impl<'a> RoughEq for &'a Area { 211 | fn rough_eq_by(&self, other: Self, tolerance: N) -> bool { 212 | self.primitives.len() == other.primitives.len() 213 | && self.primitives.iter().all(|own_primitive| { 214 | other 215 | .primitives 216 | .iter() 217 | .any(|other_primitive| own_primitive.rough_eq_by(other_primitive, tolerance)) 218 | }) 219 | } 220 | } 221 | 222 | use line_path::ConcatError; 223 | 224 | #[derive(Debug)] 225 | pub enum AreaError { 226 | WeldingShouldWork(ConcatError), 227 | LeftOver(String), 228 | } 229 | 230 | impl Area { 231 | pub fn from_pieces(mut paths: Vec) -> Result { 232 | // println!( 233 | // "PATHS \n{:#?}", 234 | // paths 235 | // .iter() 236 | // .map(|path| format!( 237 | // "Path: {:?}", 238 | // path.points 239 | // .iter() 240 | // .map(|p| format!("{}", p)) 241 | // .collect::>() 242 | // )) 243 | // .collect::>() 244 | // ); 245 | 246 | let mut complete_paths = Vec::::new(); 247 | 248 | let mut combining_tolerance = THICKNESS; 249 | 250 | while !paths.is_empty() && combining_tolerance < 1.0 { 251 | join_within_vec(&mut paths, |path_a, path_b| { 252 | if path_b 253 | .start() 254 | .rough_eq_by(path_a.end(), combining_tolerance) 255 | { 256 | Ok(Some( 257 | path_a 258 | .concat_weld(&path_b, combining_tolerance) 259 | .map_err(AreaError::WeldingShouldWork)?, 260 | )) 261 | } else { 262 | Ok(None) 263 | } 264 | })?; 265 | 266 | paths.retain(|path| { 267 | if path.length() < combining_tolerance { 268 | false 269 | } else if let Some(closed) = ClosedLinePath::try_clone_from(path) { 270 | complete_paths.push(closed); 271 | false 272 | } else if (path.start() - path.end()).norm() <= combining_tolerance { 273 | if let Some(welded_path) = 274 | path.with_new_start_and_end(path.start(), path.start()) 275 | { 276 | complete_paths.push( 277 | ClosedLinePath::new(welded_path).expect("Welded should be closed"), 278 | ); 279 | false 280 | } else { 281 | true 282 | } 283 | } else { 284 | true 285 | } 286 | }); 287 | 288 | combining_tolerance *= 2.0; 289 | } 290 | 291 | if !paths.is_empty() { 292 | let min_distance = paths 293 | .iter() 294 | .map(|other| OrderedFloat((paths[0].start() - other.end()).norm())) 295 | .min() 296 | .expect("should have a min"); 297 | 298 | return Err(AreaError::LeftOver(format!( 299 | "Start to closest end: {}\n{}\n\n{}", 300 | min_distance, 301 | "SVG MISSING", //self.debug_svg(), 302 | format!( 303 | r#""#, 304 | paths[0].to_svg() 305 | ) 306 | ))); 307 | } 308 | 309 | Ok(Area::new( 310 | complete_paths.into_iter().map(PrimitiveArea::new).collect(), 311 | )) 312 | } 313 | } 314 | 315 | #[test] 316 | fn area_area_test() { 317 | assert_rough_eq_by!(Area::new_simple(ClosedLinePath::new(LinePath::new(vec![ 318 | P2::new(0.0, 0.0), 319 | P2::new(3.0, 0.0), 320 | P2::new(3.0, 4.0), 321 | P2::new(0.0, 4.0), 322 | P2::new(0.0, 0.0), 323 | ]).unwrap()).unwrap()).area(), 12.0, 0.1) 324 | } -------------------------------------------------------------------------------- /src/intersect.rs: -------------------------------------------------------------------------------- 1 | use {N, P2, THICKNESS}; 2 | use rough_eq::RoughEq; 3 | 4 | #[derive(Copy, Clone, Debug, PartialEq)] 5 | pub struct Intersection { 6 | pub along_a: N, 7 | pub along_b: N, 8 | pub position: P2, 9 | } 10 | 11 | pub trait Intersect { 12 | fn intersect(&self) -> Vec; 13 | } 14 | 15 | use segments::{Segment, LineSegment}; 16 | use bbox::HasBoundingBox; 17 | 18 | impl Intersect for (LineSegment, LineSegment) { 19 | fn intersect(&self) -> Vec { 20 | let (ref a, ref b) = *self; 21 | 22 | if !a.bounding_box().overlaps(&b.bounding_box()) { 23 | return vec![]; 24 | } 25 | 26 | let a_direction = a.direction(); 27 | let b_direction = b.direction(); 28 | 29 | match ( 30 | b.project_with_max_distance(a.start(), THICKNESS, THICKNESS), 31 | b.project_with_max_distance(a.end(), THICKNESS, THICKNESS), 32 | a.project_with_max_distance(b.start(), THICKNESS, THICKNESS), 33 | a.project_with_max_distance(b.end(), THICKNESS, THICKNESS), 34 | ) { 35 | // a rough subset of b 36 | (Some((a_start_along_b, _)), Some((a_end_along_b, _)), ..) => vec![ 37 | Intersection { 38 | along_a: 0.0, 39 | along_b: a_start_along_b, 40 | position: a.start(), 41 | }, 42 | Intersection { 43 | along_a: a.length(), 44 | along_b: a_end_along_b, 45 | position: a.end(), 46 | }, 47 | ], 48 | // b rough subset of a 49 | (_, _, Some((b_start_along_a, _)), Some((b_end_along_a, _))) => vec![ 50 | Intersection { 51 | along_b: 0.0, 52 | along_a: b_start_along_a, 53 | position: b.start(), 54 | }, 55 | Intersection { 56 | along_b: b.length(), 57 | along_a: b_end_along_a, 58 | position: b.end(), 59 | }, 60 | ], 61 | // single point touches at ends 62 | (Some((a_start_along_b, _)), None, ..) => vec![Intersection { 63 | along_a: 0.0, 64 | along_b: a_start_along_b, 65 | position: a.start(), 66 | }], 67 | (None, Some((a_end_along_b, _)), ..) => vec![Intersection { 68 | along_a: a.length(), 69 | along_b: a_end_along_b, 70 | position: a.end(), 71 | }], 72 | (_, _, Some((b_start_along_a, _)), None) => vec![Intersection { 73 | along_b: 0.0, 74 | along_a: b_start_along_a, 75 | position: b.start(), 76 | }], 77 | (_, _, None, Some((b_end_along_a, _))) => vec![Intersection { 78 | along_b: b.length(), 79 | along_a: b_end_along_a, 80 | position: b.end(), 81 | }], 82 | (None, None, None, None) => { 83 | let det = b_direction.x * a_direction.y - b_direction.y * a_direction.x; 84 | let parallel = det.rough_eq(0.0); 85 | if parallel { 86 | // parallel and apart 87 | vec![] 88 | } else { 89 | let delta = b.start() - a.start(); 90 | let along_a = (delta.y * b_direction.x - delta.x * b_direction.y) / det; 91 | let along_b = (delta.y * a_direction.x - delta.x * a_direction.y) / det; 92 | 93 | if along_a > -THICKNESS 94 | && along_b > -THICKNESS 95 | && along_a < a.length() + THICKNESS 96 | && along_b < b.length() + THICKNESS 97 | { 98 | // single point roughly within segments 99 | vec![Intersection { 100 | along_a, 101 | along_b, 102 | position: a.start() + a_direction * along_a, 103 | }] 104 | } else { 105 | // single point outside of segments 106 | vec![] 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | use line_path::LinePath; 115 | use segment_grid::{SegmentGrid, PathSegmentIdx}; 116 | 117 | impl<'a, 'b> Intersect for (&'a LinePath, &'b LinePath) { 118 | fn intersect(&self) -> Vec { 119 | let mut intersections = Vec::::new(); 120 | let (a, b) = *self; 121 | 122 | let mut grid = SegmentGrid::new(a.length().max(b.length()) / 10.0); 123 | 124 | grid.insert(a); 125 | let (_, interactions) = grid.insert(b); 126 | 127 | for (segment_idx_b, PathSegmentIdx{segment_idx: segment_idx_a, ..}) in interactions.into_iter() { 128 | let (segment_a, a_distance) = (a.nth_segment(segment_idx_a.as_idx()), a.distances[segment_idx_a.as_idx()]); 129 | let (segment_b, b_distance) = (b.nth_segment(segment_idx_b.as_idx()), b.distances[segment_idx_b.as_idx()]); 130 | 131 | for intersection in (segment_a, segment_b).intersect() { 132 | let close_exists = intersections.iter().any(|existing| { 133 | existing 134 | .position 135 | .rough_eq_by(intersection.position, THICKNESS) 136 | }); 137 | 138 | if !close_exists { 139 | intersections.push(Intersection { 140 | along_a: a_distance + intersection.along_a, 141 | along_b: b_distance + intersection.along_b, 142 | position: intersection.position, 143 | }) 144 | } 145 | } 146 | } 147 | 148 | intersections 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | const TINY_BIT: N = THICKNESS / 3.0; 154 | 155 | #[test] 156 | fn line_segments_apart() { 157 | // ---- 158 | // ---- 159 | 160 | assert_eq!( 161 | ( 162 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 163 | LineSegment::new(P2::new(0.0, 1.0), P2::new(1.0, 1.0)), 164 | ).intersect(), 165 | vec![] 166 | ); 167 | 168 | // ---- / 169 | // / 170 | 171 | assert_eq!( 172 | ( 173 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 174 | LineSegment::new(P2::new(0.0, 1.0), P2::new(2.0, 0.0)), 175 | ).intersect(), 176 | vec![] 177 | ); 178 | 179 | // ---- ---- 180 | 181 | assert_eq!( 182 | ( 183 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 184 | LineSegment::new(P2::new(2.0, 0.0), P2::new(3.0, 0.0)), 185 | ).intersect(), 186 | vec![] 187 | ); 188 | } 189 | 190 | use closed_line_path::ClosedLinePath; 191 | 192 | #[test] 193 | fn line_segments_intersecting() { 194 | // / 195 | // --/-- 196 | // / 197 | 198 | assert_eq!( 199 | ( 200 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 201 | LineSegment::new(P2::new(0.0, 1.0), P2::new(1.0, -1.0)), 202 | ).intersect(), 203 | vec![Intersection { 204 | along_a: 0.5, 205 | along_b: 1.118034, 206 | position: P2::new(0.5, 0.0), 207 | }] 208 | ); 209 | 210 | // | 211 | // |---- 212 | // | 213 | 214 | assert_eq!( 215 | ( 216 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 217 | LineSegment::new(P2::new(0.0, 1.0), P2::new(0.0, -1.0)), 218 | ).intersect(), 219 | vec![Intersection { 220 | along_a: 0.0, 221 | along_b: 1.0, 222 | position: P2::new(0.0, 0.0), 223 | }] 224 | ); 225 | } 226 | 227 | #[test] 228 | fn line_segments_barely_intersecting() { 229 | // | 230 | // (---- 231 | // | 232 | 233 | assert_eq!( 234 | ( 235 | LineSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0)), 236 | LineSegment::new(P2::new(-TINY_BIT, 1.0), P2::new(-TINY_BIT, -1.0)), 237 | ).intersect(), 238 | vec![Intersection { 239 | along_a: 0.0, 240 | along_b: 1.0, 241 | position: P2::new(0.0, 0.0), 242 | }] 243 | ); 244 | } 245 | 246 | #[cfg(test)] 247 | use arc_line_path::ArcLinePath; 248 | 249 | impl<'a, 'b> Intersect for (&'a ClosedLinePath, &'b ClosedLinePath) { 250 | fn intersect(&self) -> Vec { 251 | let (a, b) = *self; 252 | (a.path(), b.path()).intersect() 253 | } 254 | } 255 | 256 | #[test] 257 | fn path_intersecting_at_curved_segment_start() { 258 | use V2; 259 | // | 260 | // ----|-. 261 | // | \ 262 | assert_eq!( 263 | ( 264 | &ArcLinePath::line(P2::new(0.0, 0.0), P2::new(1.0, 0.0)) 265 | .unwrap() 266 | .concat( 267 | &ArcLinePath::arc(P2::new(1.0, 0.0), V2::new(1.0, 0.0), P2::new(2.0, 1.0),) 268 | .unwrap() 269 | ) 270 | .unwrap() 271 | .to_line_path(), 272 | &LinePath::new(vec![P2::new(1.0, 1.0), P2::new(1.0, -1.0)]).unwrap(), 273 | ).intersect(), 274 | vec![Intersection { 275 | along_a: 1.0, 276 | along_b: 1.0, 277 | position: P2::new(1.0, 0.0), 278 | }] 279 | ); 280 | } 281 | 282 | #[test] 283 | fn path_intersecting_close_before_curved_segment_start() { 284 | use V2; 285 | // | 286 | // ----(-. 287 | // | \ 288 | assert_eq!( 289 | ( 290 | &ArcLinePath::line(P2::new(0.0, 0.0), P2::new(1.0, 0.0)) 291 | .unwrap() 292 | .concat( 293 | &ArcLinePath::arc(P2::new(1.0, 0.0), V2::new(1.0, 0.0), P2::new(2.0, 1.0),) 294 | .unwrap() 295 | ) 296 | .unwrap() 297 | .to_line_path(), 298 | &LinePath::new(vec![ 299 | P2::new(1.0 - TINY_BIT, 1.0), 300 | P2::new(1.0 - TINY_BIT, -1.0), 301 | ]).unwrap(), 302 | ).intersect(), 303 | vec![Intersection { 304 | along_a: 1.0, 305 | along_b: 1.0, 306 | position: P2::new(1.0, 0.0), 307 | }] 308 | ); 309 | } 310 | 311 | #[test] 312 | fn path_intersecting_close_after_curved_segment_start() { 313 | use V2; 314 | // | 315 | // ----)-. 316 | // | \ 317 | assert_eq!( 318 | ( 319 | &ArcLinePath::line(P2::new(0.0, 0.0), P2::new(1.0, 0.0)) 320 | .unwrap() 321 | .concat( 322 | &ArcLinePath::arc(P2::new(1.0, 0.0), V2::new(1.0, 0.0), P2::new(2.0, 1.0),) 323 | .unwrap() 324 | ) 325 | .unwrap() 326 | .to_line_path(), 327 | &LinePath::new(vec![ 328 | P2::new(1.0 - TINY_BIT, 1.0), 329 | P2::new(1.0 - TINY_BIT, -1.0), 330 | ]).unwrap(), 331 | ).intersect(), 332 | vec![Intersection { 333 | along_a: 1.0, 334 | along_b: 1.0, 335 | position: P2::new(1.0, 0.0), 336 | }] 337 | ); 338 | } 339 | -------------------------------------------------------------------------------- /src/embedding.rs: -------------------------------------------------------------------------------- 1 | use fnv::{FnvHashMap, FnvHashSet}; 2 | use grid::{Grid, GridShape}; 3 | use intersect::Intersect; 4 | use line_path::LinePath; 5 | use ordered_float::OrderedFloat; 6 | use segments::LineSegment; 7 | use stable_vec::StableVec; 8 | use std::collections::hash_map::Entry; 9 | use util::SmallSortedSet; 10 | use {P2, V2, VecLike, N}; 11 | 12 | #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 13 | pub struct PieceSegmentIndex { 14 | pub piece_idx: usize, 15 | pub segment_idx: usize, 16 | } 17 | 18 | #[derive(Clone)] 19 | pub struct Embedding { 20 | piece_segment_grid: Grid, 21 | pub pieces: StableVec<(LinePath, L)>, 22 | } 23 | 24 | struct PieceIntersection { 25 | segment_idx: usize, 26 | along: N, 27 | position: P2, 28 | } 29 | 30 | impl Embedding { 31 | pub fn new(cell_width: N) -> Self { 32 | Embedding { 33 | piece_segment_grid: Grid::new(cell_width), 34 | pieces: StableVec::new(), 35 | } 36 | } 37 | 38 | pub fn insert(&mut self, new_path: LinePath, label: L) { 39 | let mut intersections_on_new: Vec = Vec::new(); 40 | let mut intersections_on_others: FnvHashMap> = 41 | FnvHashMap::default(); 42 | 43 | for (new_segment_idx, new_segment) in new_path.segments().enumerate() { 44 | let mut seen_other_piece_segments = SmallSortedSet::<[PieceSegmentIndex; 16]>::new(); 45 | 46 | self.piece_segment_grid.visit(&new_segment, |cell_content| { 47 | for other_piece_segment_idx in cell_content.iter() { 48 | if !seen_other_piece_segments.contains(other_piece_segment_idx) { 49 | let other_segment = self.pieces[other_piece_segment_idx.piece_idx] 50 | .0 51 | .nth_segment(other_piece_segment_idx.segment_idx); 52 | 53 | for intersection in (new_segment, other_segment).intersect() { 54 | intersections_on_new.push(PieceIntersection { 55 | segment_idx: new_segment_idx, 56 | along: intersection.along_a, 57 | position: intersection.position, 58 | }); 59 | 60 | let other_intersection = PieceIntersection { 61 | segment_idx: other_piece_segment_idx.segment_idx, 62 | along: intersection.along_b, 63 | position: intersection.position, 64 | }; 65 | 66 | match intersections_on_others.entry(other_piece_segment_idx.piece_idx) { 67 | Entry::Vacant(vacant) => { 68 | vacant.insert(vec![other_intersection]); 69 | } 70 | Entry::Occupied(occupied) => { 71 | occupied.into_mut().push(other_intersection); 72 | } 73 | } 74 | } 75 | 76 | seen_other_piece_segments.insert(*other_piece_segment_idx); 77 | } 78 | } 79 | }) 80 | } 81 | 82 | for (other_piece_idx, other_intersections) in intersections_on_others.into_iter() { 83 | let (other_piece, other_label) = self.remove_piece(other_piece_idx); 84 | self.insert_piece_splits(other_piece, other_intersections, other_label) 85 | } 86 | 87 | if intersections_on_new.is_empty() { 88 | self.insert_whole_piece(new_path, label); 89 | } else { 90 | self.insert_piece_splits(new_path, intersections_on_new, label); 91 | } 92 | } 93 | 94 | fn insert_piece_splits( 95 | &mut self, 96 | initial_piece: LinePath, 97 | mut intersections: Vec, 98 | label: L, 99 | ) { 100 | intersections.sort_unstable_by(|intersection_a, intersection_b| { 101 | intersection_a 102 | .segment_idx 103 | .cmp(&intersection_b.segment_idx) 104 | .then(OrderedFloat(intersection_a.along).cmp(&OrderedFloat(intersection_b.along))) 105 | }); 106 | let mut original_points_iter = initial_piece.points.iter().enumerate().peekable(); 107 | let mut intersections_iter = intersections.into_iter().peekable(); 108 | 109 | let mut new_point_groups = vec![VecLike::new()]; 110 | 111 | loop { 112 | let take_point = { 113 | let maybe_next_point_with_idx = original_points_iter.peek(); 114 | let maybe_next_intersection = intersections_iter.peek(); 115 | 116 | match (maybe_next_point_with_idx, maybe_next_intersection) { 117 | (Some((next_point_idx, _next_point)), Some(next_intersection)) => { 118 | if *next_point_idx <= next_intersection.segment_idx { 119 | true 120 | } else { 121 | false 122 | } 123 | } 124 | (Some(_), None) => true, 125 | (None, Some(_)) => false, 126 | (None, None) => break, 127 | } 128 | }; 129 | 130 | if take_point { 131 | new_point_groups 132 | .last_mut() 133 | .expect("should have last point group") 134 | .push( 135 | original_points_iter 136 | .next() 137 | .expect("already peeked point!") 138 | .1 139 | .clone(), 140 | ); 141 | } else { 142 | let intersection_point = intersections_iter 143 | .next() 144 | .expect("already peeked intersetion!") 145 | .position; 146 | new_point_groups 147 | .last_mut() 148 | .expect("should have last point group") 149 | .push(intersection_point); 150 | new_point_groups.push(Some(intersection_point).into_iter().collect()); 151 | } 152 | } 153 | 154 | for points_group in new_point_groups.into_iter() { 155 | if let Some(split_piece) = LinePath::new(points_group) { 156 | self.insert_whole_piece(split_piece, label.clone()) 157 | } 158 | } 159 | } 160 | 161 | fn insert_whole_piece(&mut self, path: LinePath, label: L) { 162 | let piece_idx = self.pieces.next_index(); 163 | 164 | for (segment_idx, segment) in path.segments().enumerate() { 165 | self.piece_segment_grid.insert_unchecked( 166 | PieceSegmentIndex { 167 | piece_idx, 168 | segment_idx, 169 | }, 170 | &segment, 171 | ); 172 | } 173 | 174 | self.pieces.push((path, label)); 175 | } 176 | 177 | fn remove_piece(&mut self, piece_idx: usize) -> (LinePath, L) { 178 | let (path, label) = self 179 | .pieces 180 | .remove(piece_idx) 181 | .expect("Tried to remove non-existing piece"); 182 | 183 | // TODO: this might redundantly try to remove several times from the same grid cells 184 | for segment in path.segments() { 185 | self.piece_segment_grid 186 | .retain(segment, |piece_segment_idx: &mut PieceSegmentIndex| { 187 | piece_segment_idx.piece_idx != piece_idx 188 | }) 189 | } 190 | 191 | (path, label) 192 | } 193 | 194 | pub fn query_pieces( 195 | &self, 196 | shape: &S, 197 | ) -> Vec<(LineSegment, &L, PieceSegmentIndex)> { 198 | let mut unique_piece_segment_indices = 199 | FnvHashSet::with_capacity_and_hasher(100, Default::default()); 200 | 201 | self.piece_segment_grid.visit(shape, |cell_content| { 202 | unique_piece_segment_indices.extend(cell_content.iter().cloned()); 203 | }); 204 | 205 | unique_piece_segment_indices 206 | .into_iter() 207 | .map(|piece_segment_idx| { 208 | let (path, label) = &self.pieces[piece_segment_idx.piece_idx]; 209 | ( 210 | path.nth_segment(piece_segment_idx.segment_idx), 211 | label, 212 | piece_segment_idx, 213 | ) 214 | }) 215 | .collect() 216 | } 217 | 218 | pub fn query_ray_along_x(&self, start_point: P2) -> Vec<(LineSegment, &L, PieceSegmentIndex)> { 219 | let ray_length = (self.piece_segment_grid.max_bounds.0 220 | - (start_point.x / self.piece_segment_grid.cell_width) as i32) 221 | .max(1) as f32 * self.piece_segment_grid.cell_width; 222 | let ray = LineSegment::new(start_point, start_point + V2::new(ray_length, 0.0)); 223 | 224 | self.query_pieces(&ray) 225 | } 226 | 227 | pub fn map_labels L2>( 228 | &mut self, 229 | mut mapper: M, 230 | ) -> Embedding { 231 | let mut new_pieces = StableVec::new(); 232 | new_pieces.grow(self.pieces.capacity()); 233 | 234 | for piece_idx in self.pieces.keys() { 235 | let (piece, old_label) = &self.pieces[piece_idx]; 236 | let new_label = mapper(&piece, &old_label, self); 237 | new_pieces 238 | .insert_into_hole(piece_idx, (piece.clone(), new_label)) 239 | .ok() 240 | .expect("Index in clone should be free!"); 241 | } 242 | 243 | Embedding { 244 | piece_segment_grid: self.piece_segment_grid.clone(), 245 | pieces: new_pieces, 246 | } 247 | } 248 | 249 | pub fn map_labels_in_place L>(&mut self, mut mapper: M) { 250 | let mut new_pieces = StableVec::new(); 251 | new_pieces.grow(self.pieces.capacity()); 252 | 253 | for piece_idx in self.pieces.keys() { 254 | let (piece, old_label) = &self.pieces[piece_idx]; 255 | let new_label = mapper(&piece, &old_label, self); 256 | new_pieces 257 | .insert_into_hole(piece_idx, (piece.clone(), new_label)) 258 | .ok() 259 | .expect("Index in clone should be free!"); 260 | } 261 | 262 | self.pieces = new_pieces; 263 | } 264 | 265 | pub fn retain_pieces bool>(&mut self, mut predicate: P) { 266 | for piece_idx in 0..self.pieces.next_index() { 267 | let (existed, keep) = self 268 | .pieces 269 | .get(piece_idx) 270 | .map(|(path, label)| (true, predicate(path, label, self))) 271 | .unwrap_or((false, false)); 272 | 273 | if existed && !keep { 274 | self.remove_piece(piece_idx); 275 | } 276 | } 277 | } 278 | } 279 | 280 | #[test] 281 | fn embedding_test() { 282 | let mut embedding = Embedding::new(0.25); 283 | 284 | #[derive(Clone, PartialEq, Eq, Debug)] 285 | enum Label { 286 | A, 287 | B, 288 | } 289 | 290 | // | 291 | // .--x--- B 292 | // A --x--' 293 | // | 294 | embedding.insert( 295 | LinePath::new(vec![ 296 | P2::new(0.0, 0.0), 297 | P2::new(1.0, 0.0), 298 | P2::new(1.0, 1.0), 299 | ]).unwrap(), 300 | Label::A, 301 | ); 302 | embedding.insert( 303 | LinePath::new(vec![ 304 | P2::new(0.5, -0.5), 305 | P2::new(0.5, 0.5), 306 | P2::new(1.5, 0.5), 307 | ]).unwrap(), 308 | Label::B, 309 | ); 310 | 311 | assert_eq!( 312 | embedding.pieces.iter().cloned().collect::>(), 313 | vec![ 314 | ( 315 | LinePath::new(vec![P2::new(0.0, 0.0), P2::new(0.5, 0.0)]).unwrap(), 316 | Label::A, 317 | ), 318 | ( 319 | LinePath::new(vec![ 320 | P2::new(0.5, 0.0), 321 | P2::new(1.0, 0.0), 322 | P2::new(1.0, 0.5), 323 | ]).unwrap(), 324 | Label::A, 325 | ), 326 | ( 327 | LinePath::new(vec![P2::new(1.0, 0.5), P2::new(1.0, 1.0)]).unwrap(), 328 | Label::A, 329 | ), 330 | ( 331 | LinePath::new(vec![P2::new(0.5, -0.5), P2::new(0.5, 0.0)]).unwrap(), 332 | Label::B, 333 | ), 334 | ( 335 | LinePath::new(vec![ 336 | P2::new(0.5, 0.0), 337 | P2::new(0.5, 0.5), 338 | P2::new(1.0, 0.5), 339 | ]).unwrap(), 340 | Label::B, 341 | ), 342 | ( 343 | LinePath::new(vec![P2::new(1.0, 0.5), P2::new(1.5, 0.5)]).unwrap(), 344 | Label::B, 345 | ), 346 | ] 347 | ) 348 | } 349 | -------------------------------------------------------------------------------- /src/edit_arc_line_path.rs: -------------------------------------------------------------------------------- 1 | use arc_line_path::{ArcLinePath, direction_along_line}; 2 | use segments::{Segment, ArcSegment}; 3 | use {VecLike, P2, V2, N}; 4 | use ordered_float::OrderedFloat; 5 | 6 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 7 | #[derive(Copy, Clone, Debug)] 8 | pub struct Corner { 9 | position: P2, 10 | in_direction: Option, 11 | out_direction: Option, 12 | } 13 | 14 | impl Corner { 15 | pub fn new(position: P2, in_direction: Option, out_direction: Option) -> Self { 16 | Corner { 17 | position, 18 | in_direction, 19 | out_direction, 20 | } 21 | } 22 | } 23 | 24 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 25 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 26 | #[derive(Clone, Debug)] 27 | pub struct EditArcLinePath { 28 | corners: VecLike, 29 | strategy: ResolutionStrategy, 30 | closed: Closedness, 31 | } 32 | 33 | impl EditArcLinePath { 34 | pub fn new(corners: V, strategy: ResolutionStrategy, closed: Closedness) -> Self 35 | where 36 | V: Into>, 37 | { 38 | EditArcLinePath { 39 | corners: corners.into(), 40 | strategy, 41 | closed, 42 | } 43 | } 44 | } 45 | 46 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 47 | #[derive(Copy, Clone, Debug)] 48 | pub enum ResolutionStrategy { 49 | AssumeSmooth, 50 | AssumeLines, 51 | } 52 | 53 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 54 | #[derive(Copy, Clone, Debug)] 55 | pub enum Closedness { 56 | AlwaysClosed, 57 | Closable, 58 | NeverClosed, 59 | } 60 | 61 | impl EditArcLinePath { 62 | fn nth(&self, i: isize) -> Option { 63 | match self.closed { 64 | Closedness::AlwaysClosed => { 65 | let len = self.corners.len() as isize; 66 | let wrapped_i = (i % len + len) % len; 67 | Some(self.corners[wrapped_i as usize]) 68 | } 69 | Closedness::NeverClosed => { 70 | if (i >= 0) { 71 | self.corners.get(i as usize).copied() 72 | } else { 73 | None 74 | } 75 | } 76 | Closedness::Closable => unimplemented!(), 77 | } 78 | } 79 | 80 | fn resolve_step(&mut self) -> bool { 81 | let mut made_progress = false; 82 | match self.strategy { 83 | ResolutionStrategy::AssumeSmooth => { 84 | for i in 0..self.corners.len() { 85 | if self.corners[i].in_direction.is_none() { 86 | if self.corners[i].out_direction.is_some() { 87 | self.corners[i].in_direction = self.corners[i].out_direction; 88 | made_progress = true; 89 | } else if let Some(previous) = self.nth(i as isize - 1) { 90 | if let Some(previous_out_direction) = previous.out_direction { 91 | if direction_along_line( 92 | previous.position, 93 | previous_out_direction, 94 | self.corners[i].position, 95 | ) { 96 | self.corners[i].in_direction = Some(previous_out_direction); 97 | made_progress = true; 98 | } else if let Some(induced_in_direction) = 99 | ArcSegment::minor_arc_with_start_direction( 100 | previous.position, 101 | previous_out_direction, 102 | self.corners[i].position, 103 | ) 104 | .map(|segment| segment.end_direction()) 105 | { 106 | self.corners[i].in_direction = Some(induced_in_direction); 107 | made_progress = true; 108 | } 109 | } 110 | } 111 | } 112 | if self.corners[i].out_direction.is_none() { 113 | if self.corners[i].in_direction.is_some() { 114 | self.corners[i].out_direction = self.corners[i].in_direction; 115 | made_progress = true; 116 | } else if let Some(next) = self.nth(i as isize + 1) { 117 | if let Some(next_in_direction) = next.in_direction { 118 | if direction_along_line( 119 | self.corners[i].position, 120 | next_in_direction, 121 | next.position, 122 | ) { 123 | self.corners[i].in_direction = Some(next_in_direction); 124 | made_progress = true; 125 | } else if let Some(induced_out_direction) = 126 | ArcSegment::minor_arc_with_start_direction( 127 | next.position, 128 | -1.0 * next_in_direction, 129 | self.corners[i].position, 130 | ) 131 | .map(|segment| -1.0 * segment.start_direction()) 132 | { 133 | self.corners[i].out_direction = Some(induced_out_direction); 134 | made_progress = true; 135 | } 136 | } else { 137 | // TODO: keep this here to set initial direction, 138 | // or do we really expect to be given at least one direction? 139 | self.corners[i].out_direction = 140 | Some((next.position - self.corners[i].position).normalize()); 141 | made_progress = true; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | ResolutionStrategy::AssumeLines => { 148 | for i in 0..self.corners.len() { 149 | if self.corners[i].in_direction.is_none() { 150 | if let Some(previous) = self.nth(i as isize - 1) { 151 | self.corners[i].in_direction = 152 | Some((self.corners[i].position - previous.position).normalize()); 153 | made_progress = true; 154 | } 155 | } 156 | if self.corners[i].out_direction.is_none() { 157 | if let Some(next) = self.nth(i as isize + 1) { 158 | self.corners[i].out_direction = 159 | Some((next.position - self.corners[i].position).normalize()); 160 | made_progress = true; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | made_progress 167 | } 168 | 169 | pub fn resolve(&self) -> (Option, EditArcLinePath) { 170 | let mut work_in_progress = self.clone(); 171 | 172 | while work_in_progress.resolve_step() {} 173 | 174 | let mut maybe_arc_line_path: Option = None; 175 | 176 | match self.closed { 177 | Closedness::AlwaysClosed => { 178 | work_in_progress.corners.push(work_in_progress.corners[0]); 179 | } 180 | _ => {} 181 | } 182 | 183 | for pair in work_in_progress.corners.windows(2) { 184 | let maybe_next_segment = match (pair[0].out_direction, pair[1].in_direction) { 185 | (Some(out_direction), Some(in_direction)) => ArcLinePath::biarc( 186 | pair[0].position, 187 | out_direction, 188 | pair[1].position, 189 | in_direction, 190 | ), 191 | _ => None, 192 | }; 193 | 194 | maybe_arc_line_path = match (maybe_arc_line_path, maybe_next_segment) { 195 | (Some(arc_line_path), Some(next_segment)) => { 196 | arc_line_path.concat(&next_segment).ok() 197 | } 198 | (maybe_arc_line_path, None) => maybe_arc_line_path, 199 | (None, maybe_next_segment) => maybe_next_segment, 200 | }; 201 | } 202 | 203 | (maybe_arc_line_path, work_in_progress) 204 | } 205 | 206 | pub fn move_corner(&mut self, idx: usize, new_position: P2) { 207 | self.corners[idx].position = new_position; 208 | } 209 | 210 | pub fn with_corner_moved(&self, idx: usize, new_position: P2) -> EditArcLinePath { 211 | let mut new = self.clone(); 212 | new.move_corner(idx, new_position); 213 | new 214 | } 215 | 216 | pub fn direct_corner(&mut self, idx: usize, new_direction: Option, in_direction: bool) { 217 | if in_direction { 218 | self.corners[idx].in_direction = new_direction; 219 | } else { 220 | self.corners[idx].out_direction = new_direction; 221 | } 222 | } 223 | 224 | pub fn with_corner_directed( 225 | &self, 226 | idx: usize, 227 | new_direction: Option, 228 | in_direction: bool, 229 | ) -> EditArcLinePath { 230 | let mut new = self.clone(); 231 | new.direct_corner(idx, new_direction, in_direction); 232 | new 233 | } 234 | 235 | pub fn add_corner(&mut self, add_to_end: bool, corner: Corner) { 236 | if add_to_end { 237 | self.corners.push(corner); 238 | } else { 239 | self.corners.insert(0, corner); 240 | } 241 | } 242 | 243 | pub fn with_corner_added(&self, add_to_end: bool, corner: Corner) -> EditArcLinePath { 244 | let mut new = self.clone(); 245 | new.add_corner(add_to_end, corner); 246 | new 247 | } 248 | 249 | pub fn insert_corner(&mut self, corner: Corner) -> bool { 250 | if let (Some(path), _) = self.resolve() { 251 | let line_path = path.to_line_path(); 252 | let distance_along_by_corner: Vec<_> = self 253 | .corners 254 | .iter() 255 | .filter_map(|c| line_path.project(c.position)) 256 | .collect(); 257 | if let Some(new_corner_projection) = line_path.project(corner.position) { 258 | if let Err(idx) = distance_along_by_corner.binary_search_by(|corner_projection| { 259 | OrderedFloat(corner_projection.0).cmp(&OrderedFloat(new_corner_projection.0)) 260 | }) { 261 | self.corners.insert(idx, corner); 262 | } 263 | true 264 | } else { 265 | false 266 | } 267 | } else { 268 | false 269 | } 270 | } 271 | 272 | pub fn with_corner_inserted(&self, corner: Corner) -> Option { 273 | let mut new = self.clone(); 274 | if new.insert_corner(corner) { 275 | Some(new) 276 | } else { 277 | None 278 | } 279 | } 280 | 281 | pub fn subsection(&self, start: N, end: N) -> Option { 282 | // TODO circular when closed 283 | 284 | if let (Some(arc_line_path), _) = self.resolve() { 285 | let line_path = arc_line_path.to_line_path(); 286 | 287 | let first_corner_position = line_path.along(start); 288 | let first_corner_direction = line_path.direction_along(start); 289 | let first_corner = Corner::new( 290 | first_corner_position, 291 | Some(first_corner_direction), 292 | Some(first_corner_direction), 293 | ); 294 | 295 | let last_corner_position = line_path.along(end); 296 | let last_corner_direction = line_path.direction_along(end); 297 | let last_corner = Corner::new( 298 | last_corner_position, 299 | Some(last_corner_direction), 300 | Some(last_corner_direction), 301 | ); 302 | 303 | let corners_between = self 304 | .corners 305 | .iter() 306 | .filter(|corner| { 307 | if let Some(_) = line_path.project(corner.position) { 308 | true 309 | } else { 310 | false 311 | } 312 | }) 313 | .copied(); 314 | 315 | let new_corners: VecLike<_> = Some(first_corner) 316 | .into_iter() 317 | .chain(corners_between) 318 | .chain(Some(last_corner)) 319 | .collect(); 320 | 321 | Some(EditArcLinePath::new( 322 | new_corners, 323 | self.strategy, 324 | self.closed, 325 | )) 326 | } else { 327 | None 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/arc_line_path.rs: -------------------------------------------------------------------------------- 1 | use intersect::{Intersect, Intersection}; 2 | use itertools::Itertools; 3 | use line_path::{ConcatError, LinePath}; 4 | use rough_eq::{RoughEq, THICKNESS}; 5 | use segments::{ 6 | ArcOrLineSegment, ArcSegment, LineSegment, Segment, MIN_ARC_LENGTH, MIN_LINE_LENGTH, 7 | }; 8 | use {P2, V2, VecLike, N}; 9 | 10 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 11 | #[derive(Copy, Clone, Debug)] 12 | pub enum ArcLinePoint { 13 | Point(P2), 14 | Apex(P2), 15 | } 16 | 17 | impl ArcLinePoint { 18 | pub fn expect_point(&self) -> Option { 19 | if let &Point(p) = self { 20 | Some(p) 21 | } else { 22 | None 23 | } 24 | } 25 | 26 | pub fn expect_apex(&self) -> Option { 27 | if let &Apex(a) = self { 28 | Some(a) 29 | } else { 30 | None 31 | } 32 | } 33 | } 34 | 35 | use self::ArcLinePoint::{Apex, Point}; 36 | 37 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 38 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 39 | #[derive(Clone, Debug)] 40 | pub struct ArcLinePath { 41 | points: VecLike, 42 | } 43 | 44 | const ARC_DIRECTION_TOLERANCE: N = 0.0001; 45 | const CURVE_LINEARIZATION_MAX_ANGLE: N = 0.1; 46 | 47 | pub fn direction_along_line(start: P2, start_direction: V2, end: P2) -> bool { 48 | start_direction.rough_eq_by((end - start).normalize(), ARC_DIRECTION_TOLERANCE) 49 | } 50 | 51 | /// Creation 52 | impl ArcLinePath { 53 | pub fn line(start: P2, end: P2) -> Option { 54 | if (end - start).norm() <= MIN_LINE_LENGTH { 55 | None 56 | } else { 57 | Some(ArcLinePath { 58 | points: vec![Point(start), Point(end)].into(), 59 | }) 60 | } 61 | } 62 | 63 | pub fn arc(start: P2, start_direction: V2, end: P2) -> Option { 64 | if (end - start).norm() <= MIN_ARC_LENGTH { 65 | None 66 | } else if direction_along_line(start, start_direction, end) { 67 | Self::line(start, end) 68 | } else { 69 | if let Some(segment) = 70 | ArcSegment::minor_arc_with_start_direction(start, start_direction, end) 71 | { 72 | Some(ArcLinePath { 73 | points: vec![ 74 | Point(segment.start()), 75 | Apex(segment.apex()), 76 | Point(segment.end()), 77 | ].into(), 78 | }) 79 | } else { 80 | Self::line(start, end) 81 | } 82 | } 83 | } 84 | 85 | pub fn biarc(start: P2, start_direction: V2, end: P2, end_direction: V2) -> Option { 86 | const MAX_SIMPLE_LINE_LENGTH: N = 0.1; 87 | const RAY_LENGTH: N = 10_000.0; 88 | 89 | if (end - start).norm() <= THICKNESS { 90 | None 91 | } else if (end - start).norm() < MAX_SIMPLE_LINE_LENGTH { 92 | Self::line(start, end) 93 | } else { 94 | let single_arc = Self::arc(start, start_direction, end)?; 95 | if single_arc 96 | .end_direction() 97 | .rough_eq_by(end_direction, ARC_DIRECTION_TOLERANCE) 98 | { 99 | Some(single_arc) 100 | } else { 101 | let start_ray = LineSegment::new(start, start + RAY_LENGTH * start_direction); 102 | let end_ray = LineSegment::new(end, end - RAY_LENGTH * end_direction); 103 | let maybe_linear_intersection = (start_ray, end_ray).intersect().into_iter().find( 104 | |intersection| { 105 | intersection.along_a < 0.8 * RAY_LENGTH 106 | && intersection.along_b < 0.8 * RAY_LENGTH 107 | }, 108 | ); 109 | 110 | let (connection_position, connection_direction) = 111 | if let Some(Intersection { position, .. }) = maybe_linear_intersection { 112 | let start_to_intersection_distance = (start - position).norm(); 113 | let end_to_intersection_distance = (end - position).norm(); 114 | 115 | if start_to_intersection_distance < end_to_intersection_distance { 116 | // arc then line 117 | ( 118 | position + start_to_intersection_distance * end_direction, 119 | end_direction, 120 | ) 121 | } else { 122 | // line then arc 123 | ( 124 | position + end_to_intersection_distance * -start_direction, 125 | start_direction, 126 | ) 127 | } 128 | } else { 129 | // http://www.ryanjuckett.com/programming/biarc-interpolation/ 130 | let v = end - start; 131 | let t = start_direction + end_direction; 132 | let same_direction = 133 | start_direction.rough_eq_by(end_direction, ARC_DIRECTION_TOLERANCE); 134 | let end_orthogonal_of_start = v.dot(&end_direction).rough_eq(0.0); 135 | 136 | if same_direction && end_orthogonal_of_start { 137 | // __ 138 | // / \ 139 | // ^ v ^ 140 | // \__/ 141 | ( 142 | P2::from_coordinates((start.coords + end.coords) / 2.0), 143 | -start_direction, 144 | ) 145 | } else { 146 | let d = if same_direction { 147 | v.dot(&v) / (4.0 * v.dot(&end_direction)) 148 | } else { 149 | // magic - I'm pretty sure this can be simplified 150 | let v_dot_t = v.dot(&t); 151 | (-v_dot_t 152 | + (v_dot_t * v_dot_t 153 | + 2.0 154 | * (1.0 - start_direction.dot(&end_direction)) 155 | * v.dot(&v)) 156 | .sqrt()) 157 | / (2.0 * (1.0 - start_direction.dot(&end_direction))) 158 | }; 159 | 160 | let start_offset_point = start + d * start_direction; 161 | let end_offset_point = end - d * end_direction; 162 | let connection_direction = 163 | (end_offset_point - start_offset_point).normalize(); 164 | 165 | ( 166 | start_offset_point + d * connection_direction, 167 | connection_direction, 168 | ) 169 | } 170 | }; 171 | 172 | match ( 173 | Self::arc(start, start_direction, connection_position), 174 | Self::arc(connection_position, connection_direction, end), 175 | ) { 176 | (Some(first), Some(second)) => first.concat(&second).ok(), 177 | (Some(first), None) => Some(first), 178 | (None, Some(second)) => Some(second), 179 | _ => None, 180 | } 181 | } 182 | } 183 | } 184 | 185 | pub fn circle(center: P2, radius: N) -> Option { 186 | let top = center + V2::new(0.0, radius); 187 | let right = center + V2::new(radius, 0.0); 188 | let bottom = center + V2::new(0.0, -radius); 189 | let left = center + V2::new(-radius, 0.0); 190 | let top_right_segment = Self::arc(top, V2::new(1.0, 0.0), right)?; 191 | let bottom_right_segment = Self::arc(right, V2::new(0.0, -1.0), bottom)?; 192 | let bottom_left_segment = Self::arc(bottom, V2::new(-1.0, 0.0), left)?; 193 | let top_left_segment = Self::arc(left, V2::new(0.0, 1.0), top)?; 194 | top_right_segment 195 | .concat(&bottom_right_segment) 196 | .and_then(|path| path.concat(&bottom_left_segment)) 197 | .and_then(|path| path.concat(&top_left_segment)) 198 | .ok() 199 | } 200 | } 201 | 202 | /// Inspection 203 | impl ArcLinePath { 204 | pub fn start(&self) -> P2 { 205 | self.points[0] 206 | .expect_point() 207 | .expect("ArcLinePath start should be a Point") 208 | } 209 | 210 | pub fn end(&self) -> P2 { 211 | self.points 212 | .last() 213 | .and_then(ArcLinePoint::expect_point) 214 | .expect("ArcLinePath end should exist and be a Point") 215 | } 216 | 217 | pub fn length(&self) -> N { 218 | self.segments().map(|segment| segment.length()).sum() 219 | } 220 | 221 | pub fn start_direction(&self) -> V2 { 222 | self.segments().next().unwrap().start_direction() 223 | } 224 | 225 | pub fn end_direction(&self) -> V2 { 226 | self.segments().last().unwrap().end_direction() 227 | } 228 | 229 | pub fn segments<'a>(&'a self) -> impl Iterator + 'a { 230 | let mut points_iter = self.points.iter(); 231 | let mut start = points_iter 232 | .next() 233 | .and_then(ArcLinePoint::expect_point) 234 | .expect("ArcLinePath should have and start with a Point"); 235 | 236 | points_iter.batching(move |iter| match iter.next() { 237 | Some(&Point(end)) => { 238 | let segment = ArcOrLineSegment::line_unchecked(start, end); 239 | start = end; 240 | Some(segment) 241 | } 242 | Some(&Apex(apex)) => { 243 | if let Some(&Point(end)) = iter.next() { 244 | //let segment = ArcOrLineSegment::arc_unchecked(start, apex, end); 245 | if let Some(segment) = ArcSegment::new(start, apex, end) { 246 | start = end; 247 | Some(ArcOrLineSegment::Arc(segment)) 248 | } else { 249 | panic!( 250 | "Invalid segment in path: {:?}, seg: {:?} {:?} {:?}", 251 | self, start, apex, end 252 | ) 253 | } 254 | } else { 255 | unreachable!("After an Apex there should be at least one more Point") 256 | } 257 | } 258 | None => None, 259 | }) 260 | } 261 | } 262 | 263 | /// Combination/Modification 264 | impl ArcLinePath { 265 | pub fn concat(&self, other: &Self) -> Result { 266 | if self.end().rough_eq(other.start()) { 267 | let old_len = self.points.len(); 268 | let other_len = other.points.len(); 269 | let concat_path = ArcLinePath { 270 | points: self 271 | .points 272 | .iter() 273 | .chain(other.points[1..].iter()) 274 | .cloned() 275 | .collect(), 276 | }; 277 | // if there was an arc at either side of the transition, make sure it's still valid 278 | if other_len >= 3 { 279 | if let (Point(start), Apex(apex), Point(end)) = ( 280 | concat_path.points[old_len - 1], 281 | concat_path.points[old_len], 282 | concat_path.points[old_len + 1], 283 | ) { 284 | if ArcSegment::new(start, apex, end).is_none() { 285 | return Err(ConcatError::CreatedInvalidSegment); 286 | } 287 | } 288 | } 289 | 290 | Ok(concat_path) 291 | } else { 292 | Err(ConcatError::PointsTooFarApart) 293 | } 294 | } 295 | 296 | pub fn reverse(&self) -> Self { 297 | let mut new_points = self.points.clone(); 298 | new_points.reverse(); 299 | ArcLinePath { 300 | points: new_points 301 | } 302 | } 303 | 304 | pub fn to_line_path_with_max_angle(&self, max_angle: N) -> LinePath { 305 | let points: VecLike = self 306 | .segments() 307 | .flat_map(|segment| segment.subdivisions_without_end(max_angle)) 308 | .chain(Some(self.end())) 309 | .collect(); 310 | 311 | if let Some(path) = LinePath::new(points.clone()) { 312 | path 313 | } else { 314 | panic!( 315 | "A valid ArcLinePath should always produce a valid LinePath: {:?}, points: {:?}", 316 | self, points 317 | ) 318 | } 319 | } 320 | 321 | pub fn to_line_path(&self) -> LinePath { 322 | self.to_line_path_with_max_angle(CURVE_LINEARIZATION_MAX_ANGLE) 323 | } 324 | } 325 | 326 | #[test] 327 | fn to_line_path() { 328 | use PI; 329 | let curved_path = ArcLinePath::line(P2::new(0.0, 0.0), P2::new(1.0, 0.0)) 330 | .expect("first line should work") 331 | .concat( 332 | &ArcLinePath::arc(P2::new(1.0, 0.0), V2::new(1.0, 0.0), P2::new(2.0, 1.0)) 333 | .expect("arc should work"), 334 | ) 335 | .expect("line>arc concat should work") 336 | .concat( 337 | &ArcLinePath::line(P2::new(2.0, 1.0), P2::new(2.0, 2.0)) 338 | .expect("second line should work"), 339 | ) 340 | .expect("line-arc>line concat should work"); 341 | 342 | println!("{:#?}", curved_path.segments().collect::>()); 343 | 344 | assert_rough_eq_by!( 345 | &LinePath::new(vec![ 346 | P2::new(0.0, 0.0), 347 | P2::new(1.0, 0.0), 348 | P2::new(1.5, 0.13397461), 349 | P2::new(1.8660254, 0.5), 350 | P2::new(2.0, 1.0), 351 | P2::new(2.0, 2.0), 352 | ]).unwrap(), 353 | &curved_path.to_line_path_with_max_angle(PI / 6.0), 354 | 0.0001 355 | ); 356 | } 357 | -------------------------------------------------------------------------------- /src/line_path.rs: -------------------------------------------------------------------------------- 1 | use {N, P2, V2, VecLike}; 2 | use rough_eq::{RoughEq, THICKNESS}; 3 | use angles::WithUniqueOrthogonal; 4 | use ordered_float::OrderedFloat; 5 | use segments::LineSegment; 6 | 7 | #[cfg_attr(feature = "compact_containers", derive(Compact))] 8 | #[cfg_attr(feature = "serde-serialization", derive(Serialize, Deserialize))] 9 | #[cfg_attr(test, derive(PartialEq))] 10 | #[derive(Clone, Debug)] 11 | pub struct LinePath { 12 | pub points: VecLike, 13 | pub distances: VecLike, 14 | } 15 | 16 | /// Creation 17 | impl LinePath { 18 | pub fn new(mut points: VecLike) -> Option { 19 | if points.len() >= 2 { 20 | let old_last = *points.last().unwrap(); 21 | let mut previous_point = points[0]; 22 | let mut distances = VecLike::with_capacity(points.len()); 23 | let mut current_distance = 0.0; 24 | let mut is_first = true; 25 | 26 | points.retain(|point| { 27 | let new_distance = (point - previous_point).norm(); 28 | 29 | if is_first || new_distance > THICKNESS { 30 | current_distance += new_distance; 31 | distances.push(current_distance); 32 | previous_point = *point; 33 | is_first = false; 34 | true 35 | } else { 36 | false 37 | } 38 | }); 39 | 40 | if points.len() >= 2 { 41 | // special case: path got shortened because last point is too close to previous 42 | // make sure that we keep the last point instead of the previous 43 | let update_last_distance = { 44 | let last = points.last_mut().unwrap(); 45 | if *last != old_last { 46 | *last = old_last; 47 | true 48 | } else { 49 | false 50 | } 51 | }; 52 | 53 | if update_last_distance { 54 | let previous_distance = distances[distances.len() - 2]; 55 | let previous_point = points[points.len() - 2]; 56 | *distances.last_mut().unwrap() = 57 | previous_distance + (old_last - previous_point).norm() 58 | } 59 | 60 | Some(LinePath { points, distances }) 61 | } else { 62 | None 63 | } 64 | } else { 65 | None 66 | } 67 | } 68 | } 69 | 70 | #[test] 71 | fn constructor() { 72 | assert_eq!( 73 | LinePath::new(vec![ 74 | P2::new(0.0, 0.0), 75 | P2::new(1.0, 0.0), 76 | P2::new(1.0, 1.0), 77 | ]), 78 | Some(LinePath { 79 | points: vec![P2::new(0.0, 0.0), P2::new(1.0, 0.0), P2::new(1.0, 1.0)], 80 | distances: vec![0.0, 1.0, 2.0], 81 | }) 82 | ); 83 | } 84 | 85 | #[test] 86 | fn constructor_simplify_points() { 87 | assert_eq!( 88 | LinePath::new(vec![ 89 | P2::new(0.0, 0.0), 90 | P2::new(0.0 + THICKNESS / 2.0, 0.0), 91 | P2::new(1.0, 0.0), 92 | P2::new(1.0 + THICKNESS / 2.0, 0.0), 93 | P2::new(1.0 - THICKNESS / 2.0, 1.0), 94 | P2::new(1.0, 1.0), 95 | ]), 96 | Some(LinePath { 97 | points: vec![P2::new(0.0, 0.0), P2::new(1.0, 0.0), P2::new(1.0, 1.0)], 98 | distances: vec![0.0, 1.0, 2.0], 99 | }) 100 | ); 101 | } 102 | 103 | #[test] 104 | fn constructor_invalid() { 105 | assert_eq!( 106 | LinePath::new(vec![P2::new(0.0, 0.0), P2::new(0.0 + THICKNESS / 2.0, 0.0)]), 107 | None 108 | ); 109 | } 110 | 111 | /// Inspection 112 | impl LinePath { 113 | pub fn start(&self) -> P2 { 114 | self.points[0] 115 | } 116 | 117 | pub fn end(&self) -> P2 { 118 | *self.points.last().unwrap() 119 | } 120 | 121 | pub fn length(&self) -> N { 122 | *self.distances.last().unwrap() 123 | } 124 | 125 | pub fn segments<'a>(&'a self) -> impl Iterator + 'a { 126 | self.points 127 | .windows(2) 128 | .map(|window| LineSegment::new(window[0], window[1])) 129 | } 130 | 131 | pub fn segments_with_distances(&self) -> impl Iterator { 132 | self.segments().zip(self.distances.windows(2)) 133 | } 134 | 135 | pub fn first_segment(&self) -> LineSegment { 136 | LineSegment::new(self.points[0], self.points[1]) 137 | } 138 | 139 | pub fn last_segment(&self) -> LineSegment { 140 | let last = self.points.len() - 1; 141 | LineSegment::new(self.points[last - 1], self.points[last]) 142 | } 143 | 144 | pub fn nth_segment(&self, n: usize) -> LineSegment { 145 | LineSegment::new(self.points[n], self.points[n + 1]) 146 | } 147 | 148 | pub fn find_on_segment(&self, distance: N) -> Option<(LineSegment, N)> { 149 | for (segment, distance_pair) in self.segments_with_distances() { 150 | if distance_pair[1] >= distance { 151 | return Some((segment, distance - distance_pair[0])); 152 | } 153 | } 154 | None 155 | } 156 | 157 | pub fn along(&self, distance: N) -> P2 { 158 | if let Some((segment, distance_on_segment)) = self.find_on_segment(distance) { 159 | segment.along(distance_on_segment) 160 | } else if distance < 0.0 { 161 | self.start() 162 | } else { 163 | self.end() 164 | } 165 | } 166 | 167 | pub fn direction_along(&self, distance: N) -> V2 { 168 | if let Some((segment, _)) = self.find_on_segment(distance) { 169 | segment.direction() 170 | } else if distance < 0.0 { 171 | self.first_segment().direction() 172 | } else { 173 | self.last_segment().direction() 174 | } 175 | } 176 | 177 | pub fn start_direction(&self) -> V2 { 178 | self.first_segment().direction() 179 | } 180 | 181 | pub fn end_direction(&self) -> V2 { 182 | self.last_segment().direction() 183 | } 184 | 185 | pub fn project_with_tolerance(&self, point: P2, tolerance: N) -> Option<(N, P2)> { 186 | self.segments_with_distances() 187 | .filter_map(|(segment, distances)| { 188 | segment.project_with_tolerance(point, tolerance).map( 189 | |(distance_on_segment, projected_point)| { 190 | (distance_on_segment + distances[0], projected_point) 191 | }, 192 | ) 193 | }) 194 | .min_by_key(|(_, projected_point)| OrderedFloat((projected_point - point).norm())) 195 | } 196 | 197 | pub fn project_with_max_distance( 198 | &self, 199 | point: P2, 200 | tolerance: N, 201 | max_distance: N, 202 | ) -> Option<(N, P2)> { 203 | self.project_with_tolerance(point, tolerance) 204 | .and_then(|(along, projected_point)| { 205 | if (projected_point - point).norm() <= max_distance { 206 | Some((along, projected_point)) 207 | } else { 208 | None 209 | } 210 | }) 211 | } 212 | 213 | pub fn project(&self, point: P2) -> Option<(N, P2)> { 214 | self.project_with_tolerance(point, THICKNESS) 215 | } 216 | 217 | pub fn includes(&self, point: P2) -> bool { 218 | self.distance_to(point) < THICKNESS 219 | } 220 | 221 | pub fn distance_to(&self, point: P2) -> N { 222 | if let Some((_, projected_point)) = self.project(point) { 223 | (point - projected_point).norm() 224 | } else { 225 | *::std::cmp::min( 226 | OrderedFloat((point - self.start()).norm()), 227 | OrderedFloat((point - self.end()).norm()), 228 | ) 229 | } 230 | } 231 | } 232 | 233 | #[derive(Debug)] 234 | pub enum ConcatError { 235 | PointsTooFarApart, 236 | CreatedInvalidSegment 237 | } 238 | 239 | // TODO: this is a super hacky newtype to avoid weird problems with impl Iterator 240 | #[derive(Copy, Clone, Debug)] 241 | pub struct ShiftVector(pub V2); 242 | 243 | /// Combination/Modification 244 | impl LinePath { 245 | pub fn concat(&self, other: &Self) -> Result { 246 | self.concat_weld(other, THICKNESS) 247 | } 248 | 249 | pub fn concat_weld(&self, other: &Self, tolerance: N) -> Result { 250 | if other.start().rough_eq_by(self.end(), tolerance) { 251 | LinePath::new( 252 | self.points 253 | .iter() 254 | .chain(other.points.iter()) 255 | .cloned() 256 | .collect(), 257 | ).ok_or(ConcatError::CreatedInvalidSegment) 258 | } else { 259 | Err(ConcatError::PointsTooFarApart) 260 | } 261 | } 262 | 263 | pub fn reverse(&self) -> Self { 264 | let mut points = self.points.clone(); 265 | points.reverse(); 266 | LinePath::new(points).expect("Reversing should always work") 267 | } 268 | 269 | pub fn subsection(&self, start: N, end: N) -> Option { 270 | if start > end && self.start().rough_eq(self.end()) { 271 | // TODO: Handle "negative start" 272 | LinePath::new( 273 | Some(self.along(start)) 274 | .into_iter() 275 | .chain(self.points.iter().zip(self.distances.iter()).filter_map(|(&point, &distance)| 276 | if start < distance { 277 | Some(point) 278 | } else { 279 | None 280 | } 281 | )).chain(self.points.iter().zip(self.distances.iter()).filter_map(|(&point, &distance)| 282 | if end > distance { 283 | Some(point) 284 | } else { 285 | None 286 | } 287 | )) 288 | .chain(Some(self.along(end))) 289 | .collect() 290 | ) 291 | } else { 292 | LinePath::new( 293 | Some(self.along(start)) 294 | .into_iter() 295 | .chain(self.points.iter().zip(self.distances.iter()).filter_map( 296 | |(&point, &distance)| { 297 | if start < distance && end > distance { 298 | Some(point) 299 | } else { 300 | None 301 | } 302 | }, 303 | )) 304 | .chain(Some(self.along(end))) 305 | .collect(), 306 | ) 307 | } 308 | } 309 | 310 | pub fn dash(&self, dash_length: N, gap_length: N) -> DashIterator { 311 | DashIterator { 312 | path: self, 313 | dash_length, 314 | gap_length, 315 | position: 0.0 316 | } 317 | } 318 | 319 | pub fn shift_orthogonally_vectors<'a>(&'a self) -> impl Iterator + 'a { 320 | fn bisector(a: P2, b: P2, c: P2) -> ShiftVector { 321 | let bisecting_direction = (LineSegment::new(a, b).direction() 322 | + LineSegment::new(b, c).direction()) 323 | .orthogonal_right() 324 | .normalize(); 325 | let amount_too_short = LineSegment::new(a, b).direction().orthogonal_right().dot(&bisecting_direction); 326 | ShiftVector(bisecting_direction * (1.0 / amount_too_short)) 327 | } 328 | let angle_bisectors = self.points.windows(3).map(|triplet| bisector(triplet[0], triplet[1], triplet[2])); 329 | 330 | let (first_vector, last_vector) = if self.points.len() >= 3 && self.start().rough_eq(self.end()) { 331 | let first_vector = bisector(self.points[self.points.len() - 2], self.points[0], self.points[1]); 332 | (first_vector, first_vector) 333 | } else { 334 | let first_vector: ShiftVector = ShiftVector(self.first_segment().direction().orthogonal_right()); 335 | let last_vector: ShiftVector = ShiftVector(self.last_segment().direction().orthogonal_right()); 336 | (first_vector, last_vector) 337 | }; 338 | 339 | Some(first_vector) 340 | .into_iter() 341 | .chain(angle_bisectors) 342 | .chain(Some(last_vector)) 343 | } 344 | 345 | pub fn shift_orthogonally(&self, shift_to_right: N) -> Option { 346 | let new_points = self 347 | .points 348 | .iter() 349 | .zip(self.shift_orthogonally_vectors()) 350 | .map(|(point, shift_vector)| point + shift_to_right * shift_vector.0) 351 | .collect(); 352 | 353 | LinePath::new(new_points) 354 | } 355 | 356 | pub fn with_new_start_and_end(&self, new_start: P2, new_end: P2) -> Option { 357 | let last = self.points.len() - 1; 358 | Self::new( 359 | Some(new_start) 360 | .into_iter() 361 | .chain(self.points[1..last].iter().cloned()) 362 | .chain(Some(new_end)) 363 | .collect(), 364 | ) 365 | } 366 | } 367 | 368 | pub struct DashIterator<'a> { 369 | path: &'a LinePath, 370 | dash_length: N, 371 | gap_length: N, 372 | position: N 373 | } 374 | 375 | impl<'a> Iterator for DashIterator<'a> { 376 | type Item = Option; 377 | 378 | fn next(&mut self) -> Option> { 379 | if self.position < self.path.length() { 380 | let old_position = self.position; 381 | self.position += self.dash_length; 382 | let dash = self.path.subsection(old_position, self.position); 383 | self.position += self.gap_length; 384 | Some(dash) 385 | } else { 386 | None 387 | } 388 | } 389 | } 390 | 391 | impl<'a> RoughEq for &'a LinePath { 392 | fn rough_eq_by(&self, other: Self, tolerance: N) -> bool { 393 | self.points.len() == other.points.len() 394 | && self 395 | .points 396 | .iter() 397 | .zip(other.points.iter()) 398 | .all(|(a, b)| a.rough_eq_by(*b, tolerance)) 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/segments.rs: -------------------------------------------------------------------------------- 1 | use angles::{signed_angle_to, WithUniqueOrthogonal}; 2 | use rough_eq::THICKNESS; 3 | use {P2, Rotation2, V2, N, PI}; 4 | 5 | pub const MIN_LINE_LENGTH: N = 0.01; 6 | pub const MIN_ARC_LENGTH: N = MIN_LINE_LENGTH; 7 | 8 | pub trait Segment { 9 | fn start(&self) -> P2; 10 | fn end(&self) -> P2; 11 | fn length(&self) -> N; 12 | fn start_direction(&self) -> V2; 13 | fn end_direction(&self) -> V2; 14 | fn subdivisions_without_end(&self, max_angle: N) -> Vec; 15 | } 16 | 17 | #[derive(Copy, Clone, Debug)] 18 | pub struct LineSegment { 19 | start: P2, 20 | end: P2, 21 | } 22 | 23 | impl LineSegment { 24 | pub fn new(start: P2, end: P2) -> Self { 25 | LineSegment { start, end } 26 | } 27 | 28 | pub fn direction(&self) -> V2 { 29 | (self.end - self.start).normalize() 30 | } 31 | } 32 | 33 | impl Segment for LineSegment { 34 | fn start(&self) -> P2 { 35 | self.start 36 | } 37 | 38 | fn end(&self) -> P2 { 39 | self.end 40 | } 41 | 42 | fn length(&self) -> N { 43 | (self.start - self.end).norm() 44 | } 45 | 46 | fn start_direction(&self) -> V2 { 47 | self.direction() 48 | } 49 | 50 | fn end_direction(&self) -> V2 { 51 | self.direction() 52 | } 53 | 54 | fn subdivisions_without_end(&self, _: N) -> Vec { 55 | vec![self.start] 56 | } 57 | } 58 | 59 | impl LineSegment { 60 | pub fn along(&self, distance: N) -> P2 { 61 | self.start + distance * self.direction() 62 | } 63 | 64 | pub fn midpoint(&self) -> P2 { 65 | P2::from_coordinates((self.start.coords + self.end.coords) / 2.0) 66 | } 67 | 68 | pub fn project_with_tolerance(&self, point: P2, tolerance: N) -> Option<(N, P2)> { 69 | if (point - self.start).norm() < tolerance { 70 | Some((0.0, self.start)) 71 | } else if (point - self.end).norm() < tolerance { 72 | Some((self.length(), self.end)) 73 | } else { 74 | let direction = self.direction(); 75 | let line_offset = direction.dot(&(point - self.start)); 76 | if line_offset >= 0.0 && line_offset <= self.length() { 77 | Some((line_offset, self.start + line_offset * direction)) 78 | } else { 79 | None 80 | } 81 | } 82 | } 83 | 84 | pub fn project_with_max_distance( 85 | &self, 86 | point: P2, 87 | tolerance: N, 88 | max_distance: N, 89 | ) -> Option<(N, P2)> { 90 | self.project_with_tolerance(point, tolerance) 91 | .and_then(|(along, projected_point)| { 92 | if (projected_point - point).norm() <= max_distance { 93 | Some((along, projected_point)) 94 | } else { 95 | None 96 | } 97 | }) 98 | } 99 | 100 | pub fn winding_angle(&self, point: P2) -> N { 101 | signed_angle_to(self.start - point, self.end - point) 102 | } 103 | 104 | pub fn side_of(&self, point: P2) -> N { 105 | self.winding_angle(point).signum() 106 | } 107 | 108 | pub fn is_point_left_of(&self, point: P2) -> bool { 109 | self.winding_angle(point) > 0.0 110 | } 111 | 112 | pub fn is_point_right_of(&self, point: P2) -> bool { 113 | self.winding_angle(point) < 0.0 114 | } 115 | 116 | pub fn signed_distance_of(&self, point: P2) -> N { 117 | let direction_orth = self.direction().orthogonal_left(); 118 | (point - self.start).dot(&direction_orth) 119 | } 120 | } 121 | 122 | use bbox::{BoundingBox, HasBoundingBox}; 123 | 124 | impl HasBoundingBox for LineSegment { 125 | fn bounding_box(&self) -> BoundingBox { 126 | BoundingBox { 127 | min: P2::new( 128 | self.start.x.min(self.end.x) - THICKNESS, 129 | self.start.y.min(self.end.y) - THICKNESS, 130 | ), 131 | max: P2::new( 132 | self.start.x.max(self.end.x) + THICKNESS, 133 | self.start.y.max(self.end.y) + THICKNESS, 134 | ), 135 | } 136 | } 137 | } 138 | 139 | #[derive(Copy, Clone, Debug)] 140 | pub struct ArcSegment { 141 | start: P2, 142 | apex: P2, 143 | end: P2, 144 | } 145 | 146 | impl ArcSegment { 147 | pub fn new(start: P2, apex: P2, end: P2) -> Option { 148 | if (start - apex).norm() < MIN_LINE_LENGTH 149 | || (end - apex).norm() < MIN_LINE_LENGTH 150 | || (start - end).norm() < MIN_ARC_LENGTH 151 | { 152 | None 153 | } else { 154 | let segment = ArcSegment { start, apex, end }; 155 | let center = segment.center(); 156 | if center.x.is_nan() || center.y.is_nan() || center.x.is_infinite() || center.y.is_infinite() { 157 | None 158 | } else { 159 | Some(segment) 160 | } 161 | } 162 | } 163 | 164 | pub fn minor_arc_with_center(start: P2, center: P2, end: P2) -> Option { 165 | let center_to_start = start - center; 166 | let center_to_end = end - center; 167 | let radius = center_to_start.norm(); 168 | 169 | let sum = center_to_start + center_to_end; 170 | 171 | let apex = if sum.norm() > 0.01 { 172 | center + sum.normalize() * radius 173 | } else { 174 | let signed_angle_span = signed_angle_to(center_to_start, center_to_end); 175 | let center_to_apex = Rotation2::new(signed_angle_span / 2.0) * center_to_start; 176 | center + center_to_apex 177 | }; 178 | 179 | // TODO: avoid redundant calculation of center, but still check for validity somehow 180 | ArcSegment::new(start, apex, end) 181 | } 182 | 183 | pub fn minor_arc_with_start_direction( 184 | start: P2, 185 | start_direction: V2, 186 | end: P2, 187 | ) -> Option { 188 | let signed_radius = { 189 | let half_chord = (end - start) / 2.0; 190 | half_chord.norm_squared() / start_direction.orthogonal_right().dot(&half_chord) 191 | }; 192 | 193 | let center = start + signed_radius * start_direction.orthogonal_right(); 194 | 195 | Self::minor_arc_with_center(start, center, end) 196 | } 197 | 198 | pub fn apex(&self) -> P2 { 199 | self.apex 200 | } 201 | 202 | #[inline] 203 | pub fn center(&self) -> P2 { 204 | // https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcenter_coordinates 205 | let (a_abs, b_abs, c_abs) = (self.start, self.apex, self.end); 206 | let (b, c) = (b_abs - a_abs, c_abs - a_abs); 207 | let d_inv = 1.0 / (2.0 * (b.x * c.y - b.y * c.x)); 208 | let (b_norm_sq, c_norm_sq) = (b.norm_squared(), c.norm_squared()); 209 | let center_x = d_inv * (c.y * b_norm_sq - b.y * c_norm_sq); 210 | let center_y = d_inv * (b.x * c_norm_sq - c.x * b_norm_sq); 211 | a_abs + V2::new(center_x, center_y) 212 | } 213 | 214 | pub fn radius(&self) -> N { 215 | (self.start - self.center()).norm() 216 | } 217 | 218 | pub fn signed_angle_span(&self) -> N { 219 | let center = self.center(); 220 | signed_angle_to(self.start - center, self.end - center) 221 | } 222 | 223 | pub fn is_minor(&self) -> bool { 224 | self.signed_angle_span() * LineSegment::new(self.start, self.end).side_of(self.apex()) < 0.0 225 | } 226 | } 227 | 228 | impl Segment for ArcSegment { 229 | fn start(&self) -> P2 { 230 | self.start 231 | } 232 | 233 | fn end(&self) -> P2 { 234 | self.end 235 | } 236 | 237 | fn start_direction(&self) -> V2 { 238 | let center = self.center(); 239 | let center_to_start_orth = (self.start - center).orthogonal_right(); 240 | 241 | if LineSegment::new(self.start, self.end).is_point_left_of(self.apex) { 242 | center_to_start_orth 243 | } else { 244 | -center_to_start_orth 245 | } 246 | } 247 | 248 | fn end_direction(&self) -> V2 { 249 | let center = self.center(); 250 | let center_to_end_orth = (self.end - center).orthogonal_right(); 251 | 252 | if LineSegment::new(self.start, self.end).is_point_left_of(self.apex) { 253 | center_to_end_orth 254 | } else { 255 | -center_to_end_orth 256 | } 257 | } 258 | 259 | fn length(&self) -> N { 260 | let simple_angle_span = self.signed_angle_span().abs(); 261 | 262 | let angle_span = if self.is_minor() { 263 | simple_angle_span 264 | } else { 265 | 2.0 * PI - simple_angle_span 266 | }; 267 | 268 | self.radius() * angle_span 269 | } 270 | 271 | fn subdivisions_without_end(&self, max_angle: N) -> Vec { 272 | let center = self.center(); 273 | let signed_angle_span = signed_angle_to(self.start - center, self.end - center); 274 | 275 | if signed_angle_span.is_nan() { 276 | println!("KAPUTT {:?} {:?}", self, center); 277 | } 278 | 279 | let subdivisions = (signed_angle_span.abs() / max_angle).floor() as usize; 280 | let subdivision_angle = signed_angle_span / (subdivisions as f32); 281 | 282 | let mut pointer = self.start - center; 283 | 284 | let mut maybe_previous_point: Option = None; 285 | 286 | (0..subdivisions.max(1)) 287 | .into_iter() 288 | .filter_map(|_| { 289 | let point = center + pointer; 290 | pointer = Rotation2::new(subdivision_angle) * pointer; 291 | 292 | if let Some(previous_point) = maybe_previous_point { 293 | if (point - previous_point).norm() > 2.0 * THICKNESS { 294 | maybe_previous_point = Some(point); 295 | Some(point) 296 | } else { 297 | None 298 | } 299 | } else { 300 | maybe_previous_point = Some(point); 301 | Some(point) 302 | } 303 | }) 304 | .collect::>() 305 | } 306 | } 307 | 308 | #[derive(Copy, Clone, Debug)] 309 | pub enum ArcOrLineSegment { 310 | Line(LineSegment), 311 | Arc(ArcSegment), 312 | } 313 | 314 | impl ArcOrLineSegment { 315 | pub fn line_unchecked(start: P2, end: P2) -> ArcOrLineSegment { 316 | ArcOrLineSegment::Line(LineSegment { start, end }) 317 | } 318 | 319 | pub fn arc_unchecked(start: P2, apex: P2, end: P2) -> ArcOrLineSegment { 320 | ArcOrLineSegment::Arc(ArcSegment { start, apex, end }) 321 | } 322 | } 323 | 324 | impl Segment for ArcOrLineSegment { 325 | fn start(&self) -> P2 { 326 | match *self { 327 | ArcOrLineSegment::Line(line) => line.start(), 328 | ArcOrLineSegment::Arc(arc) => arc.start(), 329 | } 330 | } 331 | fn end(&self) -> P2 { 332 | match *self { 333 | ArcOrLineSegment::Line(line) => line.end(), 334 | ArcOrLineSegment::Arc(arc) => arc.end(), 335 | } 336 | } 337 | fn length(&self) -> N { 338 | match *self { 339 | ArcOrLineSegment::Line(line) => line.length(), 340 | ArcOrLineSegment::Arc(arc) => arc.length(), 341 | } 342 | } 343 | fn start_direction(&self) -> V2 { 344 | match *self { 345 | ArcOrLineSegment::Line(line) => line.start_direction(), 346 | ArcOrLineSegment::Arc(arc) => arc.start_direction(), 347 | } 348 | } 349 | fn end_direction(&self) -> V2 { 350 | match *self { 351 | ArcOrLineSegment::Line(line) => line.end_direction(), 352 | ArcOrLineSegment::Arc(arc) => arc.end_direction(), 353 | } 354 | } 355 | fn subdivisions_without_end(&self, max_angle: N) -> Vec { 356 | match *self { 357 | ArcOrLineSegment::Line(line) => line.subdivisions_without_end(max_angle), 358 | ArcOrLineSegment::Arc(arc) => arc.subdivisions_without_end(max_angle), 359 | } 360 | } 361 | } 362 | 363 | #[cfg(test)] 364 | const TOL: f32 = 0.0001; 365 | 366 | #[cfg(test)] 367 | use rough_eq::RoughEq; 368 | 369 | #[test] 370 | fn minor_arc_test() { 371 | let o = V2::new(10.0, 5.0); 372 | let minor_arc = ArcSegment::new( 373 | P2::new(0.0, 1.0) + o, 374 | P2::new(-3.0f32.sqrt() / 2.0, 0.5) + o, 375 | P2::new(-1.0, 0.0) + o, 376 | ).expect("should be valid"); 377 | assert_rough_eq_by!(minor_arc.center(), P2::new(0.0, 0.0) + o, TOL); 378 | assert!(minor_arc.is_minor()); 379 | assert_rough_eq_by!(minor_arc.length(), PI / 2.0, TOL); 380 | 381 | let minor_arc_rev = ArcSegment::new( 382 | P2::new(-1.0, 0.0) + o, 383 | P2::new(-3.0f32.sqrt() / 2.0, 0.5) + o, 384 | P2::new(0.0, 1.0) + o, 385 | ).expect("should be valid"); 386 | assert_rough_eq_by!(minor_arc_rev.center(), P2::new(0.0, 0.0) + o, TOL); 387 | assert!(minor_arc_rev.is_minor()); 388 | assert_rough_eq_by!(minor_arc_rev.length(), PI / 2.0, TOL); 389 | 390 | let minor_arc_by_center = ArcSegment::minor_arc_with_center( 391 | P2::new(0.0, 1.0) + o, 392 | P2::new(0.0, 0.0) + o, 393 | P2::new(-1.0, 0.0) + o, 394 | ).expect("should be valid"); 395 | assert_rough_eq_by!( 396 | minor_arc_by_center.apex(), 397 | P2::new(-2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0) + o, 398 | TOL 399 | ); 400 | assert_rough_eq_by!(minor_arc_by_center.length(), PI / 2.0, TOL); 401 | 402 | let minor_arc_by_center_rev = ArcSegment::minor_arc_with_center( 403 | P2::new(-1.0, 0.0) + o, 404 | P2::new(0.0, 0.0) + o, 405 | P2::new(0.0, 1.0) + o, 406 | ).expect("should be valid"); 407 | assert_rough_eq_by!( 408 | minor_arc_by_center_rev.apex(), 409 | P2::new(-2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0) + o, 410 | TOL 411 | ); 412 | assert_rough_eq_by!(minor_arc_by_center_rev.length(), PI / 2.0, TOL); 413 | 414 | let minor_arc_by_direction = ArcSegment::minor_arc_with_start_direction( 415 | P2::new(0.0, 1.0) + o, 416 | V2::new(-1.0, 0.0), 417 | P2::new(-1.0, 0.0) + o, 418 | ).expect("should be valid"); 419 | assert_rough_eq_by!( 420 | minor_arc_by_direction.apex(), 421 | P2::new(-2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0) + o, 422 | TOL 423 | ); 424 | assert_rough_eq_by!(minor_arc_by_direction.length(), PI / 2.0, TOL); 425 | 426 | let minor_arc_by_direction_rev = ArcSegment::minor_arc_with_start_direction( 427 | P2::new(-1.0, 0.0) + o, 428 | V2::new(0.0, 1.0), 429 | P2::new(0.0, 1.0) + o, 430 | ).expect("should be valid"); 431 | assert_rough_eq_by!( 432 | minor_arc_by_direction_rev.apex(), 433 | P2::new(-2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0) + o, 434 | TOL 435 | ); 436 | assert_rough_eq_by!(minor_arc_by_direction_rev.length(), PI / 2.0, TOL); 437 | } 438 | 439 | #[test] 440 | fn colinear_apex_test() { 441 | assert!(ArcSegment::new(P2::new(0.0, 0.0), P2::new(1.0, 0.0), P2::new(2.0, 0.0)).is_none()); 442 | } 443 | 444 | #[test] 445 | fn major_arc_test() { 446 | let o = V2::new(10.0, 5.0); 447 | let major_arc = ArcSegment::new( 448 | P2::new(0.0, -1.0) + o, 449 | P2::new(-3.0f32.sqrt() / 2.0, 0.5) + o, 450 | P2::new(1.0, 0.0) + o, 451 | ).expect("should be valid"); 452 | assert_rough_eq_by!(major_arc.center(), P2::new(0.0, 0.0) + o, TOL); 453 | assert!(!major_arc.is_minor()); 454 | assert_rough_eq_by!(major_arc.length(), 3.0 * PI / 2.0, TOL); 455 | 456 | let major_arc_rev = ArcSegment::new( 457 | P2::new(-1.0, 0.0) + o, 458 | P2::new(-3.0f32.sqrt() / 2.0, 0.5) + o, 459 | P2::new(0.0, -1.0) + o, 460 | ).expect("should be valid"); 461 | assert_rough_eq_by!(major_arc_rev.center(), P2::new(0.0, 0.0) + o, TOL); 462 | assert!(!major_arc_rev.is_minor()); 463 | assert_rough_eq_by!(major_arc_rev.length(), 3.0 * PI / 2.0, TOL); 464 | } 465 | -------------------------------------------------------------------------------- /src/area_embedding.rs: -------------------------------------------------------------------------------- 1 | use areas::{Area, AreaError, PrimitiveArea}; 2 | use closed_line_path::ClosedLinePath; 3 | use embedding::Embedding; 4 | use line_path::LinePath; 5 | use rough_eq::{RoughEq, THICKNESS}; 6 | use segments::Segment; 7 | use std::hash::Hash; 8 | use util::join_within_vec; 9 | use util::SmallSet; 10 | use bbox::HasBoundingBox; 11 | use {P2, N}; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct AreaLabel { 15 | pub own_right_label: L, 16 | pub right_labels: SmallSet<[L; 4]>, 17 | pub left_labels: SmallSet<[L; 4]>, 18 | } 19 | 20 | pub struct AreaEmbedding { 21 | embedding: Embedding>, 22 | areas: Vec<(Area, L)>, 23 | calculated_containment: bool, 24 | } 25 | 26 | impl AreaEmbedding { 27 | pub fn new(cell_width: N) -> Self { 28 | AreaEmbedding { 29 | embedding: Embedding::new(cell_width), 30 | areas: Vec::new(), 31 | calculated_containment: false, 32 | } 33 | } 34 | 35 | pub fn insert(&mut self, area: Area, label: L) { 36 | for primitive in area.primitives.iter() { 37 | self.embedding.insert( 38 | primitive.boundary.0.clone(), 39 | AreaLabel { 40 | own_right_label: label.clone(), 41 | left_labels: SmallSet::new(), 42 | right_labels: SmallSet::new(), 43 | }, 44 | ); 45 | } 46 | self.areas.push((area, label)); 47 | self.calculated_containment = false; 48 | } 49 | 50 | fn ensure_containment_calculated(&mut self) { 51 | if self.calculated_containment { 52 | return; 53 | } 54 | 55 | let keys = self.embedding.pieces.keys().collect::>(); 56 | for piece_idx in keys { 57 | let (midpoint, midpoint_direction, own_label) = { 58 | let (piece, area_label) = &self.embedding.pieces[piece_idx]; 59 | let half_along = piece.length() / 2.0; 60 | ( 61 | piece.along(half_along), 62 | piece.direction_along(half_along), 63 | area_label.own_right_label.clone(), 64 | ) 65 | }; 66 | 67 | // inspired by http://geomalgorithms.com/a03-_inclusion.html 68 | for (area, label) in &self.areas { 69 | enum PieceAreaRelation { 70 | CoincidentForward, 71 | CoincidentBackward, 72 | Inside, 73 | Outside 74 | } 75 | 76 | if label != &own_label { 77 | if !area.bounding_box().contains(midpoint) { 78 | continue; 79 | } 80 | 81 | let mut winding_number = 0; 82 | let mut coincident_relation = None; 83 | 84 | 'area: for primitive in &area.primitives { 85 | for segment in primitive.boundary.path().segments() { 86 | let signed_distance = segment.signed_distance_of(midpoint); 87 | let segment_direction = segment.direction(); 88 | 89 | if signed_distance.abs() < THICKNESS { 90 | let line_offset = 91 | segment_direction.dot(&(midpoint - segment.start())); 92 | if line_offset >= -THICKNESS && line_offset <= segment.length() + THICKNESS { 93 | // on the segment 94 | if midpoint_direction.dot(&segment_direction) > 0.0 { 95 | coincident_relation = 96 | Some(PieceAreaRelation::CoincidentForward); 97 | } else { 98 | coincident_relation = 99 | Some(PieceAreaRelation::CoincidentBackward); 100 | } 101 | break 'area; 102 | } 103 | } 104 | 105 | if segment.start().y <= midpoint.y { 106 | // start y <= P.y 107 | if segment.end().y > midpoint.y { 108 | // an upward crossing 109 | if signed_distance > 0.0 { 110 | // P left of edge 111 | winding_number += 1; // have a valid up intersect 112 | } 113 | } 114 | } else { 115 | // start y > P.y (no test needed) 116 | if segment.end().y <= midpoint.y { 117 | // a downward crossing 118 | if signed_distance < 0.0 { 119 | winding_number -= 1; // have a valid down intersect 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | let relation = coincident_relation.unwrap_or(if winding_number == 0 { 127 | PieceAreaRelation::Outside 128 | } else { 129 | PieceAreaRelation::Inside 130 | }); 131 | 132 | let (_, piece_area_label) = &mut self.embedding.pieces[piece_idx]; 133 | 134 | match relation { 135 | PieceAreaRelation::Inside => { 136 | piece_area_label.right_labels.insert(label.clone()); 137 | piece_area_label.left_labels.insert(label.clone()); 138 | } 139 | PieceAreaRelation::CoincidentForward => { 140 | piece_area_label.right_labels.insert(label.clone()); 141 | } 142 | PieceAreaRelation::CoincidentBackward => { 143 | piece_area_label.left_labels.insert(label.clone()); 144 | } 145 | _ => {} 146 | } 147 | } 148 | } 149 | } 150 | 151 | self.calculated_containment = true; 152 | } 153 | 154 | pub fn view<'a>(&'a mut self, inital_filter: AreaFilter) -> AreaEmbeddingView<'a, L> 155 | where 156 | L: 'static, 157 | { 158 | self.ensure_containment_calculated(); 159 | AreaEmbeddingView { 160 | area_embedding: self, 161 | area_filter: inital_filter, 162 | } 163 | } 164 | } 165 | 166 | pub enum AreaFilter { 167 | Always, 168 | Function(Box bool>), 169 | Not(Box>), 170 | And(Box>, Box>), 171 | Or(Box>, Box>), 172 | } 173 | 174 | impl AreaFilter { 175 | fn apply(&self, labels: &[L]) -> bool { 176 | match self { 177 | AreaFilter::Always => true, 178 | AreaFilter::Function(ref filter_fn) => filter_fn(labels), 179 | AreaFilter::Not(filter) => !filter.apply(labels), 180 | AreaFilter::And(left, right) => left.apply(labels) && right.apply(labels), 181 | AreaFilter::Or(left, right) => left.apply(labels) || right.apply(labels), 182 | } 183 | } 184 | 185 | pub fn has(label: L) -> Self { 186 | AreaFilter::Function(Box::new(move |label_set| { 187 | label_set.contains(&label) 188 | })) 189 | } 190 | 191 | pub fn any_of(unioned_labels: Vec) -> Self { 192 | AreaFilter::Function(Box::new(move |label_set| { 193 | unioned_labels 194 | .iter() 195 | .any(|unioned_label| label_set.contains(unioned_label)) 196 | })) 197 | } 198 | 199 | pub fn all_of(intersected_labels: Vec) -> Self { 200 | AreaFilter::Function(Box::new(move |label_set| { 201 | intersected_labels 202 | .iter() 203 | .all(|intersected_label| label_set.contains(intersected_label)) 204 | })) 205 | } 206 | 207 | pub fn and(self, other: Self) -> Self { 208 | AreaFilter::And(Box::new(self), Box::new(other)) 209 | } 210 | 211 | pub fn or(self, other: Self) -> Self { 212 | AreaFilter::Or(Box::new(self), Box::new(other)) 213 | } 214 | 215 | pub fn not(self) -> Self { 216 | AreaFilter::Not(Box::new(self)) 217 | } 218 | } 219 | 220 | pub struct AreaEmbeddingView<'a, L: PartialEq + Eq + Clone + Hash + 'a> { 221 | area_embedding: &'a AreaEmbedding, 222 | area_filter: AreaFilter, 223 | } 224 | 225 | impl<'a, L: PartialEq + Eq + Clone + Hash + 'static> AreaEmbeddingView<'a, L> { 226 | pub fn get_unique_pieces(&self) -> Vec<(LinePath, AreaLabel)> { 227 | let mut pieces = self 228 | .area_embedding 229 | .embedding 230 | .pieces 231 | .iter() 232 | .filter_map(|(piece, area_labels)| { 233 | let left_labels = &area_labels.left_labels; 234 | let mut right_labels = area_labels.right_labels.clone(); 235 | right_labels.insert(area_labels.own_right_label.clone()); 236 | 237 | let filter_applies_to_left = self.area_filter.apply(&left_labels); 238 | let filter_applies_to_right = self.area_filter.apply(&right_labels); 239 | 240 | if !filter_applies_to_left && filter_applies_to_right { 241 | // forward boundary 242 | Some((piece.clone(), area_labels.clone())) 243 | } else if filter_applies_to_left && !filter_applies_to_right { 244 | // backward boundary 245 | Some((piece.reverse(), area_labels.clone())) 246 | } else { 247 | // inside or outside 248 | None 249 | } 250 | }) 251 | .collect::>(); 252 | 253 | // only keep unique pieces 254 | let mut midpoints = vec![]; 255 | 256 | pieces.retain(|(piece, _label)| { 257 | let piece_midpoint = piece.along(piece.length() / 2.0); 258 | let is_new = midpoints 259 | .iter() 260 | .all(|old_midpoint: &P2| (piece_midpoint - old_midpoint).norm() > THICKNESS); 261 | if is_new { 262 | midpoints.push(piece_midpoint); 263 | true 264 | } else { 265 | false 266 | } 267 | }); 268 | 269 | pieces 270 | } 271 | 272 | pub fn get_area(&self) -> Result { 273 | let pieces = self 274 | .get_unique_pieces() 275 | .into_iter() 276 | .map(|(piece, _label)| piece) 277 | .collect::>(); 278 | 279 | Area::from_pieces(pieces) 280 | } 281 | 282 | pub fn get_areas_with_pieces( 283 | &self, 284 | ) -> Result)>)>, AreaError> { 285 | let mut wip_boundaries_and_corresponding_pieces: Vec<_> = self 286 | .get_unique_pieces() 287 | .into_iter() 288 | .map(|(piece, label)| (piece.clone(), vec![(piece, label)])) 289 | .collect(); 290 | 291 | let mut complete_paths = Vec::<(ClosedLinePath, Vec<(LinePath, AreaLabel)>)>::new(); 292 | 293 | let mut combining_tolerance = THICKNESS; 294 | const MAX_COMBINING_TOLERANCE: N = 1.0; 295 | 296 | while !wip_boundaries_and_corresponding_pieces.is_empty() 297 | && combining_tolerance < MAX_COMBINING_TOLERANCE 298 | { 299 | join_within_vec( 300 | &mut wip_boundaries_and_corresponding_pieces, 301 | |(path_a, pieces_a), (path_b, pieces_b)| { 302 | if path_b 303 | .start() 304 | .rough_eq_by(path_a.end(), combining_tolerance) 305 | { 306 | Ok(Some(( 307 | path_a 308 | .concat_weld(&path_b, combining_tolerance) 309 | .map_err(AreaError::WeldingShouldWork)?, 310 | pieces_a.into_iter().chain(pieces_b).cloned().collect(), 311 | ))) 312 | } else { 313 | Ok(None) 314 | } 315 | }, 316 | )?; 317 | 318 | wip_boundaries_and_corresponding_pieces.retain(|(path, pieces)| { 319 | if path.length() < combining_tolerance { 320 | false 321 | } else if let Some(closed) = ClosedLinePath::try_clone_from(path) { 322 | complete_paths.push((closed, pieces.clone())); 323 | false 324 | } else if (path.start() - path.end()).norm() <= combining_tolerance { 325 | if let Some(welded_path) = 326 | path.with_new_start_and_end(path.start(), path.start()) 327 | { 328 | complete_paths.push(( 329 | ClosedLinePath::new(welded_path).expect("Welded should be closed"), 330 | pieces.clone(), 331 | )); 332 | false 333 | } else { 334 | true 335 | } 336 | } else { 337 | true 338 | } 339 | }); 340 | 341 | combining_tolerance *= 2.0; 342 | } 343 | 344 | // if !wip_boundaries_and_corresponding_pieces.is_empty() { 345 | // let min_distance = wip_boundaries_and_corresponding_pieces 346 | // .iter() 347 | // .map(|other| { 348 | // OrderedFloat( 349 | // (wip_boundaries_and_corresponding_pieces[0].0.start() - other.0.end()) 350 | // .norm(), 351 | // ) 352 | // }) 353 | // .min() 354 | // .expect("should have a min"); 355 | 356 | // return Err(AreaError::LeftOver(format!( 357 | // "Start to closest end: {}\n{}\n\n{}", 358 | // min_distance, 359 | // "SVG MISSING", //self.debug_svg(), 360 | // format!( 361 | // r#""#, 362 | // wip_boundaries_and_corresponding_pieces[0].0.to_svg() 363 | // ) 364 | // ))); 365 | // } 366 | 367 | let mut disjoint_area_groups = complete_paths 368 | .into_iter() 369 | .map(|(boundary_path, pieces)| (vec![PrimitiveArea::new(boundary_path)], pieces)) 370 | .collect::>(); 371 | 372 | let _: Result<(), ()> = join_within_vec( 373 | &mut disjoint_area_groups, 374 | |(boundaries_a, pieces_a), (boundaries_b, pieces_b)| { 375 | // TODO: maybe use a faster way to check containment here? 376 | if boundaries_a[0].fully_contains(&boundaries_b[0]) { 377 | Ok(Some(( 378 | boundaries_a 379 | .iter() 380 | .chain(boundaries_b.iter()) 381 | .cloned() 382 | .collect(), 383 | pieces_a.iter().chain(pieces_b.iter()).cloned().collect(), 384 | ))) 385 | } else { 386 | Ok(None) 387 | } 388 | }, 389 | ); 390 | 391 | Ok(disjoint_area_groups 392 | .into_iter() 393 | .map(|(primitives_group, pieces)| (Area::new(primitives_group.into()), pieces)) 394 | .collect()) 395 | } 396 | } 397 | 398 | #[test] 399 | fn area_embedding_test() { 400 | use closed_line_path::ClosedLinePath; 401 | use line_path::LinePath; 402 | use rough_eq::RoughEq; 403 | use P2; 404 | 405 | // _____ 406 | // __|__ | 407 | // | |__|__| 408 | // |_____| 409 | // 410 | 411 | let area_a = Area::new_simple( 412 | ClosedLinePath::new( 413 | LinePath::new(vec![ 414 | P2::new(0.0, 0.0), 415 | P2::new(0.0, 1.0), 416 | P2::new(1.0, 1.0), 417 | P2::new(1.0, 0.0), 418 | P2::new(0.0, 0.0), 419 | ]).unwrap(), 420 | ).unwrap(), 421 | ); 422 | let area_b = Area::new_simple( 423 | ClosedLinePath::new( 424 | LinePath::new(vec![ 425 | P2::new(0.5, 0.5), 426 | P2::new(0.5, 1.5), 427 | P2::new(1.5, 1.5), 428 | P2::new(1.5, 0.5), 429 | P2::new(0.5, 0.5), 430 | ]).unwrap(), 431 | ).unwrap(), 432 | ); 433 | 434 | #[derive(Clone, Hash, Eq, PartialEq, Debug)] 435 | enum TestLabel { 436 | A, 437 | B, 438 | C, 439 | } 440 | 441 | let mut embedding = AreaEmbedding::new(0.2); 442 | embedding.insert(area_a, TestLabel::A); 443 | embedding.insert(area_b, TestLabel::B); 444 | 445 | { 446 | let union_area_ab = embedding.view(AreaFilter::any_of(vec![TestLabel::A, TestLabel::B])); 447 | 448 | assert_rough_eq_by!( 449 | &union_area_ab.get_area().expect("Should be valid area"), 450 | &Area::new_simple( 451 | ClosedLinePath::new( 452 | LinePath::new(vec![ 453 | P2::new(0.0, 0.0), 454 | P2::new(0.0, 1.0), 455 | P2::new(0.5, 1.0), 456 | P2::new(0.5, 1.5), 457 | P2::new(1.5, 1.5), 458 | P2::new(1.5, 0.5), 459 | P2::new(1.0, 0.5), 460 | P2::new(1.0, 0.0), 461 | P2::new(0.0, 0.0), 462 | ]).unwrap(), 463 | ).unwrap() 464 | ), 465 | 0.01 466 | ); 467 | } 468 | 469 | // __.__ __. 470 | // | | | | 471 | // |__|__|__| 472 | // 473 | 474 | let area_c = Area::new_simple( 475 | ClosedLinePath::new( 476 | LinePath::new(vec![ 477 | P2::new(0.5, 0.0), 478 | P2::new(0.5, 1.0), 479 | P2::new(1.5, 1.0), 480 | P2::new(1.5, 0.0), 481 | P2::new(0.5, 0.0), 482 | ]).unwrap(), 483 | ).unwrap(), 484 | ); 485 | 486 | embedding.insert(area_c, TestLabel::C); 487 | 488 | { 489 | let union_area_ac = embedding.view(AreaFilter::any_of(vec![TestLabel::A, TestLabel::C])); 490 | 491 | assert_rough_eq_by!( 492 | &union_area_ac.get_area().expect("Should be valid area"), 493 | &Area::new_simple( 494 | ClosedLinePath::new( 495 | LinePath::new(vec![ 496 | P2::new(0.0, 0.0), 497 | P2::new(0.0, 1.0), 498 | P2::new(0.5, 1.0), 499 | P2::new(1.0, 1.0), 500 | P2::new(1.5, 1.0), 501 | P2::new(1.5, 0.5), 502 | P2::new(1.5, 0.0), 503 | P2::new(1.0, 0.0), 504 | P2::new(0.5, 0.0), 505 | P2::new(0.0, 0.0), 506 | ]).unwrap(), 507 | ).unwrap() 508 | ), 509 | 0.01 510 | ); 511 | } 512 | 513 | { 514 | let union_area_abc = embedding.view(AreaFilter::any_of(vec![ 515 | TestLabel::A, 516 | TestLabel::B, 517 | TestLabel::C, 518 | ])); 519 | 520 | assert_rough_eq_by!( 521 | &union_area_abc.get_area().expect("Should be valid area"), 522 | &Area::new_simple( 523 | ClosedLinePath::new( 524 | LinePath::new(vec![ 525 | P2::new(0.0, 0.0), 526 | P2::new(0.0, 1.0), 527 | P2::new(0.5, 1.0), 528 | P2::new(0.5, 1.5), 529 | P2::new(1.5, 1.5), 530 | P2::new(1.5, 1.0), 531 | P2::new(1.5, 0.5), 532 | P2::new(1.5, 0.0), 533 | P2::new(1.0, 0.0), 534 | P2::new(0.5, 0.0), 535 | P2::new(0.0, 0.0), 536 | ]).unwrap(), 537 | ).unwrap() 538 | ), 539 | 0.01 540 | ); 541 | } 542 | 543 | { 544 | let a_int_c_minus_b = embedding.view( 545 | AreaFilter::all_of(vec![TestLabel::A, TestLabel::C]) 546 | .and(AreaFilter::any_of(vec![TestLabel::B]).not()), 547 | ); 548 | 549 | assert_rough_eq_by!( 550 | &a_int_c_minus_b.get_area().expect("Should be valid area"), 551 | &Area::new_simple( 552 | ClosedLinePath::new( 553 | LinePath::new(vec![ 554 | P2::new(0.5, 0.5), 555 | P2::new(1.0, 0.5), 556 | P2::new(1.0, 0.0), 557 | P2::new(0.5, 0.0), 558 | P2::new(0.5, 0.5), 559 | ]).unwrap(), 560 | ).unwrap() 561 | ), 562 | 0.01 563 | ); 564 | } 565 | 566 | // _____ 567 | // __|__ __| 568 | // | |XX|XX| 569 | // |__|XX|__| 570 | // 571 | 572 | { 573 | let union_of_intersections = 574 | embedding.view(AreaFilter::Function(Box::new(|labels| labels.len() >= 2))); 575 | 576 | assert_rough_eq_by!( 577 | &union_of_intersections 578 | .get_area() 579 | .expect("Should be valid area"), 580 | &Area::new_simple( 581 | ClosedLinePath::new( 582 | LinePath::new(vec![ 583 | P2::new(0.5, 1.0), 584 | P2::new(1.0, 1.0), 585 | P2::new(1.5, 1.0), 586 | P2::new(1.5, 0.5), 587 | P2::new(1.0, 0.5), 588 | P2::new(1.0, 0.0), 589 | P2::new(0.5, 0.0), 590 | P2::new(0.5, 0.5), 591 | P2::new(0.5, 1.0), 592 | ]).unwrap(), 593 | ).unwrap() 594 | ), 595 | 0.01 596 | ); 597 | 598 | let (area, pieces) = union_of_intersections 599 | .get_areas_with_pieces() 600 | .expect("Should be valid area") 601 | .into_iter() 602 | .next() 603 | .unwrap(); 604 | 605 | assert_rough_eq_by!( 606 | &area, 607 | &Area::new_simple( 608 | ClosedLinePath::new( 609 | LinePath::new(vec![ 610 | P2::new(0.5, 1.0), 611 | P2::new(1.0, 1.0), 612 | P2::new(1.5, 1.0), 613 | P2::new(1.5, 0.5), 614 | P2::new(1.0, 0.5), 615 | P2::new(1.0, 0.0), 616 | P2::new(0.5, 0.0), 617 | P2::new(0.5, 0.5), 618 | P2::new(0.5, 1.0), 619 | ]).unwrap(), 620 | ).unwrap() 621 | ), 622 | 0.01 623 | ); 624 | 625 | assert_rough_eq_by!( 626 | &pieces[0].0, 627 | &LinePath::new(vec![P2::new(0.5, 1.0), P2::new(1.0, 1.0)]).unwrap(), 628 | 0.01 629 | ); 630 | 631 | assert_eq!(&pieces[0].1.left_labels[..], &[TestLabel::B]); 632 | assert_eq!(&pieces[0].1.right_labels[..], &[TestLabel::B, TestLabel::C]); 633 | assert_eq!(pieces[0].1.own_right_label, TestLabel::A); 634 | 635 | assert_eq!(&pieces[1].1.left_labels[..], &[TestLabel::B]); 636 | assert_eq!(&pieces[1].1.right_labels[..], &[TestLabel::B]); 637 | assert_eq!(pieces[1].1.own_right_label, TestLabel::C); 638 | 639 | assert_eq!(&pieces[2].1.left_labels[..], &[]); 640 | assert_eq!(&pieces[2].1.right_labels[..], &[TestLabel::C]); 641 | assert_eq!(pieces[2].1.own_right_label, TestLabel::B); 642 | 643 | assert_eq!(&pieces[3].1.left_labels[..], &[TestLabel::C]); 644 | assert_eq!(&pieces[3].1.right_labels[..], &[TestLabel::C]); 645 | assert_eq!(pieces[3].1.own_right_label, TestLabel::B); 646 | 647 | assert_eq!(&pieces[4].1.left_labels[..], &[TestLabel::C]); 648 | assert_eq!(&pieces[4].1.right_labels[..], &[TestLabel::C]); 649 | assert_eq!(pieces[4].1.own_right_label, TestLabel::A); 650 | 651 | assert_eq!(&pieces[5].1.left_labels[..], &[]); 652 | assert_eq!(&pieces[5].1.right_labels[..], &[TestLabel::C]); 653 | assert_eq!(pieces[5].1.own_right_label, TestLabel::A); 654 | 655 | assert_eq!(&pieces[6].1.left_labels[..], &[TestLabel::A]); 656 | assert_eq!(&pieces[6].1.right_labels[..], &[TestLabel::A]); 657 | assert_eq!(pieces[6].1.own_right_label, TestLabel::C); 658 | 659 | assert_eq!(&pieces[7].1.left_labels[..], &[TestLabel::A]); 660 | assert_eq!(&pieces[7].1.right_labels[..], &[TestLabel::A, TestLabel::C]); 661 | assert_eq!(pieces[7].1.own_right_label, TestLabel::B); 662 | } 663 | } 664 | 665 | 666 | #[test] 667 | fn area_embedding_test_x_coords_smaller_y_coords() { 668 | use closed_line_path::ClosedLinePath; 669 | use line_path::LinePath; 670 | use rough_eq::RoughEq; 671 | use P2; 672 | 673 | // _____ 674 | // __|__ | 675 | // | |__|__| 676 | // |_____| 677 | // 678 | 679 | let area_a = Area::new_simple( 680 | ClosedLinePath::new( 681 | LinePath::new(vec![ 682 | P2::new(0.0 - 30.0, 0.0 - 10.0), 683 | P2::new(0.0 - 30.0, 1.0 - 10.0), 684 | P2::new(1.0 - 30.0, 1.0 - 10.0), 685 | P2::new(1.0 - 30.0, 0.0 - 10.0), 686 | P2::new(0.0 - 30.0, 0.0 - 10.0), 687 | ]).unwrap(), 688 | ).unwrap(), 689 | ); 690 | let area_b = Area::new_simple( 691 | ClosedLinePath::new( 692 | LinePath::new(vec![ 693 | P2::new(0.5 - 30.0, 0.5 - 10.0), 694 | P2::new(0.5 - 30.0, 1.5 - 10.0), 695 | P2::new(1.5 - 30.0, 1.5 - 10.0), 696 | P2::new(1.5 - 30.0, 0.5 - 10.0), 697 | P2::new(0.5 - 30.0, 0.5 - 10.0), 698 | ]).unwrap(), 699 | ).unwrap(), 700 | ); 701 | 702 | #[derive(Clone, Hash, Eq, PartialEq, Debug)] 703 | enum TestLabel { 704 | A, 705 | B, 706 | } 707 | 708 | let mut embedding = AreaEmbedding::new(0.333); 709 | embedding.insert(area_a, TestLabel::A); 710 | embedding.insert(area_b, TestLabel::B); 711 | 712 | { 713 | let union_of_intersections = 714 | embedding.view(AreaFilter::Function(Box::new(|labels| labels.len() >= 2))); 715 | 716 | assert_rough_eq_by!( 717 | &union_of_intersections.get_area().expect("Should be valid area"), 718 | &Area::new_simple( 719 | ClosedLinePath::new( 720 | LinePath::new(vec![ 721 | P2::new(0.5 - 30.0, 0.5 - 10.0), 722 | P2::new(0.5 - 30.0, 1.0 - 10.0), 723 | P2::new(1.0 - 30.0, 1.0 - 10.0), 724 | P2::new(1.0 - 30.0, 0.5 - 10.0), 725 | P2::new(0.5 - 30.0, 0.5 - 10.0), 726 | ]).unwrap(), 727 | ).unwrap() 728 | ), 729 | 0.01 730 | ); 731 | } 732 | } --------------------------------------------------------------------------------