فهرست منبع

WIP on mesh generation... cleaning up...

Vicente Penades 6 سال پیش
والد
کامیت
cc7b10f055

+ 51 - 15
src/SharpGLTF.Toolkit/Geometry/SkinnedMeshBuilder.cs

@@ -7,13 +7,13 @@ namespace SharpGLTF.Geometry
 {
     using Collections;
 
-    public class SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial>
-        where TVertex : struct
-        where TSkin : struct
+    public class SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin>
+        where TVertex : struct, VertexTypes.IVertex
+        where TSkin : struct, VertexTypes.IVertexJoints
     {
         #region lifecycle
 
-        internal SkinnedPrimitiveBuilder(SkinnedMeshBuilder<TVertex, TSkin, TMaterial> mesh, TMaterial material)
+        internal SkinnedPrimitiveBuilder(SkinnedMeshBuilder<TMaterial, TVertex, TSkin> mesh, TMaterial material)
         {
             this._Mesh = mesh;
             this._Material = material;
@@ -23,7 +23,7 @@ namespace SharpGLTF.Geometry
 
         #region data
 
-        private readonly SkinnedMeshBuilder<TVertex, TSkin, TMaterial> _Mesh;
+        private readonly SkinnedMeshBuilder<TMaterial, TVertex, TSkin> _Mesh;
 
         private readonly TMaterial _Material;
 
@@ -34,7 +34,7 @@ namespace SharpGLTF.Geometry
 
         #region properties
 
-        public SkinnedMeshBuilder<TVertex, TSkin, TMaterial> Mesh => _Mesh;
+        public SkinnedMeshBuilder<TMaterial, TVertex, TSkin> Mesh => _Mesh;
 
         public TMaterial Material => _Material;
 
@@ -76,9 +76,9 @@ namespace SharpGLTF.Geometry
         #endregion
     }
 
-    public class SkinnedMeshBuilder<TVertex, TSkin, TMaterial>
-        where TVertex : struct
-        where TSkin : struct
+    public class SkinnedMeshBuilder<TMaterial, TVertex, TSkin>
+        where TVertex : struct, VertexTypes.IVertex
+        where TSkin : struct, VertexTypes.IVertexJoints
     {
         #region lifecycle
 
@@ -91,7 +91,7 @@ namespace SharpGLTF.Geometry
 
         #region data
 
-        private readonly Dictionary<TMaterial, SkinnedPrimitiveBuilder<TVertex,TSkin, TMaterial>> _Primitives = new Dictionary<TMaterial, SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial>>();
+        private readonly Dictionary<TMaterial, SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin>> _Primitives = new Dictionary<TMaterial, SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin>>();
 
         #endregion
 
@@ -99,7 +99,7 @@ namespace SharpGLTF.Geometry
 
         public string Name { get; set; }
 
-        public IReadOnlyCollection<SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial>> Primitives => _Primitives.Values;
+        public IReadOnlyCollection<SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin>> Primitives => _Primitives.Values;
 
         #endregion
 
@@ -115,9 +115,9 @@ namespace SharpGLTF.Geometry
 
         public void AddTriangle(TMaterial material, (TVertex, TSkin) a, (TVertex, TSkin) b, (TVertex, TSkin) c)
         {
-            if (!_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial> primitive))
+            if (!_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin> primitive))
             {
-                primitive = new SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial>(this, material);
+                primitive = new SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin>(this, material);
                 _Primitives[material] = primitive;
             }
 
@@ -126,18 +126,54 @@ namespace SharpGLTF.Geometry
 
         public IEnumerable<(int, int, int)> GetTriangles(TMaterial material)
         {
-            if (_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial> primitive)) return primitive.Triangles;
+            if (_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin> primitive)) return primitive.Triangles;
 
             return Enumerable.Empty<(int, int, int)>();
         }
 
         public IReadOnlyList<int> GetIndices(TMaterial material)
         {
-            if (_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TVertex, TSkin, TMaterial> primitive)) return primitive.Indices;
+            if (_Primitives.TryGetValue(material, out SkinnedPrimitiveBuilder<TMaterial, TVertex, TSkin> primitive)) return primitive.Indices;
 
             return new int[0];
         }
 
+        internal static IEnumerable<(TMaterial, MemoryAccessor[], MemoryAccessor)[]> MergeBuffers(IEnumerable<SkinnedMeshBuilder<TMaterial, TVertex, TSkin>> meshBuilders)
+        {
+            var vertexBlocks = VertexTypes.VertexUtils.CreateVertexMemoryAccessors
+                (
+                meshBuilders
+                .SelectMany(item => item.Primitives)
+                .Select(item => item.Vertices)
+                ).ToList();
+
+            var indexBlocks = VertexTypes.VertexUtils.CreateIndexMemoryAccessors
+                (
+                meshBuilders
+                .SelectMany(item => item.Primitives)
+                .Select(item => item.Indices)
+                ).ToList();
+
+            int bidx = 0;
+
+            foreach (var meshBuilder in meshBuilders)
+            {
+                var dstMesh = new (TMaterial, MemoryAccessor[], MemoryAccessor)[meshBuilder.Primitives.Count];
+
+                int pidx = 0;
+
+                foreach (var primitiveBuilder in meshBuilder.Primitives)
+                {
+                    dstMesh[pidx] = (primitiveBuilder.Material, vertexBlocks[bidx], indexBlocks[bidx]);
+
+                    ++pidx;
+                    ++bidx;
+                }
+
+                yield return dstMesh;
+            }
+        }
+
         #endregion
     }
 }

+ 6 - 119
src/SharpGLTF.Toolkit/Geometry/StaticMeshBuilder.cs

