paint bucket mostly working
This commit is contained in:
parent
502bae0947
commit
08f3c30b29
|
|
@ -69,7 +69,46 @@ impl PaintBucketAction {
|
|||
|
||||
impl Action for PaintBucketAction {
|
||||
fn execute(&mut self, document: &mut Document) {
|
||||
println!("=== PaintBucketAction::execute (Planar Graph Approach) ===");
|
||||
println!("=== PaintBucketAction::execute ===");
|
||||
|
||||
// Optimization: Check if we're clicking on an existing shape first
|
||||
// This is much faster than building a planar graph
|
||||
if let Some(AnyLayer::Vector(vector_layer)) = document.get_layer_mut(&self.layer_id) {
|
||||
// Iterate through objects in reverse order (topmost first)
|
||||
for object in vector_layer.objects.iter().rev() {
|
||||
// Find the corresponding shape
|
||||
if let Some(shape) = vector_layer.shapes.iter().find(|s| s.id == object.shape_id) {
|
||||
// Apply the object's transform to get the transformed path
|
||||
let transform_affine = object.transform.to_affine();
|
||||
|
||||
// Transform the click point to shape's local coordinates (inverse transform)
|
||||
let inverse_transform = transform_affine.inverse();
|
||||
let local_point = inverse_transform * self.click_point;
|
||||
|
||||
// Test if the local point is inside the shape using winding number
|
||||
use vello::kurbo::Shape as KurboShape;
|
||||
let winding = shape.path().winding(local_point);
|
||||
|
||||
if winding != 0 {
|
||||
// Point is inside this shape! Just change its fill color
|
||||
println!("Clicked on existing shape, changing fill color");
|
||||
|
||||
// Store the shape ID before the immutable borrow ends
|
||||
let shape_id = shape.id;
|
||||
|
||||
// Find mutable reference to the shape and update its fill
|
||||
if let Some(shape_mut) = vector_layer.shapes.iter_mut().find(|s| s.id == shape_id) {
|
||||
shape_mut.fill_color = Some(self.fill_color);
|
||||
println!("Updated shape fill color");
|
||||
}
|
||||
|
||||
return; // Done! No need to create a new shape
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("No existing shape at click point, creating new fill region");
|
||||
}
|
||||
|
||||
// Step 1: Extract curves from all shapes (rectangles, ellipses, paths, etc.)
|
||||
let all_curves = extract_curves_from_all_shapes(document, &self.layer_id);
|
||||
|
|
@ -85,41 +124,22 @@ impl Action for PaintBucketAction {
|
|||
println!("Building planar graph...");
|
||||
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
|
||||
println!("Rendering planar graph debug visualization...");
|
||||
let (nodes_shape, edges_shape) = graph.render_debug();
|
||||
let nodes_object = Object::new(nodes_shape.id);
|
||||
let edges_object = Object::new(edges_shape.id);
|
||||
|
||||
if let Some(AnyLayer::Vector(vector_layer)) = document.get_layer_mut(&self.layer_id) {
|
||||
vector_layer.add_shape_internal(edges_shape);
|
||||
vector_layer.add_object_internal(edges_object);
|
||||
vector_layer.add_shape_internal(nodes_shape);
|
||||
vector_layer.add_object_internal(nodes_object);
|
||||
println!("DEBUG: Added graph visualization (yellow=edges, red=nodes)");
|
||||
}
|
||||
|
||||
// Step 4: Find all faces
|
||||
println!("Finding faces in planar graph...");
|
||||
let faces = graph.find_faces();
|
||||
|
||||
// Step 5: Find which face contains the click point
|
||||
println!("Finding face containing click point {:?}...", self.click_point);
|
||||
if let Some(face_idx) = graph.find_face_containing_point(self.click_point, &faces) {
|
||||
println!("Found face {} containing click point!", face_idx);
|
||||
// Step 3: Trace the face containing the click point (optimized - only traces one face)
|
||||
println!("Tracing face from click point {:?}...", self.click_point);
|
||||
if let Some(face) = graph.trace_face_from_point(self.click_point) {
|
||||
println!("Successfully traced face containing click point!");
|
||||
|
||||
// Build the face boundary using actual curve segments
|
||||
let face = &faces[face_idx];
|
||||
let face_path = graph.build_face_path(face);
|
||||
let face_path = graph.build_face_path(&face);
|
||||
|
||||
println!("DEBUG: Creating face shape with fill color: r={}, g={}, b={}, a={}",
|
||||
self.fill_color.r, self.fill_color.g, self.fill_color.b, self.fill_color.a);
|
||||
|
||||
let face_shape = crate::shape::Shape::new(face_path)
|
||||
.with_fill(self.fill_color); // Use the requested fill color
|
||||
|
||||
println!("DEBUG: Face shape created with fill_color: {:?}", face_shape.fill_color);
|
||||
|
||||
let face_object = Object::new(face_shape.id);
|
||||
|
||||
// Store the created IDs for rollback
|
||||
|
|
@ -127,9 +147,15 @@ impl Action for PaintBucketAction {
|
|||
self.created_object_id = Some(face_object.id);
|
||||
|
||||
if let Some(AnyLayer::Vector(vector_layer)) = document.get_layer_mut(&self.layer_id) {
|
||||
let shape_id_for_debug = face_shape.id;
|
||||
vector_layer.add_shape_internal(face_shape);
|
||||
vector_layer.add_object_internal(face_object);
|
||||
println!("DEBUG: Added filled shape for face {}", face_idx);
|
||||
println!("DEBUG: Added filled shape");
|
||||
|
||||
// Verify the shape still has the fill color after being added
|
||||
if let Some(added_shape) = vector_layer.shapes.iter().find(|s| s.id == shape_id_for_debug) {
|
||||
println!("DEBUG: After adding to layer, shape fill_color = {:?}", added_shape.fill_color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Click point is not inside any face!");
|
||||
|
|
|
|||
|
|
@ -10,13 +10,8 @@
|
|||
|
||||
use crate::curve_intersections::{find_curve_intersections, find_self_intersections};
|
||||
use crate::curve_segment::CurveSegment;
|
||||
use crate::shape::{Shape, ShapeColor, StrokeStyle};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Mutex;
|
||||
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);
|
||||
use vello::kurbo::{BezPath, CubicBez, Point};
|
||||
|
||||
/// A node in the planar graph (intersection point or endpoint)
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -343,43 +338,6 @@ impl PlanarGraph {
|
|||
}
|
||||
}
|
||||
|
||||
/// Render debug visualization of the planar graph
|
||||
///
|
||||
/// Returns two shapes: one for nodes (red circles) and one for edges (yellow lines)
|
||||
pub fn render_debug(&self) -> (Shape, Shape) {
|
||||
// Render nodes as red circles
|
||||
let mut nodes_path = BezPath::new();
|
||||
for node in &self.nodes {
|
||||
let circle = Circle::new(node.position, 3.0);
|
||||
nodes_path.extend(circle.to_path(0.1));
|
||||
}
|
||||
let nodes_shape = Shape::new(nodes_path).with_stroke(
|
||||
ShapeColor::rgb(255, 0, 0),
|
||||
StrokeStyle {
|
||||
width: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Render edges as yellow straight lines
|
||||
let mut edges_path = BezPath::new();
|
||||
for edge in &self.edges {
|
||||
let start_pos = self.nodes[edge.start_node].position;
|
||||
let end_pos = self.nodes[edge.end_node].position;
|
||||
edges_path.move_to(start_pos);
|
||||
edges_path.line_to(end_pos);
|
||||
}
|
||||
let edges_shape = Shape::new(edges_path).with_stroke(
|
||||
ShapeColor::rgb(255, 255, 0),
|
||||
StrokeStyle {
|
||||
width: 0.5,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
(nodes_shape, edges_shape)
|
||||
}
|
||||
|
||||
/// Find all faces in the planar graph
|
||||
pub fn find_faces(&self) -> Vec<Face> {
|
||||
let mut faces = Vec::new();
|
||||
|
|
@ -728,6 +686,161 @@ impl PlanarGraph {
|
|||
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<Face> {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -2743,35 +2743,6 @@ impl PaneRenderer for StagePane {
|
|||
egui::FontId::proportional(14.0),
|
||||
egui::Color32::from_gray(200),
|
||||
);
|
||||
|
||||
// Render planar graph debug visualization
|
||||
if let Ok(debug_graph_opt) = lightningbeam_core::planar_graph::DEBUG_GRAPH.lock() {
|
||||
if let Some(ref graph) = *debug_graph_opt {
|
||||
// Draw node labels
|
||||
for (idx, node) in graph.nodes.iter().enumerate() {
|
||||
// Transform world coords to screen coords
|
||||
let screen_x = (node.position.x + self.pan_offset.x as f64) * self.zoom as f64 + rect.min.x as f64;
|
||||
let screen_y = (node.position.y + self.pan_offset.y as f64) * self.zoom as f64 + rect.min.y as f64;
|
||||
let screen_pos = egui::pos2(screen_x as f32, screen_y as f32);
|
||||
|
||||
// Draw small circle at node
|
||||
ui.painter().circle_filled(
|
||||
screen_pos,
|
||||
4.0,
|
||||
egui::Color32::from_rgb(255, 100, 100),
|
||||
);
|
||||
|
||||
// Draw node number label
|
||||
ui.painter().text(
|
||||
screen_pos + egui::vec2(8.0, -8.0),
|
||||
egui::Align2::LEFT_BOTTOM,
|
||||
format!("{}", idx),
|
||||
egui::FontId::monospace(14.0),
|
||||
egui::Color32::from_rgb(0, 0, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
|
|
|
|||
Loading…
Reference in New Issue