//! Validated reader for Linden Lab's legacy `.llm` avatar mesh format. use std::collections::BTreeMap; use std::path::Path; use libremetaverse_types::compat::{Object, SortedList}; use libremetaverse_types::{Error, Vector2, Vector3}; use crate::rendering::{ LindenMeshFace, LindenMeshMorph, LindenMeshMorphVertex, LindenMeshSkinWeightElement, LindenMeshVertex, LindenMeshVertexRemap, LindenSkeleton, }; const MESH_HEADER: &str = "Linden Binary Mesh 1.0"; const MORPH_FOOTER: &str = "End Morphs"; const MAX_MESH_BYTES: usize = 64 * 1024 * 1024; const MAX_MORPHS: usize = 16_384; const MORPH_VERTEX_BYTES: usize = 48; /// Complete native state for a full Linden avatar mesh. #[derive(Clone, Debug)] pub struct LindenMesh { pub min_pixel_width: f32, pub morphs: Vec, pub skin_weights: Vec, name: String, header: String, has_weights: bool, has_detail_tex_coords: bool, position: Vector3, rotation_angles: Vector3, scale: Vector3, num_vertices: u16, vertices: Vec, num_faces: u16, faces: Vec, num_skin_joints: u16, skin_joints: Vec, num_remaps: i32, vertex_remaps: Vec, lod_meshes: SortedList, skeleton: Option, } /// Complete native state for a reference-only Linden LOD mesh. #[derive(Clone, Debug, PartialEq)] pub struct ReferenceMesh { pub min_pixel_width: f32, pub header: String, pub has_weights: bool, pub has_detail_tex_coords: bool, pub position: Vector3, pub rotation_angles: Vector3, pub rotation_order: u8, pub scale: Vector3, pub num_faces: u16, pub faces: Vec, } impl LindenMesh { pub(crate) fn native_new( name: String, skeleton: Option, ) -> Result { Ok(Self { min_pixel_width: 0.0, morphs: Vec::new(), skin_weights: Vec::new(), name, header: String::new(), has_weights: false, has_detail_tex_coords: false, position: Vector3::zero(), rotation_angles: Vector3::zero(), scale: Vector3::zero(), num_vertices: 0, vertices: Vec::new(), num_faces: 0, faces: Vec::new(), num_skin_joints: 0, skin_joints: Vec::new(), num_remaps: 0, vertex_remaps: Vec::new(), lod_meshes: SortedList(BTreeMap::new()), skeleton: Some(skeleton.unwrap_or(LindenSkeleton::load_with_method()?)), }) } pub(crate) fn native_load_mesh(&mut self, filename: &str) -> Result<(), Error> { let bytes = read_mesh_file(filename)?; let parsed = ParsedFullMesh::parse(&bytes)?; self.header = parsed.header; self.has_weights = parsed.has_weights; self.has_detail_tex_coords = parsed.has_detail_tex_coords; self.position = parsed.position; self.rotation_angles = parsed.rotation_angles; self.scale = parsed.scale; self.num_vertices = parsed.num_vertices; self.vertices = parsed.vertices; self.num_faces = parsed.num_faces; self.faces = parsed.faces; self.num_skin_joints = parsed.num_skin_joints; self.skin_joints = parsed.skin_joints; self.morphs = parsed.morphs; self.num_remaps = parsed.num_remaps; self.vertex_remaps = parsed.vertex_remaps; self.expand_skin_weights()?; Ok(()) } pub(crate) fn native_load_lod_mesh( &mut self, level: i32, filename: &str, ) -> Result { let object = if is_eye_lod(filename) { let mut mesh = Self::native_new(String::new(), None)?; mesh.native_load_mesh(filename)?; Object::opaque(mesh) } else { let mut mesh = ReferenceMesh::native_new()?; mesh.native_load_mesh(filename)?; Object::opaque(mesh) }; self.lod_meshes.0.insert(level, object.clone()); Ok(object) } pub(crate) fn native_load_reference_mesh( &mut self, lod_level: i32, filename: &str, ) -> Result { if is_eye_lod(filename) { return Err(Error::Argument); } let mut mesh = ReferenceMesh::native_new()?; mesh.native_load_mesh(filename)?; self.lod_meshes .0 .insert(lod_level, Object::opaque(mesh.clone())); Ok(mesh) } fn expand_skin_weights(&mut self) -> Result<(), Error> { self.skin_weights.clear(); let Some(skeleton) = &self.skeleton else { return Ok(()); }; let mut joints = skeleton.build_expanded_joint_list(Box::new(self.skin_joints.clone().into_iter()))?; if joints.is_empty() { match self.name.as_str() { "eyeBallLeftMesh" => joints.extend(["mEyeLeft".to_owned(), "mSkull".to_owned()]), "eyeBallRightMesh" => { joints.extend(["mEyeRight".to_owned(), "mSkull".to_owned()]); } _ => {} } } if joints.is_empty() { return Ok(()); } self.skin_weights.reserve(self.vertices.len()); for vertex in &self.vertices { let raw_index = vertex.weight.floor() as i32; let bone_index = raw_index - 1; let blend = (vertex.weight - raw_index as f32).clamp(0.0, 1.0); let valid_pair = usize::try_from(bone_index) .ok() .filter(|index| index.saturating_add(1) < joints.len()); let weight = if let Some(index) = valid_pair { LindenMeshSkinWeightElement { bone1: joints[index].clone(), bone2: joints[index + 1].clone(), weight1: 1.0 - blend, weight2: blend, } } else { let nearest = bone_index.clamp(0, joints.len().saturating_sub(1) as i32) as usize; LindenMeshSkinWeightElement { bone1: joints[nearest].clone(), bone2: "mPelvis".to_owned(), weight1: 1.0, weight2: 0.0, } }; self.skin_weights.push(weight); } Ok(()) } pub(crate) fn native_faces(&self) -> Vec { self.faces.clone() } pub(crate) fn native_set_faces(&mut self, value: Vec) { self.num_faces = value.len().try_into().unwrap_or(u16::MAX); self.faces = value; } pub(crate) fn native_has_detail_tex_coords(&self) -> bool { self.has_detail_tex_coords } pub(crate) fn native_set_has_detail_tex_coords(&mut self, value: bool) { self.has_detail_tex_coords = value; } pub(crate) fn native_has_weights(&self) -> bool { self.has_weights } pub(crate) fn native_set_has_weights(&mut self, value: bool) { self.has_weights = value; } pub(crate) fn native_header(&self) -> String { self.header.clone() } pub(crate) fn native_set_header(&mut self, value: String) { self.header = value; } pub(crate) fn native_lod_meshes(&self) -> SortedList { self.lod_meshes.clone() } pub(crate) fn native_set_lod_meshes(&mut self, value: SortedList) { self.lod_meshes = value; } pub(crate) fn native_name(&self) -> String { self.name.clone() } pub(crate) fn native_set_name(&mut self, value: String) { self.name = value; } pub(crate) fn native_num_faces(&self) -> u16 { self.num_faces } pub(crate) fn native_set_num_faces(&mut self, value: u16) { self.num_faces = value; } pub(crate) fn native_num_remaps(&self) -> i32 { self.num_remaps } pub(crate) fn native_set_num_remaps(&mut self, value: i32) { self.num_remaps = value; } pub(crate) fn native_num_skin_joints(&self) -> u16 { self.num_skin_joints } pub(crate) fn native_set_num_skin_joints(&mut self, value: u16) { self.num_skin_joints = value; } pub(crate) fn native_num_vertices(&self) -> u16 { self.num_vertices } pub(crate) fn native_set_num_vertices(&mut self, value: u16) { self.num_vertices = value; } pub(crate) fn native_position(&self) -> Vector3 { self.position } pub(crate) fn native_set_position(&mut self, value: Vector3) { self.position = value; } pub(crate) fn native_rotation_angles(&self) -> Vector3 { self.rotation_angles } pub(crate) fn native_set_rotation_angles(&mut self, value: Vector3) { self.rotation_angles = value; } pub(crate) fn native_scale(&self) -> Vector3 { self.scale } pub(crate) fn native_set_scale(&mut self, value: Vector3) { self.scale = value; } pub(crate) fn native_skeleton(&self) -> Option { self.skeleton.clone() } pub(crate) fn native_set_skeleton(&mut self, value: Option) { self.skeleton = value; } pub(crate) fn native_skin_joints(&self) -> Vec { self.skin_joints.clone() } pub(crate) fn native_set_skin_joints(&mut self, value: Vec) { self.num_skin_joints = value.len().try_into().unwrap_or(u16::MAX); self.skin_joints = value; } pub(crate) fn native_vertex_remaps(&self) -> Vec { self.vertex_remaps.clone() } pub(crate) fn native_set_vertex_remaps(&mut self, value: Vec) { self.num_remaps = value.len().try_into().unwrap_or(i32::MAX); self.vertex_remaps = value; } pub(crate) fn native_vertices(&self) -> Vec { self.vertices.clone() } pub(crate) fn native_set_vertices(&mut self, value: Vec) { self.num_vertices = value.len().try_into().unwrap_or(u16::MAX); self.vertices = value; } } impl ReferenceMesh { pub(crate) fn native_new() -> Result { Ok(Self { min_pixel_width: 0.0, header: String::new(), has_weights: false, has_detail_tex_coords: false, position: Vector3::zero(), rotation_angles: Vector3::zero(), rotation_order: 0, scale: Vector3::zero(), num_faces: 0, faces: Vec::new(), }) } pub(crate) fn native_load_mesh(&mut self, filename: &str) -> Result<(), Error> { let bytes = read_mesh_file(filename)?; let mut reader = Reader::new(&bytes); self.header = reader.fixed_string(24, "reference mesh header")?; validate_header(&self.header)?; self.has_weights = reader.byte("reference weights flag")? != 0; self.has_detail_tex_coords = reader.byte("reference detail flag")? != 0; self.position = reader.vector3("reference position")?; self.rotation_angles = reader.vector3("reference rotation")?; self.rotation_order = reader.byte("reference rotation order")?; self.scale = reader.vector3("reference scale")?; self.num_faces = reader.u16("reference face count")?; self.faces = read_faces(&mut reader, self.num_faces)?; reader.finish("reference mesh trailing bytes")?; Ok(()) } } #[derive(Debug)] struct ParsedFullMesh { header: String, has_weights: bool, has_detail_tex_coords: bool, position: Vector3, rotation_angles: Vector3, scale: Vector3, num_vertices: u16, vertices: Vec, num_faces: u16, faces: Vec, num_skin_joints: u16, skin_joints: Vec, morphs: Vec, num_remaps: i32, vertex_remaps: Vec, } impl ParsedFullMesh { fn parse(bytes: &[u8]) -> Result { let mut reader = Reader::new(bytes); let header = reader.fixed_string(24, "mesh header")?; validate_header(&header)?; let has_weights = reader.byte("weights flag")? != 0; let has_detail_tex_coords = reader.byte("detail flag")? != 0; let position = reader.vector3("position")?; let rotation_angles = reader.vector3("rotation")?; let _rotation_order = reader.byte("rotation order")?; let scale = reader.vector3("scale")?; let num_vertices = reader.u16("vertex count")?; let count = usize::from(num_vertices); let mut vertices = vec![empty_vertex(); count]; for vertex in &mut vertices { vertex.coord = reader.vector3("vertex coordinates")?; } for vertex in &mut vertices { vertex.normal = reader.vector3("vertex normals")?; } for vertex in &mut vertices { vertex.bi_normal = reader.vector3("vertex binormals")?; } for vertex in &mut vertices { vertex.tex_coord = reader.vector2("vertex texture coordinates")?; } if has_detail_tex_coords { for vertex in &mut vertices { vertex.detail_tex_coord = reader.vector2("vertex detail texture coordinates")?; } } if has_weights { for vertex in &mut vertices { vertex.weight = reader.f32("vertex weight")?; if !vertex.weight.is_finite() { return Err(parse_error(reader.position(), "non-finite vertex weight")); } } } let num_faces = reader.u16("face count")?; let faces = read_faces(&mut reader, num_faces)?; let (num_skin_joints, skin_joints) = if has_weights { let count = reader.u16("skin joint count")?; let mut joints = Vec::with_capacity(usize::from(count)); for _ in 0..count { joints.push(reader.fixed_string(64, "skin joint name")?); } (count, joints) } else { (0, Vec::new()) }; let mut morphs = Vec::new(); loop { let name = reader.fixed_string(64, "morph name")?; if name == MORPH_FOOTER { break; } if morphs.len() >= MAX_MORPHS { return Err(parse_error(reader.position(), "morph count exceeds limit")); } let signed_count = reader.i32("morph vertex count")?; let morph_count = usize::try_from(signed_count) .map_err(|_| parse_error(reader.position(), "negative morph vertex count"))?; reader.require( morph_count .checked_mul(MORPH_VERTEX_BYTES) .ok_or_else(|| parse_error(reader.position(), "morph size overflow"))?, "morph vertices", )?; let mut morph_vertices = Vec::with_capacity(morph_count); for _ in 0..morph_count { morph_vertices.push(LindenMeshMorphVertex { vertex_index: reader.u32("morph vertex index")?, coord: reader.vector3("morph coordinates")?, normal: reader.vector3("morph normal")?, bi_normal: reader.vector3("morph binormal")?, tex_coord: reader.vector2("morph texture coordinates")?, }); } morphs.push(LindenMeshMorph { name, num_vertices: signed_count, vertices: morph_vertices, }); } let (num_remaps, vertex_remaps) = if reader.remaining() == 0 { (0, Vec::new()) } else { let signed_count = reader.i32("vertex remap count")?; let count = usize::try_from(signed_count) .map_err(|_| parse_error(reader.position(), "negative vertex remap count"))?; reader.require( count .checked_mul(8) .ok_or_else(|| parse_error(reader.position(), "vertex remap size overflow"))?, "vertex remaps", )?; let mut remaps = Vec::with_capacity(count); for _ in 0..count { remaps.push(LindenMeshVertexRemap { remap_source: reader.i32("remap source")?, remap_destination: reader.i32("remap destination")?, }); } (signed_count, remaps) }; reader.finish("mesh trailing bytes")?; Ok(Self { header, has_weights, has_detail_tex_coords, position, rotation_angles, scale, num_vertices, vertices, num_faces, faces, num_skin_joints, skin_joints, morphs, num_remaps, vertex_remaps, }) } } fn empty_vertex() -> LindenMeshVertex { LindenMeshVertex { bi_normal: Vector3::zero(), coord: Vector3::zero(), detail_tex_coord: Vector2::zero(), normal: Vector3::zero(), tex_coord: Vector2::zero(), weight: 0.0, } } fn read_faces(reader: &mut Reader<'_>, count: u16) -> Result, Error> { reader.require(usize::from(count) * 6, "face indices")?; let mut faces = Vec::with_capacity(usize::from(count)); for _ in 0..count { faces.push(LindenMeshFace { indices: vec![ reader.i16("face index")?, reader.i16("face index")?, reader.i16("face index")?, ], }); } Ok(faces) } fn read_mesh_file(filename: &str) -> Result, Error> { let metadata = std::fs::metadata(filename).map_err(|_| Error::Argument)?; let length = usize::try_from(metadata.len()).map_err(|_| Error::Argument)?; if length > MAX_MESH_BYTES { return Err(Error::Argument); } let bytes = std::fs::read(filename).map_err(|_| Error::Argument)?; if bytes.len() > MAX_MESH_BYTES { return Err(Error::Argument); } Ok(bytes) } fn is_eye_lod(filename: &str) -> bool { Path::new(filename) .file_name() .and_then(|name| name.to_str()) == Some("avatar_eye_1.llm") } fn validate_header(header: &str) -> Result<(), Error> { if header == MESH_HEADER { Ok(()) } else { Err(parse_error(0, "unrecognized Linden mesh header")) } } pub(crate) fn trim_at_nul(value: &str) -> String { value .split_once('\0') .map_or(value, |(prefix, _)| prefix) .to_owned() } struct Reader<'a> { bytes: &'a [u8], position: usize, } impl<'a> Reader<'a> { fn new(bytes: &'a [u8]) -> Self { Self { bytes, position: 0 } } fn position(&self) -> usize { self.position } fn remaining(&self) -> usize { self.bytes.len().saturating_sub(self.position) } fn require(&self, count: usize, context: &'static str) -> Result<(), Error> { if count <= self.remaining() { Ok(()) } else { Err(parse_error(self.position, context)) } } fn take(&mut self, count: usize, context: &'static str) -> Result<&'a [u8], Error> { self.require(count, context)?; let start = self.position; self.position += count; Ok(&self.bytes[start..self.position]) } fn byte(&mut self, context: &'static str) -> Result { Ok(self.take(1, context)?[0]) } fn i16(&mut self, context: &'static str) -> Result { let bytes: [u8; 2] = self .take(2, context)? .try_into() .map_err(|_| parse_error(self.position, context))?; Ok(i16::from_le_bytes(bytes)) } fn u16(&mut self, context: &'static str) -> Result { let bytes: [u8; 2] = self .take(2, context)? .try_into() .map_err(|_| parse_error(self.position, context))?; Ok(u16::from_le_bytes(bytes)) } fn i32(&mut self, context: &'static str) -> Result { let bytes: [u8; 4] = self .take(4, context)? .try_into() .map_err(|_| parse_error(self.position, context))?; Ok(i32::from_le_bytes(bytes)) } fn u32(&mut self, context: &'static str) -> Result { let bytes: [u8; 4] = self .take(4, context)? .try_into() .map_err(|_| parse_error(self.position, context))?; Ok(u32::from_le_bytes(bytes)) } fn f32(&mut self, context: &'static str) -> Result { let bytes: [u8; 4] = self .take(4, context)? .try_into() .map_err(|_| parse_error(self.position, context))?; let value = f32::from_le_bytes(bytes); if value.is_finite() { Ok(value) } else { Err(parse_error(self.position, context)) } } fn vector2(&mut self, context: &'static str) -> Result { Ok(Vector2 { x: self.f32(context)?, y: self.f32(context)?, }) } fn vector3(&mut self, context: &'static str) -> Result { Ok(Vector3 { x: self.f32(context)?, y: self.f32(context)?, z: self.f32(context)?, }) } fn fixed_string(&mut self, count: usize, context: &'static str) -> Result { Ok(trim_at_nul(&String::from_utf8_lossy( self.take(count, context)?, ))) } fn finish(&self, context: &'static str) -> Result<(), Error> { if self.remaining() == 0 { Ok(()) } else { Err(parse_error(self.position, context)) } } } const fn parse_error(position: usize, context: &'static str) -> Error { Error::Parse { position, context } } #[cfg(test)] mod tests { use super::*; fn base_header(weights: bool, detail: bool) -> Vec { let mut bytes = [0_u8; 24].to_vec(); bytes[..MESH_HEADER.len()].copy_from_slice(MESH_HEADER.as_bytes()); bytes.push(u8::from(weights)); bytes.push(u8::from(detail)); for value in [1.0_f32, 2.0, 3.0, 0.1, 0.2, 0.3] { bytes.extend(value.to_le_bytes()); } bytes.push(7); for value in [4.0_f32, 5.0, 6.0] { bytes.extend(value.to_le_bytes()); } bytes } #[test] fn parses_full_mesh_arrays_morphs_and_remaps() { let mut bytes = base_header(false, true); bytes.extend(1_u16.to_le_bytes()); for value in [ 1.0_f32, 2.0, 3.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.25, 0.75, 0.5, 0.5, ] { bytes.extend(value.to_le_bytes()); } bytes.extend(1_u16.to_le_bytes()); for index in [0_i16, 0, 0] { bytes.extend(index.to_le_bytes()); } let mut morph_name = [0_u8; 64]; morph_name[..5].copy_from_slice(b"smile"); bytes.extend(morph_name); bytes.extend(1_i32.to_le_bytes()); bytes.extend(0_u32.to_le_bytes()); for value in [0.1_f32; 11] { bytes.extend(value.to_le_bytes()); } let mut footer = [0_u8; 64]; footer[..MORPH_FOOTER.len()].copy_from_slice(MORPH_FOOTER.as_bytes()); bytes.extend(footer); bytes.extend(1_i32.to_le_bytes()); bytes.extend(3_i32.to_le_bytes()); bytes.extend(4_i32.to_le_bytes()); let mesh = ParsedFullMesh::parse(&bytes).unwrap(); assert_eq!(mesh.vertices.len(), 1); assert_eq!(mesh.faces[0].indices, vec![0, 0, 0]); assert_eq!(mesh.morphs[0].name, "smile"); assert_eq!(mesh.vertex_remaps[0].remap_destination, 4); } #[test] fn rejects_bad_header_truncation_negative_counts_and_trailing_data() { assert!(ParsedFullMesh::parse(b"bad").is_err()); let mut negative = base_header(false, false); negative.extend(0_u16.to_le_bytes()); negative.extend(0_u16.to_le_bytes()); let mut name = [0_u8; 64]; name[0] = b'x'; negative.extend(name); negative.extend((-1_i32).to_le_bytes()); assert!(ParsedFullMesh::parse(&negative).is_err()); let mut valid = base_header(false, false); valid.extend(0_u16.to_le_bytes()); valid.extend(0_u16.to_le_bytes()); let mut footer = [0_u8; 64]; footer[..MORPH_FOOTER.len()].copy_from_slice(MORPH_FOOTER.as_bytes()); valid.extend(footer); valid.push(1); assert!(ParsedFullMesh::parse(&valid).is_err()); } #[test] fn trim_stops_at_first_nul() { assert_eq!(trim_at_nul("abc\0def\0"), "abc"); } }