@@ -8,103 +8,12 @@ namespace SharpGLTF.Geometry
 {
     using Collections;
 
-    public class StaticPrimitiveBuilder<TVertex, TMaterial>
-        where TVertex : struct
+    public class StaticMeshBuilder<TMaterial, TVertex> : SkinnedMeshBuilder<TMaterial, TVertex, VertexTypes.VertexJoints0>
+        where TVertex : struct, VertexTypes.IVertex
     {
-        #region lifecycle
+        public StaticMeshBuilder(string name = null) : base(name) { }
 
-        internal StaticPrimitiveBuilder(StaticMeshBuilder<TVertex, TMaterial> mesh, TMaterial material)
-        {
-            this._Mesh = mesh;
-            this._Material = material;
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly StaticMeshBuilder<TVertex, TMaterial> _Mesh;
-
-        private readonly TMaterial _Material;
-
-        private readonly VertexColumn<TVertex> _Vertices = new VertexColumn<TVertex>();
-        private readonly List<int> _Indices = new List<int>();
-
-        #endregion
-
-        #region properties
-
-        public StaticMeshBuilder<TVertex, TMaterial> Mesh => _Mesh;
-
-        public TMaterial Material => _Material;
-
-        public IReadOnlyList<TVertex> Vertices => _Vertices;
-
-        public IReadOnlyList<int> Indices => _Indices;
-
-        public IEnumerable<(int, int, int)> Triangles
-        {
-            get
-            {
-                for (int i = 2; i < _Indices.Count; i += 3)
-                {
-                    yield return (_Indices[i - 2], _Indices[i - 1], _Indices[i]);
-                }
-            }
-        }
-
-        #endregion
-
-        #region API
-
-        public void AddTriangle(TVertex a, TVertex b, TVertex c)
-        {
-            var aa = _Vertices.Use(a);
-            var bb = _Vertices.Use(b);
-            var cc = _Vertices.Use(c);
-
-            // check for degenerated triangles:
-            if (aa == bb) return;
-            if (aa == cc) return;
-            if (bb == cc) return;
-
-            _Indices.Add(aa);
-            _Indices.Add(bb);
-            _Indices.Add(cc);
-        }
-
-        #endregion
-    }
-
-    public class StaticMeshBuilder<TVertex, TMaterial>
-        where TVertex : struct
-    {
-        #region lifecycle
-
-        public StaticMeshBuilder(string name = null)
-        {
-            this.Name = name;
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly Dictionary<TMaterial, StaticPrimitiveBuilder<TVertex, TMaterial>> _Primitives = new Dictionary<TMaterial, StaticPrimitiveBuilder<TVertex, TMaterial>>();
-
-        #endregion
-
-        #region properties
-
-        public string Name { get; set; }
-
-        public IReadOnlyCollection<StaticPrimitiveBuilder<TVertex, TMaterial>> Primitives => _Primitives.Values;
-
-        #endregion
-
-        #region API
-
-        public void AddPolygon(TMaterial material, params TVertex[] points)
+        public new void AddPolygon(TMaterial material, params TVertex[] points)
         {
             for (int i = 2; i < points.Length; ++i)
             {
@@ -112,31 +21,9 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        public void AddTriangle(TMaterial material, TVertex a, TVertex b, TVertex c)
+        public new void AddTriangle(TMaterial material, TVertex a, TVertex b, TVertex c)
         {
-            if (!_Primitives.TryGetValue(material, out StaticPrimitiveBuilder<TVertex, TMaterial> primitive))
-            {
-                primitive = new StaticPrimitiveBuilder<TVertex, TMaterial>(this, material);
-                _Primitives[material] = primitive;
-            }
-
-            primitive.AddTriangle(a, b, c);
+            AddTriangle(material, (a, default), (b, default), (c, default));
         }
-
-        public IEnumerable<(int, int, int)> GetTriangles(TMaterial material)
-        {
-            if (_Primitives.TryGetValue(material, out StaticPrimitiveBuilder<TVertex, TMaterial> primitive)) return primitive.Triangles;
-
-            return Enumerable.Empty<(int, int, int)>();
-        }
-
-        public IReadOnlyList<int> GetIndices(TMaterial material)
-        {
-            if (_Primitives.TryGetValue(material, out StaticPrimitiveBuilder<TVertex, TMaterial> primitive)) return primitive.Indices;
-
-            return new int[0];
-        }
-
-        #endregion
     }
 }

+ 40 - 3
src/SharpGLTF.Toolkit/Geometry/VertexTypes/SkinnedVertices.cs

@@ -5,24 +5,61 @@ using System.Text;
 
 namespace SharpGLTF.Geometry.VertexTypes
 {
-    public struct SkinJoints4
+    public interface IVertexJoints { }
+
+    public struct VertexJoints0 : IVertexJoints
+    {
+    }
+
+    public struct VertexJoints4 : IVertexJoints
+    {
+        public VertexJoints4(int jointIndex)
+        {
+            Joints_0 = new Vector4(jointIndex);
+            Weights_0 = Vector4.UnitX;
+        }
+
+        public VertexJoints4(int jointIndex1, int jointIndex2)
+        {
+            Joints_0 = new Vector4(jointIndex1, jointIndex2, 0, 0);
+            Weights_0 = new Vector4(0.5f, 0.5f, 0, 0);
+        }
+
+        [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
+        public Vector4 Joints_0;
+
+        [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
+        public Vector4 Weights_0;
+    }
+
+    public struct VertexJoints8 : IVertexJoints
     {
-        public SkinJoints4(int jointIndex)
+        public VertexJoints8(int jointIndex)
         {
             Joints_0 = new Vector4(jointIndex);
+            Joints_1 = new Vector4(jointIndex);
             Weights_0 = Vector4.UnitX;
+            Weights_1 = Vector4.Zero;
         }
 
-        public SkinJoints4(int jointIndex1, int jointIndex2)
+        public VertexJoints8(int jointIndex1, int jointIndex2)
         {
             Joints_0 = new Vector4(jointIndex1, jointIndex2, 0, 0);
+            Joints_1 = Vector4.Zero;
             Weights_0 = new Vector4(0.5f, 0.5f, 0, 0);
+            Weights_1 = Vector4.Zero;
         }
 
         [VertexAttribute("JOINTS_0", Schema2.EncodingType.UNSIGNED_BYTE, false)]
         public Vector4 Joints_0;
 
+        [VertexAttribute("JOINTS_1", Schema2.EncodingType.UNSIGNED_BYTE, false)]
+        public Vector4 Joints_1;
+
         [VertexAttribute("WEIGHTS_0", Schema2.EncodingType.UNSIGNED_BYTE, true)]
         public Vector4 Weights_0;
+
+        [VertexAttribute("WEIGHTS_1", Schema2.EncodingType.UNSIGNED_BYTE, true)]
+        public Vector4 Weights_1;
     }
 }

+ 13 - 5
src/SharpGLTF.Toolkit/Geometry/VertexTypes/StaticVertices.cs

@@ -5,7 +5,9 @@ using System.Text;
 
 namespace SharpGLTF.Geometry.VertexTypes
 {
-    public struct VertexPosition
+    public interface IVertex { }
+
+    public struct VertexPosition : IVertex
     {
         public VertexPosition(float px, float py, float pz)
         {
@@ -16,8 +18,14 @@ namespace SharpGLTF.Geometry.VertexTypes
         public Vector3 Position;
     }
 
-    public struct VertexPositionNormal
+    public struct VertexPositionNormal : IVertex
     {
+        public VertexPositionNormal(Vector3 p, Vector3 n)
+        {
+            Position = p;
+            Normal = Vector3.Normalize(n);
+        }
+
         public VertexPositionNormal(float px, float py, float pz, float nx, float ny, float nz)
         {
             Position = new Vector3(px, py, pz);
@@ -31,7 +39,7 @@ namespace SharpGLTF.Geometry.VertexTypes
         public Vector3 Normal;
     }
 
-    public struct VertexPositionNormalColor1
+    public struct VertexPositionNormalColor1 : IVertex
     {
         public VertexPositionNormalColor1(Vector3 pos, Vector3 nrm, Vector4 color)
         {
@@ -50,9 +58,9 @@ namespace SharpGLTF.Geometry.VertexTypes
         public Vector4 Color;
     }
 
-    public struct VertexPositionNormalTex1
+    public struct VertexPositionNormalTexture1 : IVertex
     {
-        public VertexPositionNormalTex1(Vector3 pos, Vector3 nrm, Vector2 tex)
+        public VertexPositionNormalTexture1(Vector3 pos, Vector3 nrm, Vector2 tex)
         {
             Position = pos;
             Normal = Vector3.Normalize(nrm);

+ 70 - 118
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -6,57 +6,107 @@ using System.Text;
 
 namespace SharpGLTF.Geometry.VertexTypes
 {
+    using Memory;
+
     public static class VertexUtils
     {
-        public static System.Reflection.FieldInfo GetVertexField(Type vertexType, string attributeName)
+        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TVertex, TSkin>(this IEnumerable<IReadOnlyList<(TVertex, TSkin)>> vertexBlocks)
+            where TVertex : struct, IVertex
+            where TSkin : struct, IVertexJoints
         {
-            foreach (var finfo in vertexType.GetFields())
+            // total number of vertices
+            var totalCount = vertexBlocks.Sum(item => item.Count);
+
+            // vertex attributes
+            var attributes = GetVertexAttributes(typeof(TVertex), typeof(TSkin), totalCount);
+
+            // create master vertex buffer
+            int byteStride = attributes[0].ByteStride;
+            var vbuffer = new ArraySegment<byte>( new Byte[byteStride * totalCount] );
+
+            var baseVertexIndex = 0;
+
+            foreach (var block in vertexBlocks)
             {
-                var attribute = _GetAccessor(finfo);
+                var accessors = MemoryAccessInfo
+                    .Slice(attributes, baseVertexIndex, block.Count)
+                    .Select(item => new MemoryAccessor(vbuffer, item))
+                    .ToArray();
 
-                if (attribute.HasValue)
+                foreach (var accessor in accessors)
                 {
-                    if (attribute.Value.Name == attributeName) return finfo;
+                    bool isSkin = false;
+                    var finfo = GetVertexField(typeof(TVertex), accessor.Attribute.Name);
+                    if (finfo == null)
+                    {
+                        finfo = GetVertexField(typeof(TSkin), accessor.Attribute.Name);
+                        isSkin = true;
+                    }
+
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) accessor.AsScalarArray().FillFrom(0, GetScalarColumn<TVertex, TSkin>(finfo, block, isSkin));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) accessor.AsVector2Array().FillFrom(0, GetVector2Column<TVertex, TSkin>(finfo, block, isSkin));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC3) accessor.AsVector3Array().FillFrom(0, GetVector3Column<TVertex, TSkin>(finfo, block, isSkin));
+                    if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC4) accessor.AsVector4Array().FillFrom(0, GetVector4Column<TVertex, TSkin>(finfo, block, isSkin));
                 }
-            }
 
-            return null;
+                yield return accessors;
+
+                baseVertexIndex += block.Count;
+            }
         }
 
-        public static MemoryAccessInfo[] GetVertexAttributes(Type vertexType, int itemsCount)
+        public static IEnumerable<MemoryAccessor> CreateIndexMemoryAccessors(this IEnumerable<IReadOnlyList<Int32>> indexBlocks)
         {
-            var attributes = new List<MemoryAccessInfo>();
+            // get attributes
+            var totalCount = indexBlocks.Sum(item => item.Count);
+            var attribute = new MemoryAccessInfo("INDEX", 0, totalCount, 0, Schema2.DimensionType.SCALAR, Schema2.EncodingType.UNSIGNED_INT);
+
+            // create master index buffer
+            var ibytes = new Byte[4 * totalCount];
+            var ibuffer = new ArraySegment<byte>(ibytes);
+
+            var baseIndicesIndex = 0;
+
+            foreach (var block in indexBlocks)
+            {
+                var accessor = new MemoryAccessor(ibuffer, attribute.Slice(baseIndicesIndex, block.Count));
 
+                accessor.AsIntegerArray().FillFrom(0, block);
+
+                yield return accessor;
+
+                baseIndicesIndex += block.Count;
+            }
+        }
+
+        private static System.Reflection.FieldInfo GetVertexField(Type vertexType, string attributeName)
+        {
             foreach (var finfo in vertexType.GetFields())
             {
-                var attribute = _GetAccessor(finfo);
+                var attribute = _GetMemoryAccessInfo(finfo);
 
                 if (attribute.HasValue)
                 {
-                    attributes.Add(attribute.Value);
+                    if (attribute.Value.Name == attributeName) return finfo;
                 }
             }
 
-            var array = attributes.ToArray();
-
-            MemoryAccessInfo.SetInterleavedInfo(array, 0, itemsCount);
-
-            return array;
+            return null;
         }
 
-        public static MemoryAccessInfo[] GetVertexAttributes(Type vertexType, Type skinType, int itemsCount)
+        private static MemoryAccessInfo[] GetVertexAttributes(Type vertexType, Type skinType, int itemsCount)
         {
             var attributes = new List<MemoryAccessInfo>();
 
             foreach (var finfo in vertexType.GetFields())
             {
-                var attribute = _GetAccessor(finfo);
+                var attribute = _GetMemoryAccessInfo(finfo);
                 if (attribute.HasValue) attributes.Add(attribute.Value);
             }
 
             foreach (var finfo in skinType.GetFields())
             {
-                var attribute = _GetAccessor(finfo);
+                var attribute = _GetMemoryAccessInfo(finfo);
                 if (attribute.HasValue) attributes.Add(attribute.Value);
             }
 
@@ -67,57 +117,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             return array;
         }
 
-        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TVertex>(this IEnumerable<IReadOnlyList<TVertex>> vertexBlocks)
-            where TVertex : struct
-        {
-            // get attributes
-            var totalCount = vertexBlocks.Sum(item => item.Count);
-            var attributes = GetVertexAttributes(typeof(TVertex), totalCount);
-
-            // create master vertex buffer
-            int byteStride = attributes[0].ByteStride;
-            var vbytes = new Byte[byteStride * totalCount];
-            var vbuffer = new ArraySegment<byte>(vbytes);
-
-            var baseVertexIndex = 0;
-
-            foreach (var block in vertexBlocks)
-            {
-                yield return MemoryAccessInfo
-                    .Slice(attributes, baseVertexIndex, block.Count)
-                    .Select(item => new MemoryAccessor(vbuffer, item))
-                    .ToArray();
-
-                baseVertexIndex += block.Count;
-            }
-        }
-
-        public static IEnumerable<MemoryAccessor[]> CreateIndexMemoryAccessors(this IEnumerable<IReadOnlyList<Int32>> indexBlocks)
-        {
-            // get attributes
-            var totalCount = indexBlocks.Sum(item => item.Count);
-
-            // create master index buffer
-            var vbytes = new Byte[4 * totalCount];
-            var vbuffer = new ArraySegment<byte>(vbytes);
-
-            var baseIndicesIndex = 0;
-
-            throw new NotImplementedException();
-
-            /*
-            foreach (var block in indexBlocks)
-            {
-                yield return MemoryAccessInfo
-                    .Slice(attributes, baseVertexIndex, block.Count)
-                    .Select(item => new MemoryAccessor(vbuffer, item))
-                    .ToArray();
-
-                baseIndicesIndex += block.Count;
-            }*/
-        }
-
-        private static MemoryAccessInfo? _GetAccessor(System.Reflection.FieldInfo finfo)
+        private static MemoryAccessInfo? _GetMemoryAccessInfo(System.Reflection.FieldInfo finfo)
         {
             var attribute = finfo.GetCustomAttributes(true)
                     .OfType<VertexAttributeAttribute>()
@@ -139,54 +139,6 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
         }
 
-        internal static Single[] GetScalarColumn<TVertex>(this System.Reflection.FieldInfo finfo, IReadOnlyList<TVertex> vertices)
-        {
-            var dst = new Single[vertices.Count];
-
-            for (int i = 0; i < dst.Length; ++i)
-            {
-                dst[i] = (Single)finfo.GetValue(vertices[i]);
-            }
-
-            return dst;
-        }
-
-        internal static Vector2[] GetVector2Column<TVertex>(this System.Reflection.FieldInfo finfo, IReadOnlyList<TVertex> vertices)
-        {
-            var dst = new Vector2[vertices.Count];
-
-            for (int i = 0; i < dst.Length; ++i)
-            {
-                dst[i] = (Vector2)finfo.GetValue(vertices[i]);
-            }
-
-            return dst;
-        }
-
-        internal static Vector3[] GetVector3Column<TVertex>(this System.Reflection.FieldInfo finfo, IReadOnlyList<TVertex> vertices)
-        {
-            var dst = new Vector3[vertices.Count];
-
-            for (int i = 0; i < dst.Length; ++i)
-            {
-                dst[i] = (Vector3)finfo.GetValue(vertices[i]);
-            }
-
-            return dst;
-        }
-
-        internal static Vector4[] GetVector4Column<TVertex>(this System.Reflection.FieldInfo finfo, IReadOnlyList<TVertex> vertices)
-        {
-            var dst = new Vector4[vertices.Count];
-
-            for (int i = 0; i < dst.Length; ++i)
-            {
-                dst[i] = (Vector4)finfo.GetValue(vertices[i]);
-            }
-
-            return dst;
-        }
-
         internal static Single[] GetScalarColumn<TVertex, TSkin>(this System.Reflection.FieldInfo finfo, IReadOnlyList<(TVertex, TSkin)> vertices, bool vertexOrSkin)
         {
             var dst = new Single[vertices.Count];

+ 3 - 76
src/SharpGLTF.Toolkit/Schema2/AccessorExtensions.cs

@@ -5,87 +5,14 @@ using System.Numerics;
 
 namespace SharpGLTF.Schema2
 {
-    using SharpGLTF.Geometry.VertexTypes;
-
     public static partial class Toolkit
     {
-        public static IReadOnlyDictionary<string, Accessor> CreateStaticVertexAccessors<TVertex>(this ModelRoot root, IReadOnlyList<TVertex> vertices)
-            where TVertex : struct
-        {
-            // get vertex attributes from TVertex type using reflection
-            var attributes = VertexUtils.GetVertexAttributes(typeof(TVertex), vertices.Count);
-
-            // create vertex buffer
-            int byteStride = attributes[0].ByteStride;
-            var vbytes = new Byte[byteStride * vertices.Count];
-            var vbuffer = root.UseBufferView(new ArraySegment<byte>(vbytes), byteStride, BufferMode.ARRAY_BUFFER);
-
-            return _CreateAccessors(root, vbuffer, attributes, 0, vertices);
-        }
-
-        internal static IReadOnlyDictionary<string, Accessor> _CreateAccessors<TVertex>(ModelRoot root, BufferView vbuffer, Geometry.MemoryAccessInfo[] attributes, int vertexStart, IReadOnlyList<TVertex> vertices)
+        public static Accessor CreateVertexAccessor(this ModelRoot root, Geometry.MemoryAccessor memAccessor)
         {
-            attributes = Geometry.MemoryAccessInfo.Slice(attributes, vertexStart, vertices.Count);
+            var accessor = root.CreateAccessor(memAccessor.Attribute.Name);
 
-            // create vertex accessors
-            var vertexAccessors = new Dictionary<String, Accessor>();
-
-            foreach (var attribute in attributes)
-            {
-                var field = VertexUtils.GetVertexField(typeof(TVertex), attribute.Name);
-
-                vertexAccessors[attribute.Name] = _CreateAccessor(root, vbuffer, attribute, vertices, field);
-            }
-
-            return vertexAccessors;
-        }
-
-        public static IReadOnlyDictionary<string, Accessor> CreateSkinedVertexAccessors<TVertex, TSkin>(this ModelRoot root, IReadOnlyList<(TVertex, TSkin)> vertices)
-        {
-            // get vertex attributes from TVertex and TSkin types using reflection
-            var attributes = VertexUtils.GetVertexAttributes(typeof(TVertex), typeof(TSkin), vertices.Count);
+            accessor.SetVertexData(memAccessor);
 
-            // create vertex buffer
-            int byteStride = attributes[0].ByteStride;
-            var vbytes = new Byte[byteStride * vertices.Count];
-            var vbuffer = root.UseBufferView(new ArraySegment<byte>(vbytes), byteStride, BufferMode.ARRAY_BUFFER);
-
-            // create vertex accessors
-            var vertexAccessors = new Dictionary<String, Accessor>();
-
-            foreach (var attribute in attributes)
-            {
-                var vfield = VertexUtils.GetVertexField(typeof(TVertex), attribute.Name);
-                var sfield = VertexUtils.GetVertexField(typeof(TSkin), attribute.Name);
-
-                if (vfield != null)
-                {
-                    vertexAccessors[attribute.Name] = root._CreateAccessor(vbuffer, attribute, vertices, vfield, false);
-                }
-                else if (sfield != null)
-                {
-                    vertexAccessors[attribute.Name] = root._CreateAccessor(vbuffer, attribute, vertices, sfield, true);
-                }
-            }
-
-            return vertexAccessors;
-        }
-
-        private static Accessor _CreateAccessor<TVertex>(this ModelRoot root, BufferView vbuffer, Geometry.MemoryAccessInfo attribute, IReadOnlyList<TVertex> vertices, System.Reflection.FieldInfo field)
-        {
-            var accessor = root.CreateAccessor(attribute.Name);
-            if (field.FieldType == typeof(Vector2)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector2Column(vertices), attribute.Encoding, attribute.Normalized);
-            if (field.FieldType == typeof(Vector3)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector3Column(vertices), attribute.Encoding, attribute.Normalized);
-            if (field.FieldType == typeof(Vector4)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector4Column(vertices), attribute.Encoding, attribute.Normalized);
-            return accessor;
-        }
-
-        private static Accessor _CreateAccessor<TVertex, TSkin>(this ModelRoot root, BufferView vbuffer, Geometry.MemoryAccessInfo attribute, IReadOnlyList<(TVertex, TSkin)> vertices, System.Reflection.FieldInfo field, bool vertexOrSkin)
-        {
-            var accessor = root.CreateAccessor(attribute.Name);
-            if (field.FieldType == typeof(Vector2)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector2Column(vertices, vertexOrSkin), attribute.Encoding, attribute.Normalized);
-            if (field.FieldType == typeof(Vector3)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector3Column(vertices, vertexOrSkin), attribute.Encoding, attribute.Normalized);
-            if (field.FieldType == typeof(Vector4)) accessor.SetVertexData(vbuffer, attribute.ByteOffset, field.GetVector4Column(vertices, vertexOrSkin), attribute.Encoding, attribute.Normalized);
             return accessor;
         }
     }

+ 86 - 128
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -10,165 +10,76 @@ namespace SharpGLTF.Schema2
 
     public static partial class Toolkit
     {
-        public static Mesh CreateMesh<TVertex>(this ModelRoot root, Geometry.StaticMeshBuilder<TVertex, Material> meshBuilder)
-            where TVertex : struct
+        #region meshes
+
+        public static Mesh CreateMesh<TVertex, TSkin>(this ModelRoot root, Geometry.SkinnedMeshBuilder<Material, TVertex, TSkin> meshBuilder)
+            where TVertex : struct, Geometry.VertexTypes.IVertex
+            where TSkin : struct, Geometry.VertexTypes.IVertexJoints
         {
-            return root.CreateMesh<TVertex, Material>(k => k, meshBuilder);
+            return root.CreateMeshes(m => m, meshBuilder).First();
         }
 
-        public static Mesh CreateMesh<TVertex, TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, Geometry.StaticMeshBuilder<TVertex, TMaterial> meshBuilder)
-            where TVertex : struct
+        public static Mesh CreateMesh<TMaterial, TVertex, TSkin>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, Geometry.SkinnedMeshBuilder<TMaterial, TVertex, TSkin> meshBuilder)
+            where TVertex : struct, Geometry.VertexTypes.IVertex
+            where TSkin : struct, Geometry.VertexTypes.IVertexJoints
         {
-            return root.CreateMeshes(materialEvaluator, meshBuilder)[0];
+            return root.CreateMeshes(materialEvaluator, meshBuilder).First();
         }
 
-        public static Mesh[] CreateMeshes<TVertex, TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, params Geometry.StaticMeshBuilder<TVertex, TMaterial>[] meshBuilders)
-            where TVertex : struct
+        public static Mesh[] CreateMeshes<TMaterial, TVertex, TSkin>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, params Geometry.SkinnedMeshBuilder<TMaterial, TVertex, TSkin>[] meshBuilders)
+            where TVertex : struct, Geometry.VertexTypes.IVertex
+            where TSkin : struct, Geometry.VertexTypes.IVertexJoints
         {
-            var vertexBlocks = Geometry.VertexTypes.VertexUtils.CreateVertexMemoryAccessors
-                (
-                meshBuilders
+            // create a new schema material for every unique material in the mesh builders.
+            var mapMaterials = meshBuilders
                 .SelectMany(item => item.Primitives)
-                .Select(item => item.Vertices)
-                ).ToList();
+                .Select(item => item.Material)
+                .Distinct()
+                .ToDictionary(k => k, k => materialEvaluator(k));
+
+            // creates meshes and primitives using MemoryAccessors using a single, shared vertex and index buffer
+            var srcMeshes = Geometry.SkinnedMeshBuilder<TMaterial, TVertex, TSkin>
+                .MergeBuffers(meshBuilders)
+                .ToList();
 
             var dstMeshes = meshBuilders
                 .Select(item => root.CreateMesh(item.Name))
                 .ToArray();
 
-            var pidx = 0;
-
             for (int i = 0; i < dstMeshes.Length; ++i)
             {
-                var srcMesh = meshBuilders[i];
                 var dstMesh = dstMeshes[i];
+                var srcMesh = srcMeshes[i];
 
-                foreach (var p in srcMesh.Primitives)
+                foreach (var srcPrim in srcMesh)
                 {
-                    var vblock = vertexBlocks[pidx];
-
-                    var prim = dstMesh.CreatePrimitive();
-
-                    foreach (var a in vblock)
-                    {
-                        var accessor = root.CreateAccessor(a.Attribute.Name);
-                        accessor.SetVertexData(a);
-                        prim.SetVertexAccessor(a.Attribute.Name, accessor);
-                    }
-
-                    ++pidx;
+                    dstMesh.CreatePrimitive()
+                        .WithMaterial( mapMaterials[srcPrim.Item1] )
+                        .WithVertexAccessors(srcPrim.Item2)
+                        .WithIndicesAccessor(PrimitiveType.TRIANGLES, srcPrim.Item3);
                 }
             }
 
             return dstMeshes;
         }
 
-        public static Mesh CreateMesh<TVertex, TSkin, TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, Geometry.SkinnedMeshBuilder<TVertex, TSkin, TMaterial> meshBuilder)
-            where TVertex : struct
-            where TSkin : struct
-        {
-            return root.CreateMeshes(materialEvaluator, meshBuilder)[0];
-        }
-
-        public static Mesh[] CreateMeshes<TVertex, TSkin, TMaterial>(this ModelRoot root, Func<TMaterial, Material> materialEvaluator, params Geometry.SkinnedMeshBuilder<TVertex, TSkin, TMaterial>[] meshBuilders)
-            where TVertex : struct
-            where TSkin : struct
-        {
-            var dstMeshes = meshBuilders
-                .Select(item => root.CreateMesh(item.Name))
-                .ToArray();
-
-            return dstMeshes;
-        }
-
-        /*
-
-        public static Mesh CreateMesh<TVertex, TMaterial>(this ModelRoot root, Geometry.StaticMeshBuilder<TVertex, TMaterial> meshBuilder, Func<TMaterial, Material> materialEvaluator)
-            where TVertex : struct
-        {
-            var dstMesh = root.CreateMesh(meshBuilder.Name);
-
-            // create vertex accessors
-            var vertexAccessors = root.CreateStaticVertexAccessors(meshBuilder.Vertices);
-
-            foreach (var mkey in meshBuilder.Materials)
-            {
-                var indices = meshBuilder.GetIndices(mkey);
-
-                // create index buffer
-                var ibytes = new Byte[4 * indices.Count];
-                var ibuffer = root.UseBufferView(new ArraySegment<byte>(ibytes), 0, BufferMode.ELEMENT_ARRAY_BUFFER);
-
-                var indicesAccessor = root
-                    .CreateAccessor("Indices");
-
-                indicesAccessor.SetIndexData(ibuffer, 0, indices);
-
-                // create mesh primitive
-                var prim = dstMesh.CreatePrimitive();
-                foreach (var va in vertexAccessors) prim.SetVertexAccessor(va.Key, va.Value);
-                prim.SetIndexAccessor(indicesAccessor);
-                prim.DrawPrimitiveType = PrimitiveType.TRIANGLES;
-
-                prim.Material = materialEvaluator(mkey);
-            }
-
-            return dstMesh;
-        }
-
-        public static Mesh CreateMesh<TVertex, TSkin, TMaterial>(this ModelRoot root, Geometry.SkinnedMeshBuilder<TVertex, TSkin, TMaterial> meshBuilder, Func<TMaterial, Material> materialEvaluator)
-            where TVertex : struct
-            where TSkin : struct
-        {
-            var dstMesh = root.CreateMesh(meshBuilder.Name);
-
-            // create vertex accessors
-            var vertexAccessors = root.CreateSkinedVertexAccessors(meshBuilder.Vertices);
-
-            foreach (var mkey in meshBuilder.Materials)
-            {
-                var indices = meshBuilder.GetIndices(mkey);
-
-                // create index buffer
-                var ibytes = new Byte[4 * indices.Count];
-                var ibuffer = root.UseBufferView(new ArraySegment<byte>(ibytes), 0, BufferMode.ELEMENT_ARRAY_BUFFER);
-
-                var indicesAccessor = root
-                    .CreateAccessor("Indices");
-
-                indicesAccessor.SetIndexData(ibuffer, 0, indices);
-
-                // create mesh primitive
-                var prim = dstMesh.CreatePrimitive();
-                foreach (var va in vertexAccessors) prim.SetVertexAccessor(va.Key, va.Value);
-                prim.SetIndexAccessor(indicesAccessor);
-                prim.DrawPrimitiveType = PrimitiveType.TRIANGLES;
-
-                prim.Material = materialEvaluator(mkey);
-            }
-
-            return dstMesh;
-        }
-        */
+        #endregion
 
         #region accessors
 
-        public static MeshPrimitive WithVertexAccessors<TVertex>(this MeshPrimitive primitive, IReadOnlyList<Geometry.MemoryAccessor> memAccessors)
-        where TVertex : struct
+        public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, string attribute, IReadOnlyList<Single> values)
         {
-            var accessors = primitive.LogicalParent.LogicalParent.CreateStaticVertexAccessors(memAccessors);
-
-            foreach (var va in accessors) primitive.SetVertexAccessor(va.Key, va.Value);
+            var root = primitive.LogicalParent.LogicalParent;
 
-            return primitive;
-        }
+            // create a vertex buffer and fill it
+            var view = root.UseBufferView(new Byte[4 * values.Count], 0, null, 0, BufferMode.ARRAY_BUFFER);
+            var array = new ScalarArray(view.Content);
+            array.FillFrom(0, values);
 
-        public static MeshPrimitive WithVertexAccessors<TVertex>(this MeshPrimitive primitive, IReadOnlyList<TVertex> vertices)
-            where TVertex : struct
-        {
-            var accessors = primitive.LogicalParent.LogicalParent.CreateStaticVertexAccessors(vertices);
+            var accessor = root.CreateAccessor();
+            primitive.SetVertexAccessor(attribute, accessor);
 
-            foreach (var va in accessors) primitive.SetVertexAccessor(va.Key, va.Value);
+            accessor.SetVertexData(view, 0, values.Count, DimensionType.SCALAR, EncodingType.FLOAT, false);
 
             return primitive;
         }
@@ -255,6 +166,53 @@ namespace SharpGLTF.Schema2
             return primitive;
         }
 
+        public static MeshPrimitive WithVertexAccessors<TVertex>(this MeshPrimitive primitive, IReadOnlyList<TVertex> vertices)
+            where TVertex : struct, Geometry.VertexTypes.IVertex
+        {
+            var xvertices = vertices.Select(item => (item, default(Geometry.VertexTypes.VertexJoints0))).ToList();
+
+            return primitive.WithVertexAccessors(xvertices);
+        }
+
+        public static MeshPrimitive WithVertexAccessors<TVertex, TSkin>(this MeshPrimitive primitive, IReadOnlyList<(TVertex, TSkin)> vertices)
+            where TVertex : struct, Geometry.VertexTypes.IVertex
+            where TSkin : struct, Geometry.VertexTypes.IVertexJoints
+        {
+            var memAccessors = Geometry.VertexTypes.VertexUtils.CreateVertexMemoryAccessors(new[] { vertices }).First();
+
+            return primitive.WithVertexAccessors(memAccessors);
+        }
+
+        public static MeshPrimitive WithVertexAccessors(this MeshPrimitive primitive, IEnumerable<Geometry.MemoryAccessor> memAccessors)
+        {
+            foreach (var va in memAccessors) primitive.WithVertexAccessor(va);
+
+            return primitive;
+        }
+
+        public static MeshPrimitive WithVertexAccessor(this MeshPrimitive primitive, Geometry.MemoryAccessor memAccessor)
+        {
+            var root = primitive.LogicalParent.LogicalParent;
+
+            primitive.SetVertexAccessor(memAccessor.Attribute.Name, root.CreateVertexAccessor(memAccessor));
+
+            return primitive;
+        }
+
+        public static MeshPrimitive WithIndicesAccessor(this MeshPrimitive primitive, PrimitiveType primitiveType, Geometry.MemoryAccessor memAccessor)
+        {
+            var root = primitive.LogicalParent.LogicalParent;
+
+            var accessor = root.CreateAccessor();
+
+            accessor.SetIndexData(memAccessor);
+
+            primitive.DrawPrimitiveType = primitiveType;
+            primitive.SetIndexAccessor(accessor);
+
+            return primitive;
+        }
+
         #endregion
 
         #region material

+ 79 - 58
src/SharpGLTF/Geometry/MemoryAccessor.cs

@@ -59,6 +59,17 @@ namespace SharpGLTF.Geometry
             this.Normalized = normalized;
         }
 
+        public MemoryAccessInfo Slice(int start, int count)
+        {
+            var stride = Math.Max(this.ByteStride, this.Dimensions.DimCount() * this.Encoding.ByteLength());
+
+            var clone = this;
+            clone.ByteOffset += start * stride;
+            clone.ItemsCount = Math.Min(clone.ItemsCount, count);
+
+            return clone;
+        }
+
         #endregion
 
         #region data
@@ -146,10 +157,7 @@ namespace SharpGLTF.Geometry
 
             for (int i = 0; i < dst.Length; ++i)
             {
-                var a = attributes[i];
-                a.ByteOffset += a.ByteStride * start;
-                a.ItemsCount = Math.Min(a.ItemsCount, count);
-                dst[i] = a;
+                dst[i] = attributes[i].Slice(start, count);
             }
 
             return dst;
@@ -161,136 +169,149 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// Wraps a <see cref="ArraySegment{Byte}"/> decoding it and exposing its content as arrays of different types.
     /// </summary>
-    public struct MemoryAccessor
+    public sealed class MemoryAccessor
     {
         #region constructor
 
         public MemoryAccessor(ArraySegment<Byte> data, MemoryAccessInfo info)
         {
-            this.Attribute = info;
-            this.Data = data;
+            this._Attribute = info;
+            this._Data = data;
         }
 
         public MemoryAccessor(MemoryAccessInfo info)
         {
-            this.Attribute = info;
-            this.Data = default;
+            this._Attribute = info;
+            this._Data = default;
         }
 
         #endregion
 
         #region data
 
-        public MemoryAccessInfo Attribute;
-        public ArraySegment<Byte> Data;
+        private MemoryAccessInfo _Attribute;
+        private ArraySegment<Byte> _Data;
+
+        #endregion
+
+        #region properties
+
+        public MemoryAccessInfo Attribute => _Attribute;
+
+        public ArraySegment<Byte> Data => _Data;
 
         #endregion
 
         #region API
 
+        internal void SetName(string name)
+        {
+            _Attribute.Name = name;
+        }
+
         public void SetIndexDataSource(ArraySegment<Byte> data, int byteOffset, int itemsCount)
         {
-            Guard.IsTrue(Attribute.IsValidIndexer, nameof(Attribute));
-            Data = data;
-            Attribute.ByteOffset = byteOffset;
-            Attribute.ItemsCount = itemsCount;
-            Attribute.ByteStride = 0;
+            Guard.IsTrue(_Attribute.IsValidIndexer, nameof(_Attribute));
+            _Data = data;
+            _Attribute.ByteOffset = byteOffset;
+            _Attribute.ItemsCount = itemsCount;
+            _Attribute.ByteStride = 0;
         }
 
         public void SetVertexDataSource(ArraySegment<Byte> data, int byteOffset, int itemsCount, int byteStride)
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Data = data;
-            Attribute.ByteOffset = byteOffset;
-            Attribute.ItemsCount = itemsCount;
-            Attribute.ByteStride = byteStride;
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            _Data = data;
+            _Attribute.ByteOffset = byteOffset;
+            _Attribute.ItemsCount = itemsCount;
+            _Attribute.ByteStride = byteStride;
         }
 
         public IntegerArray AsIntegerArray()
         {
-            Guard.IsTrue(Attribute.IsValidIndexer, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.SCALAR, nameof(Attribute));
-            return new IntegerArray(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.Encoding.ToIndex());
+            Guard.IsTrue(_Attribute.IsValidIndexer, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.SCALAR, nameof(_Attribute));
+            return new IntegerArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.Encoding.ToIndex());
         }
 
         public ScalarArray AsScalarArray()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.SCALAR, nameof(Attribute));
-            return new ScalarArray(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.SCALAR, nameof(_Attribute));
+            return new ScalarArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public Vector2Array AsVector2Array()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.VEC2, nameof(Attribute));
-            return new Vector2Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.VEC2, nameof(_Attribute));
+            return new Vector2Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public Vector3Array AsVector3Array()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.VEC3, nameof(Attribute));
-            return new Vector3Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.VEC3, nameof(_Attribute));
+            return new Vector3Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public Vector4Array AsVector4Array()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.VEC4, nameof(Attribute));
-            return new Vector4Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.VEC4, nameof(_Attribute));
+            return new Vector4Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public QuaternionArray AsQuaternionArray()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.VEC4, nameof(Attribute));
-            return new QuaternionArray(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.VEC4, nameof(_Attribute));
+            return new QuaternionArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public Matrix4x4Array AsMatrix4x4Array()
         {
-            Guard.IsTrue(Attribute.IsValidVertexAttribute, nameof(Attribute));
-            Guard.IsTrue(Attribute.Dimensions == DIMENSIONS.MAT4, nameof(Attribute));
-            return new Matrix4x4Array(Data, Attribute.ByteOffset, Attribute.ItemsCount, Attribute.ByteStride, Attribute.Encoding, Attribute.Normalized);
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.MAT4, nameof(_Attribute));
+            return new Matrix4x4Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
         public static IEncodedArray<Single> CreateScalarSparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(bottom.Attribute.Dimensions == topValues.Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom.Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues.Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom.Attribute.ItemsCount), nameof(topKeys));
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
             return new SparseArray<Single>(bottom.AsScalarArray(), topValues.AsScalarArray(), topKeys);
         }
 
         public static IEncodedArray<Vector2> CreateVector2SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(bottom.Attribute.Dimensions == topValues.Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom.Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues.Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom.Attribute.ItemsCount), nameof(topKeys));
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
             return new SparseArray<Vector2>(bottom.AsVector2Array(), topValues.AsVector2Array(), topKeys);
         }
 
         public static IEncodedArray<Vector3> CreateVector3SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(bottom.Attribute.Dimensions == topValues.Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom.Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues.Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom.Attribute.ItemsCount), nameof(topKeys));
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
             return new SparseArray<Vector3>(bottom.AsVector3Array(), topValues.AsVector3Array(), topKeys);
         }
 
         public static IEncodedArray<Vector4> CreateVector4SparseArray(MemoryAccessor bottom, IntegerArray topKeys, MemoryAccessor topValues)
         {
-            Guard.IsTrue(bottom.Attribute.Dimensions == topValues.Attribute.Dimensions, nameof(topValues));
-            Guard.IsTrue(topKeys.Count <= bottom.Attribute.ItemsCount, nameof(topKeys));
-            Guard.IsTrue(topKeys.Count == topValues.Attribute.ItemsCount, nameof(topValues));
-            Guard.IsTrue(topKeys.All(item => item < (uint)bottom.Attribute.ItemsCount), nameof(topKeys));
+            Guard.IsTrue(bottom._Attribute.Dimensions == topValues._Attribute.Dimensions, nameof(topValues));
+            Guard.IsTrue(topKeys.Count <= bottom._Attribute.ItemsCount, nameof(topKeys));
+            Guard.IsTrue(topKeys.Count == topValues._Attribute.ItemsCount, nameof(topValues));
+            Guard.IsTrue(topKeys.All(item => item < (uint)bottom._Attribute.ItemsCount), nameof(topKeys));
 
             return new SparseArray<Vector4>(bottom.AsVector4Array(), topValues.AsVector4Array(), topKeys);
         }

