Browse Source

first triangle!

Vicente Penades 7 years ago
parent
commit
efa267963b

+ 115 - 2
src/glTF2Sharp.DOM/Collections/LinqDictionary.cs

@@ -12,11 +12,11 @@ namespace glTF2Sharp.Collections
     /// <typeparam name="TKey"></typeparam>
     /// <typeparam name="TValueIn"></typeparam>
     /// <typeparam name="TValueOut"></typeparam>
-    struct LinqDictionary<TKey, TValueIn, TValueOut> : IReadOnlyDictionary<TKey, TValueOut>
+    struct ReadOnlyLinqDictionary<TKey, TValueIn, TValueOut> : IReadOnlyDictionary<TKey, TValueOut>
     {
         #region lifecycle
 
-        public LinqDictionary(IReadOnlyDictionary<TKey,TValueIn> dict, Func<TValueIn,TValueOut> valConverter)
+        public ReadOnlyLinqDictionary(IReadOnlyDictionary<TKey,TValueIn> dict, Func<TValueIn,TValueOut> valConverter)
         {
             _Source = dict;
             _ValueConverter = valConverter;
@@ -80,4 +80,117 @@ namespace glTF2Sharp.Collections
 
         #endregion
     }
+
+
+    struct LinqDictionary<TKey, TValueIn, TValueOut> : IDictionary<TKey, TValueOut>
+    {
+        #region lifecycle
+
+        public LinqDictionary(IDictionary<TKey, TValueIn> dict, Func<TValueOut, TValueIn> inConverter, Func<TValueIn, TValueOut> outConverter)
+        {
+            _Source = dict;
+            _InConverter = inConverter;
+            _OutConverter = outConverter;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly IDictionary<TKey, TValueIn> _Source;
+        private readonly Func<TValueOut, TValueIn> _InConverter;
+        private readonly Func<TValueIn, TValueOut> _OutConverter;        
+
+        #endregion
+
+        #region API
+
+        public TValueOut this[TKey key]
+        {
+            get => _OutConverter(_Source[key]);
+            set => _Source[key] = _InConverter(value);
+        }
+
+        public ICollection<TKey> Keys => _Source.Keys;
+
+        public ICollection<TValueOut> Values
+        {
+            get
+            {
+                var cvt = _OutConverter;
+                return _Source.Values.Select(item => cvt(item)).ToList();
+            }
+        }
+
+        public int Count => _Source.Count;        
+
+        public bool IsReadOnly => throw new NotImplementedException();
+
+        public bool ContainsKey(TKey key) { return _Source.ContainsKey(key); }
+
+        public bool TryGetValue(TKey key, out TValueOut value)
+        {
+            if (!_Source.TryGetValue(key, out TValueIn val))
+            {
+                value = default;
+                return false;
+            }
+
+            value = _OutConverter(val);
+            return true;
+        }
+
+        public IEnumerator<KeyValuePair<TKey, TValueOut>> GetEnumerator()
+        {
+            var cvt = _OutConverter;
+            return _Source
+                .Select(item => new KeyValuePair<TKey, TValueOut>(item.Key, cvt(item.Value)))
+                .GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            var cvt = _OutConverter;
+            return _Source
+                .Select(item => new KeyValuePair<TKey, TValueOut>(item.Key, cvt(item.Value)))
+                .GetEnumerator();
+        }
+
+        public void Add(TKey key, TValueOut value)
+        {
+            this[key] = value;
+        }
+
+        public bool Remove(TKey key)
+        {
+            return _Source.Remove(key);
+        }
+
+        public void Add(KeyValuePair<TKey, TValueOut> item)
+        {
+            this[item.Key] = item.Value;
+        }
+
+        public void Clear()
+        {
+            _Source.Clear();
+        }
+
+        public bool Contains(KeyValuePair<TKey, TValueOut> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void CopyTo(KeyValuePair<TKey, TValueOut>[] array, int arrayIndex)
+        {
+            throw new NotImplementedException();
+        }
+
+        public bool Remove(KeyValuePair<TKey, TValueOut> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+    }
 }

+ 1 - 1
src/glTF2Sharp.DOM/Memory/ByteArrayAccessors.cs

@@ -180,7 +180,7 @@ namespace glTF2Sharp.Memory
     {
         #region constructors
 
-        public static IntegerAccessor Create(Byte[] data, Schema2.IndexType type, Boolean normalized)
+        public static IntegerAccessor Create(Byte[] data, Schema2.IndexType type)
         {
             return Create(new BYTES(data), type);
         }        

+ 26 - 14
src/glTF2Sharp.DOM/Schema2/gltf.Accessors.cs

@@ -72,7 +72,7 @@ namespace glTF2Sharp.Schema2
         internal void SetDataBuffer(BufferView buffer, int byteOffset, ComponentType ct, ElementType et, int count)
         {
             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));
@@ -87,12 +87,15 @@ namespace glTF2Sharp.Schema2
             _UpdateBounds(this._type.Length());
         }
 
-        internal void SetIndexBuffer(BufferView buffer, IndexType type, int byteOffset, int count)
+        public void SetIndexData(BufferView buffer, IndexType type, int byteOffset, int count)
         {
             Guard.NotNull(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(count, 0, nameof(count));
+            
 
             this._bufferView = buffer.LogicalIndex;
             this._componentType = type.ToComponent();
@@ -103,11 +106,12 @@ namespace glTF2Sharp.Schema2
 
             _UpdateBounds(1);
         }
-        
-        internal void SetVertexBuffer(BufferView buffer, ComponentType ctype, ElementType etype, bool? normalized, int byteOffset, int count)
+
+        public void SetVertexData(BufferView buffer, ComponentType ctype, ElementType etype, bool? normalized, int byteOffset, int count)
         {
             Guard.NotNull(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(count, 0, nameof(count));
@@ -120,7 +124,7 @@ namespace glTF2Sharp.Schema2
             this._count = count;
 
             _UpdateBounds(this._type.Length());
-        }
+        }        
 
         public ArraySegment<Byte> GetVertexBytes(int vertexIdx)
         {
@@ -441,33 +445,41 @@ namespace glTF2Sharp.Schema2
         }
     }
 
-    /*
+    
     public partial class ModelRoot
     {
-        internal Accessor _CreateVertexAccessor(BufferView buffer, Runtime.AttributeInfo desc, int extraByteOffset, int count)
+        public Accessor CreateAccessor()
         {
             var accessor = new Accessor();
 
             _accessors.Add(accessor);
 
-            accessor.SetVertexBuffer(buffer, desc, extraByteOffset, count);
-
             return accessor;
         }
 
-        internal Accessor _CreateIndexAccessor(BufferView buffer, Runtime.AttributeInfo desc, int extraByteOffset, int count)
+        internal Accessor _CreateVertexAccessor(BufferView buffer, ComponentType ctype, ElementType etype, bool? normalized, int extraByteOffset, int count)
         {
-            if (desc.Dimension != Runtime.Encoding.DimensionType.Scalar) throw new ArgumentException(nameof(desc));
+            var accessor = new Accessor();
+
+            _accessors.Add(accessor);
+
+            accessor.SetVertexData(buffer, ctype, etype, normalized, extraByteOffset, count);
+
+            return accessor;
+        }        
 
+        internal Accessor _CreateIndexAccessor(BufferView buffer, IndexType encoding, int extraByteOffset, int count)
+        {
             var accessor = new Accessor();
 
             _accessors.Add(accessor);
 
-            accessor.SetIndexBuffer(buffer, desc.Packed.ToSchema2().ToIndex(), extraByteOffset, count);
+            accessor.SetIndexData(buffer, encoding, extraByteOffset, count);
 
             return accessor;
         }
 
+        /*
         internal Accessor _CreateDataAccessor(Byte[] data, Runtime.Encoding.DimensionType dtype, int count)
         {
             var buffer = new Buffer(data);
@@ -482,8 +494,8 @@ namespace glTF2Sharp.Schema2
             accessor.SetDataBuffer(bufferView, 0, ComponentType.FLOAT, dtype.ToSchema2(), count);
 
             return accessor;
-        }
-    }*/
+        }*/
+    }
 
    
 }

