use binrw::{BinRead, BinWrite};
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
#[brw(little)]
pub struct VertexNormal {
pub x: i8,
pub z: i8,
pub y: i8,
}
impl VertexNormal {
pub fn to_normalized(&self) -> [f32; 3] {
[
f32::from(self.x) / 127.0,
f32::from(self.y) / 127.0, f32::from(self.z) / 127.0, ]
}
pub fn from_normalized(normal: [f32; 3]) -> Self {
Self {
x: (normal[0] * 127.0) as i8,
z: (normal[2] * 127.0) as i8, y: (normal[1] * 127.0) as i8, }
}
}
#[derive(Debug, Clone, BinRead, BinWrite)]
#[brw(little)]
pub struct McnrChunk {
#[br(count = 145)]
pub normals: Vec<VertexNormal>,
#[br(parse_with = binrw::helpers::until_eof)]
pub padding: Vec<u8>,
}
impl Default for McnrChunk {
fn default() -> Self {
Self {
normals: vec![VertexNormal { x: 0, y: 127, z: 0 }; 145], padding: vec![0; 13],
}
}
}
impl McnrChunk {
pub const NORMAL_COUNT: usize = 145;
pub const SIZE_BYTES: usize = 448;
pub fn get_outer_normal(&self, x: usize, y: usize) -> Option<VertexNormal> {
if x >= 9 || y >= 9 {
return None;
}
let index = y * 9 + x;
self.normals.get(index).copied()
}
pub fn get_inner_normal(&self, x: usize, y: usize) -> Option<VertexNormal> {
if x >= 8 || y >= 8 {
return None;
}
let index = 81 + (y * 8 + x);
self.normals.get(index).copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
use binrw::{BinReaderExt, BinWriterExt};
use std::io::Cursor;
#[test]
fn vertex_normal_to_normalized() {
let normal = VertexNormal {
x: 127,
y: 127,
z: 0,
};
let normalized = normal.to_normalized();
assert!((normalized[0] - 1.0).abs() < 0.01);
assert!((normalized[1] - 1.0).abs() < 0.01);
assert!(normalized[2].abs() < 0.01);
}
#[test]
fn vertex_normal_from_normalized() {
let normal = VertexNormal::from_normalized([1.0, 0.0, -1.0]);
assert_eq!(normal.x, 127);
assert_eq!(normal.y, 0);
assert_eq!(normal.z, -127);
}
#[test]
fn vertex_normal_round_trip() {
let original = [0.5, -0.5, 0.707];
let normal = VertexNormal::from_normalized(original);
let normalized = normal.to_normalized();
assert!((normalized[0] - original[0]).abs() < 0.02);
assert!((normalized[1] - original[1]).abs() < 0.02);
assert!((normalized[2] - original[2]).abs() < 0.02);
}
#[test]
fn vertex_normal_coordinate_swap() {
let normal = VertexNormal {
x: 10,
z: 20,
y: 30,
};
let normalized = normal.to_normalized();
assert_eq!((normalized[0] * 127.0) as i8, 10);
assert_eq!((normalized[1] * 127.0) as i8, 30);
assert_eq!((normalized[2] * 127.0) as i8, 20);
}
#[test]
fn parse_mcnr_chunk() {
let mut data = Vec::new();
for _ in 0..145 {
data.extend_from_slice(&[0i8 as u8, 0, 127]); }
data.extend_from_slice(&[0u8; 13]);
assert_eq!(data.len(), 448);
let mut cursor = Cursor::new(&data);
let chunk: McnrChunk = cursor.read_le().unwrap();
assert_eq!(chunk.normals.len(), 145);
assert_eq!(chunk.padding.len(), 13);
let first = chunk.normals[0];
assert_eq!(first.x, 0);
assert_eq!(first.z, 0);
assert_eq!(first.y, 127);
}
#[test]
fn get_outer_normal() {
let chunk = McnrChunk::default();
let normal = chunk.get_outer_normal(0, 0);
assert!(normal.is_some());
assert!(chunk.get_outer_normal(8, 8).is_some()); assert!(chunk.get_outer_normal(9, 0).is_none()); assert!(chunk.get_outer_normal(0, 9).is_none()); }
#[test]
fn get_inner_normal() {
let chunk = McnrChunk::default();
let normal = chunk.get_inner_normal(0, 0);
assert!(normal.is_some());
let first_inner = chunk.normals[81];
let accessed = chunk.get_inner_normal(0, 0).unwrap();
assert_eq!(accessed.x, first_inner.x);
assert_eq!(accessed.y, first_inner.y);
assert_eq!(accessed.z, first_inner.z);
assert!(chunk.get_inner_normal(7, 7).is_some()); assert!(chunk.get_inner_normal(8, 0).is_none()); assert!(chunk.get_inner_normal(0, 8).is_none()); }
#[test]
fn outer_grid_indexing() {
let chunk = McnrChunk::default();
assert_eq!(chunk.normals[0].x, chunk.get_outer_normal(0, 0).unwrap().x);
assert_eq!(chunk.normals[8].x, chunk.get_outer_normal(8, 0).unwrap().x);
assert_eq!(chunk.normals[9].x, chunk.get_outer_normal(0, 1).unwrap().x);
assert_eq!(chunk.normals[80].x, chunk.get_outer_normal(8, 8).unwrap().x);
}
#[test]
fn inner_grid_indexing() {
let chunk = McnrChunk::default();
assert_eq!(chunk.normals[81].x, chunk.get_inner_normal(0, 0).unwrap().x);
assert_eq!(chunk.normals[88].x, chunk.get_inner_normal(7, 0).unwrap().x);
assert_eq!(chunk.normals[89].x, chunk.get_inner_normal(0, 1).unwrap().x);
assert_eq!(
chunk.normals[144].x,
chunk.get_inner_normal(7, 7).unwrap().x
);
}
#[test]
fn default_chunk() {
let chunk = McnrChunk::default();
assert_eq!(chunk.normals.len(), 145);
assert_eq!(chunk.padding.len(), 13);
for normal in &chunk.normals {
assert_eq!(normal.x, 0);
assert_eq!(normal.z, 0);
assert_eq!(normal.y, 127);
}
assert!(chunk.padding.iter().all(|&b| b == 0));
}
#[test]
fn round_trip_serialization() {
let mut original = McnrChunk::default();
original.normals[0] = VertexNormal {
x: 10,
y: 20,
z: 30,
};
original.normals[80] = VertexNormal {
x: -50,
y: 60,
z: -70,
};
original.normals[144] = VertexNormal {
x: 100,
y: -100,
z: 50,
};
let mut buffer = Cursor::new(Vec::new());
buffer.write_le(&original).unwrap();
buffer.set_position(0);
let parsed: McnrChunk = buffer.read_le().unwrap();
assert_eq!(parsed.normals.len(), original.normals.len());
assert_eq!(parsed.padding.len(), original.padding.len());
for (i, (orig, parsed)) in original
.normals
.iter()
.zip(parsed.normals.iter())
.enumerate()
{
assert_eq!(orig.x, parsed.x, "Mismatch at index {i}");
assert_eq!(orig.y, parsed.y, "Mismatch at index {i}");
assert_eq!(orig.z, parsed.z, "Mismatch at index {i}");
}
}
#[test]
fn size_validation() {
let chunk = McnrChunk::default();
let mut buffer = Cursor::new(Vec::new());
buffer.write_le(&chunk).unwrap();
assert_eq!(buffer.get_ref().len(), McnrChunk::SIZE_BYTES);
assert_eq!(buffer.get_ref().len(), 448);
}
#[test]
fn constants() {
assert_eq!(McnrChunk::NORMAL_COUNT, 145);
assert_eq!(McnrChunk::SIZE_BYTES, 448);
assert_eq!(McnrChunk::SIZE_BYTES, 145 * 3 + 13);
}
}