+ 44 - 32
src/SharpGLTF/Geometry/MeshPrimitive.cs

@@ -28,7 +28,7 @@ namespace SharpGLTF.Geometry
             : base(accessor)
         {
             _MemoryAccessor = accessor._GetMemoryAccessor();
-            _MemoryAccessor.Attribute.Name = attributeName;
+            _MemoryAccessor.SetName(attributeName);
             _Sparse = accessor._GetSparseMemoryAccessor();
         }
 
@@ -65,8 +65,8 @@ namespace SharpGLTF.Geometry
 
         #region data
 
-        private Geometry.MemoryAccessor _MemoryAccessor;
-        private KeyValuePair<Memory.IntegerArray, Geometry.MemoryAccessor>? _Sparse;
+        private MemoryAccessor _MemoryAccessor;
+        private KeyValuePair<Memory.IntegerArray, MemoryAccessor>? _Sparse;
 
         #endregion
 
@@ -139,7 +139,7 @@ namespace SharpGLTF.Geometry
             : base(accessor)
         {
             _MemoryAccessor = accessor._GetMemoryAccessor();
-            _MemoryAccessor.Attribute.Name = "INDEX";
+            _MemoryAccessor.SetName("INDEX");
         }
 
         public IndicesAccessor(MemoryAccessInfo info)
