Add additional skeletal animation and skinning test files

Added comprehensive test models and scripts for skeletal animation features:

Skeletal Animation Test Models (models/):
- skelanim-empty.usda: Edge case with no animation data
- skelanim-mixed.usda: Mixed static and time-sampled (incomplete skeleton binding)
- skelanim-rotation-only.usda: Only rotation channels animated
- skelanim-single-joint.usda: Single joint animation
- skelanim-static.usda: Static values only (incomplete skeleton binding)
- skelanim-timesampled.usda: Time-sampled values (incomplete skeleton binding)

Synthetic Skin Test Models (models/):
- synthetic-skin-8influences.usda: 8 influences per vertex test
- synthetic-skin-16influences.usda: 16 influences per vertex test
- synthetic-skin-32influences.usda: 32 influences per vertex test

Test Scripts (web/js/):
- test-anim-debug.js: Animation debugging utility
- test-16influences.js: Test 16 influences per vertex
- test-all-bone-reduction.js: Bone reduction testing
- test-boundaries.js: Boundary condition tests
- test-digit-limits.js: Digit parsing limits
- test-malicious.js: Malicious input handling
- test-number-parsing.js: Number parsing validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2025-11-05 05:06:51 +09:00
parent 8fdfa376da
commit 6b41081a04
16 changed files with 909 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Z"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["joint0"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
]
def SkelAnimation "EmptyAnim"
{
uniform token[] joints = ["joint0"]
# No animation data - edge case test
}
}
}

View File

@@ -0,0 +1,45 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Z"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["root", "root/spine", "root/leftArm", "root/rightArm"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-0.5, 1.5, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0.5, 1.5, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-0.5, 1.5, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0.5, 1.5, 0, 1) )
]
def SkelAnimation "MixedAnim"
{
uniform token[] joints = ["root", "root/spine", "root/leftArm", "root/rightArm"]
# Static translations (bind pose)
float3[] translations = [(0, 0, 0), (0, 1, 0), (-0.5, 1.5, 0), (0.5, 1.5, 0)]
# Time-sampled rotations (animated)
quatf[] rotations.timeSamples = {
0: [(1, 0, 0, 0), (1, 0, 0, 0), (1, 0, 0, 0), (1, 0, 0, 0)],
0.5: [(0.9962, 0, 0.0872, 0), (1, 0, 0, 0), (0.9659, 0, 0, 0.2588), (0.9659, 0, 0, -0.2588)],
1: [(0.9848, 0, 0.1736, 0), (1, 0, 0, 0), (0.8660, 0, 0, 0.5), (0.8660, 0, 0, -0.5)],
1.5: [(0.9962, 0, 0.0872, 0), (1, 0, 0, 0), (0.9659, 0, 0, 0.2588), (0.9659, 0, 0, -0.2588)],
2: [(1, 0, 0, 0), (1, 0, 0, 0), (1, 0, 0, 0), (1, 0, 0, 0)]
}
# Static scales (uniform)
half3[] scales = [(1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1)]
}
}
}

View File

@@ -0,0 +1,35 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Z"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["joint0", "joint0/joint1"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )
]
def SkelAnimation "RotationOnlyAnim"
{
uniform token[] joints = ["joint0", "joint0/joint1"]
# Only rotations are animated, no translations or scales
quatf[] rotations.timeSamples = {
0: [(1, 0, 0, 0), (1, 0, 0, 0)],
0.5: [(0.9239, 0, 0.3827, 0), (0.9659, 0.2588, 0, 0)],
1: [(0.7071, 0, 0.7071, 0), (0.8660, 0.5, 0, 0)],
1.5: [(0.9239, 0, 0.3827, 0), (0.9659, 0.2588, 0, 0)],
2: [(1, 0, 0, 0), (1, 0, 0, 0)]
}
}
}
}

View File

