2
0
Эх сурвалжийг харах

progress on animation and skinning API

Vicente Penades 6 жил өмнө
parent
commit
86a9e6ca79

+ 1 - 1
src/SharpGLTF.DOM/Collections/ChildrenCollection.cs

@@ -7,7 +7,7 @@ using System.Text;
 namespace SharpGLTF.Collections
 namespace SharpGLTF.Collections
 {
 {
     [System.Diagnostics.DebuggerDisplay("{Count}")]
     [System.Diagnostics.DebuggerDisplay("{Count}")]
-    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CollectionDebugView<>))]
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CollectionDebugProxy<>))]
     [Serializable]
     [Serializable]
     sealed class ChildrenCollection<T, TParent> : IList<T>, IReadOnlyList<T>
     sealed class ChildrenCollection<T, TParent> : IList<T>, IReadOnlyList<T>
         where T : class, IChildOf<TParent>
         where T : class, IChildOf<TParent>

+ 43 - 9
src/SharpGLTF.DOM/Debug/DebugViews.cs

@@ -5,15 +5,12 @@ using System.Numerics;
 using System.Text;
 using System.Text;
 
 
 namespace SharpGLTF.Debug
 namespace SharpGLTF.Debug
-{
-    /// <summary>
-    /// class to visualize collection items during debug
-    /// </summary>
-    internal sealed class _CollectionDebugView<T>
+{    
+    internal sealed class _CollectionDebugProxy<T>
     {
     {
         // https://referencesource.microsoft.com/#mscorlib/system/collections/generic/debugview.cs,29
         // https://referencesource.microsoft.com/#mscorlib/system/collections/generic/debugview.cs,29
 
 
-        public _CollectionDebugView(ICollection<T> collection)
+        public _CollectionDebugProxy(ICollection<T> collection)
         {
         {
             _Collection = collection ?? throw new ArgumentNullException(nameof(collection));
             _Collection = collection ?? throw new ArgumentNullException(nameof(collection));
         }
         }
@@ -32,10 +29,9 @@ namespace SharpGLTF.Debug
         }
         }
     }
     }
 
 
-    [System.Diagnostics.DebuggerDisplay("BufferView[{_Value.LogicalIndex}] {_Value.Name} {_Value._target} Bytes:{_Value.Buffer1.Count}")]
-    internal sealed class _BufferDebugView
+    internal sealed class _BufferDebugProxy
     {
     {
-        public _BufferDebugView(Schema2.BufferView value) { _Value = value; }
+        public _BufferDebugProxy(Schema2.BufferView value) { _Value = value; }
 
 
         public int LogicalIndex => _Value.LogicalParent.LogicalBufferViews.IndexOfReference(_Value);
         public int LogicalIndex => _Value.LogicalParent.LogicalBufferViews.IndexOfReference(_Value);
 
 
@@ -50,4 +46,42 @@ namespace SharpGLTF.Debug
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         public Schema2.Accessor[] Accessors => _Value.FindAccessors().ToArray();
         public Schema2.Accessor[] Accessors => _Value.FindAccessors().ToArray();
     }
     }
+
+    internal sealed class _AccessorDebugProxy
+    {
+        public _AccessorDebugProxy(Schema2.Accessor value) { _Value = value; }
+
+        private readonly Schema2.Accessor _Value;
+
+        public String Identity => $"Accessor[{_Value.LogicalIndex}] {_Value.Name}";
+
+        public Schema2.BufferView Source => _Value.SourceBufferView;
+
+        public (Schema2.ElementType, Schema2.ComponentType, bool) Format => (_Value.Dimensions, _Value.Encoding, _Value.Normalized);
+
+        public Object[] Items
+        {
+            get
+            {
+                if (_Value.Dimensions == Schema2.ElementType.SCALAR) return _Value.AsScalarArray().Cast<Object>().ToArray();
+                if (_Value.Dimensions == Schema2.ElementType.VEC2) return _Value.AsVector2Array().Cast<Object>().ToArray();
+                if (_Value.Dimensions == Schema2.ElementType.VEC3) return _Value.AsVector3Array().Cast<Object>().ToArray();
+                if (_Value.Dimensions == Schema2.ElementType.VEC4) return _Value.AsVector4Array().Cast<Object>().ToArray();
+                if (_Value.Dimensions == Schema2.ElementType.MAT4) return _Value.AsMatrix4x4Array().Cast<Object>().ToArray();
+
+                var itemSize = _Value.ItemByteSize;
+                var byteStride = Math.Max(_Value.SourceBufferView.ByteStride, itemSize);
+                var items = new ArraySegment<Byte>[_Value.Count];
+
+                var buffer = _Value.SourceBufferView.Content.Slice(_Value.ByteOffset, _Value.Count * byteStride);
+
+                for (int i = 0; i < items.Length; ++i )
+                {
+                    items[i] = buffer.Slice(i * byteStride, itemSize);
+                }
+
+                return items.Cast<Object>().ToArray();
+            }
+        }
+    }
 }
 }

+ 7 - 0
src/SharpGLTF.DOM/Geometry/MemoryAccessor.cs

@@ -225,6 +225,13 @@ namespace SharpGLTF.Geometry
             return new Vector4Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
             return new Vector4Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
         }
         }
 
 
+        public QuaternionArray AsQuaternionArray()
+        {
+            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
+            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.VEC4, nameof(Attribute));
+            return new QuaternionArray(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+        }
+
         public Matrix4x4Array AsMatrix4x4Array()
         public Matrix4x4Array AsMatrix4x4Array()
         {
         {
             Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
             Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));

+ 0 - 7
src/SharpGLTF.DOM/Memory/Arrays.cs

