├── .gitignore ├── rustfmt.toml ├── .github ├── codecov.yml └── workflows │ └── test.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── util.rs ├── lib.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | max_width = 80 3 | tab_spaces = 2 4 | use_small_heuristics = "Max" 5 | wrap_comments = true 6 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 90..100 3 | round: down 4 | precision: 1 5 | status: 6 | patch: 7 | default: 8 | target: 90% 9 | project: 10 | default: 11 | threshold: 1% 12 | 13 | comment: 14 | layout: "files" 15 | require_changes: true 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polygon_clipping" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | description = "An algorithm for computing boolean operations on polygons." 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | license = "MIT" 10 | readme = "README.md" 11 | repository = "https://github.com/andriyDev/polygon_clipping_rs" 12 | 13 | categories = ["mathematics"] 14 | keywords = ["polygon", "boolean", "geometry", "union", "intersection"] 15 | 16 | [dependencies] 17 | glam = "0.24.1" 18 | 19 | [dev-dependencies] 20 | rand = "0.8.5" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Andriy Dzikh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 13 | 14 | jobs: 15 | build_and_test: 16 | name: ubuntu / ${{ matrix.toolchain }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | toolchain: [stable, beta, nightly] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: dtolnay/rust-toolchain@stable 24 | with: 25 | toolchain: ${{ matrix.toolchain }} 26 | - run: cargo build 27 | - name: cargo test 28 | run: cargo test --all-features --all-targets 29 | - name: cargo test --doc 30 | run: cargo test --all-features --doc 31 | coverage: 32 | name: ubuntu / stable / coverage 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v3 36 | with: 37 | submodules: true 38 | - uses: dtolnay/rust-toolchain@stable 39 | with: 40 | components: llvm-tools-preview 41 | - uses: taiki-e/install-action@cargo-llvm-cov 42 | - name: cargo llvm-cov 43 | run: cargo llvm-cov --all-features --lcov --output-path lcov.info 44 | - name: Upload coverage to Codecov 45 | uses: codecov/codecov-action@v3 46 | with: 47 | fail_ci_if_error: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # polygon_clipping_rs 2 | 3 | A Rust crate to compute boolean operations (i.e., intersection, union, 4 | difference, and XOR) of two polygons. 5 | 6 | ## Example 7 | 8 | ```rust 9 | use glam::Vec2; 10 | use polygon_clipping::{BooleanResult, Polygon, SourceEdge, union}; 11 | 12 | // Subject polygon has two disjoint sections (contours). 13 | let subject = Polygon { 14 | contours: vec![ 15 | vec![ 16 | Vec2::new(1.0, 1.0), 17 | Vec2::new(2.0, 1.0), 18 | Vec2::new(2.0, 2.0), 19 | Vec2::new(1.0, 2.0), 20 | ], 21 | vec![ 22 | Vec2::new(3.0, 1.0), 23 | Vec2::new(4.0, 1.0), 24 | Vec2::new(4.0, 2.0), 25 | Vec2::new(3.0, 2.0), 26 | ], 27 | ], 28 | }; 29 | let clip = Polygon { 30 | contours: vec![vec![ 31 | Vec2::new(2.0, 1.0), 32 | Vec2::new(3.0, 1.0), 33 | Vec2::new(3.0, 2.0), 34 | Vec2::new(2.0, 2.0), 35 | ]], 36 | }; 37 | 38 | let BooleanResult { polygon, contour_source_edges } = union(&subject, &clip); 39 | assert_eq!(polygon, Polygon { 40 | contours: vec![vec![ 41 | Vec2::new(1.0, 1.0), 42 | Vec2::new(2.0, 1.0), 43 | Vec2::new(3.0, 1.0), 44 | Vec2::new(4.0, 1.0), 45 | Vec2::new(4.0, 2.0), 46 | Vec2::new(3.0, 2.0), 47 | Vec2::new(2.0, 2.0), 48 | Vec2::new(1.0, 2.0), 49 | ]], 50 | }); 51 | assert_eq!(contour_source_edges, vec![vec![ 52 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 53 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 54 | SourceEdge { is_from_subject: true, contour: 1, edge: 0 }, 55 | SourceEdge { is_from_subject: true, contour: 1, edge: 1 }, 56 | SourceEdge { is_from_subject: true, contour: 1, edge: 2 }, 57 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 58 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 59 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 60 | ]]); 61 | ``` 62 | 63 | ## Polygon representation 64 | 65 | Polygons are represented as a set of "contours". Each contour is a loop of 66 | vertices. Points contained by an even number of contours are considered outside 67 | the polygon, and points contained by an odd number of contours are considered 68 | inside the polygon. Note that "polygon" is not quite correct since this includes 69 | "multipolygons" - essentially two completely disjoint shapes. 70 | 71 | ### Invalid/malformed polygons 72 | 73 | This implementation does not account for "malformed" polygons. The behavior in 74 | these cases is undefined. Some malformed polygons include: 75 | 76 | * Polygons containing `NaN` or `Infinity` coordinates. It is pretty obvious why 77 | this would be a problem. 78 | * Polygons containing overlapping edges. If a single polygon contains 79 | overlapping edges, it is unclear what the edge implies. In other words, any 80 | polygon with overlapping edges can be "reorganized" such that the overlapping 81 | edges are not present in the new polygon - the overlapping edge was never 82 | needed! 83 | 84 | ## Algorithm 85 | 86 | This is an implementation of the paper: 87 | ```text 88 | Francisco Martínez, Carlos Ogayar, Juan R. Jiménez, Antonio J. Rueda, 89 | A simple algorithm for Boolean operations on polygons, 90 | Advances in Engineering Software, 91 | Volume 64, 92 | 2013, 93 | Pages 11-19, 94 | ISSN 0965-9978, 95 | https://doi.org/10.1016/j.advengsoft.2013.04.004. 96 | (https://www.sciencedirect.com/science/article/pii/S0965997813000379) 97 | ``` 98 | 99 | ### Differences to the original algorithm 100 | 101 | These are intentional changes to the original algorithm. 102 | 103 | * The paper reports using pointers for everything. This makes cleanup messy and 104 | Rust really doesn't like all the cyclic references for obvious reasons. This 105 | implementation uses separate data structures to split the data into chunks we 106 | can mutate independently. This can mean there is more (maybe less though) 107 | memory usage than the paper's implementation. However, this implementation uses 108 | fewer small allocations - allocations are batched together. 109 | * In addition to the result polygon, we also return the source edges for each 110 | edge in the result polygon. This is useful when there is some "metadata" about 111 | edges in the source polygons that you would like to retain in the result 112 | polygon. An example is if each polygon is a room with edges being walls, but 113 | some edges are doors. It may be useful to know which edges in the result polygon 114 | are still doors. 115 | 116 | ### Deficiencies to the original algorithm 117 | 118 | These are problems in the implementation that could be addressed in the future. 119 | 120 | * The paper describes using a binary search tree for the "sweep line" 121 | data structure. The current implementation uses a sorted `Vec`, so some 122 | operations may have different performance characteristics. The paper also 123 | mentions that events could store their position in the sweep line to avoid a 124 | search. 125 | * This implementation does not properly handle more than two edges of a contour 126 | meeting at a single vertex. The paper briefly mentions a solution (although not 127 | as clear as I would like). 128 | 129 | ### A personal note 130 | 131 | This paper is quite clever, and the general idea is fairly intuitive. 132 | **However**, the paper seems to hide critical information in single sentences 133 | that seem benign (e.g., events must be sorted by non-vertical edges first), and 134 | more importantly leaves a lot of figuring out the details of the algorithm to 135 | the reader. This made it confusing when my version required significant 136 | differences to the pseudo-code in the paper. In addition, some flags are 137 | described but not how to compute them, and the "special cases" are treated as 138 | footnotes rather than parts of the algorithm that take lots of time and work to 139 | restructure and figure out. 140 | 141 | Alright, I'm done complaining. 142 | 143 | ## License 144 | 145 | Licensed under the [MIT license](LICENSE). 146 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use glam::Vec2; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq)] 4 | pub enum EdgeIntersectionResult { 5 | NoIntersection, 6 | PointIntersection(Vec2), 7 | LineIntersection(Vec2, Vec2), 8 | } 9 | 10 | // Find the intersection of two line segments. Line segments cannot intersect at 11 | // end points (although if one line's end point is present on the interior of 12 | // the other line, that will be an intersection). The same line segment is also 13 | // considered a line intersection. 14 | pub fn edge_intersection( 15 | line_1: (Vec2, Vec2), 16 | line_2: (Vec2, Vec2), 17 | ) -> EdgeIntersectionResult { 18 | // An implementation of Schneider and Eberly line intersection. 19 | 20 | let line_1_vector = line_1.1 - line_1.0; 21 | let line_2_vector = line_2.1 - line_2.0; 22 | 23 | let relative_start = line_2.0 - line_1.0; 24 | let cross = line_1_vector.perp_dot(line_2_vector); 25 | let cross_squared = cross * cross; 26 | 27 | if cross_squared > 0.0 { 28 | // Line segments are not parallel, so either they intersect at a point or 29 | // not at all. 30 | let s = relative_start.perp_dot(line_2_vector) / cross; 31 | if s < 0.0 || 1.0 < s { 32 | return EdgeIntersectionResult::NoIntersection; 33 | } 34 | 35 | let t = relative_start.perp_dot(line_1_vector) / cross; 36 | if t < 0.0 || 1.0 < t { 37 | return EdgeIntersectionResult::NoIntersection; 38 | } 39 | 40 | if (s == 0.0 || s == 1.0) && (t == 0.0 || t == 1.0) { 41 | return EdgeIntersectionResult::NoIntersection; 42 | } 43 | 44 | return EdgeIntersectionResult::PointIntersection( 45 | line_1.0 + s * line_1_vector, 46 | ); 47 | } 48 | // Line segments are parallel, so either they are on the same line and 49 | // overlapping, or there is no intersection. 50 | let cross = relative_start.perp_dot(line_1_vector); 51 | 52 | if cross.abs() > 0.0 { 53 | // Lines are not on the same line, so no overlap. 54 | return EdgeIntersectionResult::NoIntersection; 55 | } 56 | 57 | let line_1_len_squared = line_1_vector.length_squared(); 58 | let sa = relative_start.dot(line_1_vector) / line_1_len_squared; 59 | let sb = sa + line_1_vector.dot(line_2_vector) / line_1_len_squared; 60 | let smin = sa.min(sb); 61 | let smax = sa.max(sb); 62 | 63 | if smax <= 0.0 || 1.0 <= smin { 64 | return EdgeIntersectionResult::NoIntersection; 65 | } 66 | 67 | EdgeIntersectionResult::LineIntersection( 68 | line_1.0 + smin.max(0.0) * line_1_vector, 69 | line_1.0 + smax.min(1.0) * line_1_vector, 70 | ) 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use glam::Vec2; 76 | 77 | use crate::util::{edge_intersection, EdgeIntersectionResult}; 78 | 79 | #[test] 80 | fn unaligned_edges_intersect() { 81 | let line_1 = (Vec2::new(1.0, 1.0), Vec2::new(5.0, 5.0)); 82 | let line_2 = (Vec2::new(4.0, 3.0), Vec2::new(4.0, 7.0)); 83 | assert_eq!( 84 | edge_intersection(line_1, line_2), 85 | EdgeIntersectionResult::PointIntersection(Vec2::new(4.0, 4.0)) 86 | ); 87 | assert_eq!( 88 | edge_intersection(line_2, line_1), 89 | EdgeIntersectionResult::PointIntersection(Vec2::new(4.0, 4.0)) 90 | ); 91 | } 92 | 93 | #[test] 94 | fn unaligned_edges_dont_intersect() { 95 | let line_intersects_after_segment_1 = 96 | (Vec2::new(1.0, 1.0), Vec2::new(5.0, 5.0)); 97 | let line_intersects_after_segment_2 = 98 | (Vec2::new(6.0, 3.0), Vec2::new(6.0, 7.0)); 99 | assert_eq!( 100 | edge_intersection( 101 | line_intersects_after_segment_1, 102 | line_intersects_after_segment_2, 103 | ), 104 | EdgeIntersectionResult::NoIntersection 105 | ); 106 | assert_eq!( 107 | edge_intersection( 108 | line_intersects_after_segment_2, 109 | line_intersects_after_segment_1, 110 | ), 111 | EdgeIntersectionResult::NoIntersection 112 | ); 113 | 114 | let line_intersects_before_segment_1 = 115 | (Vec2::new(1.0, 1.0), Vec2::new(5.0, 5.0)); 116 | let line_intersects_before_segment_2 = 117 | (Vec2::new(1.0, 0.0), Vec2::new(5.0, 0.0)); 118 | assert_eq!( 119 | edge_intersection( 120 | line_intersects_before_segment_1, 121 | line_intersects_before_segment_2, 122 | ), 123 | EdgeIntersectionResult::NoIntersection 124 | ); 125 | assert_eq!( 126 | edge_intersection( 127 | line_intersects_before_segment_2, 128 | line_intersects_before_segment_1, 129 | ), 130 | EdgeIntersectionResult::NoIntersection 131 | ); 132 | 133 | let t_intersection_1 = (Vec2::ONE, Vec2::ONE * 5.0); 134 | let t_intersection_2 = (Vec2::ONE * 3.0, Vec2::new(3.0, 0.0)); 135 | 136 | assert_eq!( 137 | edge_intersection(t_intersection_1, t_intersection_2), 138 | EdgeIntersectionResult::PointIntersection(t_intersection_2.0) 139 | ); 140 | assert_eq!( 141 | edge_intersection(t_intersection_2, t_intersection_1), 142 | EdgeIntersectionResult::PointIntersection(t_intersection_2.0) 143 | ); 144 | assert_eq!( 145 | edge_intersection( 146 | t_intersection_1, 147 | (t_intersection_2.1, t_intersection_2.0), 148 | ), 149 | EdgeIntersectionResult::PointIntersection(t_intersection_2.0) 150 | ); 151 | assert_eq!( 152 | edge_intersection( 153 | (t_intersection_2.1, t_intersection_2.0), 154 | t_intersection_1, 155 | ), 156 | EdgeIntersectionResult::PointIntersection(t_intersection_2.0) 157 | ); 158 | } 159 | 160 | #[test] 161 | fn edges_intersect_at_point() { 162 | let line_1 = (Vec2::new(-1.0, 2.0), Vec2::new(1.0, 1.0)); 163 | let line_2 = (Vec2::new(1.0, 1.0), Vec2::new(3.0, 1.0)); 164 | let line_3 = (Vec2::new(3.0, 1.0), Vec2::new(7.0, 9.0)); 165 | let t_line = (Vec2::new(2.0, 1.0), Vec2::new(2.0, 3.0)); 166 | 167 | assert_eq!( 168 | edge_intersection(line_1, line_2), 169 | EdgeIntersectionResult::NoIntersection 170 | ); 171 | assert_eq!( 172 | edge_intersection(line_2, line_1), 173 | EdgeIntersectionResult::NoIntersection 174 | ); 175 | assert_eq!( 176 | edge_intersection(line_2, line_3), 177 | EdgeIntersectionResult::NoIntersection 178 | ); 179 | assert_eq!( 180 | edge_intersection(line_3, line_2), 181 | EdgeIntersectionResult::NoIntersection 182 | ); 183 | assert_eq!( 184 | edge_intersection(line_2, t_line), 185 | EdgeIntersectionResult::PointIntersection(t_line.0), 186 | ); 187 | assert_eq!( 188 | edge_intersection(t_line, line_2), 189 | EdgeIntersectionResult::PointIntersection(t_line.0), 190 | ); 191 | assert_eq!( 192 | edge_intersection(line_2, (t_line.1, t_line.0)), 193 | EdgeIntersectionResult::PointIntersection(t_line.0), 194 | ); 195 | assert_eq!( 196 | edge_intersection((t_line.1, t_line.0), line_2), 197 | EdgeIntersectionResult::PointIntersection(t_line.0), 198 | ); 199 | } 200 | 201 | #[test] 202 | fn aligned_edges_intersect() { 203 | let start_line = (Vec2::ONE, Vec2::ONE * 4.0); 204 | let overlapping_line = (Vec2::ONE * 2.0, Vec2::ONE * 7.0); 205 | let covering_line = (Vec2::ONE * -1.0, Vec2::ONE * 7.0); 206 | let covered_line = (Vec2::ONE * 2.0, Vec2::ONE * 3.0); 207 | let offset_line_1 = (start_line.0 + Vec2::Y, start_line.1 + Vec2::Y); 208 | let offset_line_2 = (Vec2::ONE * 5.0, Vec2::ONE * 7.0); 209 | 210 | assert_eq!( 211 | edge_intersection(start_line, overlapping_line,), 212 | EdgeIntersectionResult::LineIntersection( 213 | Vec2::ONE * 2.0, 214 | Vec2::ONE * 4.0 215 | ) 216 | ); 217 | assert_eq!( 218 | edge_intersection(overlapping_line, start_line,), 219 | EdgeIntersectionResult::LineIntersection( 220 | Vec2::ONE * 2.0, 221 | Vec2::ONE * 4.0 222 | ) 223 | ); 224 | 225 | assert_eq!( 226 | edge_intersection(start_line, covering_line), 227 | EdgeIntersectionResult::LineIntersection(start_line.0, start_line.1) 228 | ); 229 | assert_eq!( 230 | edge_intersection(covering_line, start_line), 231 | EdgeIntersectionResult::LineIntersection(start_line.0, start_line.1) 232 | ); 233 | 234 | assert_eq!( 235 | edge_intersection(start_line, covered_line), 236 | EdgeIntersectionResult::LineIntersection(covered_line.0, covered_line.1) 237 | ); 238 | assert_eq!( 239 | edge_intersection(covered_line, start_line), 240 | EdgeIntersectionResult::LineIntersection(covered_line.0, covered_line.1) 241 | ); 242 | 243 | assert_eq!( 244 | edge_intersection(start_line, offset_line_1), 245 | EdgeIntersectionResult::NoIntersection 246 | ); 247 | assert_eq!( 248 | edge_intersection(offset_line_1, start_line), 249 | EdgeIntersectionResult::NoIntersection 250 | ); 251 | 252 | assert_eq!( 253 | edge_intersection(start_line, offset_line_2), 254 | EdgeIntersectionResult::NoIntersection 255 | ); 256 | assert_eq!( 257 | edge_intersection(offset_line_2, start_line), 258 | EdgeIntersectionResult::NoIntersection 259 | ); 260 | } 261 | 262 | #[test] 263 | fn aligned_edges_dont_intersect_at_point() { 264 | let line_1 = (Vec2::ONE, Vec2::ONE * 3.0); 265 | let line_2 = (Vec2::ONE * 3.0, Vec2::ONE * 7.0); 266 | let line_3 = (Vec2::ONE * 7.0, Vec2::ONE * 10.0); 267 | 268 | assert_eq!( 269 | edge_intersection(line_1, line_2), 270 | EdgeIntersectionResult::NoIntersection 271 | ); 272 | assert_eq!( 273 | edge_intersection(line_2, line_1), 274 | EdgeIntersectionResult::NoIntersection 275 | ); 276 | assert_eq!( 277 | edge_intersection(line_2, line_3), 278 | EdgeIntersectionResult::NoIntersection 279 | ); 280 | assert_eq!( 281 | edge_intersection(line_3, line_2), 282 | EdgeIntersectionResult::NoIntersection 283 | ); 284 | } 285 | 286 | #[test] 287 | fn edge_intersecting_self() { 288 | let line = (Vec2::ONE, Vec2::ONE * 5.0); 289 | 290 | // There should be a line intersection if the same line is passed in (even 291 | // if end points are not intersections). 292 | assert_eq!( 293 | edge_intersection(line, line), 294 | EdgeIntersectionResult::LineIntersection(line.0, line.1) 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | use std::{ 3 | cmp::Reverse, 4 | collections::{BinaryHeap, HashMap}, 5 | f32::{EPSILON, INFINITY}, 6 | }; 7 | 8 | use glam::Vec2; 9 | use util::{edge_intersection, EdgeIntersectionResult}; 10 | 11 | mod util; 12 | 13 | #[derive(Clone, PartialEq, Debug)] 14 | pub struct Polygon { 15 | pub contours: Vec>, 16 | } 17 | 18 | impl Polygon { 19 | // Computes the bounding box (min, max) of the polygon. Returns None if there 20 | // are no vertices. 21 | pub fn compute_bounds(&self) -> Option<(Vec2, Vec2)> { 22 | self.contours.iter().flatten().fold(None, |bounds, &point| { 23 | Some(match bounds { 24 | None => (point, point), 25 | Some((min, max)) => (min.min(point), max.max(point)), 26 | }) 27 | }) 28 | } 29 | } 30 | 31 | // The source of an edge. 32 | #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] 33 | pub struct SourceEdge { 34 | // Whether the edge is from the subject polygon (otherwise, the clip 35 | // polygon). 36 | pub is_from_subject: bool, 37 | // The index of the contour in the source polygon. 38 | pub contour: usize, 39 | // The edge in the contour of the source polygon. 40 | pub edge: usize, 41 | } 42 | 43 | // The result of performing a boolean operation. 44 | #[derive(Clone, PartialEq, Debug)] 45 | pub struct BooleanResult { 46 | // The resulting polygon. 47 | pub polygon: Polygon, 48 | // The source of each edge in `polygon`. `source_edge` will have one entry 49 | // per contour in `polygon` and each entry will have the same number of edges 50 | // as that contour in `polygon`. 51 | pub contour_source_edges: Vec>, 52 | } 53 | 54 | pub fn intersection(subject: &Polygon, clip: &Polygon) -> BooleanResult { 55 | perform_boolean(subject, clip, Operation::Intersection) 56 | } 57 | 58 | pub fn union(subject: &Polygon, clip: &Polygon) -> BooleanResult { 59 | perform_boolean(subject, clip, Operation::Union) 60 | } 61 | 62 | pub fn difference(subject: &Polygon, clip: &Polygon) -> BooleanResult { 63 | perform_boolean(subject, clip, Operation::Difference) 64 | } 65 | 66 | pub fn xor(subject: &Polygon, clip: &Polygon) -> BooleanResult { 67 | perform_boolean(subject, clip, Operation::XOR) 68 | } 69 | 70 | #[derive(Clone, Copy, PartialEq, Eq)] 71 | enum Operation { 72 | Intersection, 73 | Union, 74 | XOR, 75 | Difference, 76 | } 77 | 78 | fn perform_boolean( 79 | subject: &Polygon, 80 | clip: &Polygon, 81 | operation: Operation, 82 | ) -> BooleanResult { 83 | // Turns `polygon` into the corresponding `BooleanResult`. 84 | fn polygon_to_boolean_result( 85 | polygon: &Polygon, 86 | is_subject: bool, 87 | ) -> BooleanResult { 88 | BooleanResult { 89 | polygon: polygon.clone(), 90 | contour_source_edges: polygon 91 | .contours 92 | .iter() 93 | .enumerate() 94 | .map(|(contour_index, contour)| { 95 | (0..contour.len()) 96 | .map(|index| SourceEdge { 97 | is_from_subject: is_subject, 98 | contour: contour_index, 99 | edge: index, 100 | }) 101 | .collect() 102 | }) 103 | .collect(), 104 | } 105 | } 106 | 107 | // This is just an optimization. If the bounding boxes of each polygon do not 108 | // intersect, we can trivially compute the boolean operation. This does mean 109 | // we won't "normalize" the polygons (e.g., removing empty contours), but that 110 | // is a totally fine tradeoff for the speed. 111 | let subject_bounds = subject.compute_bounds(); 112 | let clip_bounds = clip.compute_bounds(); 113 | match (subject_bounds, clip_bounds) { 114 | (None, None) => { 115 | return BooleanResult { 116 | polygon: Polygon { contours: vec![] }, 117 | contour_source_edges: vec![], 118 | } 119 | } 120 | (Some(_), None) => { 121 | return if operation == Operation::Intersection { 122 | BooleanResult { 123 | polygon: Polygon { contours: vec![] }, 124 | contour_source_edges: vec![], 125 | } 126 | } else { 127 | polygon_to_boolean_result(subject, /* is_subject= */ true) 128 | }; 129 | } 130 | (None, Some(_)) => { 131 | return if operation == Operation::Intersection 132 | || operation == Operation::Difference 133 | { 134 | BooleanResult { 135 | polygon: Polygon { contours: vec![] }, 136 | contour_source_edges: vec![], 137 | } 138 | } else { 139 | polygon_to_boolean_result(clip, /* is_subject= */ false) 140 | }; 141 | } 142 | (Some((subject_min, subject_max)), Some((clip_min, clip_max))) => { 143 | if subject_max.x < clip_min.x 144 | || subject_max.y < clip_min.y 145 | || clip_max.x < subject_min.x 146 | || clip_max.y < subject_min.y 147 | { 148 | return match operation { 149 | Operation::Intersection => BooleanResult { 150 | polygon: Polygon { contours: vec![] }, 151 | contour_source_edges: vec![], 152 | }, 153 | Operation::Difference => { 154 | polygon_to_boolean_result(subject, /* is_subject= */ true) 155 | } 156 | Operation::Union | Operation::XOR => { 157 | let mut subject_result = 158 | polygon_to_boolean_result(subject, /* is_subject= */ true); 159 | let mut clip_result = 160 | polygon_to_boolean_result(clip, /* is_subject= */ false); 161 | subject_result 162 | .polygon 163 | .contours 164 | .append(&mut clip_result.polygon.contours); 165 | subject_result 166 | .contour_source_edges 167 | .append(&mut clip_result.contour_source_edges); 168 | subject_result 169 | } 170 | }; 171 | } 172 | } 173 | } 174 | 175 | // We know the bounds are not None, since those cases are trivially computed. 176 | let subject_bounds = subject_bounds.unwrap(); 177 | let clip_bounds = clip_bounds.unwrap(); 178 | 179 | let mut event_queue = BinaryHeap::new(); 180 | let mut event_relations = Vec::new(); 181 | 182 | let x_limit = match operation { 183 | Operation::Intersection => subject_bounds.1.x.min(clip_bounds.1.x), 184 | Operation::Difference => subject_bounds.1.x, 185 | Operation::Union | Operation::XOR => INFINITY, 186 | }; 187 | 188 | create_events_for_polygon( 189 | subject, 190 | /* is_subject= */ true, 191 | &mut event_queue, 192 | &mut event_relations, 193 | x_limit, 194 | ); 195 | create_events_for_polygon( 196 | clip, 197 | /* is_subject= */ false, 198 | &mut event_queue, 199 | &mut event_relations, 200 | x_limit, 201 | ); 202 | 203 | let result_events = 204 | subdivide_edges(event_queue, &mut event_relations, operation, x_limit); 205 | join_contours(result_events, event_relations, operation) 206 | } 207 | 208 | // An "event" of an edge. Each edge of a polygon is comprised of a "left" event 209 | // and a "right" event. 210 | #[derive(Clone, Debug)] 211 | struct Event { 212 | // The id of the event. 213 | event_id: usize, 214 | // The point where the event occurs. 215 | point: Vec2, 216 | // True iff this is the "left" event of the edge. Left generally refers to 217 | // the point with the lower x coordinate, although for vertical edges, the 218 | // left is the point with the lower y coordinate. 219 | left: bool, 220 | // Did this event come from the subject or the clip? 221 | is_subject: bool, 222 | // The other point of this edge. This point will never change after creation. 223 | // It is just provided to determine the line that the edge sits on (which 224 | // also can never change). 225 | other_point: Vec2, 226 | } 227 | 228 | impl PartialEq for Event { 229 | fn eq(&self, other: &Self) -> bool { 230 | self.event_id == other.event_id 231 | } 232 | } 233 | 234 | impl PartialOrd for Event { 235 | fn partial_cmp(&self, other: &Self) -> Option { 236 | // This is primarily used for a min heap, so here we will say "prefer" to 237 | // mean less. 238 | 239 | // The first thing that matters is the order of points. 240 | match lex_order_points(&self.point, &other.point) { 241 | std::cmp::Ordering::Equal => {} 242 | ord => return Some(ord), 243 | } 244 | // Prefer right events to left events. This way the sweep line will contain 245 | // fewer edges (and be more accurate) as right events remove edges from the 246 | // sweep line. 247 | match self.left.cmp(&other.left) { 248 | std::cmp::Ordering::Equal => {} 249 | ord => return Some(ord), 250 | } 251 | // Prefer horizontal edges to vertical edges. Edges use the previous edge in 252 | // the sweep line to determine whether they are in the result. If we don't 253 | // prefer horizontal edges, it is possible for a T intersection to handle 254 | // the left edge, then the vertical edge, then the right edge (meaning the 255 | // vertical edge will have nothing in the sweep line to compare against). 256 | match self.is_vertical().cmp(&other.is_vertical()) { 257 | std::cmp::Ordering::Equal => {} 258 | ord => return Some(ord), 259 | } 260 | // We know the events share the same point. Prefer the line which slopes 261 | // above the other one. 262 | match point_relative_to_line( 263 | self.point, 264 | self.other_point, 265 | other.other_point, 266 | ) { 267 | std::cmp::Ordering::Equal => {} 268 | // If this is a right point, then the point and other_point are in the 269 | // wrong order, so reverse the ordering. 270 | ord => return if self.left { Some(ord) } else { Some(ord.reverse()) }, 271 | } 272 | // Prefer subject edges over clip edges. 273 | match self.is_subject.cmp(&other.is_subject) { 274 | std::cmp::Ordering::Equal => {} 275 | ord => return Some(ord.reverse()), 276 | } 277 | 278 | Some(self.event_id.cmp(&other.event_id)) 279 | } 280 | } 281 | 282 | impl Eq for Event {} 283 | 284 | impl Ord for Event { 285 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 286 | self.partial_cmp(other).unwrap() 287 | } 288 | } 289 | 290 | // Determine the lexical order of `a` and `b`. In other words, sort by x, then 291 | // y. 292 | fn lex_order_points(a: &Vec2, b: &Vec2) -> std::cmp::Ordering { 293 | match a.x.partial_cmp(&b.x) { 294 | Some(std::cmp::Ordering::Equal) => {} 295 | Some(ord) => return ord, 296 | None => panic!(), 297 | } 298 | match a.y.partial_cmp(&b.y) { 299 | Some(std::cmp::Ordering::Equal) => {} 300 | Some(ord) => return ord, 301 | None => panic!(), 302 | } 303 | std::cmp::Ordering::Equal 304 | } 305 | 306 | impl Event { 307 | // Determines whether the edge is a vertical edge. 308 | fn is_vertical(&self) -> bool { 309 | (self.point.x - self.other_point.x).abs() < EPSILON 310 | } 311 | 312 | // Determine whether `self` and `relation` imply the edge is in the result 313 | // based on the operation. 314 | fn in_result(&self, relation: &EventRelation, operation: Operation) -> bool { 315 | if relation.edge_coincidence_type != EdgeCoincidenceType::NoCoincidence { 316 | return relation.edge_coincidence_type.in_result(operation); 317 | } 318 | match operation { 319 | // The edge is in the result iff it is inside the other polygon, aka if 320 | // the closest edge below is an out-in transition. 321 | Operation::Intersection => !relation.other_in_out, 322 | // The edge is in the result iff it is outside the other polygon, aka if 323 | // the closest edge below is an in-out transition (or is non-existent). 324 | Operation::Union => relation.other_in_out, 325 | // The edge is in the result iff it is either the subject and we are 326 | // outside the clip polygon (the closest edge below is an in-out 327 | // transition), or it is the clip and we are inside the clip polygon (the 328 | // closest edge below is an out-in transition). 329 | Operation::Difference => self.is_subject == relation.other_in_out, 330 | // Every edge is part of the result. 331 | Operation::XOR => true, 332 | } 333 | } 334 | 335 | // Determines whether `self` and `relation` that are in the result is an 336 | // in-out transition or not. 337 | fn result_in_out( 338 | &self, 339 | relation: &EventRelation, 340 | operation: Operation, 341 | ) -> bool { 342 | // These variables make the logic below more intuitive (e.g. a && b for 343 | // intersection). 344 | let is_inside_self = !relation.in_out; 345 | let is_inside_other = !relation.other_in_out; 346 | 347 | // For all of these cases, we already know the edge is in the result. 348 | let result_out_in = match operation { 349 | // The edge is an out-in transition iff we are inside both this polygon 350 | // and the other polygon. 351 | Operation::Intersection => is_inside_self && is_inside_other, 352 | // The edge is an out-in transition iff we are inside either this polygon 353 | // or the other polygon. 354 | Operation::Union => is_inside_self || is_inside_other, 355 | // The edge is an out-in transition iff we are in the subject polygon and 356 | // not in the clip polygon (nothing was subtracted), or we are in the clip 357 | // polygon and not in the subject (since we know the edge is in the 358 | // result, we must have just left the subject polygon). 359 | Operation::Difference => { 360 | if self.is_subject { 361 | is_inside_self && !is_inside_other 362 | } else { 363 | !is_inside_self && is_inside_other 364 | } 365 | } 366 | // The edge is an out-in transition iff the polygons are in opposite 367 | // states. 368 | Operation::XOR => is_inside_self != is_inside_other, 369 | }; 370 | 371 | !result_out_in 372 | } 373 | } 374 | 375 | // Returns whether `point` is above (Greater) or below (Less) the line defined 376 | // by `a` and `b`. Note if b is to the left of a, the returned ordering will be 377 | // reversed. 378 | fn point_relative_to_line(a: Vec2, b: Vec2, point: Vec2) -> std::cmp::Ordering { 379 | 0.0.partial_cmp(&(b - a).perp_dot(point - a)).unwrap() 380 | } 381 | 382 | // The relationship of the event to the rest of the edges. While `Event` is 383 | // immutable, the EventRelation can change over the course of the algorithm. 384 | #[derive(Default, Clone, PartialEq, Debug)] 385 | struct EventRelation { 386 | // The ID of the point that this edge connects to. This can change through 387 | // intersections. 388 | sibling_id: usize, 389 | // The point that this edge connects to. This can change through 390 | // intersections. 391 | sibling_point: Vec2, 392 | // Indicates if this edge represents an inside-outside transition into the 393 | // polygon. 394 | in_out: bool, 395 | // Same as `in_out`, but for the other polygon (if this event refers to the 396 | // subject, this will refer to the clip). 397 | other_in_out: bool, 398 | // Whether the edge is in the result. 399 | in_result: bool, 400 | // The ID of the previous event in the sweep line that was in the result. 401 | prev_in_result: Option, 402 | // The type of coincidence between another edge. 403 | edge_coincidence_type: EdgeCoincidenceType, 404 | // The edge that this event comes from. This can change for coincident edges 405 | // to prefer to report the subject edge. 406 | source_edge: SourceEdge, 407 | } 408 | 409 | // The type of edge coincidence (overlapping edges). 410 | #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] 411 | enum EdgeCoincidenceType { 412 | // A standard edge before any coincident edge has been detected. 413 | #[default] 414 | NoCoincidence, 415 | // There is a coincident edge and it has the same in-out transition as this 416 | // edge. 417 | SameTransition, 418 | // There is a coincient edge and it has a different in-out transition as 419 | // this edge. 420 | DifferentTransition, 421 | // There is a coincident edge, but we only need one of these edges in the 422 | // result - this edge will not be in the result. 423 | DuplicateCoincidence, 424 | } 425 | 426 | impl EdgeCoincidenceType { 427 | fn in_result(&self, operation: Operation) -> bool { 428 | match self { 429 | EdgeCoincidenceType::NoCoincidence => panic!(), 430 | EdgeCoincidenceType::DuplicateCoincidence => false, 431 | EdgeCoincidenceType::SameTransition => { 432 | operation == Operation::Intersection || operation == Operation::Union 433 | } 434 | EdgeCoincidenceType::DifferentTransition => { 435 | operation == Operation::Difference 436 | } 437 | } 438 | } 439 | } 440 | 441 | // Creates a left and right event for each edge in the polygon. Returns the 442 | // bounds of the polygon for convenience. 443 | fn create_events_for_polygon( 444 | polygon: &Polygon, 445 | is_subject: bool, 446 | event_queue: &mut BinaryHeap>, 447 | event_relations: &mut Vec, 448 | x_limit: f32, 449 | ) { 450 | for (contour_index, contour) in polygon.contours.iter().enumerate() { 451 | for point_index in 0..contour.len() { 452 | let next_point_index = 453 | if point_index == contour.len() - 1 { 0 } else { point_index + 1 }; 454 | 455 | let point_1 = contour[point_index]; 456 | let point_2 = contour[next_point_index]; 457 | // This entire edge is passed the `x_limit`, so it will never be 458 | // processed. 459 | if x_limit < point_1.x.min(point_2.x) { 460 | continue; 461 | } 462 | let (event_1_left, event_2_left) = 463 | match lex_order_points(&point_1, &point_2) { 464 | std::cmp::Ordering::Equal => continue, // Ignore degenerate edges. 465 | std::cmp::Ordering::Less => (true, false), 466 | std::cmp::Ordering::Greater => (false, true), 467 | }; 468 | 469 | let event_id_1 = event_relations.len(); 470 | let event_id_2 = event_relations.len() + 1; 471 | 472 | event_queue.push(Reverse(Event { 473 | event_id: event_id_1, 474 | point: point_1, 475 | left: event_1_left, 476 | is_subject, 477 | other_point: point_2, 478 | })); 479 | event_queue.push(Reverse(Event { 480 | event_id: event_id_2, 481 | point: point_2, 482 | left: event_2_left, 483 | is_subject, 484 | other_point: point_1, 485 | })); 486 | 487 | event_relations.push(EventRelation { 488 | sibling_id: event_id_2, 489 | sibling_point: point_2, 490 | source_edge: SourceEdge { 491 | is_from_subject: is_subject, 492 | contour: contour_index, 493 | edge: point_index, 494 | }, 495 | ..Default::default() 496 | }); 497 | event_relations.push(EventRelation { 498 | sibling_id: event_id_1, 499 | sibling_point: point_1, 500 | source_edge: SourceEdge { 501 | is_from_subject: is_subject, 502 | contour: contour_index, 503 | edge: point_index, 504 | }, 505 | ..Default::default() 506 | }); 507 | } 508 | } 509 | } 510 | 511 | // An event that can be sorted into the sweep line. The sweep line data 512 | // structure will hold the edges currently intersecting the sweep line in 513 | // order from top to bottom. Note the event will always be a left event, since 514 | // right events will remove the associated left event (so the sweep line will 515 | // never contain a right event). 516 | struct SweepLineEvent(Event); 517 | 518 | impl PartialEq for SweepLineEvent { 519 | fn eq(&self, other: &Self) -> bool { 520 | self.0.event_id == other.0.event_id 521 | } 522 | } 523 | impl PartialOrd for SweepLineEvent { 524 | fn partial_cmp(&self, other: &Self) -> Option { 525 | // We want to sort the events (i.e., edges) by their height. For edges that 526 | // cross, we want to order by their leftmost points. Note since these events 527 | // are part of the sweep line, we can assume the sweep line intersects both 528 | // lines at at least one X coordinate). 529 | 530 | // Pick the leftmost point. 531 | if self.0.point.x < other.0.point.x { 532 | // Use `self's line to determine the ordering. 533 | match point_relative_to_line( 534 | self.0.point, 535 | self.0.other_point, 536 | other.0.point, 537 | ) { 538 | std::cmp::Ordering::Equal => {} 539 | order => return Some(order), 540 | } 541 | 542 | // `other`s left point is on `self`s line, so use `other`s right point to 543 | // order the edges. 544 | match point_relative_to_line( 545 | self.0.point, 546 | self.0.other_point, 547 | other.0.other_point, 548 | ) { 549 | std::cmp::Ordering::Equal => {} 550 | order => return Some(order), 551 | } 552 | } else { 553 | // Use `other`s line to determine the ordering. 554 | match point_relative_to_line( 555 | other.0.point, 556 | other.0.other_point, 557 | self.0.point, 558 | ) { 559 | std::cmp::Ordering::Equal => {} 560 | order => return Some(order.reverse()), 561 | } 562 | 563 | // `self`s left point is on `other`s line, so use `self`s right point to 564 | // order the edges. 565 | match point_relative_to_line( 566 | other.0.point, 567 | other.0.other_point, 568 | self.0.other_point, 569 | ) { 570 | std::cmp::Ordering::Equal => {} 571 | order => return Some(order.reverse()), 572 | } 573 | } 574 | 575 | // The lines are colinear. Just order by the events to disambiguate. 576 | self.0.partial_cmp(&other.0) 577 | } 578 | } 579 | impl Eq for SweepLineEvent {} 580 | impl Ord for SweepLineEvent { 581 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 582 | self.partial_cmp(other).unwrap() 583 | } 584 | } 585 | 586 | // Check for intersections between two events in the sweep line. `new_event` is 587 | // the event just inserted into the sweep line and `existing_event` is the event 588 | // that was already in the sweep line. 589 | fn check_for_intersection( 590 | new_event: &Event, 591 | existing_event: &Event, 592 | event_queue: &mut BinaryHeap>, 593 | event_relations: &mut Vec, 594 | operation: Operation, 595 | ) { 596 | match edge_intersection( 597 | (new_event.point, event_relations[new_event.event_id].sibling_point), 598 | ( 599 | existing_event.point, 600 | event_relations[existing_event.event_id].sibling_point, 601 | ), 602 | ) { 603 | EdgeIntersectionResult::NoIntersection => {} // Do nothing. 604 | EdgeIntersectionResult::PointIntersection(point) => { 605 | // Split the edges, but only if the the split point isn't at an end point. 606 | if !point.abs_diff_eq(new_event.point, EPSILON) 607 | && !point.abs_diff_eq( 608 | event_relations[new_event.event_id].sibling_point, 609 | EPSILON, 610 | ) 611 | { 612 | split_edge(new_event, point, event_queue, event_relations); 613 | } 614 | if !point.abs_diff_eq(existing_event.point, EPSILON) 615 | && !point.abs_diff_eq( 616 | event_relations[existing_event.event_id].sibling_point, 617 | EPSILON, 618 | ) 619 | { 620 | split_edge(existing_event, point, event_queue, event_relations); 621 | } 622 | } 623 | EdgeIntersectionResult::LineIntersection(start, end) => { 624 | let new_event_coincident_event_id; 625 | match ( 626 | start.abs_diff_eq(new_event.point, EPSILON), 627 | end.abs_diff_eq( 628 | event_relations[new_event.event_id].sibling_point, 629 | EPSILON, 630 | ), 631 | ) { 632 | (true, true) => { 633 | // The edge is fully covered, so no new splits are necessary. 634 | new_event_coincident_event_id = new_event.event_id; 635 | } 636 | (false, false) => { 637 | split_edge(new_event, end, event_queue, event_relations); 638 | new_event_coincident_event_id = 639 | split_edge(new_event, start, event_queue, event_relations); 640 | } 641 | (true, false) => { 642 | split_edge(new_event, end, event_queue, event_relations); 643 | new_event_coincident_event_id = new_event.event_id; 644 | } 645 | (false, true) => { 646 | new_event_coincident_event_id = 647 | split_edge(new_event, start, event_queue, event_relations); 648 | } 649 | } 650 | 651 | let existing_event_coincident_event_id; 652 | match ( 653 | start.abs_diff_eq(existing_event.point, EPSILON), 654 | end.abs_diff_eq( 655 | event_relations[existing_event.event_id].sibling_point, 656 | EPSILON, 657 | ), 658 | ) { 659 | (true, true) => { 660 | // The edge is fully covered, so no new splits are necessary. 661 | existing_event_coincident_event_id = existing_event.event_id; 662 | } 663 | (false, false) => { 664 | split_edge(existing_event, end, event_queue, event_relations); 665 | existing_event_coincident_event_id = 666 | split_edge(existing_event, start, event_queue, event_relations); 667 | } 668 | (true, false) => { 669 | split_edge(existing_event, end, event_queue, event_relations); 670 | existing_event_coincident_event_id = existing_event.event_id; 671 | } 672 | (false, true) => { 673 | existing_event_coincident_event_id = 674 | split_edge(existing_event, start, event_queue, event_relations); 675 | } 676 | } 677 | 678 | let same_transition = event_relations[new_event.event_id].in_out 679 | == event_relations[existing_event.event_id].in_out; 680 | 681 | // The prev_in_result of the new edge can sometimes equal the pre-existing 682 | // edge. Since the edges are intersecting, their prev_in_result 683 | // should match (since neither is "more important"). 684 | event_relations[new_event_coincident_event_id].prev_in_result = 685 | event_relations[existing_event.event_id].prev_in_result; 686 | 687 | // We say the "primary" coincident edge is the one that will represent 688 | // both edges. The "duplicate" coincident edge will not contribute to the 689 | // final polygon. 690 | let (primary_edge_event_id, duplicate_edge_event_id) = 691 | if event_relations[existing_event_coincident_event_id].in_result { 692 | (existing_event_coincident_event_id, new_event_coincident_event_id) 693 | } else { 694 | (new_event_coincident_event_id, existing_event_coincident_event_id) 695 | }; 696 | // In the final result, we want to prefer subject edges over clip edges, 697 | // so change the primary edge (which is the only one possibly in the 698 | // result) to use the subject edge if one of them is a clip edge. 699 | match ( 700 | event_relations[new_event.event_id].source_edge.is_from_subject, 701 | event_relations[existing_event.event_id].source_edge.is_from_subject, 702 | ) { 703 | // Neither edge is "preferred", so just go with the defaults. 704 | (true, true) => {} 705 | (false, false) => {} 706 | // The subject edge should be preferred, so assign those. 707 | (true, false) => { 708 | let source_edge = event_relations[new_event.event_id].source_edge; 709 | event_relations[primary_edge_event_id].source_edge = source_edge; 710 | let sibling_id = event_relations[primary_edge_event_id].sibling_id; 711 | event_relations[sibling_id].source_edge = source_edge; 712 | } 713 | (false, true) => { 714 | let source_edge = 715 | event_relations[existing_event.event_id].source_edge; 716 | event_relations[primary_edge_event_id].source_edge = source_edge; 717 | let sibling_id = event_relations[primary_edge_event_id].sibling_id; 718 | event_relations[sibling_id].source_edge = source_edge; 719 | } 720 | } 721 | 722 | let primary_edge_relation = &mut event_relations[primary_edge_event_id]; 723 | primary_edge_relation.edge_coincidence_type = if same_transition { 724 | EdgeCoincidenceType::SameTransition 725 | } else { 726 | EdgeCoincidenceType::DifferentTransition 727 | }; 728 | primary_edge_relation.in_result = 729 | primary_edge_relation.edge_coincidence_type.in_result(operation); 730 | 731 | let duplicate_edge_relation = 732 | &mut event_relations[duplicate_edge_event_id]; 733 | duplicate_edge_relation.edge_coincidence_type = 734 | EdgeCoincidenceType::DuplicateCoincidence; 735 | duplicate_edge_relation.in_result = false; 736 | } 737 | } 738 | } 739 | 740 | // Splits an edge into two parts at `point`. Siblings are updated for the 741 | // existing events and new events are generated. Returns the index of the left 742 | // event of the new edge. 743 | fn split_edge( 744 | edge_event: &Event, 745 | point: Vec2, 746 | event_queue: &mut BinaryHeap>, 747 | event_relations: &mut Vec, 748 | ) -> usize { 749 | let (sibling_id, sibling_point, source_edge) = { 750 | let relation = &event_relations[edge_event.event_id]; 751 | (relation.sibling_id, relation.sibling_point, relation.source_edge) 752 | }; 753 | 754 | let split_1_id = event_relations.len(); 755 | let split_2_id = event_relations.len() + 1; 756 | 757 | event_queue.push(Reverse(Event { 758 | event_id: split_1_id, 759 | point, 760 | left: false, 761 | is_subject: edge_event.is_subject, 762 | other_point: edge_event.point, 763 | })); 764 | event_queue.push(Reverse(Event { 765 | event_id: split_2_id, 766 | point, 767 | left: true, 768 | is_subject: edge_event.is_subject, 769 | other_point: edge_event.other_point, 770 | })); 771 | 772 | event_relations.push(EventRelation { 773 | sibling_id: edge_event.event_id, 774 | sibling_point: edge_event.point, 775 | source_edge, 776 | ..Default::default() 777 | }); 778 | event_relations.push(EventRelation { 779 | sibling_id, 780 | sibling_point, 781 | source_edge, 782 | ..Default::default() 783 | }); 784 | 785 | let edge_event_relation = &mut event_relations[edge_event.event_id]; 786 | edge_event_relation.sibling_id = split_1_id; 787 | edge_event_relation.sibling_point = point; 788 | 789 | let edge_sibling_relation = &mut event_relations[sibling_id]; 790 | edge_sibling_relation.sibling_id = split_2_id; 791 | edge_sibling_relation.sibling_point = point; 792 | 793 | split_2_id 794 | } 795 | 796 | // Determines the flags in `event_relation`. These are used to determine whether 797 | // edges are in the result or not. This assumes `prev_event` is already in the 798 | // sweep line and has had its own flags computed. 799 | fn set_information( 800 | (event, event_relation): (&Event, &mut EventRelation), 801 | prev_event: Option<(&Event, &EventRelation)>, 802 | operation: Operation, 803 | ) { 804 | match prev_event { 805 | None => { 806 | // There is no previous event, so this must be the external contour of 807 | // one of the polygons. 808 | event_relation.in_out = false; 809 | // Even if there is no previous event, we mark it as an in-out 810 | // transition since this treats the other as being "outside". 811 | event_relation.other_in_out = true; 812 | } 813 | Some((prev_event, prev_event_relation)) => { 814 | if event.is_subject == prev_event.is_subject { 815 | // The events are from the same polygon, so this event should be the 816 | // opposite of `prev_event`. 817 | event_relation.in_out = !prev_event_relation.in_out; 818 | // The nearest other polygon's edge stays the same. 819 | event_relation.other_in_out = prev_event_relation.other_in_out; 820 | } else { 821 | // `prev_event` is from the other polygon, so the nearest edge of its 822 | // other polygon is the same as this event. Flip its sign just as 823 | // above. 824 | event_relation.in_out = !prev_event_relation.other_in_out; 825 | event_relation.other_in_out = if !prev_event.is_vertical() { 826 | // When the previous edge is not vertical, since `prev_event` is the 827 | // other polygon, we just copy the in_out directly. 828 | prev_event_relation.in_out 829 | } else { 830 | // When the previous edge is vertical, this edge really cares about 831 | // the in_out transition of the top of the previous edge. For 832 | // horizontal edges, this is the same as in_out, but for vertical 833 | // edges, the top of the edge has the opposite in_out. 834 | !prev_event_relation.in_out 835 | }; 836 | } 837 | 838 | // The in_result part is obvious. If the previous event is vertical, we do 839 | // not want to use it as prev_in_result, since we are just skimming the 840 | // edge of the polygon. 841 | event_relation.prev_in_result = 842 | if prev_event_relation.in_result && !prev_event.is_vertical() { 843 | Some(prev_event.event_id) 844 | } else { 845 | prev_event_relation.prev_in_result 846 | }; 847 | } 848 | } 849 | 850 | event_relation.in_result = event.in_result(event_relation, operation); 851 | } 852 | 853 | // Goes through the `event_queue` and subdivides intersecting edges. Returns a 854 | // Vec of events corresponding to the edges that are in the final result based 855 | // on `operation`. Events to the right of `x_limit` will be skipped. 856 | fn subdivide_edges( 857 | mut event_queue: BinaryHeap>, 858 | event_relations: &mut Vec, 859 | operation: Operation, 860 | x_limit: f32, 861 | ) -> Vec { 862 | let mut sweep_line = Vec::new(); 863 | let mut result = Vec::new(); 864 | while let Some(Reverse(event)) = event_queue.pop() { 865 | // Every event in `event_queue` must have a greater X value, so we can skip 866 | // all remaining events. 867 | if x_limit < event.point.x { 868 | break; 869 | } 870 | 871 | if event.left { 872 | let sweep_line_event = SweepLineEvent(event.clone()); 873 | let pos = sweep_line 874 | .binary_search(&sweep_line_event) 875 | .expect_err("event is new and must be inserted"); 876 | sweep_line.insert(pos, sweep_line_event); 877 | if pos == 0 { 878 | set_information( 879 | (&event, &mut event_relations[event.event_id]), 880 | /* prev_event= */ None, 881 | operation, 882 | ) 883 | } else { 884 | let prev_event = &sweep_line[pos - 1].0; 885 | { 886 | let (event_relation, prev_event_relation) = borrow_two_mut( 887 | event_relations, 888 | event.event_id, 889 | prev_event.event_id, 890 | ); 891 | set_information( 892 | (&event, event_relation), 893 | Some((prev_event, prev_event_relation)), 894 | operation, 895 | ); 896 | } 897 | check_for_intersection( 898 | &event, 899 | prev_event, 900 | &mut event_queue, 901 | event_relations, 902 | operation, 903 | ); 904 | } 905 | if pos + 1 < sweep_line.len() { 906 | // If the inserted event isn't last, check for intersection with next 907 | // event. 908 | let next_event = &sweep_line[pos + 1].0; 909 | check_for_intersection( 910 | &event, 911 | next_event, 912 | &mut event_queue, 913 | event_relations, 914 | operation, 915 | ); 916 | } 917 | } else { 918 | // The right edge event is in the result if its left edge event is also in 919 | // the result. 920 | event_relations[event.event_id].in_result = 921 | event_relations[event_relations[event.event_id].sibling_id].in_result; 922 | let pos = sweep_line 923 | .binary_search(&order_sibling(&event, &event_relations[event.event_id])) 924 | .expect("this is a right event, so the left event must have already been inserted."); 925 | sweep_line.remove(pos); 926 | if 0 < pos && pos < sweep_line.len() { 927 | let (prev_event, next_event) = 928 | (&sweep_line[pos - 1].0, &sweep_line[pos].0); 929 | check_for_intersection( 930 | prev_event, 931 | next_event, 932 | &mut event_queue, 933 | event_relations, 934 | operation, 935 | ); 936 | } 937 | } 938 | 939 | if event_relations[event.event_id].in_result { 940 | result.push(event); 941 | } 942 | } 943 | 944 | // Only keep events that are still in the result by the end. With no 945 | // coincident edges, in_result can never change after the first pass. However, 946 | // with coincident edges, an edge that previously thought it was in the result 947 | // may no longer be in the result. Consider the subject and the clip being the 948 | // same, and the operation being difference or XOR. The first subject edge 949 | // will have no previous event in the sweep line, so it will think it is in 950 | // the result. Then the clip edge will be processed and now the edge is no 951 | // longer in the result. 952 | result.retain(|event| event_relations[event.event_id].in_result); 953 | 954 | result 955 | } 956 | 957 | // Borrows two elements from a slice mutably. It should be unreachable to ever 958 | // call this with two of the same index. 959 | fn borrow_two_mut(slice: &mut [T], a: usize, b: usize) -> (&mut T, &mut T) { 960 | if a < b { 961 | let (left, right) = slice.split_at_mut(b); 962 | (&mut left[a], &mut right[0]) 963 | } else if b < a { 964 | let (left, right) = slice.split_at_mut(a); 965 | (&mut right[0], &mut left[b]) 966 | } else { 967 | unreachable!(); 968 | } 969 | } 970 | 971 | // Derives a SweepLineEvent corresponding to the sibling of `event`. `event` is 972 | // assumed to be a right event (since that is the only time you need to 973 | // determine the order sibling). 974 | fn order_sibling( 975 | event: &Event, 976 | event_relation: &EventRelation, 977 | ) -> SweepLineEvent { 978 | SweepLineEvent(Event { 979 | event_id: event_relation.sibling_id, 980 | point: event_relation.sibling_point, 981 | left: true, 982 | is_subject: event.is_subject, 983 | other_point: event.point, 984 | }) 985 | } 986 | 987 | // The flags for each event used to derive contours. 988 | #[derive(Default)] 989 | struct EventContourFlags { 990 | // The index into the result events that this event corresponds to. 991 | result_id: usize, 992 | // Whether this event corresponds to an in-out transition of the output 993 | // polygon. 994 | result_in_out: bool, 995 | // The ID of the contour this event belongs to. 996 | contour_id: usize, 997 | // The ID of the parent contour if it exists. 998 | parent_id: Option, 999 | // Whether this event has already been processed - events do not need to be 1000 | // processed multipled times. 1001 | processed: bool, 1002 | // The depth of the contour. Even if a shell, odd if a hole. 1003 | depth: u32, 1004 | } 1005 | 1006 | // Computes the depth and the ID of the parent contour (if the parent exists). 1007 | fn compute_depth( 1008 | event: &Event, 1009 | event_relations: &[EventRelation], 1010 | event_id_to_contour_flags: &HashMap, 1011 | ) -> (u32, Option) { 1012 | match event_relations[event.event_id].prev_in_result { 1013 | None => (0, None), 1014 | Some(prev_in_result) => { 1015 | let prev_contour_flags = &event_id_to_contour_flags[&prev_in_result]; 1016 | 1017 | if !prev_contour_flags.result_in_out { 1018 | (prev_contour_flags.depth + 1, Some(prev_contour_flags.contour_id)) 1019 | } else { 1020 | (prev_contour_flags.depth, prev_contour_flags.parent_id) 1021 | } 1022 | } 1023 | } 1024 | } 1025 | 1026 | // Computes the contour starting at `start_event`. Events that are part of the 1027 | // contour will be assigned the `depth`, `contour_id`, and `parent_contour_id`. 1028 | fn compute_contour( 1029 | start_event: &Event, 1030 | contour_id: usize, 1031 | depth: u32, 1032 | parent_contour_id: Option, 1033 | event_relations: &[EventRelation], 1034 | event_id_to_contour_flags: &mut HashMap, 1035 | result_events: &[Event], 1036 | ) -> (Vec, Vec) { 1037 | let mut contour = Vec::new(); 1038 | let mut contour_source_edges = Vec::new(); 1039 | contour.push(start_event.point); 1040 | contour_source_edges.push(event_relations[start_event.event_id].source_edge); 1041 | let mut current_event = event_to_sibling_and_mark( 1042 | start_event, 1043 | contour_id, 1044 | depth, 1045 | parent_contour_id, 1046 | event_relations, 1047 | event_id_to_contour_flags, 1048 | &result_events, 1049 | ); 1050 | 1051 | while current_event.point != start_event.point { 1052 | let result_id = 1053 | event_id_to_contour_flags[¤t_event.event_id].result_id; 1054 | if 0 < result_id 1055 | && result_events[result_id - 1] 1056 | .point 1057 | .abs_diff_eq(current_event.point, EPSILON) 1058 | { 1059 | current_event = &result_events[result_id - 1]; 1060 | event_id_to_contour_flags 1061 | .get_mut(¤t_event.event_id) 1062 | .unwrap() 1063 | .processed = true; 1064 | } else { 1065 | // One of the adjacent events in `result_events` must be connected to 1066 | // the current event, panic otherwise. 1067 | debug_assert!(result_id + 1 < result_events.len()); 1068 | debug_assert!( 1069 | result_events[result_id + 1] 1070 | .point 1071 | .abs_diff_eq(current_event.point, EPSILON), 1072 | "left={}, right={}, result_id={}, event_id={}", 1073 | result_events[result_id + 1].point, 1074 | current_event.point, 1075 | result_id, 1076 | current_event.event_id, 1077 | ); 1078 | current_event = &result_events[result_id + 1]; 1079 | event_id_to_contour_flags 1080 | .get_mut(¤t_event.event_id) 1081 | .unwrap() 1082 | .processed = true; 1083 | } 1084 | contour.push(current_event.point); 1085 | contour_source_edges 1086 | .push(event_relations[current_event.event_id].source_edge); 1087 | current_event = event_to_sibling_and_mark( 1088 | current_event, 1089 | contour_id, 1090 | depth, 1091 | parent_contour_id, 1092 | &event_relations, 1093 | event_id_to_contour_flags, 1094 | &result_events, 1095 | ); 1096 | } 1097 | 1098 | (contour, contour_source_edges) 1099 | } 1100 | 1101 | // Finds the sibling of `event`, sets its flags to match the provided arguments, 1102 | // and returns the sibling event. 1103 | fn event_to_sibling_and_mark<'a>( 1104 | event: &Event, 1105 | contour_id: usize, 1106 | depth: u32, 1107 | parent_contour_id: Option, 1108 | event_relations: &[EventRelation], 1109 | event_id_to_contour_flags: &mut HashMap, 1110 | result_events: &'a [Event], 1111 | ) -> &'a Event { 1112 | let sibling_id = event_relations[event.event_id].sibling_id; 1113 | let contour_relation = 1114 | event_id_to_contour_flags.get_mut(&sibling_id).unwrap(); 1115 | contour_relation.processed = true; 1116 | contour_relation.contour_id = contour_id; 1117 | contour_relation.depth = depth; 1118 | contour_relation.parent_id = parent_contour_id; 1119 | &result_events[contour_relation.result_id] 1120 | } 1121 | 1122 | // Determines the contours of the result polygon from the `result_events`. 1123 | fn join_contours( 1124 | result_events: Vec, 1125 | event_relations: Vec, 1126 | operation: Operation, 1127 | ) -> BooleanResult { 1128 | let mut event_id_to_contour_flags = result_events 1129 | .iter() 1130 | .enumerate() 1131 | .map(|(result_id, event)| { 1132 | let event_meta = &event_relations[event.event_id]; 1133 | ( 1134 | event.event_id, 1135 | EventContourFlags { 1136 | result_id, 1137 | result_in_out: event.result_in_out(event_meta, operation), 1138 | ..Default::default() 1139 | }, 1140 | ) 1141 | }) 1142 | .collect::>(); 1143 | 1144 | let mut contours = Vec::new(); 1145 | let mut contour_source_edges = Vec::new(); 1146 | for result_event in result_events.iter() { 1147 | if event_id_to_contour_flags[&result_event.event_id].processed { 1148 | continue; 1149 | } 1150 | let (depth, parent_contour_id) = 1151 | compute_depth(result_event, &event_relations, &event_id_to_contour_flags); 1152 | let (mut contour, mut source_edges_for_contour) = compute_contour( 1153 | result_event, 1154 | contours.len(), 1155 | depth, 1156 | parent_contour_id, 1157 | &event_relations, 1158 | &mut event_id_to_contour_flags, 1159 | &result_events, 1160 | ); 1161 | 1162 | if depth % 2 == 1 { 1163 | contour.reverse(); 1164 | source_edges_for_contour.reverse(); 1165 | } 1166 | 1167 | contours.push(contour); 1168 | contour_source_edges.push(source_edges_for_contour); 1169 | } 1170 | 1171 | BooleanResult { polygon: Polygon { contours }, contour_source_edges } 1172 | } 1173 | 1174 | #[cfg(test)] 1175 | mod tests; 1176 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Reverse, 3 | collections::BinaryHeap, 4 | f32::{EPSILON, INFINITY}, 5 | }; 6 | 7 | use glam::Vec2; 8 | use rand::seq::SliceRandom; 9 | 10 | use crate::{ 11 | check_for_intersection, create_events_for_polygon, difference, intersection, 12 | split_edge, union, xor, BooleanResult, EdgeCoincidenceType, Event, 13 | EventRelation, Operation, Polygon, SourceEdge, 14 | }; 15 | 16 | #[test] 17 | fn split_edge_events_ordered_correctly() { 18 | let expected_events = [ 19 | // Edge start events. 20 | Event { 21 | event_id: 100, 22 | point: Vec2::new(3.0, 2.0), 23 | left: true, 24 | is_subject: false, 25 | other_point: Vec2::new(5.0, 2.0), 26 | }, 27 | Event { 28 | event_id: 90, 29 | point: Vec2::new(3.5, 1.0), 30 | left: true, 31 | is_subject: true, 32 | other_point: Vec2::new(5.0, 3.0), 33 | }, 34 | // Edge intersection events. 35 | Event { 36 | event_id: 95, 37 | point: Vec2::new(4.25, 2.0), 38 | left: false, 39 | is_subject: true, 40 | other_point: Vec2::new(3.5, 1.0), 41 | }, 42 | Event { 43 | event_id: 93, 44 | point: Vec2::new(4.25, 2.0), 45 | left: false, 46 | is_subject: false, 47 | other_point: Vec2::new(3.0, 2.0), 48 | }, 49 | Event { 50 | event_id: 105, 51 | point: Vec2::new(4.25, 2.0), 52 | left: true, 53 | is_subject: false, 54 | other_point: Vec2::new(5.0, 2.0), 55 | }, 56 | Event { 57 | event_id: 101, 58 | point: Vec2::new(4.25, 2.0), 59 | left: true, 60 | is_subject: true, 61 | other_point: Vec2::new(5.0, 3.0), 62 | }, 63 | // Edge end events. 64 | Event { 65 | event_id: 97, 66 | point: Vec2::new(5.0, 2.0), 67 | left: false, 68 | is_subject: false, 69 | other_point: Vec2::new(3.0, 2.0), 70 | }, 71 | Event { 72 | event_id: 89, 73 | point: Vec2::new(5.0, 3.0), 74 | left: false, 75 | is_subject: true, 76 | other_point: Vec2::new(3.5, 1.0), 77 | }, 78 | ]; 79 | 80 | assert!( 81 | expected_events[0] < expected_events[1], 82 | "left={:?} right={:?}", 83 | expected_events[0], 84 | expected_events[1] 85 | ); 86 | assert!( 87 | expected_events[1] < expected_events[2], 88 | "left={:?} right={:?}", 89 | expected_events[1], 90 | expected_events[2] 91 | ); 92 | assert!( 93 | expected_events[2] < expected_events[3], 94 | "left={:?} right={:?}", 95 | expected_events[2], 96 | expected_events[3] 97 | ); 98 | assert!( 99 | expected_events[3] < expected_events[4], 100 | "left={:?} right={:?}", 101 | expected_events[3], 102 | expected_events[4] 103 | ); 104 | assert!( 105 | expected_events[4] < expected_events[5], 106 | "left={:?} right={:?}", 107 | expected_events[4], 108 | expected_events[5] 109 | ); 110 | assert!( 111 | expected_events[5] < expected_events[6], 112 | "left={:?} right={:?}", 113 | expected_events[5], 114 | expected_events[6] 115 | ); 116 | assert!( 117 | expected_events[6] < expected_events[7], 118 | "left={:?} right={:?}", 119 | expected_events[6], 120 | expected_events[7] 121 | ); 122 | 123 | let mut sorted_events = expected_events.iter().cloned().collect::>(); 124 | sorted_events.shuffle(&mut rand::thread_rng()); 125 | sorted_events.sort(); 126 | 127 | assert_eq!(sorted_events, expected_events); 128 | } 129 | 130 | // Consumes the `event_queue` and turns it into a sorted Vec of events. 131 | fn event_queue_to_vec(event_queue: BinaryHeap>) -> Vec { 132 | let mut event_queue = event_queue 133 | .into_sorted_vec() 134 | .iter() 135 | .map(|e| e.0.clone()) 136 | .collect::>(); 137 | // into_sorted_vec returns the sort of Reverse(Event), so reverse the order to 138 | // get the sort order of Event. 139 | event_queue.reverse(); 140 | 141 | event_queue 142 | } 143 | 144 | #[test] 145 | fn no_bounds_for_empty_polygon() { 146 | assert_eq!( 147 | Polygon { contours: vec![vec![], vec![], vec![]] }.compute_bounds(), 148 | None 149 | ); 150 | } 151 | 152 | #[test] 153 | fn computes_bounds_for_non_empty_polygon() { 154 | assert_eq!( 155 | Polygon { 156 | contours: vec![ 157 | vec![Vec2::new(1.0, 1.0), Vec2::new(5.0, 2.0)], 158 | vec![], 159 | vec![Vec2::new(2.0, 5.0), Vec2::new(3.0, 3.0)] 160 | ] 161 | } 162 | .compute_bounds(), 163 | Some((Vec2::new(1.0, 1.0), Vec2::new(5.0, 5.0))) 164 | ); 165 | } 166 | 167 | #[test] 168 | fn creates_events_for_polygon() { 169 | let polygon = Polygon { 170 | contours: vec![ 171 | vec![ 172 | Vec2::new(1.0, 1.0), 173 | Vec2::new(3.0, 1.0), 174 | Vec2::new(3.0, 3.0), 175 | Vec2::new(1.0, 3.0), 176 | ], 177 | vec![ 178 | Vec2::new(4.0, 1.0), 179 | Vec2::new(5.0, 1.0), 180 | Vec2::new(6.0, 2.0), 181 | Vec2::new(5.0, 2.0), 182 | ], 183 | ], 184 | }; 185 | 186 | let mut event_queue = BinaryHeap::new(); 187 | let mut event_relations = Vec::new(); 188 | create_events_for_polygon( 189 | &polygon, 190 | /* is_subject= */ true, 191 | &mut event_queue, 192 | &mut event_relations, 193 | /* x_limit= */ INFINITY, 194 | ); 195 | let event_queue = event_queue_to_vec(event_queue); 196 | assert_eq!( 197 | event_queue, 198 | [ 199 | Event { 200 | event_id: 0, 201 | point: Vec2::new(1.0, 1.0), 202 | left: true, 203 | is_subject: true, 204 | other_point: Vec2::new(3.0, 1.0), 205 | }, 206 | Event { 207 | event_id: 7, 208 | point: Vec2::new(1.0, 1.0), 209 | left: true, 210 | is_subject: true, 211 | other_point: Vec2::new(1.0, 3.0), 212 | }, 213 | Event { 214 | event_id: 6, 215 | point: Vec2::new(1.0, 3.0), 216 | left: false, 217 | is_subject: true, 218 | other_point: Vec2::new(1.0, 1.0), 219 | }, 220 | Event { 221 | event_id: 5, 222 | point: Vec2::new(1.0, 3.0), 223 | left: true, 224 | is_subject: true, 225 | other_point: Vec2::new(3.0, 3.0), 226 | }, 227 | Event { 228 | event_id: 1, 229 | point: Vec2::new(3.0, 1.0), 230 | left: false, 231 | is_subject: true, 232 | other_point: Vec2::new(1.0, 1.0), 233 | }, 234 | Event { 235 | event_id: 2, 236 | point: Vec2::new(3.0, 1.0), 237 | left: true, 238 | is_subject: true, 239 | other_point: Vec2::new(3.0, 3.0), 240 | }, 241 | Event { 242 | event_id: 4, 243 | point: Vec2::new(3.0, 3.0), 244 | left: false, 245 | is_subject: true, 246 | other_point: Vec2::new(1.0, 3.0), 247 | }, 248 | Event { 249 | event_id: 3, 250 | point: Vec2::new(3.0, 3.0), 251 | left: false, 252 | is_subject: true, 253 | other_point: Vec2::new(3.0, 1.0), 254 | }, 255 | Event { 256 | event_id: 8, 257 | point: Vec2::new(4.0, 1.0), 258 | left: true, 259 | is_subject: true, 260 | other_point: Vec2::new(5.0, 1.0), 261 | }, 262 | Event { 263 | event_id: 15, 264 | point: Vec2::new(4.0, 1.0), 265 | left: true, 266 | is_subject: true, 267 | other_point: Vec2::new(5.0, 2.0), 268 | }, 269 | Event { 270 | event_id: 9, 271 | point: Vec2::new(5.0, 1.0), 272 | left: false, 273 | is_subject: true, 274 | other_point: Vec2::new(4.0, 1.0), 275 | }, 276 | Event { 277 | event_id: 10, 278 | point: Vec2::new(5.0, 1.0), 279 | left: true, 280 | is_subject: true, 281 | other_point: Vec2::new(6.0, 2.0), 282 | }, 283 | Event { 284 | event_id: 14, 285 | point: Vec2::new(5.0, 2.0), 286 | left: false, 287 | is_subject: true, 288 | other_point: Vec2::new(4.0, 1.0), 289 | }, 290 | Event { 291 | event_id: 13, 292 | point: Vec2::new(5.0, 2.0), 293 | left: true, 294 | is_subject: true, 295 | other_point: Vec2::new(6.0, 2.0), 296 | }, 297 | Event { 298 | event_id: 11, 299 | point: Vec2::new(6.0, 2.0), 300 | left: false, 301 | is_subject: true, 302 | other_point: Vec2::new(5.0, 1.0), 303 | }, 304 | Event { 305 | event_id: 12, 306 | point: Vec2::new(6.0, 2.0), 307 | left: false, 308 | is_subject: true, 309 | other_point: Vec2::new(5.0, 2.0), 310 | }, 311 | ] 312 | ); 313 | assert_eq!( 314 | event_relations, 315 | [ 316 | EventRelation { 317 | sibling_id: 1, 318 | sibling_point: Vec2::new(3.0, 1.0), 319 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 320 | ..Default::default() 321 | }, 322 | EventRelation { 323 | sibling_id: 0, 324 | sibling_point: Vec2::new(1.0, 1.0), 325 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 326 | ..Default::default() 327 | }, 328 | EventRelation { 329 | sibling_id: 3, 330 | sibling_point: Vec2::new(3.0, 3.0), 331 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 332 | ..Default::default() 333 | }, 334 | EventRelation { 335 | sibling_id: 2, 336 | sibling_point: Vec2::new(3.0, 1.0), 337 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 338 | ..Default::default() 339 | }, 340 | EventRelation { 341 | sibling_id: 5, 342 | sibling_point: Vec2::new(1.0, 3.0), 343 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 344 | ..Default::default() 345 | }, 346 | EventRelation { 347 | sibling_id: 4, 348 | sibling_point: Vec2::new(3.0, 3.0), 349 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 350 | ..Default::default() 351 | }, 352 | EventRelation { 353 | sibling_id: 7, 354 | sibling_point: Vec2::new(1.0, 1.0), 355 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 356 | ..Default::default() 357 | }, 358 | EventRelation { 359 | sibling_id: 6, 360 | sibling_point: Vec2::new(1.0, 3.0), 361 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 362 | ..Default::default() 363 | }, 364 | EventRelation { 365 | sibling_id: 9, 366 | sibling_point: Vec2::new(5.0, 1.0), 367 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 0 }, 368 | ..Default::default() 369 | }, 370 | EventRelation { 371 | sibling_id: 8, 372 | sibling_point: Vec2::new(4.0, 1.0), 373 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 0 }, 374 | ..Default::default() 375 | }, 376 | EventRelation { 377 | sibling_id: 11, 378 | sibling_point: Vec2::new(6.0, 2.0), 379 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 1 }, 380 | ..Default::default() 381 | }, 382 | EventRelation { 383 | sibling_id: 10, 384 | sibling_point: Vec2::new(5.0, 1.0), 385 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 1 }, 386 | ..Default::default() 387 | }, 388 | EventRelation { 389 | sibling_id: 13, 390 | sibling_point: Vec2::new(5.0, 2.0), 391 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 2 }, 392 | ..Default::default() 393 | }, 394 | EventRelation { 395 | sibling_id: 12, 396 | sibling_point: Vec2::new(6.0, 2.0), 397 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 2 }, 398 | ..Default::default() 399 | }, 400 | EventRelation { 401 | sibling_id: 15, 402 | sibling_point: Vec2::new(4.0, 1.0), 403 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 3 }, 404 | ..Default::default() 405 | }, 406 | EventRelation { 407 | sibling_id: 14, 408 | sibling_point: Vec2::new(5.0, 2.0), 409 | source_edge: SourceEdge { is_from_subject: true, contour: 1, edge: 3 }, 410 | ..Default::default() 411 | }, 412 | ] 413 | ); 414 | } 415 | 416 | #[test] 417 | fn creates_events_for_polygon_with_x_limit() { 418 | let polygon = Polygon { 419 | contours: vec![ 420 | vec![ 421 | Vec2::new(1.0, 1.0), 422 | Vec2::new(3.0, 1.0), 423 | Vec2::new(3.0, 3.0), 424 | Vec2::new(1.0, 3.0), 425 | ], 426 | vec![ 427 | Vec2::new(4.0, 1.0), 428 | Vec2::new(5.0, 1.0), 429 | Vec2::new(6.0, 2.0), 430 | Vec2::new(5.0, 2.0), 431 | ], 432 | ], 433 | }; 434 | 435 | let mut event_queue = BinaryHeap::new(); 436 | let mut event_relations = Vec::new(); 437 | create_events_for_polygon( 438 | &polygon, 439 | /* is_subject= */ true, 440 | &mut event_queue, 441 | &mut event_relations, 442 | /* x_limit= */ 2.0, 443 | ); 444 | let event_queue = event_queue_to_vec(event_queue); 445 | assert_eq!( 446 | event_queue, 447 | [ 448 | Event { 449 | event_id: 0, 450 | point: Vec2::new(1.0, 1.0), 451 | left: true, 452 | is_subject: true, 453 | other_point: Vec2::new(3.0, 1.0), 454 | }, 455 | Event { 456 | event_id: 5, 457 | point: Vec2::new(1.0, 1.0), 458 | left: true, 459 | is_subject: true, 460 | other_point: Vec2::new(1.0, 3.0), 461 | }, 462 | Event { 463 | event_id: 4, 464 | point: Vec2::new(1.0, 3.0), 465 | left: false, 466 | is_subject: true, 467 | other_point: Vec2::new(1.0, 1.0), 468 | }, 469 | Event { 470 | event_id: 3, 471 | point: Vec2::new(1.0, 3.0), 472 | left: true, 473 | is_subject: true, 474 | other_point: Vec2::new(3.0, 3.0), 475 | }, 476 | Event { 477 | event_id: 1, 478 | point: Vec2::new(3.0, 1.0), 479 | left: false, 480 | is_subject: true, 481 | other_point: Vec2::new(1.0, 1.0), 482 | }, 483 | Event { 484 | event_id: 2, 485 | point: Vec2::new(3.0, 3.0), 486 | left: false, 487 | is_subject: true, 488 | other_point: Vec2::new(1.0, 3.0), 489 | }, 490 | ] 491 | ); 492 | assert_eq!( 493 | event_relations, 494 | [ 495 | EventRelation { 496 | sibling_id: 1, 497 | sibling_point: Vec2::new(3.0, 1.0), 498 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 499 | ..Default::default() 500 | }, 501 | EventRelation { 502 | sibling_id: 0, 503 | sibling_point: Vec2::new(1.0, 1.0), 504 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 505 | ..Default::default() 506 | }, 507 | EventRelation { 508 | sibling_id: 3, 509 | sibling_point: Vec2::new(1.0, 3.0), 510 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 511 | ..Default::default() 512 | }, 513 | EventRelation { 514 | sibling_id: 2, 515 | sibling_point: Vec2::new(3.0, 3.0), 516 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 517 | ..Default::default() 518 | }, 519 | EventRelation { 520 | sibling_id: 5, 521 | sibling_point: Vec2::new(1.0, 1.0), 522 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 523 | ..Default::default() 524 | }, 525 | EventRelation { 526 | sibling_id: 4, 527 | sibling_point: Vec2::new(1.0, 3.0), 528 | source_edge: SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 529 | ..Default::default() 530 | }, 531 | ] 532 | ); 533 | } 534 | 535 | #[test] 536 | fn splits_edges() { 537 | let mut event_queue = BinaryHeap::new(); 538 | let mut event_relations = vec![ 539 | EventRelation { 540 | sibling_id: 1, 541 | sibling_point: Vec2::new(1.0, 1.0), 542 | ..Default::default() 543 | }, 544 | EventRelation { 545 | sibling_id: 0, 546 | sibling_point: Vec2::new(0.0, 0.0), 547 | source_edge: SourceEdge { is_from_subject: true, contour: 4, edge: 20 }, 548 | ..Default::default() 549 | }, 550 | ]; 551 | 552 | const SPLIT_EDGE: Vec2 = Vec2::new(0.75, 0.75); 553 | assert_eq!( 554 | split_edge( 555 | &Event { 556 | event_id: 1, 557 | point: Vec2::new(1.0, 1.0), 558 | left: true, 559 | is_subject: true, 560 | other_point: Vec2::new(0.0, 0.0), 561 | }, 562 | SPLIT_EDGE, 563 | &mut event_queue, 564 | &mut event_relations, 565 | ), 566 | 3 567 | ); 568 | 569 | let event_queue = event_queue_to_vec(event_queue); 570 | assert_eq!( 571 | event_queue, 572 | [ 573 | Event { 574 | event_id: 2, 575 | point: SPLIT_EDGE, 576 | left: false, 577 | is_subject: true, 578 | other_point: Vec2::new(0.0, 0.0), 579 | }, 580 | Event { 581 | event_id: 3, 582 | point: SPLIT_EDGE, 583 | left: true, 584 | is_subject: true, 585 | other_point: Vec2::new(1.0, 1.0), 586 | } 587 | ] 588 | ); 589 | assert_eq!( 590 | event_relations, 591 | [ 592 | EventRelation { 593 | sibling_id: 3, 594 | sibling_point: SPLIT_EDGE, 595 | ..Default::default() 596 | }, 597 | EventRelation { 598 | sibling_id: 2, 599 | sibling_point: SPLIT_EDGE, 600 | source_edge: SourceEdge { is_from_subject: true, contour: 4, edge: 20 }, 601 | ..Default::default() 602 | }, 603 | EventRelation { 604 | sibling_id: 1, 605 | sibling_point: Vec2::new(1.0, 1.0), 606 | source_edge: SourceEdge { is_from_subject: true, contour: 4, edge: 20 }, 607 | ..Default::default() 608 | }, 609 | EventRelation { 610 | sibling_id: 0, 611 | sibling_point: Vec2::new(0.0, 0.0), 612 | source_edge: SourceEdge { is_from_subject: true, contour: 4, edge: 20 }, 613 | ..Default::default() 614 | }, 615 | ] 616 | ); 617 | } 618 | 619 | #[test] 620 | fn check_for_intersection_finds_no_intersection() { 621 | let mut event_queue = BinaryHeap::new(); 622 | let mut event_relations = vec![ 623 | EventRelation { 624 | sibling_id: 1, 625 | sibling_point: Vec2::new(3.0, 4.0), 626 | ..Default::default() 627 | }, 628 | EventRelation { 629 | sibling_id: 0, 630 | sibling_point: Vec2::new(1.0, 2.0), 631 | ..Default::default() 632 | }, 633 | EventRelation { 634 | sibling_id: 3, 635 | sibling_point: Vec2::new(3.0, 3.0), 636 | ..Default::default() 637 | }, 638 | EventRelation { 639 | sibling_id: 2, 640 | sibling_point: Vec2::new(1.0, 1.0), 641 | ..Default::default() 642 | }, 643 | ]; 644 | let expected_event_relations = event_relations.clone(); 645 | 646 | check_for_intersection( 647 | &Event { 648 | event_id: 0, 649 | point: Vec2::new(1.0, 2.0), 650 | left: true, 651 | is_subject: false, 652 | other_point: Vec2::new(3.0, 4.0), 653 | }, 654 | &Event { 655 | event_id: 2, 656 | point: Vec2::new(1.0, 1.0), 657 | left: true, 658 | is_subject: true, 659 | other_point: Vec2::new(3.0, 3.0), 660 | }, 661 | &mut event_queue, 662 | &mut event_relations, 663 | Operation::Union, 664 | ); 665 | 666 | // No new events. 667 | let event_queue = event_queue_to_vec(event_queue); 668 | assert_eq!(event_queue, []); 669 | assert_eq!(event_relations, expected_event_relations); 670 | } 671 | 672 | #[test] 673 | fn check_for_intersection_finds_point_intersection() { 674 | let mut event_queue = BinaryHeap::new(); 675 | let mut event_relations = vec![ 676 | EventRelation { 677 | sibling_id: 1, 678 | sibling_point: Vec2::new(3.0, 3.0), 679 | source_edge: SourceEdge { is_from_subject: false, contour: 4, edge: 20 }, 680 | ..Default::default() 681 | }, 682 | EventRelation { 683 | sibling_id: 0, 684 | sibling_point: Vec2::new(1.0, 2.0), 685 | ..Default::default() 686 | }, 687 | EventRelation { 688 | sibling_id: 3, 689 | sibling_point: Vec2::new(3.0, 4.0), 690 | source_edge: SourceEdge { is_from_subject: true, contour: 13, edge: 37 }, 691 | ..Default::default() 692 | }, 693 | EventRelation { 694 | sibling_id: 2, 695 | sibling_point: Vec2::new(1.0, 1.0), 696 | ..Default::default() 697 | }, 698 | ]; 699 | 700 | check_for_intersection( 701 | &Event { 702 | event_id: 0, 703 | point: Vec2::new(1.0, 2.0), 704 | left: true, 705 | is_subject: false, 706 | other_point: Vec2::new(3.0, 3.0), 707 | }, 708 | &Event { 709 | event_id: 2, 710 | point: Vec2::new(1.0, 1.0), 711 | left: true, 712 | is_subject: true, 713 | other_point: Vec2::new(3.0, 4.0), 714 | }, 715 | &mut event_queue, 716 | &mut event_relations, 717 | Operation::Union, 718 | ); 719 | 720 | let event_queue = event_queue_to_vec(event_queue); 721 | assert_eq!( 722 | event_queue, 723 | [ 724 | Event { 725 | event_id: 6, 726 | point: Vec2::new(2.0, 2.5), 727 | left: false, 728 | is_subject: false, 729 | other_point: Vec2::new(1.0, 2.0), 730 | }, 731 | Event { 732 | event_id: 4, 733 | point: Vec2::new(2.0, 2.5), 734 | left: false, 735 | is_subject: false, 736 | other_point: Vec2::new(1.0, 2.0), 737 | }, 738 | Event { 739 | event_id: 5, 740 | point: Vec2::new(2.0, 2.5), 741 | left: true, 742 | is_subject: false, 743 | other_point: Vec2::new(3.0, 3.0), 744 | }, 745 | Event { 746 | event_id: 7, 747 | point: Vec2::new(2.0, 2.5), 748 | left: true, 749 | is_subject: false, 750 | other_point: Vec2::new(3.0, 3.0), 751 | }, 752 | ] 753 | ); 754 | assert_eq!( 755 | event_relations, 756 | [ 757 | EventRelation { 758 | sibling_id: 4, 759 | sibling_point: Vec2::new(2.0, 2.5), 760 | source_edge: SourceEdge { 761 | is_from_subject: false, 762 | contour: 4, 763 | edge: 20 764 | }, 765 | ..Default::default() 766 | }, 767 | EventRelation { 768 | sibling_id: 5, 769 | sibling_point: Vec2::new(2.0, 2.5), 770 | ..Default::default() 771 | }, 772 | EventRelation { 773 | sibling_id: 6, 774 | sibling_point: Vec2::new(2.0, 2.5), 775 | source_edge: SourceEdge { 776 | is_from_subject: true, 777 | contour: 13, 778 | edge: 37 779 | }, 780 | ..Default::default() 781 | }, 782 | EventRelation { 783 | sibling_id: 7, 784 | sibling_point: Vec2::new(2.0, 2.5), 785 | ..Default::default() 786 | }, 787 | EventRelation { 788 | sibling_id: 0, 789 | sibling_point: Vec2::new(1.0, 2.0), 790 | source_edge: SourceEdge { 791 | is_from_subject: false, 792 | contour: 4, 793 | edge: 20 794 | }, 795 | ..Default::default() 796 | }, 797 | EventRelation { 798 | sibling_id: 1, 799 | sibling_point: Vec2::new(3.0, 3.0), 800 | source_edge: SourceEdge { 801 | is_from_subject: false, 802 | contour: 4, 803 | edge: 20 804 | }, 805 | ..Default::default() 806 | }, 807 | EventRelation { 808 | sibling_id: 2, 809 | sibling_point: Vec2::new(1.0, 1.0), 810 | source_edge: SourceEdge { 811 | is_from_subject: true, 812 | contour: 13, 813 | edge: 37 814 | }, 815 | ..Default::default() 816 | }, 817 | EventRelation { 818 | sibling_id: 3, 819 | sibling_point: Vec2::new(3.0, 4.0), 820 | source_edge: SourceEdge { 821 | is_from_subject: true, 822 | contour: 13, 823 | edge: 37 824 | }, 825 | ..Default::default() 826 | }, 827 | ] 828 | ); 829 | } 830 | 831 | #[test] 832 | fn check_for_intersection_finds_fully_overlapped_line() { 833 | let mut event_queue = BinaryHeap::new(); 834 | let original_event_relations = vec![ 835 | EventRelation { 836 | sibling_id: 1, 837 | sibling_point: Vec2::new(3.0, 3.0), 838 | in_out: true, 839 | prev_in_result: Some(1337), 840 | ..Default::default() 841 | }, 842 | EventRelation { 843 | sibling_id: 0, 844 | sibling_point: Vec2::new(0.0, 0.0), 845 | ..Default::default() 846 | }, 847 | EventRelation { 848 | sibling_id: 3, 849 | sibling_point: Vec2::new(2.0, 2.0), 850 | in_out: false, 851 | prev_in_result: Some(420), 852 | ..Default::default() 853 | }, 854 | EventRelation { 855 | sibling_id: 2, 856 | sibling_point: Vec2::new(1.0, 1.0), 857 | ..Default::default() 858 | }, 859 | ]; 860 | 861 | let mut event_relations = original_event_relations.clone(); 862 | check_for_intersection( 863 | &Event { 864 | event_id: 0, 865 | point: Vec2::new(0.0, 0.0), 866 | left: true, 867 | is_subject: false, 868 | other_point: Vec2::new(3.0, 3.0), 869 | }, 870 | &Event { 871 | event_id: 2, 872 | point: Vec2::new(1.0, 1.0), 873 | left: true, 874 | is_subject: true, 875 | other_point: Vec2::new(2.0, 2.0), 876 | }, 877 | &mut event_queue, 878 | &mut event_relations, 879 | Operation::Union, 880 | ); 881 | 882 | let event_queue = event_queue_to_vec(event_queue); 883 | let expected_event_queue = [ 884 | Event { 885 | event_id: 6, 886 | point: Vec2::new(1.0, 1.0), 887 | left: false, 888 | is_subject: false, 889 | other_point: Vec2::new(0.0, 0.0), 890 | }, 891 | Event { 892 | event_id: 7, 893 | point: Vec2::new(1.0, 1.0), 894 | left: true, 895 | is_subject: false, 896 | other_point: Vec2::new(2.0, 2.0), 897 | }, 898 | Event { 899 | event_id: 4, 900 | point: Vec2::new(2.0, 2.0), 901 | left: false, 902 | is_subject: false, 903 | other_point: Vec2::new(0.0, 0.0), 904 | }, 905 | Event { 906 | event_id: 5, 907 | point: Vec2::new(2.0, 2.0), 908 | left: true, 909 | is_subject: false, 910 | other_point: Vec2::new(3.0, 3.0), 911 | }, 912 | ]; 913 | assert_eq!(event_queue, expected_event_queue); 914 | let expected_event_relations = [ 915 | EventRelation { 916 | sibling_id: 6, 917 | sibling_point: Vec2::new(1.0, 1.0), 918 | in_out: true, 919 | prev_in_result: Some(1337), 920 | ..Default::default() 921 | }, 922 | EventRelation { 923 | sibling_id: 5, 924 | sibling_point: Vec2::new(2.0, 2.0), 925 | ..Default::default() 926 | }, 927 | EventRelation { 928 | sibling_id: 3, 929 | sibling_point: Vec2::new(2.0, 2.0), 930 | prev_in_result: Some(420), 931 | // Event 2 is the existing event, but is not in the result, so this is a 932 | // duplicate. 933 | edge_coincidence_type: EdgeCoincidenceType::DuplicateCoincidence, 934 | ..Default::default() 935 | }, 936 | EventRelation { 937 | sibling_id: 2, 938 | sibling_point: Vec2::new(1.0, 1.0), 939 | ..Default::default() 940 | }, 941 | EventRelation { 942 | sibling_id: 7, 943 | sibling_point: Vec2::new(1.0, 1.0), 944 | ..Default::default() 945 | }, 946 | EventRelation { 947 | sibling_id: 1, 948 | sibling_point: Vec2::new(3.0, 3.0), 949 | ..Default::default() 950 | }, 951 | EventRelation { 952 | sibling_id: 0, 953 | sibling_point: Vec2::new(0.0, 0.0), 954 | ..Default::default() 955 | }, 956 | EventRelation { 957 | sibling_id: 4, 958 | sibling_point: Vec2::new(2.0, 2.0), 959 | // The event should copy the prev_in_result from event 2. 960 | prev_in_result: Some(420), 961 | // This event comes from event 0 which is the new event, so this will be 962 | // the "primary" coincident edge. 963 | edge_coincidence_type: EdgeCoincidenceType::DifferentTransition, 964 | ..Default::default() 965 | }, 966 | ]; 967 | assert_eq!(event_relations, expected_event_relations); 968 | 969 | let mut event_queue = BinaryHeap::new(); 970 | event_relations = original_event_relations.clone(); 971 | check_for_intersection( 972 | &Event { 973 | event_id: 2, 974 | point: Vec2::new(1.0, 1.0), 975 | left: true, 976 | is_subject: true, 977 | other_point: Vec2::new(2.0, 2.0), 978 | }, 979 | &Event { 980 | event_id: 0, 981 | point: Vec2::new(0.0, 0.0), 982 | left: true, 983 | is_subject: false, 984 | other_point: Vec2::new(3.0, 3.0), 985 | }, 986 | &mut event_queue, 987 | &mut event_relations, 988 | Operation::Union, 989 | ); 990 | 991 | let event_queue = event_queue_to_vec(event_queue); 992 | assert_eq!(event_queue, expected_event_queue); 993 | assert_eq!( 994 | event_relations, 995 | [ 996 | expected_event_relations[0].clone(), 997 | expected_event_relations[1].clone(), 998 | EventRelation { 999 | sibling_id: 3, 1000 | sibling_point: Vec2::new(2.0, 2.0), 1001 | // `prev_in_result` was copied from event 0, since that is the existing 1002 | // event. 1003 | prev_in_result: Some(1337), 1004 | // Event 2 is the new event (and the existing event is not in the 1005 | // result), so it will be the "primary" coincident edge. 1006 | edge_coincidence_type: EdgeCoincidenceType::DifferentTransition, 1007 | ..Default::default() 1008 | }, 1009 | expected_event_relations[3].clone(), 1010 | expected_event_relations[4].clone(), 1011 | expected_event_relations[5].clone(), 1012 | expected_event_relations[6].clone(), 1013 | EventRelation { 1014 | sibling_id: 4, 1015 | sibling_point: Vec2::new(2.0, 2.0), 1016 | prev_in_result: None, 1017 | edge_coincidence_type: EdgeCoincidenceType::DuplicateCoincidence, 1018 | ..Default::default() 1019 | }, 1020 | ] 1021 | ); 1022 | } 1023 | 1024 | #[test] 1025 | fn check_for_intersection_finds_partially_overlapped_lines() { 1026 | let mut event_queue = BinaryHeap::new(); 1027 | let original_event_relations = vec![ 1028 | EventRelation { 1029 | sibling_id: 1, 1030 | sibling_point: Vec2::new(2.0, 2.0), 1031 | in_out: false, 1032 | prev_in_result: Some(1337), 1033 | ..Default::default() 1034 | }, 1035 | EventRelation { 1036 | sibling_id: 0, 1037 | sibling_point: Vec2::new(0.0, 0.0), 1038 | ..Default::default() 1039 | }, 1040 | EventRelation { 1041 | sibling_id: 3, 1042 | sibling_point: Vec2::new(3.0, 3.0), 1043 | in_out: false, 1044 | in_result: true, 1045 | prev_in_result: Some(420), 1046 | ..Default::default() 1047 | }, 1048 | EventRelation { 1049 | sibling_id: 2, 1050 | sibling_point: Vec2::new(1.0, 1.0), 1051 | ..Default::default() 1052 | }, 1053 | ]; 1054 | 1055 | let mut event_relations = original_event_relations.clone(); 1056 | check_for_intersection( 1057 | &Event { 1058 | event_id: 0, 1059 | point: Vec2::new(0.0, 0.0), 1060 | left: true, 1061 | is_subject: false, 1062 | other_point: Vec2::new(2.0, 2.0), 1063 | }, 1064 | &Event { 1065 | event_id: 2, 1066 | point: Vec2::new(1.0, 1.0), 1067 | left: true, 1068 | is_subject: true, 1069 | other_point: Vec2::new(3.0, 3.0), 1070 | }, 1071 | &mut event_queue, 1072 | &mut event_relations, 1073 | Operation::Intersection, 1074 | ); 1075 | 1076 | let event_queue = event_queue_to_vec(event_queue); 1077 | assert_eq!( 1078 | event_queue, 1079 | [ 1080 | Event { 1081 | event_id: 4, 1082 | point: Vec2::new(1.0, 1.0), 1083 | left: false, 1084 | is_subject: false, 1085 | other_point: Vec2::new(0.0, 0.0), 1086 | }, 1087 | Event { 1088 | event_id: 5, 1089 | point: Vec2::new(1.0, 1.0), 1090 | left: true, 1091 | is_subject: false, 1092 | other_point: Vec2::new(3.0, 3.0), 1093 | }, 1094 | Event { 1095 | event_id: 6, 1096 | point: Vec2::new(2.0, 2.0), 1097 | left: false, 1098 | is_subject: false, 1099 | other_point: Vec2::new(0.0, 0.0), 1100 | }, 1101 | Event { 1102 | event_id: 7, 1103 | point: Vec2::new(2.0, 2.0), 1104 | left: true, 1105 | is_subject: false, 1106 | other_point: Vec2::new(3.0, 3.0), 1107 | }, 1108 | ] 1109 | ); 1110 | assert_eq!( 1111 | event_relations, 1112 | [ 1113 | EventRelation { 1114 | sibling_id: 4, 1115 | sibling_point: Vec2::new(1.0, 1.0), 1116 | prev_in_result: Some(1337), 1117 | ..Default::default() 1118 | }, 1119 | EventRelation { 1120 | sibling_id: 5, 1121 | sibling_point: Vec2::new(1.0, 1.0), 1122 | ..Default::default() 1123 | }, 1124 | EventRelation { 1125 | sibling_id: 6, 1126 | sibling_point: Vec2::new(2.0, 2.0), 1127 | prev_in_result: Some(420), 1128 | // The operation is intersection, so two edges means the "primary" edge 1129 | // is in the result. 1130 | in_result: true, 1131 | edge_coincidence_type: EdgeCoincidenceType::SameTransition, 1132 | ..Default::default() 1133 | }, 1134 | EventRelation { 1135 | sibling_id: 7, 1136 | sibling_point: Vec2::new(2.0, 2.0), 1137 | ..Default::default() 1138 | }, 1139 | EventRelation { 1140 | sibling_id: 0, 1141 | sibling_point: Vec2::new(0.0, 0.0), 1142 | ..Default::default() 1143 | }, 1144 | EventRelation { 1145 | sibling_id: 1, 1146 | sibling_point: Vec2::new(2.0, 2.0), 1147 | prev_in_result: Some(420), 1148 | edge_coincidence_type: EdgeCoincidenceType::DuplicateCoincidence, 1149 | ..Default::default() 1150 | }, 1151 | EventRelation { 1152 | sibling_id: 2, 1153 | sibling_point: Vec2::new(1.0, 1.0), 1154 | ..Default::default() 1155 | }, 1156 | EventRelation { 1157 | sibling_id: 3, 1158 | sibling_point: Vec2::new(3.0, 3.0), 1159 | ..Default::default() 1160 | }, 1161 | ] 1162 | ); 1163 | 1164 | let mut event_queue = BinaryHeap::new(); 1165 | event_relations = original_event_relations.clone(); 1166 | check_for_intersection( 1167 | &Event { 1168 | event_id: 2, 1169 | point: Vec2::new(1.0, 1.0), 1170 | left: true, 1171 | is_subject: true, 1172 | other_point: Vec2::new(3.0, 3.0), 1173 | }, 1174 | &Event { 1175 | event_id: 0, 1176 | point: Vec2::new(0.0, 0.0), 1177 | left: true, 1178 | is_subject: false, 1179 | other_point: Vec2::new(2.0, 2.0), 1180 | }, 1181 | &mut event_queue, 1182 | &mut event_relations, 1183 | Operation::Difference, 1184 | ); 1185 | 1186 | let event_queue = event_queue_to_vec(event_queue); 1187 | assert_eq!( 1188 | event_queue, 1189 | [ 1190 | Event { 1191 | event_id: 6, 1192 | point: Vec2::new(1.0, 1.0), 1193 | left: false, 1194 | is_subject: false, 1195 | other_point: Vec2::new(0.0, 0.0), 1196 | }, 1197 | Event { 1198 | event_id: 7, 1199 | point: Vec2::new(1.0, 1.0), 1200 | left: true, 1201 | is_subject: false, 1202 | other_point: Vec2::new(2.0, 2.0), 1203 | }, 1204 | Event { 1205 | event_id: 4, 1206 | point: Vec2::new(2.0, 2.0), 1207 | left: false, 1208 | is_subject: true, 1209 | other_point: Vec2::new(1.0, 1.0), 1210 | }, 1211 | Event { 1212 | event_id: 5, 1213 | point: Vec2::new(2.0, 2.0), 1214 | left: true, 1215 | is_subject: true, 1216 | other_point: Vec2::new(3.0, 3.0), 1217 | }, 1218 | ] 1219 | ); 1220 | assert_eq!( 1221 | event_relations, 1222 | [ 1223 | EventRelation { 1224 | sibling_id: 6, 1225 | sibling_point: Vec2::new(1.0, 1.0), 1226 | prev_in_result: Some(1337), 1227 | ..Default::default() 1228 | }, 1229 | EventRelation { 1230 | sibling_id: 7, 1231 | sibling_point: Vec2::new(1.0, 1.0), 1232 | ..Default::default() 1233 | }, 1234 | EventRelation { 1235 | sibling_id: 4, 1236 | sibling_point: Vec2::new(2.0, 2.0), 1237 | // `prev_in_result` was copied from event 0. 1238 | prev_in_result: Some(1337), 1239 | // Event 0 is not in the result, so this event is chosen as the 1240 | // "primary" coincident event. 1241 | edge_coincidence_type: EdgeCoincidenceType::SameTransition, 1242 | // Cleared because the operation is difference. 1243 | in_result: false, 1244 | ..Default::default() 1245 | }, 1246 | EventRelation { 1247 | sibling_id: 5, 1248 | sibling_point: Vec2::new(2.0, 2.0), 1249 | ..Default::default() 1250 | }, 1251 | EventRelation { 1252 | sibling_id: 2, 1253 | sibling_point: Vec2::new(1.0, 1.0), 1254 | ..Default::default() 1255 | }, 1256 | EventRelation { 1257 | sibling_id: 3, 1258 | sibling_point: Vec2::new(3.0, 3.0), 1259 | ..Default::default() 1260 | }, 1261 | EventRelation { 1262 | sibling_id: 0, 1263 | sibling_point: Vec2::new(0.0, 0.0), 1264 | ..Default::default() 1265 | }, 1266 | EventRelation { 1267 | sibling_id: 1, 1268 | sibling_point: Vec2::new(2.0, 2.0), 1269 | edge_coincidence_type: EdgeCoincidenceType::DuplicateCoincidence, 1270 | ..Default::default() 1271 | }, 1272 | ] 1273 | ); 1274 | } 1275 | 1276 | #[test] 1277 | fn boolean_of_rhombuses() { 1278 | let subject = Polygon { 1279 | contours: vec![vec![ 1280 | Vec2::new(1.0, 1.0), 1281 | Vec2::new(3.5, 1.0), 1282 | Vec2::new(5.0, 3.0), 1283 | Vec2::new(3.0, 3.0), 1284 | ]], 1285 | }; 1286 | let clip = Polygon { 1287 | contours: vec![vec![ 1288 | Vec2::new(3.0, 2.0), 1289 | Vec2::new(5.0, 2.0), 1290 | Vec2::new(7.0, 4.0), 1291 | Vec2::new(5.0, 4.0), 1292 | ]], 1293 | }; 1294 | 1295 | assert_eq!( 1296 | union(&subject, &clip), 1297 | BooleanResult { 1298 | polygon: Polygon { 1299 | contours: vec![vec![ 1300 | Vec2::new(1.0, 1.0), 1301 | Vec2::new(3.5, 1.0), 1302 | Vec2::new(4.25, 2.0), 1303 | Vec2::new(5.0, 2.0), 1304 | Vec2::new(7.0, 4.0), 1305 | Vec2::new(5.0, 4.0), 1306 | Vec2::new(4.0, 3.0), 1307 | Vec2::new(3.0, 3.0), 1308 | ]] 1309 | }, 1310 | contour_source_edges: vec![vec![ 1311 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1312 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1313 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1314 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1315 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1316 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1317 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1318 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1319 | ]], 1320 | } 1321 | ); 1322 | 1323 | assert_eq!( 1324 | intersection(&subject, &clip), 1325 | BooleanResult { 1326 | polygon: Polygon { 1327 | contours: vec![vec![ 1328 | Vec2::new(3.0, 2.0), 1329 | Vec2::new(4.25, 2.0), 1330 | Vec2::new(5.0, 3.0), 1331 | Vec2::new(4.0, 3.0), 1332 | ]] 1333 | }, 1334 | contour_source_edges: vec![vec![ 1335 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1336 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1337 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1338 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1339 | ]], 1340 | } 1341 | ); 1342 | 1343 | assert_eq!( 1344 | difference(&subject, &clip), 1345 | BooleanResult { 1346 | polygon: Polygon { 1347 | contours: vec![vec![ 1348 | Vec2::new(1.0, 1.0), 1349 | Vec2::new(3.5, 1.0), 1350 | Vec2::new(4.25, 2.0), 1351 | Vec2::new(3.0, 2.0), 1352 | Vec2::new(4.0, 3.0), 1353 | Vec2::new(3.0, 3.0), 1354 | ]] 1355 | }, 1356 | contour_source_edges: vec![vec![ 1357 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1358 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1359 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1360 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1361 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1362 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1363 | ]], 1364 | } 1365 | ); 1366 | 1367 | assert_eq!( 1368 | xor(&subject, &clip), 1369 | BooleanResult { 1370 | polygon: Polygon { 1371 | contours: vec![ 1372 | vec![ 1373 | Vec2::new(1.0, 1.0), 1374 | Vec2::new(3.5, 1.0), 1375 | Vec2::new(4.25, 2.0), 1376 | Vec2::new(3.0, 2.0), 1377 | Vec2::new(4.0, 3.0), 1378 | Vec2::new(3.0, 3.0), 1379 | ], 1380 | vec![ 1381 | Vec2::new(4.0, 3.0), 1382 | Vec2::new(5.0, 3.0), 1383 | Vec2::new(4.25, 2.0), 1384 | Vec2::new(5.0, 2.0), 1385 | Vec2::new(7.0, 4.0), 1386 | Vec2::new(5.0, 4.0), 1387 | ] 1388 | ] 1389 | }, 1390 | contour_source_edges: vec![ 1391 | vec![ 1392 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1393 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1394 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1395 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1396 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1397 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1398 | ], 1399 | vec![ 1400 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1401 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1402 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1403 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1404 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1405 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1406 | ] 1407 | ], 1408 | } 1409 | ); 1410 | } 1411 | 1412 | #[test] 1413 | fn boolean_of_squares() { 1414 | let subject = Polygon { 1415 | contours: vec![vec![ 1416 | Vec2::new(1.0, 1.0), 1417 | Vec2::new(3.0, 1.0), 1418 | Vec2::new(3.0, 3.0), 1419 | Vec2::new(1.0, 3.0), 1420 | ]], 1421 | }; 1422 | let clip = Polygon { 1423 | contours: vec![vec![ 1424 | Vec2::new(2.0, 2.0), 1425 | Vec2::new(4.0, 2.0), 1426 | Vec2::new(4.0, 4.0), 1427 | Vec2::new(2.0, 4.0), 1428 | ]], 1429 | }; 1430 | 1431 | assert_eq!( 1432 | union(&subject, &clip), 1433 | BooleanResult { 1434 | polygon: Polygon { 1435 | contours: vec![vec![ 1436 | Vec2::new(1.0, 1.0), 1437 | Vec2::new(3.0, 1.0), 1438 | Vec2::new(3.0, 2.0), 1439 | Vec2::new(4.0, 2.0), 1440 | Vec2::new(4.0, 4.0), 1441 | Vec2::new(2.0, 4.0), 1442 | Vec2::new(2.0, 3.0), 1443 | Vec2::new(1.0, 3.0), 1444 | ]] 1445 | }, 1446 | contour_source_edges: vec![vec![ 1447 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1448 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1449 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1450 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1451 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1452 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1453 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1454 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1455 | ]], 1456 | } 1457 | ); 1458 | 1459 | assert_eq!( 1460 | intersection(&subject, &clip), 1461 | BooleanResult { 1462 | polygon: Polygon { 1463 | contours: vec![vec![ 1464 | Vec2::new(2.0, 2.0), 1465 | Vec2::new(3.0, 2.0), 1466 | Vec2::new(3.0, 3.0), 1467 | Vec2::new(2.0, 3.0), 1468 | ]] 1469 | }, 1470 | contour_source_edges: vec![vec![ 1471 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1472 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1473 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1474 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1475 | ]], 1476 | } 1477 | ); 1478 | 1479 | assert_eq!( 1480 | difference(&subject, &clip), 1481 | BooleanResult { 1482 | polygon: Polygon { 1483 | contours: vec![vec![ 1484 | Vec2::new(1.0, 1.0), 1485 | Vec2::new(3.0, 1.0), 1486 | Vec2::new(3.0, 2.0), 1487 | Vec2::new(2.0, 2.0), 1488 | Vec2::new(2.0, 3.0), 1489 | Vec2::new(1.0, 3.0), 1490 | ]] 1491 | }, 1492 | contour_source_edges: vec![vec![ 1493 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1494 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1495 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1496 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1497 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1498 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1499 | ]], 1500 | } 1501 | ); 1502 | 1503 | assert_eq!( 1504 | xor(&subject, &clip), 1505 | BooleanResult { 1506 | polygon: Polygon { 1507 | contours: vec![ 1508 | vec![ 1509 | Vec2::new(1.0, 1.0), 1510 | Vec2::new(3.0, 1.0), 1511 | Vec2::new(3.0, 2.0), 1512 | Vec2::new(2.0, 2.0), 1513 | Vec2::new(2.0, 3.0), 1514 | Vec2::new(1.0, 3.0), 1515 | ], 1516 | vec![ 1517 | Vec2::new(2.0, 3.0), 1518 | Vec2::new(3.0, 3.0), 1519 | Vec2::new(3.0, 2.0), 1520 | Vec2::new(4.0, 2.0), 1521 | Vec2::new(4.0, 4.0), 1522 | Vec2::new(2.0, 4.0), 1523 | ] 1524 | ] 1525 | }, 1526 | contour_source_edges: vec![ 1527 | vec![ 1528 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1529 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1530 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1531 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1532 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1533 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1534 | ], 1535 | vec![ 1536 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1537 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1538 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1539 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1540 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1541 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1542 | ] 1543 | ], 1544 | } 1545 | ); 1546 | } 1547 | 1548 | #[test] 1549 | fn add_and_remove_squares() { 1550 | let subject = Polygon { 1551 | contours: vec![vec![ 1552 | Vec2::new(1.0, 1.0), 1553 | Vec2::new(3.0, 1.0), 1554 | Vec2::new(3.0, 3.0), 1555 | Vec2::new(1.0, 3.0), 1556 | ]], 1557 | }; 1558 | let clip = Polygon { 1559 | contours: vec![vec![ 1560 | Vec2::new(1.0, 1.0), 1561 | Vec2::new(2.0, 1.0), 1562 | Vec2::new(2.0, 2.0), 1563 | Vec2::new(1.0, 2.0), 1564 | ]], 1565 | }; 1566 | 1567 | // All boolean operations between the clip and the subject. 1568 | assert_eq!( 1569 | intersection(&subject, &clip), 1570 | BooleanResult { 1571 | polygon: clip.clone(), 1572 | contour_source_edges: vec![vec![ 1573 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1574 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1575 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1576 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1577 | ]], 1578 | } 1579 | ); 1580 | let expected_union = Polygon { 1581 | contours: vec![vec![ 1582 | Vec2::new(1.0, 1.0), 1583 | Vec2::new(2.0, 1.0), 1584 | Vec2::new(3.0, 1.0), 1585 | Vec2::new(3.0, 3.0), 1586 | Vec2::new(1.0, 3.0), 1587 | Vec2::new(1.0, 2.0), 1588 | ]], 1589 | }; 1590 | assert_eq!( 1591 | union(&subject, &clip), 1592 | BooleanResult { 1593 | polygon: expected_union.clone(), 1594 | contour_source_edges: vec![vec![ 1595 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1596 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1597 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1598 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1599 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1600 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1601 | ]], 1602 | } 1603 | ); 1604 | 1605 | let expected_difference = BooleanResult { 1606 | polygon: Polygon { 1607 | contours: vec![vec![ 1608 | Vec2::new(1.0, 2.0), 1609 | Vec2::new(2.0, 2.0), 1610 | Vec2::new(2.0, 1.0), 1611 | Vec2::new(3.0, 1.0), 1612 | Vec2::new(3.0, 3.0), 1613 | Vec2::new(1.0, 3.0), 1614 | ]], 1615 | }, 1616 | contour_source_edges: vec![vec![ 1617 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1618 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1619 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1620 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1621 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1622 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1623 | ]], 1624 | }; 1625 | assert_eq!(difference(&subject, &clip), expected_difference); 1626 | 1627 | let xor_result = xor(&subject, &clip); 1628 | assert_eq!(xor_result, expected_difference); 1629 | 1630 | assert_eq!( 1631 | intersection(&xor_result.polygon, &clip), 1632 | BooleanResult { 1633 | polygon: Polygon { contours: vec![] }, 1634 | contour_source_edges: vec![] 1635 | } 1636 | ); 1637 | assert_eq!( 1638 | union(&xor_result.polygon, &clip), 1639 | BooleanResult { 1640 | polygon: expected_union, 1641 | contour_source_edges: vec![vec![ 1642 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1643 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1644 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1645 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1646 | SourceEdge { is_from_subject: true, contour: 0, edge: 5 }, 1647 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1648 | ]], 1649 | } 1650 | ); 1651 | } 1652 | 1653 | #[test] 1654 | fn cut_and_fill_hole() { 1655 | let subject = Polygon { 1656 | contours: vec![vec![ 1657 | Vec2::new(1.0, 1.0), 1658 | Vec2::new(5.0, 1.0), 1659 | Vec2::new(5.0, 5.0), 1660 | Vec2::new(1.0, 5.0), 1661 | ]], 1662 | }; 1663 | let clip = Polygon { 1664 | contours: vec![vec![ 1665 | Vec2::new(2.0, 2.0), 1666 | Vec2::new(4.0, 2.0), 1667 | Vec2::new(4.0, 4.0), 1668 | Vec2::new(2.0, 4.0), 1669 | ]], 1670 | }; 1671 | 1672 | let expected_subject_with_hole = Polygon { 1673 | contours: vec![ 1674 | vec![ 1675 | Vec2::new(1.0, 1.0), 1676 | Vec2::new(5.0, 1.0), 1677 | Vec2::new(5.0, 5.0), 1678 | Vec2::new(1.0, 5.0), 1679 | ], 1680 | vec![ 1681 | Vec2::new(2.0, 4.0), 1682 | Vec2::new(4.0, 4.0), 1683 | Vec2::new(4.0, 2.0), 1684 | Vec2::new(2.0, 2.0), 1685 | ], 1686 | ], 1687 | }; 1688 | 1689 | let expected_difference = BooleanResult { 1690 | polygon: expected_subject_with_hole.clone(), 1691 | contour_source_edges: vec![ 1692 | vec![ 1693 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1694 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1695 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1696 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1697 | ], 1698 | vec![ 1699 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1700 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1701 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1702 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1703 | ], 1704 | ], 1705 | }; 1706 | assert_eq!(difference(&subject, &clip), expected_difference); 1707 | assert_eq!(xor(&subject, &clip), expected_difference); 1708 | 1709 | assert_eq!( 1710 | union(&expected_subject_with_hole, &clip), 1711 | BooleanResult { 1712 | polygon: subject.clone(), 1713 | contour_source_edges: vec![vec![ 1714 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1715 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1716 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1717 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1718 | ]] 1719 | } 1720 | ); 1721 | assert_eq!( 1722 | xor(&expected_subject_with_hole, &clip), 1723 | BooleanResult { 1724 | polygon: subject.clone(), 1725 | contour_source_edges: vec![vec![ 1726 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1727 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1728 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1729 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1730 | ]] 1731 | } 1732 | ); 1733 | 1734 | assert_eq!( 1735 | union(&expected_subject_with_hole, &subject), 1736 | BooleanResult { 1737 | polygon: subject.clone(), 1738 | contour_source_edges: vec![vec![ 1739 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1740 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1741 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1742 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1743 | ]] 1744 | } 1745 | ); 1746 | assert_eq!( 1747 | xor(&expected_subject_with_hole, &subject), 1748 | BooleanResult { 1749 | polygon: clip, 1750 | contour_source_edges: vec![vec![ 1751 | SourceEdge { is_from_subject: true, contour: 1, edge: 2 }, 1752 | SourceEdge { is_from_subject: true, contour: 1, edge: 1 }, 1753 | SourceEdge { is_from_subject: true, contour: 1, edge: 0 }, 1754 | SourceEdge { is_from_subject: true, contour: 1, edge: 3 }, 1755 | ]] 1756 | } 1757 | ); 1758 | } 1759 | 1760 | #[test] 1761 | fn partially_overlapping_edges_are_split() { 1762 | let subject = Polygon { 1763 | contours: vec![vec![ 1764 | Vec2::new(1.0, 1.0), 1765 | Vec2::new(2.5, 1.0), 1766 | Vec2::new(4.0, 1.0), 1767 | Vec2::new(4.0, 4.0), 1768 | Vec2::new(3.9, 4.0), 1769 | Vec2::new(1.1, 4.0), 1770 | Vec2::new(1.0, 4.0), 1771 | ]], 1772 | }; 1773 | let clip = Polygon { 1774 | contours: vec![vec![ 1775 | Vec2::new(2.0, 1.0), 1776 | Vec2::new(3.0, 1.0), 1777 | Vec2::new(4.0, 2.0), 1778 | Vec2::new(4.0, 3.0), 1779 | Vec2::new(3.0, 4.0), 1780 | Vec2::new(2.0, 4.0), 1781 | Vec2::new(1.0, 3.0), 1782 | Vec2::new(1.0, 2.0), 1783 | ]], 1784 | }; 1785 | 1786 | let subdivided_subject = Polygon { 1787 | contours: vec![vec![ 1788 | Vec2::new(1.0, 1.0), 1789 | Vec2::new(2.0, 1.0), 1790 | Vec2::new(2.5, 1.0), 1791 | Vec2::new(3.0, 1.0), 1792 | Vec2::new(4.0, 1.0), 1793 | Vec2::new(4.0, 2.0), 1794 | Vec2::new(4.0, 3.0), 1795 | Vec2::new(4.0, 4.0), 1796 | Vec2::new(3.9, 4.0), 1797 | Vec2::new(3.0, 4.0), 1798 | Vec2::new(2.0, 4.0), 1799 | Vec2::new(1.1, 4.0), 1800 | Vec2::new(1.0, 4.0), 1801 | Vec2::new(1.0, 3.0), 1802 | Vec2::new(1.0, 2.0), 1803 | ]], 1804 | }; 1805 | let subdivided_clip = Polygon { 1806 | contours: vec![vec![ 1807 | Vec2::new(1.0, 2.0), 1808 | Vec2::new(2.0, 1.0), 1809 | Vec2::new(2.5, 1.0), 1810 | Vec2::new(3.0, 1.0), 1811 | Vec2::new(4.0, 2.0), 1812 | Vec2::new(4.0, 3.0), 1813 | Vec2::new(3.0, 4.0), 1814 | Vec2::new(2.0, 4.0), 1815 | Vec2::new(1.0, 3.0), 1816 | ]], 1817 | }; 1818 | 1819 | assert_eq!( 1820 | intersection(&subject, &clip), 1821 | BooleanResult { 1822 | polygon: subdivided_clip.clone(), 1823 | contour_source_edges: vec![vec![ 1824 | SourceEdge { is_from_subject: false, contour: 0, edge: 7 }, 1825 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1826 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1827 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1828 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1829 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1830 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1831 | SourceEdge { is_from_subject: false, contour: 0, edge: 5 }, 1832 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1833 | ]], 1834 | } 1835 | ); 1836 | assert_eq!( 1837 | intersection(&clip, &subject), 1838 | BooleanResult { 1839 | polygon: subdivided_clip, 1840 | contour_source_edges: vec![vec![ 1841 | SourceEdge { is_from_subject: true, contour: 0, edge: 7 }, 1842 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1843 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1844 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1845 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1846 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1847 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1848 | SourceEdge { is_from_subject: true, contour: 0, edge: 5 }, 1849 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1850 | ]], 1851 | } 1852 | ); 1853 | assert_eq!( 1854 | union(&subject, &clip), 1855 | BooleanResult { 1856 | polygon: subdivided_subject.clone(), 1857 | contour_source_edges: vec![vec![ 1858 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1859 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1860 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1861 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1862 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1863 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1864 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1865 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1866 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1867 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1868 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1869 | SourceEdge { is_from_subject: true, contour: 0, edge: 5 }, 1870 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1871 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1872 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1873 | ]], 1874 | } 1875 | ); 1876 | assert_eq!( 1877 | union(&clip, &subject), 1878 | BooleanResult { 1879 | polygon: subdivided_subject, 1880 | contour_source_edges: vec![vec![ 1881 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 1882 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1883 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1884 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1885 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1886 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1887 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 1888 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1889 | SourceEdge { is_from_subject: false, contour: 0, edge: 4 }, 1890 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1891 | SourceEdge { is_from_subject: false, contour: 0, edge: 4 }, 1892 | SourceEdge { is_from_subject: false, contour: 0, edge: 5 }, 1893 | SourceEdge { is_from_subject: false, contour: 0, edge: 6 }, 1894 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1895 | SourceEdge { is_from_subject: false, contour: 0, edge: 6 }, 1896 | ]], 1897 | } 1898 | ); 1899 | assert_eq!( 1900 | difference(&subject, &clip), 1901 | BooleanResult { 1902 | polygon: Polygon { 1903 | contours: vec![ 1904 | vec![Vec2::new(1.0, 1.0), Vec2::new(2.0, 1.0), Vec2::new(1.0, 2.0)], 1905 | vec![ 1906 | Vec2::new(1.0, 3.0), 1907 | Vec2::new(2.0, 4.0), 1908 | Vec2::new(1.1, 4.0), 1909 | Vec2::new(1.0, 4.0), 1910 | ], 1911 | vec![Vec2::new(3.0, 1.0), Vec2::new(4.0, 1.0), Vec2::new(4.0, 2.0)], 1912 | vec![ 1913 | Vec2::new(3.0, 4.0), 1914 | Vec2::new(4.0, 3.0), 1915 | Vec2::new(4.0, 4.0), 1916 | Vec2::new(3.9, 4.0), 1917 | ], 1918 | ] 1919 | }, 1920 | contour_source_edges: vec![ 1921 | vec![ 1922 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 1923 | SourceEdge { is_from_subject: false, contour: 0, edge: 7 }, 1924 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1925 | ], 1926 | vec![ 1927 | SourceEdge { is_from_subject: false, contour: 0, edge: 5 }, 1928 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1929 | SourceEdge { is_from_subject: true, contour: 0, edge: 5 }, 1930 | SourceEdge { is_from_subject: true, contour: 0, edge: 6 }, 1931 | ], 1932 | vec![ 1933 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 1934 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1935 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 1936 | ], 1937 | vec![ 1938 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 1939 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 1940 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 1941 | SourceEdge { is_from_subject: true, contour: 0, edge: 4 }, 1942 | ] 1943 | ], 1944 | } 1945 | ); 1946 | assert_eq!( 1947 | difference(&clip, &subject), 1948 | BooleanResult { 1949 | polygon: Polygon { contours: vec![] }, 1950 | contour_source_edges: vec![], 1951 | } 1952 | ); 1953 | } 1954 | 1955 | #[test] 1956 | fn trivially_computes_operations_for_disjoint_bounding_boxes() { 1957 | let subject = Polygon { 1958 | contours: vec![ 1959 | vec![ 1960 | Vec2::new(1.0, 1.0), 1961 | Vec2::new(2.0, 1.0), 1962 | Vec2::new(2.0, 2.0), 1963 | Vec2::new(1.0, 2.0), 1964 | ], 1965 | // Empty contour to check that the original polygon is used "verbatim". 1966 | vec![], 1967 | vec![ 1968 | Vec2::new(2.5, 2.5), 1969 | Vec2::new(3.5, 2.5), 1970 | Vec2::new(3.5, 3.5), 1971 | Vec2::new(2.5, 3.5), 1972 | ], 1973 | ], 1974 | }; 1975 | 1976 | let clip = Polygon { 1977 | contours: vec![vec![ 1978 | Vec2::new(-2.0, 1.0), 1979 | Vec2::new(-1.0, 1.0), 1980 | Vec2::new(-1.0, 2.0), 1981 | Vec2::new(-2.0, 2.0), 1982 | ]], 1983 | }; 1984 | 1985 | let expected_union = BooleanResult { 1986 | polygon: Polygon { 1987 | contours: vec![ 1988 | // Subject contours. 1989 | vec![ 1990 | Vec2::new(1.0, 1.0), 1991 | Vec2::new(2.0, 1.0), 1992 | Vec2::new(2.0, 2.0), 1993 | Vec2::new(1.0, 2.0), 1994 | ], 1995 | vec![], 1996 | vec![ 1997 | Vec2::new(2.5, 2.5), 1998 | Vec2::new(3.5, 2.5), 1999 | Vec2::new(3.5, 3.5), 2000 | Vec2::new(2.5, 3.5), 2001 | ], 2002 | // Clip contours. 2003 | vec![ 2004 | Vec2::new(-2.0, 1.0), 2005 | Vec2::new(-1.0, 1.0), 2006 | Vec2::new(-1.0, 2.0), 2007 | Vec2::new(-2.0, 2.0), 2008 | ], 2009 | ], 2010 | }, 2011 | contour_source_edges: vec![ 2012 | vec![ 2013 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 2014 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2015 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2016 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 2017 | ], 2018 | vec![], 2019 | vec![ 2020 | SourceEdge { is_from_subject: true, contour: 2, edge: 0 }, 2021 | SourceEdge { is_from_subject: true, contour: 2, edge: 1 }, 2022 | SourceEdge { is_from_subject: true, contour: 2, edge: 2 }, 2023 | SourceEdge { is_from_subject: true, contour: 2, edge: 3 }, 2024 | ], 2025 | vec![ 2026 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 2027 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 2028 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 2029 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 2030 | ], 2031 | ], 2032 | }; 2033 | assert_eq!(union(&subject, &clip), expected_union); 2034 | assert_eq!(xor(&subject, &clip), expected_union); 2035 | 2036 | assert_eq!( 2037 | intersection(&subject, &clip), 2038 | BooleanResult { 2039 | polygon: Polygon { contours: vec![] }, 2040 | contour_source_edges: vec![], 2041 | } 2042 | ); 2043 | assert_eq!( 2044 | difference(&subject, &clip), 2045 | BooleanResult { 2046 | polygon: subject.clone(), 2047 | contour_source_edges: vec![ 2048 | vec![ 2049 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 2050 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2051 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2052 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 2053 | ], 2054 | vec![], 2055 | vec![ 2056 | SourceEdge { is_from_subject: true, contour: 2, edge: 0 }, 2057 | SourceEdge { is_from_subject: true, contour: 2, edge: 1 }, 2058 | SourceEdge { is_from_subject: true, contour: 2, edge: 2 }, 2059 | SourceEdge { is_from_subject: true, contour: 2, edge: 3 }, 2060 | ], 2061 | ], 2062 | } 2063 | ); 2064 | } 2065 | 2066 | #[test] 2067 | fn trivially_computes_operations_for_empty_polygons() { 2068 | let non_empty_polygon = Polygon { 2069 | contours: vec![ 2070 | vec![ 2071 | Vec2::new(1.0, 1.0), 2072 | Vec2::new(2.0, 1.0), 2073 | Vec2::new(2.0, 2.0), 2074 | Vec2::new(1.0, 2.0), 2075 | ], 2076 | // Empty contour to check that the original polygon is used "verbatim". 2077 | vec![], 2078 | vec![ 2079 | Vec2::new(2.5, 2.5), 2080 | Vec2::new(3.5, 2.5), 2081 | Vec2::new(3.5, 3.5), 2082 | Vec2::new(2.5, 3.5), 2083 | ], 2084 | ], 2085 | }; 2086 | 2087 | let empty_polygon = Polygon { 2088 | contours: vec![ 2089 | // Empty contour to ensure this doesn't count. 2090 | vec![], 2091 | ], 2092 | }; 2093 | 2094 | let empty_boolean_result = BooleanResult { 2095 | polygon: Polygon { contours: vec![] }, 2096 | contour_source_edges: vec![], 2097 | }; 2098 | let non_empty_boolean_result_as_subject = BooleanResult { 2099 | polygon: non_empty_polygon.clone(), 2100 | contour_source_edges: vec![ 2101 | vec![ 2102 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 2103 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2104 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2105 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 2106 | ], 2107 | vec![], 2108 | vec![ 2109 | SourceEdge { is_from_subject: true, contour: 2, edge: 0 }, 2110 | SourceEdge { is_from_subject: true, contour: 2, edge: 1 }, 2111 | SourceEdge { is_from_subject: true, contour: 2, edge: 2 }, 2112 | SourceEdge { is_from_subject: true, contour: 2, edge: 3 }, 2113 | ], 2114 | ], 2115 | }; 2116 | let non_empty_boolean_result_as_clip = BooleanResult { 2117 | polygon: non_empty_polygon.clone(), 2118 | contour_source_edges: vec![ 2119 | vec![ 2120 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 2121 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 2122 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 2123 | SourceEdge { is_from_subject: false, contour: 0, edge: 3 }, 2124 | ], 2125 | vec![], 2126 | vec![ 2127 | SourceEdge { is_from_subject: false, contour: 2, edge: 0 }, 2128 | SourceEdge { is_from_subject: false, contour: 2, edge: 1 }, 2129 | SourceEdge { is_from_subject: false, contour: 2, edge: 2 }, 2130 | SourceEdge { is_from_subject: false, contour: 2, edge: 3 }, 2131 | ], 2132 | ], 2133 | }; 2134 | assert_eq!( 2135 | union(&non_empty_polygon, &empty_polygon), 2136 | non_empty_boolean_result_as_subject 2137 | ); 2138 | assert_eq!( 2139 | union(&empty_polygon, &non_empty_polygon), 2140 | non_empty_boolean_result_as_clip 2141 | ); 2142 | 2143 | assert_eq!( 2144 | intersection(&non_empty_polygon, &empty_polygon), 2145 | empty_boolean_result 2146 | ); 2147 | assert_eq!( 2148 | intersection(&empty_polygon, &non_empty_polygon), 2149 | empty_boolean_result 2150 | ); 2151 | 2152 | assert_eq!( 2153 | difference(&non_empty_polygon, &empty_polygon), 2154 | non_empty_boolean_result_as_subject 2155 | ); 2156 | assert_eq!( 2157 | difference(&empty_polygon, &non_empty_polygon), 2158 | empty_boolean_result 2159 | ); 2160 | 2161 | assert_eq!( 2162 | xor(&non_empty_polygon, &empty_polygon), 2163 | non_empty_boolean_result_as_subject 2164 | ); 2165 | assert_eq!( 2166 | xor(&empty_polygon, &non_empty_polygon), 2167 | non_empty_boolean_result_as_clip 2168 | ); 2169 | 2170 | assert_eq!(union(&empty_polygon, &empty_polygon), empty_boolean_result); 2171 | assert_eq!( 2172 | intersection(&empty_polygon, &empty_polygon), 2173 | empty_boolean_result 2174 | ); 2175 | assert_eq!(difference(&empty_polygon, &empty_polygon), empty_boolean_result); 2176 | assert_eq!(xor(&empty_polygon, &empty_polygon), empty_boolean_result); 2177 | } 2178 | 2179 | #[test] 2180 | fn floating_point_inaccuracy_polygons() { 2181 | let subject = Polygon { 2182 | contours: vec![vec![ 2183 | Vec2::new(2.0, 0.0), 2184 | Vec2::new(1.0, 0.0), 2185 | Vec2::new(1.0, -2.0), 2186 | Vec2::new(2.0, -1.0), 2187 | ]], 2188 | }; 2189 | let clip = Polygon { 2190 | contours: vec![vec![ 2191 | Vec2::new(2.0, -0.01), 2192 | Vec2::new(2.0, 0.01), 2193 | Vec2::new(1.0, 0.01), 2194 | Vec2::new(1.0, -0.01), 2195 | ]], 2196 | }; 2197 | 2198 | let BooleanResult { polygon, contour_source_edges } = union(&subject, &clip); 2199 | assert_eq!( 2200 | polygon, 2201 | Polygon { 2202 | contours: vec![vec![ 2203 | Vec2::new(1.0, -2.0), 2204 | Vec2::new(2.0, -1.0), 2205 | Vec2::new(2.0, -0.01), 2206 | Vec2::new(2.0, 0.0), 2207 | Vec2::new(2.0, 0.01), 2208 | Vec2::new(1.0, 0.01), 2209 | Vec2::new(1.0, 0.0), 2210 | Vec2::new(1.0, -0.01), 2211 | ]] 2212 | } 2213 | ); 2214 | assert_eq!( 2215 | contour_source_edges, 2216 | vec![vec![ 2217 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2218 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 2219 | SourceEdge { is_from_subject: true, contour: 0, edge: 3 }, 2220 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 2221 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 2222 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 2223 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2224 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2225 | ]] 2226 | ); 2227 | } 2228 | 2229 | #[test] 2230 | fn sweep_line_point_on_other_edge() { 2231 | let subject = Polygon { 2232 | contours: vec![vec![ 2233 | Vec2::new(-1.0 + EPSILON, 0.0), 2234 | Vec2::new(-1.0 + EPSILON, 1.0 - EPSILON), 2235 | Vec2::new(-2.0 + EPSILON, 2.0 - EPSILON), 2236 | Vec2::new(-2.0 + EPSILON, 0.0), 2237 | ]], 2238 | }; 2239 | let clip = Polygon { 2240 | contours: vec![vec![ 2241 | Vec2::new(-2.0 + EPSILON, 0.01 + EPSILON), 2242 | Vec2::new(-2.0 + EPSILON, -0.01 + EPSILON), 2243 | Vec2::new(-1.0, -0.01 + EPSILON), 2244 | Vec2::new(-1.0, 0.01 + EPSILON), 2245 | ]], 2246 | }; 2247 | let BooleanResult { polygon, contour_source_edges } = union(&subject, &clip); 2248 | assert_eq!( 2249 | polygon, 2250 | Polygon { 2251 | contours: vec![vec![ 2252 | clip.contours[0][1], 2253 | clip.contours[0][2], 2254 | subject.contours[0][0], 2255 | subject.contours[0][1], 2256 | subject.contours[0][2], 2257 | clip.contours[0][0], 2258 | subject.contours[0][3], 2259 | ]] 2260 | } 2261 | ); 2262 | assert_eq!( 2263 | contour_source_edges, 2264 | vec![vec![ 2265 | SourceEdge { is_from_subject: false, contour: 0, edge: 1 }, 2266 | SourceEdge { is_from_subject: false, contour: 0, edge: 2 }, 2267 | SourceEdge { is_from_subject: true, contour: 0, edge: 0 }, 2268 | SourceEdge { is_from_subject: true, contour: 0, edge: 1 }, 2269 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2270 | SourceEdge { is_from_subject: true, contour: 0, edge: 2 }, 2271 | SourceEdge { is_from_subject: false, contour: 0, edge: 0 }, 2272 | ]] 2273 | ); 2274 | } 2275 | --------------------------------------------------------------------------------