@@ -191,13 +191,13 @@ namespace SharpGLTF.Geometry
         #endregion
     }
 
-    public class MeshPrimitive
+    public class MeshPrimitive<TMaterial>
     {
         #region lifecycle
 
-        internal MeshPrimitive(Mesh owner) { _Owner = owner; }
+        internal MeshPrimitive(Mesh<TMaterial> owner) { _Owner = owner; }
 
-        internal MeshPrimitive(Mesh owner, Schema2.MeshPrimitive primitive)
+        internal MeshPrimitive(Mesh<TMaterial> owner, Schema2.MeshPrimitive primitive)
             : this(owner)
         {
             _Vertices = primitive.VertexAccessors
@@ -215,14 +215,28 @@ namespace SharpGLTF.Geometry
 
             _Indices = primitive.IndexAccessor == null ? null : new IndicesAccessor(primitive.IndexAccessor);
             _PrimitiveDrawType = primitive.DrawPrimitiveType;
-            _MaterialIndex = primitive.Material?.LogicalIndex;
+
+            if (primitive.Material != null)
+            {
+                if (typeof(TMaterial) == typeof(Schema2.Material))
+                {
+                    _Material = (TMaterial)(Object)primitive.Material;
+                }
+
+                if (typeof(TMaterial) == typeof(int?))
+                {
+                    var materialIndex = primitive.Material?.LogicalIndex;
+
+                    _Material = (TMaterial)(Object)materialIndex;
+                }
+            }
         }
 
         #endregion
 
         #region data
 
-        private readonly Mesh _Owner;
+        private readonly Mesh<TMaterial> _Owner;
 
         private VertexAccessor[] _Vertices;
         private readonly List<VertexAccessor[]> _MorphAccessors = new List<VertexAccessor[]>();
@@ -231,7 +245,7 @@ namespace SharpGLTF.Geometry
 
         private Schema2.PrimitiveType _PrimitiveDrawType;
 
-        private int? _MaterialIndex;
+        private TMaterial _Material;
 
         #endregion
 
@@ -241,10 +255,10 @@ namespace SharpGLTF.Geometry
 
         public IndicesAccessor Indices => _Indices;
 
-        public int? MaterialLogicalIndex
+        public TMaterial Material
         {
-            get => _MaterialIndex;
-            set => _MaterialIndex = value;
+            get => _Material;
+            set => _Material = value;
         }
 
         #endregion
@@ -275,54 +289,52 @@ namespace SharpGLTF.Geometry
 
             dstPrim.DrawPrimitiveType = this._PrimitiveDrawType;
 
-            var material = _MaterialIndex.HasValue ? dstPrim.LogicalParent.LogicalParent.LogicalMaterials[_MaterialIndex.Value] : null;
+            if (typeof(TMaterial) == typeof(Schema2.Material))
+            {
+                var material = (Schema2.Material)(Object)_Material;
+                dstPrim.Material = material;
+            }
 
-            dstPrim.Material = material;
+            if (typeof(TMaterial) == typeof(int?))
+            {
+                var materialIndex = (int?)(Object)_Material;
+                var material = materialIndex.HasValue ? dstPrim.LogicalParent.LogicalParent.LogicalMaterials[materialIndex.Value] : null;
+                dstPrim.Material = material;
+            }
         }
 
         #endregion
     }
 
     [System.Diagnostics.DebuggerDisplay("Mesh {Name}")]
