chair 0.4.0

An efficient binary mesh format which is both small and extremely fast to read.
Documentation
use crate::{
	atlas::*,
	features::*,
	vertex::*
};
#[cfg(feature = "mesh")]
use crate::mesh::*;

use std::io::{Cursor, Read};

use byteorder::*;
use log::*;
use thiserror::*;

#[derive(Debug, Error)]
pub enum Error {
	#[error("failed to read mesh")]
	Io(#[from] std::io::Error),
	#[error("wrong mesh header")]
	WrongHeader([u8; 5]),
	#[error("unknown mesh version {0}")]
	UnknownVersion(u8),
	#[error("mesh contains banned features: {0:?}")]
	BannedFeatures(MeshFeatures),
	#[error("mesh does not contain required features: {0:?}")]
	MissingFeatures(MeshFeatures),
	#[error("non-utf8 texture name #{0}")]
	InvalidTextureName(u8),
	#[error("atlas does not contain texture '{0}'")]
	MissingTexture(String),
	#[error("object {0} uses an unknown texture index")]
	UnknownTexture(u8),
	#[error("unknown colour index {0}")]
	UnknownColour(u16)
}

pub type Result<T> = std::result::Result<T, Error>;

/// Configuration for reading meshes
/// # Examples
/// ```
/// # use chair::*;
/// # struct EmptyAtlas;
/// # impl Atlas for EmptyAtlas { fn get(&self, name: &str) -> Option<&AtlasRegion> { None } }
/// # let atlas = EmptyAtlas;
/// let reader = MeshReader::new::<BasicVertex>(&atlas, None)
/// 	// refuse to load any meshes that can be animated, even though the information would be discarded
/// 	.ban_feature(MeshFeature::Rigged);
/// // Minimal file with no vertices
/// let bytes = b"CHAIR\x00\x00\x00\x00\x00\x00\x00";
/// reader.read::<BasicVertex>(bytes).unwrap();
/// ```
///
pub struct MeshReader<'a, A: Atlas> {
	atlas: &'a A,
	fallback: Option<&'a AtlasRegion>,
	banned: MeshFeatures,
	required: MeshFeatures
}

impl<'a, A: Atlas> MeshReader<'a, A> {
	pub fn new<V: Vertex>(atlas: &'a A, fallback: Option<&str>) -> Self {
		Self {
			atlas,
			fallback: match fallback.map(|name| atlas.get(name)) {
				Some(region) => region,
				None => {
					error!("Atlas does not contain fallback texture, will error out if missing textures exist");
					None
				}
			},
			banned: MeshFeatures::default(),
			// always require features present in the vertex format
			required: V::features()
		}
	}

	pub fn ban_feature(mut self, feature: MeshFeature) -> Self {
		self.banned |= feature;
		self
	}

	pub fn ban_features(mut self, features: MeshFeatures) -> Self {
		self.banned |= features;
		self
	}

	pub fn require_feature(mut self, feature: MeshFeature) -> Self {
		self.required |= feature;
		self
	}

	pub fn require_features(mut self, features: MeshFeatures) -> Self {
		self.required |= features;
		self
	}

