Пример #1
0
        private void AddAdditionalMeshToCharacterObject(WowObject characterObject, WhModel whModel)
        {
            var itemObject = new WowObject()
            {
                Parent         = characterObject,
                GlobalPosition = new Vec3(0, 0, 0),
            };

            // На этот раз добавляем не в MainMesh а делаем дополнительный меш, таким образом этот меш будет исползовать те же кости что и основной
            characterObject.Meshes.Add(MakeMeshFromWhModel(whModel));
        }
Пример #2
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);
        }
Пример #3
0
        private static void MakeUnityCompatibleHumanoidSkeleton(WowObject wowObject)
        {
            // Удаляем все кости кроме тех что заданы в маппинге, либо если приатачен объект к кости
            var normalMappedBoneNames = _normalToHumanoidBoneNamesMapping.Keys.ToArray();

            wowObject.RemoveBones(
                boneToRemove =>
            {
                // не удаляем кость, если она есть в маппине для костей гуманоида
                if (Array.IndexOf(normalMappedBoneNames, boneToRemove.GetName()) >= 0)
                {
                    return(false);
                }

                // не удаляем кость, если для нее есть прикрепленные объекты
                if (boneToRemove.AttachedWowObjects.Count > 0)
                {
                    return(false);
                }

                // иначе удаляем ксоть
                return(true);
            }
                );

            // Оставшиеся кости переименовываем в названия, понятные юнити (для создания humanoid аватара)

            foreach (var bone in wowObject.Bones)
            {
                if (bone != null)
                {
                    var boneName = bone.GetName();

                    if (boneName != null && _normalToHumanoidBoneNamesMapping.TryGetValue(boneName, out var boneHumanoidName))
                    {
                        bone.SetName(boneHumanoidName);
                    }
                }
            }
        }
Пример #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);
        }
Пример #5
0
        private void TranslateBonePositionsFromGlobalToLocal(WowObject wowObject)
        {
            var rootBone = wowObject.GetRootBone();

            TranslateBoneChildrenPositionsFromGlobalToLocal(rootBone);
        }
Пример #6
0
        public WowObject BuildFromCharacterWhModel(WhModel whCharacterModel)
        {
            // ToDo: Смотреть как работает, Wow.Model.draw,
            // по сути в зависимости от алгоритма отрисовки идет и алгоритм построения объектов для дальнейшей отрисовки (то что тут происодит)

            // Сам перс

            var characterObject = new WowObject();

            // Добавляем MainMesh
            characterObject.Meshes.Add(MakeMeshFromWhModel(whCharacterModel));

            characterObject.Bones = MakeBoneHierarchyFromWhModel(whCharacterModel);
            characterObject.OptimizeBones();
            TranslateBonePositionsFromGlobalToLocal(characterObject);

            // Рога

            if (whCharacterModel.HornsModel != null)
            {
                AddAdditionalMeshToCharacterObject(characterObject, whCharacterModel.HornsModel);
            }

            // Маунт

            if (whCharacterModel.Mount != null)
            {
                // ToDo: хз че тут  делать, создать его вообще отдельно?

                //var mountObject = new WowObject()
                //{
                //    Parent = characterObject
                //};

                //// MainMesh (не факт что я вообще тут правильно делаю, т.к. еще не тестил)
                //mountObject.Meshes.Add(MakeMeshFromWhModel(whCharacterModel.Mount));

                //characterObject.Children.Add(mountObject);
            }

            // Итемы

            foreach (var whItemInSlot in whCharacterModel.Items)
            {
                var whItem = whItemInSlot.Value;

                if (whItem?.Models == null)
                {
                    continue;
                }

                foreach (var whItemModel in whItem.Models)
                {
                    if (whItemModel?.Model == null)
                    {
                        continue;
                    }

                    if (whItemModel.Bone > -1 && whItemModel.Bone < whCharacterModel.Bones.Length)
                    {
                        var itemPosition = Vec3.Scale(Vec3.ConvertPositionFromWh(whItemModel.Attachment.Position), _scale);

                        var itemObject = new WowObject()
                        {
                            Parent         = characterObject,
                            GlobalPosition = itemPosition,
                        };

                        // MainMesh - по сути он использоваться будет тлолько в AttachedWowObjects кости
                        itemObject.Meshes.Add(MakeMeshFromWhModel(whItemModel.Model));

                        // Если у итема есть кость, к которой можно прикрепиться
                        if (itemObject.Parent != null && whItemModel.Attachment.Bone >= 0)
                        {
                            var parentAttachmentBone   = itemObject.Parent.Bones[whItemModel.Attachment.Bone];
                            var whParentAttachmentBone = whItemModel.Model.Parent.Bones[whItemModel.Attachment.Bone];

                            // записываем объект этого итема в кость, к которой крепимся(у родительского объекта)
                            parentAttachmentBone.AttachedWowObjects.Add(itemObject);

                            // меняем данные о костях в вершинах так итема так, чтобы при привязке к скелету родителя этот итем двигался вместе с костью
                            // (при этом в иерархии объектов он не будет в ноде кости, а будет на том же уровне что и родительский объект, к скелету которого мы привязываем итем)

                            itemObject.MainMesh.ApplyTransform(
                                whParentAttachmentBone.LastUpdatedTranslation,
                                whParentAttachmentBone.LastUpdatedRotation,
                                whParentAttachmentBone.LastUpdatedScale);

                            var eachVertexItemBoneIndexes = new ByteVec4((byte)parentAttachmentBone.Index, 0, 0, 0);
                            var eachVertexItemBoneWeights = new Vec4(1, 0, 0, 0);

                            foreach (var vertex in itemObject.MainMesh.Vertices)
                            {
                                vertex.BoneIndexes = eachVertexItemBoneIndexes;
                                vertex.BoneWeights = eachVertexItemBoneWeights;
                            }
                        }

                        if (whItem.Visual?.Models != null && whItemModel.Model.Loaded)
                        {
                            foreach (var visual in whItem.Visual.Models)
                            {
                                if (visual != null)
                                {
                                    var visualPosition = Vec3.Scale(Vec3.ConvertPositionFromWh(visual.Attachment.Position), _scale);

                                    var visualObject = new WowObject()
                                    {
                                        Parent         = characterObject,
                                        GlobalPosition = visualPosition
                                    };

                                    // MainMesh (не факт что я вообще тут правильно делаю, т.к. еще не тестил)
                                    visualObject.Meshes.Add(MakeMeshFromWhModel(visual.Model));
                                }
                            }
                        }
                    }
                    else if (whItemModel.Bone == -1)
                    {
                        AddAdditionalMeshToCharacterObject(characterObject, whItemModel.Model);
                    }
                }
            }

            foreach (var collection in whCharacterModel.CollectionModels.Values)
            {
                if (collection == null)
                {
                    continue;
                }

                AddAdditionalMeshToCharacterObject(characterObject, collection);
            }

            return(characterObject);
        }