#![warn(missing_docs, missing_debug_implementations)]
use arrayvec::ArrayVec;
use bitvec::prelude::*;
use std::fmt;
use std::ops::{
BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub, SubAssign,
};
use nalgebra::*;
mod hull;
pub use bitvec;
pub use hull::*;
pub const POINTS: [Exact; 8] = [
Exact(point!(0, 0, 0)),
Exact(point!(0, 0, 2)),
Exact(point!(0, 2, 0)),
Exact(point!(0, 2, 2)),
Exact(point!(2, 0, 0)),
Exact(point!(2, 0, 2)),
Exact(point!(2, 2, 0)),
Exact(point!(2, 2, 2)),
];
#[derive(Debug, Clone, Copy)]
pub struct Plane {
pub p0: Point3<f32>,
pub n: Vector3<f32>,
}
impl Plane {
pub fn is_point_above(&self, p1: &Point3<f32>) -> bool {
(p1 - self.p0).dot(&self.n) > 0.
}
pub fn is_point_below(&self, p1: &Point3<f32>) -> bool {
(p1 - self.p0).dot(&self.n) < 0.
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Vertex {
index: u8,
}
impl Vertex {
#[inline]
pub fn is_valid(self) -> bool {
self.index < 8
}
#[inline]
pub fn to_exact(self) -> Exact {
POINTS[self.index as usize]
}
pub fn to_f32(self) -> Point3<f32> {
self.to_exact().0.cast() / 2.
}
#[inline]
pub fn to_u8(self) -> u8 {
self.index
}
#[inline]
pub fn from_u8(byte: u8) -> Self {
let this = Self { index: byte };
assert!(this.is_valid(), "invalid vertex");
this
}
#[inline]
pub fn generator() -> impl Iterator<Item = Self> {
(0..8u8).map(|bits| Self { index: bits })
}
#[inline]
pub fn to_set(self) -> VertexSet {
VertexSet::new() | self
}
}
#[macro_export]
macro_rules! const_vertex_set {
($($val:expr),*) => {
VertexSet {
bits: bitarr![const Lsb0, u8; $($val),*],
}
}
}
#[macro_export]
macro_rules! vertex_set {
($($val:expr),*) => {
VertexSet {
bits: $crate::bitvec::bitarr![$crate::bitvec::order::Lsb0, u8; $($val),*],
}
}
}
pub const FACES: [VertexSet; 6] = {
[
const_vertex_set![0, 0, 0, 0, 1, 1, 1, 1],
const_vertex_set![0, 0, 1, 1, 0, 0, 1, 1],
const_vertex_set![0, 1, 0, 1, 0, 1, 0, 1],
const_vertex_set![1, 1, 1, 1, 0, 0, 0, 0],
const_vertex_set![1, 1, 0, 0, 1, 1, 0, 0],
const_vertex_set![1, 0, 1, 0, 1, 0, 1, 0],
]
};
pub const SYMMETRIES: [VertexSet; 6] = [
const_vertex_set![1, 1, 0, 0, 0, 0, 1, 1],
const_vertex_set![0, 0, 1, 1, 1, 1, 0, 0],
const_vertex_set![1, 0, 0, 1, 1, 0, 0, 1],
const_vertex_set![0, 1, 1, 0, 0, 1, 1, 0],
const_vertex_set![0, 1, 0, 1, 1, 0, 1, 0],
const_vertex_set![1, 0, 1, 0, 0, 1, 0, 1],
];
pub const ANOMALOUS_CONFIGURATIONS: [VertexSet; 8] = [
const_vertex_set![1, 0, 0, 1, 0, 1, 0, 0],
const_vertex_set![1, 0, 0, 1, 0, 0, 1, 0],
const_vertex_set![1, 0, 0, 0, 0, 1, 1, 0],
const_vertex_set![0, 1, 1, 0, 1, 0, 0, 0],
const_vertex_set![0, 1, 1, 0, 0, 0, 0, 1],
const_vertex_set![0, 1, 0, 0, 1, 0, 0, 1],
const_vertex_set![0, 0, 1, 0, 1, 0, 0, 1],
const_vertex_set![0, 0, 0, 1, 0, 1, 1, 0],
];
pub const AXIS_VECTORS: [Vector3<i32>; 6] = [
vector![1, 0, 0],
vector![0, 1, 0],
vector![0, 0, 1],
vector![-1, 0, 0],
vector![0, -1, 0],
vector![0, 0, -1],
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Axis {
PosX = 0,
PosY = 1,
PosZ = 2,
NegX = 3,
NegY = 4,
NegZ = 5,
}
impl Axis {
#[inline]
pub fn from_adjacent_coords(p0: &Point3<i32>, p1: &Point3<i32>) -> Option<Self> {
let v0 = p1 - p0;
AXIS_VECTORS
.iter()
.copied()
.position(|va| v0 == va)
.map(|i| unsafe { Self::from_u8_unchecked(i as u8) })
}
#[inline]
pub fn to_offset(self) -> Vector3<i32> {
AXIS_VECTORS[self.to_index()]
}
#[inline]
pub unsafe fn from_u8_unchecked(byte: u8) -> Self {
std::mem::transmute(byte)
}
#[inline]
pub fn to_face_set(self) -> VertexSet {
FACES[self as usize]
}
#[inline]
pub fn is_parallel(self, other: Axis) -> bool {
self as u8 % 3 == other as u8 % 3
}
#[inline]
pub fn opposite(self) -> Axis {
match self {
Axis::PosX => Axis::NegX,
Axis::PosY => Axis::NegY,
Axis::PosZ => Axis::NegZ,
Axis::NegX => Axis::PosX,
Axis::NegY => Axis::PosY,
Axis::NegZ => Axis::PosZ,
}
}
#[inline]
pub fn is_negative(self) -> bool {
match self {
Axis::PosX => false,
Axis::PosY => false,
Axis::PosZ => false,
Axis::NegX => true,
Axis::NegY => true,
Axis::NegZ => true,
}
}
#[inline]
pub fn is_positive(self) -> bool {
!self.is_negative()
}
#[inline]
pub fn to_face_center(self) -> Exact {
match self {
Axis::PosX => Exact(Point3::new(2, 1, 1)),
Axis::PosY => Exact(Point3::new(1, 2, 1)),
Axis::PosZ => Exact(Point3::new(1, 1, 2)),
Axis::NegX => Exact(Point3::new(0, 1, 1)),
Axis::NegY => Exact(Point3::new(1, 0, 1)),
Axis::NegZ => Exact(Point3::new(1, 1, 0)),
}
}
#[inline]
pub(crate) fn to_bits(self) -> BitArray<Lsb0, [u8; 1]> {
BitArray::new([self as u8])
}
#[inline]
pub fn to_index(self) -> usize {
self as u8 as usize
}
#[inline]
pub fn generator() -> impl Iterator<Item = Self> {
(0u8..6).map(|b| unsafe { Self::from_u8_unchecked(b) })
}
#[inline]
pub fn requires_winding_flip(self) -> bool {
use Axis::*;
match self {
NegX | PosY | NegZ => true,
PosX | NegY | PosZ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VertexSet {
pub bits: BitArray<Lsb0, [u8; 1]>,
}
impl fmt::Display for VertexSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
write!(f, "VertexSet({:?})", self.bits)
} else {
write!(f, "VertexSet([")?;
for (i, v) in self.vertices().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "{}", v.to_f32())?;
}
write!(f, "])")
}
}
}
impl Default for VertexSet {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl VertexSet {
#[inline]
pub fn new() -> Self {
Self {
bits: BitArray::zeroed(),
}
}
#[inline]
pub fn from_u8(byte: u8) -> Self {
Self {
bits: BitArray::new([byte]),
}
}
#[inline]
pub fn len(self) -> usize {
self.bits.count_ones()
}
#[inline]
pub fn contains(self, v: Vertex) -> bool {
*self.bits.get(v.to_u8() as usize).unwrap()
}
#[inline]
pub fn insert(&mut self, vertex: Vertex) {
self.bits.set(vertex.to_u8() as usize, true);
}
#[inline]
pub fn complement(self) -> VertexSet {
Self { bits: !self.bits }
}
#[inline]
pub fn subset_of(self, other: VertexSet) -> bool {
(self & other) == self
}
#[inline]
pub fn is_coplanar(self) -> bool {
let len = self.len();
if len != 4 {
return len < 4;
}
!ANOMALOUS_CONFIGURATIONS.iter().any(|ac| ac.subset_of(self))
}
#[inline]
pub fn is_empty(self) -> bool {
self.bits.not_any()
}
#[inline]
pub fn vertices(&self) -> impl Iterator<Item = Vertex> + '_ {
self.bits.iter_ones().map(|i| Vertex::from_u8(i as u8))
}
#[inline]
pub fn hull_plane(self, subset: VertexSet) -> Option<Plane> {
assert!(
subset.len() >= 3 && subset.is_coplanar() && subset.subset_of(self),
"expected a non-degenerate coplanar subset of this set"
);
let difference = self - subset;
let (p0, n) = {
let mut subset_vs = subset.vertices().take(3).map(Vertex::to_f32);
let p0 = subset_vs.next().unwrap();
let p1 = subset_vs.next().unwrap();
let p2 = subset_vs.next().unwrap();
let v0 = p1 - p0;
let v1 = p2 - p0;
let n = v0.cross(&v1);
(p0, n)
};
let mut difference_vs = difference.vertices().map(Vertex::to_f32);
let first = match difference_vs.next() {
None => return Some(Plane { p0, n }),
Some(v) => v,
};
let sign = (first - p0).dot(&n) > 0.;
difference_vs
.all(|p| ((p - p0).dot(&n) > 0.) == sign)
.then(|| Plane { p0, n })
}
#[inline]
pub fn is_on_hull(self, subset: VertexSet) -> bool {
self.hull_plane(subset).is_some()
}
#[inline]
pub fn to_u8(self) -> u8 {
self.bits.into_inner()[0]
}
#[inline]
pub fn generator() -> impl Iterator<Item = Self> {
(0..=255u8).map(Self::from_u8)
}
#[inline]
pub fn centroid(self) -> Point3<f32> {
let coords = self
.vertices()
.map(Vertex::to_f32)
.map(|p| p.coords)
.sum::<Vector3<f32>>()
/ self.len() as f32;
Point3::from(coords)
}
}
impl BitOr for VertexSet {
type Output = Self;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
Self {
bits: self.bits | rhs.bits,
}
}
}
impl BitAnd for VertexSet {
type Output = Self;
#[inline]
fn bitand(self, rhs: Self) -> Self::Output {
Self {
bits: self.bits & rhs.bits,
}
}
}
impl BitXor for VertexSet {
type Output = Self;
#[inline]
fn bitxor(self, rhs: Self) -> Self::Output {
Self {
bits: self.bits ^ rhs.bits,
}
}
}
impl Not for VertexSet {
type Output = Self;
#[inline]
fn not(self) -> Self::Output {
self.complement()
}
}
impl Sub for VertexSet {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
self & !rhs
}
}
impl BitOrAssign for VertexSet {
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
impl BitAndAssign for VertexSet {
#[inline]
fn bitand_assign(&mut self, rhs: Self) {
*self = *self & rhs;
}
}
impl BitXorAssign for VertexSet {
#[inline]
fn bitxor_assign(&mut self, rhs: Self) {
*self = *self ^ rhs;
}
}
impl SubAssign for VertexSet {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl BitOr<Vertex> for VertexSet {
type Output = VertexSet;
#[inline]
fn bitor(mut self, rhs: Vertex) -> Self::Output {
self.insert(rhs);
self
}
}
impl BitAnd<Vertex> for VertexSet {
type Output = bool;
#[inline]
fn bitand(self, rhs: Vertex) -> Self::Output {
self.contains(rhs)
}
}
impl BitXor<Vertex> for VertexSet {
type Output = VertexSet;
#[inline]
fn bitxor(self, rhs: Vertex) -> Self::Output {
self ^ rhs.to_set()
}
}
impl FromIterator<Vertex> for VertexSet {
#[inline]
fn from_iter<T: IntoIterator<Item = Vertex>>(iter: T) -> Self {
let mut this = VertexSet::new();
for vertex in iter {
this.insert(vertex);
}
this
}
}
impl IntoIterator for VertexSet {
type IntoIter = arrayvec::IntoIter<Vertex, 8>;
type Item = Vertex;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.vertices().collect::<ArrayVec<Vertex, 8>>().into_iter()
}
}
#[derive(Debug, thiserror::Error)]
#[error("cannot construct a valid atom (four or more vertices and enclosing a nonzero volume) from vertex set: {:#}", vertices)]
pub struct DegeneracyError {
pub vertices: VertexSet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Atom {
vertices: VertexSet,
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
write!(f, "Atom({:?})", self.vertices.bits)
} else {
write!(f, "Atom([")?;
for (i, v) in self.vertices.vertices().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "{}", v.to_f32())?;
}
write!(f, "])")
}
}
}
impl Atom {
#[inline]
pub fn new(vertices: VertexSet) -> Self {
Self::try_new(vertices).unwrap()
}
#[inline]
pub fn try_new(vertices: VertexSet) -> Result<Self, DegeneracyError> {
if vertices.is_coplanar() {
Err(DegeneracyError { vertices })
} else {
Ok(Self { vertices })
}
}
#[inline]
pub fn to_set(self) -> VertexSet {
self.vertices
}
#[inline]
pub fn compound_hull(self) -> CompoundHull {
CompoundHull::new(self)
}
#[inline]
pub fn to_u8(self) -> u8 {
self.vertices.to_u8()
}
#[inline]
pub fn generator() -> impl Iterator<Item = Self> {
VertexSet::generator().filter_map(|vs| Self::try_new(vs).ok())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub struct Exact(pub Point3<i32>);
impl Exact {
pub fn to_f32(self) -> Point3<f32> {
self.0.cast::<f32>() / 2.
}
}