Browse Source

Improving morph target export.
Adding more DebuggerDisplay features.

Vicente Penades 6 years ago
parent
commit
250c6a0023

+ 1 - 0
examples/InfiniteSkinnedTentacle/Program.cs

@@ -55,6 +55,7 @@ namespace InfiniteSkinnedTentacle
             RecusiveTentacle(scene, scene, Matrix4x4.CreateTranslation(+25, 0, -25), mesh, Quaternion.CreateFromYawPitchRoll(0.2f, 0f, 0f), 2);            
 
             model.SaveGLB("recursive tentacles.glb");
+            model.SaveGLTF("recursive tentacles.gltf");
 
             model.SaveAsWavefront("recursive_tentacles_at_000.obj", model.LogicalAnimations[0], 0);
             model.SaveAsWavefront("recursive_tentacles_at_025.obj", model.LogicalAnimations[0], 0.25f);

+ 13 - 0
src/SharpGLTF.Core/Debug/DebugViews.cs

@@ -84,4 +84,17 @@ namespace SharpGLTF.Debug
             }
         }
     }
+
+    internal sealed class _MeshDebugProxy
+    {
+        public _MeshDebugProxy(Schema2.Mesh value) { _Value = value; }
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private readonly Schema2.Mesh _Value;
+
+        public String Name => _Value.Name;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
+        public Schema2.MeshPrimitive[] Primitives => _Value.Primitives.ToArray();
+    }
 }

+ 1 - 1
src/SharpGLTF.Core/Memory/ColorArray.cs

