Browse Source

Wrapping up SparseWeight8 API
Code Cleanup
+Analyzers

Vicente Penades 6 years ago
parent
commit
aa7c82c8fc

+ 14 - 0
Analyzers.targets

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+
+  <PropertyGroup>
+    <CodeAnalysisRuleSet>$(MSBUILDTHISFILEDIRECTORY)SharpGLTF.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
+  <ItemGroup>    
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.3" PrivateAssets="all" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
+  </ItemGroup>
+	
+</Project>

+ 8 - 0
SharpGLTF.ruleset

@@ -53,4 +53,12 @@
     <Rule Id="CA1710" Action="Info" />
     <Rule Id="CA1710" Action="Info" />
     <Rule Id="CA2225" Action="Info" />
     <Rule Id="CA2225" Action="Info" />
   </Rules>
   </Rules>
+  <Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
+    <Rule Id="CA1816" Action="Error" />
+    <Rule Id="CA2000" Action="Error" />
+    <Rule Id="CA2213" Action="Error" />
+    <Rule Id="CA2216" Action="Error" />
+    <Rule Id="CA2242" Action="Error" />
+    <Rule Id="CA1303" Action="Info" />
+  </Rules>
 </RuleSet>
 </RuleSet>

+ 10 - 2
src/SharpGLTF.Core/Memory/ColorArray.cs