@@ -0,0 +1,37 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Y"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["bone"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
]
def SkelAnimation "SingleJointAnim"
{
uniform token[] joints = ["bone"]
# Single joint with time-sampled rotation
float3[] translations = [(0, 0, 0)]
quatf[] rotations.timeSamples = {
0: [(1, 0, 0, 0)],
0.25: [(0.9239, 0, 0.3827, 0)],
0.5: [(0.7071, 0, 0.7071, 0)],
0.75: [(0.9239, 0, 0.3827, 0)],
1: [(1, 0, 0, 0)]
}
half3[] scales = [(1, 1, 1)]
}
}
}

View File

@@ -0,0 +1,33 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Z"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["joint0", "joint0/joint1", "joint0/joint1/joint2"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )
]
def SkelAnimation "StaticAnim"
{
uniform token[] joints = ["joint0", "joint0/joint1", "joint0/joint1/joint2"]
# All static (non-time-sampled) values
float3[] translations = [(0.5, 0, 0), (0, 1.5, 0), (0, 0.75, 0)]
quatf[] rotations = [(1, 0, 0, 0), (0.7071, 0, 0.7071, 0), (0.9239, 0.3827, 0, 0)]
half3[] scales = [(1, 1, 1), (1.2, 1.2, 1.2), (0.8, 0.8, 0.8)]
}
}
}

View File

@@ -0,0 +1,53 @@
#usda 1.0
(
defaultPrim = "Root"
upAxis = "Z"
)
def Xform "Root"
{
def Skeleton "Skel"
{
uniform token[] joints = ["joint0", "joint0/joint1"]
uniform matrix4d[] bindTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2, 0, 1) )
]
uniform matrix4d[] restTransforms = [
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ),
( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2, 0, 1) )
]
def SkelAnimation "Anim"
{
uniform token[] joints = ["joint0", "joint0/joint1"]
# Time-sampled translations
float3[] translations.timeSamples = {
0: [(0, 0, 0), (0, 2, 0)],
1: [(1, 0, 0), (0, 2.5, 0)],
2: [(2, 0, 0), (0, 3, 0)],
3: [(1, 0, 0), (0, 2.5, 0)],
4: [(0, 0, 0), (0, 2, 0)]
}
# Time-sampled rotations
quatf[] rotations.timeSamples = {
0: [(1, 0, 0, 0), (1, 0, 0, 0)],
1: [(0.9239, 0, 0.3827, 0), (0.9659, 0.2588, 0, 0)],
2: [(0.7071, 0, 0.7071, 0), (0.8660, 0.5, 0, 0)],
3: [(0.9239, 0, 0.3827, 0), (0.9659, 0.2588, 0, 0)],
4: [(1, 0, 0, 0), (1, 0, 0, 0)]
}
# Time-sampled scales
half3[] scales.timeSamples = {
0: [(1, 1, 1), (1, 1, 1)],
1: [(1.2, 1, 1), (1, 1.1, 1)],
2: [(1.5, 1, 1), (1, 1.2, 1)],
3: [(1.2, 1, 1), (1, 1.1, 1)],
4: [(1, 1, 1), (1, 1, 1)]
}
}
}
}

View File

