use std::collections::VecDeque;
use compare_variables::*;
use num::Integer;
use rayon::iter::ParallelIterator;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::composite::*;
use crate::polysegment::Polysegment;
use crate::primitive::Primitive;
use crate::segment::{Segment, arc_segment::ArcSegment, line_segment::LineSegment};
use crate::{CentroidData, Transformation};
use crate::{DEFAULT_EPSILON, DEFAULT_MAX_ULPS};
use bounding_box::BoundingBox;
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Contour example][example_contour]")]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image("example_contour", "docs/img/example_contour.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, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Contour(Polysegment);
impl Contour {
pub fn new(polysegment: Polysegment) -> Self {
return polysegment.into();
}
pub fn polysegment(&self) -> &Polysegment {
return &self.0;
}
pub fn vec_deque(&self) -> &VecDeque<Segment> {
return self.0.vec_deque();
}
pub fn segments(&self) -> &[Segment] {
return self.0.as_slices().0;
}
pub fn back(&self) -> Option<&Segment> {
return self.0.back();
}
pub fn front(&self) -> Option<&Segment> {
return self.0.front();
}
pub fn is_empty(&self) -> bool {
return self.0.is_empty();
}
pub fn get(&self, index: usize) -> Option<&Segment> {
return self.0.get(index);
}
pub fn reverse(&mut self) -> () {
self.0.reverse();
}
pub fn len(&self) -> usize {
return self.0.len();
}
pub fn iter(&self) -> std::collections::vec_deque::Iter<'_, Segment> {
return self.0.iter();
}
pub fn par_iter(&self) -> rayon::collections::vec_deque::Iter<'_, Segment> {
return self.0.par_iter();
}
pub fn points(&self) -> PointIterator<'_> {
return PointIterator::new(self.polysegment(), true, Polygonizer::default());
}
pub fn polygonize(&self, polygonizer: Polygonizer) -> PointIterator<'_> {
return PointIterator::new(self.polysegment(), true, polygonizer);
}
pub fn contains(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
if !BoundingBox::from(self).contains(&BoundingBox::from(other)) {
return false;
}
if other
.points()
.any(|point| !self.contains_point(point, epsilon, max_ulps))
{
return false;
}
return self
.polysegment()
.intersections_polysegment_par(other.polysegment(), epsilon, max_ulps)
.count()
== 0;
}
pub fn intersection_cut(
&self,
other: &Polysegment,
epsilon: f64,
max_ulps: u32,
) -> Vec<Polysegment> {
let mut lines = self
.polysegment()
.intersection_cut(other, epsilon, max_ulps);
if lines.len() > 1 {
match lines.pop() {
Some(mut last_polyline) => {
let mut first_polyline =
std::mem::replace(lines.first_mut().unwrap(), Polysegment::new());
last_polyline.append(&mut first_polyline);
lines[0] = last_polyline;
}
None => (),
}
}
return lines;
}
pub fn area(&self) -> f64 {
return self
.iter()
.map(|segment| match segment {
Segment::LineSegment(line) => {
0.5 * (line.start()[0] * line.stop()[1] - line.stop()[0] * line.start()[1])
}
Segment::ArcSegment(arc) => {
let center = arc.center();
let area_13 = 0.5
* (center[0] * (arc.stop()[1] - arc.start()[1])
- (arc.stop()[0] - arc.start()[0]) * center[1]);
let angle = arc.offset_angle();
let radius = arc.radius();
let area_2 = 0.5 * angle * radius.powi(2);
return area_13 + area_2;
}
})
.sum::<f64>()
.abs();
}
pub fn length(&self) -> f64 {
return self.polysegment().length();
}
pub fn circle(center: [f64; 2], radius: f64) -> Self {
match ArcSegment::circle(center, radius) {
Ok(segment) => {
let polysegment = Polysegment::from(Segment::from(segment));
return Contour::new(polysegment);
}
Err(_) => return Contour::new(Polysegment::new()),
}
}
pub fn rectangle(pt1: [f64; 2], pt2: [f64; 2]) -> Self {
let xmin = pt1[0].min(pt2[0]);
let xmax = pt1[0].max(pt2[0]);
let ymin = pt1[1].min(pt2[1]);
let ymax = pt1[1].max(pt2[1]);
let lower_left = [xmin, ymin];
let upper_left = [xmin, ymax];
let lower_right = [xmax, ymin];
let upper_right = [xmax, ymax];
return Polysegment::from_points(&[upper_left, lower_left, lower_right, upper_right])
.into();
}
pub fn arrow_from_tail_length_angle(
tail: [f64; 2],
length: f64,
angle: f64,
stem_width: f64,
head_size: ArrowHeadSize,
) -> crate::error::Result<Self> {
let height = head_size.height();
compare_variables!(0.0 <= length)?;
compare_variables!(0.0 <= height)?;
compare_variables!(0.0 <= stem_width)?;
let sin = angle.sin();
let cos = angle.cos();
let triangle_side_length = head_size.side_length();
let x_flat_end_center = tail[0] + (length - height) * cos;
let y_flat_end_center = tail[1] + (length - height) * sin;
let x_tail_l = tail[0] - 0.5 * stem_width * sin;
let y_tail_l = tail[1] + 0.5 * stem_width * cos;
let x_tail_to_head_l = x_tail_l + (length - height) * cos;
let y_tail_to_head_l = y_tail_l + (length - height) * sin;
let x_head_outer_l = x_flat_end_center - 0.5 * triangle_side_length * sin;
let y_head_outer_l = y_flat_end_center + 0.5 * triangle_side_length * cos;
let x_pointed_end = tail[0] + length * cos;
let y_pointed_end = tail[1] + length * sin;
let x_tail_r = tail[0] + 0.5 * stem_width * sin;
let y_tail_r = tail[1] - 0.5 * stem_width * cos;
let x_tail_to_head_r = x_tail_r + (length - height) * cos;
let y_tail_to_head_r = y_tail_r + (length - height) * sin;
let x_head_outer_r = x_flat_end_center + 0.5 * triangle_side_length * sin;
let y_head_outer_r = y_flat_end_center - 0.5 * triangle_side_length * cos;
let mut vertices: Vec<[f64; 2]> = Vec::with_capacity(7);
vertices.push([x_tail_l, y_tail_l]);
vertices.push([x_tail_to_head_l, y_tail_to_head_l]);
vertices.push([x_head_outer_l, y_head_outer_l]);
vertices.push([x_pointed_end, y_pointed_end]);
vertices.push([x_head_outer_r, y_head_outer_r]);
vertices.push([x_tail_to_head_r, y_tail_to_head_r]);
vertices.push([x_tail_r, y_tail_r]);
return Ok(Polysegment::from_points(&vertices).into());
}
pub fn arrow_from_tail_head(
tail: [f64; 2],
head: [f64; 2],
stem_width: f64,
head_size: ArrowHeadSize,
) -> crate::error::Result<Self> {
let length = ((head[0] - tail[0]).powi(2) + (head[1] - tail[1]).powi(2)).sqrt();
let angle = (head[1] - tail[1]).atan2(head[0] - tail[0]);
return Contour::arrow_from_tail_length_angle(tail, length, angle, stem_width, head_size);
}
pub fn arrow_from_head_length_angle(
head: [f64; 2],
length: f64,
angle: f64,
stem_width: f64,
head_size: ArrowHeadSize,
) -> crate::error::Result<Self> {
let x_tail = head[0] - length * angle.cos();
let y_tail = head[1] - length * angle.sin();
let tail = [x_tail, y_tail];
return Contour::arrow_from_tail_length_angle(tail, length, angle, stem_width, head_size);
}
}
impl crate::composite::private::Sealed for Contour {}
impl Composite for Contour {
fn segment(&self, key: SegmentKey) -> Option<&crate::segment::Segment> {
return self.polysegment().segment(key);
}
fn num_segments(&self) -> usize {
return self.polysegment().num_segments();
}
fn centroid(&self) -> [f64; 2] {
return CentroidData::from(self).into();
}
fn intersections_primitive<'a, T: Primitive>(
&'a self,
primitive: &'a T,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
self.polysegment()
.intersections_primitive(primitive, epsilon, max_ulps)
}
fn intersections_primitive_par<'a, T: Primitive + std::marker::Sync>(
&'a self,
primitive: &'a T,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
self.polysegment()
.intersections_primitive_par(primitive, epsilon, max_ulps)
}
fn intersections_polysegment<'a>(
&'a self,
polysegment: &'a Polysegment,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
self.polysegment()
.intersections_polysegment(polysegment, epsilon, max_ulps)
}
fn intersections_polysegment_par<'a>(
&'a self,
polysegment: &'a Polysegment,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
self.polysegment()
.intersections_polysegment_par(polysegment, epsilon, max_ulps)
}
fn intersections_contour<'a>(
&'a self,
contour: &'a Contour,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
let is_identical = std::ptr::eq(self, contour);
self.polysegment()
.intersections_polysegment(contour.polysegment(), epsilon, max_ulps)
.filter(move |i| {
if is_identical
&& i.left.segment_idx == 0
&& i.right.segment_idx + 1 == self.num_segments()
{
if let Some(start) = self.polysegment().front() {
let pt = start.start();
return !approx::ulps_eq!(
pt,
i.point,
epsilon = epsilon,
max_ulps = max_ulps
);
}
};
return true;
})
}
fn intersections_contour_par<'a>(
&'a self,
contour: &'a Contour,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
let is_identical = std::ptr::eq(self, contour);
self.polysegment()
.intersections_polysegment_par(contour.polysegment(), epsilon, max_ulps)
.filter(move |i| {
if is_identical
&& i.left.segment_idx == 0
&& i.right.segment_idx + 1 == self.num_segments()
{
if let Some(start) = self.polysegment().front() {
let pt = start.start();
return !approx::ulps_eq!(
pt,
i.point,
epsilon = epsilon,
max_ulps = max_ulps
);
}
};
return true;
})
}
fn intersections_shape<'a>(
&'a self,
shape: &'a crate::prelude::Shape,
epsilon: f64,
max_ulps: u32,
) -> impl Iterator<Item = Intersection> + 'a {
shape
.intersections_contour(self, epsilon, max_ulps)
.map(Intersection::switch)
}
fn intersections_shape_par<'a>(
&'a self,
shape: &'a crate::prelude::Shape,
epsilon: f64,
max_ulps: u32,
) -> impl ParallelIterator<Item = Intersection> + 'a {
shape
.intersections_contour_par(self, epsilon, max_ulps)
.map(Intersection::switch)
}
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_contour(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_contour_par(self, epsilon, max_ulps)
.map(Intersection::switch);
}
fn contains_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
let bb = BoundingBox::from(self);
if !bb.approx_contains_point(point, epsilon, max_ulps) {
return false;
}
for segment in self.0.vec_deque().iter() {
if segment.contains_point(point, epsilon, max_ulps) {
return true;
}
}
if let Ok(ray) = LineSegment::new(
[bb.xmin() - 1.0, point[1]],
point,
DEFAULT_EPSILON,
DEFAULT_MAX_ULPS,
) {
let mut counter = 0;
for segment in self.0.vec_deque().iter() {
counter += segment
.intersections_primitive(&ray, epsilon, max_ulps)
.len();
}
return counter.is_odd();
} else {
return false;
}
}
}
impl std::ops::Index<usize> for Contour {
type Output = Segment;
fn index(&self, index: usize) -> &Self::Output {
&self.polysegment()[index]
}
}
impl Transformation for Contour {
fn translate(&mut self, shift: [f64; 2]) -> () {
return self.0.translate(shift);
}
fn rotate(&mut self, center: [f64; 2], angle: f64) -> () {
return self.0.rotate(center, angle);
}
fn line_reflection(&mut self, start: [f64; 2], stop: [f64; 2]) -> () {
return self.0.line_reflection(start, stop);
}
fn point_reflection(&mut self, point: [f64; 2]) -> () {
return self.0.point_reflection(point);
}
fn scale(&mut self, factor: f64) -> () {
return self.0.scale(factor);
}
}
impl From<&Contour> for BoundingBox {
fn from(value: &Contour) -> BoundingBox {
return BoundingBox::from(&value.0);
}
}
impl From<&Contour> for CentroidData {
fn from(value: &Contour) -> Self {
return (&value.0).into();
}
}
impl From<Polysegment> for Contour {
fn from(mut polysegment: Polysegment) -> Self {
if let Some(first) = polysegment.vec_deque().front() {
if let Some(last) = polysegment.vec_deque().back() {
if let Ok(ls) = LineSegment::new(
last.stop(),
first.start(),
DEFAULT_EPSILON,
DEFAULT_MAX_ULPS,
) {
polysegment.push_back(ls.into());
}
}
}
polysegment.make_contiguous();
return Contour(polysegment);
}
}
impl From<Contour> for Polysegment {
fn from(polygon: Contour) -> Self {
return polygon.0;
}
}
impl From<BoundingBox> for Contour {
fn from(bounding_box: BoundingBox) -> Self {
return Polysegment::from(bounding_box).into();
}
}
impl From<&BoundingBox> for Contour {
fn from(bounding_box: &BoundingBox) -> Self {
return Polysegment::from(bounding_box).into();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ArrowHeadSize {
Height(f64),
SideLength(f64),
}
impl ArrowHeadSize {
pub fn height(&self) -> f64 {
match self {
ArrowHeadSize::Height(h) => return *h,
ArrowHeadSize::SideLength(len) => return 0.5 * 3.0f64.sqrt() * *len,
}
}
pub fn side_length(&self) -> f64 {
match self {
ArrowHeadSize::Height(h) => return 2.0 / 3.0f64.sqrt() * *h,
ArrowHeadSize::SideLength(len) => return *len,
}
}
}