Browse Source

Added lots of Argument checks.
Improving scenes API.

Vicente Penades 6 years ago
parent
commit
a89058eae9
51 changed files with 874 additions and 469 deletions
  1. 2 2
      Analyzers.targets
  2. 2 0
      SharpGLTF.ruleset
  3. 7 7
      src/Shared/Guard.cs
  4. 14 1
      src/Shared/_Extensions.cs
  5. 17 1
      src/SharpGLTF.Core/Animations/SamplerFactory.cs
  6. 0 1
      src/SharpGLTF.Core/Collections/ChildrenCollection.cs
  7. 61 3
      src/SharpGLTF.Core/IO/JsonSerializable.cs
  8. 6 2
      src/SharpGLTF.Core/Memory/FloatingArrays.cs
  9. 4 4
      src/SharpGLTF.Core/Memory/IntegerArrays.cs
  10. 14 0
      src/SharpGLTF.Core/Memory/MemoryAccessor.cs
  11. 1 1
      src/SharpGLTF.Core/Memory/SparseArrays.cs
  12. 1 1
      src/SharpGLTF.Core/Schema2/gltf.Accessors.cs
  13. 1 0
      src/SharpGLTF.Core/Schema2/gltf.Animations.cs
  14. 2 0
      src/SharpGLTF.Core/Schema2/gltf.Buffer.cs
  15. 1 1
      src/SharpGLTF.Core/Schema2/gltf.BufferView.cs
  16. 3 0
      src/SharpGLTF.Core/Schema2/gltf.Camera.cs
  17. 3 0
      src/SharpGLTF.Core/Schema2/gltf.ExtraProperties.cs
  18. 1 1
      src/SharpGLTF.Core/Schema2/gltf.MaterialsFactory.cs
  19. 3 3
      src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs
  20. 3 3
      src/SharpGLTF.Core/Schema2/gltf.Node.cs
  21. 1 1
      src/SharpGLTF.Core/Schema2/gltf.Root.cs
  22. 4 0
      src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs
  23. 1 0
      src/SharpGLTF.Core/Schema2/gltf.Serialization.Write.cs
  24. 1 1
      src/SharpGLTF.Core/Schema2/gltf.Skin.cs
  25. 6 3
      src/SharpGLTF.Core/Schema2/gltf.Textures.cs
  26. 1 1
      src/SharpGLTF.Core/Schema2/khr.lights.cs
  27. 1 2
      src/SharpGLTF.Core/Transforms/IndexWeight.cs
  28. 12 6
      src/SharpGLTF.Core/Transforms/MeshTransforms.cs
  29. 1 1
      src/SharpGLTF.Core/Transforms/SparseWeight8.cs
  30. 36 0
      src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs
  31. 13 3
      src/SharpGLTF.Toolkit/Animations/CurveFactory.cs
  32. 3 0
      src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs
  33. 2 2
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  34. 1 1
      src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs
  35. 5 0
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs
  36. 15 1
      src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs
  37. 3 1
      src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs
  38. 4 4
      src/SharpGLTF.Toolkit/Materials/MaterialBuilder.cs
  39. 31 4
      src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs
  40. 47 0
      src/SharpGLTF.Toolkit/Scenes/Content.Schema2.cs
  41. 11 238
      src/SharpGLTF.Toolkit/Scenes/Content.cs
  42. 17 17
      src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs
  43. 87 57
      src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs
  44. 17 4
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs
  45. 2 2
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs
  46. 87 0
      src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs
  47. 220 0
      src/SharpGLTF.Toolkit/Scenes/Transformers.cs
  48. 6 0
      src/SharpGLTF.Toolkit/Scenes/readme.md
  49. 4 0
      src/SharpGLTF.Toolkit/Schema2/LightExtensions.cs
  50. 88 88
      src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs
  51. 1 1
      src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

+ 2 - 2
Analyzers.targets

@@ -6,8 +6,8 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>    
   <ItemGroup>    
-    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" PrivateAssets="all" />
-    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.3" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.4" PrivateAssets="all" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
     <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
 	
 	

+ 2 - 0
SharpGLTF.ruleset

@@ -40,6 +40,7 @@
     <Rule Id="SA1649" Action="None" />
     <Rule Id="SA1649" Action="None" />
     <Rule Id="SA1652" Action="None" />
     <Rule Id="SA1652" Action="None" />
     <Rule Id="SA1605" Action="None" />
     <Rule Id="SA1605" Action="None" />
+    <Rule Id="SA1310" Action="Info" />
   </Rules>
   </Rules>
   <Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
   <Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
     <Rule Id="CA1001" Action="Error" />
     <Rule Id="CA1001" Action="Error" />
@@ -60,5 +61,6 @@
     <Rule Id="CA2216" Action="Error" />
     <Rule Id="CA2216" Action="Error" />
     <Rule Id="CA2242" Action="Error" />
     <Rule Id="CA2242" Action="Error" />
     <Rule Id="CA1303" Action="Info" />
     <Rule Id="CA1303" Action="Info" />
+    <Rule Id="CA1308" Action="None" />
   </Rules>
   </Rules>
 </RuleSet>
 </RuleSet>

+ 7 - 7
src/Shared/Guard.cs

@@ -34,8 +34,8 @@ namespace SharpGLTF
 
 
             bool isDir = false;
             bool isDir = false;
 
 
-            isDir |= filePath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString());
-            isDir |= filePath.EndsWith(System.IO.Path.AltDirectorySeparatorChar.ToString());
+            isDir |= filePath.EndsWith(new String(System.IO.Path.DirectorySeparatorChar, 1), StringComparison.Ordinal);
+            isDir |= filePath.EndsWith(new String(System.IO.Path.AltDirectorySeparatorChar, 1), StringComparison.Ordinal);
 
 
             if (!isDir) return;
             if (!isDir) return;
 
 
@@ -167,15 +167,15 @@ namespace SharpGLTF
 
 
         public static void MustShareLogicalParent(Schema2.LogicalChildOfRoot a, Schema2.LogicalChildOfRoot b, string parameterName)
         public static void MustShareLogicalParent(Schema2.LogicalChildOfRoot a, Schema2.LogicalChildOfRoot b, string parameterName)
         {
         {
-            MustShareLogicalParent(a?.LogicalParent, b, parameterName);
+            MustShareLogicalParent(a?.LogicalParent, nameof(a.LogicalParent), b, parameterName);
         }
         }
 
 
-        public static void MustShareLogicalParent(Schema2.ModelRoot a, Schema2.LogicalChildOfRoot b, string parameterName)
+        public static void MustShareLogicalParent(Schema2.ModelRoot a, string aName, Schema2.LogicalChildOfRoot b, string bName)
         {
         {
-            if (a is null) throw new ArgumentNullException("this");
-            if (b is null) throw new ArgumentNullException(parameterName);
+            if (a is null) throw new ArgumentNullException(aName);
+            if (b is null) throw new ArgumentNullException(bName);
 
 
-            if (a != b.LogicalParent) throw new ArgumentException("LogicalParent mismatch", parameterName);
+            if (a != b.LogicalParent) throw new ArgumentException("LogicalParent mismatch", bName);
         }
         }
 
 
         #endregion
         #endregion

+ 14 - 1
src/Shared/_Extensions.cs

@@ -372,7 +372,20 @@ namespace SharpGLTF
 
 
             return false;
             return false;
         }
         }
-        
+
+        internal static bool _IsImage(this IReadOnlyList<Byte> image, string format)
+        {
+            if (string.IsNullOrWhiteSpace(format)) return image._IsImage();
+
+            if (format.EndsWith("png", StringComparison.OrdinalIgnoreCase)) return image._IsPngImage();
+            if (format.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)) return image._IsJpgImage();
+            if (format.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase)) return image._IsJpgImage();
+            if (format.EndsWith("dds", StringComparison.OrdinalIgnoreCase)) return image._IsDdsImage();
+            if (format.EndsWith("webp", StringComparison.OrdinalIgnoreCase)) return image._IsWebpImage();
+
+            return false;
+        }
+
         #endregion
         #endregion
 
 
         #region vertex & index accessors
         #region vertex & index accessors

+ 17 - 1
src/SharpGLTF.Core/Animations/SamplerFactory.cs

@@ -32,6 +32,10 @@ namespace SharpGLTF.Animations
 
 
         public static Single[] CreateTangent(Single[] fromValue, Single[] toValue, Single scale = 1)
         public static Single[] CreateTangent(Single[] fromValue, Single[] toValue, Single scale = 1)
         {
         {
+            Guard.NotNull(fromValue, nameof(fromValue));
+            Guard.NotNull(toValue, nameof(toValue));
+            Guard.IsTrue(fromValue.Length == toValue.Length, nameof(toValue));
+
             var r = new float[fromValue.Length];
             var r = new float[fromValue.Length];
 
 
             for (int i = 0; i < r.Length; ++i)
             for (int i = 0; i < r.Length; ++i)
@@ -123,6 +127,8 @@ namespace SharpGLTF.Animations
         /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP amount.</returns>
         /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP amount.</returns>
         public static (T, T, float) FindPairContainingOffset<T>(this IEnumerable<(float, T)> sequence, float offset)
         public static (T, T, float) FindPairContainingOffset<T>(this IEnumerable<(float, T)> sequence, float offset)
         {
         {
+            Guard.NotNull(sequence, nameof(sequence));
+
             if (!sequence.Any()) return (default(T), default(T), 0);
             if (!sequence.Any()) return (default(T), default(T), 0);
 
 
             (float, T)? left = null;
             (float, T)? left = null;
@@ -175,6 +181,8 @@ namespace SharpGLTF.Animations
         /// <returns>Two consecutive offsets and a LERP amount.</returns>
         /// <returns>Two consecutive offsets and a LERP amount.</returns>
         public static (float, float, float) FindPairContainingOffset(IEnumerable<float> sequence, float offset)
         public static (float, float, float) FindPairContainingOffset(IEnumerable<float> sequence, float offset)
         {
         {
+            Guard.NotNull(sequence, nameof(sequence));
+
             if (!sequence.Any()) return (0, 0, 0);
             if (!sequence.Any()) return (0, 0, 0);
 
 
             float? left = null;
             float? left = null;
@@ -224,6 +232,9 @@ namespace SharpGLTF.Animations
 
 
         public static Single[] InterpolateLinear(Single[] start, Single[] end, Single amount)
         public static Single[] InterpolateLinear(Single[] start, Single[] end, Single amount)
         {
         {
+            Guard.NotNull(start, nameof(start));
+            Guard.NotNull(end, nameof(end));
+
             var startW = 1 - amount;
             var startW = 1 - amount;
             var endW = amount;
             var endW = amount;
 
 
@@ -253,6 +264,11 @@ namespace SharpGLTF.Animations
 
 
         public static Single[] InterpolateCubic(Single[] start, Single[] outgoingTangent, Single[] end, Single[] incomingTangent, Single amount)
         public static Single[] InterpolateCubic(Single[] start, Single[] outgoingTangent, Single[] end, Single[] incomingTangent, Single amount)
         {
         {
+            Guard.NotNull(start, nameof(start));
+            Guard.NotNull(outgoingTangent, nameof(outgoingTangent));
+            Guard.NotNull(end, nameof(end));
+            Guard.NotNull(incomingTangent, nameof(incomingTangent));
+
             var hermite = CreateHermitePointWeights(amount);
             var hermite = CreateHermitePointWeights(amount);
 
 
             var result = new float[start.Length];
             var result = new float[start.Length];
@@ -324,7 +340,7 @@ namespace SharpGLTF.Animations
 
 
             return new ArrayCubicSampler(collection);
             return new ArrayCubicSampler(collection);
         }
         }
-        
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 0 - 1
src/SharpGLTF.Core/Collections/ChildrenCollection.cs

@@ -8,7 +8,6 @@ namespace SharpGLTF.Collections
 {
 {
     [System.Diagnostics.DebuggerDisplay("{Count}")]
     [System.Diagnostics.DebuggerDisplay("{Count}")]
     [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CollectionDebugProxy<>))]
     [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._CollectionDebugProxy<>))]
-    [Serializable]
     sealed class ChildrenCollection<T, TParent> : IList<T>, IReadOnlyList<T>
     sealed class ChildrenCollection<T, TParent> : IList<T>, IReadOnlyList<T>
         where T : class, IChildOf<TParent>
         where T : class, IChildOf<TParent>
         where TParent : class
         where TParent : class

+ 61 - 3
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -31,6 +31,8 @@ namespace SharpGLTF.IO
 
 
         internal void Serialize(JsonWriter writer)
         internal void Serialize(JsonWriter writer)
         {
         {
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WriteStartObject();
             writer.WriteStartObject();
             SerializeProperties(writer);
             SerializeProperties(writer);
             writer.WriteEndObject();
             writer.WriteEndObject();
@@ -41,6 +43,9 @@ namespace SharpGLTF.IO
         protected static void SerializeProperty(JsonWriter writer, string name, Object value)
         protected static void SerializeProperty(JsonWriter writer, string name, Object value)
         {
         {
             if (value == null) return;
             if (value == null) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value);
             _Serialize(writer, value);
         }
         }
@@ -49,6 +54,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             writer.WriteValue(value.Value);
             writer.WriteValue(value.Value);
         }
         }
@@ -57,6 +65,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             writer.WriteValue(value.Value);
             writer.WriteValue(value.Value);
         }
         }
@@ -65,6 +76,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             writer.WriteValue(value.Value);
             writer.WriteValue(value.Value);
         }
         }
@@ -73,6 +87,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             writer.WriteValue(value.Value);
             writer.WriteValue(value.Value);
         }
         }
@@ -81,6 +98,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value.Value);
             _Serialize(writer, value.Value);
         }
         }
@@ -89,6 +109,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value.Value);
             _Serialize(writer, value.Value);
         }
         }
@@ -97,6 +120,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value.Value);
             _Serialize(writer, value.Value);
         }
         }
@@ -105,6 +131,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value.Value);
             _Serialize(writer, value.Value);
         }
         }
