ComputeJointInfluences for meshes with only a subset of the joint

Hello all,

I was running into some issues with meshes that store only a few joints from a full skeleton as the skeleton might contain joints that have no skinning influence. Maya writes usd files like this for example.

The indices I got back from the mesh map to the meshes skel:joints, but not to the skeletons:joints. I had hoped that by going through the skinningQuery, this remapping would be handled ( so the indices I get back are now into the skeleton’s joints and no longer in the meshes. I have the code I tried below. To unblock people, I now just load the two arrays and build a remapping table by comparing the paths. I have a feeling there is a more elegant way, does anyone know what I am doing wrong here?

Thanks,

Koen

UsdSkelCache skelCache;

VtArray<int> jointIndices;

VtArray<float> jointWeights;

if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim))

{

skelCache.Populate(skelRoot, UsdTraverseInstanceProxies());



*// Find the Skeleton that should affect this prim.*

UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton();



if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) 

{

    skinningQuery.ComputeJointInfluences(&jointIndices, &jointWeights);

    

    *// auto mapper = skinningQuery.GetJointMapper();*

    *// VtArray<int> remappedIndices;*

    *// mapper->Remap(jointIndices, &remappedIndices);*

}

}

Hi Koen,

let me know if that works: if so, I would like to publish it as a tutorial, but I would need more insights from you what to focus on… (obviously this was created with the help of our β€˜helper Friends’… , I am constantly trying to prevent Drift and Haluzination… β†’ SO if that little MD file is BS and AIslop, than let me know, so that I can get my system more targeted…)

UsdSkel Joint Index Remapping: Mesh-Local to Skeleton Space

Problem: Joint indices returned from UsdSkelSkinningQuery::ComputeJointInfluences() map to the mesh’s skel:joints array, but you need indices into the skeleton’s joints array. The mesh may only store a subset of joints from the full skeleton.

Root Cause: GetJointMapper() provides mapping FROM skeleton joint order TO mesh-local joint order, but you need the inverse mapping. The UsdSkelBindingAPI::GetJointsAttr() documentation [7] explains that mesh skel:joints is optional and β€œmay vary from the order of joints on the Skeleton itself” [8], which is why remapping is necessary.

Solution

The key insight is that GetJointMapper() maps FROM skeleton TO mesh-local, but you need to map FROM mesh-local TO skeleton. You need to build the inverse mapping by comparing joint paths. The UsdSkelSkinningQuery::GetJointOrder() method [1] provides access to the mesh-local joint order, while UsdSkelSkeletonQuery::GetJointOrder() [2] provides the skeleton’s joint order. This exact problem and solution approach has been discussed in the Alliance for OpenUSD forum [9], confirming the path-comparison remapping strategy.

C++ Solution

#include <pxr/usd/usdSkel/cache.h>
#include <pxr/usd/usdSkel/skinningQuery.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/base/vt/array.h>
#include <pxr/base/tf/token.h>
#include <unordered_map>

UsdSkelCache skelCache;
VtArray<int> jointIndices;
VtArray<float> jointWeights;

if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim)) {
    // Populate the cache - required before GetSkinningQuery() works
    skelCache.Populate(skelRoot, UsdTraverseInstanceProxies());
    
    // Find the Skeleton that should affect this prim
    // See UsdSkelBindingAPI::GetInheritedSkeleton() [[3]](#appendix-sources)
    UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton();
    
    // Get skinning query - see UsdSkelSkinningQuery class reference [[1]](#appendix-sources)
    if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) {
        // Get joint influences (indices are into mesh's skel:joints)
        // See UsdSkelSkinningQuery::ComputeJointInfluences() [[1]](#appendix-sources)
        skinningQuery.ComputeJointInfluences(&jointIndices, &jointWeights);
        
        // Get the skeleton query to access skeleton joint order
        UsdSkelSkeletonQuery skeletonQuery = skelCache.GetSkeletonQuery(skel);
        if (!skeletonQuery) {
            // Fallback: get skeleton directly
            skeletonQuery = skelCache.GetSkeletonQuery(skel.GetPrim());
        }
        
        if (skeletonQuery) {
            // Get mesh-local joint order (from skel:joints)
            // See UsdSkelSkinningQuery::GetJointOrder() [[1]](#appendix-sources)
            VtTokenArray meshJointOrder;
            skinningQuery.GetJointOrder(&meshJointOrder);
            
            // Get skeleton joint order
            // See UsdSkelSkeletonQuery::GetJointOrder() [[2]](#appendix-sources)
            VtTokenArray skeletonJointOrder = skeletonQuery.GetJointOrder();
            
            // Build reverse lookup: mesh joint path -> skeleton joint index
            std::unordered_map<TfToken, int, TfToken::HashFunctor> meshToSkelMap;
            for (size_t i = 0; i < meshJointOrder.size(); ++i) {
                const TfToken& meshJointPath = meshJointOrder[i];
                
                // Find this mesh joint in the skeleton's joint order
                for (size_t j = 0; j < skeletonJointOrder.size(); ++j) {
                    if (skeletonJointOrder[j] == meshJointPath) {
                        meshToSkelMap[meshJointPath] = static_cast<int>(j);
                        break;
                    }
                }
            }
            
            // Remap joint indices from mesh-local to skeleton space
            VtArray<int> remappedIndices;
            remappedIndices.reserve(jointIndices.size());
            
            for (int meshLocalIdx : jointIndices) {
                if (meshLocalIdx >= 0 && 
                    static_cast<size_t>(meshLocalIdx) < meshJointOrder.size()) {
                    const TfToken& meshJointPath = meshJointOrder[meshLocalIdx];
                    auto it = meshToSkelMap.find(meshJointPath);
                    if (it != meshToSkelMap.end()) {
                        remappedIndices.push_back(it->second);
                    } else {
                        // Joint not found in skeleton (shouldn't happen, but handle gracefully)
                        remappedIndices.push_back(-1);
                    }
                } else {
                    remappedIndices.push_back(-1);
                }
            }
            
            // Now remappedIndices contains indices into skeleton's joints array
            // Use remappedIndices instead of jointIndices
        }
    }
}

