🎮 Game

Humanoid Retargeter is a code-only library that takes an animation source (FBX, BVH, glTF/GLB/VRM, EA ANT, Renderware DFF/ANM, MMD PMX/PMD/VMD) plus a target humanoid rig and produces retargeted clips as DMX plus vmdl/KV3 ModelDoc text. Around that core it has a bone mapper with rig presets and topology fallback, geometric, planar and a managed SAME graph-network solver, foot-plant and root-motion cleanup, sprite baking, a ported Bullet physics stack for MMD rigid bodies and soft bodies, and MMD playback components with a game resource. All file IO and asset compilation is left to the caller; the library works on byte arrays and strings.

🔧 Editor

An editor extension that retargets humanoid animation clips onto s&box models. It reads source files (FBX, GLB, MMD PMX/PMD, sprite rigs), maps bones through presets or auto-detection, shows a skinned preview with trimming and camera controls, and writes out DMX clips, vmdl files and anim graphs, including a Smart Port path that copies an existing character's graph onto a new model. Much of the bulk is vendored code it needs to decompile compiled Source 2 resources: an embedded ValveResourceFormat copy, ZstdSharp, an LZ4 decoder, a DMX datamodel library and Valve KeyValues3 readers, plus editor-side dev gates (M0Gate, UiSmokeGate, the Citizen and stock-animation gates) that only arm when an env var and matching marker file are both present and that refuse to run outside the hr-editor-rig scratch project.

🧪 Unit Test

test

🐞 Possible Bugs

Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/CollisionDispatch/InternalEdgeUtility.cs
GenerateInternalEdgeInfo reads indexList[triangleIndex], [triangleIndex+1], [triangleIndex+2] instead of triangleIndex*3, so triangles overlap and the last one reads past the end of the index array.
The edge 0 branch in AdjustInternalEdgeContacts uses the nearest point left over from the edge 2-0 search because only the edge 1 and edge 2 branches recompute it.
For SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE the trimesh assignment is commented out, so trimesh stays null and GetTriangleInfoMap throws a NullReferenceException.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/CylinderShape.cs
LocalGetSupportingVertexWithoutMargin always calls CylinderLocalSupportX, but the shape is built with m_upAxis = 1 (Y up), so radius and half height are swapped in the support point; BatchedUnitVectorGetSupportingVertexWithoutMargin uses CylinderLocalSupportY, so the two paths disagree.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/GImpactMeshShapePart.cs
TrimeshPrimitiveManager.GetVertex checks `svertices != null` in the float branch instead of `fvertices != null`, so float vertex buffers always return zero and the array read is dead code.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/HeightfieldTerrainShape.cs
In QuadTreeNode.Intersects the X slab test uses direction.X both as the ray origin and in the degenerate check instead of source.X, so X intervals are computed from the direction vector and nodes are culled or accepted wrongly during ray queries.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/SwappedConvexConcaveCreateFunc.cs
The initialization method call 'Inititialize' likely contains a typo and should be 'Initialize'.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletDynamics/Generic6DofConstraint.cs
SetLimit's axis 3-5 branch writes to m_linearLimits.m_lowerLimit/m_upperLimit with index axis - 3 instead of m_angularLimits[axis - 3], so angular limits set through SetLimit are silently dropped and corrupt the linear limits.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletDynamics/RaycastVehicle.cs
RayCast ignores the hit body and always writes s_fixedObject into m_raycastInfo.m_groundObject, with the correct line commented out directly below it, so UpdateFriction's reaction impulse never reaches the body the wheel is standing on.
m_sideImpulse, m_forwardImpulse, m_axle and m_forwardWS are hardcoded to 4 entries while AddWheel appends to m_wheelInfo without a limit, so calling AddWheel a fifth time makes UpdateVehicle/UpdateFriction throw IndexOutOfRangeException on the first frame.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/LinearMath/DebugDraw.cs
In DrawCone the second radius offset is written to offsetRadius instead of offset2Radius, so offset2Radius stays zero and two of the four cone lines run to the axis point instead of the rim.
Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/LinearMath/HullLibrary.cs
AddPoint reads p[vcount] into a copy of the IndexedVector3 struct, writes x/y/z to that copy and never writes it back, so the cube vertices it claims to add stay zero while vcount advances.
Editor/Embedded/ValveResourceFormat/Blocks/VBIB.cs
GetBlendWeightsArray handles R16G16B16A16_UNORM by reading 4 bytes per vector and dividing by 255, so 16-bit weights come out as garbage; it needs 16-bit reads and a 65535 divisor.
GetBlendIndicesArray routes R16G16B16A16_UINT into the R8G8B8A8_UINT byte-reading branch, so 16-bit joint indices are read one byte at a time and the upper bytes are ignored.
Editor/Embedded/ValveResourceFormat/ResourceTypes/ModelAnimation/Bone.cs
The guard in `SetParent` checks `Children.Contains(parent)` instead of whether the parent already lists this bone, so calling `SetParent` again with the same parent adds the bone to `parent.Children` a second time and leaves it in its old parent's children if it is reparented.