Ver código fonte

upgrading SparseWeight8 to be used for skinning too.

Vicente Penades 6 anos atrás
pai
commit
5564e3759f

+ 2 - 2
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -38,7 +38,7 @@ namespace SharpGLTF.Transforms
 
 
         protected MorphTransform()
         protected MorphTransform()
         {
         {
-            Update(new SparseWeight8((0, 1)), false);
+            Update(SparseWeight8.Create((0, 1)), false);
         }
         }
 
 
         protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
         protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
@@ -73,7 +73,7 @@ namespace SharpGLTF.Transforms
 
 
             if (morphWeights.IsWeightless)
             if (morphWeights.IsWeightless)
             {
             {
-                _Weights = new SparseWeight8((0, 1));
+                _Weights = SparseWeight8.Create((0, 1));
                 return;
                 return;
             }
             }
 
 

+ 116 - 37
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Numerics;
 using System.Text;
 using System.Text;
 
 
 namespace SharpGLTF.Transforms
 namespace SharpGLTF.Transforms
@@ -9,6 +10,11 @@ namespace SharpGLTF.Transforms
     /// <summary>
     /// <summary>
     /// Represents a sparse collection of non zero weight values, with a maximum of 8 weights.
     /// Represents a sparse collection of non zero weight values, with a maximum of 8 weights.
     /// </summary>
     /// </summary>
