use binrw::{BinRead, BinWrite, helpers::until_eof};
#[derive(Debug, Clone, PartialEq, Default, BinRead, BinWrite)]
pub struct MbmhChunk {
#[br(parse_with = until_eof)]
pub entries: Vec<MbmhEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, BinRead, BinWrite)]
pub struct MbmhEntry {
pub map_object_id: u32,
pub texture_id: u32,
pub unknown: u32,
pub mbmi_count: u32,
pub mbnv_count: u32,
pub mbmi_start: u32,
pub mbnv_start: u32,
}
#[derive(Debug, Clone, PartialEq, Default, BinRead, BinWrite)]
pub struct MbbbChunk {
#[br(parse_with = until_eof)]
pub entries: Vec<MbbbEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Default, BinRead, BinWrite)]
pub struct MbbbEntry {
pub map_object_id: u32,
pub min: [f32; 3],
pub max: [f32; 3],
}
#[derive(Debug, Clone, PartialEq, BinRead, BinWrite, Default)]
pub struct MbnvChunk {
#[br(parse_with = until_eof)]
pub vertices: Vec<MbnvVertex>,
}
#[derive(Debug, Clone, Copy, PartialEq, BinRead, BinWrite)]
pub struct MbnvVertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub uv: [f32; 2],
pub color: [[u8; 4]; 3],
}
#[derive(Debug, Clone, PartialEq, Eq, BinRead, BinWrite, Default)]
pub struct MbmiChunk {
#[br(parse_with = until_eof)]
pub indices: Vec<u16>,
}
impl MbmhChunk {
#[must_use]
pub fn count(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn total_vertices(&self) -> u32 {
self.entries.iter().map(|e| e.mbnv_count).sum()
}
#[must_use]
pub fn total_indices(&self) -> u32 {
self.entries.iter().map(|e| e.mbmi_count).sum()
}
}
impl MbbbChunk {
#[must_use]
pub fn count(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl MbbbEntry {
#[must_use]
pub fn center(&self) -> [f32; 3] {
[
(self.min[0] + self.max[0]) / 2.0,
(self.min[1] + self.max[1]) / 2.0,
(self.min[2] + self.max[2]) / 2.0,
]
}
#[must_use]
pub fn size(&self) -> [f32; 3] {
[
self.max[0] - self.min[0],
self.max[1] - self.min[1],
self.max[2] - self.min[2],
]
}
}
impl MbnvChunk {
#[must_use]
pub fn count(&self) -> usize {
self.vertices.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.vertices.is_empty()
}
}
impl MbmiChunk {
#[must_use]
pub fn count(&self) -> usize {
self.indices.len()
}
#[must_use]
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
}
impl Default for MbnvVertex {
fn default() -> Self {
Self {
position: [0.0, 0.0, 0.0],
normal: [0.0, 0.0, 1.0], uv: [0.0, 0.0],
color: [[0, 0, 0, 0]; 3],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use binrw::BinReaderExt;
use std::io::Cursor;
#[test]
fn test_mbmh_entry_size() {
assert_eq!(std::mem::size_of::<MbmhEntry>(), 28);
}
#[test]
fn test_mbmh_parse_single() {
let data: Vec<u8> = vec![
0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let mut cursor = Cursor::new(data);
let mbmh: MbmhChunk = cursor.read_le().unwrap();
assert_eq!(mbmh.count(), 1);
assert_eq!(mbmh.entries[0].map_object_id, 1);
assert_eq!(mbmh.entries[0].texture_id, 10);
assert_eq!(mbmh.entries[0].mbmi_count, 96);
assert_eq!(mbmh.entries[0].mbnv_count, 64);
}
#[test]
fn test_mbmh_round_trip() {
let original = MbmhChunk {
entries: vec![MbmhEntry {
map_object_id: 1,
texture_id: 10,
unknown: 0,
mbmi_count: 96,
mbnv_count: 64,
mbmi_start: 0,
mbnv_start: 0,
}],
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
buffer.set_position(0);
let parsed: MbmhChunk = buffer.read_le().unwrap();
assert_eq!(parsed, original);
}
#[test]
fn test_mbmh_totals() {
let mbmh = MbmhChunk {
entries: vec![
MbmhEntry {
map_object_id: 1,
texture_id: 10,
unknown: 0,
mbmi_count: 96,
mbnv_count: 64,
mbmi_start: 0,
mbnv_start: 0,
},
MbmhEntry {
map_object_id: 2,
texture_id: 11,
unknown: 0,
mbmi_count: 48,
mbnv_count: 32,
mbmi_start: 96,
mbnv_start: 64,
},
],
};
assert_eq!(mbmh.total_vertices(), 96); assert_eq!(mbmh.total_indices(), 144); }
#[test]
fn test_mbbb_entry_size() {
assert_eq!(std::mem::size_of::<MbbbEntry>(), 28);
}
#[test]
fn test_mbbb_parse_single() {
let data: Vec<u8> = vec![
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, ];
let mut cursor = Cursor::new(data);
let mbbb: MbbbChunk = cursor.read_le().unwrap();
assert_eq!(mbbb.count(), 1);
assert_eq!(mbbb.entries[0].map_object_id, 1);
assert_eq!(mbbb.entries[0].min, [0.0, 0.0, 0.0]);
assert_eq!(mbbb.entries[0].max, [1.0, 1.0, 1.0]);
}
#[test]
fn test_mbbb_center_size() {
let entry = MbbbEntry {
map_object_id: 1,
min: [0.0, 0.0, 0.0],
max: [10.0, 20.0, 30.0],
};
assert_eq!(entry.center(), [5.0, 10.0, 15.0]);
assert_eq!(entry.size(), [10.0, 20.0, 30.0]);
}
#[test]
fn test_mbnv_vertex_size() {
assert_eq!(std::mem::size_of::<MbnvVertex>(), 44);
}
#[test]
fn test_mbnv_parse_single() {
let mut data = Vec::new();
data.extend_from_slice(&1.0f32.to_le_bytes());
data.extend_from_slice(&2.0f32.to_le_bytes());
data.extend_from_slice(&3.0f32.to_le_bytes());
data.extend_from_slice(&0.0f32.to_le_bytes());
data.extend_from_slice(&0.0f32.to_le_bytes());
data.extend_from_slice(&1.0f32.to_le_bytes());
data.extend_from_slice(&0.5f32.to_le_bytes());
data.extend_from_slice(&0.5f32.to_le_bytes());
data.extend_from_slice(&[255, 0, 0, 255]); data.extend_from_slice(&[0, 255, 0, 255]); data.extend_from_slice(&[0, 0, 255, 255]);
let mut cursor = Cursor::new(data);
let mbnv: MbnvChunk = cursor.read_le().unwrap();
assert_eq!(mbnv.count(), 1);
assert_eq!(mbnv.vertices[0].position, [1.0, 2.0, 3.0]);
assert_eq!(mbnv.vertices[0].normal, [0.0, 0.0, 1.0]);
assert_eq!(mbnv.vertices[0].uv, [0.5, 0.5]);
assert_eq!(mbnv.vertices[0].color[0], [255, 0, 0, 255]);
}
#[test]
fn test_mbmi_parse() {
let data: Vec<u8> = vec![
0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, ];
let mut cursor = Cursor::new(data);
let mbmi: MbmiChunk = cursor.read_le().unwrap();
assert_eq!(mbmi.count(), 6);
assert_eq!(mbmi.triangle_count(), 2);
assert_eq!(mbmi.indices, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_mbmi_round_trip() {
let original = MbmiChunk {
indices: vec![0, 1, 2, 3, 4, 5],
};
let mut buffer = Cursor::new(Vec::new());
original.write_le(&mut buffer).unwrap();
buffer.set_position(0);
let parsed: MbmiChunk = buffer.read_le().unwrap();
assert_eq!(parsed, original);
}
}