Python Solution

from pxr import Usd, UsdSkel, Vt

def remap_joint_indices_to_skeleton_space(prim):
    """
    Remap joint indices from mesh-local skel:joints to skeleton joints.
    
    Args:
        prim: The skinnable prim (mesh) with UsdSkelBindingAPI
        
    Returns:
        tuple: (remapped_indices, joint_weights) or None if remapping fails
    """
    skel_cache = UsdSkel.Cache()
    
    # Find SkelRoot
    skel_root = UsdSkel.Root.Find(prim)
    if not skel_root:
        return None
    
    skel_cache.Populate(skel_root, Usd.TraverseInstanceProxies())
    
    # Get the skeleton
    # See UsdSkelBindingAPI::GetInheritedSkeleton() [[3]](#appendix-sources)
    binding_api = UsdSkel.BindingAPI(prim)
    skel = binding_api.GetInheritedSkeleton()
    if not skel:
        return None
    
    # Get skinning query - see UsdSkelSkinningQuery class reference [[1]](#appendix-sources)
    skinning_query = skel_cache.GetSkinningQuery(prim)
    if not skinning_query:
        return None
    
    # Get joint influences (indices are into mesh's skel:joints)
    # See UsdSkelSkinningQuery::ComputeJointInfluences() [[1]](#appendix-sources)
    joint_indices = Vt.IntArray()
    joint_weights = Vt.FloatArray()
    if not skinning_query.ComputeJointInfluences(joint_indices, joint_weights):
        return None
    
    # Get skeleton query - see UsdSkelSkeletonQuery class reference [[2]](#appendix-sources)
    skeleton_query = skel_cache.GetSkeletonQuery(skel)
    if not skeleton_query:
        return None
    
    # Get mesh-local joint order (from skel:joints)
    # See UsdSkelSkinningQuery::GetJointOrder() [[1]](#appendix-sources)
    mesh_joint_order = Vt.TokenArray()
    if not skinning_query.GetJointOrder(mesh_joint_order):
        # If no custom joint order, mesh uses skeleton order directly
        skeleton_joint_order = skeleton_query.GetJointOrder()
        # If mesh order matches skeleton order, no remapping needed
        return (joint_indices, joint_weights)
    
    # Get skeleton joint order
    skeleton_joint_order = skeleton_query.GetJointOrder()
    
    # Build reverse lookup: mesh joint path -> skeleton joint index
    mesh_to_skel_map = {}
    for i, mesh_joint_path in enumerate(mesh_joint_order):
        try:
            skel_idx = skeleton_joint_order.index(mesh_joint_path)
            mesh_to_skel_map[i] = skel_idx
        except ValueError:
            # Joint not found in skeleton (shouldn't happen, but handle gracefully)
            mesh_to_skel_map[i] = -1
    
    # Remap joint indices from mesh-local to skeleton space
    remapped_indices = Vt.IntArray()
    remapped_indices.reserve(len(joint_indices))
    
    for mesh_local_idx in joint_indices:
        if mesh_local_idx >= 0 and mesh_local_idx < len(mesh_joint_order):
            skel_idx = mesh_to_skel_map.get(mesh_local_idx, -1)
            remapped_indices.append(skel_idx)
        else:
            remapped_indices.append(-1)
    
    return (remapped_indices, joint_weights)


# Usage example:
stage = Usd.Stage.Open("path/to/file.usd")
mesh_prim = stage.GetPrimAtPath("/SkelRoot/Mesh")

result = remap_joint_indices_to_skeleton_space(mesh_prim)
if result:
    remapped_indices, joint_weights = result
    # Now remapped_indices contains indices into skeleton's joints array
    print(f"Remapped {len(remapped_indices)} joint indices")

Why GetJointMapper().Remap() Doesn’t Work Directly

The GetJointMapper() returns a mapper that maps FROM skeleton joint order TO mesh-local joint order. When you call Remap() on it, you’re mapping skeleton-space data to mesh-local space, which is the opposite of what you need. The UsdSkel Schemas In-Depth documentation [5] explains that remapping via mapper works by matching joint names, but the mapper operates in the forward direction (skeleton β†’ mesh-local) only.

The mapper is designed for use cases like:

  • Mapping skeleton joint transforms to mesh-local joint order for skinning
  • Mapping animation data from skeleton order to mesh-local order

But for remapping indices (not transforms), you need the inverse mapping, which requires comparing the joint paths/orders directly using GetJointOrder() methods [1] [2]. This limitation has been discussed in related forum threads [10] and GitHub issues [11], where users encountered similar challenges with explicit joint orders.

Alternative: Using GetJointOrder() Directly

If the mapper is null (meaning mesh uses skeleton order directly), you can skip remapping:

VtTokenArray meshJointOrder;
if (skinningQuery.GetJointOrder(&meshJointOrder)) {
    // Mesh has custom joint order, need remapping
    // ... use remapping code above ...
} else {
    // Mesh uses skeleton order directly, no remapping needed
    // jointIndices already map to skeleton joints
}

Key Takeaways

  1. GetJointMapper() maps skeleton β†’ mesh-local, not the other way around (see [5])
  2. To remap indices, compare joint paths between mesh skel:joints and skeleton joints using GetJointOrder() methods [1] [2]
  3. Build a reverse lookup map from mesh joint paths to skeleton joint indices (as discussed in [9])
  4. Handle the case where mapper is null (mesh uses skeleton order directly) - see UsdSkelBindingAPI::GetJointsAttr() behavior [7]

Appendix: Sources

[1] UsdSkelSkinningQuery Class Reference