@@ -8,7 +8,6 @@ using System.Text;
 namespace SharpGLTF.Memory
 namespace SharpGLTF.Memory
 {
 {
     public interface IEncodedArray<T> : IReadOnlyCollection<T>
     public interface IEncodedArray<T> : IReadOnlyCollection<T>
-        where T : unmanaged
     {
     {
         T this[int index] { get; set; }
         T this[int index] { get; set; }
 
 
@@ -20,7 +19,6 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     struct EncodedArrayEnumerator<T> : IEnumerator<T>
     struct EncodedArrayEnumerator<T> : IEnumerator<T>
-        where T : unmanaged
     {
     {
         #region lifecycle
         #region lifecycle
 
 
@@ -91,7 +89,6 @@ namespace SharpGLTF.Memory
         }
         }
 
 
         public static void FillFrom<T>(this IEncodedArray<T> dst, int dstIndex, IEnumerable<T> src)
         public static void FillFrom<T>(this IEncodedArray<T> dst, int dstIndex, IEnumerable<T> src)
-            where T : unmanaged
         {
         {
             using (var ator = src.GetEnumerator())
             using (var ator = src.GetEnumerator())
             {
             {
@@ -112,7 +109,6 @@ namespace SharpGLTF.Memory
         }
         }
 
 
         public static void CopyTo<T>(IEncodedArray<T> src, IEncodedArray<T> dst, int dstOffset = 0)
         public static void CopyTo<T>(IEncodedArray<T> src, IEncodedArray<T> dst, int dstOffset = 0)
-            where T : unmanaged
         {
         {
             for (int i = 0; i < src.Count; ++i)
             for (int i = 0; i < src.Count; ++i)
             {
             {
@@ -121,7 +117,6 @@ namespace SharpGLTF.Memory
         }
         }
 
 
         public static void CopyTo<T>(T[] src, IEncodedArray<T> dst, int dstOffset = 0)
         public static void CopyTo<T>(T[] src, IEncodedArray<T> dst, int dstOffset = 0)
-            where T : unmanaged
         {
         {
             for (int i = 0; i < src.Length; ++i)
             for (int i = 0; i < src.Length; ++i)
             {
             {
@@ -130,13 +125,11 @@ namespace SharpGLTF.Memory
         }
         }
 
 
         public static void Copy<T>(IEncodedArray<T> src, T[] dst)
         public static void Copy<T>(IEncodedArray<T> src, T[] dst)
-            where T : unmanaged
         {
         {
             Copy<T>(src, new ArraySegment<T>(dst));
             Copy<T>(src, new ArraySegment<T>(dst));
         }
         }
 
 
         public static void Copy<T>(IEncodedArray<T> src, ArraySegment<T> dst)
         public static void Copy<T>(IEncodedArray<T> src, ArraySegment<T> dst)
-            where T : unmanaged
         {
         {
             var c = src.Count;
             var c = src.Count;
             for (int i = 0; i < c; ++i) dst.Array[dst.Offset + i] = src[i];
             for (int i = 0; i < c; ++i) dst.Array[dst.Offset + i] = src[i];

+ 125 - 61
src/SharpGLTF.DOM/Memory/FloatingArrays.cs

@@ -240,9 +240,9 @@ namespace SharpGLTF.Memory
         /// </param>
         /// </param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="normalized">True if values are normalized.</param>
         /// <param name="normalized">True if values are normalized.</param>
-        public ScalarArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public ScalarArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 1, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 1, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -250,7 +250,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Single[] _DebugItems => this.ToArray();
         private Single[] _DebugItems => this.ToArray();
@@ -260,12 +260,12 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Single this[int index]
         public Single this[int index]
         {
         {
-            get => _Accesor[index, 0];
-            set => _Accesor[index, 0] = value;
+            get => _Accessor[index, 0];
+            set => _Accessor[index, 0] = value;
         }
         }
 
 
         public void CopyTo(ArraySegment<Single> dst) { EncodedArrayUtils.Copy<Single>(this, dst); }
         public void CopyTo(ArraySegment<Single> dst) { EncodedArrayUtils.Copy<Single>(this, dst); }
@@ -292,9 +292,9 @@ namespace SharpGLTF.Memory
         public Vector2Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         public Vector2Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
             : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
             : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public Vector2Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Vector2Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 2, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 2, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -302,7 +302,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Vector2[] _DebugItems => this.ToArray();
         private Vector2[] _DebugItems => this.ToArray();
@@ -312,19 +312,19 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Vector2 this[int index]
         public Vector2 this[int index]
         {
         {
             get
             get
             {
             {
-                return new Vector2(_Accesor[index, 0], _Accesor[index, 1]);
+                return new Vector2(_Accessor[index, 0], _Accessor[index, 1]);
             }
             }
 
 
             set
             set
             {
             {
-                _Accesor[index, 0] = value.X;
-                _Accesor[index, 1] = value.Y;
+                _Accessor[index, 0] = value.X;
+                _Accessor[index, 1] = value.Y;
             }
             }
         }
         }
 
 
@@ -374,9 +374,9 @@ namespace SharpGLTF.Memory
         /// </param>
         /// </param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
         /// <param name="normalized">True if values are normalized.</param>
         /// <param name="normalized">True if values are normalized.</param>
-        public Vector3Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Vector3Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 3, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 3, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -384,7 +384,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Vector3[] _DebugItems => this.ToArray();
         private Vector3[] _DebugItems => this.ToArray();
@@ -394,20 +394,20 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Vector3 this[int index]
         public Vector3 this[int index]
         {
         {
             get
             get
             {
             {
-                return new Vector3(_Accesor[index, 0], _Accesor[index, 1], _Accesor[index, 2]);
+                return new Vector3(_Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2]);
             }
             }
 
 
             set
             set
             {
             {
-                _Accesor[index, 0] = value.X;
-                _Accesor[index, 1] = value.Y;
-                _Accesor[index, 2] = value.Z;
+                _Accessor[index, 0] = value.X;
+                _Accessor[index, 1] = value.Y;
+                _Accessor[index, 2] = value.Z;
             }
             }
         }
         }
 
 
@@ -435,9 +435,9 @@ namespace SharpGLTF.Memory
         public Vector4Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         public Vector4Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
             : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
             : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public Vector4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Vector4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -445,7 +445,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Vector4[] _DebugItems => this.ToArray();
         private Vector4[] _DebugItems => this.ToArray();
@@ -455,21 +455,21 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Vector4 this[int index]
         public Vector4 this[int index]
         {
         {
             get
             get
             {
             {
-                return new Vector4(_Accesor[index, 0], _Accesor[index, 1], _Accesor[index, 2], _Accesor[index, 3]);
+                return new Vector4(_Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2], _Accessor[index, 3]);
             }
             }
 
 
             set
             set
             {
             {
-                _Accesor[index, 0] = value.X;
-                _Accesor[index, 1] = value.Y;
-                _Accesor[index, 2] = value.Z;
-                _Accesor[index, 3] = value.W;
+                _Accessor[index, 0] = value.X;
+                _Accessor[index, 1] = value.Y;
+                _Accessor[index, 2] = value.Z;
+                _Accessor[index, 3] = value.W;
             }
             }
         }
         }
 
 
@@ -499,7 +499,7 @@ namespace SharpGLTF.Memory
 
 
         public QuaternionArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         public QuaternionArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -507,7 +507,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Quaternion[] _DebugItems => this.ToArray();
         private Quaternion[] _DebugItems => this.ToArray();
@@ -517,21 +517,21 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Quaternion this[int index]
         public Quaternion this[int index]
         {
         {
             get
             get
             {
             {
-                return new Quaternion(_Accesor[index, 0], _Accesor[index, 1], _Accesor[index, 2], _Accesor[index, 3]);
+                return new Quaternion(_Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2], _Accessor[index, 3]);
             }
             }
 
 
             set
             set
             {
             {
-                _Accesor[index, 0] = value.X;
-                _Accesor[index, 1] = value.Y;
-                _Accesor[index, 2] = value.Z;
-                _Accesor[index, 3] = value.W;
+                _Accessor[index, 0] = value.X;
+                _Accessor[index, 1] = value.Y;
+                _Accessor[index, 2] = value.Z;
+                _Accessor[index, 3] = value.W;
             }
             }
         }
         }
 
 
@@ -561,7 +561,7 @@ namespace SharpGLTF.Memory
 
 
         public Matrix4x4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         public Matrix4x4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 16, encoding, normalized);
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 16, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -569,7 +569,7 @@ namespace SharpGLTF.Memory
         #region data
         #region data
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private FloatingAccessor _Accesor;
+        private FloatingAccessor _Accessor;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private Matrix4x4[] _DebugItems => this.ToArray();
         private Matrix4x4[] _DebugItems => this.ToArray();
@@ -579,7 +579,7 @@ namespace SharpGLTF.Memory
         #region API
         #region API
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _Accesor.Count;
+        public int Count => _Accessor.Count;
 
 
         public Matrix4x4 this[int index]
         public Matrix4x4 this[int index]
         {
         {
@@ -587,31 +587,31 @@ namespace SharpGLTF.Memory
             {
             {
                 return new Matrix4x4
                 return new Matrix4x4
                     (
                     (
-                    _Accesor[index, 0], _Accesor[index, 1], _Accesor[index, 2], _Accesor[index, 3],
-                    _Accesor[index, 4], _Accesor[index, 5], _Accesor[index, 6], _Accesor[index, 7],
-                    _Accesor[index, 8], _Accesor[index, 9], _Accesor[index, 10], _Accesor[index, 11],
-                    _Accesor[index, 12], _Accesor[index, 13], _Accesor[index, 14], _Accesor[index, 15]
+                    _Accessor[index, 0], _Accessor[index, 1], _Accessor[index, 2], _Accessor[index, 3],
+                    _Accessor[index, 4], _Accessor[index, 5], _Accessor[index, 6], _Accessor[index, 7],
+                    _Accessor[index, 8], _Accessor[index, 9], _Accessor[index, 10], _Accessor[index, 11],
+                    _Accessor[index, 12], _Accessor[index, 13], _Accessor[index, 14], _Accessor[index, 15]
                     );
                     );
             }
             }
 
 
             set
             set
             {
             {
-                _Accesor[index, 0] = value.M11;
-                _Accesor[index, 1] = value.M12;
-                _Accesor[index, 2] = value.M13;
-                _Accesor[index, 3] = value.M14;
-                _Accesor[index, 4] = value.M21;
-                _Accesor[index, 5] = value.M22;
-                _Accesor[index, 6] = value.M23;
-                _Accesor[index, 7] = value.M24;
-                _Accesor[index, 8] = value.M31;
-                _Accesor[index, 9] = value.M32;
-                _Accesor[index, 10] = value.M33;
-                _Accesor[index, 11] = value.M34;
-                _Accesor[index, 12] = value.M41;
-                _Accesor[index, 13] = value.M42;
-                _Accesor[index, 14] = value.M43;
-                _Accesor[index, 15] = value.M44;
+                _Accessor[index, 0] = value.M11;
+                _Accessor[index, 1] = value.M12;
+                _Accessor[index, 2] = value.M13;
+                _Accessor[index, 3] = value.M14;
+                _Accessor[index, 4] = value.M21;
+                _Accessor[index, 5] = value.M22;
+                _Accessor[index, 6] = value.M23;
+                _Accessor[index, 7] = value.M24;
+                _Accessor[index, 8] = value.M31;
+                _Accessor[index, 9] = value.M32;
+                _Accessor[index, 10] = value.M33;
+                _Accessor[index, 11] = value.M34;
+                _Accessor[index, 12] = value.M41;
+                _Accessor[index, 13] = value.M42;
+                _Accessor[index, 14] = value.M43;
+                _Accessor[index, 15] = value.M44;
             }
             }
         }
         }
 
 
@@ -625,4 +625,68 @@ namespace SharpGLTF.Memory
 
 
         #endregion
         #endregion
     }
     }
+
+    public struct MultiArray : IEncodedArray<Single[]>
+    {
+        #region constructors
+
+        public MultiArray(BYTES source, int byteOffset, int itemsCount, int byteStride, int dimensions, ENCODING encoding, Boolean normalized)
+        {
+            _Dimensions = dimensions;
+            _Accessor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, dimensions, encoding, normalized);
+        }
+
+        #endregion
+
+        #region data
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private readonly int _Dimensions;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private FloatingAccessor _Accessor;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
+        private Single[][] _DebugItems => this.ToArray();
+
+        #endregion
+
+        #region API
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        public int Count => _Accessor.Count;
+
+        public Single[] this[int index]
+        {
+            get
+            {
+                var val = new Single[_Dimensions];
+
+                for (int i = 0; i < val.Length; ++i) val[i] = _Accessor[index, i];
+
+                return val;
+            }
+
+            set
+            {
+                Guard.NotNull(value, nameof(value));
+                Guard.IsTrue(value.Length == _Dimensions, nameof(value));
+
+                for (int i = 0; i < _Dimensions; ++i)
+                {
+                    _Accessor[index, i] = value[i];
+                }
+            }
+        }
+
+        public void CopyTo(ArraySegment<Single[]> dst) { EncodedArrayUtils.Copy<Single[]>(this, dst); }
+
+        public IEnumerator<Single[]> GetEnumerator() { return new EncodedArrayEnumerator<Single[]>(this); }
+
+        IEnumerator IEnumerable.GetEnumerator() { return new EncodedArrayEnumerator<Single[]>(this); }
+
+        public (Single[], Single[]) GetBounds() { throw new NotImplementedException(); }
+
+        #endregion
+    }
 }
 }

+ 72 - 32
src/SharpGLTF.DOM/Schema2/gltf.Accessors.cs

@@ -6,6 +6,8 @@ using System.Text;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
+    using Memory;
+
     using EXCEPTION = IO.ModelException;
     using EXCEPTION = IO.ModelException;
 
 
     using ROOT = ModelRoot;
     using ROOT = ModelRoot;
@@ -13,6 +15,7 @@ namespace SharpGLTF.Schema2
     // https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
     // https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
 
 
     [System.Diagnostics.DebuggerDisplay("Accessor[{LogicalIndex}] BufferView[{SourceBufferView.LogicalIndex}][{ByteOffset}...] => 0 => {Dimensions}x{Encoding}x{Normalized} => [{Count}]")]
     [System.Diagnostics.DebuggerDisplay("Accessor[{LogicalIndex}] BufferView[{SourceBufferView.LogicalIndex}][{ByteOffset}...] => 0 => {Dimensions}x{Encoding}x{Normalized} => [{Count}]")]
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._AccessorDebugProxy))]
     public sealed partial class Accessor
     public sealed partial class Accessor
     {
     {
         #region debug
         #region debug
@@ -133,18 +136,26 @@ namespace SharpGLTF.Schema2
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
 
 
-            var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithIndexData(buffer, byteOffset, items.Count, encoding);
+            WithIndexData(buffer, byteOffset, items.Count, encoding)
+                .AsIndicesArray()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<UInt32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<UInt32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
 
 
-            var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithIndexData(buffer, byteOffset, items.Count, encoding);
+            WithIndexData(buffer, byteOffset, items.Count, encoding)
+                .AsIndicesArray()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
         public Accessor WithIndexData(BufferView buffer, int byteOffset, int itemCount, IndexType encoding)
         public Accessor WithIndexData(BufferView buffer, int byteOffset, int itemCount, IndexType encoding)
@@ -175,57 +186,77 @@ namespace SharpGLTF.Schema2
             return this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
             return this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Single> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, IReadOnlyList<Single> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustBePositiveAndMultipleOf(ElementType.SCALAR.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
             Guard.MustBePositiveAndMultipleOf(ElementType.SCALAR.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            var array = new Memory.ScalarArray(buffer.Content.Slice(byteOffset), buffer.ByteStride);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithVertexData(buffer, byteOffset, items.Count, ElementType.SCALAR, encoding, normalized);
+            WithVertexData(buffer, bufferByteOffset, items.Count, ElementType.SCALAR, encoding, normalized)
+                .AsScalarArray()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector2> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, IReadOnlyList<Vector2> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC2.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC2.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            var array = new Memory.Vector2Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC2, encoding, normalized);
+            WithVertexData(buffer, bufferByteOffset, items.Count, ElementType.VEC2, encoding, normalized)
+                .AsVector2Array()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector3> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, IReadOnlyList<Vector3> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC3.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC3.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            var array = new Memory.Vector3Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC3, encoding, normalized);
+            WithVertexData(buffer, bufferByteOffset, items.Count, ElementType.VEC3, encoding, normalized)
+                .AsVector3Array()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector4> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, IReadOnlyList<Vector4> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            var array = new Memory.Vector4Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
+            WithVertexData(buffer, bufferByteOffset, items.Count, ElementType.VEC4, encoding, normalized)
+                .AsVector4Array()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Quaternion> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, IReadOnlyList<Quaternion> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
             Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            var array = new Memory.QuaternionArray(buffer.Content.Slice(byteOffset), buffer.ByteStride);
-            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
-            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
+            WithVertexData(buffer, bufferByteOffset, items.Count, ElementType.VEC4, encoding, normalized)
+                .AsQuaternionArray()
+                .FillFrom(0, items);
+
+            this.UpdateBounds();
+
+            return this;
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int bufferByteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
@@ -233,10 +264,10 @@ namespace SharpGLTF.Schema2
 
 
             if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ARRAY_BUFFER, nameof(buffer));
             if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ARRAY_BUFFER, nameof(buffer));
 
 
