use anyhow::Result;
use crate::gf;
use crate::sdf::{self, Value, Variability};
use crate::usd::{Attribute, Prim, Relationship, Stage};
use super::impl_skel_schema;
use super::tokens as tok;
use super::{InfluenceInterpolation, SkinningMethod};
use crate::schemas::common::{get_typed, get_with_api};
#[derive(Clone, derive_more::Deref)]
pub struct SkelRoot(Prim);
impl SkelRoot {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_SKEL_ROOT)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_SKEL_ROOT).map(|o| o.map(Self))
}
}
impl_skel_schema!(boundable SkelRoot);
#[derive(Clone, derive_more::Deref)]
pub struct Skeleton(Prim);
impl Skeleton {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_SKELETON)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_SKELETON).map(|o| o.map(Self))
}
pub fn joints_attr(&self) -> Attribute {
self.attribute(tok::A_JOINTS)
}
pub fn create_joints_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_JOINTS, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn joint_names_attr(&self) -> Attribute {
self.attribute(tok::A_JOINT_NAMES)
}
pub fn create_joint_names_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_JOINT_NAMES, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn bind_transforms_attr(&self) -> Attribute {
self.attribute(tok::A_BIND_TRANSFORMS)
}
pub fn create_bind_transforms_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_BIND_TRANSFORMS, "matrix4d[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn rest_transforms_attr(&self) -> Attribute {
self.attribute(tok::A_REST_TRANSFORMS)
}
pub fn create_rest_transforms_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_REST_TRANSFORMS, "matrix4d[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn joints(&self) -> Result<Vec<String>> {
token_vec(&self.joints_attr())
}
pub fn bind_transforms(&self) -> Result<Vec<gf::Matrix4d>> {
mat4_vec(&self.bind_transforms_attr())
}
pub fn rest_transforms(&self) -> Result<Vec<gf::Matrix4d>> {
mat4_vec(&self.rest_transforms_attr())
}
pub fn joint_parent_indices(&self) -> Result<Vec<Option<usize>>> {
let joints = self.joints()?;
let by_path = super::topology::joint_index_map(&joints);
Ok(joints
.iter()
.map(|p| p.rsplit_once('/').and_then(|(parent, _)| by_path.get(parent).copied()))
.collect())
}
pub fn joint_short_names(&self) -> Result<Vec<String>> {
Ok(self
.joints()?
.iter()
.map(|p| p.rsplit_once('/').map(|(_, n)| n).unwrap_or(p).to_string())
.collect())
}
pub fn map_anim_joints(&self, anim_joints: &[String]) -> Result<Vec<Option<usize>>> {
let joints = self.joints()?;
let by_path = super::topology::joint_index_map(&joints);
Ok(anim_joints.iter().map(|p| by_path.get(p.as_str()).copied()).collect())
}
}
impl_skel_schema!(boundable Skeleton);
#[derive(Clone, derive_more::Deref)]
pub struct SkelAnimation(Prim);
impl SkelAnimation {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_SKEL_ANIMATION)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_SKEL_ANIMATION).map(|o| o.map(Self))
}
pub fn joints_attr(&self) -> Attribute {
self.attribute(tok::A_JOINTS)
}
pub fn create_joints_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_JOINTS, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn blend_shapes_attr(&self) -> Attribute {
self.attribute(tok::A_BLEND_SHAPES)
}
pub fn create_blend_shapes_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_BLEND_SHAPES, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn translations_attr(&self) -> Attribute {
self.attribute(tok::A_TRANSLATIONS)
}
pub fn create_translations_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_TRANSLATIONS, "float3[]")?
.set_custom(false)?)
}
pub fn rotations_attr(&self) -> Attribute {
self.attribute(tok::A_ROTATIONS)
}
pub fn create_rotations_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_ROTATIONS, "quatf[]")?.set_custom(false)?)
}
pub fn scales_attr(&self) -> Attribute {
self.attribute(tok::A_SCALES)
}
pub fn create_scales_attr(&self) -> Result<Attribute> {
Ok(self.create_attribute(tok::A_SCALES, "half3[]")?.set_custom(false)?)
}
pub fn blend_shape_weights_attr(&self) -> Attribute {
self.attribute(tok::A_BLEND_SHAPE_WEIGHTS)
}
pub fn create_blend_shape_weights_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_BLEND_SHAPE_WEIGHTS, "float[]")?
.set_custom(false)?)
}
pub fn joints(&self) -> Result<Vec<String>> {
token_vec(&self.joints_attr())
}
pub fn blend_shapes(&self) -> Result<Vec<String>> {
token_vec(&self.blend_shapes_attr())
}
}
impl_skel_schema!(typed SkelAnimation);
#[derive(Debug, Clone, PartialEq)]
pub struct Inbetween {
pub name: String,
pub weight: Option<f32>,
pub offsets: Vec<gf::Vec3f>,
pub normal_offsets: Vec<gf::Vec3f>,
}
#[derive(Clone, derive_more::Deref)]
pub struct BlendShape(Prim);
impl BlendShape {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_BLEND_SHAPE)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_BLEND_SHAPE).map(|o| o.map(Self))
}
pub fn offsets_attr(&self) -> Attribute {
self.attribute(tok::A_OFFSETS)
}
pub fn create_offsets_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_OFFSETS, "vector3f[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn normal_offsets_attr(&self) -> Attribute {
self.attribute(tok::A_NORMAL_OFFSETS)
}
pub fn create_normal_offsets_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_NORMAL_OFFSETS, "vector3f[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn point_indices_attr(&self) -> Attribute {
self.attribute(tok::A_POINT_INDICES)
}
pub fn create_point_indices_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_POINT_INDICES, "int[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn offsets(&self) -> Result<Vec<gf::Vec3f>> {
vec3f_vec(&self.offsets_attr())
}
pub fn normal_offsets(&self) -> Result<Vec<gf::Vec3f>> {
vec3f_vec(&self.normal_offsets_attr())
}
pub fn point_indices(&self) -> Result<Vec<i32>> {
Ok(match self.point_indices_attr().get::<Value>()? {
Some(Value::IntVec(v)) => v,
_ => Vec::new(),
})
}
pub fn inbetweens(&self) -> Result<Vec<Inbetween>> {
let mut out = Vec::new();
let props = self.stage().prim_at(self.path().clone()).property_names()?;
for name in &props {
let Some(rest) = name.strip_prefix(tok::NS_INBETWEENS) else {
continue;
};
if rest.contains(':') {
continue;
}
let attr = self.attribute(name);
let offsets = vec3f_vec(&attr)?;
let weight = match attr.get_metadata::<Value>(tok::META_WEIGHT)? {
Some(Value::Float(f)) => Some(f),
Some(Value::Double(d)) => Some(d as f32),
_ => None,
};
let normal_offsets_name = format!("{}{rest}:{}", tok::NS_INBETWEENS, tok::A_NORMAL_OFFSETS);
let normal_offsets = if props.iter().any(|n| n == &normal_offsets_name) {
vec3f_vec(&self.attribute(&normal_offsets_name))?
} else {
Vec::new()
};
out.push(Inbetween {
name: rest.to_string(),
weight,
offsets,
normal_offsets,
});
}
Ok(out)
}
}
impl_skel_schema!(typed BlendShape);
#[derive(Clone, derive_more::Deref)]
pub struct SkelBindingAPI(Prim);
impl SkelBindingAPI {
pub fn apply(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(
stage.override_prim(path)?.add_applied_schema(tok::API_SKEL_BINDING)?,
))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_with_api(stage, path, &[tok::API_SKEL_BINDING]).map(|o| o.map(Self))
}
pub fn skeleton_rel(&self) -> Relationship {
self.relationship(tok::REL_SKEL_SKELETON)
}
pub fn create_skeleton_rel(&self) -> Result<Relationship> {
Ok(self.create_relationship(tok::REL_SKEL_SKELETON)?.set_custom(false)?)
}
pub fn animation_source_rel(&self) -> Relationship {
self.relationship(tok::REL_SKEL_ANIMATION_SOURCE)
}
pub fn create_animation_source_rel(&self) -> Result<Relationship> {
Ok(self
.create_relationship(tok::REL_SKEL_ANIMATION_SOURCE)?
.set_custom(false)?)
}
pub fn blend_shape_targets_rel(&self) -> Relationship {
self.relationship(tok::REL_SKEL_BLEND_SHAPE_TARGETS)
}
pub fn create_blend_shape_targets_rel(&self) -> Result<Relationship> {
Ok(self
.create_relationship(tok::REL_SKEL_BLEND_SHAPE_TARGETS)?
.set_custom(false)?)
}
pub fn joint_indices_attr(&self) -> Attribute {
self.attribute(tok::A_JOINT_INDICES)
}
pub fn create_joint_indices_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_JOINT_INDICES, "int[]")?
.set_custom(false)?)
}
pub fn joint_weights_attr(&self) -> Attribute {
self.attribute(tok::A_JOINT_WEIGHTS)
}
pub fn create_joint_weights_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_JOINT_WEIGHTS, "float[]")?
.set_custom(false)?)
}
pub fn joints_attr(&self) -> Attribute {
self.attribute(tok::A_SKEL_JOINTS)
}
pub fn create_joints_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SKEL_JOINTS, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn blend_shapes_attr(&self) -> Attribute {
self.attribute(tok::A_SKEL_BLEND_SHAPES)
}
pub fn create_blend_shapes_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SKEL_BLEND_SHAPES, "token[]")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn geom_bind_transform_attr(&self) -> Attribute {
self.attribute(tok::A_GEOM_BIND_TRANSFORM)
}
pub fn create_geom_bind_transform_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_GEOM_BIND_TRANSFORM, "matrix4d")?
.set_custom(false)?)
}
pub fn skinning_method_attr(&self) -> Attribute {
self.attribute(tok::A_SKINNING_METHOD)
}
pub fn create_skinning_method_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_SKINNING_METHOD, "token")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn skeleton(&self) -> Result<Option<sdf::Path>> {
Ok(self.skeleton_rel().targets()?.into_iter().next())
}
pub fn animation_source(&self) -> Result<Option<sdf::Path>> {
Ok(self.animation_source_rel().targets()?.into_iter().next())
}
pub fn inherited_skeleton(&self) -> Result<Option<sdf::Path>> {
inherited_rel(self.stage(), self.path(), tok::REL_SKEL_SKELETON)
}
pub fn inherited_animation_source(&self) -> Result<Option<sdf::Path>> {
inherited_rel(self.stage(), self.path(), tok::REL_SKEL_ANIMATION_SOURCE)
}
pub fn joint_indices(&self) -> Result<Vec<i32>> {
Ok(match self.joint_indices_attr().get::<Value>()? {
Some(Value::IntVec(v)) => v,
_ => Vec::new(),
})
}
pub fn joint_weights(&self) -> Result<Vec<f32>> {
Ok(match self.joint_weights_attr().get::<Value>()? {
Some(Value::FloatVec(v)) => v,
Some(Value::DoubleVec(v)) => v.into_iter().map(|d| d as f32).collect(),
_ => Vec::new(),
})
}
pub fn joint_subset(&self) -> Result<Vec<String>> {
token_vec(&self.joints_attr())
}
pub fn blend_shapes(&self) -> Result<Vec<String>> {
token_vec(&self.blend_shapes_attr())
}
pub fn blend_shape_targets(&self) -> Result<Vec<sdf::Path>> {
self.blend_shape_targets_rel().targets()
}
pub fn geom_bind_transform(&self) -> Result<Option<gf::Matrix4d>> {
Ok(match self.geom_bind_transform_attr().get::<Value>()? {
Some(Value::Matrix4d(m)) => Some(m),
_ => None,
})
}
pub fn skinning_method(&self) -> Result<SkinningMethod> {
Ok(self.skinning_method_attr().get::<SkinningMethod>()?.unwrap_or_default())
}
pub fn elements_per_element(&self) -> Result<i32> {
Ok(
match self
.joint_indices_attr()
.get_metadata::<Value>(tok::META_ELEMENT_SIZE)?
{
Some(Value::Int(n)) => n,
Some(Value::Int64(n)) => n as i32,
_ => 1,
},
)
}
pub fn interpolation(&self) -> Result<InfluenceInterpolation> {
Ok(self
.joint_indices_attr()
.get_metadata::<InfluenceInterpolation>(tok::META_INTERPOLATION)?
.unwrap_or_default())
}
}
impl_skel_schema!(single_api SkelBindingAPI);
fn inherited_rel(stage: &Stage, prim: &sdf::Path, rel_name: &str) -> Result<Option<sdf::Path>> {
let mut cur = prim.clone();
loop {
let rel = cur.append_property(rel_name)?;
if let Some(target) = stage.relationship_at(rel).targets()?.into_iter().next() {
return Ok(Some(target));
}
match cur.parent() {
Some(p) if !cur.is_abs_root() => cur = p,
_ => return Ok(None),
}
}
}
fn token_vec(attr: &Attribute) -> Result<Vec<String>> {
Ok(match attr.get::<Value>()? {
Some(Value::TokenVec(v) | Value::StringVec(v)) => v,
_ => Vec::new(),
})
}
fn mat4_vec(attr: &Attribute) -> Result<Vec<gf::Matrix4d>> {
Ok(match attr.get::<Value>()? {
Some(Value::Matrix4dVec(v)) => v,
_ => Vec::new(),
})
}
fn vec3f_vec(attr: &Attribute) -> Result<Vec<gf::Vec3f>> {
Ok(match attr.get::<Value>()? {
Some(Value::Vec3fVec(v)) => v,
Some(Value::Vec3dVec(v)) => v
.into_iter()
.map(|a| gf::vec3f(a.x as f32, a.y as f32, a.z as f32))
.collect(),
Some(Value::Vec3hVec(v)) => v
.into_iter()
.map(|a| gf::vec3f(a.x.to_f32(), a.y.to_f32(), a.z.to_f32()))
.collect(),
_ => Vec::new(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schemas::geom::Boundable;
#[test]
fn skeleton_roundtrip_and_topology() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let skel = Skeleton::define(&stage, "/Rig")?;
skel.create_joints_attr()?.set(Value::TokenVec(vec![
"Root".into(),
"Root/Hip".into(),
"Root/Hip/Knee".into(),
]))?;
let skel = Skeleton::get(&stage, "/Rig")?.expect("Skeleton");
assert_eq!(skel.joints()?, vec!["Root", "Root/Hip", "Root/Hip/Knee"]);
assert_eq!(skel.joint_parent_indices()?, vec![None, Some(0), Some(1)]);
assert_eq!(skel.joint_short_names()?, vec!["Root", "Hip", "Knee"]);
assert_eq!(skel.map_anim_joints(&["Root/Hip".to_string()])?, vec![Some(1)]);
Ok(())
}
#[test]
fn skel_root_is_boundable() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let root = SkelRoot::define(&stage, "/Char")?;
root.create_extent_attr()?.set(Value::Vec3fVec(vec![
[-1.0_f32, 0.0, -1.0].into(),
[1.0, 2.0, 1.0].into(),
]))?;
let root = SkelRoot::get(&stage, "/Char")?.expect("SkelRoot");
assert_eq!(
root.extent_attr().get::<Value>()?.and_then(|v| v.try_as_vec_3f_vec()),
Some(vec![gf::vec3f(-1.0, 0.0, -1.0), gf::vec3f(1.0, 2.0, 1.0)])
);
Ok(())
}
#[test]
fn binding_roundtrip_and_inheritance() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let root = SkelBindingAPI::apply(&stage, "/Char")?;
root.create_skeleton_rel()?.add_target(sdf::path("/Char/Rig")?)?;
let body = SkelBindingAPI::apply(&stage, "/Char/Body")?;
body.create_joint_indices_attr()?.set(Value::IntVec(vec![0, 1]))?;
body.create_joint_weights_attr()?.set(Value::FloatVec(vec![1.0, 1.0]))?;
body.create_skinning_method_attr()?.set(SkinningMethod::ClassicLinear)?;
let body = SkelBindingAPI::get(&stage, "/Char/Body")?.expect("SkelBindingAPI");
assert!(body.skeleton()?.is_none()); assert_eq!(
body.inherited_skeleton()?.map(|p| p.as_str().to_string()),
Some("/Char/Rig".to_string())
);
assert_eq!(body.joint_indices()?, vec![0, 1]);
assert_eq!(body.joint_weights()?, vec![1.0, 1.0]);
assert_eq!(body.elements_per_element()?, 1);
assert_eq!(body.interpolation()?, InfluenceInterpolation::Vertex);
assert_eq!(body.skinning_method()?, SkinningMethod::ClassicLinear);
Ok(())
}
#[test]
fn blend_shape_inbetween() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let bs = BlendShape::define(&stage, "/Smile")?;
bs.create_offsets_attr()?
.set(Value::Vec3fVec(vec![[0.0_f32, 0.1, 0.0].into()]))?;
bs.create_point_indices_attr()?.set(Value::IntVec(vec![0]))?;
let inb = bs
.create_attribute("inbetweens:half", "vector3f[]")?
.set_custom(false)?;
inb.set(Value::Vec3fVec(vec![[0.0_f32, 0.04, 0.0].into()]))?
.set_metadata(tok::META_WEIGHT, Value::Float(0.5))?;
let bs = BlendShape::get(&stage, "/Smile")?.expect("BlendShape");
assert_eq!(bs.offsets()?, vec![gf::vec3f(0.0, 0.1, 0.0)]);
assert_eq!(bs.point_indices()?, vec![0]);
let inbetweens = bs.inbetweens()?;
assert_eq!(inbetweens.len(), 1);
assert_eq!(inbetweens[0].name, "half");
assert_eq!(inbetweens[0].weight, Some(0.5));
assert_eq!(inbetweens[0].offsets, vec![gf::vec3f(0.0, 0.04, 0.0)]);
Ok(())
}
}