Browse Source

progress with vertex operators

Vicente Penades 6 years ago
parent
commit
9ec9214ea6

+ 12 - 37
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -238,31 +238,18 @@ namespace SharpGLTF.Geometry
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning
         {
-            var p1 = default(TvP);
-            var p2 = default(TvP);
-            var p3 = default(TvP);
 
-            var m1 = default(TvM);
-            var m2 = default(TvM);
-            var m3 = default(TvM);
+            var p1 = a.Item1.CloneAs<TvP>();
+            var p2 = b.Item1.CloneAs<TvP>();
+            var p3 = c.Item1.CloneAs<TvP>();
 
-            var s1 = default(TvS);
-            var s2 = default(TvS);
-            var s3 = default(TvS);
+            var m1 = a.Item2.CloneAs<TvM>();
+            var m2 = b.Item2.CloneAs<TvM>();
+            var m3 = c.Item2.CloneAs<TvM>();
 
-            p1.AssignFrom(a.Item1);
-            p2.AssignFrom(b.Item1);
-            p3.AssignFrom(c.Item1);
-
-            /*
-            m1.AssignFrom(a.Item2);
-            m2.AssignFrom(b.Item2);
-            m3.AssignFrom(c.Item2);
-
-            s1.AssignFrom(a.Item3);
-            s2.AssignFrom(b.Item3);
-            s3.AssignFrom(c.Item3);
-            */
+            var s1 = a.Item3.CloneAs<TvS>();
+            var s2 = b.Item3.CloneAs<TvS>();
+            var s3 = c.Item3.CloneAs<TvS>();
 
             AddTriangle((p1, m1, s1), (p2, m2, s2), (p3, m3, s3));
         }
@@ -270,31 +257,19 @@ namespace SharpGLTF.Geometry
         public TvPP GetVertexPosition<TvPP>(int index)
             where TvPP : struct, IVertexPosition
         {
-            var p = default(TvPP);
-
-            p.AssignFrom(_Vertices[index].Item1);
-
-            return p;
+            return _Vertices[index].Item1.CloneAs<TvPP>();
         }
 
         public TvMM GetVertexMaterial<TvMM>(int index)
             where TvMM : struct, IVertexMaterial
         {
-            var m = default(TvMM);
-
-            // m.AssignFrom(_Vertices[index].Item2);
-
-            return m;
+            return _Vertices[index].Item2.CloneAs<TvMM>();
         }
 
         public TvSS GetVertexSkinning<TvSS>(int index)
             where TvSS : struct, IVertexSkinning
         {
-            var s = default(TvSS);
-
-            // s.AssignFrom(_Vertices[index].Item3);
-
-            return s;
+            return _Vertices[index].Item3.CloneAs<TvSS>();
         }
 
         #endregion

+ 25 - 6
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexColumns.cs

@@ -58,11 +58,11 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             var cctt = default(TvM);
 
-            if (Colors0 != null) cctt.SetColor(0, Colors0[index]);
-            if (Colors1 != null) cctt.SetColor(1, Colors1[index]);
+            if (Colors0 != null && cctt.MaxColors > 0) cctt.SetColor(0, Colors0[index]);
+            if (Colors1 != null && cctt.MaxColors > 1) cctt.SetColor(1, Colors1[index]);
 
-            if (Textures0 != null) cctt.SetTexCoord(0, Textures0[index]);
-            if (Textures1 != null) cctt.SetTexCoord(1, Textures1[index]);
+            if (Textures0 != null && cctt.MaxTextures > 0) cctt.SetTexCoord(0, Textures0[index]);
+            if (Textures1 != null && cctt.MaxTextures > 1) cctt.SetTexCoord(1, Textures1[index]);
 
             return cctt;
         }
@@ -72,8 +72,27 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             var jjjj = default(TvS);
 
-            if (Joints0 != null && Weights0 != null) jjjj.SetJoints(0, Joints0[index], Weights0[index]);
-            if (Joints1 != null && Weights1 != null) jjjj.SetJoints(1, Joints1[index], Weights1[index]);
+            if (Joints0 != null && Weights0 != null)
+            {
+                var j = Joints0[index];
+                var w = Weights0[index];
+
+                jjjj.SetJoint(0, (int)j.X, w.X);
+                jjjj.SetJoint(1, (int)j.Y, w.Y);
+                jjjj.SetJoint(2, (int)j.Z, w.Z);
+                jjjj.SetJoint(3, (int)j.W, w.W);
+            }
+
+            if (Joints1 != null && Weights1 != null)
+            {
+                var j = Joints1[index];
+                var w = Weights1[index];
+
+                jjjj.SetJoint(4, (int)j.X, w.X);
+                jjjj.SetJoint(5, (int)j.Y, w.Y);
+                jjjj.SetJoint(6, (int)j.Z, w.Z);
+                jjjj.SetJoint(7, (int)j.W, w.W);
+            }
 
             return jjjj;
         }

