Skip to content

feat(skel): UsdSkel reader + skinning toolkit#69

Merged
mxpv merged 2 commits into
mxpv:mainfrom
bresilla:feat/skel-schema
May 24, 2026
Merged

feat(skel): UsdSkel reader + skinning toolkit#69
mxpv merged 2 commits into
mxpv:mainfrom
bresilla:feat/skel-schema

Conversation

@bresilla
Copy link
Copy Markdown
Contributor

@bresilla bresilla commented May 24, 2026

Proposal for follow-up reorganisation

Before this lands, a question for @mxpv: if you're open to it, I'd like to move src/physics/ and src/skel/ (and the matching tests/+ fixtures/) under a src/noncore/ subfolder in a follow-up PR.

The plan is to keep contributing domain schemas — UsdAnim is next, then UsdLux and UsdGeom.Camera. Adding one top-level folder per schema family will get crowded fast, and these schemas aren't part of the AOUSD core spec anyway, so noncore (or schemas, or whatever you prefer) feels like a natural grouping. Happy to defer to your naming preference.

If you'd rather keep the flat layout, no problem — this PR doesn't depend on the reorg either way.


Summary

Adds the skel cargo feature with a complete UsdSkel schema reader plus the time-independent half of Pixar's UsdSkel object model. Covers sections 1, 3, 4, and 6 of the canonical UsdSkel landing page (https://openusd.org/release/api/usd_skel_page_front.html), minus animation evaluation (left for a follow-up anim feature).

What's deliberately out

Time-dependent evaluation (UsdSkelAnimQuery, ComputeSkinningTransforms(time), stage-time interpolation of
SkelAnimation samples). Resolvers take pre-evaluated joint poses so the follow-up anim layer can plug in directly.

Instancing-of-bind-state and a stateful UsdSkelCache were intentionally skipped — discovery is exposed as plain stateless functions to fit the existing single-shot Stage API surface.

Tests

All green; no regressions in other test binaries.

ROADMAP

Flips the UsdSkel row to :white_check_mark: / main.

EXAMPLE POSSIBLE BECAUSE OF THIS

image

Copilot AI review requested due to automatic review settings May 24, 2026 13:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new skel feature/module implementing a UsdSkel schema reader plus time-independent resolvers and math helpers, with fixtures and integration/unit tests.

Changes:

  • Introduces openusd::skel behind a new Cargo feature (skel) including tokens, read/decode structs, topology, anim mapping, skinning math, and resolvers.
  • Adds a comprehensive UsdSkel USDA fixture and integration tests covering schema discovery, decoding, inheritance, and resolver behavior.
  • Updates crate exports and roadmap to reflect UsdSkel support.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/skel_reader.rs Adds integration tests validating end-to-end UsdSkel decoding and resolver behavior using a fixture stage.
