Bladeren bron

update branch

Bert Temme 2 jaren geleden
bovenliggende
commit
2f7d29aef6
57 gewijzigde bestanden met toevoegingen van 381 en 197 verwijderingen
  1. 4 4
      build/SharpGLTF.CodeGen/CodeGen/CodeEmitUtils.cs
  2. 11 13
      build/SharpGLTF.CodeGen/CodeGen/EmitCSharp.cs
  3. 3 1
      build/SharpGLTF.CodeGen/SchemaReflection/SchemaTypes.cs
  4. 1 1
      src/Directory.Build.props
  5. 1 1
      src/Shared/Guard.cs
  6. 48 9
      src/Shared/_Extensions.cs
  7. 18 6
      src/SharpGLTF.Core/Animations/CurveSampler.cs
  8. 1 3
      src/SharpGLTF.Core/Animations/CurveSamplers.Fixed.cs
  9. 3 2
      src/SharpGLTF.Core/Diagnostics/DebuggerDisplay.cs
  10. 1 1
      src/SharpGLTF.Core/Memory/MemoryAccessor.Validation.cs
  11. 50 18
      src/SharpGLTF.Core/Schema2/Serialization.Binary.cs
  12. 1 1
      src/SharpGLTF.Core/Schema2/Serialization.ReadContext.cs
  13. 1 1
      src/SharpGLTF.Core/Schema2/gltf.Buffer.cs
  14. 9 5
      src/SharpGLTF.Core/Schema2/gltf.BufferView.cs
  15. 3 4
      src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs
  16. 2 2
      src/SharpGLTF.Core/Schema2/gltf.Images.cs
  17. 1 1
      src/SharpGLTF.Core/Schema2/gltf.LogicalChildOfRoot.cs
  18. 6 5
      src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs
  19. 28 26
      src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs
  20. 5 5
      src/SharpGLTF.Core/Schema2/gltf.Skin.cs
  21. 7 2
      src/SharpGLTF.Runtime/Runtime/MeshDecoder.Schema2.cs
  22. 2 0
      src/SharpGLTF.Runtime/Runtime/VertexNormalsFactory.cs
  23. 2 0
      src/SharpGLTF.Runtime/Runtime/VertexTangentsFactory.cs
  24. 2 2
      src/SharpGLTF.Toolkit/BaseBuilder.cs
  25. 26 0
      src/SharpGLTF.Toolkit/Collections/Triple.cs
  26. 2 2
      src/SharpGLTF.Toolkit/Collections/ValueListSet.cs
  27. 10 6
      src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs
  28. 1 1
      src/SharpGLTF.Toolkit/Geometry/Packed/PackedEncoding.cs
  29. 4 0
      src/SharpGLTF.Toolkit/Geometry/Packed/PackedMeshBuilder.cs
  30. 1 1
      src/SharpGLTF.Toolkit/Geometry/Packed/PackedPrimitiveBuilder.cs
  31. 3 0
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  32. 6 0
      src/SharpGLTF.Toolkit/Geometry/PrimitiveInterfaces.cs
  33. 14 7
      src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs
  34. 27 21
      src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs
  35. 6 6
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.Builder.cs
  36. 4 2
      src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs
  37. 2 0
      src/SharpGLTF.Toolkit/Materials/MaterialEnums.cs
  38. 3 0
      src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs
  39. 3 2
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs
  40. 9 4
      src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs
  41. 3 3
      src/SharpGLTF.Toolkit/Schema2/EvaluatedTriangle.cs
  42. 3 0
      src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs
  43. 17 7
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  44. 1 1
      tests/Directory.Build.props
  45. 5 5
      tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs
  46. 1 1
      tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs
  47. 1 1
      tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj
  48. 1 1
      tests/SharpGLTF.Core.Tests/AssemblyAPITests.cs
  49. 1 1
      tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadGeneratedTests.cs
  50. 5 0
      tests/SharpGLTF.Core.Tests/Validation/InvalidFilesTests.cs
  51. 2 2
      tests/SharpGLTF.DownloadTestFiles/DownloadUtils.cs
  52. 4 4
      tests/SharpGLTF.NUnit/NUnitGltfUtils.cs
  53. 1 1
      tests/SharpGLTF.NUnit/Plotting.cs
  54. 1 1
      tests/SharpGLTF.NUnit/Reports.cs
  55. 2 2
      tests/SharpGLTF.NUnit/TestFiles.cs
  56. 1 1
      tests/SharpGLTF.ThirdParty.Tests/CesiumInstancingTests.cs
  57. 1 1
      tests/SharpGLTF.ThirdParty.Tests/SandboxTests.cs

+ 4 - 4
build/SharpGLTF.CodeGen/CodeGen/CodeEmitUtils.cs

@@ -71,13 +71,13 @@ namespace SharpGLTF.CodeGen
         {
             while(true)
             {
-                var indices = _FindDescriptionKeyword(description);
-                if (indices.start < 0) return description;
+                var (start, len) = _FindDescriptionKeyword(description);
+                if (start < 0) return description;
 
-                var block = description.Substring(indices.start , indices.len);
+                var block = description.Substring(start , len);
                 var name = block.Substring(2, block.Length - 4);
 
-                description = description.Replace(block, $"<see cref=\"{name}\"/>");
+                description = description.Replace(block, $"<see cref=\"{name}\"/>", StringComparison.Ordinal);
             }
         }
 

+ 11 - 13
build/SharpGLTF.CodeGen/CodeGen/EmitCSharp.cs

@@ -93,9 +93,9 @@ namespace SharpGLTF.CodeGen
 
         #region setup & declaration
 
-        private string _SanitizeName(string name)
+        private static string _SanitizeName(string name)
         {
-            return name.Replace(" ", "");
+            return name.Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase);
         }
 
         private _RuntimeType _UseType(SchemaType stype)
@@ -149,7 +149,7 @@ namespace SharpGLTF.CodeGen
 
             foreach(var f in type.Fields)
             {
-                var runtimeName = _SanitizeName(f.PersistentName).Replace("@","at");
+                var runtimeName = _SanitizeName(f.PersistentName).Replace("@","at", StringComparison.Ordinal);
 
                 SetFieldName(f, $"_{runtimeName}");
                 SetPropertyName(f, runtimeName);
@@ -202,7 +202,7 @@ namespace SharpGLTF.CodeGen
                         var container = extra?.CollectionContainer;
                         if (string.IsNullOrWhiteSpace(container)) container = _DefaultCollectionContainer;
 
-                        return container.Replace("TItem", _GetRuntimeName(arrayType.ItemType));
+                        return container.Replace("TItem", _GetRuntimeName(arrayType.ItemType), StringComparison.Ordinal);
                     }
 
                 case DictionaryType dictType:
@@ -252,10 +252,10 @@ namespace SharpGLTF.CodeGen
 
             switch (type)
             {
-                case StringType stype: if (value is string) return value;
+                case StringType _:
 
-                    return value == null
-                        ? null
+                    return value is string
+                        ? value
                         : Convert.ChangeType(value, typeof(string), System.Globalization.CultureInfo.InvariantCulture);
 
                 case BlittableType btype:
@@ -266,15 +266,13 @@ namespace SharpGLTF.CodeGen
 
                             var str = value as string;
 
-                            if (str.ToLowerInvariant() == "false") return false;
-                            if (str.ToLowerInvariant() == "true") return true;
+                            if (str.ToUpperInvariant() == "FALSE") return false;
+                            if (str.ToUpperInvariant() == "TRUE") return true;
                             throw new NotImplementedException();
                         }
 
-                        if (value is string) return value;
-
-                        return value == null
-                            ? null
+                        return value is string
+                            ? value
                             : Convert.ChangeType(value, btype.DataType.AsType(), System.Globalization.CultureInfo.InvariantCulture);
                     }
 

+ 3 - 1
build/SharpGLTF.CodeGen/SchemaReflection/SchemaTypes.cs

@@ -335,7 +335,7 @@ namespace SharpGLTF.SchemaReflection
 
             public bool Equals(FieldInfo x, FieldInfo y) { return Compare(x,y) == 0; }
 
-            public int GetHashCode(FieldInfo obj) { return obj._PersistentName.GetHashCode(); }
+            public int GetHashCode(FieldInfo obj) { return obj._PersistentName.GetHashCode(StringComparison.Ordinal); }
         }
 
         private static readonly _Comparer _DefaultComparer = new _Comparer();
@@ -420,7 +420,9 @@ namespace SharpGLTF.SchemaReflection
 
         private readonly SchemaType _ReferencedType;
 
+        #pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
         public override string PersistentName => throw new NotImplementedException();
+        #pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
 
         #endregion
     }

+ 1 - 1
src/Directory.Build.props

@@ -30,7 +30,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <NoWarn>1701;1702;1591;CA1062;CA1304;CA1310;CA1000;CA1510;CA1512</NoWarn>
+    <NoWarn>1701;1702;1591;CA1033;CA1062;CA1304;CA1310;CA1000;CA1510;CA1512;CA1861;CA1852;CA1823</NoWarn>
   </PropertyGroup>  
 
   <PropertyGroup>