+ 10 - 6
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -7,20 +7,24 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     public struct VertexEmpty : IVertexMaterial, IVertexSkinning
     {
+        public void Validate() { }
+
         public int MaxJoints => 0;
 
+        public int MaxColors => 0;
+
+        public int MaxTextures => 0;
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }
 
-        void IVertexSkinning.SetJoints(int jointSet, Vector4 joints, Vector4 weights) { }
-
-        public void Validate() { }
+        Vector4 IVertexMaterial.GetColor(int index) { throw new NotSupportedException(); }
 
-        public KeyValuePair<int, float> GetJoint(int index) { return default; }
+        Vector2 IVertexMaterial.GetTexCoord(int index) { throw new NotSupportedException(); }
 
-        public void SetJoint(int index, KeyValuePair<int, float> jw) { }
+        void IVertexSkinning.SetJoint(int index, int joint, float weight) { }
 
-        public void AssignFrom(IVertexSkinning vertex) { }
+        JointWeightPair IVertexSkinning.GetJoint(int index) { throw new NotSupportedException(); }
     }
 }

+ 158 - 2
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -7,10 +7,16 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     public interface IVertexMaterial
     {
-        void SetColor(int setIndex, Vector4 color);
-        void SetTexCoord(int setIndex, Vector2 coord);
+        int MaxColors { get; }
+        int MaxTextures { get; }
 
         void Validate();
+
+        Vector4 GetColor(int index);
+        Vector2 GetTexCoord(int index);
+
+        void SetColor(int setIndex, Vector4 color);
+        void SetTexCoord(int setIndex, Vector2 coord);
     }
 
     /// <summary>
