Files
mod_live2d/source_code/Cubism/Framework/CubismModelSettingJson.cs
T
2026-09-10 10:59:37 +03:00

240 lines
10 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace Cthangover.Live2D.Cubism.Framework
{
/// <summary>
/// Interface for reading a Cubism model3.json setting file.
/// All accessors return default/empty values when the corresponding
/// data is absent from the JSON.
/// </summary>
public interface ICubismModelSetting
{
/// <summary>Returns the relative path to the .moc3 file.</summary>
string GetModelFileName();
/// <summary>Returns the number of texture references in the model.</summary>
int GetTextureCount();
/// <summary>Returns the relative path to the texture at the given index.</summary>
string GetTextureFileName(int index);
/// <summary>Returns the relative path to the physics3.json file, or null.</summary>
string GetPhysicsFileName();
/// <summary>Returns the relative path to the pose3.json file, or null.</summary>
string GetPoseFileName();
/// <summary>Returns the number of expressions defined in the model.</summary>
int GetExpressionCount();
/// <summary>Returns the display name of the expression at the given index.</summary>
string GetExpressionName(int index);
/// <summary>Returns the relative path to the .exp3.json file for the expression at the given index.</summary>
string GetExpressionFileName(int index);
/// <summary>Returns the number of motion groups.</summary>
int GetMotionGroupCount();
/// <summary>Returns the name of the motion group at the given index.</summary>
string GetMotionGroupName(int index);
/// <summary>Returns the number of motions within the named group.</summary>
int GetMotionCount(string groupName);
/// <summary>Returns the relative path to the motion3.json file for the given group and index.</summary>
string GetMotionFileName(string groupName, int index);
/// <summary>Returns the relative path to the associated sound file, or null.</summary>
string GetMotionSoundFileName(string groupName, int index);
/// <summary>Returns the fade-in time in seconds for the motion, or -1 if not specified.</summary>
float GetMotionFadeInTimeValue(string groupName, int index);
/// <summary>Returns the fade-out time in seconds for the motion, or -1 if not specified.</summary>
float GetMotionFadeOutTimeValue(string groupName, int index);
/// <summary>Returns the number of parameters in the EyeBlink group.</summary>
int GetEyeBlinkParameterCount();
/// <summary>Returns the parameter ID at the given index within the EyeBlink group.</summary>
string GetEyeBlinkParameterId(int index);
/// <summary>Returns the number of parameters in the LipSync group.</summary>
int GetLipSyncParameterCount();
/// <summary>Returns the parameter ID at the given index within the LipSync group.</summary>
string GetLipSyncParameterId(int index);
/// <summary>Returns the number of hit area definitions.</summary>
int GetHitAreasCount();
/// <summary>Returns the hit area ID at the given index.</summary>
string GetHitAreaId(int index);
/// <summary>Returns the hit area display name at the given index.</summary>
string GetHitAreaName(int index);
/// <summary>
/// Returns the Layout section as a dictionary of lowercased key -> float value.
/// Supported keys: width, height, x, y, center_x, center_y, top, bottom, left, right.
/// </summary>
Dictionary<string, float> GetLayoutMap();
}
/// <summary>
/// JSON parser for the Cubism .model3.json format (Live2D model settings).
/// Implements <see cref="ICubismModelSetting"/>.
///
/// Provides typed access to all sections: FileReferences (moc, textures,
/// expressions, motions), Groups (EyeBlink, LipSync), HitAreas, and Layout.
/// All string path values are returned as-is (relative to the model directory);
/// it is the caller's responsibility to resolve them through a file provider.
///
/// Instances hold a <see cref="JsonDocument"/> and should be disposed
/// via <see cref="Dispose"/> when no longer needed.
/// </summary>
public class CubismModelSettingJson : ICubismModelSetting
{
private readonly JsonDocument _json;
private readonly JsonElement _root;
private readonly JsonElement? _fileRefs;
private readonly JsonElement? _groups;
private readonly JsonElement? _hitAreas;
private readonly JsonElement? _layout;
/// <summary>
/// Parses the given .model3.json bytes.
/// </summary>
public CubismModelSettingJson(byte[] jsonBytes)
{
_json = JsonDocument.Parse(jsonBytes);
_root = _json.RootElement;
if (_root.TryGetProperty("FileReferences", out var fr))
_fileRefs = fr;
if (_root.TryGetProperty("Groups", out var gr))
_groups = gr;
if (_root.TryGetProperty("HitAreas", out var ha))
_hitAreas = ha;
if (_root.TryGetProperty("Layout", out var lo))
_layout = lo;
}
public string GetModelFileName() =>
_fileRefs?.GetProperty("Moc").GetString();
public int GetTextureCount() =>
_fileRefs?.TryGetProperty("Textures", out var arr) == true ? arr.GetArrayLength() : 0;
public string GetTextureFileName(int index) =>
_fileRefs?.GetProperty("Textures")[index].GetString();
public string GetPhysicsFileName() =>
TryGetString(_fileRefs, "Physics");
public string GetPoseFileName() =>
TryGetString(_fileRefs, "Pose");
public int GetExpressionCount() =>
_fileRefs?.TryGetProperty("Expressions", out var arr) == true ? arr.GetArrayLength() : 0;
public string GetExpressionName(int index) =>
_fileRefs?.GetProperty("Expressions")[index].GetProperty("Name").GetString();
public string GetExpressionFileName(int index) =>
_fileRefs?.GetProperty("Expressions")[index].GetProperty("File").GetString();
public int GetMotionGroupCount() =>
_fileRefs?.TryGetProperty("Motions", out var motions) == true ? motions.EnumerateObject().Count() : 0;
public string GetMotionGroupName(int index)
{
int i = 0;
foreach (var prop in _fileRefs?.GetProperty("Motions").EnumerateObject() ?? default)
if (i++ == index) return prop.Name;
return null;
}
public int GetMotionCount(string groupName) =>
_fileRefs?.TryGetProperty("Motions", out var m) == true &&
m.TryGetProperty(groupName, out var grp) ? grp.GetArrayLength() : 0;
public string GetMotionFileName(string groupName, int index) =>
_fileRefs?.GetProperty("Motions").GetProperty(groupName)[index].GetProperty("File").GetString();
public string GetMotionSoundFileName(string groupName, int index) =>
TryGetString(_fileRefs?.GetProperty("Motions").GetProperty(groupName)[index], "Sound");
public float GetMotionFadeInTimeValue(string groupName, int index) =>
TryGetFloat(_fileRefs?.GetProperty("Motions").GetProperty(groupName)[index], "FadeInTime");
public float GetMotionFadeOutTimeValue(string groupName, int index) =>
TryGetFloat(_fileRefs?.GetProperty("Motions").GetProperty(groupName)[index], "FadeOutTime");
public int GetEyeBlinkParameterCount()
{
foreach (var group in _groups?.EnumerateArray() ?? default)
if (group.GetProperty("Name").GetString() == "EyeBlink")
return group.GetProperty("Ids").GetArrayLength();
return 0;
}
public string GetEyeBlinkParameterId(int index)
{
foreach (var group in _groups?.EnumerateArray() ?? default)
if (group.GetProperty("Name").GetString() == "EyeBlink")
return group.GetProperty("Ids")[index].GetString();
return null;
}
public int GetLipSyncParameterCount()
{
foreach (var group in _groups?.EnumerateArray() ?? default)
if (group.GetProperty("Name").GetString() == "LipSync")
return group.GetProperty("Ids").GetArrayLength();
return 0;
}
public string GetLipSyncParameterId(int index)
{
foreach (var group in _groups?.EnumerateArray() ?? default)
if (group.GetProperty("Name").GetString() == "LipSync")
return group.GetProperty("Ids")[index].GetString();
return null;
}
public int GetHitAreasCount() =>
_hitAreas?.GetArrayLength() ?? 0;
public string GetHitAreaId(int index) =>
_hitAreas?[index].GetProperty("Id").GetString();
public string GetHitAreaName(int index) =>
_hitAreas?[index].GetProperty("Name").GetString();
public Dictionary<string, float> GetLayoutMap()
{
var map = new Dictionary<string, float>();
if (_layout == null) return map;
foreach (var prop in _layout.Value.EnumerateObject())
if (prop.Value.ValueKind == JsonValueKind.Number)
map[prop.Name.ToLowerInvariant()] = prop.Value.GetSingle();
return map;
}
private static string TryGetString(JsonElement? parent, string key) =>
parent?.TryGetProperty(key, out var val) == true ? val.GetString() : null;
private static float TryGetFloat(JsonElement? parent, string key) =>
parent?.TryGetProperty(key, out var val) == true && val.ValueKind == JsonValueKind.Number
? val.GetSingle() : -1f;
/// <summary>
/// Releases the underlying <see cref="JsonDocument"/>.
/// </summary>
public void Dispose() => _json?.Dispose();
}
}