Browse Source

Improved sparse accessors support.

Vicente Penades Armengot 5 months ago
parent
commit
0e9d4b0528

+ 5 - 0
src/SharpGLTF.Core/Memory/AttributeFormat.cs

@@ -51,6 +51,11 @@ namespace SharpGLTF.Memory
 
         #region constructors
 
+        public static implicit operator AttributeFormat(Schema2.IndexEncodingType indexer)
+        {
+            return new AttributeFormat(indexer.ToComponent());
+        }
+
         public static implicit operator AttributeFormat(ENCODING enc) { return new AttributeFormat(enc); }
 
         public static implicit operator AttributeFormat(DIMENSIONS dim) { return new AttributeFormat(dim); }

+ 4 - 0
src/SharpGLTF.Core/Memory/ColorArray.cs

@@ -13,6 +13,10 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector4"/> values.
     /// </summary>
+    /// <remarks>
+    /// When wrapping colors, it is possible to use a <see cref="Vector3"/> RGB or <see cref="Vector4"/> RGBA inputs.
+    /// In case the input data is of type <see cref="Vector3"/>, a <see cref="_DefaultW"/> value is provided to fill the output W.
+    /// </remarks>
     [System.Diagnostics.DebuggerDisplay("Color4[{Count}]")]
     public readonly struct ColorArray : IAccessorArray<Vector4>
     {

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

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
+using BYTES = System.ArraySegment<System.Byte>;
 using DIMENSIONS = SharpGLTF.Schema2.DimensionType;
 using ENCODING = SharpGLTF.Schema2.EncodingType;
 
@@ -39,6 +40,18 @@ namespace SharpGLTF.Memory
             return true;
         }
 
+        internal BYTES _GetBytes()
+        {
+            var o = this._Slicer.ByteOffset;
+            var l = this._Slicer.StepByteLength * this._Slicer.ItemsCount;
+
+            var data = _Data.Slice(o);
+
+            data = data.Slice(0, Math.Min(data.Count, l));
+
+            return data;
+        }
+
         public static bool HaveOverlappingBuffers(IEnumerable<MemoryAccessor> abc)
         {
             var items = abc.ToList();

+ 146 - 137
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -2,10 +2,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
-using System.Reflection;
 
 using BYTES = System.ArraySegment<System.Byte>;
-
 using DIMENSIONS = SharpGLTF.Schema2.DimensionType;
 using ENCODING = SharpGLTF.Schema2.EncodingType;
 
@@ -24,7 +22,7 @@ namespace SharpGLTF.Memory
             return _Slicer._GetDebuggerDisplay();
         }
 
-        #endregion
+        #endregion        
 
         #region constructor
 
@@ -46,119 +44,7 @@ namespace SharpGLTF.Memory
         {
             this._Slicer = info;
             this._Data = default;
-        }
-
-        public static IAccessorArray<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.NotNull(bottom, nameof(bottom));
-            Guard.NotNull(topValues, nameof(topValues));
-            Guard.IsTrue(bottom._Slicer.Dimensions == topValues._Slicer.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Slicer.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Slicer.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Single>(bottom.AsScalarArray(), topValues.AsScalarArray(), topKeys);
-        }
-
-        public static IAccessorArray<Single> CreateScalarSparseArray(int bottomCount, IntegerArray topKeys, MemoryAccessor topValues)
-        {            
-            Guard.NotNull(topValues, nameof(topValues));            
-            Guard.IsTrue(topKeys.Count <= bottomCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottomCount), nameof(topKeys));
-
-            return new SparseArray<Single>(new ZeroAccessorArray<float>(bottomCount), topValues.AsScalarArray(), topKeys);
-        }
-
-
-        public static IAccessorArray<Vector2> CreateVector2SparseArray(int bottomCount, IntegerArray topKeys, MemoryAccessor topValues)
-        {            
-            Guard.NotNull(topValues, nameof(topValues));            
-            Guard.IsTrue(topKeys.Count <= bottomCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottomCount), nameof(topKeys));
-
-            return new SparseArray<Vector2>(new ZeroAccessorArray<Vector2>(bottomCount), topValues.AsVector2Array(), topKeys);
-        }
-
-        public static IAccessorArray<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.NotNull(bottom, nameof(bottom));
-            Guard.NotNull(topValues, nameof(topValues));
-            Guard.IsTrue(bottom._Slicer.Dimensions == topValues._Slicer.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Slicer.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Slicer.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector2>(bottom.AsVector2Array(), topValues.AsVector2Array(), topKeys);
-        }
-
-
-        public static IAccessorArray<Vector3> CreateVector3SparseArray(int bottomCount, IntegerArray topKeys, MemoryAccessor topValues)
-        {            
-            Guard.NotNull(topValues, nameof(topValues));            
-            Guard.IsTrue(topKeys.Count <= bottomCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottomCount), nameof(topKeys));
-
-            return new SparseArray<Vector3>(new ZeroAccessorArray<Vector3>(bottomCount), topValues.AsVector3Array(), topKeys);
-        }
-
-        public static IAccessorArray<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.NotNull(bottom, nameof(bottom));
-            Guard.NotNull(topValues, nameof(topValues));
-            Guard.IsTrue(bottom._Slicer.Dimensions == topValues._Slicer.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Slicer.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Slicer.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector3>(bottom.AsVector3Array(), topValues.AsVector3Array(), topKeys);
-        }
-
-        public static IAccessorArray<Vector4> CreateVector4SparseArray(int bottomCount, IntegerArray topKeys, MemoryAccessor topValues)
-        {            
-            Guard.NotNull(topValues, nameof(topValues));            
-            Guard.IsTrue(topKeys.Count <= bottomCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottomCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(new ZeroAccessorArray<Vector4>(bottomCount), topValues.AsVector4Array(), topKeys);
-        }
-
-        public static IAccessorArray<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
-        {
-            Guard.NotNull(bottom, nameof(bottom));
-            Guard.NotNull(topValues, nameof(topValues));
-            Guard.IsTrue(bottom._Slicer.Dimensions == topValues._Slicer.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Slicer.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Slicer.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(bottom.AsVector4Array(), topValues.AsVector4Array(), topKeys);
-        }
-
-        public static IAccessorArray<Vector4> CreateColorSparseArray(int bottomCount, IntegerArray topKeys, MemoryAccessor topValues, Single defaultW = 1)
-        {            
-            Guard.NotNull(topValues, nameof(topValues));            
-            Guard.IsTrue(topKeys.Count <= bottomCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottomCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(new ZeroAccessorArray<Vector4>(bottomCount), topValues.AsColorArray(defaultW), topKeys);
-        }
-
-        public static IAccessorArray<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues, Single defaultW = 1)
-        {
-            Guard.NotNull(bottom, nameof(bottom));
-            Guard.NotNull(topValues, nameof(topValues));
-            Guard.IsTrue(bottom._Slicer.Dimensions == topValues._Slicer.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom._Slicer.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues._Slicer.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Slicer.ItemsCount), nameof(topKeys));
-
-            return new SparseArray<Vector4>(bottom.AsColorArray(defaultW), topValues.AsColorArray(defaultW), topKeys);
-        }
+        }        
 
         #endregion
 
