// Copyright 2020-2021 Ian Jackson and contributors to Otter // SPDX-License-Identifier: AGPL-3.0-or-later // There is NO WARRANTY. use crate::prelude::*; use std::ops::{Add,Sub,Mul,Neg}; use num_traits::NumCast; //---------- common types ---------- pub type Coord = i32; #[derive(Clone,Copy,Serialize,Deserialize,Hash)] #[derive(Eq,PartialEq,Ord,PartialOrd)] #[serde(transparent)] pub struct PosC{ pub coords: [T; 2] } pub type Pos = PosC; #[derive(Clone,Copy,Serialize,Deserialize,Hash)] #[derive(Eq,PartialEq,Ord,PartialOrd)] #[serde(transparent)] pub struct RectC{ pub corners: [PosC; 2] } pub type Rect = RectC; // ---------- CheckedArith ---------- #[derive(Error,Clone,Copy,Debug,Serialize,Deserialize)] #[error("error parsing Z coordinate")] pub struct CoordinateOverflow; pub trait CheckedArith: Copy + Clone + Debug + 'static { fn checked_add(self, rhs: Self) -> Result; fn checked_sub(self, rhs: Self) -> Result; fn checked_neg(self) -> Result; } pub trait CheckedArithMul: Copy + Clone + Debug + 'static { fn checked_mul(self, rhs: RHS) -> Result; } macro_rules! checked_inherent { {$n:ident($($formal:tt)*) $($actual:tt)*} => { fn $n(self $($formal)*) -> Result { self.$n($($actual)*).ok_or(CoordinateOverflow) } } } #[allow(clippy::only_used_in_recursion)] // FP nightly (1bfe40d11 2022-03-18 impl CheckedArith for i32 { checked_inherent!{checked_add(, rhs: Self) rhs} checked_inherent!{checked_sub(, rhs: Self) rhs} checked_inherent!{checked_neg( ) } } impl CheckedArithMul for i32 { checked_inherent!{checked_mul(, rhs: Self) rhs} } impl CheckedArithMul for i32 { fn checked_mul(self, rhs: f64) -> Result { let lhs: f64 = self.into(); let out: f64 = lhs.checked_mul(rhs)?; let out: Self = NumCast::from(out).ok_or(CoordinateOverflow)?; Ok(out) } } macro_rules! checked_float { {$n:ident($($formal:tt)*) $($modify:tt)*} => { fn $n(self $($formal)*) -> Result { let out = self $($modify)*; if out.is_finite() { Ok(out) } else { Err(CoordinateOverflow) } } } } impl CheckedArith for f64 { checked_float!{checked_add(, rhs: Self) + rhs } checked_float!{checked_sub(, rhs: Self) - rhs } checked_float!{checked_neg() .neg()} } impl CheckedArithMul for f64 { checked_float!{checked_mul(, rhs: Self) * rhs } } pub trait Mean { fn mean(&self, other: &Self) -> Self; } impl Mean for i32 { fn mean(&self, other: &Self) -> Self { ((*self as i64 + *other as i64) / 2) as i32 } } impl Mean for f64 { fn mean(&self, other: &Self) -> Self { self * 0.5 + other * 0.5 } } //---------- Pos ---------- pub trait PosPromote { fn promote(&self) -> PosC; } impl PosPromote for PosC where T: Into + Copy + Debug { fn promote(&self) -> PosC { self.map(|v| v.into()) } } #[derive(Error,Debug,Copy,Clone,Serialize,Deserialize)] pub struct PosCFromIteratorError; display_as_debug!{PosCFromIteratorError} #[macro_export] macro_rules! pos_zip_try_map { { $( $input:expr ),* => $closure:expr } => { PosC::try_from_iter_2( izip!($( $input .coords(), )*) .map($closure) ) } } #[macro_export] macro_rules! pos_zip_map { { $( $input:expr ),* => $closure:expr } => { PosC::from_iter_2( izip!($( $input .coords(), )*) .map($closure) ) } } impl PosC { pub const fn new(x: T, y: T) -> Self { PosC{ coords: [x,y] } } pub fn both(v: T) -> Self where T: Copy { PosC::new(v,v) } pub fn zero() -> Self where T: num_traits::Zero + Copy { PosC::both(::zero()) } pub fn coords(self) -> impl ExactSizeIterator + FusedIterator { self.coords.into_iter() } #[throws(CoordinateOverflow)] pub fn len2(self) -> f64 where PosC: PosPromote { self.promote().coords() .try_fold(0., |b, c| { let c2 = c.checked_mul(c)?; b.checked_add(c2) })? } #[throws(CoordinateOverflow)] pub fn len(self) -> f64 where PosC: PosPromote { let d2 = self.len2()?; let d = d2.sqrt(); if !d.is_finite() { throw!(CoordinateOverflow) } d } } impl PosC where T: Copy { pub fn x(self) -> T { self.coords[0] } pub fn y(self) -> T { self.coords[1] } } #[allow(clippy::should_implement_trait)] // this one is fallible, which is a bit odd impl PosC { #[throws(PosCFromIteratorError)] pub fn from_iter>(i: I) -> Self { PosC{ coords: i .collect::>() .into_inner() .map_err(|_| PosCFromIteratorError)? }} } impl PosC where T: Debug { pub fn from_iter_2>(i: I) -> Self { PosC{ coords: i .collect::>() .into_inner() .unwrap() }} } impl Debug for PosC where T: Debug + Copy { #[throws(fmt::Error)] fn fmt(&self, f: &mut Formatter) { write!(f, "[{:?},{:?}]", self.x(), self.y())?; } } impl PosC { /// Panics if the iterator doesn't yield exactly 2 elements #[throws(E)] pub fn try_from_iter_2< E: Debug, I: Iterator> >(i: I) -> Self { PosC{ coords: i .collect::,E>>()? .into_inner().unwrap() }} } impl Add> for PosC { type Output = Result; #[throws(CoordinateOverflow)] fn add(self, rhs: PosC) -> PosC { pos_zip_try_map!( self, rhs => |(a,b)| a.checked_add(b) )? } } impl Sub> for PosC { type Output = Result; #[throws(CoordinateOverflow)] fn sub(self, rhs: PosC) -> PosC { pos_zip_try_map!( self, rhs => |(a,b)| a.checked_sub(b) )? } } impl> Mul for PosC { type Output = Result; #[throws(CoordinateOverflow)] fn mul(self, rhs: S) -> PosC { pos_zip_try_map!( self => |a| a.checked_mul(rhs) )? } } impl Neg for PosC { type Output = Result; #[throws(CoordinateOverflow)] fn neg(self) -> Self { pos_zip_try_map!( self => |a| a.checked_neg() )? } } impl PosC { pub fn map U>(self, f: F) -> PosC { pos_zip_map!( self => f ) } } impl PosC { pub fn try_map Result> (self, f: F) -> Result,E> { pos_zip_try_map!( self => f ) } } impl Mean for PosC where T: Mean + Debug + Copy { fn mean(&self, other: &Self) -> Self where T: Mean { pos_zip_map!( self, other => |(a,b)| a.mean(&b) ) } } // ---------- Rect ---------- impl RectC where T: Copy { pub fn tl(&self) -> PosC { self.corners[0] } pub fn br(&self) -> PosC { self.corners[1] } } impl Debug for RectC where T: Debug + Copy { #[throws(fmt::Error)] fn fmt(&self, f: &mut Formatter) { write!(f, "Rect[{:?},{:?}]", self.tl(), self.br())?; } } impl RectC { pub fn contains(&self, p: PosC) -> bool where T: PartialOrd + Copy { (0..2).all(|i| { p.coords[i] >= self.tl().coords[i] && p.coords[i] <= self.br().coords[i] }) } pub fn overlaps(&self, other: &RectC) -> bool where T: PartialOrd + Copy { ! (0..2).any(|i| ( other.br().coords[i] < self .tl().coords[i] || self .br().coords[i] < other.tl().coords[i] )) } pub fn empty() -> Self where T: num_traits::Zero + num_traits::One + Copy { RectC{ corners: [ PosC::both( ::one() ), PosC::both( ::zero() ), ]} } } impl RectC where T: Mean + Debug + Copy { pub fn middle(&self) -> PosC { Mean::mean(&self.tl(), &self.br()) } } impl RectC where T: CheckedArith + Debug + Copy { #[throws(CoordinateOverflow)] pub fn size(&self) -> PosC { (self.br() - self.tl())? } } #[test] fn empty_area() { let empty = Rect::empty(); for x in -3..3 { for y in -3..3 { dbg!(empty,x,y); assert!(! empty.contains(PosC::new(x,y))); } } } // ---------- Region ---------- #[derive(Clone,Debug,Serialize,Deserialize)] #[derive(Ord,PartialOrd,Eq,PartialEq)] pub enum RegionC { Rect(RectC), } pub type Region = RegionC; impl RegionC { pub fn contains(&self, pos: PosC) -> bool where T: PartialOrd { use RegionC::*; match &self { Rect(a) => a.contains(pos), } } pub fn overlaps(&self, other: &RegionC) -> bool where T: PartialOrd { use RegionC::*; match (self, other) { (Rect(a), Rect(b)) => a.overlaps(b) } } pub fn empty() -> Self where T: Copy + num_traits::Zero + num_traits::One { RegionC::Rect(RectC::empty()) } }