-    public class Mesh : NamedObject
+    public class Mesh<TMaterial> : NamedObject
     {
         #region lifecycle
 
         public Mesh() { }
 
-        public static Mesh[] Create(IReadOnlyList<Schema2.Mesh> src)
-        {
-            var dst = new Mesh[src.Count];
-
-            for (int i = 0; i < dst.Length; ++i)
-            {
-                dst[i] = new Mesh(src[i]);
-            }
-
-            return dst;
-        }
-
+        /*
         public Mesh(Schema2.Mesh mesh)
             : base(mesh)
         {
             _Primitives.AddRange(mesh.Primitives, item => new MeshPrimitive(this, item));
             _MorpthWeights.AddRange(mesh.MorphWeights);
-        }
+        }*/
 
         #endregion
 
         #region data
 
-        private readonly List<MeshPrimitive> _Primitives = new List<MeshPrimitive>();
+        private readonly List<MeshPrimitive<TMaterial>> _Primitives = new List<MeshPrimitive<TMaterial>>();
         private readonly List<Single> _MorpthWeights = new List<float>();
 
         #endregion
 
         #region API
 
-        public MeshPrimitive CreatePrimitive()
+        public MeshPrimitive<TMaterial> CreatePrimitive()
         {
-            var p = new MeshPrimitive(this);
+            var p = new MeshPrimitive<TMaterial>(this);
             _Primitives.Add(p);
 
             return p;

+ 1 - 1
src/SharpGLTF/Schema2/gltf.Asset.cs

@@ -28,7 +28,7 @@ namespace SharpGLTF.Schema2
 
         private static readonly Version ZEROVERSION = new Version(0, 0);
         private static readonly Version MINVERSION = new Version(2, 0);
-        private static readonly Version MAXVERSION = new Version(2, 1);
+        private static readonly Version MAXVERSION = new Version(2, 0);
 
         public string Copyright { get => _copyright; set => _copyright = value.AsEmptyNullable(); }
         public string Generator { get => _generator; set => _generator = value.AsEmptyNullable(); }

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

@@ -34,18 +34,18 @@ namespace SharpGLTF.Geometry
             var indices = new UInt32[] { 0, 1, 2 };
 
             // create a new mesh:
-            var srcMesh = new Mesh();
+            var srcMesh = new Mesh<int?>();
 
             // setup a mesh primitive
             var srcPrimitive = srcMesh.CreatePrimitive();
-            srcPrimitive.AllocateVertices(positions.Length, "POSITION", "NORMAL");          // (#1)
+            srcPrimitive.AllocateVertices(positions.Length, "POSITION", "NORMAL");  // (#1)
             srcPrimitive.AllocateIndices(indices.Length, PrimitiveType.TRIANGLES);  // (#2)
 
             // assign vertices and indices
             srcPrimitive.Vertices[0].SetValues(0, positions);
             srcPrimitive.Vertices[1].SetValues(0, normals);
             srcPrimitive.Indices.SetValues(0, indices);
-            srcPrimitive.MaterialLogicalIndex = 0; // using material with index 0, which will be created later
+            srcPrimitive.Material = 0; // using material with index 0, which will be created later
 
             // check the values we've set match the input data
             CollectionAssert.AreEqual(positions, srcPrimitive.Vertices[0].AsVector3Array());

+ 74 - 51
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -12,7 +12,7 @@ namespace SharpGLTF.Schema2.Authoring
 
     using STATICVERTEX = Geometry.VertexTypes.VertexPositionNormal;
     using VPOS = Geometry.VertexTypes.VertexPosition;
-    using SKIN4 = Geometry.VertexTypes.SkinJoints4;
+    using SKIN4 = Geometry.VertexTypes.VertexJoints4;
 
     [TestFixture]
     public class CreateModelTests
@@ -227,7 +227,7 @@ namespace SharpGLTF.Schema2.Authoring
             var mesh = model.CreateMesh("mesh1");
 
             mesh.CreatePrimitive()
-                .WithMaterial(model.CreateMaterial("Default").WithDefault(Vector4.One))
+                .WithMaterial(model.CreateMaterial("Default").WithDefault(Vector4.One).WithDoubleSide(true))
                 .WithVertexAccessors(vertices)
                 .WithIndicesAccessor(PrimitiveType.TRIANGLES, new int[] { 0, 1, 2, 0, 2, 3 });
 
@@ -244,25 +244,64 @@ namespace SharpGLTF.Schema2.Authoring
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
 
-            var meshBuilder = new StaticMeshBuilder<STATICVERTEX, Vector4>("mesh1");
+            var meshBuilder = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh1");
 
             var v1 = new STATICVERTEX(-10, 10, 0, -10, 10, 15);
             var v2 = new STATICVERTEX( 10, 10, 0, 10, 10, 15);
             var v3 = new STATICVERTEX( 10,-10, 0, 10, -10, 15);
             var v4 = new STATICVERTEX(-10,-10, 0, -10, -10, 15);            
-            meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
+            meshBuilder.AddPolygon(Vector4.One, v1, v2, v3, v4);
 
             var model = ModelRoot.CreateModel();
 
             // setup a lambda function that creates a material for a given color
-            Material createMaterialForColor(Vector4 color)
-            {
-                return model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
-            };
+            Material createMaterialForColor(Vector4 color) => model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
+
+            model.UseScene("Default")
+                .CreateNode("RootNode")
+                .WithMesh( model.CreateMesh(createMaterialForColor, meshBuilder));
+
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");
+        }
+
+        [Test(Description = "Creates a scene with 4 meshes, where the meshes have been initialized so they can share the same vertex and index buffers")]
+        public void CreateSharedBuffersScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            // create several meshes
+            var meshBuilder1 = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh1");
+            var meshBuilder2 = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh2");
+            var meshBuilder3 = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh3");
+            var meshBuilder4 = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh4");
+
+            meshBuilder1.AddCube(new Vector4(1, 1, 0, 1), Matrix4x4.Identity);
+            meshBuilder2.AddCube(new Vector4(1, 0, 1, 1), Matrix4x4.Identity);
+            meshBuilder3.AddSphere(new Vector4(0, 1, 1, 1), 0.5f, Matrix4x4.Identity);
+            meshBuilder4.AddSphere(new Vector4(1, 1, 0, 1), 0.5f, Matrix4x4.Identity);
+
+            // create the gltf model
+            var model = ModelRoot.CreateModel();
+
+            // setup a lambda function that creates a material for a given color
+            Material createMaterialForColor(Vector4 color) => model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
+
+            var meshes = model.CreateMeshes(createMaterialForColor, meshBuilder1, meshBuilder2, meshBuilder3, meshBuilder4);
 
-            model
-                .UseScene("Default")
-                .CreateNode("RootNode").WithMesh( model.CreateMesh(createMaterialForColor, meshBuilder));
+            model.UseScene("Default").CreateNode("Cube1").WithMesh(meshes[0]).WithLocalTranslation(new Vector3(-5, 0, 0));
+            model.UseScene("Default").CreateNode("Cube2").WithMesh(meshes[1]).WithLocalTranslation(new Vector3(0, 5, 0));
+            model.UseScene("Default").CreateNode("Sphere1").WithMesh(meshes[2]).WithLocalTranslation(new Vector3(+5, 0, 0));
+            model.UseScene("Default").CreateNode("Sphere2").WithMesh(meshes[3]).WithLocalTranslation(new Vector3(0, -5, 0));
+
+            model.MergeBuffers();
+
+            Assert.AreEqual(1, model.LogicalBuffers.Count);
+            Assert.AreEqual(2, model.LogicalBufferViews.Count);
+            Assert.AreEqual(BufferMode.ARRAY_BUFFER, model.LogicalBufferViews[0].DeviceBufferTarget);
+            Assert.AreEqual(BufferMode.ELEMENT_ARRAY_BUFFER, model.LogicalBufferViews[1].DeviceBufferTarget);
+            Assert.AreEqual(3, model.LogicalMaterials.Count);
 
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.gltf");
@@ -284,45 +323,27 @@ namespace SharpGLTF.Schema2.Authoring
             };
 
             // create a mesh
