2
0
Эх сурвалжийг харах

Improving mesh buffers packaging.

Vicente Penades 6 жил өмнө
parent
commit
147d1dbc03

+ 30 - 0
src/Shared/_Extensions.cs

@@ -406,6 +406,36 @@ namespace SharpGLTF
 
         #region vertex & index accessors
 
+        public static String ToDebugString(this EncodingType encoding, DimensionType dimensions, bool normalized)
+        {
+            var txt = string.Empty;
+
+            switch (encoding)
+            {
+                case EncodingType.BYTE: txt += "SByte"; break;
+                case EncodingType.FLOAT: txt += "Float"; break;
+                case EncodingType.SHORT: txt += "SShort"; break;
+                case EncodingType.UNSIGNED_BYTE: txt += "UByte"; break;
+                case EncodingType.UNSIGNED_INT: txt += "UInt"; break;
+                case EncodingType.UNSIGNED_SHORT: txt += "UShort"; break;
+            }
+
+            switch (dimensions)
+            {
+                case DimensionType.SCALAR: break;
+                case DimensionType.VEC2: txt += "2"; break;
+                case DimensionType.VEC3: txt += "3"; break;
+                case DimensionType.VEC4: txt += "4"; break;
+                case DimensionType.MAT2: txt += "2x2"; break;
+                case DimensionType.MAT3: txt += "3x3"; break;
+                case DimensionType.MAT4: txt += "4x4"; break;
+            }
+
+            if (normalized) txt = "Norm" + txt;
+
+            return txt;
+        }
+
         public static int ByteLength(this IndexEncodingType encoding)
         {
             switch (encoding)

+ 2 - 2
src/SharpGLTF.Core/Debug/DebugViews.cs

@@ -29,9 +29,9 @@ namespace SharpGLTF.Debug
         }
     }
 
