Browse Source

Nuget package updates.
Code cleanup.
+Docs.

Vicente Penades 5 years ago
parent
commit
ee8f2c649d

+ 1 - 1
build/SharpGLTF.CodeGen/SharpGLTF.CodeGen.csproj

@@ -8,7 +8,7 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="LibGit2Sharp" Version="0.26.2" />    
     <PackageReference Include="LibGit2Sharp" Version="0.26.2" />    
-    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.1.8" />
+    <PackageReference Include="NJsonSchema.CodeGeneration.CSharp" Version="10.1.12" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 14 - 3
src/Shared/Guard.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Linq;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 
 
 namespace SharpGLTF
 namespace SharpGLTF
@@ -63,12 +64,22 @@ namespace SharpGLTF
 
 
         #region null / empty
         #region null / empty
 
 
-        public static void NotNull(object target, string parameterName, string message = "")
+        // Preventing CA1062...
+        // https://github.com/dotnet/roslyn-analyzers/issues/2691
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void NotNull(object target, string parameterName)
+        {
+            if (target == null) throw new ArgumentNullException(parameterName);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static void NotNull(object target, string parameterName, string message)
         {
         {
-            if (target != null) return;
-            throw new ArgumentNullException(parameterName, message);
+            if (target == null) throw new ArgumentNullException(parameterName, message);
         }
         }
 
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static void MustBeNull(object target, string parameterName, string message = "")
         public static void MustBeNull(object target, string parameterName, string message = "")
         {
         {
             if (target == null) return;
             if (target == null) return;

+ 4 - 4
src/SharpGLTF.Core/Animations/CubicSamplers.cs

@@ -52,7 +52,7 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Vector3, Vector3, Vector3)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Vector3 TangentIn, Vector3 Value, Vector3 TangentOut)> ToSplineCurve()
         {
         {
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }
@@ -116,7 +116,7 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Quaternion, Quaternion, Quaternion)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Quaternion TangentIn, Quaternion Value, Quaternion TangentOut)> ToSplineCurve()
         {
         {
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }
@@ -180,7 +180,7 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Transforms.SparseWeight8, Transforms.SparseWeight8, Transforms.SparseWeight8)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Transforms.SparseWeight8 TangentIn, Transforms.SparseWeight8 Value, Transforms.SparseWeight8 TangentOut)> ToSplineCurve()
         {
         {
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }
@@ -244,7 +244,7 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public IReadOnlyDictionary<float, (float[], float[], float[])> ToSplineCurve()
+        public IReadOnlyDictionary<float, (float[] TangentIn, float[] Value, float[] TangentOut)> ToSplineCurve()
         {
         {
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }

+ 4 - 4
src/SharpGLTF.Core/Animations/Interfaces.cs

@@ -10,7 +10,7 @@ namespace SharpGLTF.Animations
     /// <typeparam name="T">The type of a point in the curve.</typeparam>
     /// <typeparam name="T">The type of a point in the curve.</typeparam>
     public interface ICurveSampler<T>
     public interface ICurveSampler<T>
     {
     {
-        T GetPoint(float offset);
+        T GetPoint(Single offset);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -27,8 +27,8 @@ namespace SharpGLTF.Animations
         /// </summary>
         /// </summary>
         int MaxDegree { get; }
         int MaxDegree { get; }
 
 
-        IReadOnlyDictionary<float, T> ToStepCurve();
-        IReadOnlyDictionary<float, T> ToLinearCurve();
-        IReadOnlyDictionary<float, (T, T, T)> ToSplineCurve();
+        IReadOnlyDictionary<Single, T> ToStepCurve();
+        IReadOnlyDictionary<Single, T> ToLinearCurve();
+        IReadOnlyDictionary<Single, (T TangentIn, T Value, T TangentOut)> ToSplineCurve();
     }
     }
 }
 }

+ 6 - 6
src/SharpGLTF.Core/Animations/LinearSamplers.cs

@@ -48,9 +48,9 @@ namespace SharpGLTF.Animations
             return new Dictionary<float, T> { [0] = _Value };
             return new Dictionary<float, T> { [0] = _Value };
         }
         }
 
 
-        public IReadOnlyDictionary<float, (T, T, T)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (T TangentIn, T Value, T TangentOut)> ToSplineCurve()
         {
         {
-            return new Dictionary<float, (T, T, T)> { [0] = (default, _Value, default) };
+            return new Dictionary<float, (T TangentIn, T Value, T TangentOut)> { [0] = (default, _Value, default) };
         }
         }
 
 
         public IReadOnlyDictionary<float, T> ToStepCurve()
         public IReadOnlyDictionary<float, T> ToStepCurve()
@@ -108,7 +108,7 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Vector3, Vector3, Vector3)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Vector3 TangentIn, Vector3 Value, Vector3 TangentOut)> ToSplineCurve()
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
@@ -174,7 +174,7 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Quaternion, Quaternion, Quaternion)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Quaternion TangentIn, Quaternion Value, Quaternion TangentOut)> ToSplineCurve()
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
@@ -241,7 +241,7 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
         }
         }
 
 
-        public IReadOnlyDictionary<float, (Transforms.SparseWeight8, Transforms.SparseWeight8, Transforms.SparseWeight8)> ToSplineCurve()
+        public IReadOnlyDictionary<float, (Transforms.SparseWeight8 TangentIn, Transforms.SparseWeight8 Value, Transforms.SparseWeight8 TangentOut)> ToSplineCurve()
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
@@ -307,7 +307,7 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
             return _Sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
         }
         }
 
 
-        public IReadOnlyDictionary<float, (float[], float[], float[])> ToSplineCurve()
+        public IReadOnlyDictionary<float, (float[] TangentIn, float[] Value, float[] TangentOut)> ToSplineCurve()
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }

+ 2 - 0
src/SharpGLTF.Core/Memory/MemoryAccessor.Validation.cs

@@ -280,6 +280,8 @@ namespace SharpGLTF.Memory
 
 
         public static void VerifyVertexIndices(MemoryAccessor memory, uint vertexCount)
         public static void VerifyVertexIndices(MemoryAccessor memory, uint vertexCount)
         {
         {
+            Guard.NotNull(memory, nameof(memory));
+
             uint restart_value = 0xff;
             uint restart_value = 0xff;
             if (memory.Attribute.Encoding == ENCODING.UNSIGNED_SHORT) restart_value = 0xffff;
             if (memory.Attribute.Encoding == ENCODING.UNSIGNED_SHORT) restart_value = 0xffff;
             if (memory.Attribute.Encoding == ENCODING.UNSIGNED_INT) restart_value = 0xffffffff;
             if (memory.Attribute.Encoding == ENCODING.UNSIGNED_INT) restart_value = 0xffffffff;

+ 19 - 0
src/SharpGLTF.Core/Memory/MemoryImage.cs

@@ -142,6 +142,25 @@ namespace SharpGLTF.Memory
             return new System.IO.MemoryStream(_Image.Array, _Image.Offset, _Image.Count, false);
             return new System.IO.MemoryStream(_Image.Array, _Image.Offset, _Image.Count, false);
         }
         }
 
 
