use super::fill_convex::*;
use super::fill_settings::*;
use crate::geo::*;
use crate::line::*;
use crate::bezier::*;
use crate::bezier::path::*;
use std::f64;
#[derive(Clone, Debug)]
enum ConcaveItem<Item> {
Edge(Item),
SelfIntersection(usize)
}
impl<Item> Into<Option<Item>> for ConcaveItem<Item> {
fn into(self) -> Option<Item> {
match self {
ConcaveItem::Edge(item) => Some(item),
ConcaveItem::SelfIntersection(_) => None
}
}
}
struct LongEdge<Coord> {
start: Coord,
end: Coord,
edge_index: (usize, usize),
ray_collided: bool
}
fn find_long_edges<Coord, Item>(edges: &[RayCollision<Coord, Item>], edge_min_len_squared: f64) -> Vec<LongEdge<Coord>>
where
Coord: Coordinate+Coordinate2D,
{
let mut long_edges = vec![];
for edge_num in 0..edges.len() {
let last_edge = if edge_num == 0 {
edges.len() - 1
} else {
edge_num-1
};
let edge_offset = edges[last_edge].position - edges[edge_num].position;
let edge_distance_squared = edge_offset.dot(&edge_offset);
if edge_distance_squared >= edge_min_len_squared {
long_edges.push(LongEdge {
start: edges[last_edge].position,
end: edges[edge_num].position,
edge_index: (last_edge, edge_num),
ray_collided: false
})
}
}
long_edges
}
fn ray_is_moving_outwards<Coord>(center: &Coord, from: &Coord, to: &Coord) -> bool
where
Coord: Coordinate+Coordinate2D,
{
let ray = (*center, *from);
let pos = ray.pos_for_point(to);
pos > 1.0
}
fn remove_small_gaps<Coord, Item>(center: &Coord, edges: &mut Vec<RayCollision<Coord, Item>>, long_edges: &mut Vec<LongEdge<Coord>>, min_gap_size: f64)
where
Coord: Coordinate+Coordinate2D,
{
let min_gap_sq = min_gap_size * min_gap_size;
let mut long_edges_to_remove = vec![];
for edge1_idx in 0..long_edges.len() {
let edge2_idx = if edge1_idx < long_edges.len()-1 { edge1_idx + 1 } else { 0 };
let edge1 = &long_edges[edge1_idx];
let edge2 = &long_edges[edge2_idx];
if ray_is_moving_outwards(center, &edge1.start, &edge1.end) && !ray_is_moving_outwards(center, &edge2.start, &edge2.end) {
let start_pos = &edge1.start;
let end_pos = &edge2.end;
let offset = *end_pos - *start_pos;
let distance_sq = offset.dot(&offset);
if distance_sq <= min_gap_sq {
let gap_line = (edge1.start, edge2.end);
let mut edge_num = edge1.edge_index.1;
loop {
if edge_num == edge2.edge_index.1 {
break;
}
let edge = &mut edges[edge_num];
let edge_ray = (*center, edge.position);
edge.position = line_intersects_ray(&edge_ray, &gap_line).unwrap_or(edge.position);
edge_num += 1;
if edge_num >= edges.len() { edge_num = 0; }
}
long_edges_to_remove.push(edge1_idx);
long_edges_to_remove.push(edge2_idx);
}
}
}
if !long_edges_to_remove.is_empty() {
long_edges_to_remove.sort_unstable();
for long_edge_num in long_edges_to_remove.into_iter().rev() {
long_edges.remove(long_edge_num);
}
}
}
pub fn trace_outline_concave<Coord, Item, RayList, RayFn>(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Vec<RayCollision<Coord, Option<Item>>>
where
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
let cast_ray = &cast_ray;
let cast_ray = &|from, to| {
cast_ray(from, to).into_iter().map(|collision| {
RayCollision {
position: collision.position,
what: ConcaveItem::Edge(collision.what)
}
})
};
let edge_min_len = options.step * 4.0;
let edge_min_len_squared = edge_min_len * edge_min_len;
let self_intersection_distance = options.step;
let mut edges = trace_outline_convex(center, options, cast_ray);
if edges.len() < 2 {
return vec![];
}
let mut long_edges = find_long_edges(&edges, edge_min_len_squared);
if let Some(min_gap) = options.min_gap {
remove_small_gaps(¢er, &mut edges, &mut long_edges, min_gap);
}
let mut long_edge_index = 0;
while long_edge_index < long_edges.len() {
let next_edge = &long_edges[long_edge_index];
if !next_edge.ray_collided {
let center_point = (next_edge.start + next_edge.end) * 0.5;
let offset = next_edge.start - next_edge.end;
let line_angle = offset.x().atan2(offset.y());
let cast_ray_to_edges = |from: Coord, to: Coord| {
let edge_collisions = cast_ray(from, to);
let ray_line = (from, to);
let extra_collisions = long_edges.iter()
.enumerate()
.filter(|(edge_index, _edge)| *edge_index != long_edge_index)
.filter_map(move |(edge_index, edge)| {
let edge_line = (edge.start, edge.end);
if let Some(intersection_point) = line_intersects_ray(&edge_line, &ray_line) {
let length = to.distance_to(&from);
let direction = (to-from) * (4.0/length);
let intersection_point = intersection_point + (direction * self_intersection_distance);
Some(RayCollision {
position: intersection_point,
what: ConcaveItem::SelfIntersection(edge_index)
})
} else {
None
}
});
edge_collisions.into_iter().chain(extra_collisions)
};
let mut new_edges = trace_outline_convex_partial(center_point, options, line_angle..(line_angle+f64::consts::PI), cast_ray_to_edges);
if new_edges.len() > 2 {
new_edges.remove(0);
let next_edge_index = next_edge.edge_index.1;
for new_edge in new_edges.iter() {
if let ConcaveItem::SelfIntersection(edge_index) = new_edge.what {
long_edges[edge_index].ray_collided = true;
}
}
let mut new_long_edges = find_long_edges(&new_edges[0..(new_edges.len())], edge_min_len_squared);
new_long_edges.retain(|edge| edge.edge_index.1 != 0);
if let Some(min_gap) = options.min_gap {
remove_small_gaps(¢er, &mut new_edges, &mut new_long_edges, min_gap);
}
let edge_index = next_edge_index;
let num_new_edges = new_edges.len()-1;
edges.splice(edge_index..edge_index, new_edges.into_iter().take(num_new_edges));
for long_edge in long_edges.iter_mut() {
if long_edge.edge_index.0 >= edge_index {
long_edge.edge_index.0 += num_new_edges;
}
if long_edge.edge_index.1 >= edge_index {
long_edge.edge_index.1 += num_new_edges;
}
}
for edge in new_long_edges.iter_mut() {
edge.edge_index.0 += edge_index;
edge.edge_index.1 += edge_index;
}
long_edges.splice((long_edge_index+1)..(long_edge_index+1), new_long_edges);
}
}
long_edge_index += 1;
}
edges.into_iter()
.map(|collision| RayCollision {
position: collision.position,
what: collision.what.into()
})
.collect()
}
pub fn flood_fill_concave<Path, Coord, Item, RayList, RayFn>(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Option<Vec<Path>>
where
Path: BezierPathFactory<Point=Coord>,
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
let collisions = trace_outline_concave(center, options, cast_ray);
let curves = fit_curve_loop::<Curve<Coord>>(&collisions.iter().map(|collision| collision.position).collect::<Vec<_>>(), options.fit_error);
if let Some(curves) = curves {
if !curves.is_empty() {
let initial_point = curves[0].start_point();
let overlapped_path = Path::from_points(initial_point, curves.into_iter().map(|curve| {
let (cp1, cp2) = curve.control_points();
let end_point = curve.end_point();
(cp1, cp2, end_point)
}));
Some(path_remove_interior_points(&vec![overlapped_path], 0.01))
} else {
None
}
} else {
None
}
}