	/// Extract features, vertices and indices from file contents
	/// If using wgpu you can use `create` to also upload it to the gpu
	pub fn read<V: Vertex>(&self, bytes: &[u8]) -> Result<(MeshFeatures, Vec<V>, Vec<Index>)> {
		let mut bytes = Cursor::new(bytes);
		let bytes = &mut bytes;
		let mut header = [0; 5];
		bytes.read_exact(&mut header)?;
		if &header != b"CHAIR" {
			return Err(Error::WrongHeader(header));
		}

		let version = bytes.read_u8()?;
		debug!("Mesh version is {version}");
		if version != 0 {
			return Err(Error::UnknownVersion(version));
		}

		let features = bytes.read_u8()?;
		// only safe since there are 8 features - none can be unused
		let features = unsafe {
			MeshFeatures::from_bits_unchecked(features)
		};
		debug!("Mesh features are {features:?}");

		let banned = features & self.banned;
		if !banned.is_empty() {
			return Err(Error::BannedFeatures(banned));
		}

		let missing = self.required & !features;
		if !missing.is_empty() {
			return Err(Error::MissingFeatures(missing));
		}

		let colours = if features.contains(MeshFeature::Coloured) {
			let len = bytes.read_u16::<LE>()?;
			debug!("Have {len} colours");
			(0..len)
				.map(|_| {
					Ok([chan(bytes)?, chan(bytes)?, chan(bytes)?, chan(bytes)?])
				})
				.collect::<Result<_>>()?
		} else {
			vec![]
		};

		let textures = if features.contains(MeshFeature::Textured) {
			let len = bytes.read_u8()?;
			debug!("Have {len} textures");
			(0..len)
				.map(|n| {
					let len = bytes.read_u8()?;
					let mut data = vec![0; len as usize];
					bytes.read_exact(&mut data)?;
					String::from_utf8(data)
						.map_err(|_| Error::InvalidTextureName(n))
				})
				.map(|name| match name {
					Ok(name) => match self.atlas.get(&name) {
						Some(region) => Ok(region),
						None => {
							warn!("Mesh uses missing texture {name}");
							if let Some(fallback) = self.fallback {
								Ok(fallback)
							} else {
								Err(Error::MissingTexture(name))
							}
						}
					},
					Err(e) => Err(e)
				})
				.collect::<Result<_>>()?
		} else {
			vec![]
		};

		let read_pos_comp = if features.contains(MeshFeature::ShortPos) {
			read_pos_short
		} else {
			read_pos_long
		};

		let read_col_index = if colours.len() > 255 {
			read_col_u16
		} else {
			read_col_u8
		};

		let read_normal = if features.contains(MeshFeature::ShortNormals) {
			read_normal_short
		} else {
			read_f32
		};

		let read_uv = if features.contains(MeshFeature::ShortUvs) {
			read_uv_short
		} else {
			read_f32
		};

/*		let skeleton = if features.contains(MeshFeature::Rigged) {
			// TODO: read skeleton
			let len = bytes.read_u8()?;
			(0..len)
				.map(|_| bytes.read_f32()?)
				.collect()
		} else {
			vec![]
		};

		let read_bone_indices = if skeleton.len() < 3 {
			|| {
				let bits = bytes.read_u8()?;
				[
					(bits & 0b11) as u32,
					(bits & 0b0000_1100) as u32]
			}
		} else {
			|| ()
		};*/

		// number of textured objects
		let object_count = bytes.read_u8()?;
		debug!("Have {object_count} objects");
		let vertices = (0..object_count)
			.flat_map(|n| {
				let vertex_count = bytes.read_u32::<LE>()?;
				debug!("Object {n} has {vertex_count} vertices");

				// allow going outside 0.0 to 1.0 range by mapping positions
				let (scale, offset) = if features.contains(MeshFeature::ShortPos) {
					// 0 to 128 precision as opposed to 0 to 255, which cannot be divided evenly
					// divided here for efficiency
					([
						bytes.read_f32::<LE>()? / 128.0,
						bytes.read_f32::<LE>()? / 128.0,
						bytes.read_f32::<LE>()? / 128.0
					], [
						bytes.read_f32::<LE>()?,
						bytes.read_f32::<LE>()?,
						bytes.read_f32::<LE>()?
					])
				} else {
					([1.0; 3], [0.0; 3])
				};
				debug!("Scale is {scale:?}, offset is {offset:?}");

				let region = if features.contains(MeshFeature::Textured) {
					textures.get(bytes.read_u8()? as usize)
						.ok_or_else(|| Error::UnknownTexture(n))?
				} else {
					&AtlasRegion {
						xy: [0.0; 2],
						x2y: [0.0; 2],
						x2y2: [0.0; 2],
						xy2: [0.0; 2]
					}
				};

				(0..vertex_count).map(|_| {
					let pos = [
						read_pos_comp(bytes, scale[0], offset[0])?,
						read_pos_comp(bytes, scale[1], offset[1])?,
						read_pos_comp(bytes, scale[2], offset[2])?
					];
					let col = if features.contains(MeshFeature::Coloured) {
						let index = read_col_index(bytes)?;
						colours.get(index as usize).copied()
							.ok_or_else(|| Error::UnknownColour(index))?
					} else {
						[1.0; 4]
					};
					let normals = if features.contains(MeshFeature::Normals) {
						[
							read_normal(bytes)?,
							read_normal(bytes)?,
							read_normal(bytes)?
						]
					} else {
						[0.0; 3]
					};
					let uvs = if features.contains(MeshFeature::Textured) {
						region.map_uvs(read_uv(bytes)?, read_uv(bytes)?)
					} else {
						[0.0; 2]
					};
/*					let (bones, weights) = if features.contains(MeshFeature::Rigged) {
						let bones = read_bone_indices()?;
						let weights = read_weights()?;
						(bones, weights)
					} else {
						([0; 4], [0.0; 4]
					};*/
					Ok(V::new(pos, col, normals, uvs, /*bones, weights*/))
				}).collect::<Result<Vec<_>>>()
			})
			.flatten()
			.collect::<Vec<V>>();

		// use smallest index size that can hold all vertices
		let read_index = if vertices.len() > 65535 {
			read_index_u32
		} else if vertices.len() > 255 {
			read_index_u16
		} else {
			read_index_u8
		};

		let index_count = bytes.read_u32::<LE>()?;
		debug!("Have {index_count} indices");
		let indices = (0..index_count)
			.map(|_| read_index(bytes))
			.collect::<Result<_>>()?;

		Ok((features, vertices, indices))
	}

