use std::marker::PhantomData;
use clipper2c_sys::ClipperPoint64;
pub trait PointScaler: Default + Clone + Copy + PartialEq + std::hash::Hash {
const MULTIPLIER: f64;
fn scale(value: f64) -> f64 {
value * Self::MULTIPLIER
}
fn descale(value: f64) -> f64 {
value / Self::MULTIPLIER
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
pub struct One;
impl PointScaler for One {
const MULTIPLIER: f64 = 1.0;
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
pub struct Deci;
impl PointScaler for Deci {
const MULTIPLIER: f64 = 10.0;
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
pub struct Centi;
impl PointScaler for Centi {
const MULTIPLIER: f64 = 100.0;
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
pub struct Milli;
impl PointScaler for Milli {
const MULTIPLIER: f64 = 1000.0;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent),
serde(bound = "P: PointScaler")
)]
pub struct Point<P: PointScaler = Centi>(
ClipperPoint64,
#[cfg_attr(feature = "serde", serde(skip))] PhantomData<P>,
);
impl<P: PointScaler> Point<P> {
pub const ZERO: Self = Self(ClipperPoint64 { x: 0, y: 0 }, PhantomData);
pub const MIN: Self = Self(
ClipperPoint64 {
x: i64::MIN,
y: i64::MIN,
},
PhantomData,
);
pub const MAX: Self = Self(
ClipperPoint64 {
x: i64::MAX,
y: i64::MAX,
},
PhantomData,
);
pub fn new(x: f64, y: f64) -> Self {
Self(
ClipperPoint64 {
x: P::scale(x).round() as i64,
y: P::scale(y).round() as i64,
},
PhantomData,
)
}
pub fn from_scaled(x: i64, y: i64) -> Self {
Self(ClipperPoint64 { x, y }, PhantomData)
}
pub fn x(&self) -> f64 {
P::descale(self.0.x as f64)
}
pub fn y(&self) -> f64 {
P::descale(self.0.y as f64)
}
pub fn x_scaled(&self) -> i64 {
self.0.x
}
pub fn y_scaled(&self) -> i64 {
self.0.y
}
pub fn distance_to(&self, to: &Self) -> f64 {
((self.x() - to.x()).powf(2.0) + (self.y() - to.y()).powf(2.0)).sqrt()
}
pub(crate) fn as_clipperpoint64(&self) -> *const ClipperPoint64 {
&self.0
}
}
impl<P: PointScaler> Default for Point<P> {
fn default() -> Self {
Self::ZERO
}
}
impl<P: PointScaler> From<ClipperPoint64> for Point<P> {
fn from(point: ClipperPoint64) -> Self {
Self(point, PhantomData)
}
}
impl<P: PointScaler> From<Point<P>> for ClipperPoint64 {
fn from(point: Point<P>) -> Self {
point.0
}
}
impl<P: PointScaler> From<(f64, f64)> for Point<P> {
fn from((x, y): (f64, f64)) -> Self {
Self::new(x, y)
}
}
impl<P: PointScaler> From<&(f64, f64)> for Point<P> {
fn from((x, y): &(f64, f64)) -> Self {
Self::new(*x, *y)
}
}
impl<P: PointScaler> From<[f64; 2]> for Point<P> {
fn from([x, y]: [f64; 2]) -> Self {
Self::new(x, y)
}
}
impl<P: PointScaler> From<&[f64; 2]> for Point<P> {
fn from([x, y]: &[f64; 2]) -> Self {
Self::new(*x, *y)
}
}
impl<P: PointScaler> From<Point<P>> for (f64, f64) {
fn from(point: Point<P>) -> Self {
(point.x(), point.y())
}
}
impl<P: PointScaler> From<Point<P>> for [f64; 2] {
fn from(point: Point<P>) -> Self {
[point.x(), point.y()]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_point_default_multiplier() {
let point = Point::<Centi>::new(1.0, 2.0);
assert_eq!(point.x(), 1.0);
assert_eq!(point.y(), 2.0);
assert_eq!(point.x_scaled(), 100);
assert_eq!(point.y_scaled(), 200);
}
#[test]
fn test_point_multiplier_rounding() {
let point = Point::<Centi>::new(2.05, 2.125);
assert_eq!(point.x_scaled(), 205);
assert_eq!(point.y_scaled(), 213);
}
#[test]
fn test_point_custom_scaler() {
#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)]
struct CustomScaler;
impl PointScaler for CustomScaler {
const MULTIPLIER: f64 = 2000.0;
}
let point = Point::<CustomScaler>::new(1.0, 2.0);
assert_eq!(point.x(), 1.0);
assert_eq!(point.y(), 2.0);
assert_eq!(point.x_scaled(), 2000);
assert_eq!(point.y_scaled(), 4000);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
let point = Point::<Centi>::new(1.0, 2.0);
let serialized = serde_json::to_string(&point).unwrap();
assert_eq!(serialized, r#"{"x":100,"y":200}"#);
let deserialized: Point<Centi> = serde_json::from_str(&serialized).unwrap();
assert_eq!(point, deserialized);
}
#[test]
fn test_distance_to() {
let point1 = Point::<Centi>::new(1.0, 2.0);
let point2 = Point::<Centi>::new(3.0, 4.0);
assert_eq!(point1.distance_to(&point2), 2.8284271247461903);
}
}