@@ -0,0 +1,114 @@
#usda 1.0
(
defaultPrim = "root"
upAxis = "Z"
metersPerUnit = 1
)
def SkelRoot "root"
{
def Skeleton "Skeleton"
{
uniform token[] joints = [
"Root", "Root/joint1", "Root/joint2", "Root/joint3", "Root/joint4", "Root/joint5", "Root/joint6", "Root/joint7",
"Root/joint8", "Root/joint9", "Root/joint10", "Root/joint11", "Root/joint12", "Root/joint13", "Root/joint14", "Root/joint15"
]
uniform matrix4d[] bindTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.5, 0, 1))
]
uniform matrix4d[] restTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.5, 0, 1))
]
}
def Mesh "Mesh" (
prepend apiSchemas = ["SkelBindingAPI"]
)
{
uniform bool doubleSided = 0
int[] faceVertexCounts = [3, 3, 3, 3, 3, 3]
int[] faceVertexIndices = [0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 1]
rel skel:skeleton = </root/Skeleton>
point3f[] points = [
(0, 0, 0),
(1, 0, 0),
(1, 1, 0),
(0, 1, 0),
(-1, 1, 0),
(-1, 0, 0),
(-1, -1, 0)
]
int[] primvars:skel:jointIndices = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
] (
elementSize = 16
interpolation = "vertex"
)
float[] primvars:skel:jointWeights = [
0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625,
0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625,
0.25, 0.20, 0.15, 0.10, 0.08, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01, 0.005, 0.003, 0.002, 0.001, 0.001,
0.20, 0.18, 0.16, 0.14, 0.10, 0.08, 0.06, 0.04, 0.02, 0.01, 0.005, 0.003, 0.002, 0.001, 0.001, 0.001,
0.01, 0.02, 0.04, 0.06, 0.08, 0.12, 0.16, 0.20, 0.16, 0.08, 0.04, 0.02, 0.005, 0.003, 0.001, 0.001,
0.001, 0.002, 0.003, 0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.12, 0.16, 0.20, 0.14, 0.10, 0.05, 0.02,
0.001, 0.001, 0.002, 0.003, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.08, 0.10, 0.15, 0.20, 0.18, 0.15,
0.40, 0.25, 0.15, 0.10, 0.05, 0.03, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
] (
elementSize = 16
interpolation = "vertex"
)
matrix4d primvars:skel:geomBindTransform = ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
normal3f[] normals = [
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1)
] (
interpolation = "vertex"
)
}
}

View File

@@ -0,0 +1,151 @@
#usda 1.0
(
defaultPrim = "root"
upAxis = "Z"
metersPerUnit = 1
)
def SkelRoot "root"
{
def Skeleton "Skeleton"
{
uniform token[] joints = [
"Root", "Root/joint1", "Root/joint2", "Root/joint3", "Root/joint4", "Root/joint5", "Root/joint6", "Root/joint7",
"Root/joint8", "Root/joint9", "Root/joint10", "Root/joint11", "Root/joint12", "Root/joint13", "Root/joint14", "Root/joint15",
"Root/joint16", "Root/joint17", "Root/joint18", "Root/joint19", "Root/joint20", "Root/joint21", "Root/joint22", "Root/joint23",
"Root/joint24", "Root/joint25", "Root/joint26", "Root/joint27", "Root/joint28", "Root/joint29", "Root/joint30", "Root/joint31"
]
uniform matrix4d[] bindTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.75, 0, 1))
]
uniform matrix4d[] restTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 4.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 5.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 6.75, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.00, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.25, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.50, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 7.75, 0, 1))
]
}
def Mesh "Mesh" (
prepend apiSchemas = ["SkelBindingAPI"]
)
{
uniform bool doubleSided = 0
int[] faceVertexCounts = [4, 4]
int[] faceVertexIndices = [0, 1, 2, 3, 0, 3, 4, 5]
rel skel:skeleton = </root/Skeleton>
point3f[] points = [
(0, 0, 0),
(2, 0, 0),
(2, 2, 0),
(0, 2, 0),
(-2, 2, 0),
(-2, 0, 0)
]
int[] primvars:skel:jointIndices = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
] (
elementSize = 32
interpolation = "vertex"
)
float[] primvars:skel:jointWeights = [
0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125,
0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125,
0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125,
0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125,
0.15, 0.12, 0.10, 0.08, 0.07, 0.06, 0.05, 0.045, 0.04, 0.035, 0.03, 0.025, 0.02, 0.018, 0.016, 0.014,
0.012, 0.010, 0.009, 0.008, 0.007, 0.006, 0.005, 0.004, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001,
0.001, 0.002, 0.003, 0.005, 0.008, 0.012, 0.018, 0.025, 0.035, 0.045, 0.055, 0.065, 0.075, 0.080, 0.085, 0.088,
0.085, 0.080, 0.070, 0.055, 0.040, 0.028, 0.018, 0.012, 0.008, 0.005, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001,
0.10, 0.08, 0.06, 0.04, 0.02, 0.01, 0.005, 0.003, 0.002, 0.001, 0.001, 0.001, 0.001, 0.001, 0.002, 0.003,
0.005, 0.01, 0.02, 0.04, 0.06, 0.08, 0.10, 0.08, 0.06, 0.04, 0.02, 0.01, 0.005, 0.003, 0.002, 0.001,
0.001, 0.001, 0.001, 0.002, 0.002, 0.003, 0.004, 0.005, 0.006, 0.008, 0.010, 0.012, 0.015, 0.018, 0.022, 0.026,
0.030, 0.035, 0.040, 0.045, 0.050, 0.055, 0.060, 0.070, 0.080, 0.090, 0.095, 0.090, 0.070, 0.050, 0.030, 0.020,
0.30, 0.25, 0.20, 0.12, 0.08, 0.03, 0.01, 0.01, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
] (
elementSize = 32
interpolation = "vertex"
)
matrix4d primvars:skel:geomBindTransform = ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
normal3f[] normals = [
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1)
] (
interpolation = "vertex"
)
}
}