-            return WithData(buffer, byteOffset, itemCount, dimensions, encoding, normalized);
+            return WithData(buffer, bufferByteOffset, itemCount, dimensions, encoding, normalized);
         }
         }
 
 
-        public Memory.IEncodedArray<Single> AsScalarArray()
+        public IEncodedArray<float> AsScalarArray()
         {
         {
             var memory = _GetMemoryAccessor();
             var memory = _GetMemoryAccessor();
 
 
@@ -246,7 +277,7 @@ namespace SharpGLTF.Schema2
             return Geometry.MemoryAccessor.CreateScalarSparseArray(memory, sparseKV.Key, sparseKV.Value);
             return Geometry.MemoryAccessor.CreateScalarSparseArray(memory, sparseKV.Key, sparseKV.Value);
         }
         }
 
 
-        public Memory.IEncodedArray<Vector2> AsVector2Array()
+        public IEncodedArray<Vector2> AsVector2Array()
         {
         {
             var memory = _GetMemoryAccessor();
             var memory = _GetMemoryAccessor();
 
 
@@ -256,7 +287,7 @@ namespace SharpGLTF.Schema2
             return Geometry.MemoryAccessor.CreateVector2SparseArray(memory, sparseKV.Key, sparseKV.Value);
             return Geometry.MemoryAccessor.CreateVector2SparseArray(memory, sparseKV.Key, sparseKV.Value);
         }
         }
 
 
