use alloc::vec::Vec;
use crate::Real;
pub trait MeshSink {
type Scalar: Real;
fn vertex(&mut self, position: [Self::Scalar; 3], normal: [Self::Scalar; 3]) -> u32;
fn triangle(&mut self, a: u32, b: u32, c: u32);
fn reserve(&mut self, _vertices: usize, _triangles: usize) {}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MeshBuffer<R: Real = f32> {
pub positions: Vec<[R; 3]>,
pub normals: Vec<[R; 3]>,
pub indices: Vec<u32>,
}
impl<R: Real> MeshBuffer<R> {
#[must_use]
pub const fn new() -> Self {
Self {
positions: Vec::new(),
normals: Vec::new(),
indices: Vec::new(),
}
}
#[must_use]
pub fn with_capacity(vertices: usize, triangles: usize) -> Self {
Self {
positions: Vec::with_capacity(vertices),
normals: Vec::with_capacity(vertices),
indices: Vec::with_capacity(triangles * 3),
}
}
pub fn reset(&mut self) {
self.positions.clear();
self.normals.clear();
self.indices.clear();
}
#[must_use]
pub fn vertex_count(&self) -> usize {
self.positions.len()
}
#[must_use]
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
pub fn shrink_to_fit(&mut self) {
self.positions.shrink_to_fit();
self.normals.shrink_to_fit();
self.indices.shrink_to_fit();
}
pub fn append(&mut self, other: &Self) {
let base = self.positions.len() as u32;
self.positions.extend_from_slice(&other.positions);
self.normals.extend_from_slice(&other.normals);
self.indices.extend(other.indices.iter().map(|&i| i + base));
}
}
impl<R: Real> Default for MeshBuffer<R> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<R: Real> MeshSink for MeshBuffer<R> {
type Scalar = R;
#[inline]
fn vertex(&mut self, position: [R; 3], normal: [R; 3]) -> u32 {
let index = self.positions.len();
debug_assert!(
index < u32::MAX as usize,
"MeshBuffer exceeded the u32 index space at {index} vertices"
);
self.positions.push(position);
self.normals.push(normal);
index as u32
}
#[inline]
fn triangle(&mut self, a: u32, b: u32, c: u32) {
self.indices.push(a);
self.indices.push(b);
self.indices.push(c);
}
#[inline]
fn reserve(&mut self, vertices: usize, triangles: usize) {
self.positions.reserve(vertices);
self.normals.reserve(vertices);
self.indices.reserve(triangles * 3);
}
}
impl<T: MeshSink + ?Sized> MeshSink for &mut T {
type Scalar = T::Scalar;
#[inline]
fn vertex(&mut self, position: [Self::Scalar; 3], normal: [Self::Scalar; 3]) -> u32 {
T::vertex(self, position, normal)
}
#[inline]
fn triangle(&mut self, a: u32, b: u32, c: u32) {
T::triangle(self, a, b, c);
}
#[inline]
fn reserve(&mut self, vertices: usize, triangles: usize) {
T::reserve(self, vertices, triangles);
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
fn fill(out: &mut MeshBuffer<f32>, vertices: u32) {
for i in 0..vertices {
let f = i as f32;
out.vertex([f, 0.0, 0.0], [0.0, 1.0, 0.0]);
}
for i in 0..vertices / 3 {
out.triangle(3 * i, 3 * i + 1, 3 * i + 2);
}
}
#[test]
fn reset_preserves_capacity() {
let mut out = MeshBuffer::<f32>::new();
fill(&mut out, 96);
let caps = (
out.positions.capacity(),
out.normals.capacity(),
out.indices.capacity(),
);
let ptrs = (
out.positions.as_ptr(),
out.normals.as_ptr(),
out.indices.as_ptr(),
);
assert!(caps.0 > 0 && caps.1 > 0 && caps.2 > 0);
out.reset();
assert_eq!(out.vertex_count(), 0);
assert_eq!(out.triangle_count(), 0);
assert!(out.is_empty());
assert_eq!(
(
out.positions.capacity(),
out.normals.capacity(),
out.indices.capacity()
),
caps
);
assert_eq!(
(
out.positions.as_ptr(),
out.normals.as_ptr(),
out.indices.as_ptr()
),
ptrs
);
}
#[test]
fn reset_is_idempotent() {
let mut out = MeshBuffer::<f64>::new();
for i in 0..12 {
out.vertex([f64::from(i), 0.0, 0.0], [0.0, 1.0, 0.0]);
}
out.reset();
let cap = out.positions.capacity();
out.reset();
assert_eq!(out.positions.capacity(), cap);
assert_eq!(out.vertex_count(), 0);
}
#[test]
fn reserve_never_shrinks() {
let mut out = MeshBuffer::<f32>::new();
out.reserve(1000, 1000);
let caps = (out.positions.capacity(), out.indices.capacity());
assert!(caps.0 >= 1000 && caps.1 >= 3000);
out.reserve(1, 1);
assert_eq!((out.positions.capacity(), out.indices.capacity()), caps);
}
#[test]
fn mesh_buffer_never_welds() {
let mut out = MeshBuffer::<f32>::new();
let p = [1.0, 2.0, 3.0];
let n = [0.0, 1.0, 0.0];
assert_eq!(out.vertex(p, n), 0);
assert_eq!(out.vertex(p, n), 1);
assert_eq!(out.vertex_count(), 2);
}
#[test]
fn mesh_sink_is_dyn_compatible() {
const _: Option<&mut dyn MeshSink<Scalar = f32>> = None;
let mut out = MeshBuffer::<f32>::new();
let dynamic: &mut dyn MeshSink<Scalar = f32> = &mut out;
dynamic.reserve(4, 1);
let a = dynamic.vertex([0.0; 3], [0.0, 1.0, 0.0]);
assert_eq!(a, 0);
}
#[test]
fn mut_ref_impl_forwards_reserve() {
struct Counting {
inner: MeshBuffer<f32>,
reserves: usize,
}
impl MeshSink for Counting {
type Scalar = f32;
fn vertex(&mut self, p: [f32; 3], n: [f32; 3]) -> u32 {
self.inner.vertex(p, n)
}
fn triangle(&mut self, a: u32, b: u32, c: u32) {
self.inner.triangle(a, b, c);
}
fn reserve(&mut self, v: usize, t: usize) {
self.reserves += 1;
self.inner.reserve(v, t);
}
}
fn through_generic<M: MeshSink>(mut sink: M) {
sink.reserve(64, 32);
}
let mut sink = Counting {
inner: MeshBuffer::new(),
reserves: 0,
};
through_generic(&mut sink);
assert_eq!(sink.reserves, 1);
assert!(sink.inner.positions.capacity() >= 64);
}
#[test]
fn indices_are_flat_triples() {
let mut out = MeshBuffer::<f32>::new();
out.triangle(0, 1, 2);
assert_eq!(out.indices, [0, 1, 2]);
assert_eq!(out.triangle_count(), 1);
}
#[test]
fn shrink_to_fit_is_the_only_release() {
let mut out = MeshBuffer::<f32>::new();
fill(&mut out, 96);
out.reset();
assert!(out.positions.capacity() > 0);
out.shrink_to_fit();
assert_eq!(out.positions.capacity(), 0);
assert_eq!(out.normals.capacity(), 0);
assert_eq!(out.indices.capacity(), 0);
}
}