use super::{IndexMode, bits};
use crate::{
CellIndex, Direction, LatLng, NUM_HEX_VERTS, NUM_PENT_VERTS,
coord::FaceIJK, error,
};
use core::{cmp::Ordering, fmt, num::NonZeroU64, str::FromStr};
const MAX: u8 = 5;
const TO_DIRECTION_HEXAGON: [Direction; NUM_HEX_VERTS as usize] = [
Direction::IJ,
Direction::J,
Direction::JK,
Direction::K,
Direction::IK,
Direction::I,
];
const TO_DIRECTION_PENTAGON: [Direction; NUM_PENT_VERTS as usize] = [
Direction::IJ,
Direction::J,
Direction::JK,
Direction::IK,
Direction::I,
];
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vertex(u8);
impl Vertex {
pub(crate) const fn new_unchecked(value: u8) -> Self {
debug_assert!(value <= MAX, "cell vertex out of range");
Self(value)
}
pub(crate) fn to_direction(self, origin: CellIndex) -> Direction {
let is_pentagon = origin.is_pentagon();
let vertex_count = if is_pentagon {
NUM_PENT_VERTS
} else {
NUM_HEX_VERTS
};
assert!(self.0 < vertex_count);
let rotations = origin.vertex_rotations();
if is_pentagon {
let index = (self.0 + rotations) % NUM_PENT_VERTS;
TO_DIRECTION_PENTAGON[usize::from(index)]
} else {
let index = (self.0 + rotations) % NUM_HEX_VERTS;
TO_DIRECTION_HEXAGON[usize::from(index)]
}
}
}
impl TryFrom<u8> for Vertex {
type Error = error::InvalidVertex;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value > MAX {
return Err(Self::Error::new(value, "out of range"));
}
Ok(Self(value))
}
}
impl From<Vertex> for u8 {
fn from(value: Vertex) -> Self {
value.0
}
}
impl From<Vertex> for u64 {
fn from(value: Vertex) -> Self {
Self::from(value.0)
}
}
impl fmt::Display for Vertex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Vertex {
fn arbitrary(
data: &mut arbitrary::Unstructured<'a>,
) -> arbitrary::Result<Self> {
u8::arbitrary(data).and_then(|byte| {
Self::try_from(byte).map_err(|_| arbitrary::Error::IncorrectFormat)
})
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VertexIndex(NonZeroU64);
impl VertexIndex {
#[must_use]
pub const fn vertex(self) -> Vertex {
Vertex::new_unchecked(bits::get_vertex(self.0.get()))
}
#[must_use]
pub fn owner(self) -> CellIndex {
let bits = bits::set_mode(self.0.get(), IndexMode::Cell);
CellIndex::new_unchecked(bits::clr_vertex(bits))
}
pub(crate) const fn new_unchecked(value: u64) -> Self {
Self(NonZeroU64::new(value).expect("valid vertex index"))
}
}
impl Ord for VertexIndex {
fn cmp(&self, other: &Self) -> Ordering {
const MASK: u64 = 0xf80f_ffff_ffff_ffff;
(self.0.get() & MASK, self.vertex())
.cmp(&(other.0.get() & MASK, other.vertex()))
}
}
impl PartialOrd for VertexIndex {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<VertexIndex> for u64 {
fn from(value: VertexIndex) -> Self {
value.0.get()
}
}
impl TryFrom<u64> for VertexIndex {
type Error = error::InvalidVertexIndex;
fn try_from(value: u64) -> Result<Self, Self::Error> {
if bits::get_mode(value) != u8::from(IndexMode::Vertex) {
return Err(Self::Error::new(Some(value), "invalid index mode"));
}
let bits = bits::set_mode(value, IndexMode::Cell);
let bits = bits::clr_vertex(bits);
let owner = CellIndex::try_from(bits)
.map_err(|err| Self::Error::new(Some(value), err.reason))?;
let vertex =
Vertex::try_from(bits::get_vertex(value)).map_err(|_| {
Self::Error::new(Some(value), "invalid vertex number")
})?;
let canonical = owner.vertex(vertex).map(u64::from);
if canonical != Some(value) {
return Err(Self::Error::new(Some(value), "non-canonical vertex"));
}
Ok(Self(NonZeroU64::new(value).expect("non-zero vertex index")))
}
}
impl From<VertexIndex> for LatLng {
fn from(value: VertexIndex) -> Self {
let vertex = Vertex::new_unchecked(bits::get_vertex(value.0.get()));
let owner = value.owner();
let fijk = FaceIJK::from(owner);
let resolution = owner.resolution();
let boundary = if owner.is_pentagon() {
fijk.pentagon_boundary(resolution, vertex, 1)
} else {
fijk.hexagon_boundary(resolution, vertex, 1)
};
boundary[0]
}
}
impl FromStr for VertexIndex {
type Err = error::InvalidVertexIndex;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u64::from_str_radix(s, 16)
.map_err(|_| Self::Err {
value: None,
reason: "invalid 64-bit hex number",
})
.and_then(Self::try_from)
}
}
impl fmt::Debug for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{:015o}_{} ({})",
self.owner().base_cell(),
u64::from(*self) & bits::DIRECTIONS_MASK,
self.vertex(),
self
)
}
}
impl fmt::Display for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:x}")
}
}
impl fmt::Binary for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Binary::fmt(&self.0, f)
}
}
impl fmt::Octal for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Octal::fmt(&self.0, f)
}
}
impl fmt::LowerHex for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
}
}
impl fmt::UpperHex for VertexIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::UpperHex::fmt(&self.0, f)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for VertexIndex {
fn arbitrary(
data: &mut arbitrary::Unstructured<'a>,
) -> arbitrary::Result<Self> {
u64::arbitrary(data).and_then(|byte| {
Self::try_from(byte).map_err(|_| arbitrary::Error::IncorrectFormat)
})
}
}
#[cfg(feature = "geo")]
impl From<VertexIndex> for geo::Point {
fn from(value: VertexIndex) -> Self {
let coord: geo::Coord = LatLng::from(value).into();
coord.into()
}
}
#[cfg(test)]
#[path = "./vertex_tests.rs"]
mod tests;