+ 79 - 0
src/glTF2Sharp.DOM/Schema2/gltf.Buffer.cs

@@ -97,4 +97,83 @@ namespace glTF2Sharp.Schema2
 
         #endregion
     }
+
+    public partial class ModelRoot
+    {
+        public Buffer CreateBuffer(int byteCount)
+        {
+            var buffer = new Buffer(byteCount);
+            _buffers.Add(buffer);
+
+            return buffer;
+        }
+
+        public Buffer CreateBuffer(ReadOnlySpan<Byte> data)
+        {
+            Guard.IsFalse(data.IsEmpty, nameof(data));
+
+            var buffer = new Buffer(data);
+            _buffers.Add(buffer);
+
+            return buffer;
+        }
+
+        public Buffer CreateIndexBuffer(params int[] indices)
+        {
+            var buffer = CreateBuffer(indices.Length * 4);
+
+            var accessor = Memory.IntegerAccessor.Create(buffer._Data, IndexType.UNSIGNED_INT);
+
+            for (int i = 0; i < indices.Length; ++i)
+            {
+                accessor[i] = (UInt32)indices[i];
+            }
+
+            return buffer;
+        }
+
+        public Buffer CreateVector3Buffer(params Vector3[] vectors)
+        {
+            var buffer = CreateBuffer(vectors.Length * 12);
+
+            var accessor = new Memory.Vector3Accessor(new ArraySegment<byte>(buffer._Data), 0, ComponentType.FLOAT, false);
+
+            for(int i=0; i < vectors.Length; ++i)
+            {
+                accessor[i] = vectors[i];
+            }
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// Merges all the Buffer objects into a single, big one.
+        /// </summary>
+        /// <remarks>
+        /// When merging the buffers, it also adjusts the BufferView offsets so the data they point to remains the same.
+        /// </remarks>
+        public void MergeBuffers()
+        {
+            // retrieve all buffers and merge them into a single, big buffer
+
+            var views = _bufferViews
+                .OrderByDescending(item => item.Data.Count)
+                .ToArray();
+
+            if (views.Length <= 1) return; // nothing to do.
+
+            var sbbuilder = new _StaticBufferBuilder(0);
+
+            foreach (var bv in views) bv._ConvertToStaticBuffer(sbbuilder);
+
+            this._buffers.Clear();
+
+            var b = new Buffer
+            {
+                _Data = sbbuilder.ToArray()
+            };
+
+            this._buffers.Add(b);
+        }
+    }
 }

+ 17 - 62
src/glTF2Sharp.DOM/Schema2/gltf.BufferView.cs

@@ -13,22 +13,29 @@ namespace glTF2Sharp.Schema2
 
         internal BufferView() { }
 
-        internal BufferView(Buffer buffer, int byteLength, int? byteOffset, int? byteStride, BufferMode? target)
+        internal BufferView(Buffer buffer, int? byteLength, int? byteOffset, int? byteStride, BufferMode? target)
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer.LogicalParent, nameof(buffer));
-            Guard.MustBeGreaterThan(byteLength, 0, nameof(byteLength));
+            if (byteLength.HasValue) Guard.MustBeGreaterThan(byteLength.Value, 0, nameof(byteLength));
 
             if (byteOffset.HasValue) Guard.MustBeGreaterThan(byteOffset.Value, 0, nameof(byteOffset));
-            if (byteStride.HasValue)
+
+            if (byteStride.HasValue && target.HasValue)
             {
-                // valid byeStrides are : 1, 2, 4 and multiples of 4
-                Guard.MustBeGreaterThan(byteStride.Value, 0, nameof(byteOffset));
+                if (target.Value == BufferMode.ELEMENT_ARRAY_BUFFER)
+                {
+                    Guard.IsTrue(byteStride.Value == 2 || byteStride.Value == 4, nameof(byteStride));
+                }
+                else if (target.Value == BufferMode.ELEMENT_ARRAY_BUFFER)
+                {
+                    Guard.IsTrue((byteStride.Value % 4) == 0, nameof(byteStride));
+                }
             }
 
             this._buffer = buffer.LogicalIndex;
 
-            this._byteLength = byteLength;
+            this._byteLength = byteLength.AsValue(buffer._Data.Length);
 
             this._byteOffset = byteOffset;
             this._byteStride = byteStride;
@@ -210,27 +217,7 @@ namespace glTF2Sharp.Schema2
 
     public partial class ModelRoot
     {
-        #region Buffer and BufferView manipulation APIs
-
-        public Buffer CreateBuffer(int byteCount)
-        {
-            var buffer = new Buffer(byteCount);
-            _buffers.Add(buffer);
-
-            return buffer;
-        }       
-
-        public Buffer CreateBuffer(ReadOnlySpan<Byte> data)
-        {
-            Guard.IsFalse(data.IsEmpty, nameof(data));
-
-            var buffer = new Buffer(data);
-            _buffers.Add(buffer);
-
-            return buffer;
-        }
-
-        public BufferView CreateBufferView(Buffer buffer, int byteLength, int? byteOffset = null, int? byteStride = null, BufferMode? target = null)
+        public BufferView CreateBufferView(Buffer buffer, int? byteLength = null, int? byteOffset = null, int? byteStride = null, BufferMode? target = null)
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
@@ -242,51 +229,19 @@ namespace glTF2Sharp.Schema2
             return bv;
         }
 
