Beispiel #1
0
 private static void EnsureBoneExistsOrAddDummy(WowObject wowObject, string boneName, string parentBoneName, Vec3 localPosition)
 {
     if (wowObject.FindBoneByName(boneName) == null)
     {
         wowObject.AddDummyBone(boneName, wowObject.FindBoneByName(parentBoneName) ?? wowObject.FindBoneByName("Hips"), localPosition);
     }
 }
Beispiel #2
0
        private static void RotateShoulderAttachments(WowObject characterWowObject, string attachmentBoneName, string upperArmBoneName, float rotationAngle)
        {
            var shoulderAttachment = characterWowObject.FindBoneByName(attachmentBoneName);
            var upperArm           = characterWowObject.FindBoneByName(upperArmBoneName);

            if (shoulderAttachment != null && upperArm != null)
            {
                var change = new WowVrcFileData.BlendshapeData.BoneData[] { new WowVrcFileData.BlendshapeData.BoneData()
                                                                            {
                                                                                LocalTransform = new WowTransform()
                                                                                {
                                                                                    position = new Vec3(),
                                                                                    rotation = Quat.RotateY(Quat.Create(), rotationAngle),
                                                                                    scale    = new Vec3(1f, 1f, 1f)
                                                                                },
                                                                                Name = upperArmBoneName
                                                                            } };

                foreach (var obj in shoulderAttachment.AttachedWowObjects)
                {
                    foreach (var mesh in obj.Meshes)
                    {
                        var basicBakedBlendshape = BlendShapeUtility.BakeBlendShape(obj.GlobalPosition, mesh.Vertices, characterWowObject.Bones, change, 1f);

                        foreach (var basicBakedBlendshapeElement in basicBakedBlendshape)
                        {
                            mesh.Vertices[basicBakedBlendshapeElement.Key].Position = new Vec3(basicBakedBlendshapeElement.Value.Position.X, basicBakedBlendshapeElement.Value.Position.Y, basicBakedBlendshapeElement.Value.Position.Z);
                            mesh.Vertices[basicBakedBlendshapeElement.Key].Normal   = new Vec3(basicBakedBlendshapeElement.Value.Normal.X, basicBakedBlendshapeElement.Value.Normal.Y, basicBakedBlendshapeElement.Value.Normal.Z);
                        }
                    }
                }
            }
        }
Beispiel #3
0
        private static string CheckRequiredBonesExist(WowObject wowObject)
        {
            string warnings = "";

            var requiredBoneNames = _normalToHumanoidBoneNamesMapping.Values.ToArray();

            foreach (var boneName in requiredBoneNames)
            {
                if (wowObject.FindBoneByName(boneName) == null)
                {
                    warnings += $"Bone \"{boneName}\" was not found\n";
                }
            }

            if (warnings == "")
            {
                warnings = null;
            }

            return(warnings);
        }