-        public Memory.IEncodedArray<Vector3> AsVector3Array()
+        public IEncodedArray<Vector3> AsVector3Array()
         {
         {
             var memory = _GetMemoryAccessor();
             var memory = _GetMemoryAccessor();
 
 
@@ -266,7 +297,7 @@ namespace SharpGLTF.Schema2
             return Geometry.MemoryAccessor.CreateVector3SparseArray(memory, sparseKV.Key, sparseKV.Value);
             return Geometry.MemoryAccessor.CreateVector3SparseArray(memory, sparseKV.Key, sparseKV.Value);
         }
         }
 
 
-        public Memory.IEncodedArray<Vector4> AsVector4Array()
+        public IEncodedArray<Vector4> AsVector4Array()
         {
         {
             var memory = _GetMemoryAccessor();
             var memory = _GetMemoryAccessor();
 
 
@@ -276,6 +307,15 @@ namespace SharpGLTF.Schema2
             return Geometry.MemoryAccessor.CreateVector4SparseArray(memory, sparseKV.Key, sparseKV.Value);
             return Geometry.MemoryAccessor.CreateVector4SparseArray(memory, sparseKV.Key, sparseKV.Value);
         }
         }
 
 
+        public IEncodedArray<Quaternion> AsQuaternionArray()
+        {
+            var memory = _GetMemoryAccessor();
+
+            if (this._sparse == null) return memory.AsQuaternionArray();
+
+            throw new NotImplementedException();
+        }
+
         public ArraySegment<Byte> TryGetVertexBytes(int vertexIdx)
         public ArraySegment<Byte> TryGetVertexBytes(int vertexIdx)
         {
         {
             if (_sparse != null) throw new InvalidOperationException("Can't be used on Acessors with Sparse Data");
             if (_sparse != null) throw new InvalidOperationException("Can't be used on Acessors with Sparse Data");

+ 247 - 46
src/SharpGLTF.DOM/Schema2/gltf.Animations.cs

@@ -2,11 +2,11 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Text;
 using System.Text;
 using System.Linq;
 using System.Linq;
+using System.Numerics;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
     using Collections;
     using Collections;
-    using System.Numerics;
 
 
     [System.Diagnostics.DebuggerDisplay("Animation[{LogicalIndex}] {Name}")]
     [System.Diagnostics.DebuggerDisplay("Animation[{LogicalIndex}] {Name}")]
     public sealed partial class Animation
     public sealed partial class Animation
@@ -30,71 +30,119 @@ namespace SharpGLTF.Schema2
 
 
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
 
 
-        /// <summary>
-        /// Gets the list of <see cref="AnimationChannel"/> instances.
-        /// </summary>
-        public IReadOnlyList<AnimationChannel> Channels => _channels;
+        internal IReadOnlyList<AnimationChannel> _Channels => _channels;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        public Accessor CreateInputAccessor(IReadOnlyList<Single> input)
+        private AnimationSampler _CreateSampler(AnimationInterpolationMode interpolation)
         {
         {
-            var buffer = LogicalParent.UseBufferView(new Byte[input.Count * 4]);
-            var accessor = LogicalParent.CreateAccessor("Animation.Input")
-                .WithData(buffer, 0, input.Count, ElementType.SCALAR, ComponentType.FLOAT, false);
+            var sampler = new AnimationSampler(interpolation);
 
 
-            Memory.EncodedArrayUtils.FillFrom(accessor.AsScalarArray(), 0, input);
+            _samplers.Add(sampler);
 
 
-            accessor.UpdateBounds();
+            return sampler;
+        }
 
 
-            return accessor;
+        /// <remarks>
+        /// There can only be one <see cref="AnimationChannel"/> for every node and path
+        /// </remarks>
+        private AnimationChannel _UseChannel(Node node, PathType path)
+        {
+            Guard.MustShareLogicalParent(this, node, nameof(node));
+
+            var channel = _channels.FirstOrDefault(item => item.TargetNode == node && item.TargetNodePath == path);
+            if (channel != null) return channel;
+
+            channel = new AnimationChannel(node, path);
+
+            _channels.Add(channel);
+
+            return channel;
         }
         }
 
 
-        public Accessor CreateOutputAccessor(IReadOnlyList<Vector3> output)
+        public void CreateScaleChannel(Node node, IReadOnlyDictionary<Single, Vector3> keyframes, bool linear = true)
         {
         {
-            var buffer = LogicalParent.UseBufferView(new Byte[output.Count * 4 * 3]);
-            var accessor = LogicalParent.CreateAccessor("Animation.Output")
-                .WithData(buffer, 0, output.Count, ElementType.VEC3, ComponentType.FLOAT, false);
+            var sampler = this._CreateSampler(linear ? AnimationInterpolationMode.LINEAR : AnimationInterpolationMode.STEP)
+                .WithVector3Keys(keyframes);
 
 
-            Memory.EncodedArrayUtils.FillFrom(accessor.AsVector3Array(), 0, output);
+            this._UseChannel(node, PathType.scale)
+                .WithSampler(sampler);
+        }
 
 
-            accessor.UpdateBounds();
+        public void CreateScaleChannel(Node node, IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
+        {
+            var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE)
+                .WithVector3Keys(keyframes);
 
 
-            return accessor;
+            this._UseChannel(node, PathType.scale)
+                .WithSampler(sampler);
         }
         }
 
 
-        /// <summary>
-        /// Creates a new <see cref="AnimationSampler"/> instance and adds it to this <see cref="Animation"/>.
-        /// </summary>
-        /// <param name="input">An <see cref="Accessor"/> containing input (TIME) values.</param>
-        /// <param name="output">An <see cref="Accessor"/> containing output (TRS) values.</param>
-        /// <param name="interpolation">how the output values are interpolated.</param>
-        /// <returns>An <see cref="AnimationSampler"/> instance.</returns>
-        public AnimationSampler CreateSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
+        public void CreateRotationChannel(Node node, IReadOnlyDictionary<Single, Quaternion> keyframes, bool linear = true)
         {
         {
-            Guard.MustShareLogicalParent(this, input, nameof(input));
-            Guard.MustShareLogicalParent(this, output, nameof(output));
+            var sampler = this._CreateSampler(linear ? AnimationInterpolationMode.LINEAR : AnimationInterpolationMode.STEP)
+                .WithQuaternionKeys(keyframes);
 
 
-            var sampler = new AnimationSampler(input, output, interpolation);
+            this._UseChannel(node, PathType.rotation)
+                .WithSampler(sampler);
+        }
 
 
-            _samplers.Add(sampler);
+        public void CreateRotationChannel(Node node, IReadOnlyDictionary<Single, (Quaternion, Quaternion, Quaternion)> keyframes)
+        {
+            var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE)
+                .WithQuaternionKeys(keyframes);
 
 
-            return sampler;
+            this._UseChannel(node, PathType.rotation)
+                .WithSampler(sampler);
         }
         }
 
 
-        public AnimationChannel CreateChannel(Node node, PathType path, AnimationSampler sampler)
+        public void CreateTranslationChannel(Node node, IReadOnlyDictionary<Single, Vector3> keyframes, bool linear = true)
         {
         {
-            Guard.MustShareLogicalParent(this, node, nameof(node));
-            Guard.NotNull(sampler, nameof(sampler));
-            Guard.IsTrue(Object.ReferenceEquals(this, sampler.LogicalParent), nameof(sampler));
+            var sampler = this._CreateSampler(linear ? AnimationInterpolationMode.LINEAR : AnimationInterpolationMode.STEP)
+                .WithVector3Keys(keyframes);
 
 
-            var channel = new AnimationChannel(node, path, sampler);
+            this._UseChannel(node, PathType.translation)
+                .WithSampler(sampler);
+        }
 
 
-            _channels.Add(channel);
+        public void CreateTranslationChannel(Node node, IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
+        {
+            var sampler = this._CreateSampler(AnimationInterpolationMode.CUBICSPLINE)
+                .WithVector3Keys(keyframes);
 
 
-            return channel;
+            this._UseChannel(node, PathType.translation)
+                .WithSampler(sampler);
+        }
+
+        public void CreateMorphChannel(Node node, AnimationInterpolationMode mode, IReadOnlyDictionary<Single, Single[]> keyframes)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IReadOnlyDictionary<Single, Vector3> FindScaleChannel(Node node)
+        {
+            var channel = _channels.FirstOrDefault(item => item.TargetNode == node && item.TargetNodePath == PathType.scale);
+            if (channel == null) return null;
+
+            return channel.Sampler.AsVector3KeyFrames();
+        }
+
+        public IReadOnlyDictionary<Single, Quaternion> FindRotationChannel(Node node)
+        {
+            var channel = _channels.FirstOrDefault(item => item.TargetNode == node && item.TargetNodePath == PathType.rotation);
+            if (channel == null) return null;
+
+            return channel.Sampler.AsQuaternionKeyFrames();
+        }
+
+        public IReadOnlyDictionary<Single, Vector3> FindTranslationChannel(Node node)
+        {
+            var channel = _channels.FirstOrDefault(item => item.TargetNode == node && item.TargetNodePath == PathType.translation);
+            if (channel == null) return null;
+
+            return channel.Sampler.AsVector3KeyFrames();
         }
         }
 
 
         #endregion
         #endregion
@@ -123,16 +171,26 @@ namespace SharpGLTF.Schema2
         #endregion
         #endregion
     }
     }
 
 