@@ -189,19 +75,33 @@ namespace SharpGLTF.Memory
         }
 
         public IAccessorArray<T> AsArrayOf<T>()
+            where T: unmanaged
         {
-            if (typeof(T) == typeof(int)) return AsIntegerArray() as IAccessorArray<T>;
-            if (typeof(T) == typeof(float)) return AsScalarArray() as IAccessorArray<T>;
+            if (typeof(T) == typeof(UInt32)) return AsIntegerArray() as IAccessorArray<T>;
+            if (typeof(T) == typeof(Single)) return AsScalarArray() as IAccessorArray<T>;
             if (typeof(T) == typeof(Vector2)) return AsVector2Array() as IAccessorArray<T>;
             if (typeof(T) == typeof(Vector3)) return AsVector3Array() as IAccessorArray<T>;
-
-            // AsColorArray is able to handle both Vector3 and Vector4 underlaying data
-            if (typeof(T) == typeof(Vector4)) return AsColorArray() as IAccessorArray<T>;
+            
+            if (typeof(T) == typeof(Vector4))
+            {
+                if (this.Attribute.Dimensions == DIMENSIONS.VEC4) return AsVector4Array() as IAccessorArray<T>;
+                if (this.Attribute.Dimensions == DIMENSIONS.VEC3) return AsColorArray() as IAccessorArray<T>;
+            }
             
             if (typeof(T) == typeof(Quaternion)) return AsQuaternionArray() as IAccessorArray<T>;
 
-            // we should create the equivalent of AsColorArray for Matrices
-            if (typeof(T) == typeof(Matrix4x4)) return AsMatrix4x4Array() as IAccessorArray<T>;
+            // TODO: we should create the equivalent of AsColorArray for Matrices
+
+            if (typeof(T) == typeof(Matrix3x2))
+            {
+                return AsMatrix2x2Array() as IAccessorArray<T>;                
+            }
+
+            if (typeof(T) == typeof(Matrix4x4))
+            {                
+                if (this.Attribute.Dimensions == DIMENSIONS.MAT3) return AsMatrix3x3Array() as IAccessorArray<T>;
+                if (this.Attribute.Dimensions == DIMENSIONS.MAT4) return AsMatrix4x4Array() as IAccessorArray<T>;
+            }
 
             throw new NotSupportedException(typeof(T).Name);
         }
@@ -239,14 +139,7 @@ namespace SharpGLTF.Memory
             Guard.IsTrue(_Slicer.IsValidVertexAttribute, nameof(_Slicer));
             Guard.IsTrue(_Slicer.Dimensions == DIMENSIONS.VEC4, nameof(_Slicer));
             return new Vector4Array(_Data, _Slicer.ByteOffset, _Slicer.ItemsCount, _Slicer.ByteStride, _Slicer.Encoding, _Slicer.Normalized);
-        }
-
-        public ColorArray AsColorArray(Single defaultW = 1)
-        {
-            Guard.IsTrue(_Slicer.IsValidVertexAttribute, nameof(_Slicer));
-            Guard.IsTrue(_Slicer.Dimensions == DIMENSIONS.VEC3 || _Slicer.Dimensions == DIMENSIONS.VEC4, nameof(_Slicer));
-            return new ColorArray(_Data, _Slicer.ByteOffset, _Slicer.ItemsCount, _Slicer.ByteStride, _Slicer.Dimensions.DimCount(), _Slicer.Encoding, _Slicer.Normalized, defaultW);
-        }
+        }        
 
         public QuaternionArray AsQuaternionArray()
         {
@@ -284,6 +177,22 @@ namespace SharpGLTF.Memory
             return new Matrix4x4Array(_Data, _Slicer.ByteOffset, _Slicer.ItemsCount, _Slicer.ByteStride, _Slicer.Encoding, _Slicer.Normalized);
         }
 
+
+        /// <summary>
+        /// Gets an array of "colors"
+        /// </summary>
+        /// <remarks>
+        /// This can be used either on <see cref="Vector3"/> and <see cref="Vector4"/> input data.
+        /// </remarks>
+        /// <param name="defaultW">default value for the W component if missing.</param>
+        /// <returns>An array of colors</returns>
+        public ColorArray AsColorArray(Single defaultW = 1)
+        {
+            Guard.IsTrue(_Slicer.IsValidVertexAttribute, nameof(_Slicer));
+            Guard.IsTrue(_Slicer.Dimensions == DIMENSIONS.VEC3 || _Slicer.Dimensions == DIMENSIONS.VEC4, nameof(_Slicer));
+            return new ColorArray(_Data, _Slicer.ByteOffset, _Slicer.ItemsCount, _Slicer.ByteStride, _Slicer.Dimensions.DimCount(), _Slicer.Encoding, _Slicer.Normalized, defaultW);
+        }
+
         public MultiArray AsMultiArray(int dimensions)
         {
             Guard.IsTrue(_Slicer.IsValidVertexAttribute, nameof(_Slicer));
@@ -304,16 +213,116 @@ namespace SharpGLTF.Memory
             }
         }
 
