Просмотр исходного кода

Progress on materials API
Added more documentation
Added more tests

Vicente Penades 6 лет назад
Родитель
Сommit
5158784c11
27 измененных файлов с 810 добавлено и 480 удалено
  1. 1 1
      src/SharpGLTF.DOM/Geometry/MeshPrimitive.cs
  2. 79 35
      src/SharpGLTF.DOM/Memory/FloatingArrays.cs
  3. 34 9
      src/SharpGLTF.DOM/Memory/IntegerArrays.cs
  4. 200 0
      src/SharpGLTF.DOM/Schema2/glb.Images.cs
  5. 50 40
      src/SharpGLTF.DOM/Schema2/gltf.Accessors.cs
  6. 8 0
      src/SharpGLTF.DOM/Schema2/gltf.Animations.cs
  7. 11 6
      src/SharpGLTF.DOM/Schema2/gltf.Buffer.cs
  8. 12 6
      src/SharpGLTF.DOM/Schema2/gltf.BufferView.cs
  9. 9 0
      src/SharpGLTF.DOM/Schema2/gltf.Camera.cs
  10. 3 0
      src/SharpGLTF.DOM/Schema2/gltf.LogicalChildOfRoot.cs
  11. 19 4
      src/SharpGLTF.DOM/Schema2/gltf.Materials.cs
  12. 16 11
      src/SharpGLTF.DOM/Schema2/gltf.MaterialsFactory.cs
  13. 18 4
      src/SharpGLTF.DOM/Schema2/gltf.Mesh.cs
  14. 24 25
      src/SharpGLTF.DOM/Schema2/gltf.MeshPrimitive.cs
  15. 2 2
      src/SharpGLTF.DOM/Schema2/gltf.Root.cs
  16. 36 4
      src/SharpGLTF.DOM/Schema2/gltf.Scene.cs
  17. 3 3
      src/SharpGLTF.DOM/Schema2/gltf.Serialization.cs
  18. 22 0
      src/SharpGLTF.DOM/Schema2/gltf.Skin.cs
  19. 33 192
      src/SharpGLTF.DOM/Schema2/gltf.Textures.cs
  20. 1 1
      tests/SharpGLTF.Tests/Geometry/CreateMeshTests.cs
  21. 1 1
      tests/SharpGLTF.Tests/Schema2/AccessorSparseTests.cs
  22. 175 0
      tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs
  23. 29 32
      tests/SharpGLTF.Tests/Schema2/Authoring/SimpleMeshBuilder.cs
  24. 0 96
      tests/SharpGLTF.Tests/Schema2/CreateModelTests.cs
  25. 14 6
      tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadModelTests.cs
  26. 8 0
      tests/SharpGLTF.Tests/WavefrontWriter.cs
  27. 2 2
      tests/SharpGLTF.Tests/readme.md

+ 1 - 1
src/SharpGLTF.DOM/Geometry/MeshPrimitive.cs

@@ -204,7 +204,7 @@ namespace SharpGLTF.Geometry
                 .Select(kvp => new VertexAccessor(kvp.Key, kvp.Value))
                 .Select(kvp => new VertexAccessor(kvp.Key, kvp.Value))
                 .ToArray();
                 .ToArray();
 
 