+ 1 - 1
src/Shared/Guard.cs

@@ -245,7 +245,7 @@ namespace SharpGLTF
 
             if (gltfURI.Any(c => _InvalidRelativePathChars.Contains(c))) throw new ArgumentException($"Invalid URI '{gltfURI}'.");
 
-            if (gltfURI.Any(chr => char.IsWhiteSpace(chr))) gltfURI = Uri.EscapeUriString(gltfURI);
+            if (gltfURI.Any(chr => char.IsWhiteSpace(chr))) gltfURI = gltfURI._EscapeStringInternal();
 
             if (!Uri.TryCreate(gltfURI, UriKind.Relative, out Uri xuri)) throw new ArgumentException($"Invalid URI '{gltfURI}'.");
 

+ 48 - 9
src/Shared/_Extensions.cs

@@ -191,6 +191,23 @@ namespace SharpGLTF
 
         #endregion
 
+        #region string extensions
+
+        #if NETSTANDARD2_0
+        internal static bool StartsWith(this string text, char c)
+        {
+            return text.StartsWith(c.ToString());
+        }
+
+        internal static string Replace(this string text, string oldText, string newText, StringComparison comparison)
+        {
+            if (comparison == StringComparison.Ordinal) return text.Replace(oldText, newText);
+            throw new NotImplementedException();
+        }
+        #endif
+
+        #endregion
+
         #region linq
 
         public static bool AreSameReference<T>(this (T x, T y) refs, out bool result)
@@ -311,6 +328,20 @@ namespace SharpGLTF
                 array[i] = value;
             }
         }
+        
+        internal static IReadOnlyList<T> EnsureList<T>(this IEnumerable<T> collection)
+        {
+            // prevents CA1851: Possible multiple enumerations...
+
+            return collection is IReadOnlyList<T> list
+                ? list
+                : collection.ToList();
+        }
+
+        internal static bool IsEmpty<T>(this IReadOnlyList<T> list)
+        {
+            return list.Count == 0;
+        }
 
         internal static int IndexOf<T>(this IReadOnlyList<T> collection, T value)
         {
@@ -563,7 +594,7 @@ namespace SharpGLTF
                                 if (!ptr.MoveNext()) break;
                                 var b = ptr.Current;
 
-                                if (!_IsDegenerated(a, b)) yield return ((int)a, (int)b);
+                                if (!_IsDegeneratedSegment(a, b)) yield return ((int)a, (int)b);
                             }
                         }
 
@@ -591,7 +622,7 @@ namespace SharpGLTF
                                 if (!ptr.MoveNext()) break;
                                 var c = ptr.Current;
 
-                                if (!_IsDegenerated(a, b, c)) yield return ((int)a, (int)b, (int)c);
+                                if (!_IsDegeneratedTriangle(a, b, c)) yield return ((int)a, (int)b, (int)c);
                             }
                         }
 
@@ -612,7 +643,7 @@ namespace SharpGLTF
                                 if (!ptr.MoveNext()) break;
                                 var c = ptr.Current;
 
-                                if (!_IsDegenerated(a, b, c)) yield return ((int)a, (int)b, (int)c);
+                                if (!_IsDegeneratedTriangle(a, b, c)) yield return ((int)a, (int)b, (int)c);
 
                                 b = c;
                             }
@@ -637,7 +668,7 @@ namespace SharpGLTF
                                 if (!ptr.MoveNext()) break;
                                 var c = ptr.Current;
 
-                                if (!_IsDegenerated(a, b, c))
+                                if (!_IsDegeneratedTriangle(a, b, c))
                                 {
                                     if (reversed) yield return ((int)b, (int)a, (int)c);
                                     else yield return ((int)a, (int)b, (int)c);
@@ -656,12 +687,12 @@ namespace SharpGLTF
             }
         }
 
-        private static bool _IsDegenerated(uint a, uint b)
+        private static bool _IsDegeneratedSegment(uint a, uint b)
         {
             return a == b;
         }
 
-        private static bool _IsDegenerated(uint a, uint b, uint c)
+        private static bool _IsDegeneratedTriangle(uint a, uint b, uint c)
         {
             if (a == b) return true;
             if (a == c) return true;
@@ -727,9 +758,9 @@ namespace SharpGLTF
                 return Convert.FromBase64String(content);
             }
 
