conspire 0.7.7

The Rust interface to conspire.
Documentation
#[cfg(test)]
mod test;

use crate::{
    geometry::{
        Coordinate, Coordinates, Direction, Directions,
        mesh::{
            Connectivity,
            tessellation::{D, Tessellation},
        },
    },
    io::invalid,
    math::{Tensor, TensorVec},
};
use std::{
    cell::OnceCell,
    collections::HashMap,
    fs::File,
    io::{BufReader, Error as ErrorIO, Read, Seek, SeekFrom},
    path::Path,
    str::SplitWhitespace,
};

impl TryFrom<&Path> for Tessellation {
    type Error = ErrorIO;
    fn try_from(path: &Path) -> Result<Self, Self::Error> {
        let mut file = File::open(path)?;
        if let Some(triangle_count) = binary_triangle_count(&mut file)? {
            read_binary(BufReader::new(file), triangle_count)
        } else {
            file.seek(SeekFrom::Start(0))?;
            let mut contents = String::new();
            BufReader::new(file).read_to_string(&mut contents)?;
            read_ascii(&contents)
        }
    }
}

fn binary_triangle_count(file: &mut File) -> Result<Option<usize>, ErrorIO> {
    let length = file.metadata()?.len();
    if length < 84 {
        return Ok(None);
    }
    let mut header = [0u8; 84];
    file.read_exact(&mut header)?;
    let triangle_count = u32::from_le_bytes([header[80], header[81], header[82], header[83]]);
    Ok((84 + 50 * triangle_count as u64 == length).then_some(triangle_count as usize))
}

fn read_binary<R: Read>(mut reader: R, triangle_count: usize) -> Result<Tessellation, ErrorIO> {
    let mut builder = Builder::with_capacity(triangle_count);
    (0..triangle_count).try_for_each(|_| {
        let normal = read_vec3_f32(&mut reader)?;
        let vertices = [
            read_vec3_f32(&mut reader)?,
            read_vec3_f32(&mut reader)?,
            read_vec3_f32(&mut reader)?,
        ];
        let mut attribute_byte_count = [0u8; 2];
        reader.read_exact(&mut attribute_byte_count)?;
        builder.push(normal, vertices);
        Ok::<(), ErrorIO>(())
    })?;
    Ok(builder.into())
}

fn read_ascii(contents: &str) -> Result<Tessellation, ErrorIO> {
    let mut builder = Builder::with_capacity(contents.len() / 200);
    let mut tokens = contents.split_whitespace();
    while let Some(token) = tokens.next() {
        if token == "facet" {
            expect(&mut tokens, "normal")?;
            let normal = read_vec3_ascii(&mut tokens)?;
            expect(&mut tokens, "outer")?;
            expect(&mut tokens, "loop")?;
            let mut vertices = [[0.0; D]; 3];
            vertices.iter_mut().try_for_each(|vertex| {
                expect(&mut tokens, "vertex")?;
                *vertex = read_vec3_ascii(&mut tokens)?;
                Ok::<(), ErrorIO>(())
            })?;
            expect(&mut tokens, "endloop")?;
            expect(&mut tokens, "endfacet")?;
            builder.push(normal, vertices);
        }
    }
    Ok(builder.into())
}

fn expect(tokens: &mut SplitWhitespace, keyword: &str) -> Result<(), ErrorIO> {
    match tokens.next() {
        Some(token) if token.eq_ignore_ascii_case(keyword) => Ok(()),
        token => Err(invalid(format!(
            "Expected {keyword} but found {}",
            token.unwrap_or("end of file")
        ))),
    }
}

fn read_vec3_ascii(tokens: &mut SplitWhitespace) -> Result<[f64; D], ErrorIO> {
    let mut vector = [0.0; D];
    vector.iter_mut().try_for_each(|entry| {
        match tokens.next() {
            Some(token) => {
                *entry = token
                    .parse()
                    .map_err(|_| invalid(format!("Expected a number but found {token}")))?
            }
            None => {
                return Err(invalid(
                    "Expected a number but found end of file".to_string(),
                ));
            }
        }
        Ok(())
    })?;
    Ok(vector)
}

fn read_vec3_f32<R: Read>(reader: &mut R) -> Result<[f64; D], ErrorIO> {
    Ok([read_f32(reader)?, read_f32(reader)?, read_f32(reader)?])
}

fn read_f32<R: Read>(reader: &mut R) -> Result<f64, ErrorIO> {
    let mut bytes = [0u8; 4];
    reader.read_exact(&mut bytes)?;
    Ok(f32::from_le_bytes(bytes) as f64)
}

struct Builder {
    connectivity: Vec<[usize; D]>,
    normals: Directions<D>,
    vertex_map: HashMap<[u64; D], usize>,
    vertices: Coordinates<D>,
}

impl Builder {
    fn with_capacity(triangle_count: usize) -> Self {
        Self {
            connectivity: Vec::with_capacity(triangle_count),
            normals: Directions::with_capacity(triangle_count),
            vertex_map: HashMap::with_capacity(D * triangle_count),
            vertices: Coordinates::with_capacity(D * triangle_count),
        }
    }
    fn push(&mut self, normal: [f64; D], vertices: [[f64; D]; 3]) {
        let nodes = [
            self.dedup(vertices[0]),
            self.dedup(vertices[1]),
            self.dedup(vertices[2]),
        ];
        self.connectivity.push(nodes);
        self.normals.push(Direction::const_from(normal));
    }
    fn dedup(&mut self, vertex: [f64; D]) -> usize {
        let key = vertex.map(|entry| entry.to_bits());
        if let Some(&index) = self.vertex_map.get(&key) {
            index
        } else {
            let index = self.vertices.len();
            self.vertices.push(Coordinate::const_from(vertex));
            self.vertex_map.insert(key, index);
            index
        }
    }
}

impl From<Builder> for Tessellation {
    fn from(builder: Builder) -> Self {
        let Builder {
            connectivity,
            normals,
            vertices,
            ..
        } = builder;
        Self {
            mesh: (
                vec![Connectivity::Triangular(connectivity.into())],
                vertices,
            )
                .into(),
            normals: vec![normals].into(),
            bvh: OnceCell::new(),
            features: OnceCell::new(),
        }
    }
}