@@ -13,7 +13,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector4"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Vector4 Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Color4[{Count}]")]
     public struct ColorArray : IList<Vector4>, IReadOnlyList<Vector4>
     {
         #region constructors

+ 7 - 7
src/SharpGLTF.Core/Memory/FloatingArrays.cs

@@ -219,7 +219,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Single"/> values
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Scalar Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Float[{Count}]")]
     public struct ScalarArray : IList<Single>, IReadOnlyList<Single>
     {
         #region constructors
@@ -330,7 +330,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector2"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Vector2 Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Vector2[{Count}]")]
     public struct Vector2Array : IList<Vector2>, IReadOnlyList<Vector2>
     {
         #region constructors
@@ -449,7 +449,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector3"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Vector3 Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Vector3[{Count}]")]
     public struct Vector3Array : IList<Vector3>, IReadOnlyList<Vector3>
     {
         #region constructors
@@ -569,7 +569,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Vector4"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Vector4 Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Vector4[{Count}]")]
     public struct Vector4Array : IList<Vector4>, IReadOnlyList<Vector4>
     {
         #region constructors
@@ -690,7 +690,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Quaternion"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Quaternion Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Quaternion[{Count}]")]
     public struct QuaternionArray : IList<Quaternion>, IReadOnlyList<Quaternion>
     {
         #region constructors
@@ -777,7 +777,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="Matrix4x4"/> values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("MAtrix4x4 Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Matrix4x4[{Count}]")]
     public struct Matrix4x4Array : IList<Matrix4x4>, IReadOnlyList<Matrix4x4>
     {
         #region constructors
@@ -882,7 +882,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="float"/> array values.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("MultiArray Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Float[][{Count}]")]
     public struct MultiArray : IList<Single[]>, IReadOnlyList<Single[]>
     {
         #region constructors

+ 1 - 1
src/SharpGLTF.Core/Memory/IntegerArrays.cs

@@ -12,7 +12,7 @@ namespace SharpGLTF.Memory
     /// <summary>
     /// Wraps an encoded <see cref="BYTES"/> and exposes it as an array of <see cref="UInt32"/> values
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("Integer Accessor {Count}")]
+    [System.Diagnostics.DebuggerDisplay("Integer[{Count}]")]
     public struct IntegerArray : IList<UInt32>, IReadOnlyList<UInt32>
     {
         #region constructors

+ 32 - 10
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -14,6 +14,38 @@ namespace SharpGLTF.Memory
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public struct MemoryAccessInfo
     {
+        #region debug
+
+        internal static string GetAttributeShortName(string attributeName)
+        {
+            if (attributeName == "POSITION") return "𝐏";
+            if (attributeName == "NORMAL") return "𝚴";
+            if (attributeName == "TANGENT") return "𝚻";
+            if (attributeName == "COLOR_0") return "𝐂₀";
+            if (attributeName == "COLOR_1") return "𝐂₁";
+            if (attributeName == "TEXCOORD_0") return "𝐔𝐕₀";
+            if (attributeName == "TEXCOORD_1") return "𝐔𝐕₁";
+
+            if (attributeName == "JOINTS_0") return "𝐉₀";
+            if (attributeName == "JOINTS_1") return "𝐉₁";
+
+            if (attributeName == "WEIGHTS_0") return "𝐖₀";
+            if (attributeName == "WEIGHTS_1") return "𝐖₁";
+            return attributeName;
+        }
+
+        internal String _GetDebuggerDisplay()
+        {
+            var txt = GetAttributeShortName(Name);
+            if (ByteOffset != 0) txt += $" Offs:{ByteOffset}ᴮʸᵗᵉˢ";
+            if (ByteStride != 0) txt += $" Strd:{ByteStride}ᴮʸᵗᵉˢ";
+            txt += $" {Encoding.ToDebugString(Dimensions, Normalized)}[{ItemsCount}]";
+
+            return txt;
+        }
+
+        #endregion
+
         #region constructor
 
         public static MemoryAccessInfo[] Create(params string[] attributes)
@@ -89,16 +121,6 @@ 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>

+ 23 - 23
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -8,15 +8,35 @@ namespace SharpGLTF.Schema2
 {
     // https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
 
-    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplayLong(),nq}")]
     [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._AccessorDebugProxy))]
     public sealed partial class Accessor
     {
         #region debug
 
-        internal string _DebuggerDisplay_TryIdentifyContent()
+        internal string _GetDebuggerDisplayShort()
         {
-            return $"{Dimensions}_{Encoding}[{_count}]";
+            return $"{Encoding.ToDebugString(Dimensions, Normalized)}[{Count}]";
+        }
+
+        internal string _GetDebuggerDisplayLong()
+        {
+            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;
         }
 
         #endregion
@@ -85,26 +105,6 @@ 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;

+ 23 - 19
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -10,6 +10,28 @@ namespace SharpGLTF.Schema2
     [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public sealed partial class BufferView
     {
+        #region debug
+
+        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;
+        }
+
+        #endregion
+
         #region lifecycle
 
         internal BufferView() { }
@@ -74,24 +96,6 @@ 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>
@@ -125,7 +129,7 @@ namespace SharpGLTF.Schema2
                 .OrderBy(item => item.ByteOffset)
                 .ToList();
 
-            return String.Join(" ", accessors.Select(item => item._DebuggerDisplay_TryIdentifyContent()));
+            return String.Join(" ", accessors.Select(item => item._GetDebuggerDisplayShort()));
         }
 
         /// <summary>

+ 17 - 1
src/SharpGLTF.Core/Schema2/gltf.Mesh.cs

@@ -6,9 +6,25 @@ using SharpGLTF.Collections;
 
 namespace SharpGLTF.Schema2
 {
-    [System.Diagnostics.DebuggerDisplay("Mesh[{LogicalIndex}] {Name}")]
+    [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
+    [System.Diagnostics.DebuggerTypeProxy(typeof(Debug._MeshDebugProxy))]
     public sealed partial class Mesh
     {
+        #region debug
+
+        private String _DebuggerDisplay()
+        {
+            var txt = $"Mesh[{this.LogicalIndex}]";
+
+            if (!string.IsNullOrWhiteSpace(this.Name)) txt += $" {this.Name}";
+
+            txt += $" Primitives[{this.Primitives.Count}]";
+
+            return txt;
+        }
+
+        #endregion
+
         #region lifecycle
 
         internal Mesh()

+ 44 - 3
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -6,14 +6,55 @@ using SharpGLTF.Collections;
 
 namespace SharpGLTF.Schema2
 {
-    [System.Diagnostics.DebuggerDisplay("MeshPrimitive[{LogicalIndex}] {_mode} {_DebuggerDisplay_TryIdentifyContent()}")]
+    [System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
     public sealed partial class MeshPrimitive : IChildOf<Mesh>
     {
         #region debug
 
-        private String _DebuggerDisplay_TryIdentifyContent()
+        private String _DebuggerDisplay()
         {
-            return String.Join(" ", VertexAccessors.Keys);
+            var txt = $" Primitive[{this.LogicalIndex}]";
+
+            return _DebuggerDisplay(txt);
+        }
+
+        internal string _DebuggerDisplay(string txt)
+        {
+            if (this.Material != null)
+            {
+                if (string.IsNullOrWhiteSpace(this.Material.Name)) txt += $" Material[{this.Material.LogicalIndex}]";
+                else txt += $" Material {this.Material.Name}";
+            }
+
+            var vcount = this.VertexAccessors.Values
+                .Select(item => item.Count)
+                .Distinct();
+
+            if (vcount.Count() > 1)
+            {
+                var vAccessors = this.VertexAccessors
+                .Select(item => $"{Memory.MemoryAccessInfo.GetAttributeShortName(item.Key)}={item.Value._GetDebuggerDisplayShort()}")
+                .ToList();
+
+                txt += $" Vrts: {String.Join(" ", vAccessors)} ⚠️Vertex Count mismatch⚠️";
+            }
+            else
+            {
+                string toShort(string name, Accessor accessor)
+                {
+                    name = Memory.MemoryAccessInfo.GetAttributeShortName(name);
+                    var t = accessor.Encoding.ToDebugString(accessor.Dimensions, accessor.Normalized);
+                    return $"{name}.{t}";
+                }
+
+                var vAccessors = this.VertexAccessors
+                    .Select(item => toShort(item.Key, item.Value))
+                    .ToList();
+
+                txt += $" Vrts: ( {String.Join(" ", vAccessors)} )[{vcount.First()}]";
+            }
+
+            return txt;
         }
 
         #endregion

+ 42 - 1
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -12,9 +12,50 @@ namespace SharpGLTF.Schema2
         Node CreateNode(string name = null);
     }
 
-    [System.Diagnostics.DebuggerDisplay("Node[{LogicalIndex}] {Name} SkinJoint:{IsSkinJoint} T:{LocalTransform.Translation.X} {LocalTransform.Translation.Y} {LocalTransform.Translation.Z}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public sealed partial class Node : IVisualNodeContainer
     {
+        #region debug
+
+        private string _GetDebuggerDisplay()
+        {
+            var txt = $"Node[{LogicalIndex}ᴵᵈˣ]";
+
+            if (!string.IsNullOrWhiteSpace(this.Name)) txt += $" {this.Name}";
+
+            if (_matrix.HasValue)
+            {
+                if (_matrix.Value != Matrix4x4.Identity)
+                {
+                    var xform = this.LocalTransform;
+                    if (xform.Scale != Vector3.One) txt += $" 𝐒:{xform.Scale}";
+                    if (xform.Rotation != Quaternion.Identity) txt += $" 𝐑:{xform.Rotation}";
+                    if (xform.Translation != Vector3.Zero) txt += $" 𝚻:{xform.Translation}";
+                }
+            }
+            else
+            {
+                if (_scale.HasValue) txt += $" 𝐒:{_scale.Value}";
+                if (_rotation.HasValue) txt += $" 𝐑:{_rotation.Value}";
+                if (_translation.HasValue) txt += $" 𝚻:{_translation.Value}";
+            }
+
+            if (this.Mesh != null)
+            {
+                if (this.Skin != null) txt += $" ⇨ Skin[{this.Skin.LogicalIndex}ᴵᵈˣ]";
+                txt += $" ⇨ Mesh[{this.Mesh.LogicalIndex}ᴵᵈˣ]";
+            }
+
+            if (this.VisualChildren.Any())
+            {
+                txt += $" | Children[{this.VisualChildren.Count()}]";
+            }
+
+            return txt;
+        }
+
+        #endregion
+
         #region constants
 
         private const string _NOTRANSFORMMESSAGE = "Node instances with a Skin must not contain spatial transformations.";

+ 4 - 4
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -30,13 +30,13 @@ namespace SharpGLTF.Transforms
         {
             if (matrix.HasValue)
             {
-                Matrix4x4.Decompose(matrix.Value, out Scale, out Rotation, out Translation);
+                Matrix4x4.Decompose(matrix.Value, out this.Scale, out this.Rotation, out this.Translation);
             }
             else
             {
-                Rotation = rotation ?? Quaternion.Identity;
-                Scale = scale ?? Vector3.One;
-                Translation = translation ?? Vector3.Zero;
+                this.Scale = scale ?? Vector3.One;
+                this.Rotation = rotation ?? Quaternion.Identity;
+                this.Translation = translation ?? Vector3.Zero;
             }
         }
 

+ 16 - 1
src/SharpGLTF.Core/Transforms/SparseWeight8.cs

@@ -15,9 +15,24 @@ namespace SharpGLTF.Transforms
     /// - As an utility class to define per vertex joint weights in mesh skinning.
     /// - As an animation key in morph targets; a mesh can have many morph targets, but realistically and due to GPU limitations, only up to 8 morph targets can be blended at the same time.
     /// </remarks>
-    [System.Diagnostics.DebuggerDisplay("[{Index0}]={Weight0}  [{Index1}]={Weight1}  [{Index2}]={Weight2}  [{Index3}]={Weight3}  [{Index4}]={Weight4}  [{Index5}]={Weight5}  [{Index6}]={Weight6}  [{Index7}]={Weight7}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public readonly struct SparseWeight8
     {
+        #region debug
+
+        private string _GetDebuggerDisplay()
+        {
+            var iw = this.GetIndexedWeights()
+                .Where(item => item.Item2 != 0)
+                .Select(item => $"[{item.Item1}]={item.Item2}");
+
+            var txt = string.Join(" ", iw);
+
+            return string.IsNullOrWhiteSpace(txt) ? "Empty" : txt;
+        }
+
+        #endregion
+
         #region constructors
 
         /// <summary>

+ 3 - 2
src/SharpGLTF.Toolkit/Geometry/PackedPrimitiveBuilder.cs

@@ -102,9 +102,10 @@ namespace SharpGLTF.Geometry
                 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
+                var tAccessor = VertexTypes.VertexUtils.CreateVertexMemoryAccessors(mtv, "MORPHTANGENT");
+                if (tAccessor != null) vAccessors.Add(tAccessor);
 
-                AddMorphTarget(pAccessor, nAccessor);
+                AddMorphTarget(pAccessor, nAccessor, tAccessor);
             }
         }
 

+ 77 - 3
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs

@@ -26,9 +26,15 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐏:{Position}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public struct VertexPosition : IVertexGeometry
     {
+        #region debug
+
+        private string _GetDebuggerDisplay() => $"𝐏:{Position}";
+
+        #endregion
+
         #region constructors
 
         public VertexPosition(Vector3 position)
@@ -100,9 +106,15 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position and a Normal.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐏:{Position} 𝚴:{Normal}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public struct VertexPositionNormal : IVertexGeometry
     {
+        #region debug
+
+        private string _GetDebuggerDisplay() => $"𝐏:{Position} 𝚴:{Normal}";
+
+        #endregion
+
         #region constructors
 
         public VertexPositionNormal(Vector3 p, Vector3 n)
@@ -182,9 +194,15 @@ namespace SharpGLTF.Geometry.VertexTypes
     /// <summary>
     /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
     /// </summary>
-    [System.Diagnostics.DebuggerDisplay("𝐏:{Position} 𝚴:{Normal} 𝚻:{Tangent}")]
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
     public struct VertexPositionNormalTangent : IVertexGeometry
     {
+        #region debug
+
+        private string _GetDebuggerDisplay() => $"𝐏:{Position} 𝚴:{Normal} 𝚻:{Tangent}";
+
+        #endregion
+
         #region constructors
 
         public VertexPositionNormalTangent(Vector3 p, Vector3 n, Vector4 t)
@@ -263,4 +281,60 @@ namespace SharpGLTF.Geometry.VertexTypes
 
         #endregion
     }
+
+    /// <summary>
+    /// Defines a Vertex attribute with a Position, a Normal and a Tangent.
+    /// </summary>
+    [System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
+    struct VertexPositionNormalTangentDisplacement : IVertexGeometry
+    {
+        #region debug
+
+        private string _GetDebuggerDisplay() => $"𝐏:{Position} 𝚴:{Normal} 𝚻:{Tangent}";
+
+        #endregion
+
+        #region data
+
+        [VertexAttribute("POSITION")]
+        public Vector3 Position;
+
+        [VertexAttribute("NORMAL")]
+        public Vector3 Normal;
+
+        [VertexAttribute("TANGENT")]
+        public Vector3 Tangent;
+
+        #endregion
+
+        #region API
+
+        void IVertexGeometry.SetPosition(Vector3 position) { this.Position = position; }
+
+        void IVertexGeometry.SetNormal(Vector3 normal) { this.Normal = normal; }
+
+        void IVertexGeometry.SetTangent(Vector4 tangent) { this.Tangent = new Vector3(tangent.X, tangent.Y, tangent.Z); }
+
+        IVertexGeometry IVertexGeometry.ToAbsoluteMorph(IVertexGeometry baseValue)
+        {
+            throw new NotSupportedException();
+        }
+
+        IVertexGeometry IVertexGeometry.ToDisplaceMorph(IVertexGeometry baseValue)
+        {
+            throw new NotSupportedException();
+        }
+
+        public Vector3 GetPosition() { return this.Position; }
+
+        public bool TryGetNormal(out Vector3 normal) { normal = this.Normal; return true; }
+
+        public bool TryGetTangent(out Vector4 tangent) { tangent = new Vector4(this.Tangent, 0); return true; }
+
+        public void ApplyTransform(Matrix4x4 xform) { throw new NotSupportedException(); }
+
+        public void Validate() { }
+
+        #endregion
+    }
 }

+ 15 - 2
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -215,10 +215,14 @@ namespace SharpGLTF.Geometry.VertexTypes
             // determine the vertex attributes from the first vertex.
             var attributes = GetVertexAttributes(vertices[0], vertices.Count);
 
+            var isMorphTangent = attributeName == "MORPHTANGENT";
+            if (isMorphTangent) attributeName = "TANGENT";
+
             var attribute = attributes.FirstOrDefault(item => item.Name == attributeName);
             if (attribute.Name == null) return null;
             attribute.ByteOffset = 0;
             attribute.ByteStride = 0;
+            if (isMorphTangent) attribute.Dimensions = Schema2.DimensionType.VEC3;
 
             // create a buffer
             var vbuffer = new ArraySegment<byte>(new Byte[attribute.PaddedByteLength * vertices.Count]);
@@ -226,7 +230,15 @@ namespace SharpGLTF.Geometry.VertexTypes
             // fill the buffer with the vertex attributes.
             var accessor = new MemoryAccessor(vbuffer, attribute);
 
-            accessor.FillAccessor(vertices);
+            if (isMorphTangent)
+            {
+                var columnFunc = _GetVertexBuilderAttributeFunc("MORPHTANGENT");
+                accessor.AsVector3Array().Fill(vertices._GetColumn<TVertex, Vector3>(columnFunc));
+            }
+            else
+            {
+                accessor.FillAccessor(vertices);
+            }
 
             return accessor;
         }
@@ -345,7 +357,8 @@ namespace SharpGLTF.Geometry.VertexTypes
         {
             if (attributeName == "POSITION") return v => v.GetGeometry().GetPosition();
             if (attributeName == "NORMAL") return v => { return v.GetGeometry().TryGetNormal(out Vector3 n) ? n : Vector3.Zero; };
-            if (attributeName == "TANGENT") return v => { return v.GetGeometry().TryGetTangent(out Vector4 n) ? n : Vector4.Zero; };
+            if (attributeName == "TANGENT") return v => { return v.GetGeometry().TryGetTangent(out Vector4 t) ? t : Vector4.Zero; };
+            if (attributeName == "MORPHTANGENT") return v => { return v.GetGeometry().TryGetTangent(out Vector4 t) ? new Vector3(t.X, t.Y, t.Z) : Vector3.Zero; };
 
             if (attributeName == "COLOR_0") return v => { var m = v.GetMaterial(); return m.MaxColors <= 0 ? Vector4.One : m.GetColor(0); };
             if (attributeName == "COLOR_1") return v => { var m = v.GetMaterial(); return m.MaxColors <= 1 ? Vector4.One : m.GetColor(1); };

+ 1 - 1
src/SharpGLTF.Toolkit/Scenes/SceneBuilder.Schema2.cs

@@ -116,7 +116,7 @@ namespace SharpGLTF.Scenes
                 // Copies all the animations to the target node.
                 if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
                 if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
-                if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);                
+                if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
             }
             else
             {

+ 1 - 0
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -679,6 +679,7 @@ namespace SharpGLTF.Schema2
                 .ToArray();
 
             var dstMesh = MeshBuilderToolkit.CreateMeshBuilderFromVertexAttributes<Materials.MaterialBuilder>(vertexAttributes);
+            dstMesh.Name = srcMesh.Name;
 
             Materials.MaterialBuilder defMat = null;
 

+ 11 - 9
tests/SharpGLTF.Tests/Scenes/SceneBuilderTests.cs

@@ -430,19 +430,21 @@ namespace SharpGLTF.Scenes
         }
 
 
-        [TestCase("Avocado.glb")]
-        [TestCase("GearboxAssy.glb")]
-        [TestCase("RiggedFigure.glb")]
-        [TestCase("RiggedSimple.glb")]
-        [TestCase("BoxAnimated.glb")]
         [TestCase("AnimatedMorphCube.glb")]
         [TestCase("AnimatedMorphSphere.glb")]
+        [TestCase("Avocado.glb")]
+        [TestCase("BoxAnimated.glb")]
+        [TestCase("BrainStem.glb")]
         [TestCase("CesiumMan.glb")]
+        [TestCase("GearboxAssy.glb")]
         [TestCase("Monster.glb")]
-        [TestCase("BrainStem.glb")]
+        [TestCase("OrientationTest.glb")]
+        [TestCase("RiggedFigure.glb")]
+        [TestCase("RiggedSimple.glb")]
         public void TestRoundTrip(string path)
         {
             TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
 
             path = TestFiles
                 .GetSampleModelsPaths()
@@ -481,8 +483,8 @@ namespace SharpGLTF.Scenes
             // save file
 
             path = System.IO.Path.GetFileNameWithoutExtension(path);
-            srcModel.AttachToCurrentTest(path +"_src" + ".glb");
-            rowModel.AttachToCurrentTest(path +"_row" + ".glb");
+            srcModel.AttachToCurrentTest(path + "_src" + ".glb");
+            rowModel.AttachToCurrentTest(path + "_row" + ".glb");
             colModel.AttachToCurrentTest(path + "_col" + ".glb");
 
             srcModel.AttachToCurrentTest(path + "_src" + ".gltf");
@@ -498,7 +500,7 @@ namespace SharpGLTF.Scenes
                 srcModel.AttachToCurrentTest(path + "_src_at01" + ".obj", srcModel.LogicalAnimations[0], 0.1f);
 
                 if (rowModel.LogicalAnimations.Count > 0)
-                    rowModel.AttachToCurrentTest(path + "_bis_at01" + ".obj", rowModel.LogicalAnimations[0], 0.1f);
+                    rowModel.AttachToCurrentTest(path + "_row_at01" + ".obj", rowModel.LogicalAnimations[0], 0.1f);
             }
         }