renderer.rs

3.0 kB · rust · 89 lines

1use super::faces::quads;2use super::Cell3d;3use crate::math::cell::push_glyph;4use std::collections::HashMap;56/// Renders the cube as rows of glyphs, plane after plane, or of digits where no glyph is mapped.7///8/// ```9/// let cell = mrlyrs::math::three::carpet(3, 1).unwrap();10/// let rows = mrlyrs::math::three::text(&cell, None);11/// assert_eq!(rows.len(), 9);12/// assert_eq!(&rows[..3], ["111", "101", "111"]);13/// ```14pub fn text(cell: &Cell3d, glyphs: Option<&HashMap<u8, String>>) -> Vec<String> {15    let types = cell.types();16    let (depth, height, width) = (types.shape[0], types.shape[1], types.shape[2]);17    let mut rows = Vec::with_capacity(depth * height);18    for z in 0..depth {19        for y in 0..height {20            let mut row = String::with_capacity(width);21            for x in 0..width {22                let at = (z * height + y) * width + x;23                push_glyph(&mut row, types.at(at) as u8, glyphs);24            }25            rows.push(row);26        }27    }28    rows29}3031/// Writes the cube's exposed quads as a Wavefront OBJ, one shared vertex per corner.32///33/// ```34/// let obj = mrlyrs::math::three::to_obj(&mrlyrs::math::three::carpet(3, 1).unwrap());35/// assert_eq!(obj.lines().filter(|line| line.starts_with("v ")).count(), 64);36/// assert_eq!(obj.lines().filter(|line| line.starts_with("f ")).count(), 72);37/// ```38pub fn to_obj(cell: &Cell3d) -> String {39    let shape = &cell.types().shape;40    let side = shape[0].max(shape[1]).max(shape[2]) as f32;41    let half = side / 2.0;42    let whole = |v: f32| (v * half + half).round() as i64;43    let mut corners: Vec<[i64; 3]> = Vec::new();44    let mut seen: HashMap<[i64; 3], usize> = HashMap::new();45    let mut faces: Vec<[usize; 4]> = Vec::new();46    for quad in quads(cell) {47        let mut face = [0usize; 4];48        for (slot, vert) in quad.verts.iter().enumerate() {49            let key = [whole(vert.x), whole(vert.y), whole(vert.z)];50            let next = seen.len() + 1;51            face[slot] = *seen.entry(key).or_insert_with(|| {52                corners.push(key);53                next54            });55        }56        faces.push(face);57    }58    let mut out = String::new();59    for corner in &corners {60        out.push_str(&format!("v {} {} {}\n", corner[0], corner[1], corner[2]));61    }62    for face in &faces {63        out.push_str(&format!(64            "f {} {} {} {}\n",65            face[0], face[1], face[2], face[3]66        ));67    }68    out69}7071#[cfg(test)]72mod tests {73    use super::*;74    use crate::math::three::designs;75    #[test]76    fn text_walks_every_plane_of_the_sponge() {77        let rows = text(&designs::carpet(3, 1).unwrap(), None);78        assert_eq!(rows.len(), 9);79        assert_eq!(&rows[..3], ["111", "101", "111"]);80    }8182    #[test]83    fn the_sponge_writes_its_obj() {84        let obj = to_obj(&designs::carpet(3, 1).unwrap());85        assert_eq!(obj.lines().filter(|l| l.starts_with("v ")).count(), 64);86        assert_eq!(obj.lines().filter(|l| l.starts_with("f ")).count(), 72);87        assert_eq!(obj.len(), 1481);88    }89}