using System.Collections.Generic; using UnityEngine; using System.IO; using System; using System.Linq; using Gallop; //初期ポーズ(T,Aポーズ)の時点でアタッチ、有効化されている必要がある public class UnityHumanoidVMDRecorder : MonoBehaviour { public bool UseParentOfAll = true; public bool UseCenterAsParentOfAll = true; /// /// 全ての親の座標・回転を絶対座標系で計算する /// UseParentOfAllがTrueでないと意味がない /// public bool UseAbsoluteCoordinateSystem = false; public bool IgnoreInitialPosition = false; public bool IgnoreInitialRotation = false; /// /// 一部のモデルではMMD上ではセンターが足元にある /// Start前に設定されている必要がある /// public bool UseBottomCenter = false; /// /// Unity上のモーフ名に1.まばたきなど番号が振られている場合、番号を除去する /// public bool TrimMorphNumber = false; public int KeyReductionLevel = 2; public bool IsRecording { get; private set; } = false; public int FrameNumber { get; private set; } = 0; int frameNumberSaved = 0; const float FPSs = 0.03333f; const string CenterNameString = "センター"; const string GrooveNameString = "グルーブ"; public enum BoneNames { 全ての親, センター, 左足IK, 右足IK, 上半身, 上半身2, 首, 頭, 左肩, 左腕, 左ひじ, 左手首, 右肩, 右腕, 右ひじ, 右手首, 左親指1, 左親指2, 左人指1, 左人指2, 左人指3, 左中指1, 左中指2, 左中指3, 左薬指1, 左薬指2, 左薬指3, 左小指1, 左小指2, 左小指3, 右親指1, 右親指2, 右人指1, 右人指2, 右人指3, 右中指1, 右中指2, 右中指3, 右薬指1, 右薬指2, 右薬指3, 右小指1, 右小指2, 右小指3, 左足, 右足, 左ひざ, 右ひざ, 左足首, 右足首, None //左つま先, 右つま先は情報付けると足首の回転、位置との矛盾が生じかねない } //コンストラクタにて初期化 //全てのボーンを名前で引く辞書 Dictionary transformDictionary = new Dictionary(); public Dictionary BoneDictionary { get; private set; } Vector3 parentInitialPosition = Vector3.zero; Quaternion parentInitialRotation = Quaternion.identity; Dictionary> positionDictionary = new Dictionary>(); Dictionary> positionDictionarySaved = new Dictionary>(); Dictionary> rotationDictionary = new Dictionary>(); Dictionary> rotationDictionarySaved = new Dictionary>(); //ボーン移動量の補正係数 //この値は大体の値、正確ではない const float DefaultBoneAmplifier = 12.5f; public Vector3 ParentOfAllOffset = new Vector3(0, 0, 0); public Vector3 LeftFootIKOffset = Vector3.zero; public Vector3 RightFootIKOffset = Vector3.zero; BoneGhost boneGhost; public MorphRecorder morphRecorder; public MorphRecorder morphRecorderSaved; float aposeDegress = 38.5f; public void Initialize() { Time.fixedDeltaTime = FPSs; List objs = GetComponentsInChildren().ToList(); BoneDictionary = new Dictionary() { //下半身などというものはUnityにはない { BoneNames.全ての親, transform}, { BoneNames.センター, objs.Find(a=>a.name.Equals("Hip"))}, { BoneNames.上半身, objs.Find(a=>a.name.Equals("Spine"))}, { BoneNames.上半身2, objs.Find(a=>a.name.Equals("Chest"))}, { BoneNames.頭, objs.Find(a=>a.name.Equals("Head"))}, { BoneNames.首, objs.Find(a=>a.name.Equals("Neck"))}, { BoneNames.左肩, objs.Find(a=>a.name.Equals("Shoulder_L"))}, { BoneNames.右肩, objs.Find(a=>a.name.Equals("Shoulder_R"))}, { BoneNames.左腕, objs.Find(a=>a.name.Equals("Arm_L"))}, { BoneNames.右腕, objs.Find(a=>a.name.Equals("Arm_R"))}, { BoneNames.左ひじ, objs.Find(a=>a.name.Equals("Elbow_L"))}, { BoneNames.右ひじ, objs.Find(a=>a.name.Equals("Elbow_R"))}, { BoneNames.左手首, objs.Find(a=>a.name.Equals("Wrist_L"))}, { BoneNames.右手首, objs.Find(a=>a.name.Equals("Wrist_R"))}, { BoneNames.左親指1, objs.Find(a=>a.name.Equals("Thumb_02_L"))}, { BoneNames.右親指1, objs.Find(a=>a.name.Equals("Thumb_02_R"))}, { BoneNames.左親指2, objs.Find(a=>a.name.Equals("Thumb_03_L"))}, { BoneNames.右親指2, objs.Find(a=>a.name.Equals("Thumb_03_R"))}, { BoneNames.左人指1, objs.Find(a=>a.name.Equals("Index_01_L"))}, { BoneNames.右人指1, objs.Find(a=>a.name.Equals("Index_01_R"))}, { BoneNames.左人指2, objs.Find(a=>a.name.Equals("Index_02_L"))}, { BoneNames.右人指2, objs.Find(a=>a.name.Equals("Index_02_R"))}, { BoneNames.左人指3, objs.Find(a=>a.name.Equals("Index_03_L"))}, { BoneNames.右人指3, objs.Find(a=>a.name.Equals("Index_03_R"))}, { BoneNames.左中指1, objs.Find(a=>a.name.Equals("Middle_01_L"))}, { BoneNames.右中指1, objs.Find(a=>a.name.Equals("Middle_01_R"))}, { BoneNames.左中指2, objs.Find(a=>a.name.Equals("Middle_02_L"))}, { BoneNames.右中指2, objs.Find(a=>a.name.Equals("Middle_02_R"))}, { BoneNames.左中指3, objs.Find(a=>a.name.Equals("Middle_03_L"))}, { BoneNames.右中指3, objs.Find(a=>a.name.Equals("Middle_03_R"))}, { BoneNames.左薬指1, objs.Find(a=>a.name.Equals("Ring_01_L"))}, { BoneNames.右薬指1, objs.Find(a=>a.name.Equals("Ring_01_R"))}, { BoneNames.左薬指2, objs.Find(a=>a.name.Equals("Ring_02_L"))}, { BoneNames.右薬指2, objs.Find(a=>a.name.Equals("Ring_02_R"))}, { BoneNames.左薬指3, objs.Find(a=>a.name.Equals("Ring_03_L"))}, { BoneNames.右薬指3, objs.Find(a=>a.name.Equals("Ring_03_R"))}, { BoneNames.左小指1, objs.Find(a=>a.name.Equals("Pinky_01_L"))}, { BoneNames.右小指1, objs.Find(a=>a.name.Equals("Pinky_01_R"))}, { BoneNames.左小指2, objs.Find(a=>a.name.Equals("Pinky_02_L"))}, { BoneNames.右小指2, objs.Find(a=>a.name.Equals("Pinky_02_R"))}, { BoneNames.左小指3, objs.Find(a=>a.name.Equals("Pinky_03_L"))}, { BoneNames.右小指3, objs.Find(a=>a.name.Equals("Pinky_03_R"))}, { BoneNames.左足IK, objs.Find(a=>a.name.Equals("Ankle_L"))}, { BoneNames.右足IK, objs.Find(a=>a.name.Equals("Ankle_R"))}, { BoneNames.左足, objs.Find(a=>a.name.Equals("Thigh_L"))}, { BoneNames.右足, objs.Find(a=>a.name.Equals("Thigh_R"))}, { BoneNames.左ひざ, objs.Find(a=>a.name.Equals("Knee_L"))}, { BoneNames.右ひざ, objs.Find(a=>a.name.Equals("Knee_R"))}, { BoneNames.左足首, objs.Find(a=>a.name.Equals("Ankle_L"))}, { BoneNames.右足首, objs.Find(a=>a.name.Equals("Ankle_R"))}, //左つま先, 右つま先は情報付けると足首の回転、位置との矛盾が生じかねない //{ BoneNames.左つま先, (animator.GetBoneTransform(HumanBodyBones.LeftToes))}, //{ BoneNames.右つま先, (animator.GetBoneTransform(HumanBodyBones.RightToes))} }; foreach (KeyValuePair pair in BoneDictionary) { transformDictionary.Add(pair.Key.ToString(), pair.Value); } var animator = GetComponentInParent().UmaAnimator; var state = animator.GetCurrentAnimatorStateInfo(0); animator.Rebind(); BoneDictionary[BoneNames.左腕].Rotate(0, 0, -aposeDegress); BoneDictionary[BoneNames.右腕].Rotate(0, 0, aposeDegress); SetInitialPositionAndRotation(); foreach (BoneNames boneName in BoneDictionary.Keys) { if (BoneDictionary[boneName] == null) { continue; } positionDictionary.Add(boneName, new List()); rotationDictionary.Add(boneName, new List()); } if (BoneDictionary[BoneNames.左足IK] != null) { LeftFootIKOffset = Quaternion.Inverse(transform.rotation) * (BoneDictionary[BoneNames.左足IK].position - transform.position); } if (BoneDictionary[BoneNames.右足IK] != null) { RightFootIKOffset = Quaternion.Inverse(transform.rotation) * (BoneDictionary[BoneNames.右足IK].position - transform.position); } boneGhost = new BoneGhost(BoneDictionary, UseBottomCenter); morphRecorder = new MorphRecorder(transform); animator.Play(state.shortNameHash, 0, state.normalizedTime); } private void FixedUpdate() { if (IsRecording) { SaveFrame(); FrameNumber++; } } void SaveFrame() { if (boneGhost != null) { boneGhost.GhostAll(); } if (morphRecorder != null) { morphRecorder.RecrodAllMorph(); } foreach (BoneNames boneName in BoneDictionary.Keys) { if (BoneDictionary[boneName] == null) { continue; } if (boneName == BoneNames.右足IK || boneName == BoneNames.左足IK) { Vector3 targetVector = Vector3.zero; if (UseCenterAsParentOfAll) { if ((!UseAbsoluteCoordinateSystem && transform.parent != null) && IgnoreInitialPosition) { targetVector = Quaternion.Inverse(transform.parent.rotation) * (BoneDictionary[boneName].position - transform.parent.position) - parentInitialPosition; } else if ((!UseAbsoluteCoordinateSystem && transform.parent != null) && !IgnoreInitialPosition) { targetVector = Quaternion.Inverse(transform.parent.rotation) * (BoneDictionary[boneName].position - transform.parent.position); } else if ((UseAbsoluteCoordinateSystem || transform.parent == null) && IgnoreInitialPosition) { targetVector = BoneDictionary[boneName].position - parentInitialPosition; } else if ((UseAbsoluteCoordinateSystem || transform.parent == null) && transform.parent && !IgnoreInitialPosition) { targetVector = BoneDictionary[boneName].position; } } else { targetVector = BoneDictionary[boneName].position - transform.position; targetVector = Quaternion.Inverse(transform.rotation) * targetVector; } targetVector -= (boneName == BoneNames.左足IK ? LeftFootIKOffset : RightFootIKOffset); Vector3 ikPosition = new Vector3(-targetVector.x, targetVector.y, -targetVector.z); positionDictionary[boneName].Add(ikPosition * DefaultBoneAmplifier); //回転は全部足首に持たせる Quaternion ikRotation = Quaternion.identity; rotationDictionary[boneName].Add(ikRotation); continue; } if (boneGhost != null && boneGhost.GhostDictionary.Keys.Contains(boneName)) { if (boneGhost.GhostDictionary[boneName].ghost == null || !boneGhost.GhostDictionary[boneName].enabled) { rotationDictionary[boneName].Add(Quaternion.identity); positionDictionary[boneName].Add(Vector3.zero); continue; } Vector3 boneVector = boneGhost.GhostDictionary[boneName].ghost.localPosition; Quaternion boneQuatenion = boneGhost.GhostDictionary[boneName].ghost.localRotation; rotationDictionary[boneName].Add(new Quaternion(-boneQuatenion.x, boneQuatenion.y, -boneQuatenion.z, boneQuatenion.w)); boneVector -= boneGhost.GhostOriginalLocalPositionDictionary[boneName]; positionDictionary[boneName].Add(new Vector3(-boneVector.x, boneVector.y, -boneVector.z) * DefaultBoneAmplifier); continue; } Quaternion fixedQuatenion = Quaternion.identity; Quaternion vmdRotation = Quaternion.identity; if (boneName == BoneNames.全ての親 && UseAbsoluteCoordinateSystem) { fixedQuatenion = BoneDictionary[boneName].rotation; } else { fixedQuatenion = BoneDictionary[boneName].localRotation; } if (boneName == BoneNames.全ての親 && IgnoreInitialRotation) { fixedQuatenion = BoneDictionary[boneName].localRotation.MinusRotation(parentInitialRotation); } vmdRotation = new Quaternion(-fixedQuatenion.x, fixedQuatenion.y, -fixedQuatenion.z, fixedQuatenion.w); rotationDictionary[boneName].Add(vmdRotation); Vector3 fixedPosition = Vector3.zero; Vector3 vmdPosition = Vector3.zero; if (boneName == BoneNames.全ての親 && UseAbsoluteCoordinateSystem) { fixedPosition = BoneDictionary[boneName].position; } else { fixedPosition = BoneDictionary[boneName].localPosition; } if (boneName == BoneNames.全ての親 && IgnoreInitialPosition) { fixedPosition -= parentInitialPosition; } vmdPosition = new Vector3(-fixedPosition.x, fixedPosition.y, -fixedPosition.z); if (boneName == BoneNames.全ての親) { positionDictionary[boneName].Add(vmdPosition * DefaultBoneAmplifier + ParentOfAllOffset); } else { positionDictionary[boneName].Add(vmdPosition * DefaultBoneAmplifier); } } } void SetInitialPositionAndRotation() { if (UseAbsoluteCoordinateSystem) { parentInitialPosition = transform.position; parentInitialRotation = transform.rotation; } else { parentInitialPosition = transform.localPosition; parentInitialRotation = transform.localRotation; } } public static void SetFPS(int fps) { Time.fixedDeltaTime = 1 / (float)fps; } /// /// レコーディングを開始または再開 /// public void StartRecording() { SetInitialPositionAndRotation(); IsRecording = true; } /// /// レコーディングを一時停止 /// public void PauseRecording() { IsRecording = false; } /// /// レコーディングを終了 /// public void StopRecording() { IsRecording = false; frameNumberSaved = FrameNumber; morphRecorderSaved = morphRecorder; FrameNumber = 0; positionDictionarySaved = positionDictionary; positionDictionary = new Dictionary>(); rotationDictionarySaved = rotationDictionary; rotationDictionary = new Dictionary>(); foreach (BoneNames boneName in BoneDictionary.Keys) { if (BoneDictionary[boneName] == null) { continue; } positionDictionary.Add(boneName, new List()); rotationDictionary.Add(boneName, new List()); } morphRecorder = new MorphRecorder(transform); } /// /// VMDを作成する /// 呼び出す際は先にStopRecordingを呼び出すこと /// /// VMDファイルに記載される専用モデル名 /// 保存先の絶対ファイルパス public void SaveVMD(string modelName, string filePath) { if (IsRecording) { Debug.Log(transform.name + "VMD保存前にレコーディングをストップしてください。"); return; } if (KeyReductionLevel <= 0) { KeyReductionLevel = 1; } Debug.Log(transform.name + "VMDファイル作成開始"); //ファイルの書き込み using (FileStream fileStream = new FileStream(filePath, FileMode.Create)) using (BinaryWriter binaryWriter = new BinaryWriter(fileStream)) { try { const string ShiftJIS = "shift_jis"; const int intByteLength = 4; //ファイルタイプの書き込み const int fileTypeLength = 30; const string RightFileType = "Vocaloid Motion Data 0002"; byte[] fileTypeBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(RightFileType); binaryWriter.Write(fileTypeBytes, 0, fileTypeBytes.Length); binaryWriter.Write(new byte[fileTypeLength - fileTypeBytes.Length], 0, fileTypeLength - fileTypeBytes.Length); //モデル名の書き込み、Shift_JISで保存 const int modelNameLength = 20; byte[] modelNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(modelName); //モデル名が長すぎたとき modelNameBytes = modelNameBytes.Take(Mathf.Min(modelNameLength, modelNameBytes.Length)).ToArray(); binaryWriter.Write(modelNameBytes, 0, modelNameBytes.Length); binaryWriter.Write(new byte[modelNameLength - modelNameBytes.Length], 0, modelNameLength - modelNameBytes.Length); //全ボーンフレーム数の書き込み void LoopWithBoneCondition(Action action) { for (int i = 0; i < frameNumberSaved; i++) { if ((i % KeyReductionLevel) != 0) { continue; } foreach (BoneNames boneName in Enum.GetValues(typeof(BoneNames))) { if (!BoneDictionary.Keys.Contains(boneName)) { continue; } if (BoneDictionary[boneName] == null) { continue; } if (!UseParentOfAll && boneName == BoneNames.全ての親) { continue; } action(boneName, i); } } } uint allKeyFrameNumber = 0; LoopWithBoneCondition((a, b) => { allKeyFrameNumber++; }); byte[] allKeyFrameNumberByte = BitConverter.GetBytes(allKeyFrameNumber); binaryWriter.Write(allKeyFrameNumberByte, 0, intByteLength); //人ボーンの書き込み LoopWithBoneCondition((boneName, i) => { const int boneNameLength = 15; string boneNameString = boneName.ToString(); if (boneName == BoneNames.全ての親 && UseCenterAsParentOfAll) { boneNameString = CenterNameString; } if (boneName == BoneNames.センター && UseCenterAsParentOfAll) { boneNameString = GrooveNameString; } byte[] boneNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(boneNameString); binaryWriter.Write(boneNameBytes, 0, boneNameBytes.Length); binaryWriter.Write(new byte[boneNameLength - boneNameBytes.Length], 0, boneNameLength - boneNameBytes.Length); byte[] frameNumberByte = BitConverter.GetBytes((ulong)i); binaryWriter.Write(frameNumberByte, 0, intByteLength); Vector3 position = positionDictionarySaved[boneName][i]; byte[] positionX = BitConverter.GetBytes(position.x); binaryWriter.Write(positionX, 0, intByteLength); byte[] positionY = BitConverter.GetBytes(position.y); binaryWriter.Write(positionY, 0, intByteLength); byte[] positionZ = BitConverter.GetBytes(position.z); binaryWriter.Write(positionZ, 0, intByteLength); Quaternion rotation = rotationDictionarySaved[boneName][i]; byte[] rotationX = BitConverter.GetBytes(rotation.x); binaryWriter.Write(rotationX, 0, intByteLength); byte[] rotationY = BitConverter.GetBytes(rotation.y); binaryWriter.Write(rotationY, 0, intByteLength); byte[] rotationZ = BitConverter.GetBytes(rotation.z); binaryWriter.Write(rotationZ, 0, intByteLength); byte[] rotationW = BitConverter.GetBytes(rotation.w); binaryWriter.Write(rotationW, 0, intByteLength); byte[] interpolateBytes = new byte[64]; binaryWriter.Write(interpolateBytes, 0, 64); }); //全モーフフレーム数の書き込み morphRecorderSaved.DisableIntron(); if (TrimMorphNumber) { morphRecorderSaved.TrimMorphNumber(); } void LoopWithMorphCondition(Action action) { for (int i = 0; i < frameNumberSaved; i++) { foreach (string morphName in morphRecorderSaved.MorphDrivers.Keys) { if (morphRecorderSaved.MorphDrivers[morphName].ValueList.Count == 0) { continue; } if (i > morphRecorderSaved.MorphDrivers[morphName].ValueList.Count) { continue; } //変化のない部分は省く if (!morphRecorderSaved.MorphDrivers[morphName].ValueList[i].enabled) { continue; } const int boneNameLength = 15; string morphNameString = morphName.ToString(); byte[] morphNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(morphNameString); //名前が長過ぎた場合書き込まない if (boneNameLength - morphNameBytes.Length < 0) { continue; } action(morphName, i); } } } uint allMorphNumber = 0; LoopWithMorphCondition((a, b) => { allMorphNumber++; }); byte[] faceFrameCount = BitConverter.GetBytes(allMorphNumber); binaryWriter.Write(faceFrameCount, 0, intByteLength); //モーフの書き込み LoopWithMorphCondition((morphName, i) => { const int boneNameLength = 15; string morphNameString = morphName.ToString(); byte[] morphNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(morphNameString); binaryWriter.Write(morphNameBytes, 0, morphNameBytes.Length); binaryWriter.Write(new byte[boneNameLength - morphNameBytes.Length], 0, boneNameLength - morphNameBytes.Length); byte[] frameNumberByte = BitConverter.GetBytes((ulong)i); binaryWriter.Write(frameNumberByte, 0, intByteLength); byte[] valueByte = BitConverter.GetBytes(morphRecorderSaved.MorphDrivers[morphName].ValueList[i].value); binaryWriter.Write(valueByte, 0, intByteLength); }); //カメラの書き込み byte[] cameraFrameCount = BitConverter.GetBytes(0); binaryWriter.Write(cameraFrameCount, 0, intByteLength); //照明の書き込み byte[] lightFrameCount = BitConverter.GetBytes(0); binaryWriter.Write(lightFrameCount, 0, intByteLength); //セルフシャドウの書き込み byte[] selfShadowCount = BitConverter.GetBytes(0); binaryWriter.Write(selfShadowCount, 0, intByteLength); //IKの書き込み //0フレームにキーフレーム一つだけ置く byte[] ikCount = BitConverter.GetBytes(1); byte[] ikFrameNumber = BitConverter.GetBytes(0); byte modelDisplay = Convert.ToByte(1); //右足IKと左足IKと右足つま先IKと左足つま先IKの4つ byte[] ikNumber = BitConverter.GetBytes(4); const int IKNameLength = 20; byte[] leftIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("左足IK"); byte[] rightIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("右足IK"); byte[] leftToeIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("左つま先IK"); byte[] rightToeIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("右つま先IK"); byte ikOn = Convert.ToByte(1); byte ikOff = Convert.ToByte(0); binaryWriter.Write(ikCount, 0, intByteLength); binaryWriter.Write(ikFrameNumber, 0, intByteLength); binaryWriter.Write(modelDisplay); binaryWriter.Write(ikNumber, 0, intByteLength); binaryWriter.Write(leftIKName, 0, leftIKName.Length); binaryWriter.Write(new byte[IKNameLength - leftIKName.Length], 0, IKNameLength - leftIKName.Length); binaryWriter.Write(ikOff); binaryWriter.Write(leftToeIKName, 0, leftToeIKName.Length); binaryWriter.Write(new byte[IKNameLength - leftToeIKName.Length], 0, IKNameLength - leftToeIKName.Length); binaryWriter.Write(ikOff); binaryWriter.Write(rightIKName, 0, rightIKName.Length); binaryWriter.Write(new byte[IKNameLength - rightIKName.Length], 0, IKNameLength - rightIKName.Length); binaryWriter.Write(ikOff); binaryWriter.Write(rightToeIKName, 0, rightToeIKName.Length); binaryWriter.Write(new byte[IKNameLength - rightToeIKName.Length], 0, IKNameLength - rightToeIKName.Length); binaryWriter.Write(ikOff); } catch (Exception ex) { Debug.Log("VMD書き込みエラー" + ex.Message); } finally { binaryWriter.Close(); } } Destroy(this); } /// /// VMDを作成する /// 呼び出す際は先にStopRecordingを呼び出すこと /// /// VMDファイルに記載される専用モデル名 /// 保存先の絶対ファイルパス /// キーの書き込み頻度を減らして容量を減らす public void SaveVMD(string modelName, int keyReductionLevel = 3) { string fileName = Application.dataPath + "/../VMDRecords/" + string.Format("UMA_{0}.vmd", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")); Directory.CreateDirectory(Application.dataPath + "/../VMDRecords"); KeyReductionLevel = keyReductionLevel; SaveVMD(modelName, fileName); } //裏で正規化されたモデル //(初期ポーズで各ボーンのlocalRotationがQuaternion.identityのモデル)を疑似的にアニメーションさせる class BoneGhost { public Dictionary GhostDictionary { get; private set; } = new Dictionary(); public Dictionary GhostOriginalLocalPositionDictionary { get; private set; } = new Dictionary(); public Dictionary GhostOriginalRotationDictionary { get; private set; } = new Dictionary(); public Dictionary OriginalRotationDictionary { get; private set; } = new Dictionary(); public bool UseBottomCenter { get; private set; } = false; const string GhostSalt = "Ghost"; private Dictionary boneDictionary = new Dictionary(); float centerOffsetLength = 0; public BoneGhost(Dictionary boneDictionary, bool useBottomCenter) { this.boneDictionary = boneDictionary; UseBottomCenter = useBottomCenter; Dictionary boneParentDictionary = new Dictionary() { { BoneNames.センター, (BoneNames.None, BoneNames.None, BoneNames.全ての親) }, { BoneNames.左足, (BoneNames.None, BoneNames.None, BoneNames.センター) }, { BoneNames.左ひざ, (BoneNames.None, BoneNames.None, BoneNames.左足) }, { BoneNames.左足首, (BoneNames.None, BoneNames.None, BoneNames.左ひざ) }, { BoneNames.右足, (BoneNames.None, BoneNames.None, BoneNames.センター) }, { BoneNames.右ひざ, (BoneNames.None, BoneNames.None, BoneNames.右足) }, { BoneNames.右足首, (BoneNames.None, BoneNames.None, BoneNames.右ひざ) }, { BoneNames.上半身, (BoneNames.None, BoneNames.None, BoneNames.センター) }, { BoneNames.上半身2, (BoneNames.None, BoneNames.None, BoneNames.上半身) }, { BoneNames.首, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, { BoneNames.頭, (BoneNames.首, BoneNames.上半身2, BoneNames.上半身) }, { BoneNames.左肩, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, { BoneNames.左腕, (BoneNames.左肩, BoneNames.上半身2, BoneNames.上半身) }, { BoneNames.左ひじ, (BoneNames.None, BoneNames.None, BoneNames.左腕) }, { BoneNames.左手首, (BoneNames.None, BoneNames.None, BoneNames.左ひじ) }, { BoneNames.左親指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, { BoneNames.左親指2, (BoneNames.左親指1, BoneNames.None, BoneNames.None) }, { BoneNames.左人指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, { BoneNames.左人指2, (BoneNames.左人指1, BoneNames.None, BoneNames.None) }, { BoneNames.左人指3, (BoneNames.左人指2, BoneNames.None, BoneNames.None) }, { BoneNames.左中指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, { BoneNames.左中指2, (BoneNames.左中指1, BoneNames.None, BoneNames.None) }, { BoneNames.左中指3, (BoneNames.左中指2, BoneNames.None, BoneNames.None) }, { BoneNames.左薬指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, { BoneNames.左薬指2, (BoneNames.左薬指1, BoneNames.None, BoneNames.None) }, { BoneNames.左薬指3, (BoneNames.左薬指2, BoneNames.None, BoneNames.None) }, { BoneNames.左小指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, { BoneNames.左小指2, (BoneNames.左小指1, BoneNames.None, BoneNames.None) }, { BoneNames.左小指3, (BoneNames.左小指2, BoneNames.None, BoneNames.None) }, { BoneNames.右肩, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, { BoneNames.右腕, (BoneNames.右肩, BoneNames.上半身2, BoneNames.上半身) }, { BoneNames.右ひじ, (BoneNames.None, BoneNames.None, BoneNames.右腕) }, { BoneNames.右手首, (BoneNames.None, BoneNames.None, BoneNames.右ひじ) }, { BoneNames.右親指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, { BoneNames.右親指2, (BoneNames.右親指1, BoneNames.None, BoneNames.None) }, { BoneNames.右人指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, { BoneNames.右人指2, (BoneNames.右人指1, BoneNames.None, BoneNames.None) }, { BoneNames.右人指3, (BoneNames.右人指2, BoneNames.None, BoneNames.None) }, { BoneNames.右中指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, { BoneNames.右中指2, (BoneNames.右中指1, BoneNames.None, BoneNames.None) }, { BoneNames.右中指3, (BoneNames.右中指2, BoneNames.None, BoneNames.None) }, { BoneNames.右薬指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, { BoneNames.右薬指2, (BoneNames.右薬指1, BoneNames.None, BoneNames.None) }, { BoneNames.右薬指3, (BoneNames.右薬指2, BoneNames.None, BoneNames.None) }, { BoneNames.右小指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, { BoneNames.右小指2, (BoneNames.右小指1, BoneNames.None, BoneNames.None) }, { BoneNames.右小指3, (BoneNames.右小指2, BoneNames.None, BoneNames.None) }, }; //Ghostの生成 foreach (BoneNames boneName in boneDictionary.Keys) { if (boneName == BoneNames.全ての親 || boneName == BoneNames.左足IK || boneName == BoneNames.右足IK) { continue; } if (boneDictionary[boneName] == null) { GhostDictionary.Add(boneName, (null, false)); continue; } Transform ghost = new GameObject(boneDictionary[boneName].name + GhostSalt).transform; if (boneName == BoneNames.センター && UseBottomCenter) { ghost.position = boneDictionary[BoneNames.全ての親].position; } else { ghost.position = boneDictionary[boneName].position; } GhostDictionary.Add(boneName, (ghost, true)); } //Ghostの親子構造を設定 foreach (BoneNames boneName in boneDictionary.Keys) { if (boneName == BoneNames.全ての親 || boneName == BoneNames.左足IK || boneName == BoneNames.右足IK) { continue; } if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) { continue; } if (boneName == BoneNames.センター) { GhostDictionary[boneName].ghost.SetParent(boneDictionary[BoneNames.全ての親]); continue; } if (boneParentDictionary[boneName].optionParent1 != BoneNames.None && boneDictionary[boneParentDictionary[boneName].optionParent1] != null) { GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].optionParent1].ghost); } else if (boneParentDictionary[boneName].optionParent2 != BoneNames.None && boneDictionary[boneParentDictionary[boneName].optionParent2] != null) { GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].optionParent2].ghost); } else if (boneParentDictionary[boneName].necessaryParent != BoneNames.None && boneDictionary[boneParentDictionary[boneName].necessaryParent] != null) { GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].necessaryParent].ghost); } else { GhostDictionary[boneName] = (GhostDictionary[boneName].ghost, false); } } //初期状態を保存 foreach (BoneNames boneName in GhostDictionary.Keys) { if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) { GhostOriginalLocalPositionDictionary.Add(boneName, Vector3.zero); GhostOriginalRotationDictionary.Add(boneName, Quaternion.identity); OriginalRotationDictionary.Add(boneName, Quaternion.identity); } else { GhostOriginalRotationDictionary.Add(boneName, GhostDictionary[boneName].ghost.rotation); OriginalRotationDictionary.Add(boneName, boneDictionary[boneName].rotation); if (boneName == BoneNames.センター && UseBottomCenter) { GhostOriginalLocalPositionDictionary.Add(boneName, Vector3.zero); continue; } GhostOriginalLocalPositionDictionary.Add(boneName, GhostDictionary[boneName].ghost.localPosition); } } centerOffsetLength = Vector3.Distance(boneDictionary[BoneNames.全ての親].position, boneDictionary[BoneNames.センター].position); } public void GhostAll() { foreach (BoneNames boneName in GhostDictionary.Keys) { if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) { continue; } Quaternion transQuaternion = boneDictionary[boneName].rotation * Quaternion.Inverse(OriginalRotationDictionary[boneName]); GhostDictionary[boneName].ghost.rotation = transQuaternion * GhostOriginalRotationDictionary[boneName]; if (boneName == BoneNames.センター && UseBottomCenter) { GhostDictionary[boneName].ghost.position = boneDictionary[boneName].position - centerOffsetLength * GhostDictionary[boneName].ghost.up; continue; } GhostDictionary[boneName].ghost.position = boneDictionary[boneName].position; } } } [Serializable] public class MorphRecorder { public List FacialMorphList; //キーはunity上のモーフ名 public Dictionary MorphDrivers { get; private set; } = new Dictionary(); public MorphRecorder(Transform model) { var facialTarget = model.GetComponentInParent().FaceDrivenKeyTarget; FacialMorphList = new List(); FacialMorphList.AddRange(facialTarget.EyeBrowMorphs); FacialMorphList.AddRange(facialTarget.EyeMorphs); FacialMorphList.AddRange(facialTarget.MouthMorphs); for (int i = 0; i < FacialMorphList.Count; i++) { string morphName = ConvertMorphName(FacialMorphList[i].name); if (MorphDrivers.Keys.Contains(morphName)) { if (!MorphDrivers[morphName].Morphs.Contains(FacialMorphList[i])) { MorphDrivers[morphName].Morphs.Add(FacialMorphList[i]); } } else { List morphList = new List(); morphList.Add(FacialMorphList[i]); var driver = new MorphDriver(morphList, i); MorphDrivers.Add(morphName, driver); } } } public string ConvertMorphName(string name) { switch (name) { case "EyeBrow_1_R": return "にこり右"; case "EyeBrow_1_L": return "にこり左"; case "EyeBrow_5_R": return "真面目右"; case "EyeBrow_5_L": return "真面目左"; case "EyeBrow_6_R": return "困る右"; case "EyeBrow_6_L": return "困る左"; case "EyeBrow_7_R": return "怒り右"; case "EyeBrow_7_L": return "怒り左"; case "EyeBrow_11_R": return "下右"; case "EyeBrow_11_L": return "下左"; case "EyeBrow_21_R": return "上右"; case "EyeBrow_21_L": return "上左"; case "Eye_2_R": return "ウィンク2右"; case "Eye_2_L": return "ウィンク2"; case "Eye_5_R": return "ウィンク右"; case "Eye_5_L": return "ウィンク"; case "Eye_8_R": return "ウィンク右"; case "Eye_8_L": return "ウィンク"; case "Eye_9_L": case "Eye_10_L": case "Eye_11_L": case "Eye_18_L": return "キリッ"; case "Eye_16_L": return "なごみ"; case "Eye_12_L": return "びっくり"; case "Eye_15_L": return "じと目"; case "Eye_14_L": return "瞳小"; case "Mouth_9_0": return "ん"; case "Mouth_12_0": return "にっこり"; case "Mouth_13_0": return "口角上げ"; case "Mouth_22_0": return "にやり"; case "Mouth_5_0": case "Mouth_6_0": case "Mouth_7_0": case "Mouth_23_0": return "あ"; case "Mouth_25_0": case "Mouth_8_0": return "い"; case "Mouth_28_0": case "Mouth_16_0": return "う"; case "Mouth_31_0": case "Mouth_10_0": case "Mouth_14_0": return "お"; case "Mouth_33_0": return "え"; case "Mouth_41_0": return "口角下げ"; case "Mouth_45_0": return "ぺろっ"; case "Mouth_54_0": return "口横広げ"; case "Mouth_55_0": return "口横狭い"; default: return name; } } public void RecrodAllMorph() { foreach (MorphDriver morphDriver in MorphDrivers.Values) { morphDriver.RecordMorph(); } } public void TrimMorphNumber() { string dot = "."; Dictionary morphDriversTemp = new Dictionary(); foreach (string morphName in MorphDrivers.Keys) { //正規表現使うより、dot探して整数か見る if (morphName.Contains(dot) && int.TryParse(morphName.Substring(0, morphName.IndexOf(dot)), out int dummy)) { morphDriversTemp.Add(morphName.Substring(morphName.IndexOf(dot) + 1), MorphDrivers[morphName]); continue; } morphDriversTemp.Add(morphName, MorphDrivers[morphName]); } MorphDrivers = morphDriversTemp; } public void DisableIntron() { foreach (string morphName in MorphDrivers.Keys) { for (int i = 0; i < MorphDrivers[morphName].ValueList.Count; i++) { //情報がなければ次へ if (MorphDrivers[morphName].ValueList.Count == 0) { continue; } //今、前、後が同じなら不必要なので無効化 if (i > 0 && i < MorphDrivers[morphName].ValueList.Count - 1 && floatCompare(MorphDrivers[morphName].ValueList[i].value, MorphDrivers[morphName].ValueList[i - 1].value) && floatCompare(MorphDrivers[morphName].ValueList[i].value, MorphDrivers[morphName].ValueList[i + 1].value)) { MorphDrivers[morphName].ValueList[i] = (MorphDrivers[morphName].ValueList[i].value, false); } } } } bool floatCompare(float f1, float f2) { int a = (int)(f1 * 100); int b = (int)(f2 * 100); return a == b; } [Serializable] public class MorphDriver { public List Morphs; public int MorphIndex { get; private set; } public List<(float value, bool enabled)> ValueList = new List<(float value, bool enabled)>(); public MorphDriver(List facialMorph, int morphIndex) { Morphs = facialMorph; MorphIndex = morphIndex; } public void RecordMorph() { float val = 0; foreach (var morph in Morphs) { val += morph.weight; } ValueList.Add((Mathf.Clamp(val, -1, 1), true)); } } } }