Link: Universal Scene Description: UsdSkelSkinningQuery Class Reference

The official OpenUSD API reference for UsdSkelSkinningQuery documents the exact methods used in this solution: ComputeJointInfluences(), GetJointMapper(), and GetJointOrder(). This class reference provides the authoritative documentation for extracting joint influences from skinnable primitives and accessing the mesh-local joint order, which is essential for building the reverse mapping from mesh indices to skeleton indices.

[2] UsdSkelSkeletonQuery Class Reference

Link: Universal Scene Description: UsdSkelSkeletonQuery Class Reference

The UsdSkelSkeletonQuery class reference documents the GetJointOrder() method that returns the skeleton’s authoritative joint order. This method provides the canonical joint ordering that skeleton-space data uses, making it the target for remapping mesh-local joint indices. The skeleton query is obtained through UsdSkelCache::GetSkelQuery() and serves as the source of truth for skeleton joint ordering in the solution.

[3] UsdSkelBindingAPI Class Reference

Link: Universal Scene Description: UsdSkelBindingAPI Class Reference

The UsdSkelBindingAPI class reference documents the GetInheritedSkeleton() method used to find the skeleton bound to a skinnable primitive. This API is the entry point for discovering skeleton bindings and is essential for obtaining the skeleton that provides the target joint order for remapping. The binding API also defines the skel:joints attribute that can specify custom joint orderings on meshes.

[4] UsdSkel API Introduction

Link: API Introduction

The UsdSkel API Introduction provides conceptual overview of the skeletal animation system, explicitly mentioning UsdSkelBindingAPI::GetInheritedSkeleton() and the use of UsdSkelSkeletonQuery for querying skeleton data. This documentation explains the relationship between skeletons, bindings, and skinning queries, providing context for why the remapping solution requires both skeleton and skinning queries to compare joint orders.

[5] UsdSkel Schemas In-Depth: Joint Order

Link: Schemas In-Depth

The UsdSkel Schemas In-Depth documentation explains flexible joint orderings and how remapping works via mapper by matching joint names. This page explicitly states that joint orderings can differ between skeletons, animations, and meshes, and can be sparse subsets, which directly explains why the index mismatch problem occurs. The documentation clarifies that the mapper remaps data from source order to target order, confirming why inverse mapping is needed for index remapping.

[6] UsdSkelSkeleton: joints Attribute

Link: UsdSkelSkeleton Class Reference

The UsdSkelSkeleton class reference documents the joints attribute, which defines the skeleton’s authoritative joint list and order. This attribute provides the canonical joint ordering that all skeleton-space operations use, making it the target for remapping mesh-local joint indices. Understanding this attribute is crucial for recognizing that skeleton joints represent the authoritative joint order in the solution.

[7] UsdSkelBindingAPI::GetJointsAttr Behavior (NVIDIA Docs)

Link: usdrt::UsdSkelBindingAPI β€” usdrt 7.5.1 documentation

NVIDIA’s Omniverse documentation for UsdSkelBindingAPI::GetJointsAttr() explains the critical behavior: this optional joints list defines which joints the jointIndices apply to, and if not defined, indices apply to the bound Skeleton’s joints. This documentation clarifies why meshes can have custom joint orderings via skel:joints that differ from the skeleton’s order, which is the root cause of the remapping problem solved in this document.

[8] NVIDIA pxr-usd-api: Joint Order Documentation

Link: UsdSkel module β€” Omniverse Kit 106.3.0 documentation

NVIDIA’s pxr-usd-api documentation explicitly states that joint token order β€œmay vary from the order of joints on the Skeleton itself”, confirming that mesh-local joint orderings can differ from skeleton joint orderings. This documentation validates the problem statement and confirms that the remapping solution is necessary when working with meshes that define custom joint orderings, such as those exported from Maya.

[9] Alliance for OpenUSD Forum: β€œComputeJointInfluences for meshes with only a subset of the joint”

Link: ComputeJointInfluences for meshes with only a subset of the joint

This forum thread describes exactly the problem solved in this document: meshes with skel:joints that are a subset of the skeleton’s joints, causing index mismatches between ComputeJointInfluences() results and skeleton joint indices. The discussion confirms the solution approach of building a remap table by comparing joint paths, validating the path-comparison remapping strategy used in this document.

[10] Alliance for OpenUSD Forum: β€œJoint ordering, documentation incorrect…?”

Link: Joint ordering, documentation incorrect or just me messing up again?

This forum discussion explores joint ordering challenges and documentation ambiguities, providing context for why the remapping problem can be confusing. The thread discusses how joint orderings work in practice and highlights common pitfalls, which helps explain why GetJointMapper().Remap() doesn’t directly solve the inverse mapping problem for indices.

[11] GitHub Issue: β€œ[UsdSkel] Explicit Joint Orders not handled by …”

Link: [UsdSkel] Explicit Joint Orders not handled by HdxCompute / maya import Β· Issue #789 Β· PixarAnimationStudios/OpenUSD Β· GitHub

This GitHub issue discusses problems with explicit joint orders not being handled correctly in certain scenarios, providing additional context for joint ordering edge cases. The issue highlights that joint ordering can be a source of confusion and that proper remapping is necessary when working with custom joint orderings, reinforcing the need for the solution documented here.

Hello Jan,

Thanks for looking into this. The Key Takeaways are actually pretty clear. The example code was pretty much what I ended up with, but I did not feel great about the string comparison for the joint paths. I messed around with the mapper a bit and came up with this, which seems to work and should match how usd internally maps the paths.

VtArray indices;
indices.resize(jointIndices.size());
for (size_t i = 0; i < jointIndices.size(); i++)
{
indices[i] = i;
}
// start with identity mapping
jointLut = indices;

// here we build the lut to map indices into the (sparse) joint array in
// the mesh to the full joint array of the skeleton
if (skinningQuery.GetJointMapper())
{
skinningQuery.GetJointMapper()->Remap(indices, &jointLut);
}

