Jelajahi Sumber

SceneBuilder: refactored some APIs to improve setting name and Extras.
+docs.

Vicente Penades 4 tahun lalu
induk
melakukan
36ee40e3f4
30 mengubah file dengan 431 tambahan dan 224 penghapusan
  1. 6 0
      src/Shared/Guard.cs
  2. 3 3
      src/Shared/_Extensions.cs
  3. 46 0
      src/SharpGLTF.Core/IO/JsonContent.Impl.cs
  4. 14 8
      src/SharpGLTF.Core/IO/JsonContent.cs
  5. 15 5
      src/SharpGLTF.Core/Memory/MemoryImage.cs
  6. 6 6
      src/SharpGLTF.Core/Schema2/gltf.Images.cs
  7. 6 5
      src/SharpGLTF.Core/Schema2/gltf.LogicalChildOfRoot.cs
  8. 4 2
      src/SharpGLTF.Core/Schema2/gltf.Material.cs
  9. 2 1
      src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs
  10. 1 1
      src/SharpGLTF.Core/Validation/ValidationContext.Guards.cs
  11. 9 5
      src/SharpGLTF.Toolkit/BaseBuilder.cs
  12. 3 57
      src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs
  13. 4 1
      src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs
  14. 1 1
      src/SharpGLTF.Toolkit/Geometry/VertexBufferColumns.cs
  15. 1 1
      src/SharpGLTF.Toolkit/Materials/ChannelBuilder.cs
  16. 3 0
      src/SharpGLTF.Toolkit/Materials/ImageBuilder.cs
  17. 3 0
      src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs
  18. 42 9
      src/SharpGLTF.Toolkit/Scenes/CameraBuilder.cs
  19. 0 12
      src/SharpGLTF.Toolkit/Scenes/Content.Schema2.cs
  20. 26 35
      src/SharpGLTF.Toolkit/Scenes/Content.cs
  21. 38 4
      src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs
  22. 18 9
      src/SharpGLTF.Toolkit/Scenes/LightBuilder.cs
  23. 12 4
      src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs
  24. 51 38
      src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs
  25. 2 0
      src/SharpGLTF.Toolkit/Scenes/Transformers.Schema2.cs
  26. 84 11
      src/SharpGLTF.Toolkit/Scenes/Transformers.cs
  27. 2 2
      tests/SharpGLTF.Tests/IO/JsonContentTests.cs
  28. 1 1
      tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs
  29. 1 1
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs
  30. 27 2
      tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs

+ 6 - 0
src/Shared/Guard.cs

@@ -245,6 +245,12 @@ namespace SharpGLTF
             foreach (var val in collection) Guard.NotNull(val, parameterName, message);
             foreach (var val in collection) Guard.NotNull(val, parameterName, message);
         }
         }
 
 
+        public static void AreTrue(IEnumerable<bool> collection, string parameterName, string message = "")
+        {
+            Guard.NotNull(collection, nameof(collection));
+            foreach (var val in collection) Guard.IsTrue(val, parameterName, message);
+        }
+
         public static void MustBeEqualTo<TValue>(IEnumerable<TValue> collection, TValue expected, string parameterName)
         public static void MustBeEqualTo<TValue>(IEnumerable<TValue> collection, TValue expected, string parameterName)
             where TValue : IComparable<TValue>
             where TValue : IComparable<TValue>
         {
         {

+ 3 - 3
src/Shared/_Extensions.cs

@@ -178,12 +178,12 @@ namespace SharpGLTF
             return dst;
             return dst;
         }
         }
 
 
-        internal static bool IsValid(this in Matrix4x4 matrix, bool mustDecompose = true, bool mustInvert = true, bool mustPositiveDeterminant = false)
+        internal static bool IsValid(this in Matrix4x4 matrix, bool mustInvert = true, bool mustDecompose = true, bool mustPositiveDeterminant = false)
         {
         {
             if (!matrix._IsFinite()) return false;
             if (!matrix._IsFinite()) return false;
-            if (mustDecompose && !Matrix4x4.Decompose(matrix, out _, out _, out _)) return false;
             if (mustInvert && !Matrix4x4.Invert(matrix, out _)) return false;
             if (mustInvert && !Matrix4x4.Invert(matrix, out _)) return false;
-            if (mustPositiveDeterminant && matrix.GetDeterminant() < 0) return false;
+            if (mustDecompose && !Matrix4x4.Decompose(matrix, out _, out _, out _)) return false;
+            if (mustPositiveDeterminant && matrix.GetDeterminant() <= 0) return false;
 
 
             return true;
             return true;
         }
         }

+ 46 - 0
src/SharpGLTF.Core/IO/JsonContent.Impl.cs

@@ -260,6 +260,52 @@ namespace SharpGLTF.IO
 
 
             return false;
             return false;
         }
         }
+
+        /// <summary>
+        /// Calculates the hash of a json DOM structure, without taking values into account
+        /// </summary>
+        /// <param name="x">the input json DOM structure</param>
+        /// <returns>A hash code</returns>
+        /// <remarks>
+        /// Theory says that two objects that are considered equal must have the same hash.
+        /// This means that we cannot use the hashes of the values because two equivalent
+        /// values (int 5) and (float 5.0f)  might have different hashes.
+        /// </remarks>
+        public static int GetStructureHashCode(Object x)
+        {
+            if (x == null) return 0;
+
+            if (x is IConvertible xval) { return 1; }
+
+            if (x is IReadOnlyList<object> xarr)
+            {
+                int h = xarr.Count;
+
+                for (int i = 0; i < xarr.Count; ++i)
+                {
+                    h ^= GetStructureHashCode(xarr[i]);
+                    h *= 17;
+                }
+
+                return h;
+            }
+
+            if (x is IReadOnlyDictionary<string, object> xdic)
+            {
+                int h = xdic.Count;
+
+                foreach (var kvp in xdic.OrderBy(item => item.Key))
+                {
+                    h ^= kvp.Key.GetHashCode();
+                    h ^= GetStructureHashCode(kvp.Value);
+                    h *= 17;
+                }
+
+                return h;
+            }
+
+            throw new ArgumentException($"Invalid type: {x.GetType()}", nameof(x));
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 14 - 8
src/SharpGLTF.Core/IO/JsonContent.cs

@@ -47,20 +47,12 @@ namespace SharpGLTF.IO
 
 
         public static implicit operator JsonContent(Double value) { return new JsonContent(value); }
         public static implicit operator JsonContent(Double value) { return new JsonContent(value); }
 
 
-        public static implicit operator JsonContent(Object[] value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(List<Object> value) { return new JsonContent(value); }
-
-        public static implicit operator JsonContent(Dictionary<String, Object> value) { return new JsonContent(value); }
-
         public static JsonContent CreateFrom(IConvertible value) { return new JsonContent(value); }
         public static JsonContent CreateFrom(IConvertible value) { return new JsonContent(value); }
         public static JsonContent CreateFrom(IList value) { return new JsonContent(value); }
         public static JsonContent CreateFrom(IList value) { return new JsonContent(value); }
         public static JsonContent CreateFrom(IDictionary value) { return new JsonContent(value); }
         public static JsonContent CreateFrom(IDictionary value) { return new JsonContent(value); }
 
 
         internal static JsonContent _Wrap(Object value) { return new JsonContent(value); }
         internal static JsonContent _Wrap(Object value) { return new JsonContent(value); }
 
 
-        public JsonContent DeepClone() { return new JsonContent(_Content);  }
-
         private JsonContent(Object value)
         private JsonContent(Object value)
         {
         {
             _Content = value == null ? null : _JsonStaticUtils.Serialize(value);
             _Content = value == null ? null : _JsonStaticUtils.Serialize(value);
@@ -68,6 +60,8 @@ namespace SharpGLTF.IO
                 _Content = null;
                 _Content = null;
         }
         }
 
 
+        public JsonContent DeepClone() { return new JsonContent(_Content); }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -75,6 +69,18 @@ namespace SharpGLTF.IO
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private readonly Object _Content;
         private readonly Object _Content;
 
 
+        public override int GetHashCode()
+        {
+            // until I figure a correct way of handling this...
+            throw new NotSupportedException("Do not use");
+        }
+
+        public override bool Equals(object obj)
+        {
+            // until I figure a correct way of handling this...
+            throw new NotSupportedException($"Use {nameof(JsonContent.AreEqualByContent)} instead.");
+        }
+
         /// <summary>
         /// <summary>
         /// Compares two <see cref="JsonContent"/> objects for equality.
         /// Compares two <see cref="JsonContent"/> objects for equality.
         /// </summary>
         /// </summary>

+ 15 - 5
src/SharpGLTF.Core/Memory/MemoryImage.cs

@@ -26,6 +26,7 @@ namespace SharpGLTF.Memory
             if (IsDds) return $"DDS {_Image.Count}ᴮʸᵗᵉˢ";
             if (IsDds) return $"DDS {_Image.Count}ᴮʸᵗᵉˢ";
             if (IsWebp) return $"WEBP {_Image.Count}ᴮʸᵗᵉˢ";
             if (IsWebp) return $"WEBP {_Image.Count}ᴮʸᵗᵉˢ";
             if (IsKtx2) return $"KTX2 {_Image.Count}ᴮʸᵗᵉˢ";
             if (IsKtx2) return $"KTX2 {_Image.Count}ᴮʸᵗᵉˢ";
+
             return "Undefined";
             return "Undefined";
         }
         }
 
 
