private static IReadOnlyList <PmxBone> AddBones([NotNull] Avatar combinedAvatar, [NotNull] Mesh combinedMesh, [NotNull, ItemNotNull] IReadOnlyList <PmxVertex> vertices) { var boneCount = combinedAvatar.AvatarSkeleton.NodeIDs.Length; var bones = new List <PmxBone>(boneCount); var hierachy = BoneUtils.BuildBoneHierarchy(combinedAvatar); for (var i = 0; i < boneCount; ++i) { var bone = new PmxBone(); var transform = combinedAvatar.AvatarSkeletonPose.Transforms[i]; var boneNode = hierachy[i]; var pmxBoneName = BoneUtils.GetPmxBoneName(boneNode.Path); bone.Name = pmxBoneName; bone.NameEnglish = BoneUtils.TranslateBoneName(pmxBoneName); // PMX's bone positions are in world coordinate system. // Unity's are in local coords. bone.InitialPosition = boneNode.InitialPositionWorld; bone.CurrentPosition = bone.InitialPosition; bone.ParentIndex = boneNode.Parent?.Index ?? -1; bone.BoneIndex = i; var singleDirectChild = GetDirectSingleChildOf(boneNode); if (singleDirectChild != null) { bone.SetFlag(BoneFlags.ToBone); bone.To_Bone = singleDirectChild.Index; } else { // TODO: Fix this; it should point to a world position. bone.To_Offset = transform.Translation.ToOpenTK().FixUnityToOpenTK(); } // No use. This is just a flag to specify more details to rotation/translation limitation. //bone.SetFlag(BoneFlags.LocalFrame); bone.InitialRotation = transform.Rotation.ToOpenTK().FixUnityToOpenTK(); bone.CurrentRotation = bone.InitialRotation; //bone.Level = boneNode.Level; bone.Level = 0; if (BoneUtils.IsBoneMovable(boneNode.Path)) { bone.SetFlag(BoneFlags.Rotation | BoneFlags.Translation); } else { bone.SetFlag(BoneFlags.Rotation); } if (ConversionConfig.Current.HideUnityGeneratedBones) { if (BoneUtils.IsNameGenerated(boneNode.Path)) { bone.ClearFlag(BoneFlags.Visible); } } bones.Add(bone); } if (ConversionConfig.Current.FixMmdCenterBones) { // Add master (全ての親) and center (センター), recompute bone hierarchy. PmxBone master = new PmxBone(), center = new PmxBone(); master.Name = "全ての親"; master.NameEnglish = "master"; center.Name = "センター"; center.NameEnglish = "center"; master.ParentIndex = 0; // "" bone center.ParentIndex = 1; // "master" bone master.CurrentPosition = master.InitialPosition = Vector3.Zero; center.CurrentPosition = center.InitialPosition = Vector3.Zero; master.SetFlag(BoneFlags.Translation | BoneFlags.Rotation); center.SetFlag(BoneFlags.Translation | BoneFlags.Rotation); bones.Insert(1, master); bones.Insert(2, center); //// Fix "MODEL_00" bone //do { // var model00 = bones.Find(b => b.Name == "グルーブ"); // if (model00 == null) { // throw new ArgumentException("MODEL_00 mapped bone is not found."); // } // model00.ParentIndex = 2; // "center" bone //} while (false); const int numBonesAdded = 2; // Fix vertices and other bones foreach (var vertex in vertices) { foreach (var boneWeight in vertex.BoneWeights) { if (boneWeight.BoneIndex == 0 && boneWeight.Weight <= 0) { continue; } if (boneWeight.BoneIndex >= 1) { boneWeight.BoneIndex += numBonesAdded; } } } for (var i = numBonesAdded + 1; i < bones.Count; ++i) { var bone = bones[i]; bone.ParentIndex += numBonesAdded; if (bone.HasFlag(BoneFlags.ToBone)) { bone.To_Bone += numBonesAdded; } } } if (ConversionConfig.Current.AppendIKBones) { // Add IK bones. PmxBone[] CreateLegIK(string leftRightJp, string leftRightEn) { var startBoneCount = bones.Count; PmxBone ikParent = new PmxBone(), ikBone = new PmxBone(); ikParent.Name = leftRightJp + "足IK親"; ikParent.NameEnglish = "leg IKP_" + leftRightEn; ikBone.Name = leftRightJp + "足IK"; ikBone.NameEnglish = "leg IK_" + leftRightEn; PmxBone master; do { master = bones.Find(b => b.Name == "全ての親"); if (master == null) { throw new ArgumentException("Missing master bone."); } } while (false); ikParent.ParentIndex = bones.IndexOf(master); ikBone.ParentIndex = startBoneCount; // IKP ikParent.SetFlag(BoneFlags.ToBone); ikBone.SetFlag(BoneFlags.ToBone); ikParent.To_Bone = startBoneCount + 1; // IK ikBone.To_Bone = -1; PmxBone ankle, knee, leg; do { var ankleName = leftRightJp + "足首"; ankle = bones.Find(b => b.Name == ankleName); var kneeName = leftRightJp + "ひざ"; knee = bones.Find(b => b.Name == kneeName); var legName = leftRightJp + "足"; leg = bones.Find(b => b.Name == legName); if (ankle == null) { throw new ArgumentException("Missing ankle bone."); } if (knee == null) { throw new ArgumentException("Missing knee bone."); } if (leg == null) { throw new ArgumentException("Missing leg bone."); } } while (false); ikBone.CurrentPosition = ikBone.InitialPosition = ankle.InitialPosition; ikParent.CurrentPosition = ikParent.InitialPosition = new Vector3(ikBone.InitialPosition.X, 0, ikBone.InitialPosition.Z); ikParent.SetFlag(BoneFlags.Translation | BoneFlags.Rotation); ikBone.SetFlag(BoneFlags.Translation | BoneFlags.Rotation | BoneFlags.IK); var ik = new PmxIK(); ik.LoopCount = 10; ik.AngleLimit = MathHelper.DegreesToRadians(114.5916f); ik.TargetBoneIndex = bones.IndexOf(ankle); var links = new IKLink[2]; links[0] = new IKLink(); links[0].BoneIndex = bones.IndexOf(knee); links[0].IsLimited = true; links[0].LowerBound = new Vector3(MathHelper.DegreesToRadians(-180), 0, 0); links[0].UpperBound = new Vector3(MathHelper.DegreesToRadians(-0.5f), 0, 0); links[1] = new IKLink(); links[1].BoneIndex = bones.IndexOf(leg); ik.Links = links; ikBone.IK = ik; return(new[] { ikParent, ikBone }); } PmxBone[] CreateToeIK(string leftRightJp, string leftRightEn) { PmxBone ikParent, ikBone = new PmxBone(); do { var parentName = leftRightJp + "足IK"; ikParent = bones.Find(b => b.Name == parentName); Debug.Assert(ikParent != null, nameof(ikParent) + " != null"); } while (false); ikBone.Name = leftRightJp + "つま先IK"; ikBone.NameEnglish = "toe IK_" + leftRightEn; ikBone.ParentIndex = bones.IndexOf(ikParent); ikBone.SetFlag(BoneFlags.ToBone); ikBone.To_Bone = -1; PmxBone toe, ankle; do { var toeName = leftRightJp + "つま先"; toe = bones.Find(b => b.Name == toeName); var ankleName = leftRightJp + "足首"; ankle = bones.Find(b => b.Name == ankleName); if (toe == null) { throw new ArgumentException("Missing toe bone."); } if (ankle == null) { throw new ArgumentException("Missing ankle bone."); } } while (false); ikBone.CurrentPosition = ikBone.InitialPosition = toe.InitialPosition; ikBone.SetFlag(BoneFlags.Translation | BoneFlags.Rotation | BoneFlags.IK); var ik = new PmxIK(); ik.LoopCount = 10; ik.AngleLimit = MathHelper.DegreesToRadians(114.5916f); ik.TargetBoneIndex = bones.IndexOf(toe); var links = new IKLink[1]; links[0] = new IKLink(); links[0].BoneIndex = bones.IndexOf(ankle); ik.Links = links.ToArray(); ikBone.IK = ik; return(new[] { ikBone }); } var leftLegIK = CreateLegIK("左", "L"); bones.AddRange(leftLegIK); var rightLegIK = CreateLegIK("右", "R"); bones.AddRange(rightLegIK); var leftToeIK = CreateToeIK("左", "L"); bones.AddRange(leftToeIK); var rightToeIK = CreateToeIK("右", "R"); bones.AddRange(rightToeIK); } if (ConversionConfig.Current.AppendEyeBones) { (int VertexStart1, int VertexCount1, int VertexStart2, int VertexCount2) FindEyesVerticeRange() { var meshNameIndex = -1; var cm = combinedMesh as CompositeMesh; Debug.Assert(cm != null, nameof(cm) + " != null"); for (var i = 0; i < cm.Names.Count; i++) { var meshName = cm.Names[i]; if (meshName == "eyes") { meshNameIndex = i; break; } } if (meshNameIndex < 0) { throw new ArgumentException("Mesh \"eyes\" is missing."); } var subMeshMaps = cm.ParentMeshIndices.Enumerate().Where(s => s.Value == meshNameIndex).ToArray(); Debug.Assert(subMeshMaps.Length == 2, "There should be 2 sub mesh maps."); Debug.Assert(subMeshMaps[1].Index - subMeshMaps[0].Index == 1, "The first sub mesh map should contain one element."); var vertexStart1 = (int)cm.SubMeshes[subMeshMaps[0].Index].FirstVertex; var vertexCount1 = (int)cm.SubMeshes[subMeshMaps[0].Index].VertexCount; var vertexStart2 = (int)cm.SubMeshes[subMeshMaps[1].Index].FirstVertex; var vertexCount2 = (int)cm.SubMeshes[subMeshMaps[1].Index].VertexCount; return(vertexStart1, vertexCount1, vertexStart2, vertexCount2); } Vector3 GetEyeBonePosition(int vertexStart, int vertexCount) { var centerPos = Vector3.Zero; var leftMostPos = new Vector3(float.MinValue, 0, 0); var rightMostPos = new Vector3(float.MaxValue, 0, 0); int leftMostIndex = -1, rightMostIndex = -1; for (var i = vertexStart; i < vertexStart + vertexCount; ++i) { var pos = vertices[i].Position; centerPos += pos; if (pos.X > leftMostPos.X) { leftMostPos = pos; leftMostIndex = i; } if (pos.X < rightMostPos.X) { rightMostPos = pos; rightMostIndex = i; } } Debug.Assert(leftMostIndex >= 0, nameof(leftMostIndex) + " >= 0"); Debug.Assert(rightMostIndex >= 0, nameof(rightMostIndex) + " >= 0"); centerPos = centerPos / vertexCount; // "Eyeball". You got the idea? var leftMostNorm = vertices[leftMostIndex].Normal; var rightMostNorm = vertices[rightMostIndex].Normal; var k1 = leftMostNorm.Z / leftMostNorm.X; var k2 = rightMostNorm.Z / rightMostNorm.X; float x1 = leftMostPos.X, x2 = rightMostPos.X, z1 = leftMostPos.Z, z2 = rightMostPos.Z; var d1 = (z2 - k2 * x2 + k2 * x1 - z1) / (k1 - k2); var x = x1 + d1; var z = z1 + k1 * d1; return(new Vector3(x, centerPos.Y, z)); } Vector3 GetEyesBonePosition(int vertexStart1, int vertexCount1, int vertexStart2, int vertexCount2) { var result = new Vector3(); for (var i = vertexStart1; i < vertexStart1 + vertexCount1; ++i) { result += vertices[i].Position; } for (var i = vertexStart2; i < vertexStart2 + vertexCount2; ++i) { result += vertices[i].Position; } result = result / (vertexCount1 + vertexCount2); return(new Vector3(0, result.Y + 0.5f, -0.6f)); } var(vs1, vc1, vs2, vc2) = FindEyesVerticeRange(); PmxBone head; do { head = bones.Find(b => b.Name == "頭"); if (head == null) { throw new ArgumentException("Missing head bone."); } } while (false); var eyes = new PmxBone(); eyes.Name = "両目"; eyes.NameEnglish = "eyes"; eyes.Parent = head; eyes.ParentIndex = bones.IndexOf(head); eyes.CurrentPosition = eyes.InitialPosition = GetEyesBonePosition(vs1, vc1, vs2, vc2); eyes.SetFlag(BoneFlags.Visible | BoneFlags.Rotation | BoneFlags.ToBone); eyes.To_Bone = -1; bones.Add(eyes); PmxBone leftEye = new PmxBone(), rightEye = new PmxBone(); leftEye.Name = "左目"; leftEye.NameEnglish = "eye_L"; rightEye.Name = "右目"; rightEye.NameEnglish = "eye_R"; leftEye.Parent = head; leftEye.ParentIndex = bones.IndexOf(head); rightEye.Parent = head; rightEye.ParentIndex = bones.IndexOf(head); leftEye.SetFlag(BoneFlags.Visible | BoneFlags.Rotation | BoneFlags.ToBone | BoneFlags.AppendRotation); rightEye.SetFlag(BoneFlags.Visible | BoneFlags.Rotation | BoneFlags.ToBone | BoneFlags.AppendRotation); leftEye.To_Bone = -1; rightEye.To_Bone = -1; leftEye.AppendParent = eyes; rightEye.AppendParent = eyes; leftEye.AppendParentIndex = bones.IndexOf(eyes); rightEye.AppendParentIndex = bones.IndexOf(eyes); leftEye.AppendRatio = 1; rightEye.AppendRatio = 1; leftEye.CurrentPosition = leftEye.InitialPosition = GetEyeBonePosition(vs1, vc1); rightEye.CurrentPosition = rightEye.InitialPosition = GetEyeBonePosition(vs2, vc2); bones.Add(leftEye); bones.Add(rightEye); // Fix vertices { var leftEyeIndex = bones.IndexOf(leftEye); var rightEyeIndex = bones.IndexOf(rightEye); for (var i = vs1; i < vs1 + vc1; ++i) { var skin = vertices[i]; // Eyes are only affected by "KUBI/ATAMA" bone by default. So we only need to set one element's values. skin.BoneWeights[0].BoneIndex = leftEyeIndex; Debug.Assert(Math.Abs(skin.BoneWeights[0].Weight - 1) < 0.000001f, "Total weight in the skin of left eye should be 1."); } for (var i = vs2; i < vs2 + vc2; ++i) { var skin = vertices[i]; // Eyes are only affected by "KUBI/ATAMA" bone by default. So we only need to set one element's values. skin.BoneWeights[0].BoneIndex = rightEyeIndex; Debug.Assert(Math.Abs(skin.BoneWeights[0].Weight - 1) < 0.000001f, "Total weight in the skin of right eye should be 1."); } } } // Finally, set the indices. The values will be used later. for (var i = 0; i < bones.Count; i++) { bones[i].BoneIndex = i; } return(bones.ToArray()); }