use crate::inflate_bytes;
use crate::packing::{Float16, Vec3PackedSnorm};
use binrw::{BinRead, Endian};
use std::{
error::Error,
io::{Cursor, Read, Seek, SeekFrom},
};
pub const SHAPE_MID_DAT: &str =
concat!(env!("CARGO_WORKSPACE_DIR"), "/resources_here/ShapeMid.dat");
enum AttributeType {
Position = 0, Normal = 1, Uv = 2, Tangent = 3, Param = 4, }
#[derive(Debug, Clone)]
pub struct ShapeMesh {
pub positions: Vec<[Float16; 4]>,
pub indices: Vec<u16>,
pub normals: Option<Vec<Vec3PackedSnorm>>,
pub uvs: Option<Vec<[Float16; 2]>>,
pub color_params: Option<Vec<u8>>,
}
impl BinRead for ShapeMesh {
type Args<'a> = ResourceShapeAttribute;
fn read_options<R: Read + Seek>(
reader: &mut R,
endian: Endian,
args: Self::Args<'_>,
) -> binrw::BinResult<Self> {
const PER_VERTEX_SIZE: u32 = 8;
const PER_INDEX_SIZE: u32 = 2;
reader.seek(SeekFrom::Start(u64::from(
args.attr_offset[AttributeType::Position as usize],
)))?;
let vertex_count = args.attr_size[AttributeType::Position as usize] / PER_VERTEX_SIZE;
let mut positions = vec![];
for _vertex in 0..vertex_count {
positions.push({
let positions = <[Float16; 4]>::read_options(reader, endian, ())?;
positions
});
}
reader.seek(SeekFrom::Start(u64::from(args.index_offset)))?;
let index_count = args.index_size / PER_INDEX_SIZE;
let mut indices = vec![];
for _index in 0..index_count {
let value = <u16>::read_options(reader, endian, ())?;
indices.push(value);
}
let normals = if args.is_valid_attribute(AttributeType::Normal) {
reader.seek(SeekFrom::Start(u64::from(
args.attr_offset[AttributeType::Normal as usize],
)))?;
let mut normals = vec![];
for _vertex in 0..vertex_count {
let packed = <Vec3PackedSnorm>::read_options(reader, endian, ())?;
normals.push(packed);
}
Some(normals)
} else {
None
};
let uvs = if args.is_valid_attribute(AttributeType::Uv) {
reader.seek(SeekFrom::Start(u64::from(
args.attr_offset[AttributeType::Uv as usize],
)))?;
let mut uvs = vec![];
for _vertex in 0..vertex_count {
uvs.push(<[Float16; 2]>::read_options(reader, endian, ())?);
}
Some(uvs)
} else {
None
};
let color_params = if args.is_valid_attribute(AttributeType::Param) {
reader.seek(SeekFrom::Start(u64::from(
args.attr_offset[AttributeType::Param as usize],
)))?;
let color_count = args.attr_size[AttributeType::Param as usize] / PER_VERTEX_SIZE;
let mut color_params = vec![];
for _color in 0..color_count {
color_params.push(<u8>::read_options(reader, endian, ())?);
}
Some(color_params)
} else {
None
};
Ok(ShapeMesh {
positions,
indices,
normals,
uvs,
color_params,
})
}
}
#[derive(BinRead, Debug, Clone, Copy)]
pub struct ResourceCommonAttribute {
pub offset: u32,
pub size: u32,
pub size_compressed: u32,
pub compression_level: u8,
pub memory_level: u8,
pad: u16,
}
#[derive(BinRead, Default, Debug, Clone, Copy)]
pub struct ResourceShapeAttribute {
pub attr_offset: [u32; 5],
pub attr_size: [u32; 5],
pub index_offset: u32,
pub index_size: u32,
pub bounding_box: [[f32; 3]; 2],
}
impl ResourceShapeAttribute {
fn is_valid_attribute(&self, attr_type: AttributeType) -> bool {
self.attr_size[attr_type as usize] != 0
}
}
#[derive(BinRead, Debug, Clone, Copy)]
pub struct ShapeElement {
pub common: ResourceCommonAttribute,
pub shape: ResourceShapeAttribute,
}
impl ShapeElement {
pub fn mesh(&mut self, file: &[u8]) -> Result<ShapeMesh, Box<dyn Error>> {
let start: usize = self.common.offset as usize;
let end: usize = self.common.offset as usize + self.common.size_compressed as usize;
let range = start..end;
let shape_data = inflate_bytes(&file[range])?;
let data =
ShapeMesh::read_options(&mut Cursor::new(shape_data), Endian::Little, self.shape)?;
Ok(data)
}
#[cfg(feature = "gltf")]
pub fn gltf(&mut self, file: &[u8]) -> Result<GltfBuilder, Box<dyn Error>> {
let data = self.mesh(file)?;
Ok(data.gltf(self.shape.bounding_box))
}
}
#[derive(BinRead, Debug, Clone, Copy)]
pub struct ResourceShapeHairTransform {
front_translate: [f32; 3],
front_rotate: [f32; 3],
side_translate: [f32; 3],
side_rotate: [f32; 3],
top_translate: [f32; 3],
top_rotate: [f32; 3],
}
#[derive(BinRead, Debug, Clone, Copy)]
pub struct ResourceShapeFacelineTransform {
pub hair_translate: [f32; 3],
pub nose_translate: [f32; 3],
pub beard_translate: [f32; 3],
}
#[derive(Clone, Copy, Debug)]
pub enum Shape {
Beard,
FaceLine,
Mask,
HatNormal,
HatCap,
ForeheadNormal,
ForeheadCap,
HairNormal,
HairCap,
Glasses,
Nose,
NoseLine,
HairTransform,
FaceLineTransform,
}
#[derive(Clone, Copy)]
pub enum GenericResourceShape {
Element(ShapeElement),
HairTransform(ResourceShapeHairTransform),
FaceLineTransform(ResourceShapeFacelineTransform),
}
#[derive(BinRead, Debug, Clone, Copy)]
#[br(little, magic = b"NFSR")]
pub struct ResourceShape {
ver: u32,
file_size: u32,
max_size: [u32; 12],
max_alignment: [u32; 12],
pub beard: [ShapeElement; 4],
pub face_line: [ShapeElement; 12],
pub mask: [ShapeElement; 12],
pub hat_normal: [ShapeElement; 132],
pub hat_cap: [ShapeElement; 132],
pub forehead_normal: [ShapeElement; 132],
pub forehead_cap: [ShapeElement; 132],
pub hair_normal: [ShapeElement; 132],
pub hair_cap: [ShapeElement; 132],
pub glasses: [ShapeElement; 1],
pub nose: [ShapeElement; 18],
pub nose_line: [ShapeElement; 18],
pub hair_transform: [ResourceShapeHairTransform; 132],
pub face_line_transform: [ResourceShapeFacelineTransform; 12],
}
impl ResourceShape {
#[allow(clippy::must_use_candidate)]
pub fn index_by_shape(&self, shape: Shape, index: usize) -> Option<GenericResourceShape> {
let shape_el = |x: &ShapeElement| GenericResourceShape::Element(*x);
let hair_t = |x: &ResourceShapeHairTransform| GenericResourceShape::HairTransform(*x);
let fl_t = |x: &ResourceShapeFacelineTransform| GenericResourceShape::FaceLineTransform(*x);
match shape {
Shape::Beard => self.beard.get(index).map(shape_el),
Shape::FaceLine => self.face_line.get(index).map(shape_el),
Shape::Mask => self.mask.get(index).map(shape_el),
Shape::HatNormal => self.hat_normal.get(index).map(shape_el),
Shape::HatCap => self.hat_cap.get(index).map(shape_el),
Shape::ForeheadNormal => self.forehead_normal.get(index).map(shape_el),
Shape::ForeheadCap => self.forehead_cap.get(index).map(shape_el),
Shape::HairNormal => self.hair_normal.get(index).map(shape_el),
Shape::HairCap => self.hair_cap.get(index).map(shape_el),
Shape::Glasses => self.glasses.get(index).map(shape_el),
Shape::Nose => self.nose.get(index).map(shape_el),
Shape::NoseLine => self.nose_line.get(index).map(shape_el),
Shape::HairTransform => self.hair_transform.get(index).map(hair_t),
Shape::FaceLineTransform => self.face_line_transform.get(index).map(fl_t),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use half::f16;
use std::{error::Error, fs::File, io::BufReader};
type R = Result<(), Box<dyn Error>>;
#[test]
fn read() -> R {
let mut bin = BufReader::new(File::open(SHAPE_MID_DAT)?);
let _ = ResourceShape::read(&mut bin)?;
Ok(())
}
#[test]
fn u16_to_f16_to_f32() {
let within_tolerance = (1.0 - f16::from_bits(15360).to_f32()).abs() < 1.0;
assert!(within_tolerance);
}
}