bracket_algorithm_traits/
algorithm3d.rs

1//! # `Algorithm3D`
2//!
3//! `Algorithm3D` provides a translation layer between your map definition and `bracket-lib`. It
4//! seems to provide sane defaults, to minimize the work *if* you want to use my defaults for things
5//! like 3D array striding. If you don't, you can override more.
6//!
7//! At the minimum, override `dimensions` to provide a `Point` containing the upper boundaries of your map.
8//! The library can derive other traits from this.
9//!
10//! You can override `in_bounds` to change how you test if a coordinate is valid.
11//!
12//! You can override `point2d_to_index` to define how an X/Y point is mapped to a unique index.
13//!
14//! You can override `index_to_point2d` to override how a unique index is mapped to an X/Y coordinate.
15
16use crate::prelude::BaseMap;
17use bracket_geometry::prelude::Point3;
18
19/// Implement these for handling conversion to/from 2D coordinates (they are separate, because you might
20/// want Dwarf Fortress style 3D!)
21pub trait Algorithm3D: BaseMap {
22    /// Convert a Point (x/y) to an array index. Defaults to a Z, Y, X striding.
23    #[allow(clippy::cast_sign_loss)]
24    fn point3d_to_index(&self, pt: Point3) -> usize {
25        let bounds = self.dimensions();
26        ((pt.z * (bounds.x * bounds.y)) + (pt.y * bounds.x) + pt.x) as usize
27    }
28
29    /// Convert an array index to a point.
30    #[allow(clippy::cast_possible_wrap)]
31    #[allow(clippy::cast_possible_truncation)]
32    fn index_to_point3d(&self, idx: usize) -> Point3 {
33        let mut my_idx = idx as i32;
34        let bounds = self.dimensions();
35        let z = my_idx / (bounds.x * bounds.y);
36        my_idx -= z * bounds.x * bounds.y;
37
38        let y = my_idx / bounds.x;
39        my_idx -= y * bounds.x;
40
41        let x = my_idx;
42        Point3::new(x, y, z)
43    }
44
45    /// Dimensions
46    fn dimensions(&self) -> Point3 {
47        panic!("You must either define the dimensions function (trait Algorithm3D) on your map, or define the various point3d_to_index and index_to_point3d functions.");
48    }
49
50    // Optional - check that an x/y/z coordinate is within the map bounds. If not provided,
51    // it falls back to using the map's dimensions from that trait implementation. Most of
52    // the time, that's what you want.
53    fn in_bounds(&self, pos: Point3) -> bool {
54        let bounds = self.dimensions();
55        pos.x >= 0
56            && pos.x < bounds.x
57            && pos.y >= 0
58            && pos.y < bounds.y
59            && pos.z >= 0
60            && pos.z < bounds.z
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use crate::prelude::{Algorithm3D, BaseMap};
67    use bracket_geometry::prelude::Point3;
68
69    #[test]
70    // Tests that we make an RGB triplet at defaults and it is black.
71    #[should_panic]
72    fn test_unimplemented_dimensions() {
73        struct TestMap {}
74        impl BaseMap for TestMap {}
75        impl Algorithm3D for TestMap {}
76
77        let map = TestMap {};
78        assert!(map.in_bounds(Point3::new(1, 1, 1)));
79    }
80
81    #[test]
82    fn test_in_bounds() {
83        struct TestMap {}
84        impl BaseMap for TestMap {}
85        impl Algorithm3D for TestMap {
86            fn dimensions(&self) -> Point3 {
87                Point3::new(2, 2, 2)
88            }
89        }
90
91        let map = TestMap {};
92        assert!(map.in_bounds(Point3::new(0, 0, 0)));
93        assert!(map.in_bounds(Point3::new(1, 1, 1)));
94        assert!(!map.in_bounds(Point3::new(3, 3, 3)));
95    }
96
97    #[test]
98    fn test_point3d_to_index() {
99        struct TestMap {}
100        impl BaseMap for TestMap {}
101        impl Algorithm3D for TestMap {
102            fn dimensions(&self) -> Point3 {
103                Point3::new(10, 10, 10)
104            }
105        }
106
107        let map = TestMap {};
108        assert!(map.point3d_to_index(Point3::new(0, 0, 0)) == 0);
109        assert!(map.point3d_to_index(Point3::new(1, 0, 0)) == 1);
110        assert!(map.point3d_to_index(Point3::new(0, 1, 0)) == 10);
111        assert!(map.point3d_to_index(Point3::new(9, 9, 0)) == 99);
112        assert!(map.point3d_to_index(Point3::new(9, 9, 9)) == 999);
113    }
114
115    #[test]
116    fn test_index_to_point3d() {
117        struct TestMap {}
118        impl BaseMap for TestMap {}
119        impl Algorithm3D for TestMap {
120            fn dimensions(&self) -> Point3 {
121                Point3::new(10, 10, 10)
122            }
123        }
124
125        let map = TestMap {};
126        let mut x = 0;
127        let mut y = 0;
128        let mut z: i32 = 0;
129        for i in 0..1000 {
130            assert!(map.index_to_point3d(i) == Point3::new(x, y, z));
131
132            x += 1;
133            if x > 9 {
134                x = 0;
135                y += 1;
136            }
137            if y > 9 {
138                y = 0;
139                z += 1;
140            }
141        }
142    }
143}