Beispiel #4
0
        public static string PrepareObject(WowObject wowObject, List <BlendShapeUtility.BakedBlendshape> bakedBlendshapes, float scale,
                                           bool removeToes, bool removeJaw, bool addDummyEyesAndEyelidVisemes, bool addDummyFingers, bool fixBlendshapes, bool fixShoulders)
        {
            var warnings = "";

            MakeUnityCompatibleHumanoidSkeleton(wowObject);

            // Если не заданы необходимые для движения глаз виземы моргания, создаем пустые (чтобы глаза двигались, т.к. врчат не делает движение глаз при отсутствии этих визем)
            if (addDummyEyesAndEyelidVisemes)
            {
                // Если костей глаз не найдено - добавляем фейковые внутрь кости головы

                EnsureBoneExistsOrAddDummy(wowObject, "LeftEye", "Head", new Vec3(-0.05f * scale, 0.1f * scale, 0.07f * scale));
                EnsureBoneExistsOrAddDummy(wowObject, "RightEye", "Head", new Vec3(0.05f * scale, 0.1f * scale, 0.07f * scale));

                // добавляем в блендшейп одну вершину. Если в блендшейпе вообще не будет изменений - eye tracking все равно не будет работать, как будто блендшейпа не существует
                var pos    = wowObject.MainMesh.Vertices[0].Position;
                var normal = wowObject.MainMesh.Vertices[0].Normal;

                if (bakedBlendshapes.FindIndex(x => x.BlendshapeName == "vrc.blink_left") == -1)
                {
                    bakedBlendshapes.Add(new BlendShapeUtility.BakedBlendshape {
                        BlendshapeName = "vrc.blink_left", Changes = new Dictionary <int, BlendShapeUtility.Vertex>()
                        {
                            { 0, new BlendShapeUtility.Vertex()
                              {
                                  Position = pos, Normal = normal
                              } }
                        }
                    });
                }
                if (bakedBlendshapes.FindIndex(x => x.BlendshapeName == "vrc.blink_right") == -1)
                {
                    bakedBlendshapes.Add(new BlendShapeUtility.BakedBlendshape {
                        BlendshapeName = "vrc.blink_right", Changes = new Dictionary <int, BlendShapeUtility.Vertex>()
                        {
                            { 0, new BlendShapeUtility.Vertex()
                              {
                                  Position = pos, Normal = normal
                              } }
                        }
                    });
                }
                if (bakedBlendshapes.FindIndex(x => x.BlendshapeName == "vrc.lowerlid_left") == -1)
                {
                    bakedBlendshapes.Add(new BlendShapeUtility.BakedBlendshape {
                        BlendshapeName = "vrc.lowerlid_left", Changes = new Dictionary <int, BlendShapeUtility.Vertex>()
                        {
                            { 0, new BlendShapeUtility.Vertex()
                              {
                                  Position = pos, Normal = normal
                              } }
                        }
                    });
                }
                if (bakedBlendshapes.FindIndex(x => x.BlendshapeName == "vrc.lowerlid_right") == -1)
                {
                    bakedBlendshapes.Add(new BlendShapeUtility.BakedBlendshape {
                        BlendshapeName = "vrc.lowerlid_right", Changes = new Dictionary <int, BlendShapeUtility.Vertex>()
                        {
                            { 0, new BlendShapeUtility.Vertex()
                              {
                                  Position = pos, Normal = normal
                              } }
                        }
                    });
                }
            }

            if (addDummyFingers)
            {
                // ToDo: не уверен что это нужно, вроде и с 3мя пальцами работает

                // Добавляем кости пальцев, чтобы юнити не ругалась (у тауренов например 3 пальца)

                EnsureBoneExistsOrAddDummy(wowObject, "Left Ring Proximal", "LeftHand", wowObject.FindBoneByName("Left Index Proximal").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Left Ring Intermediate", "Left Ring Proximal", wowObject.FindBoneByName("Left Index Intermediate").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Left Little Proximal", "LeftHand", wowObject.FindBoneByName("Left Index Proximal").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Left Little Intermediate", "Left Little Proximal", wowObject.FindBoneByName("Left Index Intermediate").LocalPosition);

                EnsureBoneExistsOrAddDummy(wowObject, "Right Ring Proximal", "RightHand", wowObject.FindBoneByName("Right Index Proximal").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Right Ring Intermediate", "Right Ring Proximal", wowObject.FindBoneByName("Right Index Intermediate").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Right Little Proximal", "RightHand", wowObject.FindBoneByName("Right Index Proximal").LocalPosition);
                EnsureBoneExistsOrAddDummy(wowObject, "Right Little Intermediate", "Right Little Proximal", wowObject.FindBoneByName("Right Index Intermediate").LocalPosition);
            }

            var noBonesWarnings = CheckRequiredBonesExist(wowObject);

            if (noBonesWarnings != null)
            {
                warnings += noBonesWarnings;
            }

            // Удаляем пальцы ног если надо (чтоб не глючило в вр режиме в врчате)
            if (removeToes)
            {
                wowObject.RemoveBonesByNames(new[] { "LeftToes", "RightToes" });
            }

            // Удаляем нижнюю челюсть, если надо (чтобы vrchat не делал автоматическую анимацию речи через эту кость - оно работает глючно)
            if (removeJaw)
            {
                wowObject.RemoveBonesByNames(new[] { "Jaw" });
            }

            if (fixShoulders)
            {
                const float shoulderRotationAngle = (float)(Math.PI / 4);
                RotateShoulderAttachments(wowObject, "attachment_shoulder.L", "LeftUpperArm", -shoulderRotationAngle);
                RotateShoulderAttachments(wowObject, "attachment_shoulder.R", "RightUpperArm", shoulderRotationAngle);
            }

            wowObject.OptimizeBones();

            // Добавляем небольшое смещение к каждой из позиций вершин, чтобы избавиться от каких-то глюков (repair_shapekeys и repair_shapekeys_mouth функции в cats плагине)
            // как минимум - если пустой blendshape например на vrc.lowerlid_left ничем не отличается от базы - eye tracking работать не будет. При этом очень мелкое значение тоже не влияет, нужно именно определенное
            if (fixBlendshapes)
            {
                var rand = new Random(34523634);

                foreach (var bakedBlendshape in bakedBlendshapes)
                {
                    if (bakedBlendshape.BlendshapeName.StartsWith("vrc."))
                    {
                        foreach (var vertex in bakedBlendshape.Changes)
                        {
                            var tinyChange = 0.00007f * (rand.NextDouble() < 0.5 ? -1f : 1f);

                            vertex.Value.Position = new Vec3(vertex.Value.Position.X + tinyChange, vertex.Value.Position.Y + tinyChange, vertex.Value.Position.Z + tinyChange);
                        }
                    }
                }
            }

            // Сортируем блендшейпы так чтобы _firstBlendshapes (список имен блендшейпов) шел сначала а потом уже все остальные
            bakedBlendshapes.Sort((x, y) =>
            {
                var xIndex = _firstBlendshapes.IndexOf(x.BlendshapeName);
                var yIndex = _firstBlendshapes.IndexOf(y.BlendshapeName);

                if (xIndex != -1 && yIndex == -1)
                {
                    return(-1);
                }

                if (xIndex == -1 && yIndex != -1)
                {
                    return(1);
                }

                if (xIndex != -1 && yIndex != -1)
                {
                    return(xIndex.CompareTo(yIndex));
                }

                return(x.BlendshapeName.CompareTo(y.BlendshapeName));
            });

            if (warnings == "")
            {
                warnings = null;
            }

            return(warnings);
        }