otter_base/
geometry.rs

1// Copyright 2020-2021 Ian Jackson and contributors to Otter
2// SPDX-License-Identifier: AGPL-3.0-or-later
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6
7use std::ops::{Add,Sub,Mul,Neg};
8
9use num_traits::NumCast;
10
11//---------- common types ----------
12
13pub type Coord = i32;
14
15#[derive(Clone,Copy,Serialize,Deserialize,Hash)]
16#[derive(Eq,PartialEq,Ord,PartialOrd)]
17#[serde(transparent)]
18pub struct PosC<T>{ pub coords: [T; 2] }
19pub type Pos = PosC<Coord>;
20
21#[derive(Clone,Copy,Serialize,Deserialize,Hash)]
22#[derive(Eq,PartialEq,Ord,PartialOrd)]
23#[serde(transparent)]
24pub struct RectC<T>{ pub corners: [PosC<T>; 2] }
25pub type Rect = RectC<Coord>;
26
27// ---------- CheckedArith ----------
28
29#[derive(Error,Clone,Copy,Debug,Serialize,Deserialize)]
30#[error("error parsing Z coordinate")]
31pub struct CoordinateOverflow;
32
33pub trait CheckedArith: Copy + Clone + Debug + 'static {
34  fn checked_add(self, rhs: Self) -> Result<Self, CoordinateOverflow>;
35  fn checked_sub(self, rhs: Self) -> Result<Self, CoordinateOverflow>;
36  fn checked_neg(self)            -> Result<Self, CoordinateOverflow>;
37}
38pub trait CheckedArithMul<RHS: Copy + Clone + Debug + 'static>:
39                               Copy + Clone + Debug + 'static {
40  fn checked_mul(self, rhs: RHS) -> Result<Self, CoordinateOverflow>;
41}
42
43macro_rules! checked_inherent { {$n:ident($($formal:tt)*) $($actual:tt)*} => {
44  fn $n(self $($formal)*) -> Result<Self, CoordinateOverflow> {
45    self.$n($($actual)*).ok_or(CoordinateOverflow)
46  }
47} }
48
49#[allow(clippy::only_used_in_recursion)] // FP nightly (1bfe40d11 2022-03-18
50impl CheckedArith for i32 {
51  checked_inherent!{checked_add(, rhs: Self) rhs}
52  checked_inherent!{checked_sub(, rhs: Self) rhs}
53  checked_inherent!{checked_neg(           )    }
54}
55impl CheckedArithMul<i32> for i32 {
56  checked_inherent!{checked_mul(, rhs: Self) rhs}
57}
58impl CheckedArithMul<f64> for i32 {
59  fn checked_mul(self, rhs: f64) -> Result<Self, CoordinateOverflow> {
60    let lhs: f64 = self.into();
61    let out: f64 = lhs.checked_mul(rhs)?;
62    let out: Self = NumCast::from(out).ok_or(CoordinateOverflow)?;
63    Ok(out)
64  }
65}
66
67macro_rules! checked_float { {$n:ident($($formal:tt)*) $($modify:tt)*} => {
68  fn $n(self $($formal)*) -> Result<Self, CoordinateOverflow> {
69    let out = self $($modify)*;
70    if out.is_finite() { Ok(out) } else { Err(CoordinateOverflow) }
71  }
72} }
73
74impl CheckedArith for f64 {
75  checked_float!{checked_add(, rhs: Self)  + rhs }
76  checked_float!{checked_sub(, rhs: Self)  - rhs }
77  checked_float!{checked_neg()              .neg()}
78}
79impl CheckedArithMul<f64> for f64 {
80  checked_float!{checked_mul(, rhs: Self)  * rhs }
81}
82
83pub trait Mean { fn mean(&self, other: &Self) -> Self; }
84
85impl Mean for i32 { fn mean(&self, other: &Self) -> Self {
86  ((*self as i64 + *other as i64) / 2) as i32
87} }
88impl Mean for f64 { fn mean(&self, other: &Self) -> Self {
89  self * 0.5 + other * 0.5
90} }
91
92//---------- Pos ----------
93
94pub trait PosPromote {
95  fn promote(&self) -> PosC<f64>;
96}
97impl<T> PosPromote for PosC<T> where T: Into<f64> + Copy + Debug {
98  fn promote(&self) -> PosC<f64> { self.map(|v| v.into()) }
99}
100
101#[derive(Error,Debug,Copy,Clone,Serialize,Deserialize)]
102pub struct PosCFromIteratorError;
103display_as_debug!{PosCFromIteratorError}
104
105#[macro_export]
106macro_rules! pos_zip_try_map { {
107  $( $input:expr ),* => $closure:expr
108} => {
109  PosC::try_from_iter_2(
110    izip!($( $input .coords(), )*)
111      .map($closure)
112  )
113} }
114#[macro_export]
115macro_rules! pos_zip_map { {
116  $( $input:expr ),* => $closure:expr
117} => {
118  PosC::from_iter_2(
119    izip!($( $input .coords(), )*)
120      .map($closure)
121  )
122} }
123
124impl<T> PosC<T> {
125  pub const fn new(x: T, y: T) -> Self { PosC{ coords: [x,y] } }
126  pub fn both(v: T) -> Self where T: Copy { PosC::new(v,v) }
127  pub fn zero() -> Self where T: num_traits::Zero + Copy {
128    PosC::both(<T as num_traits::Zero>::zero())
129  }
130
131  pub fn coords(self) -> impl ExactSizeIterator<Item=T> + FusedIterator {
132    self.coords.into_iter()
133  }
134
135  #[throws(CoordinateOverflow)]
136  pub fn len2(self) -> f64 where PosC<T>: PosPromote {
137    self.promote().coords()
138      .try_fold(0., |b, c| {
139        let c2 = c.checked_mul(c)?;
140        b.checked_add(c2)
141      })?
142  }
143
144  #[throws(CoordinateOverflow)]
145  pub fn len(self) -> f64 where PosC<T>: PosPromote {
146    let d2 = self.len2()?;
147    let d = d2.sqrt();
148    if !d.is_finite() { throw!(CoordinateOverflow) }
149    d
150  }
151}
152impl<T> PosC<T> where T: Copy {
153  pub fn x(self) -> T { self.coords[0] }
154  pub fn y(self) -> T { self.coords[1] }
155}
156
157#[allow(clippy::should_implement_trait)] // this one is fallible, which is a bit odd
158impl<T> PosC<T> {
159  #[throws(PosCFromIteratorError)]
160  pub fn from_iter<I: Iterator<Item=T>>(i: I) -> Self { PosC{ coords:
161    i
162      .collect::<ArrayVec<_,2>>()
163      .into_inner()
164      .map_err(|_| PosCFromIteratorError)?
165  }}
166}
167
168impl<T> PosC<T> where T: Debug {
169  pub fn from_iter_2<I: Iterator<Item=T>>(i: I) -> Self { PosC{ coords:
170    i
171      .collect::<ArrayVec<_,2>>()
172      .into_inner()
173      .unwrap()
174  }}
175}
176
177impl<T> Debug for PosC<T> where T: Debug + Copy {
178  #[throws(fmt::Error)]
179  fn fmt(&self, f: &mut Formatter) {
180    write!(f, "[{:?},{:?}]", self.x(), self.y())?;
181  }
182}
183
184impl<T:Debug> PosC<T> {
185  /// Panics if the iterator doesn't yield exactly 2 elements
186  #[throws(E)]
187  pub fn try_from_iter_2<
188    E: Debug,
189    I: Iterator<Item=Result<T,E>>
190  >(i: I) -> Self { PosC{ coords:
191    i
192      .collect::<Result<ArrayVec<_,2>,E>>()?
193      .into_inner().unwrap()
194  }}
195}
196
197impl<T:CheckedArith> Add<PosC<T>> for PosC<T> {
198  type Output = Result<Self, CoordinateOverflow>;
199  #[throws(CoordinateOverflow)]
200  fn add(self, rhs: PosC<T>) -> PosC<T> {
201    pos_zip_try_map!( self, rhs => |(a,b)| a.checked_add(b) )?
202  }
203}
204
205impl<T:CheckedArith> Sub<PosC<T>> for PosC<T> {
206  type Output = Result<Self, CoordinateOverflow>;
207  #[throws(CoordinateOverflow)]
208  fn sub(self, rhs: PosC<T>) -> PosC<T> {
209    pos_zip_try_map!( self, rhs => |(a,b)| a.checked_sub(b) )?
210  }
211}
212
213impl<S:Copy+Debug+Clone+'static,T:CheckedArithMul<S>> Mul<S> for PosC<T> {
214  type Output = Result<Self, CoordinateOverflow>;
215  #[throws(CoordinateOverflow)]
216  fn mul(self, rhs: S) -> PosC<T> {
217    pos_zip_try_map!( self => |a| a.checked_mul(rhs) )?
218  }
219}
220
221impl<T:CheckedArith> Neg for PosC<T> {
222  type Output = Result<Self, CoordinateOverflow>;
223  #[throws(CoordinateOverflow)]
224  fn neg(self) -> Self {
225    pos_zip_try_map!( self => |a| a.checked_neg() )?
226  }
227}
228
229impl<T:Copy+Clone+Debug> PosC<T> {
230  pub fn map<U:Copy+Clone+Debug, F: FnMut(T) -> U>(self, f: F) -> PosC<U> {
231    pos_zip_map!( self => f )
232  }
233}
234
235impl<T:Copy+Clone+Debug> PosC<T> {
236  pub fn try_map<E:Debug, U:Copy+Clone+Debug, F: FnMut(T) -> Result<U,E>>
237    (self, f: F) -> Result<PosC<U>,E>
238  {
239    pos_zip_try_map!( self => f )
240  }
241}
242
243impl<T> Mean for PosC<T> where T: Mean + Debug + Copy {
244  fn mean(&self, other: &Self) -> Self where T: Mean {
245    pos_zip_map!( self, other => |(a,b)| a.mean(&b) )
246  }
247}
248
249// ---------- Rect ----------
250
251impl<T> RectC<T> where T: Copy {
252  pub fn tl(&self) -> PosC<T> { self.corners[0] }
253  pub fn br(&self) -> PosC<T> { self.corners[1] }
254}
255
256impl<T> Debug for RectC<T> where T: Debug + Copy {
257  #[throws(fmt::Error)]
258  fn fmt(&self, f: &mut Formatter) {
259    write!(f, "Rect[{:?},{:?}]", self.tl(), self.br())?;
260  }
261}
262
263impl<T> RectC<T> {
264  pub fn contains(&self, p: PosC<T>) -> bool where T: PartialOrd + Copy {
265    (0..2).all(|i| {
266      p.coords[i] >= self.tl().coords[i] &&
267      p.coords[i] <= self.br().coords[i]
268    })
269  }
270
271  pub fn overlaps(&self, other: &RectC<T>) -> bool where T: PartialOrd + Copy {
272    ! (0..2).any(|i| (
273      other.br().coords[i] < self .tl().coords[i] ||
274      self .br().coords[i] < other.tl().coords[i]
275    ))
276  }
277
278  pub fn empty() -> Self where T: num_traits::Zero + num_traits::One + Copy {
279    RectC{ corners: [
280      PosC::both( <T as num_traits::One >::one()  ),
281      PosC::both( <T as num_traits::Zero>::zero() ),
282    ]}
283  }
284}
285
286impl<T> RectC<T> where T: Mean + Debug + Copy {
287  pub fn middle(&self) -> PosC<T> {
288    Mean::mean(&self.tl(),
289               &self.br())
290  }
291}
292
293impl<T> RectC<T> where T: CheckedArith + Debug + Copy {
294  #[throws(CoordinateOverflow)]
295  pub fn size(&self) -> PosC<T> {
296    (self.br() - self.tl())?
297  }
298}
299
300#[test]
301fn empty_area() {
302  let empty = Rect::empty();
303  for x in -3..3 { for y in -3..3 {
304    dbg!(empty,x,y);
305    assert!(! empty.contains(PosC::new(x,y)));
306  } }
307}
308
309// ---------- Region ----------
310
311#[derive(Clone,Debug,Serialize,Deserialize)]
312#[derive(Ord,PartialOrd,Eq,PartialEq)]
313pub enum RegionC<T:Copy> {
314  Rect(RectC<T>),
315}
316pub type Region = RegionC<Coord>;
317
318impl<T:Copy> RegionC<T> {
319  pub fn contains(&self, pos: PosC<T>) -> bool where T: PartialOrd {
320    use RegionC::*;
321    match &self {
322      Rect(a) => a.contains(pos),
323    }
324  }
325
326  pub fn overlaps(&self, other: &RegionC<T>) -> bool where T: PartialOrd {
327    use RegionC::*;
328    match (self, other) {
329      (Rect(a), Rect(b)) => a.overlaps(b)
330    }
331  }
332
333  pub fn empty() -> Self where T: Copy + num_traits::Zero + num_traits::One {
334    RegionC::Rect(RectC::empty())
335  }
336
337}