mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-05-25 05:40:21 -04:00
Add option to export Live2D Cubism models
This commit is contained in:
parent
6d41693b85
commit
aea6cbc97f
@ -21,6 +21,7 @@ namespace AssetStudioCLI.Options
|
|||||||
ExportRaw,
|
ExportRaw,
|
||||||
Dump,
|
Dump,
|
||||||
Info,
|
Info,
|
||||||
|
ExportLive2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum AssetGroupOption
|
internal enum AssetGroupOption
|
||||||
@ -132,11 +133,12 @@ namespace AssetStudioCLI.Options
|
|||||||
optionDefaultValue: WorkMode.Export,
|
optionDefaultValue: WorkMode.Export,
|
||||||
optionName: "-m, --mode <value>",
|
optionName: "-m, --mode <value>",
|
||||||
optionDescription: "Specify working mode\n" +
|
optionDescription: "Specify working mode\n" +
|
||||||
"<Value: export(default) | exportRaw | dump | info>\n" +
|
"<Value: export(default) | exportRaw | dump | info | live2d>\n" +
|
||||||
"Export - Exports converted assets\n" +
|
"Export - Exports converted assets\n" +
|
||||||
"ExportRaw - Exports raw data\n" +
|
"ExportRaw - Exports raw data\n" +
|
||||||
"Dump - Makes asset dumps\n" +
|
"Dump - Makes asset dumps\n" +
|
||||||
"Info - Loads file(s), shows the number of supported for export assets and exits\n" +
|
"Info - Loads file(s), shows the number of supported for export assets and exits\n" +
|
||||||
|
"Live2D - Exports Live2D Cubism 3 models\n" +
|
||||||
"Example: \"-m info\"\n",
|
"Example: \"-m info\"\n",
|
||||||
optionHelpGroup: HelpGroups.General
|
optionHelpGroup: HelpGroups.General
|
||||||
);
|
);
|
||||||
@ -414,6 +416,17 @@ namespace AssetStudioCLI.Options
|
|||||||
case "info":
|
case "info":
|
||||||
o_workMode.Value = WorkMode.Info;
|
o_workMode.Value = WorkMode.Info;
|
||||||
break;
|
break;
|
||||||
|
case "live2d":
|
||||||
|
o_workMode.Value = WorkMode.ExportLive2D;
|
||||||
|
o_exportAssetTypes.Value = new List<ClassIDType>()
|
||||||
|
{
|
||||||
|
ClassIDType.AnimationClip,
|
||||||
|
ClassIDType.GameObject,
|
||||||
|
ClassIDType.MonoBehaviour,
|
||||||
|
ClassIDType.Texture2D,
|
||||||
|
ClassIDType.Transform,
|
||||||
|
};
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n");
|
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n");
|
||||||
Console.WriteLine(o_workMode.Description);
|
Console.WriteLine(o_workMode.Description);
|
||||||
@ -422,6 +435,11 @@ namespace AssetStudioCLI.Options
|
|||||||
break;
|
break;
|
||||||
case "-t":
|
case "-t":
|
||||||
case "--asset-type":
|
case "--asset-type":
|
||||||
|
if (o_workMode.Value == WorkMode.ExportLive2D)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var splittedTypes = ValueSplitter(value);
|
var splittedTypes = ValueSplitter(value);
|
||||||
o_exportAssetTypes.Value = new List<ClassIDType>();
|
o_exportAssetTypes.Value = new List<ClassIDType>();
|
||||||
foreach (var type in splittedTypes)
|
foreach (var type in splittedTypes)
|
||||||
@ -773,29 +791,37 @@ namespace AssetStudioCLI.Options
|
|||||||
sb.AppendLine("[Current Options]");
|
sb.AppendLine("[Current Options]");
|
||||||
sb.AppendLine($"# Working Mode: {o_workMode}");
|
sb.AppendLine($"# Working Mode: {o_workMode}");
|
||||||
sb.AppendLine($"# Input Path: \"{inputPath}\"");
|
sb.AppendLine($"# Input Path: \"{inputPath}\"");
|
||||||
if (o_workMode.Value != WorkMode.Info)
|
switch (o_workMode.Value)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"# Output Path: \"{o_outputFolder}\"");
|
case WorkMode.Info:
|
||||||
sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}");
|
sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}");
|
||||||
sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}");
|
sb.AppendLine($"# Log Level: {o_logLevel}");
|
||||||
sb.AppendLine($"# Export Image Format: {o_imageFormat}");
|
sb.AppendLine($"# Log Output: {o_logOutput}");
|
||||||
sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
|
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
|
||||||
sb.AppendLine($"# Log Level: {o_logLevel}");
|
sb.AppendLine(ShowCurrentFilter());
|
||||||
sb.AppendLine($"# Log Output: {o_logOutput}");
|
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
|
||||||
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
|
break;
|
||||||
sb.AppendLine(ShowCurrentFilter());
|
case WorkMode.ExportLive2D:
|
||||||
sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\"");
|
sb.AppendLine($"# Output Path: \"{o_outputFolder}\"");
|
||||||
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
|
sb.AppendLine($"# Log Level: {o_logLevel}");
|
||||||
sb.AppendLine($"# Restore TextAsset extension: {!f_notRestoreExtensionName.Value}");
|
sb.AppendLine($"# Log Output: {o_logOutput}");
|
||||||
}
|
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
|
||||||
else
|
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
|
||||||
{
|
break;
|
||||||
sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}");
|
default:
|
||||||
sb.AppendLine($"# Log Level: {o_logLevel}");
|
sb.AppendLine($"# Output Path: \"{o_outputFolder}\"");
|
||||||
sb.AppendLine($"# Log Output: {o_logOutput}");
|
sb.AppendLine($"# Export Asset Type(s): {string.Join(", ", o_exportAssetTypes.Value)}");
|
||||||
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
|
sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}");
|
||||||
sb.AppendLine(ShowCurrentFilter());
|
sb.AppendLine($"# Export Image Format: {o_imageFormat}");
|
||||||
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
|
sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
|
||||||
|
sb.AppendLine($"# Log Level: {o_logLevel}");
|
||||||
|
sb.AppendLine($"# Log Output: {o_logOutput}");
|
||||||
|
sb.AppendLine($"# Export Asset List: {o_exportAssetList}");
|
||||||
|
sb.AppendLine(ShowCurrentFilter());
|
||||||
|
sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\"");
|
||||||
|
sb.AppendLine($"# Unity Version: \"{o_unityVersion}\"");
|
||||||
|
sb.AppendLine($"# Restore TextAsset extension: {!f_notRestoreExtensionName.Value}");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
sb.AppendLine("======");
|
sb.AppendLine("======");
|
||||||
Logger.Info(sb.ToString());
|
Logger.Info(sb.ToString());
|
||||||
|
@ -36,7 +36,7 @@ namespace AssetStudioCLI
|
|||||||
if (studio.LoadAssets())
|
if (studio.LoadAssets())
|
||||||
{
|
{
|
||||||
studio.ParseAssets();
|
studio.ParseAssets();
|
||||||
if (options.filterBy != FilterBy.None)
|
if (options.filterBy != FilterBy.None && options.o_workMode.Value != WorkMode.ExportLive2D)
|
||||||
{
|
{
|
||||||
studio.FilterAssets();
|
studio.FilterAssets();
|
||||||
}
|
}
|
||||||
@ -44,12 +44,18 @@ namespace AssetStudioCLI
|
|||||||
{
|
{
|
||||||
studio.ExportAssetList();
|
studio.ExportAssetList();
|
||||||
}
|
}
|
||||||
if (options.o_workMode.Value == WorkMode.Info)
|
switch (options.o_workMode.Value)
|
||||||
{
|
{
|
||||||
studio.ShowExportableAssetsInfo();
|
case WorkMode.Info:
|
||||||
return;
|
studio.ShowExportableAssetsInfo();
|
||||||
|
break;
|
||||||
|
case WorkMode.ExportLive2D:
|
||||||
|
studio.ExportLive2D();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
studio.ExportAssets();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
studio.ExportAssets();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -6,6 +6,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using static AssetStudioCLI.Exporter;
|
using static AssetStudioCLI.Exporter;
|
||||||
|
using static CubismLive2DExtractor.Live2DExtractor;
|
||||||
using Ansi = AssetStudioCLI.CLIAnsiColors;
|
using Ansi = AssetStudioCLI.CLIAnsiColors;
|
||||||
|
|
||||||
namespace AssetStudioCLI
|
namespace AssetStudioCLI
|
||||||
@ -14,6 +15,7 @@ namespace AssetStudioCLI
|
|||||||
{
|
{
|
||||||
public AssetsManager assetsManager = new AssetsManager();
|
public AssetsManager assetsManager = new AssetsManager();
|
||||||
public List<AssetItem> parsedAssetsList = new List<AssetItem>();
|
public List<AssetItem> parsedAssetsList = new List<AssetItem>();
|
||||||
|
private static Dictionary<AssetStudio.Object, string> containers = new Dictionary<AssetStudio.Object, string>();
|
||||||
private readonly CLIOptions options;
|
private readonly CLIOptions options;
|
||||||
|
|
||||||
public Studio(CLIOptions cliOptions)
|
public Studio(CLIOptions cliOptions)
|
||||||
@ -51,7 +53,6 @@ namespace AssetStudioCLI
|
|||||||
Logger.Info("Parse assets...");
|
Logger.Info("Parse assets...");
|
||||||
|
|
||||||
var fileAssetsList = new List<AssetItem>();
|
var fileAssetsList = new List<AssetItem>();
|
||||||
var containers = new Dictionary<AssetStudio.Object, string>();
|
|
||||||
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
|
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
|
||||||
|
|
||||||
Progress.Reset();
|
Progress.Reset();
|
||||||
@ -147,7 +148,6 @@ namespace AssetStudioCLI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsedAssetsList.AddRange(fileAssetsList);
|
parsedAssetsList.AddRange(fileAssetsList);
|
||||||
containers.Clear();
|
|
||||||
fileAssetsList.Clear();
|
fileAssetsList.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,5 +379,78 @@ namespace AssetStudioCLI
|
|||||||
}
|
}
|
||||||
Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items.");
|
Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ExportLive2D()
|
||||||
|
{
|
||||||
|
var baseDestPath = Path.Combine(options.o_outputFolder.Value, "Live2DOutput");
|
||||||
|
var useFullContainerPath = false;
|
||||||
|
|
||||||
|
Progress.Reset();
|
||||||
|
Logger.Info($"Searching for Live2D files...");
|
||||||
|
|
||||||
|
var cubismMocs = parsedAssetsList.Where(x =>
|
||||||
|
{
|
||||||
|
if (x.Type == ClassIDType.MonoBehaviour)
|
||||||
|
{
|
||||||
|
((MonoBehaviour)x.Asset).m_Script.TryGet(out var m_Script);
|
||||||
|
return m_Script?.m_ClassName == "CubismMoc";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).Select(x => x.Asset).ToArray();
|
||||||
|
if (cubismMocs.Length == 0)
|
||||||
|
{
|
||||||
|
Logger.Default.Log(LoggerEvent.Info, "Live2D Cubism models were not found.", ignoreLevel: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cubismMocs.Length > 1)
|
||||||
|
{
|
||||||
|
var basePathSet = cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToHashSet();
|
||||||
|
|
||||||
|
if (basePathSet.Count != cubismMocs.Length)
|
||||||
|
{
|
||||||
|
useFullContainerPath = true;
|
||||||
|
Logger.Debug($"useFullContainerPath: {useFullContainerPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var basePathList = useFullContainerPath ?
|
||||||
|
cubismMocs.Select(x => containers[x]).ToList() :
|
||||||
|
cubismMocs.Select(x => containers[x].Substring(0, containers[x].LastIndexOf("/"))).ToList();
|
||||||
|
var lookup = containers.ToLookup(
|
||||||
|
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
|
||||||
|
x => x.Key
|
||||||
|
);
|
||||||
|
|
||||||
|
var totalModelCount = lookup.LongCount(x => x.Key != null);
|
||||||
|
Logger.Info($"Found {totalModelCount} model(s).");
|
||||||
|
var name = "";
|
||||||
|
var modelCounter = 0;
|
||||||
|
foreach (var assets in lookup)
|
||||||
|
{
|
||||||
|
var container = assets.Key;
|
||||||
|
if (container == null)
|
||||||
|
continue;
|
||||||
|
name = container;
|
||||||
|
|
||||||
|
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container.Color(Ansi.BrightCyan)}\"");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
|
||||||
|
container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container;
|
||||||
|
var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
ExtractLive2D(assets, destPath, modelName);
|
||||||
|
modelCounter++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error($"Live2D model export error: \"{name}\"", ex);
|
||||||
|
}
|
||||||
|
Progress.Report(modelCounter, (int)totalModelCount);
|
||||||
|
}
|
||||||
|
var status = modelCounter > 0 ?
|
||||||
|
$"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s) to \"{options.o_outputFolder.Value.Color(Ansi.BrightCyan)}\"" :
|
||||||
|
"Nothing exported.";
|
||||||
|
Logger.Default.Log(LoggerEvent.Info, status, ignoreLevel: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
AssetStudioGUI/AssetStudioGUIForm.Designer.cs
generated
20
AssetStudioGUI/AssetStudioGUIForm.Designer.cs
generated
@ -66,6 +66,8 @@
|
|||||||
this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripMenuItem();
|
this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripMenuItem();
|
this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripMenuItem();
|
this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
|
this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
|
this.allLive2DModelsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripMenuItem();
|
this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripMenuItem11 = new System.Windows.Forms.ToolStripMenuItem();
|
this.toolStripMenuItem11 = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
@ -354,6 +356,8 @@
|
|||||||
this.toolStripSeparator4,
|
this.toolStripSeparator4,
|
||||||
this.toolStripMenuItem2,
|
this.toolStripMenuItem2,
|
||||||
this.toolStripMenuItem3,
|
this.toolStripMenuItem3,
|
||||||
|
this.toolStripSeparator6,
|
||||||
|
this.allLive2DModelsToolStripMenuItem,
|
||||||
this.toolStripSeparator2,
|
this.toolStripSeparator2,
|
||||||
this.toolStripMenuItem10});
|
this.toolStripMenuItem10});
|
||||||
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
||||||
@ -460,6 +464,18 @@
|
|||||||
this.toolStripMenuItem9.Text = "Filtered assets";
|
this.toolStripMenuItem9.Text = "Filtered assets";
|
||||||
this.toolStripMenuItem9.Click += new System.EventHandler(this.toolStripMenuItem9_Click);
|
this.toolStripMenuItem9.Click += new System.EventHandler(this.toolStripMenuItem9_Click);
|
||||||
//
|
//
|
||||||
|
// toolStripSeparator6
|
||||||
|
//
|
||||||
|
this.toolStripSeparator6.Name = "toolStripSeparator6";
|
||||||
|
this.toolStripSeparator6.Size = new System.Drawing.Size(263, 6);
|
||||||
|
//
|
||||||
|
// allLive2DModelsToolStripMenuItem
|
||||||
|
//
|
||||||
|
this.allLive2DModelsToolStripMenuItem.Name = "allLive2DModelsToolStripMenuItem";
|
||||||
|
this.allLive2DModelsToolStripMenuItem.Size = new System.Drawing.Size(266, 22);
|
||||||
|
this.allLive2DModelsToolStripMenuItem.Text = "Live2D Cubism models";
|
||||||
|
this.allLive2DModelsToolStripMenuItem.Click += new System.EventHandler(this.allLive2DModelsToolStripMenuItem_Click);
|
||||||
|
//
|
||||||
// toolStripSeparator2
|
// toolStripSeparator2
|
||||||
//
|
//
|
||||||
this.toolStripSeparator2.Name = "toolStripSeparator2";
|
this.toolStripSeparator2.Name = "toolStripSeparator2";
|
||||||
@ -510,7 +526,7 @@
|
|||||||
this.allToolStripMenuItem.CheckOnClick = true;
|
this.allToolStripMenuItem.CheckOnClick = true;
|
||||||
this.allToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
|
this.allToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||||
this.allToolStripMenuItem.Name = "allToolStripMenuItem";
|
this.allToolStripMenuItem.Name = "allToolStripMenuItem";
|
||||||
this.allToolStripMenuItem.Size = new System.Drawing.Size(88, 22);
|
this.allToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||||
this.allToolStripMenuItem.Text = "All";
|
this.allToolStripMenuItem.Text = "All";
|
||||||
this.allToolStripMenuItem.Click += new System.EventHandler(this.typeToolStripMenuItem_Click);
|
this.allToolStripMenuItem.Click += new System.EventHandler(this.typeToolStripMenuItem_Click);
|
||||||
//
|
//
|
||||||
@ -1368,6 +1384,8 @@
|
|||||||
private System.Windows.Forms.ComboBox listSearchFilterMode;
|
private System.Windows.Forms.ComboBox listSearchFilterMode;
|
||||||
private System.Windows.Forms.ComboBox listSearchHistory;
|
private System.Windows.Forms.ComboBox listSearchHistory;
|
||||||
private System.Windows.Forms.RichTextBox listSearch;
|
private System.Windows.Forms.RichTextBox listSearch;
|
||||||
|
private System.Windows.Forms.ToolStripSeparator toolStripSeparator6;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem allLive2DModelsToolStripMenuItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1880,6 +1880,40 @@ namespace AssetStudioGUI
|
|||||||
listSearch.SelectionStart = listSearch.Text.Length;
|
listSearch.SelectionStart = listSearch.Text.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void allLive2DModelsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (exportableAssets.Count > 0)
|
||||||
|
{
|
||||||
|
var cubismMocs = exportableAssets.Where(x =>
|
||||||
|
{
|
||||||
|
if (x.Type == ClassIDType.MonoBehaviour)
|
||||||
|
{
|
||||||
|
((MonoBehaviour)x.Asset).m_Script.TryGet(out var m_Script);
|
||||||
|
return m_Script?.m_ClassName == "CubismMoc";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).Select(x => x.Asset).ToArray();
|
||||||
|
if (cubismMocs.Length == 0)
|
||||||
|
{
|
||||||
|
Logger.Info("Live2D Cubism models were not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveFolderDialog = new OpenFolderDialog();
|
||||||
|
saveFolderDialog.InitialFolder = saveDirectoryBackup;
|
||||||
|
if (saveFolderDialog.ShowDialog(this) == DialogResult.OK)
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
saveDirectoryBackup = saveFolderDialog.Folder;
|
||||||
|
Studio.ExportLive2D(cubismMocs, saveFolderDialog.Folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info("No exportable assets loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region FMOD
|
#region FMOD
|
||||||
private void FMODinit()
|
private void FMODinit()
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using static AssetStudioGUI.Exporter;
|
using static AssetStudioGUI.Exporter;
|
||||||
|
using static CubismLive2DExtractor.Live2DExtractor;
|
||||||
using Object = AssetStudio.Object;
|
using Object = AssetStudio.Object;
|
||||||
|
|
||||||
namespace AssetStudioGUI
|
namespace AssetStudioGUI
|
||||||
@ -54,6 +55,7 @@ namespace AssetStudioGUI
|
|||||||
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
|
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
|
||||||
public static List<AssetItem> exportableAssets = new List<AssetItem>();
|
public static List<AssetItem> exportableAssets = new List<AssetItem>();
|
||||||
public static List<AssetItem> visibleAssets = new List<AssetItem>();
|
public static List<AssetItem> visibleAssets = new List<AssetItem>();
|
||||||
|
private static Dictionary<Object, string> allContainers = new Dictionary<Object, string>();
|
||||||
internal static Action<string> StatusStripUpdate = x => { };
|
internal static Action<string> StatusStripUpdate = x => { };
|
||||||
|
|
||||||
public static int ExtractFolder(string path, string savePath)
|
public static int ExtractFolder(string path, string savePath)
|
||||||
@ -262,6 +264,7 @@ namespace AssetStudioGUI
|
|||||||
if (pptr.TryGet(out var obj))
|
if (pptr.TryGet(out var obj))
|
||||||
{
|
{
|
||||||
objectAssetItemDic[obj].Container = container;
|
objectAssetItemDic[obj].Container = container;
|
||||||
|
allContainers[obj] = container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var tmp in exportableAssets)
|
foreach (var tmp in exportableAssets)
|
||||||
@ -738,5 +741,66 @@ namespace AssetStudioGUI
|
|||||||
info.UseShellExecute = true;
|
info.UseShellExecute = true;
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ExportLive2D(Object[] cubismMocs, string exportPath)
|
||||||
|
{
|
||||||
|
var baseDestPath = Path.Combine(exportPath, "Live2DOutput");
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(state =>
|
||||||
|
{
|
||||||
|
Progress.Reset();
|
||||||
|
Logger.Info($"Searching for Live2D files...");
|
||||||
|
|
||||||
|
var useFullContainerPath = false;
|
||||||
|
if (cubismMocs.Length > 1)
|
||||||
|
{
|
||||||
|
var basePathSet = cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToHashSet();
|
||||||
|
|
||||||
|
if (basePathSet.Count != cubismMocs.Length)
|
||||||
|
{
|
||||||
|
useFullContainerPath = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var basePathList = useFullContainerPath ?
|
||||||
|
cubismMocs.Select(x => allContainers[x]).ToList() :
|
||||||
|
cubismMocs.Select(x => allContainers[x].Substring(0, allContainers[x].LastIndexOf("/"))).ToList();
|
||||||
|
var lookup = allContainers.ToLookup(
|
||||||
|
x => basePathList.Find(b => x.Value.Contains(b) && x.Value.Split('/').Any(y => y == b.Substring(b.LastIndexOf("/") + 1))),
|
||||||
|
x => x.Key
|
||||||
|
);
|
||||||
|
|
||||||
|
var totalModelCount = lookup.LongCount(x => x.Key != null);
|
||||||
|
var name = "";
|
||||||
|
var modelCounter = 0;
|
||||||
|
foreach (var assets in lookup)
|
||||||
|
{
|
||||||
|
var container = assets.Key;
|
||||||
|
if (container == null)
|
||||||
|
continue;
|
||||||
|
name = container;
|
||||||
|
|
||||||
|
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{container}\"...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var modelName = useFullContainerPath ? Path.GetFileNameWithoutExtension(container) : container.Substring(container.LastIndexOf('/') + 1);
|
||||||
|
container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container;
|
||||||
|
var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
ExtractLive2D(assets, destPath, modelName);
|
||||||
|
modelCounter++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error($"Live2D model export error: \"{name}\"", ex);
|
||||||
|
}
|
||||||
|
Progress.Report(modelCounter, (int)totalModelCount);
|
||||||
|
}
|
||||||
|
Logger.Info($"Finished exporting [{modelCounter}/{totalModelCount}] Live2D model(s).");
|
||||||
|
if (Properties.Settings.Default.openAfterExport && modelCounter > 0)
|
||||||
|
{
|
||||||
|
OpenFolderInExplorer(exportPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class CubismExpression3Json
|
||||||
|
{
|
||||||
|
public string Type;
|
||||||
|
public float FadeInTime;
|
||||||
|
public float FadeOutTime;
|
||||||
|
public SerializableExpressionParameter[] Parameters;
|
||||||
|
|
||||||
|
public class SerializableExpressionParameter
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
public float Value;
|
||||||
|
public int Blend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
AssetStudioUtility/CubismLive2DExtractor/CubismModel3Json.cs
Normal file
28
AssetStudioUtility/CubismLive2DExtractor/CubismModel3Json.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class CubismModel3Json
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public string Name;
|
||||||
|
public SerializableFileReferences FileReferences;
|
||||||
|
public SerializableGroup[] Groups;
|
||||||
|
|
||||||
|
public class SerializableFileReferences
|
||||||
|
{
|
||||||
|
public string Moc;
|
||||||
|
public string[] Textures;
|
||||||
|
public string Physics;
|
||||||
|
public JObject Motions;
|
||||||
|
public JArray Expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableGroup
|
||||||
|
{
|
||||||
|
public string Target;
|
||||||
|
public string Name;
|
||||||
|
public string[] Ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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, AnimationClip[] animationClips)
|
||||||
|
{
|
||||||
|
var rootTransform = GetTransform(rootGameObject);
|
||||||
|
CreateBonePathHash(rootTransform);
|
||||||
|
ConvertAnimations(animationClips);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConvertAnimations(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;
|
||||||
|
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(Transform m_Transform)
|
||||||
|
{
|
||||||
|
var name = GetTransformPath(m_Transform);
|
||||||
|
var crc = new SevenZip.CRC();
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(name);
|
||||||
|
crc.Update(bytes, 0, (uint)bytes.Length);
|
||||||
|
bonePathHash[crc.GetDigest()] = name;
|
||||||
|
int index;
|
||||||
|
while ((index = name.IndexOf("/", StringComparison.Ordinal)) >= 0)
|
||||||
|
{
|
||||||
|
name = name.Substring(index + 1);
|
||||||
|
crc = new SevenZip.CRC();
|
||||||
|
bytes = Encoding.UTF8.GetBytes(name);
|
||||||
|
crc.Update(bytes, 0, (uint)bytes.Length);
|
||||||
|
bonePathHash[crc.GetDigest()] = name;
|
||||||
|
}
|
||||||
|
foreach (var pptr in m_Transform.m_Children)
|
||||||
|
{
|
||||||
|
if (pptr.TryGet(out var child))
|
||||||
|
CreateBonePathHash(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class CubismMotion3Json
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public SerializableMeta Meta;
|
||||||
|
public SerializableCurve[] Curves;
|
||||||
|
public SerializableUserData[] UserData;
|
||||||
|
|
||||||
|
public class SerializableMeta
|
||||||
|
{
|
||||||
|
public float Duration;
|
||||||
|
public float Fps;
|
||||||
|
public bool Loop;
|
||||||
|
public bool AreBeziersRestricted;
|
||||||
|
public int CurveCount;
|
||||||
|
public int TotalSegmentCount;
|
||||||
|
public int TotalPointCount;
|
||||||
|
public int UserDataCount;
|
||||||
|
public int TotalUserDataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
public class SerializableCurve
|
||||||
|
{
|
||||||
|
public string Target;
|
||||||
|
public string Id;
|
||||||
|
public List<float> Segments;
|
||||||
|
};
|
||||||
|
|
||||||
|
public class SerializableUserData
|
||||||
|
{
|
||||||
|
public float Time;
|
||||||
|
public string Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
using AssetStudio;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class CubismPhysics3Json
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public SerializableMeta Meta;
|
||||||
|
public SerializablePhysicsSettings[] PhysicsSettings;
|
||||||
|
|
||||||
|
public class SerializableNormalizationValue
|
||||||
|
{
|
||||||
|
public float Minimum;
|
||||||
|
public float Default;
|
||||||
|
public float Maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableParameter
|
||||||
|
{
|
||||||
|
public string Target;
|
||||||
|
public string Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableInput
|
||||||
|
{
|
||||||
|
public SerializableParameter Source;
|
||||||
|
public float Weight;
|
||||||
|
public string Type;
|
||||||
|
public bool Reflect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableOutput
|
||||||
|
{
|
||||||
|
public SerializableParameter Destination;
|
||||||
|
public int VertexIndex;
|
||||||
|
public float Scale;
|
||||||
|
public float Weight;
|
||||||
|
public string Type;
|
||||||
|
public bool Reflect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableVertex
|
||||||
|
{
|
||||||
|
public Vector2 Position;
|
||||||
|
public float Mobility;
|
||||||
|
public float Delay;
|
||||||
|
public float Acceleration;
|
||||||
|
public float Radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableNormalization
|
||||||
|
{
|
||||||
|
public SerializableNormalizationValue Position;
|
||||||
|
public SerializableNormalizationValue Angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializablePhysicsSettings
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
public SerializableInput[] Input;
|
||||||
|
public SerializableOutput[] Output;
|
||||||
|
public SerializableVertex[] Vertices;
|
||||||
|
public SerializableNormalization Normalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableMeta
|
||||||
|
{
|
||||||
|
public int PhysicsSettingCount;
|
||||||
|
public int TotalInputCount;
|
||||||
|
public int TotalOutputCount;
|
||||||
|
public int VertexCount;
|
||||||
|
public SerializableEffectiveForces EffectiveForces;
|
||||||
|
public SerializablePhysicsDictionary[] PhysicsDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializableEffectiveForces
|
||||||
|
{
|
||||||
|
public Vector2 Gravity;
|
||||||
|
public Vector2 Wind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerializablePhysicsDictionary
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
public string Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
AssetStudioUtility/CubismLive2DExtractor/CubismPhysicsRig.cs
Normal file
75
AssetStudioUtility/CubismLive2DExtractor/CubismPhysicsRig.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using AssetStudio;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class CubismPhysicsNormalizationTuplet
|
||||||
|
{
|
||||||
|
public float Maximum;
|
||||||
|
public float Minimum;
|
||||||
|
public float Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsNormalization
|
||||||
|
{
|
||||||
|
public CubismPhysicsNormalizationTuplet Position;
|
||||||
|
public CubismPhysicsNormalizationTuplet Angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsParticle
|
||||||
|
{
|
||||||
|
public Vector2 InitialPosition;
|
||||||
|
public float Mobility;
|
||||||
|
public float Delay;
|
||||||
|
public float Acceleration;
|
||||||
|
public float Radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsOutput
|
||||||
|
{
|
||||||
|
public string DestinationId;
|
||||||
|
public int ParticleIndex;
|
||||||
|
public Vector2 TranslationScale;
|
||||||
|
public float AngleScale;
|
||||||
|
public float Weight;
|
||||||
|
public CubismPhysicsSourceComponent SourceComponent;
|
||||||
|
public bool IsInverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CubismPhysicsSourceComponent
|
||||||
|
{
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
Angle,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsInput
|
||||||
|
{
|
||||||
|
public string SourceId;
|
||||||
|
public Vector2 ScaleOfTranslation;
|
||||||
|
public float AngleScale;
|
||||||
|
public float Weight;
|
||||||
|
public CubismPhysicsSourceComponent SourceComponent;
|
||||||
|
public bool IsInverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsSubRig
|
||||||
|
{
|
||||||
|
public CubismPhysicsInput[] Input;
|
||||||
|
public CubismPhysicsOutput[] Output;
|
||||||
|
public CubismPhysicsParticle[] Particles;
|
||||||
|
public CubismPhysicsNormalization Normalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysicsRig
|
||||||
|
{
|
||||||
|
public CubismPhysicsSubRig[] SubRigs;
|
||||||
|
public Vector2 Gravity = new Vector2(0, -1);
|
||||||
|
public Vector2 Wind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CubismPhysics
|
||||||
|
{
|
||||||
|
public string m_Name;
|
||||||
|
public CubismPhysicsRig _rig;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class ImportedKeyframedAnimation
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public float SampleRate { get; set; }
|
||||||
|
public float Duration { get; set; }
|
||||||
|
|
||||||
|
public List<ImportedAnimationKeyframedTrack> TrackList { get; set; } = new List<ImportedAnimationKeyframedTrack>();
|
||||||
|
public List<ImportedEvent> Events = new List<ImportedEvent>();
|
||||||
|
|
||||||
|
public ImportedAnimationKeyframedTrack FindTrack(string name)
|
||||||
|
{
|
||||||
|
var track = TrackList.Find(x => x.Name == name);
|
||||||
|
if (track == null)
|
||||||
|
{
|
||||||
|
track = new ImportedAnimationKeyframedTrack { Name = name };
|
||||||
|
TrackList.Add(track);
|
||||||
|
}
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportedKeyframe<T>
|
||||||
|
{
|
||||||
|
public float time { get; set; }
|
||||||
|
public T value { get; set; }
|
||||||
|
public T inSlope { get; set; }
|
||||||
|
public T outSlope { get; set; }
|
||||||
|
public float[] coeff { get; set; }
|
||||||
|
|
||||||
|
public ImportedKeyframe(float time, T value, T inSlope, T outSlope, float[] coeff)
|
||||||
|
{
|
||||||
|
this.time = time;
|
||||||
|
this.value = value;
|
||||||
|
this.inSlope = inSlope;
|
||||||
|
this.outSlope = outSlope;
|
||||||
|
this.coeff = coeff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Evaluate(float sampleTime)
|
||||||
|
{
|
||||||
|
float t = sampleTime - time;
|
||||||
|
return (t * (t * (t * coeff[0] + coeff[1]) + coeff[2])) + coeff[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportedAnimationKeyframedTrack
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Target { get; set; }
|
||||||
|
public List<ImportedKeyframe<float>> Curve = new List<ImportedKeyframe<float>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportedEvent
|
||||||
|
{
|
||||||
|
public float time { get; set; }
|
||||||
|
public string value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
431
AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs
Normal file
431
AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
////
|
||||||
|
// Based on UnityLive2DExtractorMod by aelurum
|
||||||
|
// https://github.com/aelurum/UnityLive2DExtractor
|
||||||
|
//
|
||||||
|
// Original version - by Perfare
|
||||||
|
// https://github.com/Perfare/UnityLive2DExtractor
|
||||||
|
////
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using AssetStudio;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public static class Live2DExtractor
|
||||||
|
{
|
||||||
|
public static void ExtractLive2D(IGrouping<string, AssetStudio.Object> assets, string destPath, string modelName)
|
||||||
|
{
|
||||||
|
var destTexturePath = Path.Combine(destPath, "textures") + Path.DirectorySeparatorChar;
|
||||||
|
var destMotionPath = Path.Combine(destPath, "motions") + Path.DirectorySeparatorChar;
|
||||||
|
var destExpressionPath = Path.Combine(destPath, "expressions") + Path.DirectorySeparatorChar;
|
||||||
|
Directory.CreateDirectory(destPath);
|
||||||
|
Directory.CreateDirectory(destTexturePath);
|
||||||
|
|
||||||
|
var monoBehaviours = new List<MonoBehaviour>();
|
||||||
|
var texture2Ds = new List<Texture2D>();
|
||||||
|
var gameObjects = new List<GameObject>();
|
||||||
|
var animationClips = new List<AnimationClip>();
|
||||||
|
|
||||||
|
foreach (var asset in assets)
|
||||||
|
{
|
||||||
|
switch (asset)
|
||||||
|
{
|
||||||
|
case MonoBehaviour m_MonoBehaviour:
|
||||||
|
monoBehaviours.Add(m_MonoBehaviour);
|
||||||
|
break;
|
||||||
|
case Texture2D m_Texture2D:
|
||||||
|
texture2Ds.Add(m_Texture2D);
|
||||||
|
break;
|
||||||
|
case GameObject m_GameObject:
|
||||||
|
gameObjects.Add(m_GameObject);
|
||||||
|
break;
|
||||||
|
case AnimationClip m_AnimationClip:
|
||||||
|
animationClips.Add(m_AnimationClip);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//physics
|
||||||
|
var physics = monoBehaviours.FirstOrDefault(x =>
|
||||||
|
{
|
||||||
|
if (x.m_Script.TryGet(out var m_Script))
|
||||||
|
{
|
||||||
|
return m_Script.m_ClassName == "CubismPhysicsController";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (physics != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buff = ParsePhysics(physics);
|
||||||
|
File.WriteAllText($"{destPath}{modelName}.physics3.json", buff);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warning($"Error in parsing physics data: {e.Message}");
|
||||||
|
physics = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//moc
|
||||||
|
var moc = monoBehaviours.First(x =>
|
||||||
|
{
|
||||||
|
if (x.m_Script.TryGet(out var m_Script))
|
||||||
|
{
|
||||||
|
return m_Script.m_ClassName == "CubismMoc";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(moc));
|
||||||
|
|
||||||
|
//texture
|
||||||
|
var textures = new SortedSet<string>();
|
||||||
|
foreach (var texture2D in texture2Ds)
|
||||||
|
{
|
||||||
|
using (var image = texture2D.ConvertToImage(flip: true))
|
||||||
|
{
|
||||||
|
textures.Add($"textures/{texture2D.m_Name}.png");
|
||||||
|
using (var file = File.OpenWrite($"{destTexturePath}{texture2D.m_Name}.png"))
|
||||||
|
{
|
||||||
|
image.WriteToStream(file, ImageFormat.Png);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//motion
|
||||||
|
var motions = new JArray();
|
||||||
|
|
||||||
|
if (gameObjects.Count > 0)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
var converter = new CubismMotion3Converter(rootGameObject, animationClips.ToArray());
|
||||||
|
if (converter.AnimationList.Count > 0)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destMotionPath);
|
||||||
|
}
|
||||||
|
foreach (ImportedKeyframedAnimation animation in converter.AnimationList)
|
||||||
|
{
|
||||||
|
var json = new CubismMotion3Json
|
||||||
|
{
|
||||||
|
Version = 3,
|
||||||
|
Meta = new CubismMotion3Json.SerializableMeta
|
||||||
|
{
|
||||||
|
Duration = animation.Duration,
|
||||||
|
Fps = animation.SampleRate,
|
||||||
|
Loop = true,
|
||||||
|
AreBeziersRestricted = true,
|
||||||
|
CurveCount = animation.TrackList.Count,
|
||||||
|
UserDataCount = animation.Events.Count
|
||||||
|
},
|
||||||
|
Curves = new CubismMotion3Json.SerializableCurve[animation.TrackList.Count]
|
||||||
|
};
|
||||||
|
int totalSegmentCount = 1;
|
||||||
|
int totalPointCount = 1;
|
||||||
|
for (int i = 0; i < animation.TrackList.Count; i++)
|
||||||
|
{
|
||||||
|
var track = animation.TrackList[i];
|
||||||
|
json.Curves[i] = new CubismMotion3Json.SerializableCurve
|
||||||
|
{
|
||||||
|
Target = track.Target,
|
||||||
|
Id = track.Name,
|
||||||
|
Segments = new List<float> { 0f, track.Curve[0].value }
|
||||||
|
};
|
||||||
|
for (var j = 1; j < track.Curve.Count; j++)
|
||||||
|
{
|
||||||
|
var curve = track.Curve[j];
|
||||||
|
var preCurve = track.Curve[j - 1];
|
||||||
|
if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) //InverseSteppedSegment
|
||||||
|
{
|
||||||
|
var nextCurve = track.Curve[j + 1];
|
||||||
|
if (nextCurve.value == curve.value)
|
||||||
|
{
|
||||||
|
json.Curves[i].Segments.Add(3f);
|
||||||
|
json.Curves[i].Segments.Add(nextCurve.time);
|
||||||
|
json.Curves[i].Segments.Add(nextCurve.value);
|
||||||
|
j += 1;
|
||||||
|
totalPointCount += 1;
|
||||||
|
totalSegmentCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (float.IsPositiveInfinity(curve.inSlope)) //SteppedSegment
|
||||||
|
{
|
||||||
|
json.Curves[i].Segments.Add(2f);
|
||||||
|
json.Curves[i].Segments.Add(curve.time);
|
||||||
|
json.Curves[i].Segments.Add(curve.value);
|
||||||
|
totalPointCount += 1;
|
||||||
|
}
|
||||||
|
else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f) //LinearSegment
|
||||||
|
{
|
||||||
|
json.Curves[i].Segments.Add(0f);
|
||||||
|
json.Curves[i].Segments.Add(curve.time);
|
||||||
|
json.Curves[i].Segments.Add(curve.value);
|
||||||
|
totalPointCount += 1;
|
||||||
|
}
|
||||||
|
else //BezierSegment
|
||||||
|
{
|
||||||
|
var tangentLength = (curve.time - preCurve.time) / 3f;
|
||||||
|
json.Curves[i].Segments.Add(1f);
|
||||||
|
json.Curves[i].Segments.Add(preCurve.time + tangentLength);
|
||||||
|
json.Curves[i].Segments.Add(preCurve.outSlope * tangentLength + preCurve.value);
|
||||||
|
json.Curves[i].Segments.Add(curve.time - tangentLength);
|
||||||
|
json.Curves[i].Segments.Add(curve.value - curve.inSlope * tangentLength);
|
||||||
|
json.Curves[i].Segments.Add(curve.time);
|
||||||
|
json.Curves[i].Segments.Add(curve.value);
|
||||||
|
totalPointCount += 3;
|
||||||
|
}
|
||||||
|
totalSegmentCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json.Meta.TotalSegmentCount = totalSegmentCount;
|
||||||
|
json.Meta.TotalPointCount = totalPointCount;
|
||||||
|
|
||||||
|
json.UserData = new CubismMotion3Json.SerializableUserData[animation.Events.Count];
|
||||||
|
var totalUserDataSize = 0;
|
||||||
|
for (var i = 0; i < animation.Events.Count; i++)
|
||||||
|
{
|
||||||
|
var @event = animation.Events[i];
|
||||||
|
json.UserData[i] = new CubismMotion3Json.SerializableUserData
|
||||||
|
{
|
||||||
|
Time = @event.time,
|
||||||
|
Value = @event.value
|
||||||
|
};
|
||||||
|
totalUserDataSize += @event.value.Length;
|
||||||
|
}
|
||||||
|
json.Meta.TotalUserDataSize = totalUserDataSize;
|
||||||
|
|
||||||
|
motions.Add(new JObject
|
||||||
|
{
|
||||||
|
{ "Name", animation.Name },
|
||||||
|
{ "File", $"motions/{animation.Name}.motion3.json" }
|
||||||
|
});
|
||||||
|
File.WriteAllText($"{destMotionPath}{animation.Name}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//expression
|
||||||
|
var expressions = new JArray();
|
||||||
|
var monoBehaviourArray = monoBehaviours.Where(x => x.m_Name.EndsWith(".exp3")).ToArray();
|
||||||
|
if (monoBehaviourArray.Length > 0)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destExpressionPath);
|
||||||
|
}
|
||||||
|
foreach (var monoBehaviour in monoBehaviourArray)
|
||||||
|
{
|
||||||
|
var fullName = monoBehaviour.m_Name;
|
||||||
|
var expressionName = fullName.Replace(".exp3", "");
|
||||||
|
var expressionObj = monoBehaviour.ToType();
|
||||||
|
if (expressionObj == null)
|
||||||
|
continue;
|
||||||
|
var expression = JsonConvert.DeserializeObject<CubismExpression3Json>(JsonConvert.SerializeObject(expressionObj));
|
||||||
|
|
||||||
|
expressions.Add(new JObject
|
||||||
|
{
|
||||||
|
{ "Name", expressionName },
|
||||||
|
{ "File", $"expressions/{fullName}.json" }
|
||||||
|
});
|
||||||
|
File.WriteAllText($"{destExpressionPath}{fullName}.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
|
||||||
|
}
|
||||||
|
|
||||||
|
//model
|
||||||
|
var groups = new List<CubismModel3Json.SerializableGroup>();
|
||||||
|
|
||||||
|
var eyeBlinkParameters = monoBehaviours.Where(x =>
|
||||||
|
{
|
||||||
|
x.m_Script.TryGet(out var m_Script);
|
||||||
|
return m_Script?.m_ClassName == "CubismEyeBlinkParameter";
|
||||||
|
}).Select(x =>
|
||||||
|
{
|
||||||
|
x.m_GameObject.TryGet(out var m_GameObject);
|
||||||
|
return m_GameObject?.m_Name;
|
||||||
|
}).ToHashSet();
|
||||||
|
if (eyeBlinkParameters.Count == 0)
|
||||||
|
{
|
||||||
|
eyeBlinkParameters = gameObjects.Where(x =>
|
||||||
|
{
|
||||||
|
return x.m_Name.ToLower().Contains("eye")
|
||||||
|
&& x.m_Name.ToLower().Contains("open")
|
||||||
|
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'));
|
||||||
|
}).Select(x => x.m_Name).ToHashSet();
|
||||||
|
}
|
||||||
|
groups.Add(new CubismModel3Json.SerializableGroup
|
||||||
|
{
|
||||||
|
Target = "Parameter",
|
||||||
|
Name = "EyeBlink",
|
||||||
|
Ids = eyeBlinkParameters.ToArray()
|
||||||
|
});
|
||||||
|
|
||||||
|
var lipSyncParameters = monoBehaviours.Where(x =>
|
||||||
|
{
|
||||||
|
x.m_Script.TryGet(out var m_Script);
|
||||||
|
return m_Script?.m_ClassName == "CubismMouthParameter";
|
||||||
|
}).Select(x =>
|
||||||
|
{
|
||||||
|
x.m_GameObject.TryGet(out var m_GameObject);
|
||||||
|
return m_GameObject?.m_Name;
|
||||||
|
}).ToHashSet();
|
||||||
|
if (lipSyncParameters.Count == 0)
|
||||||
|
{
|
||||||
|
lipSyncParameters = gameObjects.Where(x =>
|
||||||
|
{
|
||||||
|
return x.m_Name.ToLower().Contains("mouth")
|
||||||
|
&& x.m_Name.ToLower().Contains("open")
|
||||||
|
&& x.m_Name.ToLower().Contains('y');
|
||||||
|
}).Select(x => x.m_Name).ToHashSet();
|
||||||
|
}
|
||||||
|
groups.Add(new CubismModel3Json.SerializableGroup
|
||||||
|
{
|
||||||
|
Target = "Parameter",
|
||||||
|
Name = "LipSync",
|
||||||
|
Ids = lipSyncParameters.ToArray()
|
||||||
|
});
|
||||||
|
|
||||||
|
var model3 = new CubismModel3Json
|
||||||
|
{
|
||||||
|
Version = 3,
|
||||||
|
Name = modelName,
|
||||||
|
FileReferences = new CubismModel3Json.SerializableFileReferences
|
||||||
|
{
|
||||||
|
Moc = $"{modelName}.moc3",
|
||||||
|
Textures = textures.ToArray(),
|
||||||
|
Motions = new JObject { { "", motions } },
|
||||||
|
Expressions = expressions,
|
||||||
|
},
|
||||||
|
Groups = groups.ToArray()
|
||||||
|
};
|
||||||
|
if (physics != null)
|
||||||
|
{
|
||||||
|
model3.FileReferences.Physics = $"{modelName}.physics3.json";
|
||||||
|
}
|
||||||
|
File.WriteAllText($"{destPath}{modelName}.model3.json", JsonConvert.SerializeObject(model3, Formatting.Indented));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParsePhysics(MonoBehaviour physics)
|
||||||
|
{
|
||||||
|
var physicsObj = physics.ToType();
|
||||||
|
if (physicsObj == null)
|
||||||
|
throw new Exception("MonoBehaviour is not readable.");
|
||||||
|
var cubismPhysicsRig = JsonConvert.DeserializeObject<CubismPhysics>(JsonConvert.SerializeObject(physicsObj))._rig;
|
||||||
|
|
||||||
|
var physicsSettings = new CubismPhysics3Json.SerializablePhysicsSettings[cubismPhysicsRig.SubRigs.Length];
|
||||||
|
for (int i = 0; i < physicsSettings.Length; i++)
|
||||||
|
{
|
||||||
|
var subRigs = cubismPhysicsRig.SubRigs[i];
|
||||||
|
physicsSettings[i] = new CubismPhysics3Json.SerializablePhysicsSettings
|
||||||
|
{
|
||||||
|
Id = $"PhysicsSetting{i + 1}",
|
||||||
|
Input = new CubismPhysics3Json.SerializableInput[subRigs.Input.Length],
|
||||||
|
Output = new CubismPhysics3Json.SerializableOutput[subRigs.Output.Length],
|
||||||
|
Vertices = new CubismPhysics3Json.SerializableVertex[subRigs.Particles.Length],
|
||||||
|
Normalization = new CubismPhysics3Json.SerializableNormalization
|
||||||
|
{
|
||||||
|
Position = new CubismPhysics3Json.SerializableNormalizationValue
|
||||||
|
{
|
||||||
|
Minimum = subRigs.Normalization.Position.Minimum,
|
||||||
|
Default = subRigs.Normalization.Position.Default,
|
||||||
|
Maximum = subRigs.Normalization.Position.Maximum
|
||||||
|
},
|
||||||
|
Angle = new CubismPhysics3Json.SerializableNormalizationValue
|
||||||
|
{
|
||||||
|
Minimum = subRigs.Normalization.Angle.Minimum,
|
||||||
|
Default = subRigs.Normalization.Angle.Default,
|
||||||
|
Maximum = subRigs.Normalization.Angle.Maximum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (int j = 0; j < subRigs.Input.Length; j++)
|
||||||
|
{
|
||||||
|
var input = subRigs.Input[j];
|
||||||
|
physicsSettings[i].Input[j] = new CubismPhysics3Json.SerializableInput
|
||||||
|
{
|
||||||
|
Source = new CubismPhysics3Json.SerializableParameter
|
||||||
|
{
|
||||||
|
Target = "Parameter", //同名GameObject父节点的名称
|
||||||
|
Id = input.SourceId
|
||||||
|
},
|
||||||
|
Weight = input.Weight,
|
||||||
|
Type = Enum.GetName(typeof(CubismPhysicsSourceComponent), input.SourceComponent),
|
||||||
|
Reflect = input.IsInverted
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (int j = 0; j < subRigs.Output.Length; j++)
|
||||||
|
{
|
||||||
|
var output = subRigs.Output[j];
|
||||||
|
physicsSettings[i].Output[j] = new CubismPhysics3Json.SerializableOutput
|
||||||
|
{
|
||||||
|
Destination = new CubismPhysics3Json.SerializableParameter
|
||||||
|
{
|
||||||
|
Target = "Parameter", //同名GameObject父节点的名称
|
||||||
|
Id = output.DestinationId
|
||||||
|
},
|
||||||
|
VertexIndex = output.ParticleIndex,
|
||||||
|
Scale = output.AngleScale,
|
||||||
|
Weight = output.Weight,
|
||||||
|
Type = Enum.GetName(typeof(CubismPhysicsSourceComponent), output.SourceComponent),
|
||||||
|
Reflect = output.IsInverted
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (int j = 0; j < subRigs.Particles.Length; j++)
|
||||||
|
{
|
||||||
|
var particles = subRigs.Particles[j];
|
||||||
|
physicsSettings[i].Vertices[j] = new CubismPhysics3Json.SerializableVertex
|
||||||
|
{
|
||||||
|
Position = particles.InitialPosition,
|
||||||
|
Mobility = particles.Mobility,
|
||||||
|
Delay = particles.Delay,
|
||||||
|
Acceleration = particles.Acceleration,
|
||||||
|
Radius = particles.Radius
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var physicsDictionary = new CubismPhysics3Json.SerializablePhysicsDictionary[physicsSettings.Length];
|
||||||
|
for (int i = 0; i < physicsSettings.Length; i++)
|
||||||
|
{
|
||||||
|
physicsDictionary[i] = new CubismPhysics3Json.SerializablePhysicsDictionary
|
||||||
|
{
|
||||||
|
Id = $"PhysicsSetting{i + 1}",
|
||||||
|
Name = $"Dummy{i + 1}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var physicsJson = new CubismPhysics3Json
|
||||||
|
{
|
||||||
|
Version = 3,
|
||||||
|
Meta = new CubismPhysics3Json.SerializableMeta
|
||||||
|
{
|
||||||
|
PhysicsSettingCount = cubismPhysicsRig.SubRigs.Length,
|
||||||
|
TotalInputCount = cubismPhysicsRig.SubRigs.Sum(x => x.Input.Length),
|
||||||
|
TotalOutputCount = cubismPhysicsRig.SubRigs.Sum(x => x.Output.Length),
|
||||||
|
VertexCount = cubismPhysicsRig.SubRigs.Sum(x => x.Particles.Length),
|
||||||
|
EffectiveForces = new CubismPhysics3Json.SerializableEffectiveForces
|
||||||
|
{
|
||||||
|
Gravity = cubismPhysicsRig.Gravity,
|
||||||
|
Wind = cubismPhysicsRig.Wind
|
||||||
|
},
|
||||||
|
PhysicsDictionary = physicsDictionary
|
||||||
|
},
|
||||||
|
PhysicsSettings = physicsSettings
|
||||||
|
};
|
||||||
|
return JsonConvert.SerializeObject(physicsJson, Formatting.Indented, new MyJsonConverter2());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ParseMoc(MonoBehaviour moc)
|
||||||
|
{
|
||||||
|
var reader = moc.reader;
|
||||||
|
reader.Reset();
|
||||||
|
reader.Position += 28; //PPtr<GameObject> m_GameObject, m_Enabled, PPtr<MonoScript>
|
||||||
|
reader.ReadAlignedString(); //m_Name
|
||||||
|
return reader.ReadBytes(reader.ReadInt32());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
AssetStudioUtility/CubismLive2DExtractor/MyJsonConverter.cs
Normal file
35
AssetStudioUtility/CubismLive2DExtractor/MyJsonConverter.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class MyJsonConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(List<float>);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
writer.WriteStartArray();
|
||||||
|
Convert(writer, (List<float>)value);
|
||||||
|
writer.WriteEndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Convert(JsonWriter writer, List<float> array)
|
||||||
|
{
|
||||||
|
foreach (var n in array)
|
||||||
|
{
|
||||||
|
var v = n.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
writer.WriteRawValue(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
AssetStudioUtility/CubismLive2DExtractor/MyJsonConverter2.cs
Normal file
28
AssetStudioUtility/CubismLive2DExtractor/MyJsonConverter2.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CubismLive2DExtractor
|
||||||
|
{
|
||||||
|
public class MyJsonConverter2 : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return objectType == typeof(float);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
Convert(writer, (float)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Convert(JsonWriter writer, float value)
|
||||||
|
{
|
||||||
|
writer.WriteRawValue(value.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user