-    public sealed partial class AnimationChannel : IChildOf<Animation>
+    sealed partial class AnimationChannel : IChildOf<Animation>
     {
     {
         #region lifecycle
         #region lifecycle
 
 
         internal AnimationChannel() { }
         internal AnimationChannel() { }
 
 
-        internal AnimationChannel(Node targetNode, PathType targetPath, AnimationSampler sampler)
+        internal AnimationChannel(Node targetNode, PathType targetPath)
         {
         {
             _target = new AnimationChannelTarget(targetNode, targetPath);
             _target = new AnimationChannelTarget(targetNode, targetPath);
+            _sampler = -1;
+        }
+
+        internal AnimationChannel WithSampler(AnimationSampler sampler)
+        {
+            Guard.NotNull(sampler, nameof(sampler));
+            Guard.IsTrue(this.LogicalParent == sampler.LogicalParent, nameof(sampler));
+
             _sampler = sampler.LogicalIndex;
             _sampler = sampler.LogicalIndex;
+
+            return this;
         }
         }
 
 
         #endregion
         #endregion
@@ -181,17 +239,15 @@ namespace SharpGLTF.Schema2
         #endregion
         #endregion
     }
     }
 
 
-    public sealed partial class AnimationSampler : IChildOf<Animation>
+    sealed partial class AnimationSampler : IChildOf<Animation>
     {
     {
         #region lifecycle
         #region lifecycle
 
 
         internal AnimationSampler() { }
         internal AnimationSampler() { }
 
 
-        internal AnimationSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
+        internal AnimationSampler(AnimationInterpolationMode interpolation)
         {
         {
             _interpolation = interpolation.AsNullable(_interpolationDefault);
             _interpolation = interpolation.AsNullable(_interpolationDefault);
-            _input = input.LogicalIndex;
-            _output = output.LogicalIndex;
         }
         }
 
 
         #endregion
         #endregion
@@ -210,7 +266,7 @@ namespace SharpGLTF.Schema2
 
 
         void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
         void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
 
 
-        public AnimationInterpolationMode Mode
+        public AnimationInterpolationMode InterpolationMode
         {
         {
             get => _interpolation.AsValue(_interpolationDefault);
             get => _interpolation.AsValue(_interpolationDefault);
             set => _interpolation = value.AsNullable(_interpolationDefault);
             set => _interpolation = value.AsNullable(_interpolationDefault);
@@ -221,6 +277,151 @@ namespace SharpGLTF.Schema2
         public Accessor Output => this.LogicalParent.LogicalParent.LogicalAccessors[this._output];
         public Accessor Output => this.LogicalParent.LogicalParent.LogicalAccessors[this._output];
 
 
         #endregion
         #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")
+                .WithData(buffer, 0, input.Count, ElementType.SCALAR, ComponentType.FLOAT, false);
+
+            Memory.EncodedArrayUtils.FillFrom(accessor.AsScalarArray(), 0, input);
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private Accessor _CreateOutputAccessor(IReadOnlyList<Vector3> output)
+        {
+            var root = LogicalParent.LogicalParent;
+
+            var buffer = root.UseBufferView(new Byte[output.Count * 4 * 3]);
+            var accessor = root.CreateAccessor("Animation.Output")
+                .WithData(buffer, 0, output.Count, ElementType.VEC3, ComponentType.FLOAT, false);
+
+            Memory.EncodedArrayUtils.FillFrom(accessor.AsVector3Array(), 0, output);
+
+            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")
+                .WithData(buffer, 0, output.Count, ElementType.VEC4, ComponentType.FLOAT, false);
+
+            Memory.EncodedArrayUtils.FillFrom(accessor.AsQuaternionArray(), 0, output);
+
+            accessor.UpdateBounds();
+
+            return accessor;
+        }
+
+        private static (Single[], TValue[]) _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[], TValue[]) _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);
+        }
+
+        public AnimationSampler WithVector3Keys(IReadOnlyDictionary<Single, Vector3> keyframes)
+        {
+            var kv = _Split(keyframes);
+            _input = this._CreateInputAccessor(kv.Item1).LogicalIndex;
+            _output = this._CreateOutputAccessor(kv.Item2).LogicalIndex;
+
+            return this;
+        }
+
+        public AnimationSampler WithVector3Keys(IReadOnlyDictionary<Single, (Vector3, Vector3, Vector3)> keyframes)
+        {
+            var kv = _Split(keyframes);
+            _input = this._CreateInputAccessor(kv.Item1).LogicalIndex;
+            _output = this._CreateOutputAccessor(kv.Item2).LogicalIndex;
+
+            return this;
+        }
+
+        public AnimationSampler WithQuaternionKeys(IReadOnlyDictionary<Single, Quaternion> keyframes)
+        {
+            var kv = _Split(keyframes);
+            _input = this._CreateInputAccessor(kv.Item1).LogicalIndex;
+            _output = this._CreateOutputAccessor(kv.Item2).LogicalIndex;
+
+            return this;
+        }
+
+        public AnimationSampler WithQuaternionKeys(IReadOnlyDictionary<Single, (Quaternion, Quaternion, Quaternion)> keyframes)
+        {
+            var kv = _Split(keyframes);
+            _input = this._CreateInputAccessor(kv.Item1).LogicalIndex;
+            _output = this._CreateOutputAccessor(kv.Item2).LogicalIndex;
+
+            return this;
+        }
+
+        public IReadOnlyDictionary<Single, Vector3> AsVector3KeyFrames()
+        {
+            if (this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE) throw new ArgumentException();
+
+            var dict = new Dictionary<Single, Vector3>();
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsVector3Array();
+
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .ToDictionary(item => item.key, item => item.val);
+        }
+
+        public IReadOnlyDictionary<Single, Quaternion> AsQuaternionKeyFrames()
+        {
+            var dict = new Dictionary<Single, Quaternion>();
+
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsQuaternionArray();
+
+            return keys
+                .Zip(frames, (key, val) => (key, val))
+                .ToDictionary(item => item.key, item => item.val);
+        }
+
+        #endregion
     }
     }
 
 
     public sealed partial class ModelRoot
     public sealed partial class ModelRoot