+    /// <remarks>
+    /// <see cref="SparseWeight8"/> is being used in two different contexts:
+    /// - As an utility class to define per vertex joint weights in mesh skinning.
+    /// - 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>
     [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 struct SparseWeight8 : IReadOnlyList<float>
     {
     {
@@ -16,7 +22,7 @@ namespace SharpGLTF.Transforms
 
 
         public static SparseWeight8 Create(params float[] weights)
         public static SparseWeight8 Create(params float[] weights)
         {
         {
-            return Create(weights as IEnumerable<float>);
+            return Create((IEnumerable<float>)weights);
         }
         }
 
 
         public static SparseWeight8 Create(IEnumerable<float> weights)
         public static SparseWeight8 Create(IEnumerable<float> weights)
@@ -30,31 +36,76 @@ namespace SharpGLTF.Transforms
                 .Take(8)
                 .Take(8)
                 .ToArray();
                 .ToArray();
 
 
-            return new SparseWeight8(indexedWeights);
+            return Create(indexedWeights);
         }
         }
 
 
-        public SparseWeight8(params (int, float)[] items)
+        public static SparseWeight8 Create(params (int, float)[] pairs)
         {
         {
-            Guard.NotNull(items, nameof(items));
-            Guard.MustBeLessThanOrEqualTo(items.Length, 8, nameof(items));
-
-            this.Index0 = items.Length > 0 ? items[0].Item1 : 0;
-            this.Index1 = items.Length > 1 ? items[1].Item1 : 0;
-            this.Index2 = items.Length > 2 ? items[2].Item1 : 0;
-            this.Index3 = items.Length > 3 ? items[3].Item1 : 0;
-            this.Index4 = items.Length > 4 ? items[4].Item1 : 0;
-            this.Index5 = items.Length > 5 ? items[5].Item1 : 0;
-            this.Index6 = items.Length > 6 ? items[6].Item1 : 0;
-            this.Index7 = items.Length > 7 ? items[7].Item1 : 0;
-
-            this.Weight0 = items.Length > 0 ? items[0].Item2 : 0;
-            this.Weight1 = items.Length > 1 ? items[1].Item2 : 0;
-            this.Weight2 = items.Length > 2 ? items[2].Item2 : 0;
-            this.Weight3 = items.Length > 3 ? items[3].Item2 : 0;
-            this.Weight4 = items.Length > 4 ? items[4].Item2 : 0;
-            this.Weight5 = items.Length > 5 ? items[5].Item2 : 0;
-            this.Weight6 = items.Length > 6 ? items[6].Item2 : 0;
-            this.Weight7 = items.Length > 7 ? items[7].Item2 : 0;
+            if (pairs == null) return default;
+
+            Span<int> indices = stackalloc int[pairs.Length];
+            Span<float> weights = stackalloc float[pairs.Length];
+
+            for (int i = 0; i < pairs.Length; ++i)
+            {
+                indices[i] = pairs[i].Item1;
+                weights[i] = pairs[i].Item2;
+            }
+
+            if (pairs.Length > 8)
+            {
+                BubbleSortByWeight(indices, weights, indices.Length);
+                indices = indices.Slice(0, 8);
+                weights = weights.Slice(0, 8);
+            }
+
+            return new SparseWeight8(indices, weights);
+        }
+
+        public SparseWeight8(in Vector4 idx0123, in Vector4 wgt0123)
+        {
+            Index0 = (int)idx0123.X;
+            Index1 = (int)idx0123.Y;
+            Index2 = (int)idx0123.Z;
+            Index3 = (int)idx0123.W;
+
+            Index4 = 0;
+            Index5 = 0;
+            Index6 = 0;
+            Index7 = 0;
+
+            Weight0 = wgt0123.X;
+            Weight1 = wgt0123.Y;
+            Weight2 = wgt0123.Z;
+            Weight3 = wgt0123.W;
+
+            Weight4 = 0;
+            Weight5 = 0;
+            Weight6 = 0;
+            Weight7 = 0;
+        }
+
+        public SparseWeight8(in Vector4 idx0123, in Vector4 idx4567, in Vector4 wgt0123, in Vector4 wgt4567)
+        {
+            Index0 = (int)idx0123.X;
+            Index1 = (int)idx0123.Y;
+            Index2 = (int)idx0123.Z;
+            Index3 = (int)idx0123.W;
+
+            Index4 = (int)idx4567.X;
+            Index5 = (int)idx4567.Y;
+            Index6 = (int)idx4567.Z;
+            Index7 = (int)idx4567.W;
+
+            Weight0 = wgt0123.X;
+            Weight1 = wgt0123.Y;
+            Weight2 = wgt0123.Z;
+            Weight3 = wgt0123.W;
+
+            Weight4 = wgt4567.X;
+            Weight5 = wgt4567.Y;
+            Weight6 = wgt4567.Z;
+            Weight7 = wgt4567.W;
         }
         }
 
 
         private SparseWeight8(ReadOnlySpan<int> indices, ReadOnlySpan<float> weights)
         private SparseWeight8(ReadOnlySpan<int> indices, ReadOnlySpan<float> weights)
@@ -154,6 +205,8 @@ namespace SharpGLTF.Transforms
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public bool IsWeightless => Weight0 == 0 & Weight1 == 0 & Weight2 == 0 & Weight3 == 0 & Weight4 == 0 & Weight5 == 0 & Weight6 == 0 & Weight7 == 0;
         public bool IsWeightless => Weight0 == 0 & Weight1 == 0 & Weight2 == 0 & Weight3 == 0 & Weight4 == 0 & Weight5 == 0 & Weight6 == 0 & Weight7 == 0;
 
 
+        public float WeightSum => Weight0 + Weight1+ Weight2 + Weight3 + Weight4 + Weight5 + Weight6 + Weight7;
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -201,6 +254,29 @@ namespace SharpGLTF.Transforms
             return _OperateLinear(x, y, (xx, yy) => xx * yy);
             return _OperateLinear(x, y, (xx, yy) => xx * yy);
         }
         }
 
 
