use std::fmt;
use arrayvec::ArrayVec;
use bitvec::prelude::*;
use nalgebra::*;
use crate::{Atom, Axis, Exact, Vertex, VertexSet, ANOMALOUS_CONFIGURATIONS, SYMMETRIES};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Face {
bits: BitArray<Lsb0, [u8; 1]>,
}
impl Face {
#[inline]
pub fn new(vertices: VertexSet, axis: Axis) -> Self {
let face_set = axis.to_face_set();
let mut face_verts = BitArray::<Lsb0, [u8; 1]>::zeroed();
for (i, v) in face_set.vertices().enumerate() {
face_verts.set(i, vertices.contains(v));
}
let mut bits = BitArray::zeroed();
if face_verts.count_ones() == 4 {
bits.set_all(true);
} else if face_verts.count_ones() == 3 {
let unset = face_verts.first_zero().unwrap();
match unset {
0 => bits[0..4].copy_from_bitslice(bits![Lsb0, u8; 0, 0, 1, 1]),
1 => bits[0..4].copy_from_bitslice(bits![Lsb0, u8; 0, 1, 1, 0]),
2 => bits[0..4].copy_from_bitslice(bits![Lsb0, u8; 1, 0, 0, 1]),
3 => bits[0..4].copy_from_bitslice(bits![Lsb0, u8; 1, 1, 0, 0]),
_ => unreachable!(),
}
}
bits[4..7].copy_from_bitslice(&axis.to_bits()[0..3]);
Self { bits }
}
#[inline]
pub fn empty(axis: Axis) -> Self {
let mut bits = BitArray::zeroed();
bits[4..7].copy_from_bitslice(&axis.to_bits()[0..3]);
Self { bits }
}
#[inline]
pub fn axis(self) -> Axis {
let mut axis_bits = BitArray::zeroed();
axis_bits[0..3].copy_from_bitslice(&self.bits[4..7]);
unsafe { Axis::from_u8_unchecked(axis_bits.into_inner()) }
}
#[inline]
pub fn join(&mut self, other: &mut Self) {
assert!(
self.axis() == other.axis().opposite(),
"cannot join faces with non-opposing axes!"
);
let mut xord = self.bits;
xord[0..4] ^= other.bits[0..4].iter().by_val();
self.bits[0..4] &= xord[0..4].iter().by_val();
other.bits[0..4] &= xord[0..4].iter().by_val();
}
#[inline]
pub fn facets(self) -> impl Iterator<Item = HullFacet> {
let index = self.bits.into_inner()[0] & 0b1111;
let axis = self.axis();
let mut face_verts: ArrayVec<Vertex, 4> = axis.to_face_set().into_iter().collect();
face_verts.swap(0, 1);
let center = axis.to_face_center();
let square = || {
let mut verts = face_verts
.clone()
.into_inner()
.unwrap()
.map(Vertex::to_exact);
if self.axis().requires_winding_flip() {
verts.swap(1, 3);
}
HullFacet::Rectangle(verts)
};
let beeg = |start_facet: usize| {
let v1 = start_facet;
let v2 = (v1 + 1) & 0b11;
let v3 = (v2 + 1) & 0b11;
if self.axis().requires_winding_flip() {
HullFacet::Triangle(
[face_verts[v2], face_verts[v1], face_verts[v3]].map(Vertex::to_exact),
)
} else {
HullFacet::Triangle(
[face_verts[v1], face_verts[v2], face_verts[v3]].map(Vertex::to_exact),
)
}
};
let smol = |facet: usize| {
let v1 = facet;
let v2 = (v1 + 1) & 0b11;
if self.axis().requires_winding_flip() {
HullFacet::Triangle([center, face_verts[v2].to_exact(), face_verts[v1].to_exact()])
} else {
HullFacet::Triangle([center, face_verts[v1].to_exact(), face_verts[v2].to_exact()])
}
};
let av = |slice: &[HullFacet]| slice.iter().copied().collect::<ArrayVec<HullFacet, 2>>();
let facets = match index {
0b0000 => av(&[]),
0b0001 => av(&[smol(0)]),
0b0010 => av(&[smol(1)]),
0b0011 => av(&[beeg(0)]),
0b0100 => av(&[smol(2)]),
0b0101 => av(&[smol(0), smol(2)]),
0b0110 => av(&[beeg(1)]),
0b0111 => av(&[beeg(0), smol(2)]),
0b1000 => av(&[smol(3)]),
0b1001 => av(&[beeg(3)]),
0b1010 => av(&[smol(1), smol(3)]),
0b1011 => av(&[beeg(0), smol(3)]),
0b1100 => av(&[beeg(2)]),
0b1101 => av(&[beeg(2), smol(0)]),
0b1110 => av(&[beeg(1), smol(3)]),
0b1111 => av(&[square()]),
_ => unreachable!(),
};
facets.into_iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct InteriorHull {
facets: ArrayVec<HullFacet, 4>,
}
impl InteriorHull {
#[inline]
pub fn new(atom: Atom) -> Self {
generate_interior_hull(atom.vertices)
}
#[inline]
pub fn facets(&self) -> impl Iterator<Item = HullFacet> {
self.facets.clone().into_iter()
}
}
#[inline]
fn generate_interior_hull(atom: VertexSet) -> InteriorHull {
let mut facets = ArrayVec::new();
let centroid = atom.centroid();
for intersection in SYMMETRIES
.into_iter()
.chain(ANOMALOUS_CONFIGURATIONS)
.map(|configuration| configuration & atom)
.filter(|intersection| intersection.len() >= 3)
{
if intersection.len() > 2 && atom.is_on_hull(intersection) {
let mut facet = HullFacet::from_vertex_set(intersection);
facet.match_winding_to_centroid(¢roid);
facets.push(facet);
} else if intersection.len() == 4 {
let mut vs = intersection.vertices();
let tris: [Vertex; 4] = [
vs.next().unwrap(),
vs.next().unwrap(),
vs.next().unwrap(),
vs.next().unwrap(),
];
let tri_lists = [
[tris[0], tris[1], tris[2]],
[tris[0], tris[1], tris[3]],
[tris[0], tris[2], tris[3]],
[tris[1], tris[2], tris[3]],
];
for tri_list in tri_lists {
let tri_set = tri_list.into_iter().collect();
if atom.is_on_hull(tri_set) {
let mut facet = HullFacet::from_vertex_set(tri_set);
facet.match_winding_to_centroid(¢roid);
facets.push(facet);
break;
}
}
}
}
InteriorHull { facets }
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExteriorHull {
faces: ArrayVec<Face, 6>,
}
impl ExteriorHull {
#[inline]
pub fn new(atom: Atom) -> Self {
let faces = Axis::generator()
.map(|axis| Face::new(atom.to_set(), axis))
.collect();
Self { faces }
}
#[inline]
pub fn join(&mut self, axis: Axis, other: &mut ExteriorHull) {
let self_axis = axis;
let other_axis = axis.opposite();
let self_face = &mut self.faces[self_axis.to_index()];
let other_face = &mut other.faces[other_axis.to_index()];
self_face.join(other_face);
}
#[inline]
pub fn facets(&self) -> impl Iterator<Item = HullFacet> + '_ {
self.faces.iter().copied().flat_map(Face::facets)
}
#[inline]
pub fn set_face(&mut self, face: Face) {
self.faces[face.axis().to_index()] = face;
}
#[inline]
pub fn face(&self, axis: Axis) -> &Face {
&self.faces[axis.to_index()]
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct CompoundHull {
interior: InteriorHull,
exterior: ExteriorHull,
}
impl CompoundHull {
#[inline]
pub fn new(atom: Atom) -> Self {
Self {
interior: InteriorHull::new(atom),
exterior: ExteriorHull::new(atom),
}
}
#[inline]
pub fn interior(&self) -> &InteriorHull {
&self.interior
}
#[inline]
pub fn exterior(&self) -> &ExteriorHull {
&self.exterior
}
#[inline]
pub fn exterior_mut(&mut self) -> &mut ExteriorHull {
&mut self.exterior
}
#[inline]
pub fn join_exteriors(&mut self, axis: Axis, other: &mut Self) {
self.exterior.join(axis, &mut other.exterior)
}
#[inline]
pub fn facets(&self) -> impl Iterator<Item = HullFacet> + '_ {
self.interior.facets().chain(self.exterior.facets())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub enum HullFacet {
Triangle([Exact; 3]),
Rectangle([Exact; 4]),
}
impl fmt::Debug for HullFacet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Triangle([a, b, c]) => write!(f, "Triangle([{}, {}, {}])", a.0, b.0, c.0),
Self::Rectangle([a, b, c, d]) => {
write!(f, "Rectangle([{}, {}, {}, {}])", a.0, b.0, c.0, d.0)
}
}
}
}
impl HullFacet {
#[inline]
pub fn normal(self) -> UnitVector3<f32> {
let (Self::Triangle([a, b, c]) | Self::Rectangle([a, b, c, _])) = self;
let (p0, p1, p2) = (a.to_f32(), b.to_f32(), c.to_f32());
let v01 = p1 - p0;
let v02 = p2 - p0;
UnitVector3::new_normalize(v01.cross(&v02))
}
#[inline]
pub fn is_normal_outwards_with_respect_to_point(self, p0: &Point3<f32>) -> bool {
let (Self::Triangle([a, _, _]) | Self::Rectangle([a, _, _, _])) = self;
let p1 = a.to_f32();
let n = self.normal();
let v = p1 - p0;
n.dot(&v) > 0.
}
#[inline]
pub fn match_winding_to_centroid(&mut self, centroid: &Point3<f32>) {
if !self.is_normal_outwards_with_respect_to_point(centroid) {
self.flip_winding();
}
}
#[inline]
pub fn flip_winding(&mut self) {
match self {
Self::Triangle([a, b, _]) => std::mem::swap(a, b),
Self::Rectangle([_, b, _, d]) => std::mem::swap(b, d),
}
}
#[inline]
pub fn from_vertex_set(vs: VertexSet) -> Self {
match vs.len() {
3 => HullFacet::Triangle(
vs.vertices()
.map(Vertex::to_exact)
.collect::<ArrayVec<_, 3>>()
.into_inner()
.unwrap(),
),
4 => {
let mut array = vs
.vertices()
.map(Vertex::to_exact)
.collect::<ArrayVec<_, 4>>()
.into_inner()
.unwrap();
array.swap(0, 1);
HullFacet::Rectangle(array)
}
_ => unreachable!("all facets must have 3 or 4 vertices!"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{vertex_set, FACES};
use maplit::hashset;
use std::collections::HashSet;
fn hull(vertex_set: VertexSet) -> CompoundHull {
Atom::new(vertex_set).compound_hull()
}
macro_rules! bad_atoms {
($($name:ident, $set:expr;)*) => {$(
#[test]
#[should_panic]
fn $name() {
Atom::new($set);
}
)*};
}
bad_atoms! {
bad_atom_f0, FACES[0];
bad_atom_f1, FACES[1];
bad_atom_f2, FACES[2];
bad_atom_f3, FACES[3];
bad_atom_f4, FACES[4];
bad_atom_f5, FACES[5];
bad_atom_s0, SYMMETRIES[0];
bad_atom_s1, SYMMETRIES[1];
bad_atom_s2, SYMMETRIES[2];
bad_atom_s3, SYMMETRIES[3];
bad_atom_s4, SYMMETRIES[4];
bad_atom_s5, SYMMETRIES[5];
bad_atom_a0, ANOMALOUS_CONFIGURATIONS[0];
bad_atom_a1, ANOMALOUS_CONFIGURATIONS[1];
bad_atom_a2, ANOMALOUS_CONFIGURATIONS[2];
bad_atom_a3, ANOMALOUS_CONFIGURATIONS[3];
bad_atom_a4, ANOMALOUS_CONFIGURATIONS[4];
bad_atom_a5, ANOMALOUS_CONFIGURATIONS[5];
bad_atom_a6, ANOMALOUS_CONFIGURATIONS[6];
bad_atom_a7, ANOMALOUS_CONFIGURATIONS[7];
}
#[test]
fn kyube() {
assert_eq!(
hull(vertex_set![1, 1, 1, 1, 1, 1, 1, 1])
.interior()
.facets()
.count(),
0
);
assert_eq!(
hull(vertex_set![1, 1, 1, 1, 1, 1, 1, 1])
.exterior()
.facets()
.count(),
6
);
}
#[test]
fn tetrahedron() {
let hull = hull(vertex_set![0, 1, 0, 0, 1, 1, 0, 1]);
assert_eq!(hull.interior().facets().count(), 1);
assert_eq!(hull.exterior().facets().count(), 3,);
}
#[test]
fn fuckin_weird_dude() {
let atom = Atom::new(vertex_set![0, 1, 1, 0, 1, 0, 0, 1]);
let hull = atom.compound_hull();
println!("atom: {:#}", atom.to_set());
for axis in Axis::generator() {
println!(
"axis: {:?}, set: {:#}",
axis,
atom.to_set() & axis.to_face_set()
);
}
for face in &hull.exterior().faces {
println!("axis/face: {:?}/{:?}", face.axis(), face);
for facet in face.facets() {
println!("\tfacet: {:?}", facet);
}
}
println!("interior facets:");
for facet in hull.interior().facets() {
println!("\tfacet: {:?}", facet);
}
assert_eq!(hull.interior().facets().count(), 4);
assert_eq!(hull.exterior().facets().count(), 0);
}
#[test]
fn join_m() {
fn v(n: u8) -> Exact {
Vertex::from_u8(n).to_exact()
}
let nz = Atom::new(vertex_set![1, 1, 0, 0, 1, 1, 1, 1]);
let pz = Atom::new(vertex_set![1, 1, 1, 1, 1, 1, 0, 0]);
let mut nz_hull = nz.compound_hull();
let mut pz_hull = pz.compound_hull();
nz_hull.join_exteriors(Axis::PosZ, &mut pz_hull);
assert_eq!(nz_hull.interior().facets().count(), 1);
assert_eq!(pz_hull.interior().facets().count(), 1);
assert_eq!(nz_hull.exterior().facets().count(), 4);
assert_eq!(pz_hull.exterior().facets().count(), 4);
let nz_ext_facet_set = nz_hull.exterior().facets().collect::<HashSet<HullFacet>>();
let pz_center = Exact(Point3::new(1, 1, 2));
assert_eq!(
nz_ext_facet_set,
hashset! {
HullFacet::Triangle([v(4), v(0), v(6)]),
HullFacet::Triangle([pz_center, v(5), v(7)]),
HullFacet::Rectangle([v(1), v(0), v(4), v(5)]),
HullFacet::Rectangle([v(5), v(4), v(6), v(7)]),
}
);
let pz_ext_facet_set = pz_hull.exterior().facets().collect::<HashSet<HullFacet>>();
let nz_center = Exact(Point3::new(1, 1, 0));
assert_eq!(
pz_ext_facet_set,
hashset! {
HullFacet::Triangle([v(3), v(1), v(5)]),
HullFacet::Triangle([nz_center, v(0), v(2)]),
HullFacet::Rectangle([v(1), v(3), v(2), v(0)]),
HullFacet::Rectangle([v(1), v(0), v(4), v(5)]),
}
);
}
#[test]
fn n_valid_atoms() {
assert_eq!(Atom::generator().count(), 127);
}
#[test]
fn interior_winding() {
for (_i, atom) in Atom::generator().enumerate() {
let centroid = atom.to_set().centroid();
for (_j, facet) in atom.compound_hull().interior().facets().enumerate() {
assert!(facet.is_normal_outwards_with_respect_to_point(¢roid));
}
}
}
#[test]
fn exterior_winding() {
for (_i, atom) in Atom::generator().enumerate() {
let centroid = atom.to_set().centroid();
for (_j, facet) in atom.compound_hull().exterior().facets().enumerate() {
assert!(facet.is_normal_outwards_with_respect_to_point(¢roid));
}
}
}
}