-    internal sealed class _BufferDebugProxy
+    internal sealed class _BufferViewDebugProxy
     {
-        public _BufferDebugProxy(Schema2.BufferView value) { _Value = value; }
+        public _BufferViewDebugProxy(Schema2.BufferView value) { _Value = value; }
 
         public int LogicalIndex => _Value.LogicalParent.LogicalBufferViews.IndexOfReference(_Value);
 

+ 15 - 1
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -11,7 +11,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Defines the pattern in which a <see cref="ArraySegment{Byte}"/> is accessed and decoded to meaningful values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("{Name} {Dimensions}.{Encoding}.{Normalized} {ByteStride}   {ByteOffset} [{ItemsCount}]")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public struct MemoryAccessInfo
     {
         #region constructor
@@ -89,6 +89,16 @@ namespace SharpGLTF.Memory
 
         #region API
 
+        internal String _GetDebuggerDisplay()
+        {
+            var txt = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Name);
+            if (ByteOffset != 0) txt += $" Offs:{ByteOffset}ᴮʸᵗᵉˢ";
+            if (ByteStride != 0) txt += $" Strd:{ByteStride}ᴮʸᵗᵉˢ";
+            txt += $" {Encoding.ToDebugString(Dimensions, Normalized)}[{ItemsCount}]";
+
+            return txt;
+        }
+
         /// <summary>
         /// Gets the number of bytes of the current encoded Item, padded to 4 bytes.
         /// </summary>
@@ -193,6 +203,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps a <see cref="ArraySegment{Byte}"/> decoding it and exposing its content as arrays of different types.
     /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{Attribute._GetDebuggerDisplay(),nq}")]
     public sealed class MemoryAccessor
     {
         #region constructor
@@ -273,7 +284,10 @@ namespace SharpGLTF.Memory
 
         #region data
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private MemoryAccessInfo _Attribute;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
         private ArraySegment<Byte> _Data;
 
         #endregion

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

@@ -8,7 +8,7 @@ namespace SharpGLTF.Schema2
 {
     // https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
 
-    [System.Diagnostics.DebuggerDisplay("Accessor[{LogicalIndex}] BufferView[{SourceBufferView.LogicalIndex}][{ByteOffset}...] => 0 => {Dimensions}x{Encoding}x{Normalized} => [{Count}]")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._AccessorDebugProxy))]
     public sealed partial class Accessor
     {
@@ -85,6 +85,26 @@ namespace SharpGLTF.Schema2
 
         #region API
 
+        internal string _GetDebuggerDisplay()
+        {
+            var path = string.Empty;
+
+            var bv = SourceBufferView;
+
+            if (bv.DeviceBufferTarget == BufferMode.ARRAY_BUFFER) path += "VertexBuffer";
+            else if (bv.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER) path += "IndexBuffer";
+            else path += "BufferView";
+            path += $"[{bv.LogicalIndex}ᴵᵈˣ] ⇨";
+
+            path += $" Accessor[{LogicalIndex}ᴵᵈˣ] Offset:{ByteOffset}ᴮʸᵗᵉˢ ⇨";
+
+            path += $" {Encoding.ToDebugString(Dimensions, Normalized)}[{Count}ᴵᵗᵉᵐˢ]";
+
+            if (IsSparse) path += " SPARSE";
+
+            return path;
+        }
+
         internal MemoryAccessor _GetMemoryAccessor()
         {
             var view = SourceBufferView;

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

@@ -6,7 +6,8 @@ using BYTES = System.ArraySegment<byte>;
 
 namespace SharpGLTF.Schema2
 {
-    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._BufferDebugProxy))]
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._BufferViewDebugProxy))]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public sealed partial class BufferView
     {
         #region lifecycle
@@ -73,6 +74,24 @@ namespace SharpGLTF.Schema2
 
         #region API
 
+        internal string _GetDebuggerDisplay()
+        {
+            var path = string.Empty;
+
+            path = $"Buffer[{this._buffer}ᴵᵈˣ] ⇨";
+
+            if (this.DeviceBufferTarget == BufferMode.ARRAY_BUFFER) path += " VertexBuffer";
+            else if (this.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER) path += " IndexBuffer";
+            else path += " BufferView";
+
+            path += $"[{this.LogicalIndex}ᴵᵈˣ]";
+            path += $"[{this._byteLength}ᴮʸᵗᵉˢ]";
+
+            if (ByteStride > 0) path += $" Stride:{ByteStride}ᴮʸᵗᵉˢ";
+
+            return path;
+        }
+
         /// <summary>
         /// Finds all the accessors using this BufferView
         /// </summary>

+ 12 - 0
src/SharpGLTF.Toolkit/Geometry/MeshBuilderToolkit.cs

@@ -23,6 +23,18 @@ namespace SharpGLTF.Geometry
 
     static class MeshBuilderToolkit
     {
+        public static IReadOnlyList<IVertexBuilder> GetMorphTargetVertices(this IMorphTargetReader morphTarget, int targetIndex, int vertexCount)
+        {
+            var c = new IVertexBuilder[vertexCount];
+
+            for (int i = 0; i < vertexCount; ++i)
+            {
+                c[i] = morphTarget.GetVertexDisplacement(targetIndex, i);
+            }
+
+            return c;
+        }
+
         public static Schema2.EncodingType GetOptimalIndexEncoding<TMaterial>(this IEnumerable<IMeshBuilder<TMaterial>> meshes)
         {
             var indices = meshes

+ 5 - 3
src/SharpGLTF.Toolkit/Geometry/MorphTargetBuilder.cs

@@ -12,7 +12,7 @@ namespace SharpGLTF.Geometry
 
         IReadOnlyCollection<int> GetTargetIndices(int morphTargetIndex);
 
-        IVertexGeometry GetVertexDisplacement(int morphTargetIndex, int vertexIndex);
+        IVertexBuilder GetVertexDisplacement(int morphTargetIndex, int vertexIndex);
     }
 
     public class MorphTargetBuilder<TvG> : IMorphTargetReader
@@ -49,9 +49,11 @@ namespace SharpGLTF.Geometry
             return morphTargetIndex < _Targets.Count ? _Targets[morphTargetIndex].Keys : (IReadOnlyCollection<int>)Array.Empty<int>();
         }
 
-        IVertexGeometry IMorphTargetReader.GetVertexDisplacement(int morphTargetIndex, int vertexIndex)
+        IVertexBuilder IMorphTargetReader.GetVertexDisplacement(int morphTargetIndex, int vertexIndex)
         {
-            return _GetVertexDisplacement(morphTargetIndex, vertexIndex);
+            var g = _GetVertexDisplacement(morphTargetIndex, vertexIndex);
+
+            return new VertexBuilder<TvG, VertexEmpty, VertexEmpty>(g);
         }
 
         TvG GetVertexDisplacement(int morphTargetIndex, int vertexIndex)

+ 2 - 0
src/SharpGLTF.Toolkit/Geometry/PackedBuffer.cs

@@ -24,6 +24,8 @@ namespace SharpGLTF.Geometry
         {
             foreach (var a in accessors)
             {
+                if (a == null) continue;
+
                 // ensure that all accessors have the same byte stride
                 if (this.ByteStride.HasValue)
                 {

+ 17 - 135
src/SharpGLTF.Toolkit/Geometry/PackedMeshBuilder.cs

@@ -16,7 +16,7 @@ namespace SharpGLTF.Geometry
     {
         #region lifecycle
 
-        internal static IEnumerable<PackedMeshBuilder<TMaterial>> PackMeshesColumnVertices(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders)
+        internal static IEnumerable<PackedMeshBuilder<TMaterial>> CreatePackedMeshes(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders, bool prefferStrided)
         {
             try
             {
@@ -27,12 +27,8 @@ namespace SharpGLTF.Geometry
                 throw new ArgumentException(ex.Message, nameof(meshBuilders), ex);
             }
 
-            var vertexBuffers = new Dictionary<string, PackedBuffer>();
-            var indexBuffer = new PackedBuffer();
             var indexEncoding = meshBuilders.GetOptimalIndexEncoding();
 
-            var dstMeshes = new List<PackedMeshBuilder<TMaterial>>();
-
             foreach (var srcMesh in meshBuilders)
             {
                 var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name);
@@ -41,83 +37,20 @@ namespace SharpGLTF.Geometry
                 {
                     if (srcPrim.Vertices.Count == 0) continue;
 
-                    var attributeNames = VertexTypes.VertexUtils
-                        .GetVertexAttributes(srcPrim.Vertices[0], srcPrim.Vertices.Count)
-                        .Select(item => item.Name)
-                        .ToList();
-
-                    var vAccessors = new List<Memory.MemoryAccessor>();
-
-                    foreach (var an in attributeNames)
-                    {
-                        var vAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices, an);
-                        if (vAccessor == null) continue;
-
-                        vAccessors.Add(vAccessor);
-
-                        if (!vertexBuffers.TryGetValue(an, out PackedBuffer packed))
-                        {
-                            vertexBuffers[an] = packed = new PackedBuffer();
-                        }
-
-                        packed.AddAccessors(vAccessor);
-                    }
-
-                    var iAccessor = VertexTypes.VertexUtils.CreateIndexMemoryAccessor(srcPrim.GetIndices(), indexEncoding);
-                    if (iAccessor != null) indexBuffer.AddAccessors(iAccessor);
-
-                    dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive, vAccessors.ToArray(), iAccessor);
-                }
+                    var dstPrim = dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive);
 
-                dstMeshes.Add(dstMesh);
-            }
-
-            foreach (var vb in vertexBuffers.Values) vb.MergeBuffers();
-            indexBuffer.MergeBuffers();
-
-            return dstMeshes;
-        }
-
-        internal static IEnumerable<PackedMeshBuilder<TMaterial>> PackMeshesRowVertices(IEnumerable<IMeshBuilder<TMaterial>> meshBuilders)
-        {
-            try
-            {
-                foreach (var m in meshBuilders) m.Validate();
-            }
-            catch (Exception ex)
-            {
-                throw new ArgumentException(ex.Message, nameof(meshBuilders), ex);
-            }
-
-            var vertexBuffer = new PackedBuffer();
-            var indexBuffer = new PackedBuffer();
-            var indexEncoding = meshBuilders.GetOptimalIndexEncoding();
-
-            var dstMeshes = new List<PackedMeshBuilder<TMaterial>>();
-
-            foreach (var srcMesh in meshBuilders)
-            {
-                var dstMesh = new PackedMeshBuilder<TMaterial>(srcMesh.Name);
-
-                foreach (var srcPrim in srcMesh.Primitives)
-                {
-                    var vAccessors = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices);
-                    if (vAccessors == null) continue;
-                    vertexBuffer.AddAccessors(vAccessors);
+                    bool useStrided = prefferStrided;
+                    if (srcPrim.MorphTargets.TargetsCount > 0) useStrided = false;
 
-                    var iAccessor = VertexTypes.VertexUtils.CreateIndexMemoryAccessor(srcPrim.GetIndices(), indexEncoding);
-                    if (iAccessor != null) indexBuffer.AddAccessors(iAccessor);
+                    if (useStrided) dstPrim.SetStridedVertices(srcPrim);
+                    else dstPrim.SetStreamedVertices(srcPrim);
 
-                    dstMesh.AddPrimitive(srcPrim.Material, srcPrim.VerticesPerPrimitive, vAccessors, iAccessor);
+                    dstPrim.SetIndices(srcPrim, indexEncoding);
+                    dstPrim.SetMorphTargets(srcPrim);
                 }
 
-                dstMeshes.Add(dstMesh);
+                yield return dstMesh;
             }
-
-            vertexBuffer.MergeBuffers();
-            indexBuffer.MergeBuffers();
-
-            return dstMeshes;
         }
 
         private PackedMeshBuilder(string name) { _MeshName = name; }
