Files
MetaCrate/crates/libremetaverse/src/animesh_skinning.rs
Chili Palmer ed516a1a5d
Some checks failed
Native code generation / deterministic (push) Failing after 2m20s
Imaging and meshing gate / native (push) Failing after 1m25s
Native Rust workspace compile / compile (push) Failing after 56s
Implement avatar animation and skinning (#67)
2026-08-10 11:45:07 +00:00

239 lines
7.8 KiB
Rust

//! 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<f32>,
pub bind_shape_matrix: Vec<f32>,
pub inverse_bind_matrices: Vec<f32>,
pub joint_names: Vec<String>,
pub lock_scale_if_joint_position: bool,
pub pelvis_offset: f32,
}
impl MeshSkinData {
pub(crate) fn native_new() -> Result<Self, Error> {
Ok(Self::default())
}
}
pub struct AnimeshSkinning;
impl AnimeshSkinning {
pub(crate) fn native_compute_skinning_matrices(
pose: Option<HashMap<String, JointPose>>,
skeleton: Option<LindenSkeleton>,
skin_data: Option<MeshSkinData>,
) -> Result<Vec<Matrix4>, 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<Matrix4>,
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<String, JointPose>,
output: &mut HashMap<String, Matrix4>,
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<String, JointPose>) -> Result<Matrix4, Error> {
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<Quaternion, Error> {
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],
}
}