Browse Source

Added lots of guard.notnull and other code fixes

Vicente Penades 3 years ago
parent
commit
f70c9bad9b
34 changed files with 269 additions and 166 deletions
  1. 18 0
      .editorconfig
  2. 2 2
      examples/Example1/Program.cs
  3. 2 0
      src/SharpGLTF.Core/Animations/CurveSampler.cs
  4. 10 11
      src/SharpGLTF.Core/Memory/MemoryImage.cs
  5. 1 1
      src/SharpGLTF.Core/Schema2/Serialization.WriteContext.cs
  6. 3 2
      src/SharpGLTF.Core/Schema2/Serialization.WriteSettings.cs
  7. 6 8
      src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs
  8. 3 1
      src/SharpGLTF.Core/Schema2/gltf.MaterialsFactory.cs
  9. 1 1
      src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs
  10. 2 0
      src/SharpGLTF.Core/Schema2/gltf.PunctualLight.cs
  11. 7 0
      src/SharpGLTF.Core/Transforms/AffineTransform.cs
  12. 21 19
      src/SharpGLTF.Core/Transforms/MeshTransforms.cs
  13. 16 9
      src/SharpGLTF.Core/Transforms/SparseWeight8.cs
  14. 1 1
      src/SharpGLTF.Core/Validation/ModelException.cs
  15. 1 0
      src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs
  16. 1 0
      src/SharpGLTF.Core/Validation/ValidationResult.cs
  17. 5 1
      src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs
  18. 4 0
      src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs
  19. 97 91
      src/SharpGLTF.Toolkit/BaseBuilder.cs
  20. 4 4
      src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs
  21. 4 0
      src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs
  22. 14 2
      src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs
  23. 3 3
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs
  24. 1 1
      src/SharpGLTF.Toolkit/Materials/MaterialBuilder.cs
  25. 20 3
      src/SharpGLTF.Toolkit/Materials/MaterialValue.cs
  26. 3 1
      src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs
  27. 2 0
      src/SharpGLTF.Toolkit/Scenes/CameraBuilder.cs
  28. 2 0
      src/SharpGLTF.Toolkit/Scenes/LightBuilder.cs
  29. 3 3
      src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs
  30. 1 0
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs
  31. 3 0
      src/SharpGLTF.Toolkit/Schema2/AccessorExtensions.cs
  32. 1 0
      src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs
  33. 5 2
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  34. 2 0
      src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

+ 18 - 0
.editorconfig

@@ -36,6 +36,24 @@ csharp_style_var_elsewhere = false:silent
 # SA1623: Property summary documentation should match accessors
 dotnet_diagnostic.SA1623.severity = silent
 
+# CA1032: Implement standard exception constructors
+dotnet_diagnostic.CA1032.severity = silent
+
+# CA1034: Nested types should not be visible
+dotnet_diagnostic.CA1034.severity = silent
+
+# CA1721: Property names should not match get methods
+dotnet_diagnostic.CA1721.severity = silent
+
+# CA1024: Use properties where appropriate
+dotnet_diagnostic.CA1024.severity = silent
+
+# CA2225: Operator overloads have named alternates
+dotnet_diagnostic.CA2225.severity = silent
+
+# CA1707: Identifiers should not contain underscores
+dotnet_diagnostic.CA1707.severity = silent
+
 [*.{cs,vb}]
 #### Naming styles ####
 

+ 2 - 2
examples/Example1/Program.cs

@@ -19,12 +19,12 @@ namespace Example1
             var material1 = new MaterialBuilder()
                 .WithDoubleSide(true)
                 .WithMetallicRoughnessShader()
-                .WithChannelParam("BaseColor", new Vector4(1,0,0,1) );
+                .WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, new Vector4(1,0,0,1) );
 
             var material2 = new MaterialBuilder()
                 .WithDoubleSide(true)
                 .WithMetallicRoughnessShader()
-                .WithChannelParam("BaseColor", new Vector4(1, 0, 1, 1));
+                .WithChannelParam(KnownChannel.BaseColor, KnownProperty.RGBA, new Vector4(1, 0, 1, 1));
 
             // create a mesh with two primitives, one for each material
 

+ 2 - 0
src/SharpGLTF.Core/Animations/CurveSampler.cs

@@ -316,6 +316,8 @@ namespace SharpGLTF.Animations
 
         public static Single[] Subtract(ROLIST left, ROLIST right)
         {
+            Guard.NotNull(left, nameof(left));
+            Guard.NotNull(right, nameof(right));
             Guard.MustBeEqualTo(right.Count, left.Count, nameof(right));
 
             var dst = new Single[left.Count];

+ 10 - 11
src/SharpGLTF.Core/Memory/MemoryImage.cs

@@ -54,15 +54,14 @@ namespace SharpGLTF.Memory
 
         internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
 
-        internal static readonly string[] _EmbeddedHeaders = 
-            { EMBEDDED_OCTET_STREAM
-            , EMBEDDED_GLTF_BUFFER
-            , EMBEDDED_JPEG_BUFFER
-            , EMBEDDED_PNG_BUFFER
-            , EMBEDDED_DDS_BUFFER
-            , EMBEDDED_WEBP_BUFFER
-            , EMBEDDED_KTX2_BUFFER
-            };
+        internal static readonly string[] _EmbeddedHeaders = {
+            EMBEDDED_OCTET_STREAM,
+            EMBEDDED_GLTF_BUFFER,
+            EMBEDDED_JPEG_BUFFER,
+            EMBEDDED_PNG_BUFFER,
+            EMBEDDED_DDS_BUFFER,
+            EMBEDDED_WEBP_BUFFER,
+            EMBEDDED_KTX2_BUFFER };
 
         public static MemoryImage Empty => default;
 
@@ -269,7 +268,7 @@ namespace SharpGLTF.Memory
                 if (IsDds) return "dds";
                 if (IsWebp) return "webp";
                 if (IsKtx2) return "ktx2";
-                throw new NotImplementedException();
+                throw new InvalidOperationException("Image format not recognized.");
             }
         }
 
@@ -286,7 +285,7 @@ namespace SharpGLTF.Memory
                 if (IsDds) return MIME_DDS;
                 if (IsWebp) return MIME_WEBP;
                 if (IsKtx2) return MIME_KTX2;
-                throw new NotImplementedException();
+                throw new InvalidOperationException("Image format not recognized.");
             }
         }
 

+ 1 - 1
src/SharpGLTF.Core/Schema2/Serialization.WriteContext.cs

@@ -159,7 +159,7 @@ namespace SharpGLTF.Schema2
         /// <summary>
         /// Gets a value indicating whether creating a defensive copy before serialization is not allowed.
         /// </summary>
-        internal bool _NoCloneWatchdog { get; private set; } = false;
+        internal bool _NoCloneWatchdog { get; private set; }
 
         #endregion
 

+ 3 - 2
src/SharpGLTF.Core/Schema2/Serialization.WriteSettings.cs

@@ -60,7 +60,7 @@ namespace SharpGLTF.Schema2
 
         #region data
 
-        private System.Text.Json.JsonWriterOptions _JsonOptions = default;
+        private System.Text.Json.JsonWriterOptions _JsonOptions;
 
         #endregion
 
@@ -152,8 +152,9 @@ namespace SharpGLTF.Schema2
         /// <param name="settings">Optional settings.</param>
         public void Save(string filePath, WriteSettings settings = null)
         {
+            Guard.NotNull(filePath, nameof(filePath));
+
             bool isGltfExtension = filePath
-                .ToLower(System.Globalization.CultureInfo.InvariantCulture)
                 .EndsWith(".gltf", StringComparison.OrdinalIgnoreCase);
 
             if (isGltfExtension) SaveGLTF(filePath, settings);

+ 6 - 8
src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs

@@ -145,14 +145,6 @@ namespace SharpGLTF.Schema2
             texInfo._LogicalTextureIndex = tex.LogicalIndex;
         }
 