+        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)
         public static SparseWeight8 Divide(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
             return _OperateLinear(x, y, (xx, yy) => xx / yy);
             return _OperateLinear(x, y, (xx, yy) => xx / yy);
@@ -279,11 +355,12 @@ namespace SharpGLTF.Transforms
         /// <param name="dstWeights">The destination weight 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>
         /// <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>
         /// <returns>The new length of the destination arrays.</returns>
-        private static int CopyTo(SparseWeight8 src, Span<int> dstIndices, Span<float> dstWeights, int dstLength)
+        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 == dstWeights.Length);
             System.Diagnostics.Debug.Assert(dstIndices.Length >= dstLength, $"{nameof(dstIndices)}.Length must be at least {nameof(dstLength)}");
             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.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)
             for (int i = 0; i < 8; ++i)
             {
             {
@@ -295,13 +372,15 @@ namespace SharpGLTF.Transforms
 
 
                 if (idx < 0)
                 if (idx < 0)
                 {
                 {
+                    // the index doesn't exist, insert it to the list.
                     dstIndices[dstLength] = pair.Item1;
                     dstIndices[dstLength] = pair.Item1;
                     dstWeights[dstLength] = pair.Item2;
                     dstWeights[dstLength] = pair.Item2;
                     ++dstLength;
                     ++dstLength;
                 }
                 }
                 else
                 else
                 {
                 {
-                    dstWeights[idx] = pair.Item2; // should perform ADD (in case there's more than one element?)
+                    // the index already exists, so we aggregate the weights
+                    dstWeights[idx] += pair.Item2;
                 }
                 }
             }
             }
 
 
@@ -405,16 +484,16 @@ namespace SharpGLTF.Transforms
             {
             {
                 for (int j = i + 1; j < count; ++j)
                 for (int j = i + 1; j < count; ++j)
                 {
                 {
-                    if (weights[j - 1] > weights[j]) continue;
+                    if (weights[i] > weights[j]) continue;
 
 
-                    if (weights[j - 1] == weights[j] && indices[j - 1] < indices[j]) continue;
+                    if (weights[i] == weights[j] && indices[i] < indices[j]) continue;
 
 
-                    var index = indices[j - 1];
-                    indices[j - 1] = indices[j];
+                    var index = indices[i];
+                    indices[i] = indices[j];
                     indices[j] = index;
                     indices[j] = index;
 
 
-                    var weight = weights[j - 1];
-                    weights[j - 1] = weights[j];
+                    var weight = weights[i];
+                    weights[i] = weights[j];
                     weights[j] = weight;
                     weights[j] = weight;
                 }
                 }
             }
             }
@@ -430,16 +509,16 @@ namespace SharpGLTF.Transforms
             {
             {
                 for (int j = i + 1; j < count; ++j)
                 for (int j = i + 1; j < count; ++j)
                 {
                 {
-                    if (indices[j - 1] < indices[j]) continue;
+                    if (indices[i] < indices[j]) continue;
 
 
-                    if (indices[j - 1] == indices[j] && weights[j - 1] > weights[j]) continue;
+                    if (indices[i] == indices[j] && weights[i] > weights[j]) continue;
 
 
-                    var index = indices[j - 1];
-                    indices[j - 1] = indices[j];
+                    var index = indices[i];
+                    indices[i] = indices[j];
                     indices[j] = index;
                     indices[j] = index;
 
 
-                    var weight = weights[j - 1];
-                    weights[j - 1] = weights[j];
+                    var weight = weights[i];
+                    weights[i] = weights[j];
                     weights[j] = weight;
                     weights[j] = weight;
                 }
                 }
             }
             }

+ 8 - 10
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -88,12 +88,11 @@ namespace SharpGLTF.Geometry
         {
         {
             Geometry = g;
             Geometry = g;
             Material = m;
             Material = m;
-            Skinning = default;
 
 
-            for (int i = 0; i < bindings.Length; ++i)
-            {
-                Skinning.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
-            }
+            var sparse = Transforms.SparseWeight8.Create(bindings);
+
+            Skinning = default;
+            Skinning.SetWeights(sparse);
         }
         }
 
 
         public VertexBuilder(TvG g, TvM m)
         public VertexBuilder(TvG g, TvM m)
