use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use he3d_math::Vec3;
use crate::aabb::Aabb;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct StaticMeshId {
index: u32,
generation: u32,
}
impl StaticMeshId {
pub(crate) const fn new(index: u32, generation: u32) -> Self {
Self { index, generation }
}
#[must_use]
pub const fn index(self) -> u32 {
self.index
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
}
#[derive(Clone, Debug)]
pub(crate) struct StaticMesh {
pub(crate) positions: Vec<Vec3>,
pub(crate) triangle_bounds: Vec<Aabb>,
pub(crate) welded: Vec<[u32; 3]>,
pub(crate) bounds: Aabb,
}
impl StaticMesh {
pub(crate) fn triangle_count(&self) -> usize {
self.triangle_bounds.len()
}
pub(crate) fn triangle(&self, index: usize) -> [Vec3; 3] {
let base = index * 3;
[
self.positions[base],
self.positions[base + 1],
self.positions[base + 2],
]
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct StaticTopology {
welded: BTreeMap<[u32; 3], u32>,
vertex_use: BTreeMap<u32, u32>,
edge_use: BTreeMap<(u32, u32), u32>,
next_vertex: u32,
}
impl StaticTopology {
pub(crate) fn weld(&mut self, position: Vec3) -> u32 {
let key = position_key(position);
if let Some(&id) = self.welded.get(&key) {
return id;
}
let id = self.next_vertex;
self.next_vertex = self.next_vertex.wrapping_add(1);
self.welded.insert(key, id);
id
}
pub(crate) fn add_triangle(&mut self, welded: [u32; 3]) {
for vertex in welded {
*self.vertex_use.entry(vertex).or_insert(0) += 1;
}
for edge in edges_of(welded) {
*self.edge_use.entry(edge).or_insert(0) += 1;
}
}
pub(crate) fn remove_triangle(&mut self, welded: [u32; 3]) {
for vertex in welded {
if let Some(count) = self.vertex_use.get_mut(&vertex) {
*count = count.saturating_sub(1);
if *count == 0 {
self.vertex_use.remove(&vertex);
}
}
}
for edge in edges_of(welded) {
if let Some(count) = self.edge_use.get_mut(&edge) {
*count = count.saturating_sub(1);
if *count == 0 {
self.edge_use.remove(&edge);
}
}
}
}
pub(crate) fn is_internal_edge(&self, first: u32, second: u32) -> bool {
let key = if first <= second {
(first, second)
} else {
(second, first)
};
self.edge_use.get(&key).copied().unwrap_or(0) >= 2
}
#[cfg(test)]
pub(crate) fn vertex_count(&self) -> usize {
self.welded.len()
}
}
fn edges_of(welded: [u32; 3]) -> [(u32, u32); 3] {
let ordered = |first: u32, second: u32| {
if first <= second {
(first, second)
} else {
(second, first)
}
};
[
ordered(welded[0], welded[1]),
ordered(welded[1], welded[2]),
ordered(welded[2], welded[0]),
]
}
fn position_key(position: Vec3) -> [u32; 3] {
let canonical = |value: f32| {
if value == 0.0 {
0.0_f32.to_bits()
} else {
value.to_bits()
}
};
[
canonical(position.x),
canonical(position.y),
canonical(position.z),
]
}
#[cfg(test)]
mod tests {
use super::StaticTopology;
use he3d_math::Vec3;
#[test]
fn identical_positions_weld_to_one_vertex() {
let mut topology = StaticTopology::default();
let first = topology.weld(Vec3::new(1.0, 2.0, 3.0));
let second = topology.weld(Vec3::new(1.0, 2.0, 3.0));
assert_eq!(first, second);
assert_eq!(topology.vertex_count(), 1);
}
#[test]
fn negative_zero_welds_with_positive_zero() {
let mut topology = StaticTopology::default();
assert_eq!(
topology.weld(Vec3::new(0.0, 0.0, 0.0)),
topology.weld(Vec3::new(-0.0, -0.0, -0.0))
);
}
#[test]
fn an_edge_shared_by_two_triangles_is_internal() {
let mut topology = StaticTopology::default();
topology.add_triangle([0, 1, 2]);
assert!(
!topology.is_internal_edge(1, 2),
"one triangle is a boundary"
);
topology.add_triangle([1, 3, 2]);
assert!(topology.is_internal_edge(1, 2));
assert!(!topology.is_internal_edge(0, 1));
}
#[test]
fn edges_weld_across_separate_meshes() {
let mut topology = StaticTopology::default();
topology.add_triangle([0, 1, 2]);
topology.add_triangle([1, 3, 2]);
assert!(
topology.is_internal_edge(2, 1),
"the order of the pair must not matter"
);
}
#[test]
fn removing_a_triangle_releases_its_edges() {
let mut topology = StaticTopology::default();
topology.add_triangle([0, 1, 2]);
topology.add_triangle([1, 3, 2]);
assert!(topology.is_internal_edge(1, 2));
topology.remove_triangle([1, 3, 2]);
assert!(!topology.is_internal_edge(1, 2));
}
#[test]
fn welding_is_stable_across_add_and_remove() {
let mut topology = StaticTopology::default();
let id = topology.weld(Vec3::X);
topology.add_triangle([id, id, id]);
topology.remove_triangle([id, id, id]);
assert_eq!(topology.weld(Vec3::X), id);
assert_eq!(topology.vertex_count(), 1);
}
}