-        private Texture TryGetTexture()
-        {
-            var texInfo = _TextureInfo?.Invoke(false);
-            if (texInfo == null) return null;
-            if (texInfo._LogicalTextureIndex < 0) return null;
-            return _Material.LogicalParent.LogicalTextures[texInfo._LogicalTextureIndex];
-        }
-
         public void SetTransform(Vector2 offset, Vector2 scale, float rotation = 0, int? texCoordOverride = null)
         {
             if (_TextureInfo == null) throw new InvalidOperationException();
@@ -256,6 +248,12 @@ namespace SharpGLTF.Schema2
 
         public bool IsDefault => Object.Equals(Value, _ValueDefault);
 
+        public Type ValueType => _ValueDefault.GetType();
+
+        /// <summary>
+        /// Gets or sets the value of this parameter. <br/>
+        /// Valid types are <see cref="float"/> <see cref="Vector3"/> and <see cref="Vector4"/>
+        /// </summary>
         public PARAMETER Value
         {
             get => _ValueGetter();

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

@@ -43,6 +43,8 @@ namespace SharpGLTF.Schema2
         /// </param>
         public void InitializePBRMetallicRoughness(params string[] extensionNames)
         {
+            Guard.NotNull(extensionNames, nameof(extensionNames));
+
             if (this._pbrMetallicRoughness == null) this._pbrMetallicRoughness = new MaterialPBRMetallicRoughness();
 
             ClearExtensions();
@@ -597,7 +599,7 @@ namespace SharpGLTF.Schema2
         public float AttenuationDistance
         {
             get => (float)_attenuationDistance.AsValue(0);
-            set => _attenuationDistance = value > _attenuationDistanceExclusiveMinimum ? value : throw new ArgumentOutOfRangeException();
+            set => _attenuationDistance = value > _attenuationDistanceExclusiveMinimum ? value : throw new ArgumentOutOfRangeException(nameof(value));
         }
 
         public IEnumerable<MaterialChannel> GetChannels(Material material)

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

@@ -279,7 +279,7 @@ namespace SharpGLTF.Schema2
                     if (va.Value.Encoding == EncodingType.UNSIGNED_BYTE) continue;
                     if (va.Value.Encoding == EncodingType.UNSIGNED_SHORT) continue;
 
-                    if (va.Key.StartsWith("TEXCOORD_")) return true;
+                    if (va.Key.StartsWith("TEXCOORD_", StringComparison.OrdinalIgnoreCase)) return true;
                 }
 
                 return false;

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

@@ -34,7 +34,9 @@ namespace SharpGLTF.Schema2
 
         internal PunctualLight(PunctualLightType ltype)
         {
+            #pragma warning disable CA1308 // Normalize strings to uppercase
             _type = ltype.ToString().ToLowerInvariant();
+            #pragma warning restore CA1308 // Normalize strings to uppercase
 
             if (ltype == PunctualLightType.Spot) _spot = new PunctualLightSpot();
         }

+ 7 - 0
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -285,6 +285,7 @@ namespace SharpGLTF.Transforms
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private readonly Vector3 _Translation;
 
+        /// <inheritdoc/>
         public override int GetHashCode()
         {
             // we can only use the translation as hash code because it's the only value that
@@ -293,11 +294,13 @@ namespace SharpGLTF.Transforms
             return _Translation.GetHashCode();
         }
 
+        /// <inheritdoc/>
         public override bool Equals(object obj)
         {
             return obj is AffineTransform other && this.Equals(other);
         }
 
+        /// <inheritdoc/>
         public bool Equals(AffineTransform other)
         {
             if (this.IsSRT && other.IsSRT)
@@ -319,6 +322,10 @@ namespace SharpGLTF.Transforms
             return this.Matrix.Equals(other.Matrix);
         }
 
+        public static bool operator ==(in AffineTransform a, in AffineTransform b) { return a.Equals(b); }
+
+        public static bool operator !=(in AffineTransform a, in AffineTransform b) { return !a.Equals(b); }
+
         #endregion
 
         #region properties

+ 21 - 19
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -43,20 +43,20 @@ namespace SharpGLTF.Transforms
         /// <summary>
         /// Transforms a vertex position from local mesh space to world space.
         /// </summary>
-        /// <param name="position">The local position of the vertex.</param>
+        /// <param name="localPosition">The local position of the vertex.</param>
         /// <param name="positionDeltas">The local position deltas of the vertex, one for each morph target, or null.</param>
         /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
         /// <returns>A position in world space.</returns>
-        V3 TransformPosition(V3 position, IReadOnlyList<V3> positionDeltas, in SparseWeight8 skinWeights);
+        V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> positionDeltas, in SparseWeight8 skinWeights);
 
         /// <summary>
         /// Transforms a vertex normal from local mesh space to world space.
         /// </summary>
-        /// <param name="normal">The local normal of the vertex.</param>
+        /// <param name="localNormal">The local normal of the vertex.</param>
         /// <param name="normalDeltas">The local normal deltas of the vertex, one for each morph target, or null.</param>
         /// <param name="skinWeights">The skin weights of the vertex, or default.</param>
         /// <returns>A normal in world space.</returns>
-        V3 TransformNormal(V3 normal, IReadOnlyList<V3> normalDeltas, in SparseWeight8 skinWeights);
+        V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> normalDeltas, in SparseWeight8 skinWeights);
 
         /// <summary>
         /// Transforms a vertex tangent from local mesh space to world space.
@@ -305,23 +305,23 @@ namespace SharpGLTF.Transforms
             _FlipFaces = determinant3x3 < 0;
         }
 
-        public V3 TransformPosition(V3 position, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> positionDeltas, in SparseWeight8 skinWeights)
         {
-            position = MorphVectors(position, morphTargets);
+            localPosition = MorphVectors(localPosition, positionDeltas);
 
-            return V3.Transform(position, _WorldMatrix);
+            return V3.Transform(localPosition, _WorldMatrix);
         }
 
-        public V3 TransformNormal(V3 normal, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> normalDeltas, in SparseWeight8 skinWeights)
         {
-            normal = MorphVectors(normal, morphTargets);
+            localNormal = MorphVectors(localNormal, normalDeltas);
 
-            return V3.Normalize(V3.TransformNormal(normal, _WorldMatrix));
+            return V3.Normalize(V3.TransformNormal(localNormal, _WorldMatrix));
         }
 
-        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in SparseWeight8 skinWeights)
         {
-            var t = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
+            var t = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), tangentDeltas);
 
             t = V3.Normalize(V3.TransformNormal(t, _WorldMatrix));
 
@@ -400,9 +400,9 @@ namespace SharpGLTF.Transforms
             }
         }
 
-        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> positionDeltas, in SparseWeight8 skinWeights)
         {
-            localPosition = MorphVectors(localPosition, morphTargets);
+            localPosition = MorphVectors(localPosition, positionDeltas);
 
             var worldPosition = V3.Zero;
 
@@ -416,9 +416,9 @@ namespace SharpGLTF.Transforms
             return worldPosition;
         }
 