View File

@@ -0,0 +1,86 @@
#usda 1.0
(
defaultPrim = "root"
upAxis = "Z"
metersPerUnit = 1
)
def SkelRoot "root"
{
def Skeleton "Skeleton"
{
uniform token[] joints = ["Root", "Root/joint1", "Root/joint2", "Root/joint3", "Root/joint4", "Root/joint5", "Root/joint6", "Root/joint7"]
uniform matrix4d[] bindTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.5, 0, 1))
]
uniform matrix4d[] restTransforms = [
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2.5, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.0, 0, 1)),
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 3.5, 0, 1))
]
}
def Mesh "Mesh" (
prepend apiSchemas = ["SkelBindingAPI"]
)
{
uniform bool doubleSided = 0
int[] faceVertexCounts = [3, 3, 3, 3]
int[] faceVertexIndices = [0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1]
rel skel:skeleton = </root/Skeleton>
point3f[] points = [
(0, 0, 0),
(1, 0, 0),
(1, 1, 0),
(0, 1, 0),
(-1, 0, 0)
]
int[] primvars:skel:jointIndices = [
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7
] (
elementSize = 8
interpolation = "vertex"
)
float[] primvars:skel:jointWeights = [
0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125,
0.30, 0.25, 0.20, 0.15, 0.05, 0.03, 0.01, 0.01,
0.05, 0.10, 0.20, 0.30, 0.20, 0.10, 0.04, 0.01,
0.01, 0.01, 0.03, 0.05, 0.15, 0.20, 0.25, 0.30,
0.40, 0.30, 0.15, 0.10, 0.05, 0.0, 0.0, 0.0
] (
elementSize = 8
interpolation = "vertex"
)
matrix4d primvars:skel:geomBindTransform = ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
normal3f[] normals = [
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1),
(0, 0, 1)
] (
interpolation = "vertex"
)
}
}

View File

@@ -0,0 +1,64 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
async function main() {
const loader = new TinyUSDZLoader();
await loader.init();
const filePath = '../../models/synthetic-skin-16influences.usda';
try {
const data = fs.readFileSync(filePath);
console.log(`Loading ${filePath} (${data.length} bytes)`);
// Enable bone reduction in loader
loader.setEnableBoneReduction(true);
loader.setTargetBoneCount(4);
// Load USD with bone reduction config
await new Promise((resolve, reject) => {
loader.parse(
data,
'synthetic-skin-16influences.usda',
(usd) => {
console.log('\n=== USD loaded successfully ===');
// Get mesh info
const renderScene = usd.getRenderScene();
console.log('\nScene has', renderScene.meshes.length, 'meshes');
resolve(usd);
},
(error) => {
console.error('Failed to load USD:', error);
reject(error);
},
{
maxMemoryLimitMB: 2048
}
);
}).then((usd) => {
const renderScene = usd.getRenderScene();
const meshes = renderScene.meshes;
console.log('\nMeshes found:', meshes.length);
for (const mesh of meshes) {
console.log('\nMesh path:', mesh.abs_path);
if (mesh.joint_and_weights) {
console.log(' Joint and weights elementSize:', mesh.joint_and_weights.elementSize);
console.log(' Weights per vertex:', mesh.joint_and_weights.elementSize);
console.log(' Total joint indices:', mesh.joint_and_weights.jointIndices?.length);
console.log(' Total joint weights:', mesh.joint_and_weights.jointWeights?.length);
const numVertices = mesh.joint_and_weights.jointIndices.length / mesh.joint_and_weights.elementSize;
console.log(' Number of vertices:', numVertices);
}
}
});
} catch (error) {
console.error('Error:', error);
}
}
main();

