use std::cmp::Ordering;
use itertools::Itertools;
use log::{debug, info};
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use crate::fsize;
use crate::geometry::geo_traits::{CollidesWith, Shape};
use crate::geometry::primitives::edge::Edge;
use crate::geometry::primitives::point::Point;
use crate::geometry::primitives::simple_polygon::SimplePolygon;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
#[serde(tag = "mode", content = "params")]
pub enum PolySimplConfig {
#[serde(rename = "disabled")]
Disabled,
#[serde(rename = "enabled")]
Enabled {
tolerance: fsize,
},
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PolySimplMode {
Inflate,
Deflate,
}
impl PolySimplMode {
pub fn flip(&self) -> Self {
match self {
Self::Inflate => Self::Deflate,
Self::Deflate => Self::Inflate,
}
}
}
#[derive(Clone, Debug, PartialEq)]
enum Candidate {
Concave(Corner),
ConvexConvex(Corner, Corner),
Collinear(Corner),
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Corner(pub usize, pub usize, pub usize);
impl Corner {
pub fn flip(&mut self) {
std::mem::swap(&mut self.0, &mut self.2);
}
pub fn to_points(self, points: &[Point]) -> [Point; 3] {
[points[self.0], points[self.1], points[self.2]]
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum CornerType {
Concave,
Convex,
Collinear,
}
impl CornerType {
pub fn from([p1, p2, p3]: [Point; 3]) -> Self {
let p1p2 = (p2.0 - p1.0, p2.1 - p1.1);
let p1p3 = (p3.0 - p1.0, p3.1 - p1.1);
let cross_prod = p1p2.0 * p1p3.1 - p1p2.1 * p1p3.0;
match cross_prod.partial_cmp(&0.0).expect("cross product is NaN") {
Ordering::Less => CornerType::Concave,
Ordering::Equal => CornerType::Collinear,
Ordering::Greater => CornerType::Convex,
}
}
}
pub fn simplify_shape(
shape: &SimplePolygon,
mode: PolySimplMode,
max_area_delta: fsize,
) -> SimplePolygon {
let original_area = shape.area();
let mut ref_points = shape.points.clone();
for _ in 0..shape.number_of_points() {
let n_points = ref_points.len() as isize;
if n_points < 4 {
break;
}
let mut corners = (0..n_points)
.map(|i| {
let i_prev = (i - 1).rem_euclid(n_points);
let i_next = (i + 1).rem_euclid(n_points);
Corner(i_prev as usize, i as usize, i_next as usize)
})
.collect_vec();
if mode == PolySimplMode::Deflate {
corners.reverse();
corners.iter_mut().for_each(|c| c.flip());
}
let mut candidates = vec![];
let mut prev_corner = corners.last().expect("corners is empty");
let mut prev_corner_type = CornerType::from(prev_corner.to_points(&ref_points));
for corner in corners.iter() {
let corner_type = CornerType::from(corner.to_points(&ref_points));
match (&corner_type, &prev_corner_type) {
(CornerType::Concave, _) => candidates.push(Candidate::Concave(*corner)),
(CornerType::Collinear, _) => candidates.push(Candidate::Collinear(*corner)),
(CornerType::Convex, CornerType::Convex) => {
candidates.push(Candidate::ConvexConvex(*prev_corner, *corner))
}
(_, _) => {}
};
(prev_corner, prev_corner_type) = (corner, corner_type);
}
let best_candidate = candidates
.iter()
.sorted_by_cached_key(|c| {
calculate_area_delta(&ref_points, c)
.unwrap_or_else(|_| NotNan::new(fsize::INFINITY).expect("area delta is NaN"))
})
.find(|c| candidate_is_valid(&ref_points, c));
if let Some(best_candidate) = best_candidate {
let new_shape = execute_candidate(&ref_points, best_candidate);
let new_shape_area = SimplePolygon::calculate_area(&new_shape);
let area_delta = (new_shape_area - original_area).abs() / original_area;
if area_delta <= max_area_delta {
debug!(
"Simplified {:?} causing {:.2}% area change",
best_candidate,
area_delta * 100.0
);
ref_points = new_shape;
} else {
break; }
} else {
break; }
}
let simpl_shape = SimplePolygon::new(ref_points);
if simpl_shape.number_of_points() < shape.number_of_points() {
info!(
"[PS] simplified from {} to {} edges with {:.3}% area difference",
shape.number_of_points(),
simpl_shape.number_of_points(),
(simpl_shape.area() - shape.area()) / shape.area() * 100.0
);
} else {
info!("[PS] no simplification possible within area change constraints");
}
simpl_shape
}
fn calculate_area_delta(
shape: &[Point],
candidate: &Candidate,
) -> Result<NotNan<fsize>, InvalidCandidate> {
let area = match candidate {
Candidate::Collinear(_) => 0.0,
Candidate::Concave(c) => {
let Point(x0, y0) = shape[c.0];
let Point(x1, y1) = shape[c.1];
let Point(x2, y2) = shape[c.2];
let area = (x0 * y1 + x1 * y2 + x2 * y0 - x0 * y2 - x1 * y0 - x2 * y1) / 2.0;
area.abs()
}
Candidate::ConvexConvex(c1, c2) => {
let replacing_vertex = replacing_vertex_convex_convex_candidate(shape, (*c1, *c2))?;
let Point(x0, y0) = shape[c1.1];
let Point(x1, y1) = replacing_vertex;
let Point(x2, y2) = shape[c2.1];
let area = (x0 * y1 + x1 * y2 + x2 * y0 - x0 * y2 - x1 * y0 - x2 * y1) / 2.0;
area.abs()
}
};
Ok(NotNan::new(area).expect("area is NaN"))
}
fn candidate_is_valid(shape: &[Point], candidate: &Candidate) -> bool {
match candidate {
Candidate::Collinear(_) => true,
Candidate::Concave(c) => {
let new_edge = Edge::new(shape[c.0], shape[c.2]);
let affected_points = [shape[c.0], shape[c.1], shape[c.2]];
edge_iter(shape)
.filter(|l| !affected_points.contains(&l.start))
.filter(|l| !affected_points.contains(&l.end))
.all(|l| !l.collides_with(&new_edge))
}
Candidate::ConvexConvex(c1, c2) => {
match replacing_vertex_convex_convex_candidate(shape, (*c1, *c2)) {
Err(_) => false,
Ok(new_vertex) => {
let new_edge_1 = Edge::new(shape[c1.0], new_vertex);
let new_edge_2 = Edge::new(new_vertex, shape[c2.2]);
let affected_points = [shape[c1.1], shape[c1.0], shape[c2.1], shape[c2.2]];
edge_iter(shape)
.filter(|l| !affected_points.contains(&l.start))
.filter(|l| !affected_points.contains(&l.end))
.all(|l| !l.collides_with(&new_edge_1) && !l.collides_with(&new_edge_2))
}
}
}
}
}
fn edge_iter(points: &[Point]) -> impl Iterator<Item = Edge> + '_ {
let n_points = points.len();
(0..n_points).map(move |i| {
let j = (i + 1) % n_points;
Edge::new(points[i], points[j])
})
}
fn execute_candidate(shape: &[Point], candidate: &Candidate) -> Vec<Point> {
let mut points = shape.iter().cloned().collect_vec();
match candidate {
Candidate::Collinear(c) | Candidate::Concave(c) => {
points.remove(c.1);
}
Candidate::ConvexConvex(c1, c2) => {
let replacing_vertex = replacing_vertex_convex_convex_candidate(shape, (*c1, *c2))
.expect("invalid candidate cannot be executed");
points.remove(c1.1);
let other_index = if c1.1 < c2.1 { c2.1 - 1 } else { c2.1 };
points.remove(other_index);
points.insert(other_index, replacing_vertex);
}
}
points
}
fn replacing_vertex_convex_convex_candidate(
shape: &[Point],
(c1, c2): (Corner, Corner),
) -> Result<Point, InvalidCandidate> {
assert_eq!(c1.2, c2.1, "non-consecutive corners {:?},{:?}", c1, c2);
assert_eq!(c1.1, c2.0, "non-consecutive corners {:?},{:?}", c1, c2);
let edge_prev = Edge::new(shape[c1.0], shape[c1.1]);
let edge_next = Edge::new(shape[c2.2], shape[c2.1]);
calculate_intersection_in_front(&edge_prev, &edge_next).ok_or(InvalidCandidate)
}
fn calculate_intersection_in_front(l1: &Edge, l2: &Edge) -> Option<Point> {
let Point(x1, y1) = l1.start;
let Point(x2, y2) = l1.end;
let Point(x3, y3) = l2.start;
let Point(x4, y4) = l2.end;
let t_nom = (x2 - x4) * (y4 - y3) - (y2 - y4) * (x4 - x3);
let t_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
let u_nom = (x2 - x4) * (y2 - y1) - (y2 - y4) * (x2 - x1);
let u_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
let t = if t_denom != 0.0 { t_nom / t_denom } else { 0.0 };
let u = if u_denom != 0.0 { u_nom / u_denom } else { 0.0 };
if t < 0.0 && u < 0.0 {
Some(Point(x2 + t * (x1 - x2), y2 + t * (y1 - y2)))
} else {
None
}
}
#[derive(Debug, Clone)]
struct InvalidCandidate;