honeycomb_core/cmap/builder/
grid.rs

1use crate::attributes::AttrStorageManager;
2use crate::cmap::{BuilderError, CMap2, DartIdType};
3use crate::geometry::{CoordsFloat, Vector2, Vertex2};
4
5// --- grid descriptor
6
7/// # Grid description used by the map builder
8///
9/// The user must specify two out of these three characteristics (third is deduced):
10///
11/// - `n_cells: [usize; 3]` -- The number of cells per axis
12/// - `len_per_cell: [T; 3]` -- The dimensions of cells per axis
13/// - `lens: [T; 3]` -- The total dimensions of the grid per axis
14///
15/// ## Generics
16///
17/// - `T: CoordsFloat` -- Generic FP type that will be used by the map's vertices.
18#[derive(Default, Clone)]
19pub struct GridDescriptor<T: CoordsFloat> {
20    pub(crate) origin: Vertex2<T>,
21    pub(crate) n_cells: Option<[usize; 3]>,
22    pub(crate) len_per_cell: Option<[T; 3]>,
23    pub(crate) lens: Option<[T; 3]>,
24    pub(crate) split_quads: bool,
25}
26
27macro_rules! setters {
28    ($fld: ident, $fldx: ident, $fldy: ident, $fldz: ident, $zero: expr, $fldty: ty) => {
29        /// Set values for all dimensions
30        #[must_use = "unused builder object"]
31        pub fn $fld(mut self, $fld: [$fldty; 3]) -> Self {
32            self.$fld = Some($fld);
33            self
34        }
35
36        /// Set x-axis value
37        #[must_use = "unused builder object"]
38        pub fn $fldx(mut self, $fld: $fldty) -> Self {
39            if let Some([ptr, _, _]) = &mut self.$fld {
40                *ptr = $fld;
41            } else {
42                self.$fld = Some([$fld, $zero, $zero]);
43            }
44            self
45        }
46
47        /// Set y-axis value
48        #[must_use = "unused builder object"]
49        pub fn $fldy(mut self, $fld: $fldty) -> Self {
50            if let Some([_, ptr, _]) = &mut self.$fld {
51                *ptr = $fld;
52            } else {
53                self.$fld = Some([$zero, $fld, $zero]);
54            }
55            self
56        }
57
58        /// Set z-axis value
59        #[must_use = "unused builder object"]
60        pub fn $fldz(mut self, $fld: $fldty) -> Self {
61            if let Some([_, _, ptr]) = &mut self.$fld {
62                *ptr = $fld;
63            } else {
64                self.$fld = Some([$zero, $zero, $fld]);
65            }
66            self
67        }
68    };
69}
70
71impl<T: CoordsFloat> GridDescriptor<T> {
72    // n_cells
73    setters!(n_cells, n_cells_x, n_cells_y, n_cells_z, 0, usize);
74
75    // len_per_cell
76    setters!(
77        len_per_cell,
78        len_per_cell_x,
79        len_per_cell_y,
80        len_per_cell_z,
81        T::zero(),
82        T
83    );
84
85    // lens
86    setters!(lens, lens_x, lens_y, lens_z, T::zero(), T);
87
88    /// Set origin (most bottom-left vertex) of the grid
89    #[must_use = "unused builder object"]
90    pub fn origin(mut self, origin: Vertex2<T>) -> Self {
91        self.origin = origin;
92        self
93    }
94
95    /// Indicate whether to split quads of the grid
96    #[must_use = "unused builder object"]
97    pub fn split_quads(mut self, split: bool) -> Self {
98        self.split_quads = split;
99        self
100    }
101}
102
103// --- parsing routine
104
105macro_rules! check_parameters {
106    ($id: ident, $msg: expr) => {
107        if $id.is_sign_negative() | $id.is_zero() {
108            return Err(BuilderError::InvalidGridParameters($msg));
109        }
110    };
111}
112
113impl<T: CoordsFloat> GridDescriptor<T> {
114    /// Parse provided grid parameters to provide what's used to actually generate the grid.
115    #[allow(clippy::type_complexity)]
116    pub(crate) fn parse_2d(self) -> Result<(Vertex2<T>, [usize; 2], [T; 2]), BuilderError> {
117        match (self.n_cells, self.len_per_cell, self.lens) {
118            // from # cells and lengths per cell
119            (Some([nx, ny, _]), Some([lpx, lpy, _]), lens) => {
120                if lens.is_some() {
121                    eprintln!("W: All three grid parameters were specified, total lengths will be ignored");
122                }
123                #[rustfmt::skip]
124                check_parameters!(lpx, "length per x cell is null or negative");
125                #[rustfmt::skip]
126                check_parameters!(lpy, "length per y cell is null or negative");
127                Ok((self.origin, [nx, ny], [lpx, lpy]))
128            }
129            // from # cells and total lengths
130            (Some([nx, ny, _]), None, Some([lx, ly, _])) => {
131                #[rustfmt::skip]
132                check_parameters!(lx, "grid length along x is null or negative");
133                #[rustfmt::skip]
134                check_parameters!(ly, "grid length along y is null or negative");
135                Ok((
136                    self.origin,
137                    [nx, ny],
138                    [lx / T::from(nx).unwrap(), ly / T::from(ny).unwrap()],
139                ))
140            }
141            // from lengths per cell and total lengths
142            (None, Some([lpx, lpy, _]), Some([lx, ly, _])) => {
143                #[rustfmt::skip]
144                check_parameters!(lpx, "length per x cell is null or negative");
145                #[rustfmt::skip]
146                check_parameters!(lpy, "length per y cell is null or negative");
147                #[rustfmt::skip]
148                check_parameters!(lx, "grid length along x is null or negative");
149                #[rustfmt::skip]
150                check_parameters!(ly, "grid length along y is null or negative");
151                Ok((
152                    self.origin,
153                    [
154                        (lx / lpx).ceil().to_usize().unwrap(),
155                        (ly / lpy).ceil().to_usize().unwrap(),
156                    ],
157                    [lpx, lpy],
158                ))
159            }
160            (_, _, _) => Err(BuilderError::MissingGridParameters),
161        }
162    }
163}
164
165// --- building routines
166
167/// Internal grid-building routine
168#[allow(clippy::too_many_lines)]
169pub fn build_2d_grid<T: CoordsFloat>(
170    origin: Vertex2<T>,
171    [n_square_x, n_square_y]: [usize; 2],
172    [len_per_x, len_per_y]: [T; 2],
173    manager: AttrStorageManager,
174) -> CMap2<T> {
175    let map: CMap2<T> = CMap2::new_with_undefined_attributes(4 * n_square_x * n_square_y, manager);
176
177    // init beta functions
178    (1..=(4 * n_square_x * n_square_y) as DartIdType)
179        .zip(generate_square_beta_values(n_square_x, n_square_y))
180        .for_each(|(dart, images)| {
181            map.set_betas(dart, images);
182        });
183
184    // place vertices
185
186    // bottow left vertex of all cells
187    (0..n_square_y)
188        // flatten the loop to expose more parallelism
189        .flat_map(|y_idx| (0..n_square_x).map(move |x_idx| (y_idx, x_idx)))
190        .for_each(|(y_idx, x_idx)| {
191            let vertex_id = map.vertex_id((1 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
192            map.force_write_vertex(
193                vertex_id,
194                origin
195                    + Vector2(
196                        T::from(x_idx).unwrap() * len_per_x,
197                        T::from(y_idx).unwrap() * len_per_y,
198                    ),
199            );
200        });
201
202    // top left vertex of all top row cells
203    (0..n_square_x).for_each(|x_idx| {
204        let y_idx = n_square_y - 1;
205        let vertex_id = map.vertex_id((4 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
206        map.force_write_vertex(
207            vertex_id,
208            origin
209                + Vector2(
210                    T::from(x_idx).unwrap() * len_per_x,
211                    T::from(y_idx + 1).unwrap() * len_per_y,
212                ),
213        );
214    });
215
216    // bottom right vertex of all right col cells
217    (0..n_square_y).for_each(|y_idx| {
218        let x_idx = n_square_x - 1;
219        let vertex_id = map.vertex_id((2 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
220        map.force_write_vertex(
221            vertex_id,
222            origin
223                + Vector2(
224                    T::from(x_idx + 1).unwrap() * len_per_x,
225                    T::from(y_idx).unwrap() * len_per_y,
226                ),
227        );
228    });
229
230    // top right vertex of the last cell
231    {
232        let (x_idx, y_idx) = (n_square_x - 1, n_square_y - 1);
233        let vertex_id = map.vertex_id((3 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType); // top right
234        map.force_write_vertex(
235            vertex_id,
236            origin
237                + Vector2(
238                    T::from(x_idx + 1).unwrap() * len_per_x,
239                    T::from(y_idx + 1).unwrap() * len_per_y,
240                ),
241        );
242    }
243
244    // check the number of built faces
245    // this is set as debug only because the operation cost scales with map size
246    // this can quickly overshadow the exectime of all previous code
247    debug_assert_eq!(map.iter_faces().count(), n_square_x * n_square_y);
248
249    map
250}
251
252#[allow(clippy::inline_always)]
253#[rustfmt::skip]
254#[inline(always)]
255fn generate_square_beta_values(n_x: usize, n_y: usize) -> impl Iterator<Item = [DartIdType; 3]> {
256    // this loop hierarchy yields the value in correct order
257    // left to right first, then bottom to top
258    (0..n_y).flat_map(move |iy| {
259        (0..n_x).flat_map(move |ix| {
260                let d1 = (1 + 4 * ix + n_x * 4 * iy) as DartIdType;
261                let (d2, d3, d4) = (d1 + 1, d1 + 2, d1 + 3);
262                // beta images of [d1, d2, d3, d4]
263                [
264                    [ d4, d2, if iy == 0     { 0 } else { d3 - 4 * n_x as DartIdType } ],
265                    [ d1, d3, if ix == n_x-1 { 0 } else { d2 + 6                     } ],
266                    [ d2, d4, if iy == n_y-1 { 0 } else { d1 + 4 * n_x as DartIdType } ],
267                    [ d3, d1, if ix == 0     { 0 } else { d4 - 6                     } ],
268                ]
269                .into_iter()
270            })
271        })
272}
273
274/// Internal grid-building routine
275#[allow(clippy::too_many_lines)]
276pub fn build_2d_splitgrid<T: CoordsFloat>(
277    origin: Vertex2<T>,
278    [n_square_x, n_square_y]: [usize; 2],
279    [len_per_x, len_per_y]: [T; 2],
280    manager: AttrStorageManager,
281) -> CMap2<T> {
282    let map: CMap2<T> = CMap2::new_with_undefined_attributes(6 * n_square_x * n_square_y, manager);
283
284    // init beta functions
285    (1..=(6 * n_square_x * n_square_y) as DartIdType)
286        .zip(generate_tris_beta_values(n_square_x, n_square_y))
287        .for_each(|(dart, images)| {
288            map.set_betas(dart, images);
289        });
290
291    // place vertices
292
293    // bottow left vertex of all cells
294    (0..n_square_y)
295        // flatten the loop to expose more parallelism
296        .flat_map(|y_idx| (0..n_square_x).map(move |x_idx| (y_idx, x_idx)))
297        .for_each(|(y_idx, x_idx)| {
298            let vertex_id = map.vertex_id((1 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
299            map.force_write_vertex(
300                vertex_id,
301                origin
302                    + Vector2(
303                        T::from(x_idx).unwrap() * len_per_x,
304                        T::from(y_idx).unwrap() * len_per_y,
305                    ),
306            );
307        });
308
309    // top left vertex of all top row cells
310    (0..n_square_x).for_each(|x_idx| {
311        let y_idx = n_square_y - 1;
312        let vertex_id = map.vertex_id((4 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
313        map.force_write_vertex(
314            vertex_id,
315            origin
316                + Vector2(
317                    T::from(x_idx).unwrap() * len_per_x,
318                    T::from(y_idx + 1).unwrap() * len_per_y,
319                ),
320        );
321    });
322
323    // bottom right vertex of all right col cells
324    (0..n_square_y).for_each(|y_idx| {
325        let x_idx = n_square_x - 1;
326        let vertex_id = map.vertex_id((2 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
327        map.force_write_vertex(
328            vertex_id,
329            origin
330                + Vector2(
331                    T::from(x_idx + 1).unwrap() * len_per_x,
332                    T::from(y_idx).unwrap() * len_per_y,
333                ),
334        );
335    });
336
337    // top right vertex of the last cell
338    {
339        let (x_idx, y_idx) = (n_square_x - 1, n_square_y - 1);
340        let vertex_id = map.vertex_id((6 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType); // top right
341        map.force_write_vertex(
342            vertex_id,
343            origin
344                + Vector2(
345                    T::from(x_idx + 1).unwrap() * len_per_x,
346                    T::from(y_idx + 1).unwrap() * len_per_y,
347                ),
348        );
349    }
350
351    // check the number of built faces
352    // this is set as debug only because the operation cost scales with map size
353    // this can quickly overshadow the exectime of all previous code
354    debug_assert_eq!(map.iter_faces().count(), 2 * n_square_x * n_square_y);
355
356    map
357}
358
359#[allow(clippy::inline_always)]
360#[rustfmt::skip]
361#[inline(always)]
362fn generate_tris_beta_values(n_x: usize, n_y: usize) -> impl Iterator<Item = [DartIdType; 3]> {
363    // this loop hierarchy yields the value in correct order
364    // left to right first, then bottom to top
365    (0..n_y).flat_map(move |iy| {
366        (0..n_x).flat_map(move |ix| {
367                let d1 = (1 + 6 * ix + n_x * 6 * iy) as DartIdType;
368                let (d2, d3, d4, d5, d6) = (d1 + 1, d1 + 2, d1 + 3, d1 + 4, d1 + 5);
369                // beta images of [d1, d2, d3, d4]
370                [
371                    [ d3, d2, if iy == 0     { 0 } else { d6 - 6 * n_x as DartIdType } ],
372                    [ d1, d3, d4                                                       ],
373                    [ d2, d1, if ix == 0     { 0 } else { d5 - 6                     } ],
374                    [ d6, d5, d2                                                       ],
375                    [ d4, d6, if ix == n_x-1 { 0 } else { d3 + 6                     } ],
376                    [ d5, d4, if iy == n_y-1 { 0 } else { d1 + 6 * n_x as DartIdType } ],
377                ]
378                .into_iter()
379            })
380        })
381}