using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace Cthangover.Live2D.Cubism.Framework
{
///
/// Interface for reading a Cubism model3.json setting file.
/// All accessors return default/empty values when the corresponding
/// data is absent from the JSON.
///
public interface ICubismModelSetting
{
/// Returns the relative path to the .moc3 file.
string GetModelFileName();
/// Returns the number of texture references in the model.
int GetTextureCount();
/// Returns the relative path to the texture at the given index.
string GetTextureFileName(int index);
/// Returns the relative path to the physics3.json file, or null.
string GetPhysicsFileName();
/// Returns the relative path to the pose3.json file, or null.
string GetPoseFileName();
/// Returns the number of expressions defined in the model.
int GetExpressionCount();
/// Returns the display name of the expression at the given index.
string GetExpressionName(int index);
/// Returns the relative path to the .exp3.json file for the expression at the given index.
string GetExpressionFileName(int index);
/// Returns the number of motion groups.
int GetMotionGroupCount();
/// Returns the name of the motion group at the given index.
string GetMotionGroupName(int index);
/// Returns the number of motions within the named group.
int GetMotionCount(string groupName);
/// Returns the relative path to the motion3.json file for the given group and index.
string GetMotionFileName(string groupName, int index);
/// Returns the relative path to the associated sound file, or null.
string GetMotionSoundFileName(string groupName, int index);
/// Returns the fade-in time in seconds for the motion, or -1 if not specified.
float GetMotionFadeInTimeValue(string groupName, int index);
/// Returns the fade-out time in seconds for the motion, or -1 if not specified.
float GetMotionFadeOutTimeValue(string groupName, int index);
/// Returns the number of parameters in the EyeBlink group.
int GetEyeBlinkParameterCount();
/// Returns the parameter ID at the given index within the EyeBlink group.
string GetEyeBlinkParameterId(int index);
/// Returns the number of parameters in the LipSync group.
int GetLipSyncParameterCount();
/// Returns the parameter ID at the given index within the LipSync group.
string GetLipSyncParameterId(int index);
/// Returns the number of hit area definitions.
int GetHitAreasCount();
/// Returns the hit area ID at the given index.
string GetHitAreaId(int index);
/// Returns the hit area display name at the given index.
string GetHitAreaName(int index);
///
/// 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.
///
Dictionary GetLayoutMap();
}
///
/// JSON parser for the Cubism .model3.json format (Live2D model settings).
/// Implements .
///
/// 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 and should be disposed
/// via when no longer needed.
///
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;
///
/// Parses the given .model3.json bytes.
///
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 GetLayoutMap()
{
var map = new Dictionary();
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;
///
/// Releases the underlying .
///
public void Dispose() => _json?.Dispose();
}
}