@@ -121,12 +120,11 @@ namespace SharpGLTF.Geometry
         {
         {
             Geometry = g;
             Geometry = g;
             Material = default;
             Material = default;
-            Skinning = default;
 
 
-            for (int i = 0; i < bindings.Length; ++i)
-            {
-                Skinning.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
-            }
+            var sparse = Transforms.SparseWeight8.Create(bindings);
+
+            Skinning = default;
+            Skinning.SetWeights(sparse);
         }
         }
 
 
         public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM, TvS) tuple)
         public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM, TvS) tuple)

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

@@ -229,32 +229,17 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
         {
             if (vertex.MaxBindings == 0) return vertex;
             if (vertex.MaxBindings == 0) return vertex;
 
 
-            Span<JointBinding> pairs = stackalloc JointBinding[vertex.MaxBindings];
-
             // 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
 
 
-            float weightsSum = 0;
-
-            for (int i = 0; i < pairs.Length; ++i)
-            {
-                var pair = vertex.GetJointBinding(i);
-
-                pairs[i] = pair.Weight == 0 ? default : pair;
+            var sparse = Transforms.SparseWeight8.OrderedByWeight(vertex.SparseWeights);
 
 
-                weightsSum += pair.Weight;
-            }
-
-            // TODO: check that joints are unique, and if not, do a merge.
+            var sum = sparse.WeightSum;
+            if (sum == 0) return default(TvS);
 
 
-            if (weightsSum == 0) weightsSum = 1;
+            sparse = Transforms.SparseWeight8.Multiply(sparse, 1.0f / sum);
 
 
-            JointBinding.InPlaceReverseBubbleSort(pairs);
-
-            for (int i = 0; i < pairs.Length; ++i)
-            {
-                vertex.SetJointBinding(i, pairs[i].Joint, pairs[i].Weight / weightsSum);
-            }
+            vertex.SetWeights(sparse);
 
 
             return vertex;
             return vertex;
         }
         }

+ 5 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
+using SharpGLTF.Transforms;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
@@ -29,6 +30,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         JointBinding IVertexSkinning.GetJointBinding(int index) { throw new NotSupportedException(); }
         JointBinding IVertexSkinning.GetJointBinding(int index) { throw new NotSupportedException(); }
 
 
+        public void SetWeights(in SparseWeight8 weights) { }
+
         public IEnumerable<JointBinding> JointBindings => Enumerable.Empty<JointBinding>();
         public IEnumerable<JointBinding> JointBindings => Enumerable.Empty<JointBinding>();
 
 
         public Vector4 JointsLow => Vector4.Zero;
         public Vector4 JointsLow => Vector4.Zero;
@@ -38,5 +41,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public Vector4 WeightsLow => Vector4.Zero;
         public Vector4 WeightsLow => Vector4.Zero;
 
 
         public Vector4 Weightshigh => Vector4.Zero;
         public Vector4 Weightshigh => Vector4.Zero;
+
+        public SparseWeight8 SparseWeights => default(SparseWeight8);
     }
     }
 }
 }

