Exemplo n.º 1
0
        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());
        }