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