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); } }
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); } } } } }
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); }
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); }