瀏覽代碼

Animation: moved some interfaces from toolkit to core

Vicente Penades 6 年之前
父節點
當前提交
8d072ec8ac

+ 543 - 0
src/SharpGLTF.Core/Animations/SamplerFactory.cs

@@ -0,0 +1,543 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Animations
+{
+    /// <summary>
+    /// Defines a curve that can be sampled at specific points.
+    /// </summary>
+    /// <typeparam name="T">The type of a point in the curve.</typeparam>
+    public interface ICurveSampler<T>
+    {
+        T GetPoint(float offset);
+    }
+
+    /// <summary>
+    /// Defines methods that convert the current curve to a Step, Linear or Spline curve.
+    /// </summary>
+    /// <typeparam name="T">The type of a point of the curve</typeparam>
+    public interface IConvertibleCurve<T>
+    {
+        /// <summary>
+        /// Gets a value indicating the maximum degree of the curve, current values are:
+        /// 0: STEP.
+        /// 1: LINEAR.
+        /// 3: CUBIC.
+        /// </summary>
+        int Degree { get; }
+
+        IReadOnlyDictionary<float, T> ToStepCurve();
+        IReadOnlyDictionary<float, T> ToLinearCurve();
+        IReadOnlyDictionary<float, (T, T, T)> ToSplineCurve();
+    }
+
+    /// <summary>
+    /// Utility class to convert curve objects to curve samplers.
+    /// </summary>
+    public static class SamplerFactory
+    {
+        #region sampler utils
+
+        /// <summary>
+        /// Calculates the Hermite point weights for a given <paramref name="amount"/>
+        /// </summary>
+        /// <param name="amount">The input amount (must be between 0 and 1)</param>
+        /// <returns>
+        /// The output weights.
+        /// - Item1: Weight for Start point
+        /// - Item2: Weight for End point
+        /// - Item3: Weight for Start Outgoing Tangent
+        /// - Item4: Weight for End Incoming Tangent
+        /// </returns>
+        public static (float, float, float, float) CreateHermitePointWeights(float amount)
+        {
+            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1, nameof(amount));
+
+            // http://mathworld.wolfram.com/HermitePolynomial.html
+
+            // https://www.cubic.org/docs/hermite.htm
+
+            var squared = amount * amount;
+            var cubed = amount * squared;
+
+            /*
+            var part1 = (2.0f * cubed) - (3.0f * squared) + 1.0f;
+            var part2 = (-2.0f * cubed) + (3.0f * squared);
+            var part3 = cubed - (2.0f * squared) + amount;
+            var part4 = cubed - squared;
+            */
+
+            var part2 = (3.0f * squared) - (2.0f * cubed);
+            var part1 = 1 - part2;
+            var part4 = cubed - squared;
+            var part3 = part4 - squared + amount;
+
+            return (part1, part2, part3, part4);
+        }
+
+        /// <summary>
+        /// Calculates the Hermite tangent weights for a given <paramref name="amount"/>
+        /// </summary>
+        /// <param name="amount">The input amount (must be between 0 and 1)</param>
+        /// <returns>
+        /// The output weights.
+        /// - Item1: Weight for Start point
+        /// - Item2: Weight for End point
+        /// - Item3: Weight for Start Outgoing Tangent
+        /// - Item4: Weight for End Incoming Tangent
+        /// </returns>
+        public static (float, float, float, float) CreateHermiteTangentWeights(float amount)
+        {
+            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1, nameof(amount));
+
+            // https://math.stackexchange.com/questions/1270776/how-to-find-tangent-at-any-point-along-a-cubic-hermite-spline
+
+            var squared = amount * amount;
+
+            /*
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -(6 * squared) + (6 * amount);
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+            */
+
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -part1;
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+
+            return (part1, part2, part3, part4);
+        }
+
+        /// <summary>
+        /// Given a <paramref name="sequence"/> of float+<typeparamref name="T"/> pairs and an <paramref name="offset"/>,
+        /// it finds two consecutive values that contain <paramref name="offset"/> between them.
+        /// </summary>
+        /// <typeparam name="T">The value type</typeparam>
+        /// <param name="sequence">A sequence of float+<typeparamref name="T"/> pairs sorted in ascending order.</param>
+        /// <param name="offset">the offset to look for in the sequence.</param>
+        /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP amount.</returns>
+        public static (T, T, float) FindPairContainingOffset<T>(this IEnumerable<(float, T)> sequence, float offset)
+        {
+            if (!sequence.Any()) return (default(T), default(T), 0);
+
+            (float, T)? left = null;
+            (float, T)? right = null;
+            (float, T)? prev = null;
+
+            var first = sequence.First();
+            if (offset < first.Item1) offset = first.Item1;
+
+            foreach (var item in sequence)
+            {
+                System.Diagnostics.Debug.Assert(!prev.HasValue || prev.Value.Item1 < item.Item1, "Values in the sequence must be sorted ascending.");
+
+                if (item.Item1 == offset)
+                {
+                    left = item; continue;
+                }
+
+                if (item.Item1 > offset)
+                {
+                    if (left == null) left = prev;
+                    right = item;
+                    break;
+                }
+
+                prev = item;
+            }
+
+            if (left == null && right == null) return (default(T), default(T), 0);
+            if (left == null) return (right.Value.Item2, right.Value.Item2, 0);
+            if (right == null) return (left.Value.Item2, left.Value.Item2, 0);
+
+            var delta = right.Value.Item1 - left.Value.Item1;
+
+            System.Diagnostics.Debug.Assert(delta > 0);
+
+            var amount = (offset - left.Value.Item1) / delta;
+
+            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1);
+
+            return (left.Value.Item2, right.Value.Item2, amount);
+        }
+
+        /// <summary>
+        /// Given a <paramref name="sequence"/> of offsets and an <paramref name="offset"/>,
+        /// it finds two consecutive offsets that contain <paramref name="offset"/> between them.
+        /// </summary>
+        /// <param name="sequence">A sequence of offsets sorted in ascending order.</param>
+        /// <param name="offset">the offset to look for in the sequence.</param>
+        /// <returns>Two consecutive offsets and a LERP amount.</returns>
+        public static (float, float, float) FindPairContainingOffset(IEnumerable<float> sequence, float offset)
+        {
+            if (!sequence.Any()) return (0, 0, 0);
+
+            float? left = null;
+            float? right = null;
+            float? prev = null;
+
+            var first = sequence.First();
+            if (offset < first) offset = first;
+
+            foreach (var item in sequence)
+            {
+                System.Diagnostics.Debug.Assert(!prev.HasValue || prev.Value < item, "Values in the sequence must be sorted ascending.");
+
+                if (item == offset)
+                {
+                    left = item; continue;
+                }
+
+                if (item > offset)
+                {
+                    if (left == null) left = prev;
+                    right = item;
+                    break;
+                }
+
+                prev = item;
+            }
+
+            if (left == null && right == null) return (0, 0, 0);
+            if (left == null) return (right.Value, right.Value, 0);
+            if (right == null) return (left.Value, left.Value, 0);
+
+            var delta = right.Value - left.Value;
+
+            System.Diagnostics.Debug.Assert(delta > 0);
+
+            var amount = (offset - left.Value) / delta;
+
+            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1);
+
+            return (left.Value, right.Value, amount);
+        }
+
+        #endregion
+
+        #region Extensions
+
+        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(float, Vector3)> collection, bool isLinear = true)
+        {
+            if (collection == null) return null;
+
+            return new Vector3LinearSampler(collection, isLinear);
+        }
+
+        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(float, Quaternion)> collection, bool isLinear = true)
+        {
+            if (collection == null) return null;
+
+            return new QuaternionLinearSampler(collection, isLinear);
+        }
+
+        public static ICurveSampler<float[]> CreateSampler(this IEnumerable<(float, float[])> collection, bool isLinear = true)
+        {
+            if (collection == null) return null;
+
+            return new ArrayLinearSampler(collection, isLinear);
+        }
+
+        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(float, (Vector3, Vector3, Vector3))> collection)
+        {
+            if (collection == null) return null;
+
+            return new Vector3CubicSampler(collection);
+        }
+
+        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(float, (Quaternion, Quaternion, Quaternion))> collection)
+        {
+            if (collection == null) return null;
+
+            return new QuaternionCubicSampler(collection);
+        }
+
+        public static ICurveSampler<float[]> CreateSampler(this IEnumerable<(float, (float[], float[], float[]))> collection)
+        {
+            if (collection == null) return null;
+
+            return new ArrayCubicSampler(collection);
+        }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Defines a <see cref="Vector3"/> curve sampler that can be sampled with STEP or LINEAR interpolations.
+    /// </summary>
+    struct Vector3LinearSampler : ICurveSampler<Vector3>, IConvertibleCurve<Vector3>
+    {
+        public Vector3LinearSampler(IEnumerable<(float, Vector3)> sequence, bool isLinear)
+        {
+            _Sequence = sequence;
+            _Linear = isLinear;
+        }
+
+        private readonly IEnumerable<(float, Vector3)> _Sequence;
+        private readonly Boolean _Linear;
+
+        public int Degree => _Linear ? 1 : 0;
+
+        public Vector3 GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            if (!_Linear) return segment.Item1;
+
+            return Vector3.Lerp(segment.Item1, segment.Item2, segment.Item3);
+        }
+
+        public IReadOnlyDictionary<float, Vector3> ToStepCurve()
+        {
+            Guard.IsFalse(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, Vector3> ToLinearCurve()
+        {
+            Guard.IsTrue(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, (Vector3, Vector3, Vector3)> ToSplineCurve()
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    /// <summary>
+    /// Defines a <see cref="Quaternion"/> curve sampler that can be sampled with STEP or LINEAR interpolations.
+    /// </summary>
+    struct QuaternionLinearSampler : ICurveSampler<Quaternion>, IConvertibleCurve<Quaternion>
+    {
+        public QuaternionLinearSampler(IEnumerable<(float, Quaternion)> sequence, bool isLinear)
+        {
+            _Sequence = sequence;
+            _Linear = isLinear;
+        }
+
+        private readonly IEnumerable<(float, Quaternion)> _Sequence;
+        private readonly Boolean _Linear;
+
+        public int Degree => _Linear ? 1 : 0;
+
+        public Quaternion GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            if (!_Linear) return segment.Item1;
+
+            return Quaternion.Slerp(segment.Item1, segment.Item2, segment.Item3);
+        }
+
+        public IReadOnlyDictionary<float, Quaternion> ToStepCurve()
+        {
+            Guard.IsFalse(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, Quaternion> ToLinearCurve()
+        {
+            Guard.IsTrue(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, (Quaternion, Quaternion, Quaternion)> ToSplineCurve()
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    /// <summary>
+    /// Defines a <see cref="float"/>[] curve sampler that can be sampled with STEP or LINEAR interpolations.
+    /// </summary>
+    struct ArrayLinearSampler : ICurveSampler<float[]>, IConvertibleCurve<float[]>
+    {
+        public ArrayLinearSampler(IEnumerable<(float, float[])> sequence, bool isLinear)
+        {
+            _Sequence = sequence;
+            _Linear = isLinear;
+        }
+
+        private readonly IEnumerable<(float, float[])> _Sequence;
+        private readonly Boolean _Linear;
+
+        public int Degree => _Linear ? 1 : 0;
+
+        public float[] GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            if (!_Linear) return segment.Item1;
+
+            var result = new float[segment.Item1.Length];
+
+            for (int i = 0; i < result.Length; ++i)
+            {
+                result[i] = (segment.Item1[i] * (1 - segment.Item3)) + (segment.Item2[i] * segment.Item3);
+            }
+
+            return result;
+        }
+
+        public IReadOnlyDictionary<float, float[]> ToStepCurve()
+        {
+            Guard.IsFalse(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, float[]> ToLinearCurve()
+        {
+            Guard.IsTrue(_Linear, nameof(_Linear));
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+
+        public IReadOnlyDictionary<float, (float[], float[], float[])> ToSplineCurve()
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    /// <summary>
+    /// Defines a <see cref="Vector3"/> curve sampler that can be sampled with CUBIC interpolation.
+    /// </summary>
+    struct Vector3CubicSampler : ICurveSampler<Vector3>, IConvertibleCurve<Vector3>
+    {
+        public Vector3CubicSampler(IEnumerable<(float, (Vector3, Vector3, Vector3))> sequence)
+        {
+            _Sequence = sequence;
+        }
+
+        private readonly IEnumerable<(float, (Vector3, Vector3, Vector3))> _Sequence;
+
+        public int Degree => 3;
+
+        public Vector3 GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            var hermite = SamplerFactory.CreateHermitePointWeights(segment.Item3);
+
+            var start = segment.Item1.Item2;
+            var tangentOut = segment.Item1.Item3;
+            var tangentIn = segment.Item2.Item1;
+            var end = segment.Item2.Item2;
+
+            return (start * hermite.Item1) + (end * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4);
+        }
+
+        public IReadOnlyDictionary<float, Vector3> ToStepCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, Vector3> ToLinearCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, (Vector3, Vector3, Vector3)> ToSplineCurve()
+        {
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+    }
+
+    /// <summary>
+    /// Defines a <see cref="Quaternion"/> curve sampler that can be sampled with CUBIC interpolation.
+    /// </summary>
+    struct QuaternionCubicSampler : ICurveSampler<Quaternion>, IConvertibleCurve<Quaternion>
+    {
+        public QuaternionCubicSampler(IEnumerable<(float, (Quaternion, Quaternion, Quaternion))> sequence)
+        {
+            _Sequence = sequence;
+        }
+
+        private readonly IEnumerable<(float, (Quaternion, Quaternion, Quaternion))> _Sequence;
+
+        public int Degree => 3;
+
+        public Quaternion GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            var hermite = SamplerFactory.CreateHermitePointWeights(segment.Item3);
+
+            var start = segment.Item1.Item2;
+            var tangentOut = segment.Item1.Item3;
+            var tangentIn = segment.Item2.Item1;
+            var end = segment.Item2.Item2;
+
+            return Quaternion.Normalize((start * hermite.Item1) + (end * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4));
+        }
+
+        public IReadOnlyDictionary<float, Quaternion> ToStepCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, Quaternion> ToLinearCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, (Quaternion, Quaternion, Quaternion)> ToSplineCurve()
+        {
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+    }
+
+    /// <summary>
+    /// Defines a <see cref="float"/>[] curve sampler that can be sampled with CUBIC interpolation.
+    /// </summary>
+    struct ArrayCubicSampler : ICurveSampler<float[]>, IConvertibleCurve<float[]>
+    {
+        public ArrayCubicSampler(IEnumerable<(float, (float[], float[], float[]))> sequence)
+        {
+            _Sequence = sequence;
+        }
+
+        private readonly IEnumerable<(float, (float[], float[], float[]))> _Sequence;
+
+        public int Degree => 3;
+
+        public float[] GetPoint(float offset)
+        {
+            var segment = SamplerFactory.FindPairContainingOffset(_Sequence, offset);
+
+            var hermite = SamplerFactory.CreateHermitePointWeights(segment.Item3);
+
+            var start = segment.Item1.Item2;
+            var tangentOut = segment.Item1.Item3;
+            var tangentIn = segment.Item2.Item1;
+            var end = segment.Item2.Item2;
+
+            var result = new float[start.Length];
+
+            for (int i = 0; i < result.Length; ++i)
+            {
+                result[i] = (start[i] * hermite.Item1) + (end[i] * hermite.Item2) + (tangentOut[i] * hermite.Item3) + (tangentIn[i] * hermite.Item4);
+            }
+
+            return result;
+        }
+
+        public IReadOnlyDictionary<float, float[]> ToStepCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, float[]> ToLinearCurve()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<float, (float[], float[], float[])> ToSplineCurve()
+        {
+            return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
+        }
+    }
+}

+ 18 - 17
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -6,6 +6,7 @@ using System.Numerics;
 
 using SharpGLTF.Collections;
 using SharpGLTF.Transforms;
+using SharpGLTF.Animations;
 
 namespace SharpGLTF.Schema2
 {
@@ -159,9 +160,9 @@ namespace SharpGLTF.Schema2
             var rfunc = FindRotationSampler(node)?.CreateCurveSampler();
             var tfunc = FindTranslationSampler(node)?.CreateCurveSampler();
 
-            if (sfunc != null) xform.Scale = sfunc(time);
-            if (rfunc != null) xform.Rotation = rfunc(time);
-            if (tfunc != null) xform.Translation = tfunc(time);
+            if (sfunc != null) xform.Scale = sfunc.GetPoint(time);
+            if (rfunc != null) xform.Rotation = rfunc.GetPoint(time);
+            if (tfunc != null) xform.Translation = tfunc.GetPoint(time);
 
             return xform;
         }
@@ -176,7 +177,7 @@ namespace SharpGLTF.Schema2
             var mfunc = FindMorphSampler(node)?.CreateCurveSampler();
             if (mfunc == null) return morphWeights;
 
-            return mfunc(time);
+            return mfunc.GetPoint(time);
         }
 
         #endregion
@@ -509,43 +510,43 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        CurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler()
+        ICurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler()
         {
             var xsampler = this as IAnimationSampler<Vector3>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateStepSamplerFunc();
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateLinearSamplerFunc();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSplineSamplerFunc();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
             }
 
             throw new NotImplementedException();
         }
 
-        CurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler()
+        ICurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler()
         {
             var xsampler = this as IAnimationSampler<Quaternion>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateStepSamplerFunc();
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateLinearSamplerFunc();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSplineSamplerFunc();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
             }
 
             throw new NotImplementedException();
         }
 
-        CurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler()
+        ICurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler()
         {
             var xsampler = this as IAnimationSampler<Single[]>;
 
             switch (this.InterpolationMode)
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateStepSamplerFunc();
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateLinearSamplerFunc();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSplineSamplerFunc();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler();
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler();
             }
 
             throw new NotImplementedException();
@@ -562,7 +563,7 @@ namespace SharpGLTF.Schema2
 
         IEnumerable<(Single, (T, T, T))> GetCubicKeys();
 
-        CurveSampler<T> CreateCurveSampler();
+        ICurveSampler<T> CreateCurveSampler();
     }
 
     public sealed partial class ModelRoot

+ 1 - 1
src/SharpGLTF.Core/SharpGLTF.Core.csproj

@@ -46,7 +46,7 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.2" >
+    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.2">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
     </PackageReference>

+ 0 - 231
src/SharpGLTF.Core/Transforms/AnimationSamplerFactory.cs

@@ -1,231 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-namespace SharpGLTF.Transforms
-{
-    public delegate T CurveSampler<T>(Single time);
-
-    internal static class AnimationSamplerFactory
-    {
-        /// <summary>
-        /// Given a <paramref name="sequence"/> of float+<typeparamref name="T"/> pairs and a <paramref name="offset"/> time,
-        /// it finds two consecutive values and the LERP amout.
-        /// </summary>
-        /// <typeparam name="T">The value type</typeparam>
-        /// <param name="sequence">A sequence of float+<typeparamref name="T"/> pairs</param>
-        /// <param name="offset">the time point within the sequence</param>
-        /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP them.</returns>
-        private static (T, T, float) _FindSample<T>(this IEnumerable<(float, T)> sequence, float offset)
-        {
-            (float, T)? left = null;
-            (float, T)? right = null;
-            (float, T)? prev = null;
-
-            if (offset < 0) offset = 0;
-
-            foreach (var item in sequence)
-            {
-                if (item.Item1 == offset)
-                {
-                    left = item; continue;
-                }
-
-                if (item.Item1 > offset)
-                {
-                    if (left == null) left = prev;
-                    right = item;
-                    break;
-                }
-
-                prev = item;
-            }
-
-            if (left == null && right == null) return (default(T), default(T), 0);
-            if (left == null) return (right.Value.Item2, right.Value.Item2, 0);
-            if (right == null) return (left.Value.Item2, left.Value.Item2, 0);
-
-            var delta = right.Value.Item1 - left.Value.Item1;
-
-            System.Diagnostics.Debug.Assert(delta > 0);
-
-            var amount = (offset - left.Value.Item1) / delta;
-
-            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1);
-
-            return (left.Value.Item2, right.Value.Item2, amount);
-        }
-
-        internal static CurveSampler<T> CreateStepSamplerFunc<T>(this IEnumerable<(float, T)> collection)
-        {
-            if (collection == null) return null;
-
-            T _sampler(float offset)
-            {
-                var sample = collection._FindSample(offset);
-                return sample.Item1;
-            }
-
-            return _sampler;
-        }
-
-        internal static CurveSampler<Vector3> CreateLinearSamplerFunc(this IEnumerable<(float, Vector3)> collection)
-        {
-            if (collection == null) return null;
-
-            Vector3 _sampler(float offset)
-            {
-                var sample = collection._FindSample(offset);
-                return Vector3.Lerp(sample.Item1, sample.Item2, sample.Item3);
-            }
-
-            return _sampler;
-        }
-
-        internal static CurveSampler<Quaternion> CreateLinearSamplerFunc(this IEnumerable<(float, Quaternion)> collection)
-        {
-            if (collection == null) return null;
-
-            Quaternion _sampler(float offset)
-            {
-                var sample = collection._FindSample(offset);
-                return Quaternion.Slerp(sample.Item1, sample.Item2, sample.Item3);
-            }
-
-            return _sampler;
-        }
-
-        internal static CurveSampler<float[]> CreateLinearSamplerFunc(this IEnumerable<(float, float[])> collection)
-        {
-            if (collection == null) return null;
-
-            float[] _sampler(float offset)
-            {
-                var sample = collection._FindSample(offset);
-                var result = new float[sample.Item1.Length];
-
-                for (int i = 0; i < result.Length; ++i)
-                {
-                    result[i] = (sample.Item1[i] * (1 - sample.Item3)) + (sample.Item2[i] * sample.Item3);
-                }
-
-                return result;
-            }
-
-            return _sampler;
-        }
-
-        internal static CurveSampler<Vector3> CreateSplineSamplerFunc(this IEnumerable<(float, (Vector3, Vector3, Vector3))> collection)
-        {
-            return CreateSplineSamplerFunc<Vector3>(collection, Hermite);
-        }
-
-        internal static CurveSampler<Quaternion> CreateSplineSamplerFunc(this IEnumerable<(float, (Quaternion, Quaternion, Quaternion))> collection)
-        {
-            return CreateSplineSamplerFunc<Quaternion>(collection, Hermite);
-        }
-
-        internal static CurveSampler<float[]> CreateSplineSamplerFunc(this IEnumerable<(float, (float[], float[], float[]))> collection)
-        {
-            return CreateSplineSamplerFunc<float[]>(collection, Hermite);
-        }
-
-        internal static CurveSampler<T> CreateSplineSamplerFunc<T>(this IEnumerable<(float, (T, T, T))> collection, Func<T, T, T, T, float, T> hermiteFunc)
-        {
-            if (collection == null) return null;
-
-            T _sampler(float offset)
-            {
-                var sample = collection._FindSample(offset);
-
-                return hermiteFunc(sample.Item1.Item2, sample.Item1.Item3, sample.Item2.Item2, sample.Item2.Item1, sample.Item3);
-            }
-
-            return _sampler;
-        }
-
-        internal static Vector3 Hermite(Vector3 start, Vector3 tangentOut, Vector3 end, Vector3 tangentIn, float amount)
-        {
-            var hermite = CalculateHermiteBasis(amount);
-
-            return (start * hermite.Item1) + (end * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4);
-        }
-
-        internal static Quaternion Hermite(Quaternion value1, Quaternion tangent1, Quaternion value2, Quaternion tangent2, float amount)
-        {
-            var hermite = CalculateHermiteBasis(amount);
-
-            return Quaternion.Normalize((value1 * hermite.Item1) + (value2 * hermite.Item2) + (tangent1 * hermite.Item3) + (tangent2 * hermite.Item4));
-        }
-
-        internal static float[] Hermite(float[] value1, float[] tangent1, float[] value2, float[] tangent2, float amount)
-        {
-            var hermite = CalculateHermiteBasis(amount);
-
-            var result = new float[value1.Length];
-
-            for (int i = 0; i < result.Length; ++i)
-            {
-                result[i] = (value1[i] * hermite.Item1) + (value2[i] * hermite.Item2) + (tangent1[i] * hermite.Item3) + (tangent2[i] * hermite.Item4);
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// for a given cubic interpolation <paramref name="amount"/>, it calculates
-        /// the weights to multiply each component:
-        /// 1: Weight for Start point
-        /// 2: Weight for End Tangent
-        /// 3: Weight for Out Tangent
-        /// 4: Weight for In Tangent
-        /// </summary>
-        /// <param name="amount">the input amount</param>
-        /// <returns>the output weights</returns>
-        public static (float, float, float, float) CalculateHermiteBasis(float amount)
-        {
-            // http://mathworld.wolfram.com/HermitePolynomial.html
-
-            // https://www.cubic.org/docs/hermite.htm
-
-            var squared = amount * amount;
-            var cubed = amount * squared;
-
-            /*
-            var part1 = (2.0f * cubed) - (3.0f * squared) + 1.0f;
-            var part2 = (-2.0f * cubed) + (3.0f * squared);
-            var part3 = cubed - (2.0f * squared) + amount;
-            var part4 = cubed - squared;
-            */
-
-            var part2 = (3.0f * squared) - (2.0f * cubed);
-            var part1 = 1 - part2;
-            var part4 = cubed - squared;
-            var part3 = part4 - squared + amount;
-
-            return (part1, part2, part3, part4);
-        }
-
-        public static (float, float, float, float) CalculateHermiteTangent(float amount)
-        {
-            // https://math.stackexchange.com/questions/1270776/how-to-find-tangent-at-any-point-along-a-cubic-hermite-spline
-
-            var squared = amount * amount;
-
-            /*
-            var part1 = (6 * squared) - (6 * amount);
-            var part2 = -(6 * squared) + (6 * amount);
-            var part3 = (3 * squared) - (4 * amount) + 1;
-            var part4 = (3 * squared) - (2 * amount);
-            */
-
-            var part1 = (6 * squared) - (6 * amount);
-            var part2 = -part1;
-            var part3 = (3 * squared) - (4 * amount) + 1;
-            var part4 = (3 * squared) - (2 * amount);
-
-            return (part1, part2, part3, part4);
-        }
-    }
-}

+ 4 - 10
src/SharpGLTF.Toolkit/Animations/Animatable.cs

@@ -8,11 +8,10 @@ using System.Text;
 namespace SharpGLTF.Animations
 {
     /// <summary>
-    /// Represents a value that can be animated.
+    /// Represents a value that can be animated using <see cref="Animations.ICurveSampler{T}"/>.
     /// </summary>
     /// <typeparam name="T">The type of the value.</typeparam>
     public class Animatable<T>
-        where T : struct
     {
         #region data
 
@@ -37,15 +36,10 @@ namespace SharpGLTF.Animations
             return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetPoint(value) : this.Default;
         }
 
-        public ICurveSampler<T> UseCurve(string track)
+        public void SetTrack(string track, ICurveSampler<T> curve)
         {
-            if (_Tracks == null) _Tracks = new Dictionary<string, ICurveSampler<T>>();
-
-            if (_Tracks.TryGetValue(track, out ICurveSampler<T> curve)) return curve;
-
-            _Tracks[track] = curve = CurveFactory.CreateSplineCurve<T>();
-
-            return curve;
+            Guard.NotNullOrEmpty(track, nameof(track));
+            if (curve == null) { _Tracks.Remove(track); return; }
         }
 
         #endregion

+ 25 - 154
src/SharpGLTF.Toolkit/Animations/Curves.cs

@@ -6,39 +6,9 @@ using System.Linq;
 
 namespace SharpGLTF.Animations
 {
-    /// <summary>
-    /// Defines a curve that can be sampled at specific points.
-    /// </summary>
-    /// <typeparam name="T">The type of a point in the curve.</typeparam>
-    public interface ICurveSampler<T>
-    {
-        T GetPoint(float offset);
-    }
-
-    /// <summary>
-    /// Defines methods that convert the current curve to a Step, Linear or Spline curve.
-    /// </summary>
-    /// <typeparam name="T">The type of a point of the curve</typeparam>
-    public interface IConvertibleCurve<T>
-        where T : struct
-    {
-        /// <summary>
-        /// Gets a value indicating the maximum degree of the curve, values are:
-        /// 0: all elements use STEP interpolation.
-        /// 1: some elements use LINEAR interpolation.
-        /// 2: some elements use CUBIC interpolation
-        /// </summary>
-        int Degree { get; }
-
-        IReadOnlyDictionary<float, T> ToStepCurve();
-        IReadOnlyDictionary<float, T> ToLinearCurve();
-        IReadOnlyDictionary<float, (T, T, T)> ToSplineCurve();
-    }
-
-    
 
     [System.Diagnostics.DebuggerDisplay("[{_Offset}] = {Sample}")]
-    public struct CurvePoint<T>
+    struct CurvePoint<T>
         where T : struct
     {
         #region lifecycle
@@ -126,7 +96,7 @@ namespace SharpGLTF.Animations
         #endregion
     }
 
-    public static class CurveFactory
+    static class CurveFactory
     {
         // TODO: we could support conversions between linear and cubic (with hermite regression)
 
@@ -139,59 +109,6 @@ namespace SharpGLTF.Animations
 
             throw new ArgumentException(nameof(T), "Generic argument not supported");
         }
-
-        /// <summary>
-        /// for a given cubic interpolation <paramref name="amount"/>, it calculates
-        /// the weights to multiply each component:
-        /// 1: Weight for Start point
-        /// 2: Weight for End Tangent
-        /// 3: Weight for Out Tangent
-        /// 4: Weight for In Tangent
-        /// </summary>
-        /// <param name="amount">the input amount</param>
-        /// <returns>the output weights</returns>
-        public static (float, float, float, float) CalculateHermiteBasis(float amount)
-        {
-            // http://mathworld.wolfram.com/HermitePolynomial.html
-
-            var squared = amount * amount;
-            var cubed = amount * squared;
-
-            /*
-            var part1 = (2.0f * cubed) - (3.0f * squared) + 1.0f;
-            var part2 = (-2.0f * cubed) + (3.0f * squared);
-            var part3 = cubed - (2.0f * squared) + amount;
-            var part4 = cubed - squared;
-            */
-
-            var part2 = (3.0f * squared) - (2.0f * cubed);
-            var part1 = 1 - part2;
-            var part4 = cubed - squared;
-            var part3 = part4 - squared + amount;
-
-            return (part1, part2, part3, part4);
-        }
-
-        public static (float, float, float, float) CalculateHermiteTangent(float amount)
-        {
-            // https://math.stackexchange.com/questions/1270776/how-to-find-tangent-at-any-point-along-a-cubic-hermite-spline
-
-            var squared = amount * amount;
-
-            /*
-            var part1 = (6 * squared) - (6 * amount);
-            var part2 = -(6 * squared) + (6 * amount);
-            var part3 = (3 * squared) - (4 * amount) + 1;
-            var part4 = (3 * squared) - (2 * amount);
-            */
-
-            var part1 = (6 * squared) - (6 * amount);
-            var part2 = -part1;
-            var part3 = (3 * squared) - (4 * amount) + 1;
-            var part4 = (3 * squared) - (2 * amount);
-
-            return (part1, part2, part3, part4);
-        }
     }
 
     struct _CurveNode<T>
@@ -207,7 +124,7 @@ namespace SharpGLTF.Animations
     /// Represents a collection of consecutive nodes that can be sampled into a continuous curve.
     /// </summary>
     /// <typeparam name="T">The type of value evaluated at any point in the curve.</typeparam>
-    public abstract class Curve<T> : IConvertibleCurve<T>, ICurveSampler<T>
+    abstract class Curve<T> : IConvertibleCurve<T>, ICurveSampler<T>
         where T : struct
     {
         #region lifecycle
@@ -253,64 +170,12 @@ namespace SharpGLTF.Animations
         {
             if (_Keys.Count == 0) return (default(_CurveNode<T>), default(_CurveNode<T>), 0);
 
-            var offsets = _FindPairContainingOffset(_Keys.Keys, offset);
+            var offsets = SamplerFactory.FindPairContainingOffset(_Keys.Keys, offset);
 
             return (_Keys[offsets.Item1], _Keys[offsets.Item2], offsets.Item3);
         }
 
-        public (float, float, float) FindLerp(float offset) { return _FindPairContainingOffset(_Keys.Keys, offset); }
-
-        /// <summary>
-        /// Given a <paramref name="sequence"/> of offsets and an <paramref name="offset"/>,
-        /// it finds two consecutive offsets that contain <paramref name="offset"/> between them.
-        /// </summary>
-        /// <param name="sequence">A sequence of offsets.</param>
-        /// <param name="offset">the offset to look for in the sequence.</param>
-        /// <returns>Two consecutive offsets and a LERP value.</returns>
-        private static (float, float, float) _FindPairContainingOffset(IEnumerable<float> sequence, float offset)
-        {
-            if (!sequence.Any()) return (0, 0, 0);
-
-            float? left = null;
-            float? right = null;
-            float? prev = null;
-
-            var first = sequence.First();
-            if (offset < first) offset = first;
-
-            foreach (var item in sequence)
-            {
-                System.Diagnostics.Debug.Assert(!prev.HasValue || prev.Value < item, "Values in the sequence must be sorted ascending.");
-
-                if (item == offset)
-                {
-                    left = item; continue;
-                }
-
-                if (item > offset)
-                {
-                    if (left == null) left = prev;
-                    right = item;
-                    break;
-                }
-
-                prev = item;
-            }
-
-            if (left == null && right == null) return (0, 0, 0);
-            if (left == null) return (right.Value, right.Value, 0);
-            if (right == null) return (left.Value, left.Value, 0);
-
-            var delta = right.Value - left.Value;
-
-            System.Diagnostics.Debug.Assert(delta > 0);
-
-            var amount = (offset - left.Value) / delta;
-
-            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1);
-
-            return (left.Value, right.Value, amount);
-        }
+        public (float, float, float) FindLerp(float offset) { return SamplerFactory.FindPairContainingOffset(_Keys.Keys, offset); }
 
         public abstract T GetPoint(float offset);
 
@@ -429,12 +294,14 @@ namespace SharpGLTF.Animations
                 return (sample.Item1.Point * (1 - sample.Item3)) + (sample.Item2.Point * sample.Item3);
             }
 
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
+
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
+            var basis = SamplerFactory.CreateHermitePointWeights(sample.Item3);
 
             return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
@@ -447,12 +314,14 @@ namespace SharpGLTF.Animations
 
             if (sample.Item1.OutgoingMode == 1) return sample.Item2.Point - sample.Item1.Point;
 
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
+
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
+            var basis = SamplerFactory.CreateHermiteTangentWeights(sample.Item3);
 
             return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
@@ -504,12 +373,14 @@ namespace SharpGLTF.Animations
                 return Vector3.Lerp(sample.Item1.Point, sample.Item2.Point, sample.Item3);
             }
 
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
+
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
+            var basis = SamplerFactory.CreateHermitePointWeights(sample.Item3);
 
             return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
@@ -522,12 +393,14 @@ namespace SharpGLTF.Animations
 
             if (sample.Item1.OutgoingMode == 1) return sample.Item2.Point - sample.Item1.Point;
 
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
+
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
+            var basis = SamplerFactory.CreateHermiteTangentWeights(sample.Item3);
 
             return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
@@ -574,17 +447,16 @@ namespace SharpGLTF.Animations
 
             if (sample.Item1.OutgoingMode == 0) return sample.Item1.Point;
 
-            if (sample.Item1.OutgoingMode == 1)
-            {
-                return Quaternion.Slerp(sample.Item1.Point, sample.Item2.Point, sample.Item3);
-            }
+            if (sample.Item1.OutgoingMode == 1) return Quaternion.Slerp(sample.Item1.Point, sample.Item2.Point, sample.Item3);
+
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
 
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
+            var basis = SamplerFactory.CreateHermitePointWeights(sample.Item3);
 
             var q = (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
 
@@ -597,17 +469,16 @@ namespace SharpGLTF.Animations
 
             if (sample.Item1.OutgoingMode == 0) return Quaternion.Identity;
 
-            if (sample.Item1.OutgoingMode == 1)
-            {
-                throw new NotImplementedException();
-            }
+            if (sample.Item1.OutgoingMode == 1) throw new NotImplementedException();
+
+            System.Diagnostics.Debug.Assert(sample.Item1.OutgoingMode == 3, "invalid interpolation mode");
 
             var pointStart = sample.Item1.Point;
             var tangentOut = sample.Item1.OutgoingTangent;
             var pointEnd = sample.Item2.Point;
             var tangentIn = sample.Item2.IncomingTangent;
 
-            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
+            var basis = SamplerFactory.CreateHermiteTangentWeights(sample.Item3);
 
             var q = (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
 

+ 6 - 0
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -174,6 +174,12 @@ namespace SharpGLTF.Scenes
             return Translation;
         }
 
+        public void SetScaleTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseScale().SetTrack(track, curve); }
+
+        public void SetTranslationTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseTranslation().SetTrack(track, curve); }
+
+        public void SetRotationTrack(string track, Animations.ICurveSampler<Quaternion> curve) { UseRotation().SetTrack(track, curve); }
+
         public Transforms.AffineTransform GetLocalTransform(string animationTrack, float time)
         {
             if (animationTrack == null) return this.LocalTransform;

+ 6 - 3
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -75,6 +75,11 @@ namespace SharpGLTF.Scenes
             }
         }
 
+        /// <summary>
+        /// Recursively converts all the <see cref="NodeBuilder"/> instances into <see cref="Schema2.Node"/> instances.
+        /// </summary>
+        /// <param name="container">The target <see cref="Schema2.Scene"/> or <see cref="Schema2.Node"/>.</param>
+        /// <param name="srcNode">The source <see cref="NodeBuilder"/> instance.</param>
         private void CreateArmature(IVisualNodeContainer container, NodeBuilder srcNode)
         {
             var dstNode = container.CreateNode(srcNode.Name);
@@ -84,12 +89,10 @@ namespace SharpGLTF.Scenes
             {
                 dstNode.LocalTransform = srcNode.LocalTransform;
 
+                // Copies all the animations to the target node.
                 if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
-
                 if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
-
                 if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
-
             }
             else
             {

+ 3 - 3
src/SharpGLTF.Toolkit/Schema2/AnimationExtensions.cs

@@ -24,7 +24,7 @@ namespace SharpGLTF.Schema2
                 var degree = curve.Degree;
                 if (degree == 0) animation.CreateScaleChannel(node, curve.ToStepCurve(), false);
                 if (degree == 1) animation.CreateScaleChannel(node, curve.ToLinearCurve(), true);
-                if (degree == 2) animation.CreateScaleChannel(node, curve.ToSplineCurve());
+                if (degree == 3) animation.CreateScaleChannel(node, curve.ToSplineCurve());
             }
 
             return node;
@@ -39,7 +39,7 @@ namespace SharpGLTF.Schema2
                 var degree = curve.Degree;
                 if (degree == 0) animation.CreateTranslationChannel(node, curve.ToStepCurve(), false);
                 if (degree == 1) animation.CreateTranslationChannel(node, curve.ToLinearCurve(), true);
-                if (degree == 2) animation.CreateTranslationChannel(node, curve.ToSplineCurve());
+                if (degree == 3) animation.CreateTranslationChannel(node, curve.ToSplineCurve());
             }
 
             return node;
@@ -54,7 +54,7 @@ namespace SharpGLTF.Schema2
                 var degree = curve.Degree;
                 if (degree == 0) animation.CreateRotationChannel(node, curve.ToStepCurve(), false);
                 if (degree == 1) animation.CreateRotationChannel(node, curve.ToLinearCurve(), true);
-                if (degree == 2) animation.CreateRotationChannel(node, curve.ToSplineCurve());
+                if (degree == 3) animation.CreateRotationChannel(node, curve.ToSplineCurve());
             }
 
             return node;

+ 11 - 11
tests/SharpGLTF.Tests/AnimationSamplingTests.cs

@@ -24,7 +24,7 @@ namespace SharpGLTF
 
             for (float amount = 0; amount <= 1; amount += 0.01f)
             {
-                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(amount);
+                var hermite = Animations.SamplerFactory.CreateHermitePointWeights(amount);
 
                 var p = Vector2.Zero;
 
@@ -40,8 +40,8 @@ namespace SharpGLTF
 
             float k = 0.3f;
 
-            var hb = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(k);
-            var ht = Transforms.AnimationSamplerFactory.CalculateHermiteTangent(k);
+            var hb = Animations.SamplerFactory.CreateHermitePointWeights(k);
+            var ht = Animations.SamplerFactory.CreateHermiteTangentWeights(k);
 
             var pp = p1 * hb.Item1 + p4 * hb.Item2 + (p2 - p1) * 4 * hb.Item3 + (p4 - p3) * 4 * hb.Item4;
             var pt = p1 * ht.Item1 + p4 * ht.Item2 + (p2 - p1) * 4 * ht.Item3 + (p4 - p3) * 4 * ht.Item4;
@@ -67,7 +67,7 @@ namespace SharpGLTF
 
             for (float amount = 0; amount <= 1; amount += 0.01f)
             {
-                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(amount);
+                var hermite = Animations.SamplerFactory.CreateHermitePointWeights(amount);
 
                 var p = Vector2.Zero;
 
@@ -105,13 +105,13 @@ namespace SharpGLTF
         [Test]
         public void TestVector3CubicSplineSampling()
         {
-            var sampler = Transforms.AnimationSamplerFactory.CreateSplineSamplerFunc(_TransAnim);
+            var sampler = Animations.SamplerFactory.CreateSampler(_TransAnim);
 
             var points = new List<Vector3>();
 
             for(int i=0; i < 300; ++i)
             {
-                var sample = sampler(((float)i) / 100.0f);
+                var sample = sampler.GetPoint(((float)i) / 100.0f);
                 points.Add( sample );
             }
 
@@ -124,12 +124,12 @@ namespace SharpGLTF
         [Test]
         public void TestQuaternionCubicSplineSampling()
         {
-            var sampler = Transforms.AnimationSamplerFactory.CreateSplineSamplerFunc(_RotAnim);
+            var sampler = Animations.SamplerFactory.CreateSampler(_RotAnim);
 
-            var a = sampler(0);
-            var b = sampler(1);
-            var bc = sampler(1.5f);
-            var c = sampler(2);
+            var a = sampler.GetPoint(0);
+            var b = sampler.GetPoint(1);
+            var bc = sampler.GetPoint(1.5f);
+            var c = sampler.GetPoint(2);
         }
 
     }

+ 4 - 8
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -8,6 +8,8 @@ using NUnit.Framework;
 
 using SharpGLTF.Schema2.Authoring;
 
+using SharpGLTF.Animations;
+
 namespace SharpGLTF.Scenes
 {
     using Geometry;
@@ -45,15 +47,9 @@ namespace SharpGLTF.Scenes
 
             var pivot = new NodeBuilder();
 
-            var tcurve = pivot.UseTranslation().UseCurve("default") as Animations.Curve<Vector3>;
-
-            var c = new Animations.CurvePoint<Vector3>(tcurve, 0);
+            var curve = new[] { (0.0f, Vector3.Zero), (1.0f, Vector3.One) };            
 
-            c.GetAt(0).MovePointTo(Vector3.Zero).MoveOutgoingTangentTo(Vector3.Zero);
-            c.GetAt(1).MovePointTo(new Vector3(10, 0, 0)).MoveIncomingTangentTo(Vector3.Zero).MoveOutgoingTangentTo(new Vector3(0, 40, 0));
-            c.GetAt(2).MovePointTo(new Vector3(20, 0, 0)).MoveIncomingTangentTo(new Vector3(0, -40, 0)).MoveOutgoingTangentTo(Vector3.Zero);
-            c.GetAt(3).MovePointTo(new Vector3(30, 0, 0));
-            c.GetAt(4).MovePointTo(new Vector3(10, -10, 0));
+            pivot.SetTranslationTrack("default", curve.CreateSampler());
 
             var scene = new SceneBuilder();