Download crates/urt-assets/src/lib.rs from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 13.1 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/crates/urt-assets/src/lib.rs
- Command line
-
hf download hf://nmarshall/urt-rs/crates/urt-assets/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/nmarshall/urt-rs/resolve/main/crates/urt-assets/src/lib.rs
13.1 kB
| //! Original-byte MD3 parsing for the Rust rewrite. No renderer or unsafe code. | |
| use std::fmt; | |
| pub struct Error(pub &'static str); | |
| impl fmt::Display for Error { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.write_str(self.0) | |
| } | |
| } | |
| impl std::error::Error for Error {} | |
| type Result<T> = std::result::Result<T, Error>; | |
| fn range(offset: usize, count: usize, stride: usize, end: usize, min: usize) -> Result<()> { | |
| let size = count | |
| .checked_mul(stride) | |
| .ok_or(Error("region size overflow"))?; | |
| if (count > 0 && offset < min) || offset.checked_add(size).is_none_or(|v| v > end) { | |
| return Err(Error("region outside MD3 bounds")); | |
| } | |
| Ok(()) | |
| } | |
| fn int(data: &[u8], offset: usize) -> Result<i32> { | |
| let raw = data | |
| .get(offset..offset.checked_add(4).ok_or(Error("offset overflow"))?) | |
| .ok_or(Error("truncated integer"))?; | |
| Ok(i32::from_le_bytes( | |
| raw.try_into().map_err(|_| Error("integer width"))?, | |
| )) | |
| } | |
| fn count(data: &[u8], offset: usize) -> Result<usize> { | |
| usize::try_from(int(data, offset)?).map_err(|_| Error("negative MD3 count/offset")) | |
| } | |
| fn float(data: &[u8], offset: usize) -> Result<f32> { | |
| let v = f32::from_bits(int(data, offset)? as u32); | |
| if !v.is_finite() { | |
| return Err(Error("nonfinite MD3 geometry")); | |
| } | |
| Ok(v) | |
| } | |
| fn text(data: &[u8], offset: usize, width: usize) -> Result<String> { | |
| let raw = data | |
| .get(offset..offset.checked_add(width).ok_or(Error("offset overflow"))?) | |
| .ok_or(Error("truncated name"))?; | |
| let end = raw.iter().position(|&x| x == 0).unwrap_or(raw.len()); | |
| Ok(String::from_utf8_lossy(&raw[..end]).into_owned()) | |
| } | |
| pub struct Surface { | |
| pub offset: usize, | |
| pub name: String, | |
| pub frames: usize, | |
| pub shaders: usize, | |
| pub vertices: usize, | |
| pub triangles: usize, | |
| pub triangles_offset: usize, | |
| pub shaders_offset: usize, | |
| pub uv_offset: usize, | |
| pub vertices_offset: usize, | |
| pub end: usize, | |
| } | |
| pub struct Vertex { | |
| pub packed: [i16; 3], | |
| pub normal_word: u16, | |
| } | |
| impl Vertex { | |
| pub fn position(&self) -> [f32; 3] { | |
| self.packed.map(|v| f32::from(v) / 64.0) | |
| } | |
| } | |
| pub struct Tag { | |
| pub name: String, | |
| pub origin: [f32; 3], | |
| pub axis: [[f32; 3]; 3], | |
| } | |
| pub struct Frame { | |
| pub mins: [f32; 3], | |
| pub maxs: [f32; 3], | |
| pub origin: [f32; 3], | |
| pub radius: f32, | |
| pub name: String, | |
| } | |
| pub struct Md3<'a> { | |
| data: &'a [u8], | |
| pub name: String, | |
| pub frames: usize, | |
| pub tags: usize, | |
| frames_offset: usize, | |
| tags_offset: usize, | |
| surfaces: Vec<Surface>, | |
| } | |
| impl<'a> Md3<'a> { | |
| pub fn parse(data: &'a [u8]) -> Result<Self> { | |
| if data.len() < 108 || &data[..4] != b"IDP3" || int(data, 4)? != 15 { | |
| return Err(Error("unsupported MD3 header")); | |
| } | |
| let frames = count(data, 76)?; | |
| let tags = count(data, 80)?; | |
| let surface_count = count(data, 84)?; | |
| let _skins = count(data, 88)?; | |
| let frames_offset = count(data, 92)?; | |
| let tags_offset = count(data, 96)?; | |
| let mut offset = count(data, 100)?; | |
| let end = count(data, 104)?; | |
| if end != data.len() { | |
| return Err(Error("MD3 declared end differs from file size")); | |
| } | |
| range(frames_offset, frames, 56, end, 108)?; | |
| range( | |
| tags_offset, | |
| frames | |
| .checked_mul(tags) | |
| .ok_or(Error("tag count overflow"))?, | |
| 112, | |
| end, | |
| 108, | |
| )?; | |
| range(offset, surface_count, 108, end, 108)?; | |
| let mut surfaces = Vec::with_capacity(surface_count); | |
| for _ in 0..surface_count { | |
| range(offset, 1, 108, end, 108)?; | |
| if &data[offset..offset + 4] != b"IDP3" { | |
| return Err(Error("invalid surface signature")); | |
| } | |
| let s = Surface { | |
| offset, | |
| name: text(data, offset + 4, 64)?, | |
| frames: count(data, offset + 72)?, | |
| shaders: count(data, offset + 76)?, | |
| vertices: count(data, offset + 80)?, | |
| triangles: count(data, offset + 84)?, | |
| triangles_offset: count(data, offset + 88)?, | |
| shaders_offset: count(data, offset + 92)?, | |
| uv_offset: count(data, offset + 96)?, | |
| vertices_offset: count(data, offset + 100)?, | |
| end: count(data, offset + 104)?, | |
| }; | |
| if s.frames != frames || s.end < 108 || s.end > end - offset { | |
| return Err(Error("invalid surface frames/end")); | |
| } | |
| range(s.triangles_offset, s.triangles, 12, s.end, 108)?; | |
| range(s.shaders_offset, s.shaders, 68, s.end, 108)?; | |
| range(s.uv_offset, s.vertices, 8, s.end, 108)?; | |
| range( | |
| s.vertices_offset, | |
| frames | |
| .checked_mul(s.vertices) | |
| .ok_or(Error("vertex count overflow"))?, | |
| 8, | |
| s.end, | |
| 108, | |
| )?; | |
| for triangle in 0..s.triangles { | |
| let p = offset + s.triangles_offset + triangle * 12; | |
| for j in 0..3 { | |
| if count(data, p + 4 * j)? >= s.vertices { | |
| return Err(Error("triangle outside vertex range")); | |
| } | |
| } | |
| } | |
| offset += s.end; | |
| surfaces.push(s); | |
| } | |
| if surface_count > 0 && offset != end { | |
| return Err(Error("surface chain differs from EOF")); | |
| } | |
| let model = Self { | |
| data, | |
| name: text(data, 8, 64)?, | |
| frames, | |
| tags, | |
| frames_offset, | |
| tags_offset, | |
| surfaces, | |
| }; | |
| for index in 0..frames { | |
| model.frame(index)?; | |
| for tag in 0..tags { | |
| model.tag(index, tag)?; | |
| } | |
| } | |
| for (index, surface) in model.surfaces.iter().enumerate() { | |
| for vertex in 0..surface.vertices { | |
| model.uv(index, vertex)?; | |
| } | |
| } | |
| Ok(model) | |
| } | |
| pub fn surfaces(&self) -> &[Surface] { | |
| &self.surfaces | |
| } | |
| fn surface(&self, index: usize) -> Result<&Surface> { | |
| self.surfaces | |
| .get(index) | |
| .ok_or(Error("surface index outside bounds")) | |
| } | |
| pub fn triangle(&self, surface: usize, index: usize) -> Result<[usize; 3]> { | |
| let s = self.surface(surface)?; | |
| range(index, 1, 1, s.triangles, 0)?; | |
| let p = s.offset + s.triangles_offset + 12 * index; | |
| Ok([ | |
| count(self.data, p)?, | |
| count(self.data, p + 4)?, | |
| count(self.data, p + 8)?, | |
| ]) | |
| } | |
| pub fn vertex(&self, surface: usize, frame: usize, index: usize) -> Result<Vertex> { | |
| let s = self.surface(surface)?; | |
| range(frame, 1, 1, s.frames, 0)?; | |
| range(index, 1, 1, s.vertices, 0)?; | |
| let p = s.offset + s.vertices_offset + (frame * s.vertices + index) * 8; | |
| let mut packed = [0; 3]; | |
| for (j, value) in packed.iter_mut().enumerate() { | |
| *value = i16::from_le_bytes( | |
| self.data[p + 2 * j..p + 2 * j + 2] | |
| .try_into() | |
| .map_err(|_| Error("vertex width"))?, | |
| ); | |
| } | |
| Ok(Vertex { | |
| packed, | |
| normal_word: u16::from_le_bytes( | |
| self.data[p + 6..p + 8] | |
| .try_into() | |
| .map_err(|_| Error("normal width"))?, | |
| ), | |
| }) | |
| } | |
| pub fn uv(&self, surface: usize, index: usize) -> Result<[f32; 2]> { | |
| let s = self.surface(surface)?; | |
| range(index, 1, 1, s.vertices, 0)?; | |
| let p = s.offset + s.uv_offset + index * 8; | |
| Ok([float(self.data, p)?, float(self.data, p + 4)?]) | |
| } | |
| pub fn shader(&self, surface: usize, index: usize) -> Result<(String, i32)> { | |
| let s = self.surface(surface)?; | |
| range(index, 1, 1, s.shaders, 0)?; | |
| let p = s.offset + s.shaders_offset + index * 68; | |
| Ok((text(self.data, p, 64)?, int(self.data, p + 64)?)) | |
| } | |
| pub fn frame(&self, index: usize) -> Result<Frame> { | |
| range(index, 1, 1, self.frames, 0)?; | |
| let p = self.frames_offset + index * 56; | |
| Ok(Frame { | |
| mins: [ | |
| float(self.data, p)?, | |
| float(self.data, p + 4)?, | |
| float(self.data, p + 8)?, | |
| ], | |
| maxs: [ | |
| float(self.data, p + 12)?, | |
| float(self.data, p + 16)?, | |
| float(self.data, p + 20)?, | |
| ], | |
| origin: [ | |
| float(self.data, p + 24)?, | |
| float(self.data, p + 28)?, | |
| float(self.data, p + 32)?, | |
| ], | |
| radius: float(self.data, p + 36)?, | |
| name: text(self.data, p + 40, 16)?, | |
| }) | |
| } | |
| pub fn tag(&self, frame: usize, index: usize) -> Result<Tag> { | |
| range(frame, 1, 1, self.frames, 0)?; | |
| range(index, 1, 1, self.tags, 0)?; | |
| let p = self.tags_offset + (frame * self.tags + index) * 112; | |
| Ok(Tag { | |
| name: text(self.data, p, 64)?, | |
| origin: [ | |
| float(self.data, p + 64)?, | |
| float(self.data, p + 68)?, | |
| float(self.data, p + 72)?, | |
| ], | |
| axis: [ | |
| [ | |
| float(self.data, p + 76)?, | |
| float(self.data, p + 80)?, | |
| float(self.data, p + 84)?, | |
| ], | |
| [ | |
| float(self.data, p + 88)?, | |
| float(self.data, p + 92)?, | |
| float(self.data, p + 96)?, | |
| ], | |
| [ | |
| float(self.data, p + 100)?, | |
| float(self.data, p + 104)?, | |
| float(self.data, p + 108)?, | |
| ], | |
| ], | |
| }) | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| fn put(d: &mut [u8], p: usize, v: i32) { | |
| d[p..p + 4].copy_from_slice(&v.to_le_bytes()); | |
| } | |
| fn fixture() -> Vec<u8> { | |
| let mut d = vec![0; 276]; | |
| d[..4].copy_from_slice(b"IDP3"); | |
| put(&mut d, 4, 15); | |
| for (p, v) in [ | |
| (76, 1), | |
| (84, 1), | |
| (92, 108), | |
| (96, 164), | |
| (100, 164), | |
| (104, 276), | |
| ] { | |
| put(&mut d, p, v); | |
| } | |
| d[164..168].copy_from_slice(b"IDP3"); | |
| put(&mut d, 236, 1); | |
| for (p, v) in [(252, 108), (256, 108), (260, 108), (264, 108), (268, 112)] { | |
| put(&mut d, p, v); | |
| } | |
| d | |
| } | |
| fn empty_surface_is_bounded() { | |
| let d = fixture(); | |
| let m = Md3::parse(&d).unwrap(); | |
| assert_eq!(m.surfaces().len(), 1); | |
| assert!(m.vertex(0, 0, 0).is_err()); | |
| } | |
| fn truncation_rejects() { | |
| let d = fixture(); | |
| assert!(Md3::parse(&d[..275]).is_err()); | |
| } | |
| fn signature_and_version_reject() { | |
| for p in [0, 4] { | |
| let mut d = fixture(); | |
| d[p] = 0; | |
| assert!(Md3::parse(&d).is_err()); | |
| } | |
| } | |
| fn negative_counts_reject() { | |
| let mut d = fixture(); | |
| put(&mut d, 76, -1); | |
| assert!(Md3::parse(&d).is_err()); | |
| } | |
| fn outside_regions_reject() { | |
| let mut d = fixture(); | |
| put(&mut d, 260, 10000); | |
| assert!(Md3::parse(&d).is_err()); | |
| } | |
| fn frame_accessor_bounds_reject() { | |
| let d = fixture(); | |
| let m = Md3::parse(&d).unwrap(); | |
| assert!(m.frame(1).is_err()); | |
| assert!(m.tag(0, 0).is_err()); | |
| } | |
| fn nonfinite_frame_rejects() { | |
| let mut d = fixture(); | |
| put(&mut d, 108, f32::NAN.to_bits() as i32); | |
| assert!(Md3::parse(&d).is_err()); | |
| } | |
| fn region_arithmetic_overflow_rejects() { | |
| assert!(range(usize::MAX, 1, 4, usize::MAX, 0).is_err()); | |
| assert!(range(0, usize::MAX, 4, usize::MAX, 0).is_err()); | |
| } | |
| fn triangle_index_outside_vertex_count_rejects() { | |
| let mut d = fixture(); | |
| d.resize(300, 0); | |
| for (p, v) in [ | |
| (104, 300), | |
| (244, 1), | |
| (248, 1), | |
| (252, 108), | |
| (260, 120), | |
| (264, 128), | |
| (268, 136), | |
| (280, 1), | |
| ] { | |
| put(&mut d, p, v); | |
| } | |
| assert!(Md3::parse(&d).is_err()); | |
| } | |
| fn signed_coordinate_extrema_and_normal_word_preserved() { | |
| let v = Vertex { | |
| packed: [i16::MIN, i16::MAX, -1], | |
| normal_word: 0xabcd, | |
| }; | |
| assert_eq!(v.position(), [-512.0, 511.984375, -0.015625]); | |
| assert_eq!(v.normal_word, 0xabcd); | |
| } | |
| } | |