//! Planar graph construction for paint bucket fill //! //! This module builds a planar graph from a collection of curves by: //! 1. Finding all intersections (curve-curve and self-intersections) //! 2. Splitting curves at intersection points to create graph edges //! 3. Creating nodes at all intersection points and curve endpoints //! 4. Connecting edges to form a complete planar graph //! //! The resulting graph can be used for face detection to identify regions for filling. use crate::curve_intersections::{find_curve_intersections, find_self_intersections}; use crate::curve_segment::CurveSegment; use std::collections::{HashMap, HashSet}; use vello::kurbo::{BezPath, CubicBez, Point}; /// A node in the planar graph (intersection point or endpoint) #[derive(Debug, Clone)] pub struct GraphNode { /// Position of the node pub position: Point, /// Indices of edges connected to this node pub edge_indices: Vec, } impl GraphNode { pub fn new(position: Point) -> Self { Self { position, edge_indices: Vec::new(), } } } /// An edge in the planar graph (curve segment between two nodes) #[derive(Debug, Clone)] pub struct GraphEdge { /// Index of start node pub start_node: usize, /// Index of end node pub end_node: usize, /// Original curve ID pub curve_id: usize, /// Parameter at start of this edge on the original curve [0, 1] pub t_start: f64, /// Parameter at end of this edge on the original curve [0, 1] pub t_end: f64, } impl GraphEdge { pub fn new( start_node: usize, end_node: usize, curve_id: usize, t_start: f64, t_end: f64, ) -> Self { Self { start_node, end_node, curve_id, t_start, t_end, } } } /// Planar graph structure #[derive(Clone)] pub struct PlanarGraph { /// All nodes in the graph pub nodes: Vec, /// All edges in the graph pub edges: Vec, /// Original curves (referenced by edges) pub curves: Vec, } impl PlanarGraph { /// Build a planar graph from a collection of curve segments /// /// # Arguments /// /// * `curve_segments` - The input curve segments /// /// # Returns /// /// A complete planar graph with nodes at all intersections and edges connecting them pub fn build(curve_segments: &[CurveSegment]) -> Self { println!("PlanarGraph::build started with {} curves", curve_segments.len()); // Convert curve segments to cubic beziers let curves: Vec = curve_segments .iter() .map(|seg| seg.to_cubic_bez()) .collect(); // Find all intersection points let intersections = Self::find_all_intersections(&curves); println!("Found {} intersection points", intersections.len()); // Create nodes and edges let (nodes, edges) = Self::build_nodes_and_edges(&curves, intersections); println!("Created {} nodes and {} edges", nodes.len(), edges.len()); let mut graph = Self { nodes, edges, curves, }; // Prune dangling nodes graph.prune_dangling_nodes(); graph } /// Find all intersections between curves /// /// Returns a map from curve_id to sorted list of (t_value, point) intersections fn find_all_intersections(curves: &[CubicBez]) -> HashMap> { let mut intersections: HashMap> = HashMap::new(); // Initialize with endpoints for all curves for (i, curve) in curves.iter().enumerate() { let curve_intersections = vec![ (0.0, curve.p0), (1.0, curve.p3), ]; intersections.insert(i, curve_intersections); } // Find curve-curve intersections println!("Checking {} curve pairs for intersections...", (curves.len() * (curves.len() - 1)) / 2); let mut total_intersections = 0; for i in 0..curves.len() { for j in (i + 1)..curves.len() { let curve_i_intersections = find_curve_intersections(&curves[i], &curves[j]); if !curve_i_intersections.is_empty() { println!(" Curves {} and {} intersect at {} points:", i, j, curve_i_intersections.len()); for (idx, intersection) in curve_i_intersections.iter().enumerate() { println!(" {} - t1={:.3}, t2={:.3}, point=({:.1}, {:.1})", idx, intersection.t1, intersection.t2.unwrap_or(0.0), intersection.point.x, intersection.point.y); } total_intersections += curve_i_intersections.len(); } for intersection in curve_i_intersections { // Add to curve i intersections .get_mut(&i) .unwrap() .push((intersection.t1, intersection.point)); // Add to curve j if let Some(t2) = intersection.t2 { intersections .get_mut(&j) .unwrap() .push((t2, intersection.point)); } } } // Find self-intersections let self_intersections = find_self_intersections(&curves[i]); for intersection in self_intersections { intersections .get_mut(&i) .unwrap() .push((intersection.t1, intersection.point)); if let Some(t2) = intersection.t2 { intersections .get_mut(&i) .unwrap() .push((t2, intersection.point)); } } } println!("Total curve-curve intersections found: {}", total_intersections); // Sort and deduplicate intersections for each curve for curve_intersections in intersections.values_mut() { curve_intersections.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); // Remove duplicates (points very close together) let mut i = 0; while i + 1 < curve_intersections.len() { let dist = (curve_intersections[i].1 - curve_intersections[i + 1].1).hypot(); if dist < 0.5 { curve_intersections.remove(i + 1); } else { i += 1; } } } intersections } /// Build nodes and edges from curves and their intersections fn build_nodes_and_edges( _curves: &[CubicBez], intersections: HashMap>, ) -> (Vec, Vec) { let mut nodes = Vec::new(); let mut edges = Vec::new(); // Helper to get or create node at a position // Uses distance-based deduplication with 0.5 pixel tolerance const NODE_TOLERANCE: f64 = 0.5; let get_or_create_node = |position: Point, nodes: &mut Vec| -> usize { // Check if there's already a node within tolerance for (idx, node) in nodes.iter().enumerate() { let dist = (position - node.position).hypot(); if dist < NODE_TOLERANCE { return idx; } } // No nearby node found, create new one let node_idx = nodes.len(); nodes.push(GraphNode::new(position)); node_idx }; // Create edges for each curve for (curve_id, curve_intersections) in intersections.iter() { // Create edges between consecutive intersection points for i in 0..(curve_intersections.len() - 1) { let (t_start, p_start) = curve_intersections[i]; let (t_end, p_end) = curve_intersections[i + 1]; // Get or create nodes let start_node = get_or_create_node(p_start, &mut nodes); let end_node = get_or_create_node(p_end, &mut nodes); // Create edge let edge_idx = edges.len(); edges.push(GraphEdge::new( start_node, end_node, *curve_id, t_start, t_end, )); // Add edge to nodes nodes[start_node].edge_indices.push(edge_idx); nodes[end_node].edge_indices.push(edge_idx); } } (nodes, edges) } /// Prune dangling nodes (nodes with only one edge) from the graph /// /// This is useful for cleaning up the graph structure by removing dead ends /// that cannot be part of any face. Nodes are pruned iteratively until only /// nodes that are part of face loops remain (or the graph becomes empty). fn prune_dangling_nodes(&mut self) { println!("Starting graph pruning..."); let mut iteration = 0; loop { // Find nodes with only 1 edge let mut nodes_to_remove = Vec::new(); for (idx, node) in self.nodes.iter().enumerate() { if node.edge_indices.len() == 1 { nodes_to_remove.push(idx); } } if nodes_to_remove.is_empty() { println!("Pruning complete after {} iterations", iteration); break; } iteration += 1; println!("Pruning iteration {}: removing {} nodes", iteration, nodes_to_remove.len()); // Find edges connected to these nodes let mut edges_to_remove = HashSet::new(); for &node_idx in &nodes_to_remove { for &edge_idx in &self.nodes[node_idx].edge_indices { edges_to_remove.insert(edge_idx); } } // Remove the edges and nodes // We need to rebuild the structure since indices change // Create new nodes list (excluding removed ones) let mut new_nodes = Vec::new(); let mut old_to_new_node: HashMap = HashMap::new(); for (old_idx, node) in self.nodes.iter().enumerate() { if !nodes_to_remove.contains(&old_idx) { let new_idx = new_nodes.len(); old_to_new_node.insert(old_idx, new_idx); new_nodes.push(node.clone()); } } // Create new edges list (excluding removed ones and updating node indices) let mut new_edges = Vec::new(); for (old_idx, edge) in self.edges.iter().enumerate() { if !edges_to_remove.contains(&old_idx) { // Update node indices if let (Some(&new_start), Some(&new_end)) = (old_to_new_node.get(&edge.start_node), old_to_new_node.get(&edge.end_node)) { let mut new_edge = edge.clone(); new_edge.start_node = new_start; new_edge.end_node = new_end; new_edges.push(new_edge); } } } // Rebuild edge_indices in nodes for node in &mut new_nodes { node.edge_indices.clear(); } for (edge_idx, edge) in new_edges.iter().enumerate() { new_nodes[edge.start_node].edge_indices.push(edge_idx); new_nodes[edge.end_node].edge_indices.push(edge_idx); } // Update graph self.nodes = new_nodes; self.edges = new_edges; println!("After pruning: {} nodes, {} edges", self.nodes.len(), self.edges.len()); } } /// Find all faces in the planar graph pub fn find_faces(&self) -> Vec { let mut faces = Vec::new(); let mut used_half_edges = HashSet::new(); println!("Finding faces: trying {} edges in both directions", self.edges.len()); // Try starting from each edge in both directions for edge_idx in 0..self.edges.len() { // Try forward direction if !used_half_edges.contains(&(edge_idx, true)) { if let Some(face) = self.trace_face(edge_idx, true, &mut used_half_edges) { let start_edge = &self.edges[edge_idx]; print!("Successfully traced face {} starting from {} -> {} (edge {} fwd) with {} edges: ", faces.len(), start_edge.start_node, start_edge.end_node, edge_idx, face.edges.len()); for (idx, (e, fwd)) in face.edges.iter().enumerate() { let e_obj: &GraphEdge = &self.edges[*e]; let (n1, n2) = if *fwd { (e_obj.start_node, e_obj.end_node) } else { (e_obj.end_node, e_obj.start_node) }; print!("{} -> {}{}", n1, n2, if idx < face.edges.len() - 1 { " -> " } else { "" }); } println!(); faces.push(face); } } // Try backward direction if !used_half_edges.contains(&(edge_idx, false)) { if let Some(face) = self.trace_face(edge_idx, false, &mut used_half_edges) { let start_edge = &self.edges[edge_idx]; print!("Successfully traced face {} starting from {} -> {} (edge {} bwd) with {} edges: ", faces.len(), start_edge.end_node, start_edge.start_node, edge_idx, face.edges.len()); for (idx, (e, fwd)) in face.edges.iter().enumerate() { let e_obj: &GraphEdge = &self.edges[*e]; let (n1, n2) = if *fwd { (e_obj.start_node, e_obj.end_node) } else { (e_obj.end_node, e_obj.start_node) }; print!("{} -> {}{}", n1, n2, if idx < face.edges.len() - 1 { " -> " } else { "" }); } println!(); faces.push(face); } } } println!("Found {} faces", faces.len()); faces } /// Trace a face starting from an edge in a given direction /// Returns None if the face is already traced or invalid fn trace_face( &self, start_edge: usize, forward: bool, used_half_edges: &mut HashSet<(usize, bool)>, ) -> Option { // Use a local set for this trace attempt // Only add to global set if we successfully complete a face let mut temp_used = HashSet::new(); let mut edge_sequence = Vec::new(); let mut visited_nodes = HashSet::new(); let mut current_edge = start_edge; let mut current_forward = forward; // Get start node info for logging let start_edge_obj = &self.edges[start_edge]; let (start_node, start_end_node) = if forward { (start_edge_obj.start_node, start_edge_obj.end_node) } else { (start_edge_obj.end_node, start_edge_obj.start_node) }; println!("trace_face: Starting from node {} -> {} (edge {} {})", start_node, start_end_node, start_edge, if forward { "fwd" } else { "bwd" }); // Mark the starting node as visited visited_nodes.insert(start_node); loop { // Check if this half-edge is already used (globally or in this trace) if used_half_edges.contains(&(current_edge, current_forward)) || temp_used.contains(&(current_edge, current_forward)) { // Already traced this half-edge let current_edge_obj = &self.edges[current_edge]; let (curr_start, curr_end) = if current_forward { (current_edge_obj.start_node, current_edge_obj.end_node) } else { (current_edge_obj.end_node, current_edge_obj.start_node) }; println!("trace_face: Found already-used edge: {} -> {} (edge {} {}) after {} steps", curr_start, curr_end, current_edge, if current_forward { "fwd" } else { "bwd" }, edge_sequence.len()); // Print the full edge sequence to understand the sub-cycle print!(" Full sequence: "); for (idx, (e, fwd)) in edge_sequence.iter().enumerate() { let e_obj: &GraphEdge = &self.edges[*e]; let (n1, n2) = if *fwd { (e_obj.start_node, e_obj.end_node) } else { (e_obj.end_node, e_obj.start_node) }; print!("{} -> {}{}", n1, n2, if idx < edge_sequence.len() - 1 { " -> " } else { "" }); } println!(" -> {} -> {} (already used)", curr_start, curr_end); return None; } edge_sequence.push((current_edge, current_forward)); temp_used.insert((current_edge, current_forward)); // Get the end node of this half-edge let edge = &self.edges[current_edge]; let end_node = if current_forward { edge.end_node } else { edge.start_node }; // Check if we've returned to the starting node - if so, we've completed the face! if end_node == start_node && edge_sequence.len() >= 2 { println!("trace_face: Completed cycle back to starting node {} after {} edges", start_node, edge_sequence.len()); // Success! Add all edges from this trace to the global used set for &half_edge in &temp_used { used_half_edges.insert(half_edge); } return Some(Face { edges: edge_sequence }); } // Check if we've visited this end node before (it's not the start, so it's a self-intersection) if visited_nodes.contains(&end_node) { println!("trace_face: Detected node revisit at node {} - rejecting self-intersecting path", end_node); return None; } // Mark this node as visited visited_nodes.insert(end_node); // Find the next edge in counterclockwise order around end_node let next = self.find_next_ccw_edge(current_edge, current_forward, end_node); if let Some((next_edge, next_forward)) = next { current_edge = next_edge; current_forward = next_forward; // Continue to next iteration } else { // Dead end - not a valid face println!("trace_face: Dead end at node {}", end_node); return None; } // Safety check to prevent infinite loops if edge_sequence.len() > self.edges.len() * 2 { println!("Warning: Potential infinite loop detected in face tracing"); return None; } } } /// Find the next edge in counterclockwise order around a node fn find_next_ccw_edge( &self, incoming_edge: usize, incoming_forward: bool, node_idx: usize, ) -> Option<(usize, bool)> { let node = &self.nodes[node_idx]; // Get the reverse of the incoming direction (pointing back to where we came FROM) // This way, angle 0 = going back, and we measure CCW turns from the incoming edge let edge = &self.edges[incoming_edge]; let incoming_dir = if incoming_forward { let start_pos = self.nodes[edge.start_node].position; let end_pos = self.nodes[edge.end_node].position; // Reverse: point from end back to start (start_pos.x - end_pos.x, start_pos.y - end_pos.y) } else { let start_pos = self.nodes[edge.start_node].position; let end_pos = self.nodes[edge.end_node].position; // Reverse: point from start back to end (end_pos.x - start_pos.x, end_pos.y - start_pos.y) }; // Find all outgoing edges from this node let mut candidates = Vec::new(); for &edge_idx in &node.edge_indices { let edge = &self.edges[edge_idx]; // Check if this edge goes out from node_idx if edge.start_node == node_idx { // Forward direction let end_pos = self.nodes[edge.end_node].position; let node_pos = node.position; let out_dir = (end_pos.x - node_pos.x, end_pos.y - node_pos.y); candidates.push((edge_idx, true, out_dir)); } if edge.end_node == node_idx { // Backward direction let start_pos = self.nodes[edge.start_node].position; let node_pos = node.position; let out_dir = (start_pos.x - node_pos.x, start_pos.y - node_pos.y); candidates.push((edge_idx, false, out_dir)); } } // Debug: show incoming edge info let incoming_edge_obj = &self.edges[incoming_edge]; let (inc_start, inc_end) = if incoming_forward { (incoming_edge_obj.start_node, incoming_edge_obj.end_node) } else { (incoming_edge_obj.end_node, incoming_edge_obj.start_node) }; println!(" find_next_ccw_edge at node {} (incoming: {} -> {}, edge {} {})", node_idx, inc_start, inc_end, incoming_edge, if incoming_forward { "fwd" } else { "bwd" }); println!(" Available edges ({} candidates):", candidates.len()); // Find the edge with the largest CCW angle (rightmost turn for face tracing) // Since incoming_dir points back to where we came from, the largest angle // gives us the rightmost turn, which traces faces correctly. let mut best_edge = None; let mut best_angle = 0.0; for &(edge_idx, forward, out_dir) in &candidates { // Skip the edge we came from (in opposite direction) if edge_idx == incoming_edge && forward == !incoming_forward { println!(" Edge {} {} -> SKIP (reverse of incoming)", edge_idx, if forward { "fwd" } else { "bwd" }); continue; } // Compute angle from incoming to outgoing (counterclockwise) let angle = angle_between_ccw(incoming_dir, out_dir); // Get the destination node for this candidate let cand_edge = &self.edges[edge_idx]; let dest_node = if forward { cand_edge.end_node } else { cand_edge.start_node }; println!(" Edge {} {} -> node {} (angle: {:.3} rad = {:.1}°){}", edge_idx, if forward { "fwd" } else { "bwd" }, dest_node, angle, angle.to_degrees(), if angle > best_angle { " <- BEST" } else { "" }); if angle > best_angle { best_angle = angle; best_edge = Some((edge_idx, forward)); } } if best_edge.is_none() { println!(" FAILED: No valid next edge found!"); } best_edge } /// Find which face contains a given point pub fn find_face_containing_point(&self, point: Point, faces: &[Face]) -> Option { for (i, face) in faces.iter().enumerate() { // Build polygon for debugging let mut polygon_points = Vec::new(); for &(edge_idx, forward) in &face.edges { let edge = &self.edges[edge_idx]; let node_idx = if forward { edge.start_node } else { edge.end_node }; polygon_points.push(self.nodes[node_idx].position); } // Calculate bounding box let mut min_x = f64::MAX; let mut max_x = f64::MIN; let mut min_y = f64::MAX; let mut max_y = f64::MIN; for p in &polygon_points { min_x = min_x.min(p.x); max_x = max_x.max(p.x); min_y = min_y.min(p.y); max_y = max_y.max(p.y); } println!("Face {}: {} edges, {} points, bbox: ({:.1},{:.1}) to ({:.1},{:.1})", i, face.edges.len(), polygon_points.len(), min_x, min_y, max_x, max_y); if self.point_in_face(point, face) { return Some(i); } } None } /// Test if a point is inside a face using ray casting fn point_in_face(&self, point: Point, face: &Face) -> bool { // Build polygon from face edges let mut polygon_points = Vec::new(); for &(edge_idx, forward) in &face.edges { let edge = &self.edges[edge_idx]; let node_idx = if forward { edge.start_node } else { edge.end_node }; polygon_points.push(self.nodes[node_idx].position); } // Ray casting algorithm point_in_polygon(point, &polygon_points) } /// Build a BezPath from a face using the actual curve segments pub fn build_face_path(&self, face: &Face) -> BezPath { use vello::kurbo::ParamCurve; let mut path = BezPath::new(); let mut first = true; for &(edge_idx, forward) in &face.edges { let edge = &self.edges[edge_idx]; let orig_curve = &self.curves[edge.curve_id]; // Get the curve segment for this edge let segment = if forward { orig_curve.subsegment(edge.t_start..edge.t_end) } else { // Reverse the segment orig_curve.subsegment(edge.t_end..edge.t_start) }; if first { path.move_to(segment.p0); first = false; } // Add the curve segment path.curve_to(segment.p1, segment.p2, segment.p3); } path.close_path(); path } /// Find the closest edge to a given point /// /// Returns (edge_index, closest_point_on_edge, distance) fn find_closest_edge_to_point(&self, point: Point) -> Option<(usize, Point, f64)> { let mut best_edge = None; let mut best_distance = f64::MAX; let mut best_point = Point::ZERO; for (edge_idx, edge) in self.edges.iter().enumerate() { // Get the edge endpoints let start_pos = self.nodes[edge.start_node].position; let end_pos = self.nodes[edge.end_node].position; // Compute closest point on line segment manually // Vector from start to end let dx = end_pos.x - start_pos.x; let dy = end_pos.y - start_pos.y; // Squared length of segment let len_sq = dx * dx + dy * dy; let closest = if len_sq < 1e-10 { // Degenerate segment (start == end), closest point is start start_pos } else { // Parameter t of closest point on line let t = ((point.x - start_pos.x) * dx + (point.y - start_pos.y) * dy) / len_sq; // Clamp t to [0, 1] to keep it on the segment let t_clamped = t.max(0.0).min(1.0); // Compute closest point Point::new( start_pos.x + t_clamped * dx, start_pos.y + t_clamped * dy ) }; let distance = (point - closest).hypot(); if distance < best_distance { best_distance = distance; best_point = closest; best_edge = Some(edge_idx); } } best_edge.map(|idx| (idx, best_point, best_distance)) } /// Determine the starting node and direction for face traversal /// /// Uses cross product to determine which side of the edge the click point is on, /// then picks the traversal direction that keeps the point on the "inside" of the face. /// /// Returns (starting_node, starting_edge, forward) fn determine_face_traversal_start(&self, edge_idx: usize, click_point: Point) -> (usize, usize, bool) { let edge = &self.edges[edge_idx]; let start_pos = self.nodes[edge.start_node].position; let end_pos = self.nodes[edge.end_node].position; // Vector along the edge (forward direction: start -> end) let edge_vec = (end_pos.x - start_pos.x, end_pos.y - start_pos.y); // Vector from edge start to click point let to_point = (click_point.x - start_pos.x, click_point.y - start_pos.y); // Cross product: positive if point is to the left of edge, negative if to the right let cross = edge_vec.0 * to_point.1 - edge_vec.1 * to_point.0; if cross > 0.0 { // Point is to the left of the edge (when going start -> end) // To keep the point on our right (inside the face), we should traverse start -> end // This means starting from start_node and going forward (edge.start_node, edge_idx, true) } else { // Point is to the right of the edge (when going start -> end) // To keep the point on our right (inside the face), we should traverse end -> start // This means starting from end_node and going backward (edge.end_node, edge_idx, false) } } /// Trace a single face from a click point /// /// This is an optimized version that only traces the one face containing the click point, /// rather than finding all faces in the graph. /// /// Returns the face if successful, None if no valid face contains the click point pub fn trace_face_from_point(&self, click_point: Point) -> Option { println!("trace_face_from_point: Finding face containing {:?}", click_point); // Step 1: Find closest edge to the click point let (closest_edge_idx, closest_point, distance) = self.find_closest_edge_to_point(click_point)?; println!(" Closest edge: {} at distance {:.2}, point: {:?}", closest_edge_idx, distance, closest_point); // Step 2: Determine starting node and direction let (start_node, start_edge, start_forward) = self.determine_face_traversal_start(closest_edge_idx, click_point); println!(" Starting from node {}, edge {} {}", start_node, start_edge, if start_forward { "forward" } else { "backward" }); // Step 3: Trace the face using CCW traversal let mut edge_sequence = Vec::new(); let mut visited_nodes = HashSet::new(); let mut current_edge = start_edge; let mut current_forward = start_forward; // Mark the starting node as visited visited_nodes.insert(start_node); loop { // Add this edge to the sequence edge_sequence.push((current_edge, current_forward)); // Get the end node of this half-edge let edge = &self.edges[current_edge]; let end_node = if current_forward { edge.end_node } else { edge.start_node }; // Check if we've returned to the starting node - if so, we've completed the face! if end_node == start_node && edge_sequence.len() >= 2 { println!(" Completed face with {} edges", edge_sequence.len()); return Some(Face { edges: edge_sequence }); } // Check if we've visited this end node before (not the start, so it's an error) if visited_nodes.contains(&end_node) { println!(" Error: Visited node {} twice before completing loop", end_node); return None; } // Mark this node as visited visited_nodes.insert(end_node); // Find the next edge in counterclockwise order let next = self.find_next_ccw_edge(current_edge, current_forward, end_node); if let Some((next_edge, next_forward)) = next { current_edge = next_edge; current_forward = next_forward; } else { println!(" Dead end at node {}", end_node); return None; } // Safety check to prevent infinite loops if edge_sequence.len() > self.edges.len() { println!(" Error: Potential infinite loop detected"); return None; } } } } /// A face in the planar graph (bounded region) #[derive(Debug, Clone)] pub struct Face { /// Sequence of (edge_index, is_forward) pairs that form the boundary pub edges: Vec<(usize, bool)>, } /// Compute the counterclockwise angle from v1 to v2 fn angle_between_ccw(v1: (f64, f64), v2: (f64, f64)) -> f64 { let angle1 = v1.1.atan2(v1.0); let angle2 = v2.1.atan2(v2.0); let mut diff = angle2 - angle1; // Normalize to [0, 2π) while diff < 0.0 { diff += 2.0 * std::f64::consts::PI; } while diff >= 2.0 * std::f64::consts::PI { diff -= 2.0 * std::f64::consts::PI; } diff } /// Test if a point is inside a polygon using ray casting fn point_in_polygon(point: Point, polygon: &[Point]) -> bool { let mut inside = false; let n = polygon.len(); for i in 0..n { let j = (i + 1) % n; let pi = polygon[i]; let pj = polygon[j]; if ((pi.y > point.y) != (pj.y > point.y)) && (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x) { inside = !inside; } } inside }