mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-07-20 19:44:16 -04:00
Merge branch 'AssetStudioMod' into ArknightsStudio
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows</TargetFrameworks>
|
||||
<TargetFrameworks>net472;net6.0;net6.0-windows;net7.0;net7.0-windows;net8.0;net8.0-windows</TargetFrameworks>
|
||||
<Version>1.0.1</Version>
|
||||
<Copyright>Copyright © Perfare 2018-2022; Copyright © aelurum 2023</Copyright>
|
||||
<DebugType>embedded</DebugType>
|
||||
@ -22,7 +22,7 @@
|
||||
<PackageReference Include="Kyaru.Texture2DDecoder">
|
||||
<Version>0.17.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
|
||||
|
30
AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs
Normal file
30
AssetStudioUtility/CubismLive2DExtractor/CubismFadeMotion.cs
Normal file
@ -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<string>();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<float> keyframe)
|
||||
{
|
||||
time = keyframe.time;
|
||||
value = keyframe.value;
|
||||
inSlope = keyframe.inSlope;
|
||||
outSlope = keyframe.outSlope;
|
||||
weightedMode = 0;
|
||||
inWeight = 0;
|
||||
outWeight = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AssetStudio;
|
||||
|
||||
@ -73,7 +72,7 @@ namespace CubismLive2DExtractor
|
||||
|
||||
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!.");
|
||||
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" has {iAnim.TrackList.Count} tracks and {iAnim.Events.Count} event!.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,7 +83,7 @@ namespace CubismLive2DExtractor
|
||||
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)}");
|
||||
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,7 +98,7 @@ namespace CubismLive2DExtractor
|
||||
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)}");
|
||||
Logger.Warning($"[Motion Converter] \"{iAnim.Name}\" read fail on binding {Array.IndexOf(m_ClipBindingConstant.genericBindings, binding)}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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,238 @@ 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<float> 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, HashSet<string> paramNames, HashSet<string> partNames, 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;
|
||||
|
||||
string target;
|
||||
string paramId = fadeMotion.ParameterIds[i];
|
||||
switch (paramId)
|
||||
{
|
||||
case "Opacity":
|
||||
case "EyeBlink":
|
||||
case "LipSync":
|
||||
target = "Model";
|
||||
break;
|
||||
default:
|
||||
if (paramNames.Contains(paramId))
|
||||
{
|
||||
target = "Parameter";
|
||||
}
|
||||
else if (partNames.Contains(paramId))
|
||||
{
|
||||
target = "PartOpacity";
|
||||
}
|
||||
else
|
||||
{
|
||||
target = paramId.ToLower().Contains("part") ? "PartOpacity" : "Parameter";
|
||||
AssetStudio.Logger.Warning($"[{fadeMotion.m_Name}] Binding error: Unable to find \"{paramId}\" among the model parts/parameters");
|
||||
}
|
||||
break;
|
||||
}
|
||||
Curves[actualCurveCount] = new SerializableCurve
|
||||
{
|
||||
// Target type.
|
||||
Target = target,
|
||||
// Identifier for mapping curve to target.
|
||||
Id = paramId,
|
||||
// [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<float>
|
||||
{
|
||||
// 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<SerializableUserData>();
|
||||
// [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<float>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace CubismLive2DExtractor
|
||||
{
|
||||
public static class Live2DExtractor
|
||||
{
|
||||
public static void ExtractLive2D(IGrouping<string, AssetStudio.Object> assets, string destPath, string modelName, AssemblyLoader assemblyLoader)
|
||||
public static void ExtractLive2D(IGrouping<string, AssetStudio.Object> 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;
|
||||
@ -26,20 +26,75 @@ namespace CubismLive2DExtractor
|
||||
Directory.CreateDirectory(destPath);
|
||||
Directory.CreateDirectory(destTexturePath);
|
||||
|
||||
var monoBehaviours = new List<MonoBehaviour>();
|
||||
var texture2Ds = new List<Texture2D>();
|
||||
var expressionList = new List<MonoBehaviour>();
|
||||
var fadeMotionList = new List<MonoBehaviour>();
|
||||
var gameObjects = new List<GameObject>();
|
||||
var animationClips = new List<AnimationClip>();
|
||||
|
||||
var textures = new SortedSet<string>();
|
||||
var eyeBlinkParameters = new HashSet<string>();
|
||||
var lipSyncParameters = new HashSet<string>();
|
||||
var parameterNames = new HashSet<string>();
|
||||
var partNames = new HashSet<string>();
|
||||
MonoBehaviour physics = null;
|
||||
|
||||
foreach (var asset in assets)
|
||||
{
|
||||
switch (asset)
|
||||
{
|
||||
case MonoBehaviour m_MonoBehaviour:
|
||||
monoBehaviours.Add(m_MonoBehaviour);
|
||||
if (m_MonoBehaviour.m_Script.TryGet(out var m_Script))
|
||||
{
|
||||
switch (m_Script.m_ClassName)
|
||||
{
|
||||
case "CubismMoc":
|
||||
File.WriteAllBytes($"{destPath}{modelName}.moc3", ParseMoc(m_MonoBehaviour)); //moc
|
||||
break;
|
||||
case "CubismPhysicsController":
|
||||
physics = physics ?? m_MonoBehaviour;
|
||||
break;
|
||||
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))
|
||||
{
|
||||
eyeBlinkParameters.Add(blinkGameObject.m_Name);
|
||||
}
|
||||
break;
|
||||
case "CubismMouthParameter":
|
||||
if (m_MonoBehaviour.m_GameObject.TryGet(out var mouthGameObject))
|
||||
{
|
||||
lipSyncParameters.Add(mouthGameObject.m_Name);
|
||||
}
|
||||
break;
|
||||
case "CubismParameter":
|
||||
if (m_MonoBehaviour.m_GameObject.TryGet(out var paramGameObject))
|
||||
{
|
||||
parameterNames.Add(paramGameObject.m_Name);
|
||||
}
|
||||
break;
|
||||
case "CubismPart":
|
||||
if (m_MonoBehaviour.m_GameObject.TryGet(out var partGameObject))
|
||||
{
|
||||
partNames.Add(partGameObject.m_Name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Texture2D m_Texture2D:
|
||||
texture2Ds.Add(m_Texture2D);
|
||||
using (var image = m_Texture2D.ConvertToImage(flip: true))
|
||||
{
|
||||
using (var file = File.OpenWrite($"{destTexturePath}{m_Texture2D.m_Name}.png"))
|
||||
{
|
||||
image.WriteToStream(file, ImageFormat.Png);
|
||||
}
|
||||
textures.Add($"textures/{m_Texture2D.m_Name}.png"); //texture
|
||||
}
|
||||
break;
|
||||
case GameObject m_GameObject:
|
||||
gameObjects.Add(m_GameObject);
|
||||
@ -50,15 +105,12 @@ namespace CubismLive2DExtractor
|
||||
}
|
||||
}
|
||||
|
||||
//physics
|
||||
var physics = monoBehaviours.FirstOrDefault(x =>
|
||||
if (textures.Count == 0)
|
||||
{
|
||||
if (x.m_Script.TryGet(out var m_Script))
|
||||
{
|
||||
return m_Script.m_ClassName == "CubismPhysicsController";
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Logger.Warning($"No textures found for \"{modelName}\" model.");
|
||||
}
|
||||
|
||||
//physics
|
||||
if (physics != null)
|
||||
{
|
||||
try
|
||||
@ -73,36 +125,51 @@ namespace CubismLive2DExtractor
|
||||
}
|
||||
}
|
||||
|
||||
//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 SortedDictionary<string, JArray>();
|
||||
|
||||
if (gameObjects.Count > 0)
|
||||
if (motionMode == Live2DMotionMode.MonoBehaviour && fadeMotionList.Count > 0) //motion from MonoBehaviour
|
||||
{
|
||||
Logger.Debug("Motion export method: MonoBehaviour (Fade motion)");
|
||||
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<CubismFadeMotion>(JsonConvert.SerializeObject(fadeMotionObj));
|
||||
if (fadeMotion.ParameterIds.Length == 0)
|
||||
continue;
|
||||
|
||||
var motionJson = new CubismMotion3Json(fadeMotion, parameterNames, partNames, 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 exportMethod = motionMode == Live2DMotionMode.AnimationClip
|
||||
? "AnimationClip"
|
||||
: "AnimationClip (no Fade motions found)";
|
||||
Logger.Debug($"Motion export method: {exportMethod}");
|
||||
var rootTransform = gameObjects[0].m_Transform;
|
||||
while (rootTransform.m_Father.TryGet(out var m_Father))
|
||||
{
|
||||
@ -114,114 +181,37 @@ 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<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;
|
||||
var motionJson = new CubismMotion3Json(animation, forceBezier);
|
||||
|
||||
json.UserData = new CubismMotion3Json.SerializableUserData[animation.Events.Count];
|
||||
var totalUserDataSize = 0;
|
||||
for (var i = 0; i < animation.Events.Count; i++)
|
||||
var animName = animation.Name;
|
||||
if (motions.ContainsKey(animName))
|
||||
{
|
||||
var @event = animation.Events[i];
|
||||
json.UserData[i] = new CubismMotion3Json.SerializableUserData
|
||||
{
|
||||
Time = @event.time,
|
||||
Value = @event.value
|
||||
};
|
||||
totalUserDataSize += @event.value.Length;
|
||||
animName = $"{animName}_{animation.GetHashCode()}";
|
||||
|
||||
if (motions.ContainsKey(animName))
|
||||
continue;
|
||||
}
|
||||
json.Meta.TotalUserDataSize = totalUserDataSize;
|
||||
|
||||
var motionPath = new JObject(new JProperty("File", $"motions/{animation.Name}.motion3.json"));
|
||||
motions.Add(animation.Name, new JArray(motionPath));
|
||||
File.WriteAllText($"{destMotionPath}{animation.Name}.motion3.json", JsonConvert.SerializeObject(json, Formatting.Indented, new MyJsonConverter()));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
if (motions.Count == 0)
|
||||
{
|
||||
Logger.Warning($"No motions found for \"{modelName}\" model.");
|
||||
}
|
||||
|
||||
//expression
|
||||
var expressions = new JArray();
|
||||
var monoBehaviourArray = monoBehaviours.Where(x => x.m_Name.EndsWith(".exp3")).ToArray();
|
||||
if (monoBehaviourArray.Length > 0)
|
||||
if (expressionList.Count > 0)
|
||||
{
|
||||
Directory.CreateDirectory(destExpressionPath);
|
||||
}
|
||||
foreach (var monoBehaviour in monoBehaviourArray)
|
||||
foreach (var monoBehaviour in expressionList)
|
||||
{
|
||||
var fullName = monoBehaviour.m_Name;
|
||||
var expressionName = fullName.Replace(".exp3", "");
|
||||
var expressionName = monoBehaviour.m_Name.Replace(".exp3", "");
|
||||
var expressionObj = monoBehaviour.ToType();
|
||||
if (expressionObj == null)
|
||||
{
|
||||
@ -238,57 +228,38 @@ namespace CubismLive2DExtractor
|
||||
expressions.Add(new JObject
|
||||
{
|
||||
{ "Name", expressionName },
|
||||
{ "File", $"expressions/{fullName}.json" }
|
||||
{ "File", $"expressions/{expressionName}.exp3.json" }
|
||||
});
|
||||
File.WriteAllText($"{destExpressionPath}{fullName}.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
|
||||
File.WriteAllText($"{destExpressionPath}{expressionName}.exp3.json", JsonConvert.SerializeObject(expression, Formatting.Indented));
|
||||
}
|
||||
|
||||
//model
|
||||
//group
|
||||
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();
|
||||
//Try looking for group IDs among the gameObjects
|
||||
if (eyeBlinkParameters.Count == 0)
|
||||
{
|
||||
eyeBlinkParameters = gameObjects.Where(x =>
|
||||
{
|
||||
return x.m_Name.ToLower().Contains("eye")
|
||||
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();
|
||||
&& (x.m_Name.ToLower().Contains('l') || x.m_Name.ToLower().Contains('r'))
|
||||
).Select(x => x.m_Name).ToHashSet();
|
||||
}
|
||||
if (lipSyncParameters.Count == 0)
|
||||
{
|
||||
lipSyncParameters = gameObjects.Where(x =>
|
||||
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 = "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",
|
||||
@ -296,6 +267,7 @@ namespace CubismLive2DExtractor
|
||||
Ids = lipSyncParameters.ToArray()
|
||||
});
|
||||
|
||||
//model
|
||||
var model3 = new CubismModel3Json
|
||||
{
|
||||
Version = 3,
|
||||
|
@ -0,0 +1,8 @@
|
||||
namespace CubismLive2DExtractor
|
||||
{
|
||||
public enum Live2DMotionMode
|
||||
{
|
||||
MonoBehaviour,
|
||||
AnimationClip
|
||||
}
|
||||
}
|
@ -154,9 +154,16 @@ namespace AssetStudio
|
||||
if (triangles.Length < 1024)
|
||||
{
|
||||
var rectP = new RectangularPolygon(0, 0, rect.Width, rect.Height);
|
||||
spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path)));
|
||||
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||
return spriteImage;
|
||||
try
|
||||
{
|
||||
spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path)));
|
||||
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||
return spriteImage;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
using (var mask = new Image<Bgra32>(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black))
|
||||
{
|
||||
@ -167,9 +174,9 @@ namespace AssetStudio
|
||||
return spriteImage;
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
Logger.Warning($"{m_Sprite.m_Name} Unable to render the packed sprite correctly.\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user