grana-object 0.1.1

Next-generation microscopy image visualization and analysis platform
Documentation
use grana_object::{Mesh, MeshVertex, PackedMesh};

#[test]
fn packs_objects_with_bounded_error() {
    let mesh = mesh(&[8, 8]);
    let raw = std::mem::size_of_val(mesh.vertices()) + std::mem::size_of_val(mesh.indices());
    let packed = PackedMesh::new(mesh.clone());
    assert_eq!(packed.object_count(), 2);
    assert_eq!(packed.vertex_count(), mesh.vertex_count());
    assert_eq!(packed.triangle_count(), mesh.triangle_count());
    assert!(packed.wide().is_empty());
    assert!(packed.bytes() < raw as u64);
    assert_geometry(&mesh, &packed);
}

#[test]
fn widens_objects_above_the_narrow_index_range() {
    let mesh = mesh(&[70_000]);
    let packed = PackedMesh::new(mesh.clone());
    assert!(
        packed.narrow().is_empty(),
        "a wide object uses wide indices"
    );
    assert!(!packed.wide().is_empty());
    assert_geometry(&mesh, &packed);
}

fn assert_geometry(source: &Mesh, packed: &PackedMesh) {
    assert_eq!(packed.vertex_count(), source.vertex_count());
    assert_eq!(packed.triangle_count(), source.triangle_count());
    let objects = source
        .vertices()
        .iter()
        .map(|vertex| vertex.object())
        .max()
        .map_or(0, |object| object as usize + 1);
    let mut low = vec![[f32::INFINITY; 3]; objects];
    let mut high = vec![[f32::NEG_INFINITY; 3]; objects];
    for vertex in source.vertices() {
        let object = vertex.object() as usize;
        for axis in 0..3 {
            low[object][axis] = low[object][axis].min(vertex.position()[axis]);
            high[object][axis] = high[object][axis].max(vertex.position()[axis]);
        }
    }
    let tolerance =
        |object: usize, axis: usize| (high[object][axis] - low[object][axis]) / 65535.0 + 1.0e-6;
    for object in 0..objects {
        let triangles = (0..source.triangle_count())
            .map(|index| source.triangle(index))
            .filter(|triangle| triangle[0].object() as usize == object)
            .collect::<Vec<_>>();
        assert_eq!(
            triangles.len(),
            packed.groups()[object].index_count() as usize / 3,
            "every source triangle is packed into its object"
        );
        for (index, triangle) in triangles.iter().enumerate() {
            let decoded = packed.triangle(object, index);
            for (corner, position) in decoded.iter().enumerate() {
                for (axis, value) in triangle[corner].position().iter().enumerate() {
                    let offset = (value - position[axis]).abs();
                    assert!(
                        offset <= tolerance(object, axis),
                        "a packed triangle drifts by {offset}"
                    );
                }
            }
        }
    }
}

fn mesh(counts: &[usize]) -> Mesh {
    let mut vertices = Vec::new();
    let mut indices = Vec::new();
    for (object, count) in counts.iter().copied().enumerate() {
        let base = vertices.len() as u32;
        for step in 0..count {
            vertices.push(MeshVertex::new(
                [step as f32, object as f32, 0.0],
                [0.0, 0.0, 1.0],
                object as u32,
            ));
        }
        for step in 0..count - 2 {
            let first = base + step as u32;
            indices.extend([first, first + 1, first + 2]);
        }
    }
    Mesh::new(vertices, indices)
}