From d572bd0e64158b08e0b6bbb85f6c1dda1861b1fa Mon Sep 17 00:00:00 2001 From: VaDiM Date: Sun, 19 Nov 2023 02:52:55 +0300 Subject: [PATCH] Add support for Live2D Fade motions --- AssetStudioCLI/Options/CLIOptions.cs | 75 ++++++- AssetStudioCLI/Studio.cs | 4 +- AssetStudioGUI/ExportOptions.Designer.cs | 156 ++++++++++---- AssetStudioGUI/ExportOptions.cs | 29 +-- AssetStudioGUI/ExportOptions.resx | 2 +- .../Properties/Settings.Designer.cs | 26 ++- AssetStudioGUI/Properties/Settings.settings | 6 + AssetStudioGUI/Studio.cs | 4 +- .../CubismLive2DExtractor/CubismFadeMotion.cs | 30 +++ .../CubismKeyframeData.cs | 26 +++ .../CubismMotion3Json.cs | 199 +++++++++++++++++- .../CubismLive2DExtractor/Live2DExtractor.cs | 143 +++++-------- .../CubismLive2DExtractor/Live2DMotionMode.cs | 8 + 13 files changed, 547 insertions(+), 161 deletions(-) create mode 100644 AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs create mode 100644 AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs create mode 100644 AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs diff --git a/AssetStudioCLI/Options/CLIOptions.cs b/AssetStudioCLI/Options/CLIOptions.cs index 5e5b8ea..7c09332 100644 --- a/AssetStudioCLI/Options/CLIOptions.cs +++ b/AssetStudioCLI/Options/CLIOptions.cs @@ -12,6 +12,7 @@ namespace AssetStudioCLI.Options General, Convert, Logger, + Live2D, FBX, Filter, Advanced, @@ -83,6 +84,9 @@ namespace AssetStudioCLI.Options public static bool convertTexture; public static Option o_imageFormat; public static Option o_audioFormat; + //live2d + public static Option o_l2dMotionMode; + public static Option f_l2dForceBezier; //fbx public static Option o_fbxScaleFactor; public static Option o_fbxBoneSize; @@ -203,7 +207,7 @@ namespace AssetStudioCLI.Options optionDefaultValue: "ASExport", optionName: "-o, --output ", optionDescription: "Specify path to the output folder\n" + - "If path isn't specifyed, 'ASExport' folder will be created in the program's work folder\n", + "If path isn't specified, 'ASExport' folder will be created in the program's work folder\n", optionHelpGroup: HelpGroups.General ); o_displayHelp = new GroupedOption @@ -260,6 +264,30 @@ namespace AssetStudioCLI.Options ); #endregion + #region Init Cubism Live2D Options + o_l2dMotionMode = new GroupedOption + ( + optionDefaultValue: CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour, + optionName: "--l2d-motion-mode ", + optionDescription: "Specify Live2D motion export mode\n" + + "\n" + + "MonoBehaviour - Try to export motions from MonoBehaviour Fade motions\n" + + "If no Fade motions are found, the AnimationClip method will be used\n" + + "AnimationClip - Try to export motions using AnimationClip assets\n" + + "Example: \"--l2d-motion-mode animationClip\"\n", + optionHelpGroup: HelpGroups.Live2D + ); + f_l2dForceBezier = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--l2d-force-bezier", + optionDescription: "(Flag) If specified, Linear motion segments will be calculated as Bezier segments\n" + + "(May help if the exported motions look jerky/not smooth enough)", + optionHelpGroup: HelpGroups.Live2D, + isFlag: true + ); + #endregion + #region Init FBX Options o_fbxScaleFactor = new GroupedOption ( @@ -411,6 +439,7 @@ namespace AssetStudioCLI.Options } }; + #region Parse "Working Mode" Option var workModeOptionIndex = resplittedArgs.FindIndex(x => x.ToLower() == "-m" || x.ToLower() == "--mode"); if (workModeOptionIndex >= 0) { @@ -437,6 +466,7 @@ namespace AssetStudioCLI.Options case "info": o_workMode.Value = WorkMode.Info; break; + case "l2d": case "live2d": o_workMode.Value = WorkMode.ExportLive2D; o_exportAssetTypes.Value = new List() @@ -468,6 +498,7 @@ namespace AssetStudioCLI.Options } resplittedArgs.RemoveRange(workModeOptionIndex, 2); } + #endregion #region Parse Flags for (int i = 0; i < resplittedArgs.Count; i++) @@ -495,6 +526,16 @@ namespace AssetStudioCLI.Options return; } break; + case "--l2d-force-bezier": + if (o_workMode.Value != WorkMode.ExportLive2D) + { + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n"); + ShowOptionDescription(o_workMode.Description); + return; + } + f_l2dForceBezier.Value = true; + resplittedArgs.RemoveAt(i); + break; } } #endregion @@ -693,6 +734,28 @@ namespace AssetStudioCLI.Options return; } break; + case "--l2d-motion-mode": + if (o_workMode.Value != WorkMode.ExportLive2D) + { + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. This option is not suitable for the current working mode [{o_workMode.Value}].\n"); + ShowOptionDescription(o_workMode.Description); + return; + } + switch (value.ToLower()) + { + case "fade": + case "monobehaviour": + o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.MonoBehaviour; + break; + case "animationclip": + o_l2dMotionMode.Value = CubismLive2DExtractor.Live2DMotionMode.AnimationClip; + break; + default: + Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported Live2D motion mode: [{value.Color(brightRed)}].\n"); + ShowOptionDescription(o_l2dMotionMode.Description); + return; + } + break; case "--fbx-scale-factor": var isFloat = float.TryParse(value, out float floatValue); if (isFloat && floatValue >= 0 && floatValue <= 100) @@ -832,12 +895,12 @@ namespace AssetStudioCLI.Options private static bool TryFindOptionDescription(string option, Dictionary dict, bool isFlag = false) { - var optionDesc = dict.Where(x => x.Key.Contains(option)); + var optionDesc = dict.Where(x => x.Key.Contains(option)).ToArray(); if (optionDesc.Any()) { var arg = isFlag ? "flag" : "option"; var rand = new Random(); - var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Count())); + var rndOption = optionDesc.ElementAt(rand.Next(0, optionDesc.Length)); Console.WriteLine($"Did you mean [{$"{rndOption.Key}".Color(CLIAnsiColors.BrightYellow)}] {arg}?"); Console.WriteLine($"Here's a description of it: \n\n{rndOption.Value}"); @@ -946,7 +1009,7 @@ namespace AssetStudioCLI.Options sb.AppendLine($"# Log Output: {o_logOutput}"); sb.AppendLine($"# Export Asset List: {o_exportAssetList}"); sb.AppendLine(ShowCurrentFilter()); - sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\""); + sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\""); sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); if (o_workMode.Value == WorkMode.Export) { @@ -975,7 +1038,9 @@ namespace AssetStudioCLI.Options } else { - sb.AppendLine($"# Assebmly Path: \"{o_assemblyPath}\""); + sb.AppendLine($"# Live2D Motion Export Method: {o_l2dMotionMode}"); + sb.AppendLine($"# Force Bezier: {f_l2dForceBezier }"); + sb.AppendLine($"# Assembly Path: \"{o_assemblyPath}\""); } sb.AppendLine($"# Unity Version: \"{o_unityVersion}\""); break; diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 12f3529..d715566 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -611,6 +611,8 @@ namespace AssetStudioCLI { var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput"); var useFullContainerPath = false; + var motionMode = CLIOptions.o_l2dMotionMode.Value; + var forceBezier = CLIOptions.f_l2dForceBezier.Value; Progress.Reset(); Logger.Info($"Searching for Live2D files..."); @@ -683,7 +685,7 @@ namespace AssetStudioCLI container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container; var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar; - ExtractLive2D(assets, destPath, modelName, assemblyLoader); + ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index a99aa21..8941fe2 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -45,6 +45,12 @@ this.topng = new System.Windows.Forms.RadioButton(); this.tobmp = new System.Windows.Forms.RadioButton(); this.converttexture = new System.Windows.Forms.CheckBox(); + this.l2dGroupBox = new System.Windows.Forms.GroupBox(); + this.l2dMotionExportMethodPanel = new System.Windows.Forms.Panel(); + this.l2dMonoBehaviourRadioButton = new System.Windows.Forms.RadioButton(); + this.l2dAnimationClipRadioButton = new System.Windows.Forms.RadioButton(); + this.l2dMotionExportMethodLabel = new System.Windows.Forms.Label(); + this.l2dForceBezierCheckBox = new System.Windows.Forms.CheckBox(); this.groupBox2 = new System.Windows.Forms.GroupBox(); this.exportAllUvsAsDiffuseMaps = new System.Windows.Forms.CheckBox(); this.exportBlendShape = new System.Windows.Forms.CheckBox(); @@ -63,9 +69,11 @@ this.castToBone = new System.Windows.Forms.CheckBox(); this.exportAllNodes = new System.Windows.Forms.CheckBox(); this.eulerFilter = new System.Windows.Forms.CheckBox(); - this.exportUvsTooltip = new System.Windows.Forms.ToolTip(this.components); + this.optionTooltip = new System.Windows.Forms.ToolTip(this.components); this.groupBox1.SuspendLayout(); this.panel1.SuspendLayout(); + this.l2dGroupBox.SuspendLayout(); + this.l2dMotionExportMethodPanel.SuspendLayout(); this.groupBox2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.boneSize)).BeginInit(); @@ -77,7 +85,7 @@ this.OKbutton.Location = new System.Drawing.Point(381, 380); this.OKbutton.Name = "OKbutton"; this.OKbutton.Size = new System.Drawing.Size(75, 23); - this.OKbutton.TabIndex = 6; + this.OKbutton.TabIndex = 4; this.OKbutton.Text = "OK"; this.OKbutton.UseVisualStyleBackColor = true; this.OKbutton.Click += new System.EventHandler(this.OKbutton_Click); @@ -88,7 +96,7 @@ this.Cancel.Location = new System.Drawing.Point(462, 380); this.Cancel.Name = "Cancel"; this.Cancel.Size = new System.Drawing.Size(75, 23); - this.Cancel.TabIndex = 7; + this.Cancel.TabIndex = 5; this.Cancel.Text = "Cancel"; this.Cancel.UseVisualStyleBackColor = true; this.Cancel.Click += new System.EventHandler(this.Cancel_Click); @@ -106,8 +114,8 @@ this.groupBox1.Controls.Add(this.converttexture); this.groupBox1.Location = new System.Drawing.Point(12, 13); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(301, 362); - this.groupBox1.TabIndex = 9; + this.groupBox1.Size = new System.Drawing.Size(301, 272); + this.groupBox1.TabIndex = 1; this.groupBox1.TabStop = false; this.groupBox1.Text = "Export"; // @@ -119,7 +127,7 @@ this.exportSpriteWithAlphaMask.Location = new System.Drawing.Point(6, 150); this.exportSpriteWithAlphaMask.Name = "exportSpriteWithAlphaMask"; this.exportSpriteWithAlphaMask.Size = new System.Drawing.Size(205, 17); - this.exportSpriteWithAlphaMask.TabIndex = 11; + this.exportSpriteWithAlphaMask.TabIndex = 6; this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied"; this.exportSpriteWithAlphaMask.UseVisualStyleBackColor = true; // @@ -131,7 +139,7 @@ this.openAfterExport.Location = new System.Drawing.Point(6, 196); this.openAfterExport.Name = "openAfterExport"; this.openAfterExport.Size = new System.Drawing.Size(137, 17); - this.openAfterExport.TabIndex = 10; + this.openAfterExport.TabIndex = 8; this.openAfterExport.Text = "Open folder after export"; this.openAfterExport.UseVisualStyleBackColor = true; // @@ -143,9 +151,9 @@ this.restoreExtensionName.Location = new System.Drawing.Point(6, 63); this.restoreExtensionName.Name = "restoreExtensionName"; this.restoreExtensionName.Size = new System.Drawing.Size(275, 17); - this.restoreExtensionName.TabIndex = 9; + this.restoreExtensionName.TabIndex = 3; this.restoreExtensionName.Text = "Try to restore/Use original TextAsset extension name"; - this.exportUvsTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension"); + this.optionTooltip.SetToolTip(this.restoreExtensionName, "If not checked, AssetStudio will export all TextAssets with the \".txt\" extension"); this.restoreExtensionName.UseVisualStyleBackColor = true; // // assetGroupOptions @@ -161,7 +169,7 @@ this.assetGroupOptions.Location = new System.Drawing.Point(6, 35); this.assetGroupOptions.Name = "assetGroupOptions"; this.assetGroupOptions.Size = new System.Drawing.Size(165, 21); - this.assetGroupOptions.TabIndex = 8; + this.assetGroupOptions.TabIndex = 2; // // label6 // @@ -169,7 +177,7 @@ this.label6.Location = new System.Drawing.Point(6, 18); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(127, 13); - this.label6.TabIndex = 7; + this.label6.TabIndex = 1; this.label6.Text = "Group exported assets by"; // // convertAudio @@ -180,7 +188,7 @@ this.convertAudio.Location = new System.Drawing.Point(6, 173); this.convertAudio.Name = "convertAudio"; this.convertAudio.Size = new System.Drawing.Size(179, 17); - this.convertAudio.TabIndex = 6; + this.convertAudio.TabIndex = 7; this.convertAudio.Text = "Convert AudioClip to WAV(PCM)"; this.convertAudio.UseVisualStyleBackColor = true; // @@ -202,8 +210,7 @@ this.towebp.Location = new System.Drawing.Point(201, 7); this.towebp.Name = "towebp"; this.towebp.Size = new System.Drawing.Size(54, 17); - this.towebp.TabIndex = 5; - this.towebp.TabStop = true; + this.towebp.TabIndex = 4; this.towebp.Text = "Webp"; this.towebp.UseVisualStyleBackColor = true; // @@ -213,7 +220,7 @@ this.totga.Location = new System.Drawing.Point(150, 7); this.totga.Name = "totga"; this.totga.Size = new System.Drawing.Size(44, 17); - this.totga.TabIndex = 2; + this.totga.TabIndex = 3; this.totga.Text = "Tga"; this.totga.UseVisualStyleBackColor = true; // @@ -223,7 +230,7 @@ this.tojpg.Location = new System.Drawing.Point(97, 7); this.tojpg.Name = "tojpg"; this.tojpg.Size = new System.Drawing.Size(48, 17); - this.tojpg.TabIndex = 4; + this.tojpg.TabIndex = 2; this.tojpg.Text = "Jpeg"; this.tojpg.UseVisualStyleBackColor = true; // @@ -234,7 +241,7 @@ this.topng.Location = new System.Drawing.Point(50, 7); this.topng.Name = "topng"; this.topng.Size = new System.Drawing.Size(44, 17); - this.topng.TabIndex = 3; + this.topng.TabIndex = 1; this.topng.TabStop = true; this.topng.Text = "Png"; this.topng.UseVisualStyleBackColor = true; @@ -245,7 +252,7 @@ this.tobmp.Location = new System.Drawing.Point(3, 7); this.tobmp.Name = "tobmp"; this.tobmp.Size = new System.Drawing.Size(46, 17); - this.tobmp.TabIndex = 2; + this.tobmp.TabIndex = 0; this.tobmp.Text = "Bmp"; this.tobmp.UseVisualStyleBackColor = true; // @@ -257,10 +264,76 @@ this.converttexture.Location = new System.Drawing.Point(6, 87); this.converttexture.Name = "converttexture"; this.converttexture.Size = new System.Drawing.Size(116, 17); - this.converttexture.TabIndex = 1; + this.converttexture.TabIndex = 4; this.converttexture.Text = "Convert Texture2D"; this.converttexture.UseVisualStyleBackColor = true; // + // l2dGroupBox + // + this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodPanel); + this.l2dGroupBox.Controls.Add(this.l2dMotionExportMethodLabel); + this.l2dGroupBox.Controls.Add(this.l2dForceBezierCheckBox); + this.l2dGroupBox.Location = new System.Drawing.Point(12, 275); + this.l2dGroupBox.Name = "l2dGroupBox"; + this.l2dGroupBox.Size = new System.Drawing.Size(301, 100); + this.l2dGroupBox.TabIndex = 2; + this.l2dGroupBox.TabStop = false; + this.l2dGroupBox.Text = "Cubism Live2D"; + // + // l2dMotionExportMethodPanel + // + this.l2dMotionExportMethodPanel.Controls.Add(this.l2dMonoBehaviourRadioButton); + this.l2dMotionExportMethodPanel.Controls.Add(this.l2dAnimationClipRadioButton); + this.l2dMotionExportMethodPanel.Location = new System.Drawing.Point(18, 40); + this.l2dMotionExportMethodPanel.Name = "l2dMotionExportMethodPanel"; + this.l2dMotionExportMethodPanel.Size = new System.Drawing.Size(263, 27); + this.l2dMotionExportMethodPanel.TabIndex = 2; + // + // l2dMonoBehaviourRadioButton + // + this.l2dMonoBehaviourRadioButton.AccessibleName = "MonoBehaviour"; + this.l2dMonoBehaviourRadioButton.AutoSize = true; + this.l2dMonoBehaviourRadioButton.Checked = true; + this.l2dMonoBehaviourRadioButton.Location = new System.Drawing.Point(3, 5); + this.l2dMonoBehaviourRadioButton.Name = "l2dMonoBehaviourRadioButton"; + this.l2dMonoBehaviourRadioButton.Size = new System.Drawing.Size(167, 17); + this.l2dMonoBehaviourRadioButton.TabIndex = 0; + this.l2dMonoBehaviourRadioButton.TabStop = true; + this.l2dMonoBehaviourRadioButton.Text = "MonoBehaviour (Fade motion)"; + this.optionTooltip.SetToolTip(this.l2dMonoBehaviourRadioButton, "If no Fade motions are found, the AnimationClip method will be used"); + this.l2dMonoBehaviourRadioButton.UseVisualStyleBackColor = true; + // + // l2dAnimationClipRadioButton + // + this.l2dAnimationClipRadioButton.AccessibleName = "AnimationClip"; + this.l2dAnimationClipRadioButton.AutoSize = true; + this.l2dAnimationClipRadioButton.Location = new System.Drawing.Point(172, 5); + this.l2dAnimationClipRadioButton.Name = "l2dAnimationClipRadioButton"; + this.l2dAnimationClipRadioButton.Size = new System.Drawing.Size(88, 17); + this.l2dAnimationClipRadioButton.TabIndex = 1; + this.l2dAnimationClipRadioButton.Text = "AnimationClip"; + this.l2dAnimationClipRadioButton.UseVisualStyleBackColor = true; + // + // l2dMotionExportMethodLabel + // + this.l2dMotionExportMethodLabel.AutoSize = true; + this.l2dMotionExportMethodLabel.Location = new System.Drawing.Point(6, 21); + this.l2dMotionExportMethodLabel.Name = "l2dMotionExportMethodLabel"; + this.l2dMotionExportMethodLabel.Size = new System.Drawing.Size(109, 13); + this.l2dMotionExportMethodLabel.TabIndex = 1; + this.l2dMotionExportMethodLabel.Text = "Motion export method"; + // + // l2dForceBezierCheckBox + // + this.l2dForceBezierCheckBox.AutoSize = true; + this.l2dForceBezierCheckBox.Location = new System.Drawing.Point(6, 77); + this.l2dForceBezierCheckBox.Name = "l2dForceBezierCheckBox"; + this.l2dForceBezierCheckBox.Size = new System.Drawing.Size(278, 17); + this.l2dForceBezierCheckBox.TabIndex = 3; + this.l2dForceBezierCheckBox.Text = "Calculate Linear motion segments as Bezier segments"; + this.optionTooltip.SetToolTip(this.l2dForceBezierCheckBox, "May help if the exported motions look jerky/not smooth enough"); + this.l2dForceBezierCheckBox.UseVisualStyleBackColor = true; + // // groupBox2 // this.groupBox2.AutoSize = true; @@ -284,7 +357,7 @@ this.groupBox2.Location = new System.Drawing.Point(313, 13); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(224, 362); - this.groupBox2.TabIndex = 11; + this.groupBox2.TabIndex = 3; this.groupBox2.TabStop = false; this.groupBox2.Text = "Fbx"; // @@ -295,9 +368,9 @@ this.exportAllUvsAsDiffuseMaps.Location = new System.Drawing.Point(6, 185); this.exportAllUvsAsDiffuseMaps.Name = "exportAllUvsAsDiffuseMaps"; this.exportAllUvsAsDiffuseMaps.Size = new System.Drawing.Size(168, 17); - this.exportAllUvsAsDiffuseMaps.TabIndex = 23; + this.exportAllUvsAsDiffuseMaps.TabIndex = 9; this.exportAllUvsAsDiffuseMaps.Text = "Export all UVs as diffuse maps"; - this.exportUvsTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " + + this.optionTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " + "map."); this.exportAllUvsAsDiffuseMaps.UseVisualStyleBackColor = true; // @@ -309,7 +382,7 @@ this.exportBlendShape.Location = new System.Drawing.Point(6, 138); this.exportBlendShape.Name = "exportBlendShape"; this.exportBlendShape.Size = new System.Drawing.Size(114, 17); - this.exportBlendShape.TabIndex = 22; + this.exportBlendShape.TabIndex = 7; this.exportBlendShape.Text = "Export blendshape"; this.exportBlendShape.UseVisualStyleBackColor = true; // @@ -321,7 +394,7 @@ this.exportAnimations.Location = new System.Drawing.Point(6, 114); this.exportAnimations.Name = "exportAnimations"; this.exportAnimations.Size = new System.Drawing.Size(109, 17); - this.exportAnimations.TabIndex = 21; + this.exportAnimations.TabIndex = 6; this.exportAnimations.Text = "Export animations"; this.exportAnimations.UseVisualStyleBackColor = true; // @@ -336,7 +409,7 @@ this.scaleFactor.Location = new System.Drawing.Point(83, 243); this.scaleFactor.Name = "scaleFactor"; this.scaleFactor.Size = new System.Drawing.Size(60, 20); - this.scaleFactor.TabIndex = 20; + this.scaleFactor.TabIndex = 13; this.scaleFactor.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; this.scaleFactor.Value = new decimal(new int[] { 1, @@ -350,7 +423,7 @@ this.label5.Location = new System.Drawing.Point(6, 245); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(64, 13); - this.label5.TabIndex = 19; + this.label5.TabIndex = 12; this.label5.Text = "ScaleFactor"; // // fbxFormat @@ -363,7 +436,7 @@ this.fbxFormat.Location = new System.Drawing.Point(77, 275); this.fbxFormat.Name = "fbxFormat"; this.fbxFormat.Size = new System.Drawing.Size(61, 21); - this.fbxFormat.TabIndex = 18; + this.fbxFormat.TabIndex = 15; // // label4 // @@ -371,7 +444,7 @@ this.label4.Location = new System.Drawing.Point(6, 280); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(59, 13); - this.label4.TabIndex = 17; + this.label4.TabIndex = 14; this.label4.Text = "FBXFormat"; // // fbxVersion @@ -388,7 +461,7 @@ this.fbxVersion.Location = new System.Drawing.Point(77, 308); this.fbxVersion.Name = "fbxVersion"; this.fbxVersion.Size = new System.Drawing.Size(47, 21); - this.fbxVersion.TabIndex = 16; + this.fbxVersion.TabIndex = 17; // // label3 // @@ -396,7 +469,7 @@ this.label3.Location = new System.Drawing.Point(6, 311); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(62, 13); - this.label3.TabIndex = 15; + this.label3.TabIndex = 16; this.label3.Text = "FBXVersion"; // // boneSize @@ -428,7 +501,7 @@ this.exportSkins.Location = new System.Drawing.Point(6, 90); this.exportSkins.Name = "exportSkins"; this.exportSkins.Size = new System.Drawing.Size(83, 17); - this.exportSkins.TabIndex = 8; + this.exportSkins.TabIndex = 5; this.exportSkins.Text = "Export skins"; this.exportSkins.UseVisualStyleBackColor = true; // @@ -438,7 +511,7 @@ this.label1.Location = new System.Drawing.Point(26, 42); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(72, 13); - this.label1.TabIndex = 7; + this.label1.TabIndex = 2; this.label1.Text = "FilterPrecision"; // // filterPrecision @@ -452,7 +525,7 @@ this.filterPrecision.Location = new System.Drawing.Point(127, 40); this.filterPrecision.Name = "filterPrecision"; this.filterPrecision.Size = new System.Drawing.Size(51, 20); - this.filterPrecision.TabIndex = 6; + this.filterPrecision.TabIndex = 3; this.filterPrecision.Value = new decimal(new int[] { 25, 0, @@ -465,7 +538,7 @@ this.castToBone.Location = new System.Drawing.Point(6, 161); this.castToBone.Name = "castToBone"; this.castToBone.Size = new System.Drawing.Size(131, 17); - this.castToBone.TabIndex = 5; + this.castToBone.TabIndex = 8; this.castToBone.Text = "All nodes cast to bone"; this.castToBone.UseVisualStyleBackColor = true; // @@ -489,7 +562,7 @@ this.eulerFilter.Location = new System.Drawing.Point(6, 22); this.eulerFilter.Name = "eulerFilter"; this.eulerFilter.Size = new System.Drawing.Size(72, 17); - this.eulerFilter.TabIndex = 3; + this.eulerFilter.TabIndex = 1; this.eulerFilter.Text = "EulerFilter"; this.eulerFilter.UseVisualStyleBackColor = true; // @@ -500,6 +573,7 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.Cancel; this.ClientSize = new System.Drawing.Size(549, 416); + this.Controls.Add(this.l2dGroupBox); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.Cancel); @@ -516,6 +590,10 @@ this.groupBox1.PerformLayout(); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); + this.l2dGroupBox.ResumeLayout(false); + this.l2dGroupBox.PerformLayout(); + this.l2dMotionExportMethodPanel.ResumeLayout(false); + this.l2dMotionExportMethodPanel.PerformLayout(); this.groupBox2.ResumeLayout(false); this.groupBox2.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).EndInit(); @@ -559,8 +637,14 @@ private System.Windows.Forms.CheckBox restoreExtensionName; private System.Windows.Forms.CheckBox openAfterExport; private System.Windows.Forms.CheckBox exportAllUvsAsDiffuseMaps; - private System.Windows.Forms.ToolTip exportUvsTooltip; + private System.Windows.Forms.ToolTip optionTooltip; private System.Windows.Forms.CheckBox exportSpriteWithAlphaMask; private System.Windows.Forms.RadioButton towebp; + private System.Windows.Forms.GroupBox l2dGroupBox; + private System.Windows.Forms.CheckBox l2dForceBezierCheckBox; + private System.Windows.Forms.Label l2dMotionExportMethodLabel; + private System.Windows.Forms.RadioButton l2dAnimationClipRadioButton; + private System.Windows.Forms.RadioButton l2dMonoBehaviourRadioButton; + private System.Windows.Forms.Panel l2dMotionExportMethodPanel; } } \ No newline at end of file diff --git a/AssetStudioGUI/ExportOptions.cs b/AssetStudioGUI/ExportOptions.cs index c3542a1..00407cc 100644 --- a/AssetStudioGUI/ExportOptions.cs +++ b/AssetStudioGUI/ExportOptions.cs @@ -1,5 +1,6 @@ using AssetStudio; using System; +using System.Linq; using System.Windows.Forms; namespace AssetStudioGUI @@ -14,15 +15,8 @@ namespace AssetStudioGUI converttexture.Checked = Properties.Settings.Default.convertTexture; exportSpriteWithAlphaMask.Checked = Properties.Settings.Default.exportSpriteWithMask; convertAudio.Checked = Properties.Settings.Default.convertAudio; - var str = Properties.Settings.Default.convertType.ToString(); - foreach (Control c in panel1.Controls) - { - if (c.Text == str) - { - ((RadioButton)c).Checked = true; - break; - } - } + var defaultImageType = Properties.Settings.Default.convertType.ToString(); + ((RadioButton)panel1.Controls.Cast().First(x => x.Text == defaultImageType)).Checked = true; openAfterExport.Checked = Properties.Settings.Default.openAfterExport; eulerFilter.Checked = Properties.Settings.Default.eulerFilter; filterPrecision.Value = Properties.Settings.Default.filterPrecision; @@ -36,7 +30,9 @@ namespace AssetStudioGUI scaleFactor.Value = Properties.Settings.Default.scaleFactor; fbxVersion.SelectedIndex = Properties.Settings.Default.fbxVersion; fbxFormat.SelectedIndex = Properties.Settings.Default.fbxFormat; - + var defaultMotionMode = Properties.Settings.Default.l2dMotionMode.ToString(); + ((RadioButton)l2dMotionExportMethodPanel.Controls.Cast().First(x => x.AccessibleName == defaultMotionMode)).Checked = true; + l2dForceBezierCheckBox.Checked = Properties.Settings.Default.l2dForceBezier; } private void OKbutton_Click(object sender, EventArgs e) @@ -46,14 +42,8 @@ namespace AssetStudioGUI Properties.Settings.Default.convertTexture = converttexture.Checked; Properties.Settings.Default.exportSpriteWithMask = exportSpriteWithAlphaMask.Checked; Properties.Settings.Default.convertAudio = convertAudio.Checked; - foreach (Control c in panel1.Controls) - { - if (((RadioButton)c).Checked) - { - Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), c.Text); - break; - } - } + var checkedImageType = (RadioButton)panel1.Controls.Cast().First(x => ((RadioButton)x).Checked); + Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), checkedImageType.Text); Properties.Settings.Default.openAfterExport = openAfterExport.Checked; Properties.Settings.Default.eulerFilter = eulerFilter.Checked; Properties.Settings.Default.filterPrecision = filterPrecision.Value; @@ -67,6 +57,9 @@ namespace AssetStudioGUI Properties.Settings.Default.scaleFactor = scaleFactor.Value; Properties.Settings.Default.fbxVersion = fbxVersion.SelectedIndex; Properties.Settings.Default.fbxFormat = fbxFormat.SelectedIndex; + var checkedMotionMode = (RadioButton)l2dMotionExportMethodPanel.Controls.Cast().First(x => ((RadioButton)x).Checked); + Properties.Settings.Default.l2dMotionMode = (CubismLive2DExtractor.Live2DMotionMode)Enum.Parse(typeof(CubismLive2DExtractor.Live2DMotionMode), checkedMotionMode.AccessibleName); + Properties.Settings.Default.l2dForceBezier = l2dForceBezierCheckBox.Checked; Properties.Settings.Default.Save(); DialogResult = DialogResult.OK; Close(); diff --git a/AssetStudioGUI/ExportOptions.resx b/AssetStudioGUI/ExportOptions.resx index 6966b18..7da86a7 100644 --- a/AssetStudioGUI/ExportOptions.resx +++ b/AssetStudioGUI/ExportOptions.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 \ No newline at end of file diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 9ba3bcf..038ad17 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace AssetStudioGUI.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -286,5 +286,29 @@ namespace AssetStudioGUI.Properties { this["exportSpriteWithMask"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("MonoBehaviour")] + public global::CubismLive2DExtractor.Live2DMotionMode l2dMotionMode { + get { + return ((global::CubismLive2DExtractor.Live2DMotionMode)(this["l2dMotionMode"])); + } + set { + this["l2dMotionMode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool l2dForceBezier { + get { + return ((bool)(this["l2dForceBezier"])); + } + set { + this["l2dForceBezier"] = value; + } + } } } diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index aef06e0..e8ab68d 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -68,5 +68,11 @@ True + + MonoBehaviour + + + False + \ No newline at end of file diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 8b0acc9..94ab0c2 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -746,6 +746,8 @@ namespace AssetStudioGUI public static void ExportLive2D(Object[] cubismMocs, string exportPath) { var baseDestPath = Path.Combine(exportPath, "Live2DOutput"); + var motionMode = Properties.Settings.Default.l2dMotionMode; + var forceBezier = Properties.Settings.Default.l2dForceBezier; ThreadPool.QueueUserWorkItem(state => { @@ -805,7 +807,7 @@ namespace AssetStudioGUI container = Path.HasExtension(container) ? container.Replace(Path.GetExtension(container), "") : container; var destPath = Path.Combine(baseDestPath, container) + Path.DirectorySeparatorChar; - ExtractLive2D(assets, destPath, modelName, assemblyLoader); + ExtractLive2D(assets, destPath, modelName, assemblyLoader, motionMode, forceBezier); modelCounter++; } catch (Exception ex) diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs new file mode 100644 index 0000000..f14b35a --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs @@ -0,0 +1,30 @@ +using System; + +namespace CubismLive2DExtractor +{ + public class AnimationCurve + { + public CubismKeyframeData[] m_Curve { get; set; } + public int m_PreInfinity { get; set; } + public int m_PostInfinity { get; set; } + public int m_RotationOrder { get; set; } + } + + public class CubismFadeMotion + { + public string m_Name { get; set; } + public string MotionName { get; set; } + public float FadeInTime { get; set; } + public float FadeOutTime { get; set; } + public string[] ParameterIds { get; set; } + public AnimationCurve[] ParameterCurves { get; set; } + public float[] ParameterFadeInTimes { get; set; } + public float[] ParameterFadeOutTimes { get; set; } + public float MotionLength { get; set; } + + public CubismFadeMotion() + { + ParameterIds = Array.Empty(); + } + } +} diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs new file mode 100644 index 0000000..2b01705 --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismKeyframeData.cs @@ -0,0 +1,26 @@ +namespace CubismLive2DExtractor +{ + public class CubismKeyframeData + { + public float time { get; set; } + public float value { get; set; } + public float inSlope { get; set; } + public float outSlope { get; set; } + public int weightedMode { get; set; } + public float inWeight { get; set; } + public float outWeight { get; set; } + + public CubismKeyframeData() { } + + public CubismKeyframeData(ImportedKeyframe keyframe) + { + time = keyframe.time; + value = keyframe.value; + inSlope = keyframe.inSlope; + outSlope = keyframe.outSlope; + weightedMode = 0; + inWeight = 0; + outWeight = 0; + } + } +} diff --git a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs index c5cd623..2d0abec 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs @@ -1,7 +1,9 @@ -using System; +// File Format Specifications +// https://github.com/Live2D/CubismSpecs/blob/master/FileFormats/motion3.json.md + +using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace CubismLive2DExtractor { @@ -18,24 +20,213 @@ namespace CubismLive2DExtractor public float Fps; public bool Loop; public bool AreBeziersRestricted; + public float FadeInTime; + public float FadeOutTime; 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 float FadeInTime; + public float FadeOutTime; public List Segments; - }; + } public class SerializableUserData { public float Time; public string Value; } + + private static void AddSegments( + CubismKeyframeData curve, + CubismKeyframeData preCurve, + CubismKeyframeData nextCurve, + SerializableCurve cubismCurve, + bool forceBezier, + ref int totalPointCount, + ref int totalSegmentCount, + ref int j + ) + { + if (Math.Abs(curve.time - preCurve.time - 0.01f) < 0.0001f) // InverseSteppedSegment + { + if (nextCurve.value == curve.value) + { + cubismCurve.Segments.Add(3f); // Segment ID + cubismCurve.Segments.Add(nextCurve.time); + cubismCurve.Segments.Add(nextCurve.value); + j += 1; + totalPointCount += 1; + totalSegmentCount++; + return; + } + } + if (float.IsPositiveInfinity(curve.inSlope)) // SteppedSegment + { + cubismCurve.Segments.Add(2f); // Segment ID + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 1; + } + else if (preCurve.outSlope == 0f && Math.Abs(curve.inSlope) < 0.0001f && !forceBezier) // LinearSegment + { + cubismCurve.Segments.Add(0f); // Segment ID + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 1; + } + else // BezierSegment + { + var tangentLength = (curve.time - preCurve.time) / 3f; + cubismCurve.Segments.Add(1f); // Segment ID + cubismCurve.Segments.Add(preCurve.time + tangentLength); + cubismCurve.Segments.Add(preCurve.outSlope * tangentLength + preCurve.value); + cubismCurve.Segments.Add(curve.time - tangentLength); + cubismCurve.Segments.Add(curve.value - curve.inSlope * tangentLength); + cubismCurve.Segments.Add(curve.time); + cubismCurve.Segments.Add(curve.value); + totalPointCount += 3; + } + totalSegmentCount++; + } + + public CubismMotion3Json(CubismFadeMotion fadeMotion, bool forceBezier) + { + Version = 3; + Meta = new SerializableMeta + { + // Duration of the motion in seconds. + Duration = fadeMotion.MotionLength, + // Framerate of the motion in seconds. + Fps = 30, + // [Optional] Status of the looping of the motion. + Loop = true, + // [Optional] Status of the restriction of Bezier handles'X translations. + AreBeziersRestricted = true, + // [Optional] Time of the overall Fade-In for easing in seconds. + FadeInTime = fadeMotion.FadeInTime, + // [Optional] Time of the overall Fade-Out for easing in seconds. + FadeOutTime = fadeMotion.FadeOutTime, + // The total number of curves. + CurveCount = (int)fadeMotion.ParameterCurves.LongCount(x => x.m_Curve.Length > 0), + // [Optional] The total number of UserData. + UserDataCount = 0 + }; + // Motion curves. + Curves = new SerializableCurve[Meta.CurveCount]; + + var totalSegmentCount = 1; + var totalPointCount = 1; + var actualCurveCount = 0; + for (var i = 0; i < fadeMotion.ParameterCurves.Length; i++) + { + if (fadeMotion.ParameterCurves[i].m_Curve.Length == 0) + continue; + + Curves[actualCurveCount] = new SerializableCurve + { + // Target type. + Target = "Parameter", + // Identifier for mapping curve to target. + Id = fadeMotion.ParameterIds[i], + // [Optional] Time of the Fade - In for easing in seconds. + FadeInTime = fadeMotion.ParameterFadeInTimes[i], + // [Optional] Time of the Fade - Out for easing in seconds. + FadeOutTime = fadeMotion.ParameterFadeOutTimes[i], + // Flattened segments. + Segments = new List + { + // First point + fadeMotion.ParameterCurves[i].m_Curve[0].time, + fadeMotion.ParameterCurves[i].m_Curve[0].value + } + }; + for (var j = 1; j < fadeMotion.ParameterCurves[i].m_Curve.Length; j++) + { + var curve = fadeMotion.ParameterCurves[i].m_Curve[j]; + var preCurve = fadeMotion.ParameterCurves[i].m_Curve[j - 1]; + var next = fadeMotion.ParameterCurves[i].m_Curve.ElementAtOrDefault(j + 1); + var nextCurve = next ?? new CubismKeyframeData(); + AddSegments(curve, preCurve, nextCurve, Curves[actualCurveCount], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j); + } + actualCurveCount++; + } + + // The total number of segments (from all curves). + Meta.TotalSegmentCount = totalSegmentCount; + // The total number of points (from all segments of all curves). + Meta.TotalPointCount = totalPointCount; + + UserData = Array.Empty(); + // [Optional] The total size of UserData in bytes. + Meta.TotalUserDataSize = 0; + } + + public CubismMotion3Json(ImportedKeyframedAnimation animation, bool forceBezier) + { + Version = 3; + Meta = new SerializableMeta + { + Duration = animation.Duration, + Fps = animation.SampleRate, + Loop = true, + AreBeziersRestricted = true, + FadeInTime = 0, + FadeOutTime = 0, + CurveCount = animation.TrackList.Count, + UserDataCount = animation.Events.Count + }; + Curves = new SerializableCurve[Meta.CurveCount]; + + var totalSegmentCount = 1; + var totalPointCount = 1; + for (var i = 0; i < Meta.CurveCount; i++) + { + var track = animation.TrackList[i]; + Curves[i] = new SerializableCurve + { + Target = track.Target, + Id = track.Name, + FadeInTime = -1, + FadeOutTime = -1, + Segments = new List + { + 0f, + track.Curve[0].value + } + }; + for (var j = 1; j < track.Curve.Count; j++) + { + var curve = new CubismKeyframeData(track.Curve[j]); + var preCurve = new CubismKeyframeData(track.Curve[j - 1]); + var next = track.Curve.ElementAtOrDefault(j + 1); + var nextCurve = next != null ? new CubismKeyframeData(next) : new CubismKeyframeData(); + AddSegments(curve, preCurve, nextCurve, Curves[i], forceBezier, ref totalPointCount, ref totalSegmentCount, ref j); + } + } + Meta.TotalSegmentCount = totalSegmentCount; + Meta.TotalPointCount = totalPointCount; + + UserData = new SerializableUserData[Meta.UserDataCount]; + var totalUserDataSize = 0; + for (var i = 0; i < Meta.UserDataCount; i++) + { + var @event = animation.Events[i]; + UserData[i] = new SerializableUserData + { + Time = @event.time, + Value = @event.value + }; + totalUserDataSize += @event.value.Length; + } + Meta.TotalUserDataSize = totalUserDataSize; + } } } diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs index db4530c..19de546 100644 --- a/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DExtractor.cs @@ -18,7 +18,7 @@ namespace CubismLive2DExtractor { public static class Live2DExtractor { - public static void ExtractLive2D(IGrouping assets, string destPath, string modelName, AssemblyLoader assemblyLoader) + public static void ExtractLive2D(IGrouping assets, string destPath, string modelName, AssemblyLoader assemblyLoader, Live2DMotionMode motionMode, bool forceBezier = false) { var destTexturePath = Path.Combine(destPath, "textures") + Path.DirectorySeparatorChar; var destMotionPath = Path.Combine(destPath, "motions") + Path.DirectorySeparatorChar; @@ -27,6 +27,7 @@ namespace CubismLive2DExtractor Directory.CreateDirectory(destTexturePath); var expressionList = new List(); + var fadeMotionList = new List(); var gameObjects = new List(); var animationClips = new List(); @@ -53,6 +54,9 @@ namespace CubismLive2DExtractor case "CubismExpressionData": expressionList.Add(m_MonoBehaviour); break; + case "CubismFadeMotionData": + fadeMotionList.Add(m_MonoBehaviour); + break; case "CubismEyeBlinkParameter": if (m_MonoBehaviour.m_GameObject.TryGet(out var blinkGameObject)) { @@ -110,7 +114,42 @@ namespace CubismLive2DExtractor //motion var motions = new SortedDictionary(); - if (gameObjects.Count > 0) + if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour + { + Directory.CreateDirectory(destMotionPath); + foreach (var fadeMotionMono in fadeMotionList) + { + var fadeMotionObj = fadeMotionMono.ToType(); + if (fadeMotionObj == null) + { + var m_Type = fadeMotionMono.ConvertToTypeTree(assemblyLoader); + fadeMotionObj = fadeMotionMono.ToType(m_Type); + if (fadeMotionObj == null) + { + Logger.Warning($"Fade motion \"{fadeMotionMono.m_Name}\" is not readable."); + continue; + } + } + var fadeMotion = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(fadeMotionObj)); + if (fadeMotion.ParameterIds.Length == 0) + continue; + + var motionJson = new CubismMotion3Json(fadeMotion, forceBezier); + + var animName = Path.GetFileNameWithoutExtension(fadeMotion.m_Name); + if (motions.ContainsKey(animName)) + { + animName = $"{animName}_{fadeMotion.GetHashCode()}"; + + if (motions.ContainsKey(animName)) + continue; + } + var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json")); + motions.Add(animName, new JArray(motionPath)); + File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter())); + } + } + else if (gameObjects.Count > 0) //motion from AnimationClip { var rootTransform = gameObjects[0].m_Transform; while (rootTransform.m_Father.TryGet(out var m_Father)) @@ -123,111 +162,27 @@ namespace CubismLive2DExtractor { Directory.CreateDirectory(destMotionPath); } - foreach (ImportedKeyframedAnimation animation in converter.AnimationList) + foreach (var 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 { 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; + var motionJson = new CubismMotion3Json(animation, forceBezier); var animName = animation.Name; if (motions.ContainsKey(animName)) { animName = $"{animName}_{animation.GetHashCode()}"; + if (motions.ContainsKey(animName)) - { continue; - } } var motionPath = new JObject(new JProperty("File", $"motions/{animName}.motion3.json")); motions.Add(animName, new JArray(motionPath)); - File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter())); + File.WriteAllText($"{destMotionPath}{animName}.motion3.json", JsonConvert.SerializeObject(motionJson, Formatting.Indented, new MyJsonConverter())); } } + else + { + Logger.Warning($"No motions found for \"{modelName}\" model."); + } //expression var expressions = new JArray(); diff --git a/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs b/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs new file mode 100644 index 0000000..60c3acc --- /dev/null +++ b/AssetStudioUtility/CubismLive2DExtractor/Live2DMotionMode.cs @@ -0,0 +1,8 @@ +namespace CubismLive2DExtractor +{ + public enum Live2DMotionMode + { + MonoBehaviour, + AnimationClip + } +}