@@ -113,6 +142,9 @@ namespace SharpGLTF.IO
         {
         {
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
             if (defval.HasValue && defval.Value.Equals(value.Value)) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value.Value);
             _Serialize(writer, value.Value);
         }
         }
@@ -120,22 +152,26 @@ namespace SharpGLTF.IO
         protected static void SerializePropertyEnumValue<T>(JsonWriter writer, string name, T? value, T? defval = null)
         protected static void SerializePropertyEnumValue<T>(JsonWriter writer, string name, T? value, T? defval = null)
             where T : struct
             where T : struct
         {
         {
-            if (!typeof(T).IsEnum) throw new ArgumentException(nameof(value));
+            Guard.IsTrue(typeof(T).IsEnum, nameof(T));
 
 
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value)) return;
             if (defval.HasValue && defval.Value.Equals(value)) return;
 
 
+            Guard.NotNull(writer, nameof(writer));
+
             SerializeProperty(writer, name, (int)(Object)value);
             SerializeProperty(writer, name, (int)(Object)value);
         }
         }
 
 
         protected static void SerializePropertyEnumSymbol<T>(JsonWriter writer, string name, T? value, T? defval = null)
         protected static void SerializePropertyEnumSymbol<T>(JsonWriter writer, string name, T? value, T? defval = null)
             where T : struct
             where T : struct
         {
         {
-            if (!typeof(T).IsEnum) throw new ArgumentException(nameof(value));
+            Guard.IsTrue(typeof(T).IsEnum, nameof(T));
 
 
             if (!value.HasValue) return;
             if (!value.HasValue) return;
             if (defval.HasValue && defval.Value.Equals(value)) return;
             if (defval.HasValue && defval.Value.Equals(value)) return;
 
 
+            Guard.NotNull(writer, nameof(writer));
+
             SerializeProperty(writer, name, Enum.GetName(typeof(T), value));
             SerializeProperty(writer, name, Enum.GetName(typeof(T), value));
         }
         }
 
 
@@ -143,6 +179,9 @@ namespace SharpGLTF.IO
             where T : JsonSerializable
             where T : JsonSerializable
         {
         {
             if (value == null) return;
             if (value == null) return;
+
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
             _Serialize(writer, value);
             _Serialize(writer, value);
         }
         }
@@ -152,6 +191,8 @@ namespace SharpGLTF.IO
             if (collection == null) return;
             if (collection == null) return;
             if (minItems.HasValue && collection.Count < minItems.Value) return;
             if (minItems.HasValue && collection.Count < minItems.Value) return;
 
 
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
 
 
             writer.WriteStartArray();
             writer.WriteStartArray();
@@ -168,6 +209,8 @@ namespace SharpGLTF.IO
             if (collection == null) return;
             if (collection == null) return;
             if (collection.Count < 1) return;
             if (collection.Count < 1) return;
 
 
+            Guard.NotNull(writer, nameof(writer));
+
             writer.WritePropertyName(name);
             writer.WritePropertyName(name);
 
 
             writer.WriteStartObject();
             writer.WriteStartObject();
@@ -182,7 +225,8 @@ namespace SharpGLTF.IO
 
 
         private static void _Serialize(JsonWriter writer, Object value)
         private static void _Serialize(JsonWriter writer, Object value)
         {
         {
-            if (value == null) throw new ArgumentNullException(nameof(value));
+            Guard.NotNull(writer, nameof(writer));
+            Guard.NotNull(value, nameof(value));
 
 
             System.Diagnostics.Debug.Assert(!value.GetType().IsEnum, "gltf schema does not define a typed way of serializing enums");
             System.Diagnostics.Debug.Assert(!value.GetType().IsEnum, "gltf schema does not define a typed way of serializing enums");
 
 
@@ -249,6 +293,8 @@ namespace SharpGLTF.IO
 
 
         internal void Deserialize(JsonReader reader)
         internal void Deserialize(JsonReader reader)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
 
 
             if (reader.TokenType == JsonToken.StartObject)
             if (reader.TokenType == JsonToken.StartObject)
@@ -275,6 +321,8 @@ namespace SharpGLTF.IO
 
 
         protected static Object DeserializeUnknownObject(JsonReader reader)
         protected static Object DeserializeUnknownObject(JsonReader reader)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
 
 
             if (reader.TokenType == JsonToken.StartArray)
             if (reader.TokenType == JsonToken.StartArray)
@@ -322,6 +370,8 @@ namespace SharpGLTF.IO
 
 
         protected static T DeserializePropertyValue<T>(JsonReader reader)
         protected static T DeserializePropertyValue<T>(JsonReader reader)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+
             _TryCastValue(reader, typeof(T), out Object v);
             _TryCastValue(reader, typeof(T), out Object v);
 
 
             System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartArray);
             System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartArray);
@@ -334,6 +384,9 @@ namespace SharpGLTF.IO
 
 
         protected static void DeserializePropertyList<T>(JsonReader reader, IList<T> list)
         protected static void DeserializePropertyList<T>(JsonReader reader, IList<T> list)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+            Guard.NotNull(list, nameof(list));
+
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
 
 
             if (reader.TokenType != JsonToken.StartArray) throw new JsonReaderException();
             if (reader.TokenType != JsonToken.StartArray) throw new JsonReaderException();
@@ -359,6 +412,9 @@ namespace SharpGLTF.IO
 
 
         protected static void DeserializePropertyDictionary<T>(JsonReader reader, IDictionary<string, T> dict)
         protected static void DeserializePropertyDictionary<T>(JsonReader reader, IDictionary<string, T> dict)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+            Guard.NotNull(dict, nameof(dict));
+
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
             if (reader.TokenType == JsonToken.PropertyName) reader.Read();
 
 
             if (reader.TokenType == JsonToken.StartArray) throw new JsonReaderException();
             if (reader.TokenType == JsonToken.StartArray) throw new JsonReaderException();
@@ -385,6 +441,8 @@ namespace SharpGLTF.IO
 
 
         private static bool _TryCastValue(JsonReader reader, Type vtype, out Object value)
         private static bool _TryCastValue(JsonReader reader, Type vtype, out Object value)
         {
         {
+            Guard.NotNull(reader, nameof(reader));
+
             value = null;
             value = null;
 
 
             if (reader.TokenType == JsonToken.EndArray) return false;
             if (reader.TokenType == JsonToken.EndArray) return false;

+ 6 - 2
src/SharpGLTF.Core/Memory/FloatingArrays.cs

@@ -15,6 +15,8 @@ namespace SharpGLTF.Memory
     /// </summary>
     /// </summary>
     struct FloatingAccessor
     struct FloatingAccessor
     {
     {
+        private const string ERR_UNSUPPORTEDENCODING = "Unsupported encoding.";
+
         #region constructors
         #region constructors
 
 
         public FloatingAccessor(BYTES source, int byteOffset, int itemsCount, int byteStride, int dimensions, ENCODING encoding, Boolean normalized)
         public FloatingAccessor(BYTES source, int byteOffset, int itemsCount, int byteStride, int dimensions, ENCODING encoding, Boolean normalized)
@@ -72,7 +74,7 @@ namespace SharpGLTF.Memory
                             break;
                             break;
                         }
                         }
 
 
-                    default: throw new ArgumentException(nameof(encoding));
+                    default: throw new ArgumentException(ERR_UNSUPPORTEDENCODING, nameof(encoding));
                 }
                 }
             }
             }
             else
             else
@@ -117,7 +119,7 @@ namespace SharpGLTF.Memory
                     case ENCODING.FLOAT:
                     case ENCODING.FLOAT:
                         break;
                         break;
 
 
-                    default: throw new ArgumentException(nameof(encoding));
+                    default: throw new ArgumentException("Unsupported encoding.", nameof(encoding));
                 }
                 }
             }
             }
         }
         }
@@ -919,7 +921,9 @@ namespace SharpGLTF.Memory
 
 
         bool ICollection<Single[]>.IsReadOnly => false;
         bool ICollection<Single[]>.IsReadOnly => false;
 
 
+        #pragma warning disable CA1819 // Properties should not return arrays
         public Single[] this[int index]
         public Single[] this[int index]
+        #pragma warning restore CA1819 // Properties should not return arrays
         {
         {
             get
             get
             {
             {

+ 4 - 4
src/SharpGLTF.Core/Memory/IntegerArrays.cs

@@ -74,7 +74,7 @@ namespace SharpGLTF.Memory
                         break;
                         break;
                     }
                     }
 
 
-                default: throw new ArgumentException(nameof(encoding));
+                default: throw new ArgumentException("Unsupported encoding.", nameof(encoding));
             }
             }
         }
         }
 
 
