Code/HumanoidRetargeter/Core/Formats/Mmd/MmdStandardSkeleton.cs

Creates a generic MMD (MikuMikuDance) humanoid skeleton model based on motion data. It builds a list of named bones with parent relationships, optional bones depending on motion tracks/IK state, and configures IK controllers for feet when motion provides tracks, then validates and returns the model.

Reflection
namespace HumanoidRetargeter.Core.Formats.Mmd;

using Vector3 = System.Numerics.Vector3;

// A generic humanoid in MMD units. No mesh, model assets or external runtime needed.
internal static class MmdStandardSkeleton
{
	internal static MmdModel Create( MmdMotion motion )
	{
		var model = new MmdModel();
		int Add( string name, int parent, Vector3 position )
		{
			model.Bones.Add( new MmdBone { Name = name, Parent = parent, Position = position } );
			return model.Bones.Count - 1;
		}
		string Name( params string[] names )
			=> names.FirstOrDefault( name => motion.Tracks.ContainsKey( name ) || motion.IkStates.ContainsKey( name ) ) ?? names[ 0 ];
		int Optional( string name, int parent, Vector3 position )
			=> motion.Tracks.ContainsKey( name ) ? Add( name, parent, position ) : parent;

		var root = Add( "全ての親", -1, Vector3.Zero );
		var center = Add( "センター", root, new Vector3( 0, 8, 0 ) );
		var groove = Optional( "グルーブ", center, new Vector3( 0, 8, 0 ) );
		var waist = Optional( "腰", groove, new Vector3( 0, 11.5f, 0 ) );
		var hips = Add( "下半身", waist, new Vector3( 0, 11.5f, 0 ) );
		var spine = Add( "上半身", waist, new Vector3( 0, 12, 0 ) );
		spine = Optional( Name( "上半身2", "上半身2" ), spine, new Vector3( 0, 14, 0 ) );
		spine = Optional( Name( "上半身3", "上半身3" ), spine, new Vector3( 0, 15, 0 ) );
		var neck = Add( "首", spine, new Vector3( 0, 16, 0 ) );
		var head = Add( "頭", neck, new Vector3( 0, 17.3f, 0 ) );
		var eyes = Optional( "両目", head, new Vector3( 0, 18, -0.7f ) );

		foreach ( var (prefix, side) in new[] { ("左", 1f), ("右", -1f) } )
		{
			Vector3 Point( float x, float y, float z = 0 ) => new( side * x, y, z );
			Optional( prefix + "目", eyes, Point( 0.35f, 18, -0.7f ) );
			var shoulderParent = Optional( prefix + "肩P", spine, Point( 1, 15.8f ) );
			var shoulder = Add( prefix + "肩", shoulderParent, Point( 1, 15.8f ) );
			var shoulderCancel = Optional( prefix + "肩C", shoulder, Point( 1.9f, 15.8f ) );
			if ( shoulderParent != spine && shoulderCancel != shoulder )
			{
				model.Bones[ shoulderCancel ].Flags = 0x100;
				model.Bones[ shoulderCancel ].AppendParent = shoulderParent;
				model.Bones[ shoulderCancel ].AppendWeight = -1;
			}
			var arm = Add( prefix + "腕", shoulderCancel, Point( 1.9f, 15.8f ) );
			arm = Optional( prefix + "腕捩", arm, Point( 3.4f, 14.7f ) );
			var elbow = Add( prefix + "ひじ", arm, Point( 4.9f, 13.6f ) );
			elbow = Optional( prefix + "手捩", elbow, Point( 6.2f, 12.6f ) );
			var hand = Add( prefix + "手首", elbow, Point( 7.5f, 11.6f ) );
			var thumb = Add( Name( prefix + "親指0", prefix + "親指0" ), hand, Point( 7.7f, 11.4f, -0.3f ) );
			thumb = Add( Name( prefix + "親指1", prefix + "親指1" ), thumb, Point( 8, 11.1f, -0.6f ) );
			Add( Name( prefix + "親指2", prefix + "親指2" ), thumb, Point( 8.3f, 10.9f, -0.7f ) );
			var fingers = new[] { "人指", "中指", "薬指", "小指" };
			for ( var finger = 0; finger < fingers.Length; finger++ )
			{
				var parent = hand;
				for ( var joint = 1; joint <= 3; joint++ )
				{
					var name = Name( prefix + fingers[ finger ] + (char)('0' + joint), prefix + fingers[ finger ] + joint );
					parent = Add( name, parent, Point( 7.8f + joint * 0.3f, 11.4f - joint * 0.22f, -0.35f + finger * 0.25f ) );
				}
			}

			var thigh = Add( prefix + "足", hips, Point( 0.9f, 11.3f ) );
			var knee = Add( prefix + "ひざ", thigh, Point( 0.9f, 6.5f, -0.1f ) );
			var anklePosition = Point( 0.9f, 1.5f );
			var toePosition = Point( 0.9f, 0.3f, -1.4f );
			var ankle = Add( prefix + "足首", knee, anklePosition );
			var toe = Add( prefix + "つま先", ankle, toePosition );
			var ikParent = Optional( Name( prefix + "足IK親", prefix + "足IK親" ), root, Point( 0.9f, 0 ) );
			var footIk = Add( Name( prefix + "足IK", prefix + "足IK" ), ikParent, anklePosition );
			var toeIk = Add( Name( prefix + "つま先IK", prefix + "つま先IK" ), footIk, toePosition );
			// FK-only poses must not be pulled back to unanimated IK controllers.
			if ( motion.Tracks.ContainsKey( model.Bones[ footIk ].Name )
				|| motion.IkStates.ContainsKey( model.Bones[ footIk ].Name ) )
			{
				var controller = model.Bones[ footIk ];
				controller.IkTarget = ankle;
				controller.Iterations = 64;
				controller.AngleLimit = 0.5f;
				controller.Links.Add( new MmdIkLink( knee, true, new Vector3( -MathF.PI, 0, 0 ), Vector3.Zero ) );
				controller.Links.Add( new MmdIkLink( thigh, false, Vector3.Zero, Vector3.Zero ) );
			}
			if ( motion.Tracks.ContainsKey( model.Bones[ toeIk ].Name ) )
			{
				var controller = model.Bones[ toeIk ];
				controller.IkTarget = toe;
				controller.Iterations = 16;
				controller.AngleLimit = 0.5f;
				controller.Links.Add( new MmdIkLink( ankle, false, Vector3.Zero, Vector3.Zero ) );
			}
		}
		model.Notes.Add( "MMD: using the built-in standard source skeleton. Select a PMX/PMD source model for exact proportions, custom bones and model physics." );
		model.Validate();
		return model;
	}
}