View File

@@ -0,0 +1,96 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
async function testFile(filePath, expectedElementSize) {
const loader = new TinyUSDZLoader();
await loader.init();
try {
const data = fs.readFileSync(filePath);
console.log(`\n=== Testing ${filePath} ===`);
console.log(`File size: ${data.length} bytes`);
console.log(`Expected elementSize: ${expectedElementSize}`);
// Enable bone reduction in loader
loader.setEnableBoneReduction(true);
loader.setTargetBoneCount(4);
// Load USD with bone reduction config
await new Promise((resolve, reject) => {
loader.parse(
data,
filePath.split('/').pop(),
(usd) => {
console.log('✓ USD loaded successfully');
// Get render scene
const ok = usd.toRenderScene();
if (!ok) {
console.error('✗ Failed to convert to render scene');
reject(new Error('Failed to convert to render scene'));
return;
}
// Get the render scene data
const renderSceneJSON = usd.getRenderSceneAsJSON();
const renderScene = JSON.parse(renderSceneJSON);
console.log(`✓ Converted to render scene with ${renderScene.meshes.length} meshes`);
for (const mesh of renderScene.meshes) {
if (mesh.joint_and_weights) {
const elementSize = mesh.joint_and_weights.elementSize;
console.log(` Mesh: ${mesh.abs_path}`);
console.log(` Original elementSize: ${expectedElementSize}`);
console.log(` After reduction: ${elementSize}`);
if (elementSize === 4) {
console.log(` ✓ Bone reduction successful: ${expectedElementSize}${elementSize}`);
} else if (elementSize === expectedElementSize && expectedElementSize <= 4) {
console.log(` ✓ No reduction needed (already ≤ 4)`);
} else {
console.log(` ✗ Bone reduction failed: expected 4, got ${elementSize}`);
}
const numIndices = mesh.joint_and_weights.jointIndices?.length || 0;
const numWeights = mesh.joint_and_weights.jointWeights?.length || 0;
const numVertices = numIndices / elementSize;
console.log(` Vertices: ${numVertices}`);
console.log(` Total indices: ${numIndices}`);
console.log(` Total weights: ${numWeights}`);
}
}
resolve();
},
(error) => {
console.error('✗ Failed to load USD:', error);
reject(error);
},
{
maxMemoryLimitMB: 2048
}
);
});
} catch (error) {
console.error('✗ Error:', error);
}
}
async function main() {
const testFiles = [
{ path: '../../models/synthetic-skin-8influences.usda', elementSize: 8 },
{ path: '../../models/synthetic-skin-16influences.usda', elementSize: 16 },
{ path: '../../models/synthetic-skin-32influences.usda', elementSize: 32 }
];
for (const test of testFiles) {
await testFile(test.path, test.elementSize);
}
console.log('\n=== All tests completed ===');
}
main();

44
web/js/test-anim-debug.js Normal file
View File

