Improve integration with Live2D assets

This commit is contained in:
VaDiM
2024-12-07 15:35:01 +03:00
parent 59db27de3a
commit 064f5cbe57
37 changed files with 1425 additions and 648 deletions

View File

@ -6,41 +6,46 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AssetStudio;
using CubismLive2DExtractor.CubismUnityClasses;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static CubismLive2DExtractor.CubismParsers;
using Object = AssetStudio.Object;
namespace CubismLive2DExtractor
{
public sealed class Live2DExtractor
{
private List<MonoBehaviour> Expressions { get; set; }
public static Dictionary<MonoBehaviour, CubismModel> MocDict { get; set; }
public static AssemblyLoader Assembly { get; set; }
public CubismModel Model { get; set; }
private List<MonoBehaviour> FadeMotions { get; set; }
private List<GameObject> GameObjects { get; set; }
private List<AnimationClip> AnimationClips { get; set; }
private List<Texture2D> Texture2Ds { get; set; }
private HashSet<string> EyeBlinkParameters { get; set; }
private HashSet<string> LipSyncParameters { get; set; }
private HashSet<string> ParameterNames { get; set; }
private HashSet<string> PartNames { get; set; }
private MonoBehaviour MocMono { get; set; }
private MonoBehaviour PhysicsMono { get; set; }
private MonoBehaviour FadeMotionLst { get; set; }
private List<MonoBehaviour> Expressions { get; set; }
private List<MonoBehaviour> ParametersCdi { get; set; }
private List<MonoBehaviour> PartsCdi { get; set; }
private List<MonoBehaviour> PoseParts { get; set; }
private List<Texture2D> Texture2Ds { get; set; }
public MonoBehaviour MocMono { get; set; }
private MonoBehaviour PhysicsMono { get; set; }
private MonoBehaviour FadeMotionLst { get; set; }
private MonoBehaviour ExpressionLst { get; set; }
private HashSet<string> ParameterNames { get; set; }
private HashSet<string> PartNames { get; set; }
private HashSet<string> EyeBlinkParameters { get; set; }
private HashSet<string> LipSyncParameters { get; set; }
public Live2DExtractor(IGrouping<string, AssetStudio.Object> assets, List<AnimationClip> inClipMotions = null, List<MonoBehaviour> inFadeMotions = null, MonoBehaviour inFadeMotionLst = null)
public Live2DExtractor(List<Object> assets, List<AnimationClip> inClipMotions = null, List<MonoBehaviour> inFadeMotions = null, MonoBehaviour inFadeMotionLst = null)
{
Expressions = new List<MonoBehaviour>();
FadeMotions = inFadeMotions ?? new List<MonoBehaviour>();
AnimationClips = inClipMotions ?? new List<AnimationClip>();
GameObjects = new List<GameObject>();
Texture2Ds = new List<Texture2D>();
EyeBlinkParameters = new HashSet<string>();
LipSyncParameters = new HashSet<string>();
@ -50,6 +55,12 @@ namespace CubismLive2DExtractor
ParametersCdi = new List<MonoBehaviour>();
PartsCdi = new List<MonoBehaviour>();
PoseParts = new List<MonoBehaviour>();
var renderTextureSet = new HashSet<Texture2D>();
var isRenderReadable = true;
var searchRenderTextures = true;
var searchModelParamCdi = true;
var searchModelPartCdi = true;
var searchPoseParts = true;
Logger.Debug("Sorting model assets..");
foreach (var asset in assets)
@ -63,12 +74,53 @@ namespace CubismLive2DExtractor
{
case "CubismMoc":
MocMono = m_MonoBehaviour;
Model = MocDict[MocMono];
if (Model != null)
{
PhysicsMono = Model.PhysicsController;
if (inFadeMotionLst == null && TryGetFadeList(Model.FadeController, out var fadeMono))
{
FadeMotionLst = inFadeMotionLst = fadeMono;
}
if (TryGetExpressionList(Model.ExpressionController, out var expressionMono))
{
ExpressionLst = expressionMono;
}
if (Model.RenderTextureList.Count > 0)
{
var renderList = Model.RenderTextureList;
foreach (var renderMono in renderList)
{
if (!TryGetRenderTexture(renderMono, out var tex))
break;
renderTextureSet.Add(tex);
}
searchRenderTextures = renderTextureSet.Count == 0;
}
if (Model.ParamDisplayInfoList.Count > 0)
{
ParametersCdi = Model.ParamDisplayInfoList;
searchModelParamCdi = false;
}
if (Model.PartDisplayInfoList.Count > 0)
{
PartsCdi = Model.PartDisplayInfoList;
searchModelPartCdi = false;
}
if (Model.PosePartList.Count > 0)
{
PoseParts = Model.PosePartList;
searchPoseParts = false;
}
}
break;
case "CubismPhysicsController":
PhysicsMono = m_MonoBehaviour;
if (PhysicsMono == null)
PhysicsMono = m_MonoBehaviour;
break;
case "CubismExpressionData":
Expressions.Add(m_MonoBehaviour);
if (ExpressionLst == null)
Expressions.Add(m_MonoBehaviour);
break;
case "CubismFadeMotionData":
if (inFadeMotions == null && inFadeMotionLst == null)
@ -107,23 +159,31 @@ namespace CubismLive2DExtractor
}
break;
case "CubismDisplayInfoParameterName":
if (m_MonoBehaviour.m_GameObject.TryGet(out _))
if (searchModelParamCdi && m_MonoBehaviour.m_GameObject.TryGet(out _))
{
ParametersCdi.Add(m_MonoBehaviour);
}
break;
case "CubismDisplayInfoPartName":
if (m_MonoBehaviour.m_GameObject.TryGet(out _))
if (searchModelPartCdi && m_MonoBehaviour.m_GameObject.TryGet(out _))
{
PartsCdi.Add(m_MonoBehaviour);
}
break;
case "CubismPosePart":
if (m_MonoBehaviour.m_GameObject.TryGet(out _))
if (searchPoseParts && m_MonoBehaviour.m_GameObject.TryGet(out _))
{
PoseParts.Add(m_MonoBehaviour);
}
break;
case "CubismRenderer":
if (searchRenderTextures && isRenderReadable)
{
isRenderReadable = TryGetRenderTexture(m_MonoBehaviour, out var renderTex);
if (isRenderReadable)
renderTextureSet.Add(renderTex);
}
break;
}
}
break;
@ -133,41 +193,43 @@ namespace CubismLive2DExtractor
AnimationClips.Add(m_AnimationClip);
}
break;
case GameObject m_GameObject:
GameObjects.Add(m_GameObject);
break;
case Texture2D m_Texture2D:
Texture2Ds.Add(m_Texture2D);
break;
}
}
if (renderTextureSet.Count > 0)
{
Texture2Ds = renderTextureSet.ToList();
}
}
public void ExtractCubismModel(string destPath, string modelName, Live2DMotionMode motionMode, AssemblyLoader assemblyLoader, bool forceBezier = false, int parallelTaskCount = 1)
public void ExtractCubismModel(string destPath, Live2DMotionMode motionMode, bool forceBezier = false, int parallelTaskCount = 1)
{
Directory.CreateDirectory(destPath);
var modelName = Model?.Name ?? "model";
#region moc3
using (var cubismModel = new CubismModel(MocMono))
using (var cubismMoc = new CubismMoc(MocMono))
{
var sb = new StringBuilder();
sb.AppendLine("Model Stats:");
sb.AppendLine($"SDK Version: {cubismModel.VersionDescription}");
if (cubismModel.Version > 0)
sb.AppendLine($"SDK Version: {cubismMoc.VersionDescription}");
if (cubismMoc.Version > 0)
{
sb.AppendLine($"Canvas Width: {cubismModel.CanvasWidth}");
sb.AppendLine($"Canvas Height: {cubismModel.CanvasHeight}");
sb.AppendLine($"Center X: {cubismModel.CentralPosX}");
sb.AppendLine($"Center Y: {cubismModel.CentralPosY}");
sb.AppendLine($"Pixel Per Unit: {cubismModel.PixelPerUnit}");
sb.AppendLine($"Part Count: {cubismModel.PartCount}");
sb.AppendLine($"Parameter Count: {cubismModel.ParamCount}");
sb.AppendLine($"Canvas Width: {cubismMoc.CanvasWidth}");
sb.AppendLine($"Canvas Height: {cubismMoc.CanvasHeight}");
sb.AppendLine($"Center X: {cubismMoc.CentralPosX}");
sb.AppendLine($"Center Y: {cubismMoc.CentralPosY}");
sb.AppendLine($"Pixel Per Unit: {cubismMoc.PixelPerUnit}");
sb.AppendLine($"Part Count: {cubismMoc.PartCount}");
sb.AppendLine($"Parameter Count: {cubismMoc.ParamCount}");
Logger.Debug(sb.ToString());
ParameterNames = cubismModel.ParamNames;
PartNames = cubismModel.PartNames;
ParameterNames = cubismMoc.ParamNames;
PartNames = cubismMoc.PartNames;
}
cubismModel.SaveMoc3($"{destPath}{modelName}.moc3");
cubismMoc.SaveMoc3($"{destPath}{modelName}.moc3");
}
#endregion
@ -204,34 +266,13 @@ namespace CubismLive2DExtractor
textures.UnionWith(textureBag);
#endregion
#region physics3.json
var isPhysicsExported = false;
if (PhysicsMono != null)
{
var physicsDict = ParseMonoBehaviour(PhysicsMono, CubismMonoBehaviourType.Physics, assemblyLoader);
if (physicsDict != null)
{
try
{
var buff = ParsePhysics(physicsDict);
File.WriteAllText($"{destPath}{modelName}.physics3.json", buff);
isPhysicsExported = true;
}
catch (Exception e)
{
Logger.Warning($"Error in parsing physics data: {e.Message}");
}
}
}
#endregion
#region cdi3.json
var isCdiExported = false;
if (ParametersCdi.Count > 0 || PartsCdi.Count > 0)
{
try
{
isCdiExported = ExportCdiJson(destPath, modelName, assemblyLoader);
isCdiExported = ExportCdiJson(destPath, modelName);
}
catch (Exception e)
{
@ -243,26 +284,34 @@ namespace CubismLive2DExtractor
#region motion3.json
var motions = new SortedDictionary<string, JArray>();
var destMotionPath = Path.Combine(destPath, "motions") + Path.DirectorySeparatorChar;
var motionFps = 0f;
if (motionMode == Live2DMotionMode.MonoBehaviour && FadeMotionLst != null) //Fade motions from Fade Motion List
{
Logger.Debug("Motion export method: MonoBehaviour (Fade motion)");
var fadeMotionLstDict = ParseMonoBehaviour(FadeMotionLst, CubismMonoBehaviourType.FadeMotionList, assemblyLoader);
var fadeMotionLstDict = ParseMonoBehaviour(FadeMotionLst, CubismMonoBehaviourType.FadeMotionList, Assembly);
if (fadeMotionLstDict != null)
{
CubismObjectList.AssetsFile = FadeMotionLst.assetsFile;
var fadeMotionAssetList = JsonConvert.DeserializeObject<CubismObjectList>(JsonConvert.SerializeObject(fadeMotionLstDict)).GetFadeMotionAssetList();
if (fadeMotionAssetList?.Count > 0)
var cubismFadeList = JsonConvert.DeserializeObject<CubismFadeMotionList>(JsonConvert.SerializeObject(fadeMotionLstDict));
var fadeMotionAssetList = new List<MonoBehaviour>();
foreach (var motionPPtr in cubismFadeList.CubismFadeMotionObjects)
{
if (motionPPtr.TryGet<MonoBehaviour>(out var fadeMono, FadeMotionLst.assetsFile))
{
fadeMotionAssetList.Add(fadeMono);
}
}
if (fadeMotionAssetList.Count > 0)
{
FadeMotions = fadeMotionAssetList;
Logger.Debug($"\"{FadeMotionLst.m_Name}\": found {fadeMotionAssetList.Count} motion(s)");
}
}
}
if (motionMode == Live2DMotionMode.MonoBehaviour && FadeMotions.Count > 0) //motion from MonoBehaviour
{
ExportFadeMotions(destMotionPath, assemblyLoader, forceBezier, motions);
ExportFadeMotions(destMotionPath, forceBezier, motions, ref motionFps);
}
if (motions.Count == 0) //motion from AnimationClip
@ -274,16 +323,10 @@ namespace CubismLive2DExtractor
exportMethod += "V2";
converter = new CubismMotion3Converter(AnimationClips, PartNames, ParameterNames);
}
else if (GameObjects.Count > 0) //AnimationClipV1
else if (Model?.ModelGameObject != null) //AnimationClipV1
{
exportMethod += "V1";
var rootTransform = GameObjects[0].m_Transform;
while (rootTransform.m_Father.TryGet(out var m_Father))
{
rootTransform = m_Father;
}
rootTransform.m_GameObject.TryGet(out var rootGameObject);
converter = new CubismMotion3Converter(rootGameObject, AnimationClips);
converter = new CubismMotion3Converter(Model.ModelGameObject, AnimationClips);
}
if (motionMode == Live2DMotionMode.MonoBehaviour)
@ -294,7 +337,7 @@ namespace CubismLive2DExtractor
}
Logger.Debug($"Motion export method: {exportMethod}");
ExportClipMotions(destMotionPath, converter, forceBezier, motions);
ExportClipMotions(destMotionPath, converter, forceBezier, motions, ref motionFps);
}
if (motions.Count == 0)
@ -311,6 +354,30 @@ namespace CubismLive2DExtractor
var expressions = new JArray();
var destExpressionPath = Path.Combine(destPath, "expressions") + Path.DirectorySeparatorChar;
if (ExpressionLst != null) //Expressions from Expression List
{
Logger.Debug("Parsing expression list..");
var expLstDict = ParseMonoBehaviour(ExpressionLst, CubismMonoBehaviourType.ExpressionList, Assembly);
if (expLstDict != null)
{
var cubismExpList = JsonConvert.DeserializeObject<CubismExpressionList>(JsonConvert.SerializeObject(expLstDict));
var expAssetList = new List<MonoBehaviour>();
foreach (var expPPtr in cubismExpList.CubismExpressionObjects)
{
if (expPPtr.TryGet<MonoBehaviour>(out var expMono, ExpressionLst.assetsFile))
{
expAssetList.Add(expMono);
}
}
if (expAssetList.Count > 0)
{
Expressions = expAssetList;
Logger.Debug($"\"{ExpressionLst.m_Name}\": found {expAssetList.Count} expression(s)");
}
}
}
if (Expressions.Count > 0)
{
Directory.CreateDirectory(destExpressionPath);
@ -318,7 +385,7 @@ namespace CubismLive2DExtractor
foreach (var monoBehaviour in Expressions)
{
var expressionName = monoBehaviour.m_Name.Replace(".exp3", "");
var expressionDict = ParseMonoBehaviour(monoBehaviour, CubismMonoBehaviourType.Expression, assemblyLoader);
var expressionDict = ParseMonoBehaviour(monoBehaviour, CubismMonoBehaviourType.Expression, Assembly);
if (expressionDict == null)
continue;
@ -339,7 +406,7 @@ namespace CubismLive2DExtractor
{
try
{
isPoseExported = ExportPoseJson(destPath, modelName, assemblyLoader);
isPoseExported = ExportPoseJson(destPath, modelName);
}
catch (Exception e)
{
@ -348,6 +415,27 @@ namespace CubismLive2DExtractor
}
#endregion
#region physics3.json
var isPhysicsExported = false;
if (PhysicsMono != null)
{
var physicsDict = ParseMonoBehaviour(PhysicsMono, CubismMonoBehaviourType.Physics, Assembly);
if (physicsDict != null)
{
try
{
var buff = ParsePhysics(physicsDict, motionFps);
File.WriteAllText($"{destPath}{modelName}.physics3.json", buff);
isPhysicsExported = true;
}
catch (Exception e)
{
Logger.Warning($"Error in parsing physics data: {e.Message}");
}
}
}
#endregion
#region model3.json
var groups = new List<CubismModel3Json.SerializableGroup>();
@ -402,20 +490,21 @@ namespace CubismLive2DExtractor
#endregion
}
private void ExportFadeMotions(string destMotionPath, AssemblyLoader assemblyLoader, bool forceBezier, SortedDictionary<string, JArray> motions)
private void ExportFadeMotions(string destMotionPath, bool forceBezier, SortedDictionary<string, JArray> motions, ref float fps)
{
Directory.CreateDirectory(destMotionPath);
foreach (var fadeMotionMono in FadeMotions)
{
var fadeMotionDict = ParseMonoBehaviour(fadeMotionMono, CubismMonoBehaviourType.FadeMotion, assemblyLoader);
var fadeMotionDict = ParseMonoBehaviour(fadeMotionMono, CubismMonoBehaviourType.FadeMotion, Assembly);
if (fadeMotionDict == null)
continue;
var fadeMotion = JsonConvert.DeserializeObject<CubismFadeMotion>(JsonConvert.SerializeObject(fadeMotionDict));
var fadeMotion = JsonConvert.DeserializeObject<CubismFadeMotionData>(JsonConvert.SerializeObject(fadeMotionDict));
if (fadeMotion.ParameterIds.Length == 0)
continue;
var motionJson = new CubismMotion3Json(fadeMotion, ParameterNames, PartNames, forceBezier);
fps = motionJson.Meta.Fps;
var animName = Path.GetFileNameWithoutExtension(fadeMotion.m_Name);
if (motions.ContainsKey(animName))
@ -430,7 +519,7 @@ namespace CubismLive2DExtractor
}
}
private static void ExportClipMotions(string destMotionPath, CubismMotion3Converter converter, bool forceBezier, SortedDictionary<string, JArray> motions)
private static void ExportClipMotions(string destMotionPath, CubismMotion3Converter converter, bool forceBezier, SortedDictionary<string, JArray> motions, ref float fps)
{
if (converter == null)
return;
@ -448,7 +537,8 @@ namespace CubismLive2DExtractor
continue;
}
var motionJson = new CubismMotion3Json(animation, forceBezier);
fps = motionJson.Meta.Fps;
if (motions.ContainsKey(animName))
{
animName = $"{animName}_{animation.GetHashCode()}";
@ -462,12 +552,12 @@ namespace CubismLive2DExtractor
}
}
private bool ExportPoseJson(string destPath, string modelName, AssemblyLoader assemblyLoader)
private bool ExportPoseJson(string destPath, string modelName)
{
var groupDict = new SortedDictionary<int, List<CubismPose3Json.ControlNode>>();
foreach (var posePartMono in PoseParts)
{
var posePartDict = ParseMonoBehaviour(posePartMono, CubismMonoBehaviourType.PosePart, assemblyLoader);
var posePartDict = ParseMonoBehaviour(posePartMono, CubismMonoBehaviourType.PosePart, Assembly);
if (posePartDict == null)
break;
@ -507,7 +597,7 @@ namespace CubismLive2DExtractor
return true;
}
private bool ExportCdiJson(string destPath, string modelName, AssemblyLoader assemblyLoader)
private bool ExportCdiJson(string destPath, string modelName)
{
var cdiJson = new CubismCdi3Json
{
@ -518,7 +608,7 @@ namespace CubismLive2DExtractor
var parameters = new SortedSet<CubismCdi3Json.ParamGroupArray>();
foreach (var paramMono in ParametersCdi)
{
var displayName = GetDisplayName(paramMono, assemblyLoader);
var displayName = GetDisplayName(paramMono);
if (displayName == null)
break;
@ -536,7 +626,7 @@ namespace CubismLive2DExtractor
var parts = new SortedSet<CubismCdi3Json.PartArray>();
foreach (var partMono in PartsCdi)
{
var displayName = GetDisplayName(partMono, assemblyLoader);
var displayName = GetDisplayName(partMono);
if (displayName == null)
break;
@ -557,9 +647,9 @@ namespace CubismLive2DExtractor
return true;
}
private static string GetDisplayName(MonoBehaviour cdiMono, AssemblyLoader assemblyLoader)
private string GetDisplayName(MonoBehaviour cdiMono)
{
var dict = ParseMonoBehaviour(cdiMono, CubismMonoBehaviourType.DisplayInfo, assemblyLoader);
var dict = ParseMonoBehaviour(cdiMono, CubismMonoBehaviourType.DisplayInfo, Assembly);
if (dict == null)
return null;
@ -571,5 +661,44 @@ namespace CubismLive2DExtractor
}
return name;
}
private bool TryGetFadeList(MonoBehaviour m_MonoBehaviour, out MonoBehaviour listMono)
{
return TryGetAsset(m_MonoBehaviour, CubismMonoBehaviourType.FadeController, "CubismFadeMotionList", out listMono);
}
private bool TryGetExpressionList(MonoBehaviour m_MonoBehaviour, out MonoBehaviour listMono)
{
return TryGetAsset(m_MonoBehaviour, CubismMonoBehaviourType.ExpressionController, "ExpressionsList", out listMono);
}
private bool TryGetRenderTexture(MonoBehaviour m_MonoBehaviour, out Texture2D renderTex)
{
return TryGetAsset(m_MonoBehaviour, CubismMonoBehaviourType.RenderTexture, "_mainTexture", out renderTex);
}
private bool TryGetAsset<T>(MonoBehaviour m_MonoBehaviour, CubismMonoBehaviourType cubismMonoType, string pptrField, out T result) where T : Object
{
result = null;
if (m_MonoBehaviour == null)
return false;
var pptrDict = (OrderedDictionary)ParseMonoBehaviour(m_MonoBehaviour, cubismMonoType, Assembly)?[pptrField];
if (pptrDict == null)
return false;
var resultPPtr = GeneratePPtr<T>(pptrDict, m_MonoBehaviour.assetsFile);
return resultPPtr.TryGet(out result);
}
private PPtr<T> GeneratePPtr<T>(OrderedDictionary pptrDict, SerializedFile assetsFile = null) where T : Object
{
return new PPtr<T>
{
m_FileID = (int)pptrDict["m_FileID"],
m_PathID = (long)pptrDict["m_PathID"],
AssetsFile = assetsFile
};
}
}
}