//! CPU-side forward kinematics and bounded linear-blend skinning. #![allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)] // Mapped signatures are fixed. use crate::animesh_runtime::JointPose; use crate::rendering::{Face, Joint, LindenSkeleton}; use libremetaverse_types::{Error, Matrix4, Quaternion, Vector3}; use std::collections::HashMap; const MAX_SKIN_JOINTS: usize = 512; const MAX_VERTICES: usize = 1_000_000; #[derive(Clone, Debug, Default)] pub struct MeshSkinData { pub alt_inverse_bind_matrices: Vec, pub bind_shape_matrix: Vec, pub inverse_bind_matrices: Vec, pub joint_names: Vec, pub lock_scale_if_joint_position: bool, pub pelvis_offset: f32, } impl MeshSkinData { pub(crate) fn native_new() -> Result { Ok(Self::default()) } } pub struct AnimeshSkinning; impl AnimeshSkinning { pub(crate) fn native_compute_skinning_matrices( pose: Option>, skeleton: Option, skin_data: Option, ) -> Result, Error> { let pose = pose.ok_or(Error::ArgumentNull)?; let skeleton = skeleton.ok_or(Error::ArgumentNull)?; let skin_data = skin_data.ok_or(Error::ArgumentNull)?; if skin_data.joint_names.len() > MAX_SKIN_JOINTS { return Err(Error::Argument); } let mut world = HashMap::new(); let mut visited = 0_usize; compute_world( &skeleton.bone(), Matrix4::identity(), &pose, &mut world, &mut visited, )?; skin_data .joint_names .iter() .enumerate() .map(|(index, name)| { let Some(world) = world.get(name).copied() else { return Ok(Matrix4::identity()); }; let inverse = extract_matrix(&skin_data.inverse_bind_matrices, index); Matrix4::multiply_with_matrix4_matrix4(inverse, world) }) .collect() } pub(crate) fn native_deform_vertices( face: Face, matrices: Vec, bind_shape: Matrix4, positions: &mut [Vector3], mut normals: Option<&mut [Vector3]>, ) -> Result<(), Error> { let count = face.vertices.len(); if count > MAX_VERTICES || positions.len() < count { return Err(Error::Argument); } if normals.as_ref().is_some_and(|values| values.len() < count) { return Err(Error::Argument); } for (index, vertex) in face.vertices.iter().enumerate() { let Some(weights) = face.weights.as_ref().and_then(|values| values.get(index)) else { positions[index] = vertex.position; if let Some(output) = normals.as_deref_mut() { output[index] = vertex.normal; } continue; }; if matrices.is_empty() { positions[index] = vertex.position; if let Some(output) = normals.as_deref_mut() { output[index] = vertex.normal; } continue; } let position = Vector3::transform(vertex.position, bind_shape)?; let influences = [ (weights.joint0, weights.weight0), (weights.joint1, weights.weight1), (weights.joint2, weights.weight2), (weights.joint3, weights.weight3), ]; let mut result = Vector3::zero(); for (joint, weight) in influences { if weight > 0.0 && weight.is_finite() { let joint = usize::try_from(joint).map_err(|_| Error::Argument)?; let matrix = matrices.get(joint).ok_or(Error::Argument)?; result = Vector3::add_with_vector3_vector3( result, Vector3::multiply_with_vector3_single( Vector3::transform(position, *matrix)?, weight, )?, )?; } } positions[index] = result; if let Some(output) = normals.as_deref_mut() { let mut result = Vector3::zero(); for (joint, weight) in influences { if weight > 0.0 && weight.is_finite() { let joint = usize::try_from(joint).map_err(|_| Error::Argument)?; let matrix = matrices.get(joint).ok_or(Error::Argument)?; result = Vector3::add_with_vector3_vector3( result, Vector3::multiply_with_vector3_single( Vector3::transform_normal(vertex.normal, *matrix)?, weight, )?, )?; } } output[index] = if result == Vector3::zero() { result } else { Vector3::normalize(result)? }; } } Ok(()) } } fn compute_world( joint: &Joint, parent: Matrix4, pose: &HashMap, output: &mut HashMap, visited: &mut usize, ) -> Result<(), Error> { *visited = visited.checked_add(1).ok_or(Error::Argument)?; if *visited > MAX_SKIN_JOINTS { return Err(Error::Argument); } let local = local_transform(joint, pose)?; let world = Matrix4::multiply_with_matrix4_matrix4(local, parent)?; output.insert(joint.base.name(), world); for alias in joint.get_aliases_list()? { output.insert(alias, world); } for child in joint.bone().unwrap_or_default() { compute_world(&child, world, pose, output, visited)?; } Ok(()) } fn local_transform(joint: &Joint, pose: &HashMap) -> Result { let mut translation = vector3(&joint.base.pos()); let mut rotation = rotation(&joint.base.rot())?; let override_pose = pose.get(&joint.base.name()).copied().or_else(|| { joint .get_aliases_list() .ok()? .into_iter() .find_map(|alias| pose.get(&alias).copied()) }); if let Some(value) = override_pose { if value.has_position { translation = value.position; } if value.has_rotation { rotation = value.rotation; } } Matrix4::multiply_with_matrix4_matrix4( Matrix4::create_from_quaternion(rotation)?, Matrix4::create_translation(translation)?, ) } fn vector3(values: &[f32]) -> Vector3 { if values.len() < 3 { Vector3::zero() } else { Vector3 { x: values[0], y: values[1], z: values[2], } } } fn rotation(values: &[f32]) -> Result { if values.len() < 3 { return Ok(Quaternion::identity()); } Quaternion::create_from_eulers_with_single_single_single( values[0].to_radians(), values[1].to_radians(), values[2].to_radians(), ) } fn extract_matrix(values: &[f32], index: usize) -> Matrix4 { let Some(offset) = index.checked_mul(16) else { return Matrix4::identity(); }; let Some(values) = values.get(offset..offset.saturating_add(16)) else { return Matrix4::identity(); }; Matrix4 { m11: values[0], m12: values[1], m13: values[2], m14: values[3], m21: values[4], m22: values[5], m23: values[6], m24: values[7], m31: values[8], m32: values[9], m33: values[10], m34: values[11], m41: values[12], m42: values[13], m43: values[14], m44: values[15], } }