//! Native `MeshFoundry` implementation and bounded Second Life mesh decoding. #![allow(clippy::cast_possible_truncation)] // Quantized protocol values are checked first. #![allow(clippy::missing_errors_doc)] // Public signatures mirror the mapped API. #![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped surface. #![allow(clippy::needless_pass_by_value)] // Mapped value parameters are owned. #![allow(clippy::unnecessary_wraps)] // Mapped constructors are fallible. #![allow(clippy::unused_self)] // IRendering operations are instance methods. use std::collections::HashMap; use std::io::Read as _; use flate2::read::ZlibDecoder; use libremetaverse::rendering::{ DetailLevel, Face, FaceMask, FacetedMesh, IRendering, MeshSkinData, SimpleMesh, Vertex, VertexWeight, }; use libremetaverse::{Primitive, PrimitiveTextureEntryFace}; use libremetaverse_imaging::ManagedImage; use libremetaverse_prim_mesher::SculptMesh; use libremetaverse_rendering_simple::SimpleRenderer; use libremetaverse_structured_data::{OSD, OSDMap, deserialize_llsd_binary_prefix}; use libremetaverse_types::compat::Object; use libremetaverse_types::{Error, Vector2, Vector3, Vector4}; const MAX_ASSET_BYTES: usize = 64 * 1024 * 1024; const MAX_SECTION_BYTES: usize = 64 * 1024 * 1024; const MAX_SECTIONS: usize = 256; const MAX_SUBMESHES: usize = 256; const MAX_FACE_VERTICES: usize = 65_536; const MAX_TOTAL_VERTICES: usize = 1_000_000; const MAX_TOTAL_INDICES: usize = 6_000_000; const MAX_JOINTS: usize = 512; const MAX_HULLS: usize = 65_536; const MAX_HULL_VERTICES: usize = 1_000_000; /// Mesh-only per-face domains that are not representable by the mapped `Face` /// fields. Consumers can recover this value with `face.user_data.downcast_ref`. #[derive(Debug)] pub struct MeshFaceAux { /// Tangent vectors in vertex order; `w` is bitangent handedness. pub tangents: Vec, } /// Full native renderer for prims, sculpts, terrain, and LLSD mesh assets. pub struct MeshFoundry; impl MeshFoundry { pub(crate) fn native_new() -> Result { Ok(Self) } pub(crate) fn native_generate_faceted_mesh( &self, prim: Primitive, lod: DetailLevel, ) -> Result, Error> { SimpleRenderer::new()? .generate_faceted_mesh(prim, lod) .map(Some) } pub(crate) fn native_generate_simple_mesh( &self, prim: Primitive, lod: DetailLevel, _with_normals: bool, ) -> Result, Error> { // The native pipeline always retains normals and UVs. This is a strict // improvement over the legacy non-normal overload and keeps one stable // vertex contract for downstream consumers. SimpleRenderer::new()? .generate_simple_mesh(prim, lod) .map(Some) } pub(crate) fn native_generate_faceted_sculpt_mesh( &self, prim: Primitive, sculpt_texture: ManagedImage, lod: DetailLevel, ) -> Result, Error> { SimpleRenderer::new()? .generate_faceted_sculpt_mesh(prim, sculpt_texture, lod) .map(Some) } pub(crate) fn native_generate_simple_sculpt_mesh( &self, prim: Primitive, sculpt_texture: ManagedImage, lod: DetailLevel, ) -> Result, Error> { SimpleRenderer::new()? .generate_simple_sculpt_mesh(prim, sculpt_texture, lod) .map(Some) } pub(crate) fn native_transform_tex_coords( &self, vertices: &mut Vec, center: Vector3, te_face: PrimitiveTextureEntryFace, prim_scale: Vector3, ) -> Result<(), Error> { SimpleRenderer::new()?.transform_tex_coords(vertices, center, te_face, prim_scale) } pub(crate) fn native_generate_faceted_mesh_mesh( &self, prim: Primitive, mesh_data: Vec, requested_lod: DetailLevel, ) -> Result, Error> { let source = prim.id; let unpacked = unpack_mesh_map(&mesh_data, source)?; let (lod, faces) = select_lod(&unpacked, requested_lod) .ok_or_else(|| render_error(source, "mesh asset has no visual LOD"))?; let skin = unpacked .get("skin") .map(|value| decode_skin(value, source)) .transpose()?; let mut mesh = decode_faceted(&prim, faces, skin, lod, source)?; apply_sculpt_modifiers(&prim, &mut mesh)?; Ok(Some(mesh)) } pub(crate) fn native_unpack_mesh(&self, asset_data: Vec) -> Result { let decoded = unpack_mesh_map(&asset_data, libremetaverse_types::UUID::zero())?; OSDMap::new_with_dictionary(decoded) } pub(crate) fn native_mesh_sub_mesh_as_simple_mesh( &self, prim: Primitive, compressed_mesh_data: Vec, ) -> Result, Error> { let source = prim.id; let value = decompress_osd(&compressed_mesh_data, source)?; let OSD::Array(submeshes) = value else { return Err(render_error(source, "mesh section is not a submesh array")); }; if submeshes.len() > MAX_SUBMESHES { return Err(render_error(source, "mesh submesh count exceeds limit")); } let mut faces = Vec::new(); let mut total_vertices = 0usize; let mut total_indices = 0usize; for (index, submesh) in submeshes.iter().enumerate() { if let Some(face) = decode_submesh(&prim, submesh, index, None, source)? { total_vertices = checked_total( total_vertices, face.vertices.len(), MAX_TOTAL_VERTICES, source, "mesh vertex budget exceeded", )?; total_indices = checked_total( total_indices, face.indices.len(), MAX_TOTAL_INDICES, source, "mesh index budget exceeded", )?; faces.push(face); } } flatten_faces(faces, source).map(Some) } pub(crate) fn native_mesh_sub_mesh_as_convex_hulls( &self, prim: Primitive, compressed_mesh_data: Vec, bounding_hull: Option<&mut Vec>, ) -> Result>, Error> { let source = prim.id; let value = decompress_osd(&compressed_mesh_data, source)?; let map = as_map(&value) .ok_or_else(|| render_error(source, "physics convex section is not a map"))?; let min = map .get("Min") .map(|value| osd_vector3(value, source, "invalid convex minimum")) .transpose()? .unwrap_or(Vector3 { x: -0.5, y: -0.5, z: -0.5, }); let max = map .get("Max") .map(|value| osd_vector3(value, source, "invalid convex maximum")) .transpose()? .unwrap_or(Vector3 { x: 0.5, y: 0.5, z: 0.5, }); validate_domain3(min, max, source, "invalid convex position domain")?; let decoded_bounding = map .get("BoundingVerts") .and_then(osd_binary) .map(|bytes| decode_quantized_positions(bytes, min, max, source)) .transpose()?; if let Some(output) = bounding_hull { output.clear(); if let Some(vertices) = decoded_bounding { *output = vertices; } } let Some(counts) = map.get("HullList").and_then(osd_binary) else { return Ok(Vec::new()); }; let positions = map .get("Positions") .and_then(osd_binary) .ok_or_else(|| render_error(source, "convex hull positions are missing"))?; if counts.len() > MAX_HULLS { return Err(render_error(source, "convex hull count exceeds limit")); } let mut cursor = 0usize; let mut total = 0usize; let mut hulls = Vec::with_capacity(counts.len()); for encoded_count in counts { let count = if *encoded_count == 0 { 256usize } else { usize::from(*encoded_count) }; total = checked_total( total, count, MAX_HULL_VERTICES, source, "convex hull vertex budget exceeded", )?; let byte_count = count .checked_mul(6) .ok_or_else(|| render_error(source, "convex hull byte count overflow"))?; let end = cursor .checked_add(byte_count) .ok_or_else(|| render_error(source, "convex hull offset overflow"))?; let bytes = positions .get(cursor..end) .ok_or_else(|| render_error(source, "truncated convex hull positions"))?; hulls.push(decode_quantized_positions(bytes, min, max, source)?); cursor = end; } if cursor != positions.len() { return Err(render_error(source, "trailing convex hull positions")); } Ok(hulls) } pub(crate) fn native_terrain_mesh( &self, z_map: Vec>, x_begin: f32, x_end: f32, y_begin: f32, y_end: f32, ) -> Result { let source = libremetaverse_types::UUID::zero(); let mesh = SculptMesh::from_height_rows(z_map, x_begin, x_end, y_begin, y_end, true) .map_err(|_| render_error(source, "generate bounded terrain geometry"))?; if mesh.coords.len() > MAX_FACE_VERTICES || mesh.coords.len() != mesh.normals.len() || mesh.coords.len() != mesh.uvs.len() { return Err(render_error(source, "invalid terrain vertex domains")); } let vertices = mesh .coords .iter() .zip(&mesh.normals) .zip(&mesh.uvs) .map(|((position, normal), uv)| { checked_vertex( Vector3 { x: position.x, y: position.y, z: position.z, }, Vector3 { x: normal.x, y: normal.y, z: normal.z, }, Vector2 { x: uv.u, y: uv.v }, source, ) }) .collect::, _>>()?; let mut indices = Vec::with_capacity( mesh.faces .len() .checked_mul(3) .ok_or_else(|| render_error(source, "terrain index count overflow"))?, ); for face in mesh.faces { push_triangle( &mut indices, face.v1, face.v2, face.v3, vertices.len(), source, )?; } make_face( 0, vertices, indices, PrimitiveTextureEntryFace::default(), Object::Undefined, Vector3::one(), source, ) } } impl IRendering for MeshFoundry { fn generate_faceted_mesh( &self, prim: Primitive, lod: DetailLevel, ) -> Result, Error> { self.native_generate_faceted_mesh(prim, lod) } fn generate_faceted_sculpt_mesh( &self, prim: Primitive, sculpt_texture: ManagedImage, lod: DetailLevel, ) -> Result, Error> { self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod) } fn generate_simple_mesh( &self, prim: Primitive, lod: DetailLevel, ) -> Result, Error> { self.native_generate_simple_mesh(prim, lod, true) } fn generate_simple_sculpt_mesh( &self, prim: Primitive, sculpt_texture: ManagedImage, lod: DetailLevel, ) -> Result, Error> { self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod) } fn transform_tex_coords( &self, vertices: &mut Vec, center: Vector3, te_face: PrimitiveTextureEntryFace, prim_scale: Vector3, ) -> Result<(), Error> { self.native_transform_tex_coords(vertices, center, te_face, prim_scale) } } fn unpack_mesh_map( asset_data: &[u8], source: libremetaverse_types::UUID, ) -> Result, Error> { if asset_data.is_empty() || asset_data.len() > MAX_ASSET_BYTES { return Err(render_error(source, "mesh asset size is out of range")); } let (header_value, payload_start) = deserialize_llsd_binary_prefix(asset_data) .map_err(|_| render_error(source, "decode mesh asset header"))?; let OSD::Map(header) = header_value else { return Err(render_error(source, "mesh asset header is not a map")); }; if header.len() > MAX_SECTIONS { return Err(render_error(source, "mesh section count exceeds limit")); } let mut output = HashMap::with_capacity(header.len() + 1); output.insert("asset_header".to_owned(), OSD::Map(header.clone())); for (name, value) in header { let OSD::Map(info) = &value else { output.insert(name, value); continue; }; let (Some(offset), Some(size)) = ( info.get("offset").and_then(osd_integer), info.get("size").and_then(osd_integer), ) else { output.insert(name, value); continue; }; if offset < 0 || size == 0 { output.insert(name, value); continue; } let offset = usize::try_from(offset) .map_err(|_| render_error(source, "mesh section offset is invalid"))?; let size = usize::try_from(size) .map_err(|_| render_error(source, "mesh section size is invalid"))?; if size > MAX_SECTION_BYTES { return Err(render_error( source, "compressed mesh section exceeds limit", )); } let begin = payload_start .checked_add(offset) .ok_or_else(|| render_error(source, "mesh section offset overflow"))?; let end = begin .checked_add(size) .ok_or_else(|| render_error(source, "mesh section size overflow"))?; let section = asset_data .get(begin..end) .ok_or_else(|| render_error(source, "mesh section is outside the asset"))?; output.insert(name, decompress_osd(section, source)?); } Ok(output) } fn decompress_osd(compressed: &[u8], source: libremetaverse_types::UUID) -> Result { if compressed.is_empty() || compressed.len() > MAX_SECTION_BYTES { return Err(render_error( source, "compressed mesh section size is out of range", )); } let mut zlib = ZlibDecoder::new(compressed); let mut output = Vec::new(); (&mut zlib) .take((MAX_SECTION_BYTES + 1) as u64) .read_to_end(&mut output) .map_err(|_| render_error(source, "decompress mesh section"))?; if output.len() > MAX_SECTION_BYTES { return Err(render_error( source, "decompressed mesh section exceeds limit", )); } let (value, _) = deserialize_llsd_binary_prefix(&output) .map_err(|_| render_error(source, "decode mesh section LLSD"))?; Ok(value) } fn select_lod( unpacked: &HashMap, requested: DetailLevel, ) -> Option<(DetailLevel, &[OSD])> { let order = [ requested, DetailLevel::Highest, DetailLevel::High, DetailLevel::Medium, DetailLevel::Low, ]; for (index, candidate) in order.into_iter().enumerate() { if index > 0 && candidate == requested { continue; } let key = lod_key(candidate); if let Some(OSD::Array(values)) = unpacked.get(key) && !values.is_empty() { return Some((candidate, values)); } } None } const fn lod_key(lod: DetailLevel) -> &'static str { match lod { DetailLevel::Highest => "high_lod", DetailLevel::High => "medium_lod", DetailLevel::Medium => "low_lod", DetailLevel::Low => "lowest_lod", } } fn decode_faceted( prim: &Primitive, submeshes: &[OSD], skin_data: Option, _lod: DetailLevel, source: libremetaverse_types::UUID, ) -> Result { if submeshes.len() > MAX_SUBMESHES { return Err(render_error(source, "mesh submesh count exceeds limit")); } let mut faces = Vec::with_capacity(submeshes.len()); let mut total_vertices = 0usize; let mut total_indices = 0usize; for (index, submesh) in submeshes.iter().enumerate() { if let Some(face) = decode_submesh(prim, submesh, index, skin_data.as_ref(), source)? { total_vertices = checked_total( total_vertices, face.vertices.len(), MAX_TOTAL_VERTICES, source, "mesh vertex budget exceeded", )?; total_indices = checked_total( total_indices, face.indices.len(), MAX_TOTAL_INDICES, source, "mesh index budget exceeded", )?; faces.push(face); } } Ok(FacetedMesh { faces, skin_data }) } #[allow(clippy::too_many_lines)] // One ordered pass validates parallel mesh domains. fn decode_submesh( prim: &Primitive, value: &OSD, face_index: usize, skin: Option<&MeshSkinData>, source: libremetaverse_types::UUID, ) -> Result, Error> { let map = as_map(value).ok_or_else(|| render_error(source, "mesh submesh is not a map"))?; if map.get("NoGeometry").and_then(osd_boolean).unwrap_or(false) { return Ok(None); } let positions = map .get("Position") .and_then(osd_binary) .ok_or_else(|| render_error(source, "mesh positions are missing"))?; if positions.is_empty() || !positions.len().is_multiple_of(6) { return Err(render_error(source, "mesh position buffer is malformed")); } let vertex_count = positions.len() / 6; if vertex_count > MAX_FACE_VERTICES { return Err(render_error( source, "mesh face exceeds 16-bit vertex range", )); } let (position_min, position_max) = domain3( map.get("PositionDomain"), Vector3 { x: -0.5, y: -0.5, z: -0.5, }, Vector3 { x: 0.5, y: 0.5, z: 0.5, }, source, "invalid mesh position domain", )?; let normals = map.get("Normal").and_then(osd_binary); if normals.is_some_and(|bytes| bytes.len() != vertex_count * 6) { return Err(render_error(source, "mesh normal buffer is misaligned")); } let tex_coords = map.get("TexCoord0").and_then(osd_binary); if tex_coords.is_some_and(|bytes| bytes.len() != vertex_count * 4) { return Err(render_error(source, "mesh primary UV buffer is misaligned")); } let (primary_min, primary_max) = if tex_coords.is_some() { domain2( Some( map.get("TexCoord0Domain") .ok_or_else(|| render_error(source, "primary UV domain is missing"))?, ), Vector2::zero(), Vector2::zero(), source, "invalid primary UV domain", )? } else { (Vector2::zero(), Vector2::zero()) }; let tex_coords1 = map.get("TexCoord1").and_then(osd_binary); if tex_coords1.is_some_and(|bytes| bytes.len() != vertex_count * 4) { return Err(render_error( source, "mesh secondary UV buffer is misaligned", )); } let (secondary_min, secondary_max) = if tex_coords1.is_some() { domain2( map.get("TexCoord1Domain"), Vector2::zero(), Vector2::zero(), source, "invalid secondary UV domain", )? } else { (Vector2::zero(), Vector2::zero()) }; let tangent_bytes = map.get("Tangent").and_then(osd_binary); if tangent_bytes.is_some_and(|bytes| bytes.len() != vertex_count * 8) { return Err(render_error(source, "mesh tangent buffer is misaligned")); } let mut vertices = Vec::with_capacity(vertex_count); let mut secondary = tex_coords1.map(|_| Vec::with_capacity(vertex_count)); for index in 0..vertex_count { let p = index * 6; let t = index * 4; let position = Vector3 { x: dequantize( read_u16(positions, p, source)?, position_min.x, position_max.x, ), y: dequantize( read_u16(positions, p + 2, source)?, position_min.y, position_max.y, ), z: dequantize( read_u16(positions, p + 4, source)?, position_min.z, position_max.z, ), }; let normal = if let Some(bytes) = normals { normalize3( Vector3 { x: dequantize(read_u16(bytes, p, source)?, -1.0, 1.0), y: dequantize(read_u16(bytes, p + 2, source)?, -1.0, 1.0), z: dequantize(read_u16(bytes, p + 4, source)?, -1.0, 1.0), }, Vector3::unit_z(), ) } else { Vector3::zero() }; let tex_coord = if let Some(bytes) = tex_coords { Vector2 { x: dequantize(read_u16(bytes, t, source)?, primary_min.x, primary_max.x), y: dequantize( read_u16(bytes, t + 2, source)?, primary_min.y, primary_max.y, ), } } else { Vector2::zero() }; vertices.push(checked_vertex(position, normal, tex_coord, source)?); if let (Some(bytes), Some(output)) = (tex_coords1, secondary.as_mut()) { output.push(Vector2 { x: dequantize( read_u16(bytes, t, source)?, secondary_min.x, secondary_max.x, ), y: dequantize( read_u16(bytes, t + 2, source)?, secondary_min.y, secondary_max.y, ), }); } } let triangles = map .get("TriangleList") .and_then(osd_binary) .ok_or_else(|| render_error(source, "mesh triangle list is missing"))?; if !triangles.len().is_multiple_of(6) || triangles.len() / 2 > MAX_TOTAL_INDICES { return Err(render_error(source, "mesh triangle buffer is malformed")); } let mut indices = Vec::with_capacity(triangles.len() / 2); for offset in (0..triangles.len()).step_by(6) { let a = read_u16(triangles, offset, source)?; let b = read_u16(triangles, offset + 2, source)?; let c = read_u16(triangles, offset + 4, source)?; for index in [a, b, c] { if usize::from(index) >= vertex_count { return Err(render_error(source, "mesh triangle index is out of range")); } indices.push(index); } } if normals.is_none() { generate_normals(&mut vertices, &indices); } let tangents = if let Some(bytes) = tangent_bytes { decode_tangents(bytes, vertex_count, source)? } else { generate_tangents(&vertices, &indices) }; let weights = match (skin, map.get("Weights").and_then(osd_binary)) { (Some(skin), Some(bytes)) => Some(decode_weights( bytes, skin.joint_names.len(), vertex_count, source, )?), (None, Some(_)) => { return Err(render_error(source, "mesh weights have no skin section")); } _ => None, }; let normalized_scale = map .get("NormalizedScale") .map(|value| osd_vector3(value, source, "invalid normalized face scale")) .transpose()? .unwrap_or(Vector3::one()); let texture = texture_for_face(prim, face_index, source)?; let mut face = make_face( i32::try_from(face_index).map_err(|_| render_error(source, "mesh face id overflow"))?, vertices, indices, texture, Object::opaque(MeshFaceAux { tangents }), normalized_scale, source, )?; face.tex_coords1 = secondary; face.weights = weights; Ok(Some(face)) } fn decode_skin(value: &OSD, source: libremetaverse_types::UUID) -> Result { let map = as_map(value).ok_or_else(|| render_error(source, "mesh skin section is not a map"))?; let names = match map.get("joint_names") { Some(OSD::Array(values)) if values.len() <= MAX_JOINTS => values .iter() .map(|value| { value .as_string() .map_err(|_| render_error(source, "invalid skin joint name")) }) .collect::, _>>()?, Some(OSD::Array(_)) => return Err(render_error(source, "skin joint count exceeds limit")), Some(_) => return Err(render_error(source, "skin joint names are not an array")), None => Vec::new(), }; let mut data = MeshSkinData::new()?; data.joint_names = names; data.inverse_bind_matrices = decode_matrix_array( map.get("inverse_bind_matrix"), data.joint_names.len(), false, source, "invalid inverse bind matrices", )?; data.alt_inverse_bind_matrices = decode_matrix_array( map.get("alt_inverse_bind_matrix"), data.joint_names.len(), true, source, "invalid alternate inverse bind matrices", )?; data.bind_shape_matrix = match map.get("bind_shape_matrix") { Some(value) => decode_matrix(value, source, "invalid bind shape matrix")?, None => identity_matrix().to_vec(), }; data.pelvis_offset = match map.get("pelvis_offset") { Some(value) => { let value = value .as_real() .map_err(|_| render_error(source, "invalid pelvis offset"))? as f32; if !value.is_finite() { return Err(render_error(source, "non-finite pelvis offset")); } value } None => 0.0, }; data.lock_scale_if_joint_position = map .get("lock_scale_if_joint_position") .map(OSD::as_boolean) .transpose() .map_err(|_| render_error(source, "invalid skin scale lock"))? .unwrap_or(false); Ok(data) } fn decode_matrix_array( value: Option<&OSD>, joint_count: usize, optional: bool, source: libremetaverse_types::UUID, context: &'static str, ) -> Result, Error> { let Some(value) = value else { if optional { return Ok(Vec::new()); } return Ok((0..joint_count).flat_map(|_| identity_matrix()).collect()); }; let OSD::Array(matrices) = value else { return Err(render_error(source, context)); }; if matrices.len() != joint_count { return Err(render_error(source, context)); } let mut output = Vec::with_capacity(joint_count * 16); for matrix in matrices { output.extend(decode_matrix(matrix, source, context)?); } Ok(output) } fn decode_matrix( value: &OSD, source: libremetaverse_types::UUID, context: &'static str, ) -> Result, Error> { let OSD::Array(values) = value else { return Err(render_error(source, context)); }; if values.len() != 16 { return Err(render_error(source, context)); } values .iter() .map(|value| { let value = value.as_real().map_err(|_| render_error(source, context))? as f32; if value.is_finite() { Ok(value) } else { Err(render_error(source, context)) } }) .collect() } fn decode_weights( bytes: &[u8], joint_count: usize, vertex_count: usize, source: libremetaverse_types::UUID, ) -> Result, Error> { if joint_count == 0 || bytes.len() > vertex_count.saturating_mul(13) { return Err(render_error(source, "mesh skin weights are out of range")); } let mut cursor = 0usize; let mut output = Vec::with_capacity(vertex_count); for _ in 0..vertex_count { if cursor >= bytes.len() { output.push(default_weight()); continue; } let mut joints = [0_i32; 4]; let mut weights = [0.0_f32; 4]; let mut count = 0usize; while cursor < bytes.len() && count < 4 { let joint = bytes[cursor]; cursor += 1; if joint == 0xff { break; } if usize::from(joint) >= joint_count { return Err(render_error( source, "mesh skin joint index is out of range", )); } let end = cursor .checked_add(2) .ok_or_else(|| render_error(source, "mesh weight offset overflow"))?; let raw = bytes .get(cursor..end) .ok_or_else(|| render_error(source, "truncated mesh skin weight"))?; cursor = end; joints[count] = i32::from(joint); weights[count] = (f32::from(u16::from_le_bytes([raw[0], raw[1]])) / 65_535.0).clamp(0.001, 0.999); count += 1; } let total: f32 = weights.iter().sum(); if total > 0.0 { for weight in &mut weights { *weight /= total; } } else { weights[0] = 1.0; } output.push(VertexWeight { joint0: joints[0], joint1: joints[1], joint2: joints[2], joint3: joints[3], weight0: weights[0], weight1: weights[1], weight2: weights[2], weight3: weights[3], }); } if cursor != bytes.len() { return Err(render_error(source, "trailing mesh skin weights")); } Ok(output) } const fn default_weight() -> VertexWeight { VertexWeight { joint0: 0, joint1: 0, joint2: 0, joint3: 0, weight0: 1.0, weight1: 0.0, weight2: 0.0, weight3: 0.0, } } fn decode_tangents( bytes: &[u8], vertex_count: usize, source: libremetaverse_types::UUID, ) -> Result, Error> { let mut tangents = Vec::with_capacity(vertex_count); for index in 0..vertex_count { let offset = index * 8; let tangent = normalize3( Vector3 { x: dequantize(read_u16(bytes, offset, source)?, -1.0, 1.0), y: dequantize(read_u16(bytes, offset + 2, source)?, -1.0, 1.0), z: dequantize(read_u16(bytes, offset + 4, source)?, -1.0, 1.0), }, Vector3::unit_x(), ); let handedness = dequantize(read_u16(bytes, offset + 6, source)?, -1.0, 1.0); tangents.push(Vector4 { x: tangent.x, y: tangent.y, z: tangent.z, w: if handedness < 0.0 { -1.0 } else { 1.0 }, }); } Ok(tangents) } fn generate_normals(vertices: &mut [Vertex], indices: &[u16]) { let mut normals = vec![Vector3::zero(); vertices.len()]; for triangle in indices.chunks_exact(3) { let a = vertices[usize::from(triangle[0])].position; let b = vertices[usize::from(triangle[1])].position; let c = vertices[usize::from(triangle[2])].position; let normal = cross(sub3(b, a), sub3(c, a)); for index in triangle { normals[usize::from(*index)] = add3(normals[usize::from(*index)], normal); } } for (vertex, normal) in vertices.iter_mut().zip(normals) { vertex.normal = normalize3(normal, Vector3::unit_z()); } } fn generate_tangents(vertices: &[Vertex], indices: &[u16]) -> Vec { let mut tangent_sum = vec![Vector3::zero(); vertices.len()]; let mut bitangent_sum = vec![Vector3::zero(); vertices.len()]; for triangle in indices.chunks_exact(3) { let ia = usize::from(triangle[0]); let ib = usize::from(triangle[1]); let ic = usize::from(triangle[2]); let edge1 = sub3(vertices[ib].position, vertices[ia].position); let edge2 = sub3(vertices[ic].position, vertices[ia].position); let duv1 = sub2(vertices[ib].tex_coord, vertices[ia].tex_coord); let duv2 = sub2(vertices[ic].tex_coord, vertices[ia].tex_coord); let determinant = duv1.x * duv2.y - duv1.y * duv2.x; if determinant.abs() <= f32::EPSILON || !determinant.is_finite() { continue; } let inverse = 1.0 / determinant; let tangent = scale3(sub3(scale3(edge1, duv2.y), scale3(edge2, duv1.y)), inverse); let bitangent = scale3(sub3(scale3(edge2, duv1.x), scale3(edge1, duv2.x)), inverse); for index in [ia, ib, ic] { tangent_sum[index] = add3(tangent_sum[index], tangent); bitangent_sum[index] = add3(bitangent_sum[index], bitangent); } } vertices .iter() .enumerate() .map(|(index, vertex)| { let projected = sub3( tangent_sum[index], scale3(vertex.normal, dot(vertex.normal, tangent_sum[index])), ); let tangent = normalize3(projected, orthogonal_tangent(vertex.normal)); let handedness = if dot(cross(vertex.normal, tangent), bitangent_sum[index]) < 0.0 { -1.0 } else { 1.0 }; Vector4 { x: tangent.x, y: tangent.y, z: tangent.z, w: handedness, } }) .collect() } fn apply_sculpt_modifiers(prim: &Primitive, mesh: &mut FacetedMesh) -> Result<(), Error> { let Some(sculpt) = prim.sculpt.as_ref() else { return Ok(()); }; let reflect = sculpt.mirror(); let invert = sculpt.invert(); let reverse = reflect ^ invert; if !reflect && !invert { return Ok(()); } for face in &mut mesh.faces { for vertex in &mut face.vertices { if reflect { vertex.position.x = -vertex.position.x; vertex.normal.x = -vertex.normal.x; } if invert { vertex.normal = scale3(vertex.normal, -1.0); } } if let Some(aux) = face.user_data.downcast_ref::() { let tangents = aux .tangents .iter() .map(|tangent| Vector4 { x: if reflect { -tangent.x } else { tangent.x }, y: tangent.y, z: tangent.z, w: if reverse { -tangent.w } else { tangent.w }, }) .collect(); face.user_data = Object::opaque(MeshFaceAux { tangents }); } if reverse { for triangle in face.indices.chunks_exact_mut(3) { triangle.swap(1, 2); } } update_face_bounds(face)?; } Ok(()) } fn flatten_faces( faces: Vec, source: libremetaverse_types::UUID, ) -> Result { let mut vertices = Vec::new(); let mut indices = Vec::new(); for face in faces { let base = vertices.len(); let combined = base .checked_add(face.vertices.len()) .ok_or_else(|| render_error(source, "simple mesh vertex count overflow"))?; if combined > MAX_FACE_VERTICES { return Err(render_error( source, "simple mesh exceeds 16-bit vertex range", )); } for index in face.indices { let global = base .checked_add(usize::from(index)) .ok_or_else(|| render_error(source, "simple mesh index overflow"))?; if global >= combined { return Err(render_error(source, "simple mesh index is out of range")); } indices.push( u16::try_from(global) .map_err(|_| render_error(source, "simple mesh index exceeds 16 bits"))?, ); } vertices.extend(face.vertices); } Ok(SimpleMesh { vertices, indices }) } fn make_face( id: i32, vertices: Vec, indices: Vec, texture_face: PrimitiveTextureEntryFace, user_data: Object, normalized_scale: Vector3, source: libremetaverse_types::UUID, ) -> Result { if vertices.is_empty() { return Err(render_error(source, "render face has no vertices")); } let mut face = Face { begin_s: 0, begin_t: 0, center: Vector3::zero(), edge: Vec::new(), id, indices, mask: FaceMask::SINGLE, max_extent: Vector3::zero(), min_extent: Vector3::zero(), normalized_scale, num_s: 0, num_t: 0, tex_coords1: None, texture_face, user_data, vertices, weights: None, }; update_face_bounds(&mut face)?; Ok(face) } fn update_face_bounds(face: &mut Face) -> Result<(), Error> { let Some(first) = face.vertices.first() else { return Err(Error::Argument); }; let mut min = first.position; let mut max = first.position; for vertex in &face.vertices[1..] { min.x = min.x.min(vertex.position.x); min.y = min.y.min(vertex.position.y); min.z = min.z.min(vertex.position.z); max.x = max.x.max(vertex.position.x); max.y = max.y.max(vertex.position.y); max.z = max.z.max(vertex.position.z); } face.min_extent = min; face.max_extent = max; face.center = Vector3 { x: (min.x + max.x) * 0.5, y: (min.y + max.y) * 0.5, z: (min.z + max.z) * 0.5, }; Ok(()) } fn texture_for_face( prim: &Primitive, index: usize, source: libremetaverse_types::UUID, ) -> Result { let Some(textures) = prim.textures.as_ref() else { return Ok(PrimitiveTextureEntryFace::default()); }; let index = u32::try_from(index).map_err(|_| render_error(source, "texture face index overflow"))?; textures .get_face(index) .map_err(|_| render_error(source, "texture face index is out of range")) .map(|face| face.cloned().unwrap_or_default()) } fn checked_vertex( position: Vector3, normal: Vector3, tex_coord: Vector2, source: libremetaverse_types::UUID, ) -> Result { if !finite3(position) || !finite3(normal) || !finite2(tex_coord) { return Err(render_error( source, "renderer produced a non-finite vertex", )); } Ok(Vertex { position, normal, tex_coord, }) } fn push_triangle( indices: &mut Vec, a: i32, b: i32, c: i32, vertex_count: usize, source: libremetaverse_types::UUID, ) -> Result<(), Error> { for index in [a, b, c] { let index = usize::try_from(index).map_err(|_| render_error(source, "negative renderer index"))?; if index >= vertex_count { return Err(render_error(source, "renderer index is out of range")); } indices.push( u16::try_from(index) .map_err(|_| render_error(source, "renderer index exceeds 16 bits"))?, ); } Ok(()) } fn domain3( value: Option<&OSD>, default_min: Vector3, default_max: Vector3, source: libremetaverse_types::UUID, context: &'static str, ) -> Result<(Vector3, Vector3), Error> { let Some(value) = value else { return Ok((default_min, default_max)); }; let map = as_map(value).ok_or_else(|| render_error(source, context))?; let min = map .get("Min") .ok_or_else(|| render_error(source, context)) .and_then(|value| osd_vector3(value, source, context))?; let max = map .get("Max") .ok_or_else(|| render_error(source, context)) .and_then(|value| osd_vector3(value, source, context))?; validate_domain3(min, max, source, context)?; Ok((min, max)) } fn domain2( value: Option<&OSD>, default_min: Vector2, default_max: Vector2, source: libremetaverse_types::UUID, context: &'static str, ) -> Result<(Vector2, Vector2), Error> { let Some(value) = value else { return Ok((default_min, default_max)); }; let map = as_map(value).ok_or_else(|| render_error(source, context))?; let min = map .get("Min") .ok_or_else(|| render_error(source, context))? .as_vector2() .map_err(|_| render_error(source, context))?; let max = map .get("Max") .ok_or_else(|| render_error(source, context))? .as_vector2() .map_err(|_| render_error(source, context))?; if !finite2(min) || !finite2(max) || min.x > max.x || min.y > max.y { return Err(render_error(source, context)); } Ok((min, max)) } fn validate_domain3( min: Vector3, max: Vector3, source: libremetaverse_types::UUID, context: &'static str, ) -> Result<(), Error> { if !finite3(min) || !finite3(max) || min.x > max.x || min.y > max.y || min.z > max.z { Err(render_error(source, context)) } else { Ok(()) } } fn decode_quantized_positions( bytes: &[u8], min: Vector3, max: Vector3, source: libremetaverse_types::UUID, ) -> Result, Error> { if !bytes.len().is_multiple_of(6) || bytes.len() / 6 > MAX_HULL_VERTICES { return Err(render_error(source, "convex position buffer is malformed")); } (0..bytes.len()) .step_by(6) .map(|offset| { Ok(Vector3 { x: dequantize(read_u16(bytes, offset, source)?, min.x, max.x), y: dequantize(read_u16(bytes, offset + 2, source)?, min.y, max.y), z: dequantize(read_u16(bytes, offset + 4, source)?, min.z, max.z), }) }) .collect() } fn read_u16(bytes: &[u8], offset: usize, source: libremetaverse_types::UUID) -> Result { let bytes = bytes .get(offset..offset + 2) .ok_or_else(|| render_error(source, "truncated quantized mesh value"))?; Ok(u16::from_le_bytes([bytes[0], bytes[1]])) } fn checked_total( current: usize, additional: usize, limit: usize, source: libremetaverse_types::UUID, context: &'static str, ) -> Result { let total = current .checked_add(additional) .ok_or_else(|| render_error(source, context))?; if total > limit { Err(render_error(source, context)) } else { Ok(total) } } fn osd_vector3( value: &OSD, source: libremetaverse_types::UUID, context: &'static str, ) -> Result { let value = value .as_vector3() .map_err(|_| render_error(source, context))?; if finite3(value) { Ok(value) } else { Err(render_error(source, context)) } } const fn as_map(value: &OSD) -> Option<&HashMap> { if let OSD::Map(map) = value { Some(map) } else { None } } const fn osd_binary(value: &OSD) -> Option<&Vec> { if let OSD::Binary(bytes) = value { Some(bytes) } else { None } } const fn osd_integer(value: &OSD) -> Option { if let OSD::Integer(value) = value { Some(*value) } else { None } } const fn osd_boolean(value: &OSD) -> Option { if let OSD::Boolean(value) = value { Some(*value) } else { None } } const fn dequantize(value: u16, min: f32, max: f32) -> f32 { min + (value as f32 / 65_535.0) * (max - min) } fn normalize3(value: Vector3, fallback: Vector3) -> Vector3 { let length_squared = dot(value, value); if length_squared > f32::EPSILON && length_squared.is_finite() { scale3(value, length_squared.sqrt().recip()) } else { fallback } } fn orthogonal_tangent(normal: Vector3) -> Vector3 { let axis = if normal.z.abs() < 0.9 { Vector3::unit_z() } else { Vector3::unit_y() }; normalize3(cross(axis, normal), Vector3::unit_x()) } const fn identity_matrix() -> [f32; 16] { [ 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, ] } const fn add3(left: Vector3, right: Vector3) -> Vector3 { Vector3 { x: left.x + right.x, y: left.y + right.y, z: left.z + right.z, } } const fn sub3(left: Vector3, right: Vector3) -> Vector3 { Vector3 { x: left.x - right.x, y: left.y - right.y, z: left.z - right.z, } } const fn sub2(left: Vector2, right: Vector2) -> Vector2 { Vector2 { x: left.x - right.x, y: left.y - right.y, } } const fn scale3(value: Vector3, scalar: f32) -> Vector3 { Vector3 { x: value.x * scalar, y: value.y * scalar, z: value.z * scalar, } } const fn dot(left: Vector3, right: Vector3) -> f32 { left.x * right.x + left.y * right.y + left.z * right.z } const fn cross(left: Vector3, right: Vector3) -> Vector3 { Vector3 { x: left.y * right.z - left.z * right.y, y: left.z * right.x - left.x * right.z, z: left.x * right.y - left.y * right.x, } } const fn finite2(value: Vector2) -> bool { value.x.is_finite() && value.y.is_finite() } const fn finite3(value: Vector3) -> bool { value.x.is_finite() && value.y.is_finite() && value.z.is_finite() } const fn render_error(source: libremetaverse_types::UUID, context: &'static str) -> Error { Error::Rendering { source, context } }