@@ -179,13 +180,15 @@ namespace SharpGLTF.Memory
         public ReadOnlyMemory<Byte> Content => _Image;
         public ReadOnlyMemory<Byte> Content => _Image;
 
 
         /// <summary>
         /// <summary>
-        /// Gets the source path of this image, or null.<br/>
-        /// ⚠️ DO NOT USE AS AN IMAGE ID ⚠️
+        /// Gets the source path of this image, or <b>null</b>.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Not all images are expected to have a source path.<br/>
         /// Not all images are expected to have a source path.<br/>
         /// Specifically images embedded in a GLB file or encoded with BASE64
         /// Specifically images embedded in a GLB file or encoded with BASE64
-        /// will not have any source path at all.
+        /// will not have any source path at all.<br/>
+        /// So if your code depends on images having a path, it might crash
+        /// on gltf files with embedded images.
         /// </remarks>
         /// </remarks>
         public string SourcePath => _SourcePathHint;
         public string SourcePath => _SourcePathHint;
 
 
@@ -222,7 +225,14 @@ namespace SharpGLTF.Memory
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether this object represents a valid image.
         /// Gets a value indicating whether this object represents a valid image.
         /// </summary>
         /// </summary>
-        public bool IsValid => _IsImage(_Image);
+        public bool IsValid
+        {
+            get
+            {
+                try { _Verify(this, string.Empty); return true; }
+                catch { return false; }
+            }
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets the most appropriate extension string for this image.
         /// Gets the most appropriate extension string for this image.
@@ -262,7 +272,7 @@ namespace SharpGLTF.Memory
 
 
         #region API
         #region API
 
 
-        public static void Verify(MemoryImage image, string paramName)
+        internal static void _Verify(MemoryImage image, string paramName)
         {
         {
             Guard.IsTrue(_IsImage(image._Image), paramName, $"{paramName} must be a valid image byte stream.");
             Guard.IsTrue(_IsImage(image._Image), paramName, $"{paramName} must be a valid image byte stream.");
 
 

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

@@ -107,7 +107,7 @@ namespace SharpGLTF.Schema2
         /// <param name="content">A <see cref="Byte"/> array containing a PNG or JPEG image.</param>
         /// <param name="content">A <see cref="Byte"/> array containing a PNG or JPEG image.</param>
         private void SetSatelliteContent(Memory.MemoryImage content)
         private void SetSatelliteContent(Memory.MemoryImage content)
         {
         {
-            Memory.MemoryImage.Verify(content, nameof(content));
+            Memory.MemoryImage._Verify(content, nameof(content));
 
 
             _DiscardContent();
             _DiscardContent();
 
 
@@ -187,7 +187,7 @@ namespace SharpGLTF.Schema2
             if (!_SatelliteContent.HasValue) { _WriteAsBufferView(); return; }
             if (!_SatelliteContent.HasValue) { _WriteAsBufferView(); return; }
 
 
             var imimg = _SatelliteContent.Value;
             var imimg = _SatelliteContent.Value;
-            Memory.MemoryImage.Verify(imimg, nameof(imimg));
+            Memory.MemoryImage._Verify(imimg, nameof(imimg));
 
 
             _uri = imimg.ToMime64();
             _uri = imimg.ToMime64();
             _mimeType = imimg.MimeType;
             _mimeType = imimg.MimeType;
@@ -207,7 +207,7 @@ namespace SharpGLTF.Schema2
             }
             }
 
 
             var imimg = _SatelliteContent.Value;
             var imimg = _SatelliteContent.Value;
-            Memory.MemoryImage.Verify(imimg, nameof(imimg));
+            Memory.MemoryImage._Verify(imimg, nameof(imimg));
 
 
             satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
             satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
 
 
@@ -223,7 +223,7 @@ namespace SharpGLTF.Schema2
             Guard.IsTrue(_bufferView.HasValue, nameof(_bufferView));
             Guard.IsTrue(_bufferView.HasValue, nameof(_bufferView));
 
 
             var imimg = this.Content;
             var imimg = this.Content;
-            Memory.MemoryImage.Verify(imimg, nameof(imimg));
+            Memory.MemoryImage._Verify(imimg, nameof(imimg));
 
 
             _uri = null;
             _uri = null;
             _mimeType = imimg.MimeType;
             _mimeType = imimg.MimeType;
@@ -262,7 +262,7 @@ namespace SharpGLTF.Schema2
                 validate.IsTrue("BufferView", bv.IsDataBuffer, "is a GPU target.");
                 validate.IsTrue("BufferView", bv.IsDataBuffer, "is a GPU target.");
             }
             }
 
 
-            Memory.MemoryImage.Verify(Content, nameof(Content));
+            Memory.MemoryImage._Verify(Content, nameof(Content));
         }
         }
 
 
         #endregion
         #endregion