+ 1 - 1
src/SharpGLTF.DOM/Schema2/gltf.BufferView.cs

@@ -10,7 +10,7 @@ namespace SharpGLTF.Schema2
 
 
     using ENCODING = ComponentType;
     using ENCODING = ComponentType;
 
 
-    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._BufferDebugView))]
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._BufferDebugProxy))]
     public sealed partial class BufferView
     public sealed partial class BufferView
     {
     {
         #region lifecycle
         #region lifecycle

+ 23 - 4
src/SharpGLTF.DOM/Schema2/gltf.Scene.cs

@@ -57,13 +57,32 @@ namespace SharpGLTF.Schema2
 
 
         #region properties - transform
         #region properties - transform
 
 
-        internal Matrix4x4? RawMatrix { get => _matrix; set => _matrix = value; }
+        public Node WithLocalTranslation(Vector3 translation)
+        {
+            var xform = this.LocalTransform;
+            xform.Translation = translation;
+            this.LocalTransform = xform;
+
+            return this;
+        }
+
+        public Node WithLocalRotation(Quaternion rotation)
+        {
+            var xform = this.LocalTransform;
+            xform.Rotation = rotation;
+            this.LocalTransform = xform;
 
 
-        internal Quaternion? RawRotation { get => _rotation; set => _rotation = value; }
+            return this;
+        }
 
 
-        internal Vector3? RawTranslation { get => _translation; set => _translation = value; }
+        public Node WithLocalScale(Vector3 scale)
+        {
+            var xform = this.LocalTransform;
+            xform.Scale = scale;
+            this.LocalTransform = xform;
 
 
-        internal Vector3? RawScale  { get => _scale; set => _scale = value; }
+            return this;
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="Node"/>.

+ 18 - 0
src/SharpGLTF.DOM/Schema2/gltf.Skin.cs

@@ -139,8 +139,26 @@ namespace SharpGLTF.Schema2
             return true;
             return true;
         }
         }
 
 
+        public void BindJoints(params Node[] joints)
+        {
+            var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
+
+            for (int i = 0; i < pairs.Length; ++i)
+            {
+                var xform = joints[i].WorldMatrix;
+
+                Matrix4x4.Invert(xform, out Matrix4x4 ixform);
+
+                pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], ixform);
+            }
+
+            BindJoints(pairs);
+        }
+
         public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
         public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
         {
         {
+            foreach (var j in joints) Guard.MustShareLogicalParent(this, j.Key, nameof(joints));
+
             // inverse bind matrices accessor
             // inverse bind matrices accessor
 
 
             var data = new Byte[joints.Length * 16 * 4];
             var data = new Byte[joints.Length * 16 * 4];

+ 37 - 0
tests/SharpGLTF.Tests/Memory/MemoryArrayTests.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Numerics;
 using System.Text;
 using System.Text;
 
 
 using NUnit.Framework;
 using NUnit.Framework;
@@ -47,5 +48,41 @@ namespace SharpGLTF.Memory
 
 
             Assert.AreEqual(3, result.Length);
             Assert.AreEqual(3, result.Length);
         }
         }
