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(); } }