use super::IndexMode;
use crate::{
coord::CoordIJK, Direction, Edge, Resolution, Vertex, CCW, CW,
DIRECTION_BITSIZE,
};
use std::{cmp, num::NonZeroU8};
const MODE_OFFSET: usize = 59;
const MODE_MASK: u64 = 0b1111 << MODE_OFFSET;
const EDGE_OFFSET: usize = 56;
const EDGE_MASK: u64 = 0b111 << EDGE_OFFSET;
const VERTEX_OFFSET: usize = 56;
const VERTEX_MASK: u64 = 0b111 << VERTEX_OFFSET;
const RESOLUTION_OFFSET: u64 = 52;
const RESOLUTION_MASK: u64 = 0b1111 << RESOLUTION_OFFSET;
const BASE_CELL_OFFSET: u64 = 45;
const BASE_CELL_MASK: u64 = 0b111_1111 << BASE_CELL_OFFSET;
pub const DIRECTIONS_MASK: u64 = 0x0000_1fff_ffff_ffff;
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub const fn get_mode(bits: u64) -> u8 {
((bits & MODE_MASK) >> MODE_OFFSET) as u8
}
#[must_use]
pub const fn clr_mode(bits: u64) -> u64 {
bits & !MODE_MASK
}
#[must_use]
pub const fn set_mode(bits: u64, mode: IndexMode) -> u64 {
clr_mode(bits) | ((mode as u64) << MODE_OFFSET)
}
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub const fn get_edge(bits: u64) -> u8 {
((bits & EDGE_MASK) >> EDGE_OFFSET) as u8
}
#[must_use]
pub fn set_edge(bits: u64, edge: Edge) -> u64 {
clr_edge(bits) | (u64::from(edge) << EDGE_OFFSET)
}
#[must_use]
pub const fn clr_edge(bits: u64) -> u64 {
bits & !EDGE_MASK
}
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub const fn get_vertex(bits: u64) -> u8 {
((bits & VERTEX_MASK) >> VERTEX_OFFSET) as u8
}
#[must_use]
pub fn set_vertex(bits: u64, vertex: Vertex) -> u64 {
clr_vertex(bits) | (u64::from(vertex) << VERTEX_OFFSET)
}
#[must_use]
pub const fn clr_vertex(bits: u64) -> u64 {
bits & !VERTEX_MASK
}
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub const fn get_resolution(bits: u64) -> Resolution {
Resolution::new_unchecked(
((bits & RESOLUTION_MASK) >> RESOLUTION_OFFSET) as u8,
)
}
#[must_use]
pub const fn clr_resolution(bits: u64) -> u64 {
bits & !RESOLUTION_MASK
}
#[must_use]
pub fn set_resolution(bits: u64, resolution: Resolution) -> u64 {
clr_resolution(bits) | (u64::from(resolution) << RESOLUTION_OFFSET)
}
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub const fn get_base_cell(bits: u64) -> u8 {
((bits & BASE_CELL_MASK) >> BASE_CELL_OFFSET) as u8
}
#[must_use]
pub fn set_base_cell(bits: u64, cell: u8) -> u64 {
(bits & !BASE_CELL_MASK) | (u64::from(cell) << BASE_CELL_OFFSET)
}
#[allow(clippy::cast_possible_truncation)] #[must_use]
pub fn get_direction(bits: u64, resolution: Resolution) -> u8 {
((bits & resolution.direction_mask()) >> resolution.direction_offset())
as u8
}
#[must_use]
pub fn clr_direction(bits: u64, resolution: Resolution) -> u64 {
bits & !resolution.direction_mask()
}
pub fn set_direction(bits: u64, cell: u8, resolution: Resolution) -> u64 {
(bits & !resolution.direction_mask())
| (u64::from(cell) << resolution.direction_offset())
}
#[must_use]
pub fn set_unused(bits: u64, resolution: Resolution) -> u64 {
let unused_end_offset = resolution.direction_offset();
let unused_bits = (1 << unused_end_offset) - 1;
bits | unused_bits
}
#[must_use]
pub fn first_axe(bits: u64) -> Option<NonZeroU8> {
const PREFIX_LENGTH: u32 = DIRECTIONS_MASK.leading_zeros();
let resolution = get_resolution(bits);
if resolution == Resolution::Zero {
return None;
}
let bit_index = (bits & DIRECTIONS_MASK).leading_zeros() - PREFIX_LENGTH;
#[allow(clippy::cast_possible_truncation)] let resolution = cmp::min(
((bit_index / DIRECTION_BITSIZE as u32) + 1) as u8,
resolution.into(),
);
NonZeroU8::new(get_direction(bits, Resolution::new_unchecked(resolution)))
}
pub fn rotate60<const CCW: bool>(mut bits: u64, count: usize) -> u64 {
if count == 1 {
for resolution in
Resolution::range(Resolution::One, get_resolution(bits))
{
let direction =
Direction::new_unchecked(get_direction(bits, resolution));
bits = set_direction(
bits,
direction.rotate60_once::<CCW>().into(),
resolution,
);
}
} else {
for resolution in
Resolution::range(Resolution::One, get_resolution(bits))
{
let direction =
Direction::new_unchecked(get_direction(bits, resolution));
bits = set_direction(
bits,
direction.rotate60::<CCW>(count).into(),
resolution,
);
}
}
bits
}
pub fn pentagon_rotate60<const CCW: bool>(mut bits: u64) -> u64 {
let res = get_resolution(bits);
if res == Resolution::Zero {
return bits;
}
let direction = if CCW { Direction::JK } else { Direction::IK }.axe();
if first_axe(bits) == direction {
for resolution in Resolution::range(Resolution::One, res) {
let direction =
Direction::new_unchecked(get_direction(bits, resolution));
bits = set_direction(
bits,
direction.rotate60::<CCW>(2).into(),
resolution,
);
}
} else {
for resolution in Resolution::range(Resolution::One, res) {
let direction =
Direction::new_unchecked(get_direction(bits, resolution));
bits = set_direction(
bits,
direction.rotate60_once::<CCW>().into(),
resolution,
);
}
}
bits
}
#[allow(clippy::inline_always)] #[inline(always)]
pub fn directions_bits_from_ijk(
mut ijk: CoordIJK,
bits: &mut u64,
resolution: Resolution,
) -> CoordIJK {
for res in Resolution::range(Resolution::One, resolution).rev() {
let last_ijk = ijk;
let last_center = if res.is_class3() {
ijk = ijk.up_aperture7::<{ CCW }>();
ijk.down_aperture7::<{ CCW }>()
} else {
ijk = ijk.up_aperture7::<{ CW }>();
ijk.down_aperture7::<{ CW }>()
};
let diff = (last_ijk - last_center).normalize();
let direction = Direction::try_from(diff).expect("unit IJK coordinate");
*bits = set_direction(*bits, direction.into(), res);
}
ijk
}
#[cfg(test)]
#[path = "./bits_tests.rs"]
mod tests;