fix paint bucket angle priority
This commit is contained in:
parent
b7c382586e
commit
502bae0947
|
|
@ -71,10 +71,10 @@ impl Action for PaintBucketAction {
|
||||||
fn execute(&mut self, document: &mut Document) {
|
fn execute(&mut self, document: &mut Document) {
|
||||||
println!("=== PaintBucketAction::execute (Planar Graph Approach) ===");
|
println!("=== PaintBucketAction::execute (Planar Graph Approach) ===");
|
||||||
|
|
||||||
// Step 1: Extract curves from stroked shapes only (not filled regions)
|
// Step 1: Extract curves from all shapes (rectangles, ellipses, paths, etc.)
|
||||||
let all_curves = extract_curves_from_stroked_shapes(document, &self.layer_id);
|
let all_curves = extract_curves_from_all_shapes(document, &self.layer_id);
|
||||||
|
|
||||||
println!("Extracted {} curves from stroked shapes", all_curves.len());
|
println!("Extracted {} curves from all shapes", all_curves.len());
|
||||||
|
|
||||||
if all_curves.is_empty() {
|
if all_curves.is_empty() {
|
||||||
println!("No curves found, returning");
|
println!("No curves found, returning");
|
||||||
|
|
@ -161,11 +161,11 @@ impl Action for PaintBucketAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract curves from stroked shapes only (not filled regions)
|
/// Extract curves from all shapes in the layer
|
||||||
///
|
///
|
||||||
/// This filters out paint bucket filled shapes which have only fills, not strokes.
|
/// Includes rectangles, ellipses, paths, and even previous paint bucket fills.
|
||||||
/// Stroked shapes define boundaries for the planar graph.
|
/// The planar graph builder will handle deduplication of overlapping edges.
|
||||||
fn extract_curves_from_stroked_shapes(
|
fn extract_curves_from_all_shapes(
|
||||||
document: &Document,
|
document: &Document,
|
||||||
layer_id: &Uuid,
|
layer_id: &Uuid,
|
||||||
) -> Vec<CurveSegment> {
|
) -> Vec<CurveSegment> {
|
||||||
|
|
@ -179,25 +179,26 @@ fn extract_curves_from_stroked_shapes(
|
||||||
|
|
||||||
// Extract curves only from this vector layer
|
// Extract curves only from this vector layer
|
||||||
if let AnyLayer::Vector(vector_layer) = layer {
|
if let AnyLayer::Vector(vector_layer) = layer {
|
||||||
|
println!("Extracting curves from {} objects in layer", vector_layer.objects.len());
|
||||||
// Extract curves from each object (which applies transforms to shapes)
|
// Extract curves from each object (which applies transforms to shapes)
|
||||||
for object in &vector_layer.objects {
|
for (obj_idx, object) in vector_layer.objects.iter().enumerate() {
|
||||||
// Find the shape for this object
|
// Find the shape for this object
|
||||||
let shape = match vector_layer.shapes.iter().find(|s| s.id == object.shape_id) {
|
let shape = match vector_layer.shapes.iter().find(|s| s.id == object.shape_id) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip shapes without strokes (these are filled regions, not boundaries)
|
// Include all shapes - planar graph will handle deduplication
|
||||||
if shape.stroke_color.is_none() {
|
// (Rectangles, ellipses, paths, and even previous paint bucket fills)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the transform matrix from the object
|
// Get the transform matrix from the object
|
||||||
let transform_affine = object.transform.to_affine();
|
let transform_affine = object.transform.to_affine();
|
||||||
|
|
||||||
let path = shape.path();
|
let path = shape.path();
|
||||||
let mut current_point = Point::ZERO;
|
let mut current_point = Point::ZERO;
|
||||||
|
let mut subpath_start = Point::ZERO; // Track start of current subpath
|
||||||
let mut segment_index = 0;
|
let mut segment_index = 0;
|
||||||
|
let mut curves_in_shape = 0;
|
||||||
|
|
||||||
for element in path.elements() {
|
for element in path.elements() {
|
||||||
// Extract curve segment from path element
|
// Extract curve segment from path element
|
||||||
|
|
@ -214,18 +215,42 @@ fn extract_curves_from_stroked_shapes(
|
||||||
|
|
||||||
all_curves.push(segment);
|
all_curves.push(segment);
|
||||||
segment_index += 1;
|
segment_index += 1;
|
||||||
|
curves_in_shape += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update current point for next iteration (keep in local space)
|
// Update current point for next iteration (keep in local space)
|
||||||
match element {
|
match element {
|
||||||
vello::kurbo::PathEl::MoveTo(p) => current_point = *p,
|
vello::kurbo::PathEl::MoveTo(p) => {
|
||||||
|
current_point = *p;
|
||||||
|
subpath_start = *p; // Mark start of new subpath
|
||||||
|
}
|
||||||
vello::kurbo::PathEl::LineTo(p) => current_point = *p,
|
vello::kurbo::PathEl::LineTo(p) => current_point = *p,
|
||||||
vello::kurbo::PathEl::QuadTo(_, p) => current_point = *p,
|
vello::kurbo::PathEl::QuadTo(_, p) => current_point = *p,
|
||||||
vello::kurbo::PathEl::CurveTo(_, _, p) => current_point = *p,
|
vello::kurbo::PathEl::CurveTo(_, _, p) => current_point = *p,
|
||||||
vello::kurbo::PathEl::ClosePath => {}
|
vello::kurbo::PathEl::ClosePath => {
|
||||||
|
// Create closing segment from current_point back to subpath_start
|
||||||
|
if let Some(mut segment) = CurveSegment::from_path_element(
|
||||||
|
shape.id.as_u128() as usize,
|
||||||
|
segment_index,
|
||||||
|
&vello::kurbo::PathEl::LineTo(subpath_start),
|
||||||
|
current_point,
|
||||||
|
) {
|
||||||
|
// Apply transform
|
||||||
|
for control_point in &mut segment.control_points {
|
||||||
|
*control_point = transform_affine * (*control_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
all_curves.push(segment);
|
||||||
|
segment_index += 1;
|
||||||
|
curves_in_shape += 1;
|
||||||
|
}
|
||||||
|
current_point = subpath_start; // ClosePath moves back to start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!(" Object {}: Extracted {} curves from shape", obj_idx, curves_in_shape);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
all_curves
|
all_curves
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,22 @@ impl PlanarGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find curve-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 i in 0..curves.len() {
|
||||||
for j in (i + 1)..curves.len() {
|
for j in (i + 1)..curves.len() {
|
||||||
let curve_i_intersections = find_curve_intersections(&curves[i], &curves[j]);
|
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 {
|
for intersection in curve_i_intersections {
|
||||||
// Add to curve i
|
// Add to curve i
|
||||||
intersections
|
intersections
|
||||||
|
|
@ -172,6 +184,8 @@ impl PlanarGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("Total curve-curve intersections found: {}", total_intersections);
|
||||||
|
|
||||||
// Sort and deduplicate intersections for each curve
|
// Sort and deduplicate intersections for each curve
|
||||||
for curve_intersections in intersections.values_mut() {
|
for curve_intersections in intersections.values_mut() {
|
||||||
curve_intersections.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
curve_intersections.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||||
|
|
@ -520,25 +534,6 @@ impl PlanarGraph {
|
||||||
// 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;
|
||||||
|
|
@ -566,16 +561,19 @@ impl PlanarGraph {
|
||||||
) -> Option<(usize, bool)> {
|
) -> Option<(usize, bool)> {
|
||||||
let node = &self.nodes[node_idx];
|
let node = &self.nodes[node_idx];
|
||||||
|
|
||||||
// Get the incoming direction vector (pointing INTO this node)
|
// 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 edge = &self.edges[incoming_edge];
|
||||||
let incoming_dir = if incoming_forward {
|
let incoming_dir = if incoming_forward {
|
||||||
let start_pos = self.nodes[edge.start_node].position;
|
let start_pos = self.nodes[edge.start_node].position;
|
||||||
let end_pos = self.nodes[edge.end_node].position;
|
let end_pos = self.nodes[edge.end_node].position;
|
||||||
(end_pos.x - start_pos.x, end_pos.y - start_pos.y)
|
// Reverse: point from end back to start
|
||||||
|
(start_pos.x - end_pos.x, start_pos.y - end_pos.y)
|
||||||
} else {
|
} else {
|
||||||
let start_pos = self.nodes[edge.start_node].position;
|
let start_pos = self.nodes[edge.start_node].position;
|
||||||
let end_pos = self.nodes[edge.end_node].position;
|
let end_pos = self.nodes[edge.end_node].position;
|
||||||
(start_pos.x - end_pos.x, start_pos.y - end_pos.y)
|
// 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
|
// Find all outgoing edges from this node
|
||||||
|
|
@ -601,34 +599,51 @@ impl PlanarGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the edge that makes the smallest left turn (most counterclockwise)
|
// Debug: show incoming edge info
|
||||||
let mut best_edge = None;
|
|
||||||
let mut best_angle = std::f64::MAX;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute angle from incoming to outgoing (counterclockwise)
|
|
||||||
let angle = angle_between_ccw(incoming_dir, out_dir);
|
|
||||||
|
|
||||||
if angle < best_angle {
|
|
||||||
best_angle = angle;
|
|
||||||
best_edge = Some((edge_idx, forward));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if best_edge.is_none() {
|
|
||||||
let incoming_edge_obj = &self.edges[incoming_edge];
|
let incoming_edge_obj = &self.edges[incoming_edge];
|
||||||
let (inc_start, inc_end) = if incoming_forward {
|
let (inc_start, inc_end) = if incoming_forward {
|
||||||
(incoming_edge_obj.start_node, incoming_edge_obj.end_node)
|
(incoming_edge_obj.start_node, incoming_edge_obj.end_node)
|
||||||
} else {
|
} else {
|
||||||
(incoming_edge_obj.end_node, incoming_edge_obj.start_node)
|
(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" });
|
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
|
best_edge
|
||||||
|
|
|
||||||
|
|
@ -2743,6 +2743,35 @@ impl PaneRenderer for StagePane {
|
||||||
egui::FontId::proportional(14.0),
|
egui::FontId::proportional(14.0),
|
||||||
egui::Color32::from_gray(200),
|
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 {
|
fn name(&self) -> &str {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue