├── LICENSE ├── README.md ├── UnityCameraVMDRecorder.cs ├── UnityHumanoidVMDRecorder.cs ├── UnityVMDQuaternionExtentions.cs └── VMDRecorderSample.unitypackage /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 hobosore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityVMDRecorder 2 | 3 | ## About 4 | Unity上の人・カメラアニメーションをランタイムで記録してvmd(MMDのアニメーションファイル)に保存するためのコードです。 5 | This code enables you to record humanoid/camera animations in unity into .vmd file (MikuMikuDance). 6 | 7 | ## Usage 8 | (2:11~) 9 | https://www.nicovideo.jp/watch/sm35513082 10 | 11 | ## Update 12 | モーフも記録できるようにしました。 (2019/08/05) 13 | Added function to record morph animations. 14 | 15 | MMD上ではセンターボーンが足元にあるモデルに対して補正できるように (2019/08/06) 16 | Supported models whose center bone in MMD is at its foot. 17 | 18 | 全ての親をセンターに移すオプションを追加 19 | MMD上で全ての親を編集できるように(2019/10/16) 20 | Added UseCenterAsParentOfAll(public bool). 21 | 22 | 23 | **MIT License** 24 | -------------------------------------------------------------------------------- /UnityCameraVMDRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using UnityEngine; 7 | 8 | public class UnityCameraVMDRecorder : MonoBehaviour 9 | { 10 | const float WorldScaleFix = 12.5f; 11 | const float FPSs = 0.03333f; 12 | 13 | public bool RecordMainCamera; 14 | /// 15 | /// カメラの座標・回転を絶対座標系で計算する 16 | /// 17 | public bool UseAbsoluteCoordinateSystem = true; 18 | public bool IgnoreInitialPosition; 19 | public bool IgnoreInitialRotation; 20 | public Vector3 Offset = Vector3.zero; 21 | public int KeyReductionLevel = 3; 22 | public List LocalPositions { get; private set; } = new List(); 23 | public List LocalRotations { get; private set; } = new List(); 24 | public bool IsRecording { get; private set; } = false; 25 | public int FrameNumber { get; private set; } = 0; 26 | 27 | Transform targetCameraTransform = null; 28 | Camera targetCamera; 29 | Vector3 initialPosition = Vector3.zero; 30 | Quaternion initialRotation = Quaternion.identity; 31 | int frameNumberSaved = 0; 32 | List positionsSaved = new List(); 33 | List rotationsSaved = new List(); 34 | 35 | // Start is called before the first frame update 36 | void Start() 37 | { 38 | Time.fixedDeltaTime = FPSs; 39 | 40 | SetCameraTransform(); 41 | 42 | if (targetCameraTransform != null) 43 | { 44 | SetInitialPositionAndRotation(); 45 | } 46 | } 47 | 48 | private void OnEnable() 49 | { 50 | SetCameraTransform(); 51 | } 52 | 53 | // Update is called once per frame 54 | void FixedUpdate() 55 | { 56 | if (IsRecording) 57 | { 58 | SaveFrame(); 59 | FrameNumber++; 60 | } 61 | } 62 | 63 | void SetInitialPositionAndRotation() 64 | { 65 | if (UseAbsoluteCoordinateSystem) 66 | { 67 | initialPosition = targetCameraTransform.position; 68 | initialRotation = targetCameraTransform.rotation; 69 | } 70 | else 71 | { 72 | initialPosition = targetCameraTransform.localPosition; 73 | initialRotation = targetCameraTransform.localRotation; 74 | } 75 | } 76 | 77 | void SetCameraTransform() 78 | { 79 | if (RecordMainCamera) 80 | { 81 | if (Camera.main != null) 82 | { 83 | targetCameraTransform = Camera.main.transform; 84 | targetCamera = Camera.main; 85 | } 86 | else 87 | { 88 | enabled = false; 89 | } 90 | } 91 | else 92 | { 93 | targetCamera = GetComponent(); 94 | 95 | if (targetCamera != null) 96 | { 97 | targetCameraTransform = transform; 98 | } 99 | else 100 | { 101 | enabled = false; 102 | } 103 | } 104 | } 105 | 106 | void SaveFrame() 107 | { 108 | Vector3 position = Vector3.zero; 109 | if (targetCameraTransform != null) 110 | { 111 | position = UseAbsoluteCoordinateSystem ? targetCameraTransform.position : targetCameraTransform.localPosition; 112 | } 113 | if (IgnoreInitialPosition) { position -= initialPosition; } 114 | Quaternion rotation = Quaternion.identity; 115 | if (targetCameraTransform != null) 116 | { 117 | rotation = UseAbsoluteCoordinateSystem ? targetCameraTransform.rotation : targetCameraTransform.localRotation; 118 | } 119 | if (IgnoreInitialRotation) { rotation = rotation.MinusRotation(initialRotation); } 120 | 121 | //座標系の変換 122 | Vector3 vmdPosition = new Vector3(-position.x, position.y, -position.z); 123 | Quaternion fixedRotation = new Quaternion(-rotation.x, rotation.y, -rotation.z, rotation.w); 124 | Vector3 vmdRotation = new Vector3(fixedRotation.eulerAngles.x, 180 - fixedRotation.eulerAngles.y, fixedRotation.eulerAngles.z); 125 | 126 | LocalPositions.Add(vmdPosition); 127 | LocalRotations.Add(vmdRotation); 128 | } 129 | 130 | public static void SetFPS(int fps) 131 | { 132 | Time.fixedDeltaTime = 1 / (float)fps; 133 | } 134 | 135 | public void SetCameraTransform(Camera camera) 136 | { 137 | targetCameraTransform = camera.transform; 138 | this.targetCamera = camera; 139 | } 140 | 141 | /// 142 | /// レコーディングを開始または再開 143 | /// 144 | public void StartRecording() 145 | { 146 | SetInitialPositionAndRotation(); 147 | IsRecording = true; 148 | } 149 | 150 | /// 151 | /// レコーディングを一時停止 152 | /// 153 | public void PauseRecording() { IsRecording = false; } 154 | 155 | /// 156 | /// レコーディングを終了 157 | /// 158 | public void StopRecording() 159 | { 160 | IsRecording = false; 161 | frameNumberSaved = FrameNumber; 162 | positionsSaved = LocalPositions; 163 | rotationsSaved = LocalRotations; 164 | 165 | FrameNumber = 0; 166 | LocalPositions = new List(); 167 | LocalRotations = new List(); 168 | } 169 | 170 | /// 171 | /// VMDを作成する 172 | /// 呼び出す際は先にStopRecordingを呼び出すこと 173 | /// 174 | /// 保存先の絶対ファイルパス 175 | public async void SaveVMD(string filePath) 176 | { 177 | if (IsRecording) 178 | { 179 | Debug.Log(targetCameraTransform.name + "VMD保存前にレコーディングをストップしてください。"); 180 | return; 181 | } 182 | 183 | if (KeyReductionLevel <= 0) { KeyReductionLevel = 1; } 184 | 185 | const string modelName = "カメラ・照明"; 186 | 187 | float fieldOfView = targetCamera.fieldOfView; 188 | bool isOrthographic = targetCamera.orthographic; 189 | 190 | Debug.Log(targetCameraTransform.name + "VMDファイル作成開始"); 191 | await Task.Run(() => 192 | { 193 | //ファイルの書き込み 194 | using (FileStream fileStream = new FileStream(filePath, FileMode.Create)) 195 | using (BinaryWriter binaryWriter = new BinaryWriter(fileStream)) 196 | { 197 | try 198 | { 199 | const string ShiftJIS = "shift_jis"; 200 | const int intByteLength = 4; 201 | 202 | //ファイルタイプの書き込み 203 | const int fileTypeLength = 30; 204 | const string RightFileType = "Vocaloid Motion Data 0002"; 205 | byte[] fileTypeBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(RightFileType); 206 | binaryWriter.Write(fileTypeBytes, 0, fileTypeBytes.Length); 207 | binaryWriter.Write(new byte[fileTypeLength - fileTypeBytes.Length], 0, fileTypeLength - fileTypeBytes.Length); 208 | 209 | //モーション名の書き込み、Shift_JISで保存 210 | const int motionNameLength = 20; 211 | byte[] motionNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(modelName); 212 | binaryWriter.Write(motionNameBytes, 0, Mathf.Min(motionNameBytes.Length, 20)); 213 | if (motionNameLength - motionNameBytes.Length > 0) 214 | { 215 | binaryWriter.Write(new byte[motionNameLength - motionNameBytes.Length], 0, motionNameLength - motionNameBytes.Length); 216 | } 217 | 218 | //全ボーンフレーム数の書き込み 219 | byte[] allKeyFrameNumberByte = BitConverter.GetBytes(0); 220 | binaryWriter.Write(allKeyFrameNumberByte, 0, intByteLength); 221 | 222 | //全モーフフレーム数の書き込み 223 | byte[] faceFrameCount = BitConverter.GetBytes(0); 224 | binaryWriter.Write(faceFrameCount, 0, intByteLength); 225 | 226 | //カメラの書き込み 227 | byte[] cameraFrameCount = BitConverter.GetBytes((uint)(frameNumberSaved / KeyReductionLevel)); 228 | binaryWriter.Write(cameraFrameCount, 0, intByteLength); 229 | for (int i = 0; i < frameNumberSaved; i++) 230 | { 231 | if ((i % KeyReductionLevel) != 0) { continue; } 232 | 233 | byte[] frameNumberByte = BitConverter.GetBytes((ulong)i); 234 | binaryWriter.Write(frameNumberByte, 0, intByteLength); 235 | 236 | byte[] cameraDistanceByte = BitConverter.GetBytes(0); 237 | binaryWriter.Write(cameraDistanceByte, 0, intByteLength); 238 | 239 | Vector3 position = positionsSaved[i] * WorldScaleFix; 240 | byte[] positionX = BitConverter.GetBytes(position.x); 241 | binaryWriter.Write(positionX, 0, intByteLength); 242 | byte[] positionY = BitConverter.GetBytes(position.y); 243 | binaryWriter.Write(positionY, 0, intByteLength); 244 | byte[] positionZ = BitConverter.GetBytes(position.z); 245 | binaryWriter.Write(positionZ, 0, intByteLength); 246 | 247 | Vector3 rotation = rotationsSaved[i] * Mathf.Deg2Rad; 248 | byte[] rotationX = BitConverter.GetBytes(rotation.x); 249 | binaryWriter.Write(rotationX, 0, intByteLength); 250 | byte[] rotationY = BitConverter.GetBytes(rotation.y); 251 | binaryWriter.Write(rotationY, 0, intByteLength); 252 | byte[] rotationZ = BitConverter.GetBytes(rotation.z); 253 | binaryWriter.Write(rotationZ, 0, intByteLength); 254 | 255 | byte[] interpolateBytes = new byte[24]; 256 | binaryWriter.Write(interpolateBytes, 0, 24); 257 | 258 | byte[] viewAngleByte = BitConverter.GetBytes((ulong)fieldOfView); 259 | binaryWriter.Write(viewAngleByte, 0, intByteLength); 260 | 261 | byte perspectiveByte = Convert.ToByte(isOrthographic); 262 | byte[] perspectiveBytes = new byte[] { perspectiveByte }; 263 | binaryWriter.Write(new byte[] { perspectiveByte }, 0, perspectiveBytes.Length); 264 | } 265 | 266 | //照明の書き込み 267 | byte[] lightFrameCount = BitConverter.GetBytes(0); 268 | binaryWriter.Write(lightFrameCount, 0, intByteLength); 269 | 270 | //セルフシャドウの書き込み 271 | byte[] selfShadowCount = BitConverter.GetBytes(0); 272 | binaryWriter.Write(selfShadowCount, 0, intByteLength); 273 | 274 | //IKの書き込み 275 | byte[] ikCount = BitConverter.GetBytes(0); 276 | binaryWriter.Write(ikCount, 0, intByteLength); 277 | } 278 | catch (Exception ex) 279 | { 280 | Debug.Log("VMD書き込みエラー" + ex.Message); 281 | } 282 | finally 283 | { 284 | binaryWriter.Close(); 285 | } 286 | } 287 | }); 288 | Debug.Log(targetCameraTransform.name + "VMDファイル作成終了"); 289 | } 290 | 291 | /// 292 | /// VMDを作成する 293 | /// 呼び出す際は先にStopRecordingを呼び出すこと 294 | /// 295 | /// 保存先の絶対ファイルパス 296 | /// キーの書き込み頻度を減らして容量を減らす 297 | public void SaveVMD(string filePath, int keyReductionLevel) 298 | { 299 | KeyReductionLevel = keyReductionLevel; 300 | SaveVMD(filePath); 301 | } 302 | } -------------------------------------------------------------------------------- /UnityHumanoidVMDRecorder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.IO; 5 | using System; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | //初期ポーズ(T,Aポーズ)の時点でアタッチ、有効化されている必要がある 10 | public class UnityHumanoidVMDRecorder : MonoBehaviour 11 | { 12 | public bool UseParentOfAll = true; 13 | public bool UseCenterAsParentOfAll = true; 14 | /// 15 | /// 全ての親の座標・回転を絶対座標系で計算する 16 | /// UseParentOfAllがTrueでないと意味がない 17 | /// 18 | public bool UseAbsoluteCoordinateSystem = true; 19 | public bool IgnoreInitialPosition = false; 20 | public bool IgnoreInitialRotation = false; 21 | /// 22 | /// 一部のモデルではMMD上ではセンターが足元にある 23 | /// Start前に設定されている必要がある 24 | /// 25 | public bool UseBottomCenter = false; 26 | /// 27 | /// Unity上のモーフ名に1.まばたきなど番号が振られている場合、番号を除去する 28 | /// 29 | public bool TrimMorphNumber = true; 30 | public int KeyReductionLevel = 3; 31 | public bool IsRecording { get; private set; } = false; 32 | public int FrameNumber { get; private set; } = 0; 33 | int frameNumberSaved = 0; 34 | const float FPSs = 0.03333f; 35 | const string CenterNameString = "センター"; 36 | const string GrooveNameString = "グルーブ"; 37 | public enum BoneNames 38 | { 39 | 全ての親, センター, 左足IK, 右足IK, 上半身, 上半身2, 首, 頭, 40 | 左肩, 左腕, 左ひじ, 左手首, 右肩, 右腕, 右ひじ, 右手首, 41 | 左親指1, 左親指2, 左人指1, 左人指2, 左人指3, 左中指1, 左中指2, 左中指3, 42 | 左薬指1, 左薬指2, 左薬指3, 左小指1, 左小指2, 左小指3, 右親指1, 右親指2, 43 | 右人指1, 右人指2, 右人指3, 右中指1, 右中指2, 右中指3, 右薬指1, 右薬指2, 44 | 右薬指3, 右小指1, 右小指2, 右小指3, 左足, 右足, 左ひざ, 右ひざ, 45 | 左足首, 右足首, None 46 | //左つま先, 右つま先は情報付けると足首の回転、位置との矛盾が生じかねない 47 | } 48 | //コンストラクタにて初期化 49 | //全てのボーンを名前で引く辞書 50 | Dictionary transformDictionary = new Dictionary(); 51 | public Dictionary BoneDictionary { get; private set; } 52 | Vector3 parentInitialPosition = Vector3.zero; 53 | Quaternion parentInitialRotation = Quaternion.identity; 54 | Dictionary> positionDictionary = new Dictionary>(); 55 | Dictionary> positionDictionarySaved = new Dictionary>(); 56 | Dictionary> rotationDictionary = new Dictionary>(); 57 | Dictionary> rotationDictionarySaved = new Dictionary>(); 58 | //ボーン移動量の補正係数 59 | //この値は大体の値、正確ではない 60 | const float DefaultBoneAmplifier = 12.5f; 61 | 62 | public Vector3 ParentOfAllOffset = new Vector3(0, 0, 0); 63 | public Vector3 LeftFootIKOffset = Vector3.zero; 64 | public Vector3 RightFootIKOffset = Vector3.zero; 65 | 66 | Animator animator; 67 | BoneGhost boneGhost; 68 | MorphRecorder morphRecorder; 69 | MorphRecorder morphRecorderSaved; 70 | 71 | // Start is called before the first frame update 72 | void Start() 73 | { 74 | Time.fixedDeltaTime = FPSs; 75 | animator = GetComponent(); 76 | BoneDictionary = new Dictionary() 77 | { 78 | //下半身などというものはUnityにはない 79 | { BoneNames.全ての親, (transform) }, 80 | { BoneNames.センター, (animator.GetBoneTransform(HumanBodyBones.Hips))}, 81 | { BoneNames.上半身, (animator.GetBoneTransform(HumanBodyBones.Spine))}, 82 | { BoneNames.上半身2, (animator.GetBoneTransform(HumanBodyBones.Chest))}, 83 | { BoneNames.頭, (animator.GetBoneTransform(HumanBodyBones.Head))}, 84 | { BoneNames.首, (animator.GetBoneTransform(HumanBodyBones.Neck))}, 85 | { BoneNames.左肩, (animator.GetBoneTransform(HumanBodyBones.LeftShoulder))}, 86 | { BoneNames.右肩, (animator.GetBoneTransform(HumanBodyBones.RightShoulder))}, 87 | { BoneNames.左腕, (animator.GetBoneTransform(HumanBodyBones.LeftUpperArm))}, 88 | { BoneNames.右腕, (animator.GetBoneTransform(HumanBodyBones.RightUpperArm))}, 89 | { BoneNames.左ひじ, (animator.GetBoneTransform(HumanBodyBones.LeftLowerArm))}, 90 | { BoneNames.右ひじ, (animator.GetBoneTransform(HumanBodyBones.RightLowerArm))}, 91 | { BoneNames.左手首, (animator.GetBoneTransform(HumanBodyBones.LeftHand))}, 92 | { BoneNames.右手首, (animator.GetBoneTransform(HumanBodyBones.RightHand))}, 93 | { BoneNames.左親指1, (animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal))}, 94 | { BoneNames.右親指1, (animator.GetBoneTransform(HumanBodyBones.RightThumbProximal))}, 95 | { BoneNames.左親指2, (animator.GetBoneTransform(HumanBodyBones.LeftThumbIntermediate))}, 96 | { BoneNames.右親指2, (animator.GetBoneTransform(HumanBodyBones.RightThumbIntermediate))}, 97 | { BoneNames.左人指1, (animator.GetBoneTransform(HumanBodyBones.LeftIndexProximal))}, 98 | { BoneNames.右人指1, (animator.GetBoneTransform(HumanBodyBones.RightIndexProximal))}, 99 | { BoneNames.左人指2, (animator.GetBoneTransform(HumanBodyBones.LeftIndexIntermediate))}, 100 | { BoneNames.右人指2, (animator.GetBoneTransform(HumanBodyBones.RightIndexIntermediate))}, 101 | { BoneNames.左人指3, (animator.GetBoneTransform(HumanBodyBones.LeftIndexDistal))}, 102 | { BoneNames.右人指3, (animator.GetBoneTransform(HumanBodyBones.RightIndexDistal))}, 103 | { BoneNames.左中指1, (animator.GetBoneTransform(HumanBodyBones.LeftMiddleProximal))}, 104 | { BoneNames.右中指1, (animator.GetBoneTransform(HumanBodyBones.RightMiddleProximal))}, 105 | { BoneNames.左中指2, (animator.GetBoneTransform(HumanBodyBones.LeftMiddleIntermediate))}, 106 | { BoneNames.右中指2, (animator.GetBoneTransform(HumanBodyBones.RightMiddleIntermediate))}, 107 | { BoneNames.左中指3, (animator.GetBoneTransform(HumanBodyBones.LeftMiddleDistal))}, 108 | { BoneNames.右中指3, (animator.GetBoneTransform(HumanBodyBones.RightMiddleDistal))}, 109 | { BoneNames.左薬指1, (animator.GetBoneTransform(HumanBodyBones.LeftRingProximal))}, 110 | { BoneNames.右薬指1, (animator.GetBoneTransform(HumanBodyBones.RightRingProximal))}, 111 | { BoneNames.左薬指2, (animator.GetBoneTransform(HumanBodyBones.LeftRingIntermediate))}, 112 | { BoneNames.右薬指2, (animator.GetBoneTransform(HumanBodyBones.RightRingIntermediate))}, 113 | { BoneNames.左薬指3, (animator.GetBoneTransform(HumanBodyBones.LeftRingDistal))}, 114 | { BoneNames.右薬指3, (animator.GetBoneTransform(HumanBodyBones.RightRingDistal))}, 115 | { BoneNames.左小指1, (animator.GetBoneTransform(HumanBodyBones.LeftLittleProximal))}, 116 | { BoneNames.右小指1, (animator.GetBoneTransform(HumanBodyBones.RightLittleProximal))}, 117 | { BoneNames.左小指2, (animator.GetBoneTransform(HumanBodyBones.LeftLittleIntermediate))}, 118 | { BoneNames.右小指2, (animator.GetBoneTransform(HumanBodyBones.RightLittleIntermediate))}, 119 | { BoneNames.左小指3, (animator.GetBoneTransform(HumanBodyBones.LeftLittleDistal))}, 120 | { BoneNames.右小指3, (animator.GetBoneTransform(HumanBodyBones.RightLittleDistal))}, 121 | { BoneNames.左足IK, (animator.GetBoneTransform(HumanBodyBones.LeftFoot))}, 122 | { BoneNames.右足IK, (animator.GetBoneTransform(HumanBodyBones.RightFoot))}, 123 | { BoneNames.左足, (animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg))}, 124 | { BoneNames.右足, (animator.GetBoneTransform(HumanBodyBones.RightUpperLeg))}, 125 | { BoneNames.左ひざ, (animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg))}, 126 | { BoneNames.右ひざ, (animator.GetBoneTransform(HumanBodyBones.RightLowerLeg))}, 127 | { BoneNames.左足首, (animator.GetBoneTransform(HumanBodyBones.LeftFoot))}, 128 | { BoneNames.右足首, (animator.GetBoneTransform(HumanBodyBones.RightFoot))}, 129 | //左つま先, 右つま先は情報付けると足首の回転、位置との矛盾が生じかねない 130 | //{ BoneNames.左つま先, (animator.GetBoneTransform(HumanBodyBones.LeftToes))}, 131 | //{ BoneNames.右つま先, (animator.GetBoneTransform(HumanBodyBones.RightToes))} 132 | }; 133 | 134 | makeTransformDictionary(transform, transformDictionary); 135 | 136 | void makeTransformDictionary(Transform rootBone, Dictionary dictionary) 137 | { 138 | if (dictionary.ContainsKey(rootBone.name)) { return; } 139 | dictionary.Add(rootBone.name, rootBone); 140 | foreach (Transform childT in rootBone) 141 | { 142 | makeTransformDictionary(childT, dictionary); 143 | } 144 | } 145 | 146 | EnforceInitialPose(animator, true); 147 | 148 | SetInitialPositionAndRotation(); 149 | 150 | foreach (BoneNames boneName in BoneDictionary.Keys) 151 | { 152 | if (BoneDictionary[boneName] == null) { continue; } 153 | 154 | positionDictionary.Add(boneName, new List()); 155 | rotationDictionary.Add(boneName, new List()); 156 | } 157 | 158 | if (BoneDictionary[BoneNames.左足IK] != null) 159 | { 160 | LeftFootIKOffset = Quaternion.Inverse(transform.rotation) * (BoneDictionary[BoneNames.左足IK].position - transform.position); 161 | } 162 | 163 | if (BoneDictionary[BoneNames.右足IK] != null) 164 | { 165 | RightFootIKOffset = Quaternion.Inverse(transform.rotation) * (BoneDictionary[BoneNames.右足IK].position - transform.position); 166 | } 167 | 168 | boneGhost = new BoneGhost(animator, BoneDictionary, UseBottomCenter); 169 | morphRecorder = new MorphRecorder(transform); 170 | } 171 | 172 | void EnforceInitialPose(Animator animator, bool aPose = false) 173 | { 174 | if (animator == null) 175 | { 176 | UnityEngine.Debug.Log("EnforceInitialPose"); 177 | UnityEngine.Debug.Log("Animatorがnullです"); 178 | return; 179 | } 180 | 181 | const int APoseDegree = 30; 182 | 183 | Vector3 position = animator.transform.position; 184 | Quaternion rotation = animator.transform.rotation; 185 | animator.transform.position = Vector3.zero; 186 | animator.transform.rotation = Quaternion.identity; 187 | 188 | int count = animator.avatar.humanDescription.skeleton.Length; 189 | for (int i = 0; i < count; i++) 190 | { 191 | if (!transformDictionary.ContainsKey(animator.avatar.humanDescription.skeleton[i].name)) 192 | { 193 | continue; 194 | } 195 | 196 | transformDictionary[animator.avatar.humanDescription.skeleton[i].name].localPosition 197 | = animator.avatar.humanDescription.skeleton[i].position; 198 | transformDictionary[animator.avatar.humanDescription.skeleton[i].name].localRotation 199 | = animator.avatar.humanDescription.skeleton[i].rotation; 200 | } 201 | 202 | animator.transform.position = position; 203 | animator.transform.rotation = rotation; 204 | 205 | if (aPose && animator.isHuman) 206 | { 207 | Transform leftUpperArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm); 208 | Transform rightUpperArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm); 209 | if (leftUpperArm == null || rightUpperArm == null) { return; } 210 | leftUpperArm.Rotate(animator.transform.forward, APoseDegree, Space.World); 211 | rightUpperArm.Rotate(animator.transform.forward, -APoseDegree, Space.World); 212 | } 213 | } 214 | 215 | 216 | private void FixedUpdate() 217 | { 218 | if (IsRecording) 219 | { 220 | SaveFrame(); 221 | FrameNumber++; 222 | } 223 | } 224 | 225 | void SaveFrame() 226 | { 227 | if (boneGhost != null) { boneGhost.GhostAll(); } 228 | if (morphRecorder != null) { morphRecorder.RecrodAllMorph(); } 229 | 230 | foreach (BoneNames boneName in BoneDictionary.Keys) 231 | { 232 | if (BoneDictionary[boneName] == null) 233 | { 234 | continue; 235 | } 236 | 237 | if (boneName == BoneNames.右足IK || boneName == BoneNames.左足IK) 238 | { 239 | Vector3 targetVector = Vector3.zero; 240 | if (UseCenterAsParentOfAll) 241 | { 242 | if ((!UseAbsoluteCoordinateSystem && transform.parent != null) && IgnoreInitialPosition ) 243 | { 244 | targetVector 245 | = Quaternion.Inverse(transform.parent.rotation) 246 | * (BoneDictionary[boneName].position - transform.parent.position) 247 | - parentInitialPosition; 248 | } 249 | else if ((!UseAbsoluteCoordinateSystem && transform.parent != null) && !IgnoreInitialPosition) 250 | { 251 | targetVector 252 | = Quaternion.Inverse(transform.parent.rotation) 253 | * (BoneDictionary[boneName].position - transform.parent.position); 254 | } 255 | else if ((UseAbsoluteCoordinateSystem || transform.parent == null) && IgnoreInitialPosition) 256 | { 257 | targetVector = BoneDictionary[boneName].position - parentInitialPosition; 258 | } 259 | else if ((UseAbsoluteCoordinateSystem || transform.parent == null) && transform.parent && !IgnoreInitialPosition) 260 | { 261 | targetVector = BoneDictionary[boneName].position; 262 | } 263 | } 264 | else 265 | { 266 | targetVector = BoneDictionary[boneName].position - transform.position; 267 | targetVector = Quaternion.Inverse(transform.rotation) * targetVector; 268 | } 269 | targetVector -= (boneName == BoneNames.左足IK ? LeftFootIKOffset : RightFootIKOffset); 270 | Vector3 ikPosition = new Vector3(-targetVector.x, targetVector.y, -targetVector.z); 271 | positionDictionary[boneName].Add(ikPosition * DefaultBoneAmplifier); 272 | //回転は全部足首に持たせる 273 | Quaternion ikRotation = Quaternion.identity; 274 | rotationDictionary[boneName].Add(ikRotation); 275 | continue; 276 | } 277 | 278 | if (boneGhost != null && boneGhost.GhostDictionary.Keys.Contains(boneName)) 279 | { 280 | if (boneGhost.GhostDictionary[boneName].ghost == null || !boneGhost.GhostDictionary[boneName].enabled) 281 | { 282 | rotationDictionary[boneName].Add(Quaternion.identity); 283 | positionDictionary[boneName].Add(Vector3.zero); 284 | continue; 285 | } 286 | 287 | Vector3 boneVector = boneGhost.GhostDictionary[boneName].ghost.localPosition; 288 | Quaternion boneQuatenion = boneGhost.GhostDictionary[boneName].ghost.localRotation; 289 | rotationDictionary[boneName].Add(new Quaternion(-boneQuatenion.x, boneQuatenion.y, -boneQuatenion.z, boneQuatenion.w)); 290 | 291 | boneVector -= boneGhost.GhostOriginalLocalPositionDictionary[boneName]; 292 | 293 | positionDictionary[boneName].Add(new Vector3(-boneVector.x, boneVector.y, -boneVector.z) * DefaultBoneAmplifier); 294 | continue; 295 | } 296 | 297 | Quaternion fixedQuatenion = Quaternion.identity; 298 | Quaternion vmdRotation = Quaternion.identity; 299 | 300 | if (boneName == BoneNames.全ての親 && UseAbsoluteCoordinateSystem) 301 | { 302 | fixedQuatenion = BoneDictionary[boneName].rotation; 303 | } 304 | else 305 | { 306 | fixedQuatenion = BoneDictionary[boneName].localRotation; 307 | } 308 | 309 | if (boneName == BoneNames.全ての親 && IgnoreInitialRotation) 310 | { 311 | fixedQuatenion = BoneDictionary[boneName].localRotation.MinusRotation(parentInitialRotation); 312 | } 313 | 314 | vmdRotation = new Quaternion(-fixedQuatenion.x, fixedQuatenion.y, -fixedQuatenion.z, fixedQuatenion.w); 315 | 316 | rotationDictionary[boneName].Add(vmdRotation); 317 | 318 | Vector3 fixedPosition = Vector3.zero; 319 | Vector3 vmdPosition = Vector3.zero; 320 | 321 | if (boneName == BoneNames.全ての親 && UseAbsoluteCoordinateSystem) 322 | { 323 | fixedPosition = BoneDictionary[boneName].position; 324 | } 325 | else 326 | { 327 | fixedPosition = BoneDictionary[boneName].localPosition; 328 | } 329 | 330 | if (boneName == BoneNames.全ての親 && IgnoreInitialPosition) 331 | { 332 | fixedPosition -= parentInitialPosition; 333 | } 334 | 335 | vmdPosition = new Vector3(-fixedPosition.x, fixedPosition.y, -fixedPosition.z); 336 | 337 | if (boneName == BoneNames.全ての親) 338 | { 339 | positionDictionary[boneName].Add(vmdPosition * DefaultBoneAmplifier + ParentOfAllOffset); 340 | } 341 | else 342 | { 343 | positionDictionary[boneName].Add(vmdPosition * DefaultBoneAmplifier); 344 | } 345 | } 346 | } 347 | 348 | void SetInitialPositionAndRotation() 349 | { 350 | if (UseAbsoluteCoordinateSystem) 351 | { 352 | parentInitialPosition = transform.position; 353 | parentInitialRotation = transform.rotation; 354 | } 355 | else 356 | { 357 | parentInitialPosition = transform.localPosition; 358 | parentInitialRotation = transform.localRotation; 359 | } 360 | } 361 | 362 | public static void SetFPS(int fps) 363 | { 364 | Time.fixedDeltaTime = 1 / (float)fps; 365 | } 366 | 367 | /// 368 | /// レコーディングを開始または再開 369 | /// 370 | public void StartRecording() 371 | { 372 | SetInitialPositionAndRotation(); 373 | IsRecording = true; 374 | } 375 | 376 | /// 377 | /// レコーディングを一時停止 378 | /// 379 | public void PauseRecording() { IsRecording = false; } 380 | 381 | /// 382 | /// レコーディングを終了 383 | /// 384 | public void StopRecording() 385 | { 386 | IsRecording = false; 387 | frameNumberSaved = FrameNumber; 388 | morphRecorderSaved = morphRecorder; 389 | FrameNumber = 0; 390 | positionDictionarySaved = positionDictionary; 391 | positionDictionary = new Dictionary>(); 392 | rotationDictionarySaved = rotationDictionary; 393 | rotationDictionary = new Dictionary>(); 394 | foreach (BoneNames boneName in BoneDictionary.Keys) 395 | { 396 | if (BoneDictionary[boneName] == null) { continue; } 397 | 398 | positionDictionary.Add(boneName, new List()); 399 | rotationDictionary.Add(boneName, new List()); 400 | } 401 | morphRecorder = new MorphRecorder(transform); 402 | } 403 | 404 | /// 405 | /// VMDを作成する 406 | /// 呼び出す際は先にStopRecordingを呼び出すこと 407 | /// 408 | /// VMDファイルに記載される専用モデル名 409 | /// 保存先の絶対ファイルパス 410 | public async void SaveVMD(string modelName, string filePath) 411 | { 412 | if (IsRecording) 413 | { 414 | Debug.Log(transform.name + "VMD保存前にレコーディングをストップしてください。"); 415 | return; 416 | } 417 | 418 | if (KeyReductionLevel <= 0) { KeyReductionLevel = 1; } 419 | 420 | Debug.Log(transform.name + "VMDファイル作成開始"); 421 | await Task.Run(() => 422 | { 423 | //ファイルの書き込み 424 | using (FileStream fileStream = new FileStream(filePath, FileMode.Create)) 425 | using (BinaryWriter binaryWriter = new BinaryWriter(fileStream)) 426 | { 427 | try 428 | { 429 | const string ShiftJIS = "shift_jis"; 430 | const int intByteLength = 4; 431 | 432 | //ファイルタイプの書き込み 433 | const int fileTypeLength = 30; 434 | const string RightFileType = "Vocaloid Motion Data 0002"; 435 | byte[] fileTypeBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(RightFileType); 436 | binaryWriter.Write(fileTypeBytes, 0, fileTypeBytes.Length); 437 | binaryWriter.Write(new byte[fileTypeLength - fileTypeBytes.Length], 0, fileTypeLength - fileTypeBytes.Length); 438 | 439 | //モデル名の書き込み、Shift_JISで保存 440 | const int modelNameLength = 20; 441 | byte[] modelNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(modelName); 442 | //モデル名が長すぎたとき 443 | modelNameBytes = modelNameBytes.Take(Mathf.Min(modelNameLength, modelNameBytes.Length)).ToArray(); 444 | binaryWriter.Write(modelNameBytes, 0, modelNameBytes.Length); 445 | binaryWriter.Write(new byte[modelNameLength - modelNameBytes.Length], 0, modelNameLength - modelNameBytes.Length); 446 | 447 | //全ボーンフレーム数の書き込み 448 | void LoopWithBoneCondition(Action action) 449 | { 450 | for (int i = 0; i < frameNumberSaved; i++) 451 | { 452 | if ((i % KeyReductionLevel) != 0) { continue; } 453 | 454 | foreach (BoneNames boneName in Enum.GetValues(typeof(BoneNames))) 455 | { 456 | if (!BoneDictionary.Keys.Contains(boneName)) { continue; } 457 | if (BoneDictionary[boneName] == null) { continue; } 458 | if (!UseParentOfAll && boneName == BoneNames.全ての親) { continue; } 459 | 460 | action(boneName, i); 461 | } 462 | } 463 | } 464 | uint allKeyFrameNumber = 0; 465 | LoopWithBoneCondition((a, b) => { allKeyFrameNumber++; }); 466 | byte[] allKeyFrameNumberByte = BitConverter.GetBytes(allKeyFrameNumber); 467 | binaryWriter.Write(allKeyFrameNumberByte, 0, intByteLength); 468 | 469 | //人ボーンの書き込み 470 | LoopWithBoneCondition((boneName, i) => 471 | { 472 | const int boneNameLength = 15; 473 | string boneNameString = boneName.ToString(); 474 | if (boneName == BoneNames.全ての親 && UseCenterAsParentOfAll) 475 | { 476 | boneNameString = CenterNameString; 477 | } 478 | if (boneName == BoneNames.センター && UseCenterAsParentOfAll) 479 | { 480 | boneNameString = GrooveNameString; 481 | } 482 | 483 | byte[] boneNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(boneNameString); 484 | binaryWriter.Write(boneNameBytes, 0, boneNameBytes.Length); 485 | binaryWriter.Write(new byte[boneNameLength - boneNameBytes.Length], 0, boneNameLength - boneNameBytes.Length); 486 | 487 | byte[] frameNumberByte = BitConverter.GetBytes((ulong)i); 488 | binaryWriter.Write(frameNumberByte, 0, intByteLength); 489 | 490 | Vector3 position = positionDictionarySaved[boneName][i]; 491 | byte[] positionX = BitConverter.GetBytes(position.x); 492 | binaryWriter.Write(positionX, 0, intByteLength); 493 | byte[] positionY = BitConverter.GetBytes(position.y); 494 | binaryWriter.Write(positionY, 0, intByteLength); 495 | byte[] positionZ = BitConverter.GetBytes(position.z); 496 | binaryWriter.Write(positionZ, 0, intByteLength); 497 | Quaternion rotation = rotationDictionarySaved[boneName][i]; 498 | byte[] rotationX = BitConverter.GetBytes(rotation.x); 499 | binaryWriter.Write(rotationX, 0, intByteLength); 500 | byte[] rotationY = BitConverter.GetBytes(rotation.y); 501 | binaryWriter.Write(rotationY, 0, intByteLength); 502 | byte[] rotationZ = BitConverter.GetBytes(rotation.z); 503 | binaryWriter.Write(rotationZ, 0, intByteLength); 504 | byte[] rotationW = BitConverter.GetBytes(rotation.w); 505 | binaryWriter.Write(rotationW, 0, intByteLength); 506 | 507 | byte[] interpolateBytes = new byte[64]; 508 | binaryWriter.Write(interpolateBytes, 0, 64); 509 | }); 510 | 511 | //全モーフフレーム数の書き込み 512 | morphRecorderSaved.DisableIntron(); 513 | if (TrimMorphNumber) { morphRecorderSaved.TrimMorphNumber(); } 514 | void LoopWithMorphCondition(Action action) 515 | { 516 | for (int i = 0; i < frameNumberSaved; i++) 517 | { 518 | foreach (string morphName in morphRecorderSaved.MorphDrivers.Keys) 519 | { 520 | if (morphRecorderSaved.MorphDrivers[morphName].ValueList.Count == 0) { continue; } 521 | if (i > morphRecorderSaved.MorphDrivers[morphName].ValueList.Count) { continue; } 522 | //変化のない部分は省く 523 | if (!morphRecorderSaved.MorphDrivers[morphName].ValueList[i].enabled) { continue; } 524 | const int boneNameLength = 15; 525 | string morphNameString = morphName.ToString(); 526 | byte[] morphNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(morphNameString); 527 | //名前が長過ぎた場合書き込まない 528 | if (boneNameLength - morphNameBytes.Length < 0) { continue; } 529 | 530 | action(morphName, i); 531 | } 532 | } 533 | } 534 | uint allMorphNumber = 0; 535 | LoopWithMorphCondition((a, b) => { allMorphNumber++; }); 536 | byte[] faceFrameCount = BitConverter.GetBytes(allMorphNumber); 537 | binaryWriter.Write(faceFrameCount, 0, intByteLength); 538 | 539 | //モーフの書き込み 540 | LoopWithMorphCondition((morphName, i) => 541 | { 542 | const int boneNameLength = 15; 543 | string morphNameString = morphName.ToString(); 544 | byte[] morphNameBytes = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes(morphNameString); 545 | 546 | binaryWriter.Write(morphNameBytes, 0, morphNameBytes.Length); 547 | binaryWriter.Write(new byte[boneNameLength - morphNameBytes.Length], 0, boneNameLength - morphNameBytes.Length); 548 | 549 | byte[] frameNumberByte = BitConverter.GetBytes((ulong)i); 550 | binaryWriter.Write(frameNumberByte, 0, intByteLength); 551 | 552 | byte[] valueByte = BitConverter.GetBytes(morphRecorderSaved.MorphDrivers[morphName].ValueList[i].value); 553 | binaryWriter.Write(valueByte, 0, intByteLength); 554 | }); 555 | 556 | //カメラの書き込み 557 | byte[] cameraFrameCount = BitConverter.GetBytes(0); 558 | binaryWriter.Write(cameraFrameCount, 0, intByteLength); 559 | 560 | //照明の書き込み 561 | byte[] lightFrameCount = BitConverter.GetBytes(0); 562 | binaryWriter.Write(lightFrameCount, 0, intByteLength); 563 | 564 | //セルフシャドウの書き込み 565 | byte[] selfShadowCount = BitConverter.GetBytes(0); 566 | binaryWriter.Write(selfShadowCount, 0, intByteLength); 567 | 568 | //IKの書き込み 569 | //0フレームにキーフレーム一つだけ置く 570 | byte[] ikCount = BitConverter.GetBytes(1); 571 | byte[] ikFrameNumber = BitConverter.GetBytes(0); 572 | byte modelDisplay = Convert.ToByte(1); 573 | //右足IKと左足IKと右足つま先IKと左足つま先IKの4つ 574 | byte[] ikNumber = BitConverter.GetBytes(4); 575 | const int IKNameLength = 20; 576 | byte[] leftIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("左足IK"); 577 | byte[] rightIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("右足IK"); 578 | byte[] leftToeIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("左つま先IK"); 579 | byte[] rightToeIKName = System.Text.Encoding.GetEncoding(ShiftJIS).GetBytes("右つま先IK"); 580 | byte ikOn = Convert.ToByte(1); 581 | byte ikOff = Convert.ToByte(0); 582 | binaryWriter.Write(ikCount, 0, intByteLength); 583 | binaryWriter.Write(ikFrameNumber, 0, intByteLength); 584 | binaryWriter.Write(modelDisplay); 585 | binaryWriter.Write(ikNumber, 0, intByteLength); 586 | binaryWriter.Write(leftIKName, 0, leftIKName.Length); 587 | binaryWriter.Write(new byte[IKNameLength - leftIKName.Length], 0, IKNameLength - leftIKName.Length); 588 | binaryWriter.Write(ikOff); 589 | binaryWriter.Write(leftToeIKName, 0, leftToeIKName.Length); 590 | binaryWriter.Write(new byte[IKNameLength - leftToeIKName.Length], 0, IKNameLength - leftToeIKName.Length); 591 | binaryWriter.Write(ikOff); 592 | binaryWriter.Write(rightIKName, 0, rightIKName.Length); 593 | binaryWriter.Write(new byte[IKNameLength - rightIKName.Length], 0, IKNameLength - rightIKName.Length); 594 | binaryWriter.Write(ikOff); 595 | binaryWriter.Write(rightToeIKName, 0, rightToeIKName.Length); 596 | binaryWriter.Write(new byte[IKNameLength - rightToeIKName.Length], 0, IKNameLength - rightToeIKName.Length); 597 | binaryWriter.Write(ikOff); 598 | } 599 | catch (Exception ex) 600 | { 601 | Debug.Log("VMD書き込みエラー" + ex.Message); 602 | } 603 | finally 604 | { 605 | binaryWriter.Close(); 606 | } 607 | } 608 | }); 609 | Debug.Log(transform.name + "VMDファイル作成終了"); 610 | } 611 | 612 | /// 613 | /// VMDを作成する 614 | /// 呼び出す際は先にStopRecordingを呼び出すこと 615 | /// 616 | /// VMDファイルに記載される専用モデル名 617 | /// 保存先の絶対ファイルパス 618 | /// キーの書き込み頻度を減らして容量を減らす 619 | public void SaveVMD(string modelName, string filePath, int keyReductionLevel) 620 | { 621 | KeyReductionLevel = keyReductionLevel; 622 | SaveVMD(modelName, filePath); 623 | } 624 | 625 | //裏で正規化されたモデル 626 | //(初期ポーズで各ボーンのlocalRotationがQuaternion.identityのモデル)を疑似的にアニメーションさせる 627 | class BoneGhost 628 | { 629 | public Dictionary GhostDictionary { get; private set; } = new Dictionary(); 630 | public Dictionary GhostOriginalLocalPositionDictionary { get; private set; } = new Dictionary(); 631 | public Dictionary GhostOriginalRotationDictionary { get; private set; } = new Dictionary(); 632 | public Dictionary OriginalRotationDictionary { get; private set; } = new Dictionary(); 633 | 634 | public bool UseBottomCenter { get; private set; } = false; 635 | 636 | const string GhostSalt = "Ghost"; 637 | private Dictionary boneDictionary = new Dictionary(); 638 | float centerOffsetLength = 0; 639 | 640 | public BoneGhost(Animator animator, Dictionary boneDictionary, bool useBottomCenter) 641 | { 642 | this.boneDictionary = boneDictionary; 643 | UseBottomCenter = useBottomCenter; 644 | 645 | Dictionary boneParentDictionary 646 | = new Dictionary() 647 | { 648 | { BoneNames.センター, (BoneNames.None, BoneNames.None, BoneNames.全ての親) }, 649 | { BoneNames.左足, (BoneNames.None, BoneNames.None, BoneNames.センター) }, 650 | { BoneNames.左ひざ, (BoneNames.None, BoneNames.None, BoneNames.左足) }, 651 | { BoneNames.左足首, (BoneNames.None, BoneNames.None, BoneNames.左ひざ) }, 652 | { BoneNames.右足, (BoneNames.None, BoneNames.None, BoneNames.センター) }, 653 | { BoneNames.右ひざ, (BoneNames.None, BoneNames.None, BoneNames.右足) }, 654 | { BoneNames.右足首, (BoneNames.None, BoneNames.None, BoneNames.右ひざ) }, 655 | { BoneNames.上半身, (BoneNames.None, BoneNames.None, BoneNames.センター) }, 656 | { BoneNames.上半身2, (BoneNames.None, BoneNames.None, BoneNames.上半身) }, 657 | { BoneNames.首, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, 658 | { BoneNames.頭, (BoneNames.首, BoneNames.上半身2, BoneNames.上半身) }, 659 | { BoneNames.左肩, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, 660 | { BoneNames.左腕, (BoneNames.左肩, BoneNames.上半身2, BoneNames.上半身) }, 661 | { BoneNames.左ひじ, (BoneNames.None, BoneNames.None, BoneNames.左腕) }, 662 | { BoneNames.左手首, (BoneNames.None, BoneNames.None, BoneNames.左ひじ) }, 663 | { BoneNames.左親指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, 664 | { BoneNames.左親指2, (BoneNames.左親指1, BoneNames.None, BoneNames.None) }, 665 | { BoneNames.左人指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, 666 | { BoneNames.左人指2, (BoneNames.左人指1, BoneNames.None, BoneNames.None) }, 667 | { BoneNames.左人指3, (BoneNames.左人指2, BoneNames.None, BoneNames.None) }, 668 | { BoneNames.左中指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, 669 | { BoneNames.左中指2, (BoneNames.左中指1, BoneNames.None, BoneNames.None) }, 670 | { BoneNames.左中指3, (BoneNames.左中指2, BoneNames.None, BoneNames.None) }, 671 | { BoneNames.左薬指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, 672 | { BoneNames.左薬指2, (BoneNames.左薬指1, BoneNames.None, BoneNames.None) }, 673 | { BoneNames.左薬指3, (BoneNames.左薬指2, BoneNames.None, BoneNames.None) }, 674 | { BoneNames.左小指1, (BoneNames.左手首, BoneNames.None, BoneNames.None) }, 675 | { BoneNames.左小指2, (BoneNames.左小指1, BoneNames.None, BoneNames.None) }, 676 | { BoneNames.左小指3, (BoneNames.左小指2, BoneNames.None, BoneNames.None) }, 677 | { BoneNames.右肩, (BoneNames.上半身2, BoneNames.None, BoneNames.上半身) }, 678 | { BoneNames.右腕, (BoneNames.右肩, BoneNames.上半身2, BoneNames.上半身) }, 679 | { BoneNames.右ひじ, (BoneNames.None, BoneNames.None, BoneNames.右腕) }, 680 | { BoneNames.右手首, (BoneNames.None, BoneNames.None, BoneNames.右ひじ) }, 681 | { BoneNames.右親指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, 682 | { BoneNames.右親指2, (BoneNames.右親指1, BoneNames.None, BoneNames.None) }, 683 | { BoneNames.右人指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, 684 | { BoneNames.右人指2, (BoneNames.右人指1, BoneNames.None, BoneNames.None) }, 685 | { BoneNames.右人指3, (BoneNames.右人指2, BoneNames.None, BoneNames.None) }, 686 | { BoneNames.右中指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, 687 | { BoneNames.右中指2, (BoneNames.右中指1, BoneNames.None, BoneNames.None) }, 688 | { BoneNames.右中指3, (BoneNames.右中指2, BoneNames.None, BoneNames.None) }, 689 | { BoneNames.右薬指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, 690 | { BoneNames.右薬指2, (BoneNames.右薬指1, BoneNames.None, BoneNames.None) }, 691 | { BoneNames.右薬指3, (BoneNames.右薬指2, BoneNames.None, BoneNames.None) }, 692 | { BoneNames.右小指1, (BoneNames.右手首, BoneNames.None, BoneNames.None) }, 693 | { BoneNames.右小指2, (BoneNames.右小指1, BoneNames.None, BoneNames.None) }, 694 | { BoneNames.右小指3, (BoneNames.右小指2, BoneNames.None, BoneNames.None) }, 695 | }; 696 | 697 | //Ghostの生成 698 | foreach (BoneNames boneName in boneDictionary.Keys) 699 | { 700 | if (boneName == BoneNames.全ての親 || boneName == BoneNames.左足IK || boneName == BoneNames.右足IK) 701 | { 702 | continue; 703 | } 704 | 705 | if (boneDictionary[boneName] == null) 706 | { 707 | GhostDictionary.Add(boneName, (null, false)); 708 | continue; 709 | } 710 | 711 | Transform ghost = new GameObject(boneDictionary[boneName].name + GhostSalt).transform; 712 | if (boneName == BoneNames.センター && UseBottomCenter) 713 | { 714 | ghost.position = boneDictionary[BoneNames.全ての親].position; 715 | } 716 | else 717 | { 718 | ghost.position = boneDictionary[boneName].position; 719 | } 720 | ghost.rotation = animator.transform.rotation; 721 | GhostDictionary.Add(boneName, (ghost, true)); 722 | } 723 | 724 | //Ghostの親子構造を設定 725 | foreach (BoneNames boneName in boneDictionary.Keys) 726 | { 727 | if (boneName == BoneNames.全ての親 || boneName == BoneNames.左足IK || boneName == BoneNames.右足IK) 728 | { 729 | continue; 730 | } 731 | 732 | if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) 733 | { 734 | continue; 735 | } 736 | 737 | if (boneName == BoneNames.センター) 738 | { 739 | GhostDictionary[boneName].ghost.SetParent(animator.transform); 740 | continue; 741 | } 742 | 743 | if (boneParentDictionary[boneName].optionParent1 != BoneNames.None && boneDictionary[boneParentDictionary[boneName].optionParent1] != null) 744 | { 745 | GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].optionParent1].ghost); 746 | } 747 | else if (boneParentDictionary[boneName].optionParent2 != BoneNames.None && boneDictionary[boneParentDictionary[boneName].optionParent2] != null) 748 | { 749 | GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].optionParent2].ghost); 750 | } 751 | else if (boneParentDictionary[boneName].necessaryParent != BoneNames.None && boneDictionary[boneParentDictionary[boneName].necessaryParent] != null) 752 | { 753 | GhostDictionary[boneName].ghost.SetParent(GhostDictionary[boneParentDictionary[boneName].necessaryParent].ghost); 754 | } 755 | else 756 | { 757 | GhostDictionary[boneName] = (GhostDictionary[boneName].ghost, false); 758 | } 759 | } 760 | 761 | //初期状態を保存 762 | foreach (BoneNames boneName in GhostDictionary.Keys) 763 | { 764 | if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) 765 | { 766 | GhostOriginalLocalPositionDictionary.Add(boneName, Vector3.zero); 767 | GhostOriginalRotationDictionary.Add(boneName, Quaternion.identity); 768 | OriginalRotationDictionary.Add(boneName, Quaternion.identity); 769 | } 770 | else 771 | { 772 | GhostOriginalRotationDictionary.Add(boneName, GhostDictionary[boneName].ghost.rotation); 773 | OriginalRotationDictionary.Add(boneName, boneDictionary[boneName].rotation); 774 | if (boneName == BoneNames.センター && UseBottomCenter) 775 | { 776 | GhostOriginalLocalPositionDictionary.Add(boneName, Vector3.zero); 777 | continue; 778 | } 779 | GhostOriginalLocalPositionDictionary.Add(boneName, GhostDictionary[boneName].ghost.localPosition); 780 | } 781 | } 782 | 783 | centerOffsetLength = Vector3.Distance(boneDictionary[BoneNames.全ての親].position, boneDictionary[BoneNames.センター].position); 784 | } 785 | 786 | public void GhostAll() 787 | { 788 | foreach (BoneNames boneName in GhostDictionary.Keys) 789 | { 790 | if (GhostDictionary[boneName].ghost == null || !GhostDictionary[boneName].enabled) { continue; } 791 | Quaternion transQuaternion = boneDictionary[boneName].rotation * Quaternion.Inverse(OriginalRotationDictionary[boneName]); 792 | GhostDictionary[boneName].ghost.rotation = transQuaternion * GhostOriginalRotationDictionary[boneName]; 793 | if (boneName == BoneNames.センター && UseBottomCenter) 794 | { 795 | GhostDictionary[boneName].ghost.position = boneDictionary[boneName].position - centerOffsetLength * GhostDictionary[boneName].ghost.up; 796 | continue; 797 | } 798 | GhostDictionary[boneName].ghost.position = boneDictionary[boneName].position; 799 | } 800 | } 801 | } 802 | 803 | class MorphRecorder 804 | { 805 | List skinnedMeshRendererList; 806 | //キーはunity上のモーフ名 807 | public Dictionary MorphDrivers { get; private set; } = new Dictionary(); 808 | 809 | public MorphRecorder(Transform model) 810 | { 811 | List searchBlendShapeSkins(Transform t) 812 | { 813 | List skinnedMeshRendererList = new List(); 814 | Queue queue = new Queue(); 815 | queue.Enqueue(t); 816 | while (queue.Count != 0) 817 | { 818 | SkinnedMeshRenderer skinnedMeshRenderer = (queue.Peek() as Transform).GetComponent(); 819 | 820 | if (skinnedMeshRenderer != null && skinnedMeshRenderer.sharedMesh.blendShapeCount != 0) 821 | { 822 | skinnedMeshRendererList.Add(skinnedMeshRenderer); 823 | } 824 | 825 | foreach (Transform childT in (queue.Dequeue() as Transform)) 826 | { 827 | queue.Enqueue(childT); 828 | } 829 | } 830 | 831 | return skinnedMeshRendererList; 832 | } 833 | skinnedMeshRendererList = searchBlendShapeSkins(model); 834 | 835 | foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRendererList) 836 | { 837 | int morphCount = skinnedMeshRenderer.sharedMesh.blendShapeCount; 838 | for (int i = 0; i < morphCount; i++) 839 | { 840 | string morphName = skinnedMeshRenderer.sharedMesh.GetBlendShapeName(i); 841 | ////モーフ名に重複があれば2コ目以降は無視 842 | if (MorphDrivers.Keys.Contains(morphName)) { continue; } 843 | MorphDrivers.Add(morphName, new MorphDriver(skinnedMeshRenderer, i)); 844 | } 845 | } 846 | } 847 | 848 | public void RecrodAllMorph() 849 | { 850 | foreach (MorphDriver morphDriver in MorphDrivers.Values) 851 | { 852 | morphDriver.RecordMorph(); 853 | } 854 | } 855 | 856 | public void TrimMorphNumber() 857 | { 858 | string dot = "."; 859 | Dictionary morphDriversTemp = new Dictionary(); 860 | foreach (string morphName in MorphDrivers.Keys) 861 | { 862 | //正規表現使うより、dot探して整数か見る 863 | if (morphName.Contains(dot) && int.TryParse(morphName.Substring(0, morphName.IndexOf(dot)), out int dummy)) 864 | { 865 | morphDriversTemp.Add(morphName.Substring(morphName.IndexOf(dot) + 1), MorphDrivers[morphName]); 866 | continue; 867 | } 868 | morphDriversTemp.Add(morphName, MorphDrivers[morphName]); 869 | } 870 | MorphDrivers = morphDriversTemp; 871 | } 872 | 873 | public void DisableIntron() 874 | { 875 | foreach (string morphName in MorphDrivers.Keys) 876 | { 877 | for (int i = 0; i < MorphDrivers[morphName].ValueList.Count; i++) 878 | { 879 | //情報がなければ次へ 880 | if (MorphDrivers[morphName].ValueList.Count == 0) { continue; } 881 | //今、前、後が同じなら不必要なので無効化 882 | if (i > 0 883 | && i < MorphDrivers[morphName].ValueList.Count - 1 884 | && MorphDrivers[morphName].ValueList[i].value == MorphDrivers[morphName].ValueList[i - 1].value 885 | && MorphDrivers[morphName].ValueList[i].value == MorphDrivers[morphName].ValueList[i + 1].value) 886 | { 887 | MorphDrivers[morphName].ValueList[i] = (MorphDrivers[morphName].ValueList[i].value, false); 888 | } 889 | } 890 | } 891 | } 892 | 893 | public class MorphDriver 894 | { 895 | const float MorphAmplifier = 0.01f; 896 | public SkinnedMeshRenderer SkinnedMeshRenderer { get; private set; } = new SkinnedMeshRenderer(); 897 | public int MorphIndex { get; private set; } 898 | 899 | public List<(float value, bool enabled)> ValueList { get; private set; } = new List<(float value, bool enabled)>(); 900 | 901 | public MorphDriver(SkinnedMeshRenderer skinnedMeshRenderer, int morphIndex) 902 | { 903 | SkinnedMeshRenderer = skinnedMeshRenderer; 904 | MorphIndex = morphIndex; 905 | } 906 | 907 | public void RecordMorph() 908 | { 909 | ValueList.Add((SkinnedMeshRenderer.GetBlendShapeWeight(MorphIndex) * MorphAmplifier, true)); 910 | } 911 | } 912 | } 913 | } -------------------------------------------------------------------------------- /UnityVMDQuaternionExtentions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public static class UnityVMDQuaternionExtentions 6 | { 7 | public static Quaternion PlusRotation(this Quaternion q1, Quaternion q2) 8 | { 9 | return Quaternion.Euler(q1.eulerAngles + q2.eulerAngles); 10 | } 11 | 12 | public static Quaternion MinusRotation(this Quaternion q1, Quaternion q2) 13 | { 14 | return Quaternion.Euler(q1.eulerAngles - q2.eulerAngles); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VMDRecorderSample.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobosore/UnityVMDRecorder/c3ff4ba855bc6435397d7c3f875b94ba9df616e6/VMDRecorderSample.unitypackage --------------------------------------------------------------------------------