@@ -145,11 +145,11 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(UInt32 item) { return this._FirstIndexOf(item); }
         public int IndexOf(UInt32 item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(UInt32[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(UInt32[] array, int arrayIndex) { Guard.NotNull(array, nameof(array)); this._CopyTo(array, arrayIndex); }
 
 
-        public void Fill(IEnumerable<Int32> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<Int32> values, int dstStart = 0) { Guard.NotNull(values, nameof(values)); values._CopyTo(this, dstStart); }
 
 
-        public void Fill(IEnumerable<UInt32> values, int dstStart = 0) { values._CopyTo(this, dstStart); }
+        public void Fill(IEnumerable<UInt32> values, int dstStart = 0) { Guard.NotNull(values, nameof(values)); values._CopyTo(this, dstStart); }
 
 
         void IList<UInt32>.Insert(int index, UInt32 item) { throw new NotSupportedException(); }
         void IList<UInt32>.Insert(int index, UInt32 item) { throw new NotSupportedException(); }
 
 

+ 14 - 0
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -131,6 +131,8 @@ namespace SharpGLTF.Memory
 
 
         public static int SetInterleavedInfo(MemoryAccessInfo[] attributes, int byteOffset, int itemsCount)
         public static int SetInterleavedInfo(MemoryAccessInfo[] attributes, int byteOffset, int itemsCount)
         {
         {
+            Guard.NotNull(attributes, nameof(attributes));
+
             var byteStride = 0;
             var byteStride = 0;
 
 
             for (int i = 0; i < attributes.Length; ++i)
             for (int i = 0; i < attributes.Length; ++i)
@@ -160,6 +162,8 @@ namespace SharpGLTF.Memory
 
 
         public static MemoryAccessInfo[] Slice(MemoryAccessInfo[] attributes, int start, int count)
         public static MemoryAccessInfo[] Slice(MemoryAccessInfo[] attributes, int start, int count)
         {
         {
+            Guard.NotNull(attributes, nameof(attributes));
+
             var dst = new MemoryAccessInfo[attributes.Length];
             var dst = new MemoryAccessInfo[attributes.Length];
 
 
             for (int i = 0; i < dst.Length; ++i)
             for (int i = 0; i < dst.Length; ++i)
@@ -194,6 +198,8 @@ namespace SharpGLTF.Memory
 
 
         public static IList<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         public static IList<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
         {
+            Guard.NotNull(bottom, nameof(bottom));
+            Guard.NotNull(topValues, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
@@ -204,6 +210,8 @@ namespace SharpGLTF.Memory
 
 
         public static IList<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         public static IList<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
         {
+            Guard.NotNull(bottom, nameof(bottom));
+            Guard.NotNull(topValues, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
@@ -214,6 +222,8 @@ namespace SharpGLTF.Memory
 
 
         public static IList<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         public static IList<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
         {
+            Guard.NotNull(bottom, nameof(bottom));
+            Guard.NotNull(topValues, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
@@ -224,6 +234,8 @@ namespace SharpGLTF.Memory
 
 
         public static IList<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         public static IList<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
         {
+            Guard.NotNull(bottom, nameof(bottom));
+            Guard.NotNull(topValues, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
@@ -234,6 +246,8 @@ namespace SharpGLTF.Memory
 
 
         public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         public static IList<Vector4> CreateColorSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
         {
+            Guard.NotNull(bottom, nameof(bottom));
+            Guard.NotNull(topValues, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
             Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));

+ 1 - 1
src/SharpGLTF.Core/Memory/SparseArrays.cs

@@ -67,7 +67,7 @@ namespace SharpGLTF.Memory
 
 
         public int IndexOf(T item) { return this._FirstIndexOf(item); }
         public int IndexOf(T item) { return this._FirstIndexOf(item); }
 
 
-        public void CopyTo(T[] array, int arrayIndex) { this._CopyTo(array, arrayIndex); }
+        public void CopyTo(T[] array, int arrayIndex) { Guard.NotNull(array, nameof(array)); this._CopyTo(array, arrayIndex); }
 
 
         void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); }
         void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); }
 
 

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

@@ -116,8 +116,8 @@ namespace SharpGLTF.Schema2
         /// <param name="normalized">The item normalization mode.</param>
         /// <param name="normalized">The item normalization mode.</param>
         public void SetData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions, EncodingType encoding, Boolean normalized)
         public void SetData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions, EncodingType encoding, Boolean normalized)
         {
         {
+            Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
-
             Guard.MustBeGreaterThanOrEqualTo(bufferByteOffset, _byteOffsetMinimum, nameof(bufferByteOffset));
             Guard.MustBeGreaterThanOrEqualTo(bufferByteOffset, _byteOffsetMinimum, nameof(bufferByteOffset));
             Guard.MustBeGreaterThanOrEqualTo(itemCount, _countMinimum, nameof(itemCount));
             Guard.MustBeGreaterThanOrEqualTo(itemCount, _countMinimum, nameof(itemCount));
 
 

+ 1 - 0
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -159,6 +159,7 @@ namespace SharpGLTF.Schema2
 
 
         public AffineTransform GetLocalTransform(Node node, float time)
         public AffineTransform GetLocalTransform(Node node, float time)
         {
         {
+            Guard.NotNull(node, nameof(node));
             Guard.MustShareLogicalParent(this, node, nameof(node));
             Guard.MustShareLogicalParent(this, node, nameof(node));
 
 
             var xform = node.LocalTransform;
             var xform = node.LocalTransform;

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

@@ -33,7 +33,9 @@ namespace SharpGLTF.Schema2
         /// </summary>
         /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalBuffers.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalBuffers.IndexOfReference(this);
 
 
+        #pragma warning disable CA1819 // Properties should not return arrays
         public Byte[] Content => _Content;
         public Byte[] Content => _Content;
+        #pragma warning restore CA1819 // Properties should not return arrays
 
 
         #endregion
         #endregion
 
 

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

@@ -172,7 +172,7 @@ namespace SharpGLTF.Schema2
         public BufferView UseBufferView(Buffer buffer, int byteOffset = 0, int? byteLength = null, int byteStride = 0, BufferMode? target = null)
         public BufferView UseBufferView(Buffer buffer, int byteOffset = 0, int? byteLength = null, int byteStride = 0, BufferMode? target = null)
         {
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer, nameof(buffer));
-            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustShareLogicalParent(this, "this", buffer, nameof(buffer));
 
 
             byteLength = byteLength.AsValue(buffer.Content.Length - byteOffset);
             byteLength = byteLength.AsValue(buffer.Content.Length - byteOffset);
 
 

+ 3 - 0
src/SharpGLTF.Core/Schema2/gltf.Camera.cs

@@ -154,6 +154,9 @@ namespace SharpGLTF.Schema2
 
 
         public static void CheckParameters(float xmag, float ymag, float znear, float zfar)
         public static void CheckParameters(float xmag, float ymag, float znear, float zfar)
         {
         {
+            Guard.MustBeGreaterThan(xmag, 0, nameof(xmag));
+            Guard.MustBeGreaterThan(ymag, 0, nameof(ymag));
+
             Guard.MustBeGreaterThanOrEqualTo(znear, 0, nameof(znear));
             Guard.MustBeGreaterThanOrEqualTo(znear, 0, nameof(znear));
             Guard.MustBeGreaterThanOrEqualTo(zfar, 0, nameof(zfar));
             Guard.MustBeGreaterThanOrEqualTo(zfar, 0, nameof(zfar));
             Guard.MustBeGreaterThan(zfar, znear, nameof(zfar));
             Guard.MustBeGreaterThan(zfar, znear, nameof(zfar));

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

@@ -184,6 +184,9 @@ namespace SharpGLTF.Schema2
         /// <param name="reader">The source reader.</param>
         /// <param name="reader">The source reader.</param>
         protected override void DeserializeProperty(string property, JsonReader reader)
         protected override void DeserializeProperty(string property, JsonReader reader)
         {
         {
+            Guard.NotNullOrEmpty(property, nameof(property));
+            Guard.NotNull(reader, nameof(reader));
+
             switch (property)
             switch (property)
             {
             {
                 case "extensions": _DeserializeExtensions(this, reader, _extensions); break;
                 case "extensions": _DeserializeExtensions(this, reader, _extensions); break;

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

@@ -165,7 +165,7 @@ namespace SharpGLTF.Schema2
             set => _baseColorFactor = value.AsNullable(_baseColorFactorDefault);
             set => _baseColorFactor = value.AsNullable(_baseColorFactorDefault);
         }
         }
 
 
-        public static Vector4 ParameterDefault => new Vector4((float)_metallicFactorDefault, (float)_roughnessFactorDefault,0,0);
+        public static Vector4 ParameterDefault => new Vector4((float)_metallicFactorDefault, (float)_roughnessFactorDefault, 0, 0);
 
 
         public Vector4 Parameter
         public Vector4 Parameter
         {
         {

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

@@ -50,7 +50,7 @@ namespace SharpGLTF.Schema2
             get => this._material.HasValue ? LogicalParent.LogicalParent.LogicalMaterials[this._material.Value] : null;
             get => this._material.HasValue ? LogicalParent.LogicalParent.LogicalMaterials[this._material.Value] : null;
             set
             set
             {
             {
-                if (value != null) Guard.MustShareLogicalParent(LogicalParent.LogicalParent, value, nameof(value));
+                if (value != null) Guard.MustShareLogicalParent(LogicalParent.LogicalParent, nameof(LogicalParent.LogicalParent), value, nameof(value));
 
 
                 this._material = value == null ? (int?)null : value.LogicalIndex;
                 this._material = value == null ? (int?)null : value.LogicalIndex;
             }
             }
@@ -123,7 +123,7 @@ namespace SharpGLTF.Schema2
 
 
             if (accessor != null)
             if (accessor != null)
             {
             {
-                Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, accessor, nameof(accessor));
+                Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, nameof(this.LogicalParent.LogicalParent), accessor, nameof(accessor));
                 _attributes[attributeKey] = accessor.LogicalIndex;
                 _attributes[attributeKey] = accessor.LogicalIndex;
             }
             }
             else
             else
@@ -143,7 +143,7 @@ namespace SharpGLTF.Schema2
         {
         {
             if (accessor == null) { this._indices = null; return; }
             if (accessor == null) { this._indices = null; return; }
 
 
-            Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, accessor, nameof(accessor));
+            Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, nameof(this.LogicalParent.LogicalParent), accessor, nameof(accessor));
 
 
             this._indices = accessor.LogicalIndex;
             this._indices = accessor.LogicalIndex;
         }
         }

+ 3 - 3
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -209,7 +209,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 if (value == null) { this._camera = null; return; }
                 if (value == null) { this._camera = null; return; }
 
 
-                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                Guard.MustShareLogicalParent(this.LogicalParent, nameof(this.LogicalParent), value, nameof(value));
 
 
                 this._camera = value.LogicalIndex;
                 this._camera = value.LogicalIndex;
             }
             }
@@ -225,7 +225,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 if (value == null) { this._mesh = null; return; }
                 if (value == null) { this._mesh = null; return; }
 
 
-                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                Guard.MustShareLogicalParent(this.LogicalParent, nameof(this.LogicalParent), value, nameof(value));
 
 
                 this._mesh = value.LogicalIndex;
                 this._mesh = value.LogicalIndex;
             }
             }
@@ -241,7 +241,7 @@ namespace SharpGLTF.Schema2
             {
             {
                 if (value == null) { this._skin = null; return; }
                 if (value == null) { this._skin = null; return; }
 
 
-                Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                Guard.MustShareLogicalParent(this.LogicalParent, nameof(this.LogicalParent), value, nameof(value));
 
 
                 Guard.IsFalse(_matrix.HasValue, _NOTRANSFORMMESSAGE);
                 Guard.IsFalse(_matrix.HasValue, _NOTRANSFORMMESSAGE);
                 Guard.IsFalse(_scale.HasValue, _NOTRANSFORMMESSAGE);
                 Guard.IsFalse(_scale.HasValue, _NOTRANSFORMMESSAGE);

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

@@ -134,7 +134,7 @@ namespace SharpGLTF.Schema2
                     return;
                     return;
                 }
                 }
 
 
-                Guard.MustShareLogicalParent(this, value, nameof(value));
+                Guard.MustShareLogicalParent(this, "this", value, nameof(value));
 
 
                 _scene = value.LogicalIndex;
                 _scene = value.LogicalIndex;
             }
             }

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

@@ -89,6 +89,8 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="MODEL"/> instance.</returns>
         /// <returns>A <see cref="MODEL"/> instance.</returns>
         public static MODEL Read(Stream stream, ReadSettings settings)
         public static MODEL Read(Stream stream, ReadSettings settings)
         {
         {
+            Guard.NotNull(stream, nameof(stream));
+
             bool binaryFile = glb._Identify(stream);
             bool binaryFile = glb._Identify(stream);
 
 
             if (binaryFile) return ReadGLB(stream, settings);
             if (binaryFile) return ReadGLB(stream, settings);
@@ -158,6 +160,8 @@ namespace SharpGLTF.Schema2
 
 
         public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
         public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
         {
         {
+            Guard.NotNull(files, nameof(files));
+
             var jsonBytes = files[fileName];
             var jsonBytes = files[fileName];
 
 
             var settings = new ReadSettings(fn => files[fn]);
             var settings = new ReadSettings(fn => files[fn]);

+ 1 - 0
src/SharpGLTF.Core/Schema2/gltf.Serialization.Write.cs

@@ -324,6 +324,7 @@ namespace SharpGLTF.Schema2
         /// </remarks>
         /// </remarks>
         public void Write(WriteSettings settings, string baseName)
         public void Write(WriteSettings settings, string baseName)
         {
         {
+            Guard.NotNull(settings, nameof(settings));
             _Write(settings, baseName, this);
             _Write(settings, baseName, this);
         }
         }
 
 

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

@@ -50,7 +50,7 @@ namespace SharpGLTF.Schema2
             get => this._skeleton.HasValue ? this.LogicalParent.LogicalNodes[this._skeleton.Value] : null;
             get => this._skeleton.HasValue ? this.LogicalParent.LogicalNodes[this._skeleton.Value] : null;
             set
             set
             {
             {
-                if (value != null) Guard.MustShareLogicalParent(this.LogicalParent, value, nameof(value));
+                if (value != null) Guard.MustShareLogicalParent(this.LogicalParent, nameof(this.LogicalParent), value, nameof(value));
                 this._skeleton = value == null ? (int?)null : value.LogicalIndex;
                 this._skeleton = value == null ? (int?)null : value.LogicalIndex;
             }
             }
         }
         }

+ 6 - 3
src/SharpGLTF.Core/Schema2/gltf.Textures.cs

@@ -83,6 +83,7 @@ namespace SharpGLTF.Schema2
 
 
         public void SetImage(Image primaryImage)
         public void SetImage(Image primaryImage)
         {
         {
+            Guard.NotNull(primaryImage, nameof(primaryImage));
             Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
             Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
 
 
             if (primaryImage.IsDds || primaryImage.IsWebp)
             if (primaryImage.IsDds || primaryImage.IsWebp)
@@ -99,6 +100,8 @@ namespace SharpGLTF.Schema2
 
 
         public void SetImages(Image primaryImage, Image fallbackImage)
         public void SetImages(Image primaryImage, Image fallbackImage)
         {
         {
+            Guard.NotNull(primaryImage, nameof(primaryImage));
+            Guard.NotNull(fallbackImage, nameof(fallbackImage));
             Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
             Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
             Guard.MustShareLogicalParent(this, fallbackImage, nameof(fallbackImage));
             Guard.MustShareLogicalParent(this, fallbackImage, nameof(fallbackImage));
             Guard.IsTrue(primaryImage.IsDds || primaryImage.IsWebp, "Primary image must be DDS or WEBP");
             Guard.IsTrue(primaryImage.IsDds || primaryImage.IsWebp, "Primary image must be DDS or WEBP");
@@ -304,9 +307,9 @@ namespace SharpGLTF.Schema2
                 return null;
                 return null;
             }
             }
 
 
-            if (primary  != null) Guard.MustShareLogicalParent(this, primary, nameof(primary));
-            if (fallback != null) Guard.MustShareLogicalParent(this, fallback, nameof(primary));
-            if (sampler  != null) Guard.MustShareLogicalParent(this, sampler, nameof(sampler));
+            if (primary  != null) Guard.MustShareLogicalParent(this, "this", primary, nameof(primary));
+            if (fallback != null) Guard.MustShareLogicalParent(this, "this", fallback, nameof(primary));
+            if (sampler  != null) Guard.MustShareLogicalParent(this, "this", sampler, nameof(sampler));
 
 
             // find if we have an equivalent texture
             // find if we have an equivalent texture
             var tex = _textures.FirstOrDefault(item => item._IsEqualentTo(primary, fallback, sampler));
             var tex = _textures.FirstOrDefault(item => item._IsEqualentTo(primary, fallback, sampler));

+ 1 - 1
src/SharpGLTF.Core/Schema2/khr.lights.cs

@@ -55,7 +55,7 @@ namespace SharpGLTF.Schema2
 
 
         internal PunctualLight(PunctualLightType ltype)
         internal PunctualLight(PunctualLightType ltype)
         {
         {
-            _type = ltype.ToString().ToLower();
+            _type = ltype.ToString().ToLowerInvariant();
 
 
             if (ltype == PunctualLightType.Spot) _spot = new PunctualLightSpot();
             if (ltype == PunctualLightType.Spot) _spot = new PunctualLightSpot();
         }
         }

+ 1 - 2
src/SharpGLTF.Core/Transforms/IndexWeight.cs

@@ -5,7 +5,6 @@ using System.Text;
 
 
 namespace SharpGLTF.Transforms
 namespace SharpGLTF.Transforms
 {
 {
-
     [System.Diagnostics.DebuggerDisplay("{Index} = {Weight}")]
     [System.Diagnostics.DebuggerDisplay("{Index} = {Weight}")]
     readonly struct IndexWeight
     readonly struct IndexWeight
     {
     {
@@ -17,7 +16,7 @@ namespace SharpGLTF.Transforms
             Weight = pair.Item2;
             Weight = pair.Item2;
         }
         }
 
 
-        public static implicit operator IndexWeight((int, float) pair) {return new IndexWeight(pair.Item1, pair.Item2); }
+        public static implicit operator IndexWeight((int, float) pair) { return new IndexWeight(pair.Item1, pair.Item2); }
 
 
         public IndexWeight(int i, float w)
         public IndexWeight(int i, float w)
         {
         {

+ 12 - 6
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -225,9 +225,9 @@ namespace SharpGLTF.Transforms
     {
     {
         #region constructor
         #region constructor
 
 
-        public SkinTransform(TRANSFORM[] invBindings, TRANSFORM[] xforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
+        public SkinTransform(TRANSFORM[] invBindings, TRANSFORM[] worldXforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
         {
         {
-            Update(invBindings, xforms, morphWeights, useAbsoluteMorphTargets);
+            Update(invBindings, worldXforms, morphWeights, useAbsoluteMorphTargets);
         }
         }
 
 
         #endregion
         #endregion
@@ -240,11 +240,11 @@ namespace SharpGLTF.Transforms
 
 
         #region API
         #region API
 
 
-        public void Update(TRANSFORM[] invBindings, TRANSFORM[] xforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
+        public void Update(TRANSFORM[] invBindings, TRANSFORM[] worldXforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
         {
         {
             Guard.NotNull(invBindings, nameof(invBindings));
             Guard.NotNull(invBindings, nameof(invBindings));
-            Guard.NotNull(xforms, nameof(xforms));
-            Guard.IsTrue(invBindings.Length == xforms.Length, nameof(xforms), $"{invBindings} and {xforms} length mismatch.");
+            Guard.NotNull(worldXforms, nameof(worldXforms));
+            Guard.IsTrue(invBindings.Length == worldXforms.Length, nameof(worldXforms), $"{invBindings} and {worldXforms} length mismatch.");
 
 
             Update(morphWeights, useAbsoluteMorphTargets);
             Update(morphWeights, useAbsoluteMorphTargets);
 
 
@@ -252,7 +252,7 @@ namespace SharpGLTF.Transforms
 
 
             for (int i = 0; i < _JointTransforms.Length; ++i)
             for (int i = 0; i < _JointTransforms.Length; ++i)
             {
             {
-                _JointTransforms[i] = invBindings[i] * xforms[i];
+                _JointTransforms[i] = invBindings[i] * worldXforms[i];
             }
             }
         }
         }
 
 
@@ -262,6 +262,8 @@ namespace SharpGLTF.Transforms
 
 
         public V3 TransformPosition(V3 localPosition, V3[] morphTargets, (int, float)[] skinWeights)
         public V3 TransformPosition(V3 localPosition, V3[] morphTargets, (int, float)[] skinWeights)
         {
         {
+            Guard.NotNull(skinWeights, nameof(skinWeights));
+
             localPosition = MorphVectors(localPosition, morphTargets);
             localPosition = MorphVectors(localPosition, morphTargets);
 
 
             var worldPosition = V3.Zero;
             var worldPosition = V3.Zero;
@@ -278,6 +280,8 @@ namespace SharpGLTF.Transforms
 
 
         public V3 TransformNormal(V3 localNormal, V3[] morphTargets, (int, float)[] skinWeights)
         public V3 TransformNormal(V3 localNormal, V3[] morphTargets, (int, float)[] skinWeights)
         {
         {
+            Guard.NotNull(skinWeights, nameof(skinWeights));
+
             localNormal = MorphVectors(localNormal, morphTargets);
             localNormal = MorphVectors(localNormal, morphTargets);
 
 
             var worldNormal = V3.Zero;
             var worldNormal = V3.Zero;
@@ -292,6 +296,8 @@ namespace SharpGLTF.Transforms
 
 
         public V4 TransformTangent(V4 localTangent, V3[] morphTargets, (int, float)[] skinWeights)
         public V4 TransformTangent(V4 localTangent, V3[] morphTargets, (int, float)[] skinWeights)
         {
         {
+            Guard.NotNull(skinWeights, nameof(skinWeights));
+
             var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
             var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
 
 
             var worldTangent = V3.Zero;
             var worldTangent = V3.Zero;

+ 1 - 1
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -279,7 +279,7 @@ namespace SharpGLTF.Transforms
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public bool IsWeightless => Weight0 == 0 & Weight1 == 0 & Weight2 == 0 & Weight3 == 0 & Weight4 == 0 & Weight5 == 0 & Weight6 == 0 & Weight7 == 0;
         public bool IsWeightless => Weight0 == 0 & Weight1 == 0 & Weight2 == 0 & Weight3 == 0 & Weight4 == 0 & Weight5 == 0 & Weight6 == 0 & Weight7 == 0;
 
 
-        public float WeightSum => Weight0 + Weight1+ Weight2 + Weight3 + Weight4 + Weight5 + Weight6 + Weight7;
+        public float WeightSum => Weight0 + Weight1 + Weight2 + Weight3 + Weight4 + Weight5 + Weight6 + Weight7;
 
 
         #endregion
         #endregion
 
 

+ 36 - 0
src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs

@@ -14,6 +14,29 @@ namespace SharpGLTF.Animations
     public class AnimatableProperty<T>
     public class AnimatableProperty<T>
         where T : struct
         where T : struct
     {
     {
+        #region lifecycle
+
+        internal AnimatableProperty() { }
+
+        internal AnimatableProperty(AnimatableProperty<T> other)
+        {
+            if (other == null) return;
+
+            if (other._Tracks != null)
+            {
+                this._Tracks = new Dictionary<string, ICurveSampler<T>>();
+
+                foreach (var kvp in other._Tracks)
+                {
+                    this._Tracks[kvp.Key] = CurveFactory.CreateCurveBuilder(kvp.Value);
+                }
+            }
+
+            this.Value = other.Value;
+        }
+
+        #endregion
+
         #region data
         #region data
 
 
         private Dictionary<string, ICurveSampler<T>> _Tracks;
         private Dictionary<string, ICurveSampler<T>> _Tracks;
@@ -28,12 +51,25 @@ namespace SharpGLTF.Animations
 
 
         #region properties
         #region properties
 
 
+        public bool IsAnimated => Tracks.Count > 0;
+
         public IReadOnlyDictionary<string, ICurveSampler<T>> Tracks => _Tracks == null ? Collections.EmptyDictionary<string, ICurveSampler<T>>.Instance : _Tracks;
         public IReadOnlyDictionary<string, ICurveSampler<T>> Tracks => _Tracks == null ? Collections.EmptyDictionary<string, ICurveSampler<T>>.Instance : _Tracks;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
+        /// <summary>
+        /// Removes the animation <paramref name="track"/>.
+        /// </summary>
+        /// <param name="track">The name of the track.</param>
+        public void RemoveTrack(string track)
+        {
+            if (_Tracks == null) return;
+            _Tracks.Remove(track);
+            if (_Tracks.Count == 0) _Tracks = null;
+        }
+
         /// <summary>
         /// <summary>
         /// Evaluates the value of this <see cref="AnimatableProperty{T}"/> at a given <paramref name="offset"/> for a given <paramref name="track"/>.
         /// Evaluates the value of this <see cref="AnimatableProperty{T}"/> at a given <paramref name="offset"/> for a given <paramref name="track"/>.
         /// </summary>
         /// </summary>

+ 13 - 3
src/SharpGLTF.Toolkit/Animations/CurveFactory.cs

@@ -3,10 +3,10 @@ using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
+using SPARSE = SharpGLTF.Transforms.SparseWeight8;
+
 namespace SharpGLTF.Animations
 namespace SharpGLTF.Animations
 {
 {
-    using SPARSE = Transforms.SparseWeight8;
-
     static class CurveFactory
     static class CurveFactory
     {
     {
         public static CurveBuilder<T> CreateCurveBuilder<T>()
         public static CurveBuilder<T> CreateCurveBuilder<T>()
@@ -16,7 +16,17 @@ namespace SharpGLTF.Animations
             if (typeof(T) == typeof(Quaternion)) return new QuaternionCurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(Quaternion)) return new QuaternionCurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(SPARSE)) return new SparseCurveBuilder() as CurveBuilder<T>;
             if (typeof(T) == typeof(SPARSE)) return new SparseCurveBuilder() as CurveBuilder<T>;
 
 
-            throw new ArgumentException(nameof(T), "Generic argument not supported");
+            throw new ArgumentException($"{nameof(T)} not supported.", nameof(T));
+        }
+
+        public static CurveBuilder<T> CreateCurveBuilder<T>(ICurveSampler<T> curve)
+            where T : struct
+        {
+            if (curve is Vector3CurveBuilder v3cb) return v3cb.Clone() as CurveBuilder<T>;
+            if (curve is QuaternionCurveBuilder q4cb) return q4cb.Clone() as CurveBuilder<T>;
+            if (curve is SparseCurveBuilder sscb) return sscb.Clone() as CurveBuilder<T>;
+
+            throw new ArgumentException($"{nameof(T)} not supported.", nameof(T));
         }
         }
     }
     }
 
 

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

@@ -114,6 +114,9 @@ namespace SharpGLTF.Geometry
 
 
         public void AddMesh(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         public void AddMesh(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
         {
+            if (mesh == null) return;
+            Guard.NotNull(materialTransform, nameof(materialTransform));
+
             foreach (var p in mesh.Primitives)
             foreach (var p in mesh.Primitives)
             {
             {
                 var materialKey = materialTransform(p.Material);
                 var materialKey = materialTransform(p.Material);

+ 2 - 2
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -252,7 +252,7 @@ namespace SharpGLTF.Geometry
 
 
             if (_Mesh.VertexPreprocessor != null)
             if (_Mesh.VertexPreprocessor != null)
             {
             {
-                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1,-1);
+                if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref a)) return (-1, -1);
                 if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1);
                 if (!_Mesh.VertexPreprocessor.PreprocessVertex(ref b)) return (-1, -1);
             }
             }
 
 
@@ -332,7 +332,7 @@ namespace SharpGLTF.Geometry
 
 
         internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
         {
-            if (primitive == null) throw new ArgumentNullException(nameof(primitive));
+            if (primitive == null) return;
 
 
             if (_PrimitiveVertexCount == 1)
             if (_PrimitiveVertexCount == 1)
             {
             {

+ 1 - 1
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -170,7 +170,7 @@ namespace SharpGLTF.Geometry
             return v;
             return v;
         }
         }
 
 
-        public static VertexBuilder<TvG, TvM, TvS> Create(Vector3 position,Vector3 normal)
+        public static VertexBuilder<TvG, TvM, TvS> Create(Vector3 position, Vector3 normal)
         {
         {
             var v = default(VertexBuilder<TvG, TvM, TvS>);
             var v = default(VertexBuilder<TvG, TvM, TvS>);
             v.Geometry.SetPosition(position);
             v.Geometry.SetPosition(position);

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

@@ -40,6 +40,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexPosition(IVertexGeometry src)
         public VertexPosition(IVertexGeometry src)
         {
         {
+            Guard.NotNull(src, nameof(src));
             this.Position = src.GetPosition();
             this.Position = src.GetPosition();
         }
         }
 
 
@@ -103,6 +104,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexPositionNormal(IVertexGeometry src)
         public VertexPositionNormal(IVertexGeometry src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Position = src.GetPosition();
             this.Position = src.GetPosition();
             src.TryGetNormal(out this.Normal);
             src.TryGetNormal(out this.Normal);
         }
         }
@@ -166,6 +169,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexPositionNormalTangent(IVertexGeometry src)
         public VertexPositionNormalTangent(IVertexGeometry src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Position = src.GetPosition();
             this.Position = src.GetPosition();
             src.TryGetNormal(out this.Normal);
             src.TryGetNormal(out this.Normal);
             src.TryGetTangent(out this.Tangent);
             src.TryGetTangent(out this.Tangent);

+ 15 - 1
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -35,6 +35,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexColor1(IVertexMaterial src)
         public VertexColor1(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
         }
         }
 
 
@@ -94,6 +96,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexColor2(IVertexMaterial src)
         public VertexColor2(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
             this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
         }
         }
@@ -153,6 +157,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexTexture1(IVertexMaterial src)
         public VertexTexture1(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
         }
         }
 
 
@@ -212,6 +218,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexTexture2(IVertexMaterial src)
         public VertexTexture2(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord1 = src.MaxTextCoords > 1 ? src.GetTexCoord(1) : Vector2.Zero;
             this.TexCoord1 = src.MaxTextCoords > 1 ? src.GetTexCoord(1) : Vector2.Zero;
         }
         }
@@ -275,11 +283,13 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexColor1Texture1(IVertexMaterial src)
         public VertexColor1Texture1(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
         }
         }
 
 
-        public static implicit operator VertexColor1Texture1((Vector4,Vector2) coloruv)
+        public static implicit operator VertexColor1Texture1((Vector4, Vector2) coloruv)
         {
         {
             return new VertexColor1Texture1(coloruv.Item1, coloruv.Item2);
             return new VertexColor1Texture1(coloruv.Item1, coloruv.Item2);
         }
         }
@@ -340,6 +350,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexColor1Texture2(IVertexMaterial src)
         public VertexColor1Texture2(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord1 = src.MaxTextCoords > 1 ? src.GetTexCoord(1) : Vector2.Zero;
             this.TexCoord1 = src.MaxTextCoords > 1 ? src.GetTexCoord(1) : Vector2.Zero;
@@ -413,6 +425,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 
 
         public VertexColor2Texture2(IVertexMaterial src)
         public VertexColor2Texture2(IVertexMaterial src)
         {
         {
+            Guard.NotNull(src, nameof(src));
+
             this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color0 = src.MaxColors > 0 ? src.GetColor(0) : Vector4.One;
             this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
             this.Color1 = src.MaxColors > 1 ? src.GetColor(1) : Vector4.One;
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord0 = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;

+ 3 - 1
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -11,11 +11,13 @@ using static System.FormattableString;
 
 
 namespace SharpGLTF.IO
 namespace SharpGLTF.IO
 {
 {
+    #pragma warning disable SA1135 // Using directives should be qualified
     using BYTES = ArraySegment<Byte>;
     using BYTES = ArraySegment<Byte>;
     using VEMPTY = Geometry.VertexTypes.VertexEmpty;
     using VEMPTY = Geometry.VertexTypes.VertexEmpty;
     using VERTEX = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
     using VERTEX = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
     using VGEOMETRY = Geometry.VertexTypes.VertexPositionNormal;
     using VGEOMETRY = Geometry.VertexTypes.VertexPositionNormal;
     using VMATERIAL = Geometry.VertexTypes.VertexTexture1;
     using VMATERIAL = Geometry.VertexTypes.VertexTexture1;
+    #pragma warning restore SA1135 // Using directives should be qualified
 
 
     /// <summary>
     /// <summary>
     /// Tiny wavefront object writer
     /// Tiny wavefront object writer
@@ -91,7 +93,7 @@ namespace SharpGLTF.IO
             }
             }
         }
         }
 
 
-        private IReadOnlyDictionary<Material, string> _WriteMaterials(IDictionary<String, BYTES> files, string baseName, IEnumerable<Material> materials)
+        private static IReadOnlyDictionary<Material, string> _WriteMaterials(IDictionary<String, BYTES> files, string baseName, IEnumerable<Material> materials)
         {
         {
             // write all image files
             // write all image files
             var images = materials
             var images = materials

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

@@ -120,7 +120,7 @@ namespace SharpGLTF.Materials
             get => _CompatibilityFallbackMaterial;
             get => _CompatibilityFallbackMaterial;
             set
             set
             {
             {
-                if (_CompatibilityFallbackMaterial == this) throw new ArgumentException(nameof(value));
+                Guard.IsFalse(_CompatibilityFallbackMaterial == this, nameof(value), "Cannot use self as fallback material");
                 _CompatibilityFallbackMaterial = value;
                 _CompatibilityFallbackMaterial = value;
             }
             }
         }
         }
@@ -176,7 +176,7 @@ namespace SharpGLTF.Materials
         {
         {
             Guard.NotNullOrEmpty(channelKey, nameof(channelKey));
             Guard.NotNullOrEmpty(channelKey, nameof(channelKey));
 
 
-            channelKey = channelKey.ToLower();
+            channelKey = channelKey.ToLowerInvariant();
 
 
             return _Channels.FirstOrDefault(item => string.Equals(channelKey, item.Key, StringComparison.OrdinalIgnoreCase));
             return _Channels.FirstOrDefault(item => string.Equals(channelKey, item.Key, StringComparison.OrdinalIgnoreCase));
         }
         }
@@ -230,7 +230,7 @@ namespace SharpGLTF.Materials
         {
         {
             this.UseChannel(channelKey)
             this.UseChannel(channelKey)
                 .UseTexture()
                 .UseTexture()
-                .WithImage(primaryImagePath);
+                .WithPrimaryImage(primaryImagePath);
 
 
             return this;
             return this;
         }
         }
@@ -239,7 +239,7 @@ namespace SharpGLTF.Materials
         {
         {
             this.UseChannel(channelKey)
             this.UseChannel(channelKey)
                 .UseTexture()
                 .UseTexture()
-                .WithImage(primaryImagePath);
+                .WithPrimaryImage(primaryImagePath);
 
 
             return this;
             return this;
         }
         }

+ 31 - 4
src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs

@@ -104,7 +104,7 @@ namespace SharpGLTF.Materials
         public BYTES PrimaryImageContent
         public BYTES PrimaryImageContent
         {
         {
             get => _PrimaryImageContent;
             get => _PrimaryImageContent;
-            set => WithImage(value);
+            set => WithPrimaryImage(value);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -127,16 +127,22 @@ namespace SharpGLTF.Materials
 
 
         public TextureBuilder WithCoordinateSet(int cset) { CoordinateSet = cset; return this; }
         public TextureBuilder WithCoordinateSet(int cset) { CoordinateSet = cset; return this; }
 
 
-        public TextureBuilder WithImage(string imagePath)
+        [Obsolete("Use WithPrimaryImage instead.")]
+        public TextureBuilder WithImage(string imagePath) { return WithPrimaryImage(imagePath); }
+
+        [Obsolete("Use WithPrimaryImage instead,")]
+        public TextureBuilder WithImage(BYTES image) { return WithPrimaryImage(image); }
+
+        public TextureBuilder WithPrimaryImage(string imagePath)
         {
         {
             var primary = System.IO.File
             var primary = System.IO.File
                 .ReadAllBytes(imagePath)
                 .ReadAllBytes(imagePath)
                 .Slice(0);
                 .Slice(0);
 
 
-            return WithImage(primary);
+            return WithPrimaryImage(primary);
         }
         }
 
 
-        public TextureBuilder WithImage(BYTES image)
+        public TextureBuilder WithPrimaryImage(BYTES image)
         {
         {
             if (image.Count > 0)
             if (image.Count > 0)
             {
             {
@@ -220,6 +226,27 @@ namespace SharpGLTF.Materials
         }
         }
 
 
         #endregion
         #endregion
+
+        #region image utilities
+
+        /// <summary>
+        /// Checks if <paramref name="data"/> represents a stream of an encoded image.
+        /// </summary>
+        /// <param name="data">A stream of bytes.</param>
+        /// <param name="extension">
+        /// An image format, valid values are:
+        /// - PNG
+        /// - JPG
+        /// - DDS
+        /// - WEBP
+        /// </param>
+        /// <returns>True if <paramref name="data"/> is an image.</returns>
+        public static bool IsImage(ArraySegment<Byte> data, string extension)
+        {
+            return data._IsImage(extension);
+        }
+
+        #endregion
     }
     }
 
 
     public class TextureTransformBuilder
     public class TextureTransformBuilder

+ 47 - 0
src/SharpGLTF.Toolkit/Scenes/Content.Schema2.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using SharpGLTF.Schema2;
+
+using SCHEMA2NODE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Node>;
+
+namespace SharpGLTF.Scenes
+{
+    partial class MorphableMeshContent : SCHEMA2NODE
+    {
+        void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
+        {
+            if (!(_Target is SCHEMA2NODE schema2Target)) return;
+
+            schema2Target.Setup(dstNode, context);
+
+            // setup morphs here!
+        }
+    }
+
+    partial class MeshContent : SCHEMA2NODE
+    {
+        void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
+        {
+            dstNode.Mesh = context.GetMesh(_Mesh);
+        }
+    }
+
+    partial class OrthographicCameraContent : SCHEMA2NODE
+    {
+        void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
+        {
+            dstNode.WithOrthographicCamera(_XMag, _YMag, _ZNear, _ZFar);
+        }
+    }
+
+    partial class PerspectiveCameraContent : SCHEMA2NODE
+    {
+        void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
+        {
+            dstNode.WithPerspectiveCamera(_AspectRatio, _FovY, _ZNear, _ZFar);
+        }
+    }
+}

+ 11 - 238
src/SharpGLTF.Toolkit/Scenes/Content.cs

@@ -2,214 +2,41 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
-using System.Text;
-using SharpGLTF.Geometry;
-using SharpGLTF.Materials;
-using SharpGLTF.Schema2;
+
+using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
-    using MESHBUILDER = IMeshBuilder<MaterialBuilder>;
-
-    interface IContentRoot
+    interface IRenderableContent
     {
     {
         MESHBUILDER GetGeometryAsset();
         MESHBUILDER GetGeometryAsset();
-
-        NodeBuilder GetArmatureAsset();
-
-        void Setup(Scene dstScene, Schema2SceneBuilder context);
     }
     }
 
 
-    interface IContent
-    {
-        void Setup(Node dstNode, Schema2SceneBuilder context);
-    }
-
-    interface IRenderableContent : IContent
-    {
-        MESHBUILDER GetGeometryAsset();
-    }
-
-    class StaticTransformer : IContentRoot
+    partial class MeshContent : IRenderableContent
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public StaticTransformer(IContent content, Matrix4x4 xform)
-        {
-            _Transform = xform;
-            _Target = content;
-        }
-
-        public StaticTransformer(MESHBUILDER mesh, Matrix4x4 xform)
-        {
-            _Transform = xform;
-            _Target = new MeshContent(mesh);
-        }
-
-        #endregion
-
-        #region data
-
-        private IContent _Target; // Can be either a morphController or a mesh, or light or camera
-
-        private Matrix4x4 _Transform;
-
-        #endregion
-
-        #region API
-
-        public NodeBuilder GetArmatureAsset() { return null; }
-
-        public MESHBUILDER GetGeometryAsset() { return (_Target as IRenderableContent)?.GetGeometryAsset(); }
-
-        public void Setup(Scene dstScene, Schema2SceneBuilder context)
-        {
-            var node = dstScene.CreateNode();
-            node.LocalMatrix = _Transform;
-
-            _Target.Setup(node, context);
-        }
-
-        #endregion
-    }
-
-    class NodeTransformer : IContentRoot
-    {
-        #region lifecycle
-
-        public NodeTransformer(IContent content, NodeBuilder node)
-        {
-            _Node = node;
-            _Target = content;
-        }
-
-        public NodeTransformer(MESHBUILDER mesh, NodeBuilder node)
-        {
-            _Node = node;
-            _Target = new MeshContent(mesh);
-        }
-
-        #endregion
-
-        #region data
-
-        private IContent _Target; // Can be either a morphController or a mesh, or light or camera
-
-        private NodeBuilder _Node;
-
-        #endregion
-
-        #region API
-
-        public NodeBuilder GetArmatureAsset() { return _Node.Root; }
-
-        public MESHBUILDER GetGeometryAsset() { return (_Target as IRenderableContent)?.GetGeometryAsset(); }
-
-        public void Setup(Schema2.Scene dstScene, Schema2SceneBuilder context)
-        {
-            var node = context.GetNode(_Node);
-
-            if (node == null) dstScene.CreateNode();
-
-            _Target.Setup(node, context);
-        }
-
-        #endregion
-    }
-
-    class SkinTransformer : IContentRoot
-    {
-        #region lifecycle
-
-        public SkinTransformer(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, NodeBuilder[] joints)
-        {
-            Guard.NotNull(mesh, nameof(mesh));
-            Guard.NotNull(joints, nameof(joints));
-            Guard.IsTrue(NodeBuilder.IsValidArmature(joints), nameof(joints));
-
-            _Target = new MeshContent(mesh);
-            _TargetBindMatrix = meshBindMatrix;
-            _Joints.AddRange(joints.Select(item => (item, (Matrix4x4?)null)));
-        }
-
-        public SkinTransformer(MESHBUILDER mesh, (NodeBuilder, Matrix4x4)[] joints)
+        public MeshContent(MESHBUILDER mesh)
         {
         {
-            Guard.NotNull(mesh, nameof(mesh));
-            Guard.NotNull(joints, nameof(joints));
-            Guard.IsTrue(NodeBuilder.IsValidArmature(joints.Select(item => item.Item1)), nameof(joints));
-
-            _Target = new MeshContent(mesh);
-            _TargetBindMatrix = null;
-            _Joints.AddRange(joints.Select(item => (item.Item1, (Matrix4x4?)item.Item2)));
+            _Mesh = mesh;
         }
         }
 
 
         #endregion
         #endregion
 
 
         #region data
         #region data
 
 
-        private IRenderableContent _Target; // Can be either a morphController or a mesh
-        private Matrix4x4? _TargetBindMatrix;
-
-        // condition: all NodeBuilder objects must have the same root.
-        private readonly List<(NodeBuilder, Matrix4x4?)> _Joints = new List<(NodeBuilder, Matrix4x4?)>();
+        private MESHBUILDER _Mesh;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        public MESHBUILDER GetGeometryAsset() { return (_Target as IRenderableContent)?.GetGeometryAsset(); }
-
-        public NodeBuilder GetArmatureAsset() { return _Joints.Select(item => item.Item1.Root).Distinct().FirstOrDefault(); }
-
-        public void Setup(Scene dstScene, Schema2SceneBuilder context)
-        {
-            var skinnedMeshNode = dstScene.CreateNode();
-
-            if (_TargetBindMatrix.HasValue)
-            {
-                var dstNodes = new Node[_Joints.Count];
-
-                for (int i = 0; i < dstNodes.Length; ++i)
-                {
-                    var srcNode = _Joints[i];
-
-                    System.Diagnostics.Debug.Assert(!srcNode.Item2.HasValue);
-
-                    dstNodes[i] = context.GetNode(srcNode.Item1);
-                }
-
-                #if DEBUG
-                for (int i = 0; i < dstNodes.Length; ++i)
-                {
-                    var srcNode = _Joints[i];
-                    System.Diagnostics.Debug.Assert(dstNodes[i].WorldMatrix == srcNode.Item1.WorldMatrix);
-                }
-                #endif
-
-                skinnedMeshNode.WithSkinBinding(_TargetBindMatrix.Value, dstNodes);
-            }
-            else
-            {
-                var skinnedJoints = _Joints
-                .Select(j => (context.GetNode(j.Item1), j.Item2.Value) )
-                .ToArray();
-
-                skinnedMeshNode.WithSkinBinding(skinnedJoints);
-            }
-
-            // set skeleton
-            // var root = _Joints[0].Item1.Root;
-            // skinnedMeshNode.Skin.Skeleton = context.GetNode(root);
-
-            _Target.Setup(skinnedMeshNode, context);
-        }
+        public MESHBUILDER GetGeometryAsset() => _Mesh;
 
 
         #endregion
         #endregion
     }
     }
 
 
-    // We really have two options here: Either implement this here, or as a derived of IMeshBuilder<MaterialBuilder>
-
-    class MorphMeshModifier : IRenderableContent // must be a child of a controller, and the parent of a mesh
+    partial class MorphableMeshContent : IRenderableContent
     {
     {
         #region data
         #region data
 
 
@@ -223,54 +50,10 @@ namespace SharpGLTF.Scenes
 
 
         public MESHBUILDER GetGeometryAsset() => _Target?.GetGeometryAsset();
         public MESHBUILDER GetGeometryAsset() => _Target?.GetGeometryAsset();
 
 
-        public void Setup(Node dstNode, Schema2SceneBuilder context)
-        {
-            _Target.Setup(dstNode, context);
-
-            // setup morphs here!
-        }
-
-        #endregion
-    }
-
-    class MeshContent : IRenderableContent
-    {
-        #region lifecycle
-
-        public MeshContent(MESHBUILDER mesh)
-        {
-            _Mesh = mesh;
-        }
-
         #endregion
         #endregion
-
-        #region data
-
-        private MESHBUILDER _Mesh;
-
-        #endregion
-
-        #region API
-
-        public MESHBUILDER GetGeometryAsset() => _Mesh;
-
-        public void Setup(Node dstNode, Schema2SceneBuilder context)
-        {
-            dstNode.Mesh = context.GetMesh(_Mesh);
-        }
-
-        #endregion
-    }
-
-    class LightContent : IContent
-    {
-        public void Setup(Node dstNode, Schema2SceneBuilder context)
-        {
-            throw new NotImplementedException();
-        }
     }
     }
 
 
-    class OrthographicCameraContent : IContent
+    partial class OrthographicCameraContent
     {
     {
         public OrthographicCameraContent(float xmag, float ymag, float znear, float zfar)
         public OrthographicCameraContent(float xmag, float ymag, float znear, float zfar)
         {
         {
@@ -284,14 +67,9 @@ namespace SharpGLTF.Scenes
         private float _YMag;
         private float _YMag;
         private float _ZNear;
         private float _ZNear;
         private float _ZFar;
         private float _ZFar;
-
-        public void Setup(Node dstNode, Schema2SceneBuilder context)
-        {
-            dstNode.WithOrthographicCamera(_XMag, _YMag, _ZNear, _ZFar);
-        }
     }
     }
 
 
-    class PerspectiveCameraContent : IContent
+    partial class PerspectiveCameraContent
     {
     {
         public PerspectiveCameraContent(float? aspectRatio, float fovy, float znear, float zfar = float.PositiveInfinity)
         public PerspectiveCameraContent(float? aspectRatio, float fovy, float znear, float zfar = float.PositiveInfinity)
         {
         {
@@ -305,10 +83,5 @@ namespace SharpGLTF.Scenes
         float _FovY;
         float _FovY;
         float _ZNear;
         float _ZNear;
         float _ZFar;
         float _ZFar;
-
-        public void Setup(Node dstNode, Schema2SceneBuilder context)
-        {
-            dstNode.WithPerspectiveCamera(_AspectRatio, _FovY, _ZNear, _ZFar);
-        }
     }
     }
 }
 }

+ 17 - 17
src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs

@@ -2,9 +2,11 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Text;
 using System.Text;
 
 
+using SCHEMA2SCENE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Scene>;
+
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
-    public class InstanceBuilder
+    public class InstanceBuilder : SCHEMA2SCENE
     {
     {
         #region lifecycle
         #region lifecycle
 
 
@@ -17,39 +19,37 @@ namespace SharpGLTF.Scenes
 
 
         #region data
         #region data
 
 
+        private string _Name;
+
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private readonly SceneBuilder _Parent;
         private readonly SceneBuilder _Parent;
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IContentRoot _Content;
+        private ContentTransformer _ContentTransformer;
 
 
         #endregion
         #endregion
 
 
         #region properties
         #region properties
 
 
-        internal IContentRoot Content
+        public string Name
         {
         {
-            get => _Content;
-            set => _Content = value;
+            get => _Name;
+            set => _Name = value;
         }
         }
 
 
-        #endregion
-
-        #region API
-
-        internal Geometry.IMeshBuilder<Materials.MaterialBuilder> GetGeometryAsset()
+        public ContentTransformer Content
         {
         {
-            return _Content?.GetGeometryAsset();
+            get => _ContentTransformer;
+            set => _ContentTransformer = value;
         }
         }
 
 
-        internal NodeBuilder GetArmatureAsset()
-        {
-            return _Content?.GetArmatureAsset();
-        }
+        #endregion
+
+        #region API
 
 
-        internal void Setup(Schema2.Scene dstScene, Schema2SceneBuilder context)
+        void SCHEMA2SCENE.Setup(Schema2.Scene dstScene, Schema2SceneBuilder context)
         {
         {
-            _Content.Setup(dstScene, context);
+            if (_ContentTransformer is SCHEMA2SCENE schema2scb) schema2scb.Setup(dstScene, context);
         }
         }
 
 
         #endregion
         #endregion

+ 87 - 57
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -31,6 +31,9 @@ namespace SharpGLTF.Scenes
         private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
         private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
 
 
         private Matrix4x4? _Matrix;
         private Matrix4x4? _Matrix;
+        private Animations.AnimatableProperty<Vector3> _Scale;
+        private Animations.AnimatableProperty<Quaternion> _Rotation;
+        private Animations.AnimatableProperty<Vector3> _Translation;
 
 
         #endregion
         #endregion
 
 
@@ -48,13 +51,16 @@ namespace SharpGLTF.Scenes
 
 
         #region properties - transform
         #region properties - transform
 
 
-        public bool HasAnimations => Scale?.Tracks.Count > 0 || Rotation?.Tracks.Count > 0 || Translation?.Tracks.Count > 0;
+        /// <summary>
+        /// Gets a value indicating whether this <see cref="NodeBuilder"/> has animations.
+        /// </summary>
+        public bool HasAnimations => (_Scale?.IsAnimated ?? false) || (_Rotation?.IsAnimated ?? false) || (_Translation?.IsAnimated ?? false);
 
 
-        public Animations.AnimatableProperty<Vector3> Scale { get; private set; }
+        public Animations.AnimatableProperty<Vector3> Scale => _Scale;
 
 
-        public Animations.AnimatableProperty<Quaternion> Rotation { get; private set; }
+        public Animations.AnimatableProperty<Quaternion> Rotation => _Rotation;
 
 
-        public Animations.AnimatableProperty<Vector3> Translation { get; private set; }
+        public Animations.AnimatableProperty<Vector3> Translation => _Translation;
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
@@ -64,18 +70,12 @@ namespace SharpGLTF.Scenes
             get => Transforms.AffineTransform.Evaluate(_Matrix, Scale?.Value, Rotation?.Value, Translation?.Value);
             get => Transforms.AffineTransform.Evaluate(_Matrix, Scale?.Value, Rotation?.Value, Translation?.Value);
             set
             set
             {
             {
-                if (value == Matrix4x4.Identity)
-                {
-                    _Matrix = null;
-                }
-                else
-                {
-                    _Matrix = value;
-                }
-
-                Scale = null;
-                Rotation = null;
-                Translation = null;
+                if (HasAnimations) { _DecomposeMatrix(value); return; }
+
+                _Matrix = value != Matrix4x4.Identity ? value : (Matrix4x4?)null;
+                _Scale = null;
+                _Rotation = null;
+                _Translation = null;
             }
             }
         }
         }
 
 
@@ -95,23 +95,9 @@ namespace SharpGLTF.Scenes
 
 
                 _Matrix = null;
                 _Matrix = null;
 
 
-                if (value.Scale != Vector3.One)
-                {
-                    if (Scale == null) Scale = new Animations.AnimatableProperty<Vector3>();
-                    Scale.Value = value.Scale;
-                }
-
-                if (value.Rotation != Quaternion.Identity)
-                {
-                    if (Rotation == null) Rotation = new Animations.AnimatableProperty<Quaternion>();
-                    Rotation.Value = value.Rotation;
-                }
-
-                if (value.Translation != Vector3.Zero)
-                {
-                    if (Translation == null) Translation = new Animations.AnimatableProperty<Vector3>();
-                    Translation.Value = value.Translation;
-                }
+                if (value.Scale != Vector3.One) UseScale().Value = value.Scale;
+                if (value.Rotation != Quaternion.Identity) UseRotation().Value = value.Rotation;
+                if (value.Translation != Vector3.Zero) UseTranslation().Value = value.Translation;
             }
             }
         }
         }
 
 
@@ -134,7 +120,7 @@ namespace SharpGLTF.Scenes
 
 
         #endregion
         #endregion
 
 
-        #region API
+        #region API - hierarchy
 
 
         public NodeBuilder CreateNode(string name = null)
         public NodeBuilder CreateNode(string name = null)
         {
         {
@@ -144,15 +130,54 @@ namespace SharpGLTF.Scenes
             return c;
             return c;
         }
         }
 
 
+        /// <summary>
+        /// Checks if the collection of joints can be used for skinning a mesh.
+        /// </summary>
+        /// <param name="joints">A collection of joints.</param>
+        /// <returns>True if the joints can be used for skinning.</returns>
+        public static bool IsValidArmature(IEnumerable<NodeBuilder> joints)
+        {
+            if (joints == null) return false;
+            if (!joints.Any()) return false;
+            if (joints.Any(item => item == null)) return false;
+
+            var root = joints.First().Root;
+
+            return joints.All(item => Object.ReferenceEquals(item.Root, root));
+        }
+
+        #endregion
+
+        #region API - transform
+
+        private void _DecomposeMatrix()
+        {
+            if (!_Matrix.HasValue) return;
+            if (_Matrix.Value == Matrix4x4.Identity) return;
+            _DecomposeMatrix(_Matrix.Value);
+            _Matrix = null;
+        }
+
+        private void _DecomposeMatrix(Matrix4x4 matrix)
+        {
+            var affine = Transforms.AffineTransform.Create(matrix);
+
+            UseScale().Value = affine.Scale;
+            UseRotation().Value = affine.Rotation;
+            UseTranslation().Value = affine.Translation;
+        }
+
         public Animations.AnimatableProperty<Vector3> UseScale()
         public Animations.AnimatableProperty<Vector3> UseScale()
         {
         {
-            if (Scale == null)
+            _DecomposeMatrix();
+
+            if (_Scale == null)
             {
             {
-                Scale = new Animations.AnimatableProperty<Vector3>();
-                Scale.Value = Vector3.One;
+                _Scale = new Animations.AnimatableProperty<Vector3>();
+                _Scale.Value = Vector3.One;
             }
             }
 
 
-            return Scale;
+            return _Scale;
         }
         }
 
 
         public Animations.CurveBuilder<Vector3> UseScale(string animationTrack)
         public Animations.CurveBuilder<Vector3> UseScale(string animationTrack)
@@ -162,13 +187,15 @@ namespace SharpGLTF.Scenes
 
 
         public Animations.AnimatableProperty<Quaternion> UseRotation()
         public Animations.AnimatableProperty<Quaternion> UseRotation()
         {
         {
-            if (Rotation == null)
+            _DecomposeMatrix();
+
+            if (_Rotation == null)
             {
             {
-                Rotation = new Animations.AnimatableProperty<Quaternion>();
-                Rotation.Value = Quaternion.Identity;
+                _Rotation = new Animations.AnimatableProperty<Quaternion>();
+                _Rotation.Value = Quaternion.Identity;
             }
             }
 
 
-            return Rotation;
+            return _Rotation;
         }
         }
 
 
         public Animations.CurveBuilder<Quaternion> UseRotation(string animationTrack)
         public Animations.CurveBuilder<Quaternion> UseRotation(string animationTrack)
@@ -178,13 +205,15 @@ namespace SharpGLTF.Scenes
 
 
         public Animations.AnimatableProperty<Vector3> UseTranslation()
         public Animations.AnimatableProperty<Vector3> UseTranslation()
         {
         {
-            if (Translation == null)
+            _DecomposeMatrix();
+
+            if (_Translation == null)
             {
             {
-                Translation = new Animations.AnimatableProperty<Vector3>();
-                Translation.Value = Vector3.Zero;
+                _Translation = new Animations.AnimatableProperty<Vector3>();
+                _Translation.Value = Vector3.Zero;
             }
             }
 
 
-            return Translation;
+            return _Translation;
         }
         }
 
 
         public Animations.CurveBuilder<Vector3> UseTranslation(string animationTrack)
         public Animations.CurveBuilder<Vector3> UseTranslation(string animationTrack)
@@ -218,17 +247,6 @@ namespace SharpGLTF.Scenes
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
             return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
         }
         }
 
 
-        public static bool IsValidArmature(IEnumerable<NodeBuilder> joints)
-        {
-            if (joints == null) return false;
-            if (!joints.Any()) return false;
-            if (joints.Any(item => item == null)) return false;
-
-            var root = joints.First().Root;
-
-            return joints.All(item => Object.ReferenceEquals(item.Root, root));
-        }
-
         #endregion
         #endregion
 
 
         #region With* API
         #region With* API
@@ -239,6 +257,18 @@ namespace SharpGLTF.Scenes
             return this;
             return this;
         }
         }
 
 
+        public NodeBuilder WithLocalScale(Vector3 scale)
+        {
+            this.UseScale().Value = scale;
+            return this;
+        }
+
+        public NodeBuilder WithLocalRotation(Quaternion rotation)
+        {
+            this.UseRotation().Value = rotation;
+            return this;
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 17 - 4
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -36,7 +36,7 @@ namespace SharpGLTF.Scenes
             // gather all MaterialBuilder unique instances
             // gather all MaterialBuilder unique instances
 
 
             var materialGroups = srcScene.Instances
             var materialGroups = srcScene.Instances
-                .Select(item => item.GetGeometryAsset())
+                .Select(item => item.Content?.GetGeometryAsset())
                 .Where(item => item != null)
                 .Where(item => item != null)
                 .SelectMany(item => item.Primitives)
                 .SelectMany(item => item.Primitives)
                 .Select(item => item.Material)
                 .Select(item => item.Material)
@@ -60,7 +60,7 @@ namespace SharpGLTF.Scenes
             // and group them by their vertex attribute layout.
             // and group them by their vertex attribute layout.
 
 
             var meshGroups = srcScene.Instances
             var meshGroups = srcScene.Instances
-            .Select(item => item.GetGeometryAsset())
+            .Select(item => item.Content?.GetGeometryAsset())
             .Where(item => item != null)
             .Where(item => item != null)
             .Distinct()
             .Distinct()
             .ToList()
             .ToList()
@@ -83,7 +83,7 @@ namespace SharpGLTF.Scenes
             // gather all NodeBuilder unique armatures
             // gather all NodeBuilder unique armatures
 
 
             var armatures = srcScene.Instances
             var armatures = srcScene.Instances
-                .Select(item => item.GetArmatureAsset())
+                .Select(item => item.Content?.GetArmatureAsset())
                 .Where(item => item != null)
                 .Where(item => item != null)
                 .Select(item => item.Root)
                 .Select(item => item.Root)
                 .Distinct()
                 .Distinct()
@@ -98,7 +98,11 @@ namespace SharpGLTF.Scenes
 
 
             // process instances
             // process instances
 
 
-            foreach (var inst in srcScene.Instances)
+            var schema2Instances = srcScene
+                .Instances
+                .OfType<IOperator<Scene>>();
+
+            foreach (var inst in schema2Instances)
             {
             {
                 inst.Setup(dstScene, this);
                 inst.Setup(dstScene, this);
             }
             }
@@ -132,6 +136,15 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         #endregion
         #endregion
+
+        #region types
+
+        public interface IOperator<T>
+        {
+            void Setup(T dst, Schema2SceneBuilder context);
+        }
+
+        #endregion
     }
     }
 
 
     public partial class SceneBuilder
     public partial class SceneBuilder

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

@@ -45,10 +45,10 @@ namespace SharpGLTF.Scenes
             return instance;
             return instance;
         }
         }
 
 
-        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshBindMatrix, params NodeBuilder[] joints)
+        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
         {
         {
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new SkinTransformer(mesh, meshBindMatrix, joints);
+            instance.Content = new SkinTransformer(mesh, meshWorldMatrix, joints);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
 
 

+ 87 - 0
src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs

@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using SharpGLTF.Schema2;
+
+using SCHEMA2NODE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Node>;
+using SCHEMA2SCENE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Scene>;
+
+namespace SharpGLTF.Scenes
+{
+    partial class StaticTransformer : SCHEMA2SCENE
+    {
+        void SCHEMA2SCENE.Setup(Scene dstScene, Schema2SceneBuilder context)
+        {
+            if (!(Content is SCHEMA2NODE schema2Target)) return;
+
+            var node = dstScene.CreateNode();
+            node.LocalMatrix = _WorldTransform;
+
+            schema2Target.Setup(node, context);
+        }
+    }
+
+    partial class NodeTransformer : SCHEMA2SCENE
+    {
+        void SCHEMA2SCENE.Setup(Scene dstScene, Schema2SceneBuilder context)
+        {
+            if (!(Content is SCHEMA2NODE schema2Target)) return;
+
+            var node = context.GetNode(_Node);
+
+            if (node == null) dstScene.CreateNode();
+
+            schema2Target.Setup(node, context);
+        }
+    }
+
+    partial class SkinTransformer : SCHEMA2SCENE
+    {
+        void SCHEMA2SCENE.Setup(Scene dstScene, Schema2SceneBuilder context)
+        {
+            if (!(Content is SCHEMA2NODE schema2Target)) return;
+
+            var skinnedMeshNode = dstScene.CreateNode();
+
+            if (_TargetBindMatrix.HasValue)
+            {
+                var dstNodes = new Node[_Joints.Count];
+
+                for (int i = 0; i < dstNodes.Length; ++i)
+                {
+                    var srcNode = _Joints[i];
+
+                    System.Diagnostics.Debug.Assert(!srcNode.Item2.HasValue);
+
+                    dstNodes[i] = context.GetNode(srcNode.Item1);
+                }
+
+                #if DEBUG
+                for (int i = 0; i < dstNodes.Length; ++i)
+                {
+                    var srcNode = _Joints[i];
+                    System.Diagnostics.Debug.Assert(dstNodes[i].WorldMatrix == srcNode.Item1.WorldMatrix);
+                }
+                #endif
+
+                skinnedMeshNode.WithSkinBinding(_TargetBindMatrix.Value, dstNodes);
+            }
+            else
+            {
+                var skinnedJoints = _Joints
+                .Select(j => (context.GetNode(j.Item1), j.Item2.Value))
+                .ToArray();
+
+                skinnedMeshNode.WithSkinBinding(skinnedJoints);
+            }
+
+            // set skeleton
+            // var root = _Joints[0].Item1.Root;
+            // skinnedMeshNode.Skin.Skeleton = context.GetNode(root);
+
+            schema2Target.Setup(skinnedMeshNode, context);
+        }
+    }
+}

+ 220 - 0
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
+
+namespace SharpGLTF.Scenes
+{
+    /// <summary>
+    /// Wraps a content object (usually a Mesh, a Camera or a light)
+    /// </summary>
+    public abstract class ContentTransformer
+    {
+        #region lifecycle
+
+        protected ContentTransformer(Object content)
+        {
+            Guard.NotNull(content, nameof(content));
+
+            _Content = content;
+        }
+
+        protected ContentTransformer(MESHBUILDER mesh)
+        {
+            Guard.NotNull(mesh, nameof(mesh));
+
+            _Content = new MeshContent(mesh);
+        }
+
+        #endregion
+
+        #region data
+
+        private Object _Content;
+
+        #endregion
+
+        #region properties
+
+        public Object Content => _Content;
+
+        #endregion
+
+        #region API
+
+        public virtual MESHBUILDER GetGeometryAsset() { return (_Content as IRenderableContent)?.GetGeometryAsset(); }
+
+        public abstract NodeBuilder GetArmatureAsset();
+
+        #endregion
+    }
+
+    public partial class StaticTransformer : ContentTransformer
+    {
+        #region lifecycle
+
+        public StaticTransformer(Object content, Matrix4x4 xform)
+            : base(content)
+        {
+            _WorldTransform = xform;
+        }
+
+        public StaticTransformer(MESHBUILDER mesh, Matrix4x4 xform)
+            : base(mesh)
+        {
+            _WorldTransform = xform;
+        }
+
+        #endregion
+
+        #region data
+
+        private Matrix4x4 _WorldTransform;
+
+        #endregion
+
+        #region properties
+
+        public Matrix4x4 WorldTransform
+        {
+            get => _WorldTransform;
+            set => _WorldTransform = value;
+        }
+
+        #endregion
+
+        #region API
+
+        public override NodeBuilder GetArmatureAsset() { return null; }
+
+        #endregion
+    }
+
+    public partial class NodeTransformer : ContentTransformer
+    {
+        #region lifecycle
+
+        public NodeTransformer(Object content, NodeBuilder node)
+            : base(content)
+        {
+            _Node = node;
+        }
+
+        public NodeTransformer(MESHBUILDER mesh, NodeBuilder node)
+            : base(mesh)
+        {
+            _Node = node;
+        }
+
+        #endregion
+
+        #region data
+
+        private NodeBuilder _Node;
+
+        #endregion
+
+        #region properties
+
+        public NodeBuilder Transform
+        {
+            get => _Node;
+            set => _Node = value;
+        }
+
+        #endregion
+
+        #region API
+
+        public override NodeBuilder GetArmatureAsset() { return _Node.Root; }
+
+        #endregion
+    }
+
+    public partial class SkinTransformer : ContentTransformer
+    {
+        #region lifecycle
+
+        public SkinTransformer(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, NodeBuilder[] joints)
+            : base(mesh)
+        {
+            SetJoints(meshWorldMatrix, joints);
+        }
+
+        public SkinTransformer(MESHBUILDER mesh, (NodeBuilder, Matrix4x4)[] joints)
+            : base(mesh)
+        {
+            SetJoints(joints);
+        }
+
+        #endregion
+
+        #region data
+
+        private Matrix4x4? _TargetBindMatrix;
+
+        // condition: all NodeBuilder objects must have the same root.
+        private readonly List<(NodeBuilder, Matrix4x4?)> _Joints = new List<(NodeBuilder, Matrix4x4?)>();
+
+        #endregion
+
+        #region API
+
+        private void SetJoints(Matrix4x4 meshWorldMatrix, NodeBuilder[] joints)
+        {
+            Guard.NotNull(joints, nameof(joints));
+            Guard.IsTrue(NodeBuilder.IsValidArmature(joints), nameof(joints));
+
+            _TargetBindMatrix = meshWorldMatrix;
+            _Joints.Clear();
+            _Joints.AddRange(joints.Select(item => (item, (Matrix4x4?)null)));
+        }
+
+        private void SetJoints((NodeBuilder, Matrix4x4)[] joints)
+        {
+            Guard.NotNull(joints, nameof(joints));
+            Guard.IsTrue(NodeBuilder.IsValidArmature(joints.Select(item => item.Item1)), nameof(joints));
+
+            _TargetBindMatrix = null;
+            _Joints.Clear();
+            _Joints.AddRange(joints.Select(item => (item.Item1, (Matrix4x4?)item.Item2)));
+        }
+
+        public (NodeBuilder, Matrix4x4)[] GetJointBindings()
+        {
+            var jb = new (NodeBuilder, Matrix4x4)[_Joints.Count];
+
+            for (int i = 0; i < jb.Length; ++i)
+            {
+                var j = _Joints[i].Item1;
+                var m = _Joints[i].Item2 ?? Transforms.SkinTransform.CalculateInverseBinding(_TargetBindMatrix ?? Matrix4x4.Identity, j.WorldMatrix);
+
+                jb[i] = (j, m);
+            }
+
+            return jb;
+        }
+
+        public override NodeBuilder GetArmatureAsset()
+        {
+            return _Joints
+                .Select(item => item.Item1.Root)
+                .Distinct()
+                .FirstOrDefault();
+        }
+
+        public Transforms.ITransform GetWorldTransformer(string animationTrack, float time)
+        {
+            var jb = GetJointBindings();
+
+            var ww = jb.Select(item => item.Item1.GetWorldMatrix(animationTrack, time)).ToArray();
+            var bb = jb.Select(item => item.Item2).ToArray();
+
+            return new Transforms.SkinTransform(bb, ww, default, false);
+        }
+
+        #endregion
+    }
+}

+ 6 - 0
src/SharpGLTF.Toolkit/Scenes/readme.md

@@ -35,6 +35,12 @@ scene.SaveGLB("scene.glb");
 In order to have a hierarchical tree of nodes, you use the NodeBuilder object, with
 In order to have a hierarchical tree of nodes, you use the NodeBuilder object, with
 which you can create standalone nodes, or whole skeleton armatures.
 which you can create standalone nodes, or whole skeleton armatures.
 
 
+```c#
+var root = new NodeBuilder("root");
+var child = root.CreateNode("child");
+
+```
+
 In this way, NodeBuilder armatures become just another asset, like a mesh or a material,
 In this way, NodeBuilder armatures become just another asset, like a mesh or a material,
 and a scene is just a collection of instances to be rendered.
 and a scene is just a collection of instances to be rendered.
 
 

+ 4 - 0
src/SharpGLTF.Toolkit/Schema2/LightExtensions.cs

@@ -22,6 +22,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="PunctualLight"/> instance.</returns>
         /// <returns>This <see cref="PunctualLight"/> instance.</returns>
         public static PunctualLight WithSpotCone(this PunctualLight light, float innerConeAngle, float outerConeAngle)
         public static PunctualLight WithSpotCone(this PunctualLight light, float innerConeAngle, float outerConeAngle)
         {
         {
+            Guard.NotNull(light, nameof(light));
+
             light.SetSpotCone(innerConeAngle, outerConeAngle);
             light.SetSpotCone(innerConeAngle, outerConeAngle);
             return light;
             return light;
         }
         }
@@ -44,6 +46,8 @@ namespace SharpGLTF.Schema2
         /// <returns>This <see cref="PunctualLight"/> instance.</returns>
         /// <returns>This <see cref="PunctualLight"/> instance.</returns>
         public static PunctualLight WithColor(this PunctualLight light, Vector3 color, float intensity = 1, float range = 0)
         public static PunctualLight WithColor(this PunctualLight light, Vector3 color, float intensity = 1, float range = 0)
         {
         {
+            Guard.NotNull(light, nameof(light));
+
             light.Color = color;
             light.Color = color;
             light.Intensity = intensity;
             light.Intensity = intensity;
             light.Range = range;
             light.Range = range;

+ 88 - 88
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -18,12 +18,12 @@ namespace SharpGLTF.Schema2
 
 
         public static Mesh CreateMesh(this ModelRoot root, IMeshBuilder<Materials.MaterialBuilder> mesh)
         public static Mesh CreateMesh(this ModelRoot root, IMeshBuilder<Materials.MaterialBuilder> mesh)
         {
         {
-            return root.CreateMeshes(mesh).First();
+            return root.CreateMeshes(mesh)[0];
         }
         }
 
 
         public static Mesh CreateMesh<TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, IMeshBuilder<TMaterial> mesh)
         public static Mesh CreateMesh<TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, IMeshBuilder<TMaterial> mesh)
         {
         {
-            return root.CreateMeshes<TMaterial>(materialEvaluator, mesh).First();
+            return root.CreateMeshes<TMaterial>(materialEvaluator, mesh)[0];
         }
         }
 
 
         public static IReadOnlyList<Mesh> CreateMeshes(this ModelRoot root, params IMeshBuilder<Materials.MaterialBuilder>[] meshBuilders)
         public static IReadOnlyList<Mesh> CreateMeshes(this ModelRoot root, params IMeshBuilder<Materials.MaterialBuilder>[] meshBuilders)
@@ -69,6 +69,8 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithIndicesAutomatic(this MeshPrimitive primitive, PrimitiveType primitiveType)
         public static MeshPrimitive WithIndicesAutomatic(this MeshPrimitive primitive, PrimitiveType primitiveType)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             primitive.DrawPrimitiveType = primitiveType;
             primitive.DrawPrimitiveType = primitiveType;
@@ -79,6 +81,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithIndicesAccessor(this MeshPrimitive primitive, PrimitiveType primitiveType, IReadOnlyList<Int32> values)
         public static MeshPrimitive WithIndicesAccessor(this MeshPrimitive primitive, PrimitiveType primitiveType, IReadOnlyList<Int32> values)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(values, nameof(values));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             // create an index buffer and fill it
             // create an index buffer and fill it
@@ -98,6 +103,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Single> values)
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Single> values)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(values, nameof(values));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             // create a vertex buffer and fill it
             // create a vertex buffer and fill it
@@ -115,6 +123,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector2> values)
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector2> values)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(values, nameof(values));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             // create a vertex buffer and fill it
             // create a vertex buffer and fill it
@@ -132,6 +143,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector3> values)
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector3> values)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(values, nameof(values));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             // create a vertex buffer and fill it
             // create a vertex buffer and fill it
@@ -150,6 +164,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector4> values)
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Vector4> values)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(values, nameof(values));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             // create a vertex buffer and fill it
             // create a vertex buffer and fill it
@@ -217,6 +234,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessors(this MeshPrimitive primitive, IEnumerable<Memory.MemoryAccessor> memAccessors)
         public static MeshPrimitive WithVertexAccessors(this MeshPrimitive primitive, IEnumerable<Memory.MemoryAccessor> memAccessors)
         {
         {
+            Guard.NotNull(memAccessors, nameof(memAccessors));
+            Guard.IsTrue(memAccessors.All(item => item != null), nameof(memAccessors));
+
             foreach (var va in memAccessors) primitive.WithVertexAccessor(va);
             foreach (var va in memAccessors) primitive.WithVertexAccessor(va);
 
 
             return primitive;
             return primitive;
@@ -224,6 +244,9 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, Memory.MemoryAccessor memAccessor)
         public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, Memory.MemoryAccessor memAccessor)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.NotNull(memAccessor, nameof(memAccessor));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             primitive.SetVertexAccessor(memAccessor.Attribute.Name, root.CreateVertexAccessor(memAccessor));
             primitive.SetVertexAccessor(memAccessor.Attribute.Name, root.CreateVertexAccessor(memAccessor));
@@ -233,6 +256,8 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithIndicesAccessor(this MeshPrimitive primitive, PrimitiveType primitiveType, Memory.MemoryAccessor memAccessor)
         public static MeshPrimitive WithIndicesAccessor(this MeshPrimitive primitive, PrimitiveType primitiveType, Memory.MemoryAccessor memAccessor)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+
             var root = primitive.LogicalParent.LogicalParent;
             var root = primitive.LogicalParent.LogicalParent;
 
 
             var accessor = root.CreateAccessor();
             var accessor = root.CreateAccessor();
@@ -251,6 +276,8 @@ namespace SharpGLTF.Schema2
 
 
         public static MeshPrimitive WithMaterial(this MeshPrimitive primitive, Material material)
         public static MeshPrimitive WithMaterial(this MeshPrimitive primitive, Material material)
         {
         {
+            Guard.NotNull(primitive, nameof(primitive));
+
             primitive.Material = material;
             primitive.Material = material;
             return primitive;
             return primitive;
         }
         }
@@ -259,6 +286,48 @@ namespace SharpGLTF.Schema2
 
 
         #region evaluation
         #region evaluation
 
 
+        public static int GetPrimitiveVertexSize(this PrimitiveType ptype)
+        {
+            switch (ptype)
+            {
+                case PrimitiveType.POINTS: return 1;
+                case PrimitiveType.LINES: return 2;
+                case PrimitiveType.LINE_LOOP: return 2;
+                case PrimitiveType.LINE_STRIP: return 2;
+                case PrimitiveType.TRIANGLES: return 3;
+                case PrimitiveType.TRIANGLE_FAN: return 3;
+                case PrimitiveType.TRIANGLE_STRIP: return 3;
+                default: throw new NotImplementedException();
+            }
+        }
+
+        public static IEnumerable<int> GetPointIndices(this MeshPrimitive primitive)
+        {
+            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 1) return Enumerable.Empty<int>();
+
+            if (primitive.IndexAccessor == null) return Enumerable.Range(0, primitive.GetVertexAccessor("POSITION").Count);
+
+            return primitive.IndexAccessor.AsIndicesArray().Select(item => (int)item);
+        }
+
+        public static IEnumerable<(int, int)> GetLineIndices(this MeshPrimitive primitive)
+        {
+            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 2) return Enumerable.Empty<(int, int)>();
+
+            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetLinesIndices(primitive.GetVertexAccessor("POSITION").Count);
+
+            return primitive.DrawPrimitiveType.GetLinesIndices(primitive.IndexAccessor.AsIndicesArray());
+        }
+
+        public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
+        {
+            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 3) return Enumerable.Empty<(int, int, int)>();
+
+            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
+
+            return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
+        }
+
         public static IEnumerable<(IVertexBuilder, Material)> EvaluatePoints(this Mesh mesh, MESHXFORM xform = null)
         public static IEnumerable<(IVertexBuilder, Material)> EvaluatePoints(this Mesh mesh, MESHXFORM xform = null)
         {
         {
             if (mesh == null) return Enumerable.Empty<(IVertexBuilder, Material)>();
             if (mesh == null) return Enumerable.Empty<(IVertexBuilder, Material)>();
@@ -275,7 +344,6 @@ namespace SharpGLTF.Schema2
             if (!points.Any()) yield break;
             if (!points.Any()) yield break;
 
 
             var vertices = prim.GetVertexColumns(xform);
             var vertices = prim.GetVertexColumns(xform);
-
             var vtype = vertices.GetCompatibleVertexType();
             var vtype = vertices.GetCompatibleVertexType();
 
 
             foreach (var t in points)
             foreach (var t in points)
@@ -302,7 +370,6 @@ namespace SharpGLTF.Schema2
             if (!lines.Any()) yield break;
             if (!lines.Any()) yield break;
 
 
             var vertices = prim.GetVertexColumns(xform);
             var vertices = prim.GetVertexColumns(xform);
-
             var vtype = vertices.GetCompatibleVertexType();
             var vtype = vertices.GetCompatibleVertexType();
 
 
             foreach (var t in lines)
             foreach (var t in lines)
@@ -330,7 +397,6 @@ namespace SharpGLTF.Schema2
             if (!triangles.Any()) yield break;
             if (!triangles.Any()) yield break;
 
 
             var vertices = prim.GetVertexColumns(xform);
             var vertices = prim.GetVertexColumns(xform);
-
             var vtype = vertices.GetCompatibleVertexType();
             var vtype = vertices.GetCompatibleVertexType();
 
 
             foreach (var t in triangles)
             foreach (var t in triangles)
@@ -343,25 +409,28 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this Mesh mesh)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this Mesh mesh, MESHXFORM xform = null)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
             if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)>();
             if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)>();
 
 
-            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM, TvS>());
+            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM, TvS>(xform));
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this MeshPrimitive prim)
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> EvaluateTriangles<TvG, TvM, TvS>(this MeshPrimitive prim, MESHXFORM xform = null)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
             if (prim == null) yield break;
             if (prim == null) yield break;
+            if (xform != null && !xform.Visible) yield break;
 
 
-            var vertices = prim.GetVertexColumns();
             var triangles = prim.GetTriangleIndices();
             var triangles = prim.GetTriangleIndices();
+            if (!triangles.Any()) yield break;
+
+            var vertices = prim.GetVertexColumns(xform);
 
 
             bool hasNormals = vertices.Normals != null;
             bool hasNormals = vertices.Normals != null;
 
 
@@ -384,77 +453,6 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this Mesh mesh, MESHXFORM xform)
-            where TvG : struct, IVertexGeometry
-            where TvM : struct, IVertexMaterial
-        {
-            if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)>();
-
-            return mesh.Primitives.SelectMany(item => item.EvaluateTriangles<TvG, TvM>(xform));
-        }
-
-        public static IEnumerable<(VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, VertexBuilder<TvG, TvM, VertexEmpty>, Material)> EvaluateTriangles<TvG, TvM>(this MeshPrimitive prim, MESHXFORM xform)
-            where TvG : struct, IVertexGeometry
-            where TvM : struct, IVertexMaterial
-        {
-            if (prim == null) yield break;
-            if (xform == null || !xform.Visible) yield break;
-
-            var vertices = prim.GetVertexColumns(xform);
-            var triangles = prim.GetTriangleIndices();
-
-            foreach (var t in triangles)
-            {
-                var a = vertices.GetVertex<TvG, TvM>(t.Item1);
-                var b = vertices.GetVertex<TvG, TvM>(xform.FlipFaces ? t.Item3 : t.Item2);
-                var c = vertices.GetVertex<TvG, TvM>(xform.FlipFaces ? t.Item2 : t.Item3);
-
-                yield return ((a.Geometry, a.Material), (b.Geometry, b.Material), (c.Geometry, c.Material), prim.Material);
-            }
-        }
-
-        public static IEnumerable<int> GetPointIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 1) return Enumerable.Empty<int>();
-
-            if (primitive.IndexAccessor == null) return Enumerable.Range(0, primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.IndexAccessor.AsIndicesArray().Select(item => (int)item);
-        }
-
-        public static IEnumerable<(int, int)> GetLineIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 2) return Enumerable.Empty<(int, int)>();
-
-            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetLinesIndices(primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.DrawPrimitiveType.GetLinesIndices(primitive.IndexAccessor.AsIndicesArray());
-        }
-
-        public static IEnumerable<(int, int, int)> GetTriangleIndices(this MeshPrimitive primitive)
-        {
-            if (primitive == null || primitive.DrawPrimitiveType.GetPrimitiveVertexSize() != 3) return Enumerable.Empty<(int, int, int)>();
-
-            if (primitive.IndexAccessor == null) return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.GetVertexAccessor("POSITION").Count);
-
-            return primitive.DrawPrimitiveType.GetTrianglesIndices(primitive.IndexAccessor.AsIndicesArray());
-        }
-
-        public static int GetPrimitiveVertexSize(this PrimitiveType ptype)
-        {
-            switch (ptype)
-            {
-                case PrimitiveType.POINTS: return 1;
-                case PrimitiveType.LINES: return 2;
-                case PrimitiveType.LINE_LOOP: return 2;
-                case PrimitiveType.LINE_STRIP: return 2;
-                case PrimitiveType.TRIANGLES: return 3;
-                case PrimitiveType.TRIANGLE_FAN: return 3;
-                case PrimitiveType.TRIANGLE_STRIP: return 3;
-                default: throw new NotImplementedException();
-            }
-        }
-
         #endregion
         #endregion
 
 
         #region mesh conversion
         #region mesh conversion
