use num_traits::Float;
use std::ops::{Add, Sub};
use crate::{Vector};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point<T, const N: usize> {
coords: [T; N],
}
#[cfg(feature = "serde")]
impl<T: Serialize, const N: usize> Serialize for Point<T, N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.coords.as_slice().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T: Deserialize<'de>, const N: usize> Deserialize<'de> for Point<T, N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let coords_vec = Vec::<T>::deserialize(deserializer)?;
let coords: [T; N] = coords_vec.try_into().map_err(|_| {
serde::de::Error::custom(format!("Point dimension mismatch: expected {}", N))
})?;
Ok(Self { coords })
}
}
pub type Point2D<T> = Point<T, 2>;
pub type Point3D<T> = Point<T, 3>;
pub trait MetricSquared<T> {
fn distance_squared(&self, other: &Self) -> T;
}
pub trait EuclideanMetric<T>: MetricSquared<T> {
fn distance(&self, other: &Self) -> T;
}
impl<T, const N: usize> Point<T, N>
where
T: Float,
{
#[inline]
pub fn new(coords: [T; N]) -> Self {
Self { coords }
}
#[inline]
pub fn coords(&self) -> [T; N]
where
T: Copy,
{
self.coords
}
#[inline]
pub fn coords_ref(&self) -> &[T; N] {
&self.coords
}
#[inline]
pub fn coords_ref_mut(&mut self) -> &mut [T; N] {
&mut self.coords
}
#[inline]
pub fn set_coords(&mut self, coords: [T; N]) {
self.coords = coords;
}
}
impl<T> From<(T, T)> for Point2D<T> {
#[inline]
fn from(tuple: (T, T)) -> Self {
Self {
coords: [tuple.0, tuple.1],
}
}
}
impl<T> From<(T, T, T)> for Point3D<T> {
#[inline]
fn from(tuple: (T, T, T)) -> Self {
Self {
coords: [tuple.0, tuple.1, tuple.2],
}
}
}
impl<T, const N: usize> From<Vector<T, N>> for Point<T, N>
where
T: Float,
{
#[inline]
fn from(vector: Vector<T, N>) -> Self {
Self {
coords: *vector.coords_ref(),
}
}
}
impl<T, const N: usize> MetricSquared<T> for Point<T, N>
where
T: Float + std::iter::Sum,
{
#[inline]
fn distance_squared(&self, other: &Self) -> T {
self.coords
.iter()
.zip(other.coords_ref().iter())
.map(|(a, b)| {
let diff = *a - *b;
diff * diff
})
.sum()
}
}
impl<T, const N: usize> EuclideanMetric<T> for Point<T, N>
where
T: Float + std::iter::Sum,
{
#[inline]
fn distance(&self, other: &Self) -> T {
self.distance_squared(other).sqrt()
}
}
impl<T, const N: usize> Sub for Point<T, N>
where
T: Float,
{
type Output = Vector<T, N>;
fn sub(self, rhs: Self) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] - rhs.coords_ref()[i]);
Vector::new(coords)
}
}
impl<T, const N: usize> Add<Vector<T, N>> for Point<T, N>
where
T: Float,
{
type Output = Point<T, N>;
fn add(self, rhs: Vector<T, N>) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] + rhs.coords_ref()[i]);
Self { coords }
}
}
impl<T, const N: usize> Sub<Vector<T, N>> for Point<T, N>
where
T: Float,
{
type Output = Point<T, N>;
fn sub(self, rhs: Vector<T, N>) -> Self::Output {
let coords = std::array::from_fn(|i| self.coords_ref()[i] - rhs.coords_ref()[i]);
Self { coords }
}
}
#[cfg(test)]
mod points_tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_construction_and_conversions() {
let p_gen = Point::new([1.0, 2.0, 3.0]);
assert_eq!(p_gen.coords_ref(), &[1.0, 2.0, 3.0]);
let p_from_arr = Point::new([1.5, 2.5]);
assert_eq!(p_from_arr.coords_ref(), &[1.5, 2.5]);
let p2d: Point2D<f32> = Point2D::from((1.0, 2.0));
assert_eq!(p2d.coords_ref(), &[1.0, 2.0]);
let p3d: Point3D<f32> = Point3D::from((1.0, 2.0, 3.0));
assert_eq!(p3d.coords_ref(), &[1.0, 2.0, 3.0]);
let alias_2d: Point2D<f64> = Point::new([1.0, 2.0]);
assert_eq!(alias_2d.coords_ref(), &[1.0, 2.0]);
}
#[test]
fn test_distance_operations_floats() {
let p1 = Point::new([0.0_f32, 0.0, 0.0]);
let p2 = Point::new([3.0, 4.0, 0.0]);
assert_relative_eq!(p1.distance_squared(&p2), 25.0);
assert_relative_eq!(p1.distance(&p2), 5.0);
assert_relative_eq!(p1.distance_squared(&p1), 0.0);
assert_relative_eq!(p1.distance(&p1), 0.0);
let p1_f64 = Point::new([0.0_f64, 0.0]);
let p2_f64 = Point::new([1.0, 1.0]);
assert_relative_eq!(p1_f64.distance(&p2_f64), 2.0_f64.sqrt());
}
#[test]
fn test_distance_squared_floats() {
let p1 = Point::new([1.0, 2.0]);
let p2 = Point::new([4.0, 6.0]);
assert_relative_eq!(p1.distance_squared(&p2), 25.0);
}
#[test]
fn test_properties_and_traits() {
let p1 = Point::new([1.0, 2.0]);
let p2 = p1;
assert_eq!(p1.coords_ref(), p2.coords_ref());
let p3 = Point::new([1.0, 2.0]);
println!("{:?}", p3);
assert!(p3 == Point::new([1.0, 2.0]));
assert!(p3 != Point::new([1.0, 3.0]));
}
#[test]
fn test_point_subtraction_to_vector() {
use super::Vector;
let p1 = Point::new([10.0_f64, 20.0, 30.0]);
let p2 = Point::new([15.0_f64, 25.0, 35.0]);
let v: Vector<f64, 3> = p2 - p1;
assert_relative_eq!(v.coords_ref()[0], 5.0);
assert_relative_eq!(v.coords_ref()[1], 5.0);
assert_relative_eq!(v.coords_ref()[2], 5.0);
let p3 = Point::new([1.0, 2.0]);
let p4 = Point::new([4.0, 6.0]);
let v2: Vector<f64, 2> = p4 - p3;
assert_eq!(v2.coords_ref(), &[3.0, 4.0]);
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use super::*;
use serde_json;
#[test]
fn test_point_serialization_roundtrip() {
let p = Point::new([1.0, 2.0, 3.0]);
let json = serde_json::to_string(&p).unwrap();
let q: Point<f64, 3> = serde_json::from_str(&json).unwrap();
assert_eq!(p, q);
}
#[test]
fn test_point2d_serialization_roundtrip() {
let p = Point2D::from((1.0, 2.0));
let json = serde_json::to_string(&p).unwrap();
let q: Point2D<f64> = serde_json::from_str(&json).unwrap();
assert_eq!(p, q);
}
#[test]
fn test_point3d_serialization_roundtrip() {
let p = Point3D::from((1.0, 2.0, 3.0));
let json = serde_json::to_string(&p).unwrap();
let q: Point3D<f64> = serde_json::from_str(&json).unwrap();
assert_eq!(p, q);
}
}