mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-05-25 05:40:21 -04:00
220 lines
8.9 KiB
C#
220 lines
8.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using AssetStudio;
|
|
|
|
namespace CubismLive2DExtractor
|
|
{
|
|
class CubismMotion3Converter
|
|
{
|
|
private Dictionary<uint, string> bonePathHash = new Dictionary<uint, string>();
|
|
public List<ImportedKeyframedAnimation> AnimationList { get; protected set; } = new List<ImportedKeyframedAnimation>();
|
|
|
|
public CubismMotion3Converter(GameObject rootGameObject, List<AnimationClip> animationClips)
|
|
{
|
|
var rootTransform = GetTransform(rootGameObject);
|
|
CreateBonePathHash(rootTransform);
|
|
ConvertAnimations(animationClips);
|
|
}
|
|
|
|
public CubismMotion3Converter(List<AnimationClip> animationClips, HashSet<string> partIds, HashSet<string> parameterIds)
|
|
{
|
|
CreateBonePathHash(partIds, pathType: "Parts/");
|
|
CreateBonePathHash(parameterIds, pathType: "Parameters/");
|
|
ConvertAnimations(animationClips);
|
|
}
|
|
|
|
private void ConvertAnimations(List<AnimationClip> animationClips)
|
|
{
|
|
foreach (var animationClip in animationClips)
|
|
{
|
|
var iAnim = new ImportedKeyframedAnimation();
|
|
AnimationList.Add(iAnim);
|
|
iAnim.Name = animationClip.m_Name;
|
|
iAnim.SampleRate = animationClip.m_SampleRate;
|
|
iAnim.Duration = animationClip.m_MuscleClip.m_StopTime;
|
|
var m_Clip = animationClip.m_MuscleClip.m_Clip.data;
|
|
var streamedFrames = m_Clip.m_StreamedClip.ReadData();
|
|
var m_ClipBindingConstant = animationClip.m_ClipBindingConstant;
|
|
for (int frameIndex = 1; frameIndex < streamedFrames.Count - 1; frameIndex++)
|
|
{
|
|
var frame = streamedFrames[frameIndex];
|
|
for (int curveIndex = 0; curveIndex < frame.keyList.Length; curveIndex++)
|
|
{
|
|
ReadStreamedData(iAnim, m_ClipBindingConstant, frame.time, frame.keyList[curveIndex]);
|
|
}
|
|
}
|
|
var m_DenseClip = m_Clip.m_DenseClip;
|
|
var streamCount = m_Clip.m_StreamedClip.curveCount;
|
|
for (int frameIndex = 0; frameIndex < m_DenseClip.m_FrameCount; frameIndex++)
|
|
{
|
|
var time = m_DenseClip.m_BeginTime + frameIndex / m_DenseClip.m_SampleRate;
|
|
var frameOffset = frameIndex * m_DenseClip.m_CurveCount;
|
|
for (int curveIndex = 0; curveIndex < m_DenseClip.m_CurveCount; curveIndex++)
|
|
{
|
|
var index = streamCount + curveIndex;
|
|
ReadCurveData(iAnim, m_ClipBindingConstant, (int)index, time, m_DenseClip.m_SampleArray, (int)frameOffset, curveIndex);
|
|
}
|
|
}
|
|
var m_ConstantClip = m_Clip.m_ConstantClip;
|
|
var denseCount = m_Clip.m_DenseClip.m_CurveCount;
|
|
var time2 = 0.0f;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int curveIndex = 0; curveIndex < m_ConstantClip.data.Length; curveIndex++)
|
|
{
|
|
var index = streamCount + denseCount + curveIndex;
|
|
ReadCurveData(iAnim, m_ClipBindingConstant, (int)index, time2, m_ConstantClip.data, 0, curveIndex);
|
|
}
|
|
time2 = animationClip.m_MuscleClip.m_StopTime;
|
|
}
|
|
foreach (var m_Event in animationClip.m_Events)
|
|
{
|
|
iAnim.Events.Add(new ImportedEvent
|
|
{
|
|
time = m_Event.time,
|
|
value = m_Event.data
|
|
});
|
|
}
|
|
|
|
if (iAnim.TrackList.Count == 0 || iAnim.Events.Count == 0)
|
|
{
|
|
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadStreamedData(ImportedKeyframedAnimation iAnim, AnimationClipBindingConstant m_ClipBindingConstant, float time, StreamedClip.StreamedCurveKey curveKey)
|
|
{
|
|
var binding = m_ClipBindingConstant.FindBinding(curveKey.index);
|
|
GetLive2dPath(binding, out var target, out var boneName);
|
|
if (string.IsNullOrEmpty(boneName))
|
|
{
|
|
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
|
|
return;
|
|
}
|
|
|
|
var track = iAnim.FindTrack(boneName);
|
|
track.Target = target;
|
|
track.Curve.Add(new ImportedKeyframe<float>(time, curveKey.value, curveKey.inSlope, curveKey.outSlope, curveKey.coeff));
|
|
}
|
|
|
|
private void ReadCurveData(ImportedKeyframedAnimation iAnim, AnimationClipBindingConstant m_ClipBindingConstant, int index, float time, float[] data, int offset, int curveIndex)
|
|
{
|
|
var binding = m_ClipBindingConstant.FindBinding(index);
|
|
GetLive2dPath(binding, out var target, out var boneName);
|
|
if (string.IsNullOrEmpty(boneName))
|
|
{
|
|
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
|
|
return;
|
|
}
|
|
|
|
var track = iAnim.FindTrack(boneName);
|
|
track.Target = target;
|
|
var value = data[curveIndex];
|
|
track.Curve.Add(new ImportedKeyframe<float>(time, value, 0, 0, null));
|
|
}
|
|
|
|
private void GetLive2dPath(GenericBinding binding, out string target, out string id)
|
|
{
|
|
var path = binding.path;
|
|
id = null;
|
|
target = null;
|
|
if (path != 0 && bonePathHash.TryGetValue(path, out var boneName))
|
|
{
|
|
var index = boneName.LastIndexOf('/');
|
|
id = boneName.Substring(index + 1);
|
|
target = boneName.Substring(0, index);
|
|
if (target == "Parameters")
|
|
{
|
|
target = "Parameter";
|
|
}
|
|
else if (target == "Parts")
|
|
{
|
|
target = "PartOpacity";
|
|
}
|
|
}
|
|
else if (binding.script.TryGet(out MonoScript script))
|
|
{
|
|
switch (script.m_ClassName)
|
|
{
|
|
case "CubismRenderController":
|
|
target = "Model";
|
|
id = "Opacity";
|
|
break;
|
|
case "CubismEyeBlinkController":
|
|
target = "Model";
|
|
id = "EyeBlink";
|
|
break;
|
|
case "CubismMouthController":
|
|
target = "Model";
|
|
id = "LipSync";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private Transform GetTransform(GameObject gameObject)
|
|
{
|
|
foreach (var m_Component in gameObject.m_Components)
|
|
{
|
|
if (m_Component.TryGet(out Transform m_Transform))
|
|
{
|
|
return m_Transform;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void CreateBonePathHash(HashSet<string> ids, string pathType)
|
|
{
|
|
foreach (var id in ids)
|
|
{
|
|
var name = pathType + id;;
|
|
bonePathHash[GetCRC(name)] = name;
|
|
int index;
|
|
while ((index = name.IndexOf("/", StringComparison.Ordinal)) >= 0)
|
|
{
|
|
name = name.Substring(index + 1);
|
|
bonePathHash[GetCRC(name)] = name;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateBonePathHash(Transform m_Transform)
|
|
{
|
|
var name = GetTransformPath(m_Transform);
|
|
bonePathHash[GetCRC(name)] = name;
|
|
int index;
|
|
while ((index = name.IndexOf("/", StringComparison.Ordinal)) >= 0)
|
|
{
|
|
name = name.Substring(index + 1);
|
|
bonePathHash[GetCRC(name)] = name;
|
|
}
|
|
foreach (var pptr in m_Transform.m_Children)
|
|
{
|
|
if (pptr.TryGet(out var child))
|
|
CreateBonePathHash(child);
|
|
}
|
|
}
|
|
|
|
private static uint GetCRC(string name)
|
|
{
|
|
var bytes = Encoding.UTF8.GetBytes(name);
|
|
return SevenZip.CRC.CalculateDigest(bytes, 0, (uint)bytes.Length);
|
|
}
|
|
|
|
private static string GetTransformPath(Transform transform)
|
|
{
|
|
transform.m_GameObject.TryGet(out var m_GameObject);
|
|
if (transform.m_Father.TryGet(out var father))
|
|
{
|
|
return GetTransformPath(father) + "/" + m_GameObject.m_Name;
|
|
}
|
|
|
|
return m_GameObject.m_Name;
|
|
}
|
|
}
|
|
}
|