├── 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
--------------------------------------------------------------------------------