@@ -114,9 +114,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
         public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Vector4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Vector4[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Vector4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Vector4> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
 
 

+ 72 - 14
src/SharpGLTF.Core/Memory/FloatingArrays.cs

@@ -300,9 +300,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Single item) { return this._FirstIndexOf(item); }
         public int IndexOf(Single item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Single[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Single[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Single> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Single> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Single>.Insert(int index, Single item) { throw new NotSupportedException(); }
         void IList<Single>.Insert(int index, Single item) { throw new NotSupportedException(); }
 
 
@@ -411,9 +419,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Vector2 item) { return this._FirstIndexOf(item); }
         public int IndexOf(Vector2 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Vector2[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Vector2[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Vector2> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Vector2> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Vector2>.Insert(int index, Vector2 item) { throw new NotSupportedException(); }
         void IList<Vector2>.Insert(int index, Vector2 item) { throw new NotSupportedException(); }
 
 
@@ -523,9 +539,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Vector3 item) { return this._FirstIndexOf(item); }
         public int IndexOf(Vector3 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Vector3[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Vector3[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Vector3> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Vector3> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Vector3>.Insert(int index, Vector3 item) { throw new NotSupportedException(); }
         void IList<Vector3>.Insert(int index, Vector3 item) { throw new NotSupportedException(); }
 
 
@@ -636,9 +660,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
         public int IndexOf(Vector4 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Vector4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Vector4[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Vector4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Vector4> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
         void IList<Vector4>.Insert(int index, Vector4 item) { throw new NotSupportedException(); }
 
 
@@ -715,9 +747,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Quaternion item) { return this._FirstIndexOf(item); }
         public int IndexOf(Quaternion item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Quaternion[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Quaternion[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Quaternion> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Quaternion> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Quaternion>.Insert(int index, Quaternion item) { throw new NotSupportedException(); }
         void IList<Quaternion>.Insert(int index, Quaternion item) { throw new NotSupportedException(); }
 
 
@@ -812,9 +852,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Matrix4x4 item) { return this._FirstIndexOf(item); }
         public int IndexOf(Matrix4x4 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Matrix4x4[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Matrix4x4[] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Matrix4x4> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Matrix4x4> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Matrix4x4>.Insert(int index, Matrix4x4 item) { throw new NotSupportedException(); }
         void IList<Matrix4x4>.Insert(int index, Matrix4x4 item) { throw new NotSupportedException(); }
 
 
@@ -894,6 +942,8 @@ namespace SharpGLTF.Memory
 
 
         public void CopyItemTo(int index, Single[] dstItem)
         public void CopyItemTo(int index, Single[] dstItem)
         {
         {
+            Guard.NotNull(dstItem, nameof(dstItem));
+
             var count = _Dimensions;
             var count = _Dimensions;
 
 
             for (int i = 0; i < count; ++i) dstItem[i] = _Accessor[index, i];
             for (int i = 0; i < count; ++i) dstItem[i] = _Accessor[index, i];
@@ -907,9 +957,17 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(Single[] item) { return this._FirstIndexOf(item); }
         public int IndexOf(Single[] item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(Single[][] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(Single[][] array, int arrayIndex)
+        {
+            Guard.NotNull(array, nameof(array));
+            this._CopyTo(array, arrayIndex);
+        }
 
 
-        public void Fill(IEnumerable<Single[]> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Single[]> values, int dstStart = 0)
+        {
+            Guard.NotNull(values, nameof(values));
+            values._CopyTo(this, dstStart);
+        }
 
 
         void IList<Single[]>.Insert(int index, Single[] item) { throw new NotSupportedException(); }
         void IList<Single[]>.Insert(int index, Single[] item) { throw new NotSupportedException(); }
 
 

+ 4 - 0
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -143,6 +143,8 @@ namespace SharpGLTF.Schema2
 
 
         public void SetIndexData(MemoryAccessor src)
         public void SetIndexData(MemoryAccessor src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ELEMENT_ARRAY_BUFFER);
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ELEMENT_ARRAY_BUFFER);
             SetIndexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Encoding.ToIndex());
             SetIndexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Encoding.ToIndex());
         }
         }
@@ -178,6 +180,8 @@ namespace SharpGLTF.Schema2
 
 
         public void SetVertexData(MemoryAccessor src)
         public void SetVertexData(MemoryAccessor src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ARRAY_BUFFER);
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ARRAY_BUFFER);
 
 
             SetVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
             SetVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);

+ 2 - 0
src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs

@@ -75,6 +75,8 @@ namespace SharpGLTF.Schema2
 
 
         protected static IEnumerable<ExtraProperties> Flatten(ExtraProperties container)
         protected static IEnumerable<ExtraProperties> Flatten(ExtraProperties container)
         {
         {
+            if (container == null) yield break;
+
             yield return container;
             yield return container;
 
 
             foreach (var c in container.GetLogicalChildren())
             foreach (var c in container.GetLogicalChildren())

+ 12 - 1
src/SharpGLTF.Core/Schema2/gltf.Material.cs

@@ -72,7 +72,18 @@ namespace SharpGLTF.Schema2
         /// <summary>
         /// <summary>
         /// Finds an instance of <see cref="MaterialChannel"/>
         /// Finds an instance of <see cref="MaterialChannel"/>
         /// </summary>
         /// </summary>
-        /// <param name="channelKey">the channel key</param>
+        /// <param name="channelKey">
+        /// The channel key. Currently, these values are used:
+        /// - "Normal"
+        /// - "Occlusion"
+        /// - "Emissive"
+        /// - When material is <see cref="MaterialPBRMetallicRoughness"/>:
+        ///   - "BaseColor"
+        ///   - "MetallicRoughness"
+        /// - When material is <see cref="MaterialPBRSpecularGlossiness"/>:
+        ///   - "Diffuse"
+        ///   - "SpecularGlossiness"
+        /// </param>
         /// <returns>A <see cref="MaterialChannel"/> structure. or null if it does not exist</returns>
         /// <returns>A <see cref="MaterialChannel"/> structure. or null if it does not exist</returns>
         public MaterialChannel? FindChannel(string channelKey)
         public MaterialChannel? FindChannel(string channelKey)
         {
         {

+ 4 - 2
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -176,9 +176,11 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Transforms.ITransform"/> object</returns>
         /// <returns>A <see cref="Transforms.ITransform"/> object</returns>
         public Transforms.ITransform GetMeshWorldTransform(Animation animation, float time)
         public Transforms.ITransform GetMeshWorldTransform(Animation animation, float time)
         {
         {
+            if (animation != null) Guard.MustShareLogicalParent(this, animation, nameof(animation));
+
             var weights = animation == null ? Transforms.SparseWeight8.Create(this.MorphWeights) : animation.GetSparseMorphWeights(this, time);
             var weights = animation == null ? Transforms.SparseWeight8.Create(this.MorphWeights) : animation.GetSparseMorphWeights(this, time);
 
 
-            if (this.Skin == null) return new Transforms.StaticTransform(this.GetWorldMatrix(animation, time), weights);
+            if (this.Skin == null) return new Transforms.StaticTransform(this.GetWorldMatrix(animation, time), weights, false);
 
 
             var jointXforms = new Matrix4x4[this.Skin.JointsCount];
             var jointXforms = new Matrix4x4[this.Skin.JointsCount];
             var invBindings = new Matrix4x4[this.Skin.JointsCount];
             var invBindings = new Matrix4x4[this.Skin.JointsCount];
@@ -190,7 +192,7 @@ namespace SharpGLTF.Schema2
                 invBindings[i] = j.Value;
                 invBindings[i] = j.Value;
             }
             }
 
 
-            return new Transforms.SkinTransform(invBindings, jointXforms, weights);
+            return new Transforms.SkinTransform(invBindings, jointXforms, weights, false);
         }
         }
 
 
         #endregion
         #endregion

+ 2 - 9
src/SharpGLTF.Core/SharpGLTF.Core.csproj

@@ -17,7 +17,6 @@
     <DebugSymbols>true</DebugSymbols>
     <DebugSymbols>true</DebugSymbols>
     <PackageLicenseFile>LICENSE</PackageLicenseFile>    
     <PackageLicenseFile>LICENSE</PackageLicenseFile>    
     <PackageIconUrl>https://raw.githubusercontent.com/vpenades/SharpGLTF/master/build/Icons/glTF2Sharp.png</PackageIconUrl>
     <PackageIconUrl>https://raw.githubusercontent.com/vpenades/SharpGLTF/master/build/Icons/glTF2Sharp.png</PackageIconUrl>
-    <CodeAnalysisRuleSet>..\..\SharpGLTF.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -42,14 +41,6 @@
   <ItemGroup>    
   <ItemGroup>    
     <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
     <PackageReference Include="System.Memory" Version="4.5.3" />
     <PackageReference Include="System.Memory" Version="4.5.3" />
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-    </PackageReference>
-    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.3">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-    </PackageReference>
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -62,4 +53,6 @@
     <AdditionalFiles Include="..\..\stylecop.json" />
     <AdditionalFiles Include="..\..\stylecop.json" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <Import Project="..\..\Analyzers.targets" />
+
 </Project>
 </Project>

+ 67 - 43
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -36,15 +36,14 @@ namespace SharpGLTF.Transforms
     {
     {
         #region constructor
         #region constructor
 
 
-        protected MorphTransform(SparseWeight8 morphWeights)
+        protected MorphTransform()
         {
         {
-            if (morphWeights.IsZero)
-            {
-                _Weights = new SparseWeight8((0, 1));
-                return;
-            }
+            Update(new SparseWeight8((0, 1)), false);
+        }
 
 
-            _Weights = Normalize(morphWeights);
+        protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Update(morphWeights, useAbsoluteMorphTargets);
         }
         }
 
 
         #endregion
         #endregion
@@ -56,18 +55,31 @@ namespace SharpGLTF.Transforms
         /// - Indices with value zero point to the master mesh
         /// - Indices with value zero point to the master mesh
         /// - Indices with value over zero point to MorphTarget[index-1].
         /// - Indices with value over zero point to MorphTarget[index-1].
         /// </summary>
         /// </summary>
-        private readonly SparseWeight8 _Weights;
+        private SparseWeight8 _Weights;
 
 
         /// <summary>
         /// <summary>
         /// True if morph targets represent absolute values.
         /// True if morph targets represent absolute values.
         /// False if morph targets represent values relative to master value.
         /// False if morph targets represent values relative to master value.
         /// </summary>
         /// </summary>
-        private readonly bool _AbsoluteMorphs;
+        private bool _AbsoluteMorphTargets;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
+        public void Update(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
+        {
+            _AbsoluteMorphTargets = useAbsoluteMorphTargets;
+
+            if (morphWeights.IsZero)
+            {
+                _Weights = new SparseWeight8((0, 1));
+                return;
+            }
+
+            _Weights = Normalize(morphWeights);
+        }
+
         /// <summary>
         /// <summary>
         /// Increments all indices and adds a new Index[0] with a weight that makes the sum of all weights equal to 1
         /// Increments all indices and adds a new Index[0] with a weight that makes the sum of all weights equal to 1
         /// </summary>
         /// </summary>
@@ -123,7 +135,7 @@ namespace SharpGLTF.Transforms
 
 
             var p = V3.Zero;
             var p = V3.Zero;
 
 
-            if (_AbsoluteMorphs)
+            if (_AbsoluteMorphTargets)
             {
             {
                 foreach (var pair in _Weights.GetPairs())
                 foreach (var pair in _Weights.GetPairs())
                 {
                 {
@@ -151,7 +163,7 @@ namespace SharpGLTF.Transforms
 
 
             var p = V4.Zero;
             var p = V4.Zero;
 
 
-            if (_AbsoluteMorphs)
+            if (_AbsoluteMorphTargets)
             {
             {
                 foreach (var pair in _Weights.GetPairs())
                 foreach (var pair in _Weights.GetPairs())
                 {
                 {
@@ -183,32 +195,18 @@ namespace SharpGLTF.Transforms
     {
     {
         #region constructor
         #region constructor
 
 
-        public StaticTransform(TRANSFORM xform, SparseWeight8 morphWeights)
-            : base(morphWeights)
+        public StaticTransform(TRANSFORM xform, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
         {
         {
-            _Transform = xform;
-
-            // http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
-
-            float determinant3x3 =
-                +(xform.M13 * xform.M21 * xform.M32)
-                + (xform.M11 * xform.M22 * xform.M33)
-                + (xform.M12 * xform.M23 * xform.M31)
-                - (xform.M12 * xform.M21 * xform.M33)
-                - (xform.M13 * xform.M22 * xform.M31)
-                - (xform.M11 * xform.M23 * xform.M32);
-
-            _Visible = Math.Abs(determinant3x3) > float.Epsilon;
-            _FlipFaces = determinant3x3 < 0;
+            Update(xform, morphWeights, useAbsoluteMorphs);
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region data
         #region data
 
 
-        private readonly TRANSFORM _Transform;
-        private readonly Boolean _Visible;
-        private readonly Boolean _FlipFaces;
+        private TRANSFORM _Transform;
+        private Boolean _Visible;
+        private Boolean _FlipFaces;
 
 
         #endregion
         #endregion
 
 
@@ -222,6 +220,26 @@ namespace SharpGLTF.Transforms
 
 
         #region API
         #region API
 
 
+        public void Update(TRANSFORM xform, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
+        {
+            Update(morphWeights, useAbsoluteMorphs);
+
+            _Transform = xform;
+
+            // http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
+
+            float determinant3x3 =
+                +(xform.M13 * xform.M21 * xform.M32)
+                + (xform.M11 * xform.M22 * xform.M33)
+                + (xform.M12 * xform.M23 * xform.M31)
+                - (xform.M12 * xform.M21 * xform.M33)
+                - (xform.M13 * xform.M22 * xform.M31)
+                - (xform.M11 * xform.M23 * xform.M32);
+
+            _Visible = Math.Abs(determinant3x3) > float.Epsilon;
+            _FlipFaces = determinant3x3 < 0;
+        }
+
         public V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights)
         public V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights)
         {
         {
             position = MorphVectors(position, morphTargets);
             position = MorphVectors(position, morphTargets);
@@ -252,31 +270,37 @@ namespace SharpGLTF.Transforms
     {
     {
         #region constructor
         #region constructor
 
 
-        public SkinTransform(TRANSFORM[] invBindings, TRANSFORM[] xforms, SparseWeight8 morphWeights)
-            : base(morphWeights)
+        public SkinTransform(TRANSFORM[] invBindings, TRANSFORM[] xforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
         {
         {
-            Guard.NotNull(invBindings, nameof(invBindings));
-            Guard.NotNull(xforms, nameof(xforms));
-            Guard.IsTrue(invBindings.Length == xforms.Length, nameof(xforms), $"{invBindings} and {xforms} length mismatch.");
-
-            _JointTransforms = new TRANSFORM[invBindings.Length];
-
-            for (int i = 0; i < _JointTransforms.Length; ++i)
-            {
-                _JointTransforms[i] = invBindings[i] * xforms[i];
-            }
+            Update(invBindings, xforms, morphWeights, useAbsoluteMorphTargets);
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region data
         #region data
 
 
-        private readonly TRANSFORM[] _JointTransforms;
+        private TRANSFORM[] _JointTransforms;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
+        public void Update(TRANSFORM[] invBindings, TRANSFORM[] xforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
+        {
+            Guard.NotNull(invBindings, nameof(invBindings));
+            Guard.NotNull(xforms, nameof(xforms));
+            Guard.IsTrue(invBindings.Length == xforms.Length, nameof(xforms), $"{invBindings} and {xforms} length mismatch.");
+
+            Update(morphWeights, useAbsoluteMorphTargets);
+
+            if (_JointTransforms == null || _JointTransforms.Length != invBindings.Length) _JointTransforms = new TRANSFORM[invBindings.Length];
+
+            for (int i = 0; i < _JointTransforms.Length; ++i)
+            {
+                _JointTransforms[i] = invBindings[i] * xforms[i];
+            }
+        }
+
         public bool Visible => true;
         public bool Visible => true;
 
 
         public bool FlipFaces => false;
         public bool FlipFaces => false;

+ 258 - 137
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -7,7 +7,7 @@ using System.Text;
 namespace SharpGLTF.Transforms
 namespace SharpGLTF.Transforms
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a sparse collection of 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>
     [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>, IReadOnlyDictionary<int, float>
     public struct SparseWeight8 : IReadOnlyList<float>, IReadOnlyDictionary<int, float>
@@ -25,7 +25,7 @@ namespace SharpGLTF.Transforms
 
 
             var indexedWeights = weights
             var indexedWeights = weights
                 .Select((val, idx) => (idx, val))
                 .Select((val, idx) => (idx, val))
-                .Where(item => item.val > 0)
+                .Where(item => item.val != 0)
                 .OrderByDescending(item => item.val)
                 .OrderByDescending(item => item.val)
                 .Take(8)
                 .Take(8)
                 .ToArray();
                 .ToArray();
@@ -101,6 +101,42 @@ namespace SharpGLTF.Transforms
         public float Weight6;
         public float Weight6;
         public float Weight7;
         public float Weight7;
 
 
+        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];
+
+            int c = 0;
+            c = CopyTo(x, indices, xWeights, c);
+            c = CopyTo(y, indices, yWeights, c);
+
+            xWeights = xWeights.Slice(0, c);
+            yWeights = yWeights.Slice(0, c);
+
+            return xWeights.SequenceEqual(yWeights);
+        }
+
+        public int GetWeightsHashCode()
+        {
+            Span<int> indices = stackalloc int[8];
+            Span<float> weights = stackalloc float[8];
+
+            var c = CopyTo(this, indices, weights, 0);
+
+            BubbleSortByIndex(indices, weights, c);
+
+            int h = 0;
+
+            for (int i = 0; i < c; ++i)
+            {
+                h += indices[i].GetHashCode() ^ weights[i].GetHashCode();
+                h *= 17;
+            }
+
+            return h;
+        }
+
         #endregion
         #endregion
 
 
         #region properties
         #region properties
@@ -121,89 +157,96 @@ namespace SharpGLTF.Transforms
 
 
         #region API
         #region API
 
 
-        public static SparseWeight8 InterpolateLinear(SparseWeight8 x, SparseWeight8 y, float amount)
+        public static SparseWeight8 Add(in SparseWeight8 x, in SparseWeight8 y)
         {
         {
-            Span<int> indices = stackalloc int[16];
-            Span<float> xWeights = stackalloc float[16];
-            Span<float> yWeights = stackalloc float[16];
-
-            int offset = 0;
-            offset = CopyTo(x, indices, xWeights, offset);
-            offset = CopyTo(y, indices, yWeights, offset);
+            return _OperateLinear(x, y, (xx, yy) => xx + yy);
+        }
 
 
-            indices = indices.Slice(0, offset);
-            xWeights = xWeights.Slice(0, offset);
-            yWeights = yWeights.Slice(0, offset);
+        public static SparseWeight8 Subtract(in SparseWeight8 x, in SparseWeight8 y)
+        {
+            return _OperateLinear(x, y, (xx, yy) => xx - yy);
+        }
 
 
-            var invAmount = 1.0f - amount;
+        public static SparseWeight8 Multiply(in SparseWeight8 x, in SparseWeight8 y)
+        {
+            return _OperateLinear(x, y, (xx, yy) => xx * yy);
+        }
 
 
-            for (int i = 0; i < indices.Length; ++i)
-            {
-                xWeights[i] = (xWeights[i] * invAmount) + (yWeights[i] * amount);
-            }
+        public static SparseWeight8 Divide(in SparseWeight8 x, in SparseWeight8 y)
+        {
+            return _OperateLinear(x, y, (xx, yy) => xx / yy);
+        }
 
 
-            BubbleSort(indices, xWeights);
+        public static SparseWeight8 InterpolateLinear(SparseWeight8 x, SparseWeight8 y, float amount)
+        {
+            var xAmount = 1.0f - amount;
+            var yAmount = amount;
 
 
-            return new SparseWeight8(indices, xWeights);
+            return _OperateLinear(x, y, (xx, yy) => (xx * xAmount) + (yy * yAmount));
         }
         }
 
 
         public static SparseWeight8 InterpolateCubic(SparseWeight8 x, SparseWeight8 xt, SparseWeight8 y, SparseWeight8 yt, float amount)
         public static SparseWeight8 InterpolateCubic(SparseWeight8 x, SparseWeight8 xt, SparseWeight8 y, SparseWeight8 yt, float amount)
         {
         {
-            Span<int> indices = stackalloc int[16];
-            Span<float> xWeights = stackalloc float[16];
-            Span<float> xTangent = stackalloc float[16];
-            Span<float> yWeights = stackalloc float[16];
-            Span<float> yTangent = stackalloc float[16];
-
-            int offset = 0;
-            offset = CopyTo(x, xt, indices, xWeights, xTangent, offset);
-            offset = CopyTo(y, yt, indices, yWeights, yTangent, offset);
-
-            indices = indices.Slice(0, offset);
-            xWeights = xWeights.Slice(0, offset);
-            xTangent = xWeights.Slice(0, offset);
-            yWeights = yWeights.Slice(0, offset);
-            yTangent = yTangent.Slice(0, offset);
-
             var basis = Animations.SamplerFactory.CreateHermitePointWeights(amount);
             var basis = Animations.SamplerFactory.CreateHermitePointWeights(amount);
 
 
-            for (int i = 0; i < indices.Length; ++i)
+            return _OperateCubic(x, xt, y, yt, (xx, xxt, yy, yyt) => (xx * basis.Item1) + (yy * basis.Item2) + (xxt * basis.Item3) + (yyt * basis.Item4));
+        }
+
+        public IEnumerable<float> Expand(int count)
+        {
+            for (int i = 0; i < count; ++i)
             {
             {
-                xWeights[i]
-                    = (xWeights[i] * basis.Item1)
-                    + (yWeights[i] * basis.Item2)
-                    + (xTangent[i] * basis.Item3)
-                    + (yTangent[i] * basis.Item4);
+                yield return GetExpandedAt(i);
             }
             }
+        }
+
+        public IEnumerator<float> GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
 
 
-            BubbleSort(indices, xWeights);
+        IEnumerator IEnumerable.GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
 
 
-            return new SparseWeight8(indices, xWeights);
+        public bool ContainsKey(int key)
+        {
+            return GetPairs().Select(item => item.Item1).Contains(key);
         }
         }
 
 
-        private static int CopyTo(SparseWeight8 p, SparseWeight8 t, Span<int> indices, Span<float> weights, Span<float> tangent, int offset)
+        public bool TryGetValue(int key, out float value)
         {
         {
-            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
-            System.Diagnostics.Debug.Assert(indices.Length == tangent.Length);
+            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;
+        }
 
 
-            for (int i = 0; i < 8; ++i)
-            {
-                var pair = p.GetPair(i);
-                if (pair.Item2 == 0) continue;
-                var idx = indices.Slice(0, offset).IndexOf(pair.Item1);
+        IEnumerator<KeyValuePair<int, float>> IEnumerable<KeyValuePair<int, float>>.GetEnumerator()
+        {
+            return GetPairs().Select(item => new KeyValuePair<int, float>(item.Item1, item.Item2)).GetEnumerator();
+        }
 
 
-                if (idx < 0)
-                {
-                    indices[offset] = pair.Item1;
-                    weights[offset] = pair.Item2;
-                    tangent[offset] = t[pair.Item1];
-                    ++offset;
-                }
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+
+            int c = this.Count;
+
+            for (int i = 0; i < c; ++i)
+            {
+                if (sb.Length > 0) sb.Append(" ");
+                sb.Append(this[i]);
             }
             }
 
 
-            return offset;
+            return sb.ToString();
         }
         }
 
 
+        #endregion
+
+        #region code
+
         private static int CopyTo(SparseWeight8 p, Span<int> indices, Span<float> weights, int offset)
         private static int CopyTo(SparseWeight8 p, Span<int> indices, Span<float> weights, int offset)
         {
         {
             System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
             System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
@@ -224,73 +267,160 @@ namespace SharpGLTF.Transforms
                 }
                 }
                 else
                 else
                 {
                 {
-                    weights[idx] = pair.Item2;
+                    weights[idx] = pair.Item2; // should perform ADD (in case there's more than one element?)
                 }
                 }
             }
             }
 
 
             return offset;
             return offset;
         }
         }
 
 
-        private static void BubbleSort(Span<int> indices, Span<float> weights)
+        /// <summary>
+        /// Performs <paramref name="operationFunc"/> over all the elements of the operands.
+        /// </summary>
+        /// <param name="x">The first <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="y">The second <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="operationFunc">The operator function to apply to every element.</param>
+        /// <returns>A new <see cref="SparseWeight8"/>.</returns>
+        private static SparseWeight8 _OperateLinear(in SparseWeight8 x, in SparseWeight8 y, Func<float, float, float> operationFunc)
         {
         {
-            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
+            // prepare buffers in the stack
+            Span<int> indices = stackalloc int[16];
+            Span<float> xxx = stackalloc float[16];
+            Span<float> yyy = stackalloc float[16];
 
 
-            for (int i = 0; i < weights.Length - 1; ++i)
+            // fill the buffers so all the elements are aligned by column
+            int offset = 0;
+            offset = CopyTo(x, indices, xxx, offset);
+            offset = CopyTo(y, indices, yyy, offset);
+
+            // perform operation element by element
+            for (int i = 0; i < offset; ++i)
             {
             {
-                for (int j = i + 1; j < weights.Length; ++j)
-                {
-                    if (weights[j - 1] < weights[j])
-                    {
-                        var index = indices[j - 1];
-                        indices[j - 1] = indices[j];
-                        indices[j] = index;
-
-                        var weight = weights[j - 1];
-                        weights[j - 1] = weights[j];
-                        weights[j] = weight;
-                    }
-                }
+                xxx[i] = 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
+
+            indices = indices.Slice(0, offset);
+            xxx = xxx.Slice(0, offset);
+
+            BubbleSortByWeight(indices, xxx, offset);
+
+            return new SparseWeight8(indices, xxx);
         }
         }
 
 
-        internal IEnumerable<(int, float)> GetPairs()
+        /// <summary>
+        /// Performs <paramref name="operationFunc"/> over all the elements of the operands.
+        /// </summary>
+        /// <param name="x">The first <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="y">The second <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="z">The third <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="w">The fourth <see cref="SparseWeight8"/> operand.</param>
+        /// <param name="operationFunc">The operator function to apply to every element.</param>
+        /// <returns>A new <see cref="SparseWeight8"/>.</returns>
+        private static SparseWeight8 _OperateCubic
+            (
+            in SparseWeight8 x,
+            in SparseWeight8 y,
+            in SparseWeight8 z,
+            in SparseWeight8 w,
+            Func<float, float, float, float, float> operationFunc
+            )
         {
         {
-            if (Weight0 > 0) yield return (Index0, Weight0);
-            if (Weight1 > 0) yield return (Index1, Weight1);
-            if (Weight2 > 0) yield return (Index2, Weight2);
-            if (Weight3 > 0) yield return (Index3, Weight3);
-            if (Weight4 > 0) yield return (Index4, Weight4);
-            if (Weight5 > 0) yield return (Index5, Weight5);
-            if (Weight6 > 0) yield return (Index6, Weight6);
-            if (Weight7 > 0) yield return (Index7, Weight7);
+            // prepare buffers in the stack
+            Span<int> indices = stackalloc int[16];
+            Span<float> xxx = stackalloc float[16];
+            Span<float> yyy = stackalloc float[16];
+            Span<float> zzz = stackalloc float[16];
+            Span<float> www = stackalloc float[16];
+
+            // fill the buffers so all the elements are aligned by column
+            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);
+
+            // 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 float GetExpandedAt(int idx)
+        private static void BubbleSortByWeight(Span<int> indices, Span<float> weights, int count = int.MaxValue)
         {
         {
-            if (idx == Index0) return Weight0;
-            if (idx == Index1) return Weight1;
-            if (idx == Index2) return Weight2;
-            if (idx == Index3) return Weight3;
-            if (idx == Index4) return Weight4;
-            if (idx == Index5) return Weight5;
-            if (idx == Index6) return Weight6;
-            if (idx == Index7) return Weight7;
-            return 0;
+            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
+
+            count = Math.Min(indices.Length, count);
+
+            for (int i = 0; i < count - 1; ++i)
+            {
+                for (int j = i + 1; j < count; ++j)
+                {
+                    if (weights[j - 1] > weights[j]) continue;
+
+                    if (weights[j - 1] == weights[j] && indices[j - 1] < indices[j]) continue;
+
+                    var index = indices[j - 1];
+                    indices[j - 1] = indices[j];
+                    indices[j] = index;
+
+                    var weight = weights[j - 1];
+                    weights[j - 1] = weights[j];
+                    weights[j] = weight;
+                }
+            }
         }
         }
 
 
-        private int GetExpandedCount()
+        private static void BubbleSortByIndex(Span<int> indices, Span<float> weights, int count = int.MaxValue)
         {
         {
-            var c = 0;
-            if (Weight0 > 0 && c <= Index0) c = Index0 + 1;
-            if (Weight1 > 0 && c <= Index1) c = Index1 + 1;
-            if (Weight2 > 0 && c <= Index2) c = Index2 + 1;
-            if (Weight3 > 0 && c <= Index3) c = Index3 + 1;
-            if (Weight4 > 0 && c <= Index4) c = Index4 + 1;
-            if (Weight5 > 0 && c <= Index5) c = Index5 + 1;
-            if (Weight6 > 0 && c <= Index6) c = Index6 + 1;
-            if (Weight7 > 0 && c <= Index7) c = Index7 + 1;
+            System.Diagnostics.Debug.Assert(indices.Length == weights.Length);
 
 
-            return c;
+            count = Math.Min(indices.Length, count);
+
+            for (int i = 0; i < count - 1; ++i)
+            {
+                for (int j = i + 1; j < count; ++j)
+                {
+                    if (indices[j - 1] < indices[j]) continue;
+
+                    if (indices[j - 1] == indices[j] && weights[j - 1] > weights[j]) continue;
+
+                    var index = indices[j - 1];
+                    indices[j - 1] = indices[j];
+                    indices[j] = index;
+
+                    var weight = weights[j - 1];
+                    weights[j - 1] = weights[j];
+                    weights[j] = weight;
+                }
+            }
+        }
+
+        internal IEnumerable<(int, float)> GetPairs()
+        {
+            if (Weight0 != 0) yield return (Index0, Weight0);
+            if (Weight1 != 0) yield return (Index1, Weight1);
+            if (Weight2 != 0) yield return (Index2, Weight2);
+            if (Weight3 != 0) yield return (Index3, Weight3);
+            if (Weight4 != 0) yield return (Index4, Weight4);
+            if (Weight5 != 0) yield return (Index5, Weight5);
+            if (Weight6 != 0) yield return (Index6, Weight6);
+            if (Weight7 != 0) yield return (Index7, Weight7);
         }
         }
 
 
         private (int, float) GetPair(int idx)
         private (int, float) GetPair(int idx)
@@ -309,43 +439,34 @@ namespace SharpGLTF.Transforms
             }
             }
         }
         }
 
 
-        public IEnumerable<float> Expand(int count)
-        {
-            for (int i = 0; i < count; ++i)
-            {
-                yield return GetExpandedAt(i);
-            }
-        }
-
-        public IEnumerator<float> GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
-
-        IEnumerator IEnumerable.GetEnumerator() { return Expand(GetExpandedCount()).GetEnumerator(); }
-
-        public bool ContainsKey(int key)
+        private float GetExpandedAt(int idx)
         {
         {
-            return GetPairs().Select(item => item.Item1).Contains(key);
+            if (idx == Index0) return Weight0;
+            if (idx == Index1) return Weight1;
+            if (idx == Index2) return Weight2;
+            if (idx == Index3) return Weight3;
+            if (idx == Index4) return Weight4;
+            if (idx == Index5) return Weight5;
+            if (idx == Index6) return Weight6;
+            if (idx == Index7) return Weight7;
+            return 0;
         }
         }
 
 
-        public bool TryGetValue(int key, out float value)
+        private int GetExpandedCount()
         {
         {
-            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;
-        }
+            var c = 0;
+            if (Weight0 != 0 && c <= Index0) c = Index0 + 1;
+            if (Weight1 != 0 && c <= Index1) c = Index1 + 1;
+            if (Weight2 != 0 && c <= Index2) c = Index2 + 1;
+            if (Weight3 != 0 && c <= Index3) c = Index3 + 1;
+            if (Weight4 != 0 && c <= Index4) c = Index4 + 1;
+            if (Weight5 != 0 && c <= Index5) c = Index5 + 1;
+            if (Weight6 != 0 && c <= Index6) c = Index6 + 1;
+            if (Weight7 != 0 && c <= Index7) c = Index7 + 1;
 
 
-        IEnumerator<KeyValuePair<int, float>> IEnumerable<KeyValuePair<int, float>>.GetEnumerator()
-        {
-            return GetPairs().Select(item => new KeyValuePair<int, float>(item.Item1, item.Item2)).GetEnumerator();
+            return c;
         }
         }
 
 
         #endregion
         #endregion
-
     }
     }
 }
 }

+ 2 - 1
src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs

@@ -12,6 +12,7 @@ namespace SharpGLTF.Animations
     /// </summary>
     /// </summary>
     /// <typeparam name="T">The type of the value.</typeparam>
     /// <typeparam name="T">The type of the value.</typeparam>
     public class AnimatableProperty<T>
     public class AnimatableProperty<T>
+        where T : struct
     {
     {
         #region data
         #region data
 
 
@@ -50,7 +51,7 @@ namespace SharpGLTF.Animations
         /// Assigns an animation curve to a given track.
         /// Assigns an animation curve to a given track.
         /// </summary>
         /// </summary>
         /// <param name="track">The name of the track.</param>
         /// <param name="track">The name of the track.</param>
-        /// <param name="curve">A <see cref="ICurveSampler{T}"/> instance, or null to remove a track./param>
+        /// <param name="curve">A <see cref="ICurveSampler{T}"/> instance, or null to remove a track.</param>
         public void SetTrack(string track, ICurveSampler<T> curve)
         public void SetTrack(string track, ICurveSampler<T> curve)
         {
         {
             Guard.NotNullOrEmpty(track, nameof(track));
             Guard.NotNullOrEmpty(track, nameof(track));

+ 5 - 27
src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs

@@ -13,6 +13,7 @@ namespace SharpGLTF.Animations
     public abstract class CurveBuilder<T>
     public abstract class CurveBuilder<T>
         : ICurveSampler<T>,
         : ICurveSampler<T>,
         IConvertibleCurve<T>
         IConvertibleCurve<T>
+        where T : struct
     {
     {
         #region lifecycle
         #region lifecycle
 
 
@@ -20,11 +21,11 @@ namespace SharpGLTF.Animations
 
 
         protected CurveBuilder(CurveBuilder<T> other)
         protected CurveBuilder(CurveBuilder<T> other)
         {
         {
+            if (other == null) return;
+
             foreach (var kvp in other._Keys)
             foreach (var kvp in other._Keys)
             {
             {
-                var key = kvp.Key;
-                var val = new _CurveNode<T>(kvp.Value, IsolateValue);
-                this._Keys[key] = val;
+                this._Keys[kvp.Key] = kvp.Value;
             }
             }
         }
         }
 
 
@@ -59,19 +60,6 @@ namespace SharpGLTF.Animations
         /// <returns>A <typeparamref name="T"/> instance.</returns>
         /// <returns>A <typeparamref name="T"/> instance.</returns>
         protected abstract T CreateValue(params float[] values);
         protected abstract T CreateValue(params float[] values);
 
 
-        /// <summary>
-        /// Ensures that the reference value is only referenced internally.
-        /// </summary>
-        /// <param name="value">A value.</param>
-        /// <returns>A copy of <paramref name="value"/>.</returns>
-        /// <remarks>
-        /// This is required for reference types like float[], where the user
-        /// can add a point using and array, and then reuse the same array to
-        /// define another point, so it can inadvertently overwrite previous
-        /// points already stored in the curve.
-        /// </remarks>
-        protected abstract T IsolateValue(T value);
-
         /// <summary>
         /// <summary>
         /// Samples the curve at a given <paramref name="offset"/>
         /// Samples the curve at a given <paramref name="offset"/>
         /// </summary>
         /// </summary>
@@ -89,8 +77,6 @@ namespace SharpGLTF.Animations
 
 
         public void SetPoint(float offset, T value, bool isLinear = true)
         public void SetPoint(float offset, T value, bool isLinear = true)
         {
         {
-            value = IsolateValue(value);
-
             if (_Keys.TryGetValue(offset, out _CurveNode<T> existing))
             if (_Keys.TryGetValue(offset, out _CurveNode<T> existing))
             {
             {
                 existing.Point = value;
                 existing.Point = value;
@@ -111,7 +97,6 @@ namespace SharpGLTF.Animations
         public void SetIncomingTangent(float offset, T tangent)
         public void SetIncomingTangent(float offset, T tangent)
         {
         {
             Guard.IsTrue(_Keys.ContainsKey(offset), nameof(offset));
             Guard.IsTrue(_Keys.ContainsKey(offset), nameof(offset));
-            tangent = IsolateValue(tangent);
 
 
             offset -= float.Epsilon;
             offset -= float.Epsilon;
 
 
@@ -137,7 +122,6 @@ namespace SharpGLTF.Animations
         public void SetOutgoingTangent(float offset, T tangent)
         public void SetOutgoingTangent(float offset, T tangent)
         {
         {
             Guard.IsTrue(_Keys.ContainsKey(offset), nameof(offset));
             Guard.IsTrue(_Keys.ContainsKey(offset), nameof(offset));
-            tangent = IsolateValue(tangent);
 
 
             var offsets = SamplerFactory.FindPairContainingOffset(_Keys.Keys, offset);
             var offsets = SamplerFactory.FindPairContainingOffset(_Keys.Keys, offset);
 
 
@@ -311,14 +295,8 @@ namespace SharpGLTF.Animations
 
 
     [System.Diagnostics.DebuggerDisplay("{IncomingTangent} -> {Point}[{Degree}] -> {OutgoingTangent}")]
     [System.Diagnostics.DebuggerDisplay("{IncomingTangent} -> {Point}[{Degree}] -> {OutgoingTangent}")]
     struct _CurveNode<T>
     struct _CurveNode<T>
+        where T : struct
     {
     {
-        public _CurveNode(_CurveNode<T> other, Func<T,T> isolateFunc)
-        {
-            IncomingTangent = isolateFunc(other.IncomingTangent);
-            Point = isolateFunc(other.Point);
-            OutgoingTangent = isolateFunc(other.OutgoingTangent);
-            Degree = other.Degree;
-        }
         public _CurveNode(T value, bool isLinear)
         public _CurveNode(T value, bool isLinear)
         {
         {
             IncomingTangent = default;
             IncomingTangent = default;

+ 16 - 46
src/SharpGLTF.Toolkit/Animations/CurveFactory.cs

@@ -5,13 +5,16 @@ using System.Text;
 
 
 namespace SharpGLTF.Animations
 namespace SharpGLTF.Animations
 {
 {
+    using SPARSE = Transforms.SparseWeight8;
+
     static class CurveFactory
     static class CurveFactory
     {
     {
         public static CurveBuilder<T> CreateCurveBuilder<T>()
         public static CurveBuilder<T> CreateCurveBuilder<T>()
+            where T : struct
         {
         {
             if (typeof(T) == typeof(Vector3)) return new Vector3CurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(Vector3)) return new Vector3CurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(Quaternion)) return new QuaternionCurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(Quaternion)) return new QuaternionCurveBuilder() as CurveBuilder<T>;
-            if (typeof(T) == typeof(Single[])) return new ArrayCurveBuilder() as CurveBuilder<T>;
+            if (typeof(T) == typeof(SPARSE)) return new SparseCurveBuilder() as CurveBuilder<T>;
 
 
             throw new ArgumentException(nameof(T), "Generic argument not supported");
             throw new ArgumentException(nameof(T), "Generic argument not supported");
         }
         }
@@ -32,11 +35,6 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        protected override Vector3 IsolateValue(Vector3 value)
-        {
-            return value;
-        }
-
         protected override Vector3 CreateValue(params float[] values)
         protected override Vector3 CreateValue(params float[] values)
         {
         {
             Guard.NotNull(values, nameof(values));
             Guard.NotNull(values, nameof(values));
@@ -93,11 +91,6 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        protected override Quaternion IsolateValue(Quaternion value)
-        {
-            return value;
-        }
-
         protected override Quaternion CreateValue(params float[] values)
         protected override Quaternion CreateValue(params float[] values)
         {
         {
             Guard.NotNull(values, nameof(values));
             Guard.NotNull(values, nameof(values));
@@ -138,58 +131,35 @@ namespace SharpGLTF.Animations
         #endregion
         #endregion
     }
     }
 
 
-    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CurveBuilderDebugProxyArray))]
-    sealed class ArrayCurveBuilder : CurveBuilder<Single[]>, ICurveSampler<Single[]>
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CurveBuilderDebugProxySparse))]
+    sealed class SparseCurveBuilder : CurveBuilder<SPARSE>, ICurveSampler<SPARSE>
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public ArrayCurveBuilder() { }
+        public SparseCurveBuilder() { }
 
 
-        private ArrayCurveBuilder(ArrayCurveBuilder other)
+        private SparseCurveBuilder(SparseCurveBuilder other)
             : base(other)
             : base(other)
         {
         {
-            System.Diagnostics.Debug.Assert(other._ValueLength == this._ValueLength);
         }
         }
 
 
-        public override CurveBuilder<Single[]> Clone() { return new ArrayCurveBuilder(this); }
-
-        #endregion
-
-        #region data
-
-        // the first "CheckValue" will fix any further calls to this value.
-        private int _ValueLength = 0;
+        public override CurveBuilder<SPARSE> Clone() { return new SparseCurveBuilder(this); }
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        protected override Single[] IsolateValue(Single[] value)
-        {
-            Guard.NotNull(value, nameof(value));
-            Guard.MustBeGreaterThan(value.Length, 0, nameof(value));
-
-            if (_ValueLength == 0) _ValueLength = value.Length;
-
-            Guard.MustBeBetweenOrEqualTo(value.Length, _ValueLength, _ValueLength, nameof(value));
-
-            var clone = new Single[_ValueLength];
-            value.CopyTo(clone, 0);
-
-            return clone;
-        }
-
-        protected override Single[] CreateValue(params Single[] values)
+        protected override SPARSE CreateValue(params Single[] values)
         {
         {
-            return values;
+            return SPARSE.Create(values);
         }
         }
 
 
-        protected override Single[] GetTangent(Single[] fromValue, Single[] toValue)
+        protected override SPARSE GetTangent(SPARSE fromValue, SPARSE toValue)
         {
         {
-            return SamplerFactory.CreateTangent(fromValue, toValue);
+            return SPARSE.Subtract(toValue, fromValue);
         }
         }
 
 
-        public override Single[] GetPoint(Single offset)
+        public override SPARSE GetPoint(Single offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
 
 
@@ -199,10 +169,10 @@ namespace SharpGLTF.Animations
                     return sample.Item1.Point;
                     return sample.Item1.Point;
 
 
                 case 1:
                 case 1:
-                    return SamplerFactory.InterpolateLinear(sample.Item1.Point, sample.Item2.Point, sample.Item3);
+                    return SPARSE.InterpolateLinear(sample.Item1.Point, sample.Item2.Point, sample.Item3);
 
 
                 case 3:
                 case 3:
-                    return SamplerFactory.InterpolateCubic
+                    return SPARSE.InterpolateCubic
                             (
                             (
                             sample.Item1.Point, sample.Item1.OutgoingTangent,
                             sample.Item1.Point, sample.Item1.OutgoingTangent,
                             sample.Item2.Point, sample.Item2.IncomingTangent,
                             sample.Item2.Point, sample.Item2.IncomingTangent,

+ 5 - 4
src/SharpGLTF.Toolkit/Debug/DebugViews.cs

@@ -8,6 +8,7 @@ using System.Text;
 namespace SharpGLTF.Debug
 namespace SharpGLTF.Debug
 {
 {
     internal abstract class _CurveBuilderDebugProxy<T>
     internal abstract class _CurveBuilderDebugProxy<T>
+        where T : struct
     {
     {
         #region lifecycle
         #region lifecycle
 
 
@@ -104,14 +105,14 @@ namespace SharpGLTF.Debug
         }
         }
     }
     }
 
 
-    sealed class _CurveBuilderDebugProxyArray : _CurveBuilderDebugProxy<float[]>
+    sealed class _CurveBuilderDebugProxySparse : _CurveBuilderDebugProxy<Transforms.SparseWeight8>
     {
     {
-        public _CurveBuilderDebugProxyArray(Animations.CurveBuilder<float[]> curve)
+        public _CurveBuilderDebugProxySparse(Animations.CurveBuilder<Transforms.SparseWeight8> curve)
             : base(curve) { }
             : base(curve) { }
 
 
-        protected override float[] GetTangent(float[] a, float[] b)
+        protected override Transforms.SparseWeight8 GetTangent(Transforms.SparseWeight8 a, Transforms.SparseWeight8 b)
         {
         {
-            return Animations.SamplerFactory.CreateTangent(a, b);
+            return Transforms.SparseWeight8.Subtract(b, a);
         }
         }
     }
     }
 }
 }

+ 0 - 1
src/SharpGLTF.Toolkit/Materials/ChannelBuilder.cs

@@ -135,5 +135,4 @@ namespace SharpGLTF.Materials
 
 
         #endregion
         #endregion
     }
     }
-
 }
 }

+ 1 - 1
src/SharpGLTF.Toolkit/Scenes/Content.cs

@@ -44,7 +44,7 @@ namespace SharpGLTF.Scenes
 
 
         #region data
         #region data
 
 
-        private IContent _Target;// Can be either a morphController or a mesh, or light or camera
+        private IContent _Target; // Can be either a morphController or a mesh, or light or camera
 
 
         private Matrix4x4 _Transform;
         private Matrix4x4 _Transform;
 
 

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

@@ -9,6 +9,7 @@ namespace SharpGLTF.Schema2
     {
     {
         public static Accessor CreateVertexAccessor(this ModelRoot root, Memory.MemoryAccessor memAccessor)
         public static Accessor CreateVertexAccessor(this ModelRoot root, Memory.MemoryAccessor memAccessor)
         {
         {
+            Guard.NotNull(root, nameof(root));
             Guard.NotNull(memAccessor, nameof(memAccessor));
             Guard.NotNull(memAccessor, nameof(memAccessor));
 
 
             var accessor = root.CreateAccessor(memAccessor.Attribute.Name);
             var accessor = root.CreateAccessor(memAccessor.Attribute.Name);

+ 14 - 0
src/SharpGLTF.Toolkit/Schema2/AnimationExtensions.cs

@@ -10,6 +10,8 @@ namespace SharpGLTF.Schema2
     {
     {
         public static Animation UseAnimation(this ModelRoot root, string name)
         public static Animation UseAnimation(this ModelRoot root, string name)
         {
         {
+            Guard.NotNull(root, nameof(root));
+
             var animation = root.LogicalAnimations.FirstOrDefault(item => item.Name == name);
             var animation = root.LogicalAnimations.FirstOrDefault(item => item.Name == name);
 
 
             return animation ?? root.CreateAnimation(name);
             return animation ?? root.CreateAnimation(name);
@@ -17,6 +19,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithScaleAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> sampler)
         public static Node WithScaleAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> sampler)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             if (sampler is Animations.IConvertibleCurve<Vector3> curve)
             if (sampler is Animations.IConvertibleCurve<Vector3> curve)
             {
             {
                 var animation = node.LogicalParent.UseAnimation(animationName);
                 var animation = node.LogicalParent.UseAnimation(animationName);
@@ -32,6 +36,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithTranslationAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> sampler)
         public static Node WithTranslationAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> sampler)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             if (sampler is Animations.IConvertibleCurve<Vector3> curve)
             if (sampler is Animations.IConvertibleCurve<Vector3> curve)
             {
             {
                 var animation = node.LogicalParent.UseAnimation(animationName);
                 var animation = node.LogicalParent.UseAnimation(animationName);
@@ -47,6 +53,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithRotationAnimation(this Node node, string animationName, Animations.ICurveSampler<Quaternion> sampler)
         public static Node WithRotationAnimation(this Node node, string animationName, Animations.ICurveSampler<Quaternion> sampler)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             if (sampler is Animations.IConvertibleCurve<Quaternion> curve)
             if (sampler is Animations.IConvertibleCurve<Quaternion> curve)
             {
             {
                 var animation = node.LogicalParent.UseAnimation(animationName);
                 var animation = node.LogicalParent.UseAnimation(animationName);
@@ -83,6 +91,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithScaleAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         public static Node WithScaleAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var root = node.LogicalParent;
             var root = node.LogicalParent;
 
 
             var animation = root.UseAnimation(animationName);
             var animation = root.UseAnimation(animationName);
@@ -94,6 +104,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithRotationAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Quaternion> keyframes)
         public static Node WithRotationAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Quaternion> keyframes)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var root = node.LogicalParent;
             var root = node.LogicalParent;
 
 
             var animation = root.UseAnimation(animationName);
             var animation = root.UseAnimation(animationName);
@@ -105,6 +117,8 @@ namespace SharpGLTF.Schema2
 
 
         public static Node WithTranslationAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         public static Node WithTranslationAnimation(this Node node, string animationName, IReadOnlyDictionary<Single, Vector3> keyframes)
         {
         {
+            Guard.NotNull(node, nameof(node));
+
             var root = node.LogicalParent;
             var root = node.LogicalParent;
 
 
             var animation = root.UseAnimation(animationName);
             var animation = root.UseAnimation(animationName);

+ 65 - 0
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -420,6 +420,12 @@ namespace SharpGLTF.Schema2
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
+            Guard.NotNull(meshBuilder, nameof(meshBuilder));
+
+            if (srcMesh == null) return;
+
+            Guard.NotNull(materialFunc, nameof(materialFunc));
+
             foreach (var srcPrim in srcMesh.Primitives)
             foreach (var srcPrim in srcMesh.Primitives)
             {
             {
                 var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
                 var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
@@ -431,8 +437,66 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Evaluates the current <paramref name="srcScene"/> at a given <paramref name="animation"/> and <paramref name="time"/>
+        /// and creates a static <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/>
+        /// </summary>
+        /// <typeparam name="TMaterial">Any material type</typeparam>
+        /// <typeparam name="TvG">A subtype of <see cref="IVertexGeometry"/></typeparam>
+        /// <typeparam name="TvM">A subtype of <see cref="IVertexMaterial"/></typeparam>
+        /// <param name="srcScene">The source <see cref="Scene"/> to evaluate.</param>
+        /// <param name="animation">The source <see cref="Animation"/> to evaluate.</param>
+        /// <param name="time">A time point, in seconds, within <paramref name="animation"/>.</param>
+        /// <param name="materialFunc">A function to convert <see cref="Material"/> into <typeparamref name="TMaterial"/>.</param>
+        /// <returns>A new <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/> containing the evaluated geometry.</returns>
+        public static MeshBuilder<TMaterial, TvG, TvM, VertexEmpty> ToStaticMeshBuilder<TMaterial, TvG, TvM>(this Scene srcScene, Animation animation, float time, Func<Material, TMaterial> materialFunc)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+        {
+            var mesh = new MeshBuilder<TMaterial, TvG, TvM, VertexEmpty>();
+
+            if (srcScene == null) return mesh;
+
+            if (animation != null) Guard.MustShareLogicalParent(srcScene, animation, nameof(animation));
+
+            Guard.NotNull(materialFunc, nameof(materialFunc));
+
+            foreach (var tri in srcScene.Triangulate<VertexPositionNormal, VertexColor1Texture1>(animation, time))
+            {
+                var material = materialFunc(tri.Item4);
+
+                mesh.UsePrimitive(material).AddTriangle(tri.Item1, tri.Item2, tri.Item3);
+            }
+
+            return mesh;
+        }
+
+        public static MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty> ToStaticMeshBuilder<TvG, TvM>(this Scene srcScene, Animation animation, float time)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+        {
+            var materials = new Dictionary<Material, Materials.MaterialBuilder>();
+
+            Materials.MaterialBuilder convertMaterial(Material srcMaterial)
+            {
+                if (materials.TryGetValue(srcMaterial, out Materials.MaterialBuilder dstMaterial)) return dstMaterial;
+
+                dstMaterial = new Materials.MaterialBuilder();
+                srcMaterial.CopyTo(dstMaterial);
+
+                // if we find an exiting match, we will use it instead.
+                var oldMaterial = materials.Values.FirstOrDefault(item => Materials.MaterialBuilder.AreEqual(dstMaterial, item));
+                if (oldMaterial != null) dstMaterial = oldMaterial;
+
+                return materials[srcMaterial] = dstMaterial;
+            }
+
+            return srcScene.ToStaticMeshBuilder<Materials.MaterialBuilder, TvG, TvM>(animation, time, convertMaterial);
+        }
+
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         {
         {
+            Guard.NotNull(model, nameof(model));
             Guard.NotNullOrEmpty(filePath, nameof(filePath));
             Guard.NotNullOrEmpty(filePath, nameof(filePath));
             Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
             Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
 
 
@@ -443,6 +507,7 @@ namespace SharpGLTF.Schema2
 
 
         public static void SaveAsWavefront(this ModelRoot model, string filePath, Animation animation, float time)
         public static void SaveAsWavefront(this ModelRoot model, string filePath, Animation animation, float time)
         {
         {
+            Guard.NotNull(model, nameof(model));
             Guard.NotNullOrEmpty(filePath, nameof(filePath));
             Guard.NotNullOrEmpty(filePath, nameof(filePath));
             Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
             Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
 
 

+ 5 - 3
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -157,7 +157,9 @@ namespace SharpGLTF.Schema2
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            return Node.Flatten(scene).SelectMany(item => item.Triangulate<TvG, TvM>(animation, time));
+            return Node
+                .Flatten(scene)
+                .SelectMany(item => item.Triangulate<TvG, TvM>(animation, time));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -173,9 +175,9 @@ namespace SharpGLTF.Schema2
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
-            var mesh = node.Mesh;
+            var mesh = node?.Mesh;
 
 
-            if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
+            if (node == null || mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
 
 
             var xform = node.GetMeshWorldTransform(animation, time);
             var xform = node.GetMeshWorldTransform(animation, time);
 
 

+ 4 - 14
src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj

@@ -16,8 +16,7 @@
     <LangVersion>latest</LangVersion>
     <LangVersion>latest</LangVersion>
     <DebugSymbols>true</DebugSymbols>
     <DebugSymbols>true</DebugSymbols>
     <PackageLicenseFile>LICENSE</PackageLicenseFile>
     <PackageLicenseFile>LICENSE</PackageLicenseFile>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <CodeAnalysisRuleSet>..\..\SharpGLTF.ruleset</CodeAnalysisRuleSet>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>    
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -33,18 +32,7 @@
   <PropertyGroup>
   <PropertyGroup>
     <DocumentationFile>bin\Docs\SharpGLTF.Toolkit.xml</DocumentationFile>
     <DocumentationFile>bin\Docs\SharpGLTF.Toolkit.xml</DocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-    </PackageReference>
-    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.3">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-    </PackageReference>
-  </ItemGroup>
-
+  
   <PropertyGroup>    
   <PropertyGroup>    
     <PackageIconUrl>https://raw.githubusercontent.com/vpenades/SharpGLTF/master/build/Icons/glTF2Sharp.png</PackageIconUrl>
     <PackageIconUrl>https://raw.githubusercontent.com/vpenades/SharpGLTF/master/build/Icons/glTF2Sharp.png</PackageIconUrl>
   </PropertyGroup>
   </PropertyGroup>
@@ -61,5 +49,7 @@
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\SharpGLTF.Core\SharpGLTF.Core.csproj" />
     <ProjectReference Include="..\SharpGLTF.Core\SharpGLTF.Core.csproj" />
   </ItemGroup>
   </ItemGroup>
+
+  <Import Project="..\..\Analyzers.targets" />
   
   
 </Project>
 </Project>

+ 3 - 3
tests/SharpGLTF.Tests/Animations/CurveBuilderTests.cs

@@ -112,9 +112,9 @@ namespace SharpGLTF.Animations
         [Test]
         [Test]
         public void CreateMorphCurve1()
         public void CreateMorphCurve1()
         {
         {
-            // Create a Quaternion curve
+            // Create a Transforms.SparseWeight8 curve
 
 
-            var curve = CurveFactory.CreateCurveBuilder<Single[]>();
+            var curve = CurveFactory.CreateCurveBuilder<Transforms.SparseWeight8>();
 
 
             curve
             curve
                 .WithPoint(0, 0f, 0f);
                 .WithPoint(0, 0f, 0f);
@@ -129,7 +129,7 @@ namespace SharpGLTF.Animations
 
 
             // convert and resample the curve to a linear and cubic curves.
             // convert and resample the curve to a linear and cubic curves.
 
 
-            var convertible = curve as IConvertibleCurve<Single[]>;
+            var convertible = curve as IConvertibleCurve<Transforms.SparseWeight8>;
             Assert.NotNull(convertible);
             Assert.NotNull(convertible);
 
 
             var linear = convertible.ToLinearCurve().Select(kvp => (kvp.Key, kvp.Value)).CreateSampler();
             var linear = convertible.ToLinearCurve().Select(kvp => (kvp.Key, kvp.Value)).CreateSampler();

+ 48 - 17
tests/SharpGLTF.Tests/Transforms/SparseWeight8Tests.cs

@@ -10,7 +10,7 @@ namespace SharpGLTF.Transforms
     public class SparseWeight8Tests
     public class SparseWeight8Tests
     {
     {
         [Test]
         [Test]
-        public void CreateSparse()
+        public void CreateSparseWeights()
         {
         {
             var sparse1 = SparseWeight8.Create(0, 0, 0, 0, 0, 0.1f, 0.7f, 0, 0, 0, 0.1f);
             var sparse1 = SparseWeight8.Create(0, 0, 0, 0, 0, 0.1f, 0.7f, 0, 0, 0, 0.1f);
             Assert.AreEqual(6, sparse1.Index0);
             Assert.AreEqual(6, sparse1.Index0);
@@ -33,10 +33,26 @@ namespace SharpGLTF.Transforms
             Assert.AreEqual(0.1f, sparse1Nrm.Weight2);
             Assert.AreEqual(0.1f, sparse1Nrm.Weight2);
             Assert.AreEqual(0.1f, sparse1Nrm.Weight3, 0.00001f); // funny enough, 0.8f + 0.1f = 0.90000036f
             Assert.AreEqual(0.1f, sparse1Nrm.Weight3, 0.00001f); // funny enough, 0.8f + 0.1f = 0.90000036f
             Assert.AreEqual(0, sparse1Nrm.Weight4);
             Assert.AreEqual(0, sparse1Nrm.Weight4);
+
+            // we must also support negative values.
+            var sparseNegative = SparseWeight8.Create(0, 1, -1);
+            Assert.AreEqual(1, sparseNegative.Index0);
+            Assert.AreEqual(2, sparseNegative.Index1);
+            Assert.AreEqual( 1, sparseNegative.Weight0);
+            Assert.AreEqual(-1, sparseNegative.Weight1);
         }
         }
 
 
         [Test]
         [Test]
-        public void TestLinearInterpolation1()
+        public void TestSparseEquality()
+        {
+            Assert.IsTrue(SparseWeight8.AreWeightsEqual(SparseWeight8.Create(0, 1), SparseWeight8.Create(0, 1)));
+
+            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)));
+        }
+
+        [Test]
+        public void TestSparseWeightsLinearInterpolation1()
         {
         {
             var x = new SparseWeight8((0,0f));
             var x = new SparseWeight8((0,0f));
             var y = new SparseWeight8((0,1f));
             var y = new SparseWeight8((0,1f));
@@ -45,27 +61,42 @@ namespace SharpGLTF.Transforms
         }
         }
 
 
         [Test]
         [Test]
-        public void TestLinearInterpolation2()
+        public void TestSparseWeightsLinearInterpolation2()
         {
         {
-            var ax = new float[] { 0, 0, 0, 0, 0, 0.1f, 0.7f, 0, 0, 0, 0.1f };
-            var ay = new float[] { 0, 0, 0.2f, 0, 0.1f, 0, 0, 0, 0, 0, 0 };
+            var ax = new float[] { 0, 0,    0, 0,    0, 0.1f, 0.7f, 0, 0, 0, 0.1f };
+            var ay = new float[] { 0, 0, 0.2f, 0, 0.1f,    0,    0, 0, 0, 0,    0, 0, 0.2f };
+            var cc = Math.Min(ax.Length, ay.Length);
 
 
             var x = SparseWeight8.Create(ax); CollectionAssert.AreEqual(ax, x.Expand(ax.Length));
             var x = SparseWeight8.Create(ax); CollectionAssert.AreEqual(ax, x.Expand(ax.Length));
             var y = SparseWeight8.Create(ay); CollectionAssert.AreEqual(ay, y.Expand(ay.Length));
             var y = SparseWeight8.Create(ay); CollectionAssert.AreEqual(ay, y.Expand(ay.Length));
 
 
             var z = SparseWeight8.InterpolateLinear(x, y, 0.5f);
             var z = SparseWeight8.InterpolateLinear(x, y, 0.5f);
-            Assert.AreEqual(6, z.Index0);
-            Assert.AreEqual(2, z.Index1);
-            Assert.AreEqual(5, z.Index2);
-            Assert.AreEqual(10, z.Index3);
-            Assert.AreEqual(4, z.Index4);
-
-            Assert.AreEqual(0.35f, z.Weight0);
-            Assert.AreEqual(0.1f, z.Weight1);
-            Assert.AreEqual(0.05f, z.Weight2);
-            Assert.AreEqual(0.05f, z.Weight3);
-            Assert.AreEqual(0.05f, z.Weight4);
-            Assert.AreEqual(0, z.Weight5);
+
+            for (int i=0; i < cc; ++i)
+            {
+                var w = (ax[i] + ay[i]) / 2;
+                Assert.AreEqual(w, z[i]);
+            }
+        }
+
+        [Test]
+        public void TestSparseWeightsCubicInterpolation()
+        {
+            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 t = SparseWeight8.Subtract(b, a);
+
+            var lr = SparseWeight8.InterpolateLinear(a, b, 0.4f);
+            var cr = SparseWeight8.InterpolateCubic(a, t, b, t, 0.4f);
+
+            Assert.AreEqual(lr[0], cr[0], 0.000001f);
+            Assert.AreEqual(lr[1], cr[1], 0.000001f);
+            Assert.AreEqual(lr[2], cr[2], 0.000001f);
+            Assert.AreEqual(lr[3], cr[3], 0.000001f);
+            Assert.AreEqual(lr[4], cr[4], 0.000001f);
+            Assert.AreEqual(lr[5], cr[5], 0.000001f);
+            Assert.AreEqual(lr[6], cr[6], 0.000001f);
+            Assert.AreEqual(lr[7], cr[7], 0.000001f);
         }
         }
     }
     }
 }
 }