-            var meshBuilder = new StaticMeshBuilder<STATICVERTEX, Vector4>("mesh1");
-
-            var v1 = new STATICVERTEX(-10, 10, 0, -10, 10, 15);
-            var v2 = new STATICVERTEX(10, 10, 0, 10, 10, 15);
-            var v3 = new STATICVERTEX(10, -10, 0, 10, -10, 15);
-            var v4 = new STATICVERTEX(-10, -10, 0, -10, -10, 15);
-            meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
+            var meshBuilder = new StaticMeshBuilder<Vector4, STATICVERTEX>("mesh1");
+            meshBuilder.AddCube(Vector4.One, Matrix4x4.Identity);            
 
             // create the gltf model
             var model = ModelRoot.CreateModel();
 
-            // setup a lambda function that creates a material for a given color
-            Material createMaterialForColor(Vector4 color)
-            {
-                return model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
-            };            
-
             model.UseScene("Default")
                 .CreateNode("RootNode")
                 .WithTranslationAnimation("track1", keyframes)
-                .WithMesh(model.CreateMesh(createMaterialForColor, meshBuilder));
+                .WithMesh(model.CreateMesh(c => model.CreateMaterial().WithDefault(c), meshBuilder));
 
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.gltf");
-        }
-
-        
+        }        
 
         [Test(Description = "Creates a skinned animated scene using a mesh builder helper class")]
         public void CreateSkinnedAnimatedMeshBuilderScene()
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLink();
-
-            // create base model
-            var model = ModelRoot.CreateModel();
-            var scene = model.UseScene("Default");
-            var snode = scene.CreateNode("RootNode");
-
+            
             // create animation sequence with 4 frames
             var keyframes = new Dictionary<Single, Quaternion>
             {
@@ -331,20 +352,9 @@ namespace SharpGLTF.Schema2.Authoring
                 [3] = Quaternion.CreateFromYawPitchRoll(0, 0, 1),
                 [4] = Quaternion.Identity,
             };