	/// Read and create a mesh on the GPU
	#[cfg(feature = "mesh")]
	pub fn create<V: Vertex>(&self, device: &wgpu::Device, bytes: &[u8]) -> Result<Mesh> {
		let (_features, vertices, indices) = self.read::<V>(bytes)?;
		Ok(Mesh::new(device, &vertices, &indices))
	}
}

fn read_pos_short(bytes: &mut Cursor<&[u8]>, scale: f32, offset: f32) -> Result<f32> {
	Ok(bytes.read_u8()? as f32 * scale + offset)
}
fn read_pos_long(bytes: &mut Cursor<&[u8]>, _: f32, _: f32) -> Result<f32> {
	read_f32(bytes)
}

fn chan(bytes: &mut Cursor<&[u8]>) -> Result<f32> {
	Ok(bytes.read_u8()? as f32 / 255.0)
}

fn read_col_u8(bytes: &mut Cursor<&[u8]>) -> Result<u16> {
	Ok(bytes.read_u8()? as u16)
}
fn read_col_u16(bytes: &mut Cursor<&[u8]>) -> Result<u16> {
	Ok(bytes.read_u16::<LE>()?)
}

fn read_normal_short(bytes: &mut Cursor<&[u8]>) -> Result<f32> {
	Ok(NORMAL[bytes.read_u8()? as usize])
}
fn read_f32(bytes: &mut Cursor<&[u8]>) -> Result<f32> {
	Ok(bytes.read_f32::<LE>()?)
}

fn read_uv_short(bytes: &mut Cursor<&[u8]>) -> Result<f32> {
	// 128 instead of 255 to be able to evenly divide
	Ok(bytes.read_u8()? as f32 / 128.0)
}

fn read_index_u32(bytes: &mut Cursor<&[u8]>) -> Result<Index> {
	Ok(bytes.read_u32::<LE>()?)
}
fn read_index_u16(bytes: &mut Cursor<&[u8]>) -> Result<Index> {
	Ok(bytes.read_u16::<LE>()? as Index)
}
fn read_index_u8(bytes: &mut Cursor<&[u8]>) -> Result<Index> {
	Ok(bytes.read_u8()? as Index)
}

const NORMAL: [f32; 256] = include!(concat!(env!("OUT_DIR"), "/normals.rs"));