/// <summary>
        /// Function to read a track containing 3D vector data.
        /// </summary>
        /// <param name="reader">The JSON reader.</param>
        /// <param name="converter">The converter used to convert the data from JSON.</param>
        /// <param name="interpolation">The interpolation mode for the track.</param>
        /// <returns>A list of 3D vector key frames.</returns>
        private static List <GorgonKeyVector3> ReadVector3(JsonReader reader, JsonVector3KeyConverter converter, out TrackInterpolationMode interpolation)
        {
            List <GorgonKeyVector3> ReadVec3Keys()
            {
                var  keys     = new List <GorgonKeyVector3>();
                Type vec3Type = typeof(GorgonKeyVector3);

                while ((reader.Read()) && (reader.TokenType != JsonToken.EndArray))
                {
                    if (reader.TokenType != JsonToken.StartObject)
                    {
                        continue;
                    }

                    keys.Add(converter.ReadJson(reader, vec3Type, null, false, null));
                }

                return(keys);
            }

            List <GorgonKeyVector3> positions = null;

            interpolation = TrackInterpolationMode.None;

            while ((reader.Read()) && (reader.TokenType != JsonToken.EndObject))
            {
                if (reader.TokenType != JsonToken.PropertyName)
                {
                    continue;
                }

                string propName = reader.Value.ToString().ToUpperInvariant();

                switch (propName)
                {
                case "INTERPOLATIONMODE":
                    interpolation = (TrackInterpolationMode)(reader.ReadAsInt32() ?? 0);
                    break;

                case "KEYFRAMES":
                    positions = ReadVec3Keys();
                    break;
                }
            }

            return(positions);
        }
        /// <summary>
        /// Function to convert a JSON formatted string into a <see cref="IGorgonAnimation"/> object.
        /// </summary>
        /// <param name="renderer">The renderer for the animation.</param>
        /// <param name="json">The JSON string containing the animation data.</param>
        /// <returns>A new <see cref="IGorgonAnimation"/>.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the <paramref name="renderer"/>, or the <paramref name="json"/> parameter is <b>null</b>.</exception>
        /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="json"/> parameter is empty.</exception>
        /// <exception cref="GorgonException">Thrown if the JSON string does not contain animation data, or there is a version mismatch.</exception>
        public IGorgonAnimation FromJson(Gorgon2D renderer, string json)
        {
            if (renderer == null)
            {
                throw new ArgumentNullException(nameof(renderer));
            }

            if (json == null)
            {
                throw new ArgumentNullException(nameof(json));
            }

            if (string.IsNullOrWhiteSpace(json))
            {
                throw new ArgumentEmptyException(nameof(json));
            }

            string animName   = string.Empty;
            float  animLength = 0;
            int    loopCount  = 0;
            bool   isLooped   = false;

            var colorConvert   = new JsonGorgonColorKeyConverter();
            var textureConvert = new JsonTextureKeyConverter(renderer.Graphics);
            var vec3Converter  = new JsonVector3KeyConverter();
            var rectConverter  = new JsonRectKeyConverter();

            TrackInterpolationMode posInterp   = TrackInterpolationMode.None;
            TrackInterpolationMode scaleInterp = TrackInterpolationMode.None;
            TrackInterpolationMode rotInterp   = TrackInterpolationMode.None;
            TrackInterpolationMode sizeInterp  = TrackInterpolationMode.None;
            TrackInterpolationMode colorInterp = TrackInterpolationMode.None;
            TrackInterpolationMode boundInterp = TrackInterpolationMode.None;

            List <GorgonKeyVector3>     positions = null;
            List <GorgonKeyVector3>     rotations = null;
            List <GorgonKeyVector3>     scales    = null;
            List <GorgonKeyVector3>     sizes     = null;
            List <GorgonKeyGorgonColor> colors    = null;
            List <GorgonKeyRectangle>   bounds    = null;
            List <GorgonKeyTexture2D>   textures  = null;

            using (var baseReader = new StringReader(json))
                using (var reader = new JsonTextReader(baseReader))
                {
                    if (!IsReadableJObject(reader))
                    {
                        throw new GorgonException(GorgonResult.CannotRead, Resources.GOR2DIO_ERR_JSON_NOT_ANIM);
                    }

                    while (reader.Read())
                    {
                        if (reader.TokenType != JsonToken.PropertyName)
                        {
                            continue;
                        }

                        string propName = reader.Value.ToString().ToUpperInvariant();

                        switch (propName)
                        {
                        case "POSITIONS":
                            positions = ReadVector3(reader, vec3Converter, out posInterp);
                            break;

                        case "SCALES":
                            scales = ReadVector3(reader, vec3Converter, out scaleInterp);
                            break;

                        case "ROTATIONS":
                            rotations = ReadVector3(reader, vec3Converter, out rotInterp);
                            break;

                        case "SIZE":
                            sizes = ReadVector3(reader, vec3Converter, out sizeInterp);
                            break;

                        case "BOUNDS":
                            bounds = ReadRects(reader, rectConverter, out boundInterp);
                            break;

                        case "COLORS":
                            colors = ReadColors(reader, colorConvert, out colorInterp);
                            break;

                        case "TEXTURES":
                            textures = ReadTextures(reader, textureConvert);
                            break;

                        case "NAME":
                            animName = reader.ReadAsString();
                            break;

                        case "LENGTH":
                            animLength = (float)(reader.ReadAsDecimal() ?? 0);
                            break;

                        case "ISLOOPED":
                            isLooped = (reader.ReadAsBoolean() ?? false);
                            break;

                        case "LOOPCOUNT":
                            loopCount = (reader.ReadAsInt32() ?? 0);
                            break;
                        }
                    }
                }

            // There's no name, so it's a broken JSON.
            if (string.IsNullOrWhiteSpace(animName))
            {
                throw new GorgonException(GorgonResult.CannotRead, Resources.GOR2DIO_ERR_JSON_NOT_ANIM);
            }

            var builder = new GorgonAnimationBuilder();

            if ((positions != null) && (positions.Count > 0))
            {
                builder.PositionInterpolationMode(posInterp)
                .EditPositions()
                .SetKeys(positions)
                .EndEdit();
            }

            if ((scales != null) && (scales.Count > 0))
            {
                builder.ScaleInterpolationMode(scaleInterp)
                .EditScale()
                .SetKeys(scales)
                .EndEdit();
            }

            if ((rotations != null) && (rotations.Count > 0))
            {
                builder.RotationInterpolationMode(rotInterp)
                .EditRotation()
                .SetKeys(rotations)
                .EndEdit();
            }

            if ((sizes != null) && (sizes.Count > 0))
            {
                builder.SizeInterpolationMode(sizeInterp)
                .EditSize()
                .SetKeys(sizes)
                .EndEdit();
            }

            if ((colors != null) && (colors.Count > 0))
            {
                builder.ColorInterpolationMode(colorInterp)
                .EditColors()
                .SetKeys(colors)
                .EndEdit();
            }

            if ((bounds != null) && (bounds.Count > 0))
            {
                builder.RotationInterpolationMode(boundInterp)
                .EditRectangularBounds()
                .SetKeys(bounds)
                .EndEdit();
            }

            if ((textures != null) && (textures.Count > 0))
            {
                builder.Edit2DTexture()
                .SetKeys(textures)
                .EndEdit();
            }

            IGorgonAnimation result = builder.Build(animName, animLength);

            result.LoopCount = loopCount;
            result.IsLooped  = isLooped;
            result.Speed     = 1.0f;

            return(result);
        }