use super::{CoordIJK, Vec2d, SQRT3_2};
use crate::{
face::{self, FaceOrientIJK},
index::bits,
BaseCell, Boundary, CellIndex, Direction, ExtendedResolution, Face, LatLng,
Resolution, Vertex, CCW, CW, DEFAULT_CELL_INDEX, NUM_HEX_VERTS,
NUM_ICOSA_FACES, NUM_PENT_VERTS,
};
static ZERO: CoordIJK = CoordIJK::new(0, 0, 0);
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct FaceIJK {
pub face: Face,
pub coord: CoordIJK,
}
impl FaceIJK {
pub const fn new(face: Face, coord: CoordIJK) -> Self {
Self { face, coord }
}
#[allow(clippy::cast_sign_loss)] pub fn base_cell_rotation(&self) -> Rotation {
debug_assert!(
(0..=2).contains(&self.coord.i())
&& (0..=2).contains(&self.coord.j())
&& (0..=2).contains(&self.coord.k())
);
let f = usize::from(self.face);
let i = self.coord.i() as usize;
let j = self.coord.j() as usize;
let k = self.coord.k() as usize;
FACE_IJK_BASE_CELLS[f][i][j][k]
}
pub(crate) fn to_cell(mut self, resolution: Resolution) -> CellIndex {
let mut bits = bits::set_resolution(DEFAULT_CELL_INDEX, resolution);
if resolution == Resolution::Zero {
let rotation = self.base_cell_rotation();
return CellIndex::new_unchecked(bits::set_base_cell(
bits,
rotation.base_cell.into(),
));
}
self.coord =
bits::directions_bits_from_ijk(self.coord, &mut bits, resolution);
let rotation = self.base_cell_rotation();
bits = bits::set_base_cell(bits, rotation.base_cell.into());
if rotation.base_cell.is_pentagon() {
if bits::first_axe(bits) == Direction::K.axe() {
if rotation.base_cell.is_cw_offset(self.face) {
bits = bits::rotate60::<{ CW }>(bits, 1);
} else {
bits = bits::rotate60::<{ CCW }>(bits, 1);
}
}
for _ in 0..rotation.count {
bits = bits::pentagon_rotate60::<{ CCW }>(bits);
}
} else {
bits = bits::rotate60::<{ CCW }>(bits, rotation.count.into());
}
CellIndex::new_unchecked(bits)
}
pub fn to_latlng(self, resolution: Resolution) -> LatLng {
Vec2d::from(self.coord).to_latlng(self.face, resolution.into(), false)
}
pub fn from_bits(
bits: u64,
resolution: Resolution,
base_cell: BaseCell,
) -> (Self, bool) {
let mut fijk = Self::from(base_cell);
let possible_overage = base_cell.is_pentagon()
|| (resolution != Resolution::Zero || fijk.coord != ZERO);
for res in Resolution::range(Resolution::One, resolution) {
if res.is_class3() {
fijk.coord = fijk.coord.down_aperture7::<{ CCW }>();
} else {
fijk.coord = fijk.coord.down_aperture7::<{ CW }>();
}
let direction =
Direction::new_unchecked(bits::get_direction(bits, res));
fijk.coord = fijk.coord.neighbor(direction);
}
(fijk, possible_overage)
}
pub fn adjust_overage_class2<const IS_SUBSTRATE: bool>(
&mut self,
class2_res: ExtendedResolution,
is_pent4: bool,
) -> Overage {
let class2_res = usize::from(class2_res);
let factor = if IS_SUBSTRATE { 3 } else { 1 };
let face = usize::from(self.face);
let dimension = self.coord.i() + self.coord.j() + self.coord.k();
let max_dim = MAX_DIM_BY_CII_RES[class2_res] * factor;
if IS_SUBSTRATE && dimension == max_dim {
return Overage::FaceEdge;
}
if dimension > max_dim {
let orientation = if self.coord.k() > 0 {
if self.coord.j() > 0 {
face::NEIGHBORS[face][face::JK]
} else {
if is_pent4 {
let origin = CoordIJK::new(max_dim, 0, 0);
let tmp = (self.coord - origin).rotate60::<{ CW }>();
self.coord = tmp + origin;
}
face::NEIGHBORS[face][face::KI]
}
} else {
face::NEIGHBORS[face][face::IJ]
};
self.face = orientation.face;
for _ in 0..orientation.ccw_rot60 {
self.coord = self.coord.rotate60::<{ CCW }>();
}
let mut trans_vec = orientation.translate;
let unit_scale = UNIT_SCALE_BY_CII_RES[class2_res] * factor;
trans_vec *= unit_scale;
self.coord = (self.coord + trans_vec).normalize();
if IS_SUBSTRATE
&& self.coord.i() + self.coord.j() + self.coord.k() == max_dim
{
return Overage::FaceEdge;
}
return Overage::NewFace;
}
Overage::None
}
pub fn adjust_pentagon_vertex_overage(
&mut self,
resolution: ExtendedResolution,
) {
while self.adjust_overage_class2::<true>(resolution, false)
== Overage::NewFace
{}
}
pub fn pentagon_boundary(
&self,
resolution: Resolution,
start: Vertex,
length: u8,
) -> Boundary {
let mut boundary = Boundary::new();
let start = u8::from(start);
let mut center = *self;
let mut vertices = [Self::default(); NUM_PENT_VERTS as usize];
let adjusted_resolution = center.vertices(resolution, &mut vertices);
let additional_iteration = u8::from(length == NUM_PENT_VERTS);
let mut last_fijk = Self::default();
for vert in start..(start + length + additional_iteration) {
let mut fijk: Self = vertices[usize::from(vert % NUM_PENT_VERTS)];
fijk.adjust_pentagon_vertex_overage(adjusted_resolution);
if resolution.is_class3() && vert > start {
let mut tmp_fijk = fijk;
let orig2d0 = Vec2d::from(last_fijk.coord);
let current_to_last_dir: u8 =
get_adjacent_face_dir(tmp_fijk.face, last_fijk.face);
let fijk_orientation: &FaceOrientIJK = &face::NEIGHBORS
[usize::from(tmp_fijk.face)]
[usize::from(current_to_last_dir)];
tmp_fijk.face = fijk_orientation.face;
for _ in 0..fijk_orientation.ccw_rot60 {
tmp_fijk.coord = tmp_fijk.coord.rotate60::<{ CCW }>();
}
let mut trans_vec = fijk_orientation.translate;
trans_vec *=
UNIT_SCALE_BY_CII_RES[usize::from(adjusted_resolution)] * 3;
tmp_fijk.coord = (tmp_fijk.coord + trans_vec).normalize();
let orig2d1 = Vec2d::from(tmp_fijk.coord);
let max_dim = f64::from(
MAX_DIM_BY_CII_RES[usize::from(adjusted_resolution)],
);
let v0 = Vec2d::new(3.0 * max_dim, 0.0);
let v1 = Vec2d::new(-1.5 * max_dim, 3.0 * SQRT3_2 * max_dim);
let v2 = Vec2d::new(-1.5 * max_dim, -3.0 * SQRT3_2 * max_dim);
let (edge0, edge1) = match usize::from(get_adjacent_face_dir(
tmp_fijk.face,
fijk.face,
)) {
face::IJ => (v0, v1),
face::JK => (v1, v2),
face::KI => (v2, v0),
_ => unreachable!("invalid face direction"),
};
let intersection =
Vec2d::intersection((orig2d0, orig2d1), (edge0, edge1));
boundary.push(intersection.to_latlng(
tmp_fijk.face,
adjusted_resolution,
true,
));
}
if vert < start + NUM_PENT_VERTS {
boundary.push(Vec2d::from(fijk.coord).to_latlng(
fijk.face,
adjusted_resolution,
true,
));
}
last_fijk = fijk;
}
boundary
}
pub fn hexagon_boundary(
&self,
resolution: Resolution,
start: Vertex,
length: u8,
) -> Boundary {
let mut boundary = Boundary::new();
let start = u8::from(start);
let mut center = *self;
let mut vertices = [Self::default(); NUM_HEX_VERTS as usize];
let adjusted_resolution = center.vertices(resolution, &mut vertices);
let additional_iteration = u8::from(length == NUM_HEX_VERTS);
let mut last_face = usize::MAX;
let mut last_overage = Overage::None;
for vert in start..(start + length + additional_iteration) {
let v = usize::from(vert % NUM_HEX_VERTS);
let mut fijk = vertices[v];
let overage =
fijk.adjust_overage_class2::<true>(adjusted_resolution, false);
if resolution.is_class3()
&& vert > start
&& usize::from(fijk.face) != last_face
&& last_overage != Overage::FaceEdge
{
let last_v: usize = (v + 5) % usize::from(NUM_HEX_VERTS);
let orig2d0 = Vec2d::from(vertices[last_v].coord);
let orig2d1 = Vec2d::from(vertices[v].coord);
let max_dim = f64::from(
MAX_DIM_BY_CII_RES[usize::from(adjusted_resolution)],
);
let v0 = Vec2d::new(3.0 * max_dim, 0.0);
let v1 = Vec2d::new(-1.5 * max_dim, 3.0 * SQRT3_2 * max_dim);
let v2 = Vec2d::new(-1.5 * max_dim, -3.0 * SQRT3_2 * max_dim);
let face2 = if last_face == usize::from(center.face) {
fijk.face
} else {
Face::new_unchecked(last_face)
};
let (edge0, edge1) = match usize::from(get_adjacent_face_dir(
center.face,
face2,
)) {
face::IJ => (v0, v1),
face::JK => (v1, v2),
face::KI => (v2, v0),
_ => unreachable!("invalid adjacent face direction"),
};
let intersection =
Vec2d::intersection((orig2d0, orig2d1), (edge0, edge1));
let is_intersection_at_vertex =
orig2d0 == intersection || orig2d1 == intersection;
if !is_intersection_at_vertex {
boundary.push(intersection.to_latlng(
center.face,
adjusted_resolution,
true,
));
}
}
if vert < start + NUM_HEX_VERTS {
boundary.push(Vec2d::from(fijk.coord).to_latlng(
fijk.face,
adjusted_resolution,
true,
));
}
last_face = fijk.face.into();
last_overage = overage;
}
boundary
}
pub fn vertices(
&mut self,
resolution: Resolution,
vertices: &mut [Self],
) -> ExtendedResolution {
const VERTS_CII: [CoordIJK; 6] = [
CoordIJK::new(2, 1, 0),
CoordIJK::new(1, 2, 0),
CoordIJK::new(0, 2, 1),
CoordIJK::new(0, 1, 2),
CoordIJK::new(1, 0, 2),
CoordIJK::new(2, 0, 1),
];
const VERTS_CIII: [CoordIJK; 6] = [
CoordIJK::new(5, 4, 0),
CoordIJK::new(1, 5, 0),
CoordIJK::new(0, 5, 4),
CoordIJK::new(0, 1, 5),
CoordIJK::new(4, 0, 5),
CoordIJK::new(5, 0, 1),
];
self.coord = self.coord.down_aperture3::<{ CCW }>();
self.coord = self.coord.down_aperture3::<{ CW }>();
let (verts, adjusted_resolution) = if resolution.is_class3() {
self.coord = self.coord.down_aperture7::<{ CW }>();
(&VERTS_CIII, ExtendedResolution::down(resolution))
} else {
(&VERTS_CII, resolution.into())
};
for (i, vertex) in vertices.iter_mut().enumerate() {
vertex.face = self.face;
vertex.coord = (self.coord + verts[i]).normalize();
}
adjusted_resolution
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Overage {
None,
FaceEdge,
NewFace,
}
fn get_adjacent_face_dir(i: Face, j: Face) -> u8 {
((ADJACENT_FACE_DIR[usize::from(i)] >> (usize::from(j) * 3)) & 0b111) as u8
}
macro_rules! adjacent_face_dir {
(central = $central:literal, IJ = $ij:literal, KI = $ki:literal, JK = $jk:literal) => {
!(0 | (0b111 << (3 * $central))
| (0b110 << (3 * $ij))
| (0b101 << (3 * $ki))
| (0b100 << (3 * $jk)))
};
}
#[rustfmt::skip]
static ADJACENT_FACE_DIR: [u64; NUM_ICOSA_FACES] = [
adjacent_face_dir!(central = 0, IJ = 4, KI = 1, JK = 5), adjacent_face_dir!(central = 1, IJ = 0, KI = 2, JK = 6), adjacent_face_dir!(central = 2, IJ = 1, KI = 3, JK = 7), adjacent_face_dir!(central = 3, IJ = 2, KI = 4, JK = 8), adjacent_face_dir!(central = 4, IJ = 3, KI = 0, JK = 9), adjacent_face_dir!(central = 5, IJ = 10, KI = 14, JK = 0), adjacent_face_dir!(central = 6, IJ = 11, KI = 10, JK = 1), adjacent_face_dir!(central = 7, IJ = 12, KI = 11, JK = 2), adjacent_face_dir!(central = 8, IJ = 13, KI = 12, JK = 3), adjacent_face_dir!(central = 9, IJ = 14, KI = 13, JK = 4), adjacent_face_dir!(central = 10, IJ = 5, KI = 6, JK = 15), adjacent_face_dir!(central = 11, IJ = 6, KI = 7, JK = 16), adjacent_face_dir!(central = 12, IJ = 7, KI = 8, JK = 17), adjacent_face_dir!(central = 13, IJ = 8, KI = 9, JK = 18), adjacent_face_dir!(central = 14, IJ = 9, KI = 5, JK = 19), adjacent_face_dir!(central = 15, IJ = 16, KI = 19, JK = 10), adjacent_face_dir!(central = 16, IJ = 17, KI = 15, JK = 11), adjacent_face_dir!(central = 17, IJ = 18, KI = 16, JK = 12), adjacent_face_dir!(central = 18, IJ = 19, KI = 17, JK = 13), adjacent_face_dir!(central = 19, IJ = 15, KI = 18, JK = 14), ];
#[rustfmt::skip]
static MAX_DIM_BY_CII_RES: &[i32] = &[
2, -1, 14, -1, 98, -1, 686, -1, 4802, -1, 33614, -1, 235298, -1, 1647086, -1, 11529602, ];
#[rustfmt::skip]
static UNIT_SCALE_BY_CII_RES: &[i32] = &[
1, -1, 7, -1, 49, -1, 343, -1, 2401, -1, 16807, -1, 117649, -1, 823543, -1, 5764801, ];
#[derive(Clone, Copy)]
pub struct Rotation {
pub base_cell: BaseCell,
pub count: u8,
}
macro_rules! bcr {
($base_cell:literal, $rotation:literal) => {
Rotation {
base_cell: BaseCell::new_unchecked($base_cell),
count: $rotation,
}
};
}
#[rustfmt::skip]
const FACE_IJK_BASE_CELLS: [[[[Rotation; 3]; 3]; 3]; NUM_ICOSA_FACES] = [
[
[
[bcr!(16, 0), bcr!(18, 0), bcr!(24, 0)],
[bcr!(33, 0), bcr!(30, 0), bcr!(32, 3)],
[bcr!(49, 1), bcr!(48, 3), bcr!(50, 3)],
], [
[bcr!(8, 0), bcr!(5, 5), bcr!(10, 5)],
[bcr!(22, 0), bcr!(16, 0), bcr!(18, 0)],
[bcr!(41, 1), bcr!(33, 0), bcr!(30, 0)],
], [
[bcr!(4, 0), bcr!(0, 5), bcr!(2, 5)],
[bcr!(15, 1), bcr!(8, 0), bcr!(5, 5)],
[bcr!(31, 1), bcr!(22, 0), bcr!(16, 0)],
],
], [
[
[bcr!(2, 0), bcr!(6, 0), bcr!(14, 0)],
[bcr!(10, 0), bcr!(11, 0), bcr!(17, 3)],
[bcr!(24, 1), bcr!(23, 3), bcr!(25, 3)],
], [
[bcr!(0, 0), bcr!(1, 5), bcr!(9, 5)],
[bcr!(5, 0), bcr!(2, 0), bcr!(6, 0)],
[bcr!(18, 1), bcr!(10, 0), bcr!(11, 0)],
], [
[bcr!(4, 1), bcr!(3, 5), bcr!(7, 5)],
[bcr!(8, 1), bcr!(0, 0), bcr!(1, 5)],
[bcr!(16, 1), bcr!(5, 0), bcr!(2, 0)],
],
], [
[
[bcr!(7, 0), bcr!(21, 0), bcr!(38, 0)],
[bcr!(9, 0), bcr!(19, 0), bcr!(34, 3)],
[bcr!(14, 1), bcr!(20, 3), bcr!(36, 3)],
], [
[bcr!(3, 0), bcr!(13, 5), bcr!(29, 5)],
[bcr!(1, 0), bcr!(7, 0), bcr!(21, 0)],
[bcr!(6, 1), bcr!(9, 0), bcr!(19, 0)],
], [
[bcr!(4, 2), bcr!(12, 5), bcr!(26, 5)],
[bcr!(0, 1), bcr!(3, 0), bcr!(13, 5)],
[bcr!(2, 1), bcr!(1, 0), bcr!(7, 0)],
]
], [
[
[bcr!(26, 0), bcr!(42, 0), bcr!(58, 0)],
[bcr!(29, 0), bcr!(43, 0), bcr!(62, 3)],
[bcr!(38, 1), bcr!(47, 3), bcr!(64, 3)],
], [
[bcr!(12, 0), bcr!(28, 5), bcr!(44, 5)],
[bcr!(13, 0), bcr!(26, 0), bcr!(42, 0)],
[bcr!(21, 1), bcr!(29, 0), bcr!(43, 0)],
], [
[bcr!(4, 3), bcr!(15, 5), bcr!(31, 5)],
[bcr!(3, 1), bcr!(12, 0), bcr!(28, 5)],
[bcr!(7, 1), bcr!(13, 0), bcr!(26, 0)],
]
], [
[
[bcr!(31, 0), bcr!(41, 0), bcr!(49, 0)],
[bcr!(44, 0), bcr!(53, 0), bcr!(61, 3)],
[bcr!(58, 1), bcr!(65, 3), bcr!(75, 3)],
], [
[bcr!(15, 0), bcr!(22, 5), bcr!(33, 5)],
[bcr!(28, 0), bcr!(31, 0), bcr!(41, 0)],
[bcr!(42, 1), bcr!(44, 0), bcr!(53, 0)],
], [
[bcr!(4, 4), bcr!(8, 5), bcr!(16, 5)],
[bcr!(12, 1), bcr!(15, 0), bcr!(22, 5)],
[bcr!(26, 1), bcr!(28, 0), bcr!(31, 0)],
]
], [
[
[bcr!(50, 0), bcr!(48, 0), bcr!(49, 3)],
[bcr!(32, 0), bcr!(30, 3), bcr!(33, 3)],
[bcr!(24, 3), bcr!(18, 3), bcr!(16, 3)],
], [
[bcr!(70, 0), bcr!(67, 0), bcr!(66, 3)],
[bcr!(52, 3), bcr!(50, 0), bcr!(48, 0)],
[bcr!(37, 3), bcr!(32, 0), bcr!(30, 3)],
], [
[bcr!(83, 0), bcr!(87, 3), bcr!(85, 3)],
[bcr!(74, 3), bcr!(70, 0), bcr!(67, 0)],
[bcr!(57, 1), bcr!(52, 3), bcr!(50, 0)],
]
], [
[
[bcr!(25, 0), bcr!(23, 0), bcr!(24, 3)],
[bcr!(17, 0), bcr!(11, 3), bcr!(10, 3)],
[bcr!(14, 3), bcr!(6, 3), bcr!(2, 3)],
], [
[bcr!(45, 0), bcr!(39, 0), bcr!(37, 3)],
[bcr!(35, 3), bcr!(25, 0), bcr!(23, 0)],
[bcr!(27, 3), bcr!(17, 0), bcr!(11, 3)],
], [
[bcr!(63, 0), bcr!(59, 3), bcr!(57, 3)],
[bcr!(56, 3), bcr!(45, 0), bcr!(39, 0)],
[bcr!(46, 3), bcr!(35, 3), bcr!(25, 0)],
]
], [
[
[bcr!(36, 0), bcr!(20, 0), bcr!(14, 3)],
[bcr!(34, 0), bcr!(19, 3), bcr!(9, 3)],
[bcr!(38, 3), bcr!(21, 3), bcr!(7, 3)],
], [
[bcr!(55, 0), bcr!(40, 0), bcr!(27, 3)],
[bcr!(54, 3), bcr!(36, 0), bcr!(20, 0)],
[bcr!(51, 3), bcr!(34, 0), bcr!(19, 3)],
], [
[bcr!(72, 0), bcr!(60, 3), bcr!(46, 3)],
[bcr!(73, 3), bcr!(55, 0), bcr!(40, 0)],
[bcr!(71, 3), bcr!(54, 3), bcr!(36, 0)],
]
], [
[
[bcr!(64, 0), bcr!(47, 0), bcr!(38, 3)],
[bcr!(62, 0), bcr!(43, 3), bcr!(29, 3)],
[bcr!(58, 3), bcr!(42, 3), bcr!(26, 3)],
], [
[bcr!(84, 0), bcr!(69, 0), bcr!(51, 3)],
[bcr!(82, 3), bcr!(64, 0), bcr!(47, 0)],
[bcr!(76, 3), bcr!(62, 0), bcr!(43, 3)],
], [
[bcr!(97, 0), bcr!(89, 3), bcr!(71, 3)],
[bcr!(98, 3), bcr!(84, 0), bcr!(69, 0)],
[bcr!(96, 3), bcr!(82, 3), bcr!(64, 0)],
]
], [
[
[bcr!(75, 0), bcr!(65, 0), bcr!(58, 3)],
[bcr!(61, 0), bcr!(53, 3), bcr!(44, 3)],
[bcr!(49, 3), bcr!(41, 3), bcr!(31, 3)],
], [
[bcr!(94, 0), bcr!(86, 0), bcr!(76, 3)],
[bcr!(81, 3), bcr!(75, 0), bcr!(65, 0)],
[bcr!(66, 3), bcr!(61, 0), bcr!(53, 3)],
], [
[bcr!(107, 0), bcr!(104, 3), bcr!(96, 3)],
[bcr!(101, 3), bcr!(94, 0), bcr!(86, 0)],
[bcr!(85, 3), bcr!(81, 3), bcr!(75, 0)],
]
], [
[
[bcr!(57, 0), bcr!(59, 0), bcr!(63, 3)],
[bcr!(74, 0), bcr!(78, 3), bcr!(79, 3)],
[bcr!(83, 3), bcr!(92, 3), bcr!(95, 3)],
], [
[bcr!(37, 0), bcr!(39, 3), bcr!(45, 3)],
[bcr!(52, 0), bcr!(57, 0), bcr!(59, 0)],
[bcr!(70, 3), bcr!(74, 0), bcr!(78, 3)],
], [
[bcr!(24, 0), bcr!(23, 3), bcr!(25, 3)],
[bcr!(32, 3), bcr!(37, 0), bcr!(39, 3)],
[bcr!(50, 3), bcr!(52, 0), bcr!(57, 0)],
]
], [
[
[bcr!(46, 0), bcr!(60, 0), bcr!(72, 3)],
[bcr!(56, 0), bcr!(68, 3), bcr!(80, 3)],
[bcr!(63, 3), bcr!(77, 3), bcr!(90, 3)],
], [
[bcr!(27, 0), bcr!(40, 3), bcr!(55, 3)],
[bcr!(35, 0), bcr!(46, 0), bcr!(60, 0)],
[bcr!(45, 3), bcr!(56, 0), bcr!(68, 3)],
], [
[bcr!(14, 0), bcr!(20, 3), bcr!(36, 3)],
[bcr!(17, 3), bcr!(27, 0), bcr!(40, 3)],
[bcr!(25, 3), bcr!(35, 0), bcr!(46, 0)],
]
], [
[
[bcr!(71, 0), bcr!(89, 0), bcr!(97, 3)],
[bcr!(73, 0), bcr!(91, 3), bcr!(103, 3)],
[bcr!(72, 3), bcr!(88, 3), bcr!(105, 3)],
], [
[bcr!(51, 0), bcr!(69, 3), bcr!(84, 3)],
[bcr!(54, 0), bcr!(71, 0), bcr!(89, 0)],
[bcr!(55, 3), bcr!(73, 0), bcr!(91, 3)],
], [
[bcr!(38, 0), bcr!(47, 3), bcr!(64, 3)],
[bcr!(34, 3), bcr!(51, 0), bcr!(69, 3)],
[bcr!(36, 3), bcr!(54, 0), bcr!(71, 0)],
]
], [
[
[bcr!(96, 0), bcr!(104, 0), bcr!(107, 3)],
[bcr!(98, 0), bcr!(110, 3), bcr!(115, 3)],
[bcr!(97, 3), bcr!(111, 3), bcr!(119, 3)],
], [
[bcr!(76, 0), bcr!(86, 3), bcr!(94, 3)],
[bcr!(82, 0), bcr!(96, 0), bcr!(104, 0)],
[bcr!(84, 3), bcr!(98, 0), bcr!(110, 3)],
], [
[bcr!(58, 0), bcr!(65, 3), bcr!(75, 3)],
[bcr!(62, 3), bcr!(76, 0), bcr!(86, 3)],
[bcr!(64, 3), bcr!(82, 0), bcr!(96, 0)],
]
], [
[
[bcr!(85, 0), bcr!(87, 0), bcr!(83, 3)],
[bcr!(101, 0), bcr!(102, 3), bcr!(100, 3)],
[bcr!(107, 3), bcr!(112, 3), bcr!(114, 3)],
], [
[bcr!(66, 0), bcr!(67, 3), bcr!(70, 3)],
[bcr!(81, 0), bcr!(85, 0), bcr!(87, 0)],
[bcr!(94, 3), bcr!(101, 0), bcr!(102, 3)],
], [
[bcr!(49, 0), bcr!(48, 3), bcr!(50, 3)],
[bcr!(61, 3), bcr!(66, 0), bcr!(67, 3)],
[bcr!(75, 3), bcr!(81, 0), bcr!(85, 0)],
]
], [
[
[bcr!(95, 0), bcr!(92, 0), bcr!(83, 0)],
[bcr!(79, 0), bcr!(78, 0), bcr!(74, 3)],
[bcr!(63, 1), bcr!(59, 3), bcr!(57, 3)],
], [
[bcr!(109, 0), bcr!(108, 0), bcr!(100, 5)],
[bcr!(93, 1), bcr!(95, 0), bcr!(92, 0)],
[bcr!(77, 1), bcr!(79, 0), bcr!(78, 0)],
], [
[bcr!(117, 4), bcr!(118, 5), bcr!(114, 5)],
[bcr!(106, 1), bcr!(109, 0), bcr!(108, 0)],
[bcr!(90, 1), bcr!(93, 1), bcr!(95, 0)],
]
], [
[
[bcr!(90, 0), bcr!(77, 0), bcr!(63, 0)],
[bcr!(80, 0), bcr!(68, 0), bcr!(56, 3)],
[bcr!(72, 1), bcr!(60, 3), bcr!(46, 3)],
], [
[bcr!(106, 0), bcr!(93, 0), bcr!(79, 5)],
[bcr!(99, 1), bcr!(90, 0), bcr!(77, 0)],
[bcr!(88, 1), bcr!(80, 0), bcr!(68, 0)],
], [
[bcr!(117, 3), bcr!(109, 5), bcr!(95, 5)],
[bcr!(113, 1), bcr!(106, 0), bcr!(93, 0)],
[bcr!(105, 1), bcr!(99, 1), bcr!(90, 0)],
]
], [
[
[bcr!(105, 0), bcr!(88, 0), bcr!(72, 0)],
[bcr!(103, 0), bcr!(91, 0), bcr!(73, 3)],
[bcr!(97, 1), bcr!(89, 3), bcr!(71, 3)],
], [
[bcr!(113, 0), bcr!(99, 0), bcr!(80, 5)],
[bcr!(116, 1), bcr!(105, 0), bcr!(88, 0)],
[bcr!(111, 1), bcr!(103, 0), bcr!(91, 0)],
], [
[bcr!(117, 2), bcr!(106, 5), bcr!(90, 5)],
[bcr!(121, 1), bcr!(113, 0), bcr!(99, 0)],
[bcr!(119, 1), bcr!(116, 1), bcr!(105, 0)],
]
], [
[
[bcr!(119, 0), bcr!(111, 0), bcr!(97, 0)],
[bcr!(115, 0), bcr!(110, 0), bcr!(98, 3)],
[bcr!(107, 1), bcr!(104, 3), bcr!(96, 3)],
], [
[bcr!(121, 0), bcr!(116, 0), bcr!(103, 5)],
[bcr!(120, 1), bcr!(119, 0), bcr!(111, 0)],
[bcr!(112, 1), bcr!(115, 0), bcr!(110, 0)],
], [
[bcr!(117, 1), bcr!(113, 5), bcr!(105, 5)],
[bcr!(118, 1), bcr!(121, 0), bcr!(116, 0)],
[bcr!(114, 1), bcr!(120, 1), bcr!(119, 0)],
]
], [
[
[bcr!(114, 0), bcr!(112, 0), bcr!(107, 0)],
[bcr!(100, 0), bcr!(102, 0), bcr!(101, 3)],
[bcr!(83, 1), bcr!(87, 3), bcr!(85, 3)],
], [
[bcr!(118, 0), bcr!(120, 0), bcr!(115, 5)],
[bcr!(108, 1), bcr!(114, 0), bcr!(112, 0)],
[bcr!(92, 1), bcr!(100, 0), bcr!(102, 0)],
], [
[bcr!(117, 0), bcr!(121, 5), bcr!(119, 5)],
[bcr!(109, 1), bcr!(118, 0), bcr!(120, 0)],
[bcr!(95, 1), bcr!(108, 1), bcr!(114, 0)],
]
]
];