-
-            // create the three joints that will affect the mesh
-            var skelet = scene.CreateNode("Skeleton");
-            var joint1 = skelet.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 0, 0));
-            var joint2 = joint1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0)).WithRotationAnimation("Base Track", keyframes);
-            var joint3 = joint2.CreateNode("Joint 3").WithLocalTranslation(new Vector3(0, 40, 0));
-
-            // setup skin
-            snode.Skin = model.CreateSkin();
-            snode.Skin.Skeleton = skelet;
-            snode.Skin.BindJoints(joint1, joint2, joint3);
-
+            
             // create the mesh
-            var meshBuilder = new SkinnedMeshBuilder<VPOS, SKIN4, Vector4>("mesh1");
+            var meshBuilder = new SkinnedMeshBuilder<Vector4, VPOS, SKIN4>("mesh1");
 
             var v1 = (new VPOS(-10, 0, +10), new SKIN4(0));
             var v2 = (new VPOS(+10, 0, +10), new SKIN4(0));
@@ -371,11 +381,24 @@ namespace SharpGLTF.Schema2.Authoring
             meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v7, v8, v12, v11);
             meshBuilder.AddPolygon(new Vector4(1, 1, 0, 1), v8, v5, v9, v12);
 
+            // create base model
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("Default");            
+
+            // create the three joints that will affect the mesh
+            var skelet = scene.CreateNode("Skeleton");
+            var joint1 = skelet.CreateNode("Joint 1").WithLocalTranslation(new Vector3(0, 0, 0));
+            var joint2 = joint1.CreateNode("Joint 2").WithLocalTranslation(new Vector3(0, 40, 0)).WithRotationAnimation("Base Track", keyframes);
+            var joint3 = joint2.CreateNode("Joint 3").WithLocalTranslation(new Vector3(0, 40, 0));
+
+            // setup skin
+            var snode = scene.CreateNode("Skeleton Node");
+            snode.Skin = model.CreateSkin();
+            snode.Skin.Skeleton = skelet;
+            snode.Skin.BindJoints(joint1, joint2, joint3);
+
             // setup a lambda function that creates a material for a given color