-        private BufferView CreateIndexBufferView(ReadOnlySpan<Byte> data)
+        public BufferView CreateIndexBufferView(ReadOnlySpan<Byte> data)
         {
             var buffer = CreateBuffer(data);
 
             return CreateBufferView(buffer, data.Length, null, null, BufferMode.ELEMENT_ARRAY_BUFFER);
         }
 
-        private BufferView CreateVertexBufferView(ReadOnlySpan<Byte> data, int byteStride)
+        public BufferView CreateVertexBufferView(ReadOnlySpan<Byte> data, int byteStride)
         {
             var buffer = CreateBuffer(data);
 
             return CreateBufferView(buffer, data.Length, null, byteStride, BufferMode.ARRAY_BUFFER);
-        }
-
-        /// <summary>
-        /// Merges all the Buffer objects into a single, big one.
-        /// </summary>
-        /// <remarks>
-        /// When merging the buffers, it also adjusts the BufferView offsets so the data they point to remains the same.
-        /// </remarks>
-        public void MergeBuffers()
-        {
-            // retrieve all buffers and merge them into a single, big buffer
-
-            var views = _bufferViews
-                .OrderByDescending(item => item.Data.Count)
-                .ToArray();
-
-            if (views.Length <= 1) return; // nothing to do.
-
-            var sbbuilder = new _StaticBufferBuilder(0);
-
-            foreach (var bv in views) bv._ConvertToStaticBuffer(sbbuilder);
-
-            this._buffers.Clear();
-
-            var b = new Buffer
-            {
-                _Data = sbbuilder.ToArray()
-            };
-
-            this._buffers.Add(b);
-        }
-
-        #endregion
+        }        
     }
 
     /// <summary>

+ 63 - 80
src/glTF2Sharp.DOM/Schema2/gltf.Geometry.cs

@@ -42,13 +42,17 @@ namespace glTF2Sharp.Schema2
             }
         }
 
-        public PrimitiveType DrawPrimitiveType => this._mode.HasValue ? _mode.Value : _modeDefault;           
+        public PrimitiveType DrawPrimitiveType
+        {
+            get => this._mode.AsValue(_modeDefault);
+            set => this._mode = value.AsNullable(_modeDefault);
+        }
 
         public int MorpthTargets => _targets.Count;
 
         public BoundingBox3? LocalBounds3 => VertexAccessors["POSITION"]?.LocalBounds3;
 
-        public IReadOnlyDictionary<String, Accessor> VertexAccessors => new LinqDictionary<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
         {
@@ -58,26 +62,48 @@ namespace glTF2Sharp.Schema2
 
                 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;
+                }
+            }
         }
 
         #endregion
 
         #region API
 
-        public IReadOnlyList<KeyValuePair<String,Accessor>> GetVertexAccessorsByBuffer(BufferView vb)
+        public Accessor GetVertexAccessor(string attributeKey)
         {
-            Guard.NotNull(vb,nameof(vb));
-            Guard.MustShareLogicalParent(this.LogicalParent, vb, nameof(vb));
+            Guard.NotNullOrEmpty(attributeKey, nameof(attributeKey));
 
-            return VertexAccessors
-                .Where(key => key.Value.Buffer ==vb)
-                .OrderBy(item => item.Value.ByteOffset)
-                .ToArray();
+            if (!_attributes.TryGetValue(attributeKey, out int idx)) return null;
+
+            return this.LogicalParent.LogicalParent._LogicalAccessors[idx];
         }
 