+ 129 - 71
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexSkinning.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
+using SharpGLTF.Transforms;
 
 
 namespace SharpGLTF.Geometry.VertexTypes
 namespace SharpGLTF.Geometry.VertexTypes
 {
 {
@@ -44,50 +45,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region API
         #region API
 
 
-        internal static void InPlaceReverseBubbleSort(Span<JointBinding> span)
-        {
-            for (int i = 1; i < span.Length; ++i)
-            {
-                bool completed = true;
-
-                for (int j = 0; j < span.Length - 1; ++j)
-                {
-                    if (_DefaultWeightComparer.Compare(span[j], span[j + 1]) < 0)
-                    {
-                        var tmp = span[j];
-                        span[j] = span[j + 1];
-                        span[j + 1] = tmp;
-                        completed = false;
-                    }
-                }
-
-                if (completed) return;
-            }
-        }
-
-        /// <summary>
-        /// Calculates the scale to use on the first <paramref name="count"/> weights.
-        /// </summary>
-        /// <param name="span">A collection of <see cref="JointBinding"/>.</param>
-        /// <param name="count">The number of items to take from the beginning of <paramref name="span"/>.</param>
-        /// <returns>A Scale factor.</returns>
-        internal static float CalculateScaleFor(Span<JointBinding> span, int count)
-        {
-            System.Diagnostics.Debug.Assert(count < span.Length, nameof(count));
-
-            float ww = 0;
-
-            int i = 0;
-
-            while (i < count) { ww += span[i++].Weight; }
-
-            float w = ww;
-
-            while (i < span.Length) { ww += span[i++].Weight; }
-
-            return ww / w;
-        }
-
         public static IEnumerable<JointBinding> GetBindings(IVertexSkinning vs)
         public static IEnumerable<JointBinding> GetBindings(IVertexSkinning vs)
         {
         {
             for (int i = 0; i < vs.MaxBindings; ++i)
             for (int i = 0; i < vs.MaxBindings; ++i)
@@ -125,8 +82,12 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         void SetJointBinding(int index, int joint, float weight);
         void SetJointBinding(int index, int joint, float weight);
 
 
+        void SetWeights(in SparseWeight8 weights);
+
         IEnumerable<JointBinding> JointBindings { get; }
         IEnumerable<JointBinding> JointBindings { get; }
 
 
+        SparseWeight8 SparseWeights { get; }
+
         Vector4 JointsLow { get; }
         Vector4 JointsLow { get; }
         Vector4 JointsHigh { get; }
         Vector4 JointsHigh { get; }
 
 
@@ -173,6 +134,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexJoints8x4(params (int, float)[] bindings)
         public VertexJoints8x4(params (int, float)[] bindings)
         {
         {
+            // var sparse = new Transforms.SparseWeight8(bindings);
+
             Guard.NotNull(bindings, nameof(bindings));
             Guard.NotNull(bindings, nameof(bindings));
             Guard.MustBeBetweenOrEqualTo(bindings.Length, 1, 4, nameof(bindings));
             Guard.MustBeBetweenOrEqualTo(bindings.Length, 1, 4, nameof(bindings));
 
 
@@ -184,7 +147,32 @@ namespace SharpGLTF.Geometry.VertexTypes
                 this.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
                 this.SetJointBinding(i, bindings[i].Item1, bindings[i].Item2);
             }
             }
         }
         }
-        
+
+        public VertexJoints8x4(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
+                );
+
+            // renormalize
+            var w = Vector4.Dot(Weights, Vector4.One);
+            if (w != 0) Weights /= w;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -201,12 +189,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region properties
         #region properties
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsLow => this.Joints;
         public Vector4 JointsLow => this.Joints;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsHigh => Vector4.Zero;
         public Vector4 JointsHigh => Vector4.Zero;
-
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 WeightsLow => this.Weights;
         public Vector4 WeightsLow => this.Weights;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 Weightshigh => Vector4.Zero;
         public Vector4 Weightshigh => Vector4.Zero;
 
 
+        public Transforms.SparseWeight8 SparseWeights => new Transforms.SparseWeight8(this.Joints, this.Weights);
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -239,19 +232,12 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            Span<JointBinding> pairs = stackalloc JointBinding[4];
-
-            pairs[0] = new JointBinding((int)Joints.X, Weights.X);
-            pairs[1] = new JointBinding((int)Joints.Y, Weights.Y);
-            pairs[2] = new JointBinding((int)Joints.Z, Weights.Z);
-            pairs[3] = new JointBinding((int)Joints.W, Weights.W);
-
-            JointBinding.InPlaceReverseBubbleSort(pairs);
-
-            Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
-            Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
+            var sparse = new Transforms.SparseWeight8(this.Joints, this.Weights);
+            this = new VertexJoints8x4(sparse);
         }
         }
 
 
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x4(weights); }
+
         public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
         public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
 
 
         #endregion
         #endregion
