use crate::CentroidData;
use crate::composite::{Composite, Intersection, SegmentKey};
use crate::error::ShapeConstructorError;
use crate::segment::SegmentRef;
use crate::{DEFAULT_EPSILON, DEFAULT_MAX_ULPS};
use crate::{Transformation, contour::Contour};
use bounding_box::{BoundingBox, ToBoundingBox};
use rayon::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Shape example][example_shape]")]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image("example_shape", "docs/img/example_shape.svg")
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Shape(Vec<Contour>);
impl Shape {
pub fn new(contours: Vec<Contour>) -> Result<Self, ShapeConstructorError<Vec<Contour>>> {
if contours.len() == 0 {
return Err(ShapeConstructorError::EmptyVec);
}
for (idx, contour) in contours.iter().enumerate() {
if contour.is_empty() {
return Err(ShapeConstructorError::EmptyContour {
input: contours,
idx,
});
}
}
let this = Shape(contours);
let outer = this.contour();
for (first_hole_idx, first_hole) in this.holes().iter().enumerate() {
if !outer.contains_contour(&first_hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleOutsideContour {
input: this.0,
idx: first_hole_idx + 1, });
}
for (second_hole_idx, second_hole) in
this.holes()[(first_hole_idx + 1)..].iter().enumerate()
{
if first_hole.contains_contour(second_hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleInsideHole {
input: this.0,
outer_hole_idx: first_hole_idx,
inner_hole_idx: second_hole_idx,
});
}
if second_hole.contains_contour(first_hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleInsideHole {
input: this.0,
outer_hole_idx: second_hole_idx,
inner_hole_idx: first_hole_idx,
});
}
}
}
if let Some(intersection) = this
.intersections_shape_par(&this, DEFAULT_EPSILON, DEFAULT_MAX_ULPS)
.find_map_any(|v| Some(v))
{
return Err(ShapeConstructorError::Intersection {
input: this.0,
intersection,
});
}
return Ok(this);
}
pub fn from_outer(outer: Contour) -> Result<Self, ShapeConstructorError<Contour>> {
if outer.is_empty() {
return Err(ShapeConstructorError::EmptyContour {
input: outer,
idx: 0,
});
}
if let Some(i) = outer
.intersections_contour_par(&outer, DEFAULT_EPSILON, DEFAULT_MAX_ULPS)
.find_map_any(|v| Some(v))
{
return Err(ShapeConstructorError::Intersection {
input: outer,
intersection: Intersection {
point: i.point,
left: i.left,
right: i.right,
},
});
}
return Ok(Shape(vec![outer]));
}
pub fn contours(&self) -> &[Contour] {
return &self.0;
}
pub fn contour<'a>(&'a self) -> &'a Contour {
return unsafe { self.0.get_unchecked(0) };
}
pub fn holes(&self) -> &[Contour] {
return &self.0[1..self.0.len()]; }
pub fn area(&self) -> f64 {
let contour_area = self.contour().area();
let holes: f64 = self
.holes()
.par_iter()
.map(|polysegment| return polysegment.area())
.sum();
return contour_area - holes;
}
pub fn add_hole(&mut self, hole: Contour) -> Result<(), ShapeConstructorError<Contour>> {
if hole.is_empty() {
return Err(ShapeConstructorError::EmptyContour {
input: hole,
idx: 0,
});
}
let outer = self.contour();
if !outer.contains_contour(&hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleOutsideContour {
input: hole,
idx: 0, });
}
for (shape_hole_idx, shape_hole) in self.holes().iter().enumerate() {
if hole.contains_contour(shape_hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleInsideHole {
input: hole,
outer_hole_idx: shape_hole_idx,
inner_hole_idx: 0,
});
}
if shape_hole.contains_contour(&hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS) {
return Err(ShapeConstructorError::HoleInsideHole {
input: hole,
outer_hole_idx: 0,
inner_hole_idx: shape_hole_idx,
});
}
}
if let Some(intersection) = self
.intersections_contour_par(&hole, DEFAULT_EPSILON, DEFAULT_MAX_ULPS)
.find_map_any(|v| Some(v))
{
return Err(ShapeConstructorError::Intersection {
input: hole,
intersection,
});
}
self.0.push(hole);
return Ok(());
}
pub fn remove_hole(&mut self, index: usize) -> Option<Contour> {
if self.holes().len() > index {
return Some(self.0.swap_remove(index + 1));
}
return None;
}
}
impl crate::composite::private::Sealed for Shape {}
impl Composite for Shape {
fn segment(&self, key: SegmentKey) -> Option<&crate::segment::Segment> {
let contour = self.0.get(key.contour_idx)?;
return contour.segment(key);
}
fn num_segments(&self) -> usize {
return self.0.iter().map(Composite::num_segments).sum();
}
fn centroid(&self) -> [f64; 2] {
return CentroidData::from(self).into();
}
fn iter<'a>(&'a self) -> impl Iterator<Item = (SegmentKey, &'a crate::segment::Segment)> {
return self.0.iter().enumerate().flat_map(|(i, c)| {
c.iter().map(move |(mut k, s)| {
k.contour_idx = i;
return (k, s);
})
});
}
fn par_iter<'a>(
&'a self,
) -> impl ParallelIterator<Item = (SegmentKey, &'a crate::segment::Segment)> {
return self.0.par_iter().enumerate().flat_map(|(i, c)| {
c.par_iter().map(move |(mut k, s)| {
k.contour_idx = i;
return (k, s);
})
});
}
fn intersections_polysegment<'a>(
&'a self,
polysegment: &'a crate::prelude::Polysegment,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
self.contours()
.iter()
.enumerate()
.flat_map(move |(c1, contour_1)| {
contour_1
.polysegment()
.intersections_polysegment(polysegment, epsilon, max_ulps)
.map(move |i| Intersection {
point: i.point,
left: SegmentKey {
contour_idx: c1,
segment_idx: i.left.segment_idx,
},
right: i.right,
})
})
}
fn intersections_polysegment_par<'a>(
&'a self,
polysegment: &'a crate::prelude::Polysegment,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
self.contours()
.par_iter()
.enumerate()
.flat_map(move |(c1, contour_1)| {
contour_1
.polysegment()
.intersections_polysegment_par(polysegment, epsilon, max_ulps)
.map(move |i| Intersection {
point: i.point,
left: SegmentKey {
contour_idx: c1,
segment_idx: i.left.segment_idx,
},
right: i.right,
})
})
}
fn intersections_shape<'a>(
&'a self,
shape: &'a Shape,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
self.contours()
.iter()
.enumerate()
.flat_map(move |(idx_1, contour_1)| {
shape
.contours()
.iter()
.enumerate()
.map(move |(idx_2, contour_2)| {
contour_1
.intersections_contour(contour_2, epsilon, max_ulps)
.map(move |i| Intersection {
point: i.point,
left: SegmentKey {
contour_idx: idx_1,
segment_idx: i.left.segment_idx,
},
right: SegmentKey {
contour_idx: idx_2,
segment_idx: i.right.segment_idx,
},
})
})
.flatten()
})
}
fn intersections_shape_par<'a>(
&'a self,
shape: &'a Shape,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
self.contours()
.par_iter()
.enumerate()
.flat_map(move |(idx_1, contour_1)| {
shape
.contours()
.par_iter()
.enumerate()
.map(move |(idx_2, contour_2)| {
contour_1
.intersections_contour_par(contour_2, epsilon, max_ulps)
.map(move |i| Intersection {
point: i.point,
left: SegmentKey {
contour_idx: idx_1,
segment_idx: i.left.segment_idx,
},
right: SegmentKey {
contour_idx: idx_2,
segment_idx: i.right.segment_idx,
},
})
})
.flatten()
})
}
fn intersections_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a
where
Self: Sized,
{
return other
.intersections_shape(self, epsilon, max_ulps)
.map(Intersection::switch);
}
fn intersections_composite_par<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a
where
Self: Sized,
{
return other
.intersections_shape_par(self, epsilon, max_ulps)
.map(Intersection::switch);
}
fn covers_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
self.contours().par_iter().enumerate().all(|(i, c)| {
if i == 0 {
c.covers_point(point, epsilon, max_ulps)
} else {
!c.contains_point(point, epsilon, max_ulps)
}
})
}
fn covers_segment<'a, T: Into<SegmentRef<'a>>>(
&self,
segment: T,
epsilon: f64,
max_ulps: u32,
) -> bool {
let segment: SegmentRef = segment.into();
self.contours().par_iter().enumerate().all(|(i, c)| {
if i == 0 {
c.covers_segment(segment.clone(), epsilon, max_ulps)
} else {
!c.contains_segment(segment.clone(), epsilon, max_ulps)
}
})
}
fn contains_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
self.contours().par_iter().enumerate().all(|(i, c)| {
if i == 0 {
c.contains_point(point, epsilon, max_ulps)
} else {
!c.covers_point(point, epsilon, max_ulps)
}
})
}
fn contains_segment<'a, T: Into<crate::prelude::SegmentRef<'a>>>(
&self,
segment: T,
epsilon: f64,
max_ulps: u32,
) -> bool {
let segment: SegmentRef = segment.into();
self.contours().par_iter().enumerate().all(|(i, c)| {
if i == 0 {
c.contains_segment(segment.clone(), epsilon, max_ulps)
} else {
!c.covers_segment(segment.clone(), epsilon, max_ulps)
}
})
}
fn covers_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool
where
Self: Sized,
{
return other.covers_shape(self, epsilon, max_ulps);
}
fn contains_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool
where
Self: Sized,
{
return other.contains_shape(self, epsilon, max_ulps);
}
fn overlaps_segment<'a, T: Into<SegmentRef<'a>>>(
&self,
segment: T,
epsilon: f64,
max_ulps: u32,
) -> bool {
let segment: SegmentRef = segment.into();
if !self
.contour()
.overlaps_segment(segment.clone(), epsilon, max_ulps)
{
return false;
}
for hole in self.holes() {
if hole.covers_segment(segment.clone(), epsilon, max_ulps) {
return false;
}
}
return true;
}
fn overlaps_contour(&self, contour: &Contour, epsilon: f64, max_ulps: u32) -> bool {
if !(self.contour().overlaps_contour(contour, epsilon, max_ulps)) {
return false;
}
for hole in self.holes() {
if hole.covers_contour(contour, epsilon, max_ulps) {
return false;
}
}
return true;
}
fn overlaps_shape(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
return std::ptr::eq(self, other)
|| (self.overlaps_contour(other.contour(), epsilon, max_ulps)
&& other.overlaps_contour(self.contour(), epsilon, max_ulps));
}
fn overlaps_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool {
return other.overlaps_shape(self, epsilon, max_ulps);
}
}
impl TryFrom<Contour> for Shape {
type Error = ShapeConstructorError<Vec<Contour>>;
fn try_from(value: Contour) -> Result<Self, Self::Error> {
return Shape::new(vec![value]);
}
}
impl Transformation for Shape {
fn translate(&mut self, shift: [f64; 2]) -> () {
self.0
.as_mut_slice()
.par_iter_mut()
.for_each(|polygon| polygon.translate(shift));
}
fn rotate(&mut self, center: [f64; 2], angle: f64) -> () {
self.0
.as_mut_slice()
.par_iter_mut()
.for_each(|polygon| polygon.rotate(center, angle));
}
fn line_reflection(&mut self, start: [f64; 2], stop: [f64; 2]) -> () {
self.0.as_mut_slice().par_iter_mut().for_each(|polygon| {
polygon.line_reflection(start, stop);
})
}
fn scale(&mut self, factor: f64) -> () {
self.0
.as_mut_slice()
.par_iter_mut()
.for_each(|polysegment| {
polysegment.scale(factor);
})
}
}
impl ToBoundingBox for Shape {
fn bounding_box(&self) -> BoundingBox {
return self.contour().bounding_box();
}
}
impl From<&Shape> for CentroidData {
fn from(value: &Shape) -> Self {
let contour_centroid = CentroidData::from(value.contour());
return value
.holes()
.iter()
.map(|contour| CentroidData::from(contour))
.fold(contour_centroid, |prev, curr| prev.subtract(&curr));
}
}
impl From<Shape> for Vec<Contour> {
fn from(value: Shape) -> Self {
return value.0;
}
}