use crate::Point;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CoordError {
OutOfRange,
NotAnInteger,
NotFinite,
}
impl core::fmt::Display for CoordError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CoordError::OutOfRange => write!(f, "coordinate is outside the i16 range"),
CoordError::NotAnInteger => write!(f, "coordinate is not an integer"),
CoordError::NotFinite => write!(f, "coordinate is not finite"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CoordError {}
fn lattice(v: f64) -> Result<i16, CoordError> {
if !v.is_finite() {
return Err(CoordError::NotFinite);
}
let t = v as i64;
if t as f64 != v {
return Err(CoordError::NotAnInteger);
}
if t < i16::MIN as i64 || t > i16::MAX as i64 {
return Err(CoordError::OutOfRange);
}
Ok(t as i16)
}
#[cfg(feature = "geo-types")]
#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
mod geo_types_impl {
use super::*;
impl From<Point> for geo_types::Coord<i16> {
#[inline]
fn from(p: Point) -> Self {
geo_types::Coord { x: p.x, y: p.y }
}
}
impl From<geo_types::Coord<i16>> for Point {
#[inline]
fn from(c: geo_types::Coord<i16>) -> Self {
Point::new(c.x, c.y)
}
}
impl From<Point> for geo_types::Point<i16> {
#[inline]
fn from(p: Point) -> Self {
geo_types::Point::new(p.x, p.y)
}
}
impl From<geo_types::Point<i16>> for Point {
#[inline]
fn from(p: geo_types::Point<i16>) -> Self {
Point::new(p.x(), p.y())
}
}
impl From<Point> for geo_types::Coord<f64> {
#[inline]
fn from(p: Point) -> Self {
geo_types::Coord {
x: p.x as f64,
y: p.y as f64,
}
}
}
impl TryFrom<geo_types::Coord<f64>> for Point {
type Error = CoordError;
#[inline]
fn try_from(c: geo_types::Coord<f64>) -> Result<Self, CoordError> {
Ok(Point::new(lattice(c.x)?, lattice(c.y)?))
}
}
}
#[cfg(feature = "glam")]
#[cfg_attr(docsrs, doc(cfg(feature = "glam")))]
mod glam_impl {
use super::*;
impl From<Point> for glam::I16Vec2 {
#[inline]
fn from(p: Point) -> Self {
glam::I16Vec2::new(p.x, p.y)
}
}
impl From<glam::I16Vec2> for Point {
#[inline]
fn from(v: glam::I16Vec2) -> Self {
Point::new(v.x, v.y)
}
}
impl From<Point> for glam::IVec2 {
#[inline]
fn from(p: Point) -> Self {
glam::IVec2::new(p.x as i32, p.y as i32)
}
}
impl TryFrom<glam::IVec2> for Point {
type Error = CoordError;
#[inline]
fn try_from(v: glam::IVec2) -> Result<Self, CoordError> {
let x = i16::try_from(v.x).map_err(|_| CoordError::OutOfRange)?;
let y = i16::try_from(v.y).map_err(|_| CoordError::OutOfRange)?;
Ok(Point::new(x, y))
}
}
impl From<Point> for glam::Vec2 {
#[inline]
fn from(p: Point) -> Self {
glam::Vec2::new(p.x as f32, p.y as f32)
}
}
impl TryFrom<glam::Vec2> for Point {
type Error = CoordError;
#[inline]
fn try_from(v: glam::Vec2) -> Result<Self, CoordError> {
Ok(Point::new(lattice(v.x as f64)?, lattice(v.y as f64)?))
}
}
}
#[cfg(feature = "mint")]
#[cfg_attr(docsrs, doc(cfg(feature = "mint")))]
mod mint_impl {
use super::*;
impl From<Point> for mint::Point2<i16> {
#[inline]
fn from(p: Point) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
impl From<mint::Point2<i16>> for Point {
#[inline]
fn from(p: mint::Point2<i16>) -> Self {
Point::new(p.x, p.y)
}
}
impl From<Point> for mint::Vector2<i16> {
#[inline]
fn from(p: Point) -> Self {
mint::Vector2 { x: p.x, y: p.y }
}
}
impl From<mint::Vector2<i16>> for Point {
#[inline]
fn from(v: mint::Vector2<i16>) -> Self {
Point::new(v.x, v.y)
}
}
impl From<Point> for mint::Point2<f32> {
#[inline]
fn from(p: Point) -> Self {
mint::Point2 {
x: p.x as f32,
y: p.y as f32,
}
}
}
impl TryFrom<mint::Point2<f32>> for Point {
type Error = CoordError;
#[inline]
fn try_from(p: mint::Point2<f32>) -> Result<Self, CoordError> {
Ok(Point::new(lattice(p.x as f64)?, lattice(p.y as f64)?))
}
}
}
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
mod num_traits_impl {
use super::*;
use num_traits::{NumCast, ToPrimitive};
impl Point {
pub fn cast<T: NumCast>(self) -> Option<(T, T)> {
Some((T::from(self.x)?, T::from(self.y)?))
}
pub fn try_cast<T: ToPrimitive>(x: T, y: T) -> Option<Point> {
Some(Point::new(
lattice(x.to_f64()?).ok()?,
lattice(y.to_f64()?).ok()?,
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lattice_accepts_exact_integers() {
assert_eq!(lattice(0.0), Ok(0));
assert_eq!(lattice(-32768.0), Ok(i16::MIN));
assert_eq!(lattice(32767.0), Ok(i16::MAX));
assert_eq!(lattice(-0.0), Ok(0));
}
#[test]
fn lattice_rejects_rather_than_rounds() {
assert_eq!(lattice(0.5), Err(CoordError::NotAnInteger));
assert_eq!(lattice(-1.25), Err(CoordError::NotAnInteger));
}
#[test]
fn lattice_rejects_rather_than_wraps() {
assert_eq!(lattice(32768.0), Err(CoordError::OutOfRange));
assert_eq!(lattice(-32769.0), Err(CoordError::OutOfRange));
assert_eq!(lattice(1e18), Err(CoordError::OutOfRange));
}
#[test]
fn lattice_rejects_nonfinite() {
assert_eq!(lattice(f64::NAN), Err(CoordError::NotFinite));
assert_eq!(lattice(f64::INFINITY), Err(CoordError::NotFinite));
assert_eq!(lattice(f64::NEG_INFINITY), Err(CoordError::NotFinite));
}
#[cfg(feature = "geo-types")]
#[test]
fn geo_types_round_trips() {
let p = Point::new(-7, 12);
let c: geo_types::Coord<i16> = p.into();
assert_eq!(Point::from(c), p);
let gp: geo_types::Point<i16> = p.into();
assert_eq!(Point::from(gp), p);
let f: geo_types::Coord<f64> = p.into();
assert_eq!(Point::try_from(f), Ok(p));
assert_eq!(
Point::try_from(geo_types::Coord { x: 0.5f64, y: 0.0 }),
Err(CoordError::NotAnInteger)
);
}
#[cfg(feature = "glam")]
#[test]
fn glam_round_trips() {
let p = Point::new(-7, 12);
assert_eq!(Point::from(glam::I16Vec2::from(p)), p);
assert_eq!(Point::try_from(glam::IVec2::from(p)), Ok(p));
assert_eq!(Point::try_from(glam::Vec2::from(p)), Ok(p));
assert_eq!(
Point::try_from(glam::IVec2::new(70_000, 0)),
Err(CoordError::OutOfRange)
);
assert_eq!(
Point::try_from(glam::Vec2::new(0.5, 0.0)),
Err(CoordError::NotAnInteger)
);
}
#[cfg(feature = "mint")]
#[test]
fn mint_round_trips() {
let p = Point::new(-7, 12);
assert_eq!(Point::from(mint::Point2::from(p)), p);
assert_eq!(Point::from(mint::Vector2::from(p)), p);
let f: mint::Point2<f32> = p.into();
assert_eq!(Point::try_from(f), Ok(p));
}
#[cfg(feature = "num-traits")]
#[test]
fn num_traits_casts() {
let p = Point::new(3, -4);
assert_eq!(p.cast::<f64>(), Some((3.0, -4.0)));
assert_eq!(p.cast::<i32>(), Some((3, -4)));
assert_eq!(p.cast::<u16>(), None, "-4 is not representable");
assert_eq!(Point::try_cast(3i32, -4i32), Some(p));
assert_eq!(Point::try_cast(99_999i32, 0i32), None);
assert_eq!(Point::try_cast(0.5f64, 0.0f64), None);
assert_eq!(Point::try_cast(-0.5f32, 0.0f32), None);
assert_eq!(Point::try_cast(f64::NAN, 0.0), None);
}
}