@@ -0,0 +1,44 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
async function main() {
const loader = new TinyUSDZLoader();
await loader.init({ useMemory64: false });
const usd = await new Promise((resolve, reject) => {
loader.load('../../models/skintest-blender.usda', resolve, null, reject);
});
console.log('\n=== Animation Debug Info ===');
const numAnims = usd.numAnimations();
console.log(`Total animations: ${numAnims}`);
for (let i = 0; i < numAnims; i++) {
const animInfo = usd.getAnimationInfo(i);
console.log(`\nAnimation ${i}:`);
console.log(' animInfo:', JSON.stringify(animInfo, null, 2));
const anim = usd.getAnimation(i);
console.log(' anim.channels:', anim.channels ? anim.channels.length : 0);
console.log(' anim.samplers:', anim.samplers ? anim.samplers.length : 0);
if (anim.channels && anim.channels.length > 0) {
console.log('\n First few channels:');
for (let j = 0; j < Math.min(3, anim.channels.length); j++) {
console.log(` Channel ${j}:`, JSON.stringify(anim.channels[j], null, 2));
}
}
if (anim.samplers && anim.samplers.length > 0) {
console.log('\n First sampler:');
const sampler = anim.samplers[0];
console.log(` times.length: ${sampler.times ? sampler.times.length : 0}`);
console.log(` values.length: ${sampler.values ? sampler.values.length : 0}`);
if (sampler.times && sampler.times.length > 0) {
console.log(` First time: ${sampler.times[0]}`);
console.log(` First values: [${sampler.values.slice(0, 3).join(', ')}]`);
}
}
}
}
main().catch(console.error);

21
web/js/test-boundaries.js Normal file
View File

@@ -0,0 +1,21 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
async function test(file, expected) {
const loader = new TinyUSDZLoader();
await loader.init();
const data = fs.readFileSync(file);
return new Promise((resolve) => {
loader.parse(data, file,
() => resolve('SUCCESS'),
() => resolve('FAIL')
);
});
}
(async () => {
console.log('Testing boundary values (should succeed):',
await test('../../models/test-digit-boundaries.usda'));
console.log('Testing over-boundary values (should fail):',
await test('../../models/test-digit-over-boundaries.usda'));
})();

View File

@@ -0,0 +1,58 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
async function testFile(filePath, shouldFail = false) {
const loader = new TinyUSDZLoader();
await loader.init();
try {
const data = fs.readFileSync(filePath);
console.log(`\nTesting ${filePath} (${data.length} bytes)`);
console.log(`Expected to ${shouldFail ? 'FAIL' : 'SUCCEED'}`);
// Load USD
await new Promise((resolve, reject) => {
loader.parse(
data,
filePath.split('/').pop(),
(usd) => {
if (shouldFail) {
console.log('✗ Unexpected: File loaded successfully (should have failed)');
} else {
console.log('✓ File loaded successfully');
}
resolve();
},
(error) => {
if (shouldFail) {
console.log('✓ Expected failure:', error.message || error);
} else {
console.error('✗ Unexpected failure:', error);
}
resolve(); // Don't reject so we can continue testing
}
);
});
} catch (error) {
console.error('✗ Test error:', error);
}
}
async function main() {
console.log('=== Testing Digit Length Guards ===');
// Test normal file with valid numbers
await testFile('../../models/test-large-numbers.usda', false);
// Test file with excessive digits (should fail)
await testFile('../../models/test-excessive-digits.usda', true);
// Test files with bone reduction (should still work)
await testFile('../../models/synthetic-skin-16influences.usda', false);
console.log('\n=== All tests completed ===');
}
main();

10
web/js/test-malicious.js Normal file
View File

@@ -0,0 +1,10 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
const loader = new TinyUSDZLoader();
await loader.init();
const data = fs.readFileSync('../../models/test-malicious-digits.usda');
loader.parse(data, 'test',
() => console.log('UNEXPECTED: Malicious file loaded'),
(e) => console.log('GOOD: Malicious file rejected')
);

View File

@@ -0,0 +1,37 @@
import { TinyUSDZLoader } from './src/tinyusdz/TinyUSDZLoader.js';
import fs from 'node:fs';
async function main() {
const loader = new TinyUSDZLoader();
await loader.init();
const filePath = '../../models/test-large-numbers.usda';
try {
const data = fs.readFileSync(filePath);
console.log(`Loading ${filePath} (${data.length} bytes)`);
// Load USD
await new Promise((resolve, reject) => {
loader.parse(
data,
'test-large-numbers.usda',
(usd) => {
console.log('✓ USD loaded successfully');
console.log('✓ Large number parsing test passed');
resolve();
},
(error) => {
console.error('✗ Failed to load USD:', error);
reject(error);
}
);
});
} catch (error) {
console.error('✗ Error:', error);
}
}
main();