src/skel/mod.rs Wires the new skel module and re-exports public APIs/types.
src/skel/tokens.rs Adds centralized UsdSkel token constants used by reader/resolvers.
src/skel/types.rs Introduces decoded data types/enums returned by reader functions.
src/skel/read.rs Implements schema readers, time-sample decoding, and stage-wide prim discovery.
src/skel/topology.rs Adds joint topology representation + validation with unit tests.
src/skel/anim_mapper.rs Adds per-joint remapping utility (UsdSkelAnimMapper-like) with unit tests.
src/skel/skinning.rs Adds pure math helpers for skinning/blend-shapes with unit tests.
src/skel/skeleton_query.rs Adds a static SkeletonResolver that precomputes topology/inverse binds.
src/skel/skinning_query.rs Adds a per-mesh SkinningResolver for remapping and performing skinning.
src/skel/binding.rs Adds subtree binding discovery and SkelRoot enumeration helpers.
src/lib.rs Exposes skel module behind the skel feature flag.
fixtures/usdSkel_scene.usda Adds a fixture USDA scene covering SkelRoot/Skeleton/SkelAnimation/BlendShape/SkelBindingAPI.
ROADMAP.md Marks UsdSkel feature as complete and documents scope/limitations.
Cargo.toml Adds the skel feature and a feature-gated integration test target.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/skel/anim_mapper.rs
Comment on lines +29 to +31
/// `true` iff `indices == [0, 1, 2, …, n-1]` AND `source.len() ==
/// target.len()`. When set, [`remap`] is a straight clone.
identity: bool,
Comment thread src/skel/anim_mapper.rs
Comment on lines +85 to +103
/// Remap a per-joint array `source` (one element per source
/// joint) into target order. Missing entries get `default`.
/// Panics if `source.len()` doesn't match the source length the
/// mapper was built with.
pub fn remap<T: Clone>(&self, source: &[T], default: T) -> Vec<T> {
if self.identity {
return source.to_vec();
}
self.indices
.iter()
.map(|&i| {
if i == MISSING {
default.clone()
} else {
source[i as usize].clone()
}
})
.collect()
}
Comment thread src/skel/anim_mapper.rs
Comment on lines +109 to +122
pub fn remap_with_stride<T: Copy>(&self, source: &[T], stride: usize, default: T) -> Vec<T> {
let mut out = Vec::with_capacity(self.indices.len() * stride);
for &i in &self.indices {
if i == MISSING {
for _ in 0..stride {
out.push(default);
}
} else {
let start = (i as usize) * stride;
out.extend_from_slice(&source[start..start + stride]);
}
}
out
}
Comment thread src/skel/read.rs
}
let attr_path = prim.append_property(name)?;
let offsets = match stage.field::<Value>(attr_path.clone(), "default")? {
Some(Value::Vec3fVec(v)) => v,
Comment on lines +122 to +143
pub fn remap_skinning_xforms(&self, skel_skinning_xforms: &[[f64; 16]]) -> Vec<[f64; 16]> {
if self.mapper.is_identity() {
return skel_skinning_xforms.to_vec();
}
// Flatten through the strided helper. Missing joints land as
// identity (fill value = 1 on the diagonal).
let flat: Vec<f64> = skel_skinning_xforms.iter().flat_map(|m| m.iter().copied()).collect();
let strided = self.mapper.remap_with_stride(&flat, 16, 0.0);
let mut out = Vec::with_capacity(self.num_mesh_joints);
for chunk in strided.chunks_exact(16) {
let mut m = [0.0f64; 16];
m.copy_from_slice(chunk);
// Fill-with-zeros leaves missing joints as the all-zeros
// matrix; promote to identity so the downstream skin
// routine doesn't collapse points to the origin.
if m == [0.0f64; 16] {
m = IDENTITY_MAT4;
}
out.push(m);
}
out
}
Comment thread src/skel/skinning.rs Outdated
- m[8] * m[2] * m[5];

let det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
if det.abs() < f64::EPSILON {
@mxpv
Copy link
Copy Markdown
Owner

mxpv commented May 24, 2026

./src/schemas/ would be nice.
We could add schema related code there and put all non core extensions:

./src/schemas/mod.rs
./src/schemas/registry.rs
./src/schemas/physics/...
./src/schemas/skel/...

@bresilla
Copy link
Copy Markdown
Contributor Author

bresilla commented May 24, 2026

Thanks @mxpv -- src/schemas/ is a better name than my noncore, happy to go with that. I'll put up a follow-up PR moving physics/ and skel/ under it (and the matching tests + fixtures) after this one lands so the rename diff stays small and reviewable. The schemas/registry.rs slot is a good place to grow into when more schemas pile up — for now I'll leave that empty in the follow-up.

@mxpv mxpv merged commit 846e30e into mxpv:main May 24, 2026
5 checks passed
@mxpv
Copy link
Copy Markdown
Owner

mxpv commented May 24, 2026

Thanks. Changes are merge. I followed up with a few changes in the main branch.

@bresilla bresilla deleted the feat/skel-schema branch May 24, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants