Browse Source

SparseWeight8 struct nearly finished

Vicente Penades 6 years ago
parent
commit
9f95eed2d4

+ 186 - 0
src/SharpGLTF.Core/Transforms/IndexWeight.cs

@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SharpGLTF.Transforms
+{
+
+    [System.Diagnostics.DebuggerDisplay("{Index} = {Weight}")]
+    readonly struct IndexWeight
+    {
+        #region constructor
+
+        public IndexWeight((int, float) pair)
+        {
+            Index = pair.Item1;
+            Weight = pair.Item2;
+        }
+
+        public static implicit operator IndexWeight((int, float) pair) {return new IndexWeight(pair.Item1, pair.Item2); }
+
+        public IndexWeight(int i, float w)
+        {
+            Index = i;
+            Weight = w;
+        }
+
+        #endregion
+
+        #region data
+
+        public readonly int Index;
+        public readonly float Weight;
+
+        #endregion
+
+        #region API
+
+        public static IndexWeight operator +(IndexWeight a, IndexWeight b)
+        {
+            System.Diagnostics.Debug.Assert(a.Index == b.Index);
+            return new IndexWeight(a.Index, a.Weight + b.Weight);
+        }
+
+        public static int IndexOf(Span<IndexWeight> span, int index)
+        {
+            for (int i = 0; i < span.Length; ++i)
+            {
+                if (span[i].Index == index) return i;
+            }
+
+            return -1;
+        }
+
+        private static IndexWeight GetSparseWeight(in SparseWeight8 src, int sparseIndex)
+        {
+            switch (sparseIndex)
+            {
+                case 0: return new IndexWeight(src.Index0, src.Weight0);
+                case 1: return new IndexWeight(src.Index1, src.Weight1);
+                case 2: return new IndexWeight(src.Index2, src.Weight2);
+                case 3: return new IndexWeight(src.Index3, src.Weight3);
+                case 4: return new IndexWeight(src.Index4, src.Weight4);
+                case 5: return new IndexWeight(src.Index5, src.Weight5);
+                case 6: return new IndexWeight(src.Index6, src.Weight6);
+                case 7: return new IndexWeight(src.Index7, src.Weight7);
+                default: throw new ArgumentOutOfRangeException(nameof(sparseIndex));
+            }
+        }
+
+        public static int CopyTo(in SparseWeight8 src, Span<IndexWeight> dst)
+        {
+            System.Diagnostics.Debug.Assert(dst.Length >= 8);
+
+            var offset = 0;
+
+            for (int i = 0; i < 8; ++i)
+            {
+                var pair = GetSparseWeight(src, i);
+                if (pair.Weight == 0) continue;
+
+                var idx = IndexOf(dst.Slice(0, offset), pair.Index);
+
+                if (idx < 0)
+                {
+                    // the index doesn't exist, insert it.
+                    dst[offset++] = pair;
+                }
+                else
+                {
+                    // the index already exists, so we aggregate the weights
+                    dst[idx] += pair;
+                }
+            }
+
+            return offset;
+        }
+
+        public static int CopyTo(in SparseWeight8 src, Span<int> dstIndices, Span<float> dstWeights, int dstLength)
+        {
+            System.Diagnostics.Debug.Assert(dstIndices.Length == dstWeights.Length);
+            System.Diagnostics.Debug.Assert(dstIndices.Length >= dstLength, $"{nameof(dstIndices)}.Length must be at least {nameof(dstLength)}");
+            System.Diagnostics.Debug.Assert(dstWeights.Length >= dstLength, $"{nameof(dstWeights)}.Length must be at least {nameof(dstLength)}");
+            System.Diagnostics.Debug.Assert(dstWeights.Slice(0, dstLength).ToArray().All(item => item == 0), "All weights must be zero");
+
+            for (int i = 0; i < 8; ++i)
+            {
+                var pair = GetSparseWeight(src, i);
+                if (pair.Weight == 0) continue;
+
+                var idx = dstIndices
+                    .Slice(0, dstLength)
+                    .IndexOf(pair.Index);
+
+                if (idx < 0)
+                {
+                    // the index doesn't exist, insert it.
+                    dstIndices[dstLength] = pair.Index;
+                    dstWeights[dstLength] = pair.Weight;
+                    ++dstLength;
+                }
+                else
+                {
+                    // the index already exists, so we aggregate the weights
+                    dstWeights[idx] += pair.Weight;
+                }
+            }
+
+            return dstLength;
+        }
+
+        public static void BubbleSortByWeight(Span<IndexWeight> pairs)
+        {
+            for (int i = 0; i < pairs.Length - 1; ++i)
+            {
+                bool sorted = true;
+
+                for (int j = 1; j < pairs.Length; ++j)
+                {
+                    var k = j - 1;
+
+                    var kk = pairs[k];
+                    var jj = pairs[j];
+
+                    if (kk.Weight  > jj.Weight) continue;
+                    if (kk.Weight == jj.Weight && kk.Index < jj.Index) continue;
+
+                    pairs[k] = jj;
+                    pairs[j] = kk;
+
+                    sorted = false;
+                }
+
+                if (sorted) return;
+            }
+        }
+
+        public static void BubbleSortByIndex(Span<IndexWeight> pairs)
+        {
+            for (int i = 0; i < pairs.Length - 1; ++i)
+            {
+                bool sorted = true;
+
+                for (int j = 1; j < pairs.Length; ++j)
+                {
+                    var k = j - 1;
+
+                    var kk = pairs[k];
+                    var jj = pairs[j];
+
+                    if (kk.Index  < jj.Index) continue;
+                    if (kk.Index == jj.Index && kk.Weight > jj.Weight) continue;
+
+                    pairs[k] = jj;
+                    pairs[j] = kk;
+
+                    sorted = false;
+                }
+
+                if (sorted) return;
+            }
+        }
+
+        #endregion
+    }
+}

+ 5 - 52
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -77,54 +77,7 @@ namespace SharpGLTF.Transforms
                 return;
                 return;
             }
             }
 
 
-            _Weights = Normalize(morphWeights);
-        }
-
-        /// <summary>
-        /// Increments all indices and adds a new Index[0] with a weight that makes the sum of all weights equal to 1
-        /// </summary>
-        /// <param name="r">A <see cref="SparseWeight8"/> object representing the weights of the morph targets.</param>
-        /// <returns>A <see cref="SparseWeight8"/> representing the morph target weights, compensated with the weight of the master values.</returns>
-        internal static SparseWeight8 Normalize(SparseWeight8 r)
-        {
-            int i = -1;
-            float w = float.MaxValue;
-            float ww = 0;
-
-            ww += r.Weight0; if (r.Weight0 < w) { i = 0; w = r.Weight0; }
-            ww += r.Weight1; if (r.Weight1 < w) { i = 1; w = r.Weight1; }
-            ww += r.Weight2; if (r.Weight2 < w) { i = 2; w = r.Weight2; }
-            ww += r.Weight3; if (r.Weight3 < w) { i = 3; w = r.Weight3; }
-            ww += r.Weight4; if (r.Weight4 < w) { i = 4; w = r.Weight4; }
-            ww += r.Weight5; if (r.Weight5 < w) { i = 5; w = r.Weight5; }
-            ww += r.Weight6; if (r.Weight6 < w) { i = 6; w = r.Weight6; }
-            ww += r.Weight7; if (r.Weight7 < w) { i = 7; w = r.Weight7; }
-
-            r.Index0 += 1;
-            r.Index1 += 1;
-            r.Index2 += 1;
-            r.Index3 += 1;
-            r.Index4 += 1;
-            r.Index5 += 1;
-            r.Index6 += 1;
-            r.Index7 += 1;
-
-            ww -= w;
-            var iw = 1 - ww;
-
-            switch (i)
-            {
-                case 0: r.Index0 = 0; r.Weight0 = iw; break;
-                case 1: r.Index1 = 0; r.Weight1 = iw; break;
-                case 2: r.Index2 = 0; r.Weight2 = iw; break;
-                case 3: r.Index3 = 0; r.Weight3 = iw; break;
-                case 4: r.Index4 = 0; r.Weight4 = iw; break;
-                case 5: r.Index5 = 0; r.Weight5 = iw; break;
-                case 6: r.Index6 = 0; r.Weight6 = iw; break;
-                case 7: r.Index7 = 0; r.Weight7 = iw; break;
-            }
-
-            return r;
+            _Weights = morphWeights.GetNormalizedWithComplement();
         }
         }
 
 
         protected V3 MorphVectors(V3 value, V3[] morphTargets)
         protected V3 MorphVectors(V3 value, V3[] morphTargets)
@@ -137,7 +90,7 @@ namespace SharpGLTF.Transforms
 
 
             if (_AbsoluteMorphTargets)
             if (_AbsoluteMorphTargets)
             {
             {
-                foreach (var pair in _Weights.GetSparseWeights())
+                foreach (var pair in _Weights.GetNonZeroWeights())
                 {
                 {
                     var val = pair.Item1 == 0 ? value : morphTargets[pair.Item1 - 1];
                     var val = pair.Item1 == 0 ? value : morphTargets[pair.Item1 - 1];
                     p += val * pair.Item2;
                     p += val * pair.Item2;
@@ -145,7 +98,7 @@ namespace SharpGLTF.Transforms
             }
             }
             else
             else
             {
             {
-                foreach (var pair in _Weights.GetSparseWeights())
+                foreach (var pair in _Weights.GetNonZeroWeights())
                 {
                 {
                     var val = pair.Item1 == 0 ? value : value + morphTargets[pair.Item1 - 1];
                     var val = pair.Item1 == 0 ? value : value + morphTargets[pair.Item1 - 1];
                     p += val * pair.Item2;
                     p += val * pair.Item2;
@@ -165,7 +118,7 @@ namespace SharpGLTF.Transforms
 
 
             if (_AbsoluteMorphTargets)
             if (_AbsoluteMorphTargets)
             {
             {
-                foreach (var pair in _Weights.GetSparseWeights())
+                foreach (var pair in _Weights.GetNonZeroWeights())
                 {
                 {
                     var val = pair.Item1 == 0 ? value : morphTargets[pair.Item1 - 1];
                     var val = pair.Item1 == 0 ? value : morphTargets[pair.Item1 - 1];
                     p += val * pair.Item2;
                     p += val * pair.Item2;
@@ -173,7 +126,7 @@ namespace SharpGLTF.Transforms
             }
             }
             else
             else
             {
             {
-                foreach (var pair in _Weights.GetSparseWeights())
+                foreach (var pair in _Weights.GetNonZeroWeights())
                 {
                 {
                     var val = pair.Item1 == 0 ? value : value + morphTargets[pair.Item1 - 1];
                     var val = pair.Item1 == 0 ? value : value + morphTargets[pair.Item1 - 1];
                     p += val * pair.Item2;
                     p += val * pair.Item2;

+ 280 - 254
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -16,15 +16,27 @@ namespace SharpGLTF.Transforms
     /// - As an animation key in morph targets; a mesh can have many morph targets, but realistically and due to GPU limitations, only up to 8 morph targets can be blended at the same time.
     /// - As an animation key in morph targets; a mesh can have many morph targets, but realistically and due to GPU limitations, only up to 8 morph targets can be blended at the same time.
     /// </remarks>
     /// </remarks>
     [System.Diagnostics.DebuggerDisplay("[{Index0}]={Weight0}  [{Index1}]={Weight1}  [{Index2}]={Weight2}  [{Index3}]={Weight3}  [{Index4}]={Weight4}  [{Index5}]={Weight5}  [{Index6}]={Weight6}  [{Index7}]={Weight7}")]
     [System.Diagnostics.DebuggerDisplay("[{Index0}]={Weight0}  [{Index1}]={Weight1}  [{Index2}]={Weight2}  [{Index3}]={Weight3}  [{Index4}]={Weight4}  [{Index5}]={Weight5}  [{Index6}]={Weight6}  [{Index7}]={Weight7}")]
-    public struct SparseWeight8 : IReadOnlyList<float>
+    public readonly struct SparseWeight8 : IReadOnlyList<float>
     {
     {
-        #region lifecycle
+        #region constructors
 
 
+        /// <summary>
+        /// Creates a new <see cref="SparseWeight8"/> from a weights collection.
+        /// If there's more than 8 non zero values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="weights">A sequence of weight values.</param>
+        /// <returns>A <see cref="SparseWeight8"/> instance.</returns>
         public static SparseWeight8 Create(params float[] weights)
         public static SparseWeight8 Create(params float[] weights)
         {
         {
             return Create((IEnumerable<float>)weights);
             return Create((IEnumerable<float>)weights);
         }
         }
 
 
+        /// <summary>
+        /// Creates a new <see cref="SparseWeight8"/> from a weights collection.
+        /// If there's more than 8 non zero values, the 8 most representative values are taken
+        /// </summary>
+        /// <param name="weights">A sequence of weight values.</param>
+        /// <returns>A <see cref="SparseWeight8"/> instance.</returns>
         public static SparseWeight8 Create(IEnumerable<float> weights)
         public static SparseWeight8 Create(IEnumerable<float> weights)
         {
         {
             if (weights == null) return default;
             if (weights == null) return default;
@@ -39,29 +51,37 @@ namespace SharpGLTF.Transforms
             return Create(indexedWeights);
             return Create(indexedWeights);
         }
         }
 
 
+        /// <summary>
+        /// Creates a new <see cref="SparseWeight8"/> from an indexed weight collection.
+        /// If there's more than 8 non zero values, the 8 most representative values are taken
+        /// </summary>
+        /// <param name="pairs">A sequence of indexed weight values.</param>
+        /// <returns>A <see cref="SparseWeight8"/> instance.</returns>
         public static SparseWeight8 Create(params (int, float)[] pairs)
         public static SparseWeight8 Create(params (int, float)[] pairs)
         {
         {
             if (pairs == null) return default;
             if (pairs == null) return default;
 
 
-            Span<int> indices = stackalloc int[pairs.Length];
-            Span<float> weights = stackalloc float[pairs.Length];
+            Span<IndexWeight> sparse = stackalloc IndexWeight[pairs.Length];
 
 
             for (int i = 0; i < pairs.Length; ++i)
             for (int i = 0; i < pairs.Length; ++i)
             {
             {
-                indices[i] = pairs[i].Item1;
-                weights[i] = pairs[i].Item2;
+                sparse[i] = pairs[i];
             }
             }
 
 
             if (pairs.Length > 8)
             if (pairs.Length > 8)
             {
             {
-                BubbleSortByWeight(indices, weights, indices.Length);
-                indices = indices.Slice(0, 8);
-                weights = weights.Slice(0, 8);
+                IndexWeight.BubbleSortByWeight(sparse);
+                sparse = sparse.Slice(0, 8);
             }
             }
 
 
-            return new SparseWeight8(indices, weights);
+            return new SparseWeight8(sparse);
         }
         }
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SparseWeight8"/> struct.
+        /// </summary>
+        /// <param name="idx0123">The indices of weights 0 to 3.</param>
+        /// <param name="wgt0123">The weights of indices 0 to 3.</param>
         public SparseWeight8(in Vector4 idx0123, in Vector4 wgt0123)
         public SparseWeight8(in Vector4 idx0123, in Vector4 wgt0123)
         {
         {
             Index0 = (int)idx0123.X;
             Index0 = (int)idx0123.X;
@@ -85,6 +105,13 @@ namespace SharpGLTF.Transforms
             Weight7 = 0;
             Weight7 = 0;
         }
         }
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SparseWeight8"/> struct.
+        /// </summary>
+        /// <param name="idx0123">The indices of weights 0 to 3.</param>
+        /// <param name="idx4567">The indices of weights 4 to 7.</param>
+        /// <param name="wgt0123">The weights of indices 0 to 3.</param>
+        /// <param name="wgt4567">The weights of indices 4 to 7.</param>
         public SparseWeight8(in Vector4 idx0123, in Vector4 idx4567, in Vector4 wgt0123, in Vector4 wgt4567)
         public SparseWeight8(in Vector4 idx0123, in Vector4 idx4567, in Vector4 wgt0123, in Vector4 wgt4567)
         {
         {
             Index0 = (int)idx0123.X;
             Index0 = (int)idx0123.X;
@@ -108,82 +135,120 @@ namespace SharpGLTF.Transforms
             Weight7 = wgt4567.W;
             Weight7 = wgt4567.W;
         }
         }
 
 
-        private SparseWeight8(ReadOnlySpan<int> indices, ReadOnlySpan<float> weights)
+        private SparseWeight8(ReadOnlySpan<IndexWeight> pairs)
+        {
+            System.Diagnostics.Debug.Assert(pairs.Length <= 8, nameof(pairs));
+
+            this = default;
+
+            if (pairs.Length < 1) return;
+            this.Index0 = pairs[0].Index;
+            this.Weight0 = pairs[0].Weight;
+
+            if (pairs.Length < 2) return;
+            this.Index1 = pairs[1].Index;
+            this.Weight1 = pairs[1].Weight;
+
+            if (pairs.Length < 3) return;
+            this.Index2 = pairs[2].Index;
+            this.Weight2 = pairs[2].Weight;
+
+            if (pairs.Length < 4) return;
+            this.Index3 = pairs[3].Index;
+            this.Weight3 = pairs[3].Weight;
+
+            if (pairs.Length < 5) return;
+            this.Index4 = pairs[4].Index;
+            this.Weight4 = pairs[4].Weight;
+
+            if (pairs.Length < 6) return;
+            this.Index5 = pairs[5].Index;
+            this.Weight5 = pairs[5].Weight;
+
+            if (pairs.Length < 7) return;
+            this.Index6 = pairs[6].Index;
+            this.Weight6 = pairs[6].Weight;
+
+            if (pairs.Length < 8) return;
+            this.Index7 = pairs[7].Index;
+            this.Weight7 = pairs[7].Weight;
+        }
+
+        private SparseWeight8(in SparseWeight8 sparse, float scale)
         {
         {
-            System.Diagnostics.Debug.Assert(indices.Length <= 8, nameof(indices));
-            System.Diagnostics.Debug.Assert(indices.Length == weights.Length, nameof(weights));
-
-            this.Index0 = indices.Length > 0 ? indices[0] : 0;
-            this.Index1 = indices.Length > 1 ? indices[1] : 0;
-            this.Index2 = indices.Length > 2 ? indices[2] : 0;
-            this.Index3 = indices.Length > 3 ? indices[3] : 0;
-            this.Index4 = indices.Length > 4 ? indices[4] : 0;
-            this.Index5 = indices.Length > 5 ? indices[5] : 0;
-            this.Index6 = indices.Length > 6 ? indices[6] : 0;
-            this.Index7 = indices.Length > 7 ? indices[7] : 0;
-
-            this.Weight0 = weights.Length > 0 ? weights[0] : 0;
-            this.Weight1 = weights.Length > 1 ? weights[1] : 0;
-            this.Weight2 = weights.Length > 2 ? weights[2] : 0;
-            this.Weight3 = weights.Length > 3 ? weights[3] : 0;
-            this.Weight4 = weights.Length > 4 ? weights[4] : 0;
-            this.Weight5 = weights.Length > 5 ? weights[5] : 0;
-            this.Weight6 = weights.Length > 6 ? weights[6] : 0;
-            this.Weight7 = weights.Length > 7 ? weights[7] : 0;
+            Index0 = sparse.Index0;
+            Index1 = sparse.Index1;
+            Index2 = sparse.Index2;
+            Index3 = sparse.Index3;
+            Index4 = sparse.Index4;
+            Index5 = sparse.Index5;
+            Index6 = sparse.Index6;
+            Index7 = sparse.Index7;
+            Weight0 = sparse.Weight0 * scale;
+            Weight1 = sparse.Weight1 * scale;
+            Weight2 = sparse.Weight2 * scale;
+            Weight3 = sparse.Weight3 * scale;
+            Weight4 = sparse.Weight4 * scale;
+            Weight5 = sparse.Weight5 * scale;
+            Weight6 = sparse.Weight6 * scale;
+            Weight7 = sparse.Weight7 * scale;
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region data
         #region data
 
 
-        public int Index0;
-        public int Index1;
-        public int Index2;
-        public int Index3;
-        public int Index4;
-        public int Index5;
-        public int Index6;
-        public int Index7;
-
-        public float Weight0;
-        public float Weight1;
-        public float Weight2;
-        public float Weight3;
-        public float Weight4;
-        public float Weight5;
-        public float Weight6;
-        public float Weight7;
+        public readonly int Index0;
+        public readonly int Index1;
+        public readonly int Index2;
+        public readonly int Index3;
+        public readonly int Index4;
+        public readonly int Index5;
+        public readonly int Index6;
+        public readonly int Index7;
+
+        public readonly float Weight0;
+        public readonly float Weight1;
+        public readonly float Weight2;
+        public readonly float Weight3;
+        public readonly float Weight4;
+        public readonly float Weight5;
+        public readonly float Weight6;
+        public readonly float Weight7;
 
 
         public static bool AreWeightsEqual(in SparseWeight8 x, in SparseWeight8 y)
         public static bool AreWeightsEqual(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
-            Span<int>   indices = stackalloc int[16];
-            Span<float> xWeights = stackalloc float[16];
-            Span<float> yWeights = stackalloc float[16];
+            const int STACKSIZE = 8 * 2;
 
 
-            int c = 0;
-            c = CopyTo(x, indices, xWeights, c);
-            c = CopyTo(y, indices, yWeights, c);
+            Span<int>   indices = stackalloc int[STACKSIZE];
+            Span<float> xWeights = stackalloc float[STACKSIZE];
+            Span<float> yWeights = stackalloc float[STACKSIZE];
 
 
-            xWeights = xWeights.Slice(0, c);
-            yWeights = yWeights.Slice(0, c);
+            int offset = 0;
+            offset = IndexWeight.CopyTo(x, indices, xWeights, offset);
+            offset = IndexWeight.CopyTo(y, indices, yWeights, offset);
+
+            xWeights = xWeights.Slice(0, offset);
+            yWeights = yWeights.Slice(0, offset);
 
 
             return xWeights.SequenceEqual(yWeights);
             return xWeights.SequenceEqual(yWeights);
         }
         }
 
 
         public int GetWeightsHashCode()
         public int GetWeightsHashCode()
         {
         {
-            Span<int> indices = stackalloc int[8];
-            Span<float> weights = stackalloc float[8];
+            Span<IndexWeight> iw = stackalloc IndexWeight[8];
 
 
-            var c = CopyTo(this, indices, weights, 0);
+            var c = IndexWeight.CopyTo(this, iw);
 
 
-            BubbleSortByIndex(indices, weights, c);
+            iw = iw.Slice(0, c);
+
+            IndexWeight.BubbleSortByIndex(iw);
 
 
             int h = 0;
             int h = 0;
 
 
-            for (int i = 0; i < c; ++i)
+            for (int i = 0; i < iw.Length; ++i)
             {
             {
-                h += indices[i].GetHashCode() ^ weights[i].GetHashCode();
+                h += iw[i].GetHashCode();
                 h *= 17;
                 h *= 17;
             }
             }
 
 
@@ -211,77 +276,100 @@ namespace SharpGLTF.Transforms
 
 
         #region API
         #region API
 
 
+        /// <summary>
+        /// Returns a copy of this <see cref="SparseWeight8"/> where all the
+        /// indices have been reordered by weight in descending order.
+        /// </summary>
+        /// <param name="sparse">The <see cref="SparseWeight8"/> to get ordered.</param>
+        /// <returns>A weight ordered <see cref="SparseWeight8"/>.</returns>
         public static SparseWeight8 OrderedByWeight(in SparseWeight8 sparse)
         public static SparseWeight8 OrderedByWeight(in SparseWeight8 sparse)
         {
         {
-            Span<int> indices = stackalloc int[8];
-            Span<float> weights = stackalloc float[8];
+            Span<IndexWeight> iw = stackalloc IndexWeight[8];
+
+            var c = IndexWeight.CopyTo(sparse, iw);
 
 
-            var c = CopyTo(sparse, indices, weights, 0);
-            BubbleSortByWeight(indices, weights, c);
+            iw = iw.Slice(0, c);
 
 
-            indices = indices.Slice(0, c);
-            weights = weights.Slice(0, c);
+            IndexWeight.BubbleSortByWeight(iw);
 
 
-            return new SparseWeight8(indices, weights);
+            return new SparseWeight8(iw);
         }
         }
 
 
+        /// <summary>
+        /// Returns a copy of this <see cref="SparseWeight8"/> where all the
+        /// indices have been reordered by index in ascending order.
+        /// </summary>
+        /// <param name="sparse">The <see cref="SparseWeight8"/> to get ordered.</param>
+        /// <returns>An index ordered <see cref="SparseWeight8"/>.</returns>
         public static SparseWeight8 OrderedByIndex(in SparseWeight8 sparse)
         public static SparseWeight8 OrderedByIndex(in SparseWeight8 sparse)
         {
         {
-            Span<int> indices = stackalloc int[8];
-            Span<float> weights = stackalloc float[8];
+            Span<IndexWeight> iw = stackalloc IndexWeight[8];
 
 
-            var c = CopyTo(sparse, indices, weights, 0);
-            BubbleSortByIndex(indices, weights, c);
+            var c = IndexWeight.CopyTo(sparse, iw);
 
 
-            indices = indices.Slice(0, c);
-            weights = weights.Slice(0, c);
+            iw = iw.Slice(0, c);
 
 
-            return new SparseWeight8(indices, weights);
+            IndexWeight.BubbleSortByIndex(iw);
+
+            return new SparseWeight8(iw);
         }
         }
 
 
+        /// <summary>
+        /// Adds <paramref name="x"/> with <paramref name="y"/> element wise.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first operand.</param>
+        /// <param name="y">The second operand.</param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 Add(in SparseWeight8 x, in SparseWeight8 y)
         public static SparseWeight8 Add(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
             return _OperateLinear(x, y, (xx, yy) => xx + yy);
             return _OperateLinear(x, y, (xx, yy) => xx + yy);
         }
         }
 
 
+        /// <summary>
+        /// Subtracts <paramref name="y"/> from <paramref name="x"/> element wise.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first operand.</param>
+        /// <param name="y">The second operand.</param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 Subtract(in SparseWeight8 x, in SparseWeight8 y)
         public static SparseWeight8 Subtract(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
             return _OperateLinear(x, y, (xx, yy) => xx - yy);
             return _OperateLinear(x, y, (xx, yy) => xx - yy);
         }
         }
 
 
+        /// <summary>
+        /// Multiplies <paramref name="x"/> with <paramref name="y"/> element wise.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first operand.</param>
+        /// <param name="y">The second operand.</param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 Multiply(in SparseWeight8 x, in SparseWeight8 y)
         public static SparseWeight8 Multiply(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
             return _OperateLinear(x, y, (xx, yy) => xx * yy);
             return _OperateLinear(x, y, (xx, yy) => xx * yy);
         }
         }
 
 
+        /// <summary>
+        /// Multiplies <paramref name="x"/> with <paramref name="y"/> element wise.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first operand.</param>
+        /// <param name="y">The second operand.</param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 Multiply(in SparseWeight8 x, Single y)
         public static SparseWeight8 Multiply(in SparseWeight8 x, Single y)
         {
         {
-            return new SparseWeight8
-            {
-                Index0 = x.Index0,
-                Index1 = x.Index1,
-                Index2 = x.Index2,
-                Index3 = x.Index3,
-                Index4 = x.Index4,
-                Index5 = x.Index5,
-                Index6 = x.Index6,
-                Index7 = x.Index7,
-                Weight0 = x.Weight0 * y,
-                Weight1 = x.Weight1 * y,
-                Weight2 = x.Weight2 * y,
-                Weight3 = x.Weight3 * y,
-                Weight4 = x.Weight4 * y,
-                Weight5 = x.Weight5 * y,
-                Weight6 = x.Weight6 * y,
-                Weight7 = x.Weight7 * y
-            };
-        }
-
-        public static SparseWeight8 Divide(in SparseWeight8 x, in SparseWeight8 y)
-        {
-            return _OperateLinear(x, y, (xx, yy) => xx / yy);
+            return new SparseWeight8(x, y);
         }
         }
 
 
+        /// <summary>
+        /// Interpolates Linearly <paramref name="x"/> with <paramref name="y"/> an <paramref name="amount"/>.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first operand.</param>
+        /// <param name="y">The second operand.</param>
+        /// <param name="amount">The amount of <paramref name="y"/></param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 InterpolateLinear(in SparseWeight8 x, in SparseWeight8 y, float amount)
         public static SparseWeight8 InterpolateLinear(in SparseWeight8 x, in SparseWeight8 y, float amount)
         {
         {
             var xAmount = 1.0f - amount;
             var xAmount = 1.0f - amount;
@@ -290,6 +378,16 @@ namespace SharpGLTF.Transforms
             return _OperateLinear(x, y, (xx, yy) => (xx * xAmount) + (yy * yAmount));
             return _OperateLinear(x, y, (xx, yy) => (xx * xAmount) + (yy * yAmount));
         }
         }
 
 
+        /// <summary>
+        /// Interpolates (<paramref name="x"/> , <paramref name="xt"/>) with (<paramref name="y"/> , <paramref name="yt"/>) an <paramref name="amount"/>.
+        /// If there's more than 8 non zero result values, the 8 most representative values are taken.
+        /// </summary>
+        /// <param name="x">The first value operand.</param>
+        /// <param name="xt">The first tangent operand.</param>
+        /// <param name="y">The second value operand.</param>
+        /// <param name="yt">The second tangent operand.</param>
+        /// <param name="amount">The amount of <paramref name="y"/></param>
+        /// <returns>A new <see cref="SparseWeight8"/></returns>
         public static SparseWeight8 InterpolateCubic(in SparseWeight8 x, in SparseWeight8 xt, in SparseWeight8 y, in SparseWeight8 yt, float amount)
         public static SparseWeight8 InterpolateCubic(in SparseWeight8 x, in SparseWeight8 xt, in SparseWeight8 y, in SparseWeight8 yt, float amount)
         {
         {
             var basis = Animations.SamplerFactory.CreateHermitePointWeights(amount);
             var basis = Animations.SamplerFactory.CreateHermitePointWeights(amount);
@@ -309,25 +407,6 @@ namespace SharpGLTF.Transforms
 
 
         IEnumerator IEnumerable.GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
         IEnumerator IEnumerable.GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
 
 
-        public bool ContainsKey(int key)
-        {
-            return GetSparseWeights().Select(item => item.Item1).Contains(key);
-        }
-
-        public bool TryGetValue(int key, out float value)
-        {
-            if (key == Index0) { value = Weight0; return true; }
-            if (key == Index1) { value = Weight1; return true; }
-            if (key == Index2) { value = Weight2; return true; }
-            if (key == Index3) { value = Weight3; return true; }
-            if (key == Index4) { value = Weight4; return true; }
-            if (key == Index5) { value = Weight5; return true; }
-            if (key == Index6) { value = Weight6; return true; }
-            if (key == Index7) { value = Weight7; return true; }
-            value = 0;
-            return false;
-        }
-
         public override string ToString()
         public override string ToString()
         {
         {
             var sb = new StringBuilder();
             var sb = new StringBuilder();
@@ -347,46 +426,6 @@ namespace SharpGLTF.Transforms
 
 
         #region code
         #region code
 
 
-        /// <summary>
-        /// Copies the current index-weight pairs to available slots in destination <paramref name="dstIndices"/> and <paramref name="dstWeights"/>
-        /// </summary>
-        /// <param name="src">The source <see cref="SparseWeight8"/>.</param>
-        /// <param name="dstIndices">The destination index array.</param>
-        /// <param name="dstWeights">The destination weight array.</param>
-        /// <param name="dstLength">The explicit length of both <paramref name="dstIndices"/> and <paramref name="dstWeights"/></param>
-        /// <returns>The new length of the destination arrays.</returns>
-        private static int CopyTo(in SparseWeight8 src, Span<int> dstIndices, Span<float> dstWeights, int dstLength)
-        {
-            System.Diagnostics.Debug.Assert(dstIndices.Length == dstWeights.Length);
-            System.Diagnostics.Debug.Assert(dstIndices.Length >= dstLength, $"{nameof(dstIndices)}.Length must be at least {nameof(dstLength)}");
-            System.Diagnostics.Debug.Assert(dstWeights.Length >= dstLength, $"{nameof(dstWeights)}.Length must be at least {nameof(dstLength)}");
-            System.Diagnostics.Debug.Assert(dstWeights.Slice(0, dstLength).ToArray().All(item => item == 0), "All weights must be zero");
-
-            for (int i = 0; i < 8; ++i)
-            {
-                var pair = src.GetSparseWeight(i);
-                if (pair.Item2 == 0) continue;
-                var idx = dstIndices
-                    .Slice(0, dstLength)
-                    .IndexOf(pair.Item1);
-
-                if (idx < 0)
-                {
-                    // the index doesn't exist, insert it to the list.
-                    dstIndices[dstLength] = pair.Item1;
-                    dstWeights[dstLength] = pair.Item2;
-                    ++dstLength;
-                }
-                else
-                {
-                    // the index already exists, so we aggregate the weights
-                    dstWeights[idx] += pair.Item2;
-                }
-            }
-
-            return dstLength;
-        }
-
         /// <summary>
         /// <summary>
         /// Performs <paramref name="operationFunc"/> over all the elements of the operands.
         /// Performs <paramref name="operationFunc"/> over all the elements of the operands.
         /// </summary>
         /// </summary>
@@ -396,32 +435,41 @@ namespace SharpGLTF.Transforms
         /// <returns>A new <see cref="SparseWeight8"/>.</returns>
         /// <returns>A new <see cref="SparseWeight8"/>.</returns>
         private static SparseWeight8 _OperateLinear(in SparseWeight8 x, in SparseWeight8 y, Func<float, float, float> operationFunc)
         private static SparseWeight8 _OperateLinear(in SparseWeight8 x, in SparseWeight8 y, Func<float, float, float> operationFunc)
         {
         {
+            const int STACKSIZE = 8 * 2;
+
             // prepare buffers in the stack
             // prepare buffers in the stack
-            Span<int> indices = stackalloc int[16];
-            Span<float> xxx = stackalloc float[16];
-            Span<float> yyy = stackalloc float[16];
+            Span<int> indices = stackalloc int[STACKSIZE];
+            Span<float> xxx = stackalloc float[STACKSIZE];
+            Span<float> yyy = stackalloc float[STACKSIZE];
 
 
             // fill the buffers so all the elements are aligned by column
             // fill the buffers so all the elements are aligned by column
             int offset = 0;
             int offset = 0;
-            offset = CopyTo(x, indices, xxx, offset);
-            offset = CopyTo(y, indices, yyy, offset);
+            offset = IndexWeight.CopyTo(x, indices, xxx, offset);
+            offset = IndexWeight.CopyTo(y, indices, yyy, offset);
 
 
             // perform operation element by element
             // perform operation element by element
+
+            int r = 0;
+            Span<IndexWeight> rrr = stackalloc IndexWeight[STACKSIZE];
+
             for (int i = 0; i < offset; ++i)
             for (int i = 0; i < offset; ++i)
             {
             {
-                xxx[i] = operationFunc(xxx[i], yyy[i]);
-            }
+                var ww = operationFunc(xxx[i], yyy[i]);
 
 
-            // sort results by relevance, so they can fit
-            // in a new structure in case there's more than
-            // 8 results
+                if (ww == 0) continue;
 
 
-            indices = indices.Slice(0, offset);
-            xxx = xxx.Slice(0, offset);
+                rrr[r++] = new IndexWeight(indices[i], ww);
+            }
 
 
-            BubbleSortByWeight(indices, xxx, offset);
+            rrr = rrr.Slice(0, r);
 
 
-            return new SparseWeight8(indices, xxx);
+            if (rrr.Length > 8)
+            {
+                IndexWeight.BubbleSortByWeight(rrr);
+                rrr = rrr.Slice(0, 8);
+            }
+
+            return new SparseWeight8(rrr);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -442,89 +490,48 @@ namespace SharpGLTF.Transforms
             Func<float, float, float, float, float> operationFunc
             Func<float, float, float, float, float> operationFunc
             )
             )
         {
         {
+            const int STACKSIZE = 8 * 4;
+
             // prepare buffers in the stack
             // prepare buffers in the stack
-            Span<int> indices = stackalloc int[32];
-            Span<float> xxx = stackalloc float[32];
-            Span<float> yyy = stackalloc float[32];
-            Span<float> zzz = stackalloc float[32];
-            Span<float> www = stackalloc float[32];
+            Span<int> indices = stackalloc int[STACKSIZE];
+            Span<float> xxx = stackalloc float[STACKSIZE];
+            Span<float> yyy = stackalloc float[STACKSIZE];
+            Span<float> zzz = stackalloc float[STACKSIZE];
+            Span<float> www = stackalloc float[STACKSIZE];
 
 
             // fill the buffers so all the elements are aligned by column
             // fill the buffers so all the elements are aligned by column
             int offset = 0;
             int offset = 0;
-            offset = CopyTo(x, indices, xxx, offset);
-            offset = CopyTo(y, indices, yyy, offset);
-            offset = CopyTo(z, indices, zzz, offset);
-            offset = CopyTo(w, indices, www, offset);
+            offset = IndexWeight.CopyTo(x, indices, xxx, offset);
+            offset = IndexWeight.CopyTo(y, indices, yyy, offset);
+            offset = IndexWeight.CopyTo(z, indices, zzz, offset);
+            offset = IndexWeight.CopyTo(w, indices, www, offset);
 
 
             // perform operation element by element
             // perform operation element by element
-            for (int i = 0; i < offset; ++i)
-            {
-                xxx[i] = operationFunc(xxx[i], yyy[i], zzz[i], www[i]);
-            }
-
-            // sort results by relevance, so they can fit
-            // in a new structure in case there's more than
-            // 8 results
-
-            indices = indices.Slice(0, offset);
-            xxx = xxx.Slice(0, offset);
-
-            BubbleSortByWeight(indices, xxx, offset);
-
-            return new SparseWeight8(indices, xxx);
-        }
 
 
-        private static void BubbleSortByWeight(Span<int> indices, Span<float> weights, int count = int.MaxValue)
-        {
-            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
-
-            count = Math.Min(indices.Length, count);
+            int r = 0;
+            Span<IndexWeight> rrr = stackalloc IndexWeight[STACKSIZE];
 
 
-            for (int i = 0; i < count - 1; ++i)
+            for (int i = 0; i < offset; ++i)
             {
             {
-                for (int j = i + 1; j < count; ++j)
-                {
-                    if (weights[i] > weights[j]) continue;
-
-                    if (weights[i] == weights[j] && indices[i] < indices[j]) continue;
+                var ww = operationFunc(xxx[i], yyy[i], zzz[i], www[i]);
 
 
-                    var index = indices[i];
-                    indices[i] = indices[j];
-                    indices[j] = index;
+                if (ww == 0) continue;
 
 
-                    var weight = weights[i];
-                    weights[i] = weights[j];
-                    weights[j] = weight;
-                }
+                rrr[r++] = new IndexWeight(indices[i], ww);
             }
             }
-        }
-
-        private static void BubbleSortByIndex(Span<int> indices, Span<float> weights, int count = int.MaxValue)
-        {
-            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
 
 
-            count = Math.Min(indices.Length, count);
+            rrr = rrr.Slice(0, r);
 
 
-            for (int i = 0; i < count - 1; ++i)
+            if (rrr.Length > 8)
             {
             {
-                for (int j = i + 1; j < count; ++j)
-                {
-                    if (indices[i] < indices[j]) continue;
-
-                    if (indices[i] == indices[j] && weights[i] > weights[j]) continue;
-
-                    var index = indices[i];
-                    indices[i] = indices[j];
-                    indices[j] = index;
-
-                    var weight = weights[i];
-                    weights[i] = weights[j];
-                    weights[j] = weight;
-                }
+                IndexWeight.BubbleSortByWeight(rrr);
+                rrr = rrr.Slice(0, 8);
             }
             }
+
+            return new SparseWeight8(rrr);
         }
         }
 
 
-        internal IEnumerable<(int, float)> GetSparseWeights()
+        internal IEnumerable<(int, float)> GetNonZeroWeights()
         {
         {
             if (Weight0 != 0) yield return (Index0, Weight0);
             if (Weight0 != 0) yield return (Index0, Weight0);
             if (Weight1 != 0) yield return (Index1, Weight1);
             if (Weight1 != 0) yield return (Index1, Weight1);
@@ -536,22 +543,6 @@ namespace SharpGLTF.Transforms
             if (Weight7 != 0) yield return (Index7, Weight7);
             if (Weight7 != 0) yield return (Index7, Weight7);
         }
         }
 
 
-        private (int, float) GetSparseWeight(int sparseIndex)
-        {
-            switch (sparseIndex)
-            {
-                case 0: return (Index0, Weight0);
-                case 1: return (Index1, Weight1);
-                case 2: return (Index2, Weight2);
-                case 3: return (Index3, Weight3);
-                case 4: return (Index4, Weight4);
-                case 5: return (Index5, Weight5);
-                case 6: return (Index6, Weight6);
-                case 7: return (Index7, Weight7);
-                default: throw new ArgumentOutOfRangeException(nameof(sparseIndex));
-            }
-        }
-
         private float GetExpandedAt(int idx)
         private float GetExpandedAt(int idx)
         {
         {
             if (idx == Index0) return Weight0;
             if (idx == Index0) return Weight0;
@@ -580,6 +571,41 @@ namespace SharpGLTF.Transforms
             return c;
             return c;
         }
         }
 
 
+        /// <summary>
+        /// Normalizes the current <see cref="SparseWeight8"/> by adding a complement weight
+        /// at index 0 that resolves <see cref="WeightSum"/> to 1.
+        /// </summary>
+        /// <returns>A new <see cref="SparseWeight8"/> with an extra weight.</returns>
+        internal SparseWeight8 GetNormalizedWithComplement()
+        {
+            Span<IndexWeight> weights = stackalloc IndexWeight[8 + 1];
+
+            var offset = IndexWeight.CopyTo(this, weights);
+
+            float ww = 0;
+
+            for (int i = 0; i < offset; ++i)
+            {
+                var w = weights[i].Weight;
+
+                ww += w;
+
+                weights[i] = new IndexWeight(weights[i].Index + 1, w);
+            }
+
+            weights[offset++] = new IndexWeight(0, 1 - ww);
+
+            weights = weights.Slice(0, offset);
+
+            if (offset > 8)
+            {
+                IndexWeight.BubbleSortByWeight(weights);
+                weights = weights.Slice(0, 8);
+            }
+
+            return new SparseWeight8(weights);
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -337,7 +337,7 @@ namespace SharpGLTF.Geometry
             for (int i = 0; i < Skinning.MaxBindings; ++i)
             for (int i = 0; i < Skinning.MaxBindings; ++i)
             {
             {
                 var jw = Skinning.GetJointBinding(i);
                 var jw = Skinning.GetJointBinding(i);
-                if (!jw.Weight._IsReal() || jw.Weight < 0 || jw.Joint < 0) sb.Append($" ❌𝐉𝐖{i} {jw.Joint}:{jw.Weight}");
+                if (!jw.Item2._IsReal() || jw.Item2 < 0 || jw.Item1 < 0) sb.Append($" ❌𝐉𝐖{i} {jw.Item1}:{jw.Item2}");
             }
             }
 
 
             return sb.ToString();
             return sb.ToString();

+ 5 - 5
src/SharpGLTF.Toolkit/Geometry/VertexTypes/FragmentPreprocessors.cs

@@ -114,11 +114,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             {
             {
                 var pair = vertex.GetJointBinding(i);
                 var pair = vertex.GetJointBinding(i);
 
 
-                Guard.MustBeGreaterThanOrEqualTo(pair.Joint, 0, $"Joint{i}");
-                Guard.IsTrue(pair.Weight._IsReal(), $"Weight{i}", "Values are not finite.");
-                if (pair.Weight == 0) Guard.IsTrue(pair.Joint == 0, "joints with weight zero must be set to zero");
+                Guard.MustBeGreaterThanOrEqualTo(pair.Item1, 0, $"Joint{i}");
+                Guard.IsTrue(pair.Item2._IsReal(), $"Weight{i}", "Values are not finite.");
+                if (pair.Item2 == 0) Guard.IsTrue(pair.Item1 == 0, "joints with weight zero must be set to zero");
 
 
-                weightsSum += pair.Weight;
+                weightsSum += pair.Item2;
             }
             }
 
 
             // TODO: check that joints are unique
             // TODO: check that joints are unique
@@ -232,7 +232,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             // Apparently the consensus is that weights are required to be normalized.
             // Apparently the consensus is that weights are required to be normalized.
             // More here: https://github.com/KhronosGroup/glTF/issues/1213
             // More here: https://github.com/KhronosGroup/glTF/issues/1213
 
 
-            var sparse = Transforms.SparseWeight8.OrderedByWeight(vertex.SparseWeights);
+            var sparse = Transforms.SparseWeight8.OrderedByWeight(vertex.GetWeights());
 
 
             var sum = sparse.WeightSum;
             var sum = sparse.WeightSum;
             if (sum == 0) return default(TvS);
             if (sum == 0) return default(TvS);

+ 16 - 17
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -18,30 +18,29 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public int MaxTextCoords => 0;
         public int MaxTextCoords => 0;
 
 
-        void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
+        void IVertexMaterial.SetColor(int index, Vector4 color) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }
+        void IVertexMaterial.SetTexCoord(int index, Vector2 coord) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        Vector4 IVertexMaterial.GetColor(int index) { throw new NotSupportedException(); }
+        Vector4 IVertexMaterial.GetColor(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        Vector2 IVertexMaterial.GetTexCoord(int index) { throw new NotSupportedException(); }
+        Vector2 IVertexMaterial.GetTexCoord(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        void IVertexSkinning.SetJointBinding(int index, int joint, float weight) { }
+        public SparseWeight8 GetWeights() { throw new NotSupportedException(); }
 
 
-        JointBinding IVertexSkinning.GetJointBinding(int index) { throw new NotSupportedException(); }
+        public void SetWeights(in SparseWeight8 weights) { throw new NotSupportedException(); }
 
 
-        public void SetWeights(in SparseWeight8 weights) { }
+        void IVertexSkinning.SetJointBinding(int index, int joint, float weight) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        public IEnumerable<JointBinding> JointBindings => Enumerable.Empty<JointBinding>();
+        (int, float) IVertexSkinning.GetJointBinding(int index) { throw new ArgumentOutOfRangeException(nameof(index)); }
 
 
-        public Vector4 JointsLow => Vector4.Zero;
-
-        public Vector4 JointsHigh => Vector4.Zero;
-
-        public Vector4 WeightsLow => Vector4.Zero;
-
-        public Vector4 Weightshigh => Vector4.Zero;
-
-        public SparseWeight8 SparseWeights => default(SparseWeight8);
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.JointsLow => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.JointsHigh => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.WeightsLow => Vector4.Zero;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        Vector4 IVertexSkinning.Weightshigh => Vector4.Zero;
     }
     }
 }
 }

+ 105 - 316
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -6,87 +6,18 @@ using SharpGLTF.Transforms;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
-    /// <summary>
-    /// Represents a a Node Joint index and its weight in a skinning system.
-    /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Joint} = {Weight}")]
-    public struct JointBinding
-    {
-        #region constructors
-
-        public JointBinding(int joint, float weight)
-        {
-            this.Joint = joint;
-            this.Weight = weight;
-            if (Weight == 0) Joint = 0;
-        }
-
-        public static implicit operator JointBinding((int, float) jw)
-        {
-            return new JointBinding(jw.Item1, jw.Item2);
-        }
-
-        #endregion
-
-        #region data
-
-        public int Joint;
-        public float Weight;
-
-        private static readonly _WeightComparer _DefaultWeightComparer = new _WeightComparer();
-
-        #endregion
-
-        #region properties
-
-        public static IComparer<JointBinding> WeightComparer => _DefaultWeightComparer;
-
-        #endregion
-
-        #region API
-
-        public static IEnumerable<JointBinding> GetBindings(IVertexSkinning vs)
-        {
-            for (int i = 0; i < vs.MaxBindings; ++i)
-            {
-                var jw = vs.GetJointBinding(i);
-                if (jw.Weight != 0) yield return jw;
-            }
-        }
-
-        #endregion
-
-        #region types
-
-        private sealed class _WeightComparer : IComparer<JointBinding>
-        {
-            public int Compare(JointBinding x, JointBinding y)
-            {
-                var a = x.Weight.CompareTo(y.Weight);
-                if (a != 0) return a;
-
-                return x.Joint.CompareTo(y.Joint);
-            }
-        }
-
-        #endregion
-    }
-
     public interface IVertexSkinning
     public interface IVertexSkinning
     {
     {
         int MaxBindings { get; }
         int MaxBindings { get; }
 
 
         void Validate();
         void Validate();
 
 
-        JointBinding GetJointBinding(int index);
-
+        (int, float) GetJointBinding(int index);
         void SetJointBinding(int index, int joint, float weight);
         void SetJointBinding(int index, int joint, float weight);
 
 
         void SetWeights(in SparseWeight8 weights);
         void SetWeights(in SparseWeight8 weights);
 
 
-        IEnumerable<JointBinding> JointBindings { get; }
-
-        SparseWeight8 SparseWeights { get; }
+        SparseWeight8 GetWeights();
 
 
         Vector4 JointsLow { get; }
         Vector4 JointsLow { get; }
         Vector4 JointsHigh { get; }
         Vector4 JointsHigh { get; }
@@ -104,73 +35,23 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexJoints8x4(int jointIndex)
         public VertexJoints8x4(int jointIndex)
         {
         {
-            Joints = new Vector4(jointIndex);
+            Joints = new Vector4(jointIndex, 0, 0, 0);
             Weights = Vector4.UnitX;
             Weights = Vector4.UnitX;
         }
         }
 
 
-        public VertexJoints8x4(JointBinding a, JointBinding b)
-        {
-            Joints = new Vector4(a.Joint, b.Joint, 0, 0);
-            Weights = new Vector4(a.Weight, b.Weight, 0, 0);
-
-            InPlaceSort();
-        }
-
-        public VertexJoints8x4(JointBinding a, JointBinding b, JointBinding c)
-        {
-            Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
-            Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
-
-            InPlaceSort();
-        }
-
-        public VertexJoints8x4(JointBinding a, JointBinding b, JointBinding c, JointBinding d)
-        {
-            Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
-            Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
-
-            InPlaceSort();
-        }
-
         public VertexJoints8x4(params (int, float)[] bindings)
         public VertexJoints8x4(params (int, float)[] bindings)
-        {
-            // var sparse = new Transforms.SparseWeight8(bindings);
-
-            Guard.NotNull(bindings, nameof(bindings));
-            Guard.MustBeBetweenOrEqualTo(bindings.Length, 1, 4, nameof(bindings));
-
-            Joints = Vector4.Zero;
-            Weights = Vector4.Zero;
-
-            for (int i = 0; i < bindings.Length; ++i)
-            {
-                this.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
-            }
-        }
+            : this(SparseWeight8.Create(bindings)) { }
 
 
-        public VertexJoints8x4(in Transforms.SparseWeight8 weights)
+        public VertexJoints8x4(in SparseWeight8 weights)
         {
         {
-            var w4 = Transforms.SparseWeight8.OrderedByWeight(weights);
-
-            Joints = new Vector4
-                (
-                w4.Index0,
-                w4.Index1,
-                w4.Index2,
-                w4.Index3
-                );
-
-            Weights = new Vector4
-                (
-                w4.Weight0,
-                w4.Weight1,
-                w4.Weight2,
-                w4.Weight3
-                );
+            var w4 = SparseWeight8.OrderedByWeight(weights);
+
+            Joints = new Vector4(w4.Index0, w4.Index1, w4.Index2, w4.Index3);
+            Weights = new Vector4(w4.Weight0, w4.Weight1, w4.Weight2, w4.Weight3);
 
 
             // renormalize
             // renormalize
             var w = Vector4.Dot(Weights, Vector4.One);
             var w = Vector4.Dot(Weights, Vector4.One);
-            if (w != 0) Weights /= w;
+            if (w != 0 && w != 1) Weights /= w;
         }
         }
 
 
         #endregion
         #endregion
@@ -190,15 +71,13 @@ namespace SharpGLTF.Geometry.VertexTypes
         #region properties
         #region properties
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsLow => this.Joints;
+        Vector4 IVertexSkinning.JointsLow => this.Joints;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsHigh => Vector4.Zero;
+        Vector4 IVertexSkinning.JointsHigh => Vector4.Zero;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 WeightsLow => this.Weights;
+        Vector4 IVertexSkinning.WeightsLow => this.Weights;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 Weightshigh => Vector4.Zero;
-
-        public Transforms.SparseWeight8 SparseWeights => new Transforms.SparseWeight8(this.Joints, this.Weights);
+        Vector4 IVertexSkinning.Weightshigh => Vector4.Zero;
 
 
         #endregion
         #endregion
 
 
@@ -206,14 +85,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
-        public JointBinding GetJointBinding(int index)
+        public SparseWeight8 GetWeights() { return new SparseWeight8(this.Joints, this.Weights); }
+
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x4(weights); }
+
+        public (int, float) GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointBinding((int)this.Joints.X, this.Weights.X);
-                case 1: return new JointBinding((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JointBinding((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JointBinding((int)this.Joints.W, this.Weights.W);
+                case 0: return ((int)this.Joints.X, this.Weights.X);
+                case 1: return ((int)this.Joints.Y, this.Weights.Y);
+                case 2: return ((int)this.Joints.Z, this.Weights.Z);
+                case 3: return ((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
@@ -232,14 +115,10 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            var sparse = new Transforms.SparseWeight8(this.Joints, this.Weights);
+            var sparse = new SparseWeight8(this.Joints, this.Weights);
             this = new VertexJoints8x4(sparse);
             this = new VertexJoints8x4(sparse);
         }
         }
 
 
-        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x4(weights); }
-
-        public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
-
         #endregion
         #endregion
     }
     }
 
 
@@ -252,57 +131,23 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexJoints16x4(int jointIndex)
         public VertexJoints16x4(int jointIndex)
         {
         {
-            Joints = new Vector4(jointIndex);
+            Joints = new Vector4(jointIndex, 0, 0, 0);
             Weights = Vector4.UnitX;
             Weights = Vector4.UnitX;
         }
         }
 
 
-        public VertexJoints16x4(JointBinding a, JointBinding b)
-        {
-            Joints = new Vector4(a.Joint, b.Joint, 0, 0);
-            Weights = new Vector4(a.Weight, b.Weight, 0, 0);
-
-            InPlaceSort();
-        }
-
-        public VertexJoints16x4(JointBinding a, JointBinding b, JointBinding c)
-        {
-            Joints = new Vector4(a.Joint, b.Joint, c.Joint, 0);
-            Weights = new Vector4(a.Weight, b.Weight, c.Weight, 0);
-
-            InPlaceSort();
-        }
+        public VertexJoints16x4(params (int, float)[] bindings)
+            : this( SparseWeight8.Create(bindings) ) { }
 
 
-        public VertexJoints16x4(JointBinding a, JointBinding b, JointBinding c, JointBinding d)
+        public VertexJoints16x4(in SparseWeight8 weights)
         {
         {
-            Joints = new Vector4(a.Joint, b.Joint, c.Joint, d.Joint);
-            Weights = new Vector4(a.Weight, b.Weight, c.Weight, d.Weight);
+            var w4 = SparseWeight8.OrderedByWeight(weights);
 
 
-            InPlaceSort();
-        }
-
-        public VertexJoints16x4(in Transforms.SparseWeight8 weights)
-        {
-            var w4 = Transforms.SparseWeight8.OrderedByWeight(weights);
-
-            Joints = new Vector4
-                (
-                w4.Index0,
-                w4.Index1,
-                w4.Index2,
-                w4.Index3
-                );
-
-            Weights = new Vector4
-                (
-                w4.Weight0,
-                w4.Weight1,
-                w4.Weight2,
-                w4.Weight3
-                );
+            Joints = new Vector4(w4.Index0, w4.Index1, w4.Index2, w4.Index3);
+            Weights = new Vector4(w4.Weight0, w4.Weight1, w4.Weight2, w4.Weight3);
 
 
             // renormalize
             // renormalize
             var w = Vector4.Dot(Weights, Vector4.One);
             var w = Vector4.Dot(Weights, Vector4.One);
-            if (w != 0) Weights /= w;
+            if (w != 0 && w != 1) Weights /= w;
         }
         }
 
 
         #endregion
         #endregion
@@ -322,15 +167,13 @@ namespace SharpGLTF.Geometry.VertexTypes
         #region properties
         #region properties
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsLow => this.Joints;
+        Vector4 IVertexSkinning.JointsLow => this.Joints;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsHigh => Vector4.Zero;
+        Vector4 IVertexSkinning.JointsHigh => Vector4.Zero;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 WeightsLow => this.Weights;
+        Vector4 IVertexSkinning.WeightsLow => this.Weights;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 Weightshigh => Vector4.Zero;
-
-        public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints, this.Weights);
+        Vector4 IVertexSkinning.Weightshigh => Vector4.Zero;
 
 
         #endregion
         #endregion
 
 
@@ -338,14 +181,18 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
-        public JointBinding GetJointBinding(int index)
+        public SparseWeight8 GetWeights() { return new SparseWeight8(this.Joints, this.Weights); }
+
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x4(weights); }
+
+        public (int, float) GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointBinding((int)this.Joints.X, this.Weights.X);
-                case 1: return new JointBinding((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JointBinding((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JointBinding((int)this.Joints.W, this.Weights.W);
+                case 0: return ((int)this.Joints.X, this.Weights.X);
+                case 1: return ((int)this.Joints.Y, this.Weights.Y);
+                case 2: return ((int)this.Joints.Z, this.Weights.Z);
+                case 3: return ((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
@@ -362,16 +209,12 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x4(weights); }
-
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            var sparse = new Transforms.SparseWeight8(this.Joints, this.Weights);
+            var sparse = new SparseWeight8(this.Joints, this.Weights);
             this = new VertexJoints16x4(sparse);
             this = new VertexJoints16x4(sparse);
         }
         }
 
 
-        public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
-
         #endregion
         #endregion
     }
     }
 
 
@@ -384,53 +227,27 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexJoints8x8(int jointIndex)
         public VertexJoints8x8(int jointIndex)
         {
         {
-            Joints0 = new Vector4(jointIndex);
-            Joints1 = new Vector4(jointIndex);
+            Joints0 = new Vector4(jointIndex, 0, 0, 0);
+            Joints1 = Vector4.Zero;
             Weights0 = Vector4.UnitX;
             Weights0 = Vector4.UnitX;
             Weights1 = Vector4.Zero;
             Weights1 = Vector4.Zero;
         }
         }
 
 
-        public VertexJoints8x8(int jointIndex1, int jointIndex2)
-        {
-            Joints0 = new Vector4(jointIndex1, jointIndex2, 0, 0);
-            Joints1 = Vector4.Zero;
-            Weights0 = new Vector4(0.5f, 0.5f, 0, 0);
-            Weights1 = Vector4.Zero;
-        }
+        public VertexJoints8x8(params (int, float)[] bindings)
+            : this(SparseWeight8.Create(bindings)) { }
 
 
         public VertexJoints8x8(in SparseWeight8 weights)
         public VertexJoints8x8(in SparseWeight8 weights)
         {
         {
-            Joints0 = new Vector4
-                (
-                weights.Index0,
-                weights.Index1,
-                weights.Index2,
-                weights.Index3
-                );
-
-            Joints1 = new Vector4
-                (
-                weights.Index4,
-                weights.Index5,
-                weights.Index6,
-                weights.Index7
-                );
-
-            Weights0 = new Vector4
-                (
-                weights.Weight0,
-                weights.Weight1,
-                weights.Weight2,
-                weights.Weight3
-                );
-
-            Weights1 = new Vector4
-                (
-                weights.Weight4,
-                weights.Weight5,
-                weights.Weight6,
-                weights.Weight7
-                );
+            var w8 = SparseWeight8.OrderedByWeight(weights);
+
+            Joints0 = new Vector4(w8.Index0, w8.Index1, w8.Index2, w8.Index3);
+            Joints1 = new Vector4(w8.Index4, w8.Index5, w8.Index6, w8.Index7);
+            Weights0 = new Vector4(w8.Weight0, w8.Weight1, w8.Weight2, w8.Weight3);
+            Weights1 = new Vector4(w8.Weight4, w8.Weight5, w8.Weight6, w8.Weight7);
+
+            // renormalize
+            var w = Vector4.Dot(Weights0, Vector4.One) + Vector4.Dot(Weights1, Vector4.One);
+            if (w != 0 && w != 1) { Weights0 /= w; Weights1 /= w; }
         }
         }
 
 
         #endregion
         #endregion
@@ -456,13 +273,13 @@ namespace SharpGLTF.Geometry.VertexTypes
         #region properties
         #region properties
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsLow => this.Joints0;
+        Vector4 IVertexSkinning.JointsLow => this.Joints0;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsHigh => this.Joints1;
+        Vector4 IVertexSkinning.JointsHigh => this.Joints1;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 WeightsLow => this.Weights0;
+        Vector4 IVertexSkinning.WeightsLow => this.Weights0;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 Weightshigh => this.Joints1;
+        Vector4 IVertexSkinning.Weightshigh => this.Joints1;
 
 
         public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1);
         public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1);
 
 
@@ -472,20 +289,22 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
+        public SparseWeight8 GetWeights() { return new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1); }
+
         public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x8(weights); }
         public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x8(weights); }
 
 
-        public JointBinding GetJointBinding(int index)
+        public (int, float) GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointBinding((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JointBinding((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JointBinding((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JointBinding((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JointBinding((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JointBinding((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JointBinding((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JointBinding((int)this.Joints1.W, this.Weights1.W);
+                case 0: return ((int)this.Joints0.X, this.Weights0.X);
+                case 1: return ((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return ((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return ((int)this.Joints0.W, this.Weights0.W);
+                case 4: return ((int)this.Joints1.X, this.Weights1.X);
+                case 5: return ((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return ((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return ((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
@@ -506,8 +325,6 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
-
         #endregion
         #endregion
     }
     }
 
 
@@ -520,53 +337,27 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexJoints16x8(int jointIndex)
         public VertexJoints16x8(int jointIndex)
         {
         {
-            Joints0 = new Vector4(jointIndex);
-            Joints1 = new Vector4(jointIndex);
+            Joints0 = new Vector4(jointIndex, 0, 0, 0);
+            Joints1 = Vector4.Zero;
             Weights0 = Vector4.UnitX;
             Weights0 = Vector4.UnitX;
             Weights1 = Vector4.Zero;
             Weights1 = Vector4.Zero;
         }
         }
 
 
-        public VertexJoints16x8(int jointIndex1, int jointIndex2)
-        {
-            Joints0 = new Vector4(jointIndex1, jointIndex2, 0, 0);
-            Joints1 = Vector4.Zero;
-            Weights0 = new Vector4(0.5f, 0.5f, 0, 0);
-            Weights1 = Vector4.Zero;
-        }
+        public VertexJoints16x8(params (int, float)[] bindings)
+            : this(SparseWeight8.Create(bindings)) { }
 
 
-        public VertexJoints16x8(in Transforms.SparseWeight8 weights)
+        public VertexJoints16x8(in SparseWeight8 weights)
         {
         {
-            Joints0 = new Vector4
-                (
-                weights.Index0,
-                weights.Index1,
-                weights.Index2,
-                weights.Index3
-                );
-
-            Joints1 = new Vector4
-                (
-                weights.Index4,
-                weights.Index5,
-                weights.Index6,
-                weights.Index7
-                );
-
-            Weights0 = new Vector4
-                (
-                weights.Weight0,
-                weights.Weight1,
-                weights.Weight2,
-                weights.Weight3
-                );
-
-            Weights1 = new Vector4
-                (
-                weights.Weight4,
-                weights.Weight5,
-                weights.Weight6,
-                weights.Weight7
-                );
+            var w8 = SparseWeight8.OrderedByWeight(weights);
+
+            Joints0 = new Vector4(w8.Index0, w8.Index1, w8.Index2, w8.Index3);
+            Joints1 = new Vector4(w8.Index4, w8.Index5, w8.Index6, w8.Index7);
+            Weights0 = new Vector4(w8.Weight0, w8.Weight1, w8.Weight2, w8.Weight3);
+            Weights1 = new Vector4(w8.Weight4, w8.Weight5, w8.Weight6, w8.Weight7);
+
+            // renormalize
+            var w = Vector4.Dot(Weights0, Vector4.One) + Vector4.Dot(Weights1, Vector4.One);
+            if (w != 0 && w != 1) { Weights0 /= w; Weights1 /= w; }
         }
         }
 
 
         #endregion
         #endregion
@@ -592,15 +383,13 @@ namespace SharpGLTF.Geometry.VertexTypes
         #region properties
         #region properties
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsLow => this.Joints0;
+        Vector4 IVertexSkinning.JointsLow => this.Joints0;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 JointsHigh => this.Joints1;
+        Vector4 IVertexSkinning.JointsHigh => this.Joints1;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 WeightsLow => this.Weights0;
+        Vector4 IVertexSkinning.WeightsLow => this.Weights0;
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        public Vector4 Weightshigh => this.Joints1;
-
-        public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1);
+        Vector4 IVertexSkinning.Weightshigh => this.Joints1;
 
 
         #endregion
         #endregion
 
 
@@ -608,20 +397,22 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
+        public SparseWeight8 GetWeights() { return new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1); }
+
         public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x8(weights); }
         public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x8(weights); }
 
 
-        public JointBinding GetJointBinding(int index)
+        public (int, float) GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)
             {
             {
-                case 0: return new JointBinding((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JointBinding((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JointBinding((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JointBinding((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JointBinding((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JointBinding((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JointBinding((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JointBinding((int)this.Joints1.W, this.Weights1.W);
+                case 0: return ((int)this.Joints0.X, this.Weights0.X);
+                case 1: return ((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return ((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return ((int)this.Joints0.W, this.Weights0.W);
+                case 4: return ((int)this.Joints1.X, this.Weights1.X);
+                case 5: return ((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return ((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return ((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
             }
         }
         }
@@ -642,8 +433,6 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
-        public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
-
         #endregion
         #endregion
     }
     }
 }
 }

+ 3 - 10
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -5,8 +5,6 @@ using System.Numerics;
 
 
 using SharpGLTF.Memory;
 using SharpGLTF.Memory;
 
 
-using JOINTWEIGHT = System.Collections.Generic.KeyValuePair<int, float>;
-
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
     static class VertexUtils
     static class VertexUtils
@@ -174,16 +172,11 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
         {
             if (src.GetType() == typeof(TvS)) return (TvS)src;
             if (src.GetType() == typeof(TvS)) return (TvS)src;
 
 
-            var dst = default(TvS);
-
-            var sparse = Transforms.SparseWeight8.OrderedByWeight(src.SparseWeights);
+            var sparse = src.MaxBindings > 0 ? src.GetWeights() : default;
 
 
-            var sum = sparse.WeightSum;
-            if (sum == 0) return default(TvS);
-
-            sparse = Transforms.SparseWeight8.Multiply(sparse, 1.0f / sum);
+            var dst = default(TvS);
 
 
-            dst.SetWeights(sparse);
+            if (dst.MaxBindings > 0) dst.SetWeights(sparse);
 
 
             return dst;
             return dst;
         }
         }

+ 15 - 14
tests/SharpGLTF.Tests/Geometry/VertexTypes/VertexSkinningTests.cs

@@ -14,25 +14,26 @@ namespace SharpGLTF.Geometry.VertexTypes
         public void TestVertexSkinningDowngradeFrom8To4Joints()
         public void TestVertexSkinningDowngradeFrom8To4Joints()
         {
         {
             // vertex with 5 bindings
             // vertex with 5 bindings
-            var v8 = new VertexJoints8x8();
-            v8.SetJointBinding(0, 1, 0.2f);
-            v8.SetJointBinding(1, 2, 0.15f);
-            v8.SetJointBinding(2, 3, 0.25f);
-            v8.SetJointBinding(3, 4, 0.10f);
-            v8.SetJointBinding(4, 5, 0.30f);
+            var v8 = new VertexJoints8x8
+                (
+                (1, 0.20f),
+                (2, 0.15f),
+                (3, 0.25f),
+                (4, 0.10f),
+                (5, 0.30f)
+                );
 
 
             // we downgrade to 4 bindings; remaining bindings should be interpolated to keep weighting 1.
             // we downgrade to 4 bindings; remaining bindings should be interpolated to keep weighting 1.
             var v4 = v8.ConvertToSkinning<VertexJoints8x4>();
             var v4 = v8.ConvertToSkinning<VertexJoints8x4>();
 
 
-            Assert.AreEqual(5, v4.GetJointBinding(0).Joint);
-            Assert.AreEqual(3, v4.GetJointBinding(1).Joint);
-            Assert.AreEqual(1, v4.GetJointBinding(2).Joint);
-            Assert.AreEqual(2, v4.GetJointBinding(3).Joint);
+            var sparse = v4.GetWeights();
 
 
-            Assert.AreEqual(0.333333f, v4.GetJointBinding(0).Weight, 0.0001f);
-            Assert.AreEqual(0.277777f, v4.GetJointBinding(1).Weight, 0.0001f);
-            Assert.AreEqual(0.222222f, v4.GetJointBinding(2).Weight, 0.0001f);
-            Assert.AreEqual(0.166666f, v4.GetJointBinding(3).Weight, 0.0001f);
+            Assert.AreEqual(1, sparse.WeightSum, 0.00001f);
+
+            Assert.AreEqual(0.333333f, sparse[5], 0.00001f);
+            Assert.AreEqual(0.277777f, sparse[3], 0.00001f);
+            Assert.AreEqual(0.222222f, sparse[1], 0.00001f);
+            Assert.AreEqual(0.166666f, sparse[2], 0.00001f);
         }
         }
     }
     }
 }
 }

+ 29 - 2
tests/SharpGLTF.Tests/Transforms/SparseWeight8Tests.cs

@@ -42,7 +42,7 @@ namespace SharpGLTF.Transforms
             Assert.AreEqual(0.1f, sparse1.Weight2);
             Assert.AreEqual(0.1f, sparse1.Weight2);
             Assert.AreEqual(0, sparse1.Weight3);
             Assert.AreEqual(0, sparse1.Weight3);
 
 
-            var sparse1Nrm = MorphTransform.Normalize(sparse1);
+            var sparse1Nrm = sparse1.GetNormalizedWithComplement();
             Assert.AreEqual(7, sparse1Nrm.Index0);
             Assert.AreEqual(7, sparse1Nrm.Index0);
             Assert.AreEqual(6, sparse1Nrm.Index1);
             Assert.AreEqual(6, sparse1Nrm.Index1);
             Assert.AreEqual(11, sparse1Nrm.Index2);
             Assert.AreEqual(11, sparse1Nrm.Index2);
@@ -63,7 +63,7 @@ namespace SharpGLTF.Transforms
         }
         }
 
 
         [Test]
         [Test]
-        public void TestSparseOrdering()
+        public void TestSparseOrdering1()
         {
         {
             var array1 = new float[] { 0.2f, 0.15f, 0.25f, 0.10f, 0.30f };
             var array1 = new float[] { 0.2f, 0.15f, 0.25f, 0.10f, 0.30f };
 
 
@@ -78,6 +78,23 @@ namespace SharpGLTF.Transforms
             CheckIndexOrdered(s5byWeights);
             CheckIndexOrdered(s5byWeights);
         }
         }
 
 
+        [Test]
+        public void TestSparseOrdering2()
+        {
+            var expanded = new float[] { 0,0,1,0,2,0,3,4,5,0,6,0,7,0,6,0,9,0,11 };
+
+            var sparse = SparseWeight8.Create(expanded);
+
+            Assert.AreEqual(11, sparse[18]);
+            Assert.AreEqual(9, sparse[16]);
+            Assert.AreEqual(7, sparse[12]);
+            Assert.AreEqual(6, sparse[10]);
+            Assert.AreEqual(6, sparse[14]);
+            Assert.AreEqual(5, sparse[8]);
+            Assert.AreEqual(4, sparse[7]);
+            Assert.AreEqual(3, sparse[6]);
+        }
+
         static void CheckWeightOrdered(SparseWeight8 sparse)
         static void CheckWeightOrdered(SparseWeight8 sparse)
         {
         {
             Assert.GreaterOrEqual(sparse.Weight0, sparse.Weight1);
             Assert.GreaterOrEqual(sparse.Weight0, sparse.Weight1);
@@ -107,6 +124,9 @@ namespace SharpGLTF.Transforms
 
 
             Assert.IsFalse(SparseWeight8.AreWeightsEqual(SparseWeight8.Create(0, 1), SparseWeight8.Create(0, 1, 0.25f)));
             Assert.IsFalse(SparseWeight8.AreWeightsEqual(SparseWeight8.Create(0, 1), SparseWeight8.Create(0, 1, 0.25f)));
             Assert.IsFalse(SparseWeight8.AreWeightsEqual(SparseWeight8.Create(0, 1), SparseWeight8.Create(1, 0)));
             Assert.IsFalse(SparseWeight8.AreWeightsEqual(SparseWeight8.Create(0, 1), SparseWeight8.Create(1, 0)));
+
+            // check if two "half weights" are equal to one "full weight"
+            Assert.IsTrue(SparseWeight8.AreWeightsEqual(SparseWeight8.Create((3, 5), (3, 5)), SparseWeight8.Create((3, 10))));
         }
         }
 
 
         [Test]
         [Test]
@@ -143,6 +163,13 @@ namespace SharpGLTF.Transforms
             var a = SparseWeight8.Create(0, 0, 0.2f, 0, 0, 0, 1);
             var a = SparseWeight8.Create(0, 0, 0.2f, 0, 0, 0, 1);
             var b = SparseWeight8.Create(1, 1, 0.4f, 0, 0, 1, 0);
             var b = SparseWeight8.Create(1, 1, 0.4f, 0, 0, 1, 0);
             var t = SparseWeight8.Subtract(b, a);
             var t = SparseWeight8.Subtract(b, a);
+            Assert.AreEqual(1, t[0]);
+            Assert.AreEqual(1, t[1]);
+            Assert.AreEqual(0.2f, t[2]);
+            Assert.AreEqual(0, t[3]);
+            Assert.AreEqual(0, t[4]);
+            Assert.AreEqual(1, t[5]);
+            Assert.AreEqual(-1, t[6]);
 
 
             var lr = SparseWeight8.InterpolateLinear(a, b, 0.4f);
             var lr = SparseWeight8.InterpolateLinear(a, b, 0.4f);
             var cr = SparseWeight8.InterpolateCubic(a, t, b, t, 0.4f);
             var cr = SparseWeight8.InterpolateCubic(a, t, b, t, 0.4f);