@@ -293,7 +293,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Image"/> instance.</returns>
         /// <returns>A <see cref="Image"/> instance.</returns>
         public Image UseImage(Memory.MemoryImage imageContent)
         public Image UseImage(Memory.MemoryImage imageContent)
         {
         {
-            Memory.MemoryImage.Verify(imageContent, nameof(imageContent));
+            Memory.MemoryImage._Verify(imageContent, nameof(imageContent));
 
 
             // If we find an image with the same content, let's reuse it.
             // If we find an image with the same content, let's reuse it.
             foreach (var img in this.LogicalImages)
             foreach (var img in this.LogicalImages)

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

@@ -14,13 +14,14 @@ namespace SharpGLTF.Schema2
         #region properties
         #region properties
 
 
         /// <summary>
         /// <summary>
-        /// Display text name, or null.<br/>⚠️ DO NOT USE AS AN OBJECT ID ⚠️
+        /// Gets or sets the display text name, or null.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
-        /// glTF does not define any name ruling for object names.
-        /// This means that names can be null or non unique.
-        /// So don't use names for anything other than object name display.
-        /// Use lookup tables instead.
+        /// glTF does not define any ruling for object names.<br/>
+        /// This means that names can be null or non unique.<br/>
+        /// So don't use <see cref="Name"/> for anything other than object name display.<br/>
+        /// If you need to reference objects by some ID, use lookup tables instead.
         /// </remarks>
         /// </remarks>
         public String Name
         public String Name
         {
         {

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

@@ -27,7 +27,8 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the <see cref="AlphaCutoff"/> value for <see cref="Alpha"/> = <see cref="AlphaMode.MASK"/>.
+        /// Gets or sets the <see cref="AlphaCutoff"/> value.<br/>
+        /// It needs to be used in combination with <see cref="Alpha"/> = <see cref="AlphaMode.MASK"/>.
         /// </summary>
         /// </summary>
         public Single AlphaCutoff
         public Single AlphaCutoff
         {
         {
@@ -36,7 +37,8 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets a value indicating whether this <see cref="Material"/> will render as Double Sided.
+        /// Gets or sets a value indicating whether this <see cref="Material"/> will render as Double Sided.<br/>
+        /// Default value: False
         /// </summary>
         /// </summary>
         public Boolean DoubleSided
         public Boolean DoubleSided
         {
         {

+ 2 - 1
src/SharpGLTF.Core/Schema2/gltf.MaterialChannel.cs

@@ -4,7 +4,8 @@ using System.Numerics;
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a material sub-channel, which usually contains a texture.
+    /// Represents a material sub-channel, which usually contains a texture.<br/>
+    /// Use <see cref="Material.Channels"/> and <see cref="Material.FindChannel(string)"/> to access it.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     /// This structure is not part of the gltf schema,
     /// This structure is not part of the gltf schema,

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

@@ -271,7 +271,7 @@ namespace SharpGLTF.Validation
 
 
         public OUTTYPE IsMatrix(PARAMNAME pname, in System.Numerics.Matrix4x4 matrix, bool mustDecompose = true, bool mustInvert = true)
         public OUTTYPE IsMatrix(PARAMNAME pname, in System.Numerics.Matrix4x4 matrix, bool mustDecompose = true, bool mustInvert = true)
         {
         {
-            if (!matrix.IsValid(mustDecompose, mustInvert)) _DataThrow(pname, "Invalid Matrix");
+            if (!matrix.IsValid(mustInvert, mustDecompose)) _DataThrow(pname, "Invalid Matrix");
             return this;
             return this;
         }
         }
 
 

+ 9 - 5
src/SharpGLTF.Toolkit/BaseBuilder.cs

@@ -32,16 +32,20 @@ namespace SharpGLTF
         #region data
         #region data
 
 
         /// <summary>
         /// <summary>
-        /// Display text name, or null.<br/>⚠️ DO NOT USE AS AN OBJECT ID ⚠️
+        /// Gets or sets the display text name, or null.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
-        /// glTF does not define any name ruling for object names.
-        /// This means that names can be null or non unique.
-        /// So don't use names for anything other than object name display.
-        /// Use lookup tables instead.
+        /// glTF does not define any ruling for object names.<br/>
+        /// This means that names can be null or non unique.<br/>
+        /// So don't use <see cref="Name"/> for anything other than object name display.<br/>
+        /// If you need to reference objects by some ID, use lookup tables instead.
         /// </remarks>
         /// </remarks>
         public string Name { get; set; }
         public string Name { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the custom data of this object.
+        /// </summary>
         public IO.JsonContent Extras { get; set; }
         public IO.JsonContent Extras { get; set; }
 
 
         protected static int GetContentHashCode(BaseBuilder x)
         protected static int GetContentHashCode(BaseBuilder x)

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

@@ -232,33 +232,7 @@ namespace SharpGLTF.Geometry
         #endregion
         #endregion
     }
     }
 
 
-    /// <summary>
-    /// Represents an utility class to help build meshes by adding primitives associated with a given material.
-    /// </summary>
-    /// <typeparam name="TvG">
-    /// The vertex fragment type with Position, Normal and Tangent.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexPosition"/>,<br/>
-    /// - <see cref="VertexPositionNormal"/>,<br/>
-    /// - <see cref="VertexPositionNormalTangent"/>.<br/>
-    /// </typeparam>
-    /// <typeparam name="TvM">
-    /// The vertex fragment type with Colors and Texture Coordinates.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexEmpty"/>,<br/>
-    /// - <see cref="VertexColor1"/>,<br/>
-    /// - <see cref="VertexTexture1"/>,<br/>
-    /// - <see cref="VertexColor1Texture1"/>.<br/>
-    /// - <see cref="VertexColor1Texture2"/>.<br/>
-    /// - <see cref="VertexColor2Texture2"/>.<br/>
-    /// </typeparam>
-    /// <typeparam name="TvS">
-    /// The vertex fragment type with Skin Joint Weights.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexEmpty"/>,<br/>
-    /// - <see cref="VertexJoints4"/>,<br/>
-    /// - <see cref="VertexJoints8"/>.<br/>
-    /// </typeparam>
+    /// <inheritdoc/>
     public class MeshBuilder<TvG, TvM, TvS> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, TvS>
     public class MeshBuilder<TvG, TvM, TvS> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvM : struct, IVertexMaterial
@@ -268,26 +242,7 @@ namespace SharpGLTF.Geometry
             : base(name) { }
             : base(name) { }
     }
     }
 
 
-    /// <summary>
-    /// Represents an utility class to help build meshes by adding primitives associated with a given material.
-    /// </summary>
-    /// <typeparam name="TvG">
-    /// The vertex fragment type with Position, Normal and Tangent.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexPosition"/>,<br/>
-    /// - <see cref="VertexPositionNormal"/>,<br/>
-    /// - <see cref="VertexPositionNormalTangent"/>.<br/>
-    /// </typeparam>
-    /// <typeparam name="TvM">
-    /// The vertex fragment type with Colors and Texture Coordinates.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexEmpty"/>,<br/>
-    /// - <see cref="VertexColor1"/>,<br/>
-    /// - <see cref="VertexTexture1"/>,<br/>
-    /// - <see cref="VertexColor1Texture1"/>.<br/>
-    /// - <see cref="VertexColor1Texture2"/>.<br/>
-    /// - <see cref="VertexColor2Texture2"/>.<br/>
-    /// </typeparam>
+    /// <inheritdoc/>
     public class MeshBuilder<TvG, TvM> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty>
     public class MeshBuilder<TvG, TvM> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvM : struct, IVertexMaterial
@@ -296,16 +251,7 @@ namespace SharpGLTF.Geometry
             : base(name) { }
             : base(name) { }
     }
     }
 
 
-    /// <summary>
-    /// Represents an utility class to help build meshes by adding primitives associated with a given material.
-    /// </summary>
-    /// <typeparam name="TvG">
-    /// The vertex fragment type with Position, Normal and Tangent.<br/>
-    /// Valid types are:<br/>
-    /// - <see cref="VertexPosition"/>,<br/>
-    /// - <see cref="VertexPositionNormal"/>,<br/>
-    /// - <see cref="VertexPositionNormalTangent"/>.<br/>
-    /// </typeparam>
+    /// <inheritdoc/>
     public class MeshBuilder<TvG> : MeshBuilder<Materials.MaterialBuilder, TvG, VertexEmpty, VertexEmpty>
     public class MeshBuilder<TvG> : MeshBuilder<Materials.MaterialBuilder, TvG, VertexEmpty, VertexEmpty>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
     {
     {

+ 4 - 1
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -10,7 +10,7 @@ using SharpGLTF.Geometry.VertexTypes;
 namespace SharpGLTF.Geometry
 namespace SharpGLTF.Geometry
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents an utility class to help build mesh primitives by adding triangles
+    /// Represents an utility class to help build mesh primitives by adding points, lines or triangles
     /// </summary>
     /// </summary>
     /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/> instance.</typeparam>
     /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/> instance.</typeparam>
     /// <typeparam name="TvG">
     /// <typeparam name="TvG">
@@ -416,6 +416,7 @@ namespace SharpGLTF.Geometry
         #endregion
         #endregion
     }
     }
 
 
+    /// <inheritdoc/>
     [System.Diagnostics.DebuggerDisplay("Points[{Points.Count}] {_Material}")]
     [System.Diagnostics.DebuggerDisplay("Points[{Points.Count}] {_Material}")]
     sealed class PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
     sealed class PointsPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
@@ -488,6 +489,7 @@ namespace SharpGLTF.Geometry
         #endregion
         #endregion
     }
     }
 
 
+    /// <inheritdoc/>
     [System.Diagnostics.DebuggerDisplay("Lines[{Lines.Count}] {_Material}")]
     [System.Diagnostics.DebuggerDisplay("Lines[{Lines.Count}] {_Material}")]
     sealed class LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
     sealed class LinesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry
@@ -561,6 +563,7 @@ namespace SharpGLTF.Geometry
         #endregion
         #endregion
     }
     }
 
 
+    /// <inheritdoc/>
     [System.Diagnostics.DebuggerDisplay("Triangles[{Triangles.Count}] {_Material}")]
     [System.Diagnostics.DebuggerDisplay("Triangles[{Triangles.Count}] {_Material}")]
     sealed class TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
     sealed class TrianglesPrimitiveBuilder<TMaterial, TvG, TvM, TvS> : PrimitiveBuilder<TMaterial, TvG, TvM, TvS>
         where TvG : struct, IVertexGeometry
         where TvG : struct, IVertexGeometry

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

@@ -14,7 +14,7 @@ namespace SharpGLTF.Geometry
     /// <remarks>
     /// <remarks>
     /// One of the use cases of <see cref="VertexBufferColumns"/> is to bind the different attribute
     /// One of the use cases of <see cref="VertexBufferColumns"/> is to bind the different attribute
     /// columns directly to the <see cref="Schema2.Accessor"/> source feed, which means that
     /// columns directly to the <see cref="Schema2.Accessor"/> source feed, which means that
-    /// if you modify the contents of a column that is binded directly to a model, you're
+    /// if you modify the contents of a column that is bound directly to a model, you're
     /// modifying the model's internal data.
     /// modifying the model's internal data.
     /// </remarks>
     /// </remarks>
     public class VertexBufferColumns
     public class VertexBufferColumns

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