@@ -294,6 +280,31 @@ namespace SharpGLTF.Geometry.VertexTypes
             InPlaceSort();
             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
+                );
+
+            // renormalize
+            var w = Vector4.Dot(Weights, Vector4.One);
+            if (w != 0) Weights /= w;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -310,12 +321,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region properties
         #region properties
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsLow => this.Joints;
         public Vector4 JointsLow => this.Joints;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsHigh => Vector4.Zero;
         public Vector4 JointsHigh => Vector4.Zero;
-
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 WeightsLow => this.Weights;
         public Vector4 WeightsLow => this.Weights;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 Weightshigh => Vector4.Zero;
         public Vector4 Weightshigh => Vector4.Zero;
 
 
+        public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints, this.Weights);
+
         #endregion
         #endregion
 
 
         #region API
         #region API
@@ -346,19 +362,12 @@ namespace SharpGLTF.Geometry.VertexTypes
             }
             }
         }
         }
 
 
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x4(weights); }
+
         public void InPlaceSort()
         public void InPlaceSort()
         {
         {
-            Span<JointBinding> pairs = stackalloc JointBinding[4];
-
-            pairs[0] = new JointBinding((int)Joints.X, Weights.X);
-            pairs[1] = new JointBinding((int)Joints.Y, Weights.Y);
-            pairs[2] = new JointBinding((int)Joints.Z, Weights.Z);
-            pairs[3] = new JointBinding((int)Joints.W, Weights.W);
-
-            JointBinding.InPlaceReverseBubbleSort(pairs);
-
-            Joints = new Vector4(pairs[0].Joint, pairs[1].Joint, pairs[2].Joint, pairs[3].Joint);
-            Weights = new Vector4(pairs[0].Weight, pairs[1].Weight, pairs[2].Weight, pairs[3].Weight);
+            var sparse = new Transforms.SparseWeight8(this.Joints, this.Weights);
+            this = new VertexJoints16x4(sparse);
         }
         }
 
 
         public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
         public IEnumerable<JointBinding> JointBindings => JointBinding.GetBindings(this);
@@ -389,6 +398,41 @@ namespace SharpGLTF.Geometry.VertexTypes
             Weights1 = Vector4.Zero;
             Weights1 = Vector4.Zero;
         }
         }
 
 
+        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
+                );
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -411,18 +455,25 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region properties
         #region properties
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsLow => this.Joints0;
         public Vector4 JointsLow => this.Joints0;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsHigh => this.Joints1;
         public Vector4 JointsHigh => this.Joints1;
-
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 WeightsLow => this.Weights0;
         public Vector4 WeightsLow => this.Weights0;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 Weightshigh => this.Joints1;
         public Vector4 Weightshigh => this.Joints1;
 
 
+        public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1);
+
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints8x8(weights); }
+
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)
@@ -540,18 +591,25 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         #region properties
         #region properties
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsLow => this.Joints0;
         public Vector4 JointsLow => this.Joints0;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 JointsHigh => this.Joints1;
         public Vector4 JointsHigh => this.Joints1;
-
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 WeightsLow => this.Weights0;
         public Vector4 WeightsLow => this.Weights0;
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector4 Weightshigh => this.Joints1;
         public Vector4 Weightshigh => this.Joints1;
 
 
+        public SparseWeight8 SparseWeights => new SparseWeight8(this.Joints0, this.Joints1, this.Weights0, this.Weights1);
+
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
         public void Validate() { FragmentPreprocessors.ValidateVertexSkinning(this); }
 
 