-        internal BYTES _GetBytes()
+        #endregion
+
+        #region sparse API
+
+        public (MemoryAccessor indices, MemoryAccessor values) ConvertToSparse()
+        {
+            var indices = new List<uint>();
+            var values = new List<BYTES>();
+
+            uint index = 0;
+
+            foreach (var item in GetItemsAsRawBytes())
+            {
+                if (!RepresentsZeroValue(item))
+                {
+                    indices.Add(index);
+                    values.Add(item);
+                }
+
+                ++index;
+            }
+
+            var indicesBuffer = new byte[indices.Count * 4];
+            for (int i = 0; i < indices.Count; ++i)
+            {
+                System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(indicesBuffer.Slice(i * 4), indices[i]);
+            }
+
+            var blen = Attribute.ItemByteLength;
+
+            var vertexBuffer = new byte[values.Count * blen];
+            for (int i = 0; i < values.Count; ++i)
+            {
+                var src = values[i];
+                var dst = new BYTES(vertexBuffer, i * blen, blen);
+
+                src.AsSpan().CopyTo(dst);
+            }
+
+            var indicesMem = new MemoryAccessor(indicesBuffer, new MemoryAccessInfo("SparseIndices", 0, values.Count, 0, ENCODING.UNSIGNED_INT));
+            var valuesMem = new MemoryAccessor(vertexBuffer, new MemoryAccessInfo("SparseValues", 0, values.Count, 0, this.Attribute.Format));
+
+            return (indicesMem, valuesMem);
+        }
+
+        private bool RepresentsZeroValue(BYTES bytes)
+        {
+            // we handle floats separately to support negative zero.
+            if (this.Attribute.Encoding == ENCODING.FLOAT)
+            {
+                var floats = System.Runtime.InteropServices.MemoryMarshal.Cast<byte, float>(bytes);
+                foreach (var f in floats)
+                {
+                    if (f != 0) return false;
+                }
+                return true;
+            }
+
+            return bytes.All(b => b == 0);
+        }
+
+        public static IAccessorArray<T> CreateSparseArray<T>(MemoryAccessor denseValues, IntegerArray sparseKeys, MemoryAccessor sparseValues)
+            where T : unmanaged
+        {
+            return _CreateSparseArray(denseValues, sparseKeys, sparseValues, m => m.AsArrayOf<T>());
+        }
+
+        public static IAccessorArray<T> CreateSparseArray<T>(int denseCount, IntegerArray sparseKeys, MemoryAccessor sparseValues)
+            where T : unmanaged
+        {
+            return _CreateSparseArray(denseCount, sparseKeys, sparseValues, m => m.AsArrayOf<T>());
+        }
+
+        public static IAccessorArray<Vector4> CreateColorSparseArray(int denseCount, IntegerArray sparseKeys, MemoryAccessor sparseValues, Single defaultW = 1)
+        {
+            return _CreateSparseArray(denseCount, sparseKeys, sparseValues, m => m.AsColorArray(defaultW));
+        }
+
+        public static IAccessorArray<Vector4> CreateColorSparseArray(MemoryAccessor denseValues, IntegerArray sparseKeys, MemoryAccessor sparseValues, Single defaultW = 1)
+        {
+            return _CreateSparseArray(denseValues, sparseKeys, sparseValues, m => m.AsColorArray(defaultW));
+        }
+
+        private static IAccessorArray<T> _CreateSparseArray<T>(int denseCount, IntegerArray sparseKeys, MemoryAccessor sparseValues, Func<MemoryAccessor, IAccessorArray<T>> toAccessor)
+            where T : unmanaged
+        {
+            Guard.NotNull(sparseValues, nameof(sparseValues));
+            Guard.IsTrue(sparseKeys.Count <= denseCount, nameof(sparseKeys));
+            System.Diagnostics.Debug.Assert(sparseKeys.All(item => item < (uint)denseCount), nameof(sparseKeys), "index keys exceed bottomCount");
+
+            var typedSparseValues = toAccessor(sparseValues);
+            Guard.IsTrue(sparseKeys.Count == typedSparseValues.Count, nameof(sparseValues));
+
+            return new SparseArray<T>(new ZeroAccessorArray<T>(denseCount), typedSparseValues, sparseKeys);
+        }
+
+        private static IAccessorArray<T> _CreateSparseArray<T>(MemoryAccessor denseValues, IntegerArray sparseKeys, MemoryAccessor sparseValues, Func<MemoryAccessor, IAccessorArray<T>> toAccessor)
+            where T : unmanaged
         {
-            var o = this._Slicer.ByteOffset;
-            var l = this._Slicer.StepByteLength * this._Slicer.ItemsCount;
+            Guard.NotNull(denseValues, nameof(denseValues));
+            Guard.NotNull(sparseValues, nameof(sparseValues));
 
-            var data = _Data.Slice(o);
+            var typedDenseValues = toAccessor(denseValues);
+            var typedSparseValues = toAccessor(sparseValues);
 
-            data = data.Slice(0, Math.Min(data.Count, l));
+            Guard.IsTrue(sparseKeys.Count <= typedDenseValues.Count, nameof(sparseKeys));
+            Guard.IsTrue(sparseKeys.Count == typedSparseValues.Count, nameof(sparseValues));
+            System.Diagnostics.Debug.Assert(sparseKeys.All(item => item < (uint)typedDenseValues.Count), nameof(sparseKeys), "index keys exceed bottomCount");
 
-            return data;
+            return new SparseArray<T>(typedDenseValues, typedSparseValues, sparseKeys);
         }
 
         #endregion

+ 25 - 13
src/SharpGLTF.Core/Memory/SparseArrays.cs

@@ -10,22 +10,34 @@ namespace SharpGLTF.Memory
     /// </summary>
     /// <typeparam name="T">An unmanage structure type.</typeparam>
     [System.Diagnostics.DebuggerDisplay("Sparse {typeof(T).Name} Accessor {Count}")]
-    public readonly struct SparseArray<T> : IAccessorArray<T>
+    public sealed class SparseArray<T> : IAccessorArray<T>
         where T : unmanaged
     {
         #region lifecycle
 
-        public SparseArray(IReadOnlyList<T> bottom, IReadOnlyList<T> top, IReadOnlyList<uint> topMapping)
+        public SparseArray(IReadOnlyList<T> denseValues, IReadOnlyList<T> sparseValues, IReadOnlyList<uint> sparseKeys)
         {
-            _BottomItems = bottom;
-            _TopItems = top;
+            Guard.NotNull(denseValues, nameof(denseValues));
+            Guard.NotNull(sparseValues, nameof(sparseValues));
+            Guard.NotNull(sparseKeys, nameof(sparseKeys));            
+            Guard.MustBeEqualTo(sparseKeys.Count, sparseValues.Count, nameof(sparseKeys.Count));
+            Guard.MustBeLessThanOrEqualTo(sparseKeys.Count, denseValues.Count, nameof(sparseKeys.Count));
+
+            _DenseItems = denseValues;
+            _SparseItems = sparseValues;
 
             // expand indices for fast access
-            _Mapping = new Dictionary<int, int>();
-            for (int val = 0; val < topMapping.Count; ++val)
+            _SparseIndices = new Dictionary<int, int>();
+            for (int val = 0; val < sparseKeys.Count; ++val)
             {
-                var key = (int)topMapping[val];
-                _Mapping[key] = val;
+                var key = (int)sparseKeys[val];
+
+                if (key >= denseValues.Count)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(sparseKeys));
+                }
+
+                _SparseIndices[key] = val;
             }
         }
 
@@ -34,13 +46,13 @@ namespace SharpGLTF.Memory
         #region data
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly IReadOnlyList<T> _BottomItems;
+        private readonly IReadOnlyList<T> _DenseItems;
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly IReadOnlyList<T> _TopItems;
+        private readonly IReadOnlyList<T> _SparseItems;
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly Dictionary<int, int> _Mapping;
+        private readonly Dictionary<int, int> _SparseIndices;
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private T[] _DebugItems => this.ToArray();
@@ -50,13 +62,13 @@ namespace SharpGLTF.Memory
         #region API
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public int Count => _BottomItems.Count;
+        public int Count => _DenseItems.Count;
 
         public bool IsReadOnly => true;
 
         public T this[int index]
         {
-            get => _Mapping.TryGetValue(index, out int topIndex) ? _TopItems[topIndex] : _BottomItems[index];
+            get => _SparseIndices.TryGetValue(index, out int topIndex) ? _SparseItems[topIndex] : _DenseItems[index];
             set => throw new NotSupportedException("Collection is read only.");
         }
 

+ 12 - 11
src/SharpGLTF.Core/Schema2/gltf.AccessorSparse.cs

@@ -1,25 +1,26 @@
 using System.Collections.Generic;
 
+using SharpGLTF.Validation;
+
+using ROOT = SharpGLTF.Schema2.ModelRoot;
+
 namespace SharpGLTF.Schema2
 {
-    using SharpGLTF.Validation;
-    using ROOT = ModelRoot;
-
     public sealed partial class AccessorSparse
     {
         #region lifecycle
 
         internal AccessorSparse() { }
 
-        internal AccessorSparse(BufferView indices, int indicesOffset, IndexEncodingType indicesEncoding, BufferView values, int valuesOffset, int count)
+        internal AccessorSparse(int sparseCount, BufferView indices, int indicesOffset, IndexEncodingType indicesEncoding, BufferView values, int valuesOffset)
         {
             Guard.NotNull(indices, nameof(indices));
             Guard.NotNull(values, nameof(values));
-            Guard.MustBeGreaterThanOrEqualTo(count, _countMinimum, nameof(count));
+            Guard.MustBeGreaterThanOrEqualTo(sparseCount, _countMinimum, nameof(sparseCount));
 
-            this._count = count;
+            this._count = sparseCount;
             this._indices = new AccessorSparseIndices(indices, indicesOffset, indicesEncoding);
-            this._values = new AccessorSparseValues(values, valuesOffset);
+            this._values = new AccessorSparseValues(values, valuesOffset);            
         }
 
         #endregion
@@ -88,10 +89,10 @@ namespace SharpGLTF.Schema2
 
         #region API
 
-        internal Memory.IntegerArray _GetIndicesArray(ROOT root, int count)
+        internal Memory.IntegerArray _GetIndicesArray(ROOT root, int sparseCount)
         {
             var srcBuffer = root.LogicalBufferViews[this._bufferView];
-            return new Memory.IntegerArray(srcBuffer.Content, this._byteOffset ?? 0, count, this._componentType);
+            return new Memory.IntegerArray(srcBuffer.Content, this._byteOffset ?? 0, sparseCount, this._componentType);
         }
 
         #endregion
@@ -138,10 +139,10 @@ namespace SharpGLTF.Schema2
 
         #region API
 
-        internal Memory.MemoryAccessor _GetMemoryAccessor(ROOT root, int count, Accessor baseAccessor)
+        internal Memory.MemoryAccessor _GetMemoryAccessor(ROOT root, int sparseCount, Accessor baseAccessor)
         {
             var view = root.LogicalBufferViews[this._bufferView];
-            var info = new Memory.MemoryAccessInfo(null, this._byteOffset ?? 0, count, view.ByteStride, baseAccessor.Dimensions, baseAccessor.Encoding, baseAccessor.Normalized);
+            var info = new Memory.MemoryAccessInfo(null, this._byteOffset ?? 0, sparseCount, view.ByteStride, baseAccessor.Format);
             return new Memory.MemoryAccessor(view.Content, info);
         }
 

+ 14 - 78
src/SharpGLTF.Core/Schema2/gltf.Accessors.Arrays.cs

@@ -47,7 +47,7 @@ namespace SharpGLTF.Schema2
         public IAccessorArray<Matrix4x4> AsMatrix4x4Array()
         {
             return _TryGetMemoryAccessor(out var mem)
-                ? mem.AsMatrix4x4Array()
+                ? mem.AsArrayOf<Matrix4x4>()
                 : new ZeroAccessorArray<Matrix4x4>(this._count);
         }
 
@@ -55,7 +55,7 @@ namespace SharpGLTF.Schema2
         internal IReadOnlyList<Matrix4x4> AsMatrix4x4ReadOnlyList()
         {
             return _TryGetMemoryAccessor(out var mem)
-                ? mem.AsMatrix4x4Array()
+                ? mem.AsArrayOf<Matrix4x4>()
                 : new ZeroAccessorArray<Matrix4x4>(this._count);
         }
 
@@ -108,98 +108,34 @@ namespace SharpGLTF.Schema2
 
         #endregion
 
-        #region Vertex Buffer Arrays        
+        #region Vertex Buffer Arrays
 
-        internal IAccessorArray<T> AsArrayOf<T>()
-        {
-            if (typeof(T) == typeof(int)) return AsIndicesArray() as IAccessorArray<T>;
-            if (typeof(T) == typeof(float)) return AsScalarArray() as IAccessorArray<T>;
-            if (typeof(T) == typeof(Vector2)) return AsVector2Array() as IAccessorArray<T>;
-            if (typeof(T) == typeof(Vector3)) return AsVector3Array() as IAccessorArray<T>;
-
-            // AsColorArray is able to handle both Vector3 and Vector4 underlaying data
-            if (typeof(T) == typeof(Vector4)) return AsColorArray() as IAccessorArray<T>;
-
-            if (typeof(T) == typeof(Quaternion)) return AsQuaternionArray() as IAccessorArray<T>;
-
-            // we should create the equivalent of AsColorArray for Matrices
-            if (typeof(T) == typeof(Matrix4x4)) return AsMatrix4x4Array() as IAccessorArray<T>;
-
-            throw new NotSupportedException(typeof(T).Name);
-        }
-
-        public IAccessorArray<Single> AsScalarArray()
-        {
-            if (_TryGetMemoryAccessor(out var memory))
-            {
-                if (this._sparse == null) return memory.AsScalarArray();
-
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateScalarSparseArray(memory, sparseKV.Key, sparseKV.Value);
-            }
-            else
-            {
-                if (this._sparse == null) return new ZeroAccessorArray<Single>(this._count);
-
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateScalarSparseArray(this._count, sparseKV.Key, sparseKV.Value);
-            }
-        }
-
-        public IAccessorArray<Vector2> AsVector2Array()
-        {
-            if (_TryGetMemoryAccessor(out var memory))
-            {
-                if (this._sparse == null) return memory.AsVector2Array();
+        public IAccessorArray<Single> AsScalarArray() => AsArrayOf<Single>();
 
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector2SparseArray(memory, sparseKV.Key, sparseKV.Value);
-            }
-            else
-            {
-                if (this._sparse == null) return new ZeroAccessorArray<Vector2>(this._count);
-
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector2SparseArray(this._count, sparseKV.Key, sparseKV.Value);
-            }
-        }
+        public IAccessorArray<Vector2> AsVector2Array() => AsArrayOf<Vector2>();
 
-        public IAccessorArray<Vector3> AsVector3Array()
-        {
-            if (_TryGetMemoryAccessor(out var memory))
-            {
+        public IAccessorArray<Vector3> AsVector3Array() => AsArrayOf<Vector3>();
 
-                if (this._sparse == null) return memory.AsVector3Array();
+        public IAccessorArray<Vector4> AsVector4Array() => AsArrayOf<Vector4>();
 
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector3SparseArray(memory, sparseKV.Key, sparseKV.Value);
-            }
-            else
-            {
-                if (this._sparse == null) return new ZeroAccessorArray<Vector3>(this._count);
-
-                var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector3SparseArray(this._count, sparseKV.Key, sparseKV.Value);
-            }
-        }
-
-        public IAccessorArray<Vector4> AsVector4Array()
+        public IAccessorArray<T> AsArrayOf<T>()
+            where T:unmanaged
         {
             if (_TryGetMemoryAccessor(out var memory))
             {
-                if (this._sparse == null) return memory.AsVector4Array();
+                if (this._sparse == null) return memory.AsArrayOf<T>();
 
                 var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector4SparseArray(memory, sparseKV.Key, sparseKV.Value);
+                return MemoryAccessor.CreateSparseArray<T>(memory, sparseKV.Key, sparseKV.Value);
             }
             else
             {
-                if (this._sparse == null) return new ZeroAccessorArray<Vector4>(this._count);
+                if (this._sparse == null) return new ZeroAccessorArray<T>(this._count);
 
                 var sparseKV = this._sparse._CreateMemoryAccessors(this);
-                return MemoryAccessor.CreateVector4SparseArray(this._count, sparseKV.Key, sparseKV.Value);
+                return MemoryAccessor.CreateSparseArray<T>(this._count, sparseKV.Key, sparseKV.Value);
             }
-        }
+        }        
 
         public IAccessorArray<Vector4> AsColorArray(Single defaultW = 1)
         {

+ 69 - 37
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -263,14 +263,19 @@ namespace SharpGLTF.Schema2
             {
                 if (other.SourceBufferView.ByteStride == 0) throw new ArgumentException("When a BufferView is shared by more than one Accessor, its ByteStride must be explicitly set.", nameof(Accessor));
 
-                SetData(other.SourceBufferView, other.ByteOffset, other.Count, other.Dimensions, other.Encoding, other.Normalized);
+                SetData(other.SourceBufferView, other.ByteOffset, other.Count, other.Format);
             }
             else
             {
-                SetZeros(other.Count, other.Dimensions, other.Encoding, other.Normalized);
+                SetZeros(other.Count, other.Format);
             }
         }
 
+        public void SetZeros(MemoryAccessInfo accessor)
+        {
+            SetZeros(accessor.ItemsCount, accessor.Format);
+        }
+
         /// <summary>
         /// Associates this <see cref="Accessor"/> with an array of zero values.
         /// </summary>        
@@ -278,10 +283,8 @@ namespace SharpGLTF.Schema2
         /// This can be used to set the base data for sparse data.
         /// </remarks>
         /// <param name="itemCount">The number of items in the accessor.</param>
-        /// <param name="dimensions">The <see cref="DimensionType"/> item type.</param>
-        /// <param name="encoding">The <see cref="EncodingType"/> item encoding.</param>
-        /// <param name="normalized">The item normalization mode.</param>
-        public void SetZeros(int itemCount, DimensionType dimensions, EncodingType encoding, Boolean normalized)
+        /// <param name="format">item data format</param>
+        public void SetZeros(int itemCount, AttributeFormat format)
         {            
             Guard.MustBeGreaterThanOrEqualTo(itemCount, _countMinimum, nameof(itemCount));
 
@@ -289,14 +292,14 @@ namespace SharpGLTF.Schema2
             this._byteOffset = null;
             this._count = itemCount;            
 
-            this._CachedType = dimensions;
-            this._type = Enum.GetName(typeof(DimensionType), dimensions);
+            this._CachedType = format.Dimensions;
+            this._type = Enum.GetName(typeof(DimensionType), format.Dimensions);
 
-            this._componentType = encoding;
-            this._normalized = normalized.AsNullable(_normalizedDefault);
+            this._componentType = format.Encoding;
+            this._normalized = format.Normalized.AsNullable(_normalizedDefault);
 
             UpdateBounds();
-        }        
+        }
 
         /// <summary>
         /// Associates this <see cref="Accessor"/> with a <see cref="BufferView"/>