@@ -7,7 +7,7 @@ using System.Text;
 namespace SharpGLTF.Materials
 namespace SharpGLTF.Materials
 {
 {
     /// <summary>
     /// <summary>
-    /// Represents a <see cref="MaterialBuilder"/> texture channel.
+    /// Represents a material channel at <see cref="MaterialBuilder"/>.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public class ChannelBuilder
     public class ChannelBuilder

+ 3 - 0
src/SharpGLTF.Toolkit/Materials/ImageBuilder.cs

@@ -7,6 +7,9 @@ using IMAGEFILE = SharpGLTF.Memory.MemoryImage;
 
 
 namespace SharpGLTF.Materials
 namespace SharpGLTF.Materials
 {
 {
+    /// <summary>
+    /// Represents an image that can be used at <see cref="TextureBuilder.PrimaryImage"/> and <see cref="TextureBuilder.FallbackImage"/>.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
     public sealed class ImageBuilder : BaseBuilder
     public sealed class ImageBuilder : BaseBuilder
     {
     {

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

@@ -11,6 +11,9 @@ using TEXWRAP = SharpGLTF.Schema2.TextureWrapMode;
 
 
 namespace SharpGLTF.Materials
 namespace SharpGLTF.Materials
 {
 {
+    /// <summary>
+    /// Represents the texture used by a <see cref="ChannelBuilder"/>
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
     public class TextureBuilder : BaseBuilder
     public class TextureBuilder : BaseBuilder
     {
     {

+ 42 - 9
src/SharpGLTF.Toolkit/Scenes/CameraBuilder.cs

@@ -5,6 +5,9 @@ using System.Text;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
+    /// <summary>
+    /// Represents an camera object.
+    /// </summary>
     public abstract class CameraBuilder : BaseBuilder
     public abstract class CameraBuilder : BaseBuilder
     {
     {
         #region lifecycle
         #region lifecycle
@@ -40,7 +43,28 @@ namespace SharpGLTF.Scenes
         /// </summary>
         /// </summary>
         public float ZFar { get; set; }
         public float ZFar { get; set; }
 
 
-        public abstract Matrix4x4 Matrix { get; }
+        /// <summary>
+        /// Gets a value indicating whether the camera parameters are correct.
+        /// </summary>
+        public bool IsValid
+        {
+            get
+            {
+                try { GetMatrix(); return true; }
+                catch { return false; }
+            }
+        }
+
+        /// <summary>
+        /// Gets the projection matrix for the camera parameters.
+        /// </summary>
+        public Matrix4x4 Matrix => GetMatrix();
+
+        #endregion
+
+        #region API
+
+        protected abstract Matrix4x4 GetMatrix();
 
 
         #endregion
         #endregion
 
 
@@ -48,6 +72,7 @@ namespace SharpGLTF.Scenes
 
 
         #pragma warning disable CA1034 // Nested types should not be visible
         #pragma warning disable CA1034 // Nested types should not be visible
 
 
+        /// <inheritdoc/>
         [System.Diagnostics.DebuggerDisplay("Orthographic ({XMag},{YMag})  {ZNear} < {ZFar}")]
         [System.Diagnostics.DebuggerDisplay("Orthographic ({XMag},{YMag})  {ZNear} < {ZFar}")]
         public sealed class Orthographic : CameraBuilder
         public sealed class Orthographic : CameraBuilder
         {
         {
@@ -93,14 +118,19 @@ namespace SharpGLTF.Scenes
             /// </summary>
             /// </summary>
             public float YMag { get; set; }
             public float YMag { get; set; }
 
 
-            /// <summary>
-            /// Gets the projection matrix for the current settings
-            /// </summary>
-            public override Matrix4x4 Matrix => Transforms.Projection.CreateOrthographicMatrix(XMag, YMag, ZNear, ZFar);
+            #endregion
+
+            #region API
+
+            protected override Matrix4x4 GetMatrix()
+            {
+                return Transforms.Projection.CreateOrthographicMatrix(XMag, YMag, ZNear, ZFar);
+            }
 
 
             #endregion
             #endregion
         }
         }
 
 
+        /// <inheritdoc/>
         [System.Diagnostics.DebuggerDisplay("Perspective {AspectRatio} {VerticalFOV}   {ZNear} < {ZFar}")]
         [System.Diagnostics.DebuggerDisplay("Perspective {AspectRatio} {VerticalFOV}   {ZNear} < {ZFar}")]
         public sealed partial class Perspective : CameraBuilder
         public sealed partial class Perspective : CameraBuilder
         {
         {
@@ -146,10 +176,13 @@ namespace SharpGLTF.Scenes
             /// </summary>
             /// </summary>
             public float VerticalFOV { get; set; }
             public float VerticalFOV { get; set; }
 
 
-            /// <summary>
-            /// Gets the projection matrix for the current settings
-            /// </summary>
-            public override Matrix4x4 Matrix => Transforms.Projection.CreateOrthographicMatrix(AspectRatio ?? 1, VerticalFOV, ZNear, ZFar);
+            #endregion
+
+            #region API
+            protected override Matrix4x4 GetMatrix()
+            {
+                return Transforms.Projection.CreateOrthographicMatrix(AspectRatio ?? 1, VerticalFOV, ZNear, ZFar);
+            }
 
 
             #endregion
             #endregion
         }
         }

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

@@ -9,18 +9,6 @@ using SCHEMA2NODE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Sch
 
 
 namespace SharpGLTF.Scenes
 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
     partial class MeshContent : SCHEMA2NODE
     {
     {
         void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)
         void SCHEMA2NODE.Setup(Node dstNode, Schema2SceneBuilder context)

+ 26 - 35
src/SharpGLTF.Toolkit/Scenes/Content.cs

@@ -1,7 +1,5 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
 
 
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 
 
@@ -12,6 +10,26 @@ namespace SharpGLTF.Scenes
         MESHBUILDER GetGeometryAsset();
         MESHBUILDER GetGeometryAsset();
     }
     }
 
 
+    /// <summary>
+    /// Represents a dummy, empty content of <see cref="ContentTransformer.Content"/>.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("Custom")]
+    partial class EmptyContent : ICloneable
+    {
+        #region lifecycle
+
+        public EmptyContent() { }
+
+        public Object Clone() { return new EmptyContent(this); }
+
+        private EmptyContent(EmptyContent other) { }
+
+        #endregion
+    }
+
+    /// <summary>
+    /// Represents a <see cref="MESHBUILDER"/> content of <see cref="ContentTransformer.Content"/>.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Mesh")]
     [System.Diagnostics.DebuggerDisplay("Mesh")]
     partial class MeshContent
     partial class MeshContent
         : IRenderableContent
         : IRenderableContent
@@ -60,25 +78,9 @@ namespace SharpGLTF.Scenes
         #endregion
         #endregion
     }
     }
 
 
-    partial class MorphableMeshContent : IRenderableContent
-    {
-        #region data
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private IRenderableContent _Target;
-
-        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
-        private readonly List<Animations.AnimatableProperty<float>> _MorphWeights = new List<Animations.AnimatableProperty<float>>();
-
-        #endregion
-
-        #region API
-
-        public MESHBUILDER GetGeometryAsset() => _Target?.GetGeometryAsset();
-
-        #endregion
-    }
-
+    /// <summary>
+    /// Represents a <see cref="CameraBuilder"/> content of <see cref="ContentTransformer.Content"/>.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Camera")]
     [System.Diagnostics.DebuggerDisplay("Camera")]
     partial class CameraContent : ICloneable
     partial class CameraContent : ICloneable
     {
     {
@@ -119,6 +121,9 @@ namespace SharpGLTF.Scenes
         #endregion
         #endregion
     }
     }
 
 
+    /// <summary>
+    /// Represents a <see cref="LightBuilder"/> content of <see cref="ContentTransformer.Content"/>.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Light")]
     [System.Diagnostics.DebuggerDisplay("Light")]
     partial class LightContent : ICloneable
     partial class LightContent : ICloneable
     {
     {
@@ -158,18 +163,4 @@ namespace SharpGLTF.Scenes
 
 
         #endregion
         #endregion
     }
     }
-
-    [System.Diagnostics.DebuggerDisplay("Custom")]
-    partial class EmptyContent : ICloneable
-    {
-        #region lifecycle
-
-        public EmptyContent() { }
-
-        public Object Clone() { return new EmptyContent(this); }
-
-        private EmptyContent(EmptyContent other) {  }
-
-        #endregion
-    }
 }
 }

+ 38 - 4
src/SharpGLTF.Toolkit/Scenes/InstanceBuilder.cs

@@ -3,12 +3,13 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 
 
-using SharpGLTF.Collections;
-
 using SCHEMA2SCENE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Scene>;
 using SCHEMA2SCENE = SharpGLTF.Scenes.Schema2SceneBuilder.IOperator<SharpGLTF.Schema2.Scene>;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
+    /// <summary>
+    /// Represents an element within <see cref="SceneBuilder.Instances"/>
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("{Content}")]
     [System.Diagnostics.DebuggerDisplay("{Content}")]
     public sealed class InstanceBuilder : SCHEMA2SCENE
     public sealed class InstanceBuilder : SCHEMA2SCENE
     {
     {
@@ -19,6 +20,18 @@ namespace SharpGLTF.Scenes
             _Parent = parent;
             _Parent = parent;
         }
         }
 
 
+        public InstanceBuilder WithName(string name)
+        {
+            if (this.Content != null) this.Content.Name = name;
+            return this;
+        }
+
+        public InstanceBuilder WithExtras(IO.JsonContent extras)
+        {
+            if (this.Content != null) this.Content.Extras = extras;
+            return this;
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data
@@ -34,17 +47,38 @@ namespace SharpGLTF.Scenes
         #region properties
         #region properties
 
 
         /// <summary>
         /// <summary>
-        /// Gets The name of this instance.
-        /// This name represents the name that will take the <see cref="Schema2.Node"/> containing this content.
+        /// Gets the display text name of this object, or null.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// glTF does not define any ruling for object names.<br/>
+        /// This means that names can be null or non unique.<br/>
+        /// So don't use names for anything other than object name display.<br/>
+        /// If you need to reference objects by some ID, use lookup tables instead.
+        /// </remarks>
         public string Name => _ContentTransformer?.Name;
         public string Name => _ContentTransformer?.Name;
 
 
+        /// <summary>
+        /// Gets the custom data of this object.
+        /// </summary>
+        public IO.JsonContent Extras => _ContentTransformer?.Extras ?? default;
+
+        /// <summary>
+        /// Gets or sets the content of this instance.<br/>
+        /// It can be one of those types:<br/>
+        /// - <see cref="FixedTransformer"/><br/>
+        /// - <see cref="RigidTransformer"/><br/>
+        /// - <see cref="SkinnedTransformer"/><br/>
+        /// </summary>
         public ContentTransformer Content
         public ContentTransformer Content
         {
         {
             get => _ContentTransformer;
             get => _ContentTransformer;
             set => _ContentTransformer = value;
             set => _ContentTransformer = value;
         }
         }
 
 
+        /// <summary>
+        /// Gets the materials used by <see cref="Content"/>.
+        /// </summary>
         public IEnumerable<Materials.MaterialBuilder> Materials
         public IEnumerable<Materials.MaterialBuilder> Materials
         {
         {
             get
             get

+ 18 - 9
src/SharpGLTF.Toolkit/Scenes/LightBuilder.cs

@@ -5,6 +5,9 @@ using System.Text;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
+    /// <summary>
+    /// Represents a light object.
+    /// </summary>
     public abstract class LightBuilder : BaseBuilder
     public abstract class LightBuilder : BaseBuilder
     {
     {
         #region lifecycle
         #region lifecycle
@@ -40,9 +43,10 @@ namespace SharpGLTF.Scenes
         public Vector3 Color { get; set; }
         public Vector3 Color { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the Brightness of light in. The units that this is defined in depend on the type of light.
-        /// point and spot lights use luminous intensity in candela (lm/sr) while directional
-        /// lights use illuminance in lux (lm/m2)
+        /// Gets or sets the Brightness of light in.<br/>
+        /// The units that this is defined in depend on the type of light.<br/>
+        /// Point and spot lights use luminous intensity in candela (lm/sr)
+        /// while directional lights use illuminance in lux (lm/m2)
         /// </summary>
         /// </summary>
         public Single Intensity { get; set; }
         public Single Intensity { get; set; }
 
 
@@ -50,6 +54,7 @@ namespace SharpGLTF.Scenes
 
 
         #region Nested types
         #region Nested types
 
 
+        /// <inheritdoc/>
         [System.Diagnostics.DebuggerDisplay("Directional")]
         [System.Diagnostics.DebuggerDisplay("Directional")]
         public sealed class Directional : LightBuilder
         public sealed class Directional : LightBuilder
         {
         {
@@ -68,6 +73,7 @@ namespace SharpGLTF.Scenes
             #endregion
             #endregion
         }
         }
 
 
+        /// <inheritdoc/>
         [System.Diagnostics.DebuggerDisplay("Point")]
         [System.Diagnostics.DebuggerDisplay("Point")]
         public sealed class Point : LightBuilder
         public sealed class Point : LightBuilder
         {
         {
@@ -95,8 +101,9 @@ namespace SharpGLTF.Scenes
             #region data
             #region data
 
 
             /// <summary>
             /// <summary>
-            /// Gets or sets a Hint defining a distance cutoff at which the light's intensity may be considered
-            /// to have reached zero. Supported only for point and spot lights. Must be > 0.
+            /// Gets or sets a Hint defining a distance cutoff at which the
+            /// light's intensity may be considered to have reached zero.<br/>
+            /// Supported only for point and spot lights. Must be > 0.<br/>
             /// When undefined, range is assumed to be infinite.
             /// When undefined, range is assumed to be infinite.
             /// </summary>
             /// </summary>
             public Single Range { get; set; }
             public Single Range { get; set; }
@@ -104,6 +111,7 @@ namespace SharpGLTF.Scenes
             #endregion
             #endregion
         }
         }
 
 
+        /// <inheritdoc/>
         [System.Diagnostics.DebuggerDisplay("Spot")]
         [System.Diagnostics.DebuggerDisplay("Spot")]
         public sealed class Spot : LightBuilder
         public sealed class Spot : LightBuilder
         {
         {
@@ -135,20 +143,21 @@ namespace SharpGLTF.Scenes
             #region data
             #region data
 
 
             /// <summary>
             /// <summary>
-            /// Gets or sets a Hint defining a distance cutoff at which the light's intensity may be considered
-            /// to have reached zero. Supported only for point and spot lights. Must be > 0.
+            /// Gets or sets a Hint defining a distance cutoff at which the
+            /// light's intensity may be considered to have reached zero.<br/>
+            /// Supported only for point and spot lights. Must be > 0.<br/>
             /// When undefined, range is assumed to be infinite.
             /// When undefined, range is assumed to be infinite.
             /// </summary>
             /// </summary>
             public Single Range { get; set; }
             public Single Range { get; set; }
 
 
             /// <summary>
             /// <summary>
-            /// Gets or sets the Angle, in radians, from centre of spotlight where falloff begins.
+            /// Gets or sets the Angle, in radians, from centre of spotlight where falloff begins.<br/>
             /// Must be greater than or equal to 0 and less than outerConeAngle.
             /// Must be greater than or equal to 0 and less than outerConeAngle.
             /// </summary>
             /// </summary>
             public Single InnerConeAngle { get; set; }
             public Single InnerConeAngle { get; set; }
 
 
             /// <summary>
             /// <summary>
-            /// Gets or sets Angle, in radians, from centre of spotlight where falloff ends.
+            /// Gets or sets Angle, in radians, from centre of spotlight where falloff ends.<br/>
             /// Must be greater than innerConeAngle and less than or equal to PI / 2.0.
             /// Must be greater than innerConeAngle and less than or equal to PI / 2.0.
             /// </summary>
             /// </summary>
             public Single OuterConeAngle { get; set; }
             public Single OuterConeAngle { get; set; }

+ 12 - 4
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -1,11 +1,7 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.ComponentModel.Design;
 using System.Linq;
 using System.Linq;
 using System.Numerics;
 using System.Numerics;
-using System.Text;
-
-using SharpGLTF.Schema2;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
@@ -116,6 +112,18 @@ namespace SharpGLTF.Scenes
 
 
         #region properties - transform
         #region properties - transform
 
 
+        public IEnumerable<string> AnimationTracksNames
+        {
+            get
+            {
+                var tracks = Enumerable.Empty<string>();
+                if (_Scale != null) tracks.Concat(_Scale.Tracks.Keys);
+                if (_Rotation != null) tracks.Concat(_Rotation.Tracks.Keys);
+                if (_Translation != null) tracks.Concat(_Translation.Tracks.Keys);
+                return tracks.Distinct();
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether this <see cref="NodeBuilder"/> has animations.
         /// Gets a value indicating whether this <see cref="NodeBuilder"/> has animations.
         /// </summary>
         /// </summary>

+ 51 - 38
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.cs

@@ -11,6 +11,9 @@ using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.Material
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
+    /// <summary>
+    /// Represents the root scene for models, cameras and lights.
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Scene {Name}")]
     [System.Diagnostics.DebuggerDisplay("Scene {Name}")]
     public partial class SceneBuilder : BaseBuilder
     public partial class SceneBuilder : BaseBuilder
     {
     {
@@ -66,26 +69,53 @@ namespace SharpGLTF.Scenes
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets all the instances in this scene.
+        /// </summary>
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         public IReadOnlyList<InstanceBuilder> Instances => _Instances;
         public IReadOnlyList<InstanceBuilder> Instances => _Instances;
 
 
+        /// <summary>
+        /// Gets all the unique material references shared by all the meshes in this scene.
+        /// </summary>
         public IEnumerable<MaterialBuilder> Materials => _Instances.SelectMany(item => item.Materials).Distinct(MaterialBuilder.ReferenceComparer);
         public IEnumerable<MaterialBuilder> Materials => _Instances.SelectMany(item => item.Materials).Distinct(MaterialBuilder.ReferenceComparer);
 
 
+        internal IEnumerable<string> AnimationTrackNames => _Instances
+            .SelectMany(item => item.Content.GetAnimationTracksNames())
+            .Distinct();
+
         #endregion
         #endregion
 
 
-        #region API
+        #region Obsolete API
 
 
-        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix)
+        [Obsolete("Remove name param and use .WithName(name);")]
+        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, String nodeName, Matrix4x4 meshWorldMatrix)
         {
         {
-            return AddRigidMesh(mesh, null, meshWorldMatrix);
+            return AddRigidMesh(mesh, meshWorldMatrix).WithName(nodeName);
         }
         }
 
 
-        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, String name, Matrix4x4 meshWorldMatrix)
+        [Obsolete("Remove name param and use .WithName(name);")]
+        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, String nodeName, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
+        {
+            return AddSkinnedMesh(mesh, meshWorldMatrix, joints).WithName(nodeName);
+        }
+
+        [Obsolete("Remove name param and use .WithName(name);")]
+        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, string nodeName, params (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints)
+        {
+            return AddSkinnedMesh(mesh, joints).WithName(nodeName);
+        }
+
+        #endregion
+
+        #region API
+        public InstanceBuilder AddRigidMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix)
         {
         {
             Guard.NotNull(mesh, nameof(mesh));
             Guard.NotNull(mesh, nameof(mesh));
+            Guard.IsTrue(meshWorldMatrix.IsValid(true, false, false), nameof(meshWorldMatrix));
 
 
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new FixedTransformer(mesh, meshWorldMatrix, name);
+            instance.Content = new FixedTransformer(mesh, meshWorldMatrix);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
 
 
@@ -106,17 +136,13 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
         public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
-        {
-            return AddSkinnedMesh(mesh, null, meshWorldMatrix, joints);
-        }
-
-        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, String name, Matrix4x4 meshWorldMatrix, params NodeBuilder[] joints)
         {
         {
             Guard.NotNull(mesh, nameof(mesh));
             Guard.NotNull(mesh, nameof(mesh));
+            Guard.IsTrue(meshWorldMatrix.IsValid(true, false, false), nameof(meshWorldMatrix));
             GuardAll.NotNull(joints, nameof(joints));
             GuardAll.NotNull(joints, nameof(joints));
 
 
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new SkinnedTransformer(mesh, meshWorldMatrix, joints, name);
+            instance.Content = new SkinnedTransformer(mesh, meshWorldMatrix, joints);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
 
 
@@ -124,17 +150,13 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, params (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints)
         public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, params (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints)
-        {
-            return AddSkinnedMesh(mesh, null, joints);
-        }
-
-        public InstanceBuilder AddSkinnedMesh(MESHBUILDER mesh, String name, params (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints)
         {
         {
             Guard.NotNull(mesh, nameof(mesh));
             Guard.NotNull(mesh, nameof(mesh));
             GuardAll.NotNull(joints.Select(item => item.Joint), nameof(joints));
             GuardAll.NotNull(joints.Select(item => item.Joint), nameof(joints));
+            GuardAll.AreTrue(joints.Select(item => item.InverseBindMatrix.IsValid(true, false, false)), nameof(joints), "Invalid matrix");
 
 
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new SkinnedTransformer(mesh, joints, name);
+            instance.Content = new SkinnedTransformer(mesh, joints);
 
 
             _Instances.Add(instance);
             _Instances.Add(instance);
 
 
@@ -154,48 +176,35 @@ namespace SharpGLTF.Scenes
         }
         }
 
 
         public InstanceBuilder AddCamera(CameraBuilder camera, Vector3 cameraPosition, Vector3 targetPosition)
         public InstanceBuilder AddCamera(CameraBuilder camera, Vector3 cameraPosition, Vector3 targetPosition)
-        {
-            return AddCamera(camera, null, cameraPosition, targetPosition);
-        }
-
-        public InstanceBuilder AddCamera(CameraBuilder camera, String name, Vector3 cameraPosition, Vector3 targetPosition)
         {
         {
             Guard.NotNull(camera, nameof(camera));
             Guard.NotNull(camera, nameof(camera));
             Guard.IsTrue(cameraPosition._IsFinite(), nameof(cameraPosition));
             Guard.IsTrue(cameraPosition._IsFinite(), nameof(cameraPosition));
             Guard.IsTrue(targetPosition._IsFinite(), nameof(targetPosition));
             Guard.IsTrue(targetPosition._IsFinite(), nameof(targetPosition));
 
 
             var xform = Matrix4x4.CreateWorld(cameraPosition, Vector3.Normalize(targetPosition - cameraPosition), Vector3.UnitY);
             var xform = Matrix4x4.CreateWorld(cameraPosition, Vector3.Normalize(targetPosition - cameraPosition), Vector3.UnitY);
-            return AddCamera(camera, name, xform);
+            return AddCamera(camera, xform);
         }
         }
 
 
         public InstanceBuilder AddCamera(CameraBuilder camera, Matrix4x4 cameraWorldMatrix)
         public InstanceBuilder AddCamera(CameraBuilder camera, Matrix4x4 cameraWorldMatrix)
-        {
-            return AddCamera(camera, null, cameraWorldMatrix);
-        }
-
-        public InstanceBuilder AddCamera(CameraBuilder camera, String name, Matrix4x4 cameraWorldMatrix)
         {
         {
             Guard.NotNull(camera, nameof(camera));
             Guard.NotNull(camera, nameof(camera));
+            Guard.IsTrue(cameraWorldMatrix.IsValid(true, false, false), nameof(cameraWorldMatrix));
 
 
             var content = new CameraContent(camera);
             var content = new CameraContent(camera);
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new FixedTransformer(content, cameraWorldMatrix, name);
+            instance.Content = new FixedTransformer(content, cameraWorldMatrix);
             _Instances.Add(instance);
             _Instances.Add(instance);
             return instance;
             return instance;
         }
         }
 
 
         public InstanceBuilder AddLight(LightBuilder light, Matrix4x4 lightWorldMatrix)
         public InstanceBuilder AddLight(LightBuilder light, Matrix4x4 lightWorldMatrix)
-        {
-            return AddLight(light, null, lightWorldMatrix);
-        }
-
-        public InstanceBuilder AddLight(LightBuilder light, String name, Matrix4x4 lightWorldMatrix)
         {
         {
             Guard.NotNull(light, nameof(light));
             Guard.NotNull(light, nameof(light));
+            Guard.IsTrue(lightWorldMatrix.IsValid(true, false, false), nameof(lightWorldMatrix));
 
 
             var content = new LightContent(light);
             var content = new LightContent(light);
             var instance = new InstanceBuilder(this);
             var instance = new InstanceBuilder(this);
-            instance.Content = new FixedTransformer(content, lightWorldMatrix, name);
+            instance.Content = new FixedTransformer(content, lightWorldMatrix);
             _Instances.Add(instance);
             _Instances.Add(instance);
             return instance;
             return instance;
         }
         }
@@ -241,12 +250,15 @@ namespace SharpGLTF.Scenes
         /// <param name="basisTransform">The transform to apply.</param>
         /// <param name="basisTransform">The transform to apply.</param>
         /// <param name="basisNodeName">The name of the dummy root node.</param>
         /// <param name="basisNodeName">The name of the dummy root node.</param>
         /// <remarks>
         /// <remarks>
-        /// In some circunstances, it's not possible to apply the <paramref name="basisTransform"/> to
-        /// the nodes in the scene. In this case a dummy node is created, and these nodes are made
-        /// children of this dummy node.
+        /// In some circunstances, it's not possible to apply the
+        /// <paramref name="basisTransform"/> to the nodes in the scene.<br/>
+        /// In these cases a dummy node is created, and these
+        /// nodes are made children of this dummy node.
         /// </remarks>
         /// </remarks>
         public void ApplyBasisTransform(Matrix4x4 basisTransform, string basisNodeName = "BasisTransform")
         public void ApplyBasisTransform(Matrix4x4 basisTransform, string basisNodeName = "BasisTransform")
         {
         {
+            Guard.IsTrue(basisTransform.IsValid(true, false, false), nameof(basisTransform));
+
             // gather all root nodes:
             // gather all root nodes:
             var rootNodes = this.FindArmatures();
             var rootNodes = this.FindArmatures();
 
 
@@ -298,6 +310,7 @@ namespace SharpGLTF.Scenes
         public IReadOnlyList<InstanceBuilder> AddScene(SceneBuilder scene, Matrix4x4 sceneTransform)
         public IReadOnlyList<InstanceBuilder> AddScene(SceneBuilder scene, Matrix4x4 sceneTransform)
         {
         {
             Guard.NotNull(scene, nameof(scene));
             Guard.NotNull(scene, nameof(scene));
+            Guard.IsTrue(sceneTransform.IsValid(true, false, false), nameof(sceneTransform));
 
 
             scene = scene.DeepClone();
             scene = scene.DeepClone();
 
 

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

@@ -19,6 +19,7 @@ namespace SharpGLTF.Scenes
             var dstNode = dstScene.CreateNode();
             var dstNode = dstScene.CreateNode();
 
 
             dstNode.Name = _NodeName;
             dstNode.Name = _NodeName;
+            dstNode.Extras = _NodeExtras;
             dstNode.LocalMatrix = _WorldTransform;
             dstNode.LocalMatrix = _WorldTransform;
 
 
             schema2Target.Setup(dstNode, context);
             schema2Target.Setup(dstNode, context);
@@ -49,6 +50,7 @@ namespace SharpGLTF.Scenes
 
 
             var skinnedMeshNode = dstScene.CreateNode();
             var skinnedMeshNode = dstScene.CreateNode();
             skinnedMeshNode.Name = _NodeName;
             skinnedMeshNode.Name = _NodeName;
+            skinnedMeshNode.Extras = _NodeExtras;
 
 
             if (_MeshPoseWorldMatrix.HasValue)
             if (_MeshPoseWorldMatrix.HasValue)
             {
             {

+ 84 - 11
src/SharpGLTF.Toolkit/Scenes/Transformers.cs

@@ -4,11 +4,14 @@ using System.Linq;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
+using SharpGLTF.IO;
+
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.MaterialBuilder>;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
     /// <summary>
     /// <summary>
+    /// Represents the content of <see cref="InstanceBuilder.Content"/>.<br/>
     /// Applies a transform to the underlaying content object (usually a Mesh, a Camera or a light)
     /// Applies a transform to the underlaying content object (usually a Mesh, a Camera or a light)
     /// </summary>
     /// </summary>
     public abstract class ContentTransformer
     public abstract class ContentTransformer
@@ -63,9 +66,27 @@ namespace SharpGLTF.Scenes
 
 
         #region properties
         #region properties
 
 
-        public abstract String Name { get; }
+        /// <summary>
+        /// Gets or sets the display text name, or null.
+        /// <para><b>⚠️ DO NOT USE AS AN OBJECT ID ⚠️</b> see remarks.</para>
+        /// </summary>
+        /// <remarks>
+        /// glTF does not define any ruling for object names.<br/>
+        /// This means that names can be null or non unique.<br/>
+        /// So don't use <see cref="Name"/> for anything other than object name display.<br/>
+        /// If you need to reference objects by some ID, use lookup tables instead.
+        /// </remarks>
+        public abstract String Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the custom data of this object.
+        /// </summary>
+        public abstract IO.JsonContent Extras { get; set; }
 
 
-        public Object Content => _Content;
+        /// <summary>
+        /// Gets the content of this transformer.<br/>
+        /// </summary>
+        internal Object Content => _Content;
 
 
         public Animations.AnimatableProperty<Transforms.SparseWeight8> Morphings => _Morphings;
         public Animations.AnimatableProperty<Transforms.SparseWeight8> Morphings => _Morphings;
 
 
@@ -103,6 +124,14 @@ namespace SharpGLTF.Scenes
 
 
         public abstract Matrix4x4 GetPoseWorldMatrix();
         public abstract Matrix4x4 GetPoseWorldMatrix();
 
 
+        internal IEnumerable<string> GetAnimationTracksNames()
+        {
+            var tracks = NodeBuilder.Flatten(this.GetArmatureRoot()).SelectMany(item => item.AnimationTracksNames);
+            if (_Morphings != null) tracks = tracks.Concat(_Morphings.Tracks.Keys);
+
+            return tracks.Distinct();
+        }
+
         #endregion
         #endregion
 
 
         #region nestedTypes
         #region nestedTypes
@@ -127,6 +156,7 @@ namespace SharpGLTF.Scenes
     }
     }
 
 
     /// <summary>
     /// <summary>
+    /// Represents the content of <see cref="InstanceBuilder.Content"/>.<br/>
     /// Applies a fixed <see cref="Matrix4x4"/> transform to the underlaying content.
     /// Applies a fixed <see cref="Matrix4x4"/> transform to the underlaying content.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Fixed Node[{_DebugName,nq}] = {Content}")]
     [System.Diagnostics.DebuggerDisplay("Fixed Node[{_DebugName,nq}] = {Content}")]
@@ -134,11 +164,10 @@ namespace SharpGLTF.Scenes
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal FixedTransformer(Object content, Matrix4x4 xform, string nodeName = null)
+        internal FixedTransformer(Object content, Matrix4x4 xform)
             : base(content)
             : base(content)
         {
         {
             _WorldTransform = xform;
             _WorldTransform = xform;
-            _NodeName = nodeName;
         }
         }
 
 
         protected FixedTransformer(FixedTransformer other)
         protected FixedTransformer(FixedTransformer other)
@@ -147,6 +176,7 @@ namespace SharpGLTF.Scenes
             Guard.NotNull(other, nameof(other));
             Guard.NotNull(other, nameof(other));
 
 
             this._NodeName = other._NodeName;
             this._NodeName = other._NodeName;
+            this._NodeExtras = other._NodeExtras.DeepClone();
             this._WorldTransform = other._WorldTransform;
             this._WorldTransform = other._WorldTransform;
         }
         }
 
 
@@ -162,6 +192,9 @@ namespace SharpGLTF.Scenes
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private String _NodeName;
         private String _NodeName;
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IO.JsonContent _NodeExtras;
+
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private Matrix4x4 _WorldTransform;
         private Matrix4x4 _WorldTransform;
 
 
@@ -169,7 +202,19 @@ namespace SharpGLTF.Scenes
 
 
         #region properties
         #region properties
 
 
-        public override String Name => _NodeName;
+        /// <inheritdoc/>
+        public override String Name
+        {
+            get => _NodeName;
+            set => _NodeName = value;
+        }
+
+        /// <inheritdoc/>
+        public override JsonContent Extras
+        {
+            get => _NodeExtras;
+            set => _NodeExtras = value;
+        }
 
 
         public Matrix4x4 WorldMatrix
         public Matrix4x4 WorldMatrix
         {
         {
@@ -190,6 +235,7 @@ namespace SharpGLTF.Scenes
     }
     }
 
 
     /// <summary>
     /// <summary>
+    /// Represents the content of <see cref="InstanceBuilder.Content"/>.<br/>
     /// Applies the transform of a single <see cref="NodeBuilder"/> to the underlaying content.
     /// Applies the transform of a single <see cref="NodeBuilder"/> to the underlaying content.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Rigid Node[{_DebugName,nq}] = {Content}")]
     [System.Diagnostics.DebuggerDisplay("Rigid Node[{_DebugName,nq}] = {Content}")]
@@ -227,7 +273,19 @@ namespace SharpGLTF.Scenes
 
 
         #region properties
         #region properties
 
 
-        public override String Name => _Node.Name;
+        /// <inheritdoc/>
+        public override String Name
+        {
+            get => _Node.Name;
+            set => _Node.Name = value;
+        }
+
+        /// <inheritdoc/>
+        public override JsonContent Extras
+        {
+            get => _Node.Extras;
+            set => _Node.Extras = value;
+        }
 
 
         public NodeBuilder Transform
         public NodeBuilder Transform
         {
         {
@@ -247,6 +305,7 @@ namespace SharpGLTF.Scenes
     }
     }
 
 
     /// <summary>
     /// <summary>
+    /// Represents the content of <see cref="InstanceBuilder.Content"/>.<br/>
     /// Applies the transforms of many <see cref="NodeBuilder"/> to the underlaying content.
     /// Applies the transforms of many <see cref="NodeBuilder"/> to the underlaying content.
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Skinned Node[{_DebugName,nq}] = {Content}")]
     [System.Diagnostics.DebuggerDisplay("Skinned Node[{_DebugName,nq}] = {Content}")]
@@ -254,17 +313,15 @@ namespace SharpGLTF.Scenes
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        internal SkinnedTransformer(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, NodeBuilder[] joints, string nodeName = null)
+        internal SkinnedTransformer(MESHBUILDER mesh, Matrix4x4 meshWorldMatrix, NodeBuilder[] joints)
             : base(mesh)
             : base(mesh)
         {
         {
-            _NodeName = nodeName;
             SetJoints(meshWorldMatrix, joints);
             SetJoints(meshWorldMatrix, joints);
         }
         }
 
 
-        internal SkinnedTransformer(MESHBUILDER mesh, (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints, string nodeName = null)
+        internal SkinnedTransformer(MESHBUILDER mesh, (NodeBuilder Joint, Matrix4x4 InverseBindMatrix)[] joints)
             : base(mesh)
             : base(mesh)
         {
         {
-            _NodeName = nodeName;
             SetJoints(joints);
             SetJoints(joints);
         }
         }
 
 
@@ -274,6 +331,7 @@ namespace SharpGLTF.Scenes
             Guard.NotNull(other, nameof(other));
             Guard.NotNull(other, nameof(other));
 
 
             this._NodeName = other._NodeName;
             this._NodeName = other._NodeName;
+            this._NodeExtras = other._NodeExtras.DeepClone();
             this._MeshPoseWorldMatrix = other._MeshPoseWorldMatrix;
             this._MeshPoseWorldMatrix = other._MeshPoseWorldMatrix;
 
 
             foreach (var (joint, inverseBindMatrix) in other._Joints)
             foreach (var (joint, inverseBindMatrix) in other._Joints)
@@ -296,6 +354,9 @@ namespace SharpGLTF.Scenes
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private String _NodeName;
         private String _NodeName;
 
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private IO.JsonContent _NodeExtras;
+
         /// <summary>
         /// <summary>
         /// Defines the world matrix of the mesh at the time of binding.
         /// Defines the world matrix of the mesh at the time of binding.
         /// </summary>
         /// </summary>
@@ -310,7 +371,19 @@ namespace SharpGLTF.Scenes
 
 
         #region properties
         #region properties
 
 
-        public override String Name => _NodeName;
+        /// <inheritdoc/>
+        public override String Name
+        {
+            get => _NodeName;
+            set => _NodeName = value;
+        }
+
+        /// <inheritdoc/>
+        public override JsonContent Extras
+        {
+            get => _NodeExtras;
+            set => _NodeExtras = value;
+        }
 
 
         #endregion
         #endregion
 
 

+ 2 - 2
tests/SharpGLTF.Tests/IO/JsonContentTests.cs

@@ -157,7 +157,7 @@ namespace SharpGLTF.IO
             var dict = new Dictionary<string, Object>();            
             var dict = new Dictionary<string, Object>();            
             dict["value"] = value;            
             dict["value"] = value;            
 
 
-            JsonContent a = dict;
+            JsonContent a = JsonContent.CreateFrom(dict);
 
 
             // roundtrip to json
             // roundtrip to json
             var json = a.ToJson();
             var json = a.ToJson();
@@ -170,7 +170,7 @@ namespace SharpGLTF.IO
         [Test]
         [Test]
         public void CreateJsonContent()
         public void CreateJsonContent()
         {
         {
-            JsonContent a = _TestStructure.CreateCompatibleDictionary();
+            JsonContent a = JsonContent.CreateFrom(_TestStructure.CreateCompatibleDictionary());
             
             
             // roundtrip to json
             // roundtrip to json
             var json = a.ToJson();
             var json = a.ToJson();

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/Authoring/BasicSceneCreationTests.cs

@@ -46,7 +46,7 @@ namespace SharpGLTF.Schema2.Authoring
             };
             };
             dict["dict2"] = new Dictionary<string, int> { ["2"] = 2, ["3"] = 3 };
             dict["dict2"] = new Dictionary<string, int> { ["2"] = 2, ["3"] = 3 };
 
 
-            JsonContent extras = dict;
+            var extras = JsonContent.CreateFrom(dict);
 
 
             root.Extras = extras;
             root.Extras = extras;
 
 

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs

@@ -194,7 +194,7 @@ namespace SharpGLTF.Schema2.LoadAndSave
                 .ToMeshBuilder( m => m.ToMaterialBuilder() );            
                 .ToMeshBuilder( m => m.ToMaterialBuilder() );            
 
 
             var editableScene = new Scenes.SceneBuilder();
             var editableScene = new Scenes.SceneBuilder();
-            editableScene.AddRigidMesh(mesh, System.Numerics.Matrix4x4.Identity);
+            editableScene.AddRigidMesh(mesh, Matrix4x4.Identity);
 
 
             model.AttachToCurrentTest("original.glb");
             model.AttachToCurrentTest("original.glb");
             editableScene.ToGltf2().AttachToCurrentTest("WithTangents.glb");
             editableScene.ToGltf2().AttachToCurrentTest("WithTangents.glb");

+ 27 - 2
tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs

@@ -27,13 +27,38 @@ namespace SharpGLTF.Scenes
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
 
 
+            var material = MaterialBuilder.CreateDefault();            
+
+            var mesh = new Cube<MaterialBuilder>(material).ToMesh(Matrix4x4.Identity);            
+
+            var scene = new SceneBuilder();
+
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity);                       
+
+            scene.AttachToCurrentTest("cube.glb");
+            scene.AttachToCurrentTest("cube.gltf");
+            scene.AttachToCurrentTest("cube.plotly");
+        }
+
+        [Test(Description = "Creates a simple cube.")]
+        public void CreateCubeSceneWithExtras()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
             var material = MaterialBuilder.CreateDefault();
             var material = MaterialBuilder.CreateDefault();
+            material.Name = "hello name";
+            material.Extras = IO.JsonContent.Serialize(new KeyValuePair<string, int>("hello", 16));
 
 
             var mesh = new Cube<MaterialBuilder>(material).ToMesh(Matrix4x4.Identity);
             var mesh = new Cube<MaterialBuilder>(material).ToMesh(Matrix4x4.Identity);
+            mesh.Name = "world name";
+            mesh.Extras = "world extras";
 
 
             var scene = new SceneBuilder();
             var scene = new SceneBuilder();
 
 
-            scene.AddRigidMesh(mesh, Matrix4x4.Identity);
+            scene.AddRigidMesh(mesh, Matrix4x4.Identity)
+                .WithName("Cube")
+                .WithExtras(17);
 
 
             scene.AttachToCurrentTest("cube.glb");
             scene.AttachToCurrentTest("cube.glb");
             scene.AttachToCurrentTest("cube.gltf");
             scene.AttachToCurrentTest("cube.gltf");
@@ -403,7 +428,7 @@ namespace SharpGLTF.Scenes
 
 
             var mesh2 = VPOSNRM.CreateCompatibleMesh("shape2");
             var mesh2 = VPOSNRM.CreateCompatibleMesh("shape2");
             mesh2.AddCube(pink, Matrix4x4.Identity);
             mesh2.AddCube(pink, Matrix4x4.Identity);
-            var inst2 = scene.AddRigidMesh(mesh2, Matrix4x4.CreateTranslation(2,0,0));
+            var inst2 = scene.AddRigidMesh(mesh2, Matrix4x4.CreateTranslation(2, 0, 0));
 
 
             scene.AttachToCurrentTest("static.glb");
             scene.AttachToCurrentTest("static.glb");
             scene.AttachToCurrentTest("static.gltf");
             scene.AttachToCurrentTest("static.gltf");