/// <summary> /// コピー元のアバターのBlendShapeBindingを基に、コピー先のアバターのBlendShapeBindingを生成します。 /// </summary> /// <param name="sourceBinding"></param> /// <param name="source"></param> /// <param name="destination"></param> /// <returns></returns> private static BlendShapeBinding CopyBlendShapeBinding( BlendShapeBinding binding, GameObject source, GameObject destination ) { var sourceMesh = CopyVRMBlendShapes.GetMesh(binding, source); if (!sourceMesh) { return(binding); } var shapeKeyName = sourceMesh.GetBlendShapeName(binding.Index); var destinationMesh = CopyVRMBlendShapes.GetMesh(binding.RelativePath, destination); if (destinationMesh) { var index = destinationMesh.GetBlendShapeIndex(shapeKeyName); if (index != -1) { binding.Index = index; return(binding); } } return(CopyVRMBlendShapes.FindShapeKey(binding, shapeKeyName, destination)); }
/// <summary> /// BlendShapeBindingに対応するメッシュを返します。 /// </summary> /// <param name="binding"></param> /// <param name="avatar"></param> /// <returns></returns> private static Mesh GetMesh(BlendShapeBinding binding, GameObject avatar) { var mesh = CopyVRMBlendShapes.GetMesh(binding.RelativePath, avatar); if (!mesh || binding.Index > mesh.blendShapeCount) { return(null); } return(mesh); }
/// <summary> /// 指定されたシェイプキー名を持つメッシュを探し、見つからなければ後方一致するものを探し、BlendShapeBindingを書き替えて返します。 /// </summary> /// <param name="binding"></param> /// <param name="shapeKeyName"></param> /// <param name="avatar"></param> /// <returns>見つからなかった場合は <c>binding</c> をそのまま返します。</returns> private static BlendShapeBinding FindShapeKey(BlendShapeBinding binding, string shapeKeyName, GameObject avatar) { var renderers = avatar.GetComponentsInChildren <SkinnedMeshRenderer>(); foreach (var renderer in renderers) { Mesh mesh = renderer.sharedMesh; if (!mesh) { continue; } var index = mesh.GetBlendShapeIndex(shapeKeyName); if (index == -1) { continue; } binding.RelativePath = renderer.transform.RelativePathFrom(avatar.transform); binding.Index = index; return(binding); } foreach (var renderer in renderers) { var mesh = renderer.sharedMesh; if (!mesh) { continue; } for (var i = 0; i < mesh.blendShapeCount; i++) { var name = mesh.GetBlendShapeName(i); if (!name.EndsWith(shapeKeyName) && !shapeKeyName.EndsWith(name)) { continue; } binding.RelativePath = renderer.transform.RelativePathFrom(avatar.transform); binding.Index = i; return(binding); } } return(binding); }
/// <summary> /// 表示中のUIから表情プリセットを設定 /// </summary> void SetClip(int index) { var clips = Proxy.BlendShapeAvatar.Clips; var clip = clips[index]; var values = new List <BlendShapeBinding>(); foreach (Transform child in Content.transform) { // IsBinaryは最初の一回だけ(気持ち悪いけど我慢する) var toggle = child.transform.Find("Toggle"); if (toggle != null) { clip.IsBinary = toggle.GetComponent <Toggle>().isOn; } // ブレンドシェイプ var text = child.transform.Find("Text"); var slider = child.transform.Find("Slider"); var input = child.transform.Find("InputField"); if (text != null && slider != null && input != null) { var value = slider.GetComponent <Slider>().value; if (value != 0) { // UIに0でない値が設定されていた場合だけ表情プリセットに追加 var s2i = slider.GetComponent <SliderToInput>(); var shape = new BlendShapeBinding(); shape.RelativePath = s2i.Mesh.transform.RelativePathFrom(Proxy.transform); shape.Index = s2i.Index; shape.Weight = value; values.Add(shape); } } } clip.Values = values.ToArray(); Proxy.BlendShapeAvatar.Clips[index] = clip; }
public Mesh BuildMesh(List <BlendShapeClip> blendShapeClips) { var blendShapeFrames = GetBlendShapeFrames(_srcMesh.blendShapeCount); var newMesh = Object.Instantiate(_srcMesh); newMesh.name = newMesh.name.Substring(0, newMesh.name.IndexOf('(')); newMesh.ClearBlendShapes(); var clipIndex = 0; foreach (var blendShapeClip in blendShapeClips) { // フォームが空の場合は飛ばす if (blendShapeClip == null || blendShapeClip.Values == null) { continue; } var vCount = _srcMesh.vertexCount; var vertices = new Vector3[vCount]; var normals = new Vector3[vCount]; var tangents = new Vector3[vCount]; var blendRatio = new List <BlendRatio>(); var baseBindings = new List <BlendShapeBinding>(); var relativePath = ""; foreach (var value in blendShapeClip.Values) { if (Path.GetFileName(value.RelativePath) != _smr.name) { baseBindings.Add(value); continue; } relativePath = value.RelativePath; blendRatio.Add(new BlendRatio() { Index = value.Index, Weight = value.Weight / 100 }); } // 関係するシェイプが無い場合は飛ばす if (blendRatio.Count == 0) { continue; } for (int i = 0; i < vCount; i++) { var v = Vector3.zero; var n = Vector3.zero; var t = Vector3.zero; foreach (var mix in blendRatio) { v += blendShapeFrames[mix.Index].vertices[i] * mix.Weight; n += blendShapeFrames[mix.Index].normals[i] * mix.Weight; t += blendShapeFrames[mix.Index].tangents[i] * mix.Weight; } vertices[i] = v; normals[i] = n.normalized; tangents[i] = t.normalized; } newMesh.AddBlendShapeFrame(blendShapeClip.BlendShapeName, 1.0f, vertices, normals, tangents); var mergedBinding = new BlendShapeBinding { RelativePath = relativePath, Index = clipIndex, Weight = 100.0f }; baseBindings.Add(mergedBinding); blendShapeClip.Values = baseBindings.ToArray(); clipIndex++; } return(newMesh); }
/// <summary> /// 表情プリセットにcsvから設定を読み込み /// </summary> public void Load(string path) { if (Vrm.VRM == null) { return; } // csv読み込み var list = new List <List <string> >(); var reader = new StreamReader(path); while (reader.Peek() >= 0) { list.Add(new List <string>()); string[] cols = reader.ReadLine().Split(','); for (int n = 0; n < cols.Length; n++) { list[list.Count - 1].Add(cols[n]); } } reader.Close(); // ブレンドシェイプの一致性確認 var index = 2; var meshes = Vrm.VRM.GetComponentsInChildren <SkinnedMeshRenderer>(); foreach (SkinnedMeshRenderer mesh in meshes) { var meshPath = mesh.transform.RelativePathFrom(Vrm.VRM.transform); for (int i = 0; i < mesh.sharedMesh.blendShapeCount; i++) { var meshName = mesh.sharedMesh.GetBlendShapeName(i); if (list[index][CSV_COL_PATH] != meshPath || list[index][CSV_COL_INDEX] != i.ToString() || list[index][CSV_COL_NAME] != meshName) { Text.text = "メッシュのパス\nブレンドシェイプ名\nが不一致"; CsvErrorCheckPlane.SetActive(true); return; } index++; } } if (list.Count != index) { Text.text = "ブレンドシェイプ数が不一致\n"; Text.text += "CSV = " + (list.Count - 2) + "\n"; Text.text += "VRM = " + (index - 2) + "\n"; CsvErrorCheckPlane.SetActive(true); return; } // 表情プリセット設定 var proxy = Vrm.VRM.GetComponent <VRMBlendShapeProxy>(); proxy.BlendShapeAvatar.Clips.Clear(); for (int col = CSV_COL_WEIGHT; col < list[CSV_ROW_LABEL].Count; col++) { var clip = ScriptableObject.CreateInstance <BlendShapeClip>(); clip.BlendShapeName = list[CSV_ROW_LABEL][col]; clip.IsBinary = bool.Parse(list[CSV_ROW_ISBINARY][col]); var values = new List <BlendShapeBinding>(); for (int row = CSV_ROW_WEIGHT; row < list.Count; row++) { if (list[row][col] != "0") { // 0でない値が設定されていた場合だけ表情プリセットに追加 var shape = new BlendShapeBinding(); shape.RelativePath = list[row][CSV_COL_PATH]; shape.Index = int.Parse(list[row][CSV_COL_INDEX]); shape.Weight = float.Parse(list[row][col]); values.Add(shape); } } clip.Values = values.ToArray(); proxy.BlendShapeAvatar.Clips.Add(clip); } BlendShape.Get(); }
/// <summary> /// 表情プリセットにcsvから設定を読み込み /// </summary> public void Load(string path) { if (Vrm.VRM == null) { return; } // csv読み込み var list = new List <List <string> >(); var reader = new StreamReader(path); while (reader.Peek() >= 0) { list.Add(new List <string>()); string[] cols = reader.ReadLine().Split(','); for (int n = 0; n < cols.Length; n++) { list[list.Count - 1].Add(cols[n]); } } reader.Close(); var index = CSV_ROW_WEIGHT; var meshes = Vrm.VRM.GetComponentsInChildren <SkinnedMeshRenderer>(); foreach (SkinnedMeshRenderer mesh in meshes) { var meshPath = mesh.transform.RelativePathFrom(Vrm.VRM.transform); for (int i = 0; i < mesh.sharedMesh.blendShapeCount; i++) { var meshName = mesh.sharedMesh.GetBlendShapeName(i); if (list[index][CSV_COL_PATH] != meshPath || list[index][CSV_COL_INDEX] != i.ToString() || list[index][CSV_COL_NAME] != meshName) { // オブジェクトのパス、インデックス、ブレンドシェイプ名が一致しなければ中断 return; } index++; } } var proxy = Vrm.VRM.GetComponent <VRMBlendShapeProxy>(); var clips = proxy.BlendShapeAvatar.Clips; if (clips.Count != list[CSV_ROW_LABEL].Count - CSV_COL_WEIGHT) { // 表情プリセットの数が一致しなければ中断 return; } // 表情プリセット設定 for (int col = CSV_COL_WEIGHT; col < list[CSV_ROW_LABEL].Count; col++) { var clip = clips[col - CSV_COL_WEIGHT]; clip.IsBinary = bool.Parse(list[CSV_ROW_ISBINARY][col]); var values = new List <BlendShapeBinding>(); for (int row = CSV_ROW_WEIGHT; row < list.Count; row++) { if (list[row][col] != "0") { // 0でない値が設定されていた場合だけ表情プリセットに追加 var shape = new BlendShapeBinding(); shape.RelativePath = list[row][CSV_COL_PATH]; shape.Index = int.Parse(list[row][CSV_COL_INDEX]); shape.Weight = float.Parse(list[row][col]); values.Add(shape); } } clip.Values = values.ToArray(); proxy.BlendShapeAvatar.Clips[col - CSV_COL_WEIGHT] = clip; } BlendShape.Get(); }