+        public void SetVertexAccessor(string attributeKey, Accessor accessor)
+        {
+            Guard.NotNullOrEmpty(attributeKey, nameof(attributeKey));
+
+            if (accessor != null)
+            {
+                Guard.MustShareLogicalParent(this.LogicalParent.LogicalParent, accessor, nameof(accessor));
+                _attributes[attributeKey] = accessor.LogicalIndex;                
+            }
+            else
+            {
+                _attributes.Remove(attributeKey);
+            }
+        }        
+
         public IReadOnlyDictionary<String, Accessor> GetMorphTargetAccessors(int idx)
         {
-            return new LinqDictionary<String, int, Accessor>(_targets[idx], alidx => this.LogicalParent.LogicalParent._LogicalAccessors[alidx]);
+            return new ReadOnlyLinqDictionary<String, int, Accessor>(_targets[idx], alidx => this.LogicalParent.LogicalParent._LogicalAccessors[alidx]);
         }
 
         public void SetMorphTargetAccessors(int idx, IReadOnlyDictionary<String, Accessor> accessors)
@@ -85,7 +111,7 @@ namespace glTF2Sharp.Schema2
             Guard.NotNull(accessors, nameof(accessors));
             foreach (var kvp in accessors)
             {
-                Guard.MustShareLogicalParent(this.LogicalParent, kvp.Value,nameof(accessors));
+                Guard.MustShareLogicalParent(this.LogicalParent, kvp.Value, nameof(accessors));
             }
 
             while (_targets.Count <= idx) _targets.Add(new Dictionary<string, int>());
@@ -94,7 +120,7 @@ namespace glTF2Sharp.Schema2
 
             target.Clear();
 
-            foreach(var kvp in accessors)
+            foreach (var kvp in accessors)
             {
                 target[kvp.Key] = kvp.Value.LogicalIndex;
             }
@@ -134,23 +160,17 @@ namespace glTF2Sharp.Schema2
                 .Distinct();
 
             return indices.Select(idx => this.LogicalParent.LogicalParent.LogicalBufferViews[idx]);            
-        }        
-
-        internal void _SetVertexAccessors(IReadOnlyDictionary<string, Accessor> attributes)
-        {
-            this._attributes.Clear();
-
-            foreach (var attribute in attributes)
-            {
-                this._attributes[attribute.Key] = attribute.Value.LogicalIndex;
-            }            
         }
 
-        internal void _SetIndexAccessors(Accessor indices, PrimitiveType ptype = PrimitiveType.TRIANGLES)
+        public IReadOnlyList<KeyValuePair<String, Accessor>> GetVertexAccessorsByBuffer(BufferView vb)
         {
-            if (indices != null) this._indices = indices.LogicalIndex;
+            Guard.NotNull(vb, nameof(vb));
+            Guard.MustShareLogicalParent(this.LogicalParent, vb, nameof(vb));
 
-            _mode = ptype;
+            return VertexAccessors
+                .Where(key => key.Value.Buffer == vb)
+                .OrderBy(item => item.Value.ByteOffset)
+                .ToArray();
         }
 
         private Accessor _GetAccessor(IReadOnlyDictionary<string, int> attributes, string attribute)
@@ -217,6 +237,15 @@ namespace glTF2Sharp.Schema2
 
         public IReadOnlyList<float> MorphWeights => _weights.Select(item => (float)item).ToArray();
 
+        public MeshPrimitive CreatePrimitive()
+        {
+            var mp = new MeshPrimitive();
+
+            _primitives.Add(mp);
+
+            return mp;
+        }
+
         internal MeshPrimitive _AddPrimitive(IReadOnlyDictionary<string, Accessor> attributes, Accessor indices, PrimitiveType ptype)
         {
             if (attributes == null) throw new ArgumentNullException(nameof(attributes));
@@ -232,8 +261,13 @@ namespace glTF2Sharp.Schema2
 
             _primitives.Add(mp);
 
-            mp._SetVertexAccessors(attributes);
-            mp._SetIndexAccessors(indices, ptype);           
+            foreach(var kvp in attributes)
+            {
+                mp.SetVertexAccessor(kvp.Key, kvp.Value);
+            }
+
+            mp.IndexAccessor = indices;
+            mp.DrawPrimitiveType = ptype;
 
             return mp;
         }
