using System.Collections.Generic; using System.Text.Json; namespace Cthangover.Live2D.Cubism.Framework { /// /// JSON parser for the Cubism .motion3.json format. /// Provides typed access to the Meta section (duration, loop, fps, fade times, /// curve statistics, beziers-restricted flag) and the Curves/UserData sections. /// /// Parsed curve segments are returned as flat float arrays (interleaved /// segment-type markers and point data), decoded by . /// public class CubismMotionJson { private readonly JsonDocument _json; private readonly JsonElement _meta; private readonly JsonElement _curves; private readonly JsonElement _userData; private readonly bool _hasUserData; private readonly bool _isLoop; private readonly float _duration; private readonly float _fps; private readonly int _curveCount; private readonly int _totalSegmentCount; private readonly int _totalPointCount; private readonly bool _areBeziersRestricted; /// /// Parses the given .motion3.json bytes and pre-extracts all Meta fields /// into readonly fields for fast repeated access. /// public CubismMotionJson(byte[] jsonBytes) { _json = JsonDocument.Parse(jsonBytes); var root = _json.RootElement; _meta = root.GetProperty("Meta"); _curves = root.GetProperty("Curves"); _hasUserData = root.TryGetProperty("UserData", out _userData); _duration = _meta.GetProperty("Duration").GetSingle(); _isLoop = _meta.TryGetProperty("Loop", out var loop) && loop.GetBoolean(); _fps = _meta.TryGetProperty("Fps", out var fpsEl) ? fpsEl.GetSingle() : 30f; _curveCount = _meta.TryGetProperty("CurveCount", out var ccEl) ? ccEl.GetInt32() : 0; _totalSegmentCount = _meta.TryGetProperty("TotalSegmentCount", out var tscEl) ? tscEl.GetInt32() : 0; _totalPointCount = _meta.TryGetProperty("TotalPointCount", out var tpcEl) ? tpcEl.GetInt32() : 0; _areBeziersRestricted = _meta.TryGetProperty("AreBeziersRestricted", out var abrEl) && abrEl.GetBoolean(); } /// Total motion duration in seconds from the Meta section. public float GetDuration() => _duration; /// Whether the motion loops. public bool IsLoop() => _isLoop; /// Frames per second, default 30. public float GetFps() => _fps; /// Number of curves in this motion. public int GetCurveCount() => _curveCount; /// Total segment count across all curves. public int GetTotalSegmentCount() => _totalSegmentCount; /// Total point count across all curves. public int GetTotalPointCount() => _totalPointCount; /// /// If true, bezier curves use binary search instead of the Cardano formula /// (non-standard bezier parameterization). /// public bool AreBeziersRestricted() => _areBeziersRestricted; /// /// Motion-level fade-in time in seconds, or -1 if not specified. /// public float GetFadeInTime() => _meta.TryGetProperty("FadeInTime", out var v) ? v.GetSingle() : -1f; /// /// Motion-level fade-out time in seconds, or -1 if not specified. /// public float GetFadeOutTime() => _meta.TryGetProperty("FadeOutTime", out var v) ? v.GetSingle() : -1f; /// /// Returns the target string for the curve at the given index /// ("Model", "Parameter", or "PartOpacity"). /// public string GetCurveTarget(int index) => _curves[index].GetProperty("Target").GetString(); /// /// Returns the parameter/part/model ID string for the curve at the given index. /// public string GetCurveId(int index) => _curves[index].GetProperty("Id").GetString(); /// /// Per-curve fade-in time in seconds, or -1 if not specified. /// public float GetCurveFadeInTime(int index) => _curves[index].TryGetProperty("FadeInTime", out var v) ? v.GetSingle() : -1f; /// /// Per-curve fade-out time in seconds, or -1 if not specified. /// public float GetCurveFadeOutTime(int index) => _curves[index].TryGetProperty("FadeOutTime", out var v) ? v.GetSingle() : -1f; /// /// Reads the raw float array of segment data for a curve. /// The first element is the segment type marker (cast to int), /// followed by point data depending on the segment type. /// public void GetCurveSegments(int index, out float[] segments) { var segArr = _curves[index].GetProperty("Segments"); segments = new float[segArr.GetArrayLength()]; int si = 0; foreach (var s in segArr.EnumerateArray()) segments[si++] = s.GetSingle(); } /// Number of user-data events in the motion. public int GetEventCount() => _hasUserData ? _userData.GetArrayLength() : 0; /// Fire time of the event at the given index, in seconds. public float GetEventTime(int index) => _hasUserData && index < _userData.GetArrayLength() ? _userData[index].GetProperty("Time").GetSingle() : 0f; /// String value of the event at the given index. public string GetEventValue(int index) => _hasUserData && index < _userData.GetArrayLength() ? _userData[index].GetProperty("Value").GetString() : null; } }