| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| new_key_type! { |
| pub struct MajorVertexKey; |
| pub struct MajorEdgeKey; |
| pub struct MinorVertexKey; |
| pub struct MinorEdgeKey; |
| pub struct DualVertexKey; |
| pub struct DualEdgeKey; |
| } |
| |
| |
| |
|
|
| use crate::aabb::{Aabb, bounding_box_around_point, bounding_box_max_extent, merge_bounding_boxes}; |
| use crate::epsilons::Epsilons; |
| use crate::intersection_path_segment::{path_segment_intersection, segments_equal}; |
| use crate::path::Path; |
| use crate::path_cubic_segment_self_intersection::path_cubic_segment_self_intersection; |
| use crate::path_segment::PathSegment; |
| #[cfg(feature = "logging")] |
| use crate::path_to_path_data; |
| use crate::quad_tree::QuadTree; |
| use glam::DVec2; |
| use slotmap::{SlotMap, new_key_type}; |
| use std::cmp::Ordering; |
| use std::collections::{HashMap, HashSet, VecDeque}; |
| use std::fmt::Display; |
|
|
| |
| #[derive(Debug, Clone, Copy)] |
| pub enum PathBooleanOperation { |
| |
| |
| |
| |
| Union, |
|
|
| |
| |
| |
| |
| Difference, |
|
|
| |
| |
| |
| |
| Intersection, |
|
|
| |
| |
| |
| |
| Exclusion, |
|
|
| |
| |
| |
| |
| Division, |
|
|
| |
| |
| |
| |
| |
| Fracture, |
| } |
|
|
| |
| #[derive(Debug, Clone, Copy)] |
| pub enum FillRule { |
| |
| NonZero, |
| |
| EvenOdd, |
| } |
|
|
| const INTERSECTION_TREE_DEPTH: usize = 8; |
| const POINT_TREE_DEPTH: usize = 8; |
|
|
| pub const EPS: Epsilons = Epsilons { |
| point: 1e-5, |
| linear: 1e-4, |
| param: 1e-8, |
| }; |
|
|
| type MajorGraphEdgeStage1 = (PathSegment, u8); |
| type MajorGraphEdgeStage2 = (PathSegment, u8, Aabb); |
|
|
| #[derive(Debug, Clone)] |
| pub struct MajorGraphEdge { |
| seg: PathSegment, |
| parent: u8, |
| incident_vertices: [MajorVertexKey; 2], |
| direction_flag: Direction, |
| twin: Option<MajorEdgeKey>, |
| } |
|
|
| #[derive(Debug, Clone, Default)] |
| pub struct MajorGraphVertex { |
| #[cfg_attr(not(feature = "logging"), expect(dead_code))] |
| pub point: DVec2, |
| outgoing_edges: Vec<MajorEdgeKey>, |
| } |
|
|
| |
| |
| |
| #[derive(Debug, Clone)] |
| struct MajorGraph { |
| edges: SlotMap<MajorEdgeKey, MajorGraphEdge>, |
| vertices: SlotMap<MajorVertexKey, MajorGraphVertex>, |
| } |
|
|
| #[derive(Debug, Clone, PartialEq)] |
| struct MinorGraphEdge { |
| segments: Vec<PathSegment>, |
| parent: u8, |
| incident_vertices: [MinorVertexKey; 2], |
| direction_flag: Direction, |
| twin: Option<MinorEdgeKey>, |
| } |
|
|
| impl MinorGraphEdge { |
| fn start_segment(&self) -> PathSegment { |
| let segment = self.segments[0]; |
| match self.direction_flag { |
| Direction::Forward => segment, |
| Direction::Backwards => segment.reverse(), |
| } |
| } |
| } |
|
|
| |
| |
| fn compare_segments(a: &PathSegment, b: &PathSegment) -> Ordering { |
| let angle_a = a.start_angle(); |
| let angle_b = b.start_angle(); |
|
|
| |
| let angle_a = (angle_a * 1000.).round() / 1000.; |
| let angle_b = (angle_b * 1000.).round() / 1000.; |
|
|
| |
| match angle_b.partial_cmp(&angle_a) { |
| Some(Ordering::Equal) => { |
| |
| let curvature_a = a.start_curvature(); |
| let curvature_b = b.start_curvature(); |
| curvature_a.partial_cmp(&curvature_b).unwrap_or(Ordering::Equal) |
| } |
| Some(ordering) => ordering, |
| None => Ordering::Equal, |
| } |
| } |
|
|
| impl PartialOrd for MinorGraphEdge { |
| fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| Some(compare_segments(&self.start_segment(), &other.start_segment())) |
| } |
| } |
|
|
| #[derive(Debug, Clone, Default)] |
| struct MinorGraphVertex { |
| outgoing_edges: Vec<MinorEdgeKey>, |
| } |
|
|
| #[derive(Debug, Clone)] |
| struct MinorGraphCycle { |
| segments: Vec<PathSegment>, |
| parent: u8, |
| direction_flag: Direction, |
| } |
|
|
| |
| |
| |
| #[derive(Debug, Clone)] |
| struct MinorGraph { |
| edges: SlotMap<MinorEdgeKey, MinorGraphEdge>, |
| vertices: SlotMap<MinorVertexKey, MinorGraphVertex>, |
| cycles: Vec<MinorGraphCycle>, |
| } |
|
|
| #[derive(Debug, Clone, PartialEq)] |
| struct DualGraphHalfEdge { |
| segments: Vec<PathSegment>, |
| parent: u8, |
| incident_vertex: DualVertexKey, |
| direction_flag: Direction, |
| twin: Option<DualEdgeKey>, |
| } |
|
|
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| struct DualGraphVertex { |
| incident_edges: Vec<DualEdgeKey>, |
| } |
|
|
| |
| |
| |
| |
| #[derive(Debug, Clone)] |
| struct DualGraphComponent { |
| edges: Vec<DualEdgeKey>, |
| vertices: Vec<DualVertexKey>, |
| outer_face: Option<DualVertexKey>, |
| } |
|
|
| |
| |
| |
| |
| |
| #[derive(Debug, Clone)] |
| struct DualGraph { |
| components: Vec<DualGraphComponent>, |
| edges: SlotMap<DualEdgeKey, DualGraphHalfEdge>, |
| vertices: SlotMap<DualVertexKey, DualGraphVertex>, |
| } |
|
|
| |
| |
| |
| |
| #[derive(Debug, Clone)] |
| struct NestingTree { |
| component: DualGraphComponent, |
| outgoing_edges: HashMap<DualVertexKey, Vec<NestingTree>>, |
| } |
|
|
| #[cfg(feature = "logging")] |
| fn major_graph_to_dot(graph: &MajorGraph) -> String { |
| let mut dot = String::from("digraph {\n"); |
| for (vertex_key, vertex) in &graph.vertices { |
| dot.push_str(&format!(" {:?} [label=\"{:.1},{:.1}\"]\n", (vertex_key.0.as_ffi() & 0xFF), vertex.point.x, vertex.point.y)); |
| } |
| for (_, edge) in &graph.edges { |
| dot.push_str(&format!( |
| " {:?} -> {:?}: {:0b}\n", |
| (edge.incident_vertices[0].0.as_ffi() & 0xFF), |
| (edge.incident_vertices[1].0.as_ffi() & 0xFF), |
| edge.parent |
| )); |
| } |
| dot.push_str("}\n"); |
| dot |
| } |
|
|
| #[cfg(feature = "logging")] |
| fn minor_graph_to_dot(edges: &SlotMap<MinorEdgeKey, MinorGraphEdge>) -> String { |
| let mut dot = String::from("digraph {\n"); |
| for edge in edges.values() { |
| dot.push_str(&format!( |
| " {:?} -> {:?}: {:0b}\n", |
| (edge.incident_vertices[0].0.as_ffi() & 0xFF), |
| (edge.incident_vertices[1].0.as_ffi() & 0xFF), |
| edge.parent |
| )); |
| } |
| dot.push_str("}\n"); |
| dot |
| } |
|
|
| #[cfg(feature = "logging")] |
| fn dual_graph_to_dot(components: &[DualGraphComponent], edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>) -> String { |
| let mut dot = String::from("strict graph {\n"); |
| for component in components { |
| for &edge_key in &component.edges { |
| let edge = &edges[edge_key]; |
| dot.push_str(&format!( |
| " {:?} -- {:?}\n", |
| (edge.incident_vertex.0.as_ffi() & 0xFF), |
| (edges[edge.twin.unwrap()].incident_vertex.0.as_ffi() & 0xFF) |
| )); |
| } |
| } |
| dot.push_str("}\n"); |
| dot |
| } |
|
|
| fn segment_to_edge(parent: u8) -> impl Fn(&PathSegment) -> Option<MajorGraphEdgeStage1> { |
| move |seg| { |
| if bounding_box_max_extent(&seg.bounding_box()) < EPS.point { |
| return None; |
| } |
|
|
| match seg { |
| |
| PathSegment::Cubic(start, _, _, end) => { |
| let direction = seg.sample_at(0.1); |
| if (*end - *start).angle_to(direction - *start).abs() < EPS.point * 4. { |
| Some((PathSegment::Line(*start, *end), parent)) |
| } else { |
| Some((*seg, parent)) |
| } |
| } |
| seg => Some((*seg, parent)), |
| } |
| } |
| } |
|
|
| fn split_at_self_intersections(edges: &mut Vec<MajorGraphEdgeStage1>) { |
| let mut new_edges = Vec::new(); |
| for (seg, parent) in edges.iter_mut() { |
| if let PathSegment::Cubic(..) = seg { |
| if let Some(intersection) = path_cubic_segment_self_intersection(seg) { |
| let mut intersection = intersection; |
| if intersection[0] > intersection[1] { |
| intersection.swap(0, 1); |
| } |
| let [t1, t2] = intersection; |
| if (t1 - t2).abs() < EPS.param { |
| let (seg1, seg2) = seg.split_at(t1); |
| *seg = seg1; |
| new_edges.push((seg2, *parent)); |
| } else { |
| let (seg1, tmp_seg) = seg.split_at(t1); |
| let (seg2, seg3) = &tmp_seg.split_at((t2 - t1) / (1. - t1)); |
| *seg = seg1; |
| new_edges.push((*seg2, *parent)); |
| new_edges.push((*seg3, *parent)); |
| } |
| } |
| } |
| } |
| edges.extend(new_edges); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fn split_at_intersections(edges: &[MajorGraphEdgeStage1]) -> (Vec<MajorGraphEdgeStage2>, Option<Aabb>) { |
| |
| let with_bounding_box: Vec<MajorGraphEdgeStage2> = edges.iter().map(|(seg, parent)| (*seg, *parent, seg.bounding_box())).collect(); |
|
|
| |
| let total_bounding_box = with_bounding_box.iter().fold(None, |acc, (_, _, bb)| Some(merge_bounding_boxes(acc, bb))); |
|
|
| let total_bounding_box = match total_bounding_box { |
| Some(bb) => bb, |
| None => return (Vec::new(), None), |
| }; |
|
|
| |
| let mut edge_tree = QuadTree::new(total_bounding_box, INTERSECTION_TREE_DEPTH, 8); |
|
|
| let mut splits_per_edge: HashMap<usize, Vec<f64>> = HashMap::new(); |
|
|
| fn add_split(splits_per_edge: &mut HashMap<usize, Vec<f64>>, i: usize, t: f64) { |
| splits_per_edge.entry(i).or_default().push(t); |
| } |
|
|
| |
| for (i, edge) in with_bounding_box.iter().enumerate() { |
| let candidates = edge_tree.find(&edge.2); |
| for &j in &candidates { |
| let candidate: &(PathSegment, u8) = &edges[j]; |
| let include_endpoints = edge.1 != candidate.1 || !(candidate.0.end().abs_diff_eq(edge.0.start(), EPS.point) || candidate.0.start().abs_diff_eq(edge.0.end(), EPS.point)); |
| let intersection = path_segment_intersection(&edge.0, &candidate.0, include_endpoints, &EPS); |
| for [t0, t1] in intersection { |
| add_split(&mut splits_per_edge, i, t0); |
| add_split(&mut splits_per_edge, j, t1); |
| } |
| } |
| edge_tree.insert(edge.2, i); |
| } |
|
|
| |
| let mut new_edges = Vec::new(); |
|
|
| for (i, (seg, parent, _)) in with_bounding_box.into_iter().enumerate() { |
| if let Some(splits) = splits_per_edge.get(&i) { |
| let mut splits = splits.clone(); |
| splits.sort_by(|a, b| a.partial_cmp(b).unwrap()); |
| let mut tmp_seg = seg; |
| let mut prev_t = 0.; |
| for &t in splits.iter() { |
| if t > 1. - EPS.param { |
| break; |
| } |
| let tt = (t - prev_t) / (1. - prev_t); |
| prev_t = t; |
| if tt < EPS.param { |
| continue; |
| } |
| if tt > 1. - EPS.param { |
| continue; |
| } |
| let (seg1, seg2) = tmp_seg.split_at(tt); |
| new_edges.push((seg1, parent, seg1.bounding_box())); |
| tmp_seg = seg2; |
| } |
| new_edges.push((tmp_seg, parent, tmp_seg.bounding_box())); |
| } else { |
| new_edges.push((seg, parent, seg.bounding_box())); |
| } |
| } |
|
|
| (new_edges, Some(total_bounding_box)) |
| } |
|
|
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum Direction { |
| Forward, |
| Backwards, |
| } |
|
|
| impl std::ops::Neg for Direction { |
| type Output = Self; |
|
|
| fn neg(self) -> Self::Output { |
| match self { |
| Self::Forward => Self::Backwards, |
| Self::Backwards => Self::Forward, |
| } |
| } |
| } |
| impl std::ops::Not for Direction { |
| type Output = Self; |
|
|
| fn not(self) -> Self::Output { |
| match self { |
| Self::Forward => Self::Backwards, |
| Self::Backwards => Self::Forward, |
| } |
| } |
| } |
|
|
| impl Direction { |
| pub fn forward(self) -> bool { |
| self == Self::Forward |
| } |
| } |
|
|
| |
| fn find_vertices(edges: &[MajorGraphEdgeStage2], bounding_box: Aabb) -> MajorGraph { |
| let mut vertex_tree = QuadTree::new(bounding_box, POINT_TREE_DEPTH, 8); |
| let mut graph = MajorGraph { |
| edges: SlotMap::with_key(), |
| vertices: SlotMap::with_key(), |
| }; |
|
|
| let mut parents: HashMap<MajorEdgeKey, u8> = HashMap::new(); |
|
|
| let mut vertex_pair_id_to_edges: HashMap<_, Vec<(MajorGraphEdgeStage2, MajorEdgeKey, MajorEdgeKey)>> = HashMap::new(); |
|
|
| for (seg, parent, bounding_box) in edges { |
| let mut get_vertex = |point: DVec2| -> MajorVertexKey { |
| let box_around_point = bounding_box_around_point(point, EPS.point); |
| if let Some(&existing_vertex) = vertex_tree.find(&box_around_point).iter().next() { |
| existing_vertex |
| } else { |
| let vertex_key = graph.vertices.insert(MajorGraphVertex { point, outgoing_edges: Vec::new() }); |
| vertex_tree.insert(box_around_point, vertex_key); |
| vertex_key |
| } |
| }; |
|
|
| let start_vertex = get_vertex(seg.start()); |
| let end_vertex = get_vertex(seg.end()); |
|
|
| if start_vertex == end_vertex { |
| match seg { |
| PathSegment::Line(..) => continue, |
| PathSegment::Cubic(_, c1, c2, _) => { |
| if c1.abs_diff_eq(*c2, EPS.point) { |
| continue; |
| } |
| } |
| PathSegment::Quadratic(_, c, _) => { |
| if seg.start().abs_diff_eq(*c, EPS.point) { |
| continue; |
| } |
| } |
| PathSegment::Arc(_, _, _, _, _, false, _) => continue, |
| _ => {} |
| } |
| } |
|
|
| let vertex_pair_id = (start_vertex.min(end_vertex), start_vertex.max(end_vertex)); |
| if let Some(existing_edges) = vertex_pair_id_to_edges.get(&vertex_pair_id) { |
| if let Some(existing_edge) = existing_edges |
| .iter() |
| .find(|(other_seg, ..)| segments_equal(seg, &other_seg.0, EPS.point) || segments_equal(&seg.reverse(), &other_seg.0, EPS.point)) |
| { |
| *parents.entry(existing_edge.1).or_default() |= parent; |
| *parents.entry(existing_edge.2).or_default() |= parent; |
| continue; |
| } |
| } |
|
|
| let fwd_edge_key = graph.edges.insert(MajorGraphEdge { |
| seg: *seg, |
| parent: *parent, |
| incident_vertices: [start_vertex, end_vertex], |
| direction_flag: Direction::Forward, |
| twin: None, |
| }); |
|
|
| let bwd_edge_key = graph.edges.insert(MajorGraphEdge { |
| seg: *seg, |
| parent: *parent, |
| incident_vertices: [end_vertex, start_vertex], |
| direction_flag: Direction::Backwards, |
| twin: Some(fwd_edge_key), |
| }); |
|
|
| graph.edges[fwd_edge_key].twin = Some(bwd_edge_key); |
|
|
| graph.vertices[start_vertex].outgoing_edges.push(fwd_edge_key); |
| graph.vertices[end_vertex].outgoing_edges.push(bwd_edge_key); |
|
|
| vertex_pair_id_to_edges |
| .entry(vertex_pair_id) |
| .or_default() |
| .push(((*seg, *parent, *bounding_box), fwd_edge_key, bwd_edge_key)); |
| } |
| for (edge_key, parent) in parents { |
| graph.edges[edge_key].parent |= parent; |
| } |
|
|
| graph |
| } |
|
|
| fn get_order(vertex: &MajorGraphVertex) -> usize { |
| vertex.outgoing_edges.len() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fn compute_minor(major_graph: &MajorGraph) -> MinorGraph { |
| let mut new_edges = SlotMap::with_key(); |
| let mut new_vertices = SlotMap::with_key(); |
| let mut to_minor_vertex = HashMap::new(); |
| let mut id_to_edge = HashMap::new(); |
| let mut visited = HashSet::new(); |
|
|
| |
| for (major_vertex_key, vertex) in &major_graph.vertices { |
| |
| if get_order(vertex) == 2 { |
| continue; |
| } |
| let start_vertex = *to_minor_vertex |
| .entry(major_vertex_key) |
| .or_insert_with(|| new_vertices.insert(MinorGraphVertex { outgoing_edges: Vec::new() })); |
|
|
| for &start_edge_key in &vertex.outgoing_edges { |
| let mut segments = Vec::new(); |
| let mut edge_key = start_edge_key; |
| let mut edge = &major_graph.edges[edge_key]; |
|
|
| while edge.parent == major_graph.edges[start_edge_key].parent |
| && edge.direction_flag == major_graph.edges[start_edge_key].direction_flag |
| && get_order(&major_graph.vertices[edge.incident_vertices[1]]) == 2 |
| { |
| segments.push(edge.seg); |
| visited.insert(edge.incident_vertices[1]); |
| let next_vertex = &major_graph.vertices[edge.incident_vertices[1]]; |
| |
| edge_key = *next_vertex.outgoing_edges.iter().find(|&&e| Some(e) != edge.twin).unwrap(); |
| edge = &major_graph.edges[edge_key]; |
| } |
| segments.push(edge.seg); |
|
|
| let end_vertex = *to_minor_vertex |
| .entry(edge.incident_vertices[1]) |
| .or_insert_with(|| new_vertices.insert(MinorGraphVertex { outgoing_edges: Vec::new() })); |
| assert!(major_graph.edges[start_edge_key].twin.is_some()); |
| assert!(edge.twin.is_some()); |
|
|
| let edge_id = (start_edge_key, edge_key); |
| let twin_id = (edge.twin.unwrap(), major_graph.edges[start_edge_key].twin.unwrap()); |
|
|
| let twin_key = id_to_edge.get(&twin_id); |
|
|
| let new_edge_key = new_edges.insert(MinorGraphEdge { |
| segments, |
| parent: major_graph.edges[start_edge_key].parent, |
| incident_vertices: [start_vertex, end_vertex], |
| direction_flag: major_graph.edges[start_edge_key].direction_flag, |
| twin: twin_key.copied(), |
| }); |
| if let Some(&twin_key) = twin_key { |
| new_edges[twin_key].twin = Some(new_edge_key); |
| } |
| id_to_edge.insert(edge_id, new_edge_key); |
| new_vertices[start_vertex].outgoing_edges.push(new_edge_key); |
| } |
| } |
|
|
| |
| let mut cycles = Vec::new(); |
| for (major_vertex_key, vertex) in &major_graph.vertices { |
| if vertex.outgoing_edges.len() != 2 || visited.contains(&major_vertex_key) { |
| continue; |
| } |
| let mut edge_key = vertex.outgoing_edges[0]; |
| let mut edge = &major_graph.edges[edge_key]; |
| let mut cycle = MinorGraphCycle { |
| segments: Vec::new(), |
| parent: edge.parent, |
| direction_flag: edge.direction_flag, |
| }; |
| loop { |
| cycle.segments.push(edge.seg); |
| visited.insert(edge.incident_vertices[0]); |
| assert_eq!(major_graph.vertices[edge.incident_vertices[1]].outgoing_edges.len(), 2, "Found an unvisited vertex of order != 2."); |
| let next_vertex = &major_graph.vertices[edge.incident_vertices[1]]; |
| edge_key = *next_vertex.outgoing_edges.iter().find(|&&e| Some(e) != edge.twin).unwrap(); |
| edge = &major_graph.edges[edge_key]; |
| if edge.incident_vertices[0] == major_vertex_key { |
| break; |
| } |
| } |
| cycles.push(cycle); |
| } |
|
|
| MinorGraph { |
| edges: new_edges, |
| vertices: new_vertices, |
| cycles, |
| } |
| } |
|
|
| fn remove_dangling_edges(graph: &mut MinorGraph) { |
| |
| fn walk(parent: u8, graph: &MinorGraph) -> HashSet<MinorVertexKey> { |
| let mut kept_vertices = HashSet::new(); |
| let mut vertex_to_level = HashMap::new(); |
|
|
| fn visit( |
| vertex: MinorVertexKey, |
| incoming_edge: Option<MinorEdgeKey>, |
| level: usize, |
| graph: &MinorGraph, |
| vertex_to_level: &mut HashMap<MinorVertexKey, usize>, |
| kept_vertices: &mut HashSet<MinorVertexKey>, |
| parent: u8, |
| ) -> usize { |
| if let Some(&existing_level) = vertex_to_level.get(&vertex) { |
| return existing_level; |
| } |
| vertex_to_level.insert(vertex, level); |
|
|
| let mut min_level = usize::MAX; |
| for &edge_key in &graph.vertices[vertex].outgoing_edges { |
| let edge = &graph.edges[edge_key]; |
| if edge.parent & parent != 0 && Some(edge_key) != incoming_edge { |
| min_level = min_level.min(visit(edge.incident_vertices[1], edge.twin, level + 1, graph, vertex_to_level, kept_vertices, parent)); |
| } |
| } |
|
|
| if min_level <= level { |
| kept_vertices.insert(vertex); |
| } |
|
|
| min_level |
| } |
|
|
| for edge in graph.edges.values() { |
| if edge.parent & parent != 0 { |
| visit(edge.incident_vertices[0], None, 0, graph, &mut vertex_to_level, &mut kept_vertices, parent); |
| } |
| } |
|
|
| kept_vertices |
| } |
|
|
| let kept_vertices_a = walk(1, graph); |
| let kept_vertices_b = walk(2, graph); |
|
|
| graph.vertices.retain(|k, _| kept_vertices_a.contains(&k) || kept_vertices_b.contains(&k)); |
|
|
| for vertex in graph.vertices.values_mut() { |
| vertex.outgoing_edges.retain(|&edge_key| { |
| let edge = &graph.edges[edge_key]; |
| (edge.parent & 1 == 1 && kept_vertices_a.contains(&edge.incident_vertices[0]) && kept_vertices_a.contains(&edge.incident_vertices[1])) |
| || (edge.parent & 2 == 2 && kept_vertices_b.contains(&edge.incident_vertices[0]) && kept_vertices_b.contains(&edge.incident_vertices[1])) |
| }); |
| } |
| |
| graph.edges.retain(|_, edge| { |
| (edge.parent & 1 == 1 && kept_vertices_a.contains(&edge.incident_vertices[0]) && kept_vertices_a.contains(&edge.incident_vertices[1])) |
| || (edge.parent & 2 == 2 && kept_vertices_b.contains(&edge.incident_vertices[0]) && kept_vertices_b.contains(&edge.incident_vertices[1])) |
| }); |
| } |
|
|
| fn sort_outgoing_edges_by_angle(graph: &mut MinorGraph) { |
| for (vertex_key, vertex) in graph.vertices.iter_mut() { |
| if vertex.outgoing_edges.len() > 2 { |
| vertex.outgoing_edges.sort_by(|&a, &b| graph.edges[a].partial_cmp(&graph.edges[b]).unwrap()); |
| if cfg!(feature = "logging") { |
| eprintln!("Outgoing edges for {:?}:", vertex_key); |
| for &edge_key in &vertex.outgoing_edges { |
| let edge = &graph.edges[edge_key]; |
| let angle = edge.start_segment().start_angle(); |
| eprintln!("{:?}: {}°", edge_key.0, angle.to_degrees()) |
| } |
| } |
| } |
| } |
| } |
|
|
| fn face_to_polygon(face: &DualGraphVertex, edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>) -> Vec<DVec2> { |
| const CNT: usize = 3; |
|
|
| face.incident_edges |
| .iter() |
| .flat_map(|&edge_key| { |
| let edge = &edges[edge_key]; |
| edge.segments.iter().flat_map(move |seg| { |
| (0..CNT).map(move |i| { |
| let t0 = i as f64 / CNT as f64; |
| let t = if edge.direction_flag.forward() { t0 } else { 1. - t0 }; |
| seg.sample_at(t) |
| }) |
| }) |
| }) |
| .collect() |
| } |
|
|
| fn interval_crosses_point(a: f64, b: f64, p: f64) -> bool { |
| let dy1 = a >= p; |
| let dy2 = b < p; |
| dy1 == dy2 |
| } |
|
|
| fn line_segment_intersects_horizontal_ray(a: DVec2, b: DVec2, point: DVec2) -> bool { |
| if !interval_crosses_point(a.y, b.y, point.y) { |
| return false; |
| } |
| let x = crate::math::lin_map(point.y, a.y, b.y, a.x, b.x); |
| x >= point.x |
| } |
|
|
| fn compute_point_winding(polygon: &[DVec2], tested_point: DVec2) -> i32 { |
| if polygon.len() <= 2 { |
| return 0; |
| } |
| let mut prev_point = polygon[polygon.len() - 1]; |
| let mut winding = 0; |
| for &point in polygon { |
| if line_segment_intersects_horizontal_ray(prev_point, point, tested_point) { |
| winding += if point.y > prev_point.y { -1 } else { 1 }; |
| } |
| prev_point = point; |
| } |
| winding |
| } |
|
|
| fn compute_winding(face: &DualGraphVertex, edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>) -> Option<i32> { |
| let polygon = face_to_polygon(face, edges); |
|
|
| for i in 0..polygon.len() { |
| let a = polygon[i]; |
| let b = polygon[(i + 1) % polygon.len()]; |
| let c = polygon[(i + 2) % polygon.len()]; |
| let center = (a + b + c) / 3.; |
| let winding = compute_point_winding(&polygon, center); |
| if winding != 0 { |
| return Some(winding); |
| } |
| } |
|
|
| None |
| } |
|
|
| fn compute_signed_area(face: &DualGraphVertex, edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>) -> f64 { |
| let polygon = face_to_polygon(face, edges); |
| if polygon.len() <= 4 { |
| return -1.; |
| } |
|
|
| #[cfg(feature = "logging")] |
| eprintln!("vertex: {:?}", face); |
| #[cfg(feature = "logging")] |
| for point in &polygon { |
| eprintln!("{}, {}", point.x, point.y); |
| } |
| let mut area = 0.; |
|
|
| for i in 0..polygon.len() { |
| let a = polygon[i]; |
| let b = polygon[(i + 1) % polygon.len()]; |
| area += a.x * b.y; |
| area -= b.x * a.y; |
| } |
|
|
| #[cfg(feature = "logging")] |
| eprintln!("winding: {}", area); |
| area |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fn compute_dual(minor_graph: &MinorGraph) -> Result<DualGraph, BooleanError> { |
| let mut new_vertices: Vec<DualVertexKey> = Vec::new(); |
| let mut minor_to_dual_edge: HashMap<MinorEdgeKey, DualEdgeKey> = HashMap::new(); |
| let mut dual_edges = SlotMap::with_key(); |
| let mut dual_vertices = SlotMap::with_key(); |
|
|
| for (start_edge_key, start_edge) in &minor_graph.edges { |
| #[cfg(feature = "logging")] |
| eprintln!("Processing start edge: {}", (start_edge_key.0.as_ffi() & 0xFF)); |
| if minor_to_dual_edge.contains_key(&start_edge_key) { |
| continue; |
| } |
|
|
| let face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); |
|
|
| let mut edge_key = start_edge_key; |
| let mut edge = start_edge; |
|
|
| loop { |
| #[cfg(feature = "logging")] |
| eprintln!("Processing edge: {}", (edge_key.0.as_ffi() & 0xFF)); |
| let twin = edge.twin.expect("Edge doesn't have a twin"); |
| let twin_dual_key = minor_to_dual_edge.get(&twin).copied(); |
|
|
| let new_edge_key = dual_edges.insert(DualGraphHalfEdge { |
| segments: edge.segments.clone(), |
| parent: edge.parent, |
| incident_vertex: face_key, |
| direction_flag: edge.direction_flag, |
| twin: twin_dual_key, |
| }); |
|
|
| if let Some(twin_key) = twin_dual_key { |
| dual_edges[twin_key].twin = Some(new_edge_key); |
| } |
|
|
| minor_to_dual_edge.insert(edge_key, new_edge_key); |
|
|
| dual_vertices[face_key].incident_edges.push(new_edge_key); |
|
|
| edge_key = get_next_edge(edge_key, minor_graph); |
| #[cfg(feature = "logging")] |
| eprintln!("Next edge: {}", (edge_key.0.as_ffi() & 0xFF)); |
| edge = &minor_graph.edges[edge_key]; |
|
|
| if edge.incident_vertices[0] == start_edge.incident_vertices[0] { |
| break; |
| } |
| } |
|
|
| new_vertices.push(face_key); |
| } |
|
|
| for cycle in &minor_graph.cycles { |
| let inner_face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); |
| let outer_face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); |
|
|
| let inner_half_edge_key = dual_edges.insert(DualGraphHalfEdge { |
| segments: cycle.segments.clone(), |
| parent: cycle.parent, |
| incident_vertex: inner_face_key, |
| direction_flag: cycle.direction_flag, |
| twin: None, |
| }); |
|
|
| let outer_half_edge_key = dual_edges.insert(DualGraphHalfEdge { |
| segments: cycle.segments.iter().cloned().rev().collect(), |
| parent: cycle.parent, |
| incident_vertex: outer_face_key, |
| direction_flag: !cycle.direction_flag, |
| twin: Some(inner_half_edge_key), |
| }); |
|
|
| dual_edges[inner_half_edge_key].twin = Some(outer_half_edge_key); |
| dual_vertices[inner_face_key].incident_edges.push(inner_half_edge_key); |
| dual_vertices[outer_face_key].incident_edges.push(outer_half_edge_key); |
| new_vertices.push(inner_face_key); |
| new_vertices.push(outer_face_key); |
| } |
|
|
| let mut components = Vec::new(); |
| let mut visited_vertices = HashSet::new(); |
| let mut visited_edges = HashSet::new(); |
|
|
| if cfg!(feature = "logging") { |
| eprintln!("faces: {}, dual-edges: {}, cycles: {}", new_vertices.len(), dual_edges.len(), minor_graph.cycles.len()) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| #[cfg(feature = "logging")] |
| for (vertex_key, vertex) in &dual_vertices { |
| eprintln!("\n\n#{:?}", vertex_key.0); |
| let polygon = face_to_polygon(vertex, &dual_edges); |
| for point in polygon.iter() { |
| eprintln!("{}, {}", point.x, point.y); |
| } |
| eprintln!("{}, {}", polygon[0].x, polygon[0].y); |
| } |
|
|
| for &start_vertex_key in &new_vertices { |
| if visited_vertices.contains(&start_vertex_key) { |
| continue; |
| } |
|
|
| let mut component_vertices = Vec::new(); |
| let mut component_edges = Vec::new(); |
|
|
| let mut stack = vec![start_vertex_key]; |
| while let Some(vertex_key) = stack.pop() { |
| if visited_vertices.insert(vertex_key) { |
| component_vertices.push(vertex_key); |
| } |
|
|
| for &edge_key in &dual_vertices[vertex_key].incident_edges { |
| if !visited_edges.insert(edge_key) { |
| continue; |
| } |
|
|
| let edge = &dual_edges[edge_key]; |
| let twin_key = edge.twin.expect("Edge doesn't have a twin."); |
| component_edges.push(edge_key); |
| component_edges.push(twin_key); |
| visited_edges.insert(twin_key); |
| stack.push(dual_edges[twin_key].incident_vertex); |
| } |
| } |
| #[cfg(feature = "logging")] |
| eprintln!("component_vertices: {}", component_vertices.len()); |
|
|
| let windings: Option<Vec<_>> = component_vertices |
| .iter() |
| .map(|face_key| compute_winding(&dual_vertices[*face_key], &dual_edges).map(|w| (face_key, w))) |
| .collect(); |
| let Some(windings) = windings else { |
| return Err(BooleanError::NoEarInPolygon); |
| }; |
|
|
| let areas: Vec<_> = component_vertices |
| .iter() |
| .map(|face_key| (face_key, compute_signed_area(&dual_vertices[*face_key], &dual_edges))) |
| .collect(); |
| #[cfg(feature = "logging")] |
| dbg!(&areas); |
|
|
| #[cfg(feature = "logging")] |
| if cfg!(feature = "logging") { |
| eprintln!( |
| "{}", |
| dual_graph_to_dot( |
| &[DualGraphComponent { |
| vertices: component_vertices.clone(), |
| edges: component_edges.clone(), |
| outer_face: None, |
| }], |
| &dual_edges, |
| ) |
| ); |
| } |
|
|
| let mut count = windings.iter().filter(|(_, winding)| winding < &0).count(); |
| let mut reverse_winding = false; |
| |
| if windings.len() > 2 && count == windings.len() - 1 { |
| count = 1; |
| reverse_winding = true; |
| } |
| let outer_face_key = if count != 1 { |
| #[cfg(feature = "logging")] |
| eprintln!("Found multiple outer faces: {areas:?}, falling back to area calculation"); |
| let (key, _) = *areas.iter().max_by_key(|(_, area)| ((area.abs() * 1000.) as u64)).unwrap(); |
| *key |
| } else { |
| *windings |
| .iter() |
| .find(|&&(&_, ref winding)| (winding < &0) ^ reverse_winding) |
| .expect("No outer face of a component found.") |
| .0 |
| }; |
| #[cfg(feature = "logging")] |
| dbg!(outer_face_key); |
|
|
| components.push(DualGraphComponent { |
| vertices: component_vertices, |
| edges: component_edges, |
| outer_face: Some(outer_face_key), |
| }); |
| } |
|
|
| Ok(DualGraph { |
| vertices: dual_vertices, |
| edges: dual_edges, |
| components, |
| }) |
| } |
|
|
| fn get_next_edge(edge_key: MinorEdgeKey, graph: &MinorGraph) -> MinorEdgeKey { |
| let edge = &graph.edges[edge_key]; |
| let vertex = &graph.vertices[edge.incident_vertices[1]]; |
| #[cfg(feature = "logging")] |
| eprintln!("{edge_key:?}, twin: {:?}, {:?}", edge.twin, vertex.outgoing_edges); |
| let index = vertex.outgoing_edges.iter().position(|&e| Some(edge_key) == graph.edges[e].twin).unwrap(); |
| vertex.outgoing_edges[(index + 1) % vertex.outgoing_edges.len()] |
| } |
|
|
| fn test_inclusion(a: &DualGraphComponent, b: &DualGraphComponent, edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, vertices: &SlotMap<DualVertexKey, DualGraphVertex>) -> Option<DualVertexKey> { |
| let tested_point = edges[a.edges[0]].segments[0].start(); |
| for (face_key, face) in b.vertices.iter().map(|&key| (key, &vertices[key])) { |
| if Some(face_key) == b.outer_face { |
| continue; |
| } |
| let mut count = 0; |
| for &edge_key in &face.incident_edges { |
| let edge = &edges[edge_key]; |
| for seg in &edge.segments { |
| count += path_segment_horizontal_ray_intersection_count(seg, tested_point); |
| } |
| } |
| if count % 2 == 1 { |
| return Some(face_key); |
| } |
| } |
| None |
| } |
| fn bounding_box_intersects_horizontal_ray(bounding_box: &Aabb, point: DVec2) -> bool { |
| interval_crosses_point(bounding_box.top, bounding_box.bottom, point[1]) && bounding_box.right >= point[0] |
| } |
|
|
| struct IntersectionSegment { |
| bounding_box: Aabb, |
| seg: PathSegment, |
| } |
|
|
| pub fn path_segment_horizontal_ray_intersection_count(orig_seg: &PathSegment, point: DVec2) -> usize { |
| let total_bounding_box = orig_seg.bounding_box(); |
|
|
| if !bounding_box_intersects_horizontal_ray(&total_bounding_box, point) { |
| return 0; |
| } |
|
|
| let mut segments = vec![IntersectionSegment { |
| bounding_box: total_bounding_box, |
| seg: *orig_seg, |
| }]; |
| let mut count = 0; |
|
|
| while !segments.is_empty() { |
| let mut next_segments = Vec::new(); |
| for segment in segments { |
| if bounding_box_max_extent(&segment.bounding_box) < EPS.linear { |
| if line_segment_intersects_horizontal_ray(segment.seg.start(), segment.seg.end(), point) { |
| count += 1; |
| } |
| } else { |
| let split = &segment.seg.split_at(0.5); |
| let bounding_box0 = split.0.bounding_box(); |
| let bounding_box1 = split.1.bounding_box(); |
|
|
| if bounding_box_intersects_horizontal_ray(&bounding_box0, point) { |
| next_segments.push(IntersectionSegment { |
| bounding_box: bounding_box0, |
| seg: split.0, |
| }); |
| } |
| if bounding_box_intersects_horizontal_ray(&bounding_box1, point) { |
| next_segments.push(IntersectionSegment { |
| bounding_box: bounding_box1, |
| seg: split.1, |
| }); |
| } |
| } |
| } |
| segments = next_segments; |
| } |
|
|
| count |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| fn compute_nesting_tree(DualGraph { components, vertices, edges }: &DualGraph) -> Vec<NestingTree> { |
| let mut nesting_trees = Vec::new(); |
|
|
| for component in components { |
| insert_component(&mut nesting_trees, component, edges, vertices); |
| } |
|
|
| nesting_trees |
| } |
|
|
| fn insert_component(trees: &mut Vec<NestingTree>, component: &DualGraphComponent, edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, vertices: &SlotMap<DualVertexKey, DualGraphVertex>) { |
| for tree in trees.iter_mut() { |
| if let Some(face_key) = test_inclusion(component, &tree.component, edges, vertices) { |
| if let Some(children) = tree.outgoing_edges.get_mut(&face_key) { |
| insert_component(children, component, edges, vertices); |
| } else { |
| tree.outgoing_edges.insert( |
| face_key, |
| vec![NestingTree { |
| component: component.clone(), |
| outgoing_edges: HashMap::new(), |
| }], |
| ); |
| } |
| return; |
| } |
| } |
|
|
| let mut new_tree = NestingTree { |
| component: component.clone(), |
| outgoing_edges: HashMap::new(), |
| }; |
|
|
| let mut i = 0; |
| while i < trees.len() { |
| if let Some(face_key) = test_inclusion(&trees[i].component, &new_tree.component, edges, vertices) { |
| |
| let tree = trees.remove(i); |
| new_tree.outgoing_edges.entry(face_key).or_default().push(tree); |
| } else { |
| i += 1; |
| } |
| } |
|
|
| trees.push(new_tree); |
| } |
|
|
| fn get_flag(count: i32, fill_rule: FillRule) -> u8 { |
| match fill_rule { |
| FillRule::NonZero => { |
| if count == 0 { |
| 0 |
| } else { |
| 1 |
| } |
| } |
| FillRule::EvenOdd => (count % 2).unsigned_abs() as u8, |
| } |
| } |
|
|
| |
| |
| |
| |
| fn flag_faces( |
| nesting_trees: &[NestingTree], |
| a_fill_rule: FillRule, |
| b_fill_rule: FillRule, |
| edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, |
| vertices: &SlotMap<DualVertexKey, DualGraphVertex>, |
| flags: &mut HashMap<DualVertexKey, u8>, |
| ) { |
| for tree in nesting_trees.iter() { |
| let mut tree_stack = vec![(tree, 0, 0)]; |
|
|
| while let Some((current_tree, a_running_count, b_running_count)) = tree_stack.pop() { |
| let mut visited_faces = HashSet::new(); |
| let mut face_stack = VecDeque::new(); |
|
|
| let outer_face_key = current_tree.component.outer_face.expect("Component doesn't have an outer face."); |
| face_stack.push_back((outer_face_key, a_running_count, b_running_count)); |
|
|
| while let Some((face_key, a_count, b_count)) = face_stack.pop_front() { |
| if visited_faces.contains(&face_key) { |
| continue; |
| } |
| visited_faces.insert(face_key); |
|
|
| let a_flag = get_flag(a_count, a_fill_rule); |
| let b_flag = get_flag(b_count, b_fill_rule); |
| *flags.entry(face_key).or_default() = a_flag | (b_flag << 1); |
|
|
| for edge_key in &vertices[face_key].incident_edges { |
| let edge = &edges[*edge_key]; |
| let twin_key = edge.twin.expect("Edge doesn't have a twin"); |
| #[cfg(feature = "logging")] |
| eprintln!("Processing edge: {:?} to: {:?}", edge_key.0, edges[twin_key].incident_vertex.0); |
| let mut next_a_count = a_count; |
| if edge.parent & 1 != 0 { |
| next_a_count += if edge.direction_flag.forward() { 1 } else { -1 }; |
| } |
| let mut next_b_count = b_count; |
| if edge.parent & 2 != 0 { |
| next_b_count += if edge.direction_flag.forward() { 1 } else { -1 }; |
| } |
| #[cfg(feature = "logging")] |
| eprintln!("next_count a: {}, b:{}", next_a_count, next_b_count); |
| face_stack.push_back((edges[twin_key].incident_vertex, next_a_count, next_b_count)); |
| } |
|
|
| |
| if let Some(subtrees) = current_tree.outgoing_edges.get(&face_key) { |
| for subtree in subtrees { |
| tree_stack.push((subtree, a_count, b_count)); |
| } |
| } |
| } |
| } |
| } |
| } |
|
|
| fn get_selected_faces<'a>(predicate: &'a impl Fn(u8) -> bool, flags: &'a HashMap<DualVertexKey, u8>) -> impl Iterator<Item = DualVertexKey> + 'a { |
| flags.iter().filter_map(|(key, &flag)| predicate(flag).then_some(*key)) |
| } |
|
|
| fn walk_faces<'a>(faces: &'a [DualVertexKey], edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, vertices: &SlotMap<DualVertexKey, DualGraphVertex>) -> impl Iterator<Item = PathSegment> + use<'a> { |
| let face_set: HashSet<_> = faces.iter().copied().collect(); |
| |
| let is_removed_edge = |edge: &DualGraphHalfEdge| face_set.contains(&edge.incident_vertex) == face_set.contains(&edges[edge.twin.unwrap()].incident_vertex); |
|
|
| let mut edge_to_next = HashMap::new(); |
| for face_key in faces { |
| let face = &vertices[*face_key]; |
| let mut prev_edge = *face.incident_edges.last().unwrap(); |
| for &edge in &face.incident_edges { |
| edge_to_next.insert(prev_edge, edge); |
| prev_edge = edge; |
| } |
| } |
|
|
| let mut visited_edges = HashSet::new(); |
| let mut result = Vec::new(); |
|
|
| for &face_key in faces { |
| let face = &vertices[face_key]; |
| for &start_edge in &face.incident_edges { |
| if is_removed_edge(&edges[start_edge]) || visited_edges.contains(&start_edge) { |
| continue; |
| } |
| let mut edge = start_edge; |
| loop { |
| let current_edge = &edges[edge]; |
| if current_edge.direction_flag.forward() { |
| result.extend(current_edge.segments.iter().cloned()); |
| } else { |
| result.extend(current_edge.segments.iter().map(PathSegment::reverse)); |
| } |
| visited_edges.insert(edge); |
| edge = *edge_to_next.get(&edge).unwrap(); |
| while is_removed_edge(&edges[edge]) { |
| edge = *edge_to_next.get(&edges[edge].twin.unwrap()).unwrap(); |
| } |
| if edge == start_edge { |
| break; |
| } |
| } |
| } |
| } |
|
|
| result.into_iter() |
| } |
|
|
| |
| |
| |
| |
| |
| fn dump_faces( |
| nesting_trees: &[NestingTree], |
| predicate: impl Fn(u8) -> bool + Copy, |
| edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, |
| vertices: &SlotMap<DualVertexKey, DualGraphVertex>, |
| flags: &HashMap<DualVertexKey, u8>, |
| ) -> Vec<Path> { |
| let mut paths = Vec::new(); |
|
|
| fn visit( |
| tree: &NestingTree, |
| predicate: impl Fn(u8) -> bool + Copy, |
| paths: &mut Vec<Path>, |
| edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, |
| vertices: &SlotMap<DualVertexKey, DualGraphVertex>, |
| flags: &HashMap<DualVertexKey, u8>, |
| ) { |
| for &face_key in tree.component.vertices.iter() { |
| let face = &vertices[face_key]; |
| let flag = flags[&face_key]; |
| if !predicate(flag) || Some(face_key) == tree.component.outer_face { |
| continue; |
| } |
|
|
| let mut path = Vec::new(); |
|
|
| for &edge_key in &face.incident_edges { |
| let edge = &edges[edge_key]; |
| if edge.direction_flag.forward() { |
| path.extend(edge.segments.iter().cloned()); |
| } else { |
| path.extend(edge.segments.iter().map(PathSegment::reverse)); |
| } |
| } |
|
|
| |
| if let Some(subtrees) = tree.outgoing_edges.get(&face_key) { |
| for subtree in subtrees { |
| let outer_face_key = subtree.component.outer_face.unwrap(); |
| for &edge_key in &vertices[outer_face_key].incident_edges { |
| let edge = &edges[edge_key]; |
| if edge.direction_flag.forward() { |
| path.extend(edge.segments.iter().cloned()); |
| } else { |
| path.extend(edge.segments.iter().map(PathSegment::reverse)); |
| } |
| } |
| } |
| } |
|
|
| paths.push(path); |
| } |
|
|
| for subtrees in tree.outgoing_edges.values() { |
| for subtree in subtrees { |
| visit(subtree, predicate, paths, edges, vertices, flags); |
| } |
| } |
| } |
|
|
| for tree in nesting_trees { |
| visit(tree, predicate, &mut paths, edges, vertices, flags); |
| } |
|
|
| paths |
| } |
|
|
| const OPERATION_PREDICATES: [fn(u8) -> bool; 6] = [ |
| |flag: u8| flag > 0, |
| |flag: u8| flag == 1, |
| |flag: u8| flag == 0b11, |
| |flag: u8| flag == 1 || flag == 2, |
| |flag: u8| (flag & 1) == 1, |
| |flag: u8| flag > 0, |
| ]; |
|
|
| |
| #[derive(Debug)] |
| pub enum BooleanError { |
| |
| MultipleOuterFaces, |
| |
| NoEarInPolygon, |
| InvalidPathCommand(char), |
| } |
|
|
| impl Display for BooleanError { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| Self::MultipleOuterFaces => f.write_str("Found multiple candidates for the outer face in a connected component of the dual graph."), |
| Self::NoEarInPolygon => f.write_str("Failed to compute winding order for one of the faces, this usually happens when the polygon is malformed."), |
| Self::InvalidPathCommand(cmd) => f.write_fmt(format_args!("Encountered a '{cmd}' while parsing the svg data which was not recognized")), |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn path_boolean(a: &Path, a_fill_rule: FillRule, b: &Path, b_fill_rule: FillRule, op: PathBooleanOperation) -> Result<Vec<Path>, BooleanError> { |
| let mut unsplit_edges: Vec<MajorGraphEdgeStage1> = a.iter().map(segment_to_edge(1)).chain(b.iter().map(segment_to_edge(2))).flatten().collect(); |
|
|
| split_at_self_intersections(&mut unsplit_edges); |
|
|
| let (split_edges, total_bounding_box) = split_at_intersections(&unsplit_edges); |
|
|
| #[cfg(feature = "logging")] |
| for (edge, _, _) in split_edges.iter() { |
| eprintln!("{}", path_to_path_data(&vec![*edge], 0.001)); |
| } |
|
|
| let total_bounding_box = match total_bounding_box { |
| Some(bb) => bb, |
| None => return Ok(Vec::new()), |
| }; |
|
|
| let major_graph = find_vertices(&split_edges, total_bounding_box); |
|
|
| #[cfg(feature = "logging")] |
| eprintln!("Major graph:"); |
| #[cfg(feature = "logging")] |
| eprintln!("{}", major_graph_to_dot(&major_graph)); |
|
|
| let mut minor_graph = compute_minor(&major_graph); |
|
|
| #[cfg(feature = "logging")] |
| eprintln!("Minor graph:"); |
| #[cfg(feature = "logging")] |
| eprintln!("{}", minor_graph_to_dot(&minor_graph.edges)); |
|
|
| remove_dangling_edges(&mut minor_graph); |
| #[cfg(feature = "logging")] |
| eprintln!("After removing dangling edges:"); |
| #[cfg(feature = "logging")] |
| eprintln!("{}", minor_graph_to_dot(&minor_graph.edges)); |
|
|
| #[cfg(feature = "logging")] |
| for (key, edge) in minor_graph.edges.iter() { |
| eprintln!("{key:?}:\n{}", path_to_path_data(&edge.segments, 0.001)); |
| } |
| #[cfg(feature = "logging")] |
| for vertex in minor_graph.vertices.values() { |
| eprintln!("{:?}", vertex); |
| } |
| sort_outgoing_edges_by_angle(&mut minor_graph); |
| #[cfg(feature = "logging")] |
| for vertex in minor_graph.vertices.values() { |
| eprintln!("{:?}", vertex); |
| } |
|
|
| for (edge_key, edge) in &minor_graph.edges { |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {:?} has invalid start vertex", edge_key); |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {:?} has invalid end vertex", edge_key); |
| assert!(edge.twin.is_some(), "Edge {:?} should have a twin", edge_key); |
| let twin = &minor_graph.edges[edge.twin.unwrap()]; |
| assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {:?}", edge_key); |
| } |
|
|
| let dual_graph = compute_dual(&minor_graph)?; |
|
|
| let nesting_trees = compute_nesting_tree(&dual_graph); |
|
|
| #[cfg(feature = "logging")] |
| for tree in &nesting_trees { |
| eprintln!("nesting_trees: {:?}", tree); |
| } |
|
|
| let DualGraph { edges, vertices, .. } = &dual_graph; |
|
|
| #[cfg(feature = "logging")] |
| eprintln!("Dual Graph:"); |
| #[cfg(feature = "logging")] |
| eprintln!("{}", dual_graph_to_dot(&dual_graph.components, edges)); |
|
|
| let mut flags = HashMap::new(); |
| flag_faces(&nesting_trees, a_fill_rule, b_fill_rule, edges, vertices, &mut flags); |
|
|
| #[cfg(feature = "logging")] |
| for (face, flag) in &flags { |
| eprintln!("{:?}: {:b}", face.0, flag); |
| } |
|
|
| let predicate = OPERATION_PREDICATES[op as usize]; |
|
|
| match op { |
| PathBooleanOperation::Division | PathBooleanOperation::Fracture => Ok(dump_faces(&nesting_trees, predicate, edges, vertices, &flags)), |
| _ => { |
| let mut selected_faces: Vec<DualVertexKey> = get_selected_faces(&predicate, &flags).collect(); |
| selected_faces.sort_unstable(); |
| Ok(vec![walk_faces(&selected_faces, edges, vertices).collect()]) |
| } |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use glam::DVec2; |
| use std::f64::consts::TAU; |
|
|
| #[test] |
| fn test_split_at_intersections() { |
| let unsplit_edges = unsplit_edges(); |
| let (split_edges, total_bounding_box) = split_at_intersections(&unsplit_edges); |
|
|
| |
| assert!(total_bounding_box.is_some()); |
|
|
| |
| assert!(split_edges.len() >= unsplit_edges.len()); |
|
|
| |
| for (_, _, bb) in &split_edges { |
| assert!(bb.left <= bb.right); |
| assert!(bb.top <= bb.bottom); |
| } |
|
|
| |
| |
| } |
|
|
| fn unsplit_edges() -> Vec<(PathSegment, u8)> { |
| let unsplit_edges = vec![ |
| (PathSegment::Arc(DVec2::new(39., 20.), 19., 19., 0., false, true, DVec2::new(20., 39.)), 1), |
| (PathSegment::Arc(DVec2::new(20., 39.), 19., 19., 0., false, true, DVec2::new(1., 20.)), 1), |
| (PathSegment::Arc(DVec2::new(1., 20.), 19., 19., 0., false, true, DVec2::new(20., 1.)), 1), |
| (PathSegment::Arc(DVec2::new(20., 1.), 19., 19., 0., false, true, DVec2::new(39., 20.)), 1), |
| (PathSegment::Arc(DVec2::new(47., 28.), 19., 19., 0., false, true, DVec2::new(28., 47.)), 2), |
| (PathSegment::Arc(DVec2::new(28., 47.), 19., 19., 0., false, true, DVec2::new(9., 28.)), 2), |
| (PathSegment::Arc(DVec2::new(9., 28.), 19., 19., 0., false, true, DVec2::new(28., 9.)), 2), |
| (PathSegment::Arc(DVec2::new(28., 9.), 19., 19., 0., false, true, DVec2::new(47., 28.)), 2), |
| ]; |
| unsplit_edges |
| } |
|
|
| #[test] |
| fn test_compute_minor() { |
| |
| let unsplit_edges = unsplit_edges(); |
| let (split_edges, total_bounding_box) = split_at_intersections(&unsplit_edges); |
| let major_graph = find_vertices(&split_edges, total_bounding_box.unwrap()); |
|
|
| |
| let minor_graph = compute_minor(&major_graph); |
|
|
| |
| eprintln!("Minor Graph:"); |
| print_minor_graph_state(&minor_graph); |
|
|
| |
| assert_eq!(minor_graph.edges.len(), 8, "Expected 8 edges in minor graph"); |
| assert_eq!(minor_graph.vertices.len(), 2, "Expected 2 vertices in minor graph"); |
| assert!(minor_graph.cycles.is_empty(), "Expected no cycles in minor graph"); |
|
|
| |
| for (vertex_key, vertex) in &minor_graph.vertices { |
| assert_eq!(vertex.outgoing_edges.len(), 4, "Vertex {:?} should have 4 outgoing edges", vertex_key); |
| } |
|
|
| |
| for (edge_key, edge) in &minor_graph.edges { |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {:?} has invalid start vertex", edge_key); |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {:?} has invalid end vertex", edge_key); |
| assert!(edge.twin.is_some(), "Edge {:?} should have a twin", edge_key); |
| let twin = &minor_graph.edges[edge.twin.unwrap()]; |
| assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {:?}", edge_key); |
| } |
|
|
| |
| assert_eq!(minor_graph.edges.values().filter(|e| e.parent == 1).count(), 4, "Expected 4 edges with parent 1"); |
| assert_eq!(minor_graph.edges.values().filter(|e| e.parent == 2).count(), 4, "Expected 4 edges with parent 2"); |
| } |
|
|
| fn print_minor_graph_state(graph: &MinorGraph) { |
| eprintln!(" Vertices: {}", graph.vertices.len()); |
| eprintln!(" Edges: {}", graph.edges.len()); |
| eprintln!(" Cycles: {}", graph.cycles.len()); |
|
|
| for (vertex_key, vertex) in &graph.vertices { |
| eprintln!(" Vertex {:?}: {} outgoing edges", vertex_key, vertex.outgoing_edges.len()); |
| } |
|
|
| for (edge_key, edge) in &graph.edges { |
| eprintln!(" Edge {:?}:", edge_key); |
| eprintln!(" Parent: {}", edge.parent); |
| eprintln!(" Twin: {:?}", edge.twin); |
| eprintln!(" Incident vertices: {:?}", edge.incident_vertices); |
| } |
| } |
|
|
| #[test] |
| fn test_sort_outgoing_edges_by_angle() { |
| |
| let unsplit_edges = unsplit_edges(); |
| let (split_edges, total_bounding_box) = split_at_intersections(&unsplit_edges); |
| let major_graph = find_vertices(&split_edges, total_bounding_box.unwrap()); |
| let mut minor_graph = compute_minor(&major_graph); |
|
|
| |
| eprintln!("Initial Minor Graph:"); |
| print_minor_graph_state(&minor_graph); |
|
|
| |
| let initial_edge_order: HashMap<MinorVertexKey, Vec<MinorEdgeKey>> = minor_graph.vertices.iter().map(|(k, v)| (k, v.outgoing_edges.clone())).collect(); |
|
|
| |
| sort_outgoing_edges_by_angle(&mut minor_graph); |
|
|
| |
| eprintln!("\nAfter sort_outgoing_edges_by_angle:"); |
| print_minor_graph_state(&minor_graph); |
|
|
| |
| assert_eq!(minor_graph.edges.len(), 8, "Number of edges should remain unchanged"); |
| assert_eq!(minor_graph.vertices.len(), 2, "Number of vertices should remain unchanged"); |
| assert!(minor_graph.cycles.is_empty(), "Expected no cycles"); |
|
|
| |
| for (vertex_key, vertex) in &minor_graph.vertices { |
| assert_eq!(vertex.outgoing_edges.len(), 4, "Vertex {:?} should have 4 outgoing edges", vertex_key); |
| } |
|
|
| |
| for (vertex_key, vertex) in &minor_graph.vertices { |
| let angles: Vec<f64> = vertex.outgoing_edges.iter().map(|&edge_key| get_incidence_angle(&minor_graph.edges[edge_key])).collect(); |
|
|
| |
| for i in 1..angles.len() { |
| assert!(angles[i] >= angles[i - 1], "Edges for vertex {:?} are not sorted by angle {} {}", vertex_key, angles[i], angles[i - 1]); |
| } |
|
|
| |
| let initial_edges: HashSet<_> = initial_edge_order[&vertex_key].iter().collect(); |
| let sorted_edges: HashSet<_> = vertex.outgoing_edges.iter().collect(); |
| assert_eq!(initial_edges, sorted_edges, "Set of edges for vertex {:?} changed after sorting", vertex_key); |
| } |
|
|
| |
| for (edge_key, edge) in &minor_graph.edges { |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {:?} has invalid start vertex", edge_key); |
| assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {:?} has invalid end vertex", edge_key); |
| assert!(edge.twin.is_some(), "Edge {:?} should have a twin", edge_key); |
| let twin = &minor_graph.edges[edge.twin.unwrap()]; |
| assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {:?}", edge_key); |
| } |
| } |
|
|
| fn get_incidence_angle(edge: &MinorGraphEdge) -> f64 { |
| let seg = &edge.segments[0]; |
| let (p0, p1) = if edge.direction_flag.forward() { |
| (seg.sample_at(0.), seg.sample_at(0.1)) |
| } else { |
| (seg.sample_at(1.), seg.sample_at(1. - 0.1)) |
| }; |
| ((p1.y - p0.y).atan2(p1.x - p0.x) + TAU) % TAU |
| } |
|
|
| #[test] |
| fn test_path_segment_horizontal_ray_intersection_count() { |
| let orig_seg = PathSegment::Arc(DVec2::new(24., 10.090978), 13.909023, 13.909023, 0., false, true, DVec2::new(47., 24.)); |
|
|
| let point = DVec2::new(37.99, 24.); |
|
|
| eprintln!("Starting test with segment: {:?}", orig_seg); |
| eprintln!("Test point: {:?}", point); |
|
|
| let count = path_segment_horizontal_ray_intersection_count(&orig_seg, point); |
|
|
| eprintln!("Final intersection count: {}", count); |
|
|
| let expected_count = 1; |
| assert_eq!(count, expected_count, "Intersection count mismatch"); |
| } |
|
|
| #[test] |
| fn test_bounding_box_intersects_horizontal_ray() { |
| let bbox = Aabb { |
| top: 10., |
| right: 40., |
| bottom: 30., |
| left: 20., |
| }; |
|
|
| assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(0., 30.))); |
| assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(20., 30.))); |
| assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(10., 20.))); |
| assert!(!bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(30., 40.))); |
| } |
| } |
|
|