@@ -18,6 +24,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexColor1 : IVertexMaterial
     {
+        #region constructors
+
         public VertexColor1(Vector4 color)
         {
             Color = color;
@@ -28,18 +36,43 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new VertexColor1(color);
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("COLOR_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Color;
 
+        public int MaxColors => 1;
+
+        public int MaxTextures => 0;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { }
 
+        public Vector4 GetColor(int index)
+        {
+            if (index != 0) throw new ArgumentOutOfRangeException(nameof(index));
+            return Color;
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            throw new NotSupportedException();
+        }
+
         public void Validate()
         {
             if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
             if (!Color.IsInRange(Vector4.Zero, Vector4.One)) throw new IndexOutOfRangeException(nameof(Color));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -47,6 +80,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexTexture1 : IVertexMaterial
     {
+        #region constructors
+
         public VertexTexture1(Vector2 uv)
         {
             TexCoord = uv;
@@ -57,17 +92,42 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new VertexTexture1(uv);
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("TEXCOORD_0")]
         public Vector2 TexCoord;
 
+        public int MaxColors => 0;
+
+        public int MaxTextures => 1;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { if (setIndex == 0) this.TexCoord = coord; }
 
+        public Vector4 GetColor(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            if (index != 0) throw new ArgumentOutOfRangeException(nameof(index));
+            return TexCoord;
+        }
+
         public void Validate()
         {
             if (!TexCoord._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -75,22 +135,48 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexColor1Texture1 : IVertexMaterial
     {
+        #region constructors
+
         public VertexColor1Texture1(Vector4 color, Vector2 tex)
         {
             Color = color;
             TexCoord = tex;
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("COLOR_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Color;
 
         [VertexAttribute("TEXCOORD_0")]
         public Vector2 TexCoord;
 
+        public int MaxColors => 1;
+
+        public int MaxTextures => 1;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord) { if (setIndex == 0) this.TexCoord = coord; }
 
+        public Vector4 GetColor(int index)
+        {
+            if (index != 0) throw new ArgumentOutOfRangeException(nameof(index));
+            return Color;
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            if (index != 0) throw new ArgumentOutOfRangeException(nameof(index));
+            return TexCoord;
+        }
+
         public void Validate()
         {
             if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
@@ -98,6 +184,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
             if (!TexCoord._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -105,6 +193,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexColor1Texture2 : IVertexMaterial
     {
+        #region constructors
+
         public VertexColor1Texture2(Vector4 color, Vector2 tex0, Vector2 tex1)
         {
             Color = color;
@@ -112,6 +202,10 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord1 = tex1;
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("COLOR_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Color;
 
@@ -121,6 +215,14 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("TEXCOORD_1")]
         public Vector2 TexCoord1;
 
+        public int MaxColors => 1;
+
+        public int MaxTextures => 2;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color) { if (setIndex == 0) this.Color = color; }
 
         void IVertexMaterial.SetTexCoord(int setIndex, Vector2 coord)
@@ -129,6 +231,22 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (setIndex == 1) this.TexCoord1 = coord;
         }
 
+        public Vector4 GetColor(int index)
+        {
+            if (index != 0) throw new ArgumentOutOfRangeException(nameof(index));
+            return Color;
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            switch (index)
+            {
+                case 0: return this.TexCoord0;
+                case 1: return this.TexCoord1;
+                default: throw new ArgumentOutOfRangeException(nameof(index));
+            }
+        }
+
         public void Validate()
         {
             if (!Color._IsReal()) throw new NotFiniteNumberException(nameof(Color));
@@ -137,6 +255,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!TexCoord0._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord0));
             if (!TexCoord1._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord1));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -144,6 +264,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexColor2Texture2 : IVertexMaterial
     {
+        #region constructors
+
         public VertexColor2Texture2(Vector4 color0, Vector4 color1, Vector2 tex0, Vector2 tex1)
         {
             Color0 = color0;
@@ -152,6 +274,10 @@ namespace SharpGLTF.Geometry.VertexTypes
             TexCoord1 = tex1;
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("COLOR_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Color0;
 
@@ -164,6 +290,14 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("TEXCOORD_1")]
         public Vector2 TexCoord1;
 
+        public int MaxColors => 2;
+
+        public int MaxTextures => 2;
+
+        #endregion
+
+        #region API
+
         void IVertexMaterial.SetColor(int setIndex, Vector4 color)
         {
             if (setIndex == 0) this.Color0 = color;
@@ -176,6 +310,26 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (setIndex == 1) this.TexCoord1 = coord;
         }
 
+        public Vector4 GetColor(int index)
+        {
+            switch (index)
+            {
+                case 0: return this.Color0;
+                case 1: return this.Color1;
+                default: throw new ArgumentOutOfRangeException(nameof(index));
+            }
+        }
+
+        public Vector2 GetTexCoord(int index)
+        {
+            switch (index)
+            {
+                case 0: return this.TexCoord0;
+                case 1: return this.TexCoord1;
+                default: throw new ArgumentOutOfRangeException(nameof(index));
+            }
+        }
+
         public void Validate()
         {
             if (!Color0._IsReal()) throw new NotFiniteNumberException(nameof(Color0));
@@ -187,5 +341,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!TexCoord0._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord0));
             if (!TexCoord1._IsReal()) throw new NotFiniteNumberException(nameof(TexCoord1));
         }
+
+        #endregion
     }
 }

+ 40 - 21
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexPosition.cs

@@ -7,19 +7,17 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     public interface IVertexPosition
     {
-        void SetPosition(Vector3 position);
-        void SetNormal(Vector3 normal);
-        void SetTangent(Vector4 tangent);
+        void Validate();
 
         Vector3 GetPosition();
         Boolean TryGetNormal(out Vector3 normal);
         Boolean TryGetTangent(out Vector4 tangent);
 
-        void AssignFrom(IVertexPosition vertex);
+        void SetPosition(Vector3 position);
+        void SetNormal(Vector3 normal);
+        void SetTangent(Vector4 tangent);
 
         void Transform(Matrix4x4 xform);
-
-        void Validate();
     }
 
     /// <summary>
@@ -27,6 +25,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexPosition : IVertexPosition
     {
+        #region constructors
+
         public VertexPosition(Vector3 position)
         {
             this.Position = position;
@@ -42,9 +42,17 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new VertexPosition(position);
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("POSITION")]
         public Vector3 Position;
 
+        #endregion
+
+        #region API
+
         void IVertexPosition.SetPosition(Vector3 position) { this.Position = position; }
 
         void IVertexPosition.SetNormal(Vector3 normal) { }
@@ -57,8 +65,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public bool TryGetTangent(out Vector4 tangent) { tangent = default; return false; }
 
-        public void AssignFrom(IVertexPosition vertex) { this.Position = vertex.GetPosition(); }
-
         public void Transform(Matrix4x4 xform)
         {
             Position = Vector3.Transform(Position, xform);
@@ -68,6 +74,8 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             if (!Position._IsReal()) throw new NotFiniteNumberException(nameof(Position));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -75,6 +83,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexPositionNormal : IVertexPosition
     {
+        #region constructors
+
         public VertexPositionNormal(Vector3 p, Vector3 n)
         {
             Position = p;
@@ -87,12 +97,20 @@ namespace SharpGLTF.Geometry.VertexTypes
             Normal = Vector3.Normalize(new Vector3(nx, ny, nz));
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("POSITION")]
         public Vector3 Position;
 
         [VertexAttribute("NORMAL")]
         public Vector3 Normal;
 
+        #endregion
+
+        #region API
+
         void IVertexPosition.SetPosition(Vector3 position) { this.Position = position; }
 
         void IVertexPosition.SetNormal(Vector3 normal) { this.Normal = normal; }
@@ -105,12 +123,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public bool TryGetTangent(out Vector4 tangent) { tangent = default; return false; }
 
-        public void AssignFrom(IVertexPosition vertex)
-        {
-            this.Position = vertex.GetPosition();
-            if (vertex.TryGetNormal(out Vector3 nrm)) this.Normal = nrm;
-        }
-
         public void Transform(Matrix4x4 xform)
         {
             Position = Vector3.Transform(Position, xform);
@@ -122,6 +134,8 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Position._IsReal()) throw new NotFiniteNumberException(nameof(Position));
             if (!Normal._IsReal()) throw new NotFiniteNumberException(nameof(Normal));
         }
+
+        #endregion
     }
 
     /// <summary>
@@ -129,6 +143,8 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// </summary>
     public struct VertexPositionNormalTangent : IVertexPosition
     {
+        #region constructors
+
         public VertexPositionNormalTangent(Vector3 p, Vector3 n, Vector4 t)
         {
             Position = p;
@@ -136,6 +152,10 @@ namespace SharpGLTF.Geometry.VertexTypes
             Tangent = t;
         }
 
+        #endregion
+
+        #region data
+
         [VertexAttribute("POSITION")]
         public Vector3 Position;
 
@@ -145,6 +165,10 @@ namespace SharpGLTF.Geometry.VertexTypes
         [VertexAttribute("TANGENT")]
         public Vector4 Tangent;
 
+        #endregion
+
+        #region API
+
         void IVertexPosition.SetPosition(Vector3 position) { this.Position = position; }
 
         void IVertexPosition.SetNormal(Vector3 normal) { this.Normal = normal; }
@@ -157,13 +181,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         public bool TryGetTangent(out Vector4 tangent) { tangent = this.Tangent; return true; }
 
-        public void AssignFrom(IVertexPosition vertex)
-        {
-            this.Position = vertex.GetPosition();
-            if (vertex.TryGetNormal(out Vector3 nrm)) this.Normal = nrm;
-            if (vertex.TryGetTangent(out Vector4 tgt)) this.Tangent = tgt;
-        }
-
         public void Transform(Matrix4x4 xform)
         {
             Position = Vector3.Transform(Position, xform);
@@ -180,5 +197,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Normal._IsReal()) throw new NotFiniteNumberException(nameof(Normal));
             if (!Tangent._IsReal()) throw new NotFiniteNumberException(nameof(Tangent));
         }
+
+        #endregion
     }
 }

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

@@ -5,24 +5,89 @@ using System.Text;
 
 namespace SharpGLTF.Geometry.VertexTypes
 {
-    using JOINTWEIGHT = KeyValuePair<int, float>;
-
-    public interface IVertexSkinning
+    /// <summary>
+    /// Represents a a Node Joint index and its weight in a skinning system.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{Joint} = {Weight}")]
+    public struct JointWeightPair : IComparable<JointWeightPair>
     {
-        void SetJoints(int jointSet, Vector4 joints, Vector4 weights);
+        public JointWeightPair(int joint, float weight)
+        {
+            this.Joint = joint;
+            this.Weight = weight;
+            if (Weight == 0) Joint = 0;
+        }
 
-        int MaxJoints { get; }
+        public int Joint;
+        public float Weight;
+
+        public int CompareTo(JointWeightPair other)
+        {
+            var a = this.Weight.CompareTo(other.Weight);
+            if (a != 0) return a;
+
+            return this.Joint.CompareTo(other.Joint);
+        }
+
+        internal static void InPlaceReverseBubbleSort(Span<JointWeightPair> span)
+        {
+            for (int i = 1; i < span.Length; ++i)
+            {
+                bool completed = true;
+
+                for (int j = 0; j < span.Length - 1; ++j)
+                {
+                    if (span[j].CompareTo(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="JointWeightPair"/>.</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<JointWeightPair> 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; }
 
-        JOINTWEIGHT GetJoint(int index);
+            float w = ww;
 
-        void SetJoint(int index, JOINTWEIGHT jw);
+            while (i < span.Length) { ww += span[i++].Weight; }
 
-        void AssignFrom(IVertexSkinning vertex);
+            return ww / w;
+        }
+    }
+
+    public interface IVertexSkinning
+    {
+        int MaxJoints { get; }
 
         // TODO: validation must ensure that:
         // - there's some positive weight
         // - every joint is unique
+        // - joints are sorted by weight
+        // - 0 weight joints point to joint 0
         void Validate();
+
+        JointWeightPair GetJoint(int index);
+
+        void SetJoint(int index, int joint, float weight);
     }
 
     /// <summary>
@@ -60,22 +125,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
-        void IVertexSkinning.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
-        {
-            if (jointSet == 0) { this.Joints = joints; this.Weights = weights; }
-        }
-
-        public void AssignFrom(IVertexSkinning vertex)
-        {
-            var c = Math.Min(this.MaxJoints, vertex.MaxJoints);
-
-            for (int i = 0; i < c; ++i)
-            {
-                var jw = vertex.GetJoint(i);
-                this.SetJoint(i, jw);
-            }
-        }
-
         public void Validate()
         {
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
@@ -84,26 +133,26 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
 
-        public JOINTWEIGHT GetJoint(int index)
+        public JointWeightPair GetJoint(int index)
         {
             switch (index)
             {
-                case 0: return new JOINTWEIGHT((int)this.Joints.X, this.Weights.X);
-                case 1: return new JOINTWEIGHT((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JOINTWEIGHT((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JOINTWEIGHT((int)this.Joints.W, this.Weights.W);
+                case 0: return new JointWeightPair((int)this.Joints.X, this.Weights.X);
+                case 1: return new JointWeightPair((int)this.Joints.Y, this.Weights.Y);
+                case 2: return new JointWeightPair((int)this.Joints.Z, this.Weights.Z);
+                case 3: return new JointWeightPair((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void SetJoint(int index, JOINTWEIGHT jw)
+        public void SetJoint(int index, int joint, float weight)
         {
             switch (index)
             {
-                case 0: { this.Joints.X = jw.Key; this.Weights.X = jw.Value; return; }
-                case 1: { this.Joints.Y = jw.Key; this.Weights.Y = jw.Value; return; }
-                case 2: { this.Joints.Z = jw.Key; this.Weights.Z = jw.Value; return; }
-                case 3: { this.Joints.W = jw.Key; this.Weights.W = jw.Value; return; }
+                case 0: { this.Joints.X = joint; this.Weights.X = weight; return; }
+                case 1: { this.Joints.Y = joint; this.Weights.Y = weight; return; }
+                case 2: { this.Joints.Z = joint; this.Weights.Z = weight; return; }
+                case 3: { this.Joints.W = joint; this.Weights.W = weight; return; }
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
@@ -146,11 +195,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
-        void IVertexSkinning.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
-        {
-            if (jointSet == 0) { this.Joints = joints; this.Weights = weights; }
-        }
-
         public void Validate()
         {
             if (!Joints._IsReal()) throw new NotFiniteNumberException(nameof(Joints));
@@ -159,35 +203,30 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights._IsReal()) throw new NotFiniteNumberException(nameof(Weights));
         }
 
-        public JOINTWEIGHT GetJoint(int index)
+        public JointWeightPair GetJoint(int index)
         {
             switch (index)
             {
-                case 0: return new JOINTWEIGHT((int)this.Joints.X, this.Weights.X);
-                case 1: return new JOINTWEIGHT((int)this.Joints.Y, this.Weights.Y);
-                case 2: return new JOINTWEIGHT((int)this.Joints.Z, this.Weights.Z);
-                case 3: return new JOINTWEIGHT((int)this.Joints.W, this.Weights.W);
+                case 0: return new JointWeightPair((int)this.Joints.X, this.Weights.X);
+                case 1: return new JointWeightPair((int)this.Joints.Y, this.Weights.Y);
+                case 2: return new JointWeightPair((int)this.Joints.Z, this.Weights.Z);
+                case 3: return new JointWeightPair((int)this.Joints.W, this.Weights.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void SetJoint(int index, JOINTWEIGHT jw)
+        public void SetJoint(int index, int joint, float weight)
         {
             switch (index)
             {
-                case 0: { this.Joints.X = jw.Key; this.Weights.X = jw.Value; return; }
-                case 1: { this.Joints.Y = jw.Key; this.Weights.Y = jw.Value; return; }
-                case 2: { this.Joints.Z = jw.Key; this.Weights.Z = jw.Value; return; }
-                case 3: { this.Joints.W = jw.Key; this.Weights.W = jw.Value; return; }
+                case 0: { this.Joints.X = joint; this.Weights.X = weight; return; }
+                case 1: { this.Joints.Y = joint; this.Weights.Y = weight; return; }
+                case 2: { this.Joints.Z = joint; this.Weights.Z = weight; return; }
+                case 3: { this.Joints.W = joint; this.Weights.W = weight; return; }
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void AssignFrom(IVertexSkinning vertex)
-        {
-            throw new NotImplementedException();
-        }
-
         #endregion
     }
 
@@ -236,12 +275,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
-        void IVertexSkinning.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
-        {
-            if (jointSet == 0) { this.Joints0 = joints; this.Weights0 = weights; }
-            if (jointSet == 1) { this.Joints1 = joints; this.Weights1 = weights; }
-        }
-
         public void Validate()
         {
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
@@ -254,43 +287,38 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
 
-        public JOINTWEIGHT GetJoint(int index)
+        public JointWeightPair GetJoint(int index)
         {
             switch (index)
             {
-                case 0: return new JOINTWEIGHT((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JOINTWEIGHT((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JOINTWEIGHT((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JOINTWEIGHT((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JOINTWEIGHT((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JOINTWEIGHT((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JOINTWEIGHT((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JOINTWEIGHT((int)this.Joints1.W, this.Weights1.W);
+                case 0: return new JointWeightPair((int)this.Joints0.X, this.Weights0.X);
+                case 1: return new JointWeightPair((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return new JointWeightPair((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return new JointWeightPair((int)this.Joints0.W, this.Weights0.W);
+                case 4: return new JointWeightPair((int)this.Joints1.X, this.Weights1.X);
+                case 5: return new JointWeightPair((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return new JointWeightPair((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return new JointWeightPair((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void SetJoint(int index, JOINTWEIGHT jw)
+        public void SetJoint(int index, int joint, float weight)
         {
             switch (index)
             {
-                case 0: { this.Joints0.X = jw.Key; this.Weights0.X = jw.Value; return; }
-                case 1: { this.Joints0.Y = jw.Key; this.Weights0.Y = jw.Value; return; }
-                case 2: { this.Joints0.Z = jw.Key; this.Weights0.Z = jw.Value; return; }
-                case 3: { this.Joints0.W = jw.Key; this.Weights0.W = jw.Value; return; }
-                case 4: { this.Joints1.X = jw.Key; this.Weights1.X = jw.Value; return; }
-                case 5: { this.Joints1.Y = jw.Key; this.Weights1.Y = jw.Value; return; }
-                case 6: { this.Joints1.Z = jw.Key; this.Weights1.Z = jw.Value; return; }
-                case 7: { this.Joints1.W = jw.Key; this.Weights1.W = jw.Value; return; }
+                case 0: { this.Joints0.X = joint; this.Weights0.X = weight; return; }
+                case 1: { this.Joints0.Y = joint; this.Weights0.Y = weight; return; }
+                case 2: { this.Joints0.Z = joint; this.Weights0.Z = weight; return; }
+                case 3: { this.Joints0.W = joint; this.Weights0.W = weight; return; }
+                case 4: { this.Joints1.X = joint; this.Weights1.X = weight; return; }
+                case 5: { this.Joints1.Y = joint; this.Weights1.Y = weight; return; }
+                case 6: { this.Joints1.Z = joint; this.Weights1.Z = weight; return; }
+                case 7: { this.Joints1.W = joint; this.Weights1.W = weight; return; }
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void AssignFrom(IVertexSkinning vertex)
-        {
-            throw new NotImplementedException();
-        }
-
         #endregion
     }
 
@@ -339,12 +367,6 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region API
 
-        void IVertexSkinning.SetJoints(int jointSet, Vector4 joints, Vector4 weights)
-        {
-            if (jointSet == 0) { this.Joints0 = joints; this.Weights0 = weights; }
-            if (jointSet == 1) { this.Joints1 = joints; this.Weights1 = weights; }
-        }
-
         public void Validate()
         {
             if (!Joints0._IsReal()) throw new NotFiniteNumberException(nameof(Joints0));
@@ -357,43 +379,38 @@ namespace SharpGLTF.Geometry.VertexTypes
             if (!Weights1._IsReal()) throw new NotFiniteNumberException(nameof(Weights1));
         }
 
-        public JOINTWEIGHT GetJoint(int index)
+        public JointWeightPair GetJoint(int index)
         {
             switch (index)
             {
-                case 0: return new JOINTWEIGHT((int)this.Joints0.X, this.Weights0.X);
-                case 1: return new JOINTWEIGHT((int)this.Joints0.Y, this.Weights0.Y);
-                case 2: return new JOINTWEIGHT((int)this.Joints0.Z, this.Weights0.Z);
-                case 3: return new JOINTWEIGHT((int)this.Joints0.W, this.Weights0.W);
-                case 4: return new JOINTWEIGHT((int)this.Joints1.X, this.Weights1.X);
-                case 5: return new JOINTWEIGHT((int)this.Joints1.Y, this.Weights1.Y);
-                case 6: return new JOINTWEIGHT((int)this.Joints1.Z, this.Weights1.Z);
-                case 7: return new JOINTWEIGHT((int)this.Joints1.W, this.Weights1.W);
+                case 0: return new JointWeightPair((int)this.Joints0.X, this.Weights0.X);
+                case 1: return new JointWeightPair((int)this.Joints0.Y, this.Weights0.Y);
+                case 2: return new JointWeightPair((int)this.Joints0.Z, this.Weights0.Z);
+                case 3: return new JointWeightPair((int)this.Joints0.W, this.Weights0.W);
+                case 4: return new JointWeightPair((int)this.Joints1.X, this.Weights1.X);
+                case 5: return new JointWeightPair((int)this.Joints1.Y, this.Weights1.Y);
+                case 6: return new JointWeightPair((int)this.Joints1.Z, this.Weights1.Z);
+                case 7: return new JointWeightPair((int)this.Joints1.W, this.Weights1.W);
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void SetJoint(int index, JOINTWEIGHT jw)
+        public void SetJoint(int index, int joint, float weight)
         {
             switch (index)
             {
-                case 0: { this.Joints0.X = jw.Key; this.Weights0.X = jw.Value; return; }
-                case 1: { this.Joints0.Y = jw.Key; this.Weights0.Y = jw.Value; return; }
-                case 2: { this.Joints0.Z = jw.Key; this.Weights0.Z = jw.Value; return; }
-                case 3: { this.Joints0.W = jw.Key; this.Weights0.W = jw.Value; return; }
-                case 4: { this.Joints1.X = jw.Key; this.Weights1.X = jw.Value; return; }
-                case 5: { this.Joints1.Y = jw.Key; this.Weights1.Y = jw.Value; return; }
-                case 6: { this.Joints1.Z = jw.Key; this.Weights1.Z = jw.Value; return; }
-                case 7: { this.Joints1.W = jw.Key; this.Weights1.W = jw.Value; return; }
+                case 0: { this.Joints0.X = joint; this.Weights0.X = weight; return; }
+                case 1: { this.Joints0.Y = joint; this.Weights0.Y = weight; return; }
+                case 2: { this.Joints0.Z = joint; this.Weights0.Z = weight; return; }
+                case 3: { this.Joints0.W = joint; this.Weights0.W = weight; return; }
+                case 4: { this.Joints1.X = joint; this.Weights1.X = weight; return; }
+                case 5: { this.Joints1.Y = joint; this.Weights1.Y = weight; return; }
+                case 6: { this.Joints1.Z = joint; this.Weights1.Z = weight; return; }
+                case 7: { this.Joints1.W = joint; this.Weights1.W = weight; return; }
                 default: throw new ArgumentOutOfRangeException(nameof(index));
             }
         }
 
-        public void AssignFrom(IVertexSkinning vertex)
-        {
-            throw new NotImplementedException();
-        }
-
         #endregion
     }
 }

+ 96 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -7,6 +7,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     using Memory;
 
+    using JOINTWEIGHT = KeyValuePair<int, float>;
+
     static class VertexUtils
     {
         public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TvP, TvM, TvS>(this IEnumerable<IReadOnlyList<(TvP, TvM, TvS)>> vertexBlocks)
@@ -186,5 +188,99 @@ namespace SharpGLTF.Geometry.VertexTypes
 
             return dst;
         }
+
+        public static TvP CloneAs<TvP>(this IVertexPosition src)
+            where TvP : struct, IVertexPosition
+        {
+            if (src.GetType() == typeof(TvP)) return (TvP)src;
+
+            var dst = default(TvP);
+
+            dst.SetPosition(src.GetPosition());
+            if (src.TryGetNormal(out Vector3 nrm)) dst.SetNormal(nrm);
+            if (src.TryGetTangent(out Vector4 tgt)) dst.SetTangent(tgt);
+
+            return dst;
+        }
+
+        public static TvM CloneAs<TvM>(this IVertexMaterial src)
+            where TvM : struct, IVertexMaterial
+        {
+            if (src.GetType() == typeof(TvM)) return (TvM)src;
+
+            var dst = default(TvM);
+
+            int i = 0;
+
+            while (i < Math.Min(src.MaxColors, dst.MaxColors))
+            {
+                dst.SetColor(i, src.GetColor(i));
+                ++i;
+            }
+
+            while (i < dst.MaxColors)
+            {
+                dst.SetColor(i, Vector4.One);
+                ++i;
+            }
+
+            i = 0;
+
+            while (i < Math.Min(src.MaxTextures, dst.MaxTextures))
+            {
+                dst.SetTexCoord(i, src.GetTexCoord(i));
+                ++i;
+            }
+
+            while (i < dst.MaxColors)
+            {
+                dst.SetTexCoord(i, Vector2.Zero);
+                ++i;
+            }
+
+            return dst;
+        }
+
+        public static unsafe TvS CloneAs<TvS>(this IVertexSkinning src)
+            where TvS : struct, IVertexSkinning
+        {
+            if (src.GetType() == typeof(TvS)) return (TvS)src;
+
+            // create copy
+
+            var dst = default(TvS);
+
+            if (dst.MaxJoints >= src.MaxJoints)
+            {
+                for (int i = 0; i < src.MaxJoints; ++i)
+                {
+                    var jw = src.GetJoint(i);
+
+                    dst.SetJoint(i, jw.Joint, jw.Weight);
+                }
+
+                return dst;
+            }
+
+            // if there's more source joints than destination joints, transfer with scale
+
+            Span<JointWeightPair> srcjw = stackalloc JointWeightPair[src.MaxJoints];
+
+            for (int i = 0; i < src.MaxJoints; ++i)
+            {
+                srcjw[i] = src.GetJoint(i);
+            }
+
+            JointWeightPair.InPlaceReverseBubbleSort(srcjw);
+
+            var w = JointWeightPair.CalculateScaleFor(srcjw, dst.MaxJoints);
+
+            for (int i = 0; i < dst.MaxJoints; ++i)
+            {
+                dst.SetJoint(i, srcjw[i].Joint, srcjw[i].Weight * w);
+            }
+
+            return dst;
+        }
     }
 }

+ 2 - 1
src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj

@@ -16,6 +16,7 @@
     <LangVersion>latest</LangVersion>
     <DebugSymbols>true</DebugSymbols>
     <PackageLicenseFile>LICENSE</PackageLicenseFile>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <ItemGroup>
@@ -46,7 +47,7 @@
     <CodeAnalysisRuleSet>..\..\SharpGLTF.ruleset</CodeAnalysisRuleSet>    
     <PackageIconUrl>https://raw.githubusercontent.com/vpenades/SharpGLTF/master/build/Icons/glTF2Sharp.png</PackageIconUrl>
   </PropertyGroup>
-
+  
   <ItemGroup>
     <AdditionalFiles Include="..\..\stylecop.json" />
   </ItemGroup>

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

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF.Geometry.VertexTypes
+{
+    [TestFixture]
+    public class JointWeightPairTests
+    {
+        [Test]
+        public void TestSorting()
+        {
+            var pairs = new[]
+            {
+                new JointWeightPair(2,0),
+                new JointWeightPair(1,0.20f),
+                new JointWeightPair(3,0.15f),
+                new JointWeightPair(2,0.25f),
+                new JointWeightPair(4,0),
+                new JointWeightPair(7,0.40f)
+            };
+
+            JointWeightPair.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);
+        }
+    }
+}

+ 35 - 0
tests/SharpGLTF.Tests/Geometry/VertexTypes/VertexSkinningTests.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF.Geometry.VertexTypes
+{
+    [TestFixture]
+    public class VertexSkinningTests
+    {
+        [Test]
+        public void TestCloneAs()
+        {
+            var v8 = new VertexJoints8x8();
+            v8.SetJoint(0, 1, 0.2f);
+            v8.SetJoint(1, 2, 0.15f);
+            v8.SetJoint(2, 3, 0.25f);
+            v8.SetJoint(3, 4, 0.10f);
+            v8.SetJoint(4, 5, 0.30f);
+
+            var v4 = v8.CloneAs<VertexJoints8x4>();
+
+            Assert.AreEqual(5, v4.GetJoint(0).Joint);
+            Assert.AreEqual(3, v4.GetJoint(1).Joint);
+            Assert.AreEqual(1, v4.GetJoint(2).Joint);
+            Assert.AreEqual(2, v4.GetJoint(3).Joint);
+
+            Assert.AreEqual(0.333333f, v4.GetJoint(0).Weight, 0.01f);
+            Assert.AreEqual(0.277777f, v4.GetJoint(1).Weight, 0.01f);
+            Assert.AreEqual(0.222222f, v4.GetJoint(2).Weight, 0.01f);
+            Assert.AreEqual(0.166666f, v4.GetJoint(3).Weight, 0.01f);
+        }
+    }
+}