+
+        [Test]
+        public void FillEncoded1()
+        {
+            var v1 = new Vector4(0.1f, 0.2f, 0.6f, 0.8f);
+            var v2 = new Vector4(1, 2, 3, 4);
+
+            var bytes = new Byte[256];
+
+            var v4n = new Vector4Array(bytes.Slice(0), 0, Schema2.ComponentType.UNSIGNED_BYTE, true);
+            v4n[1] = v1;
+            VectorAssert.AreEqual(v4n[1], v1, 0.1f);
+
+            var v4u = new Vector4Array(bytes.Slice(0), 0, Schema2.ComponentType.UNSIGNED_BYTE, false);
+            v4u[1] = v2;
+            VectorAssert.AreEqual(v4u[1], v2);
+        }
+
+        [Test]
+        public void FillEncoded2()
+        {
+            var v1 = new Vector4(0.1f, 0.2f, 0.6f, 0.8f);
+            var v2 = new Vector4(1, 2, 3, 4);
+
+            var bytes = new Byte[256];
+
+            var v4n = new Vector4Array(bytes.Slice(0), 0, 5, 8, Schema2.ComponentType.UNSIGNED_BYTE, true);
+            var v4u = new Vector4Array(bytes.Slice(0), 4, 5, 8, Schema2.ComponentType.UNSIGNED_BYTE, false);
+
+            v4n[1] = v1;
+            VectorAssert.AreEqual(v4n[1], v1, 0.1f);
+
+            
+            v4u[1] = v2;
+            VectorAssert.AreEqual(v4u[1], v2);
+        }
     }
     }
 }
 }

+ 120 - 20
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -170,9 +170,9 @@ namespace SharpGLTF.Schema2.Authoring
         }
         }
 
 
 
 
