doryen_extra/
base.rs

1/* BSD 3-Clause License
2 *
3 * Copyright © 2019, Alexander Krivács Schrøder <alexschrod@gmail.com>.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright notice,
10 *    this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 *    this list of conditions and the following disclaimer in the documentation
14 *    and/or other materials provided with the distribution.
15 *
16 * 3. Neither the name of the copyright holder nor the names of its
17 *    contributors may be used to endorse or promote products derived from
18 *    this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33use std::convert::TryFrom;
34use std::num::TryFromIntError;
35
36#[macro_use]
37mod def_macro;
38
39define_two_property_arithmetic_struct!(Position, UPosition, FPosition, x, y, ORIGIN, "({}, {})");
40define_two_property_arithmetic_struct!(Size, USize, FSize, width, height, ZERO, "{}x{}");
41
42impl Size {
43    /// Returns the area represented by this size
44    pub fn area(self) -> i32 {
45        self.width * self.height
46    }
47}
48
49impl USize {
50    /// Returns the area represented by this size
51    pub fn area(self) -> u32 {
52        self.width * self.height
53    }
54}
55
56impl FSize {
57    /// Returns the area represented by this size
58    pub fn area(self) -> f32 {
59        self.width * self.height
60    }
61}
62
63/// Represents a rectangle, using a position and size.
64#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
65#[cfg_attr(
66    feature = "serialization",
67    derive(serde_derive::Serialize, serde_derive::Deserialize)
68)]
69pub struct Rectangle {
70    /// The location of the rectangle's upper-left corner
71    pub position: Position,
72    /// The width and height of the rectangle
73    pub size: USize,
74}
75
76impl Rectangle {
77    /// Returns a new rectangle with the given position and size
78    pub fn new(position: Position, size: USize) -> Self {
79        Self { position, size }
80    }
81
82    /// Returns a new rectangle with the given raw position and size values
83    pub fn new_from_raw(x: i32, y: i32, width: u32, height: u32) -> Self {
84        Self {
85            position: Position::new(x, y),
86            size: USize::new(width, height),
87        }
88    }
89
90    /// Returns whether a given position is within the rectangle or not
91    pub fn contains_position(&self, position: Position) -> bool {
92        position.x >= self.position.x
93            && position.x <= self.position.x + self.size.width as i32
94            && position.y >= self.position.y
95            && position.y <= self.position.y + self.size.height as i32
96    }
97
98    /// Returns whether a given position is within the rectangle or not
99    pub fn contains_fposition(&self, position: FPosition) -> bool {
100        position.x >= self.position.x as f32
101            && position.x <= self.position.x as f32 + self.size.width as f32
102            && position.y >= self.position.y as f32
103            && position.y <= self.position.y as f32 + self.size.height as f32
104    }
105}
106
107/// Represents a floating-point rectangle, using a position and size.
108#[derive(Copy, Clone, Default, PartialEq, Debug)]
109#[cfg_attr(
110    feature = "serialization",
111    derive(serde_derive::Serialize, serde_derive::Deserialize)
112)]
113pub struct FRectangle {
114    /// The location of the rectangle's upper-left corner
115    pub position: FPosition,
116    /// The width and height of the rectangle
117    pub size: FSize,
118}
119
120impl FRectangle {
121    /// Returns a new rectangle with the given position and size
122    ///
123    /// # Panics
124    /// This function may panic if the width or height is < 0.
125    pub fn new(position: FPosition, size: FSize) -> Self {
126        assert!(size.width >= 0.0);
127        assert!(size.height >= 0.0);
128
129        Self { position, size }
130    }
131
132    /// Returns a new rectangle with the given raw position and size values
133    ///
134    /// # Panics
135    /// This function may panic if the width or height is < 0.
136    pub fn new_from_raw(x: f32, y: f32, width: f32, height: f32) -> Self {
137        assert!(width >= 0.0);
138        assert!(height >= 0.0);
139
140        Self {
141            position: FPosition::new(x, y),
142            size: FSize::new(width, height),
143        }
144    }
145
146    /// Returns whether a given position is within the rectangle or not
147    pub fn contains_position(&self, position: FPosition) -> bool {
148        position.x >= self.position.x
149            && position.x <= self.position.x + self.size.width
150            && position.y >= self.position.y
151            && position.y <= self.position.y + self.size.height
152    }
153}
154
155impl std::ops::Add<USize> for Position {
156    type Output = Rectangle;
157
158    fn add(self, rhs: USize) -> Self::Output {
159        Rectangle::new(self, rhs)
160    }
161}
162
163impl std::ops::Add<FSize> for FPosition {
164    type Output = FRectangle;
165
166    fn add(self, rhs: FSize) -> Self::Output {
167        FRectangle::new(self, rhs)
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    // It should be sufficient to test only Position, as Size uses the same macro
176    // to create its types.
177
178    #[test]
179    #[allow(clippy::float_cmp)]
180    fn new_sets_values() {
181        let p = Position::new(-1, -2);
182        assert_eq!(p.x, -1);
183        assert_eq!(p.y, -2);
184
185        let up = UPosition::new(1, 2);
186        assert_eq!(up.x, 1);
187        assert_eq!(up.y, 2);
188
189        let fp = FPosition::new(1., -2.);
190        assert_eq!(fp.x, 1.);
191        assert_eq!(fp.y, -2.);
192
193        let us = USize::new(1, 2);
194        let r = Rectangle::new(p, us);
195        assert_eq!(r.position, p);
196        assert_eq!(r.size, us);
197
198        let r2 = Rectangle::new_from_raw(1, 2, 3, 4);
199        assert_eq!(r2.position, Position::new(1, 2));
200        assert_eq!(r2.size, USize::new(3, 4));
201    }
202
203    #[test]
204    #[allow(clippy::float_cmp)]
205    fn from_sets_values() {
206        let p: Position = From::from((-1, -2));
207        assert_eq!(p.x, -1);
208        assert_eq!(p.y, -2);
209
210        let up: UPosition = From::from((1, 2));
211        assert_eq!(up.x, 1);
212        assert_eq!(up.y, 2);
213
214        let fp: FPosition = From::from((1., -2.));
215        assert_eq!(fp.x, 1.);
216        assert_eq!(fp.y, -2.);
217    }
218
219    #[test]
220    #[allow(clippy::float_cmp)]
221    fn from_gets_values() {
222        let p = Position::new(-1, -2);
223        let (px, py): (i32, i32) = From::from(p);
224        assert_eq!(px, -1);
225        assert_eq!(py, -2);
226
227        let up = UPosition::new(1, 2);
228        let (upx, upy): (u32, u32) = From::from(up);
229        assert_eq!(upx, 1);
230        assert_eq!(upy, 2);
231
232        let fp = FPosition::new(1., -2.);
233        let (fpx, fpy): (f32, f32) = From::from(fp);
234        assert_eq!(fpx, 1.);
235        assert_eq!(fpy, -2.);
236    }
237
238    #[test]
239    fn display_is_correct() {
240        let p = Position::new(-1, -2);
241        assert_eq!(p.to_string(), "(-1, -2)");
242
243        let up = UPosition::new(1, 2);
244        assert_eq!(up.to_string(), "(1, 2)");
245
246        let fp = FPosition::new(1.5, -2.7);
247        assert_eq!(fp.to_string(), "(1.5, -2.7)");
248
249        let s = Size::new(-1, -2);
250        assert_eq!(s.to_string(), "-1x-2");
251
252        let us = USize::new(1, 2);
253        assert_eq!(us.to_string(), "1x2");
254
255        let fs = FSize::new(1.5, -2.7);
256        assert_eq!(fs.to_string(), "1.5x-2.7");
257    }
258
259    #[test]
260    fn addition() {
261        let p = Position::new(-1, -2);
262        let p2 = Position::new(3, 4);
263        assert_eq!(p + p2, Position::new(2, 2));
264
265        let up = UPosition::new(1, 2);
266        let up2 = UPosition::new(3, 4);
267        assert_eq!(up + up2, UPosition::new(4, 6));
268
269        let fp = FPosition::new(-1.5, -3.0);
270        let fp2 = FPosition::new(4.5, 6.0);
271        assert_eq!(fp + fp2, FPosition::new(3., 3.));
272
273        let us = USize::new(1, 2);
274        assert_eq!(p + us, Rectangle::new(p, us));
275        let fs = FSize::new(4.5, 6.0);
276        assert_eq!(fp + fs, FRectangle::new(fp, fs));
277    }
278
279    #[test]
280    fn addition_scalar() {
281        let p = Position::new(-1, -2);
282        let p2 = 2;
283        assert_eq!(p + p2, Position::new(1, 0));
284
285        let up = UPosition::new(1, 2);
286        let up2 = 3;
287        assert_eq!(up + up2, UPosition::new(4, 5));
288
289        let fp = FPosition::new(-1.5, -3.0);
290        let fp2 = 1.5;
291        assert_eq!(fp + fp2, FPosition::new(0., -1.5));
292    }
293
294    #[test]
295    fn addition_tuple() {
296        let p = Position::new(-1, -2);
297        let p2 = (3, 4);
298        assert_eq!(p + p2, Position::new(2, 2));
299
300        let up = UPosition::new(1, 2);
301        let up2 = (3, 4);
302        assert_eq!(up + up2, UPosition::new(4, 6));
303
304        let fp = FPosition::new(-1.5, -3.0);
305        let fp2 = (4.5, 6.0);
306        assert_eq!(fp + fp2, FPosition::new(3., 3.));
307    }
308
309    #[test]
310    fn add_assign_scalar() {
311        let mut p = Position::new(-1, -2);
312        p += 2;
313        assert_eq!(p, Position::new(1, 0));
314
315        let mut up = UPosition::new(1, 2);
316        up += 3;
317        assert_eq!(up, UPosition::new(4, 5));
318
319        let mut fp = FPosition::new(-1.5, -3.0);
320        fp += 1.5;
321        assert_eq!(fp, FPosition::new(0., -1.5));
322    }
323
324    #[test]
325    fn add_assign_tuple() {
326        let mut p = Position::new(-1, -2);
327        p += (3, 4);
328        assert_eq!(p, Position::new(2, 2));
329
330        let mut up = UPosition::new(1, 2);
331        up += (3, 4);
332        assert_eq!(up, UPosition::new(4, 6));
333
334        let mut fp = FPosition::new(-1.5, -3.0);
335        fp += (4.5, 6.0);
336        assert_eq!(fp, FPosition::new(3., 3.));
337    }
338
339    #[test]
340    fn subtraction() {
341        let p = Position::new(-1, -2);
342        let p2 = Position::new(3, 4);
343        assert_eq!(p - p2, Position::new(-4, -6));
344
345        let up = UPosition::new(3, 4);
346        let up2 = UPosition::new(1, 2);
347        assert_eq!(up - up2, UPosition::new(2, 2));
348
349        let fp = FPosition::new(-1.5, -3.0);
350        let fp2 = FPosition::new(4.5, 6.0);
351        assert_eq!(fp - fp2, FPosition::new(-6., -9.));
352    }
353
354    #[test]
355    fn subtraction_scalar() {
356        let p = Position::new(-1, -2);
357        let p2 = 2;
358        assert_eq!(p - p2, Position::new(-3, -4));
359
360        let up = UPosition::new(1, 2);
361        let up2 = 1;
362        assert_eq!(up - up2, UPosition::new(0, 1));
363
364        let fp = FPosition::new(-1.5, -3.0);
365        let fp2 = 1.5;
366        assert_eq!(fp - fp2, FPosition::new(-3., -4.5));
367    }
368
369    #[test]
370    fn subtraction_tuple() {
371        let p = Position::new(-1, -2);
372        let p2 = (3, 4);
373        assert_eq!(p - p2, Position::new(-4, -6));
374
375        let up = UPosition::new(3, 4);
376        let up2 = (1, 2);
377        assert_eq!(up - up2, UPosition::new(2, 2));
378
379        let fp = FPosition::new(-1.5, -3.0);
380        let fp2 = (4.5, 6.0);
381        assert_eq!(fp - fp2, FPosition::new(-6., -9.));
382    }
383
384    #[test]
385    fn sub_assign_scalar() {
386        let mut p = Position::new(-1, -2);
387        p -= 2;
388        assert_eq!(p, Position::new(-3, -4));
389
390        let mut up = UPosition::new(6, 3);
391        up -= 3;
392        assert_eq!(up, UPosition::new(3, 0));
393
394        let mut fp = FPosition::new(-1.5, -3.0);
395        fp -= 1.5;
396        assert_eq!(fp, FPosition::new(-3.0, -4.5));
397    }
398
399    #[test]
400    fn sub_assign_tuple() {
401        let mut p = Position::new(-1, -2);
402        p -= (3, 4);
403        assert_eq!(p, Position::new(-4, -6));
404
405        let mut up = UPosition::new(3, 4);
406        up -= (1, 2);
407        assert_eq!(up, UPosition::new(2, 2));
408
409        let mut fp = FPosition::new(-1.5, -3.0);
410        fp -= (4.5, 6.0);
411        assert_eq!(fp, FPosition::new(-6., -9.));
412    }
413
414    #[test]
415    fn multiplication_scalar() {
416        let p = Position::new(-1, -2);
417        let p2 = 2;
418        assert_eq!(p * p2, Position::new(-2, -4));
419
420        let up = UPosition::new(1, 2);
421        let up2 = 3;
422        assert_eq!(up * up2, UPosition::new(3, 6));
423
424        let fp = FPosition::new(-1.5, -3.0);
425        let fp2 = 1.5;
426        assert_eq!(fp * fp2, FPosition::new(-2.25, -4.5));
427    }
428
429    #[test]
430    fn mul_assign_scalar() {
431        let mut p = Position::new(-1, -2);
432        p *= 2;
433        assert_eq!(p, Position::new(-2, -4));
434
435        let mut up = UPosition::new(6, 3);
436        up *= 3;
437        assert_eq!(up, UPosition::new(18, 9));
438
439        let mut fp = FPosition::new(-1.5, -3.0);
440        fp *= 1.5;
441        assert_eq!(fp, FPosition::new(-2.25, -4.5));
442    }
443
444    #[test]
445    fn division_scalar() {
446        let p = Position::new(-2, -4);
447        let p2 = 2;
448        assert_eq!(p / p2, Position::new(-1, -2));
449
450        let up = UPosition::new(18, 9);
451        let up2 = 3;
452        assert_eq!(up / up2, UPosition::new(6, 3));
453
454        let fp = FPosition::new(-1.5, -3.0);
455        let fp2 = 1.5;
456        assert_eq!(fp / fp2, FPosition::new(-1., -2.));
457    }
458
459    #[test]
460    fn div_assign_scalar() {
461        let mut p = Position::new(-2, -4);
462        p /= 2;
463        assert_eq!(p, Position::new(-1, -2));
464
465        let mut up = UPosition::new(6, 3);
466        up /= 3;
467        assert_eq!(up, UPosition::new(2, 1));
468
469        let mut fp = FPosition::new(-1.5, -3.0);
470        fp /= 1.5;
471        assert_eq!(fp, FPosition::new(-1., -2.));
472    }
473
474    #[test]
475    fn rem_scalar() {
476        let p = Position::new(-5, -6);
477        let p2 = 4;
478        assert_eq!(p % p2, Position::new(-1, -2));
479
480        let up = UPosition::new(18, 9);
481        let up2 = 4_u32;
482        assert_eq!(up % up2, UPosition::new(2, 1));
483
484        let fp = FPosition::new(-2., -4.);
485        let fp2 = 1.5;
486        assert_eq!(fp % fp2, FPosition::new(-0.5, -1.));
487    }
488
489    #[test]
490    fn rem_assign_scalar() {
491        let mut p = Position::new(-3, -5);
492        p %= 2;
493        assert_eq!(p, Position::new(-1, -1));
494
495        let mut up = UPosition::new(6, 3);
496        up %= 4_u32;
497        assert_eq!(up, UPosition::new(2, 3));
498
499        let mut fp = FPosition::new(-5.5, -7.0);
500        fp %= 1.5;
501        assert_eq!(fp, FPosition::new(-1., -1.));
502    }
503
504    #[test]
505    fn negate() {
506        let p = Position::new(-5, -6);
507        assert_eq!(-p, Position::new(5, 6));
508
509        let fp = FPosition::new(-2., -4.);
510        assert_eq!(-fp, FPosition::new(2., 4.));
511    }
512
513    #[test]
514    fn round() {
515        let fp = FPosition::new(-2.5, 2.5);
516        assert_eq!(fp.round(), Position::new(-3, 3));
517
518        let ufp = FPosition::new(2.5, 2.5);
519        assert_eq!(ufp.round_u(), UPosition::new(3, 3));
520    }
521
522    #[test]
523    #[should_panic]
524    fn round_u_less_than_zero_panics() {
525        let fp = FPosition::new(-3.5, 2.5);
526        fp.round_u();
527    }
528
529    #[test]
530    fn trunc() {
531        let fp = FPosition::new(-2.5, 2.5);
532        assert_eq!(fp.trunc(), Position::new(-2, 2));
533
534        let ufp = FPosition::new(3.5, 2.5);
535        assert_eq!(ufp.trunc_u(), UPosition::new(3, 2));
536    }
537
538    #[test]
539    #[should_panic]
540    fn trunc_u_less_than_zero_panics() {
541        let fp = FPosition::new(-3.5, 2.5);
542        fp.trunc_u();
543    }
544
545    #[test]
546    #[allow(clippy::float_cmp)]
547    fn area() {
548        let s = Size::new(3, 2);
549        assert_eq!(s.area(), 6);
550
551        let us = USize::new(3, 2);
552        assert_eq!(us.area(), 6);
553
554        let fs = FSize::new(3.5, 2.5);
555        assert_eq!(fs.area(), 8.75);
556    }
557
558    #[test]
559    fn contains_position() {
560        let r = Rectangle::new_from_raw(-5, -10, 10, 20);
561        let fr = FRectangle::new_from_raw(-5., -10., 10., 20.);
562
563        // All (integer) points inside, including the corners and along the edges.
564        for x in -5..=5 {
565            for y in -10..=10 {
566                assert!(r.contains_position(Position::new(x, y)));
567                assert!(r.contains_fposition(FPosition::new(x as f32, y as f32)));
568                assert!(fr.contains_position(FPosition::new(x as f32, y as f32)));
569            }
570        }
571
572        // All points along the outside
573        for &x in &[-6, 6] {
574            for y in -11..=11 {
575                assert!(!r.contains_position(Position::new(x, y)));
576                assert!(!r.contains_fposition(FPosition::new(x as f32, y as f32)));
577                assert!(!fr.contains_position(FPosition::new(x as f32, y as f32)));
578            }
579        }
580        for x in -6..=6 {
581            for &y in &[-11, 11] {
582                assert!(!r.contains_position(Position::new(x, y)));
583                assert!(!r.contains_fposition(FPosition::new(x as f32, y as f32)));
584                assert!(!fr.contains_position(FPosition::new(x as f32, y as f32)));
585            }
586        }
587    }
588
589    #[test]
590    fn from_position_conversions() {
591        use std::convert::TryFrom;
592
593        let p = Position::new(1, 2);
594        let p_up = UPosition::try_from(p);
595        assert!(p_up.is_ok());
596        assert_eq!(p_up.unwrap(), UPosition::new(1, 2));
597
598        let np = Position::new(-1, -2);
599        let np_up = UPosition::try_from(np);
600        assert!(np_up.is_err());
601
602        let p_fp = FPosition::from(p);
603        assert_eq!(p_fp, FPosition::new(1.0, 2.0));
604        let np_fp = FPosition::from(np);
605        assert_eq!(np_fp, FPosition::new(-1.0, -2.0));
606    }
607
608    #[test]
609    fn from_uposition_conversions() {
610        use std::convert::TryFrom;
611
612        let up = UPosition::new(1, 2);
613        let up_p = Position::try_from(up);
614        assert!(up_p.is_ok());
615        assert_eq!(up_p.unwrap(), Position::new(1, 2));
616
617        let bp = UPosition::new(u32::MAX / 2 + 1, u32::MAX);
618        let bp_p = Position::try_from(bp);
619        assert!(bp_p.is_err());
620
621        let up_fp = FPosition::from(up);
622        assert_eq!(up_fp, FPosition::new(1.0, 2.0));
623        let bp_fp = FPosition::from(bp);
624        assert_eq!(
625            bp_fp,
626            FPosition::new((u32::MAX / 2 + 1) as f32, u32::MAX as f32)
627        );
628    }
629
630    #[test]
631    fn from_fposition_conversions() {
632        use std::convert::TryFrom;
633
634        let fp = FPosition::new(1., 2.);
635        let fp_p = Position::try_from(fp);
636        assert!(fp_p.is_ok());
637        assert_eq!(fp_p.unwrap(), Position::new(1, 2));
638
639        let bfp = FPosition::new((u32::MAX / 2 + 1) as f32, u32::MAX as f32);
640        let bfp_p = Position::try_from(bfp);
641        assert!(bfp_p.is_err());
642        assert_eq!(bfp_p.unwrap_err(), TryFromPositionError::FloatToInt);
643
644        let fp_up = UPosition::try_from(fp);
645        assert!(fp_up.is_ok());
646        assert_eq!(fp_up.unwrap(), UPosition::new(1, 2));
647
648        let ebfp = FPosition::new((u64::MAX / 2 + 1) as f32, u64::MAX as f32);
649        let ebfp_up = UPosition::try_from(ebfp);
650        assert!(ebfp_up.is_err());
651        assert_eq!(ebfp_up.unwrap_err(), TryFromPositionError::FloatToInt);
652    }
653}