@@ -307,7 +310,20 @@ namespace SharpGLTF.Schema2
         /// <param name="dimensions">The <see cref="DimensionType"/> item type.</param>
         /// <param name="encoding">The <see cref="EncodingType"/> item encoding.</param>
         /// <param name="normalized">The item normalization mode.</param>
+        [Obsolete("Use SetData with AttributeFormat. This will be removed soon.")]
         public void SetData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions, EncodingType encoding, Boolean normalized)
+        {
+            SetData(buffer, bufferByteOffset, itemCount, (dimensions, encoding, normalized));
+        }
+
+        /// <summary>
+        /// Associates this <see cref="Accessor"/> with a <see cref="BufferView"/>
+        /// </summary>
+        /// <param name="buffer">The <see cref="BufferView"/> source.</param>
+        /// <param name="bufferByteOffset">The start byte offset within <paramref name="buffer"/>.</param>
+        /// <param name="itemCount">The number of items in the accessor.</param>
+        /// <param name="format">The item data format.</param>
+        public void SetData(BufferView buffer, int bufferByteOffset, int itemCount, AttributeFormat format)
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
@@ -318,11 +334,11 @@ namespace SharpGLTF.Schema2
             this._byteOffset = bufferByteOffset.AsNullable(_byteOffsetDefault, _byteOffsetMinimum, int.MaxValue);
             this._count = itemCount;
 
-            this._CachedType = dimensions;
-            this._type = Enum.GetName(typeof(DimensionType), dimensions);
+            this._CachedType = format.Dimensions;
+            this._type = Enum.GetName(typeof(DimensionType), format.Dimensions);
 
-            this._componentType = encoding;
-            this._normalized = normalized.AsNullable(_normalizedDefault);
+            this._componentType = format.Encoding;
+            this._normalized = format.Normalized.AsNullable(_normalizedDefault);
 
             UpdateBounds();
         }
@@ -336,6 +352,7 @@ namespace SharpGLTF.Schema2
         }
 
         public void CreateSparseData<T>(IReadOnlyDictionary<int,T> data)
+            where T:unmanaged
         {
             if (!Enum.IsDefined(typeof(EncodingType), this._componentType))
             {
@@ -358,38 +375,49 @@ namespace SharpGLTF.Schema2
                 ++idx;
             }
 
-            SetSparseData(data.Count, indices, values);
+            SetSparseData(indices, values);
         }
 
-        public void SetSparseData(int count, MemoryAccessor indices, MemoryAccessor values)
+        public void SetSparseData(MemoryAccessor sparseIndices, MemoryAccessor sparseValues)
         {
-            Guard.MustBeGreaterThan(count, 0, nameof(count));
+            Guard.MustBeGreaterThan(sparseIndices.Attribute.ItemsCount, 0, nameof(sparseIndices));
+            Guard.MustBeLessThanOrEqualTo(sparseIndices.Attribute.ItemsCount, _count, nameof(sparseIndices));
+
+            Guard.MustBeEqualTo(sparseIndices.Attribute.ItemsCount, sparseValues.Attribute.ItemsCount, nameof(sparseValues));
 
-            var indicesBV = this.LogicalParent.UseBufferView(indices.Data);
-            var valuesBV = this.LogicalParent.UseBufferView(values.Data);
+            var indicesBV = this.LogicalParent.UseBufferView(sparseIndices.Data);
+            var valuesBV = this.LogicalParent.UseBufferView(sparseValues.Data);
 
-            SetSparseData(count, indicesBV, 0, indices.Attribute.Encoding.ToIndex(), valuesBV, 0);
+            SetSparseData(sparseIndices.Attribute.ItemsCount, indicesBV, 0, sparseIndices.Attribute.Encoding.ToIndex(), valuesBV, 0);
         }
 
-        public void SetSparseData(int count, BufferView indices, int indicesByteOffset, IndexEncodingType indicesEncoding, BufferView values, int valuesByteOffset)
+        public void SetSparseData(int sparseCount, BufferView indices, int indicesByteOffset, IndexEncodingType indicesEncoding, BufferView values, int valuesByteOffset)
         {
-            Guard.MustBeGreaterThan(count, 0, nameof(count));
+            Guard.MustBeGreaterThan(sparseCount, 0, nameof(sparseCount));
+            Guard.MustBeLessThanOrEqualTo(sparseCount, _count, nameof(sparseCount));
 
             Guard.NotNull(indices, nameof(indices));
             Guard.MustShareLogicalParent(this, indices, nameof(indices));
             Guard.IsFalse(indices.IsVertexBuffer, nameof(indices));
             Guard.IsFalse(indices.IsIndexBuffer, nameof(indices));
-
             Guard.MustBeGreaterThanOrEqualTo(indicesByteOffset, 0, nameof(indicesByteOffset));
 
             Guard.NotNull(values, nameof(values));
             Guard.MustShareLogicalParent(this, values, nameof(values));
-            Guard.IsFalse(values.IsVertexBuffer, nameof(indices));
-            Guard.IsFalse(values.IsIndexBuffer, nameof(indices));
-
+            Guard.IsFalse(values.IsVertexBuffer, nameof(values));
+            Guard.IsFalse(values.IsIndexBuffer, nameof(values));
             Guard.MustBeGreaterThanOrEqualTo(valuesByteOffset, 0, nameof(valuesByteOffset));
 
-            this._sparse = new AccessorSparse(indices, indicesByteOffset, indicesEncoding, values, valuesByteOffset, count);
+            var sparseData = new AccessorSparse(sparseCount, indices, indicesByteOffset, indicesEncoding, values, valuesByteOffset);
+
+            // validation
+            var sparseResult = sparseData._CreateMemoryAccessors(this);
+
+            Guard.MustBeEqualTo(sparseResult.Key.Count, sparseCount, nameof(indices));
+            Guard.MustBeEqualTo(sparseResult.Value.Attribute.ItemsCount, sparseCount, nameof(indices));
+
+            // assign and update
+            this._sparse = sparseData;
 
             UpdateBounds();
         }        