-        public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> normalDeltas, in SparseWeight8 skinWeights)
         {
-            localNormal = MorphVectors(localNormal, morphTargets);
+            localNormal = MorphVectors(localNormal, normalDeltas);
 
             var worldNormal = V3.Zero;
 
@@ -430,9 +430,9 @@ namespace SharpGLTF.Transforms
             return V3.Normalize(localNormal);
         }
 
-        public V4 TransformTangent(V4 localTangent, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
+        public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in SparseWeight8 skinWeights)
         {
-            var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
+            var localTangentV = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), tangentDeltas);
 
             var worldTangent = V3.Zero;
 
@@ -443,7 +443,7 @@ namespace SharpGLTF.Transforms
 
             worldTangent = V3.Normalize(worldTangent);
 
-            return new V4(worldTangent, localTangent.W);
+            return new V4(worldTangent, tangent.W);
         }
 
         #endregion
@@ -483,6 +483,8 @@ namespace SharpGLTF.Transforms
 
         public InstancingTransform(AffineTransform[] instances)
         {
+            Guard.NotNull(instances, nameof(instances));
+
             _LocalMatrices = new TRANSFORM[instances.Length];
 
             for (int i = 0; i < _LocalMatrices.Length; ++i)

+ 16 - 9
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -214,7 +214,7 @@ namespace SharpGLTF.Transforms
         private SparseWeight8(ReadOnlySpan<IndexWeight> iw)
         {
             #if DEBUG
-            if (iw.Length != 8) throw new ArgumentException(nameof(iw));
+            if (iw.Length != 8) throw new ArgumentException("Span must contain 8 elements.", nameof(iw));
             if (!IndexWeight.IsWellFormed(iw, out var err)) throw new ArgumentException(err, nameof(iw));
             #endif
 
@@ -297,6 +297,7 @@ namespace SharpGLTF.Transforms
         public readonly int Index7;
         public readonly float Weight7;
 
+        /// <inheritdoc/>
         public override int GetHashCode()
         {
             // we calculate the hash form the highest weight.
@@ -315,6 +316,19 @@ namespace SharpGLTF.Transforms
             return h.GetHashCode();
         }
 
+        /// <inheritdoc/>
+        public override bool Equals(object obj)
+        {
+            return obj is SparseWeight8 other && AreEqual(this, other);
+        }
+
+        /// <inheritdoc/>
+        public bool Equals(SparseWeight8 other) { return AreEqual(this, other); }
+
+        public static bool operator ==(SparseWeight8 left, SparseWeight8 right) { return left.Equals(right); }
+
+        public static bool operator !=(SparseWeight8 left, SparseWeight8 right) { return !left.Equals(right); }
+
         internal static bool AreEqual(in SparseWeight8 x, in SparseWeight8 y)
         {
             const int STACKSIZE = 8;
@@ -361,13 +375,6 @@ namespace SharpGLTF.Transforms
             return true;
         }
 
-        public bool Equals(SparseWeight8 other) { return AreEqual(this, other); }
-
-        public override bool Equals(object obj)
-        {
-            return obj is SparseWeight8 other && AreEqual(this, other);
-        }
-
         #endregion
 
         #region properties
@@ -569,7 +576,7 @@ namespace SharpGLTF.Transforms
 
             for (int i = 0; i < c; ++i)
             {
-                if (sb.Length > 0) sb.Append(" ");
+                if (sb.Length > 0) sb.Append(' ');
                 sb.Append(this[i]);
             }
 

+ 1 - 1
src/SharpGLTF.Core/Validation/ModelException.cs

@@ -84,7 +84,7 @@ namespace SharpGLTF.Validation
 
             var gen = mex._Generator;
 
-            if (gen.ToLowerInvariant().Contains("sharpgltf"))
+            if (gen.ToUpperInvariant().Contains("SHARPGLTF"))
             {
                 mex.MessageSuffix = $"Model generated by <{gen}> seems to be malformed.";
                 return;

+ 1 - 0
src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs

@@ -137,6 +137,7 @@ namespace SharpGLTF.Validation
 
         public OUTTYPE IsValidURI(PARAMNAME parameterName, string gltfURI, params string[] validHeaders)
         {
+            Guard.NotNull(validHeaders, nameof(validHeaders));
             try { Guard.IsValidURI(parameterName, gltfURI, validHeaders); return this; }
             catch (ArgumentException ex) { _SchemaThrow(parameterName, ex.Message); }
             return this;

+ 1 - 0
src/SharpGLTF.Core/Validation/ValidationResult.cs

@@ -48,6 +48,7 @@ namespace SharpGLTF.Validation
 
         public void SetSchemaError(System.IO.EndOfStreamException ex)
         {
+            Guard.NotNull(ex, nameof(ex));
             SetError(new SchemaException(null, ex.Message));
         }
 

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

@@ -153,7 +153,11 @@ namespace SharpGLTF.Animations
 
         #region extended API
 
-        public void SetValue(params float[] elements) { this.Value = _Convert(elements); }
+        public void SetValue(params float[] elements)
+        {
+            Guard.NotNull(elements, nameof(elements));
+            this.Value = _Convert(elements);
+        }
 
         private static T _Convert(float[] elements)
         {

+ 4 - 0
src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs

@@ -212,6 +212,8 @@ namespace SharpGLTF.Animations
 
         public void SetCurve(IConvertibleCurve<T> convertible)
         {
+            Guard.NotNull(convertible, nameof(convertible));
+
             if (convertible.MaxDegree == 0)
             {
                 var step = convertible.ToStepCurve();
@@ -273,6 +275,8 @@ namespace SharpGLTF.Animations
 
         public void SetCurve(Schema2.IAnimationSampler<T> curve)
         {
+            Guard.NotNull(curve, nameof(curve));
+
             switch (curve.InterpolationMode)
             {
                 case Schema2.AnimationInterpolationMode.STEP:

+ 97 - 91
src/SharpGLTF.Toolkit/BaseBuilder.cs

@@ -1,93 +1,99 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF
-{
-    public abstract class BaseBuilder
-    {
-        #region lifecycle
-
-        public BaseBuilder() { }
-
-        public BaseBuilder(string name)
-        {
-            this.Name = name;
-        }
-
-        public BaseBuilder(string name, IO.JsonContent extras)
-        {
-            this.Name = name;
-            this.Extras = extras;
-        }
-
-        public BaseBuilder(BaseBuilder other)
-        {
-            this.Name = other.Name;
-            this.Extras = other.Extras.DeepClone();
-        }
-
-        #endregion
-
-        #region data
-
-        /// <summary>
-        /// Gets or sets the display text name, or null.
-        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
-        /// </summary>
-        /// <remarks>
-        /// glTF does not define any rule for object names.<br/>
-        /// This means that names can be null or non unique.<br/>
-        /// So don't use <see cref="Name"/> for anything other than object name display.<br/>
-        /// If you need to reference objects by some ID, use lookup tables instead.
-        /// </remarks>
-        public string Name { get; set; }
-
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF
+{
+    public abstract class BaseBuilder
+    {
+        #region lifecycle
+
+        protected BaseBuilder() { }
+
+        protected BaseBuilder(string name)
+        {
+            this.Name = name;
+        }
+
+        protected BaseBuilder(string name, IO.JsonContent extras)
+        {
+            this.Name = name;
+            this.Extras = extras;
+        }
+
+        protected BaseBuilder(BaseBuilder other)
+        {
+            Guard.NotNull(other, nameof(other));
+
+            this.Name = other.Name;
+            this.Extras = other.Extras.DeepClone();
+        }
+
+        #endregion
+
+        #region data
+
+        /// <summary>
+        /// Gets or sets the display text name, or null.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
+        /// </summary>
+        /// <remarks>
+        /// glTF does not define any rule for object names.<br/>
+        /// This means that names can be null or non unique.<br/>
+        /// So don't use <see cref="Name"/> for anything other than object name display.<br/>
+        /// If you need to reference objects by some ID, use lookup tables instead.
+        /// </remarks>
+        public string Name { get; set; }
+
         /// <summary>
         /// Gets or sets the custom data of this object.
-        /// </summary>
-        public IO.JsonContent Extras { get; set; }
-
-        protected static int GetContentHashCode(BaseBuilder x)
-        {
-            return x?.Name?.GetHashCode() ?? 0;
-        }
-
-        protected static bool AreEqualByContent(BaseBuilder x, BaseBuilder y)
-        {
-            if ((x, y).AreSameReference(out bool areTheSame)) return areTheSame;
-
-            if (x.Name != y.Name) return false;
-
-            return IO.JsonContent.AreEqualByContent(x.Extras, y.Extras, 0.0001f);
-        }
-
-        #endregion
-
-        #region API
-
-        internal void SetNameAndExtrasFrom(BaseBuilder source)
-        {
-            this.Name = source.Name;
-            this.Extras = source.Extras.DeepClone();
-        }
-
-        internal void SetNameAndExtrasFrom(Schema2.LogicalChildOfRoot source)
-        {
-            this.Name = source.Name;
-            this.Extras = source.Extras.DeepClone();
-        }
-
-        /// <summary>
-        /// Copies the Name and Extras values to <paramref name="target"/> only if the values are defined.
-        /// </summary>
-        /// <param name="target">The target object</param>
-        internal void TryCopyNameAndExtrasTo(Schema2.LogicalChildOfRoot target)
-        {
-            if (this.Name != null) target.Name = this.Name;
-            if (this.Extras.Content != null) target.Extras = this.Extras.DeepClone();
-        }
-
-        #endregion
-    }
-}
+        /// </summary>
+        public IO.JsonContent Extras { get; set; }
+
+        protected static int GetContentHashCode(BaseBuilder x)
+        {
+            #if NET6_0_OR_GREATER
+            return x?.Name?.GetHashCode(StringComparison.InvariantCulture) ?? 0;
+            #else
+            return x?.Name?.GetHashCode() ?? 0;
+            #endif
+        }
+
+        protected static bool AreEqualByContent(BaseBuilder x, BaseBuilder y)
+        {
+            if ((x, y).AreSameReference(out bool areTheSame)) return areTheSame;
+
+            if (x.Name != y.Name) return false;
+
+            return IO.JsonContent.AreEqualByContent(x.Extras, y.Extras, 0.0001f);
+        }
+
+        #endregion
+
+        #region API
+
+        internal void SetNameAndExtrasFrom(BaseBuilder source)
+        {
+            this.Name = source.Name;
+            this.Extras = source.Extras.DeepClone();
+        }
+
+        internal void SetNameAndExtrasFrom(Schema2.LogicalChildOfRoot source)
+        {
+            this.Name = source.Name;
+            this.Extras = source.Extras.DeepClone();
+        }
+
+        /// <summary>
+        /// Copies the Name and Extras values to <paramref name="target"/> only if the values are defined.
+        /// </summary>
+        /// <param name="target">The target object</param>
+        internal void TryCopyNameAndExtrasTo(Schema2.LogicalChildOfRoot target)
+        {
+            if (this.Name != null) target.Name = this.Name;
+            if (this.Extras.Content != null) target.Extras = this.Extras.DeepClone();
+        }
+
+        #endregion
+    }
+}

+ 4 - 4
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -305,17 +305,17 @@ namespace SharpGLTF.Geometry
             return _Positions.TryGetValue(position, out List<TvG> geos) ? (IReadOnlyList<TvG>)geos : Array.Empty<TvG>();
         }
 
-        public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta)
+        public void SetVertexDelta(Vector3 meshVertex, VertexGeometryDelta geometryDelta)
         {
-            if (_Positions.TryGetValue(key, out List<TvG> geos))
+            if (_Positions.TryGetValue(meshVertex, out List<TvG> geos))
             {
                 foreach (var g in geos) SetVertexDelta(g, geometryDelta, VertexMaterialDelta.Zero);
             }
         }
 
-        public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
+        public void SetVertexDelta(Vector3 meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta)
         {
-            if (_Positions.TryGetValue(key, out List<TvG> geos))
+            if (_Positions.TryGetValue(meshVertex, out List<TvG> geos))
             {
                 foreach (var g in geos) SetVertexDelta(g, geometryDelta, materialDelta);
             }

+ 4 - 0
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -31,6 +31,8 @@ namespace SharpGLTF.Geometry
 
         public VertexBufferColumns(VertexBufferColumns other)
         {
+            Guard.NotNull(other, nameof(other));
+
             this.Positions = other.Positions;
             this.Normals = other.Normals;
             this.Tangents = other.Tangents;
@@ -129,6 +131,8 @@ namespace SharpGLTF.Geometry
 
         public VertexBufferColumns WithTransform(Transforms.IGeometryTransform transform)
         {
+            Guard.NotNull(transform, nameof(transform));
+
             var clone = new VertexBufferColumns(this);
             clone._ApplyTransform(transform);
             return clone;

+ 14 - 2
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -280,17 +280,23 @@ namespace SharpGLTF.Geometry
         public TvM Material;
         public TvS Skinning;
 
+        /// <inheritdoc/>
+        public override int GetHashCode() { return Geometry.GetHashCode(); }
+
+        /// <inheritdoc/>
         public override bool Equals(object obj) { return obj is VertexBuilder<TvG, TvM, TvS> other && AreEqual(this, other); }
+
+        /// <inheritdoc/>
         public bool Equals(VertexBuilder<TvG, TvM, TvS> other) { return AreEqual(this, other); }
+
         public static bool operator ==(in VertexBuilder<TvG, TvM, TvS> a, in VertexBuilder<TvG, TvM, TvS> b) { return AreEqual(a, b); }
+
         public static bool operator !=(in VertexBuilder<TvG, TvM, TvS> a, in VertexBuilder<TvG, TvM, TvS> b) { return !AreEqual(a, b); }
         public static bool AreEqual(in VertexBuilder<TvG, TvM, TvS> a, in VertexBuilder<TvG, TvM, TvS> b)
         {
             return a.Geometry.Equals(b.Geometry) && a.Material.Equals(b.Material) && a.Skinning.Equals(b.Skinning);
         }
 
-        public override int GetHashCode() { return Geometry.GetHashCode(); }
-
         #endregion
 
         #region properties
@@ -388,6 +394,8 @@ namespace SharpGLTF.Geometry
 
         public VertexBuilder<TvG, TvM, TvS> WithMaterial(params Vector2[] uvs)
         {
+            Guard.NotNull(uvs, nameof(uvs));
+
             var v = this;
             for (int i = 0; i < uvs.Length; ++i) v.Material.SetTexCoord(i, uvs[i]);
             return v;
@@ -395,6 +403,8 @@ namespace SharpGLTF.Geometry
 
         public VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, params Vector2[] uvs)
         {
+            Guard.NotNull(uvs, nameof(uvs));
+
             var v = this;
             v.Material.SetColor(0, color0);
             for (int i = 0; i < uvs.Length; ++i) v.Material.SetTexCoord(i, uvs[i]);
@@ -403,6 +413,8 @@ namespace SharpGLTF.Geometry
 
         public VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, Vector4 color1, params Vector2[] uvs)
         {
+            Guard.NotNull(uvs, nameof(uvs));
+
             var v = this;
             v.Material.SetColor(0, color0);
             v.Material.SetColor(1, color1);

+ 3 - 3
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexEmpty.cs

@@ -13,7 +13,7 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// in a <see cref="VertexBuilder{TvG, TvM, TvS}"/> structure.
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Empty")]
-    public readonly struct VertexEmpty : IVertexMaterial, IVertexSkinning
+    public readonly struct VertexEmpty : IVertexMaterial, IVertexSkinning, IEquatable<VertexEmpty>
     {
         #region constructor
         public void Validate() { }
@@ -22,11 +22,11 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #region data
 
+        public override int GetHashCode() { return 0; }
         public override bool Equals(object obj) { return obj is VertexEmpty; }
         public bool Equals(VertexEmpty other) { return true; }
         public static bool operator ==(in VertexEmpty a, in VertexEmpty b) { return true; }
         public static bool operator !=(in VertexEmpty a, in VertexEmpty b) { return false; }
-        public override int GetHashCode() { return 0; }
 
         #endregion
 
@@ -70,7 +70,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public SparseWeight8 GetBindings() { return default; }
 
         /// <inheritdoc/>
-        public void SetBindings(in SparseWeight8 weights) { throw new NotSupportedException(); }
+        public void SetBindings(in SparseWeight8 bindings) { throw new NotSupportedException(); }
 
         /// <inheritdoc/>
         public void SetBindings(params (int Index, float Weight)[] bindings) { throw new NotSupportedException(); }

+ 1 - 1
src/SharpGLTF.Toolkit/Materials/MaterialBuilder.cs

@@ -99,7 +99,7 @@ namespace SharpGLTF.Materials
         /// <summary>
         /// Gets or sets a value indicating whether triangles must be rendered from both sides.
         /// </summary>
-        public Boolean DoubleSided { get; set; } = false;
+        public Boolean DoubleSided { get; set; }
 
         public String ShaderStyle
         {

+ 20 - 3
src/SharpGLTF.Toolkit/Materials/MaterialValue.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF.Materials
             if (value is Vector2 v2) return v2;
             if (value is Vector3 v3) return v3;
             if (value is Vector4 v4) return v4;
-            throw new ArgumentException(nameof(value));
+            throw new ArgumentException("Value type not supported.", nameof(value));
         }
 
         private MaterialValue(float x) { _Length = 1; _X = x; _Y = 0; _Z = 0; _W = 0; }
@@ -94,7 +94,7 @@ namespace SharpGLTF.Materials
                     case 2: return typeof(Vector2);
                     case 3: return typeof(Vector3);
                     case 4: return typeof(Vector4);
-                    default: throw new NotImplementedException();
+                    default: throw new InvalidOperationException($"{_Length} not supported.");
                 }
             }
         }
@@ -189,16 +189,28 @@ namespace SharpGLTF.Materials
             private readonly MaterialValue _Default;
             private MaterialValue _Value;
 
+            /// <inheritdoc/>
             public override int GetHashCode()
             {
                 return Key.GetHashCode() ^ _Value.GetHashCode();
             }
 
+            /// <inheritdoc/>
+            public override bool Equals(object obj)
+            {
+                return obj is _Property other && Equals(other);
+            }
+
+            /// <inheritdoc/>
             public bool Equals(_Property other)
             {
                 return AreEqual(this, other);
             }
 
+            public static bool operator ==(_Property a, _Property b) { return a.Equals(b); }
+
+            public static bool operator !=(_Property a, _Property b) { return !a.Equals(b); }
+
             public static bool AreEqual(_Property a, _Property b)
             {
                 if (a.Key != b.Key) return false;
@@ -272,6 +284,9 @@ namespace SharpGLTF.Materials
 
             public static bool AreEqual(Collection x, Collection y)
             {
+                Guard.NotNull(x, nameof(x));
+                Guard.NotNull(y, nameof(y));
+
                 if (x._Properties.Length != y._Properties.Length) return false;
 
                 for (int i = 0; i < x._Properties.Length; ++i)
@@ -406,12 +421,14 @@ namespace SharpGLTF.Materials
 
             public void CopyTo(Collection other)
             {
+                Guard.NotNull(other, nameof(other));
+
                 for (int i = 0; i < this._Properties.Length; ++i)
                 {
                     var src = this._Properties[i];
                     var dst = other._Properties[i];
 
-                    if (src.Name != dst.Name) throw new ArgumentException(nameof(other));
+                    if (src.Name != dst.Name) throw new ArgumentException("Naming mismatch.", nameof(other));
 
                     dst.Value = src.Value;
                 }

+ 3 - 1
src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs

@@ -63,7 +63,7 @@ namespace SharpGLTF.Materials
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private TextureTransformBuilder _Transform;
 
-        public int CoordinateSet { get; set; } = 0;
+        public int CoordinateSet { get; set; }
 
         public TEXMIPMAP MinFilter { get; set; } = TEXMIPMAP.DEFAULT;
 
@@ -75,6 +75,8 @@ namespace SharpGLTF.Materials
 
         public static bool AreEqualByContent(TextureBuilder x, TextureBuilder y)
         {
+            if (x == null || y == null) return true;
+
             if ((x, y).AreSameReference(out bool areTheSame)) return areTheSame;
 
             if (!BaseBuilder.AreEqualByContent(x, y)) return false;

+ 2 - 0
src/SharpGLTF.Toolkit/Scenes/CameraBuilder.cs

@@ -23,6 +23,8 @@ namespace SharpGLTF.Scenes
         protected CameraBuilder(CameraBuilder other)
             : base(other)
         {
+            Guard.NotNull(other, nameof(other));
+
             this.ZNear = other.ZNear;
             this.ZFar = other.ZFar;
         }

+ 2 - 0
src/SharpGLTF.Toolkit/Scenes/LightBuilder.cs

@@ -27,6 +27,8 @@ namespace SharpGLTF.Scenes
         protected LightBuilder(LightBuilder other)
             : base(other)
         {
+            Guard.NotNull(other, nameof(other));
+
             this.Color = other.Color;
             this.Intensity = other.Intensity;
         }

+ 3 - 3
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -108,9 +108,9 @@ namespace SharpGLTF.Scenes
             get
             {
                 var tracks = Enumerable.Empty<string>();
-                if (_Scale != null) tracks.Concat(_Scale.Tracks.Keys);
-                if (_Rotation != null) tracks.Concat(_Rotation.Tracks.Keys);
-                if (_Translation != null) tracks.Concat(_Translation.Tracks.Keys);
+                if (_Scale != null) tracks = tracks.Concat(_Scale.Tracks.Keys);
+                if (_Rotation != null) tracks = tracks.Concat(_Rotation.Tracks.Keys);
+                if (_Translation != null) tracks = tracks.Concat(_Translation.Tracks.Keys);
                 return tracks.Distinct();
             }
         }

+ 1 - 0
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -144,6 +144,7 @@ namespace SharpGLTF.Scenes
         {
             Guard.NotNull(mesh, nameof(mesh));
             Guard.IsTrue(meshWorldMatrix.IsValid(_Extensions.MatrixCheck.WorldTransform), nameof(meshWorldMatrix));
+            Guard.NotNull(joints, nameof(joints));
             GuardAll.NotNull(joints, nameof(joints));
 
             var instance = new InstanceBuilder(this);

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

@@ -22,6 +22,9 @@ namespace SharpGLTF.Schema2
         public static unsafe BufferView CreateBufferView<T>(this ModelRoot root, IReadOnlyList<T> data)
             where T : unmanaged
         {
+            Guard.NotNull(root, nameof(root));
+            Guard.NotNull(data, nameof(data));
+
             var view = root.CreateBufferView(sizeof(T) * data.Count);
 
             if (typeof(T) == typeof(int))

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

@@ -426,6 +426,7 @@ namespace SharpGLTF.Schema2
         {
             Guard.NotNull(srcMaterial, nameof(srcMaterial));
             Guard.NotNull(dstMaterial, nameof(dstMaterial));
+            Guard.NotNull(channelKeys, nameof(channelKeys));
 
             foreach (var k in channelKeys)
             {

+ 5 - 2
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -383,7 +383,7 @@ namespace SharpGLTF.Schema2
                 .OfType<IReadOnlyDictionary<string, Object>>()
                 .SelectMany(item => item.Keys)
                 .Distinct()
-                .Where(item => item.StartsWith("_"));
+                .Where(item => item.StartsWith("_", StringComparison.Ordinal));
 
             foreach (var key in keys)
             {
@@ -405,7 +405,7 @@ namespace SharpGLTF.Schema2
         {
             Guard.NotNullOrEmpty(attribute, nameof(attribute));
 
-            attribute = attribute.ToUpper();
+            attribute = attribute.ToUpperInvariant();
             var expectedType = values.Where(item => item != null).FirstOrDefault()?.GetType();
             if (expectedType == null) return instancing;
 
@@ -800,6 +800,9 @@ namespace SharpGLTF.Schema2
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
+            Guard.NotNull(triangles, nameof(triangles));
+            Guard.NotNull(materialFunc, nameof(materialFunc));
+
             var mats = new Dictionary<TMaterial, Materials.MaterialBuilder>();
 
             Materials.MaterialBuilder useMaterial(TMaterial srcMat)

+ 2 - 0
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -67,6 +67,7 @@ namespace SharpGLTF.Schema2
         public static Node WithSkinBinding(this Node node, Matrix4x4 meshPoseTransform, params Node[] joints)
         {
             Guard.NotNull(node, nameof(node));
+            Guard.NotNull(joints, nameof(joints));
 
             foreach (var j in joints) Guard.MustShareLogicalParent(node, j, nameof(joints));
 
@@ -80,6 +81,7 @@ namespace SharpGLTF.Schema2
         public static Node WithSkinBinding(this Node node, params (Node Joint, Matrix4x4 InverseBindMatrix)[] joints)
         {
             Guard.NotNull(node, nameof(node));
+            Guard.NotNull(joints, nameof(joints));
 
             foreach (var j in joints)
             {