-            Material createMaterialForColor(Vector4 color)
-            {
-                return model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
-            };
+            Material createMaterialForColor(Vector4 color) => model.CreateMaterial().WithDefault(color).WithDoubleSide(true);
 
             snode.WithMesh(model.CreateMesh(createMaterialForColor, meshBuilder));
 

+ 117 - 0
tests/SharpGLTF.Tests/Schema2/Authoring/SolidMeshUtils.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Schema2.Authoring
+{
+    using Geometry;
+    
+    using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
+    
+
+    static class SolidMeshUtils
+    {
+        public static void AddCube<TMaterial>(this StaticMeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Matrix4x4 xform)
+        {
+            meshBuilder._AddCubeFace(material, Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ, xform);
+            meshBuilder._AddCubeFace(material, -Vector3.UnitX, Vector3.UnitZ, Vector3.UnitY, xform);
+
+            meshBuilder._AddCubeFace(material, Vector3.UnitY, Vector3.UnitZ, Vector3.UnitX, xform);
+            meshBuilder._AddCubeFace(material, -Vector3.UnitY, Vector3.UnitX, Vector3.UnitZ, xform);
+
+            meshBuilder._AddCubeFace(material, Vector3.UnitZ, Vector3.UnitX, Vector3.UnitY, xform);
+            meshBuilder._AddCubeFace(material, -Vector3.UnitZ, Vector3.UnitY, Vector3.UnitX, xform);
+        }
+
+        private static void _AddCubeFace<TMaterial>(this StaticMeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Vector3 origin, Vector3 axisX, Vector3 axisY, Matrix4x4 xform)
+        {
+            var p1 = Vector3.Transform(origin - axisX - axisY, xform);
+            var p2 = Vector3.Transform(origin + axisX - axisY, xform);
+            var p3 = Vector3.Transform(origin + axisX + axisY, xform);
+            var p4 = Vector3.Transform(origin - axisX + axisY, xform);
+            var n = Vector3.Normalize(Vector3.TransformNormal(origin, xform));
+
+            meshBuilder.AddPolygon
+                (material,
+                new VPOSNRM(p1, n),
+                new VPOSNRM(p2, n),
+                new VPOSNRM(p3, n),
+                new VPOSNRM(p4, n)
+                );
+        }
+
+        public static void AddSphere<TMaterial>(this StaticMeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Single radius, Matrix4x4 xform)
+        {
+            // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
+
+            var t = 1 + (float)(Math.Sqrt(5.0) / 2);
+
+            var v0 = new Vector3(-1, t, 0) * radius;
+            var v1 = new Vector3(1, t, 0) * radius;
+            var v2 = new Vector3(-1, -t, 0) * radius;
+            var v3 = new Vector3(1, -t, 0) * radius;
+
+            var v4 = new Vector3(0, -1, t) * radius;
+            var v5 = new Vector3(0, 1, t) * radius;
+            var v6 = new Vector3(0, -1, -t) * radius;
+            var v7 = new Vector3(0, 1, -t) * radius;
+
+            var v8 = new Vector3(t, 0, -1) * radius;
+            var v9 = new Vector3(t, 0, 1) * radius;
+            var v10 = new Vector3(-t, 0, -1) * radius;
+            var v11 = new Vector3(-t, 0, 1) * radius;
+
+            // 5 faces around point 0
+            meshBuilder._AddSphereTriangle(material, xform, v0, v11, v5);
+            meshBuilder._AddSphereTriangle(material, xform, v0, v5, v1);
+            meshBuilder._AddSphereTriangle(material, xform, v0, v1, v7);
+            meshBuilder._AddSphereTriangle(material, xform, v0, v7, v10);
+            meshBuilder._AddSphereTriangle(material, xform, v0, v10, v11);
+
+            // 5 adjacent faces
+            meshBuilder._AddSphereTriangle(material, xform, v1, v5, v9);
+            meshBuilder._AddSphereTriangle(material, xform, v5, v11, v4);
+            meshBuilder._AddSphereTriangle(material, xform, v11, v10, v2);
+            meshBuilder._AddSphereTriangle(material, xform, v10, v7, v6);
+            meshBuilder._AddSphereTriangle(material, xform, v7, v1, v8);
+
+            // 5 faces around point 3
+            meshBuilder._AddSphereTriangle(material, xform, v3, v9, v4);
+            meshBuilder._AddSphereTriangle(material, xform, v3, v4, v2);
+            meshBuilder._AddSphereTriangle(material, xform, v3, v2, v6);
+            meshBuilder._AddSphereTriangle(material, xform, v3, v6, v8);
+            meshBuilder._AddSphereTriangle(material, xform, v3, v8, v9);
+
+            // 5 adjacent faces
+            meshBuilder._AddSphereTriangle(material, xform, v4, v9, v5);
+            meshBuilder._AddSphereTriangle(material, xform, v2, v4, v11);
+            meshBuilder._AddSphereTriangle(material, xform, v6, v2, v10);
+            meshBuilder._AddSphereTriangle(material, xform, v8, v6, v7);
+            meshBuilder._AddSphereTriangle(material, xform, v9, v8, v1);
+        }
+
+        private static void _AddSphereTriangle<TMaterial>(this StaticMeshBuilder<TMaterial, VPOSNRM> meshBuilder, TMaterial material, Matrix4x4 xform, Vector3 a, Vector3 b, Vector3 c)
+        {
+            var ab = Vector3.Normalize(a + b) * a.Length();
+            var bc = Vector3.Normalize(b + c) * b.Length();
+            var ca = Vector3.Normalize(c + a) * c.Length();
+
+            var aa = new VPOSNRM(Vector3.Transform(a, xform), Vector3.Normalize(Vector3.TransformNormal(a, xform)));
+            var bb = new VPOSNRM(Vector3.Transform(b, xform), Vector3.Normalize(Vector3.TransformNormal(b, xform)));
+            var cc = new VPOSNRM(Vector3.Transform(c, xform), Vector3.Normalize(Vector3.TransformNormal(c, xform)));
+
+            // meshBuilder.AddTriangle(material, aa, bb, cc);
+
+            var aabb = new VPOSNRM(Vector3.Transform(ab, xform), Vector3.Normalize(Vector3.TransformNormal(ab, xform)));
+            var bbcc = new VPOSNRM(Vector3.Transform(bc, xform), Vector3.Normalize(Vector3.TransformNormal(bc, xform)));
+            var ccaa = new VPOSNRM(Vector3.Transform(ca, xform), Vector3.Normalize(Vector3.TransformNormal(ca, xform)));
+
+            meshBuilder.AddTriangle(material, aabb, bbcc, ccaa);
+
+            meshBuilder.AddTriangle(material, aa, aabb, ccaa);
+            meshBuilder.AddTriangle(material, bb, bbcc, aabb);
+            meshBuilder.AddTriangle(material, cc, ccaa, bbcc);
+        }
+    }
+}

+ 2 - 2
tests/SharpGLTF.Tests/WavefrontWriter.cs

@@ -18,7 +18,7 @@ namespace SharpGLTF
     {
         #region data
 
-        private readonly Geometry.StaticMeshBuilder<VERTEX, MATERIAL> _Mesh = new Geometry.StaticMeshBuilder<VERTEX, MATERIAL>();
+        private readonly Geometry.StaticMeshBuilder<MATERIAL, VERTEX> _Mesh = new Geometry.StaticMeshBuilder<MATERIAL, VERTEX>();
         
         #endregion
 
@@ -43,7 +43,7 @@ namespace SharpGLTF
             {
                 foreach (var v in p.Vertices)
                 {
-                    sb.AppendLine(Invariant($"v {v.Position.X} {v.Position.Y} {v.Position.Z}"));
+                    sb.AppendLine(Invariant($"v {v.Item1.Position.X} {v.Item1.Position.Y} {v.Item1.Position.Z}"));
                 }
             }