+        /// <summary>
+        /// Saves the image stored in this <see cref="MemoryImage"/> to a file.
+        /// </summary>
+        /// <param name="filePath">A destination file path, with an extension matching <see cref="FileExtension"/></param>
+        public void SaveToFile(string filePath)
+        {
+            Guard.FilePathMustBeValid(filePath, nameof(filePath));
+            Guard.IsTrue(filePath.EndsWith("." + this.FileExtension, StringComparison.OrdinalIgnoreCase), nameof(filePath), $"{nameof(filePath)} must use extension '.{this.FileExtension}'");
+
+            using (var dst = System.IO.File.Create(filePath))
+            {
+                using (var src = Open())
+                {
+                    src.CopyTo(dst);
+                }
+            }
+
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the internal buffer.
         /// Gets the internal buffer.
         /// </summary>
         /// </summary>

+ 463 - 0
src/SharpGLTF.Core/Schema2/gltf.AnimationSampler.cs

@@ -0,0 +1,463 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Numerics;
+
+using SharpGLTF.Animations;
+using SharpGLTF.Collections;
+using SharpGLTF.Transforms;
+using SharpGLTF.Validation;
+
+namespace SharpGLTF.Schema2
+{
+    public interface IAnimationSampler<T>
+    {
+        /// <summary>
+        /// Gets a value indicating the interpolation being used.
+        /// If the value is <see cref="AnimationInterpolationMode.STEP"/> or <see cref="AnimationInterpolationMode.LINEAR"/>, <see cref="GetLinearKeys"/> should be used.
+        /// If the value is <see cref="AnimationInterpolationMode.CUBICSPLINE"/>, <see cref="GetCubicKeys"/> should be used.
+        /// </summary>
+        AnimationInterpolationMode InterpolationMode { get; }
+
+        /// <summary>
+        /// Gets the linear animation entries for <see cref="AnimationInterpolationMode.STEP"/> and <see cref="AnimationInterpolationMode.LINEAR"/> modes.
+        /// </summary>
+        /// <returns>A sequence of Time-Value keys.</returns>
+        IEnumerable<(Single Key, T Value)> GetLinearKeys();
+
+        /// <summary>
+        /// Gets the cubic animation entries fot <see cref="AnimationInterpolationMode.CUBICSPLINE"/> mode.
+        /// </summary>
+        /// <returns>A sequence of Time-(TangentIn,Value,TangentOut) keys.</returns>
+        IEnumerable<(Single Key, (T TangentIn, T Value, T TangentOut))> GetCubicKeys();
+
+        /// <summary>
+        /// Creates an interpolation sampler that can be used to query the value of the curve at any time.
+        /// </summary>
+        /// <param name="isolateMemory">
+        /// If true, this call will do an internal copy of the curve data,
+        /// so it will not reference the source date in the original document.
+        /// </param>
+        /// <returns>An object that can be used to sample the curve at any time.</returns>
+        /// <remarks>
+        /// When <paramref name="isolateMemory"/> is true, it also arranges the data so it's much
+        /// faster to query.
+        /// </remarks>
+        ICurveSampler<T> CreateCurveSampler(bool isolateMemory = false);
+    }
+
+    sealed partial class AnimationSampler :
+        IChildOf<Animation>,
+        IAnimationSampler<Vector3>,
+        IAnimationSampler<Quaternion>,
+        IAnimationSampler<SparseWeight8>,
+        IAnimationSampler<Single[]>
+    {
+        #region lifecycle
+
+        internal AnimationSampler() { }
+
+        internal AnimationSampler(AnimationInterpolationMode interpolation)
+        {
+            _interpolation = interpolation.AsNullable(_interpolationDefault);
+        }
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the <see cref="Animation"/> instance that owns this <see cref="AnimationSampler"/> instance.
+        /// </summary>
+        public Animation LogicalParent { get; private set; }
+
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="AnimationSampler"/> at <see cref="Animation._samplers"/>.
+        /// </summary>
+        public int LogicalIndex => LogicalParent._Samplers.IndexOfReference(this);
+
+        void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
+
+        public AnimationInterpolationMode InterpolationMode
+        {
+            get => _interpolation.AsValue(_interpolationDefault);
+            set => _interpolation = value.AsNullable(_interpolationDefault);
+        }
+
+        public Accessor Input => this.LogicalParent.LogicalParent.LogicalAccessors[this._input];
+
+        public Accessor Output => this.LogicalParent.LogicalParent.LogicalAccessors[this._output];
+
+        public float Duration { get { var keys = Input.AsScalarArray(); return keys.Count == 0 ? 0 : keys[keys.Count - 1]; } }
+
+        #endregion
+
+        #region API
+
+        private Accessor _CreateInputAccessor(IReadOnlyList<Single> input)
+        {
+            var root = LogicalParent.LogicalParent;
+
+            var buffer = root.UseBufferView(new Byte[input.Count * 4]);
+            var accessor = root.CreateAccessor("Animation.Input");
+
+            accessor.SetData(buffer, 0, input.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
+
+            Memory.EncodedArrayUtils._CopyTo(input, accessor.AsScalarArray());
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private Accessor _CreateOutputAccessor(IReadOnlyList<Vector3> output)
+        {
+            var root = LogicalParent.LogicalParent;
+
+            var buffer = root.UseBufferView(new Byte[output.Count * 4 * 3]);
+
+            System.Diagnostics.Debug.Assert(buffer.ByteStride == 0);
+
+            var accessor = root.CreateAccessor("Animation.Output");
+
+            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC3, EncodingType.FLOAT, false);
+
+            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector3Array());
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private Accessor _CreateOutputAccessor(IReadOnlyList<Quaternion> output)
+        {
+            var root = LogicalParent.LogicalParent;
+
+            var buffer = root.UseBufferView(new Byte[output.Count * 4 * 4]);
+            var accessor = root.CreateAccessor("Animation.Output");
+
+            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC4, EncodingType.FLOAT, false);
+
+            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsQuaternionArray());
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private Accessor _CreateOutputAccessor(IReadOnlyList<SparseWeight8> output, int expandedCount)
+        {
+            var root = LogicalParent.LogicalParent;
+
+            var buffer = root.UseBufferView(new Byte[output.Count * 4 * expandedCount]);
+            var accessor = root.CreateAccessor("Animation.Output");
+
+            accessor.SetData(buffer, 0, output.Count * expandedCount, DimensionType.SCALAR, EncodingType.FLOAT, false);
+
+            var dst = accessor.AsScalarArray();
+
+            for (int i = 0; i < output.Count; ++i)
+            {
+                var src = output[i];
+
+                for (int j = 0; j < expandedCount; ++j)
+                {
+                    dst[(i * expandedCount) + j] = src[j];
+                }
+            }
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private static (Single[] Keys, TValue[] Values) _Split<TValue>(IReadOnlyDictionary<Single, TValue> keyframes)
+        {
+            var sorted = keyframes
+                .OrderBy(item => item.Key)
+                .ToList();
+
+            var keys = new Single[sorted.Count];
+            var vals = new TValue[sorted.Count];
+
+            for (int i = 0; i < keys.Length; ++i)
+            {
+                keys[i] = sorted[i].Key;
+                vals[i] = sorted[i].Value;
+            }
+
+            return (keys, vals);
+        }
+
+        private static (Single[] Keys, TValue[] Values) _Split<TValue>(IReadOnlyDictionary<Single, (TValue TangentIn, TValue Value, TValue TangentOut)> keyframes)
+        {
+            var sorted = keyframes
+                .OrderBy(item => item.Key)
+                .ToList();
+
+            var keys = new Single[sorted.Count];
+            var vals = new TValue[sorted.Count * 3];
+
+            for (int i = 0; i < keys.Length; ++i)
+            {
+                keys[i] = sorted[i].Key;
+                vals[(i * 3) + 0] = sorted[i].Value.TangentIn;
+                vals[(i * 3) + 1] = sorted[i].Value.Value;
+                vals[(i * 3) + 2] = sorted[i].Value.TangentOut;
+            }
+
+            return (keys, vals);
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, Vector3> keyframes)
+        {
+            var (keys, values) = _Split(keyframes);
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values).LogicalIndex;
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, Quaternion> keyframes)
+        {
+            var (keys, values) = _Split(keyframes);
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values).LogicalIndex;
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, SparseWeight8> keyframes, int expandedCount)
+        {
+            var (keys, values) = _Split(keyframes);
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values, expandedCount).LogicalIndex;
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, (Vector3 TangentIn, Vector3 Value, Vector3 TangentOut)> keyframes)
+        {
+            var (keys, values) = _Split(keyframes);
+
+            // this might not be true for a looped animation, where first and last might be the same
+            values[0] = Vector3.Zero;
+            values[values.Length - 1] = Vector3.Zero;
+
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values).LogicalIndex;
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, (Quaternion TangentIn, Quaternion Value, Quaternion TangentOut)> keyframes)
+        {
+            var (keys, values) = _Split(keyframes);
+
+            // this might not be true for a looped animation, where first and last might be the same
+            values[0] = default;
+            values[values.Length - 1] = default;
+
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values).LogicalIndex;
+        }
+
+        internal void SetKeys(IReadOnlyDictionary<Single, (SparseWeight8 TangentIn, SparseWeight8 Value, SparseWeight8 TangentOut)> keyframes, int expandedCount)
+        {
+            var (keys, values) = _Split(keyframes);
+
+            // this might not be true for a looped animation, where first and last might be the same
+            values[0] = default;
+            values[values.Length - 1] = default;
+
+            _input = this._CreateInputAccessor(keys).LogicalIndex;
+            _output = this._CreateOutputAccessor(values, expandedCount).LogicalIndex;
+        }
+
+        IEnumerable<(Single, Vector3)> IAnimationSampler<Vector3>.GetLinearKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsVector3Array();
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, Quaternion)> IAnimationSampler<Quaternion>.GetLinearKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsQuaternionArray();
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, SparseWeight8)> IAnimationSampler<SparseWeight8>.GetLinearKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var dimensions = this.Output.Count / this.Input.Count;
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsMultiArray(dimensions);
+
+            return keys.Zip(frames, (key, val) => (key, SparseWeight8.Create(val)));
+        }
+
+        IEnumerable<(Single, Single[])> IAnimationSampler<Single[]>.GetLinearKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var dimensions = this.Output.Count / this.Input.Count;
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsMultiArray(dimensions);
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, (Vector3, Vector3, Vector3))> IAnimationSampler<Vector3>.GetCubicKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var keys = this.Input.AsScalarArray();
+            var frames = _GroupByTangentValueTangent(this.Output.AsVector3Array());
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> IAnimationSampler<Quaternion>.GetCubicKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var keys = this.Input.AsScalarArray();
+            var frames = _GroupByTangentValueTangent(this.Output.AsQuaternionArray());
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, (Single[], Single[], Single[]))> IAnimationSampler<Single[]>.GetCubicKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var dimensions = this.Output.Count / (this.Input.Count * 3);
+
+            var keys = this.Input.AsScalarArray();
+            var frames = _GroupByTangentValueTangent(this.Output.AsMultiArray(dimensions));
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
+        IEnumerable<(Single, (SparseWeight8, SparseWeight8, SparseWeight8))> IAnimationSampler<SparseWeight8>.GetCubicKeys()
+        {
+            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
+
+            var dimensions = this.Output.Count / (this.Input.Count * 3);
+
+            var keys = this.Input.AsScalarArray();
+            var frames = _GroupByTangentValueTangent(this.Output.AsMultiArray(dimensions));
+
+            return keys.Zip(frames, (key, val) => (key, SparseWeight8.AsTuple(val.TangentIn, val.Value, val.TangentOut)) );
+        }
+
+        private static IEnumerable<(T TangentIn, T Value, T TangentOut)> _GroupByTangentValueTangent<T>(IEnumerable<T> collection)
+        {
+            using (var ptr = collection.GetEnumerator())
+            {
+                while (true)
+                {
+                    if (!ptr.MoveNext()) break;
+                    var a = ptr.Current;
+                    if (!ptr.MoveNext()) break;
+                    var b = ptr.Current;
+                    if (!ptr.MoveNext()) break;
+                    var c = ptr.Current;
+
+                    yield return (a, b, c);
+                }
+            }
+        }
+
+        ICurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler(bool isolateMemory)
+        {
+            var xsampler = this as IAnimationSampler<Vector3>;
+
+            switch (this.InterpolationMode)
+            {
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        ICurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler(bool isolateMemory)
+        {
+            var xsampler = this as IAnimationSampler<Quaternion>;
+
+            switch (this.InterpolationMode)
+            {
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        ICurveSampler<SparseWeight8> IAnimationSampler<SparseWeight8>.CreateCurveSampler(bool isolateMemory)
+        {
+            var xsampler = this as IAnimationSampler<SparseWeight8>;
+
+            switch (this.InterpolationMode)
+            {
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        ICurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler(bool isolateMemory)
+        {
+            var xsampler = this as IAnimationSampler<Single[]>;
+
+            switch (this.InterpolationMode)
+            {
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
+            }
+
+            throw new NotImplementedException();
+        }
+
+        #endregion
+
+        #region validation
+
+        protected override void OnValidateReferences(ValidationContext validate)
+        {
+            base.OnValidateReferences(validate);
+
+            validate
+                .IsNullOrIndex("Input", _input, this.LogicalParent.LogicalParent.LogicalAccessors)
+                .IsNullOrIndex("Output", _output, this.LogicalParent.LogicalParent.LogicalAccessors);
+        }
+
+        protected override void OnValidateContent(ValidationContext validate)
+        {
+            base.OnValidateContent(validate);
+
+            if (Output.Dimensions != DimensionType.SCALAR)
+            {
+                var outMult = InterpolationMode == AnimationInterpolationMode.CUBICSPLINE ? 3 : 1;
+
+                validate.AreEqual("Output", Output.Count, Input.Count * outMult);
+            }
+
+            Input.ValidateAnimationInput(validate);
+            Output.ValidateAnimationOutput(validate);
+        }
+
+        #endregion
+    }
+
+}

+ 4 - 427
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -82,7 +82,7 @@ namespace SharpGLTF.Schema2
                 .SetSampler(sampler);
                 .SetSampler(sampler);
         }
         }
 
 
-        public void CreateScaleChannel(Node node, IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
+        public void CreateScaleChannel(Node node, IReadOnlyDictionary<Single, (Vector3 TangentIn, Vector3 Value, Vector3 TangentOut)> keyframes)
         {
         {
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
 
 
@@ -102,7 +102,7 @@ namespace SharpGLTF.Schema2
                 .SetSampler(sampler);
                 .SetSampler(sampler);
         }
         }
 
 
-        public void CreateRotationChannel(Node node, IReadOnlyDictionary<Single, (Quaternion, Quaternion, Quaternion)> keyframes)
+        public void CreateRotationChannel(Node node, IReadOnlyDictionary<Single, (Quaternion TangentIn, Quaternion Value, Quaternion TangentOut)> keyframes)
         {
         {
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
 
 
@@ -122,7 +122,7 @@ namespace SharpGLTF.Schema2
                 .SetSampler(sampler);
                 .SetSampler(sampler);
         }
         }
 
 
-        public void CreateTranslationChannel(Node node, IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
+        public void CreateTranslationChannel(Node node, IReadOnlyDictionary<Single, (Vector3 TangentIn, Vector3 Value, Vector3 TangentOut)> keyframes)
         {
         {
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
 
 
@@ -142,7 +142,7 @@ namespace SharpGLTF.Schema2
                 .SetSampler(sampler);
                 .SetSampler(sampler);
         }
         }
 
 
-        public void CreateMorphChannel(Node node, IReadOnlyDictionary<Single, (SparseWeight8, SparseWeight8, SparseWeight8)> keyframes, int morphCount)
+        public void CreateMorphChannel(Node node, IReadOnlyDictionary<Single, (SparseWeight8 TangentIn, SparseWeight8 Value, SparseWeight8 TangentOut)> keyframes, int morphCount)
         {
         {
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
             var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE);
 
 
@@ -336,429 +336,6 @@ namespace SharpGLTF.Schema2
         #endregion
         #endregion
     }
     }
 
 
-    sealed partial class AnimationSampler : IChildOf<Animation>,
-        IAnimationSampler<Vector3>,
-        IAnimationSampler<Quaternion>,
-        IAnimationSampler<SparseWeight8>,
-        IAnimationSampler<Single[]>
-    {
-        #region lifecycle
-
-        internal AnimationSampler() { }
-
-        internal AnimationSampler(AnimationInterpolationMode interpolation)
-        {
-            _interpolation = interpolation.AsNullable(_interpolationDefault);
-        }
-
-        #endregion
-
-        #region properties
-
-        /// <summary>
-        /// Gets the <see cref="Animation"/> instance that owns this <see cref="AnimationSampler"/> instance.
-        /// </summary>
-        public Animation LogicalParent { get; private set; }
-
-        /// <summary>
-        /// Gets the zero-based index of this <see cref="AnimationSampler"/> at <see cref="Animation._samplers"/>.
-        /// </summary>
-        public int LogicalIndex => LogicalParent._Samplers.IndexOfReference(this);
-
-        void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
-
-        public AnimationInterpolationMode InterpolationMode
-        {
-            get => _interpolation.AsValue(_interpolationDefault);
-            set => _interpolation = value.AsNullable(_interpolationDefault);
-        }
-
-        public Accessor Input => this.LogicalParent.LogicalParent.LogicalAccessors[this._input];
-
-        public Accessor Output => this.LogicalParent.LogicalParent.LogicalAccessors[this._output];
-
-        public float Duration { get { var keys = Input.AsScalarArray(); return keys.Count == 0 ? 0 : keys[keys.Count - 1]; } }
-
-        #endregion
-
-        #region API
-
-        private Accessor _CreateInputAccessor(IReadOnlyList<Single> input)
-        {
-            var root = LogicalParent.LogicalParent;
-
-            var buffer = root.UseBufferView(new Byte[input.Count * 4]);
-            var accessor = root.CreateAccessor("Animation.Input");
-
-            accessor.SetData(buffer, 0, input.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
-
-            Memory.EncodedArrayUtils._CopyTo(input, accessor.AsScalarArray());
-
-            accessor.UpdateBounds();
-
-            return accessor;
-        }
-
-        private Accessor _CreateOutputAccessor(IReadOnlyList<Vector3> output)
-        {
-            var root = LogicalParent.LogicalParent;
-
-            var buffer = root.UseBufferView(new Byte[output.Count * 4 * 3]);
-
-            System.Diagnostics.Debug.Assert(buffer.ByteStride == 0);
-
-            var accessor = root.CreateAccessor("Animation.Output");
-
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC3, EncodingType.FLOAT, false);
-
-            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector3Array());
-
-            accessor.UpdateBounds();
-
-            return accessor;
-        }
-
-        private Accessor _CreateOutputAccessor(IReadOnlyList<Quaternion> output)
-        {
-            var root = LogicalParent.LogicalParent;
-
-            var buffer = root.UseBufferView(new Byte[output.Count * 4 * 4]);
-            var accessor = root.CreateAccessor("Animation.Output");
-
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC4, EncodingType.FLOAT, false);
-
-            Memory.EncodedArrayUtils._CopyTo(output, accessor.AsQuaternionArray());
-
-            accessor.UpdateBounds();
-
-            return accessor;
-        }
-
-        private Accessor _CreateOutputAccessor(IReadOnlyList<SparseWeight8> output, int expandedCount)
-        {
-            var root = LogicalParent.LogicalParent;
-
-            var buffer = root.UseBufferView(new Byte[output.Count * 4 * expandedCount]);
-            var accessor = root.CreateAccessor("Animation.Output");
-
-            accessor.SetData(buffer, 0, output.Count * expandedCount, DimensionType.SCALAR, EncodingType.FLOAT, false);
-
-            var dst = accessor.AsScalarArray();
-
-            for (int i = 0; i < output.Count; ++i)
-            {
-                var src = output[i];
-
-                for (int j = 0; j < expandedCount; ++j)
-                {
-                    dst[(i * expandedCount) + j] = src[j];
-                }
-            }
-
-            accessor.UpdateBounds();
-
-            return accessor;
-        }
-
-        private static (Single[] Keys, TValue[] Values) _Split<TValue>(IReadOnlyDictionary<Single, TValue> keyframes)
-        {
-            var sorted = keyframes
-                .OrderBy(item => item.Key)
-                .ToList();
-
-            var keys = new Single[sorted.Count];
-            var vals = new TValue[sorted.Count];
-
-            for (int i = 0; i < keys.Length; ++i)
-            {
-                keys[i] = sorted[i].Key;
-                vals[i] = sorted[i].Value;
-            }
-
-            return (keys, vals);
-        }
-
-        private static (Single[] Keys, TValue[] Values) _Split<TValue>(IReadOnlyDictionary<Single, (TValue, TValue, TValue)> keyframes)
-        {
-            var sorted = keyframes
-                .OrderBy(item => item.Key)
-                .ToList();
-
-            var keys = new Single[sorted.Count];
-            var vals = new TValue[sorted.Count * 3];
-
-            for (int i = 0; i < keys.Length; ++i)
-            {
-                keys[i] = sorted[i].Key;
-                vals[(i * 3) + 0] = sorted[i].Value.Item1;
-                vals[(i * 3) + 1] = sorted[i].Value.Item2;
-                vals[(i * 3) + 2] = sorted[i].Value.Item3;
-            }
-
-            return (keys, vals);
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, Vector3> keyframes)
-        {
-            var kv = _Split(keyframes);
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values).LogicalIndex;
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, Quaternion> keyframes)
-        {
-            var kv = _Split(keyframes);
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values).LogicalIndex;
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, SparseWeight8> keyframes, int expandedCount)
-        {
-            var kv = _Split(keyframes);
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values, expandedCount).LogicalIndex;
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
-        {
-            var kv = _Split(keyframes);
-
-            // this might not be true for a looped animation, where first and last might be the same
-            kv.Values[0] = Vector3.Zero;
-            kv.Values[kv.Values.Length - 1] = Vector3.Zero;
-
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values).LogicalIndex;
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, (Quaternion, Quaternion, Quaternion)> keyframes)
-        {
-            var kv = _Split(keyframes);
-
-            // this might not be true for a looped animation, where first and last might be the same
-            kv.Values[0] = default;
-            kv.Values[kv.Values.Length - 1] = default;
-
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values).LogicalIndex;
-        }
-
-        internal void SetKeys(IReadOnlyDictionary<Single, (SparseWeight8, SparseWeight8, SparseWeight8)> keyframes, int expandedCount)
-        {
-            var kv = _Split(keyframes);
-
-            // this might not be true for a looped animation, where first and last might be the same
-            kv.Values[0] = default;
-            kv.Values[kv.Values.Length - 1] = default;
-
-            _input = this._CreateInputAccessor(kv.Keys).LogicalIndex;
-            _output = this._CreateOutputAccessor(kv.Values, expandedCount).LogicalIndex;
-        }
-
-        IEnumerable<(Single, Vector3)> IAnimationSampler<Vector3>.GetLinearKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var keys = this.Input.AsScalarArray();
-            var frames = this.Output.AsVector3Array();
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, Quaternion)> IAnimationSampler<Quaternion>.GetLinearKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var keys = this.Input.AsScalarArray();
-            var frames = this.Output.AsQuaternionArray();
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, SparseWeight8)> IAnimationSampler<SparseWeight8>.GetLinearKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var dimensions = this.Output.Count / this.Input.Count;
-
-            var keys = this.Input.AsScalarArray();
-            var frames = this.Output.AsMultiArray(dimensions);
-
-            return keys.Zip(frames, (key, val) => (key, SparseWeight8.Create(val)));
-        }
-
-        IEnumerable<(Single, Single[])> IAnimationSampler<Single[]>.GetLinearKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var dimensions = this.Output.Count / this.Input.Count;
-
-            var keys = this.Input.AsScalarArray();
-            var frames = this.Output.AsMultiArray(dimensions);
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, (Vector3, Vector3, Vector3))> IAnimationSampler<Vector3>.GetCubicKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var keys = this.Input.AsScalarArray();
-            var frames = _GroupByThree(this.Output.AsVector3Array());
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> IAnimationSampler<Quaternion>.GetCubicKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var keys = this.Input.AsScalarArray();
-            var frames = _GroupByThree(this.Output.AsQuaternionArray());
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, (Single[], Single[], Single[]))> IAnimationSampler<Single[]>.GetCubicKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var dimensions = this.Output.Count / (this.Input.Count * 3);
-
-            var keys = this.Input.AsScalarArray();
-            var frames = _GroupByThree(this.Output.AsMultiArray(dimensions));
-
-            return keys.Zip(frames, (key, val) => (key, val));
-        }
-
-        IEnumerable<(Single, (SparseWeight8, SparseWeight8, SparseWeight8))> IAnimationSampler<SparseWeight8>.GetCubicKeys()
-        {
-            Guard.IsFalse(this.InterpolationMode != AnimationInterpolationMode.CUBICSPLINE, nameof(InterpolationMode));
-
-            var dimensions = this.Output.Count / (this.Input.Count * 3);
-
-            var keys = this.Input.AsScalarArray();
-            var frames = _GroupByThree(this.Output.AsMultiArray(dimensions));
-
-            return keys.Zip(frames, (key, val) => (key, (SparseWeight8.Create(val.Item1), SparseWeight8.Create(val.Item2), SparseWeight8.Create(val.Item3))));
-        }
-
-        private static IEnumerable<(T, T, T)> _GroupByThree<T>(IEnumerable<T> collection)
-        {
-            using (var ptr = collection.GetEnumerator())
-            {
-                while (true)
-                {
-                    if (!ptr.MoveNext()) break;
-                    var a = ptr.Current;
-                    if (!ptr.MoveNext()) break;
-                    var b = ptr.Current;
-                    if (!ptr.MoveNext()) break;
-                    var c = ptr.Current;
-
-                    yield return (a, b, c);
-                }
-            }
-        }
-
-        ICurveSampler<Vector3> IAnimationSampler<Vector3>.CreateCurveSampler(bool isolateMemory)
-        {
-            var xsampler = this as IAnimationSampler<Vector3>;
-
-            switch (this.InterpolationMode)
-            {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
-            }
-
-            throw new NotImplementedException();
-        }
-
-        ICurveSampler<Quaternion> IAnimationSampler<Quaternion>.CreateCurveSampler(bool isolateMemory)
-        {
-            var xsampler = this as IAnimationSampler<Quaternion>;
-
-            switch (this.InterpolationMode)
-            {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
-            }
-
-            throw new NotImplementedException();
-        }
-
-        ICurveSampler<SparseWeight8> IAnimationSampler<SparseWeight8>.CreateCurveSampler(bool isolateMemory)
-        {
-            var xsampler = this as IAnimationSampler<SparseWeight8>;
-
-            switch (this.InterpolationMode)
-            {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
-            }
-
-            throw new NotImplementedException();
-        }
-
-        ICurveSampler<Single[]> IAnimationSampler<Single[]>.CreateCurveSampler(bool isolateMemory)
-        {
-            var xsampler = this as IAnimationSampler<Single[]>;
-
-            switch (this.InterpolationMode)
-            {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys().CreateSampler(false, isolateMemory);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys().CreateSampler(true, isolateMemory);
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys().CreateSampler(isolateMemory);
-            }
-
-            throw new NotImplementedException();
-        }
-
-        #endregion
-
-        #region validation
-
-        protected override void OnValidateReferences(ValidationContext validate)
-        {
-            base.OnValidateReferences(validate);
-
-            validate
-                .IsNullOrIndex("Input", _input, this.LogicalParent.LogicalParent.LogicalAccessors)
-                .IsNullOrIndex("Output", _output, this.LogicalParent.LogicalParent.LogicalAccessors);
-        }
-
-        protected override void OnValidateContent(ValidationContext validate)
-        {
-            base.OnValidateContent(validate);
-
-            if (Output.Dimensions != DimensionType.SCALAR)
-            {
-                var outMult = InterpolationMode == AnimationInterpolationMode.CUBICSPLINE ? 3 : 1;
-
-                validate.AreEqual("Output", Output.Count, Input.Count * outMult);
-            }
-
-            Input.ValidateAnimationInput(validate);
-            Output.ValidateAnimationOutput(validate);
-        }
-
-        #endregion
-    }
-
-    public interface IAnimationSampler<T>
-    {
-        AnimationInterpolationMode InterpolationMode { get; }
-
-        IEnumerable<(Single, T)> GetLinearKeys();
-
-        IEnumerable<(Single, (T, T, T))> GetCubicKeys();
-
-        ICurveSampler<T> CreateCurveSampler(bool isolateMemory = false);
-    }
-
     public sealed partial class ModelRoot
     public sealed partial class ModelRoot
     {
     {
         /// <summary>
         /// <summary>

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -159,7 +159,7 @@ namespace SharpGLTF.Schema2
         /// If the image is stored externaly as an image file,
         /// If the image is stored externaly as an image file,
         /// it creates a new BufferView and stores the image in the BufferView.
         /// it creates a new BufferView and stores the image in the BufferView.
         /// </summary>
         /// </summary>
-        public void TransferToInternalBuffer()
+        internal void TransferToInternalBuffer()
         {
         {
             if (this._SatelliteImageContent == null) return;
             if (this._SatelliteImageContent == null) return;
 
 

+ 0 - 4
src/SharpGLTF.Core/SharpGLTF.Core.csproj

@@ -34,10 +34,6 @@
     <None Include="Schema2\Generated\*.cs">
     <None Include="Schema2\Generated\*.cs">
       <ExcludeFromStyleCop>true</ExcludeFromStyleCop>
       <ExcludeFromStyleCop>true</ExcludeFromStyleCop>
     </None>
     </None>
-  </ItemGroup>
-
-  <ItemGroup>
-    <None Include="..\..\.editorconfig" Link=".editorconfig" />
   </ItemGroup>  
   </ItemGroup>  
 
 
 </Project>
 </Project>

+ 6 - 0
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -218,6 +218,12 @@ namespace SharpGLTF.Transforms
             Weight7 = sparse.Weight7 * scale;
             Weight7 = sparse.Weight7 * scale;
         }
         }
 
 
+
+        internal static (SparseWeight8 TangentIn, SparseWeight8 Value, SparseWeight8 TangentOut) AsTuple(float[] tangentIn, float[] value, float[] tangentOut)
+        {
+            return (Create(tangentIn), Create(value), Create(tangentOut));
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data

+ 15 - 1
src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs

@@ -168,7 +168,11 @@ namespace SharpGLTF.Validation
             this.NonNegative($"{parameterName}.offset", offset.Value);
             this.NonNegative($"{parameterName}.offset", offset.Value);
             this.IsGreater($"{parameterName}.length", length, 0);
             this.IsGreater($"{parameterName}.length", length, 0);
 
 
-            if (array == null) _LinkThrow(parameterName, $".{offset} exceeds the number of available items (null).");
+            if (array == null)
+            {
+                _LinkThrow(parameterName, $".{offset} exceeds the number of available items (null).");
+                return this;
+            }
 
 
             if (offset > array.Count - length)
             if (offset > array.Count - length)
             {
             {
@@ -263,6 +267,8 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE ArePositions(PARAMNAME pname, IList<System.Numerics.Vector3> positions)
         public OUTTYPE ArePositions(PARAMNAME pname, IList<System.Numerics.Vector3> positions)
         {
         {
+            Guard.NotNull(positions, nameof(positions));
+
             for (int i = 0; i < positions.Count; ++i)
             for (int i = 0; i < positions.Count; ++i)
             {
             {
                 IsPosition((pname, i), positions[i]);
                 IsPosition((pname, i), positions[i]);
@@ -273,6 +279,8 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE AreNormals(PARAMNAME pname, IList<System.Numerics.Vector3> normals)
         public OUTTYPE AreNormals(PARAMNAME pname, IList<System.Numerics.Vector3> normals)
         {
         {
+            Guard.NotNull(normals, nameof(normals));
+
             for (int i = 0; i < normals.Count; ++i)
             for (int i = 0; i < normals.Count; ++i)
             {
             {
                 IsNormal((pname, i), normals[i]);
                 IsNormal((pname, i), normals[i]);
@@ -283,6 +291,8 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE AreTangents(PARAMNAME pname, IList<System.Numerics.Vector4> tangents)
         public OUTTYPE AreTangents(PARAMNAME pname, IList<System.Numerics.Vector4> tangents)
         {
         {
+            Guard.NotNull(tangents, nameof(tangents));
+
             for (int i = 0; i < tangents.Count; ++i)
             for (int i = 0; i < tangents.Count; ++i)
             {
             {
                 if (!tangents[i].IsValidTangent()) _DataThrow((pname, i), "Invalid Tangent");
                 if (!tangents[i].IsValidTangent()) _DataThrow((pname, i), "Invalid Tangent");
@@ -293,6 +303,8 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE AreRotations(PARAMNAME pname, IList<System.Numerics.Quaternion> rotations)
         public OUTTYPE AreRotations(PARAMNAME pname, IList<System.Numerics.Quaternion> rotations)
         {
         {
+            Guard.NotNull(rotations, nameof(rotations));
+
             for (int i = 0; i < rotations.Count; ++i)
             for (int i = 0; i < rotations.Count; ++i)
             {
             {
                 if (!rotations[i].IsNormalized()) _DataThrow((pname, i), "Invalid Rotation");
                 if (!rotations[i].IsNormalized()) _DataThrow((pname, i), "Invalid Rotation");
@@ -303,6 +315,8 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE AreJoints(PARAMNAME pname, IList<System.Numerics.Vector4> joints, int skinsMaxJointCount)
         public OUTTYPE AreJoints(PARAMNAME pname, IList<System.Numerics.Vector4> joints, int skinsMaxJointCount)
         {
         {
+            Guard.NotNull(joints, nameof(joints));
+
             for (int i = 0; i < joints.Count; ++i)
             for (int i = 0; i < joints.Count; ++i)
             {
             {
                 var jjjj = joints[i];
                 var jjjj = joints[i];

+ 1 - 0
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -16,6 +16,7 @@ namespace SharpGLTF.Validation
 
 
         public ValidationContext(ValidationResult result)
         public ValidationContext(ValidationResult result)
         {
         {
+            Guard.NotNull(result, nameof(result));
             _Root = result.Root;
             _Root = result.Root;
             _Mode = result.Mode;
             _Mode = result.Mode;
             _Current = null;
             _Current = null;

+ 1 - 1
src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs

@@ -280,7 +280,7 @@ namespace SharpGLTF.Animations
             return d;
             return d;
         }
         }
 
 
-        IReadOnlyDictionary<float, (T, T, T)> IConvertibleCurve<T>.ToSplineCurve()
+        IReadOnlyDictionary<float, (T TangentIn, T Value, T TangentOut)> IConvertibleCurve<T>.ToSplineCurve()
         {
         {
             var d = new Dictionary<float, (T, T, T)>();
             var d = new Dictionary<float, (T, T, T)>();
 
 

+ 3 - 3
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -191,7 +191,7 @@ namespace SharpGLTF.Geometry
             Weights1 = null;
             Weights1 = null;
         }
         }
 
 
-        private void _FillMorphData(Vector3[] array, Func<VertexBufferColumns, Vector3> selector)
+        private void _FillMorphData(Vector3[] array, Converter<VertexBufferColumns, Vector3> selector)
         {
         {
             if (array == null) return;
             if (array == null) return;
 
 
@@ -201,7 +201,7 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        private void _FillMorphData(Vector3[] array, Func<VertexBufferColumns, Vector4> selector)
+        private void _FillMorphData(Vector3[] array, Converter<VertexBufferColumns, Vector4> selector)
         {
         {
             if (array == null) return;
             if (array == null) return;
 
 
@@ -212,7 +212,7 @@ namespace SharpGLTF.Geometry
             }
             }
         }
         }
 
 
-        private void _FillMorphData(Vector4[] array, Func<VertexBufferColumns, Vector4> selector)
+        private void _FillMorphData(Vector4[] array, Converter<VertexBufferColumns, Vector4> selector)
         {
         {
             if (array == null) return;
             if (array == null) return;
 
 

+ 2 - 2
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -322,7 +322,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
         }
         }
 
 
-        private static Func<IVertexBuilder, Object> _GetVertexBuilderAttributeFunc(string attributeName)
+        private static Converter<IVertexBuilder, Object> _GetVertexBuilderAttributeFunc(string attributeName)
         {
         {
             if (attributeName == "POSITION") return v => v.GetGeometry().GetPosition();
             if (attributeName == "POSITION") return v => v.GetGeometry().GetPosition();
             if (attributeName == "NORMAL") return v => { return v.GetGeometry().TryGetNormal(out Vector3 n) ? n : Vector3.Zero; };
             if (attributeName == "NORMAL") return v => { return v.GetGeometry().TryGetNormal(out Vector3 n) ? n : Vector3.Zero; };
@@ -351,7 +351,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return v => v.GetMaterial().GetCustomAttribute(attributeName);
             return v => v.GetMaterial().GetCustomAttribute(attributeName);
         }
         }
 
 
-        private static TColumn[] _GetColumn<TVertex, TColumn>(this IReadOnlyList<TVertex> vertices, Func<IVertexBuilder, Object> func)
+        private static TColumn[] _GetColumn<TVertex, TColumn>(this IReadOnlyList<TVertex> vertices, Converter<IVertexBuilder, Object> func)
             where TVertex : IVertexBuilder
             where TVertex : IVertexBuilder
         {
         {
             var dst = new TColumn[vertices.Count];
             var dst = new TColumn[vertices.Count];