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)); }
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); }
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); } } } }
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); }
private void TranslateBonePositionsFromGlobalToLocal(WowObject wowObject) { var rootBone = wowObject.GetRootBone(); TranslateBoneChildrenPositionsFromGlobalToLocal(rootBone); }
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); }