I would love OpenUsd to support something like this which includes this remapping ( seems to small of a contribution, but I might look into that )

skinningQuery.ComputeSkelJointInfluences(&jointIndices, &jointWeights );

cool!
which means that my little helper was not totally of!!, because I am not deep enough into the subject to be able to give you a meaningfull answer myself.

I can see that you ended up using C++…
some insights from the text down below…


Why Python Doesn’t Have It:

  1. Not in C++ API - The C++ API doesn’t have it either, so Python can’t expose it
  2. Application-Level Concern - OpenUSD considers this an application problem, not a library problem
  3. Performance - Building inverse maps costs memory/CPU, so it’s opt-in
  4. Flexibility - Different apps need different remapping strategies

Alternative C++ Solution: Using the Mapper’s Internal Index Map

Your friend discovered a more efficient approach that leverages the mapper’s internal _indexMap structure instead of string comparisons. This is a clever optimization: (more insights fiΓΊrteh down)


β†’ What more context would be needed to make it a tutorial? could you provide the sample or a similar sample, so we could make it a round tutorial?

cheers Jan

Here’s an update on the insights.




UsdSkel Joint Index Remapping: Mesh-Local to Skeleton Space

Problem: Joint indices returned from UsdSkelSkinningQuery::ComputeJointInfluences() map to the mesh’s skel:joints array, but you need indices into the skeleton’s joints array. The mesh may only store a subset of joints from the full skeleton.

Root Cause: GetJointMapper() provides mapping FROM skeleton joint order TO mesh-local joint order, but you need the inverse mapping. The UsdSkelBindingAPI::GetJointsAttr() documentation [7] explains that mesh skel:joints is optional and β€œmay vary from the order of joints on the Skeleton itself” [8], which is why remapping is necessary.

Understanding the Problem: Why C++ is Needed

The Core Issue: Two Different Numbering Systems

Think of it like this: imagine you have a skeleton (like a human skeleton) with 100 bones numbered 0-99. But a mesh (like a hand mesh) only needs 15 of those bones. The mesh creates its own numbering system (0-14) for just those 15 bones it uses.

The Problem:

  • When you ask the mesh β€œwhich bones affect this vertex?”, it gives you numbers like [2, 5, 8]
  • But those numbers refer to the mesh’s own list (positions 2, 5, 8 in the mesh’s list of 15 bones)
  • You need to know which bones those are in the skeleton’s list (positions 23, 47, 89 in the skeleton’s list of 100 bones)

Why This Happens:

  • Maya (and other DCC tools) export meshes with only the joints they actually use
  • This saves memory and makes files smaller
  • But it creates this β€œtranslation problem” between two numbering systems

What OpenUSD Python Provides (and What It Doesn’t)

What OpenUSD Python DOES provide:

  • ComputeJointInfluences() - Gets the joint indices and weights for each vertex
  • GetJointMapper() - A tool that maps skeleton β†’ mesh-local (forward direction)
  • GetJointOrder() - Gets the list of joint names/paths for mesh or skeleton

What OpenUSD Python DOESN’T provide:

  • A built-in way to map mesh-local indices β†’ skeleton indices (reverse direction)
  • A simple β€œremap these indices” function that does the inverse mapping
  • Automatic handling of the index translation problem

Why Python Doesn’t Have This:
The OpenUSD Python bindings are β€œthin wrappers” around the C++ code. They expose the same methods, but:

  • The GetJointMapper().Remap() method only works in one direction (skeleton β†’ mesh)
  • There’s no built-in inverse mapper
  • You have to manually build the reverse lookup yourself

What the C++ Solution Does (Step-by-Step)

The C++ solution manually builds a β€œtranslation dictionary” by comparing joint names. Here’s how it works:

Step 1: Get Both Lists

// Get the mesh's joint list: ["/Skeleton/Hand", "/Skeleton/Finger1", "/Skeleton/Finger2", ...]
skinningQuery.GetJointOrder(&meshJointOrder);

// Get the skeleton's joint list: ["/Skeleton/Root", "/Skeleton/Spine", ..., "/Skeleton/Hand", ...]
skeletonQuery.GetJointOrder(&skeletonJointOrder);

Step 2: Build a Translation Dictionary

// For each joint in the mesh's list, find where it appears in the skeleton's list
// Example: "/Skeleton/Hand" is at position 2 in mesh list, but position 47 in skeleton list
// Store: meshToSkelMap["/Skeleton/Hand"] = 47

Step 3: Translate the Indices

// When you have a mesh-local index like 2, look up:
// 1. What joint name is at position 2 in mesh list? β†’ "/Skeleton/Hand"
// 2. What position is "/Skeleton/Hand" in skeleton list? β†’ 47
// 3. Use 47 as the skeleton-space index

Why C++ Works Better Here

C++ Advantages:

  1. Direct Access - C++ can directly access the internal data structures
  2. Performance - Building lookup maps is faster in C++
  3. Control - You can build exactly the data structure you need
  4. No Limitations - You’re not limited by what Python bindings expose

The C++ Solution Provides:

  • A std::unordered_map (like a fast lookup dictionary) that maps joint names β†’ skeleton indices
  • Direct iteration over arrays without Python overhead
  • Efficient memory management for large meshes
  • The ability to build custom remapping logic that OpenUSD doesn’t provide