@@ -419,20 +447,26 @@ namespace SharpGLTF.Schema2
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.IsFalse(buffer.IsVertexBuffer, nameof(buffer));
 
-            SetData(buffer, bufferByteOffset, itemCount, DimensionType.SCALAR, encoding.ToComponent(), false);
+            SetData(buffer, bufferByteOffset, itemCount, encoding.ToComponent());
         }
 
         #endregion
 
         #region Vertex Buffer API
-
+        
         public void SetVertexData(MemoryAccessor src)
         {
             Guard.NotNull(src, nameof(src));
 
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.StepByteLength, BufferMode.ARRAY_BUFFER);
 
-            SetVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
+            SetVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Format);
+        }
+
+        [Obsolete("Use SetVertexData with AttributeFormat. This will be removed soon.")]
+        public void SetVertexData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions = DimensionType.VEC3, EncodingType encoding = EncodingType.FLOAT, Boolean normalized = false)
+        {
+            SetVertexData(buffer, bufferByteOffset, itemCount, (dimensions, encoding, normalized));
         }
 
         /// <summary>
@@ -441,17 +475,15 @@ namespace SharpGLTF.Schema2
         /// <param name="buffer">The <see cref="BufferView"/> source.</param>
         /// <param name="bufferByteOffset">The start byte offset within <paramref name="buffer"/>.</param>
         /// <param name="itemCount">The number of items in the accessor.</param>
-        /// <param name="dimensions">The <see cref="DimensionType"/> item type.</param>
-        /// <param name="encoding">The <see cref="EncodingType"/> item encoding.</param>
-        /// <param name="normalized">The item normalization mode.</param>
-        public void SetVertexData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions = DimensionType.VEC3, EncodingType encoding = EncodingType.FLOAT, Boolean normalized = false)
+        /// <param name="format">The item format.</param>        
+        public void SetVertexData(BufferView buffer, int bufferByteOffset, int itemCount, AttributeFormat format)
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
-            Guard.MustBePositiveAndMultipleOf(dimensions.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
+            Guard.MustBePositiveAndMultipleOf(format.ByteSize, 4, nameof(format));
             Guard.IsFalse(buffer.IsIndexBuffer, nameof(buffer));
 
-            SetData(buffer, bufferByteOffset, itemCount, dimensions, encoding, normalized);
+            SetData(buffer, bufferByteOffset, itemCount, format);
         }
 
         #endregion

+ 8 - 7
src/SharpGLTF.Core/Schema2/gltf.AnimationSampler.cs

@@ -6,6 +6,7 @@ using System.Text;
 
 using SharpGLTF.Animations;
 using SharpGLTF.Collections;
+using SharpGLTF.Memory;
 using SharpGLTF.Transforms;
 using SharpGLTF.Validation;
 
@@ -138,7 +139,7 @@ namespace SharpGLTF.Schema2
             var buffer = root.CreateBufferView(input.Count * 4);
             var accessor = root.CreateAccessor("Animation.Input");
 
-            accessor.SetData(buffer, 0, input.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, input.Count, AttributeFormat.Float1);
 
             Memory.EncodedArrayUtils._CopyTo(input, accessor.AsScalarArray());
 
@@ -160,7 +161,7 @@ namespace SharpGLTF.Schema2
 
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, output.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, output.Count, AttributeFormat.Float1);
 
             Memory.EncodedArrayUtils._CopyTo(output, accessor.AsScalarArray());
 
@@ -182,7 +183,7 @@ namespace SharpGLTF.Schema2
 
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC2, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, output.Count, AttributeFormat.Float2);
 
             Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector2Array());
 
@@ -204,7 +205,7 @@ namespace SharpGLTF.Schema2
 
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC3, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, output.Count, AttributeFormat.Float3);
 
             Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector3Array());
 
@@ -226,7 +227,7 @@ namespace SharpGLTF.Schema2
 
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC4, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, output.Count, AttributeFormat.Float4);
 
             Memory.EncodedArrayUtils._CopyTo(output, accessor.AsVector4Array());
 
@@ -245,7 +246,7 @@ namespace SharpGLTF.Schema2
             var buffer = root.CreateBufferView(output.Count * 4 * 4);
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, output.Count, DimensionType.VEC4, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, output.Count, AttributeFormat.Float4);
 
             Memory.EncodedArrayUtils._CopyTo(output, accessor.AsQuaternionArray());
 
@@ -284,7 +285,7 @@ namespace SharpGLTF.Schema2
             var buffer = root.CreateBufferView(itemCount * 4 * itemsStride);
             var accessor = root.CreateAccessor("Animation.Output");
 
-            accessor.SetData(buffer, 0, itemCount * itemsStride, DimensionType.SCALAR, EncodingType.FLOAT, false);
+            accessor.SetData(buffer, 0, itemCount * itemsStride, AttributeFormat.Float1);
 
             IList<float> dst = accessor.AsScalarArray();
 

+ 1 - 0
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -154,6 +154,7 @@ namespace SharpGLTF.Schema2
         }
 
         internal IReadOnlyList<T> GetVertices<T>(string attributeKey)