@@ -419,63 +453,12 @@ namespace glTF2Sharp.Schema2
 
     public partial class ModelRoot
     {
-        /*
-        public void AddMeshes(MeshCollectionBuilder<int> meshes)
-        {
-            Guard.NotNull(meshes, nameof(meshes));
-
-            var buffers = _CreateSharedMeshBuffers(meshes._VertexBuffers, meshes._IndexBuffers);
-
-            this._meshes.Clear();            
-
-            foreach (var k in meshes.Keys)
-            {
-                var srcMesh = meshes[k];
-
-                var dstMesh = new Mesh();
-                this._meshes.Add(dstMesh);
-
-                foreach (var p in srcMesh._Primitives)
-                {
-                    dstMesh._AddPrimitive(p, buffers);
-                }
-            }
-        }        
-
-        public Mesh AddMesh(MeshBuilder srcMesh)
+        public Mesh CreateMesh()
         {
-            Guard.NotNull(srcMesh, nameof(srcMesh));
-
-            var buffers = _CreateSharedMeshBuffers(srcMesh._VertexBuffers, srcMesh._IndexBuffers);
-
             var dstMesh = new Mesh();
             this._meshes.Add(dstMesh);
 
-            foreach (var p in srcMesh._Primitives)
-            {
-                dstMesh._AddPrimitive(p, buffers);
-            }
-
             return dstMesh;
         }
-
-        private IReadOnlyDictionary<_DataBuffer, BufferView> _CreateSharedMeshBuffers(IEnumerable<_DataBuffer> vertexBuffers, IEnumerable<_DataBuffer> indexBuffers)
-        {
-            var dict = new Dictionary<_DataBuffer, BufferView>(ReferenceComparer<_DataBuffer>.Instance);
-
-            foreach(var vb in vertexBuffers)
-            {
-                dict[vb] = CreateVertexBufferView(vb._Data, vb._ByteStride);
-            }
-
-            foreach (var vb in indexBuffers)
-            {
-                dict[vb] = CreateIndexBufferView(vb._Data);
-            }
-
-            return dict;
-        }*/
-    }
-
-    
+    }    
 }

+ 8 - 8
src/glTF2Sharp.DOM/Schema2/gltf.Materials.cs

@@ -66,7 +66,7 @@ namespace glTF2Sharp.Schema2
 
         public Boolean DoubleSided
         {
-            get => _doubleSided ?? _doubleSidedDefault;
+            get => _doubleSided.AsValue(_doubleSidedDefault);
             set => _doubleSided = value.AsNullable(_doubleSidedDefault);
         }
 
@@ -115,7 +115,7 @@ namespace glTF2Sharp.Schema2
                     this,
                     "Emissive",
                     _GetEmissiveTexture,
-                    () => { var rgb = _emissiveFactor ?? _emissiveFactorDefault; return new Vector4(rgb, 1); },
+                    () => { var rgb = _emissiveFactor.AsValue(_emissiveFactorDefault); return new Vector4(rgb, 1); },
                     value => _emissiveFactor = new Vector3(value.X, value.Y, value.Z).AsNullable(_emissiveFactorDefault)
                     );
 
@@ -236,7 +236,7 @@ namespace glTF2Sharp.Schema2
                 material,
                 "BaseColor",
                 _GetBaseTexture,
-                () => (_baseColorFactor ?? _baseColorFactorDefault),
+                () => _baseColorFactor.AsValue(_baseColorFactorDefault),
                 value => _baseColorFactor = value.AsNullable(_baseColorFactorDefault)
                 );
 
@@ -281,7 +281,7 @@ namespace glTF2Sharp.Schema2
                 material,
                 "Diffuse",
                 _GetDiffuseTexture,
-                () => (_diffuseFactor ?? _diffuseFactorDefault),
+                () => _diffuseFactor.AsValue(_diffuseFactorDefault),
                 value => _diffuseFactor = value.AsNullable(_diffuseFactorDefault)
                 );
 
@@ -299,7 +299,7 @@ namespace glTF2Sharp.Schema2
                 material,
                 "Specular",
                 null,
-                () => { var rgb = _specularFactor ?? _specularFactorDefault; return new Vector4(rgb, 1); },
+                () => { var rgb = _specularFactor.AsValue(_specularFactorDefault); return new Vector4(rgb, 1); },
                 value => _specularFactor = new Vector3(value.X,value.Y,value.Z).AsNullable(_specularFactorDefault)
                 );
         }
