use std::f64::consts::PI;
use crate::{
CircularArc2, Classification, Contour2, CurveError, CurvePolicy, CurveResult, CurveString2,
Point2, Region2, RegionContourProfile, RegionView2, Segment2,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FiniteProjectionOptions {
arc_chord_error: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FiniteProjectionCertificate {
source_segment_count: usize,
line_segment_count: usize,
arc_segment_count: usize,
emitted_point_count: usize,
emitted_arc_sample_count: usize,
arc_chord_error: f64,
closed: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FinitePolyline2 {
points: Vec<[f64; 2]>,
certificate: FiniteProjectionCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FiniteRegionProjectionCertificate {
material_ring_count: usize,
hole_ring_count: usize,
source_segment_count: usize,
line_segment_count: usize,
arc_segment_count: usize,
emitted_point_count: usize,
emitted_arc_sample_count: usize,
arc_chord_error: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FiniteRegionProjection2 {
material_rings: Vec<FinitePolyline2>,
hole_rings: Vec<FinitePolyline2>,
certificate: FiniteRegionProjectionCertificate,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FiniteRegionProfile2 {
material: FinitePolyline2,
holes: Vec<FinitePolyline2>,
}
impl FiniteProjectionOptions {
pub fn try_new(arc_chord_error: f64) -> CurveResult<Self> {
if arc_chord_error.is_finite() && arc_chord_error > 0.0 {
Ok(Self { arc_chord_error })
} else {
Err(CurveError::InvalidFiniteProjectionOptions)
}
}
pub const fn arc_chord_error(&self) -> f64 {
self.arc_chord_error
}
}
impl FiniteProjectionCertificate {
pub const fn source_segment_count(&self) -> usize {
self.source_segment_count
}
pub const fn line_segment_count(&self) -> usize {
self.line_segment_count
}
pub const fn arc_segment_count(&self) -> usize {
self.arc_segment_count
}
pub const fn emitted_point_count(&self) -> usize {
self.emitted_point_count
}
pub const fn emitted_arc_sample_count(&self) -> usize {
self.emitted_arc_sample_count
}
pub const fn arc_chord_error(&self) -> f64 {
self.arc_chord_error
}
pub const fn is_closed(&self) -> bool {
self.closed
}
}
impl FinitePolyline2 {
fn new(points: Vec<[f64; 2]>, certificate: FiniteProjectionCertificate) -> Self {
Self {
points,
certificate,
}
}
pub fn points(&self) -> &[[f64; 2]] {
&self.points
}
pub fn into_points(self) -> Vec<[f64; 2]> {
self.points
}
pub const fn certificate(&self) -> &FiniteProjectionCertificate {
&self.certificate
}
pub const fn arc_chord_error(&self) -> f64 {
self.certificate.arc_chord_error()
}
pub const fn is_closed(&self) -> bool {
self.certificate.is_closed()
}
pub fn signed_ring_area(&self) -> f64 {
finite_ring_signed_area(&self.points)
}
}
impl FiniteRegionProjectionCertificate {
pub const fn material_ring_count(&self) -> usize {
self.material_ring_count
}
pub const fn hole_ring_count(&self) -> usize {
self.hole_ring_count
}
pub const fn source_segment_count(&self) -> usize {
self.source_segment_count
}
pub const fn line_segment_count(&self) -> usize {
self.line_segment_count
}
pub const fn arc_segment_count(&self) -> usize {
self.arc_segment_count
}
pub const fn emitted_point_count(&self) -> usize {
self.emitted_point_count
}
pub const fn emitted_arc_sample_count(&self) -> usize {
self.emitted_arc_sample_count
}
pub const fn arc_chord_error(&self) -> f64 {
self.arc_chord_error
}
}
impl FiniteRegionProjection2 {
fn new(
material_rings: Vec<FinitePolyline2>,
hole_rings: Vec<FinitePolyline2>,
certificate: FiniteRegionProjectionCertificate,
) -> Self {
Self {
material_rings,
hole_rings,
certificate,
}
}
pub fn material_rings(&self) -> &[FinitePolyline2] {
&self.material_rings
}
pub fn hole_rings(&self) -> &[FinitePolyline2] {
&self.hole_rings
}
pub const fn certificate(&self) -> &FiniteRegionProjectionCertificate {
&self.certificate
}
}
impl FiniteRegionProfile2 {
fn new(material: FinitePolyline2, holes: Vec<FinitePolyline2>) -> Self {
Self { material, holes }
}
pub const fn material(&self) -> &FinitePolyline2 {
&self.material
}
pub fn holes(&self) -> &[FinitePolyline2] {
&self.holes
}
}
pub fn finite_ring_signed_area(ring: &[[f64; 2]]) -> f64 {
if ring.len() < 3 {
return 0.0;
}
let mut area = 0.0;
for edge in ring.windows(2) {
area += edge[0][0] * edge[1][1] - edge[1][0] * edge[0][1];
}
if let (Some(first), Some(last)) = (ring.first(), ring.last()) {
area += last[0] * first[1] - first[0] * last[1];
}
0.5 * area
}
impl CurveString2 {
pub fn project_to_finite_polyline(
&self,
options: &FiniteProjectionOptions,
) -> CurveResult<FinitePolyline2> {
project_curve_string(self, options, false)
}
}
impl Contour2 {
pub fn project_to_finite_ring(
&self,
options: &FiniteProjectionOptions,
) -> CurveResult<FinitePolyline2> {
project_curve_string(self.curve_string(), options, true)
}
}
impl Region2 {
pub fn project_to_finite_region(
&self,
options: &FiniteProjectionOptions,
) -> CurveResult<FiniteRegionProjection2> {
self.as_view().project_to_finite_region(options)
}
pub fn project_to_finite_profiles(
&self,
options: &FiniteProjectionOptions,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<FiniteRegionProfile2>>> {
self.as_view().project_to_finite_profiles(options, policy)
}
}
impl<'a> RegionView2<'a> {
pub fn project_to_finite_region(
&self,
options: &FiniteProjectionOptions,
) -> CurveResult<FiniteRegionProjection2> {
let material_rings = project_contour_slice(self.material_contours(), options)?;
let hole_rings = project_contour_slice(self.hole_contours(), options)?;
let certificate = finite_region_certificate(&material_rings, &hole_rings, options);
Ok(FiniteRegionProjection2::new(
material_rings,
hole_rings,
certificate,
))
}
pub fn project_to_finite_profiles(
&self,
options: &FiniteProjectionOptions,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<FiniteRegionProfile2>>> {
match self.contour_profiles(policy) {
Classification::Decided(profiles) => profiles
.iter()
.map(|profile| project_region_profile(profile, options))
.collect::<CurveResult<Vec<_>>>()
.map(Classification::Decided),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
}
fn project_region_profile(
profile: &RegionContourProfile<'_>,
options: &FiniteProjectionOptions,
) -> CurveResult<FiniteRegionProfile2> {
let material = profile.material.project_to_finite_ring(options)?;
let holes = profile
.holes
.iter()
.map(|hole| hole.project_to_finite_ring(options))
.collect::<CurveResult<Vec<_>>>()?;
Ok(FiniteRegionProfile2::new(material, holes))
}
fn project_contour_slice(
contours: &[&Contour2],
options: &FiniteProjectionOptions,
) -> CurveResult<Vec<FinitePolyline2>> {
contours
.iter()
.map(|contour| contour.project_to_finite_ring(options))
.collect()
}
fn finite_region_certificate(
material_rings: &[FinitePolyline2],
hole_rings: &[FinitePolyline2],
options: &FiniteProjectionOptions,
) -> FiniteRegionProjectionCertificate {
let mut certificate = FiniteRegionProjectionCertificate {
material_ring_count: material_rings.len(),
hole_ring_count: hole_rings.len(),
source_segment_count: 0,
line_segment_count: 0,
arc_segment_count: 0,
emitted_point_count: 0,
emitted_arc_sample_count: 0,
arc_chord_error: options.arc_chord_error,
};
for ring in material_rings.iter().chain(hole_rings) {
let ring_certificate = ring.certificate();
certificate.source_segment_count += ring_certificate.source_segment_count();
certificate.line_segment_count += ring_certificate.line_segment_count();
certificate.arc_segment_count += ring_certificate.arc_segment_count();
certificate.emitted_point_count += ring_certificate.emitted_point_count();
certificate.emitted_arc_sample_count += ring_certificate.emitted_arc_sample_count();
}
certificate
}
fn project_curve_string(
curve: &CurveString2,
options: &FiniteProjectionOptions,
close: bool,
) -> CurveResult<FinitePolyline2> {
let first = curve.start().ok_or(CurveError::EmptyCurveString)?;
let mut points = Vec::with_capacity(curve.len() + 1);
push_if_new(&mut points, finite_point(first)?);
let mut line_segment_count = 0;
let mut arc_segment_count = 0;
let mut emitted_arc_sample_count = 0;
for segment in curve.segments() {
match segment {
Segment2::Line(line) => {
line_segment_count += 1;
push_if_new(&mut points, finite_point(line.end())?);
}
Segment2::Arc(arc) => {
arc_segment_count += 1;
emitted_arc_sample_count +=
append_arc_samples(&mut points, arc, options.arc_chord_error)?;
}
}
}
if close {
close_ring(&mut points);
}
let emitted_point_count = points.len();
let certificate = FiniteProjectionCertificate {
source_segment_count: curve.len(),
line_segment_count,
arc_segment_count,
emitted_point_count,
emitted_arc_sample_count,
arc_chord_error: options.arc_chord_error,
closed: close,
};
Ok(FinitePolyline2::new(points, certificate))
}
fn finite_point(point: &Point2) -> CurveResult<[f64; 2]> {
let x = point
.x()
.to_f64_lossy()
.filter(|value| value.is_finite())
.ok_or(CurveError::NonFiniteProjectionPoint)?;
let y = point
.y()
.to_f64_lossy()
.filter(|value| value.is_finite())
.ok_or(CurveError::NonFiniteProjectionPoint)?;
Ok([x, y])
}
fn append_arc_samples(
points: &mut Vec<[f64; 2]>,
arc: &CircularArc2,
chord_error: f64,
) -> CurveResult<usize> {
let start = finite_point(arc.start())?;
let end = finite_point(arc.end())?;
let center = finite_point(arc.center())?;
let radius = ((start[0] - center[0]).powi(2) + (start[1] - center[1]).powi(2)).sqrt();
if !radius.is_finite() || radius <= f64::EPSILON {
return Err(CurveError::NonFiniteProjectionPoint);
}
let a0 = (start[1] - center[1]).atan2(start[0] - center[0]);
let a1 = (end[1] - center[1]).atan2(end[0] - center[0]);
let mut sweep = a1 - a0;
if arc.is_clockwise() {
if sweep > 0.0 {
sweep -= 2.0 * PI;
}
} else if sweep < 0.0 {
sweep += 2.0 * PI;
}
let max_angle = (1.0 - (chord_error / radius).min(1.0)).acos().max(1e-3) * 2.0;
let steps = ((sweep.abs() / max_angle).ceil() as usize).max(1);
let before = points.len();
for step in 1..=steps {
let t = step as f64 / steps as f64;
let angle = a0 + sweep * t;
push_if_new(
points,
[
center[0] + radius * angle.cos(),
center[1] + radius * angle.sin(),
],
);
}
Ok(points.len() - before)
}
fn close_ring(points: &mut Vec<[f64; 2]>) {
if points.len() >= 2 && points.first() != points.last() {
points.push(points[0]);
}
}
fn push_if_new(points: &mut Vec<[f64; 2]>, point: [f64; 2]) {
if points.last().is_none_or(|last| *last != point) {
points.push(point);
}
}