h3o/coord/
faceijk.rs

1//! The H3 system centers an `IJK` coordinate system on each face of the
2//! icosahedron; the combination of a face number and `IJK` coordinates on that
3//! face's coordinate system is represented using the structure type `FaceIJK`.
4//!
5//! Each grid resolution is rotated ~19.1° relative to the next coarser
6//! resolution. The rotation alternates between counterclockwise and clockwise
7//! at each successive resolution, so that each resolution will have one of two
8//! possible orientations: Class II or Class III (using a terminology coined by
9//! R. Buckminster Fuller). The base cells, which make up resolution 0, are
10//! Class II.
11
12use super::{CoordIJK, Vec2d, SQRT3_2};
13use crate::{
14    face::{self, FaceOrientIJK},
15    index::bits,
16    BaseCell, Boundary, CellIndex, Direction, ExtendedResolution, Face, LatLng,
17    Resolution, Vertex, CCW, CW, DEFAULT_CELL_INDEX, NUM_HEX_VERTS,
18    NUM_ICOSA_FACES, NUM_PENT_VERTS,
19};
20
21static ZERO: CoordIJK = CoordIJK::new(0, 0, 0);
22
23// -----------------------------------------------------------------------------
24
25/// Face number and `IJK` coordinates on that face-centered coordinate system.
26#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
27pub struct FaceIJK {
28    /// Face number.
29    pub face: Face,
30    /// `ijk` coordinates on that face.
31    pub coord: CoordIJK,
32}
33
34impl FaceIJK {
35    pub const fn new(face: Face, coord: CoordIJK) -> Self {
36        Self { face, coord }
37    }
38
39    /// Returns the number of 60° counterclockwise rotations to rotate into the
40    /// coordinate system of the base cell at that coordinates.
41    #[allow(
42        clippy::cast_sign_loss,
43        reason = "safe because components values are in [0; 2]"
44    )]
45    pub fn base_cell_rotation(&self) -> Rotation {
46        // Should always be the case, or we have a nasty bug to fix.
47        debug_assert!(
48            (0..=2).contains(&self.coord.i())
49                && (0..=2).contains(&self.coord.j())
50                && (0..=2).contains(&self.coord.k())
51        );
52
53        let f = usize::from(self.face);
54        let i = self.coord.i() as usize;
55        let j = self.coord.j() as usize;
56        let k = self.coord.k() as usize;
57
58        FACE_IJK_BASE_CELLS[f][i][j][k]
59    }
60
61    /// Converts a `FaceIJK` address to the corresponding [`CellIndex`].
62    pub(crate) fn to_cell(mut self, resolution: Resolution) -> CellIndex {
63        // Initialize the index.
64        let mut bits = bits::set_resolution(DEFAULT_CELL_INDEX, resolution);
65
66        // Handle resolution 0 (base cell).
67        if resolution == Resolution::Zero {
68            let rotation = self.base_cell_rotation();
69            return CellIndex::new_unchecked(h3o_bit::set_base_cell(
70                bits,
71                rotation.base_cell.into(),
72            ));
73        }
74
75        // We need to find the correct base cell FaceIJK for this H3 index
76
77        // Build the index from finest resolution up.
78        self.coord =
79            directions_bits_from_ijk(self.coord, &mut bits, resolution);
80
81        // Lookup the correct base cell.
82        let rotation = self.base_cell_rotation();
83        bits = h3o_bit::set_base_cell(bits, rotation.base_cell.into());
84
85        // Rotate if necessary to get canonical base cell orientation
86        // for this base cell
87        if rotation.base_cell.is_pentagon() {
88            // Force rotation out of missing k-axes sub-sequence
89            if bits::first_axe(bits) == Direction::K.axe() {
90                // Check for a CW/CCW offset face (default is CCW).
91                if rotation.base_cell.is_cw_offset(self.face) {
92                    bits = bits::rotate60::<{ CW }>(bits, 1);
93                } else {
94                    bits = bits::rotate60::<{ CCW }>(bits, 1);
95                }
96            }
97
98            for _ in 0..rotation.count {
99                bits = bits::pentagon_rotate60::<{ CCW }>(bits);
100            }
101        } else {
102            bits = bits::rotate60::<{ CCW }>(bits, rotation.count.into());
103        }
104
105        CellIndex::new_unchecked(bits)
106    }
107
108    /// Determines the center point in spherical coordinates of a cell given by
109    /// a `FaceIJK` address at a specified resolution.
110    ///
111    /// # Arguments
112    ///
113    /// * `fijk` - The `FaceIJK` address of the cell.
114    /// * `resolution` - The H3 resolution of the cell.
115    pub fn to_latlng(self, resolution: Resolution) -> LatLng {
116        Vec2d::from(self.coord).to_latlng(self.face, resolution.into(), false)
117    }
118
119    /// Returns the `FaceIJK` address that correspond to a given cell index.
120    ///
121    /// # Arguments
122    ///
123    /// * `bits` - Raw bits of the cell index.
124    /// * `resolution` - Resolution of the cell index.
125    /// * `base_cell` - Base cell of the cell index.
126    pub fn from_bits(
127        bits: u64,
128        resolution: Resolution,
129        base_cell: BaseCell,
130    ) -> (Self, bool) {
131        let mut fijk = Self::from(base_cell);
132        let possible_overage = base_cell.is_pentagon()
133            || (resolution != Resolution::Zero || fijk.coord != ZERO);
134
135        for res in Resolution::range(Resolution::One, resolution) {
136            if res.is_class3() {
137                // Class III == rotate CCW.
138                fijk.coord = fijk.coord.down_aperture7::<{ CCW }>();
139            } else {
140                // Class II == rotate CW.
141                fijk.coord = fijk.coord.down_aperture7::<{ CW }>();
142            }
143
144            // SAFETY: loop upper bound is the index resolution.
145            let direction =
146                Direction::new_unchecked(bits::get_direction(bits, res));
147            fijk.coord = fijk.coord.neighbor(direction);
148        }
149
150        (fijk, possible_overage)
151    }
152
153    /// Adjusts a `FaceIJK` address in place so that the resulting cell address
154    /// is relative to the correct icosahedral face.
155    ///
156    /// We usually assume that the cell center point falls on the same
157    /// icosahedron face as its base cell, but it is possible that the cell
158    /// center point lies on an adjacent face (that's what we call an overage in
159    /// the code), in which case we need to use a projection centered on that
160    /// adjacent face instead.
161    ///
162    /// # Arguments
163    ///
164    /// * `resolution` - The H3 resolution of the cell.
165    /// * `is_pent4` - Whether or not the cell is a pentagon with a leading
166    ///                digit 4.
167    /// * `in_substrate` - Whether or not the cell is in a substrate grid.
168    pub fn adjust_overage_class2<const IS_SUBSTRATE: bool>(
169        &mut self,
170        class2_res: ExtendedResolution,
171        is_pent4: bool,
172    ) -> Overage {
173        let class2_res = usize::from(class2_res);
174        let factor = if IS_SUBSTRATE { 3 } else { 1 };
175        let face = usize::from(self.face);
176        let dimension = self.coord.i() + self.coord.j() + self.coord.k();
177
178        // Get the maximum dimension value; scale if a substrate grid.
179        let max_dim = MAX_DIM_BY_CII_RES[class2_res] * factor;
180
181        // Check for overage.
182        if IS_SUBSTRATE && dimension == max_dim {
183            return Overage::FaceEdge;
184        }
185
186        if dimension > max_dim {
187            let orientation = if self.coord.k() > 0 {
188                if self.coord.j() > 0 {
189                    face::NEIGHBORS[face][face::JK]
190                } else {
191                    // Adjust for the pentagonal missing sequence.
192                    if is_pent4 {
193                        // Translate origin to center of pentagon and rotate to
194                        // adjust for the missing sequence.
195                        let origin = CoordIJK::new(max_dim, 0, 0);
196                        let tmp = (self.coord - origin).rotate60::<{ CW }>();
197                        // Translate the origin back to the center of the triangle
198                        self.coord = tmp + origin;
199                    }
200                    face::NEIGHBORS[face][face::KI]
201                }
202            } else {
203                face::NEIGHBORS[face][face::IJ]
204            };
205            self.face = orientation.face;
206
207            // Rotate and translate for adjacent face.
208            for _ in 0..orientation.ccw_rot60 {
209                self.coord = self.coord.rotate60::<{ CCW }>();
210            }
211
212            let mut trans_vec = orientation.translate;
213            let unit_scale = UNIT_SCALE_BY_CII_RES[class2_res] * factor;
214            trans_vec *= unit_scale;
215            self.coord = (self.coord + trans_vec).normalize();
216
217            // Overage points on pentagon boundaries can end up on edges.
218            if IS_SUBSTRATE
219                && self.coord.i() + self.coord.j() + self.coord.k() == max_dim
220            {
221                return Overage::FaceEdge;
222            }
223            return Overage::NewFace;
224        }
225
226        Overage::None
227    }
228
229    /// Adjusts a `FaceIJK` address for a pentagon vertex in a substrate grid so
230    /// that the resulting cell address is relative to the correct icosahedral
231    /// face.
232    ///
233    /// # Arguments
234    ///
235    /// * `resolution` - The H3 resolution of the cell.
236    pub fn adjust_pentagon_vertex_overage(
237        &mut self,
238        resolution: ExtendedResolution,
239    ) {
240        while self.adjust_overage_class2::<true>(resolution, false)
241            == Overage::NewFace
242        {}
243    }
244
245    /// Generates the cell boundary in spherical coordinates for a pentagonal
246    /// cell given by a `FaceIJK` address at a specified resolution.
247    ///
248    /// # Arguments
249    ///
250    /// * `resolution` - The H3 resolution of the cell.
251    /// * `start` - The first topological vertex to return.
252    /// * `length` - The number of topological vertexes to return.
253    pub fn pentagon_boundary(
254        &self,
255        resolution: Resolution,
256        start: Vertex,
257        length: u8,
258    ) -> Boundary {
259        let mut boundary = Boundary::new();
260        let start = u8::from(start);
261        let mut center = *self;
262        let mut vertices = [Self::default(); NUM_PENT_VERTS as usize];
263        let adjusted_resolution = center.vertices(resolution, &mut vertices);
264
265        // If we're returning the entire loop, we need one more iteration in case
266        // of a distortion vertex on the last edge.
267        let additional_iteration = u8::from(length == NUM_PENT_VERTS);
268
269        // Convert each vertex to lat/lng.
270        // Adjust the face of each vertex as appropriate and introduce
271        // edge-crossing vertices as needed.
272        let mut last_fijk = Self::default();
273        for vert in start..(start + length + additional_iteration) {
274            let mut fijk: Self = vertices[usize::from(vert % NUM_PENT_VERTS)];
275            fijk.adjust_pentagon_vertex_overage(adjusted_resolution);
276
277            // All Class III pentagon edges cross icosahedron edges.
278            //
279            // Note that Class II pentagons have vertices on the edge,
280            // not edge intersections.
281            if resolution.is_class3() && vert > start {
282                // Find hex2d of the two vertexes on the last face.
283                let mut tmp_fijk = fijk;
284
285                let orig2d0 = Vec2d::from(last_fijk.coord);
286                let current_to_last_dir: u8 =
287                    get_adjacent_face_dir(tmp_fijk.face, last_fijk.face);
288
289                let fijk_orientation: &FaceOrientIJK = &face::NEIGHBORS
290                    [usize::from(tmp_fijk.face)]
291                    [usize::from(current_to_last_dir)];
292
293                tmp_fijk.face = fijk_orientation.face;
294
295                // Rotate and translate for adjacent face.
296                for _ in 0..fijk_orientation.ccw_rot60 {
297                    tmp_fijk.coord = tmp_fijk.coord.rotate60::<{ CCW }>();
298                }
299                let mut trans_vec = fijk_orientation.translate;
300                trans_vec *=
301                    UNIT_SCALE_BY_CII_RES[usize::from(adjusted_resolution)] * 3;
302                tmp_fijk.coord = (tmp_fijk.coord + trans_vec).normalize();
303
304                let orig2d1 = Vec2d::from(tmp_fijk.coord);
305
306                // Find the appropriate icosahedron face edge vertexes.
307                let max_dim = f64::from(
308                    MAX_DIM_BY_CII_RES[usize::from(adjusted_resolution)],
309                );
310                let v0 = Vec2d::new(3.0 * max_dim, 0.0);
311                let v1 = Vec2d::new(-1.5 * max_dim, 3.0 * SQRT3_2 * max_dim);
312                let v2 = Vec2d::new(-1.5 * max_dim, -3.0 * SQRT3_2 * max_dim);
313
314                let (edge0, edge1) = match usize::from(get_adjacent_face_dir(
315                    tmp_fijk.face,
316                    fijk.face,
317                )) {
318                    face::IJ => (v0, v1),
319                    face::JK => (v1, v2),
320                    face::KI => (v2, v0),
321                    _ => unreachable!("invalid face direction"),
322                };
323
324                // Find the intersection and add the lat/lng point to the
325                // result.
326                let intersection =
327                    Vec2d::intersection((orig2d0, orig2d1), (edge0, edge1));
328                boundary.push(intersection.to_latlng(
329                    tmp_fijk.face,
330                    adjusted_resolution,
331                    true,
332                ));
333            }
334
335            // convert vertex to lat/lng and add to the result
336            // vert == start + NUM_PENT_VERTS is only used to test for possible
337            // intersection on last edge
338            if vert < start + NUM_PENT_VERTS {
339                boundary.push(Vec2d::from(fijk.coord).to_latlng(
340                    fijk.face,
341                    adjusted_resolution,
342                    true,
343                ));
344            }
345
346            last_fijk = fijk;
347        }
348
349        boundary
350    }
351
352    /// Generates the cell boundary in spherical coordinates for a cell given by
353    /// a `FaceIJK` address at a specified resolution.
354    ///
355    /// # Arguments
356    ///
357    /// * `resolution` - The H3 resolution of the cell.
358    /// * `start` - The first topological vertex to return.
359    /// * `length` - The number of topological vertexes to return.
360    pub fn hexagon_boundary(
361        &self,
362        resolution: Resolution,
363        start: Vertex,
364        length: u8,
365    ) -> Boundary {
366        let mut boundary = Boundary::new();
367        let start = u8::from(start);
368        let mut center = *self;
369        let mut vertices = [Self::default(); NUM_HEX_VERTS as usize];
370        let adjusted_resolution = center.vertices(resolution, &mut vertices);
371
372        // If we're returning the entire loop, we need one more iteration in
373        // case of a distortion vertex on the last edge.
374        let additional_iteration = u8::from(length == NUM_HEX_VERTS);
375
376        // Convert each vertex to lat/lng.
377        // Adjust the face of each vertex as appropriate and introduce
378        // edge-crossing vertices as needed.
379
380        let mut last_face = usize::MAX;
381        let mut last_overage = Overage::None;
382        for vert in start..(start + length + additional_iteration) {
383            let v = usize::from(vert % NUM_HEX_VERTS);
384            let mut fijk = vertices[v];
385            let overage =
386                fijk.adjust_overage_class2::<true>(adjusted_resolution, false);
387
388            // Check for edge-crossing.
389            //
390            // Each face of the underlying icosahedron is a different projection
391            // plane. So if an edge of the hexagon crosses an icosahedron edge,
392            // an additional vertex must be introduced at that intersection
393            // point. Then each half of the cell edge can be projected to
394            // geographic coordinates using the appropriate icosahedron face
395            // projection.
396            // Note that Class II cell edges have vertices on the face edge,
397            // with no edge line intersections.
398            if resolution.is_class3()
399                && vert > start
400                && usize::from(fijk.face) != last_face
401                && last_overage != Overage::FaceEdge
402            {
403                // Find hex2d of the two vertexes on original face.
404                let last_v: usize = (v + 5) % usize::from(NUM_HEX_VERTS);
405                let orig2d0 = Vec2d::from(vertices[last_v].coord);
406                let orig2d1 = Vec2d::from(vertices[v].coord);
407
408                // Find the appropriate icosahedron face edge vertexes.
409                let max_dim = f64::from(
410                    MAX_DIM_BY_CII_RES[usize::from(adjusted_resolution)],
411                );
412                let v0 = Vec2d::new(3.0 * max_dim, 0.0);
413                let v1 = Vec2d::new(-1.5 * max_dim, 3.0 * SQRT3_2 * max_dim);
414                let v2 = Vec2d::new(-1.5 * max_dim, -3.0 * SQRT3_2 * max_dim);
415
416                let face2 = if last_face == usize::from(center.face) {
417                    fijk.face
418                } else {
419                    Face::new_unchecked(last_face)
420                };
421                let (edge0, edge1) = match usize::from(get_adjacent_face_dir(
422                    center.face,
423                    face2,
424                )) {
425                    face::IJ => (v0, v1),
426                    face::JK => (v1, v2),
427                    face::KI => (v2, v0),
428                    _ => unreachable!("invalid adjacent face direction"),
429                };
430
431                // Find the intersection and add the lat/lng point to the result
432                let intersection =
433                    Vec2d::intersection((orig2d0, orig2d1), (edge0, edge1));
434                // If a point of intersection occurs at a hexagon vertex, then
435                // each adjacent hexagon edge will lie completely on a single
436                // icosahedron face, and no additional vertex is required.
437                let is_intersection_at_vertex =
438                    orig2d0 == intersection || orig2d1 == intersection;
439                if !is_intersection_at_vertex {
440                    boundary.push(intersection.to_latlng(
441                        center.face,
442                        adjusted_resolution,
443                        true,
444                    ));
445                }
446            }
447
448            // Convert vertex to lat/lng and add to the result.
449            //
450            // `vert == start + NUM_HEX_VERTS` is only used to test for possible
451            // intersection on last edge.
452            if vert < start + NUM_HEX_VERTS {
453                boundary.push(Vec2d::from(fijk.coord).to_latlng(
454                    fijk.face,
455                    adjusted_resolution,
456                    true,
457                ));
458            }
459
460            last_face = fijk.face.into();
461            last_overage = overage;
462        }
463
464        boundary
465    }
466
467    /// Returns the vertices of a cell as substrate `FaceIJK` addresses.
468    ///
469    /// # Arguments
470    ///
471    /// * `resolution` - The H3 resolution of the cell. This may be adjusted if
472    ///                  necessary for the substrate grid resolution.
473    /// * `vertices` - output array for the vertices.
474    pub fn vertices(
475        &mut self,
476        resolution: Resolution,
477        vertices: &mut [Self],
478    ) -> ExtendedResolution {
479        // The vertexes of an origin-centered cell in a Class II resolution on a
480        // substrate grid with aperture sequence 33r. The aperture 3 gets us the
481        // vertices, and the 3r gets us back to Class II.
482        //
483        // Vertices listed CCW from the i-axes.
484        const VERTS_CII: [CoordIJK; 6] = [
485            CoordIJK::new(2, 1, 0),
486            CoordIJK::new(1, 2, 0),
487            CoordIJK::new(0, 2, 1),
488            CoordIJK::new(0, 1, 2),
489            CoordIJK::new(1, 0, 2),
490            CoordIJK::new(2, 0, 1),
491        ];
492
493        // The vertexes of an origin-centered cell in a Class III resolution on
494        // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us
495        // the vertices, and the 3r7r gets us to Class II.
496        //
497        // Vertices listed CCW from the i-axes.
498        const VERTS_CIII: [CoordIJK; 6] = [
499            CoordIJK::new(5, 4, 0),
500            CoordIJK::new(1, 5, 0),
501            CoordIJK::new(0, 5, 4),
502            CoordIJK::new(0, 1, 5),
503            CoordIJK::new(4, 0, 5),
504            CoordIJK::new(5, 0, 1),
505        ];
506
507        // Adjust the center point to be in an aperture 33r substrate grid.
508        self.coord = self.coord.down_aperture3::<{ CCW }>();
509        self.coord = self.coord.down_aperture3::<{ CW }>();
510
511        // If res is Class III we need to add a CW aperture 7 to get to
512        // icosahedral Class II.
513        let (verts, adjusted_resolution) = if resolution.is_class3() {
514            self.coord = self.coord.down_aperture7::<{ CW }>();
515            (&VERTS_CIII, ExtendedResolution::down(resolution))
516        } else {
517            (&VERTS_CII, resolution.into())
518        };
519
520        // The center point is now in the same substrate grid as the origin
521        // cell vertices.
522        // Add the center point substrate coordinates to each vertex to translate
523        // the vertices to that cell.
524        for (i, vertex) in vertices.iter_mut().enumerate() {
525            vertex.face = self.face;
526            vertex.coord = (self.coord + verts[i]).normalize();
527        }
528
529        adjusted_resolution
530    }
531}
532
533/// Set the directions of a cell index (in-place) from finest resolution up.
534///
535/// IJK coordinates are adjusted during the traversal so that, at the end, they
536/// should match the IJK of the base cell in the coordinate system of the
537/// current base cell.
538///
539/// Returns the adjusted `IJK` coordinates.
540#[allow(
541    clippy::inline_always,
542    reason = "4-5% boost, up to 13% at resolution 1."
543)]
544#[inline(always)]
545fn directions_bits_from_ijk(
546    mut ijk: CoordIJK,
547    bits: &mut u64,
548    resolution: Resolution,
549) -> CoordIJK {
550    for res in Resolution::range(Resolution::One, resolution).rev() {
551        let last_ijk = ijk;
552        let last_center = if res.is_class3() {
553            // Rotate CCW.
554            ijk = ijk.up_aperture7::<{ CCW }>();
555            ijk.down_aperture7::<{ CCW }>()
556        } else {
557            // Rotate CW.
558            ijk = ijk.up_aperture7::<{ CW }>();
559            ijk.down_aperture7::<{ CW }>()
560        };
561
562        let diff = (last_ijk - last_center).normalize();
563        let direction = Direction::try_from(diff).expect("unit IJK coordinate");
564        // SAFETY: `res` is in [resolution; 1], thus valid.
565        *bits = bits::set_direction(*bits, direction.into(), res);
566    }
567
568    ijk
569}
570
571// -----------------------------------------------------------------------------
572
573/// Overage type.
574#[derive(Debug, Clone, Copy, Eq, PartialEq)]
575pub enum Overage {
576    /// No overage (on original face).
577    None,
578    /// On face edge (only occurs on substrate grids).
579    FaceEdge,
580    /// Overage on new face interior.
581    NewFace,
582}
583
584// -----------------------------------------------------------------------------
585
586fn get_adjacent_face_dir(i: Face, j: Face) -> u8 {
587    ((ADJACENT_FACE_DIR[usize::from(i)] >> (usize::from(j) * 3)) & 0b111) as u8
588}
589
590// To reduce the footprints of the lookup table, we use a bitset where the
591// direction is encoded on 3-bit:
592// - `000`: central face.
593// - `001`: IJ quadrant.
594// - `010`: KI quadrant.
595// - `011`: JK quadrant.
596// - `111`: invalid face.
597macro_rules! adjacent_face_dir {
598    (central = $central:literal, IJ = $ij:literal, KI = $ki:literal, JK = $jk:literal) => {
599        !(0 | (0b111 << (3 * $central))
600            | (0b110 << (3 * $ij))
601            | (0b101 << (3 * $ki))
602            | (0b100 << (3 * $jk)))
603    };
604}
605
606/// Direction from the origin face to the destination face, relative to the
607/// origin face's coordinate system.
608#[rustfmt::skip]
609static ADJACENT_FACE_DIR: [u64; NUM_ICOSA_FACES] = [
610    adjacent_face_dir!(central = 0,  IJ = 4,  KI = 1,  JK = 5), // Face  0.
611    adjacent_face_dir!(central = 1,  IJ = 0,  KI = 2,  JK = 6), // Face  1.
612    adjacent_face_dir!(central = 2,  IJ = 1,  KI = 3,  JK = 7), // Face  2.
613    adjacent_face_dir!(central = 3,  IJ = 2,  KI = 4,  JK = 8), // Face  3.
614    adjacent_face_dir!(central = 4,  IJ = 3,  KI = 0,  JK = 9), // Face  4.
615    adjacent_face_dir!(central = 5,  IJ = 10, KI = 14, JK = 0), // Face  5.
616    adjacent_face_dir!(central = 6,  IJ = 11, KI = 10, JK = 1), // Face  6.
617    adjacent_face_dir!(central = 7,  IJ = 12, KI = 11, JK = 2), // Face  7.
618    adjacent_face_dir!(central = 8,  IJ = 13, KI = 12, JK = 3), // Face  8.
619    adjacent_face_dir!(central = 9,  IJ = 14, KI = 13, JK = 4), // Face  9.
620    adjacent_face_dir!(central = 10, IJ = 5,  KI = 6,  JK = 15), // Face 10.
621    adjacent_face_dir!(central = 11, IJ = 6,  KI = 7,  JK = 16), // Face 11.
622    adjacent_face_dir!(central = 12, IJ = 7,  KI = 8,  JK = 17), // Face 12.
623    adjacent_face_dir!(central = 13, IJ = 8,  KI = 9,  JK = 18), // Face 13.
624    adjacent_face_dir!(central = 14, IJ = 9,  KI = 5,  JK = 19), // Face 14.
625    adjacent_face_dir!(central = 15, IJ = 16, KI = 19, JK = 10), // Face 15.
626    adjacent_face_dir!(central = 16, IJ = 17, KI = 15, JK = 11), // Face 16.
627    adjacent_face_dir!(central = 17, IJ = 18, KI = 16, JK = 12), // Face 17.
628    adjacent_face_dir!(central = 18, IJ = 19, KI = 17, JK = 13), // Face 18.
629    adjacent_face_dir!(central = 19, IJ = 15, KI = 18, JK = 14), // Face 19.
630];
631
632// -----------------------------------------------------------------------------
633
634/// Overage distance table.
635#[rustfmt::skip]
636static MAX_DIM_BY_CII_RES: &[i32] = &[
637     2,        // Resolution  0.
638    -1,        // Resolution  1.
639     14,       // Resolution  2.
640    -1,        // Resolution  3.
641     98,       // Resolution  4.
642    -1,        // Resolution  5.
643     686,      // Resolution  6.
644    -1,        // Resolution  7.
645     4802,     // Resolution  8.
646    -1,        // Resolution  9.
647     33614,    // Resolution 10.
648    -1,        // Resolution 11.
649     235298,   // Resolution 12.
650    -1,        // Resolution 13.
651     1647086,  // Resolution 14.
652    -1,        // Resolution 15.
653     11529602, // Resolution 16.
654];
655
656// -----------------------------------------------------------------------------
657
658/// Unit scale distance table.
659#[rustfmt::skip]
660static UNIT_SCALE_BY_CII_RES: &[i32] = &[
661     1,       // Resolution  0.
662    -1,       // Resolution  1.
663     7,       // Resolution  2.
664    -1,       // Resolution  3.
665     49,      // Resolution  4.
666    -1,       // Resolution  5.
667     343,     // Resolution  6.
668    -1,       // Resolution  7.
669     2401,    // Resolution  8.
670    -1,       // Resolution  9.
671     16807,   // Resolution 10.
672    -1,       // Resolution 11.
673     117649,  // Resolution 12.
674    -1,       // Resolution 13.
675     823543,  // Resolution 14.
676    -1,       // Resolution 15.
677     5764801, // Resolution 16.
678];
679
680// -----------------------------------------------------------------------------
681
682/// Base cell and its associated number of 60° CCW rotations.
683#[derive(Clone, Copy)]
684pub struct Rotation {
685    /// Base cell.
686    pub base_cell: BaseCell,
687    /// Number of 60° CCW rotations.
688    pub count: u8,
689}
690
691// Macro to save typing when declaring base cell rotations.
692macro_rules! bcr {
693    ($base_cell:literal, $rotation:literal) => {
694        Rotation {
695            base_cell: BaseCell::new_unchecked($base_cell),
696            count: $rotation,
697        }
698    };
699}
700
701/// Resolution 0 base cell lookup table for each face.
702#[rustfmt::skip]
703const FACE_IJK_BASE_CELLS: [[[[Rotation; 3]; 3]; 3]; NUM_ICOSA_FACES] = [
704    [
705        [
706            [bcr!(16, 0), bcr!(18, 0), bcr!(24, 0)],
707            [bcr!(33, 0), bcr!(30, 0), bcr!(32, 3)],
708            [bcr!(49, 1), bcr!(48, 3), bcr!(50, 3)],
709        ], [
710            [bcr!(8,  0), bcr!(5,  5), bcr!(10, 5)],
711            [bcr!(22, 0), bcr!(16, 0), bcr!(18, 0)],
712            [bcr!(41, 1), bcr!(33, 0), bcr!(30, 0)],
713        ], [
714            [bcr!(4,  0), bcr!(0,  5), bcr!(2,  5)],
715            [bcr!(15, 1), bcr!(8,  0), bcr!(5,  5)],
716            [bcr!(31, 1), bcr!(22, 0), bcr!(16, 0)],
717        ],
718    ], [
719        [
720            [bcr!(2,  0), bcr!(6,  0), bcr!(14, 0)],
721            [bcr!(10, 0), bcr!(11, 0), bcr!(17, 3)],
722            [bcr!(24, 1), bcr!(23, 3), bcr!(25, 3)],
723        ], [
724            [bcr!(0,  0), bcr!(1,  5), bcr!(9,  5)],
725            [bcr!(5,  0), bcr!(2,  0), bcr!(6,  0)],
726            [bcr!(18, 1), bcr!(10, 0), bcr!(11, 0)],
727        ], [
728            [bcr!(4,  1), bcr!(3, 5), bcr!(7, 5)],
729            [bcr!(8,  1), bcr!(0, 0), bcr!(1, 5)],
730            [bcr!(16, 1), bcr!(5, 0), bcr!(2, 0)],
731        ],
732    ], [
733        [
734            [bcr!(7,  0), bcr!(21, 0), bcr!(38, 0)],
735            [bcr!(9,  0), bcr!(19, 0), bcr!(34, 3)],
736            [bcr!(14, 1), bcr!(20, 3), bcr!(36, 3)],
737        ], [
738            [bcr!(3, 0), bcr!(13, 5), bcr!(29, 5)],
739            [bcr!(1, 0), bcr!(7,  0), bcr!(21, 0)],
740            [bcr!(6, 1), bcr!(9,  0), bcr!(19, 0)],
741        ], [
742            [bcr!(4, 2), bcr!(12, 5), bcr!(26, 5)],
743            [bcr!(0, 1), bcr!(3,  0), bcr!(13, 5)],
744            [bcr!(2, 1), bcr!(1,  0), bcr!(7,  0)],
745        ]
746    ], [
747        [
748            [bcr!(26, 0), bcr!(42, 0), bcr!(58, 0)],
749            [bcr!(29, 0), bcr!(43, 0), bcr!(62, 3)],
750            [bcr!(38, 1), bcr!(47, 3), bcr!(64, 3)],
751        ], [
752            [bcr!(12, 0), bcr!(28, 5), bcr!(44, 5)],
753            [bcr!(13, 0), bcr!(26, 0), bcr!(42, 0)],
754            [bcr!(21, 1), bcr!(29, 0), bcr!(43, 0)],
755        ], [
756            [bcr!(4, 3), bcr!(15, 5), bcr!(31, 5)],
757            [bcr!(3, 1), bcr!(12, 0), bcr!(28, 5)],
758            [bcr!(7, 1), bcr!(13, 0), bcr!(26, 0)],
759        ]
760    ], [
761        [
762            [bcr!(31, 0), bcr!(41, 0), bcr!(49, 0)],
763            [bcr!(44, 0), bcr!(53, 0), bcr!(61, 3)],
764            [bcr!(58, 1), bcr!(65, 3), bcr!(75, 3)],
765        ], [
766            [bcr!(15, 0), bcr!(22, 5), bcr!(33, 5)],
767            [bcr!(28, 0), bcr!(31, 0), bcr!(41, 0)],
768            [bcr!(42, 1), bcr!(44, 0), bcr!(53, 0)],
769        ], [
770            [bcr!(4,  4), bcr!(8,  5), bcr!(16, 5)],
771            [bcr!(12, 1), bcr!(15, 0), bcr!(22, 5)],
772            [bcr!(26, 1), bcr!(28, 0), bcr!(31, 0)],
773        ]
774    ], [
775        [
776            [bcr!(50, 0), bcr!(48, 0), bcr!(49, 3)],
777            [bcr!(32, 0), bcr!(30, 3), bcr!(33, 3)],
778            [bcr!(24, 3), bcr!(18, 3), bcr!(16, 3)],
779        ], [
780            [bcr!(70, 0), bcr!(67, 0), bcr!(66, 3)],
781            [bcr!(52, 3), bcr!(50, 0), bcr!(48, 0)],
782            [bcr!(37, 3), bcr!(32, 0), bcr!(30, 3)],
783        ], [
784            [bcr!(83, 0), bcr!(87, 3), bcr!(85, 3)],
785            [bcr!(74, 3), bcr!(70, 0), bcr!(67, 0)],
786            [bcr!(57, 1), bcr!(52, 3), bcr!(50, 0)],
787        ]
788    ], [
789        [
790            [bcr!(25, 0), bcr!(23, 0), bcr!(24, 3)],
791            [bcr!(17, 0), bcr!(11, 3), bcr!(10, 3)],
792            [bcr!(14, 3), bcr!(6,  3), bcr!(2,  3)],
793        ], [
794            [bcr!(45, 0), bcr!(39, 0), bcr!(37, 3)],
795            [bcr!(35, 3), bcr!(25, 0), bcr!(23, 0)],
796            [bcr!(27, 3), bcr!(17, 0), bcr!(11, 3)],
797        ], [
798            [bcr!(63, 0), bcr!(59, 3), bcr!(57, 3)],
799            [bcr!(56, 3), bcr!(45, 0), bcr!(39, 0)],
800            [bcr!(46, 3), bcr!(35, 3), bcr!(25, 0)],
801        ]
802    ], [
803        [
804            [bcr!(36, 0), bcr!(20, 0), bcr!(14, 3)],
805            [bcr!(34, 0), bcr!(19, 3), bcr!(9,  3)],
806            [bcr!(38, 3), bcr!(21, 3), bcr!(7,  3)],
807        ], [
808            [bcr!(55, 0), bcr!(40, 0), bcr!(27, 3)],
809            [bcr!(54, 3), bcr!(36, 0), bcr!(20, 0)],
810            [bcr!(51, 3), bcr!(34, 0), bcr!(19, 3)],
811        ], [
812            [bcr!(72, 0), bcr!(60, 3), bcr!(46, 3)],
813            [bcr!(73, 3), bcr!(55, 0), bcr!(40, 0)],
814            [bcr!(71, 3), bcr!(54, 3), bcr!(36, 0)],
815        ]
816    ], [
817        [
818            [bcr!(64, 0), bcr!(47, 0), bcr!(38, 3)],
819            [bcr!(62, 0), bcr!(43, 3), bcr!(29, 3)],
820            [bcr!(58, 3), bcr!(42, 3), bcr!(26, 3)],
821        ], [
822            [bcr!(84, 0), bcr!(69, 0), bcr!(51, 3)],
823            [bcr!(82, 3), bcr!(64, 0), bcr!(47, 0)],
824            [bcr!(76, 3), bcr!(62, 0), bcr!(43, 3)],
825        ], [
826            [bcr!(97, 0), bcr!(89, 3), bcr!(71, 3)],
827            [bcr!(98, 3), bcr!(84, 0), bcr!(69, 0)],
828            [bcr!(96, 3), bcr!(82, 3), bcr!(64, 0)],
829        ]
830    ], [
831        [
832            [bcr!(75, 0), bcr!(65, 0), bcr!(58, 3)],
833            [bcr!(61, 0), bcr!(53, 3), bcr!(44, 3)],
834            [bcr!(49, 3), bcr!(41, 3), bcr!(31, 3)],
835        ], [
836            [bcr!(94, 0), bcr!(86, 0), bcr!(76, 3)],
837            [bcr!(81, 3), bcr!(75, 0), bcr!(65, 0)],
838            [bcr!(66, 3), bcr!(61, 0), bcr!(53, 3)],
839        ], [
840            [bcr!(107, 0), bcr!(104, 3), bcr!(96, 3)],
841            [bcr!(101, 3), bcr!(94,  0), bcr!(86, 0)],
842            [bcr!(85,  3), bcr!(81,  3), bcr!(75, 0)],
843        ]
844    ], [
845        [
846            [bcr!(57, 0), bcr!(59, 0), bcr!(63, 3)],
847            [bcr!(74, 0), bcr!(78, 3), bcr!(79, 3)],
848            [bcr!(83, 3), bcr!(92, 3), bcr!(95, 3)],
849        ], [
850            [bcr!(37, 0), bcr!(39, 3), bcr!(45, 3)],
851            [bcr!(52, 0), bcr!(57, 0), bcr!(59, 0)],
852            [bcr!(70, 3), bcr!(74, 0), bcr!(78, 3)],
853        ], [
854            [bcr!(24, 0), bcr!(23, 3), bcr!(25, 3)],
855            [bcr!(32, 3), bcr!(37, 0), bcr!(39, 3)],
856            [bcr!(50, 3), bcr!(52, 0), bcr!(57, 0)],
857        ]
858    ], [
859        [
860            [bcr!(46, 0), bcr!(60, 0), bcr!(72, 3)],
861            [bcr!(56, 0), bcr!(68, 3), bcr!(80, 3)],
862            [bcr!(63, 3), bcr!(77, 3), bcr!(90, 3)],
863        ], [
864            [bcr!(27, 0), bcr!(40, 3), bcr!(55, 3)],
865            [bcr!(35, 0), bcr!(46, 0), bcr!(60, 0)],
866            [bcr!(45, 3), bcr!(56, 0), bcr!(68, 3)],
867        ], [
868            [bcr!(14, 0), bcr!(20, 3), bcr!(36, 3)],
869            [bcr!(17, 3), bcr!(27, 0), bcr!(40, 3)],
870            [bcr!(25, 3), bcr!(35, 0), bcr!(46, 0)],
871        ]
872    ], [
873        [
874            [bcr!(71, 0), bcr!(89, 0), bcr!(97,  3)],
875            [bcr!(73, 0), bcr!(91, 3), bcr!(103, 3)],
876            [bcr!(72, 3), bcr!(88, 3), bcr!(105, 3)],
877        ], [
878            [bcr!(51, 0), bcr!(69, 3), bcr!(84, 3)],
879            [bcr!(54, 0), bcr!(71, 0), bcr!(89, 0)],
880            [bcr!(55, 3), bcr!(73, 0), bcr!(91, 3)],
881        ], [
882            [bcr!(38, 0), bcr!(47, 3), bcr!(64, 3)],
883            [bcr!(34, 3), bcr!(51, 0), bcr!(69, 3)],
884            [bcr!(36, 3), bcr!(54, 0), bcr!(71, 0)],
885        ]
886    ], [
887        [
888            [bcr!(96, 0), bcr!(104, 0), bcr!(107, 3)],
889            [bcr!(98, 0), bcr!(110, 3), bcr!(115, 3)],
890            [bcr!(97, 3), bcr!(111, 3), bcr!(119, 3)],
891        ], [
892            [bcr!(76, 0), bcr!(86, 3), bcr!(94,  3)],
893            [bcr!(82, 0), bcr!(96, 0), bcr!(104, 0)],
894            [bcr!(84, 3), bcr!(98, 0), bcr!(110, 3)],
895        ], [
896            [bcr!(58, 0), bcr!(65, 3), bcr!(75, 3)],
897            [bcr!(62, 3), bcr!(76, 0), bcr!(86, 3)],
898            [bcr!(64, 3), bcr!(82, 0), bcr!(96, 0)],
899        ]
900    ], [
901        [
902            [bcr!(85,  0), bcr!(87,  0), bcr!(83,  3)],
903            [bcr!(101, 0), bcr!(102, 3), bcr!(100, 3)],
904            [bcr!(107, 3), bcr!(112, 3), bcr!(114, 3)],
905        ], [
906            [bcr!(66, 0), bcr!(67,  3), bcr!(70,  3)],
907            [bcr!(81, 0), bcr!(85,  0), bcr!(87,  0)],
908            [bcr!(94, 3), bcr!(101, 0), bcr!(102, 3)],
909        ], [
910            [bcr!(49, 0), bcr!(48, 3), bcr!(50, 3)],
911            [bcr!(61, 3), bcr!(66, 0), bcr!(67, 3)],
912            [bcr!(75, 3), bcr!(81, 0), bcr!(85, 0)],
913        ]
914    ], [
915        [
916            [bcr!(95, 0), bcr!(92, 0), bcr!(83, 0)],
917            [bcr!(79, 0), bcr!(78, 0), bcr!(74, 3)],
918            [bcr!(63, 1), bcr!(59, 3), bcr!(57, 3)],
919        ], [
920            [bcr!(109, 0), bcr!(108, 0), bcr!(100, 5)],
921            [bcr!(93,  1), bcr!(95,  0), bcr!(92,  0)],
922            [bcr!(77,  1), bcr!(79,  0), bcr!(78,  0)],
923        ], [
924            [bcr!(117, 4), bcr!(118, 5), bcr!(114, 5)],
925            [bcr!(106, 1), bcr!(109, 0), bcr!(108, 0)],
926            [bcr!(90,  1), bcr!(93,  1), bcr!(95,  0)],
927        ]
928    ], [
929        [
930            [bcr!(90, 0), bcr!(77, 0), bcr!(63, 0)],
931            [bcr!(80, 0), bcr!(68, 0), bcr!(56, 3)],
932            [bcr!(72, 1), bcr!(60, 3), bcr!(46, 3)],
933        ], [
934            [bcr!(106, 0), bcr!(93, 0), bcr!(79, 5)],
935            [bcr!(99,  1), bcr!(90, 0), bcr!(77, 0)],
936            [bcr!(88,  1), bcr!(80, 0), bcr!(68, 0)],
937        ], [
938            [bcr!(117, 3), bcr!(109, 5), bcr!(95, 5)],
939            [bcr!(113, 1), bcr!(106, 0), bcr!(93, 0)],
940            [bcr!(105, 1), bcr!(99,  1), bcr!(90, 0)],
941        ]
942    ], [
943        [
944            [bcr!(105, 0), bcr!(88, 0), bcr!(72, 0)],
945            [bcr!(103, 0), bcr!(91, 0), bcr!(73, 3)],
946            [bcr!(97,  1), bcr!(89, 3), bcr!(71, 3)],
947        ], [
948            [bcr!(113, 0), bcr!(99,  0), bcr!(80, 5)],
949            [bcr!(116, 1), bcr!(105, 0), bcr!(88, 0)],
950            [bcr!(111, 1), bcr!(103, 0), bcr!(91, 0)],
951        ], [
952            [bcr!(117, 2), bcr!(106, 5), bcr!(90, 5)],
953            [bcr!(121, 1), bcr!(113, 0), bcr!(99, 0)],
954            [bcr!(119, 1), bcr!(116, 1), bcr!(105, 0)],
955        ]
956    ], [
957        [
958            [bcr!(119, 0), bcr!(111, 0), bcr!(97, 0)],
959            [bcr!(115, 0), bcr!(110, 0), bcr!(98, 3)],
960            [bcr!(107, 1), bcr!(104, 3), bcr!(96, 3)],
961        ], [
962            [bcr!(121, 0), bcr!(116, 0), bcr!(103, 5)],
963            [bcr!(120, 1), bcr!(119, 0), bcr!(111, 0)],
964            [bcr!(112, 1), bcr!(115, 0), bcr!(110, 0)],
965        ], [
966            [bcr!(117, 1), bcr!(113, 5), bcr!(105, 5)],
967            [bcr!(118, 1), bcr!(121, 0), bcr!(116, 0)],
968            [bcr!(114, 1), bcr!(120, 1), bcr!(119, 0)],
969        ]
970    ], [
971        [
972            [bcr!(114, 0), bcr!(112, 0), bcr!(107, 0)],
973            [bcr!(100, 0), bcr!(102, 0), bcr!(101, 3)],
974            [bcr!(83,  1), bcr!(87,  3), bcr!(85,  3)],
975        ], [
976            [bcr!(118, 0), bcr!(120, 0), bcr!(115, 5)],
977            [bcr!(108, 1), bcr!(114, 0), bcr!(112, 0)],
978            [bcr!(92,  1), bcr!(100, 0), bcr!(102, 0)],
979        ], [
980            [bcr!(117, 0), bcr!(121, 5), bcr!(119, 5)],
981            [bcr!(109, 1), bcr!(118, 0), bcr!(120, 0)],
982            [bcr!(95,  1), bcr!(108, 1), bcr!(114, 0)],
983        ]
984    ]
985];