+        public void SetWeights(in SparseWeight8 weights) { this = new VertexJoints16x8(weights); }
+
         public JointBinding GetJointBinding(int index)
         public JointBinding GetJointBinding(int index)
         {
         {
             switch (index)
             switch (index)

+ 5 - 35
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -176,44 +176,14 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
             var dst = default(TvS);
             var dst = default(TvS);
 
 
-            // if there's enough room for all bindings, just fill it up.
-            if (dst.MaxBindings >= src.MaxBindings)
-            {
-                int i;
-
-                for (i = 0; i < src.MaxBindings; ++i)
-                {
-                    var jw = src.GetJointBinding(i);
-
-                    dst.SetJointBinding(i, jw.Joint, jw.Weight);
-                }
+            var sparse = Transforms.SparseWeight8.OrderedByWeight(src.SparseWeights);
 
 
-                while (i < dst.MaxBindings)
-                {
-                    dst.SetJointBinding(i++, 0, 0);
-                }
-
-                return dst;
-            }
+            var sum = sparse.WeightSum;
+            if (sum == 0) return default(TvS);
 
 
-            // if there's more source joints than destination joints,
-            // transfer only the most representative joints, and renormalize.
+            sparse = Transforms.SparseWeight8.Multiply(sparse, 1.0f / sum);
 
 
-            Span<JointBinding> srcjw = stackalloc JointBinding[src.MaxBindings];
-
-            for (int i = 0; i < src.MaxBindings; ++i)
-            {
-                srcjw[i] = src.GetJointBinding(i);
-            }
-
-            JointBinding.InPlaceReverseBubbleSort(srcjw);
-
-            var w = JointBinding.CalculateScaleFor(srcjw, dst.MaxBindings);
-
-            for (int i = 0; i < dst.MaxBindings; ++i)
-            {
-                dst.SetJointBinding(i, srcjw[i].Joint, srcjw[i].Weight * w);
-            }
+            dst.SetWeights(sparse);
 
 
             return dst;
             return dst;
         }
         }

+ 0 - 36
tests/SharpGLTF.Tests/Geometry/VertexTypes/JointWeightPairTests.cs

@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF.Geometry.VertexTypes
-{
-    [TestFixture]
-    [Category("Toolkit")]
-    public class JointWeightPairTests
-    {
-        [Test]
-        public void TestJointWeightSorting()
-        {
-            var pairs = new[]
-            {
-                new JointBinding(2,0),
-                new JointBinding(1,0.20f),
-                new JointBinding(3,0.15f),
-                new JointBinding(2,0.25f),
-                new JointBinding(4,0),
-                new JointBinding(7,0.40f)
-            };
-
-            JointBinding.InPlaceReverseBubbleSort(pairs);
-
-            Assert.AreEqual(7, pairs[0].Joint);
-            Assert.AreEqual(2, pairs[1].Joint);
-            Assert.AreEqual(1, pairs[2].Joint);
-            Assert.AreEqual(3, pairs[3].Joint);
-            Assert.AreEqual(0, pairs[4].Joint);
-            Assert.AreEqual(0, pairs[5].Joint);
-        }
-    }
-}

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

@@ -17,6 +17,26 @@ namespace SharpGLTF.Transforms
             Assert.AreEqual(5, sparse1.Index1);
             Assert.AreEqual(5, sparse1.Index1);
             Assert.AreEqual(10, sparse1.Index2);
             Assert.AreEqual(10, sparse1.Index2);
 
 
+            var sparse9to2 = SparseWeight8.Create(0, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f);
+            Assert.AreEqual(9, sparse9to2.Index0);
+            Assert.AreEqual(8, sparse9to2.Index1);
+            Assert.AreEqual(7, sparse9to2.Index2);
+            Assert.AreEqual(6, sparse9to2.Index3);
+            Assert.AreEqual(5, sparse9to2.Index4);
+            Assert.AreEqual(4, sparse9to2.Index5);
+            Assert.AreEqual(3, sparse9to2.Index6);
+            Assert.AreEqual(2, sparse9to2.Index7);
+            Assert.AreEqual(0.9f, sparse9to2.Weight0);
+            Assert.AreEqual(0.8f, sparse9to2.Weight1);
+            Assert.AreEqual(0.7f, sparse9to2.Weight2);
+            Assert.AreEqual(0.6f, sparse9to2.Weight3);
+            Assert.AreEqual(0.5f, sparse9to2.Weight4);
+            Assert.AreEqual(0.4f, sparse9to2.Weight5);
+            Assert.AreEqual(0.3f, sparse9to2.Weight6);
+            Assert.AreEqual(0.2f, sparse9to2.Weight7);
+
+
+
             Assert.AreEqual(0.7f, sparse1.Weight0);
             Assert.AreEqual(0.7f, sparse1.Weight0);
             Assert.AreEqual(0.1f, sparse1.Weight1);
             Assert.AreEqual(0.1f, sparse1.Weight1);
             Assert.AreEqual(0.1f, sparse1.Weight2);
             Assert.AreEqual(0.1f, sparse1.Weight2);
