| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683 | /** * Copyright(c) Live2D Inc. All rights reserved. * * Use of this source code is governed by the Live2D Open Software license * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. */using System;using System.Collections.Generic;using Live2D.Cubism.Core;using Live2D.Cubism.Framework.MouthMovement;using Live2D.Cubism.Rendering;#if UNITY_EDITORusing UnityEditor;#endifusing UnityEngine;namespace Live2D.Cubism.Framework.Json{    /// <summary>    /// Contains Cubism motion3.json data.    /// </summary>    [Serializable]    // ReSharper disable once ClassCannotBeInstantiated    public sealed class CubismMotion3Json    {        #region Load Methods        /// <summary>        /// Loads a motion3.json asset.        /// </summary>        /// <param name="motion3Json">motion3.json to deserialize.</param>        /// <returns>Deserialized motion3.json on success; <see langword="null"/> otherwise.</returns>        public static CubismMotion3Json LoadFrom(string motion3Json)        {            if (string.IsNullOrEmpty(motion3Json))            {                return null;            }            var cubismMotion3Json = JsonUtility.FromJson<CubismMotion3Json>(motion3Json);            cubismMotion3Json.Meta.FadeInTime = -1.0f;            cubismMotion3Json.Meta.FadeOutTime = -1.0f;            for (var i = 0; i < cubismMotion3Json.Curves.Length; ++i)            {                cubismMotion3Json.Curves[i].FadeInTime = -1.0f;                cubismMotion3Json.Curves[i].FadeOutTime = -1.0f;            }            JsonUtility.FromJsonOverwrite(motion3Json, cubismMotion3Json);            return cubismMotion3Json;        }        /// <summary>        /// Loads a motion3.json asset.        /// </summary>        /// <param name="motion3JsonAsset">motion3.json to deserialize.</param>        /// <returns>Deserialized motion3.json on success; <see langword="null"/> otherwise.</returns>        public static CubismMotion3Json LoadFrom(TextAsset motion3JsonAsset)        {            return (motion3JsonAsset == null)                ? null                : LoadFrom(motion3JsonAsset.text);        }        #endregion        #region Json Data        /// <summary>        /// The model3.json format version.        /// </summary>        [SerializeField]        public int Version;        /// <summary>        /// Motion meta info.        /// </summary>        [SerializeField]        public SerializableMeta Meta;        /// <summary>        /// Curves.        /// </summary>        [SerializeField]        public SerializableCurve[] Curves;        /// <summary>        /// User data.        /// </summary>        [SerializeField]        public SerializableUserData[] UserData;        #endregion        #region Constructors        /// <summary>        /// Makes construction only possible through factories.        /// </summary>        private CubismMotion3Json()        {        }        #endregion        /// <summary>        /// Converts motion curve segments into <see cref="Keyframe"/>s.        /// </summary>        /// <param name="segments">Data to convert.</param>        /// <returns>Keyframes.</returns>        public static Keyframe[] ConvertCurveSegmentsToKeyframes(float[] segments)        {            // Return early on invalid input.            if (segments.Length < 1)            {                return new Keyframe[0];            }            // Initialize container for keyframes.            var keyframes = new List<Keyframe> { new Keyframe(segments[0], segments[1]) };            // Parse segments.            for (var i = 2; i < segments.Length;)            {                Parsers[segments[i]](segments, keyframes, ref i);            }            // Return result.            return keyframes.ToArray();        }        /// <summary>        /// Converts stepped curves to liner curves.        /// </summary>        /// <param name="curve">Data to convert.</param>        /// <returns>Animation curve.</returns>        public static AnimationCurve ConvertSteppedCurveToLinerCurver(CubismMotion3Json.SerializableCurve curve, float poseFadeInTime)        {            poseFadeInTime = (poseFadeInTime < 0) ? 0.5f : poseFadeInTime;            var segments = curve.Segments;            var segmentsCount = 2;            for(var index = 2; index < curve.Segments.Length; index += 3)            {                // if current segment type is stepped and                // next segment type is stepped or next segment is last segment                // then convert segment type to liner.                var currentSegmentTypeIsStepped = (curve.Segments[index] == 2);                var currentSegmentIsLast = (index == (curve.Segments.Length - 3));                var nextSegmentTypeIsStepped = (currentSegmentIsLast) ? false : (curve.Segments[index + 3] == 2);                var nextSegmentIsLast = (currentSegmentIsLast) ? false : ((index + 3) == (curve.Segments.Length - 3));                if ( currentSegmentTypeIsStepped && (nextSegmentTypeIsStepped || nextSegmentIsLast) )                {                    Array.Resize(ref segments, segments.Length + 3);                    segments[segmentsCount + 0] = 0;                    segments[segmentsCount + 1] = curve.Segments[index + 1];                    segments[segmentsCount + 2] = curve.Segments[index - 1];                    segments[segmentsCount + 3] = 0;                    segments[segmentsCount + 4] = curve.Segments[index + 1] + poseFadeInTime;                    segments[segmentsCount + 5] = curve.Segments[index + 2];                    segmentsCount += 6;                }                else if(curve.Segments[index] == 1)                {                    segments[segmentsCount + 0] = curve.Segments[index + 0];                    segments[segmentsCount + 1] = curve.Segments[index + 1];                    segments[segmentsCount + 2] = curve.Segments[index + 2];                    segments[segmentsCount + 3] = curve.Segments[index + 3];                    segments[segmentsCount + 4] = curve.Segments[index + 4];                    segments[segmentsCount + 5] = curve.Segments[index + 5];                    segments[segmentsCount + 6] = curve.Segments[index + 6];                    index += 4;                    segmentsCount += 7;                }                else                {                    segments[segmentsCount + 0] = curve.Segments[index + 0];                    segments[segmentsCount + 1] = curve.Segments[index + 1];                    segments[segmentsCount + 2] = curve.Segments[index + 2];                    segmentsCount += 3;                }            }            return new AnimationCurve(ConvertCurveSegmentsToKeyframes(segments));        }        /// <summary>        /// Instantiates an <see cref="AnimationClip"/>.        /// </summary>        /// <param name="shouldImportAsOriginalWorkflow">Should import as original workflow.</param>        /// <param name="shouldClearAnimationCurves">Should clear animation clip curves.</param>        /// <param name="isCallFormModelJson">Is function call form <see cref="CubismModel3Json"/>.</param>        /// <param name="poseJson">pose3.json asset.</param>        /// <returns>The instantiated clip on success; <see langword="null"/> otherwise.</returns>        /// <remarks>        /// Note this method generates <see cref="AnimationClip.legacy"/> clips when called at runtime.        /// </remarks>        public AnimationClip ToAnimationClip(bool shouldImportAsOriginalWorkflow = false, bool shouldClearAnimationCurves = false,                                             bool isCallFormModelJson = false, CubismPose3Json poseJson = null)        {            // Check béziers restriction flag.            if (!Meta.AreBeziersRestricted)            {                Debug.LogWarning("Béziers are not restricted and curves might be off. Please export motions from Cubism in restricted mode for perfect match.");            }            // Create animation clip.            var animationClip = new AnimationClip            {#if UNITY_EDITOR                frameRate = Meta.Fps#else                frameRate = Meta.Fps,                legacy = true,                wrapMode = (Meta.Loop)                  ? WrapMode.Loop                  : WrapMode.Default#endif            };            return ToAnimationClip(animationClip, shouldImportAsOriginalWorkflow, shouldClearAnimationCurves, isCallFormModelJson, poseJson);        }        /// <summary>        /// Instantiates an <see cref="AnimationClip"/>.        /// </summary>        /// <param name="animationClip">Previous animation clip.</param>        /// <param name="shouldImportAsOriginalWorkflow">Should import as original workflow.</param>        /// <param name="shouldClearAnimationCurves">Should clear animation clip curves.</param>        /// <param name="isCallFormModelJson">Is function call form <see cref="CubismModel3Json"/>.</param>        /// <param name="poseJson">pose3.json asset.</param>        /// <returns>The instantiated clip on success; <see langword="null"/> otherwise.</returns>        /// <remarks>        /// Note this method generates <see cref="AnimationClip.legacy"/> clips when called at runtime.        /// </remarks>        public AnimationClip ToAnimationClip(AnimationClip animationClip, bool shouldImportAsOriginalWorkflow = false, bool shouldClearAnimationCurves = false                                                                        , bool isCallFormModelJson = false, CubismPose3Json poseJson = null)        {            // Clear curves.            if (shouldClearAnimationCurves && (!shouldImportAsOriginalWorkflow || (isCallFormModelJson && shouldImportAsOriginalWorkflow)))            {                animationClip.ClearCurves();            }            // Convert curves.            for (var i = 0; i < Curves.Length; ++i)            {                var curve = Curves[i];                // If should import as original workflow mode, skip add part opacity curve when call not from model3.json.                if (curve.Target == "PartOpacity" && shouldImportAsOriginalWorkflow && !isCallFormModelJson)                {                    continue;                }                var relativePath = string.Empty;                var type = default(Type);                var propertyName = string.Empty;                var animationCurve = new AnimationCurve(ConvertCurveSegmentsToKeyframes(curve.Segments));                // Create model binding.                if (curve.Target == "Model")                {                    // Bind opacity.                    if (curve.Id == "Opacity")                    {                        relativePath = string.Empty;                        propertyName = "Opacity";                        type = typeof(CubismRenderController);                    }                    // Bind eye-blink.                    else if (curve.Id == "EyeBlink")                    {                        relativePath = string.Empty;                        propertyName = "EyeOpening";                        type = typeof(CubismEyeBlinkController);                    }                    // Bind lip-sync.                    else if (curve.Id == "LipSync")                    {                        relativePath = string.Empty;                        propertyName = "MouthOpening";                        type = typeof(CubismMouthController);                    }                }                // Create parameter binding.                else if (curve.Target == "Parameter")                {                    relativePath = "Parameters/" + curve.Id;                    propertyName = "Value";                    type = typeof(CubismParameter);                }                // Create part opacity binding.                else if (curve.Target == "PartOpacity")                {                    relativePath = "Parts/" + curve.Id;                    propertyName = "Opacity";                    type = typeof(CubismPart);                    // original workflow.                    if (shouldImportAsOriginalWorkflow && poseJson != null && poseJson.FadeInTime != 0.0f)                    {                        animationCurve = ConvertSteppedCurveToLinerCurver(curve, poseJson.FadeInTime);                    }                }#if UNITY_EDITOR                var curveBinding = new EditorCurveBinding                {                    path = relativePath,                    propertyName = propertyName,                    type = type                };                AnimationUtility.SetEditorCurve(animationClip, curveBinding, animationCurve);#else                animationClip.SetCurve(relativePath, type, propertyName, animationCurve);#endif            }#if UNITY_EDITOR            // Apply settings.            var animationClipSettings = new AnimationClipSettings            {                loopTime = Meta.Loop,                stopTime = Meta.Duration            };            AnimationUtility.SetAnimationClipSettings(animationClip, animationClipSettings);#endif#if UNITY_EDITOR            // Add animation events from user data.            if (UserData != null)            {                var animationEvents = new List<AnimationEvent>();                for (var i = 0; i < UserData.Length; ++i)                {                    var animationEvent = new AnimationEvent                    {                        time = UserData[i].Time,                        stringParameter = UserData[i].Value,                    };                    animationEvents.Add(animationEvent);                }                if (animationEvents.Count > 0)                {                    AnimationUtility.SetAnimationEvents(animationClip, animationEvents.ToArray());                }            }#endif            return animationClip;        }        #region Segment Parsing        /// <summary>        /// Offset to use for setting of keyframes.        /// </summary>        private const float OffsetGranularity = 0.01f;        /// <summary>        /// Handles parsing of a single segment.        /// </summary>        /// <param name="segments">Curve segments.</param>        /// <param name="result">Buffer to append result to.</param>        /// <param name="position">Offset of segment.</param>        private delegate void SegmentParser(float[] segments, List<Keyframe> result, ref int position);        /// <summary>        /// Available segment parsers.        /// </summary>        // ReSharper disable once InconsistentNaming        private static Dictionary<float, SegmentParser> Parsers = new Dictionary<float, SegmentParser>        {            {0f, ParseLinearSegment},            {1f, ParseBezierSegment},            {2f, ParseSteppedSegment},            {3f, ParseInverseSteppedSegment}        };        /// <summary>        /// Parses a linear segment.        /// </summary>        /// <param name="segments">Curve segments.</param>        /// <param name="result">Buffer to append result to.</param>        /// <param name="position">Offset of segment.</param>        private static void ParseLinearSegment(float[] segments, List<Keyframe> result, ref int position)        {            // Compute slope.            var length = (segments[position + 1] - result[result.Count - 1].time);            var slope = (segments[position + 2] - result[result.Count - 1].value) / length;            // Determine tangents.            var outTangent = slope;            var inTangent = outTangent;            // Create keyframes.            var keyframe = new Keyframe(                result[result.Count - 1].time,                result[result.Count - 1].value,                result[result.Count - 1].inTangent,                outTangent);            result[result.Count - 1] = keyframe;            keyframe = new Keyframe(                segments[position + 1],                segments[position + 2],                inTangent,                0);            result.Add(keyframe);            // Update position.            position += 3;        }        /// <summary>        /// Parses a bezier segment.        /// </summary>        /// <param name="segments">Curve segments.</param>        /// <param name="result">Buffer to append result to.</param>        /// <param name="position">Offset of segment.</param>        private static void ParseBezierSegment(float[] segments, List<Keyframe> result, ref int position)        {            // Compute tangents.            var tangentLength = Mathf.Abs(result[result.Count - 1].time - segments[position + 5]) * 0.333333f;            var outTangent = (segments[position + 2] - result[result.Count - 1].value) / tangentLength;            var inTangent = (segments[position + 6] - segments[position + 4]) / tangentLength;            // Create keyframes.            var keyframe = new Keyframe(                result[result.Count - 1].time,                result[result.Count - 1].value,                result[result.Count - 1].inTangent,                outTangent);            result[result.Count - 1] = keyframe;            keyframe = new Keyframe(                segments[position + 5],                segments[position + 6],                inTangent,                0);            result.Add(keyframe);            // Update position.            position += 7;        }        /// <summary>        /// Parses a stepped segment.        /// </summary>        /// <param name="segments">Curve segments.</param>        /// <param name="result">Buffer to append result to.</param>        /// <param name="position">Offset of segment.</param>        private static void ParseSteppedSegment(float[] segments, List<Keyframe> result, ref int position)        {            // Create keyframe.            result.Add(                new Keyframe(segments[position + 1], segments[position + 2])                {                    inTangent = float.PositiveInfinity                });            // Update position.            position += 3;        }        /// <summary>        /// Parses a inverse-stepped segment.        /// </summary>        /// <param name="segments">Curve segments.</param>        /// <param name="result">Buffer to append result to.</param>        /// <param name="position">Offset of segment.</param>        private static void ParseInverseSteppedSegment(float[] segments, List<Keyframe> result, ref int position)        {            // Compute tangents.            var keyframe = result[result.Count - 1];            var tangent = (float)Math.Atan2(                (segments[position + 2] - keyframe.value),                (segments[position + 1] - keyframe.time));            keyframe.outTangent = tangent;            result[result.Count - 1] = keyframe;            result.Add(                new Keyframe(keyframe.time + OffsetGranularity, segments[position + 2])                {                    inTangent = tangent,                    outTangent = 0                });            result.Add(                new Keyframe(segments[position + 1], segments[position + 2])                {                    inTangent = 0                });            // Update position.            position += 3;        }        #endregion        #region Json Object Types        /// <summary>        /// Motion meta info.        /// </summary>        [Serializable]        public struct SerializableMeta        {            /// <summary>            /// Duration in seconds.            /// </summary>            [SerializeField]            public float Duration;            /// <summary>            /// Framerate in seconds.            /// </summary>            [SerializeField]            public float Fps;            /// <summary>            /// True if motion is looping.            /// </summary>            [SerializeField]            public bool Loop;            /// <summary>            /// Number of curves.            /// </summary>            [SerializeField]            public int CurveCount;            /// <summary>            /// Total number of curve segments.            /// </summary>            [SerializeField]            public int TotalSegmentCount;            /// <summary>            /// Total number of curve points.            /// </summary>            [SerializeField]            public int TotalPointCount;            /// <summary>            /// True if beziers are restricted.            /// </summary>            [SerializeField]            public bool AreBeziersRestricted;            /// <summary>            /// Total number of UserData.            /// </summary>            [SerializeField]            public int UserDataCount;            /// <summary>            /// Total size of UserData in bytes.            /// </summary>            [SerializeField]            public int TotalUserDataSize;            /// <summary>            /// [Optional] Time of the Fade-In for easing in seconds.            /// </summary>            [SerializeField]            public float FadeInTime;            /// <summary>            /// [Optional] Time of the Fade-Out for easing in seconds.            /// </summary>            [SerializeField]            public float FadeOutTime;        };        /// <summary>        /// Single motion curve.        /// </summary>        [Serializable]        public struct SerializableCurve        {            /// <summary>            /// Target type.            /// </summary>            [SerializeField]            public string Target;            /// <summary>            /// Id within target.            /// </summary>            [SerializeField]            public string Id;            /// <summary>            /// Flattened curve segments.            /// </summary>            [SerializeField]            public float[] Segments;            /// <summary>            /// [Optional] Time of the overall Fade-In for easing in seconds.            /// </summary>            [SerializeField]            public float FadeInTime;            /// <summary>            /// [Optional] Time of the overall Fade-Out for easing in seconds.            /// </summary>            [SerializeField]            public float FadeOutTime;        };        /// <summary>        /// User data.        /// </summary>        [Serializable]        public struct SerializableUserData        {            /// <summary>            /// Time in seconds.            /// </summary>            [SerializeField]            public float Time;            /// <summary>            /// Content of user data.            /// </summary>            [SerializeField]            public string Value;        }        #endregion    }}
 |