RFD 1104 details: passthrough, display, export, and the retest checklist
The problem, in one table
| Wrong, breaks eyes or fingers | Right |
|---|---|
| Rotate the hips or armature only | Rotate vrm.scene, the root, only |
Run AIGC rig repair (alignSkinnedMeshToRig, skeleton display offsets) |
Skip repair when userData.vrmNormalized is set |
Build skeleton visualization from humanoid Normalized_* nodes |
Build it from the primary skinned mesh (AvatarBody skeleton) |
Always export with rotateY(π) |
Yaw only when getWorldDirection().z > 0.5 |
Reference pattern this project follows (this project’s own earlier code, and @pixiv/three-vrm): VRMUtils.rotateVRM0 adds scene.rotation.y += Math.PI only when needed; src/library/load-utils.js’s loadVRM() rotates the scene root, not the hips; src/library/modelOrientationUtils.js’s applyVrm0SceneForwardFix(scene) applies the same fix.
Upload path
Entry: drag-drop or the file picker, into SceneManager.loadVRM, into VRMLoader.processVRM with passthrough: true.
Passthrough policy, uploads only:
- Facing (VRM0):
applyVrm0SceneForwardFix(vrm.scene), on the scene root only, whenforward.z > 0.5. - Flags: set
vrm.scene.userData.vrmNormalized = trueandvrm.scene.userData.vrmBindPassthrough = true. - No scale, center, floor snap, rebind, bone rename, or AIGC rig repair on upload.
Log line: [VRM] Upload passthrough — scene yaw only if needed; no scale/rebind/rename.
processModel must early-return for a VRM or a vrmBindPassthrough model: no autoscale, no AIGC yaw repair. Never call validateAigcRigContract or normalizeRiggedModelTransforms repair paths on an uploaded VRM.
Remote log check (?remoteLog=1, writes logs/remote-log.txt)
After a re-upload, grep for:
[VRM] Multi-skin layout after normalize
rotated scene root (all skins move together)
Regression signals, meaning the bug returned:
rotated armature via hips parent
VRM0 normalization: rotated model to face camera { beforeZ: 1, afterZ: -1 }
Processing bone … Normalized_Hips
Display path
| Concern | Rule |
|---|---|
| Skeleton overlay | getPrimarySkeletonBones(modelRoot), from the primary skinned mesh |
| Joint gizmos | A fixed, uniform SKELETON_JOINT_SPHERE_RADIUS (0.012), depthTest: false overlay |
| Bone gizmo position | getBoneDisplayWorldPosition; no AIGC display offset when userData.vrm or vrmNormalized |
updateSkeletonDisplayCorrection |
Skipped for an uploaded VRM |
| Trait or loot load | characterManager, vrmManager, load-utils: the same scene-root forward fix and rebind, never a hips-only rotation |
Export path
Entry: the Save panel, VRMExporter.exportToVRM, or SceneManager.exportToVRM.
- Rebind skinned meshes before the glTF parse, when
userData.vrmorvrmNormalizedis set. - Yaw only if the model’s world-forward has
z > 0.5, the same rule as upload; never a blindrotateY(π)on an already-correct upload. - Strip internal flags from the exported GLB:
vrmNormalized,preserveExportedOrientation,fromAigc, and so on (glbExportUtils.stripInternalExportUserData). - Restore the viewport quaternion after export, if a temporary yaw was applied.
Round-trip test: export a multi-skin reference model, re-import it, and confirm the eyes and finger bones still align in skeleton mode.
Implementation map
| Area | File |
|---|---|
| Upload normalize | src/library/vrmLoader.js, processVRM with passthrough: true |
| Forward fix | src/library/modelOrientationUtils.js, applyVrm0SceneForwardFix |
| Rebind and skeleton visualization | src/library/rigBoneUtils.js |
Skip AIGC processModel |
src/library/sceneManager.js, processModel |
| Trait or loot load | src/library/characterManager.js, vrmManager.js, load-utils.js |
| Export | src/library/VRMExporter.js, glbExportUtils.js |
| The VRM-versus-AIGC contract | RFD 1083 |
| Tests | src/__tests__/rigBoneUtils.test.js |
Retest checklist
- Hard refresh the dev tab (
Ctrl+Shift+R). - Upload a multi-skin VRM0 reference model.
- Grep the remote log for
[VRM] Multi-skin layoutand the scene-root rotation line. - Solid mode: textures correct. Skeleton mode: eye bones on the eye mesh, finger joints on the finger mesh.
- Export the VRM, re-import it, confirm the same alignment.