@@ -42,6 +62,44 @@ namespace SharpGLTF.Transforms
             Assert.AreEqual(-1, sparseNegative.Weight1);
             Assert.AreEqual(-1, sparseNegative.Weight1);
         }
         }
 
 
+        [Test]
+        public void TestSparseOrdering()
+        {
+            var array1 = new float[] { 0.2f, 0.15f, 0.25f, 0.10f, 0.30f };
+
+            var s5 = SparseWeight8.Create(array1);
+            CollectionAssert.AreEqual(array1, s5.Expand(5));
+
+            var s5byWeights = SparseWeight8.OrderedByWeight(s5);
+            CollectionAssert.AreEqual(array1, s5byWeights.Expand(5));
+            CheckWeightOrdered(s5byWeights);
+            var s5byIndices = SparseWeight8.OrderedByIndex(s5byWeights);
+            CollectionAssert.AreEqual(array1, s5byIndices.Expand(5));
+            CheckIndexOrdered(s5byWeights);
+        }
+
+        static void CheckWeightOrdered(SparseWeight8 sparse)
+        {
+            Assert.GreaterOrEqual(sparse.Weight0, sparse.Weight1);
+            Assert.GreaterOrEqual(sparse.Weight1, sparse.Weight2);
+            Assert.GreaterOrEqual(sparse.Weight2, sparse.Weight3);
+            Assert.GreaterOrEqual(sparse.Weight3, sparse.Weight4);
+            Assert.GreaterOrEqual(sparse.Weight4, sparse.Weight5);
+            Assert.GreaterOrEqual(sparse.Weight5, sparse.Weight6);
+            Assert.GreaterOrEqual(sparse.Weight6, sparse.Weight7);
+        }
+
+        static void CheckIndexOrdered(SparseWeight8 sparse)
+        {
+            Assert.LessOrEqual(sparse.Index0, sparse.Index0);
+            Assert.LessOrEqual(sparse.Index1, sparse.Index1);
+            Assert.LessOrEqual(sparse.Index2, sparse.Index2);
+            Assert.LessOrEqual(sparse.Index3, sparse.Index3);
+            Assert.LessOrEqual(sparse.Index4, sparse.Index4);
+            Assert.LessOrEqual(sparse.Index5, sparse.Index5);
+            Assert.LessOrEqual(sparse.Index6, sparse.Index6);
+        }
+
         [Test]
         [Test]
         public void TestSparseEquality()
         public void TestSparseEquality()
         {
         {
@@ -54,8 +112,8 @@ namespace SharpGLTF.Transforms
         [Test]
         [Test]
         public void TestSparseWeightsLinearInterpolation1()
         public void TestSparseWeightsLinearInterpolation1()
         {
         {
-            var x = new SparseWeight8((0,0f));
-            var y = new SparseWeight8((0,1f));
+            var x = SparseWeight8.Create((0,0f));
+            var y = SparseWeight8.Create((0,1f));
 
 
             var z = SparseWeight8.InterpolateLinear(x, y, 0.5f);
             var z = SparseWeight8.InterpolateLinear(x, y, 0.5f);
         }
         }