@@ -307,7 +307,7 @@ namespace glTF2Sharp.Schema2
 
     public partial class ModelRoot
     {
-        internal Material _AddLogicalMaterial()
+        public Material AddLogicalMaterial()
         {
             var mat = new Material();
 
@@ -316,7 +316,7 @@ namespace glTF2Sharp.Schema2
             return mat;
         }
 
-        internal Material _AddLogicalMaterial(IReadOnlyList<string> channelKeys)
+        public Material AddLogicalMaterial(IReadOnlyList<string> channelKeys)
         {
             var mat = Material.CreateBestChoice(channelKeys);
 
@@ -325,7 +325,7 @@ namespace glTF2Sharp.Schema2
             return mat;
         }
 
-        internal Material _AddLogicalMaterial(Type mtype)
+        public Material AddLogicalMaterial(Type mtype)
         {
             Material mat = null;
 

+ 1 - 1
src/glTF2Sharp.DOM/Schema2/gltf.Textures.cs

@@ -33,7 +33,7 @@ namespace glTF2Sharp.Schema2
 
         public Double Scale
         {
-            get => this._scale ?? _scaleDefault;
+            get => this._scale.AsValue(_scaleDefault);
             set => this._scale = value.AsNullable(_scaleDefault);
         }
 

+ 7 - 2
src/glTF2Sharp.DOM/_Extensions.cs

@@ -180,11 +180,16 @@ namespace glTF2Sharp
             }
         }
 
-        internal static T? AsNullable<T>(this T value, T defval) where T : struct, IEquatable<T>
+        internal static T AsValue<T>(this T? value, T defval) where T : struct
         {
-            return value.Equals(defval) ? (T?)null : value;
+            return value ?? defval;
         }
 
+        internal static T? AsNullable<T>(this T value, T defval) where T : struct
+        {
+            return value.Equals(defval) ? (T?)null : value;
+        }        
+
         internal static T? AsNullable<T>(this T value, T defval, T minval, T maxval) where T : struct, IEquatable<T>, IComparable<T>
         {
             if (value.Equals(defval)) return null;

+ 1 - 28
src/glTF2Sharp.Tests/GitUtils.cs

@@ -4,32 +4,5 @@ using System.Text;
 
 namespace glTF2Sharp
 {
-    static class GitUtils
-    {
-        public static void Syncronize(string remoteUrl, string localDirectory)
-        {
-            if (LibGit2Sharp.Repository.Discover(localDirectory) == null)
-            {
-                NUnit.Framework.TestContext.Progress.WriteLine($"Cloning {remoteUrl} can take several minutes; Please wait...");
-
-                LibGit2Sharp.Repository.Clone(remoteUrl, localDirectory);
-
-                NUnit.Framework.TestContext.Progress.WriteLine($"... Clone Completed");
-
-                return;
-            }
-            
-            using (var repo = new LibGit2Sharp.Repository(localDirectory))
-            {
-                var options = new LibGit2Sharp.PullOptions
-                {
-                    FetchOptions = new LibGit2Sharp.FetchOptions()
-                };
-
-                var r = LibGit2Sharp.Commands.Pull(repo, new LibGit2Sharp.Signature("Anonymous", "[email protected]", new DateTimeOffset(DateTime.Now)), options);
-
-                NUnit.Framework.TestContext.Progress.WriteLine($"{remoteUrl} is {r.Status}");
-            }
-        }
-    }
+    
 }

+ 48 - 0
src/glTF2Sharp.Tests/Schema2/CreateModelTests.cs

@@ -20,5 +20,53 @@ namespace glTF2Sharp.Schema2
             Assert.AreEqual("Empty Scene", scene.Name);
             Assert.AreEqual("Empty Scene", root.DefaultScene.Name);            
         }
+
+        [Test]
+        public void CreateTriangleScene()
+        {
+            var root = ModelRoot.CreateNew();            
+
+            // create a mesh with a triangle
+
+            var mesh = root.CreateMesh();
+
+            var primitive = mesh.CreatePrimitive();
+            primitive.DrawPrimitiveType = PrimitiveType.TRIANGLES;
+            // primitive.Material = root.AddLogicalMaterial(typeof(MaterialPBRMetallicRoughness));
+
+            var positions = root.CreateVector3Buffer
+                (
+                new System.Numerics.Vector3(0, 10, 0),
+                new System.Numerics.Vector3(-10, -10, 0),
+                new System.Numerics.Vector3(10, -10, 0)
+                );
+
+            var indices = root.CreateIndexBuffer(0, 1, 2);
+
+            var positionsView = root.CreateBufferView(positions,null,null,null, BufferMode.ARRAY_BUFFER);
+            var indicesView =   root.CreateBufferView(indices, null, null, null, BufferMode.ELEMENT_ARRAY_BUFFER);
+
+            var positionsAccessor = root.CreateAccessor();
+            positionsAccessor.SetVertexData(positionsView, ComponentType.FLOAT, ElementType.VEC3, false, 0, 3);
+
+            var indicesAccessor = root.CreateAccessor();
+            indicesAccessor.SetIndexData(indicesView, IndexType.UNSIGNED_INT, 0, 3);
+
+            primitive.SetVertexAccessor("POSITION", positionsAccessor);
+            primitive.IndexAccessor = indicesAccessor;
+
+            // create a scene
+
+            var scene = root.UseScene("Empty Scene");
+
+            var triangleNode = scene.AddNode("Triangle");
+
+            triangleNode.Mesh = mesh;
+
+            // save
+
+            root.AttachToCurrentTest("result.glb");
+            root.AttachToCurrentTest("result.gltf");
+        }
     }
 }