-            for (int i = 0; i < primitive.MorpthTargetsCount; ++i)
+            for (int i = 0; i < primitive.MorphTargetsCount; ++i)
             {
             {
                 var accessors = primitive.GetMorphTargetAccessors(i)
                 var accessors = primitive.GetMorphTargetAccessors(i)
                     .Select(kvp => new VertexAccessor(kvp.Key, kvp.Value))
                     .Select(kvp => new VertexAccessor(kvp.Key, kvp.Value))

+ 79 - 35
src/SharpGLTF.DOM/Memory/FloatingArrays.cs

@@ -12,17 +12,17 @@ namespace SharpGLTF.Memory
     using ENCODING = Schema2.ComponentType;
     using ENCODING = Schema2.ComponentType;
 
 
     /// <summary>
     /// <summary>
-    /// Wraps a <see cref="ArraySegment{Byte}"/> containing encoded floating point values
+    /// Wraps a <see cref="ArraySegment{Byte}"/> containing encoded <see cref="Single"/> values
     /// </summary>
     /// </summary>
     struct FloatingAccessor
     struct FloatingAccessor
     {
     {
         #region constructors
         #region constructors
 
 
-        public FloatingAccessor(BYTES data, 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)
         {
         {
             var enclen = encoding.ByteLength();
             var enclen = encoding.ByteLength();
 
 
-            this._Data = data.Slice(byteOffset);
+            this._Data = source.Slice(byteOffset);
             this._Getter = null;
             this._Getter = null;
             this._Setter = null;
             this._Setter = null;
             this._ByteStride = Math.Max(byteStride, enclen * dimensions);
             this._ByteStride = Math.Max(byteStride, enclen * dimensions);
@@ -208,19 +208,41 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Single Scalar values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Single"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Scalar Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Scalar Accessor {Count}")]
     public struct ScalarArray : IEncodedArray<Single>
     public struct ScalarArray : IEncodedArray<Single>
     {
     {
         #region constructors
         #region constructors
 
 
-        public ScalarArray(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
-
-        public ScalarArray(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScalarArray"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="byteStride">
+        /// The byte stride between elements.
+        /// If the value is zero, the size of the item is used instead.
+        /// </param>
+        /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
+        /// <param name="normalized">True if values are normalized.</param>
+        public ScalarArray(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ScalarArray"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="byteOffset">The zero-based index of the first <see cref="Byte"/> in <paramref name="source"/>.</param>
+        /// <param name="itemsCount">The number of <see cref="Single"/> items in <paramref name="source"/>.</param>
+        /// <param name="byteStride">
+        /// The byte stride between elements.
+        /// If the value is zero, the size of the item is used instead.
+        /// </param>
+        /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
+        /// <param name="normalized">True if values are normalized.</param>
+        public ScalarArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 1, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 1, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -260,19 +282,19 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Vector2 values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector2"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Vector2 Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Vector2 Accessor {Count}")]
     public struct Vector2Array : IEncodedArray<Vector2>
     public struct Vector2Array : IEncodedArray<Vector2>
     {
     {
         #region constructors
         #region constructors
 
 
-        public Vector2Array(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
+        public Vector2Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public Vector2Array(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Vector2Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 2, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 2, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -320,19 +342,41 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Vector3 values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector3"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Vector3 Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Vector3 Accessor {Count}")]
     public struct Vector3Array : IEncodedArray<Vector3>
     public struct Vector3Array : IEncodedArray<Vector3>
     {
     {
         #region constructors
         #region constructors
 
 
-        public Vector3Array(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
-
-        public Vector3Array(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Vector3Array"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="byteStride">
+        /// The byte stride between elements.
+        /// If the value is zero, the size of the item is used instead.
+        /// </param>
+        /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
+        /// <param name="normalized">True if values are normalized.</param>
+        public Vector3Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Vector3Array"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="byteOffset">The zero-based index of the first <see cref="Byte"/> in <paramref name="source"/>.</param>
+        /// <param name="itemsCount">The number of <see cref="Vector3"/> items in <paramref name="source"/>.</param>
+        /// <param name="byteStride">
+        /// The byte stride between elements.
+        /// If the value is zero, the size of the item is used instead.
+        /// </param>
+        /// <param name="encoding">A value of <see cref="ENCODING"/>.</param>
+        /// <param name="normalized">True if values are normalized.</param>
+        public Vector3Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 3, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 3, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -381,19 +425,19 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Vector4 values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector4"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Vector4 Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Vector4 Accessor {Count}")]
     public struct Vector4Array : IEncodedArray<Vector4>
     public struct Vector4Array : IEncodedArray<Vector4>
     {
     {
         #region constructors
         #region constructors
 
 
-        public Vector4Array(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
+        public Vector4Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public Vector4Array(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Vector4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -443,19 +487,19 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Quaternion values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Quaternion"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("Quaternion Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Quaternion Accessor {Count}")]
     public struct QuaternionArray : IEncodedArray<Quaternion>
     public struct QuaternionArray : IEncodedArray<Quaternion>
     {
     {
         #region constructors
         #region constructors
 
 
-        public QuaternionArray(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
+        public QuaternionArray(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public QuaternionArray(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public QuaternionArray(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 4, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion
@@ -505,19 +549,19 @@ namespace SharpGLTF.Memory
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of Matrix4x4 values
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Matrix4x4"/> values
     /// </summary>
     /// </summary>
     [System.Diagnostics.DebuggerDisplay("MAtrix4x4 Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("MAtrix4x4 Accessor {Count}")]
     public struct Matrix4x4Array : IEncodedArray<Matrix4x4>
     public struct Matrix4x4Array : IEncodedArray<Matrix4x4>
     {
     {
         #region constructors
         #region constructors
 
 
-        public Matrix4x4Array(BYTES data, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
-            : this(data, 0, int.MaxValue, byteStride, encoding, normalized) { }
+        public Matrix4x4Array(BYTES source, int byteStride = 0, ENCODING encoding = ENCODING.FLOAT, Boolean normalized = false)
+            : this(source, 0, int.MaxValue, byteStride, encoding, normalized) { }
 
 
-        public Matrix4x4Array(BYTES data, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
+        public Matrix4x4Array(BYTES source, int byteOffset, int itemsCount, int byteStride, ENCODING encoding, Boolean normalized)
         {
         {
-            _Accesor = new FloatingAccessor(data, byteOffset, itemsCount, byteStride, 16, encoding, normalized);
+            _Accesor = new FloatingAccessor(source, byteOffset, itemsCount, byteStride, 16, encoding, normalized);
         }
         }
 
 
         #endregion
         #endregion

+ 34 - 9
src/SharpGLTF.DOM/Memory/IntegerArrays.cs

@@ -11,19 +11,41 @@ namespace SharpGLTF.Memory
     using ENCODING = Schema2.IndexType;
     using ENCODING = Schema2.IndexType;
 
 
     /// <summary>
     /// <summary>
-    /// Wraps an encoded byte array and exposes it as a collection of UInt32 indices
-    /// </summary
+    /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="UInt32"/> values
+    /// </summary>
     [System.Diagnostics.DebuggerDisplay("Integer Accessor {Count}")]
     [System.Diagnostics.DebuggerDisplay("Integer Accessor {Count}")]
     public struct IntegerArray : IEncodedArray<UInt32>
     public struct IntegerArray : IEncodedArray<UInt32>
     {
     {
         #region constructors
         #region constructors
 
 
-        public IntegerArray(BYTES data, ENCODING encoding = ENCODING.UNSIGNED_INT)
-            : this(data, 0, int.MaxValue, encoding) { }
-
-        public IntegerArray(BYTES data, int byteOffset, int itemsCount, ENCODING encoding)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IntegerArray"/> struct.
+        /// </summary>
+        /// <param name="source">The array to wrap.</param>
+        /// <param name="byteOffset">The zero-based index of the first <see cref="Byte"/> in <paramref name="source"/>.</param>
+        /// <param name="itemsCount">The number of <see cref="UInt32"/> items in <paramref name="source"/>.</param>
+        /// <param name="encoding">Byte encoding.</param>
+        public IntegerArray(Byte[] source, int byteOffset, int itemsCount, ENCODING encoding)
+            : this(new BYTES(source), byteOffset, itemsCount, encoding) { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IntegerArray"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="encoding">Byte encoding.</param>
+        public IntegerArray(BYTES source, ENCODING encoding = ENCODING.UNSIGNED_INT)
+            : this(source, 0, int.MaxValue, encoding) { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IntegerArray"/> struct.
+        /// </summary>
+        /// <param name="source">The array range to wrap.</param>
+        /// <param name="byteOffset">The zero-based index of the first <see cref="Byte"/> in <paramref name="source"/>.</param>
+        /// <param name="itemsCount">The number of <see cref="UInt32"/> items in <paramref name="source"/>.</param>
+        /// <param name="encoding">Byte encoding.</param>
+        public IntegerArray(BYTES source, int byteOffset, int itemsCount, ENCODING encoding)
         {
         {
-            _Data = data.Slice(byteOffset);
+            _Data = source.Slice(byteOffset);
             _ByteStride = encoding.ByteLength();
             _ByteStride = encoding.ByteLength();
             this._Setter = null;
             this._Setter = null;
             this._Getter = null;
             this._Getter = null;
@@ -79,9 +101,9 @@ namespace SharpGLTF.Memory
 
 
         #region data
         #region data
 
 
-        delegate UInt32 _GetterCallback(int index);
+        private delegate UInt32 _GetterCallback(int index);
 
 
-        delegate void _SetterCallback(int index, UInt32 value);
+        private delegate void _SetterCallback(int index, UInt32 value);
 
 
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private readonly BYTES _Data;
         private readonly BYTES _Data;
@@ -102,6 +124,9 @@ namespace SharpGLTF.Memory
 
 
         #region API
         #region API
 
 
+        /// <summary>
+        /// Gets the number of elements in the range delimited by the <see cref="IntegerArray"/>
+        /// </summary>
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         public int Count => _Data.Count / _ByteStride;
         public int Count => _Data.Count / _ByteStride;
 
 

+ 200 - 0
src/SharpGLTF.DOM/Schema2/glb.Images.cs

@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Schema2
+{
+    [System.Diagnostics.DebuggerDisplay("Image[{LogicalIndex}] {Name}")]
+    public sealed partial class Image
+    {
+        #region Base64 constants
+
+        const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream;base64,";
+        const string EMBEDDEDGLTFBUFFER = "data:application/gltf-buffer;base64,";
+        const string EMBEDDEDJPEGBUFFER = "data:image/jpeg;base64,";
+        const string EMBEDDEDPNGBUFFER = "data:image/png;base64,";
+
+        const string MIMEPNG = "image/png";
+        const string MIMEJPEG = "image/jpeg";
+
+        #endregion
+
+        #region lifecycle
+
+        internal Image() { }
+
+        #endregion
+
+        #region data
+
+        // this is the actual compressed image in PNG or JPEG, -NOT- the pixels data.
+        private Byte[] _ExternalImageContent;
+
+        #endregion
+
+        #region properties
+
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Image"/> at <see cref="ModelRoot.LogicalImages"/>
+        /// </summary>
+        public int LogicalIndex => this.LogicalParent.LogicalImages.IndexOfReference(this);
+
+        public bool IsPng => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("png");
+        public bool IsJpeg => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("jpg") | _mimeType.Contains("jpeg");
+
+        #endregion
+
+        #region API
+
+        private static bool _IsPng(IReadOnlyList<Byte> data)
+        {
+            if (data[0] != 0x89) return false;
+            if (data[1] != 0x50) return false;
+            if (data[2] != 0x4e) return false;
+            if (data[3] != 0x47) return false;
+
+            return true;
+        }
+
+        private static bool _IsJpeg(IReadOnlyList<Byte> data)
+        {
+            if (data[0] != 0xff) return false;
+            if (data[1] != 0xd8) return false;
+
+            return true;
+        }
+
+        public ArraySegment<Byte> GetImageContent()
+        {
+            if (_ExternalImageContent != null) return new ArraySegment<byte>(_ExternalImageContent);
+
+            if (this._bufferView.HasValue)
+            {
+                var bv = this.LogicalParent.LogicalBufferViews[this._bufferView.Value];
+
+                return bv.Content;
+            }
+
+            throw new InvalidOperationException();
+        }
+
+        public Image WithExternalFile(string filePath)
+        {
+            var content = System.IO.File.ReadAllBytes(filePath);
+            return WithExternalContent(content);
+        }
+
+        public Image WithExternalContent(Byte[] content)
+        {
+            if (_IsPng(content)) _mimeType = MIMEPNG; // these strings might be wrong
+            if (_IsJpeg(content)) _mimeType = MIMEJPEG; // these strings might be wrong
+
+            this._uri = null;
+            this._bufferView = null;
+            this._ExternalImageContent = content;
+
+            return this;
+        }
+
+        public void UseBufferViewContainer()
+        {
+            if (this._ExternalImageContent == null) return;
+
+            var data = new ArraySegment<Byte>(this._ExternalImageContent);
+
+            var bv = this.LogicalParent.UseBufferView(data);
+
+            this._uri = null;
+            this._bufferView = bv.LogicalIndex;
+
+            this._ExternalImageContent = null;
+        }
+
+        #endregion
+
+        #region binary read
+
+        internal void _ResolveUri(AssetReader externalReferenceSolver)
+        {
+            if (!String.IsNullOrWhiteSpace(_uri))
+            {
+                _ExternalImageContent = _LoadImageUnchecked(_uri, externalReferenceSolver);
+            }
+
+            _uri = null; // When _Data is not empty, clear URI
+        }
+
+        private static Byte[] _LoadImageUnchecked(string uri, AssetReader externalReferenceSolver)
+        {
+            return uri._TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER)
+                ?? uri._TryParseBase64Unchecked(EMBEDDEDOCTETSTREAM)
+                ?? uri._TryParseBase64Unchecked(EMBEDDEDJPEGBUFFER)
+                ?? uri._TryParseBase64Unchecked(EMBEDDEDPNGBUFFER)
+                ?? externalReferenceSolver?.Invoke(uri);
+        }
+
+        #endregion
+
+        #region binary write
+
+        internal void _EmbedAssets()
+        {
+            if (_ExternalImageContent != null)
+            {
+                var mimeContent = Convert.ToBase64String(_ExternalImageContent, Base64FormattingOptions.None);
+
+                if (_IsPng(_ExternalImageContent))
+                {
+                    _mimeType = MIMEPNG;
+                    _uri = EMBEDDEDPNGBUFFER + mimeContent;
+                    return;
+                }
+
+                if (_IsJpeg(_ExternalImageContent))
+                {
+                    _mimeType = MIMEJPEG;
+                    _uri = EMBEDDEDJPEGBUFFER + mimeContent;
+                    return;
+                }
+
+                throw new NotImplementedException();
+            }
+        }
+
+        internal void _WriteExternalAssets(string uri, AssetWriter writer)
+        {
+            if (_ExternalImageContent != null)
+            {
+                if (this._mimeType.Contains("png")) uri += ".png";
+                if (this._mimeType.Contains("jpg")) uri += ".jpg";
+                if (this._mimeType.Contains("jpeg")) uri += ".jpg";
+
+                this._uri = uri;
+                writer(uri, _ExternalImageContent);
+            }
+        }
+
+        internal void _ClearAfterWrite() { this._uri = null; }
+
+        #endregion
+    }
+
+    public partial class ModelRoot
+    {
+        /// <summary>
+        /// Creates a new <see cref="Image"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalImages"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Image"/> instance.</returns>
+        public Image CreateImage(string name = null)
+        {
+            var image = new Image();
+            image.Name = name;
+
+            this._images.Add(image);
+
+            return image;
+        }
+    }
+}

+ 50 - 40
src/SharpGLTF.DOM/Schema2/gltf.Accessors.cs

@@ -36,6 +36,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Accessor"/> at <see cref="ModelRoot.LogicalAccessors"/>
+        /// </summary>
         public int LogicalIndex                 => this.LogicalParent.LogicalAccessors.IndexOfReference(this);
         public int LogicalIndex                 => this.LogicalParent.LogicalAccessors.IndexOfReference(this);
 
 
         internal int _LogicalBufferViewIndex    => this._bufferView.AsValue(-1);
         internal int _LogicalBufferViewIndex    => this._bufferView.AsValue(-1);
@@ -91,23 +94,24 @@ namespace SharpGLTF.Schema2
 
 
         #region Data Buffer API
         #region Data Buffer API
 
 
-        internal void SetData(BufferView buffer, int byteOffset, ElementType dimensions, ComponentType encoding, Boolean normalized, int count)
+        public Accessor WithData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions, ComponentType encoding, Boolean normalized)
         {
         {
-            Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
 
 
-            Guard.MustBeGreaterThanOrEqualTo(byteOffset, 0, nameof(byteOffset));
-            Guard.MustBeGreaterThan(count, 0, nameof(count));
+            Guard.MustBeGreaterThanOrEqualTo(byteOffset, _byteOffsetMinimum, nameof(byteOffset));
+            Guard.MustBeGreaterThanOrEqualTo(itemCount, _countMinimum, nameof(itemCount));
 
 
             this._bufferView = buffer.LogicalIndex;
             this._bufferView = buffer.LogicalIndex;
-            this._byteOffset = byteOffset;
-            this._count = count;
+            this._byteOffset = byteOffset.AsNullable(_byteOffsetDefault, _byteOffsetMinimum, int.MaxValue);
+            this._count = itemCount;
 
 
             this._type = dimensions;
             this._type = dimensions;
             this._componentType = encoding;
             this._componentType = encoding;
-            this._normalized = normalized.AsNullable(false);
+            this._normalized = normalized.AsNullable(_normalizedDefault);
 
 
             UpdateBounds();
             UpdateBounds();
+
+            return this;
         }
         }
 
 
         public Memory.Matrix4x4Array AsMatrix4x4Array()
         public Memory.Matrix4x4Array AsMatrix4x4Array()
@@ -122,13 +126,13 @@ namespace SharpGLTF.Schema2
         public Accessor WithIndexData(Geometry.MemoryAccessor src)
         public Accessor WithIndexData(Geometry.MemoryAccessor src)
         {
         {
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ELEMENT_ARRAY_BUFFER);
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ELEMENT_ARRAY_BUFFER);
-            this.WithIndexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Encoding.ToIndex());
-
-            return this;
+            return this.WithIndexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Encoding.ToIndex());
         }
         }
 
 
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<Int32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<Int32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         {
         {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+
             var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
             var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             return WithIndexData(buffer, byteOffset, items.Count, encoding);
             return WithIndexData(buffer, byteOffset, items.Count, encoding);
@@ -136,6 +140,8 @@ namespace SharpGLTF.Schema2
 
 
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<UInt32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         public Accessor WithIndexData(BufferView buffer, int byteOffset, IReadOnlyList<UInt32> items, IndexType encoding = IndexType.UNSIGNED_INT)
         {
         {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+
             var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
             var array = new Memory.IntegerArray(buffer.Content, byteOffset, items.Count, encoding);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             return WithIndexData(buffer, byteOffset, items.Count, encoding);
             return WithIndexData(buffer, byteOffset, items.Count, encoding);
@@ -145,22 +151,10 @@ namespace SharpGLTF.Schema2
         {
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
-            if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ELEMENT_ARRAY_BUFFER, nameof(buffer));
 
 
-            Guard.MustBeGreaterThanOrEqualTo(byteOffset, 0, nameof(byteOffset));
-            Guard.MustBeGreaterThan(itemCount, 0, nameof(itemCount));
-
-            this._bufferView = buffer.LogicalIndex;
-            this._byteOffset = byteOffset;
-            this._count = itemCount;
-
-            this._type = ElementType.SCALAR;
-            this._componentType = encoding.ToComponent();
-            this._normalized = null;
-
-            UpdateBounds();
+            if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ELEMENT_ARRAY_BUFFER, nameof(buffer));
 
 
-            return this;
+            return WithData(buffer, byteOffset, itemCount, ElementType.SCALAR, encoding.ToComponent(), false);
         }
         }
 
 
         public Memory.IntegerArray AsIndicesArray()
         public Memory.IntegerArray AsIndicesArray()
@@ -178,38 +172,48 @@ namespace SharpGLTF.Schema2
         public Accessor WithVertexData(Geometry.MemoryAccessor src)
         public Accessor WithVertexData(Geometry.MemoryAccessor src)
         {
         {
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ARRAY_BUFFER);
             var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ARRAY_BUFFER);
-            this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
+            return this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
+        }
 
 
-            return this;
+        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector2> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustBePositiveAndMultipleOf(ElementType.VEC2.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
+
+            var array = new Memory.Vector2Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
+            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
+            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC2, encoding, normalized);
         }
         }
 
 
         public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector3> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector3> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustBePositiveAndMultipleOf(ElementType.VEC3.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
+
             var array = new Memory.Vector3Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
             var array = new Memory.Vector3Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             Memory.EncodedArrayUtils.FillFrom(array, 0, items);
             return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC3, encoding, normalized);
             return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC3, encoding, normalized);
         }
         }
 
 
-        public Accessor WithVertexData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector4> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
-            Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
-            if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ARRAY_BUFFER, nameof(buffer));
-
-            Guard.MustBeGreaterThanOrEqualTo(byteOffset, 0, nameof(byteOffset));
-            Guard.MustBeGreaterThan(itemCount, 0, nameof(itemCount));
+            Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            this._bufferView = buffer.LogicalIndex;
-            this._byteOffset = byteOffset;
-            this._count = itemCount;
+            var array = new Memory.Vector4Array(buffer.Content.Slice(byteOffset), buffer.ByteStride);
+            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
+            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
+        }
 
 
-            this._type = dimensions;
-            this._componentType = encoding;
-            this._normalized = normalized.AsNullable(false);
+        public Accessor WithVertexData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        {
+            Guard.NotNull(buffer, nameof(buffer));
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustBePositiveAndMultipleOf(dimensions.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
 
 
-            UpdateBounds();
+            if (buffer.DeviceBufferTarget.HasValue) Guard.IsTrue(buffer.DeviceBufferTarget.Value == BufferMode.ARRAY_BUFFER, nameof(buffer));
 
 
-            return this;
+            return WithData(buffer, byteOffset, itemCount, dimensions, encoding, normalized);
         }
         }
 
 
         public Memory.IEncodedArray<Single> AsScalarArray()
         public Memory.IEncodedArray<Single> AsScalarArray()
@@ -372,6 +376,12 @@ namespace SharpGLTF.Schema2
 
 
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates a new <see cref="Accessor"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalAccessors"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Accessor"/> instance.</returns>
         public Accessor CreateAccessor(string name = null)
         public Accessor CreateAccessor(string name = null)
         {
         {
             var accessor = new Accessor
             var accessor = new Accessor

+ 8 - 0
src/SharpGLTF.DOM/Schema2/gltf.Animations.cs

@@ -22,6 +22,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Animation"/> at <see cref="ModelRoot.LogicalAnimations"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalAnimations.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalAnimations.IndexOfReference(this);
 
 
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
@@ -194,6 +197,11 @@ namespace SharpGLTF.Schema2
 
 
     public sealed partial class ModelRoot
     public sealed partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates a new <see cref="Animation"/> instance and adds it to <see cref="ModelRoot.LogicalAnimations"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Animation"/> instance.</returns>
         public Animation CreateAnimation(string name = null)
         public Animation CreateAnimation(string name = null)
         {
         {
             var anim = new Animation
             var anim = new Animation

+ 11 - 6
src/SharpGLTF.DOM/Schema2/gltf.Buffer.cs

@@ -27,6 +27,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Buffer"/> at <see cref="ModelRoot.LogicalBuffers"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalBuffers.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalBuffers.IndexOfReference(this);
 
 
         public Byte[] Content => _Content;
         public Byte[] Content => _Content;
@@ -82,10 +85,11 @@ namespace SharpGLTF.Schema2
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
         /// <summary>
         /// <summary>
-        /// Creates a buffer with <paramref name="byteCount"/> size.
+        /// Creates a new <see cref="Buffer"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalBuffers"/>.
         /// </summary>
         /// </summary>
-        /// <param name="byteCount">the size of the buffer</param>
-        /// <returns>the buffer</returns>
+        /// <param name="byteCount">the size of the buffer, in bytes.</param>
+        /// <returns>A <see cref="Buffer"/> instance.</returns>
         public Buffer CreateBuffer(int byteCount)
         public Buffer CreateBuffer(int byteCount)
         {
         {
             var buffer = new Buffer();
             var buffer = new Buffer();
@@ -97,10 +101,11 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Finds and existing buffer that is already using <paramref name="content"/> , or creates a new one if none is found.
+        /// Creates or reuses a <see cref="Buffer"/> instance
+        /// at <see cref="ModelRoot.LogicalBuffers"/>.
         /// </summary>
         /// </summary>
         /// <param name="content">the byte array to be wrapped as a buffer</param>
         /// <param name="content">the byte array to be wrapped as a buffer</param>
-        /// <returns>the buffer</returns>
+        /// <returns>A <see cref="Buffer"/> instance.</returns>
         public Buffer UseBuffer(Byte[] content)
         public Buffer UseBuffer(Byte[] content)
         {
         {
             Guard.IsFalse(content == null, nameof(content));
             Guard.IsFalse(content == null, nameof(content));
@@ -119,7 +124,7 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Merges all the Buffer objects into a single, big one.
+        /// Merges all the <see cref="ModelRoot.LogicalBuffers"/> instances into a single, big one.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// When merging the buffers, it also adjusts the BufferView offsets so the data they point to remains the same.
         /// When merging the buffers, it also adjusts the BufferView offsets so the data they point to remains the same.

+ 12 - 6
src/SharpGLTF.DOM/Schema2/gltf.BufferView.cs

@@ -54,6 +54,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="BufferView"/> at <see cref="ModelRoot.LogicalBufferViews"/>
+        /// </summary>
         public int LogicalIndex                 => this.LogicalParent.LogicalBufferViews.IndexOfReference(this);
         public int LogicalIndex                 => this.LogicalParent.LogicalBufferViews.IndexOfReference(this);
 
 
         public BufferMode? DeviceBufferTarget   => this._target;
         public BufferMode? DeviceBufferTarget   => this._target;
@@ -130,12 +133,13 @@ namespace SharpGLTF.Schema2
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
         /// <summary>
         /// <summary>
-        /// Creates or reuses a <see cref="BufferView"/> matching the input parameters.
+        /// Creates or reuses a <see cref="BufferView"/> instance
+        /// at <see cref="ModelRoot.LogicalBufferViews"/>.
         /// </summary>
         /// </summary>
         /// <param name="data">The array range to wrap.</param>
         /// <param name="data">The array range to wrap.</param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="target">The type hardware device buffer, or null</param>
         /// <param name="target">The type hardware device buffer, or null</param>
-        /// <returns>A <see cref="BufferView"/> wrapping <paramref name="data"/></returns>
+        /// <returns>A <see cref="BufferView"/> instance.</returns>
         public BufferView UseBufferView(ArraySegment<Byte> data, int byteStride = 0, BufferMode? target = null)
         public BufferView UseBufferView(ArraySegment<Byte> data, int byteStride = 0, BufferMode? target = null)
         {
         {
             Guard.NotNull(data.Array, nameof(data));
             Guard.NotNull(data.Array, nameof(data));
@@ -143,14 +147,15 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates or reuses a <see cref="BufferView"/> matching the input parameters.
+        /// Creates or reuses a <see cref="BufferView"/> instance
+        /// at <see cref="ModelRoot.LogicalBufferViews"/>.
         /// </summary>
         /// </summary>
         /// <param name="buffer">The array to wrap.</param>
         /// <param name="buffer">The array to wrap.</param>
         /// <param name="byteOffset">The zero-based index of the first Byte in <paramref name="buffer"/></param>
         /// <param name="byteOffset">The zero-based index of the first Byte in <paramref name="buffer"/></param>
         /// <param name="byteLength">The number of elements in <paramref name="buffer"/></param>
         /// <param name="byteLength">The number of elements in <paramref name="buffer"/></param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="target">The type hardware device buffer, or null</param>
         /// <param name="target">The type hardware device buffer, or null</param>
-        /// <returns>A <see cref="BufferView"/> wrapping <paramref name="buffer"/></returns>
+        /// <returns>A <see cref="BufferView"/> instance.</returns>
         public BufferView UseBufferView(Byte[] buffer, int byteOffset = 0, int? byteLength = null, int byteStride = 0, BufferMode? target = null)
         public BufferView UseBufferView(Byte[] buffer, int byteOffset = 0, int? byteLength = null, int byteStride = 0, BufferMode? target = null)
         {
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer, nameof(buffer));
@@ -158,14 +163,15 @@ namespace SharpGLTF.Schema2
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates or reuses a <see cref="BufferView"/> matching the input parameters.
+        /// Creates or reuses a <see cref="BufferView"/> instance
+        /// at <see cref="ModelRoot.LogicalBufferViews"/>.
         /// </summary>
         /// </summary>
         /// <param name="buffer">The buffer to wrap.</param>
         /// <param name="buffer">The buffer to wrap.</param>
         /// <param name="byteOffset">The zero-based index of the first Byte in <paramref name="buffer"/></param>
         /// <param name="byteOffset">The zero-based index of the first Byte in <paramref name="buffer"/></param>
         /// <param name="byteLength">The number of elements in <paramref name="buffer"/></param>
         /// <param name="byteLength">The number of elements in <paramref name="buffer"/></param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="byteStride">For strided vertex buffers, it must be a value multiple of 4, 0 otherwise</param>
         /// <param name="target">The type hardware device buffer, or null</param>
         /// <param name="target">The type hardware device buffer, or null</param>
-        /// <returns>A <see cref="BufferView"/> wrapping <paramref name="buffer"/></returns>
+        /// <returns>A <see cref="BufferView"/> instance.</returns>
         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));

+ 9 - 0
src/SharpGLTF.DOM/Schema2/gltf.Camera.cs

@@ -15,6 +15,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Camera"/> at <see cref="ModelRoot.LogicalCameras"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalCameras.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalCameras.IndexOfReference(this);
 
 
         public CameraType Type
         public CameraType Type
@@ -28,6 +31,12 @@ namespace SharpGLTF.Schema2
 
 
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates a new <see cref="Camera"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalCameras"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Camera"/> instance.</returns>
         public Camera CreateCamera(string name = null)
         public Camera CreateCamera(string name = null)
         {
         {
             var camera = new Camera
             var camera = new Camera

+ 3 - 0
src/SharpGLTF.DOM/Schema2/gltf.LogicalChildOfRoot.cs

@@ -23,6 +23,9 @@ namespace SharpGLTF.Schema2
 
 
         #region IChildOf<ROOT>
         #region IChildOf<ROOT>
 
 
+        /// <summary>
+        /// Gets the <see cref="ModelRoot"/> instance that owns this object.
+        /// </summary>
         public ModelRoot LogicalParent { get; private set; }
         public ModelRoot LogicalParent { get; private set; }
 
 
         void IChildOf<ModelRoot>._SetLogicalParent(ModelRoot parent) { LogicalParent = parent; }
         void IChildOf<ModelRoot>._SetLogicalParent(ModelRoot parent) { LogicalParent = parent; }

+ 19 - 4
src/SharpGLTF.DOM/Schema2/gltf.Materials.cs

@@ -17,30 +17,45 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Material"/> at <see cref="ModelRoot.LogicalMaterials"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalMaterials.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalMaterials.IndexOfReference(this);
 
 
+        /// <summary>
+        /// Gets or sets the <see cref="AlphaMode"/> of this <see cref="Material"/> instance.
+        /// </summary>
         public AlphaMode Alpha
         public AlphaMode Alpha
         {
         {
             get => _alphaMode.AsValue(_alphaModeDefault);
             get => _alphaMode.AsValue(_alphaModeDefault);
             set => _alphaMode = value.AsNullable(_alphaModeDefault);
             set => _alphaMode = value.AsNullable(_alphaModeDefault);
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets the <see cref="AlphaCutoff"/> of this <see cref="Material"/> instance.
+        /// </summary>
         public Double AlphaCutoff
         public Double AlphaCutoff
         {
         {
             get => _alphaCutoff.AsValue(_alphaCutoffDefault);
             get => _alphaCutoff.AsValue(_alphaCutoffDefault);
             set => _alphaCutoff = value.AsNullable(_alphaCutoffDefault, _alphaCutoffMinimum, double.MaxValue);
             set => _alphaCutoff = value.AsNullable(_alphaCutoffDefault, _alphaCutoffMinimum, double.MaxValue);
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this <see cref="Material"/> will render as Double Sided.
+        /// </summary>
         public Boolean DoubleSided
         public Boolean DoubleSided
         {
         {
             get => _doubleSided.AsValue(_doubleSidedDefault);
             get => _doubleSided.AsValue(_doubleSidedDefault);
             set => _doubleSided = value.AsNullable(_doubleSidedDefault);
             set => _doubleSided = value.AsNullable(_doubleSidedDefault);
         }
         }
 
 
+        /// <summary>
+        /// Gets a value indicating whether this <see cref="Material"/> instance has Unlit extension.
+        /// </summary>
         public Boolean Unlit => this.GetExtension<MaterialUnlit_KHR>() != null;
         public Boolean Unlit => this.GetExtension<MaterialUnlit_KHR>() != null;
 
 
         /// <summary>
         /// <summary>
-        /// Gets a collection of channel views available for this material.
+        /// Gets a collection of <see cref="MaterialChannelView"/> elements available in this <see cref="Material"/> instance.
         /// </summary>
         /// </summary>
         public IEnumerable<MaterialChannelView> Channels => _GetChannels();
         public IEnumerable<MaterialChannelView> Channels => _GetChannels();
 
 
@@ -49,11 +64,11 @@ namespace SharpGLTF.Schema2
         #region API
         #region API
 
 
         /// <summary>
         /// <summary>
-        /// Returns an object that allows to read and write information of a given channel of the material.
+        /// Finds an instance of <see cref="MaterialChannelView"/>
         /// </summary>
         /// </summary>
         /// <param name="key">the channel key</param>
         /// <param name="key">the channel key</param>
-        /// <returns>the channel view</returns>
-        public MaterialChannelView GetChannel(string key)
+        /// <returns>A <see cref="MaterialChannelView"/> instance, or null if <paramref name="key"/> does not exist.</returns>
+        public MaterialChannelView FindChannel(string key)
         {
         {
             return Channels.FirstOrDefault(item => item.Key == key);
             return Channels.FirstOrDefault(item => item.Key == key);
         }
         }

+ 16 - 11
src/SharpGLTF.DOM/Schema2/gltf.MaterialsFactory.cs

@@ -10,21 +10,21 @@ namespace SharpGLTF.Schema2
     {
     {
         #region API
         #region API
 
 
-        public Material InitializeDefault()
+        public Material WithDefault(Vector4 diffuseColor)
         {
         {
-            return this.InitializePBRMetallicRoughness();
-        }
-
-        public Material InitializeDefault(Vector4 diffuseColor)
-        {
-            this.InitializePBRMetallicRoughness()
-                .GetChannel("BaseColor")
+            this.WithPBRMetallicRoughness()
+                .FindChannel("BaseColor")
                 .SetFactor(diffuseColor);
                 .SetFactor(diffuseColor);
 
 
             return this;
             return this;
         }
         }
 
 
-        public Material InitializePBRMetallicRoughness()
+        public Material WithDefault()
+        {
+            return this.WithPBRMetallicRoughness();
+        }
+
+        public Material WithPBRMetallicRoughness()
         {
         {
             this._pbrMetallicRoughness = new MaterialPBRMetallicRoughness();
             this._pbrMetallicRoughness = new MaterialPBRMetallicRoughness();
 
 
@@ -34,7 +34,7 @@ namespace SharpGLTF.Schema2
             return this;
             return this;
         }
         }
 
 
-        public Material InitializePBRSpecularGlossiness()
+        public Material WithPBRSpecularGlossiness()
         {
         {
             this.RemoveExtensions<MaterialUnlit_KHR>();
             this.RemoveExtensions<MaterialUnlit_KHR>();
             this.SetExtension(new MaterialPBRSpecularGlossiness_KHR());
             this.SetExtension(new MaterialPBRSpecularGlossiness_KHR());
@@ -42,7 +42,7 @@ namespace SharpGLTF.Schema2
             return this;
             return this;
         }
         }
 
 
-        public Material InitializeUnlit()
+        public Material WithUnlit()
         {
         {
             this.RemoveExtensions<MaterialPBRSpecularGlossiness_KHR>();
             this.RemoveExtensions<MaterialPBRSpecularGlossiness_KHR>();
             this.SetExtension(new MaterialUnlit_KHR());
             this.SetExtension(new MaterialUnlit_KHR());
@@ -98,6 +98,11 @@ namespace SharpGLTF.Schema2
 
 
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates a new <see cref="Material"/> instance and adds it to <see cref="ModelRoot.LogicalMaterials"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Material"/> instance.</returns>
         public Material CreateMaterial(string name = null)
         public Material CreateMaterial(string name = null)
         {
         {
             var mat = new Material();
             var mat = new Material();

+ 18 - 4
src/SharpGLTF.DOM/Schema2/gltf.Mesh.cs

@@ -23,6 +23,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Mesh"/> at <see cref="ModelRoot.LogicalMeshes"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalMeshes.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalMeshes.IndexOfReference(this);
 
 
         public IEnumerable<Node> VisualParents => Node.FindNodesUsingMesh(this);
         public IEnumerable<Node> VisualParents => Node.FindNodesUsingMesh(this);
@@ -37,6 +40,11 @@ namespace SharpGLTF.Schema2
 
 
         #region API
         #region API
 
 
+        /// <summary>
+        /// Creates a new <see cref="MeshPrimitive"/> instance
+        /// and adds it to the current <see cref="Mesh"/>.
+        /// </summary>
+        /// <returns>A <see cref="MeshPrimitive"/> instance.</returns>
         public MeshPrimitive CreatePrimitive()
         public MeshPrimitive CreatePrimitive()
         {
         {
             var mp = new MeshPrimitive();
             var mp = new MeshPrimitive();
@@ -63,14 +71,20 @@ namespace SharpGLTF.Schema2
 
 
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates a new <see cref="Mesh"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalMeshes"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Mesh"/> instance.</returns>
         public Mesh CreateMesh(string name = null)
         public Mesh CreateMesh(string name = null)
         {
         {
-            var dstMesh = new Mesh();
-            dstMesh.Name = name;
+            var mesh = new Mesh();
+            mesh.Name = name;
 
 
-            this._meshes.Add(dstMesh);
+            this._meshes.Add(mesh);
 
 
-            return dstMesh;
+            return mesh;
         }
         }
     }
     }
 }
 }

+ 24 - 25
src/SharpGLTF.DOM/Schema2/gltf.MeshPrimitive.cs

@@ -35,12 +35,21 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="MeshPrimitive"/> at <see cref="Mesh.Primitives"/>.
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.Primitives.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.Primitives.IndexOfReference(this);
 
 
+        /// <summary>
+        /// Gets the <see cref="Mesh"/> instance that owns this <see cref="MeshPrimitive"/> instance.
+        /// </summary>
         public Mesh LogicalParent { get; private set; }
         public Mesh LogicalParent { get; private set; }
 
 
         void IChildOf<Mesh>._SetLogicalParent(Mesh parent) { LogicalParent = parent; }
         void IChildOf<Mesh>._SetLogicalParent(Mesh parent) { LogicalParent = parent; }
 
 
+        /// <summary>
+        /// Gets or sets the <see cref="Material"/> instance, or null.
+        /// </summary>
         public Material Material
         public Material Material
         {
         {
             get => this._material.HasValue ? LogicalParent.LogicalParent.LogicalMaterials[this._material.Value] : null;
             get => this._material.HasValue ? LogicalParent.LogicalParent.LogicalMaterials[this._material.Value] : null;
@@ -58,34 +67,13 @@ namespace SharpGLTF.Schema2
             set => this._mode = value.AsNullable(_modeDefault);
             set => this._mode = value.AsNullable(_modeDefault);
         }
         }
 
 
-        public int MorpthTargetsCount => _targets.Count;
+        public int MorphTargetsCount => _targets.Count;
 
 
         public Transforms.BoundingBox3? LocalBounds3 => VertexAccessors["POSITION"]?.LocalBounds3;
         public Transforms.BoundingBox3? LocalBounds3 => VertexAccessors["POSITION"]?.LocalBounds3;
 
 
         public IReadOnlyDictionary<String, Accessor> VertexAccessors => new ReadOnlyLinqDictionary<String, int, Accessor>(_attributes, alidx => this.LogicalParent.LogicalParent.LogicalAccessors[alidx]);
         public IReadOnlyDictionary<String, Accessor> VertexAccessors => new ReadOnlyLinqDictionary<String, int, Accessor>(_attributes, alidx => this.LogicalParent.LogicalParent.LogicalAccessors[alidx]);
 
 
-        public Accessor IndexAccessor
-        {
-            get
-            {
-                if (!this._indices.HasValue) return null;
-
-                return this.LogicalParent.LogicalParent.LogicalAccessors[this._indices.Value];
-            }
-
-            set
-            {
-                if (value == null)
-                {
-                    this._indices = null;
-                }
-                else
-                {
-                    Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, value, nameof(value));
-                    this._indices = value.LogicalIndex;
-                }
-            }
-        }
+        public Accessor IndexAccessor { get => GetIndexAccessor(); set => SetIndexAccessor(value); }
 
 
         #endregion
         #endregion
 
 
@@ -109,7 +97,7 @@ namespace SharpGLTF.Schema2
 
 
             if (includeMorphs)
             if (includeMorphs)
             {
             {
-                for (int i = 0; i < MorpthTargetsCount; ++i)
+                for (int i = 0; i < MorphTargetsCount; ++i)
                 {
                 {
                     foreach (var key in attributes)
                     foreach (var key in attributes)
                     {
                     {
@@ -151,9 +139,20 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
+        public Accessor GetIndexAccessor()
+        {
+            if (!this._indices.HasValue) return null;
+
+            return this.LogicalParent.LogicalParent.LogicalAccessors[this._indices.Value];
+        }
+
         public void SetIndexAccessor(Accessor accessor)
         public void SetIndexAccessor(Accessor accessor)
         {
         {
-            _indices = accessor == null ? (int?)null : accessor.LogicalIndex;
+            if (accessor == null) { this._indices = null; return; }
+
+            Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, accessor, nameof(accessor));
+
+            this._indices = accessor.LogicalIndex;
         }
         }
 
 
         public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int idx)
         public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int idx)

+ 2 - 2
src/SharpGLTF.DOM/Schema2/gltf.Root.cs

@@ -12,9 +12,9 @@ namespace SharpGLTF.Schema2
         #region lifecycle
         #region lifecycle
 
 
         /// <summary>
         /// <summary>
-        /// Creates a new, empty model.
+        /// Creates a new <see cref = "ModelRoot" /> instance.
         /// </summary>
         /// </summary>
-        /// <returns>A new model</returns>
+        /// <returns>A <see cref="ModelRoot"/> instance.</returns>
         public static ModelRoot CreateModel()
         public static ModelRoot CreateModel()
         {
         {
             var root = new ModelRoot();
             var root = new ModelRoot();

+ 36 - 4
src/SharpGLTF.DOM/Schema2/gltf.Scene.cs

@@ -32,14 +32,13 @@ namespace SharpGLTF.Schema2
 
 
         #region properties - hierarchy
         #region properties - hierarchy
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Node"/> at <see cref="ModelRoot.LogicalNodes"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalNodes.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalNodes.IndexOfReference(this);
 
 
         public Node VisualParent => this.LogicalParent._FindVisualParentNode(this);
         public Node VisualParent => this.LogicalParent._FindVisualParentNode(this);
 
 
-        public IEnumerable<Node> VisualChildren => GetVisualChildren();
-
-        public Boolean IsSkinJoint => Skin.FindSkinsUsing(this).Any();
-
         public Scene VisualScene
         public Scene VisualScene
         {
         {
             get
             get
@@ -50,6 +49,10 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
+        public IEnumerable<Node> VisualChildren => GetVisualChildren();
+
+        public Boolean IsSkinJoint => Skin.FindSkinsUsing(this).Any();
+
         #endregion
         #endregion
 
 
         #region properties - transform
         #region properties - transform
@@ -160,6 +163,13 @@ namespace SharpGLTF.Schema2
             return allChildren;
             return allChildren;
         }
         }
 
 
+        /// <summary>
+        /// Creates a new <see cref="Node"/> instance,
+        /// adds it to <see cref="ModelRoot.LogicalNodes"/>
+        /// and references it in the current <see cref="Node"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Node"/> instance.</returns>
         public Node CreateNode(string name)
         public Node CreateNode(string name)
         {
         {
             var node = this.LogicalParent._CreateLogicalNode(this._children);
             var node = this.LogicalParent._CreateLogicalNode(this._children);
@@ -260,6 +270,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Scene"/> at <see cref="ModelRoot.LogicalScenes"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalScenes.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalScenes.IndexOfReference(this);
 
 
         internal IReadOnlyList<int> _VisualChildrenIndices => _nodes;
         internal IReadOnlyList<int> _VisualChildrenIndices => _nodes;
@@ -284,6 +297,13 @@ namespace SharpGLTF.Schema2
             return VisualChildren.Any(item => item._ContainsVisualNode(node, true));
             return VisualChildren.Any(item => item._ContainsVisualNode(node, true));
         }
         }
 
 
+        /// <summary>
+        /// Creates a new <see cref="Node"/> instance,
+        /// adds it to <see cref="ModelRoot.LogicalNodes"/>
+        /// and references it in the current <see cref="Scene"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Node"/> instance.</returns>
         public Node CreateNode(String name)
         public Node CreateNode(String name)
         {
         {
             return this.LogicalParent._CreateLogicalNode(this._nodes);
             return this.LogicalParent._CreateLogicalNode(this._nodes);
@@ -316,6 +336,12 @@ namespace SharpGLTF.Schema2
 
 
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
+        /// <summary>
+        /// Creates or reuses a <see cref="Scene"/> instance
+        /// at <see cref="ModelRoot.LogicalScenes"/>.
+        /// </summary>
+        /// <param name="index">The zero-based index of the <see cref="Scene"/> in <see cref="ModelRoot.LogicalScenes"/>.</param>
+        /// <returns>A <see cref="Scene"/> instance.</returns>
         public Scene UseScene(int index)
         public Scene UseScene(int index)
         {
         {
             Guard.MustBeGreaterThanOrEqualTo(index, 0, nameof(index));
             Guard.MustBeGreaterThanOrEqualTo(index, 0, nameof(index));
@@ -328,6 +354,12 @@ namespace SharpGLTF.Schema2
             return _scenes[index];
             return _scenes[index];
         }
         }
 
 
+        /// <summary>
+        /// Creates or reuses a <see cref="Scene"/> instance
+        /// at <see cref="ModelRoot.LogicalScenes"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Scene"/> instance.</returns>
         public Scene UseScene(string name)
         public Scene UseScene(string name)
         {
         {
             var scene = _scenes.FirstOrDefault(item => item.Name == name);
             var scene = _scenes.FirstOrDefault(item => item.Name == name);

+ 3 - 3
src/SharpGLTF.DOM/Schema2/gltf.Serialization.cs

@@ -196,7 +196,7 @@ namespace SharpGLTF.Schema2
         #region Write API
         #region Write API
 
 
         // TODO: usually when we save the gltf file, we need to amend/fix several features,
         // TODO: usually when we save the gltf file, we need to amend/fix several features,
-        // which goes against good practices of not modyfing any file when it is being saved.
+        // which goes against good practices of not modifying any file when it is being saved.
         // a possible solution would be to do a shallow copy of RootObject and update Buffers, BufferViews, etc
         // a possible solution would be to do a shallow copy of RootObject and update Buffers, BufferViews, etc
         // an issue that complicates things is that it requires to copy the extensions of root, buffers, etc
         // an issue that complicates things is that it requires to copy the extensions of root, buffers, etc
 
 
@@ -247,7 +247,7 @@ namespace SharpGLTF.Schema2
                 for (int i = 0; i < this._buffers.Count; ++i)
                 for (int i = 0; i < this._buffers.Count; ++i)
                 {
                 {
                     var buffer = this._buffers[i];
                     var buffer = this._buffers[i];
-                    var bname = this._buffers.Count != 1 ? $"{name}.{i}.bin" : $"{name}.bin";
+                    var bname = this._buffers.Count != 1 ? $"{name}_{i}.bin" : $"{name}.bin";
                     buffer._WriteToExternal(bname, settings.FileWriter);
                     buffer._WriteToExternal(bname, settings.FileWriter);
                 }
                 }
             }
             }
@@ -255,7 +255,7 @@ namespace SharpGLTF.Schema2
             for (int i = 0; i < this._images.Count; ++i)
             for (int i = 0; i < this._images.Count; ++i)
             {
             {
                 var image = this._images[i];
                 var image = this._images[i];
-                var iname = $"{name}.{i}";
+                var iname = this._buffers.Count != 1 ? $"{name}_{i}" : $"{name}";
                 if (settings.EmbedImages) image._EmbedAssets();
                 if (settings.EmbedImages) image._EmbedAssets();
                 else image._WriteExternalAssets(iname, settings.FileWriter);
                 else image._WriteExternalAssets(iname, settings.FileWriter);
             }
             }

+ 22 - 0
src/SharpGLTF.DOM/Schema2/gltf.Skin.cs

@@ -32,6 +32,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Skin"/> at <see cref="ModelRoot.LogicalSkins"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalSkins.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalSkins.IndexOfReference(this);
 
 
         public IEnumerable<Node> VisualParents => Node.FindNodesUsingSkin(this);
         public IEnumerable<Node> VisualParents => Node.FindNodesUsingSkin(this);
@@ -140,4 +143,23 @@ namespace SharpGLTF.Schema2
 
 
         #endregion
         #endregion
     }
     }
+
+    public partial class ModelRoot
+    {
+        /// <summary>
+        /// Creates a new <see cref="Skin"/> instance
+        /// and adds it to <see cref="ModelRoot.LogicalSkins"/>.
+        /// </summary>
+        /// <param name="name">The name of the instance.</param>
+        /// <returns>A <see cref="Skin"/> instance.</returns>
+        public Skin CreateSkin(string name = null)
+        {
+            var skin = new Skin();
+            skin.Name = name;
+
+            this._skins.Add(skin);
+
+            return skin;
+        }
+    }
 }
 }

+ 33 - 192
src/SharpGLTF.DOM/Schema2/gltf.Textures.cs

@@ -65,18 +65,29 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Texture"/> at <see cref="ModelRoot.LogicalTextures"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalTextures.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalTextures.IndexOfReference(this);
 
 
         public Sampler Sampler
         public Sampler Sampler
         {
         {
             get => _sampler.HasValue ? LogicalParent.LogicalSamplers[_sampler.Value] : null;
             get => _sampler.HasValue ? LogicalParent.LogicalSamplers[_sampler.Value] : null;
-            set => _sampler = value == null ? null : (int?)LogicalParent._UseSampler(value);
+            set
+            {
+                if (value != null) Guard.MustShareLogicalParent(this, value, nameof(value));
+                _sampler = value?.LogicalIndex;
+            }
         }
         }
 
 
         public Image Source
         public Image Source
         {
         {
             get => _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
             get => _source.HasValue ? LogicalParent.LogicalImages[_source.Value] : null;
-            set => _source = value == null ? null : (int?)LogicalParent._UseImage(value);
+            set
+            {
+                if (value != null) Guard.MustShareLogicalParent(this, value, nameof(value));
+                _source = value?.LogicalIndex;
+            }
         }
         }
 
 
         #endregion
         #endregion
@@ -101,6 +112,9 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the zero-based index of this <see cref="Sampler"/> at <see cref="ModelRoot.LogicalSamplers"/>
+        /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalSamplers.IndexOfReference(this);
         public int LogicalIndex => this.LogicalParent.LogicalSamplers.IndexOfReference(this);
 
 
         public TextureInterpolationMode MagFilter => _magFilter ?? TextureInterpolationMode.LINEAR;
         public TextureInterpolationMode MagFilter => _magFilter ?? TextureInterpolationMode.LINEAR;
@@ -114,197 +128,17 @@ namespace SharpGLTF.Schema2
         #endregion
         #endregion
     }
     }
 
 
-    [System.Diagnostics.DebuggerDisplay("Image[{LogicalIndex}] {Name}")]
-    public sealed partial class Image
-    {
-        #region Base64 constants
-
-        const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream;base64,";
-        const string EMBEDDEDGLTFBUFFER = "data:application/gltf-buffer;base64,";
-        const string EMBEDDEDJPEGBUFFER = "data:image/jpeg;base64,";
-        const string EMBEDDEDPNGBUFFER = "data:image/png;base64,";
-
-        const string MIMEPNG = "image/png";
-        const string MIMEJPEG = "image/jpeg";
-
-        #endregion
-
-        #region lifecycle
-
-        internal Image() { }
-
-        #endregion
-
-        #region data
-
-        // this is the actual compressed image in PNG or JPEG, -NOT- the pixels data.
-        private Byte[] _ExternalImageContent;
-
-        #endregion
-
-        #region properties
-
-        public int LogicalIndex => this.LogicalParent.LogicalImages.IndexOfReference(this);
-
-        public bool IsPng => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("png");
-        public bool IsJpeg => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("jpg") | _mimeType.Contains("jpeg");
-
-        #endregion
-
-        #region API
-
-        private static bool _IsPng(IReadOnlyList<Byte> data)
-        {
-            if (data[0] != 0x89) return false;
-            if (data[1] != 0x50) return false;
-            if (data[2] != 0x4e) return false;
-            if (data[3] != 0x47) return false;
-
-            return true;
-        }
-
-        private static bool _IsJpeg(IReadOnlyList<Byte> data)
-        {
-            if (data[0] != 0xff) return false;
-            if (data[1] != 0xd8) return false;
-
-            return true;
-        }
-
-        public ArraySegment<Byte> GetImageContent()
-        {
-            if (_ExternalImageContent != null) return new ArraySegment<byte>(_ExternalImageContent);
-
-            if (this._bufferView.HasValue)
-            {
-                var bv = this.LogicalParent.LogicalBufferViews[this._bufferView.Value];
-
-                return bv.Content;
-            }
-
-            throw new InvalidOperationException();
-        }
-
-        public Image SetExternalContent(Byte[] data)
-        {
-            if (_IsPng(data)) _mimeType = MIMEPNG; // these strings might be wrong
-            if (_IsJpeg(data)) _mimeType = MIMEJPEG; // these strings might be wrong
-
-            this._uri = null;
-            this._bufferView = null;
-            this._ExternalImageContent = data;
-
-            return this;
-        }
-
-        public void UseBufferViewContainer()
-        {
-            if (this._ExternalImageContent == null) return;
-
-            var data = new ArraySegment<Byte>(this._ExternalImageContent);
-
-            var bv = this.LogicalParent.UseBufferView(data);
-
-            this._uri = null;
-            this._bufferView = bv.LogicalIndex;
-
-            this._ExternalImageContent = null;
-        }
-
-        #endregion
-
-        #region binary read
-
-        internal void _ResolveUri(AssetReader externalReferenceSolver)
-        {
-            if (!String.IsNullOrWhiteSpace(_uri))
-            {
-                _ExternalImageContent = _LoadImageUnchecked(_uri, externalReferenceSolver);
-            }
-
-            _uri = null; // When _Data is not empty, clear URI
-        }
-
-        private static Byte[] _LoadImageUnchecked(string uri, AssetReader externalReferenceSolver)
-        {
-            return uri._TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER)
-                ?? uri._TryParseBase64Unchecked(EMBEDDEDOCTETSTREAM)
-                ?? uri._TryParseBase64Unchecked(EMBEDDEDJPEGBUFFER)
-                ?? uri._TryParseBase64Unchecked(EMBEDDEDPNGBUFFER)
-                ?? externalReferenceSolver?.Invoke(uri);
-        }
-
-        #endregion
-
-        #region binary write
-
-        internal void _EmbedAssets()
-        {
-            if (_ExternalImageContent != null)
-            {
-                var mimeContent = Convert.ToBase64String(_ExternalImageContent, Base64FormattingOptions.None);
-
-                if (_IsPng(_ExternalImageContent))
-                {
-                    _mimeType = MIMEPNG;
-                    _uri = EMBEDDEDPNGBUFFER + mimeContent;
-                    return;
-                }
-
-                if (_IsJpeg(_ExternalImageContent))
-                {
-                    _mimeType = MIMEJPEG;
-                    _uri = EMBEDDEDJPEGBUFFER + mimeContent;
-                    return;
-                }
-
-                throw new NotImplementedException();
-            }
-        }
-
-        internal void _WriteExternalAssets(string uri, AssetWriter writer)
-        {
-            if (_ExternalImageContent != null)
-            {
-                if (this._mimeType.Contains("png")) uri += ".png";
-                if (this._mimeType.Contains("jpg")) uri += ".jpg";
-                if (this._mimeType.Contains("jpeg")) uri += ".jpg";
-
-                this._uri = uri;
-                writer(uri, _ExternalImageContent);
-            }
-        }
-
-        internal void _ClearAfterWrite() { this._uri = null; }
-
-        #endregion
-    }
-
     public partial class ModelRoot
     public partial class ModelRoot
     {
     {
-        internal int _UseImage(Image image)
-        {
-            Guard.NotNull(image, nameof(image));
-
-            return _images.Use(image);
-        }
-
-        internal int _UseSampler(Sampler sampler)
-        {
-            Guard.NotNull(sampler, nameof(sampler));
-
-            return _samplers.Use(sampler);
-        }
-
-        internal Image _AddImage()
-        {
-            var img = new Image();
-
-            _images.Add(img);
-
-            return img;
-        }
-
+        /// <summary>
+        /// Creates or reuses a <see cref="Sampler"/> instance
+        /// at <see cref="ModelRoot.LogicalSamplers"/>.
+        /// </summary>
+        /// <param name="mag">A value of <see cref="TextureInterpolationMode"/>.</param>
+        /// <param name="min">A value of <see cref="TextureMipMapMode"/>.</param>
+        /// <param name="ws">The <see cref="TextureWrapMode"/> in the S axis.</param>
+        /// <param name="wt">The <see cref="TextureWrapMode"/> in the T axis.</param>
+        /// <returns>A <see cref="Sampler"/> instance.</returns>
         public Sampler UseSampler(TextureInterpolationMode mag, TextureMipMapMode min, TextureWrapMode ws, TextureWrapMode wt)
         public Sampler UseSampler(TextureInterpolationMode mag, TextureMipMapMode min, TextureWrapMode ws, TextureWrapMode wt)
         {
         {
             foreach (var s in this._samplers)
             foreach (var s in this._samplers)
@@ -319,6 +153,13 @@ namespace SharpGLTF.Schema2
             return ss;
             return ss;
         }
         }
 
 
+        /// <summary>
+        /// Creates or reuses a <see cref="Texture"/> instance
+        /// at <see cref="ModelRoot.LogicalTextures"/>.
+        /// </summary>
+        /// <param name="image">The source <see cref="Image"/>.</param>
+        /// <param name="sampler">The source <see cref="Sampler"/>.</param>
+        /// <returns>A <see cref="Texture"/> instance.</returns>
         public Texture UseTexture(Image image, Sampler sampler)
         public Texture UseTexture(Image image, Sampler sampler)
         {
         {
             if (image == null) return null;
             if (image == null) return null;
@@ -338,7 +179,7 @@ namespace SharpGLTF.Schema2
             return tex;
             return tex;
         }
         }
 
 
-        internal T UseTextureInfo<T>(Image image, Sampler sampler, int textureSet)
+        internal T _UseTextureInfo<T>(Image image, Sampler sampler, int textureSet)
             where T : TextureInfo, new()
             where T : TextureInfo, new()
         {
         {
             var tex = UseTexture(image, sampler);
             var tex = UseTexture(image, sampler);

+ 1 - 1
tests/SharpGLTF.Tests/Geometry/CreateMeshTests.cs

@@ -61,7 +61,7 @@ namespace SharpGLTF.Geometry
             var node = scene.CreateNode("main scene");
             var node = scene.CreateNode("main scene");
 
 
             var material = root.CreateMaterial("DefaultMaterial")
             var material = root.CreateMaterial("DefaultMaterial")
-                .InitializeDefault(new Vector4(1, 0, 0, 1));
+                .WithDefault(new Vector4(1, 0, 0, 1));
             material.DoubleSided = true;            
             material.DoubleSided = true;            
 
 
             node.Mesh = root.CreateMesh();
             node.Mesh = root.CreateMesh();

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/AccessorSparseTests.cs

@@ -21,7 +21,7 @@ namespace SharpGLTF.Schema2
         #endregion
         #endregion
 
 
         [Test]
         [Test]
-        public void LoadSparseModels()
+        public void TestLoadSparseModel()
         {
         {
             var path = TestFiles.GetSampleFilePaths().FirstOrDefault(item => item.Contains("SimpleSparseAccessor.gltf"));
             var path = TestFiles.GetSampleFilePaths().FirstOrDefault(item => item.Contains("SimpleSparseAccessor.gltf"));
             
             

+ 175 - 0
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF.Schema2.Authoring
+{
+    [TestFixture]
+    public class CreateModelTests
+    {
+        [Test(Description = "Creates an empty model")]
+        public void CreateEmptyScene()
+        {
+            var root = ModelRoot.CreateModel();
+
+            var scene = root.UseScene("Empty Scene");
+
+            Assert.NotNull(scene);            
+            Assert.AreEqual("Empty Scene", root.DefaultScene.Name);            
+        }
+
+        [Test(Description ="Creates a model with a triangle mesh")]
+        public void CreateSolidTriangleScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();            
+
+            // create model
+            var model = ModelRoot.CreateModel();
+            // create scene
+            var scene = model.DefaultScene = model.UseScene("Default");
+            // create node
+            var rnode = scene.CreateNode("Triangle Node");
+            // create mesh
+            var rmesh = rnode.Mesh = model.CreateMesh("Triangle Mesh");
+
+            // create a vertex buffer with positions and fill it
+            var positionsView = model.UseBufferView(new Byte[12 * 3], 0, null, 0, BufferMode.ARRAY_BUFFER);
+            var positionsArray = new Memory.Vector3Array(positionsView.Content);
+            positionsArray[0] = new Vector3(0, 10, 0);
+            positionsArray[1] = new Vector3(-10, -10, 0);
+            positionsArray[2] = new Vector3(10, -10, 0);
+
+            // create an index buffer and fill it
+            var indicesView = model.UseBufferView(new Byte[4 * 3], 0, null, 0, BufferMode.ELEMENT_ARRAY_BUFFER);
+            var indicesArray = new Memory.IntegerArray(indicesView.Content);
+            indicesArray[0] = 0;
+            indicesArray[1] = 1;
+            indicesArray[2] = 2;
+
+            // create a positions accessor
+            var positionsAccessor = model
+                .CreateAccessor()
+                .WithVertexData(positionsView, 0, 3, ElementType.VEC3, ComponentType.FLOAT, false);
+
+            // create an indices accessor
+            var indicesAccessor = model
+                .CreateAccessor()
+                .WithIndexData(indicesView, 0, 3, IndexType.UNSIGNED_INT);
+            
+            // create mehh primitive
+            var primitive = rmesh.CreatePrimitive();
+            primitive.DrawPrimitiveType = PrimitiveType.TRIANGLES;
+            primitive.SetVertexAccessor("POSITION", positionsAccessor);
+            primitive.IndexAccessor = indicesAccessor;
+
+            // save result
+            model.MergeBuffers();
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");
+        }
+
+        [Test(Description ="Creates a model with a textured triangle mesh")]
+        public void CreateTextureTriangleScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            // we'll use our icon as the source texture
+            var imagePath = System.IO.Path.Combine(TestContext.CurrentContext.WorkDirectory, "..\\..\\..\\..\\..\\build\\Icons\\glTF2Sharp.png");
+
+            // create a basic scene
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("Default");
+            var rnode = scene.CreateNode("Triangle Node");
+            var rmesh = rnode.Mesh = model.CreateMesh("Triangle Mesh");
+
+            // define the triangle positions
+            var sourcePositions = new[]
+            {
+                new Vector3(0, 10, 0),
+                new Vector3(-10, -10, 0),
+                new Vector3(10, -10, 0)
+            };
+
+            // define the triangle UV coordinates
+            var sourceTextures = new[]
+            {
+                new Vector2(0.5f, -0.8f),
+                new Vector2(-0.5f, 1.2f),
+                new Vector2(1.5f, 1.2f)
+            };
+
+            // create a vertex buffer
+            int byteStride = (3 + 2) * 4;
+            var vbuffer = model.UseBufferView(new Byte[byteStride * 3], byteStride, BufferMode.ARRAY_BUFFER);
+
+            // create positions accessor and fill it
+            var vpositions = model
+                .CreateAccessor("Triangle Positions")
+                .WithVertexData(vbuffer, 0, sourcePositions);
+
+            // create texcoord accessor and fill it
+            var vtextures = model
+                .CreateAccessor("Triangle texture coords")
+                .WithVertexData(vbuffer, 12, sourceTextures);
+
+            // create a mesh primitive and assgin the accessors and other properties
+            var primitive = rmesh.CreatePrimitive();
+            primitive.SetVertexAccessor("POSITION", vpositions);
+            primitive.SetVertexAccessor("TEXCOORD_0", vtextures);
+            primitive.DrawPrimitiveType = PrimitiveType.TRIANGLES;
+
+            // create and assign a material
+            primitive.Material = model
+                .CreateMaterial("Default")
+                .WithPBRMetallicRoughness();
+
+            primitive.Material.DoubleSided = true;
+
+            // PBRMetallicRoughness has a "BaseColor" and a "Metallic" and a "Roughness" channels.
+            primitive.Material
+                .FindChannel("BaseColor")
+                .SetTexture(0, model.CreateImage().WithExternalFile(imagePath) );
+
+            // save result            
+            model.MergeBuffers();
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");            
+        }
+
+        [Test(Description = "Creates a simple scene using a mesh builder helper class")]
+        public void CreateMeshBuilderScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            var meshBuilder = new SimpleSceneBuilder<Vector4>();
+            meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), (-10, 10,  0), (10, 10,  0), (10, -10,  0), (-10, -10,  0));
+            meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), (-10, 10, 10), (10, 10, 10), (10, -10, 10), (-10, -10, 10));
+            meshBuilder.AddPolygon(new Vector4(1, 0, 0, 1), (-10, 10, 20), (10, 10, 20), (10, -10, 20), (-10, -10, 20));
+
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("Default");
+            var rnode = scene.CreateNode("RootNode");            
+
+            // setup a lambda function that creates a material for a given color
+            Material createMaterialForColor(Vector4 color)
+            {
+                var material = model.CreateMaterial().WithDefault(color);
+                material.DoubleSided = true;
+                return material;
+            };
+
+            // fill our node with the mesh
+            meshBuilder.CopyToNode(rnode, createMaterialForColor);
+
+            model.MergeBuffers();
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");
+        }       
+    }
+}

+ 29 - 32
tests/SharpGLTF.Tests/SimpleSceneBuilder.cs → tests/SharpGLTF.Tests/Schema2/Authoring/SimpleMeshBuilder.cs

@@ -4,29 +4,35 @@ using System.Numerics;
 using System.Text;
 using System.Text;
 using System.Linq;
 using System.Linq;
 
 
-namespace SharpGLTF
+namespace SharpGLTF.Schema2.Authoring
 {
 {
-    using COLOR = Vector4;
-
-    class SimpleSceneBuilder
+    class SimpleSceneBuilder<TMaterial>
     {
     {
         #region data
         #region data
 
 
         private readonly VertexColumn<Vector3> _Positions = new VertexColumn<Vector3>();        
         private readonly VertexColumn<Vector3> _Positions = new VertexColumn<Vector3>();        
-        private readonly Dictionary<COLOR, List<int>> _Indices = new Dictionary<COLOR, List<int>>();        
+        private readonly Dictionary<TMaterial, List<int>> _Indices = new Dictionary<TMaterial, List<int>>();        
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        public void AddPolygon(COLOR color, params (float,float,float)[] points)
+        public void AddPolygon(TMaterial material, params (float,float,float)[] points)
         {
         {
             var vertices = points.Select(item => new Vector3(item.Item1, item.Item2, item.Item3)).ToArray();
             var vertices = points.Select(item => new Vector3(item.Item1, item.Item2, item.Item3)).ToArray();
 
 
-            AddPolygon(color, vertices);
+            AddPolygon(material, vertices);
         }
         }
 
 
-        public void AddTriangle(COLOR color, Vector3 a, Vector3 b, Vector3 c)
+        public void AddPolygon(TMaterial material, params Vector3[] points)
+        {
+            for (int i = 2; i < points.Length; ++i)
+            {
+                AddTriangle(material, points[0], points[i - 1], points[i]);
+            }
+        }
+
+        public void AddTriangle(TMaterial material, Vector3 a, Vector3 b, Vector3 c)
         {
         {
             var aa = _Positions.Use(a);
             var aa = _Positions.Use(a);
             var bb = _Positions.Use(b);
             var bb = _Positions.Use(b);
@@ -37,33 +43,29 @@ namespace SharpGLTF
             if (aa == cc) return;
             if (aa == cc) return;
             if (bb == cc) return;
             if (bb == cc) return;
 
 
-            if (!_Indices.TryGetValue(color, out List<int> indices))
+            if (!_Indices.TryGetValue(material, out List<int> indices))
             {
             {
                 indices = new List<int>();
                 indices = new List<int>();
-                _Indices[color] = indices;
+                _Indices[material] = indices;
             }
             }
 
 
             indices.Add(aa);
             indices.Add(aa);
             indices.Add(bb);
             indices.Add(bb);
             indices.Add(cc);
             indices.Add(cc);
-        }
+        }             
 
 
-        public void AddPolygon(COLOR color, params Vector3[] points)
+        public void CopyToNode(Node dstNode, Func<TMaterial, Material> materialEvaluator)
         {
         {
-            for(int i=2; i < points.Length; ++i)
-            {
-                AddTriangle(color, points[0], points[i - 1], points[i]);
-            }
+            dstNode.Mesh = dstNode.LogicalParent.CreateMesh();
+            CopyToMesh(dstNode.Mesh, materialEvaluator);
         }
         }
 
 
-        public Schema2.ModelRoot ToModel()
+        public void CopyToMesh(Mesh dstMesh, Func<TMaterial,Material> materialEvaluator)
         {
         {
-            var root = Schema2.ModelRoot.CreateModel();
-
-            var node = root.UseScene(0).CreateNode("Default");            
+            var root = dstMesh.LogicalParent;            
 
 
             // create vertex buffer
             // create vertex buffer
-            const int byteStride = 12 * 2;            
+            const int byteStride = 12 * 2;
 
 
             var vbuffer = root.UseBufferView(new Byte[byteStride * _Positions.Count], byteStride, Schema2.BufferMode.ARRAY_BUFFER);
             var vbuffer = root.UseBufferView(new Byte[byteStride * _Positions.Count], byteStride, Schema2.BufferMode.ARRAY_BUFFER);
 
 
@@ -77,10 +79,7 @@ namespace SharpGLTF
                 .CreateAccessor("Normals")
                 .CreateAccessor("Normals")
                 .WithVertexData(vbuffer, 12, _CalculateNormals());
                 .WithVertexData(vbuffer, 12, _CalculateNormals());
 
 
-            var nnn = normals.AsVector3Array();
-
-            // create mesh
-            node.Mesh = root.CreateMesh();
+            var nnn = normals.AsVector3Array();            
 
 
             foreach (var kvp in _Indices)
             foreach (var kvp in _Indices)
             {
             {
@@ -92,18 +91,16 @@ namespace SharpGLTF
                     .WithIndexData(ibuffer, 0, kvp.Value);
                     .WithIndexData(ibuffer, 0, kvp.Value);
 
 
                 // create mesh primitive
                 // create mesh primitive
-                var prim = node.Mesh.CreatePrimitive();
+                var prim = dstMesh.CreatePrimitive();
                 prim.SetVertexAccessor("POSITION", positions);
                 prim.SetVertexAccessor("POSITION", positions);
                 prim.SetVertexAccessor("NORMAL", normals);
                 prim.SetVertexAccessor("NORMAL", normals);
                 prim.SetIndexAccessor(indices);
                 prim.SetIndexAccessor(indices);
-                prim.DrawPrimitiveType = Schema2.PrimitiveType.TRIANGLES;
-                prim.Material = root.CreateMaterial().InitializeDefault(kvp.Key);
-                prim.Material.DoubleSided = true;
-            }
+                prim.DrawPrimitiveType = PrimitiveType.TRIANGLES;
 
 
-            root.MergeBuffers();
+                prim.Material = materialEvaluator(kvp.Key);
+            }
 
 
-            return root;
+            root.MergeBuffers();            
         }
         }
 
 
         private Vector3[] _CalculateNormals()
         private Vector3[] _CalculateNormals()

+ 0 - 96
tests/SharpGLTF.Tests/Schema2/CreateModelTests.cs

@@ -1,96 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Text;
-
-using NUnit.Framework;
-
-namespace SharpGLTF.Schema2
-{
-    [TestFixture]
-    public class CreateModelTests
-    {
-        [Test(Description = "Creates an empty model")]
-        public void CreateEmptyScene()
-        {
-            var root = ModelRoot.CreateModel();
-
-            var scene = root.UseScene("Empty Scene");
-
-            Assert.NotNull(scene);            
-            Assert.AreEqual("Empty Scene", root.DefaultScene.Name);            
-        }
-
-        [Test(Description ="Creates a model with a triangle mesh")]
-        public void CreateTriangleScene()
-        {
-            TestContext.CurrentContext.AttachShowDirLink();
-
-            // Although this is a valid way of creating a gltf mesh, it will be extremely GPU inefficient.            
-
-            var root = ModelRoot.CreateModel();
-            
-            // create a vertex buffer with positions and fill it
-            var positionsView = root.UseBufferView(new Byte[12 * 3], 0, null, 0, BufferMode.ARRAY_BUFFER);
-            var positionsArray = new Memory.Vector3Array(positionsView.Content);
-            positionsArray[0] = new System.Numerics.Vector3(0, 10, 0);
-            positionsArray[1] = new System.Numerics.Vector3(-10, -10, 0);
-            positionsArray[2] = new System.Numerics.Vector3(10, -10, 0);
-
-            // create an index buffer and fill it
-            var indicesView = root.UseBufferView(new Byte[4 * 3], 0, null, 0, BufferMode.ELEMENT_ARRAY_BUFFER);
-            var indicesArray = new Memory.IntegerArray(indicesView.Content);
-            indicesArray[0] = 0;
-            indicesArray[1] = 1;
-            indicesArray[2] = 2;
-
-            // create a positions accessor
-            var positionsAccessor = root
-                .CreateAccessor()
-                .WithVertexData(positionsView, 0, 3, ElementType.VEC3, ComponentType.FLOAT, false);
-
-            // create an indices accessor
-            var indicesAccessor = root
-                .CreateAccessor()
-                .WithIndexData(indicesView, 0, 3, IndexType.UNSIGNED_INT);
-
-            // create a mesh and a mesh primitive
-            var mesh = root.CreateMesh();
-            var primitive = mesh.CreatePrimitive();
-            primitive.DrawPrimitiveType = PrimitiveType.TRIANGLES;
-            primitive.SetVertexAccessor("POSITION", positionsAccessor);
-            primitive.IndexAccessor = indicesAccessor;
-
-            // create a scene
-            var scene = root.DefaultScene = root.UseScene("Empty Scene");
-
-            // create a node
-            var node = scene.CreateNode("Triangle");
-
-            // assign the mesh we previously created
-            node.Mesh = mesh;
-
-            // save
-            root.AttachToCurrentTest("result.glb");
-            root.AttachToCurrentTest("result.gltf");            
-        }
-
-        [Test(Description = "Creates a simple scene using a helper class")]
-        public void CreateManyTrianglesScene()
-        {
-            TestContext.CurrentContext.AttachShowDirLink();
-            TestContext.CurrentContext.AttachGltfValidatorLink();
-
-            var builder = new SimpleSceneBuilder();
-
-            builder.AddPolygon(new Vector4(1, 1, 1, 1), (-10, 10,  0), (10, 10,  0), (10, -10,  0), (-10, -10,  0));
-            builder.AddPolygon(new Vector4(1, 1, 0, 1), (-10, 10, 10), (10, 10, 10), (10, -10, 10), (-10, -10, 10));
-            builder.AddPolygon(new Vector4(1, 0, 0, 1), (-10, 10, 20), (10, 10, 20), (10, -10, 20), (-10, -10, 20));
-
-            var model = builder.ToModel();
-
-            model.AttachToCurrentTest("result.glb");
-            model.AttachToCurrentTest("result.gltf");
-        }
-    }
-}

+ 14 - 6
tests/SharpGLTF.Tests/Schema2/LoadModelTests.cs → tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadModelTests.cs

@@ -4,7 +4,7 @@ using System.Text;
 
 
 using NUnit.Framework;
 using NUnit.Framework;
 
 
-namespace SharpGLTF.Schema2
+namespace SharpGLTF.Schema2.LoadAndSave
 {
 {
     [TestFixture]
     [TestFixture]
     public class LoadModelTests
     public class LoadModelTests
@@ -71,6 +71,8 @@ namespace SharpGLTF.Schema2
         [Test]
         [Test]
         public void TestLoadSampleModels()
         public void TestLoadSampleModels()
         {
         {
+            TestContext.CurrentContext.AttachShowDirLink();
+
             foreach (var f in TestFiles.GetSampleFilePaths())
             foreach (var f in TestFiles.GetSampleFilePaths())
             {
             {
                 var root = GltfUtils.LoadModel(f);
                 var root = GltfUtils.LoadModel(f);
@@ -92,18 +94,24 @@ namespace SharpGLTF.Schema2
 
 
         #endregion
         #endregion
 
 
-        #region test polly model
+        #region testing polly model
 
 
         [Test(Description ="Example of traversing the visual tree all the way to individual vertices and indices")]
         [Test(Description ="Example of traversing the visual tree all the way to individual vertices and indices")]
         public void TestLoadPolly()
         public void TestLoadPolly()
         {
         {
-            var path = TestFiles.GetPollyFilePath();
+            TestContext.CurrentContext.AttachShowDirLink();
+            
+            // load Polly model
+            var polly = GltfUtils.LoadModel( TestFiles.GetPollyFilePath() );
 
 
-            var polly = GltfUtils.LoadModel(path);
+            Assert.NotNull(polly);            
 
 
-            Assert.NotNull(polly);
+            // Save as GLB, and also evaluate all triangles and save as Wavefront OBJ
+            polly.MergeBuffers();
+            polly.AttachToCurrentTest("polly_out.glb");
+            TestContext.CurrentContext.AttachToCurrentTestAsWavefrontObject("polly_out.obj", polly);
 
 
-            TestContext.CurrentContext.AttachToCurrentTestAsWavefrontObject(System.IO.Path.GetFileName(path), polly);
+            // hierarchically browse some elements of the model:
 
 
             var scene = polly.DefaultScene;
             var scene = polly.DefaultScene;
 
 

+ 8 - 0
tests/SharpGLTF.Tests/WavefrontWriter.cs

@@ -12,9 +12,15 @@ namespace SharpGLTF
     class VertexColumn<T> : IReadOnlyList<T>
     class VertexColumn<T> : IReadOnlyList<T>
         where T:IEquatable<T>
         where T:IEquatable<T>
     {
     {
+        #region data
+
         private readonly List<T> _Vertices = new List<T>();
         private readonly List<T> _Vertices = new List<T>();
         private readonly Dictionary<T, int> _VertexCache = new Dictionary<T, int>();
         private readonly Dictionary<T, int> _VertexCache = new Dictionary<T, int>();
 
 
+        #endregion
+
+        #region API
+
         public T this[int index] => _Vertices[index];
         public T this[int index] => _Vertices[index];
 
 
         public int Count => _Vertices.Count;
         public int Count => _Vertices.Count;
@@ -34,6 +40,8 @@ namespace SharpGLTF
 
 
             return index;
             return index;
         }
         }
+
+        #endregion
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 2 - 2
tests/SharpGLTF.Tests/readme.md

@@ -1,4 +1,4 @@
-#### Unit Testing
+#### Unit Tests
 
 
 Unit tests use 3D models from these repositories:
 Unit tests use 3D models from these repositories:
 
 
@@ -6,6 +6,6 @@ Unit tests use 3D models from these repositories:
 - https://github.com/KhronosGroup/glTF-Blender-Exporter.git
 - https://github.com/KhronosGroup/glTF-Blender-Exporter.git
 - https://github.com/bghgary/glTF-Asset-Generator.git
 - https://github.com/bghgary/glTF-Asset-Generator.git
 
 
-The _first time_ your run the tests will clone the repositories into the _target bin folder_;
+**WARNING:** The _first time_ your run the tests will clone the repositories into the _target bin folder_;
 The sample models repository is ~2gb, so it takes a while to download, a perfect time to have a coffee.
 The sample models repository is ~2gb, so it takes a while to download, a perfect time to have a coffee.