@@ -554,16 +552,18 @@ namespace SharpGLTF.Schema2
             where TvS : struct, IVertexSkinning
             where TvS : struct, IVertexSkinning
         {
         {
             Guard.NotNull(meshBuilder, nameof(meshBuilder));
             Guard.NotNull(meshBuilder, nameof(meshBuilder));
+            Guard.NotNull(materialFunc, nameof(materialFunc));
 
 
             if (srcMesh == null) return;
             if (srcMesh == null) return;
 
 
-            Guard.NotNull(materialFunc, nameof(materialFunc));
-
             foreach (var srcPrim in srcMesh.Primitives)
             foreach (var srcPrim in srcMesh.Primitives)
             {
             {
-                var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
+                if (srcPrim != null) continue;
+
+                var dstMat = materialFunc(srcPrim.Material);
+                var dstPrim = meshBuilder.UsePrimitive(dstMat);
 
 
-                foreach (var tri in srcPrim.EvaluateTriangles<TvG, TvM, TvS>())
+                foreach (var tri in srcPrim.EvaluateTriangles<TvG, TvM, TvS>(null))
                 {
                 {
                     dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
                     dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
                 }
                 }
@@ -578,11 +578,11 @@ namespace SharpGLTF.Schema2
         /// <typeparam name="TvG">A subtype of <see cref="IVertexGeometry"/></typeparam>
         /// <typeparam name="TvG">A subtype of <see cref="IVertexGeometry"/></typeparam>
         /// <typeparam name="TvM">A subtype of <see cref="IVertexMaterial"/></typeparam>
         /// <typeparam name="TvM">A subtype of <see cref="IVertexMaterial"/></typeparam>
         /// <param name="srcScene">The source <see cref="Scene"/> to evaluate.</param>
         /// <param name="srcScene">The source <see cref="Scene"/> to evaluate.</param>
+        /// <param name="materialFunc">A function to convert <see cref="Material"/> into <typeparamref name="TMaterial"/>.</param>
         /// <param name="animation">The source <see cref="Animation"/> to evaluate.</param>
         /// <param name="animation">The source <see cref="Animation"/> to evaluate.</param>
         /// <param name="time">A time point, in seconds, within <paramref name="animation"/>.</param>
         /// <param name="time">A time point, in seconds, within <paramref name="animation"/>.</param>
-        /// <param name="materialFunc">A function to convert <see cref="Material"/> into <typeparamref name="TMaterial"/>.</param>
         /// <returns>A new <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/> containing the evaluated geometry.</returns>
         /// <returns>A new <see cref="MeshBuilder{TMaterial, TvG, TvM, TvS}"/> containing the evaluated geometry.</returns>
-        public static MeshBuilder<TMaterial, TvG, TvM, VertexEmpty> ToStaticMeshBuilder<TMaterial, TvG, TvM>(this Scene srcScene, Animation animation, float time, Func<Material, TMaterial> materialFunc)
+        public static MeshBuilder<TMaterial, TvG, TvM, VertexEmpty> ToStaticMeshBuilder<TMaterial, TvG, TvM>(this Scene srcScene, Func<Material, TMaterial> materialFunc, Animation animation, float time)
             where TvG : struct, IVertexGeometry
             where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvM : struct, IVertexMaterial
         {
         {
@@ -624,7 +624,7 @@ namespace SharpGLTF.Schema2
                 return materials[srcMaterial] = dstMaterial;
                 return materials[srcMaterial] = dstMaterial;
             }
             }
 
 
-            return srcScene.ToStaticMeshBuilder<Materials.MaterialBuilder, TvG, TvM>(animation, time, convertMaterial);
+            return srcScene.ToStaticMeshBuilder<Materials.MaterialBuilder, TvG, TvM>(convertMaterial, animation, time);
         }
         }
 
 
         public static IMeshBuilder<Materials.MaterialBuilder> ToMeshBuilder(this Mesh srcMesh)
         public static IMeshBuilder<Materials.MaterialBuilder> ToMeshBuilder(this Mesh srcMesh)
@@ -675,7 +675,7 @@ namespace SharpGLTF.Schema2
             foreach (var srcTri in srcMesh.EvaluateLines())
             foreach (var srcTri in srcMesh.EvaluateLines())
             {
             {
                 var dstPrim = GetPrimitive(srcTri.Item3, 2);
                 var dstPrim = GetPrimitive(srcTri.Item3, 2);
-                dstPrim.AddLine(srcTri.Item1,srcTri.Item2);
+                dstPrim.AddLine(srcTri.Item1, srcTri.Item2);
             }
             }
 
 
             foreach (var srcTri in srcMesh.EvaluateTriangles())
             foreach (var srcTri in srcMesh.EvaluateTriangles())

+ 1 - 1
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -271,7 +271,7 @@ namespace SharpGLTF.Schema2
 
 
             var xform = node.GetMeshWorldTransform(animation, time);
             var xform = node.GetMeshWorldTransform(animation, time);
 
 
-            return mesh.EvaluateTriangles<TvG, TvM>(xform);
+            return mesh.EvaluateTriangles<TvG, TvM, VertexEmpty>(xform);
         }
         }
 
 
         public static Scenes.SceneBuilder ToSceneBuilder(this Scene srcScene)
         public static Scenes.SceneBuilder ToSceneBuilder(this Scene srcScene)