+ 3 - 3
src/glTF2Sharp.Tests/TestFiles.cs

@@ -32,9 +32,9 @@ namespace glTF2Sharp
             NUnit.Framework.TestContext.Progress.WriteLine("Checking out test files... It might take a while, please, wait.");
             NUnit.Framework.TestContext.Progress.WriteLine("...");
 
-            GitUtils.Syncronize("https://github.com/KhronosGroup/glTF-Sample-Models.git", _SampleModelsDir);
-            GitUtils.Syncronize("https://github.com/KhronosGroup/glTF-Blender-Exporter.git", _PollyModelsDir);
-            GitUtils.Syncronize("https://github.com/bghgary/glTF-Asset-Generator.git", _GeneratedAssetsDir);            
+            TestUtils.SyncronizeGitRepository("https://github.com/KhronosGroup/glTF-Sample-Models.git", _SampleModelsDir);
+            TestUtils.SyncronizeGitRepository("https://github.com/KhronosGroup/glTF-Blender-Exporter.git", _PollyModelsDir);
+            TestUtils.SyncronizeGitRepository("https://github.com/bghgary/glTF-Asset-Generator.git", _GeneratedAssetsDir);            
         }
 
         #endregion

+ 57 - 0
src/glTF2Sharp.Tests/TestUtils.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace glTF2Sharp
+{
+    static class TestUtils
+    {
+        public static string GetAttachmentPath(this NUnit.Framework.TestContext context, string fileName)
+        {
+            if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentNullException(nameof(fileName));
+            if (System.IO.Path.IsPathRooted(fileName)) throw new ArgumentException(nameof(fileName), "path must be a relative path");
+
+            return System.IO.Path.Combine(context.TestDirectory, $"{context.Test.ID}.{fileName}");
+        }
+
+        public static void AttachToCurrentTest(this Schema2.ModelRoot model, string fileName)
+        {
+            fileName = NUnit.Framework.TestContext.CurrentContext.GetAttachmentPath(fileName);
+
+            if (fileName.ToLower().EndsWith(".gltf")) model.SaveGLTF(fileName, Newtonsoft.Json.Formatting.Indented);
+            if (fileName.ToLower().EndsWith(".glb"))
+            {
+                model.MergeBuffers();
+                model.SaveGLB(fileName);
+            }
+
+            NUnit.Framework.TestContext.AddTestAttachment(fileName);
+        }
+
+        public static void SyncronizeGitRepository(string remoteUrl, string localDirectory)
+        {
+            if (LibGit2Sharp.Repository.Discover(localDirectory) == null)
+            {
+                NUnit.Framework.TestContext.Progress.WriteLine($"Cloning {remoteUrl} can take several minutes; Please wait...");
+
+                LibGit2Sharp.Repository.Clone(remoteUrl, localDirectory);
+
+                NUnit.Framework.TestContext.Progress.WriteLine($"... Clone Completed");
+
+                return;
+            }
+
+            using (var repo = new LibGit2Sharp.Repository(localDirectory))
+            {
+                var options = new LibGit2Sharp.PullOptions
+                {
+                    FetchOptions = new LibGit2Sharp.FetchOptions()
+                };
+
+                var r = LibGit2Sharp.Commands.Pull(repo, new LibGit2Sharp.Signature("Anonymous", "[email protected]", new DateTimeOffset(DateTime.Now)), options);
+
+                NUnit.Framework.TestContext.Progress.WriteLine($"{remoteUrl} is {r.Status}");
+            }
+        }
+    }
+}