use super::fill_settings::*;
use super::super::*;
use super::super::super::*;
use super::super::super::super::geo::*;
use std::f64;
use std::ops::{Range};
#[derive(Clone)]
pub struct RayCollision<Coord, Item>
where
Coord: Coordinate+Coordinate2D,
{
pub position: Coord,
pub what: Item
}
impl<Coord, Item> RayCollision<Coord, Item>
where
Coord: Coordinate+Coordinate2D,
{
pub fn new(position: Coord, what: Item) -> RayCollision<Coord, Item> {
RayCollision { position, what }
}
}
pub fn trace_outline_convex<Coord, Item, RayList, RayFn>(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Vec<RayCollision<Coord, Item>>
where
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
trace_outline_convex_partial(center, options, (0.0)..(2.0*f64::consts::PI), cast_ray)
}
fn find_nearest_collision<Coord, Item, RayList>(candidates: RayList, center: Coord, ray_vector: Coord) -> Option<(RayCollision<Coord, Item>, f64)>
where
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
{
let mut nearest_collision = None;
let mut nearest_distance_squared = f64::MAX;
for ray_collision in candidates {
let collision_vector = ray_collision.position - center;
let direction = collision_vector.dot(&ray_vector);
if direction < 0.0 { continue; }
let distance = collision_vector.dot(&collision_vector);
if distance < nearest_distance_squared {
nearest_collision = Some(ray_collision);
nearest_distance_squared = distance;
}
}
nearest_collision.map(|nearest_collision| (nearest_collision, nearest_distance_squared))
}
fn perform_ray_cast<Coord, Item, RayList, RayFn>(center: Coord, theta: f64, cast_ray: RayFn) -> Option<(RayCollision<Coord, Item>, f64)>
where
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
let ray_vector = [1.0 * theta.sin(), 1.0 * theta.cos()];
let ray_vector = Coord::from_components(&ray_vector);
let ray_target = center + ray_vector;
let ray_collisions = cast_ray(center, ray_target);
find_nearest_collision(ray_collisions, center, ray_vector)
}
pub (super) fn trace_outline_convex_partial<Coord, Item, RayList, RayFn>(center: Coord, options: &FillSettings, angles: Range<f64>, cast_ray: RayFn) -> Vec<RayCollision<Coord, Item>>
where
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
let min_step = 0.02;
let step_size = options.step;
let max_step = step_size * 2.0;
let max_step_squared = max_step * max_step;
let mut collisions = vec![];
let mut stack = vec![];
struct StackEntry<Coord: Coordinate+Coordinate2D, Item> {
angle: Range<f64>,
start_pos: Option<(RayCollision<Coord, Item>, f64)>,
end_pos: Option<Coord>
}
for check_point in 0..4 {
let check_point = (3-check_point) as f64;
let theta = angles.start + (angles.end-angles.start)/4.0 * check_point;
let end_theta = theta + (angles.end-angles.start)/4.0;
let start_pos = perform_ray_cast(center, theta, &cast_ray);
let end_pos = perform_ray_cast(center, end_theta, &cast_ray);
stack.push(StackEntry {
angle: theta..end_theta,
start_pos: start_pos,
end_pos: end_pos.map(|(end_pos, _distance)| end_pos.position)
});
}
while let Some(entry) = stack.pop() {
if let (Some((start_pos, _start_distance_squared)), Some(end_pos)) = (entry.start_pos.as_ref(), entry.end_pos) {
let offset = end_pos - start_pos.position;
let distance_squared = offset.dot(&offset);
if distance_squared < max_step_squared {
collisions.push(entry.start_pos.unwrap().0);
} else {
let mid_point = (entry.angle.start + entry.angle.end) / 2.0;
let mid_ray = perform_ray_cast(center, mid_point, &cast_ray);
let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position);
if let Some(mid_ray_pos) = mid_ray_pos {
let mid_to_end = end_pos - mid_ray_pos;
let mid_to_end_sq = mid_to_end.dot(&mid_to_end);
if mid_to_end_sq < (step_size * step_size) {
let three_quarters_sq = (9.0*distance_squared)/16.0;
let start_to_mid = mid_ray_pos - start_pos.position;
let start_to_mid_sq = start_to_mid.dot(&start_to_mid);
if start_to_mid_sq >= three_quarters_sq {
collisions.push(entry.start_pos.unwrap().0);
continue;
}
}
}
stack.push(StackEntry {
angle: mid_point..entry.angle.end,
start_pos: mid_ray,
end_pos: entry.end_pos
});
stack.push(StackEntry {
angle: entry.angle.start..mid_point,
start_pos: entry.start_pos,
end_pos: mid_ray_pos
})
}
} else {
if entry.angle.end - entry.angle.start > min_step {
let mid_point = (entry.angle.start + entry.angle.end) / 2.0;
let mid_ray = perform_ray_cast(center, mid_point, &cast_ray);
let mid_ray_pos = mid_ray.as_ref().map(|(collision, _)| collision.position);
stack.push(StackEntry {
angle: mid_point..entry.angle.end,
start_pos: mid_ray,
end_pos: entry.end_pos
});
stack.push(StackEntry {
angle: entry.angle.start..mid_point,
start_pos: entry.start_pos,
end_pos: mid_ray_pos
})
}
}
}
collisions
}
pub fn flood_fill_convex<Path, Coord, Item, RayList, RayFn>(center: Coord, options: &FillSettings, cast_ray: RayFn) -> Option<Path>
where
Path: BezierPathFactory<Point=Coord>,
Coord: Coordinate+Coordinate2D,
RayList: IntoIterator<Item=RayCollision<Coord, Item>>,
RayFn: Fn(Coord, Coord) -> RayList,
{
let collisions = trace_outline_convex(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();
Some(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)
})))
} else {
None
}
} else {
None
}
}