-            if (content.StartsWith(",", StringComparison.OrdinalIgnoreCase))
+            if (content.StartsWith(','))
             {
-                content = content.Substring(",".Length);
+                content = content.Substring(1);
 
                 if (content.Length == 1) return new Byte[] { Byte.Parse(content, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) };
 
@@ -741,7 +772,15 @@ namespace SharpGLTF
 
         #endregion
 
-        #region json        
+        #region json
+
+        public static string _EscapeStringInternal(this string uri)
+        {
+            // https://stackoverflow.com/questions/4396598/whats-the-difference-between-escapeuristring-and-escapedatastring
+            #pragma warning disable SYSLIB0013 // Type or member is obsolete
+            return Uri.EscapeUriString(uri);
+            #pragma warning restore SYSLIB0013 // Type or member is obsolete
+        }
 
         #if NET6_0
 

+ 18 - 6
src/SharpGLTF.Core/Animations/CurveSampler.cs

@@ -152,6 +152,8 @@ namespace SharpGLTF.Animations
         {
             Guard.NotNull(sequence, nameof(sequence));
 
+            sequence = sequence.EnsureList();
+
             if (!sequence.Any()) return (default(T), default(T), 0);
 
             (float Key, T Value)? left = null;
@@ -172,7 +174,7 @@ namespace SharpGLTF.Animations
 
                 if (item.Key > offset)
                 {
-                    if (left == null) left = prev;
+                    left ??= prev;
                     right = item;
                     break;
                 }
@@ -213,6 +215,8 @@ namespace SharpGLTF.Animations
         {
             Guard.NotNull(sequence, nameof(sequence));
 
+            sequence = sequence.EnsureList();
+
             if (!sequence.Any()) return (0, 0, 0);
 
             float? left = null;
@@ -233,7 +237,7 @@ namespace SharpGLTF.Animations
 
                 if (item > offset)
                 {
-                    if (left == null) left = prev;
+                    left ??= prev;
                     right = item;
                     break;
                 }
@@ -274,15 +278,23 @@ namespace SharpGLTF.Animations
         /// <returns>A sequence of 1 second chunks.</returns>
         internal static IEnumerable<(float, T)[]> SplitByTime<T>(this IEnumerable<(Single Time, T Value)> sequence)
         {
-            if (!sequence.Any()) yield break;
+            List<(float, T)> segment = null;
 
-            var segment = new List<(float, T)>();
             int time = 0;
+            
+            (Single Time, T Value) last = default;
 
-            var last = sequence.First();
+            bool isFirst = true;
 
             foreach (var item in sequence)
             {
+                if (isFirst)
+                {
+                    last = item;
+                    segment ??= new List<(float, T)>();
+                    isFirst = false;
+                }
+
                 var t = (int)item.Time;
 
                 if (time > t) throw new InvalidOperationException("unexpected data encountered.");
@@ -307,7 +319,7 @@ namespace SharpGLTF.Animations
                 last = item;
             }
 
-            if (segment.Count > 0) yield return segment.ToArray();
+            if (segment != null && segment.Count > 0) yield return segment.ToArray();
         }
 
         #endregion

+ 1 - 3
src/SharpGLTF.Core/Animations/CurveSamplers.Fixed.cs

@@ -17,13 +17,11 @@ namespace SharpGLTF.Animations
 
         public static ICurveSampler<T> Create(IEnumerable<(float Key, T Value)> sequence)
         {
-            System.Diagnostics.Debug.Assert(!sequence.Skip(1).Any());
             return new FixedSampler<T>(sequence.First().Value);
         }
 
         public static ICurveSampler<T> Create(IEnumerable<(float Key, (T, T, T) Value)> sequence)
-        {
-            System.Diagnostics.Debug.Assert(!sequence.Skip(1).Any());
+        {            
             return new FixedSampler<T>(sequence.First().Value.Item2);
         }
 

+ 3 - 2
src/SharpGLTF.Core/Diagnostics/DebuggerDisplay.cs

@@ -86,11 +86,12 @@ namespace SharpGLTF.Diagnostics
 
             var vcounts = prim.VertexAccessors.Values
                 .Select(item => item.Count)
-                .Distinct();
+                .Distinct()
+                .ToList();
 
             var vcount = vcounts.First();
 
-            if (vcounts.Count() > 1)
+            if (vcounts.Count > 1)
             {
                 var vAccessors = prim.VertexAccessors
                     .OrderBy(item => item.Key, Memory.MemoryAccessInfo.NameComparer)

+ 1 - 1
src/SharpGLTF.Core/Memory/MemoryAccessor.Validation.cs

@@ -186,7 +186,7 @@ namespace SharpGLTF.Memory
 
             if (weights0 == null) throw new ArgumentNullException(nameof(weights0));
 
-            if (weights0.Attribute.Encoding != weights1.Attribute.Encoding) throw new ArgumentException("WEIGHTS_0 and WEIGHTS_1 format mismatch.", nameof(weights1.Attribute));
+            if (weights0.Attribute.Encoding != weights1.Attribute.Encoding) throw new ArgumentException("WEIGHTS_0 and WEIGHTS_1 format mismatch.", nameof(weights1));
 
             var len = weights0.Attribute.ItemByteLength;
             Span<Byte> dst = stackalloc byte[len * 2];

+ 50 - 18
src/SharpGLTF.Core/Schema2/Serialization.Binary.cs

@@ -45,7 +45,6 @@ namespace SharpGLTF.Schema2
                 return false;
             }
         }
-        
 
         internal static bool _Identify(Stream stream)
         {
@@ -92,34 +91,64 @@ namespace SharpGLTF.Schema2
 
             using (var binaryReader = new BinaryReader(stream, Encoding.ASCII))
             {
-                _ReadBinaryHeader(binaryReader);
+                // body length can actually be smaller than the stream length,
+                // in which case, the data afterwards is considered "extra data".
 
-                var chunks = new Dictionary<uint, Byte[]>();
+                var remaining = _ReadBinaryHeader(binaryReader);
                 
-                while (binaryReader._TryReadUInt32(out var chunkLength)) // keep reading until EndOfFile
+                void _checkCanRead(long count)
+                {
+                    if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
+                    if (count > int.MaxValue) throw new Validation.SchemaException(null, $"{count} bytes to read exceeds maximum capacity.");
+                    if (remaining < count) throw new Validation.SchemaException(null, "unexpected End of GLB block.");
+                    remaining -= count;
+                }                
+
+                var chunks = new Dictionary<uint, Byte[]>();
+
+                while (remaining >= 4)
                 {
+                    remaining -= 4;
+                    if (!binaryReader._TryReadUInt32(out var chunkLength)) break;
+
+                    if (chunkLength  == 0)
+                    {
+                        throw new Validation.SchemaException(null, $"The chunk must non zero size.");
+                    }                    
+
                     if ((chunkLength & 3) != 0)
                     {
                         throw new Validation.SchemaException(null, $"The chunk must be padded to 4 bytes: {chunkLength}");
                     }
 
+                    _checkCanRead(4);
                     uint chunkId = binaryReader.ReadUInt32();
 
-                    if (chunks.ContainsKey(chunkId)) throw new Validation.SchemaException(null, $"Duplicated chunk found {chunkId}");
-
+                    if (chunks.ContainsKey(chunkId))
+                    {
+                        throw new Validation.SchemaException(null, $"Duplicated chunk found {chunkId}");
+                    }
+                    
+                    _checkCanRead(chunkLength);
                     var data = binaryReader.ReadBytes((int)chunkLength);
 
                     chunks[chunkId] = data;
                 }
 
-                if (!chunks.ContainsKey(CHUNKJSON)) throw new Validation.SchemaException(null, "JSON Chunk chunk not found");
+                // finish reading remainder
+                // if (remaining > 0) { binaryReader.ReadBytes((int)remaining); }
+
+                if (!chunks.ContainsKey(CHUNKJSON)) throw new Validation.SchemaException(null, "JSON Chunk chunk not found");                
+
+                // warnings
                 // if (!chunks.ContainsKey(CHUNKBIN)) throw new Validation.SchemaException(null, "BIN Chunk chunk not found");
+                // if (remaining > 0) throw new Validation.SchemaException(null, "Extra bytes found");
 
                 return chunks;
             }
         }
 
-        private static void _ReadBinaryHeader(BinaryReader binaryReader)
+        private static long _ReadBinaryHeader(BinaryReader binaryReader)
         {
             Guard.NotNull(binaryReader, nameof(binaryReader));
 
@@ -129,24 +158,27 @@ namespace SharpGLTF.Schema2
             uint version = binaryReader.ReadUInt32();
             if (version != GLTFVERSION2) throw new Validation.SchemaException(null, $"Unknown version number: {version}");
 
-            uint length = binaryReader.ReadUInt32();
-
-            uint? fileLength = null;
+            uint bodyLength = binaryReader.ReadUInt32(); // length of the actual glb body            
 
-            try
+            try // check stream is large enough
             {
-                fileLength = (uint)binaryReader.BaseStream.Length;                
+                if (binaryReader.BaseStream.CanSeek)
+                {
+                    var fileLength = (uint)binaryReader.BaseStream.Length;
+
+                    if (bodyLength > fileLength)
+                    {
+                        throw new Validation.SchemaException(null, $"The specified length of the file ({bodyLength}) is not equal to the actual length of the file ({fileLength}).");
+                    }
+                }
             }
             catch (System.NotSupportedException)
             {
                 // Some streams like Android assets don't support getting the length
                 // https://github.com/vpenades/SharpGLTF/issues/178
-            }
+            }            
 
-            if (fileLength.HasValue && length != fileLength.Value)
-            {
-                throw new Validation.SchemaException(null, $"The specified length of the file ({length}) is not equal to the actual length of the file ({fileLength}).");
-            }
+            return bodyLength - 12;
         }
 
         #endregion

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

@@ -251,7 +251,7 @@ namespace SharpGLTF.Schema2
             catch (System.IO.EndOfStreamException ex)
             {
                 var vr = new Validation.ValidationResult(null, this.Validation);
-                vr.SetSchemaError(ex);
+                vr.SetSchemaError(ex);                
                 return (null, vr);
             }
             catch (Validation.SchemaException ex)

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

@@ -70,7 +70,7 @@ namespace SharpGLTF.Schema2
         {
             writer.WriteAllBytesToEnd(satelliteUri, new ArraySegment<byte>(_Content.GetPaddedContent()));
 
-            this._uri = Uri.EscapeUriString(satelliteUri);
+            this._uri = satelliteUri._EscapeStringInternal();
             this._byteLength = _Content.Length;
         }
 

+ 9 - 5
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -144,12 +144,16 @@ namespace SharpGLTF.Schema2
         /// <returns>true if the buffer is interleaved</returns>
         public bool IsInterleaved(IEnumerable<Accessor> accessors)
         {
-            Guard.NotNullOrEmpty(accessors, nameof(accessors));
-            Guard.IsTrue(accessors.All(item => item.SourceBufferView == this), nameof(accessors));
+            Guard.NotNullOrEmpty(accessors, nameof(accessors));            
 
-            return accessors
-                .Select(item => item.ByteOffset)
-                .All(o => o < this.ByteStride);
+            foreach(var accessor in accessors)
+            {
+                Guard.NotNull(accessor, nameof(accessor));
+                Guard.IsTrue(accessor.SourceBufferView == this, nameof(accessors));
+                if (accessor.ByteOffset >= this.ByteStride) return false;
+            }
+
+            return true;
         }
 
         internal static bool AreEqual(BufferView bv, BYTES content, int byteStride, BufferMode? target)

+ 3 - 4
src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs

@@ -8,7 +8,6 @@ using SharpGLTF.IO;
 
 using JsonToken = System.Text.Json.JsonTokenType;
 
-// using JSONEXTRAS = SharpGLTF.IO.JsonContent;
 using JSONEXTRAS = System.Text.Json.Nodes.JsonNode;
 
 namespace SharpGLTF.Schema2
@@ -192,7 +191,7 @@ namespace SharpGLTF.Schema2
             SerializeProperty(writer, "extras", content);
         }
 
-        private static IReadOnlyDictionary<string, JsonSerializable> _ToDictionary(JsonSerializable context, IEnumerable<JsonSerializable> serializables)
+        private static Dictionary<string, JsonSerializable> _ToDictionary(JsonSerializable context, IEnumerable<JsonSerializable> serializables)
         {
             var dict = new Dictionary<string, JsonSerializable>();
 
@@ -227,7 +226,7 @@ namespace SharpGLTF.Schema2
 
                 case "extras":
                     {
-                        var content = System.Text.Json.Nodes.JsonNode.Parse(ref reader);
+                        var content = JSONEXTRAS.Parse(ref reader);
                         _extras = content;
                         break;
                     }
@@ -236,7 +235,7 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        private static void _DeserializeExtensions(JsonSerializable parent, ref Utf8JsonReader reader, IList<JsonSerializable> extensions)
+        private static void _DeserializeExtensions(JsonSerializable parent, ref Utf8JsonReader reader, List<JsonSerializable> extensions)
         {
             reader.Read();
 

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -234,8 +234,8 @@ namespace SharpGLTF.Schema2
 
             satelliteUri = writer.WriteImage(satelliteUri, imimg);
 
-            satelliteUri = satelliteUri.Replace("\\", "/");
-            _uri = Uri.EscapeUriString(satelliteUri);
+            satelliteUri = satelliteUri.Replace("\\", "/", StringComparison.Ordinal);
+            _uri = satelliteUri._EscapeStringInternal();
             _mimeType = null;
         }
 

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

@@ -100,7 +100,7 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        private bool RenameIfAvailable(string newName, ISet<string> usedNames)
+        private bool RenameIfAvailable(string newName, HashSet<string> usedNames)
         {
             if (usedNames.Contains(newName)) return false;
             this.Name = newName;

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

@@ -15,7 +15,7 @@ namespace SharpGLTF.Schema2
     /// to have an homogeneous and easy to use API.
     /// </remarks>
     [System.Diagnostics.DebuggerDisplay("Channel {_Key}")]
-    public readonly struct MaterialChannel
+    public readonly struct MaterialChannel : IEquatable<MaterialChannel>
     {
         #region lifecycle
 
@@ -49,16 +49,14 @@ namespace SharpGLTF.Schema2
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private readonly IReadOnlyList<IMaterialParameter> _Parameters;
-
-        /// <inheritdoc />
+        
         public override int GetHashCode()
         {
             if (_Material == null) return 0;
 
             return _Material.GetHashCode() ^ _Key.GetHashCode(StringComparison.InvariantCulture);
         }
-
-        /// <inheritdoc />
+        
         public override bool Equals(object obj) { return obj is MaterialChannel other && Equals(other); }
 
         public bool Equals(MaterialChannel other)
@@ -69,6 +67,9 @@ namespace SharpGLTF.Schema2
             return true;
         }
 
+        public static bool operator ==(in MaterialChannel a, in MaterialChannel b) { return a.Equals(b); }
+        public static bool operator !=(in MaterialChannel a, in MaterialChannel b) { return !a.Equals(b); }
+
         #endregion
 
         #region properties

+ 28 - 26
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -375,38 +375,39 @@ namespace SharpGLTF.Schema2
                 validate.IsTrue(nameof(_indices), !incompatiblePrimitiveCount, "Mismatch between indices count and PrimitiveType");
             }
 
-            // check vertex attributes accessors ByteStride
+            // check access to shared BufferViews            
 
-            foreach (var group in this.VertexAccessors.Values.GroupBy(item => item.SourceBufferView))
-            {
-                if (!group.Skip(1).Any()) continue;
-
-                // if more than one accessor shares a BufferView, it must define a ByteStride
-
-                validate.IsGreater("ByteStride", group.Key.ByteStride, 0); // " must be defined when two or more accessors use the same BufferView."
+            var accessorsWithSharedBufferViews = this.VertexAccessors
+                .Values
+                .Distinct() // it is allowed that multiple attributes use the same accessor
+                .GroupBy(item => item.SourceBufferView);
 
-                // determine if we're sequential or strided by checking if the memory buffers overlap
-                var memories = group.Select(item => item._GetMemoryAccessor());
-                var overlap = Memory.MemoryAccessor.HaveOverlappingBuffers(memories);
+            foreach (var group in accessorsWithSharedBufferViews)
+            {
+                if (!group.Skip(1).Any()) continue; // buffer is not shared, skipping overlap tests
 
-                bool ok = false;
+                // if more than one accessor shares a BufferView, it can happen that:
+                // - ByteStride is 0, in which case access to the shared BufferView is sequential.
+                // - ByteStride is not 0, in which case access to the shared BufferView is interleaved.                
 
-                // strided buffer detected
-                if (overlap)
+                if (group.Key.ByteStride > 0)
                 {
-                    ok = group.Sum(item => item.Format.ByteSizePadded) <= group.Key.ByteStride;
+                    foreach(var item in group)
+                    {
+                        if (item.Format.ByteSizePadded > group.Key.ByteStride)
+                        {
+                            validate._LinkThrow("Attributes", $"Attribute element size {item.Format.ByteSizePadded} exceeds ByteStride {group.Key.ByteStride}");
+                        }                        
+                    }                    
                 }
-
-                // sequential buffer detected
                 else
                 {
-                    ok = group.All(item => item.Format.ByteSizePadded <= group.Key.ByteStride);
-                }
-
-                if (!ok)
-                {
-                    var accessors = string.Join(" ", group.Select(item => item.LogicalIndex));
-                    validate._LinkThrow("Attributes", $"Inconsistent accessors configuration: {accessors}");
+                    var memories = group.Select(item => item._GetMemoryAccessor());
+                    var overlap = Memory.MemoryAccessor.HaveOverlappingBuffers(memories);
+                    if (overlap)
+                    {
+                        validate._LinkThrow("Attributes", $"BufferView access is expected to be sequential, but some buffers overlap.");
+                    }
                 }
             }
 
@@ -429,10 +430,11 @@ namespace SharpGLTF.Schema2
                 .Where(item => item.Mesh == this.LogicalParent)
                 .Select(item => item.Skin)
                 .Where(item => item != null)
-                .Select(item => item.JointsCount);
+                .Select(item => item.JointsCount)
+                .ToList();            
 
             // if no skins found, use a max joint value that essentially skips max joint validation
-            var maxJoints = skins.Any() ? skins.Max() : int.MaxValue;
+            var maxJoints = skins.Count != 0 ? skins.Max() : int.MaxValue;
 
             Accessor.ValidateVertexAttributes(validate, this.VertexAccessors, maxJoints);
         }

+ 5 - 5
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -223,19 +223,19 @@ namespace SharpGLTF.Schema2
         {
             if (nodes == null) return null;
 
+            nodes = nodes.EnsureList();
+
             foreach (var j in nodes)
             {
                 Guard.NotNull(j, nameof(nodes));
                 Guard.MustShareLogicalParent(this, j, nameof(nodes));
-            }
-
-            var workingNodes = nodes.ToList();
+            }            
 
-            var rootJoint = workingNodes.First();
+            var rootJoint = nodes.First();
 
             while (true)
             {
-                if (workingNodes.All(j => rootJoint == j || rootJoint._ContainsVisualNode(j, true))) return rootJoint;
+                if (nodes.All(j => rootJoint == j || rootJoint._ContainsVisualNode(j, true))) return rootJoint;
 
                 if (rootJoint.VisualParent == null) break;
 

+ 7 - 2
src/SharpGLTF.Runtime/Runtime/MeshDecoder.Schema2.cs

@@ -61,7 +61,9 @@ namespace SharpGLTF.Runtime
         {
             if (_Primitives.Length == 0) return;
 
-            var geometries = _Primitives.Select(item => item._Geometry);
+            var geometries = _Primitives
+                .Select(item => item._Geometry)
+                .ToList();
 
             // we can only skip normals and tangents calculation if all the
             // primitives have them. If we get a case in wich some primitives
@@ -77,9 +79,12 @@ namespace SharpGLTF.Runtime
 
             var morphTargetsCount = _Primitives.Min(item => item.MorphTargetsCount);
 
+            var targets = new List<_MorphTargetDecoder>();
+
             for (int i = 0; i < morphTargetsCount; ++i)
             {
-                var targets = _Primitives.Select(item => item._MorphTargets[i]);
+                targets.Clear();
+                targets.AddRange(_Primitives.Select(item => item._MorphTargets[i]));
 
                 hasNormals = targets.All(item => item.HasNormals);
                 hasTangents = targets.All(item => item.HasTangents);

+ 2 - 0
src/SharpGLTF.Runtime/Runtime/VertexNormalsFactory.cs

@@ -50,6 +50,8 @@ namespace SharpGLTF.Runtime
         {
             Guard.NotNull(primitives, nameof(primitives));
 
+            primitives = primitives.EnsureList();
+
             var normalMap = new Dictionary<Vector3, Vector3>();
 
             // calculate

+ 2 - 0
src/SharpGLTF.Runtime/Runtime/VertexTangentsFactory.cs

@@ -70,6 +70,8 @@ namespace SharpGLTF.Runtime
         {
             Guard.NotNull(primitives, nameof(primitives));
 
+            primitives = primitives.EnsureList();
+
             var tangentsMap = new Dictionary<VERTEXKEY, (Vector3 u, Vector3 v)>();
 
             // calculate

+ 2 - 2
src/SharpGLTF.Toolkit/BaseBuilder.cs

@@ -98,8 +98,8 @@ namespace SharpGLTF
         /// <param name="target">The target object</param>
         internal void TryCopyNameAndExtrasTo(Schema2.LogicalChildOfRoot target)
         {
-            target.Name = this?.Name;
-            target.Extras = this?.Extras?.DeepClone();
+            target.Name = this.Name;
+            target.Extras = this.Extras?.DeepClone();
         }
 
         #endregion

+ 26 - 0
src/SharpGLTF.Toolkit/Collections/Triple.cs

@@ -7,6 +7,8 @@ namespace SharpGLTF.Collections
 {
     public readonly struct Triple<T> : IReadOnlyList<T>, IEquatable<Triple<T>>
     {
+        #region constructors
+
         public static implicit operator Triple<T>(in (T A, T B, T C) triple)
         {
             return new Triple<T>(triple.A, triple.B, triple.C);
@@ -19,6 +21,10 @@ namespace SharpGLTF.Collections
             C = @c;
         }
 
+        #endregion
+
+        #region data
+
         public readonly T A;
         public readonly T B;
         public readonly T C;
@@ -45,6 +51,20 @@ namespace SharpGLTF.Collections
             return true;
         }
 
+        public static bool operator ==(in Triple<T> left, in Triple<T> right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(in Triple<T> left, in Triple<T> right)
+        {
+            return !left.Equals(right);
+        }
+
+        #endregion
+
+        #region properties
+
         public int Count => 3;
 
         public T this[int index]
@@ -61,6 +81,10 @@ namespace SharpGLTF.Collections
             }
         }
 
+        #endregion
+
+        #region API
+
         public IEnumerator<T> GetEnumerator()
         {
             yield return A;
@@ -74,5 +98,7 @@ namespace SharpGLTF.Collections
             yield return B;
             yield return C;
         }
+
+        #endregion
     }
 }

+ 2 - 2
src/SharpGLTF.Toolkit/Collections/ValueListSet.cs

@@ -67,7 +67,7 @@ namespace SharpGLTF.Collections
             get
             {
                 if (index < 0 || index >= _Count) throw new ArgumentOutOfRangeException(nameof(index));
-                if (_Entries[index].HashCode == -1) throw new ArgumentException(nameof(index));
+                if (_Entries[index].HashCode == -1) throw new ArgumentException("Invalid entry", nameof(index));
                 return _Entries[index].Value;
             }
         }
@@ -82,7 +82,7 @@ namespace SharpGLTF.Collections
         {
             if (_Count <= 0) return;
 
-            _Entries.AsSpan().Fill(default);
+            _Entries.AsSpan().Clear();
             _Buckets.AsSpan().Fill(-1);
 
             _Count = 0;

+ 10 - 6
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -90,31 +90,35 @@ namespace SharpGLTF.Geometry
 
         public static Schema2.EncodingType GetOptimalIndexEncoding<TMaterial>(this IEnumerable<IMeshBuilder<TMaterial>> meshes)
         {
+            Guard.NotNull(meshes, nameof(meshes));
+
             var indices = meshes
                 .SelectMany(item => item.Primitives)
                 .Where(item => item.VerticesPerPrimitive >= 2) // points will never use index buffers
                 .SelectMany(prim => prim.GetIndices());
 
-            var maxIndex = indices.Any() ? indices.Max() : 0;
+            var maxIndex = indices.Aggregate(0, (a,b) => Math.Max(a,b));
 
             return maxIndex < 65535 ? Schema2.EncodingType.UNSIGNED_SHORT : Schema2.EncodingType.UNSIGNED_INT;
         }
 
         public static Schema2.EncodingType GetOptimalJointEncoding<TMaterial>(this IEnumerable<IMeshBuilder<TMaterial>> meshes)
         {
+            Guard.NotNull(meshes, nameof(meshes));
+
             var indices = meshes
                 .SelectMany(item => item.Primitives)
                 .SelectMany(item => item.Vertices)
                 .Select(item => item.GetSkinning().GetBindings().MaxIndex);
 
-            var maxIndex = indices.Any() ? indices.Max() : 0;
+            var maxIndex = indices.Aggregate(0, (a, b) => Math.Max(a, b));
 
             return maxIndex < 256 ? Schema2.EncodingType.UNSIGNED_BYTE : Schema2.EncodingType.UNSIGNED_SHORT;
         }
         
         public static IMeshBuilder<TMaterial> CreateMeshBuilderFromVertexAttributes
             <
-            #if !NETSTANDARD
+            #if NET6_0_OR_GREATER
             [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
             #endif
             TMaterial>(params string[] vertexAttributes)
@@ -126,12 +130,12 @@ namespace SharpGLTF.Geometry
             return mesh as IMeshBuilder<TMaterial>;
         }
         
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetMeshBuilderType
             (
-            #if !NETSTANDARD
+            #if NET6_0_OR_GREATER
             [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
             #endif
             Type materialType, string[] vertexAttributes)
@@ -149,7 +153,7 @@ namespace SharpGLTF.Geometry
         {
             var posnrm = new Dictionary<Vector3, Vector3>();
 
-            void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
+            static void addDirection(Dictionary<Vector3, Vector3> dict, Vector3 pos, Vector3 dir)
             {
                 if (!dir._IsFinite()) return;
                 if (!dict.TryGetValue(pos, out Vector3 n)) n = Vector3.Zero;

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/Packed/PackedEncoding.cs

@@ -19,7 +19,7 @@ namespace SharpGLTF.Geometry
             if (JointsEncoding.HasValue) return;
 
             var indices = vertices.Select(item => item.GetSkinning().GetBindings().MaxIndex);
-            var maxIndex = indices.Any() ? indices.Max() : 0;
+            var maxIndex = indices.Aggregate(0, (a, b) => Math.Max(a, b));
             JointsEncoding = maxIndex < 256 ? ENCODING.UNSIGNED_BYTE : ENCODING.UNSIGNED_SHORT;
         }
     }

+ 4 - 0
src/SharpGLTF.Toolkit/Geometry/Packed/PackedMeshBuilder.cs

@@ -27,6 +27,10 @@ namespace SharpGLTF.Geometry
         /// <returns>A collectio of <see cref="PackedMeshBuilder{TMaterial}"/> meshes.</returns>
         internal static IEnumerable<PackedMeshBuilder<TMaterial>> CreatePackedMeshes(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders, Scenes.SceneBuilderSchema2Settings settings)
         {
+            Guard.NotNull(meshBuilders, nameof(meshBuilders));
+
+            meshBuilders = meshBuilders.EnsureList();
+
             try
             {
                 foreach (var m in meshBuilders) m.Validate();

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/Packed/PackedPrimitiveBuilder.cs

@@ -129,7 +129,7 @@ namespace SharpGLTF.Geometry
                 var name = accessor.Attribute.Name;
                 if (!name.EndsWith("DELTA", StringComparison.Ordinal)) throw new InvalidOperationException();
 
-                name = name.Replace("DELTA", string.Empty);
+                name = name.Replace("DELTA", string.Empty, StringComparison.Ordinal);
 
                 var attr = accessor.Attribute;
                 attr.Name = name;

+ 3 - 0
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -106,6 +106,9 @@ namespace SharpGLTF.Geometry
         /// <summary>
         /// Gets the type of the vertex used by this primitive.
         /// </summary>
+        #if NET6_0_OR_GREATER
+        [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+        #endif
         public Type VertexType => typeof(VertexBuilder<TvG, TvM, TvS>);
 
         /// <summary>

+ 6 - 0
src/SharpGLTF.Toolkit/Geometry/PrimitiveInterfaces.cs

@@ -10,6 +10,9 @@ namespace SharpGLTF.Geometry
         /// <summary>
         /// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
         /// </summary>
+        #if NET6_0_OR_GREATER
+        [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+        #endif
         Type VertexType { get; }
 
         /// <summary>
@@ -72,6 +75,9 @@ namespace SharpGLTF.Geometry
         /// <summary>
         /// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
         /// </summary>
+        #if NET6_0_OR_GREATER
+        [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+        #endif
         Type VertexType { get; }
 
         void SetVertexDelta(int morphTargetIndex, int vertexIndex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta);

+ 14 - 7
src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs

@@ -88,7 +88,7 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        private static IList<T> _IsolateColumn<T>(IList<T> column)
+        private static T[] _IsolateColumn<T>(IList<T> column)
         {
             if (column == null) return null;
 
@@ -310,7 +310,7 @@ namespace SharpGLTF.Geometry
 
         public VertexBufferColumns AddMorphTarget()
         {
-            if (_MorphTargets == null) _MorphTargets = new List<VertexBufferColumns>();
+            _MorphTargets ??= new List<VertexBufferColumns>();
             var mt = new VertexBufferColumns();
             _MorphTargets.Add(mt);
 
@@ -321,6 +321,9 @@ namespace SharpGLTF.Geometry
 
         #region API - Vertex indexing
 
+        #if NET6_0_OR_GREATER
+        [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+        #endif
         public Type GetCompatibleVertexType()
         {
             var hasNormals = Normals != null;
@@ -395,7 +398,13 @@ namespace SharpGLTF.Geometry
             return s;
         }
 
-        public IVertexBuilder GetVertex(Type vertexType, int index)
+        public IVertexBuilder GetVertex
+            (
+            #if NET6_0_OR_GREATER
+            [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+            #endif
+            Type vertexType,
+            int index)
         {
             var g = GetVertexGeometry<VertexPositionNormalTangent>(index);
             var m = GetVertexMaterial<VertexColor2Texture2>(index);
@@ -435,8 +444,7 @@ namespace SharpGLTF.Geometry
             Guard.NotNull(primitives, nameof(primitives));
 
             var agents = primitives
-                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
-                .ToList();
+                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices));
 
             Runtime.VertexNormalsFactory.CalculateSmoothNormals(agents);
         }
@@ -446,8 +454,7 @@ namespace SharpGLTF.Geometry
             Guard.NotNull(primitives, nameof(primitives));
 
             var agents = primitives
-                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices))
-                .ToList();
+                .Select(item => new _NormalTangentAgent(item.Vertices, item.Indices));
 
             Runtime.VertexTangentsFactory.CalculateTangents(agents);
         }

+ 27 - 21
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -80,7 +80,7 @@ namespace SharpGLTF.Geometry
     {
         #region debug
 
-        internal string _GetDebuggerDisplay()
+        internal readonly string _GetDebuggerDisplay()
         {
             var txt = "Vertex";
 
@@ -281,7 +281,7 @@ namespace SharpGLTF.Geometry
         public TvS Skinning;
 
         /// <inheritdoc/>
-        public override int GetHashCode() { return Geometry.GetHashCode(); }
+        public readonly override int GetHashCode() { return Geometry.GetHashCode(); }
 
         /// <inheritdoc/>
         public override bool Equals(object obj) { return obj is VertexBuilder<TvG, TvM, TvS> other && AreEqual(this, other); }
@@ -304,7 +304,7 @@ namespace SharpGLTF.Geometry
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public Vector3 Position
         {
-            get => Geometry.GetPosition();
+            readonly get => Geometry.GetPosition();
             set => Geometry.SetPosition(value);
         }
 
@@ -312,7 +312,7 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        public void Validate()
+        public readonly void Validate()
         {
             VertexPreprocessorLambdas.ValidateVertexGeometry(Geometry);
             VertexPreprocessorLambdas.ValidateVertexMaterial(Material);
@@ -333,11 +333,11 @@ namespace SharpGLTF.Geometry
 
         #pragma warning restore CA1000 // Do not declare static members on generic types
 
-        IVertexGeometry IVertexBuilder.GetGeometry() { return this.Geometry; }
+        readonly IVertexGeometry IVertexBuilder.GetGeometry() { return this.Geometry; }
 
-        IVertexMaterial IVertexBuilder.GetMaterial() { return this.Material; }
+        readonly IVertexMaterial IVertexBuilder.GetMaterial() { return this.Material; }
 
-        IVertexSkinning IVertexBuilder.GetSkinning() { return this.Skinning; }
+        readonly IVertexSkinning IVertexBuilder.GetSkinning() { return this.Skinning; }
 
         void IVertexBuilder.SetGeometry(IVertexGeometry geometry)
         {
@@ -361,21 +361,21 @@ namespace SharpGLTF.Geometry
 
         #region With* fluent API
 
-        public VertexBuilder<TvG, TvM, TvS> TransformedBy(in Matrix4x4 transform)
+        public readonly VertexBuilder<TvG, TvM, TvS> TransformedBy(in Matrix4x4 transform)
         {
             var clone = this;
             clone.Geometry.ApplyTransform(transform);
             return clone;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position)
         {
             var v = this;
             v.Geometry.SetPosition(position);
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position, in Vector3 normal)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position, in Vector3 normal)
         {
             var v = this;
             v.Geometry.SetPosition(position);
@@ -383,7 +383,7 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position, in Vector3 normal, in Vector4 tangent)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithGeometry(in Vector3 position, in Vector3 normal, in Vector4 tangent)
         {
             var v = this;
             v.Geometry.SetPosition(position);
@@ -392,7 +392,7 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithMaterial(params Vector2[] uvs)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithMaterial(params Vector2[] uvs)
         {
             Guard.NotNull(uvs, nameof(uvs));
 
@@ -401,7 +401,7 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, params Vector2[] uvs)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, params Vector2[] uvs)
         {
             Guard.NotNull(uvs, nameof(uvs));
 
@@ -411,7 +411,7 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, Vector4 color1, params Vector2[] uvs)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithMaterial(in Vector4 color0, Vector4 color1, params Vector2[] uvs)
         {
             Guard.NotNull(uvs, nameof(uvs));
 
@@ -422,14 +422,14 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithSkinning(in Transforms.SparseWeight8 sparse)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithSkinning(in Transforms.SparseWeight8 sparse)
         {
             var v = this;
             v.Skinning.SetBindings(sparse);
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithSkinning(params (int Index, float Weight)[] bindings)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithSkinning(params (int Index, float Weight)[] bindings)
         {
             var v = this;
 
@@ -440,7 +440,7 @@ namespace SharpGLTF.Geometry
             return v;
         }
 
-        public VertexBuilder<TvG, TvM, TvS> WithSkinning(IEnumerable<(int Index, float Weight)> bindings)
+        public readonly VertexBuilder<TvG, TvM, TvS> WithSkinning(IEnumerable<(int Index, float Weight)> bindings)
         {
             var v = this;
 
@@ -486,11 +486,11 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        public IVertexGeometry GetGeometry() { return Geometry; }
+        public readonly IVertexGeometry GetGeometry() { return Geometry; }
 
-        public IVertexMaterial GetMaterial() { return Material; }
+        public readonly IVertexMaterial GetMaterial() { return Material; }
 
-        public IVertexSkinning GetSkinning() { return Skinning; }
+        public readonly IVertexSkinning GetSkinning() { return Skinning; }
 
         public void SetGeometry(IVertexGeometry geometry) { this.Geometry = geometry; }
 
@@ -498,7 +498,13 @@ namespace SharpGLTF.Geometry
 
         public void SetSkinning(IVertexSkinning skinning) { this.Skinning = skinning; }
 
-        public IVertexBuilder ConvertToType(Type vertexType)
+        public readonly IVertexBuilder ConvertToType
+            (
+            #if NET6_0_OR_GREATER
+            [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+            #endif
+            Type vertexType
+            )
         {
             var v = (IVertexBuilder)Activator.CreateInstance(vertexType);
 

+ 6 - 6
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.Builder.cs

@@ -7,7 +7,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     static partial class VertexUtils
     {
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexGeometryType(params string[] vertexAttributes)
@@ -18,7 +18,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return t;
         }
 
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexMaterialType(params string[] vertexAttributes)
@@ -36,7 +36,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return GetVertexMaterialType(colors, uvcoords);
         }
 
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexMaterialType(int colors, int uvcoords)
@@ -65,7 +65,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return typeof(VertexEmpty);
         }
 
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexSkinningType(params string[] vertexAttributes)
@@ -79,7 +79,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return typeof(VertexEmpty);
         }
 
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexBuilderType(params string[] vertexAttributes)
@@ -93,7 +93,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return vtype.MakeGenericType(tvg, tvm, tvs);
         }
 
-        #if !NETSTANDARD
+        #if NET6_0_OR_GREATER
         [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
         #endif
         public static Type GetVertexBuilderType(bool hasNormals, bool hasTangents, int numCols, int numUV, int numJoints)

+ 4 - 2
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -95,7 +95,7 @@ namespace SharpGLTF.IO
             return files;
         }
 
-        private IReadOnlyDictionary<String, Action<Stream>> _GetFileGenerators(string baseName) 
+        private Dictionary<String, Action<Stream>> _GetFileGenerators(string baseName) 
         {
             Guard.IsFalse(baseName.Any(c => char.IsWhiteSpace(c)), nameof(baseName), "Whitespace characters not allowed in filename");
 
@@ -108,8 +108,10 @@ namespace SharpGLTF.IO
             return fileGenerators;
         }
 
-        private static IReadOnlyDictionary<Material, string> _GetMaterialFileGenerator(IDictionary<String, Action<Stream>> fileGenerators, string baseName, IEnumerable<Material> materials)
+        private static Dictionary<Material, string> _GetMaterialFileGenerator(IDictionary<String, Action<Stream>> fileGenerators, string baseName, IEnumerable<Material> materials)
         {
+            if (!(materials is List<Material>)) materials = materials.ToList();
+
             // write all image files
             var images = materials
                 .Select(item => item.DiffuseTexture)

+ 2 - 0
src/SharpGLTF.Toolkit/Materials/MaterialEnums.cs

@@ -160,11 +160,13 @@ namespace SharpGLTF.Materials
                 case KnownChannel.Normal: yield return new _Property(KnownProperty.NormalScale, 1f); break;
                 case KnownChannel.Occlusion: yield return new _Property(KnownProperty.OcclusionStrength, 1f); break;
 
+                #pragma warning disable CS0618 // Type or member is obsolete - We want to warn users that this is obsolete, but not to ourselves!
                 case KnownChannel.Diffuse: yield return new _Property(KnownProperty.RGBA, Vector4.One); break;
                 case KnownChannel.SpecularGlossiness:
                     yield return new _Property(KnownProperty.SpecularFactor, Vector3.One);
                     yield return new _Property(KnownProperty.GlossinessFactor, 1f);
                     break;
+                #pragma warning restore CS0618 // Type or member is obsolete
 
                 case KnownChannel.BaseColor: yield return new _Property(KnownProperty.RGBA, Vector4.One); break;
                 case KnownChannel.MetallicRoughness:

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

@@ -263,6 +263,9 @@ namespace SharpGLTF.Scenes
         public static bool IsValidArmature(IEnumerable<NodeBuilder> joints)
         {
             if (joints == null) return false;
+
+            joints = joints.EnsureList();
+
             if (!joints.Any()) return false;
             if (joints.Any(item => item == null)) return false;
 

+ 3 - 2
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -220,12 +220,13 @@ namespace SharpGLTF.Scenes
             return dstScene;
         }
 
-        private static IReadOnlyDictionary<Node, MESHBUILDER> _GatherMeshInstances(IEnumerable<Node> srcNodes)
+        private static Dictionary<Node, MESHBUILDER> _GatherMeshInstances(IEnumerable<Node> srcNodes)
         {
             // filter all the nodes with meshes
 
             var srcInstances = srcNodes
-                .Where(item => item.Mesh != null);
+                .Where(item => item.Mesh != null)
+                .ToList();
 
             // create a dictionary of shared Mesh => MeshBuilder pairs.
 

+ 9 - 4
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -1,8 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Security.Cryptography;
-using System.Xml.Linq;
 
 using SharpGLTF.Schema2;
 
@@ -36,7 +34,9 @@ namespace SharpGLTF.Scenes
         {
             // gather all FixedTransformers with renderables
 
-            var renderables = instances.Where(item => item.HasRenderableContent);
+            var renderables = instances
+                .Where(item => item.HasRenderableContent)
+                .ToList();
 
             // gather all renderables attached to the scene root.
 
@@ -68,9 +68,14 @@ namespace SharpGLTF.Scenes
 
         private _MeshInstancing(NodeBuilder parentNode, IEnumerable<FixedTransformer> children, int gpuMinCount)
         {
-            System.Diagnostics.Debug.Assert(children.All(item => item.ParentNode == parentNode), "all items must have the same parentNode");
+            Guard.NotNull(children,nameof(children));
 
             #if DEBUG
+
+            children = children.EnsureList();
+
+            System.Diagnostics.Debug.Assert(children.All(item => item.ParentNode == parentNode), "all items must have the same parentNode");
+
             var hasMoreThanOne = children
                 .Select(item => item.Content)
                 .Cast<IRenderableContent>()

+ 3 - 3
src/SharpGLTF.Toolkit/Schema2/EvaluatedTriangle.cs

@@ -45,7 +45,7 @@ namespace SharpGLTF.Schema2
                 );
         }
 
-        private static IReadOnlyList<(Material Material, VertexBufferColumns Vertices, IEnumerable<(int, int, int)> Triangles)> _GatherMeshGeometry(Mesh mesh)
+        private static List<(Material Material, VertexBufferColumns Vertices, IEnumerable<(int, int, int)> Triangles)> _GatherMeshGeometry(Mesh mesh)
         {
             var primitives = mesh.Primitives
                             .Where(prim => prim.GetTriangleIndices().Any())
@@ -62,7 +62,7 @@ namespace SharpGLTF.Schema2
                     .Select(p => (p.Item2, p.Item3))
                     .ToList();
 
-                if (prims.Any()) VertexBufferColumns.CalculateSmoothNormals(prims);
+                if (prims.Count > 0) VertexBufferColumns.CalculateSmoothNormals(prims);
             }
 
             if (needsTangents)
@@ -72,7 +72,7 @@ namespace SharpGLTF.Schema2
                     .Select(p => (p.Item2, p.Item3))
                     .ToList();
 
-                if (prims.Any()) VertexBufferColumns.CalculateTangents(prims);
+                if (prims.Count > 0) VertexBufferColumns.CalculateTangents(prims);
             }
 
             return primitives;

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

@@ -263,6 +263,8 @@ namespace SharpGLTF.Schema2
 
             if (srcMaterial.FindChannel("Diffuse") != null || srcMaterial.FindChannel("SpecularGlossiness") != null)
             {
+                #pragma warning disable CS0618 // Type or member is obsolete
+
                 dstMaterial.WithSpecularGlossinessShader();
                 srcMaterial.CopyChannelsTo(dstMaterial, "Diffuse", "SpecularGlossiness");
                 // srcMaterial.CopyChannelsTo(dstMaterial, "ClearCoat", "ClearCoatRoughness", "ClearCoatNormal");
@@ -278,6 +280,7 @@ namespace SharpGLTF.Schema2
                 }
 
                 return;
+                #pragma warning restore CS0618 // Type or member is obsolete
             }
 
             if (srcMaterial.FindChannel("BaseColor") != null || srcMaterial.FindChannel("MetallicRoughness") != null)

+ 17 - 7
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -272,6 +272,9 @@ namespace SharpGLTF.Schema2
         public static MeshPrimitive WithVertexAccessors(this MeshPrimitive primitive, IEnumerable<MemoryAccessor> memAccessors)
         {
             Guard.NotNull(memAccessors, nameof(memAccessors));
+
+            memAccessors = memAccessors.EnsureList();
+
             Guard.IsTrue(memAccessors.All(item => item != null), nameof(memAccessors));
 
             foreach (var va in memAccessors) primitive.WithVertexAccessor(va);
@@ -362,7 +365,10 @@ namespace SharpGLTF.Schema2
             Guard.NotNull(instancing, nameof(instancing));
             Guard.NotNull(transforms, nameof(transforms));
 
-            var xfrms = transforms.Select(item => item.GetDecomposed());
+            var xfrms = transforms
+                .Select(item => item.GetDecomposed())
+                .ToList();
+
             var hasS = xfrms.Any(item => item.Scale != Vector3.One);
             var hasR = xfrms.Any(item => item.Rotation != Quaternion.Identity);
             var hasT = xfrms.Any(item => item.Translation != Vector3.Zero);
@@ -384,7 +390,7 @@ namespace SharpGLTF.Schema2
                 .SelectMany(item => item)
                 .Select(item => item.Key)
                 .Distinct()
-                .Where(item => item.StartsWith("_", StringComparison.Ordinal));
+                .Where(item => item.StartsWith('_'));
 
             // for each attribute key found, fill the IDs
             foreach (var key in keys)
@@ -419,7 +425,7 @@ namespace SharpGLTF.Schema2
         /// <summary>
         /// Takes a list of <see cref="JSONEXTRAS"/> and selects a specific property of a specific data type.
         /// </summary>        
-        private static IReadOnlyList<T> _SelectAttribute<T>(IReadOnlyList<JSONEXTRAS> values, string propertyName)
+        private static List<T> _SelectAttribute<T>(IReadOnlyList<JSONEXTRAS> values, string propertyName)
         {
             var result = new List<T>();
 
@@ -475,14 +481,18 @@ namespace SharpGLTF.Schema2
             if (xform != null && !xform.Visible) yield break;
 
             var points = prim.GetPointIndices();
-            if (!points.Any()) yield break;
 
-            var vertices = prim.GetVertexColumns();
-            var vtype = vertices.GetCompatibleVertexType();
+            VertexBufferColumns vertices = null;
+            Type vtype = null;
 
             foreach (var xinst in Transforms.InstancingTransform.Evaluate(xform))
             {
-                var xvertices = xinst != null ? vertices.WithTransform(xinst) : vertices;
+                vertices ??= prim.GetVertexColumns();
+                vtype ??= vertices.GetCompatibleVertexType();
+
+                var xvertices = xinst != null
+                    ? vertices.WithTransform(xinst)
+                    : vertices;
 
                 foreach (var t in points)
                 {

+ 1 - 1
tests/Directory.Build.props

@@ -19,7 +19,7 @@
   <!-- Testing & Analysers =================================================================================== -->  
 
   <PropertyGroup>
-    <NoWarn>1701;1702;1591;CA1062;CA1304;CA1310;CA1000;CA5394;CA1510;CA1512</NoWarn>
+    <NoWarn>1701;1702;1591;CA1062;CA1303;CA1304;CA1307;CA1310;CA1000;CA5394;CA1510;CA1512;CA1852;CA1861</NoWarn>
   </PropertyGroup>  
 
   <PropertyGroup>

+ 5 - 5
tests/SharpGLTF.Cesium.Tests/ExtInstanceFeaturesTests.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF.Cesium
             settings.GpuMeshInstancingMinCount = 0;
 
             var modelRoot = ModelRoot.Load(ResourceInfo.From("tree.glb"));
-            var meshBuilder = modelRoot.LogicalMeshes.First().ToMeshBuilder();
+            var meshBuilder = modelRoot.LogicalMeshes[0].ToMeshBuilder();
             var sceneBuilder = new SceneBuilder();
             var quaternion = Quaternion.CreateFromYawPitchRoll(0, 0, 0);
             var scale = Vector3.One;
@@ -47,12 +47,12 @@ namespace SharpGLTF.Cesium
             var model = sceneBuilder.ToGltf2(settings);
             model.LogicalNodes[0].SetFeatureIds(featureIds);
 
-            var cesiumExtInstanceFeaturesExtension = (MeshExtInstanceFeatures)model.LogicalNodes[0].Extensions.Where(item => item is MeshExtInstanceFeatures).FirstOrDefault();
+            var cesiumExtInstanceFeaturesExtension = model.LogicalNodes[0].GetExtension<MeshExtInstanceFeatures>();
 
             Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds, Is.Not.Null);
-            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds.Equals(featureIds));
-            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[0].Equals(featureId0));
-            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[1].Equals(featureId1));
+            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds, Is.EqualTo(featureIds));
+            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[0], Is.EqualTo(featureId0));
+            Assert.That(cesiumExtInstanceFeaturesExtension.FeatureIds[1], Is.EqualTo(featureId1));
 
             var ctx = new ValidationResult(model, ValidationMode.Strict, true);
             model.ValidateContent(ctx.GetContext());

+ 1 - 1
tests/SharpGLTF.Cesium.Tests/ExtMeshFeaturesTests.cs

@@ -55,7 +55,7 @@ namespace SharpGLTF.Cesium
             var cesiumExtMeshFeaturesExtension = (MeshExtMeshFeatures)model.LogicalMeshes[0].Primitives[0].Extensions.FirstOrDefault();
             Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.Not.Null);
 
-            Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds.Equals(featureIds));
+            Assert.That(cesiumExtMeshFeaturesExtension.FeatureIds, Is.EqualTo(featureIds));
 
             // Check there should be a custom vertex attribute with name _FEATURE_ID_{attribute}
             var attribute = cesiumExtMeshFeaturesExtension.FeatureIds[0].Attribute;

+ 1 - 1
tests/SharpGLTF.Cesium.Tests/SharpGLTF.Cesium.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>net471;net8.0-windows</TargetFrameworks>
+    <TargetFrameworks>net471;net6.0;net8.0</TargetFrameworks>
     <IsPackable>false</IsPackable>
     <RootNamespace>SharpGLTF</RootNamespace>
     <LangVersion>latest</LangVersion>

+ 1 - 1
tests/SharpGLTF.Core.Tests/AssemblyAPITests.cs

@@ -37,7 +37,7 @@ namespace SharpGLTF
 
             public event EventHandler EventX;
 
-            public struct Structure
+            public struct SomeStructure
             {
                 public int X;
                 public int Y { get; }

+ 1 - 1
tests/SharpGLTF.Core.Tests/Schema2/LoadAndSave/LoadGeneratedTests.cs

@@ -77,7 +77,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
                 }
 
                 /*
-                if (ShouldLoad && !filePath.ToLowerInvariant().Contains("compatibility"))
+                if (ShouldLoad && !filePath.ToUpperInvariant().Contains("COMPATIBILITY"))
                 {
                     var model = ModelRoot.Load(filePath);
                     model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(filePath), ".obj"));

+ 5 - 0
tests/SharpGLTF.Core.Tests/Validation/InvalidFilesTests.cs

@@ -43,6 +43,11 @@ namespace SharpGLTF.Validation
             {
                 var json = System.IO.File.ReadAllText(f + ".report.json");
                 var report = GltfValidator.ValidationReport.Parse(json);
+                
+                if (report.Issues.Messages.Any(item => item.Code.Contains("GLB_CHUNK_TOO_BIG")) && report.Issues.NumErrors > 0)
+                {
+                    // System.Diagnostics.Debugger.Break();
+                }
 
                 TestContext.Progress.WriteLine($"{f}...");
                 TestContext.WriteLine($"{f}...");

+ 2 - 2
tests/SharpGLTF.DownloadTestFiles/DownloadUtils.cs

@@ -22,7 +22,7 @@ namespace SharpGLTF
 
                     LibGit2Sharp.Repository.Clone(remoteUrl, localDirectoryPath);
 
-                    Console.WriteLine($"... Clone Completed");
+                    Console.WriteLine("... Clone Completed");
 
                     return;
                 }
@@ -59,7 +59,7 @@ namespace SharpGLTF
                     wc.DownloadFile(remoteUri, localFilePath);
                 }
 
-                if (localFilePath.ToLowerInvariant().EndsWith(".zip"))
+                if (localFilePath.ToUpperInvariant().EndsWith(".ZIP"))
                 {
                     Console.WriteLine($"Extracting {localFilePath}...");
 

+ 4 - 4
tests/SharpGLTF.NUnit/NUnitGltfUtils.cs

@@ -45,7 +45,7 @@ namespace SharpGLTF
         {
             var gl2model = ModelRoot.CreateModel();
 
-            var gl2mesh = gl2model.CreateMeshes(mesh).First();
+            var gl2mesh = gl2model.CreateMeshes(mesh)[0];
 
             var node = gl2model.UseScene(0).CreateNode();
             node.Mesh = gl2mesh;
@@ -64,7 +64,7 @@ namespace SharpGLTF
                     .WriteObject(f => model.SaveGLB(f, settings))
                     .FullName;
             }
-            else if (fileName.ToLowerInvariant().EndsWith(".gltf"))
+            else if (fileName.ToUpperInvariant().EndsWith(".GLTF"))
             {
                 if (settings == null) settings = new WriteSettings { JsonIndented = true };
 
@@ -73,7 +73,7 @@ namespace SharpGLTF
                     .WriteObject(f => model.Save(f, settings))
                     .FullName;
             }
-            else if (fileName.ToLowerInvariant().EndsWith(".obj"))
+            else if (fileName.ToUpperInvariant().EndsWith(".OBJ"))
             {
                 // skip exporting to obj if gpu instancing is there
                 if (Node.Flatten(model.DefaultScene).Any(n => n.GetGpuInstancing() != null)) return fileName;                
@@ -85,7 +85,7 @@ namespace SharpGLTF
                     .WriteObject(f => model.SaveAsWavefront(f))
                     .FullName;
             }
-            else if (fileName.ToLowerInvariant().EndsWith(".plotly"))
+            else if (fileName.ToUpperInvariant().EndsWith(".PLOTLY"))
             {
                 fileName = fileName.Replace(".plotly", ".html");
 

+ 1 - 1
tests/SharpGLTF.NUnit/Plotting.cs

@@ -239,7 +239,7 @@ namespace SharpGLTF
     {
         public static void AttachToCurrentTest(this Plotting.Point2Series points, string fileName)
         {
-            System.Diagnostics.Debug.Assert(fileName.ToLowerInvariant().EndsWith(".html"));
+            System.Diagnostics.Debug.Assert(fileName.ToUpperInvariant().EndsWith(".HTML"));
 
             NUnit.Framework.AttachmentInfo
                 .From(fileName)

+ 1 - 1
tests/SharpGLTF.NUnit/Reports.cs

@@ -56,7 +56,7 @@ namespace SharpGLTF.Reporting
             this.VertexAttributes = new[] { "SCENE" };
         }
 
-        internal void SetFrom(IEnumerable<VisualReport> many)
+        internal void SetFrom(IReadOnlyList<VisualReport> many)
         {
             NumVertices = many.Sum(item => item.NumVertices);
             NumTriangles = many.Sum(item => item.NumTriangles);

+ 2 - 2
tests/SharpGLTF.NUnit/TestFiles.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF
             {
                 _TestFilesDir = System.IO.Path.Combine(wdir, "TestFiles");
 
-                if (wdir.ToLowerInvariant().EndsWith("tests") && System.IO.Directory.Exists(_TestFilesDir))
+                if (wdir.ToUpperInvariant().EndsWith("TESTS") && System.IO.Directory.Exists(_TestFilesDir))
                 {
                     examplesFound = true;
                     break;
@@ -221,7 +221,7 @@ namespace SharpGLTF
         public static IEnumerable<string> GetMeshIntancingModelPaths()
         {
             var fromBabylon = GetBabylonJSModelsPaths()
-                .Where(item => item.ToLowerInvariant().Contains("teapot"));
+                .Where(item => item.ToUpperInvariant().Contains("TEAPOT"));
 
             var meshInstPath = _UsingInternalFiles("gltf-GpuMeshInstancing");
 

+ 1 - 1
tests/SharpGLTF.ThirdParty.Tests/CesiumInstancingTests.cs

@@ -16,7 +16,7 @@ namespace SharpGLTF.ThirdParty
         public void WriteInstancedGlbWithFeatureIds()
         {
             var modelRoot = ModelRoot.Load(ResourceInfo.From("tree.glb"));
-            var meshBuilder = modelRoot.LogicalMeshes.First().ToMeshBuilder();
+            var meshBuilder = modelRoot.LogicalMeshes[0].ToMeshBuilder();
             var sceneBuilder = new SceneBuilder();
             var quaternion = Quaternion.CreateFromYawPitchRoll(0, 0, 0);
             var scale = Vector3.One;

+ 1 - 1
tests/SharpGLTF.ThirdParty.Tests/SandboxTests.cs

@@ -25,7 +25,7 @@ namespace SharpGLTF.ThirdParty
         [Test]
         public void MaterialCreate()
         {
-            Memory.MemoryImage.TryParseMime64("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==", out var memoryImage);
+            Assert.That(Memory.MemoryImage.TryParseMime64("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==", out var memoryImage));
 
             var material = new MaterialBuilder()
                 .WithAlpha(Materials.AlphaMode.OPAQUE);