grana-object 0.1.1

Next-generation microscopy image visualization and analysis platform
Documentation
use std::f32::consts::TAU;

use glam::Vec3;

use crate::MeshVertex;

pub const TUBE_SIDES: usize = 8;

const SPHERE_RINGS: usize = 4;
const EPSILON: f32 = 1.0e-12;

pub fn push_tube(
    vertices: &mut Vec<MeshVertex>,
    indices: &mut Vec<u32>,
    points: &[[f32; 3]],
    radii: &[f32],
    object: u32,
) {
    assert!(points.len() >= 2, "a tube follows at least two nodes");
    assert_eq!(
        points.len(),
        radii.len(),
        "a tube carries one radius per node"
    );
    assert!(
        points
            .iter()
            .flatten()
            .chain(radii)
            .all(|value| value.is_finite()),
        "a tube is finite"
    );
    assert!(
        radii.iter().all(|radius| *radius > 0.0),
        "a tube radius is a positive length"
    );
    assert!(
        points
            .windows(2)
            .all(|pair| { Vec3::from(pair[1]).distance_squared(Vec3::from(pair[0])) > EPSILON }),
        "a tube follows distinct nodes"
    );
    let points = points
        .iter()
        .map(|point| Vec3::from(*point))
        .collect::<Vec<_>>();
    let tangents = tangents(&points);
    let mut frame = basis(tangents[0]);
    let mut rings = Vec::with_capacity(points.len());
    for (index, point) in points.iter().enumerate() {
        if index > 0 {
            frame = transport(frame, tangents[index - 1], tangents[index]);
        }
        let base = vertices.len() as u32;
        for side in 0..TUBE_SIDES {
            let angle = TAU * side as f32 / TUBE_SIDES as f32;
            let normal = frame.0 * angle.cos() + frame.1 * angle.sin();
            vertices.push(MeshVertex::new(
                (*point + normal * radii[index]).to_array(),
                normal.to_array(),
                object,
            ));
        }
        rings.push(base);
    }
    for pair in rings.windows(2) {
        connect(indices, pair[0], pair[1]);
    }
    let start = cap(vertices, points[0], -tangents[0], radii[0], object);
    for side in 0..TUBE_SIDES as u32 {
        let next = (side + 1) % TUBE_SIDES as u32;
        indices.extend_from_slice(&[start, rings[0] + next, rings[0] + side]);
    }
    let last = points.len() - 1;
    let end = cap(vertices, points[last], tangents[last], radii[last], object);
    for side in 0..TUBE_SIDES as u32 {
        let next = (side + 1) % TUBE_SIDES as u32;
        indices.extend_from_slice(&[end, rings[last] + side, rings[last] + next]);
    }
}

pub fn push_sphere(
    vertices: &mut Vec<MeshVertex>,
    indices: &mut Vec<u32>,
    center: [f32; 3],
    radius: f32,
    object: u32,
) {
    assert!(
        center.iter().all(|value| value.is_finite()),
        "a sphere center is finite"
    );
    assert!(
        radius.is_finite() && radius > 0.0,
        "a sphere radius is a positive length"
    );
    let center = Vec3::from(center);
    let north = vertices.len() as u32;
    vertices.push(MeshVertex::new(
        (center + Vec3::Z * radius).to_array(),
        Vec3::Z.to_array(),
        object,
    ));
    let mut rings = Vec::with_capacity(SPHERE_RINGS - 1);
    for ring in 1..SPHERE_RINGS {
        let polar = std::f32::consts::PI * ring as f32 / SPHERE_RINGS as f32;
        let base = vertices.len() as u32;
        for side in 0..TUBE_SIDES {
            let azimuth = TAU * side as f32 / TUBE_SIDES as f32;
            let normal = Vec3::new(
                polar.sin() * azimuth.cos(),
                polar.sin() * azimuth.sin(),
                polar.cos(),
            );
            vertices.push(MeshVertex::new(
                (center + normal * radius).to_array(),
                normal.to_array(),
                object,
            ));
        }
        rings.push(base);
    }
    let south = vertices.len() as u32;
    vertices.push(MeshVertex::new(
        (center - Vec3::Z * radius).to_array(),
        (-Vec3::Z).to_array(),
        object,
    ));
    for side in 0..TUBE_SIDES as u32 {
        let next = (side + 1) % TUBE_SIDES as u32;
        indices.extend_from_slice(&[north, rings[0] + side, rings[0] + next]);
    }
    for pair in rings.windows(2) {
        connect(indices, pair[0], pair[1]);
    }
    let last = rings.len() - 1;
    for side in 0..TUBE_SIDES as u32 {
        let next = (side + 1) % TUBE_SIDES as u32;
        indices.extend_from_slice(&[south, rings[last] + next, rings[last] + side]);
    }
}

fn connect(indices: &mut Vec<u32>, lower: u32, upper: u32) {
    for side in 0..TUBE_SIDES as u32 {
        let next = (side + 1) % TUBE_SIDES as u32;
        indices.extend_from_slice(&[
            lower + side,
            upper + side,
            upper + next,
            lower + side,
            upper + next,
            lower + next,
        ]);
    }
}

fn cap(vertices: &mut Vec<MeshVertex>, point: Vec3, normal: Vec3, radius: f32, object: u32) -> u32 {
    let index = vertices.len() as u32;
    vertices.push(MeshVertex::new(
        (point + normal * radius).to_array(),
        normal.to_array(),
        object,
    ));
    index
}

fn tangents(points: &[Vec3]) -> Vec<Vec3> {
    let mut tangents = Vec::with_capacity(points.len());
    for (index, point) in points.iter().enumerate() {
        let direction = if index == 0 {
            points[1] - *point
        } else if index + 1 == points.len() {
            *point - points[index - 1]
        } else {
            points[index + 1] - points[index - 1]
        };
        let tangent = if direction.length_squared() > EPSILON {
            direction.normalize()
        } else if index > 0 {
            tangents[index - 1]
        } else {
            Vec3::Z
        };
        tangents.push(tangent);
    }
    tangents
}

fn basis(tangent: Vec3) -> (Vec3, Vec3) {
    let helper = if tangent.x.abs() < 0.9 {
        Vec3::X
    } else {
        Vec3::Y
    };
    let u = tangent.cross(helper).normalize();
    (u, tangent.cross(u))
}

fn transport(frame: (Vec3, Vec3), from: Vec3, to: Vec3) -> (Vec3, Vec3) {
    let axis = from.cross(to);
    let sine = axis.length();
    let rotated = if sine > 1.0e-6 {
        rotate(
            frame.0,
            axis / sine,
            sine.atan2(from.dot(to).clamp(-1.0, 1.0)),
        )
    } else {
        frame.0
    };
    let projected = rotated - to * rotated.dot(to);
    let u = if projected.length_squared() > EPSILON {
        projected.normalize()
    } else {
        basis(to).0
    };
    (u, to.cross(u))
}

fn rotate(vector: Vec3, axis: Vec3, angle: f32) -> Vec3 {
    let (sine, cosine) = angle.sin_cos();
    vector * cosine + axis.cross(vector) * sine + axis * axis.dot(vector) * (1.0 - cosine)
}