check nodes instead of half edges to confirm faces in paint bucket graph
This commit is contained in:
parent
71f9283356
commit
7d90eed1ec
|
|
@ -85,6 +85,11 @@ impl Action for PaintBucketAction {
|
||||||
println!("Building planar graph...");
|
println!("Building planar graph...");
|
||||||
let graph = PlanarGraph::build(&all_curves);
|
let graph = PlanarGraph::build(&all_curves);
|
||||||
|
|
||||||
|
// Store graph for debug visualization
|
||||||
|
if let Ok(mut debug_graph) = crate::planar_graph::DEBUG_GRAPH.lock() {
|
||||||
|
*debug_graph = Some(graph.clone());
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3: Render debug visualization of planar graph
|
// Step 3: Render debug visualization of planar graph
|
||||||
println!("Rendering planar graph debug visualization...");
|
println!("Rendering planar graph debug visualization...");
|
||||||
let (nodes_shape, edges_shape) = graph.render_debug();
|
let (nodes_shape, edges_shape) = graph.render_debug();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,12 @@ use crate::curve_intersections::{find_curve_intersections, find_self_intersectio
|
||||||
use crate::curve_segment::CurveSegment;
|
use crate::curve_segment::CurveSegment;
|
||||||
use crate::shape::{Shape, ShapeColor, StrokeStyle};
|
use crate::shape::{Shape, ShapeColor, StrokeStyle};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::Mutex;
|
||||||
use vello::kurbo::{BezPath, Circle, CubicBez, Point, Shape as KurboShape};
|
use vello::kurbo::{BezPath, Circle, CubicBez, Point, Shape as KurboShape};
|
||||||
|
|
||||||
|
/// Global debug storage for the last planar graph (for visualization)
|
||||||
|
pub static DEBUG_GRAPH: Mutex<Option<PlanarGraph>> = Mutex::new(None);
|
||||||
|
|
||||||
/// A node in the planar graph (intersection point or endpoint)
|
/// A node in the planar graph (intersection point or endpoint)
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GraphNode {
|
pub struct GraphNode {
|
||||||
|
|
@ -66,6 +70,7 @@ impl GraphEdge {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Planar graph structure
|
/// Planar graph structure
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PlanarGraph {
|
pub struct PlanarGraph {
|
||||||
/// All nodes in the graph
|
/// All nodes in the graph
|
||||||
pub nodes: Vec<GraphNode>,
|
pub nodes: Vec<GraphNode>,
|
||||||
|
|
@ -373,8 +378,19 @@ impl PlanarGraph {
|
||||||
// Try forward direction
|
// Try forward direction
|
||||||
if !used_half_edges.contains(&(edge_idx, true)) {
|
if !used_half_edges.contains(&(edge_idx, true)) {
|
||||||
if let Some(face) = self.trace_face(edge_idx, true, &mut used_half_edges) {
|
if let Some(face) = self.trace_face(edge_idx, true, &mut used_half_edges) {
|
||||||
println!("Successfully traced face {} starting from edge {} fwd with {} edges",
|
let start_edge = &self.edges[edge_idx];
|
||||||
faces.len(), edge_idx, face.edges.len());
|
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);
|
faces.push(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,8 +398,19 @@ impl PlanarGraph {
|
||||||
// Try backward direction
|
// Try backward direction
|
||||||
if !used_half_edges.contains(&(edge_idx, false)) {
|
if !used_half_edges.contains(&(edge_idx, false)) {
|
||||||
if let Some(face) = self.trace_face(edge_idx, false, &mut used_half_edges) {
|
if let Some(face) = self.trace_face(edge_idx, false, &mut used_half_edges) {
|
||||||
println!("Successfully traced face {} starting from edge {} bwd with {} edges",
|
let start_edge = &self.edges[edge_idx];
|
||||||
faces.len(), edge_idx, face.edges.len());
|
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);
|
faces.push(face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -401,43 +428,124 @@ impl PlanarGraph {
|
||||||
forward: bool,
|
forward: bool,
|
||||||
used_half_edges: &mut HashSet<(usize, bool)>,
|
used_half_edges: &mut HashSet<(usize, bool)>,
|
||||||
) -> Option<Face> {
|
) -> Option<Face> {
|
||||||
|
// 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 edge_sequence = Vec::new();
|
||||||
|
let mut visited_nodes = HashSet::new();
|
||||||
let mut current_edge = start_edge;
|
let mut current_edge = start_edge;
|
||||||
let mut current_forward = forward;
|
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 {
|
loop {
|
||||||
// Mark this half-edge as used
|
// Check if this half-edge is already used (globally or in this trace)
|
||||||
if used_half_edges.contains(&(current_edge, current_forward)) {
|
if used_half_edges.contains(&(current_edge, current_forward))
|
||||||
|
|| temp_used.contains(&(current_edge, current_forward)) {
|
||||||
// Already traced this half-edge
|
// 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
edge_sequence.push((current_edge, current_forward));
|
edge_sequence.push((current_edge, current_forward));
|
||||||
used_half_edges.insert((current_edge, current_forward));
|
temp_used.insert((current_edge, current_forward));
|
||||||
|
|
||||||
// Get the end node of this half-edge
|
// Get the end node of this half-edge
|
||||||
let edge = &self.edges[current_edge];
|
let edge = &self.edges[current_edge];
|
||||||
|
let start_node_this_edge = if current_forward {
|
||||||
|
edge.start_node
|
||||||
|
} else {
|
||||||
|
edge.end_node
|
||||||
|
};
|
||||||
let end_node = if current_forward {
|
let end_node = if current_forward {
|
||||||
edge.end_node
|
edge.end_node
|
||||||
} else {
|
} else {
|
||||||
edge.start_node
|
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
|
// Find the next edge in counterclockwise order around end_node
|
||||||
let next = self.find_next_ccw_edge(current_edge, current_forward, end_node);
|
let next = self.find_next_ccw_edge(current_edge, current_forward, end_node);
|
||||||
|
|
||||||
|
if edge_sequence.len() <= 5 {
|
||||||
|
if let Some((next_edge, next_forward)) = next {
|
||||||
|
let next_edge_obj = &self.edges[next_edge];
|
||||||
|
let next_end_node = if next_forward {
|
||||||
|
next_edge_obj.end_node
|
||||||
|
} else {
|
||||||
|
next_edge_obj.start_node
|
||||||
|
};
|
||||||
|
println!(" Step {}: {} -> {} (edge {} {}) -> next: {} -> {} (edge {} {})",
|
||||||
|
edge_sequence.len(), start_node_this_edge, end_node,
|
||||||
|
current_edge, if current_forward { "fwd" } else { "bwd" },
|
||||||
|
end_node, next_end_node, next_edge, if next_forward { "fwd" } else { "bwd" });
|
||||||
|
} else {
|
||||||
|
println!(" Step {}: {} -> {} (edge {} {}) -> next: None",
|
||||||
|
edge_sequence.len(), start_node_this_edge, end_node,
|
||||||
|
current_edge, if current_forward { "fwd" } else { "bwd" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((next_edge, next_forward)) = next {
|
if let Some((next_edge, next_forward)) = next {
|
||||||
current_edge = next_edge;
|
current_edge = next_edge;
|
||||||
current_forward = next_forward;
|
current_forward = next_forward;
|
||||||
|
// Continue to next iteration
|
||||||
// Check if we've completed the loop
|
|
||||||
if current_edge == start_edge && current_forward == forward {
|
|
||||||
return Some(Face { edges: edge_sequence });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Dead end - not a valid face
|
// Dead end - not a valid face
|
||||||
println!("trace_face: Dead end at node {} (from edge {} {})",
|
println!("trace_face: Dead end at node {}", end_node);
|
||||||
end_node, current_edge, if current_forward { "fwd" } else { "bwd" });
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -513,8 +621,14 @@ impl PlanarGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
if best_edge.is_none() {
|
if best_edge.is_none() {
|
||||||
println!("find_next_ccw_edge FAILED at node {}: {} candidates total, incoming edge {} {}, found no valid next edge",
|
let incoming_edge_obj = &self.edges[incoming_edge];
|
||||||
node_idx, candidates.len(), incoming_edge, if incoming_forward { "fwd" } else { "bwd" });
|
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 FAILED at node {}: {} candidates total, incoming {} -> {} (edge {} {}), found no valid next edge",
|
||||||
|
node_idx, candidates.len(), inc_start, inc_end, incoming_edge, if incoming_forward { "fwd" } else { "bwd" });
|
||||||
}
|
}
|
||||||
|
|
||||||
best_edge
|
best_edge
|
||||||
|
|
@ -523,6 +637,29 @@ impl PlanarGraph {
|
||||||
/// Find which face contains a given point
|
/// Find which face contains a given point
|
||||||
pub fn find_face_containing_point(&self, point: Point, faces: &[Face]) -> Option<usize> {
|
pub fn find_face_containing_point(&self, point: Point, faces: &[Face]) -> Option<usize> {
|
||||||
for (i, face) in faces.iter().enumerate() {
|
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) {
|
if self.point_in_face(point, face) {
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@ pub enum Tool {
|
||||||
PaintBucket,
|
PaintBucket,
|
||||||
/// Eyedropper - pick colors from the canvas
|
/// Eyedropper - pick colors from the canvas
|
||||||
Eyedropper,
|
Eyedropper,
|
||||||
|
/// Line tool - draw straight lines
|
||||||
|
Line,
|
||||||
|
/// Polygon tool - draw polygons
|
||||||
|
Polygon,
|
||||||
|
/// Bezier edit tool - edit bezier curve control points
|
||||||
|
BezierEdit,
|
||||||
|
/// Text tool - add and edit text
|
||||||
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tool state tracking for interactive operations
|
/// Tool state tracking for interactive operations
|
||||||
|
|
@ -125,6 +133,10 @@ impl Tool {
|
||||||
Tool::Ellipse => "Ellipse",
|
Tool::Ellipse => "Ellipse",
|
||||||
Tool::PaintBucket => "Paint Bucket",
|
Tool::PaintBucket => "Paint Bucket",
|
||||||
Tool::Eyedropper => "Eyedropper",
|
Tool::Eyedropper => "Eyedropper",
|
||||||
|
Tool::Line => "Line",
|
||||||
|
Tool::Polygon => "Polygon",
|
||||||
|
Tool::BezierEdit => "Bezier Edit",
|
||||||
|
Tool::Text => "Text",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,6 +150,10 @@ impl Tool {
|
||||||
Tool::Ellipse => "ellipse.svg",
|
Tool::Ellipse => "ellipse.svg",
|
||||||
Tool::PaintBucket => "paint_bucket.svg",
|
Tool::PaintBucket => "paint_bucket.svg",
|
||||||
Tool::Eyedropper => "eyedropper.svg",
|
Tool::Eyedropper => "eyedropper.svg",
|
||||||
|
Tool::Line => "line.svg",
|
||||||
|
Tool::Polygon => "polygon.svg",
|
||||||
|
Tool::BezierEdit => "bezier_edit.svg",
|
||||||
|
Tool::Text => "text.svg",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,6 +167,10 @@ impl Tool {
|
||||||
Tool::Ellipse,
|
Tool::Ellipse,
|
||||||
Tool::PaintBucket,
|
Tool::PaintBucket,
|
||||||
Tool::Eyedropper,
|
Tool::Eyedropper,
|
||||||
|
Tool::Line,
|
||||||
|
Tool::Polygon,
|
||||||
|
Tool::BezierEdit,
|
||||||
|
Tool::Text,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,6 +184,10 @@ impl Tool {
|
||||||
Tool::Ellipse => "E",
|
Tool::Ellipse => "E",
|
||||||
Tool::PaintBucket => "B",
|
Tool::PaintBucket => "B",
|
||||||
Tool::Eyedropper => "I",
|
Tool::Eyedropper => "I",
|
||||||
|
Tool::Line => "L",
|
||||||
|
Tool::Polygon => "G",
|
||||||
|
Tool::BezierEdit => "A",
|
||||||
|
Tool::Text => "T",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue