Implement avatar animation and skinning (#67)
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

This commit is contained in:
2026-08-10 11:45:07 +00:00
parent b1f60db3d6
commit ed516a1a5d
13 changed files with 3458 additions and 423 deletions

View File

@@ -9,6 +9,11 @@ use roxmltree::{Document, Node};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
const MAX_SKELETON_BYTES: u64 = 8 * 1024 * 1024;
const MAX_SKELETON_BONES: usize = 512;
const MAX_COLLISION_VOLUMES: usize = 512;
const MAX_SKELETON_DEPTH: usize = 128;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct JointBase {
name: String,
@@ -310,7 +315,17 @@ impl LindenSkeleton {
let Some(file_name) = file_name else {
return Self::get_default();
};
if std::fs::metadata(&file_name)
.map_err(|_| crate::Error::Argument)?
.len()
> MAX_SKELETON_BYTES
{
return Err(crate::Error::Argument);
}
let text = std::fs::read_to_string(file_name).map_err(|_| crate::Error::Argument)?;
if text.len() as u64 > MAX_SKELETON_BYTES {
return Err(crate::Error::Argument);
}
parse_skeleton_xml(&text)
}
@@ -469,10 +484,20 @@ fn parse_base(node: Node<'_, '_>) -> Result<JointBase, crate::Error> {
))
}
fn parse_joint(node: Node<'_, '_>, names: &mut HashSet<String>) -> Result<Joint, crate::Error> {
fn parse_joint(
node: Node<'_, '_>,
names: &mut HashSet<String>,
depth: usize,
bone_count: &mut usize,
collision_volume_count: &mut usize,
) -> Result<Joint, crate::Error> {
if !node.has_tag_name("bone") {
return Err(parse_error(node_position(node)));
}
if depth > MAX_SKELETON_DEPTH || *bone_count >= MAX_SKELETON_BONES {
return Err(parse_error(node_position(node)));
}
*bone_count += 1;
let base = parse_base(node)?;
if !names.insert(base.name.clone()) {
return Err(parse_error(node_position(node)));
@@ -487,12 +512,22 @@ fn parse_joint(node: Node<'_, '_>, names: &mut HashSet<String>) -> Result<Joint,
let mut bones = Vec::new();
for child in node.children().filter(Node::is_element) {
if child.has_tag_name("collision_volume") {
if *collision_volume_count >= MAX_COLLISION_VOLUMES {
return Err(parse_error(node_position(child)));
}
if child.children().any(|node| node.is_element()) {
return Err(parse_error(node_position(child)));
}
collision_volumes.push(CollisionVolume::from_base(parse_base(child)?));
*collision_volume_count += 1;
} else if child.has_tag_name("bone") {
bones.push(parse_joint(child, names)?);
bones.push(parse_joint(
child,
names,
depth + 1,
bone_count,
collision_volume_count,
)?);
} else {
return Err(parse_error(node_position(child)));
}
@@ -508,6 +543,9 @@ fn parse_joint(node: Node<'_, '_>, names: &mut HashSet<String>) -> Result<Joint,
}
pub(crate) fn parse_skeleton_xml(text: &str) -> Result<LindenSkeleton, crate::Error> {
if text.len() as u64 > MAX_SKELETON_BYTES {
return Err(parse_error(0));
}
let document = Document::parse(text).map_err(|_| parse_error(0))?;
let root = document.root_element();
if !root.has_tag_name("linden_skeleton") {
@@ -528,12 +566,23 @@ pub(crate) fn parse_skeleton_xml(text: &str) -> Result<LindenSkeleton, crate::Er
let expected_collision_volumes = num_collision_volumes
.parse::<usize>()
.map_err(|_| parse_error(node_position(root)))?;
if expected_bones > MAX_SKELETON_BONES || expected_collision_volumes > MAX_COLLISION_VOLUMES {
return Err(parse_error(node_position(root)));
}
let root_bones = root.children().filter(Node::is_element).collect::<Vec<_>>();
if root_bones.len() != 1 || !root_bones[0].has_tag_name("bone") {
return Err(parse_error(node_position(root)));
}
let mut names = HashSet::new();
let bone = parse_joint(root_bones[0], &mut names)?;
let mut bone_count = 0;
let mut collision_volume_count = 0;
let bone = parse_joint(
root_bones[0],
&mut names,
0,
&mut bone_count,
&mut collision_volume_count,
)?;
let (actual_bones, actual_collision_volumes) = skeleton_counts(&bone);
if actual_bones != expected_bones || actual_collision_volumes != expected_collision_volumes {
return Err(parse_error(node_position(root)));