use thiserror::Error;
pub use line_segment::LineSegment;
pub use point::{IntoPoint, Point};
pub use region::{IntoRegion, Region};
pub use shape::Shape;
mod line_segment;
mod point;
mod region;
mod shape;
#[cfg(test)]
mod tests;
#[derive(Error, Debug, PartialEq)]
pub enum ShapelikeError {
#[error("the current operation is unsupported")]
UnsupportedOperation,
#[error("shapes have unmatched dimensions {0} and {1}")]
UnmatchedDimensions(usize, usize),
#[error("unexpected dimension {0} (expected: {1})")]
UnexpectedDimension(usize, usize),
}
pub trait Shapelike {
fn get_center(&self) -> Point;
fn get_dimension(&self) -> usize;
fn get_min_bounding_region(&self) -> Region;
fn get_area(&self) -> f64;
fn get_min_distance(&self, other: &Shape) -> Result<f64, ShapelikeError>;
fn intersects_shape(&self, other: &Shape) -> Result<bool, ShapelikeError>
where
Self: Sized,
{
check_dimensions_match(self, other)?;
match other {
Shape::Point(point) => self.contains_point(point),
Shape::LineSegment(line) => self.intersects_line_segment(line),
Shape::Region(region) => self.intersects_region(region),
}
}
fn contains_point(&self, _point: &Point) -> Result<bool, ShapelikeError> {
Err(ShapelikeError::UnsupportedOperation)
}
fn intersects_line_segment(&self, _line: &LineSegment) -> Result<bool, ShapelikeError> {
Err(ShapelikeError::UnsupportedOperation)
}
fn intersects_region(&self, _region: &Region) -> Result<bool, ShapelikeError> {
Err(ShapelikeError::UnsupportedOperation)
}
}
fn min_distance_point(s: &Point, t: &Point) -> f64 {
s.coordinate_iter()
.zip(t.coordinate_iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
fn min_distance_point_line(s: &Point, t: &LineSegment) -> Result<f64, ShapelikeError> {
if s.get_dimension() != 2 {
return Err(ShapelikeError::UnsupportedOperation);
}
let (x1, x2) = t.get_coordinate(0);
let (y1, y2) = t.get_coordinate(1);
let x0 = s.get_coordinate(0);
let y0 = s.get_coordinate(1);
if x2 >= x1 - std::f64::EPSILON && x2 <= x1 + std::f64::EPSILON {
return Ok((x0 - x1).abs());
}
if y2 >= y1 - std::f64::EPSILON && y2 <= y1 + std::f64::EPSILON {
return Ok((y0 - y1).abs());
}
Ok(((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)).abs()
/ ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)).sqrt())
}
fn min_distance_point_region(s: &Point, t: &Region) -> f64 {
let mut distance = 0.0;
for (coordinate, (low, high)) in s.coordinate_iter().zip(t.coordinates_iter()) {
distance += if coordinate < low {
(low - coordinate).powi(2)
} else if coordinate > high {
(coordinate - high).powi(2)
} else {
0.0
};
}
distance
}
fn min_distance_region(s: &Region, t: &Region) -> f64 {
let mut distance = 0.0;
for ((s_low, s_high), (t_low, t_high)) in s.coordinates_iter().zip(t.coordinates_iter()) {
let x = {
if t_high < s_low {
(t_high - s_low).abs()
} else if s_high < t_low {
(t_low - s_high).abs()
} else {
0.0
}
};
distance += x * x;
}
distance
}
fn check_dimensions_match<S: Shapelike, T: Shapelike>(
s1: &S,
s2: &T,
) -> Result<(), ShapelikeError> {
let d1 = s1.get_dimension();
let d2 = s2.get_dimension();
if d1 != d2 {
Err(ShapelikeError::UnmatchedDimensions(d1, d2))
} else {
Ok(())
}
}