use std::collections::VecDeque;
use std::f64::consts::TAU;
use compare_variables::*;
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, SegmentRef, arc_segment::ArcSegment, line_segment::LineSegment};
use crate::{CentroidData, Transformation};
use crate::{DEFAULT_EPSILON, DEFAULT_MAX_ULPS};
use bounding_box::{BoundingBox, ToBoundingBox};
#[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 as_slice(&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 segments(&self) -> std::collections::vec_deque::Iter<'_, Segment> {
return self.0.segments();
}
pub fn segments_par(&self) -> rayon::collections::vec_deque::Iter<'_, Segment> {
return self.0.segments_par();
}
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 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
.segments()
.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);
}
fn covers_or_contains_point<const COVERS: bool>(
&self,
point: [f64; 2],
epsilon: f64,
max_ulps: u32,
) -> bool {
let bb = BoundingBox::from(self);
if COVERS {
if !bb.approx_covers_point(point, epsilon, max_ulps) {
return false;
}
} else {
if !bb.contains_point(point) {
return false;
}
}
for segment in self.segments() {
if segment.covers_point(point, epsilon, max_ulps) {
if COVERS {
return true;
} else {
return false;
}
}
}
let mut inside = false;
for segment in self.segments() {
match segment {
Segment::LineSegment(l) => {
let [x1, y1] = l.start();
let [x2, y2] = l.stop();
if approx::ulps_eq!(y1, y2, epsilon = epsilon, max_ulps = max_ulps) {
continue;
}
if (y1 > point[1]) != (y2 > point[1]) {
let x_intersect = x1 + (point[1] - y1) * (x2 - x1) / (y2 - y1);
if x_intersect > point[0] {
inside = !inside;
}
}
}
Segment::ArcSegment(a) => {
for split in a.split_y_monotonic(epsilon, max_ulps) {
if let Some(partial_arc) = split {
let [cx, cy] = partial_arc.center();
let r = partial_arc.radius();
let dy = point[1] - cy;
let disc = r * r - dy * dy;
if disc < 0.0 {
continue;
}
let sqrt = disc.sqrt();
let candidates = [cx - sqrt, cx + sqrt];
let mut hit = None;
for x in candidates {
let theta = (point[1] - cy).atan2(x - cx);
if partial_arc.covers_angle(theta) {
hit = Some(x);
break;
}
}
if let Some(x_intersect) = hit {
if x_intersect > point[0] {
inside = !inside;
}
}
} else {
break;
}
}
}
}
}
return inside;
}
}
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 iter<'a>(&'a self) -> impl Iterator<Item = (SegmentKey, &'a crate::segment::Segment)> {
return self.0.iter();
}
fn par_iter<'a>(
&'a self,
) -> impl ParallelIterator<Item = (SegmentKey, &'a crate::segment::Segment)> {
return self.0.par_iter();
}
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 covers_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
return self.covers_or_contains_point::<true>(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();
if !self
.bounding_box()
.approx_covers(&segment.bounding_box(), epsilon, max_ulps)
{
return false;
}
if !self.covers_point(segment.start(), epsilon, max_ulps)
|| !self.covers_point(segment.stop(), epsilon, max_ulps)
{
return false;
}
match segment {
SegmentRef::LineSegment(line_segment) => {
let mut intersections: Vec<[f64; 2]> = self
.intersections_primitive_par(&segment, epsilon, max_ulps)
.map(|i| i.point)
.collect();
let s = line_segment.start();
intersections.sort_unstable_by(|a, b| {
((a[0] - s[0]).powi(2) + (a[1] - s[1]).powi(2))
.total_cmp(&((b[0] - s[0]).powi(2) + (b[1] - s[1]).powi(2)))
});
for w in intersections.windows(2) {
if let Some(start) = w.get(0) {
if let Some(stop) = w.get(1) {
let mx = 0.5 * (start[0] + stop[0]);
let my = 0.5 * (start[1] + stop[1]);
if !self.covers_point([mx, my], epsilon, max_ulps) {
return false;
}
}
}
}
return true;
}
SegmentRef::ArcSegment(arc_segment) => {
let c = arc_segment.center();
let r = arc_segment.radius();
let dir = if arc_segment.is_positive() { 1.0 } else { -1.0 };
let start_angle = arc_segment.start_angle();
let mut angles: Vec<f64> = self
.intersections_primitive_par(&segment, epsilon, max_ulps)
.map(|i| {
let mut a = (i.point[1] - c[1]).atan2(i.point[0] - c[0]);
let needs_wrap = (dir * (a - start_angle) < -epsilon) as i32 as f64;
a += dir * needs_wrap * TAU;
a
})
.collect();
angles.sort_unstable_by(f64::total_cmp);
for w in angles.windows(2) {
if let Some(start) = w.get(0) {
if let Some(stop) = w.get(1) {
let mid_angle = 0.5 * (stop + start);
let mx = c[0] + r * mid_angle.cos();
let my = c[1] + r * mid_angle.sin();
if !self.covers_point([mx, my], epsilon, max_ulps) {
return false;
}
}
}
}
return true;
}
}
}
fn contains_point(&self, point: [f64; 2], epsilon: f64, max_ulps: u32) -> bool {
return self.covers_or_contains_point::<false>(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();
if !self.bounding_box().contains(&segment.bounding_box()) {
return false;
}
if !self.contains_point(segment.start(), epsilon, max_ulps)
|| !self.contains_point(segment.stop(), epsilon, max_ulps)
{
return false;
}
return self
.intersections_primitive(&segment, epsilon, max_ulps)
.map(|i| i.point)
.count()
== 0;
}
fn covers_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool {
return other.covers_contour(self, epsilon, max_ulps);
}
fn contains_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool {
return other.contains_contour(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.bounding_box().overlaps(&segment.bounding_box()) {
return false;
}
match segment {
SegmentRef::LineSegment(line_segment) => {
let mut intersections: Vec<[f64; 2]> = self
.intersections_primitive_par(&segment, epsilon, max_ulps)
.map(|i| i.point)
.collect();
intersections.push(line_segment.start());
intersections.push(line_segment.stop());
let s = line_segment.start();
intersections.sort_unstable_by(|a, b| {
((a[0] - s[0]).powi(2) + (a[1] - s[1]).powi(2))
.total_cmp(&((b[0] - s[0]).powi(2) + (b[1] - s[1]).powi(2)))
});
for w in intersections.windows(2) {
if let Some(start) = w.get(0) {
if let Some(stop) = w.get(1) {
let mx = 0.5 * (start[0] + stop[0]);
let my = 0.5 * (start[1] + stop[1]);
if self.contains_point([mx, my], epsilon, max_ulps) {
return true;
}
}
}
}
return false;
}
SegmentRef::ArcSegment(arc_segment) => {
let c = arc_segment.center();
let r = arc_segment.radius();
let dir = if arc_segment.is_positive() { 1.0 } else { -1.0 };
let start_angle = arc_segment.start_angle();
let mut angles: Vec<f64> = self
.intersections_primitive_par(&segment, epsilon, max_ulps)
.map(|i| {
let mut a = (i.point[1] - c[1]).atan2(i.point[0] - c[0]);
let needs_wrap = (dir * (a - start_angle) < -epsilon) as i32 as f64;
a += dir * needs_wrap * TAU;
a
})
.collect();
angles.push(arc_segment.start_angle());
angles.push(arc_segment.stop_angle());
angles.sort_unstable_by(f64::total_cmp);
for w in angles.windows(2) {
if let Some(start) = w.get(0) {
if let Some(stop) = w.get(1) {
let mid_angle = 0.5 * (stop + start);
let mx = c[0] + r * mid_angle.cos();
let my = c[1] + r * mid_angle.sin();
if self.contains_point([mx, my], epsilon, max_ulps) {
return true;
}
}
}
}
return false;
}
}
}
fn overlaps_contour(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
if std::ptr::eq(self, other) {
return true;
}
let b_self = BoundingBox::from(self);
let b_other = BoundingBox::from(other);
if !b_self.overlaps(&b_other) {
return false;
}
if self
.segments_par()
.any(|s| other.overlaps_segment(s, epsilon, max_ulps))
{
return true;
}
return self.covers_contour(other, epsilon, max_ulps)
|| other.covers_contour(self, epsilon, max_ulps);
}
fn overlaps_shape(&self, shape: &crate::shape::Shape, epsilon: f64, max_ulps: u32) -> bool {
return shape.overlaps_contour(self, epsilon, max_ulps);
}
fn overlaps_composite<'a, T: Composite>(
&'a self,
other: &'a T,
epsilon: f64,
max_ulps: u32,
) -> bool {
return other.overlaps_contour(self, epsilon, max_ulps);
}
}
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 ToBoundingBox for Contour {
fn bounding_box(&self) -> BoundingBox {
return self.0.bounding_box();
}
}
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();
}
}
impl From<Segment> for Contour {
fn from(value: Segment) -> Self {
return Polysegment::from(value).into();
}
}
impl From<LineSegment> for Contour {
fn from(value: LineSegment) -> Self {
return Polysegment::from(value).into();
}
}
impl From<ArcSegment> for Contour {
fn from(value: ArcSegment) -> Self {
return Polysegment::from(value).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,
}
}
}