+            where T : unmanaged
         {
             var accessor = GetVertexAccessor(attributeKey);
 

+ 3 - 1
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
 
+using SharpGLTF.Memory;
+
 namespace SharpGLTF.Schema2
 {
     [System.Diagnostics.DebuggerDisplay("Skin[{LogicalIndex}] {Name}")]
@@ -208,7 +210,7 @@ namespace SharpGLTF.Schema2
 
             var ibmsView = LogicalParent.UseBufferView(data);
 
-            UseInverseBindMatricesAccessor().SetData(ibmsView, 0, joints.Count, DimensionType.MAT4, EncodingType.FLOAT, false);            
+            UseInverseBindMatricesAccessor().SetData(ibmsView, 0, joints.Count, AttributeFormat.Float4x4);            
 
             // joints
 

+ 1 - 1
src/SharpGLTF.Ext.3DTiles/Schema2/Ext.CESIUM_primitive_outline.cs

@@ -29,7 +29,7 @@ namespace SharpGLTF.Schema2
             var bview = model.UseBufferView(dstData);
             var accessor = model.CreateAccessor(accessorName);
 
-            accessor.SetData(bview, 0, dstArray.Count, DimensionType.SCALAR, EncodingType.UNSIGNED_INT, false);
+            accessor.SetData(bview, 0, dstArray.Count, IndexEncodingType.UNSIGNED_INT);
 
             primitive.SetCesiumOutline(accessor);
         }

+ 48 - 0
src/SharpGLTF.Toolkit/Schema2/AccessorExtensions.cs

@@ -7,6 +7,54 @@ namespace SharpGLTF.Schema2
 {
     public static partial class Toolkit
     {
+        /// <summary>
+        /// Creates a new <see cref="Accessor"/> and fills it with data from <paramref name="memAccessor"/>
+        /// </summary>
+        /// <remarks>
+        /// If enough number of zero values is detected, a sparse accessor will be created instead.
+        /// </remarks>
+        /// <param name="root">the model where the <see cref="Accessor"/> will be created.</param>
+        /// <param name="memAccessor">the data to be used to fill the <see cref="Accessor"/>.</param>
+        /// <param name="sparsityPercent">The percentage threshold of zero values that determine whether to use sparse accessors. Or -1 to disable sparse accessors creation</param>
+        /// <returns>The created <see cref="Accessor"/>.</returns>
+        public static Accessor CreateMorphTargetAccessor(this ModelRoot root, Memory.MemoryAccessor memAccessor, int sparsityPercent = 60)
+        {
+            Guard.NotNull(root, nameof(root));
+            Guard.NotNull(memAccessor, nameof(memAccessor));
+
+            var accessor = root.CreateAccessor(memAccessor.Attribute.Name);            
+
+            var (indices, values) = memAccessor.ConvertToSparse();
+
+            if (indices.Attribute.ItemsCount == 0)
+            {
+                accessor.SetZeros(memAccessor.Attribute);
+                return accessor;
+            }
+
+            var sparsity = indices.Attribute.ItemsCount * 100 / memAccessor.Attribute.ItemsCount;
+
+            if (sparsity > sparsityPercent)
+            {
+                accessor.SetVertexData(memAccessor);
+                return accessor;
+            }
+
+            // set base data (all zeros)
+            accessor.SetZeros(memAccessor.Attribute);
+
+            // set sparse data on top of base data
+            accessor.SetSparseData(indices, values);            
+
+            return accessor;            
+        }
+
+        /// <summary>
+        /// Creates a new <see cref="Accessor"/> and fills it with data from <paramref name="memAccessor"/>
+        /// </summary>
+        /// <param name="root">the model where the <see cref="Accessor"/> will be created.</param>
+        /// <param name="memAccessor">the data to be used to fill the <see cref="Accessor"/>.</param>
+        /// <returns>The created <see cref="Accessor"/>.</returns>
         public static Accessor CreateVertexAccessor(this ModelRoot root, Memory.MemoryAccessor memAccessor)
         {
             Guard.NotNull(root, nameof(root));

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

@@ -159,7 +159,7 @@ namespace SharpGLTF.Schema2
                 var accessor = root.CreateAccessor();
                 primitive.SetVertexAccessor(attribute, accessor);
 
-                accessor.SetVertexData(view, 0, values.Count, dims, EncodingType.FLOAT, false);
+                accessor.SetVertexData(view, 0, values.Count, new AttributeFormat(dims, EncodingType.FLOAT, false));
             }
             
             switch (values)
@@ -275,7 +275,7 @@ namespace SharpGLTF.Schema2
 
             var root = primitive.LogicalParent.LogicalParent;
 
-            var accessors = memAccessors.ToDictionary(item => item.Attribute.Name, item => root.CreateVertexAccessor(item));
+            var accessors = memAccessors.ToDictionary(item => item.Attribute.Name, item => root.CreateMorphTargetAccessor(item));
             
             primitive.SetMorphTargetAccessors(targetIndex, accessors);
 
@@ -299,7 +299,7 @@ namespace SharpGLTF.Schema2
             }
             else
             {
-                accessor.SetVertexData(view, 0, values.Count, typeof(T).ToDimension(), EncodingType.FLOAT, false);
+                accessor.SetVertexData(view, 0, values.Count,new AttributeFormat(typeof(T).ToDimension()));
             }
 
             instancing.SetAccessor(attribute, accessor);

+ 3 - 1
tests/SharpGLTF.Core.Tests/Schema2/Authoring/AdvancedCreationTests.cs

@@ -7,6 +7,8 @@ using System.Threading.Tasks;
 
 using NUnit.Framework;
 
+using SharpGLTF.Memory;
+
 namespace SharpGLTF.Schema2.Authoring
 {
     internal class AdvancedCreationTests
@@ -162,7 +164,7 @@ namespace SharpGLTF.Schema2.Authoring
         private static Accessor _SetMorphTarget(MeshPrimitive primitive, int morphTargetIndex, Dictionary<int, Vector3> morphs1Pos)
         {
             var accessor = primitive.LogicalParent.LogicalParent.CreateAccessor();
-            accessor.SetZeros(3, DimensionType.VEC3, EncodingType.FLOAT, false);
+            accessor.SetZeros(3, AttributeFormat.Float3);
             accessor.CreateSparseData(morphs1Pos);
 
             var attributes = new Dictionary<string, Accessor>();

+ 3 - 2
tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Numerics;
 using System.Text;
 
 using NUnit.Framework;
@@ -253,9 +254,9 @@ namespace SharpGLTF.Schema2.LoadAndSave
 
             if (!accessor._TryGetMemoryAccessor(out var baseMem)) Assert.Fail("can't get underlaying data");
 
-            var basePositions = baseMem.AsVector3Array();
+            var basePositions = baseMem.AsArrayOf<Vector3>();
 
-            var positions = accessor.AsVector3Array();
+            var positions = accessor.AsArrayOf<Vector3>();
         }
 
         [Test]