use anyhow::Result;
use crate::sdf::{Path, Value, Variability};
use crate::usd::{Prim, Stage};
use crate::schemas::skel::tokens::{
API_SKEL_BINDING, A_GEOM_BIND_TRANSFORM, A_JOINT_INDICES, A_JOINT_WEIGHTS, A_SKEL_BLEND_SHAPES, A_SKEL_JOINTS,
A_SKINNING_METHOD, META_ELEMENT_SIZE, META_INTERPOLATION, REL_SKEL_ANIMATION_SOURCE, REL_SKEL_BLEND_SHAPE_TARGETS,
REL_SKEL_SKELETON,
};
use crate::schemas::skel::types::{InfluenceInterpolation, SkinningMethod};
pub fn apply_skel_binding<'s>(stage: &'s Stage, path: impl Into<Path>) -> Result<SkelBindingAuthor<'s>> {
let prim = stage.override_prim(path)?.add_applied_schema(API_SKEL_BINDING)?;
Ok(SkelBindingAuthor { prim })
}
pub struct SkelBindingAuthor<'s> {
prim: Prim<'s>,
}
impl<'s> SkelBindingAuthor<'s> {
pub fn into_prim(self) -> Prim<'s> {
self.prim
}
pub fn set_skeleton(self, skeleton: impl Into<Path>) -> Result<Self> {
let rel_path = self.prim.path().append_property(REL_SKEL_SKELETON)?;
self.prim
.stage()
.create_relationship(rel_path)?
.set_custom(false)?
.set_targets([skeleton.into()])?;
Ok(self)
}
pub fn set_animation_source(self, anim: impl Into<Path>) -> Result<Self> {
let rel_path = self.prim.path().append_property(REL_SKEL_ANIMATION_SOURCE)?;
self.prim
.stage()
.create_relationship(rel_path)?
.set_custom(false)?
.set_targets([anim.into()])?;
Ok(self)
}
pub fn set_blend_shape_targets<I, P>(self, targets: I) -> Result<Self>
where
I: IntoIterator<Item = P>,
P: Into<Path>,
{
let rel_path = self.prim.path().append_property(REL_SKEL_BLEND_SHAPE_TARGETS)?;
let paths: Vec<Path> = targets.into_iter().map(Into::into).collect();
self.prim
.stage()
.create_relationship(rel_path)?
.set_custom(false)?
.set_targets(paths)?;
Ok(self)
}
pub fn set_skinning_method(self, method: SkinningMethod) -> Result<Self> {
let attr_path = self.prim.path().append_property(A_SKINNING_METHOD)?;
self.prim
.stage()
.create_attribute(attr_path, "token")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::Token(method.as_token().to_string()))?;
Ok(self)
}
pub fn set_geom_bind_transform(self, transform: [f64; 16]) -> Result<Self> {
let attr_path = self.prim.path().append_property(A_GEOM_BIND_TRANSFORM)?;
self.prim
.stage()
.create_attribute(attr_path, "matrix4d")?
.set_custom(false)?
.set(Value::Matrix4d(transform))?;
Ok(self)
}
pub fn set_joints<I, S>(self, joints: I) -> Result<Self>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let tokens: Vec<String> = joints.into_iter().map(Into::into).collect();
let attr_path = self.prim.path().append_property(A_SKEL_JOINTS)?;
self.prim
.stage()
.create_attribute(attr_path, "token[]")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::TokenVec(tokens))?;
Ok(self)
}
pub fn set_joint_indices(
self,
indices: Vec<i32>,
element_size: i32,
interpolation: InfluenceInterpolation,
) -> Result<Self> {
let attr_path = self.prim.path().append_property(A_JOINT_INDICES)?;
self.prim
.stage()
.create_attribute(attr_path, "int[]")?
.set_custom(false)?
.set(Value::IntVec(indices))?
.set_metadata(META_ELEMENT_SIZE, Value::Int(element_size))?
.set_metadata(META_INTERPOLATION, Value::Token(interpolation.as_token().to_string()))?;
Ok(self)
}
pub fn set_joint_weights(
self,
weights: Vec<f32>,
element_size: i32,
interpolation: InfluenceInterpolation,
) -> Result<Self> {
let attr_path = self.prim.path().append_property(A_JOINT_WEIGHTS)?;
self.prim
.stage()
.create_attribute(attr_path, "float[]")?
.set_custom(false)?
.set(Value::FloatVec(weights))?
.set_metadata(META_ELEMENT_SIZE, Value::Int(element_size))?
.set_metadata(META_INTERPOLATION, Value::Token(interpolation.as_token().to_string()))?;
Ok(self)
}
pub fn set_blend_shapes<I, S>(self, blend_shapes: I) -> Result<Self>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let tokens: Vec<String> = blend_shapes.into_iter().map(Into::into).collect();
let attr_path = self.prim.path().append_property(A_SKEL_BLEND_SHAPES)?;
self.prim
.stage()
.create_attribute(attr_path, "token[]")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::TokenVec(tokens))?;
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf;
fn identity() -> [f64; 16] {
let mut m = [0.0; 16];
m[0] = 1.0;
m[5] = 1.0;
m[10] = 1.0;
m[15] = 1.0;
m
}
#[test]
fn apply_skel_binding_adds_api_to_prim() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Body")?.set_type_name("Mesh")?;
apply_skel_binding(&stage, sdf::path("/Body")?)?;
let api = stage.api_schemas(&sdf::path("/Body")?)?;
assert!(api.iter().any(|s| s == "SkelBindingAPI"));
Ok(())
}
#[test]
fn full_binding_roundtrip_via_reader() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/World/Body")?.set_type_name("Mesh")?;
apply_skel_binding(&stage, sdf::path("/World/Body")?)?
.set_skeleton(sdf::path("/World/Rig")?)?
.set_animation_source(sdf::path("/World/Anim")?)?
.set_skinning_method(SkinningMethod::ClassicLinear)?
.set_geom_bind_transform(identity())?
.set_joints(["Root/Hip", "Root/Hip/Knee"])?
.set_joint_indices(vec![0, 1, 0, 1], 2, InfluenceInterpolation::Vertex)?
.set_joint_weights(vec![1.0, 0.0, 0.5, 0.5], 2, InfluenceInterpolation::Vertex)?
.set_blend_shapes(["smile"])?
.set_blend_shape_targets([sdf::path("/World/Smile")?])?;
let bind = crate::schemas::skel::read_skel_binding(&stage, &sdf::path("/World/Body")?)?
.expect("SkelBindingAPI authored");
assert_eq!(bind.path, "/World/Body");
assert_eq!(bind.skeleton.as_deref(), Some("/World/Rig"));
assert_eq!(bind.animation_source.as_deref(), Some("/World/Anim"));
assert_eq!(bind.skinning_method, SkinningMethod::ClassicLinear);
assert!(bind.geom_bind_transform.is_some());
assert_eq!(
bind.joint_subset,
vec!["Root/Hip".to_string(), "Root/Hip/Knee".to_string()]
);
assert_eq!(bind.joint_indices, vec![0, 1, 0, 1]);
assert_eq!(bind.joint_weights, vec![1.0, 0.0, 0.5, 0.5]);
assert_eq!(bind.elements_per_element, 2);
assert_eq!(bind.interpolation, InfluenceInterpolation::Vertex);
assert_eq!(bind.blend_shapes, vec!["smile".to_string()]);
assert_eq!(bind.blend_shape_targets, vec!["/World/Smile".to_string()]);
Ok(())
}
#[test]
fn skinning_method_dual_quaternion_authored() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage.define_prim("/Body")?.set_type_name("Mesh")?;
apply_skel_binding(&stage, sdf::path("/Body")?)?.set_skinning_method(SkinningMethod::DualQuaternion)?;
match stage.field::<sdf::Value>("/Body.primvars:skel:skinningMethod", sdf::FieldKey::Default)? {
Some(sdf::Value::Token(t)) => assert_eq!(t, "dualQuaternion"),
other => panic!("expected dualQuaternion token, got {other:?}"),
}
Ok(())
}
}