239 lines
7.8 KiB
Rust
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],
|
|
}
|
|
}
|