@@ -134,10 +67,12 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        public void AddPrimitive(TMaterial material, int primitiveVertexCount, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
+        public PackedPrimitiveBuilder<TMaterial> AddPrimitive(TMaterial material, int primitiveVertexCount)
         {
-            var p = new PackedPrimitiveBuilder<TMaterial>(material, primitiveVertexCount, vrtAccessors, idxAccessor);
+            var p = new PackedPrimitiveBuilder<TMaterial>(material, primitiveVertexCount);
             _Primitives.Add(p);
+
+            return p;
         }
 
         public Mesh CreateSchema2Mesh(ModelRoot root, Func<TMaterial, Material> materialEvaluator)
@@ -151,67 +86,14 @@ namespace SharpGLTF.Geometry
                 p.CopyToMesh(dstMesh, materialEvaluator);
             }
 
+            // TODO: set default morph target weights.
+
             return dstMesh;
         }
 
-        #endregion
-    }
-
-    class PackedPrimitiveBuilder<TMaterial>
-    {
-        #region lifecycle
-
-        internal PackedPrimitiveBuilder(TMaterial material, int primitiveVertexCount, Memory.MemoryAccessor[] vrtAccessors, Memory.MemoryAccessor idxAccessor)
+        public static void MergeBuffers(IEnumerable<PackedMeshBuilder<TMaterial>> meshes)
         {
-            Guard.MustBeBetweenOrEqualTo(primitiveVertexCount, 1, 3, nameof(primitiveVertexCount));
-
-            Guard.NotNull(vrtAccessors, nameof(vrtAccessors));
-
-            if (primitiveVertexCount == 1) Guard.MustBeNull(idxAccessor, nameof(idxAccessor));
-            else                           Guard.NotNull(idxAccessor, nameof(idxAccessor));
-
-            _Material = material;
-            _VerticesPerPrimitive = primitiveVertexCount;
-            _VertexAccessors = vrtAccessors;
-            _IndexAccessors = idxAccessor; // indices can be null for points
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly TMaterial _Material;
-        private readonly int _VerticesPerPrimitive;
-
-        private readonly Memory.MemoryAccessor[] _VertexAccessors;
-
-        private readonly Memory.MemoryAccessor _IndexAccessors;
-
-        #endregion
-
-        #region API
-
-        internal void CopyToMesh(Mesh dstMesh, Func<TMaterial, Material> materialEvaluator)
-        {
-            if (_VerticesPerPrimitive < 1 || _VerticesPerPrimitive > 3) return;
-
-            if (_VerticesPerPrimitive == 1)
-            {
-                dstMesh.CreatePrimitive()
-                        .WithMaterial(materialEvaluator(_Material))
-                        .WithVertexAccessors(_VertexAccessors)
-                        .WithIndicesAutomatic(PrimitiveType.POINTS);
-
-                return;
-            }
-
-            var pt = PrimitiveType.LINES;
-            if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
-
-            dstMesh.CreatePrimitive()
-                        .WithMaterial(materialEvaluator(_Material))
-                        .WithVertexAccessors(_VertexAccessors)
-                        .WithIndicesAccessor(pt, _IndexAccessors);
+            PackedPrimitiveBuilder<TMaterial>.MergeBuffers(meshes.SelectMany(m => m._Primitives));
         }
 
         #endregion

+ 223 - 0
src/SharpGLTF.Toolkit/Geometry/PackedPrimitiveBuilder.cs

@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using SharpGLTF.Schema2;
+
+namespace SharpGLTF.Geometry
+{
+    sealed class PackedPrimitiveBuilder<TMaterial>
+    {
+        #region lifecycle
+
+        public PackedPrimitiveBuilder(TMaterial material, int primitiveVertexCount)
+        {
+            Guard.MustBeBetweenOrEqualTo(primitiveVertexCount, 1, 3, nameof(primitiveVertexCount));
+
+            _Material = material;
+            _VerticesPerPrimitive = primitiveVertexCount;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly TMaterial _Material;
+        private readonly int _VerticesPerPrimitive;
+
+        private Type _StridedVertexType;
+        private Memory.MemoryAccessor[] _VertexAccessors;
+
+        private Memory.MemoryAccessor _IndexAccessors;
+
+        private readonly List<Memory.MemoryAccessor[]> _MorphTargets = new List<Memory.MemoryAccessor[]>();
+
+        #endregion
+
+        #region API
+
+        public void SetStridedVertices(IPrimitiveReader<TMaterial> srcPrim)
+        {
+            Guard.NotNull(srcPrim, nameof(srcPrim));
+
+            var vAccessors = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices);
+
+            Guard.NotNull(vAccessors, nameof(srcPrim));
+
+            _StridedVertexType = srcPrim.VertexType;
+            _VertexAccessors = vAccessors;
+        }
+
+        public void SetStreamedVertices(IPrimitiveReader<TMaterial> srcPrim)
+        {
+            Guard.NotNull(srcPrim, nameof(srcPrim));
+
+            var attributeNames = VertexTypes.VertexUtils
+                        .GetVertexAttributes(srcPrim.Vertices[0], srcPrim.Vertices.Count)
+                        .Select(item => item.Name)
+                        .ToList();
+
+            var vAccessors = new List<Memory.MemoryAccessor>();
+
+            foreach (var an in attributeNames)
+            {
+                var vAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(srcPrim.Vertices, an);
+                if (vAccessor == null) continue;
+
+                System.Diagnostics.Debug.Assert(vAccessor.Attribute.ByteOffset == 0);
+                System.Diagnostics.Debug.Assert(vAccessor.Attribute.ByteStride == 0);
+
+                vAccessors.Add(vAccessor);
+            }
+
+            _VertexAccessors = vAccessors.ToArray();
+        }
+
+        public void SetIndices(IPrimitiveReader<TMaterial> srcPrim, EncodingType encoding)
+        {
+            Guard.NotNull(srcPrim, nameof(srcPrim));
+
+            var iAccessor = VertexTypes.VertexUtils.CreateIndexMemoryAccessor(srcPrim.GetIndices(), encoding);
+
+            if (_VerticesPerPrimitive == 1) Guard.MustBeNull(iAccessor, nameof(srcPrim));
+            else Guard.NotNull(iAccessor, nameof(iAccessor));
+
+            _IndexAccessors = iAccessor;
+        }
+
+        public void SetMorphTargets(IPrimitiveReader<TMaterial> srcPrim)
+        {
+            var vAccessors = new List<Memory.MemoryAccessor>();
+
+            for (int i = 0; i < srcPrim.MorphTargets.TargetsCount; ++i)
+            {
+                var mtv = srcPrim.MorphTargets.GetMorphTargetVertices(i, srcPrim.Vertices.Count);
+
+                vAccessors.Clear();
+
+                var pAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(mtv, "POSITION");
+                if (pAccessor != null) vAccessors.Add(pAccessor);
+
+                var nAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(mtv, "NORMAL");
+                if (nAccessor != null) vAccessors.Add(nAccessor);
+
+                // tangets is tricky because for morph targets, it's stored as 3 components, not 4
+
+                AddMorphTarget(pAccessor, nAccessor);
+            }
+        }
+
+        private void AddMorphTarget(params Memory.MemoryAccessor[] morphTarget)
+        {
+            morphTarget = morphTarget.Where(item => item != null).ToArray();
+
+            _MorphTargets.Add(morphTarget);
+        }
+
+        internal void CopyToMesh(Mesh dstMesh, Func<TMaterial, Material> materialEvaluator)
+        {
+            if (_VerticesPerPrimitive < 1 || _VerticesPerPrimitive > 3) return;
+
+            if (_VerticesPerPrimitive == 1)
+            {
+                var dstPrim = dstMesh.CreatePrimitive()
+                        .WithMaterial(materialEvaluator(_Material))
+                        .WithVertexAccessors(_VertexAccessors)
+                        .WithIndicesAutomatic(PrimitiveType.POINTS);
+
+                CopyMorphTargets(dstPrim);
+
+                return;
+            }
+            else
+            {
+
+                var pt = PrimitiveType.LINES;
+                if (_VerticesPerPrimitive == 3) pt = PrimitiveType.TRIANGLES;
+
+                var dstPrim = dstMesh.CreatePrimitive()
+                            .WithMaterial(materialEvaluator(_Material))
+                            .WithVertexAccessors(_VertexAccessors)
+                            .WithIndicesAccessor(pt, _IndexAccessors);
+
+                CopyMorphTargets(dstPrim);
+            }
+        }
+
+        private void CopyMorphTargets(MeshPrimitive dstPrim)
+        {
+            for (int i = 0; i < _MorphTargets.Count; ++i)
+            {
+                dstPrim.WithMorphTargetAccessors(i, _MorphTargets[i]);
+            }
+        }
+
+        #endregion
+
+        #region API - Buffer merging
+
+        public static void MergeBuffers(IEnumerable<PackedPrimitiveBuilder<TMaterial>> primitives)
+        {
+            _MergeIndices(primitives);
+            _MergeStridedVertices(primitives.Where(p => p._StridedVertexType != null));
+            _MergeStreamedVertices(primitives.Where(p => p._StridedVertexType == null).Select(p => p._VertexAccessors));
+            _MergeStreamedVertices(primitives.SelectMany(p => p._MorphTargets));
+        }
+
+        private static void _MergeStreamedVertices(IEnumerable<Memory.MemoryAccessor[]> primitives)
+        {
+            var vertexBuffers = new Dictionary<string, PackedBuffer>();
+
+            foreach (var vvv in primitives)
+            {
+                foreach (var v in vvv)
+                {
+                    var k = v.Attribute.Name;
+
+                    if (!vertexBuffers.TryGetValue(k, out PackedBuffer packed))
+                    {
+                        vertexBuffers[k] = packed = new PackedBuffer();
+                    }
+
+                    packed.AddAccessors(v);
+                }
+            }
+
+            foreach (var vb in vertexBuffers.Values) vb.MergeBuffers();
+        }
+
+        private static void _MergeStridedVertices(IEnumerable<PackedPrimitiveBuilder<TMaterial>> primitives)
+        {
+            var perVertexGroups = primitives
+                .ToList()
+                .GroupBy(item => item._StridedVertexType);
+
+            foreach (var group in perVertexGroups)
+            {
+                var vertexBuffers = new PackedBuffer();
+
+                foreach (var p in group)
+                {
+                    vertexBuffers.AddAccessors(p._VertexAccessors);
+                }
+
+                vertexBuffers.MergeBuffers();
+            }
+        }
+
+        private static void _MergeIndices(IEnumerable<PackedPrimitiveBuilder<TMaterial>> primitives)
+        {
+            var indexBuffers = new PackedBuffer();
+
+            foreach (var p in primitives)
+            {
+                indexBuffers.AddAccessors(p._IndexAccessors);
+            }
+
+            indexBuffers.MergeBuffers();
+        }
+
+        #endregion
+    }
+}

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

@@ -12,6 +12,11 @@ namespace SharpGLTF.Geometry
 {
     public interface IPrimitiveReader<TMaterial>
     {
+        /// <summary>
+        /// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
+        /// </summary>
+        Type VertexType { get; }
+
         /// <summary>
         /// Gets the current <typeparamref name="TMaterial"/> instance used by this primitive.
         /// </summary>
@@ -59,7 +64,7 @@ namespace SharpGLTF.Geometry
     public interface IPrimitiveBuilder
     {
         /// <summary>
-        /// Gets the type of vertex used by this <see cref="IVertexBuilder"/>.
+        /// Gets a generic type of <see cref="VertexBuilder{TvG, TvM, TvS}"/>.
         /// </summary>
         Type VertexType { get; }
 

+ 3 - 9
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -223,8 +223,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             // create a buffer
             var vbuffer = new ArraySegment<byte>(new Byte[attribute.PaddedByteLength * vertices.Count]);
 
-            // fill the buffer with the vertex blocks.
-
+            // fill the buffer with the vertex attributes.
             var accessor = new MemoryAccessor(vbuffer, attribute);
 
             accessor.FillAccessor(vertices);
@@ -244,8 +243,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             int byteStride = attributes[0].ByteStride;
             var vbuffer = new ArraySegment<byte>(new Byte[byteStride * vertices.Count]);
 
-            // fill the buffer with the vertex blocks.
-
+            // fill the buffer with the vertex attributes.
             var accessors = MemoryAccessInfo
                 .Slice(attributes, 0, vertices.Count)
                 .Select(item => new MemoryAccessor(vbuffer, item))
@@ -274,16 +272,13 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             if (indices == null || indices.Count == 0) return null;
 
-            // determine appropiate encoding
-            // var maxIndex = indices.Max();
-            // var encoding = maxIndex < 65535 ? Schema2.EncodingType.UNSIGNED_SHORT : Schema2.EncodingType.UNSIGNED_INT;
-
             var attribute = new MemoryAccessInfo("INDEX", 0, indices.Count, 0, Schema2.DimensionType.SCALAR, encoding);
 
             // create buffer
             var ibytes = new Byte[encoding.ByteLength() * indices.Count];
             var ibuffer = new ArraySegment<byte>(ibytes);
 
+            // fill the buffer with indices.
             var accessor = new MemoryAccessor(ibuffer, attribute.Slice(0, indices.Count));
 
             accessor.AsIntegerArray().Fill(indices);
@@ -291,7 +286,6 @@ namespace SharpGLTF.Geometry.VertexTypes
             return accessor;
         }
 
-
         public static MemoryAccessInfo[] GetVertexAttributes(this IVertexBuilder firstVertex, int vertexCount)
         {
             var tvg = firstVertex.GetGeometry().GetType();

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

@@ -31,7 +31,7 @@ namespace SharpGLTF.Scenes
 
         public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
 
-        public void AddScene(Scene dstScene, SceneBuilder srcScene)
+        public void AddScene(Scene dstScene, SceneBuilder srcScene, bool useStridedBuffers = true)
         {
             // gather all unique MeshBuilders
 
@@ -62,7 +62,7 @@ namespace SharpGLTF.Scenes
 
             // create a Schema2.Mesh for every MeshBuilder.
 
-            var dstMeshes = dstScene.LogicalParent.CreateMeshes(mat => _Materials[mat], true,  srcMeshes);
+            var dstMeshes = dstScene.LogicalParent.CreateMeshes(mat => _Materials[mat], useStridedBuffers,  srcMeshes);
 
             for (int i = 0; i < srcMeshes.Length; ++i)
             {
@@ -145,15 +145,16 @@ namespace SharpGLTF.Scenes
         /// <summary>
         /// Converts this <see cref="SceneBuilder"/> instance into a <see cref="ModelRoot"/> instance.
         /// </summary>
+        /// <param name="useStridedBuffers">True to generate strided vertex buffers whenever possible.</param>
         /// <returns>A new <see cref="ModelRoot"/> instance.</returns>
-        public ModelRoot ToSchema2()
+        public ModelRoot ToSchema2(bool useStridedBuffers = true)
         {
             var dstModel = ModelRoot.CreateModel();
 
             var dstScene = dstModel.UseScene(0);
 
             var context = new Schema2SceneBuilder();
-            context.AddScene(dstScene, this);
+            context.AddScene(dstScene, this, useStridedBuffers);
 
             return dstModel;
         }

+ 19 - 14
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -79,21 +79,11 @@ namespace SharpGLTF.Schema2
 
             // create Schema2.Mesh collections for every gathered group.
 
-            List<PackedMeshBuilder<TMaterial>> srcMeshes = null;
+            var srcMeshes = PackedMeshBuilder<TMaterial>
+                .CreatePackedMeshes(meshBuilders, strided)
+                .ToList();
 
-            if (strided)
-            {
-                var meshGroups = meshBuilders.GroupBy(item => item.GetType());
-
-                srcMeshes = meshGroups
-                    .SelectMany(grp => PackedMeshBuilder<TMaterial>.PackMeshesRowVertices(grp.ToArray()))
-                    .ToList();
-            }
-            else
-            {
-                srcMeshes = PackedMeshBuilder<TMaterial>.PackMeshesColumnVertices(meshBuilders)
-                    .ToList();
-            }
+            PackedMeshBuilder<TMaterial>.MergeBuffers(srcMeshes);
 
             // create schema2 meshes
 
@@ -316,6 +306,21 @@ namespace SharpGLTF.Schema2
             return primitive;
         }
 
+        public static MeshPrimitive WithMorphTargetAccessors(this MeshPrimitive primitive, int targetIndex, IEnumerable<Memory.MemoryAccessor> memAccessors)
+        {
+            Guard.NotNull(primitive, nameof(primitive));
+            Guard.MustBeGreaterThanOrEqualTo(targetIndex, 0, nameof(targetIndex));
+            Guard.NotNull(memAccessors, nameof(memAccessors));
+
+            var root = primitive.LogicalParent.LogicalParent;
+
+            var accessors = memAccessors.ToDictionary(item => item.Attribute.Name, item => root.CreateVertexAccessor(item));
+
+            primitive.SetMorphTargetAccessors(targetIndex, accessors);
+
+            return primitive;
+        }
+
         #endregion
 
         #region material

+ 83 - 22
tests/SharpGLTF.Tests/Reports.cs

@@ -11,13 +11,9 @@ namespace SharpGLTF.Reporting
 {
     public class VisualReport
     {
-        private VisualReport(Schema2.MeshPrimitive prim)
-        {
-            SetFrom(prim);
-        }
-
-        internal VisualReport() { }
+        #region data
 
+        public int NumVertices { get; internal set; }
         public int NumTriangles { get; internal set; }
         public int NumLines { get; internal set; }
         public int NumPoints { get; internal set; }
@@ -26,17 +22,24 @@ namespace SharpGLTF.Reporting
 
         public IEnumerable<string> VertexAttributes { get; internal set; }
 
-        public void SetFrom(Schema2.Mesh mesh)
-        {
-            SetFrom(mesh.Primitives.Select(prim => new VisualReport(prim)));
-        }
+        #endregion
 
-        public void SetFrom(Schema2.MeshPrimitive prim)
+        #region API
+
+        protected virtual string DebuggerDisplay
         {
-            NumPoints = prim.GetPointIndices().Count();
-            NumLines = prim.GetLineIndices().Count();
-            NumTriangles = prim.GetTriangleIndices().Count();
-            Bounds = prim.GetVertexAccessor("POSITION").AsVector3Array().GetBounds();
+            get
+            {
+                var txt = string.Empty;
+
+                if (!VertexAttributes.Contains("SCENE")) txt += " " + string.Join(" ", VertexAttributes);
+
+                txt += " Vrts:" + NumVertices;
+                txt += " Tris:" + NumTriangles;
+
+                return txt;
+            }
+
         }
 
         public void SetFrom(Schema2.Scene scene)
@@ -49,10 +52,13 @@ namespace SharpGLTF.Reporting
                 .SelectMany(item => new[] { item.Item1, item.Item2, item.Item3 })
                 .Select(item => item.GetGeometry().GetPosition())
                 .GetBounds();
+
+            this.VertexAttributes = new[] { "SCENE" };
         }
 
         internal void SetFrom(IEnumerable<VisualReport> many)
         {
+            NumVertices = many.Sum(item => item.NumVertices);
             NumTriangles = many.Sum(item => item.NumTriangles);
             NumLines = many.Sum(item => item.NumLines);
             NumPoints = many.Sum(item => item.NumPoints);
@@ -60,33 +66,88 @@ namespace SharpGLTF.Reporting
             var min = many.Select(item => item.Bounds.Item1).GetMin();
             var max = many.Select(item => item.Bounds.Item2).GetMax();
             Bounds = (min, max);
+
+            VertexAttributes = many
+                .SelectMany(item => item.VertexAttributes)
+                .Distinct()
+                .ToArray();
         }
+
+        #endregion
     }
 
+    [System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")]
+    public sealed class PrimitiveReport : VisualReport
+    {
+        internal PrimitiveReport(Schema2.MeshPrimitive prim)
+        {
+            var vertices = prim.GetVertexAccessor("POSITION").AsVector3Array();
+
+            NumVertices = vertices.Count;
+            NumPoints = prim.GetPointIndices().Count();
+            NumLines = prim.GetLineIndices().Count();
+            NumTriangles = prim.GetTriangleIndices().Count();
+            Bounds = vertices.GetBounds();
+
+            VertexAttributes = prim.VertexAccessors.Keys.ToArray();
+        }
+
+        public string MaterialName { get; private set; }
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        protected override string DebuggerDisplay => "PRIM " + MaterialName + " " + base.DebuggerDisplay;
+    }
+
+    [System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")]
     public class MeshReport : VisualReport
     {
-        internal MeshReport(string name)
+        internal MeshReport(Schema2.Mesh mesh)
         {
-            Name = name;
+            Name = mesh.Name;
+
+            _Primitives = mesh.Primitives
+                .Select(prim => new PrimitiveReport(prim))
+                .ToArray();
+
+            this.SetFrom(_Primitives);
         }
 
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
+        private readonly PrimitiveReport[] _Primitives;        
+
         public String Name { get; private set; }
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        protected override string DebuggerDisplay => "MESH " + Name + " " + base.DebuggerDisplay;
     }
+
+    [System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")]
     public class ModelReport : VisualReport
     {
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private readonly List<MeshReport> _Meshes = new List<MeshReport>();
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
         private readonly List<VisualReport> _Scenes = new List<VisualReport>();
 
+        private readonly List<int> _VertexBuffers = new List<int>();
+        private readonly List<int> _IndexBuffers = new List<int>();
+        private readonly List<int> _DataBuffers = new List<int>();
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        protected override string DebuggerDisplay => "MODEL" + base.DebuggerDisplay;
+
         public static ModelReport CreateReportFrom(Schema2.ModelRoot model)
         {
             var rrrr = new ModelReport();
 
-            foreach(var mesh in model.LogicalMeshes)
+            foreach (var bv in model.LogicalBufferViews.Where(item => item.DeviceBufferTarget == BufferMode.ARRAY_BUFFER)) rrrr._VertexBuffers.Add(bv.Content.Count);
+            foreach (var iv in model.LogicalBufferViews.Where(item => item.DeviceBufferTarget == BufferMode.ELEMENT_ARRAY_BUFFER)) rrrr._IndexBuffers.Add(iv.Content.Count);
+            foreach (var dv in model.LogicalBufferViews.Where(item => item.DeviceBufferTarget == null)) rrrr._DataBuffers.Add(dv.Content.Count);
+
+
+            foreach (var mesh in model.LogicalMeshes)
             {
-                var r = new MeshReport(mesh.Name);
-                r.SetFrom(mesh);
-                rrrr._Meshes.Add(r);
-                
+                rrrr._Meshes.Add( new MeshReport(mesh) );                
             }
 
             foreach (var scene in model.LogicalScenes)

+ 31 - 26
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -448,52 +448,57 @@ namespace SharpGLTF.Scenes
                 .GetSampleModelsPaths()
                 .FirstOrDefault(item => item.Contains(path));
 
-            var modelSrc = Schema2.ModelRoot.Load(path);
-            Assert.NotNull(modelSrc);
+            var srcModel = Schema2.ModelRoot.Load(path);
+            Assert.NotNull(srcModel);
 
             // perform roundtrip
 
-            var sceneSrc = Schema2.Schema2Toolkit.ToSceneBuilder(modelSrc.DefaultScene);            
+            var srcScene = Schema2Toolkit.ToSceneBuilder(srcModel.DefaultScene);            
 
-            // var cube = new Cube<MaterialBuilder>(MaterialBuilder.CreateDefault(), 1, 0.01f, 1);
-            // scene.AddMesh(cube.ToMesh(Matrix4x4.Identity), Matrix4x4.Identity);
+            var rowModel = srcScene.ToSchema2();
+            var colModel = srcScene.ToSchema2(false);
 
-            var modelBis = sceneSrc.ToSchema2();
-
-            var sceneBis = Schema2.Schema2Toolkit.ToSceneBuilder(modelBis.DefaultScene);
+            var rowScene = Schema2Toolkit.ToSceneBuilder(rowModel.DefaultScene);
+            var colScene = Schema2Toolkit.ToSceneBuilder(colModel.DefaultScene);
 
             // compare files
 
-            var srcTris = modelSrc.DefaultScene.EvaluateTriangles().ToList();
-            var bisTris = modelBis.DefaultScene.EvaluateTriangles().ToList();
+            var srcTris = srcModel.DefaultScene.EvaluateTriangles().ToList();
+            var rowTris = rowModel.DefaultScene.EvaluateTriangles().ToList();
+            var colTris = colModel.DefaultScene.EvaluateTriangles().ToList();
 
-            Assert.AreEqual(srcTris.Count, bisTris.Count);
+            Assert.AreEqual(srcTris.Count, rowTris.Count);
+            Assert.AreEqual(srcTris.Count, colTris.Count);
 
-            var srcRep = Reporting.ModelReport.CreateReportFrom(modelSrc);
-            var bisRep = Reporting.ModelReport.CreateReportFrom(modelBis);
+            var srcRep = Reporting.ModelReport.CreateReportFrom(srcModel);
+            var rowRep = Reporting.ModelReport.CreateReportFrom(rowModel);
+            var colRep = Reporting.ModelReport.CreateReportFrom(colModel);
 
-            Assert.AreEqual(srcRep.NumTriangles, bisRep.NumTriangles);
-            NumericsAssert.AreEqual(srcRep.Bounds.Item1, bisRep.Bounds.Item1, 0.0001f);
-            NumericsAssert.AreEqual(srcRep.Bounds.Item2, bisRep.Bounds.Item2, 0.0001f);
+            Assert.AreEqual(srcRep.NumTriangles, rowRep.NumTriangles);
+            NumericsAssert.AreEqual(srcRep.Bounds.Item1, rowRep.Bounds.Item1, 0.0001f);
+            NumericsAssert.AreEqual(srcRep.Bounds.Item2, rowRep.Bounds.Item2, 0.0001f);
 
             // save file
 
             path = System.IO.Path.GetFileNameWithoutExtension(path);
-            modelSrc.AttachToCurrentTest(path +"_src" + ".glb");
-            modelBis.AttachToCurrentTest(path +"_bis" + ".glb");
+            srcModel.AttachToCurrentTest(path +"_src" + ".glb");
+            rowModel.AttachToCurrentTest(path +"_row" + ".glb");
+            colModel.AttachToCurrentTest(path + "_col" + ".glb");
 
-            modelSrc.AttachToCurrentTest(path + "_src" + ".gltf");
-            modelBis.AttachToCurrentTest(path + "_bis" + ".gltf");
+            srcModel.AttachToCurrentTest(path + "_src" + ".gltf");
+            rowModel.AttachToCurrentTest(path + "_row" + ".gltf");
+            colModel.AttachToCurrentTest(path + "_col" + ".gltf");
 
-            modelSrc.AttachToCurrentTest(path + "_src" + ".obj");
-            modelBis.AttachToCurrentTest(path + "_bis" + ".obj");
+            srcModel.AttachToCurrentTest(path + "_src" + ".obj");
+            rowModel.AttachToCurrentTest(path + "_row" + ".obj");
+            colModel.AttachToCurrentTest(path + "_col" + ".obj");
 
-            if (modelSrc.LogicalAnimations.Count > 0)
+            if (srcModel.LogicalAnimations.Count > 0)
             {
-                modelSrc.AttachToCurrentTest(path + "_src_at01" + ".obj", modelSrc.LogicalAnimations[0], 0.1f);
+                srcModel.AttachToCurrentTest(path + "_src_at01" + ".obj", srcModel.LogicalAnimations[0], 0.1f);
 
-                if (modelBis.LogicalAnimations.Count > 0)
-                    modelBis.AttachToCurrentTest(path + "_bis_at01" + ".obj", modelBis.LogicalAnimations[0], 0.1f);
+                if (rowModel.LogicalAnimations.Count > 0)
+                    rowModel.AttachToCurrentTest(path + "_bis_at01" + ".obj", rowModel.LogicalAnimations[0], 0.1f);
             }
         }