use super::texture::Textures;
use super::{blocks, dictionary, fx16, fx32};
use crate::Result;
use crate::io::Reader;
use crate::media::{Rgba, bgr555};
#[derive(Debug, Clone, PartialEq)]
pub struct Nsbmd {
pub models: Vec<Model>,
pub textures: Option<Textures>,
}
impl Nsbmd {
pub fn parse(file: &[u8]) -> Result<Self> {
let file = Reader::new("NSBMD", file);
let mut nsbmd = Self {
models: Vec::new(),
textures: None,
};
for (magic, offset) in blocks(&file, b"BMD0")? {
let block = super::block(&file, "NSBMD", offset)?;
match &magic {
b"MDL0" => {
for (name, entry) in dictionary(&block, 8)? {
let at = Reader::new("NSBMD", entry).u32()? as usize;
let len = block.u32_at(at)? as usize;
let model = Reader::new("NSBMD model", block.slice_at(at, len)?);
nsbmd.models.push(Model::parse(name, &model)?);
}
}
b"TEX0" => nsbmd.textures = Some(Textures::from_block(&block)?),
_ => {}
}
}
Ok(nsbmd)
}
}
pub(super) type Matrix = [[f32; 4]; 4];
pub(super) const IDENTITY: Matrix = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
#[derive(Debug, Clone, PartialEq)]
pub struct Model {
pub name: String,
pub nodes: Vec<Node>,
pub materials: Vec<Material>,
pub shapes: Vec<Shape>,
pub scale: f32,
pub inverse_scale: f32,
pub bounding_box: ([f32; 3], [f32; 3]),
pub(super) commands: Vec<u8>,
pub(super) inverse_binds: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub name: String,
pub transform: Matrix,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Material {
pub name: String,
pub texture: Option<String>,
pub palette: Option<String>,
pub diffuse: Rgba,
pub ambient: Rgba,
pub emission: Rgba,
pub diffuse_is_vertex_color: bool,
pub lights: u8,
pub alpha: u8,
pub front: bool,
pub back: bool,
pub repeat: [bool; 2],
pub mirror: [bool; 2],
pub texture_size: [u16; 2],
pub texture_scale: [f32; 2],
pub texture_translation: [f32; 2],
}
impl Material {
pub fn texture_name(&self) -> &str {
self.texture.as_deref().unwrap_or(&self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shape {
pub name: String,
pub(super) commands: Vec<u8>,
}
impl Model {
fn parse(name: String, model: &Reader) -> Result<Self> {
let offset = |at| -> Result<usize> { Ok(model.u32_at(at)? as usize) };
let (commands, materials, shapes, inverse_binds) =
(offset(0x04)?, offset(0x08)?, offset(0x0C)?, offset(0x10)?);
let box_scale = fx32(model.u32_at(0x38)?);
let corner = |at| -> Result<[f32; 3]> {
let value =
|i: usize| Ok::<_, crate::Error>(fx16(model.u16_at(at + 2 * i)?) * box_scale);
Ok([value(0)?, value(1)?, value(2)?])
};
let nodes = dictionary(model, 0x40)?
.into_iter()
.map(|(name, entry)| {
let at = 0x40 + Reader::new("NSBMD", entry).u32()? as usize;
Ok(Node {
name,
transform: node_transform(model, at)?,
})
})
.collect::<Result<_>>()?;
let shapes = dictionary(model, shapes)?
.into_iter()
.map(|(name, entry)| {
let at = shapes + Reader::new("NSBMD", entry).u32()? as usize;
let list = at + model.u32_at(at + 8)? as usize;
let len = model.u32_at(at + 12)? as usize;
Ok(Shape {
name,
commands: model.slice_at(list, len)?.to_vec(),
})
})
.collect::<Result<_>>()?;
Ok(Self {
name,
nodes,
materials: parse_materials(model, materials)?,
shapes,
scale: fx32(model.u32_at(0x1C)?),
inverse_scale: fx32(model.u32_at(0x20)?),
bounding_box: (corner(0x2C)?, corner(0x32)?),
commands: model
.slice_at(commands, model.len().saturating_sub(commands))?
.to_vec(),
inverse_binds: model
.slice_at(inverse_binds, model.len().saturating_sub(inverse_binds))?
.to_vec(),
})
}
}
fn node_transform(model: &Reader, at: usize) -> Result<Matrix> {
let mut node = model.clone();
node.seek(at)?;
let flags = node.u16()?;
let first = fx16(node.u16()?);
let translation = match flags & 1 {
0 => fx32s(&mut node, 3)?,
_ => vec![0.0; 3],
};
let mut rotation = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
if flags & 2 == 0 {
if flags & 8 != 0 {
const REST: [[usize; 4]; 9] = [
[4, 5, 7, 8],
[3, 5, 6, 8],
[3, 4, 6, 7],
[1, 2, 7, 8],
[0, 2, 6, 8],
[0, 1, 6, 7],
[1, 2, 4, 5],
[0, 2, 3, 5],
[0, 1, 3, 4],
];
let pivot = (flags >> 4 & 15) as usize;
let rest = REST
.get(pivot)
.ok_or_else(|| model.error_at(at, "pivot index out of range"))?;
let (a, b) = (fx16(node.u16()?), fx16(node.u16()?));
let sign = |bit: u16| if flags >> bit & 1 != 0 { -1.0 } else { 1.0 };
rotation = [0.0; 9];
rotation[pivot] = sign(8);
rotation[rest[0]] = a;
rotation[rest[1]] = b;
rotation[rest[2]] = sign(9) * b;
rotation[rest[3]] = sign(10) * a;
} else {
rotation[0] = first;
for value in &mut rotation[1..] {
*value = fx16(node.u16()?);
}
}
}
let scale = match flags & 4 {
0 => fx32s(&mut node, 3)?,
_ => vec![1.0; 3],
};
let mut matrix = IDENTITY;
for row in 0..3 {
for column in 0..3 {
matrix[row][column] = rotation[column * 3 + row] * scale[column];
}
matrix[row][3] = translation[row];
}
Ok(matrix)
}
fn parse_materials(model: &Reader, at: usize) -> Result<Vec<Material>> {
let mut materials = dictionary(model, at + 4)?
.into_iter()
.map(|(name, entry)| {
parse_material(
model,
at + Reader::new("NSBMD", entry).u32()? as usize,
name,
)
})
.collect::<Result<Vec<_>>>()?;
for (dictionary_at, palette) in [(model.u16_at(at)?, false), (model.u16_at(at + 2)?, true)] {
for (name, entry) in dictionary(model, at + dictionary_at as usize)? {
let entry = Reader::new("NSBMD", entry);
let users = model.slice_at(at + entry.u16_at(0)? as usize, entry.u8_at(2)? as usize)?;
for &user in users {
let material = materials
.get_mut(user as usize)
.ok_or_else(|| model.error_at(at, "texture bound to a missing material"))?;
match palette {
false => material.texture = Some(name.clone()),
true => material.palette = Some(name.clone()),
}
}
}
}
Ok(materials)
}
fn parse_material(model: &Reader, at: usize, name: String) -> Result<Material> {
let colors = model.u32_at(at + 0x04)?;
let shine = model.u32_at(at + 0x08)?;
let polygon = model.u32_at(at + 0x0C)?;
let texture = model.u32_at(at + 0x14)?;
let flags = model.u16_at(at + 0x1E)?;
let mut matrix = model.clone();
matrix.seek(at + 0x2C)?;
let scale = match flags & 2 {
0 => fx32s(&mut matrix, 2)?,
_ => vec![1.0; 2],
};
if flags & 4 == 0 {
matrix.skip(4)?; }
let translation = match flags & 8 {
0 => fx32s(&mut matrix, 2)?,
_ => vec![0.0; 2],
};
let used = flags & 1 != 0;
Ok(Material {
name,
texture: None,
palette: None,
diffuse: bgr555(colors as u16),
ambient: bgr555((colors >> 16) as u16),
emission: bgr555((shine >> 16) as u16),
diffuse_is_vertex_color: colors >> 15 & 1 != 0,
lights: (polygon & 15) as u8,
alpha: (polygon >> 16 & 31) as u8,
front: polygon >> 7 & 1 != 0,
back: polygon >> 6 & 1 != 0,
repeat: [texture >> 16 & 1 != 0, texture >> 17 & 1 != 0],
mirror: [texture >> 18 & 1 != 0, texture >> 19 & 1 != 0],
texture_size: [model.u16_at(at + 0x20)?, model.u16_at(at + 0x22)?],
texture_scale: if used { [scale[0], scale[1]] } else { [1.0; 2] },
texture_translation: if used {
[translation[0], translation[1]]
} else {
[0.0; 2]
},
})
}
fn fx32s(reader: &mut Reader, count: usize) -> Result<Vec<f32>> {
(0..count).map(|_| reader.u32().map(fx32)).collect()
}