-        struct myVertex
+        struct mySimpleVertex
         {
         {
-            public myVertex(float px, float py, float pz, float nx, float ny, float nz)
+            public mySimpleVertex(float px, float py, float pz, float nx, float ny, float nz)
             {
             {
                 Position = new Vector3(px, py, pz);
                 Position = new Vector3(px, py, pz);
                 Normal = Vector3.Normalize(new Vector3(nx, ny, nz));
                 Normal = Vector3.Normalize(new Vector3(nx, ny, nz));
@@ -188,12 +188,12 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
 
 
-            var meshBuilder = new InterleavedMeshBuilder<myVertex, Vector4>();
+            var meshBuilder = new InterleavedMeshBuilder<mySimpleVertex, Vector4>();
 
 
-            var v1 = new myVertex(-10, 10, 0, -10, 10, 15);
-            var v2 = new myVertex( 10, 10, 0, 10, 10, 15);
-            var v3 = new myVertex( 10,-10, 0, 10, -10, 15);
-            var v4 = new myVertex(-10,-10, 0, -10, -10, 15);            
+            var v1 = new mySimpleVertex(-10, 10, 0, -10, 10, 15);
+            var v2 = new mySimpleVertex( 10, 10, 0, 10, 10, 15);
+            var v3 = new mySimpleVertex( 10,-10, 0, 10, -10, 15);
+            var v4 = new mySimpleVertex(-10,-10, 0, -10, -10, 15);            
             meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
             meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
 
 
             var model = ModelRoot.CreateModel();
             var model = ModelRoot.CreateModel();
@@ -221,12 +221,12 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
 
 
-            var meshBuilder = new InterleavedMeshBuilder<myVertex, Vector4>();
+            var meshBuilder = new InterleavedMeshBuilder<mySimpleVertex, Vector4>();
 
 
-            var v1 = new myVertex(-10, 10, 0, -10, 10, 15);
-            var v2 = new myVertex(10, 10, 0, 10, 10, 15);
-            var v3 = new myVertex(10, -10, 0, 10, -10, 15);
-            var v4 = new myVertex(-10, -10, 0, -10, -10, 15);
+            var v1 = new mySimpleVertex(-10, 10, 0, -10, 10, 15);
+            var v2 = new mySimpleVertex(10, 10, 0, 10, 10, 15);
+            var v3 = new mySimpleVertex(10, -10, 0, 10, -10, 15);
+            var v4 = new mySimpleVertex(-10, -10, 0, -10, -10, 15);
             meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
             meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
 
 
             var model = ModelRoot.CreateModel();
             var model = ModelRoot.CreateModel();
@@ -245,17 +245,117 @@ namespace SharpGLTF.Schema2.Authoring
             // fill our node with the mesh
             // fill our node with the mesh
             meshBuilder.CopyToNode(rnode, createMaterialForColor);
             meshBuilder.CopyToNode(rnode, createMaterialForColor);
 
 
+            // create animation sequence with 4 frames
+            var keyframes = new Dictionary<Single, Vector3>()
+            {
+                [1] = new Vector3(0, 0, 0),
+                [2] = new Vector3(50, 0, 0),
+                [3] = new Vector3(0, 50, 0),
+                [4] = new Vector3(0, 0, 0),
+            };
+
             var animation = model.CreateAnimation("Animation");
             var animation = model.CreateAnimation("Animation");
-            var asampler = animation.CreateSampler
-                (
-                animation.CreateInputAccessor( new float[] { 1, 2, 3, 4 } ),
-                animation.CreateOutputAccessor( new[] { new Vector3(0, 0, 0), new Vector3(50, 0, 0), new Vector3(0, 50, 0), new Vector3(0, 0, 0) } ),
-                AnimationInterpolationMode.LINEAR
-                );
-
-            animation.CreateChannel(rnode, PathType.translation, asampler);
+            animation.CreateTranslationChannel(rnode, keyframes);
             
             
 
 
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");
+        }
+
+        struct mySkinnedVertex
+        {
+            public mySkinnedVertex(float px, float py, float pz, int jointIndex)
+            {
+                Position = new Vector3(px, py, pz);
+                Joints_0 = new Vector4(jointIndex);
+                Weights_0 = Vector4.UnitX;
+            }
+
+            public mySkinnedVertex(float px, float py, float pz, int jointIndex1, int jointIndex2)
+            {
+                Position = new Vector3(px, py, pz);
+                Joints_0 = new Vector4(jointIndex1, jointIndex2,0,0);
+                Weights_0 = new Vector4(0.5f, 0.5f, 0, 0);
+            }
+
+            public Vector3 Position;            
+            public Vector4 Joints_0;
+            public Vector4 Weights_0;
+        }
+
+        [Test(Description = "Creates a skinned animated scene using a mesh builder helper class")]
+        public void CreateSkinnedAnimatedMeshBuilderScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            // create base model
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("Default");
+            var snode = scene.CreateNode("RootNode");
+
+            // create the three joints that will affect the mesh
+            var skelet = scene.CreateNode("Skeleton");
+            var jnode1 = skelet.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 0, 0));
+            var jnode2 = jnode1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0));
+            var jnode3 = jnode2.CreateNode("Joint 3").WithLocalTranslation(new Vector3(0, 40, 0));
+
+            // setup skin
+            snode.Skin = model.CreateSkin();
+            snode.Skin.Skeleton = skelet;
+            snode.Skin.BindJoints(jnode1, jnode2, jnode3);
+
+            // create the mesh
+            var meshBuilder = new InterleavedMeshBuilder<mySkinnedVertex, Vector4>();
+
+            var v1 = new mySkinnedVertex(-10, 0, +10, 0);
+            var v2 = new mySkinnedVertex(+10, 0, +10, 0);
+            var v3 = new mySkinnedVertex(+10, 0, -10, 0);
+            var v4 = new mySkinnedVertex(-10, 0, -10, 0);
+
+            var v5 = new mySkinnedVertex(-10, 40, +10, 0, 1);
+            var v6 = new mySkinnedVertex(+10, 40, +10, 0, 1);
+            var v7 = new mySkinnedVertex(+10, 40, -10, 0, 1);
+            var v8 = new mySkinnedVertex(-10, 40, -10, 0, 1);
+
+            var v9  = new mySkinnedVertex(-5, 80, +5, 2);
+            var v10 = new mySkinnedVertex(+5, 80, +5, 2);
+            var v11 = new mySkinnedVertex(+5, 80, -5, 2);
+            var v12 = new mySkinnedVertex(-5, 80, -5, 2);
+
+            meshBuilder.AddPolygon(new Vector4(1, 0, 1, 1), v1, v2, v6, v5);
+            meshBuilder.AddPolygon(new Vector4(1, 0, 1, 1), v2, v3, v7, v6);
+            meshBuilder.AddPolygon(new Vector4(1, 0, 1, 1), v3, v4, v8, v7);
+            meshBuilder.AddPolygon(new Vector4(1, 0, 1, 1), v4, v1, v5, v8);
+
+            meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v5, v6, v10, v9);
+            meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v6, v7, v11, v10);
+            meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v7, v8, v12, v11);
+            meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v8, v5, v9, v12);
+
+            // setup a lambda function that creates a material for a given color
+            Material createMaterialForColor(Vector4 color)
+            {
+                var material = model.CreateMaterial().WithDefault(color);
+                material.DoubleSided = true;
+                return material;
+            };
+
+            // fill our node with the mesh
+            meshBuilder.CopyToNode(snode, createMaterialForColor);
+
+            // create animation sequence with 4 frames
+            var keyframes = new Dictionary<Single, Quaternion>
+            {
+                [1] = Quaternion.Identity,
+                [2] = Quaternion.CreateFromYawPitchRoll(0, 1, 0),
+                [3] = Quaternion.CreateFromYawPitchRoll(0, 0, 1),
+                [4] = Quaternion.Identity,
+            };
+
+            model.CreateAnimation("Animation")
+                .CreateRotationChannel(jnode2, keyframes);
+
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.gltf");
             model.AttachToCurrentTest("result.gltf");
         }
         }

+ 1 - 2
tests/SharpGLTF.Tests/Schema2/Authoring/InterleavedMeshBuilder.cs

@@ -182,6 +182,5 @@ namespace SharpGLTF.Schema2.Authoring
         }
         }
 
 
         #endregion
         #endregion
-
-    }
+    }    
 }
 }

+ 20 - 0
tests/SharpGLTF.Tests/VectorAssert.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF
+{
+    public static class VectorAssert
+    {
+        public static void AreEqual(Vector4 a, Vector4 b, double delta = 0)
+        {
+            Assert.AreEqual(a.X, b.X, delta);
+            Assert.AreEqual(a.Y, b.Y, delta);
+            Assert.AreEqual(a.Z, b.Z, delta);
+            Assert.AreEqual(a.W, b.W, delta);
+        }
+    }
+}