How All the Pieces Fit Together

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. USD File (Maya Export)                                   β”‚
β”‚    - Skeleton has 100 joints                                β”‚
β”‚    - Mesh only uses 15 joints                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. UsdSkelCache (The Organizer)                             β”‚
β”‚    - Populates cache with skeleton structure                β”‚
β”‚    - Creates query objects for mesh and skeleton            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. UsdSkelSkinningQuery (Mesh Info)                         β”‚
β”‚    - GetJointOrder() β†’ mesh's 15 joints                     β”‚
β”‚    - ComputeJointInfluences() β†’ indices [2,5,8] (mesh-local)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. UsdSkelSkeletonQuery (Skeleton Info)                    β”‚
β”‚    - GetJointOrder() β†’ skeleton's 100 joints                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 5. C++ Remapping Logic (The Translation)                    β”‚
β”‚    - Compare joint names between lists                      β”‚
β”‚    - Build lookup map: mesh index β†’ skeleton index          β”‚
β”‚    - Translate [2,5,8] β†’ [47,89,92] (skeleton-space)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The Missing Piece: Why OpenUSD Doesn’t Solve This

OpenUSD’s Design Philosophy:

  • OpenUSD provides the building blocks (queries, mappers, order access)
  • It doesn’t provide every possible combination of operations
  • The inverse mapping is considered an β€œapplication-level” concern

What’s Missing:

  • No GetInverseJointMapper() method
  • No RemapIndicesToSkeletonSpace() convenience function
  • No automatic detection that remapping is needed

Why It’s Missing:

  1. Not Common Enough - Most applications don’t need skeleton-space indices
  2. Performance - Building inverse maps has a cost, so it’s opt-in
  3. Flexibility - Different applications need different remapping strategies
  4. API Bloat - Adding every possible operation would make the API huge

What Your Friend’s C++ Solution Achieves

The C++ solution provides:

  1. :white_check_mark: Complete Control - You build exactly the remapping you need
  2. :white_check_mark: Performance - Fast lookup using hash maps
  3. :white_check_mark: Clarity - The code explicitly shows what’s happening
  4. :white_check_mark: Reliability - No hidden behavior or limitations

What Python Can’t Do:

  • :cross_mark: Can’t efficiently build custom lookup structures
  • :cross_mark: Limited by what bindings expose
  • :cross_mark: Performance overhead for large meshes
  • :cross_mark: Less control over memory management

Solution

The key insight is that GetJointMapper() maps FROM skeleton TO mesh-local, but you need to map FROM mesh-local TO skeleton. You need to build the inverse mapping by comparing joint paths. The UsdSkelSkinningQuery::GetJointOrder() method [1] provides access to the mesh-local joint order, while UsdSkelSkeletonQuery::GetJointOrder() [2] provides the skeleton’s joint order. This exact problem and solution approach has been discussed in the Alliance for OpenUSD forum [9], confirming the path-comparison remapping strategy.

C++ Solution

#include <pxr/usd/usdSkel/cache.h>
#include <pxr/usd/usdSkel/skinningQuery.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/base/vt/array.h>
#include <pxr/base/tf/token.h>
#include <unordered_map>

UsdSkelCache skelCache;
VtArray<int> jointIndices;
VtArray<float> jointWeights;

if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim)) {
    // Populate the cache - required before GetSkinningQuery() works
    skelCache.Populate(skelRoot, UsdTraverseInstanceProxies());
    
    // Find the Skeleton that should affect this prim
    // See UsdSkelBindingAPI::GetInheritedSkeleton() [[3]](#appendix-sources)
    UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton();
    
    // Get skinning query - see UsdSkelSkinningQuery class reference [[1]](#appendix-sources)
    if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) {
        // Get joint influences (indices are into mesh's skel:joints)
        // See UsdSkelSkinningQuery::ComputeJointInfluences() [[1]](#appendix-sources)
        skinningQuery.ComputeJointInfluences(&jointIndices, &jointWeights);
        
        // Get the skeleton query to access skeleton joint order
        UsdSkelSkeletonQuery skeletonQuery = skelCache.GetSkeletonQuery(skel);
        if (!skeletonQuery) {
            // Fallback: get skeleton directly
            skeletonQuery = skelCache.GetSkeletonQuery(skel.GetPrim());
        }
        
        if (skeletonQuery) {
            // Get mesh-local joint order (from skel:joints)
            // See UsdSkelSkinningQuery::GetJointOrder() [[1]](#appendix-sources)
            VtTokenArray meshJointOrder;
            skinningQuery.GetJointOrder(&meshJointOrder);
            
            // Get skeleton joint order
            // See UsdSkelSkeletonQuery::GetJointOrder() [[2]](#appendix-sources)
            VtTokenArray skeletonJointOrder = skeletonQuery.GetJointOrder();
            
            // Build reverse lookup: mesh joint path -> skeleton joint index
            std::unordered_map<TfToken, int, TfToken::HashFunctor> meshToSkelMap;
            for (size_t i = 0; i < meshJointOrder.size(); ++i) {
                const TfToken& meshJointPath = meshJointOrder[i];
                
                // Find this mesh joint in the skeleton's joint order
                for (size_t j = 0; j < skeletonJointOrder.size(); ++j) {
                    if (skeletonJointOrder[j] == meshJointPath) {
                        meshToSkelMap[meshJointPath] = static_cast<int>(j);
                        break;
                    }
                }
            }
            
            // Remap joint indices from mesh-local to skeleton space
            VtArray<int> remappedIndices;
            remappedIndices.reserve(jointIndices.size());
            
            for (int meshLocalIdx : jointIndices) {
                if (meshLocalIdx >= 0 && 
                    static_cast<size_t>(meshLocalIdx) < meshJointOrder.size()) {
                    const TfToken& meshJointPath = meshJointOrder[meshLocalIdx];
                    auto it = meshToSkelMap.find(meshJointPath);
                    if (it != meshToSkelMap.end()) {
                        remappedIndices.push_back(it->second);
                    } else {
                        // Joint not found in skeleton (shouldn't happen, but handle gracefully)
                        remappedIndices.push_back(-1);
                    }
                } else {
                    remappedIndices.push_back(-1);
                }
            }
            
            // Now remappedIndices contains indices into skeleton's joints array
            // Use remappedIndices instead of jointIndices
        }
    }
}

Understanding the C++ Code: A Detailed Walkthrough

For non-programmers, here’s what each part of the C++ solution does:

Part 1: Setting Up the Cache

UsdSkelCache skelCache;
if (UsdSkelRoot skelRoot = UsdSkelRoot::Find(prim)) {
    skelCache.Populate(skelRoot, UsdTraverseInstanceProxies());

What this does: Think of UsdSkelCache as a β€œphone book” that organizes all skeleton information. Before you can ask questions, you need to β€œpopulate” it by telling it to scan the USD file and build its index. This is like loading a database before you can query it.

Part 2: Finding the Skeleton

UsdSkelSkeleton skel = UsdSkelBindingAPI(prim).GetInheritedSkeleton();

What this does: This finds which skeleton is β€œbound” to your mesh. Think of it as asking β€œwhich skeleton controls this mesh?” The answer might be inherited from a parent object, so GetInheritedSkeleton() follows the chain up to find it.

Part 3: Getting Joint Influences

if (UsdSkelSkinningQuery skinningQuery = skelCache.GetSkinningQuery(prim)) {
    skinningQuery.ComputeJointInfluences(&jointIndices, &jointWeights);

What this does: This asks the mesh β€œwhich joints affect each vertex, and how much?” The jointIndices array contains numbers like [2, 5, 8] - but these are mesh-local indices (positions in the mesh’s joint list, not the skeleton’s).

Part 4: Getting Both Joint Lists

VtTokenArray meshJointOrder;
skinningQuery.GetJointOrder(&meshJointOrder);

VtTokenArray skeletonJointOrder = skeletonQuery.GetJointOrder();

What this does: This gets the actual names of joints in both lists. For example:

  • meshJointOrder might be: ["/Skeleton/Hand", "/Skeleton/Finger1", "/Skeleton/Finger2"]
  • skeletonJointOrder might be: ["/Skeleton/Root", "/Skeleton/Spine", ..., "/Skeleton/Hand", "/Skeleton/Finger1", ...]

Part 5: Building the Translation Dictionary

std::unordered_map<TfToken, int, TfToken::HashFunctor> meshToSkelMap;
for (size_t i = 0; i < meshJointOrder.size(); ++i) {
    const TfToken& meshJointPath = meshJointOrder[i];
    for (size_t j = 0; j < skeletonJointOrder.size(); ++j) {
        if (skeletonJointOrder[j] == meshJointPath) {
            meshToSkelMap[meshJointPath] = static_cast<int>(j);
            break;
        }
    }
}

What this does: This is the β€œtranslation dictionary” builder. It goes through each joint in the mesh’s list and finds where that same joint appears in the skeleton’s list.

Example:

  • Mesh joint at position 0: "/Skeleton/Hand" β†’ Found at skeleton position 47 β†’ Store: meshToSkelMap["/Skeleton/Hand"] = 47
  • Mesh joint at position 1: "/Skeleton/Finger1" β†’ Found at skeleton position 89 β†’ Store: meshToSkelMap["/Skeleton/Finger1"] = 89

The std::unordered_map is like a fast lookup table - you can ask β€œwhat skeleton index does /Skeleton/Hand map to?” and get 47 instantly.

Part 6: Translating the Indices

VtArray<int> remappedIndices;
for (int meshLocalIdx : jointIndices) {
    const TfToken& meshJointPath = meshJointOrder[meshLocalIdx];
    auto it = meshToSkelMap.find(meshJointPath);
    if (it != meshToSkelMap.end()) {
        remappedIndices.push_back(it->second);
    }
}

What this does: This is where the actual translation happens. For each mesh-local index (like 2):

  1. Look up the joint name: meshJointOrder[2] β†’ "/Skeleton/Finger2"
  2. Find in dictionary: meshToSkelMap["/Skeleton/Finger2"] β†’ 92
  3. Store skeleton index: Add 92 to remappedIndices

Result: [2, 5, 8] (mesh-local) becomes [47, 89, 92] (skeleton-space)

Why This Approach Works

The Key Insight: Joints have names (like "/Skeleton/Hand"), and names are the same in both lists. The C++ solution uses names as the β€œbridge” between the two numbering systems:

Mesh Index β†’ Joint Name β†’ Skeleton Index
    2      β†’ "/Hand"    β†’     47

Why Names Work:

  • Joint names are unique identifiers
  • They’re the same regardless of position in the list
  • They’re what OpenUSD uses internally to match things up

The Algorithm:

  1. Get both lists (mesh joints, skeleton joints)
  2. For each mesh joint, find its name in the skeleton list
  3. Remember the mapping: mesh position β†’ skeleton position
  4. Use the mapping to translate indices

Alternative C++ Solution: Using the Mapper’s Internal Index Map

Your friend discovered a more efficient approach that leverages the mapper’s internal _indexMap structure instead of string comparisons. This is a clever optimization:

VtArray<int> indices;
indices.resize(jointIndices.size());
for (size_t i = 0; i < jointIndices.size(); i++)
{
    indices[i] = i;
}
// Start with identity mapping: [0, 1, 2, 3, ...] for mesh-local indices
VtArray<int> jointLut = indices;

// Build the lookup table (LUT) to map indices from the (sparse) joint array 
// in the mesh to the full joint array of the skeleton
if (skinningQuery.GetJointMapper())
{
    skinningQuery.GetJointMapper()->Remap(indices, &jointLut);
}

How This Works:

The GetJointMapper() internally maintains an _indexMap array that maps skeleton indices β†’ mesh-local positions. When you call Remap() with an identity array [0, 1, 2, ...] representing mesh-local positions, the mapper uses its internal mapping to determine where those positions correspond in skeleton space.

Why This Is Better:

  1. No String Comparisons - Avoids expensive token/path string comparisons
  2. Uses Internal Mapping - Leverages the mapper’s pre-computed _indexMap structure
  3. More Efficient - Direct array lookups instead of nested loops with string matching
  4. Matches USD Internals - Uses the same mechanism USD uses internally for remapping

How It Works Internally:

The mapper’s Remap() method (see animMapper.h lines 226-237) uses _indexMap[i] to map source index i to target index. When you pass mesh-local indices [0, 1, 2, ...]:

  • The mapper looks up where each mesh-local position maps in skeleton space
  • The result jointLut contains skeleton indices corresponding to each mesh-local position
  • You can then use jointLut[meshLocalIdx] to get the skeleton index

Example:

// If mesh has 3 joints at skeleton positions [47, 89, 92]:
// Input:  indices = [0, 1, 2]  (mesh-local positions)
// Output: jointLut = [47, 89, 92]  (skeleton indices)

// Then to remap jointIndices:
for (int meshIdx : jointIndices) {
    int skeletonIdx = jointLut[meshIdx];  // Direct lookup!
}

Important Note:

This solution works because the mapper’s _indexMap stores the forward mapping (skeleton β†’ mesh-local), but when you pass mesh-local indices as the source, the mapper’s internal logic effectively gives you access to the inverse mapping. This is more efficient than building your own reverse lookup table with string comparisons.

Comparison:

Approach String Comparison Mapper-Based
Speed O(nΓ—m) string comparisons O(n) array lookups
Memory Hash map of tokens Direct array access
Complexity Manual reverse lookup Uses mapper internals
Maintenance More code to maintain Leverages existing API

Your Friend’s Insight:

The key insight is that GetJointMapper()->Remap() can be used creatively to extract the inverse mapping by passing mesh-local indices and letting the mapper’s internal _indexMap do the work. This avoids the need for manual string-based reverse lookup construction.

Potential API Improvement:

As your friend noted, OpenUSD could provide a convenience method like:

// Proposed API (doesn't exist yet):
VtIntArray RemapIndicesToSkeletonSpace(const VtIntArray& meshLocalIndices) const;

This would encapsulate the mapper-based remapping logic and make it more accessible. The implementation would be similar to your friend’s solution but provided as a first-class API method, eliminating the need for users to understand the mapper’s internal structure.

Python Solution

from pxr import Usd, UsdSkel, Vt

def remap_joint_indices_to_skeleton_space(prim):
    """
    Remap joint indices from mesh-local skel:joints to skeleton joints.
    
    Args:
        prim: The skinnable prim (mesh) with UsdSkelBindingAPI
        
    Returns:
        tuple: (remapped_indices, joint_weights) or None if remapping fails
    """
    skel_cache = UsdSkel.Cache()
    
    # Find SkelRoot
    skel_root = UsdSkel.Root.Find(prim)
    if not skel_root:
        return None
    
    skel_cache.Populate(skel_root, Usd.TraverseInstanceProxies())
    
    # Get the skeleton
    # See UsdSkelBindingAPI::GetInheritedSkeleton() [[3]](#appendix-sources)
    binding_api = UsdSkel.BindingAPI(prim)
    skel = binding_api.GetInheritedSkeleton()
    if not skel:
        return None
    
    # Get skinning query - see UsdSkelSkinningQuery class reference [[1]](#appendix-sources)
    skinning_query = skel_cache.GetSkinningQuery(prim)
    if not skinning_query:
        return None
    
    # Get joint influences (indices are into mesh's skel:joints)
    # See UsdSkelSkinningQuery::ComputeJointInfluences() [[1]](#appendix-sources)
    joint_indices = Vt.IntArray()
    joint_weights = Vt.FloatArray()
    if not skinning_query.ComputeJointInfluences(joint_indices, joint_weights):
        return None
    
    # Get skeleton query - see UsdSkelSkeletonQuery class reference [[2]](#appendix-sources)
    skeleton_query = skel_cache.GetSkeletonQuery(skel)
    if not skeleton_query:
        return None
    
    # Get mesh-local joint order (from skel:joints)
    # See UsdSkelSkinningQuery::GetJointOrder() [[1]](#appendix-sources)
    mesh_joint_order = Vt.TokenArray()
    if not skinning_query.GetJointOrder(mesh_joint_order):
        # If no custom joint order, mesh uses skeleton order directly
        skeleton_joint_order = skeleton_query.GetJointOrder()
        # If mesh order matches skeleton order, no remapping needed
        return (joint_indices, joint_weights)
    
    # Get skeleton joint order
    skeleton_joint_order = skeleton_query.GetJointOrder()
    
    # Build reverse lookup: mesh joint path -> skeleton joint index
    mesh_to_skel_map = {}
    for i, mesh_joint_path in enumerate(mesh_joint_order):
        try:
            skel_idx = skeleton_joint_order.index(mesh_joint_path)
            mesh_to_skel_map[i] = skel_idx
        except ValueError:
            # Joint not found in skeleton (shouldn't happen, but handle gracefully)
            mesh_to_skel_map[i] = -1
    
    # Remap joint indices from mesh-local to skeleton space
    remapped_indices = Vt.IntArray()
    remapped_indices.reserve(len(joint_indices))
    
    for mesh_local_idx in joint_indices:
        if mesh_local_idx >= 0 and mesh_local_idx < len(mesh_joint_order):
            skel_idx = mesh_to_skel_map.get(mesh_local_idx, -1)
            remapped_indices.append(skel_idx)
        else:
            remapped_indices.append(-1)
    
    return (remapped_indices, joint_weights)


# Usage example:
stage = Usd.Stage.Open("path/to/file.usd")
mesh_prim = stage.GetPrimAtPath("/SkelRoot/Mesh")

result = remap_joint_indices_to_skeleton_space(mesh_prim)
if result:
    remapped_indices, joint_weights = result
    # Now remapped_indices contains indices into skeleton's joints array
    print(f"Remapped {len(remapped_indices)} joint indices")

Summary: Why C++ Solves What Python Cannot

Direct Answer to β€œWhy is OpenUSD Python Missing This?”

OpenUSD Python bindings are β€œthin wrappers” - they expose C++ functions but don’t add convenience methods. The Python API has:

:white_check_mark: What Python HAS:

  • ComputeJointInfluences() - Gets mesh-local indices
  • GetJointOrder() - Gets joint name lists
  • GetJointMapper() - Gets forward mapper (skeleton β†’ mesh)

:cross_mark: What Python DOESN’T HAVE:

  • RemapIndicesToSkeletonSpace() - No inverse remapping function
  • GetInverseJointMapper() - No reverse mapper
  • Automatic index translation - No built-in solution

Why Python Doesn’t Have It:

  1. Not in C++ API - The C++ API doesn’t have it either, so Python can’t expose it
  2. Application-Level Concern - OpenUSD considers this an application problem, not a library problem
  3. Performance - Building inverse maps costs memory/CPU, so it’s opt-in
  4. Flexibility - Different apps need different remapping strategies

What C++ Solves That Python Cannot

C++ Advantages:

  1. Custom Data Structures

    • C++ can build std::unordered_map (fast hash table) for O(1) lookups
    • Python dictionaries work, but have more overhead for large meshes
    • C++ gives you control over memory layout and performance
  2. Direct Array Access

    • C++ can iterate arrays without Python overhead
    • Direct pointer access to joint name arrays
    • No Python object wrapping/unwrapping
  3. Performance at Scale

    • For meshes with thousands of vertices, C++ is significantly faster
    • Building lookup maps is a one-time cost, but Python adds overhead to every operation
    • Memory management is more efficient
  4. Complete Control

    • You can build exactly the remapping logic you need
    • No limitations from Python bindings
    • Can optimize for your specific use case

What Your Friend’s C++ Code Achieves:

// This builds a fast lookup table that Python can't match:
std::unordered_map<TfToken, int> meshToSkelMap;
// Lookup time: O(1) - constant time, regardless of skeleton size
// Python dict: O(1) average, but more overhead per lookup

The Bottom Line:

  • Python CAN do this - but it’s slower and less efficient
  • C++ DOES this better - faster, more control, better for production
  • OpenUSD doesn’t provide it - in either language, you build it yourself
  • C++ is the right choice - when performance and control matter

The Missing API: What Should Exist But Doesn’t

What Would Be Ideal:

// This doesn't exist, but would be nice:
VtIntArray remappedIndices = skinningQuery.RemapIndicesToSkeletonSpace(jointIndices);

Why It Doesn’t Exist:

  • OpenUSD philosophy: provide building blocks, not every convenience function
  • Different applications need different remapping strategies
  • Adding it would require maintaining inverse mapper state
  • Performance concerns - not everyone needs this operation

What Exists Instead:

  • Building blocks: GetJointOrder(), GetJointMapper()
  • You build the solution: Compare lists, build map, translate indices
  • This gives flexibility but requires more code

Why GetJointMapper().Remap() Doesn’t Work Directly

The GetJointMapper() returns a mapper that maps FROM skeleton joint order TO mesh-local joint order. When you call Remap() on it, you’re mapping skeleton-space data to mesh-local space, which is the opposite of what you need. The UsdSkel Schemas In-Depth documentation [5] explains that remapping via mapper works by matching joint names, but the mapper operates in the forward direction (skeleton β†’ mesh-local) only.

The mapper is designed for use cases like:

  • Mapping skeleton joint transforms to mesh-local joint order for skinning
  • Mapping animation data from skeleton order to mesh-local order

But for remapping indices (not transforms), you need the inverse mapping, which requires comparing the joint paths/orders directly using GetJointOrder() methods [1] [2]. This limitation has been discussed in related forum threads [10] and GitHub issues [11], where users encountered similar challenges with explicit joint orders.

Alternative: Using GetJointOrder() Directly

If the mapper is null (meaning mesh uses skeleton order directly), you can skip remapping:

VtTokenArray meshJointOrder;
if (skinningQuery.GetJointOrder(&meshJointOrder)) {
    // Mesh has custom joint order, need remapping
    // ... use remapping code above ...
} else {
    // Mesh uses skeleton order directly, no remapping needed
    // jointIndices already map to skeleton joints
}

Key Takeaways

  1. GetJointMapper() maps skeleton β†’ mesh-local, not the other way around (see [5])
  2. Two approaches to remap indices:
    • String-based approach: Compare joint paths between mesh skel:joints and skeleton joints using GetJointOrder() methods [1] [2] - more intuitive but slower
    • Mapper-based approach: Use GetJointMapper()->Remap() with identity array to leverage internal _indexMap - more efficient, matches USD internals
  3. Build a reverse lookup map from mesh joint paths to skeleton joint indices (as discussed in [9])
  4. Handle the case where mapper is null (mesh uses skeleton order directly) - see UsdSkelBindingAPI::GetJointsAttr() behavior [7]

Recommended Approach

Use the mapper-based solution (your friend’s approach) when:

  • Performance matters (large meshes, many joints)
  • You want to match USD’s internal behavior
  • You’re comfortable with leveraging internal mapper structures

Use the string-based solution when:

  • You need maximum clarity and explicitness
  • You want to understand exactly what’s happening
  • Performance is not critical
  • You’re learning/debugging the remapping process

Hello Jan,

thank you for taking the effort to investigate this. However, just speaking for myself here, I am not sure posting pages of ML generated answers here is the right way to go. I think it’s safe to assume that everyone who would like to has access to the tools to generate these kinds of essays themselves. I am afraid more interesting conversations might get lost in the wall of text you generated.

I hope that makes sense,

Thanks,

Koen

1 Like

Hello Koen,

That is very true… and you got a good point there.

Thx for the honest feedback.