Browse Source

progress on skins, morphs and animation.

Vicente Penades 6 years ago
parent
commit
776dfb2307

+ 20 - 0
src/Shared/_Extensions.cs

@@ -224,6 +224,26 @@ namespace SharpGLTF
             return _sampler;
         }
 
+        internal static Func<float, float[]> GetLinearSamplerFunc(this IEnumerable<(float, float[])> collection)
+        {
+            if (collection == null) return null;
+
+            float[] _sampler(float offset)
+            {
+                var sample = collection.GetSample(offset);
+                var result = new float[sample.Item1.Length];
+
+                for (int i = 0; i < result.Length; ++i)
+                {
+                    result[i] = sample.Item1[i] * (1 - sample.Item3) + sample.Item2[i] * sample.Item3;
+                }
+
+                return result;
+            }
+
+            return _sampler;
+        }
+
         #endregion
 
         #region linq

+ 8 - 0
src/SharpGLTF.Core/Memory/MemoryAccessor.cs

@@ -317,6 +317,14 @@ namespace SharpGLTF.Memory
             return new Matrix4x4Array(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, _Attribute.Encoding, _Attribute.Normalized);
         }
 
+        public MultiArray AsMultiArray(int dimensions)
+        {
+            Guard.IsTrue(_Attribute.IsValidVertexAttribute, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.Dimensions == DIMENSIONS.SCALAR, nameof(_Attribute));
+            Guard.IsTrue(_Attribute.ByteStride == 0, nameof(_Attribute));
+            return new MultiArray(_Data, _Attribute.ByteOffset, _Attribute.ItemsCount, _Attribute.ByteStride, dimensions, _Attribute.Encoding, _Attribute.Normalized);
+        }
+
         #endregion
     }
 }

+ 9 - 0
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -262,6 +262,15 @@ namespace SharpGLTF.Schema2
             throw new NotImplementedException();
         }
 
+        public IList<Single[]> AsMultiArray(int dimensions)
+        {
+            var memory = _GetMemoryAccessor();
+
+            if (this._sparse == null) return memory.AsMultiArray(dimensions);
+
+            throw new NotImplementedException();
+        }
+
         public ArraySegment<Byte> TryGetVertexBytes(int vertexIdx)
         {
             if (_sparse != null) throw new InvalidOperationException("Can't be used on Acessors with Sparse Data");

+ 46 - 4
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -32,6 +32,8 @@ namespace SharpGLTF.Schema2
 
         internal IReadOnlyList<AnimationChannel> _Channels => _channels;
 
+        public float Duration => _samplers.Select(item => item.Duration).Max();
+
         #endregion
 
         #region API
@@ -157,6 +159,40 @@ namespace SharpGLTF.Schema2
             return channel.Sampler.AsLinearVector3KeyFrames();
         }
 
+        public Transforms.AffineTransform GetLocalTransform(Node node, float time)
+        {
+            Guard.MustShareLogicalParent(this, node, nameof(node));
+
+            var xform = node.LocalTransform;
+
+            var sfunc = this.FindScaleChannel(node).GetLinearSamplerFunc();
+            var rfunc = this.FindRotationChannel(node).GetLinearSamplerFunc();
+            var tfunc = this.FindTranslationChannel(node).GetLinearSamplerFunc();
+
+            if (sfunc != null) xform.Scale = sfunc(time);
+            if (rfunc != null) xform.Rotation = rfunc(time);
+            if (tfunc != null) xform.Translation = tfunc(time);
+
+            return xform;
+        }
+
+        public IReadOnlyList<float> GetMorphWeights(Node node, float time)
+        {
+            var morphWeights = node.MorphWeights;
+            if (morphWeights == null || morphWeights.Count == 0) return morphWeights;
+
+            Guard.MustShareLogicalParent(this, node, nameof(node));
+
+            var channel = _channels.FirstOrDefault(item => item.TargetNode == node && item.TargetNodePath == PropertyPath.weights);
+            if (channel == null) return morphWeights;
+
+            var frames = channel.Sampler.AsLinearArrayKeyFrames(morphWeights.Count);
+
+            var mfunc = frames.GetLinearSamplerFunc();
+
+            return mfunc(time);
+        }
+
         #endregion
     }
 
@@ -286,6 +322,8 @@ namespace SharpGLTF.Schema2
 
         public Accessor Output => this.LogicalParent.LogicalParent.LogicalAccessors[this._output];
 
+        public float Duration { get { var keys = Input.AsScalarArray(); return keys[keys.Count - 1]; } }
+
         #endregion
 
         #region API
@@ -411,8 +449,6 @@ namespace SharpGLTF.Schema2
         {
             if (this.InterpolationMode == AnimationInterpolationMode.CUBICSPLINE) throw new ArgumentException();
 
-            var dict = new Dictionary<Single, Vector3>();
-
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsVector3Array();
 
@@ -421,14 +457,20 @@ namespace SharpGLTF.Schema2
 
         public IEnumerable<(Single, Quaternion)> AsLinearQuaternionKeyFrames()
         {
-            var dict = new Dictionary<Single, Quaternion>();
-
             var keys = this.Input.AsScalarArray();
             var frames = this.Output.AsQuaternionArray();
 
             return keys.Zip(frames, (key, val) => (key, val));
         }
 
+        public IEnumerable<(Single, Single[])> AsLinearArrayKeyFrames(int dimensions)
+        {
+            var keys = this.Input.AsScalarArray();
+            var frames = this.Output.AsMultiArray(dimensions);
+
+            return keys.Zip(frames, (key, val) => (key, val));
+        }
+
         #endregion
     }
 

+ 3 - 24
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -124,19 +124,9 @@ namespace SharpGLTF.Schema2
 
         public Transforms.AffineTransform GetLocalTransform(Animation animation, float time)
         {
-            Guard.MustShareLogicalParent(this, animation, nameof(animation));
+            if (animation == null) return this.LocalTransform;
 
-            var xform = this.LocalTransform;
-
-            var sfunc = animation.FindScaleChannel(this).GetLinearSamplerFunc();
-            var rfunc = animation.FindRotationChannel(this).GetLinearSamplerFunc();
-            var tfunc = animation.FindTranslationChannel(this).GetLinearSamplerFunc();
-
-            if (sfunc != null) xform.Scale = sfunc(time);
-            if (rfunc != null) xform.Rotation = rfunc(time);
-            if (tfunc != null) xform.Translation = tfunc(time);
-
-            return xform;
+            return animation.GetLocalTransform(this, time);
         }
 
         /// <summary>
@@ -183,18 +173,7 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="Transforms.ITransform"/> object</returns>
         public Transforms.ITransform GetMeshWorldTransform(Animation animation, float time)
         {
-            var weights = this.MorphWeights;
-
-            if (weights != null && weights.Count == 0) weights = null;
-
-            if (weights != null && animation != null)
-            {
-                Guard.MustShareLogicalParent(this, animation, nameof(animation));
-                
-                // TODO: get input only (time) channel, and create
-                // var mfunc = animation.FindMorphingChannel(this).GetSamplerFunc();
-
-            }
+            var weights = animation == null ? this.MorphWeights : animation.GetMorphWeights(this, time);
 
             if (this.Skin == null) return new Transforms.StaticTransform(this.GetWorldMatrix(animation, time), weights);
 

+ 24 - 4
src/SharpGLTF.Core/Transforms/MeshTransforms.cs

@@ -10,6 +10,9 @@ using V4 = System.Numerics.Vector4;
 
 namespace SharpGLTF.Transforms
 {
+    /// <summary>
+    /// Interface for a mesh transform object
+    /// </summary>
     public interface ITransform
     {
         V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights);
@@ -29,12 +32,27 @@ namespace SharpGLTF.Transforms
                 return;
             }
 
-            _InvWeight = 1 - morphWeights.Sum();
-            if (_InvWeight == 1) return;
+            var sum = morphWeights.Sum();
 
-            _MorphWeights = new float[morphWeights.Count];
+            if (sum == 0)
+            {
+                _InvWeight = 1;
+                return;
+            }
 
+            _MorphWeights = new float[morphWeights.Count];
             for (int i = 0; i < morphWeights.Count; ++i) _MorphWeights[i] = morphWeights[i];
+
+            if (sum <= 1)
+            {
+                _InvWeight = 1 - sum;
+            }
+            else
+            {
+                _InvWeight = 0;
+                for (int i = 0; i < morphWeights.Count; ++i) _MorphWeights[i] = _MorphWeights[i] / sum;
+            }
+
         }
 
         #endregion
@@ -134,9 +152,11 @@ namespace SharpGLTF.Transforms
 
             var worldPosition = V3.Zero;
 
+            var wnrm = 1.0f / skinWeights.Sum(item => item.Item2);
+
             foreach (var jw in skinWeights)
             {
-                worldPosition += V3.Transform(localPosition, _JointTransforms[jw.Item1]) * jw.Item2;
+                worldPosition += V3.Transform(localPosition, _JointTransforms[jw.Item1]) * jw.Item2 * wnrm;
             }
 
             return worldPosition;

+ 20 - 20
src/SharpGLTF.Toolkit/Geometry/MeshBuilder.cs

@@ -26,7 +26,7 @@ namespace SharpGLTF.Geometry
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
     /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/> instance.</typeparam>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
@@ -50,8 +50,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
-    public class MeshBuilder<TMaterial, TvP, TvM, TvS> : IMeshBuilder<TMaterial>
-        where TvP : struct, IVertexGeometry
+    public class MeshBuilder<TMaterial, TvG, TvM, TvS> : IMeshBuilder<TMaterial>
+        where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
     {
@@ -66,7 +66,7 @@ namespace SharpGLTF.Geometry
 
         #region data
 
-        private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvP, TvM, TvS>>();
+        private readonly Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> _Primitives = new Dictionary<(TMaterial, int), PrimitiveBuilder<TMaterial, TvG, TvM, TvS>>();
 
         internal IPolygonTriangulator _Triangulator = NaivePolygonTriangulation.Default;
 
@@ -86,7 +86,7 @@ namespace SharpGLTF.Geometry
 
         public IEnumerable<TMaterial> Materials => _Primitives.Keys.Select(item => item.Item1).Distinct();
 
-        public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvP, TvM, TvS>> Primitives => _Primitives.Values;
+        public IReadOnlyCollection<PrimitiveBuilder<TMaterial, TvG, TvM, TvS>> Primitives => _Primitives.Values;
 
         IReadOnlyCollection<IPrimitive<TMaterial>> IMeshBuilder<TMaterial>.Primitives => _Primitives.Values;
 
@@ -94,18 +94,18 @@ namespace SharpGLTF.Geometry
 
         #region API
 
-        private PrimitiveBuilder<TMaterial, TvP, TvM, TvS> _UsePrimitive((TMaterial, int) key)
+        private PrimitiveBuilder<TMaterial, TvG, TvM, TvS> _UsePrimitive((TMaterial, int) key)
         {
-            if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive))
+            if (!_Primitives.TryGetValue(key, out PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive))
             {
-                primitive = new PrimitiveBuilder<TMaterial, TvP, TvM, TvS>(this, key.Item1, key.Item2, StrictMode);
+                primitive = new PrimitiveBuilder<TMaterial, TvG, TvM, TvS>(this, key.Item1, key.Item2, StrictMode);
                 _Primitives[key] = primitive;
             }
 
             return primitive;
         }
 
-        public PrimitiveBuilder<TMaterial, TvP, TvM, TvS> UsePrimitive(TMaterial material, int primitiveVertexCount = 3)
+        public PrimitiveBuilder<TMaterial, TvG, TvM, TvS> UsePrimitive(TMaterial material, int primitiveVertexCount = 3)
         {
             Guard.NotNull(material, nameof(material));
             Guard.MustBeBetweenOrEqualTo(primitiveVertexCount, 1, 3, nameof(primitiveVertexCount));
@@ -121,7 +121,7 @@ namespace SharpGLTF.Geometry
             return _UsePrimitive((material, primitiveVertexCount));
         }
 
-        public void AddMesh(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
+        public void AddMesh(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, Func<TMaterial, TMaterial> materialTransform, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
             foreach (var p in mesh.Primitives)
             {
@@ -136,7 +136,7 @@ namespace SharpGLTF.Geometry
         /// of the this <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}"/> using the given lambfa function.
         /// </summary>
         /// <param name="vertexTransform">A lambda function to transform <see cref="VertexBuilder{TvP, TvM, TvS}"/> vertices.</param>
-        public void TransformVertices(Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
+        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
             foreach (var p in Primitives) p.TransformVertices(vertexTransform);
         }
@@ -155,7 +155,7 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
@@ -179,8 +179,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x4"/>,
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
-    public class MeshBuilder<TvP, TvM, TvS> : MeshBuilder<Materials.MaterialBuilder, TvP, TvM, TvS>
-        where TvP : struct, IVertexGeometry
+    public class MeshBuilder<TvG, TvM, TvS> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
     {
@@ -191,7 +191,7 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
@@ -206,8 +206,8 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexTexture1"/>,
     /// <see cref="VertexColor1Texture1"/>.
     /// </typeparam>
-    public class MeshBuilder<TvP, TvM> : MeshBuilder<Materials.MaterialBuilder, TvP, TvM, VertexEmpty>
-        where TvP : struct, IVertexGeometry
+    public class MeshBuilder<TvG, TvM> : MeshBuilder<Materials.MaterialBuilder, TvG, TvM, VertexEmpty>
+        where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
     {
         public MeshBuilder(string name = null)
@@ -217,15 +217,15 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// Represents an utility class to help build meshes by adding primitives associated with a given material.
     /// </summary>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
     /// <see cref="VertexPositionNormal"/>,
     /// <see cref="VertexPositionNormalTangent"/>.
     /// </typeparam>
-    public class MeshBuilder<TvP> : MeshBuilder<Materials.MaterialBuilder, TvP, VertexEmpty, VertexEmpty>
-        where TvP : struct, IVertexGeometry
+    public class MeshBuilder<TvG> : MeshBuilder<Materials.MaterialBuilder, TvG, VertexEmpty, VertexEmpty>
+        where TvG : struct, IVertexGeometry
     {
         public MeshBuilder(string name = null)
             : base(name) { }

+ 26 - 26
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -15,8 +15,8 @@ namespace SharpGLTF.Geometry
 
         int VertexCount { get; }
 
-        VertexBuilder<TvPP, TvMM, TvSS> GetVertex<TvPP, TvMM, TvSS>(int index)
-            where TvPP : struct, IVertexGeometry
+        VertexBuilder<TvGG, TvMM, TvSS> GetVertex<TvGG, TvMM, TvSS>(int index)
+            where TvGG : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning;
 
@@ -31,13 +31,13 @@ namespace SharpGLTF.Geometry
 
     public interface IPrimitiveBuilder
     {
-        void AddTriangle<TvPP, TvMM, TvSS>
+        void AddTriangle<TvGG, TvMM, TvSS>
             (
-            VertexBuilder<TvPP, TvMM, TvSS> a,
-            VertexBuilder<TvPP, TvMM, TvSS> b,
-            VertexBuilder<TvPP, TvMM, TvSS> c
+            VertexBuilder<TvGG, TvMM, TvSS> a,
+            VertexBuilder<TvGG, TvMM, TvSS> b,
+            VertexBuilder<TvGG, TvMM, TvSS> c
             )
-            where TvPP : struct, IVertexGeometry
+            where TvGG : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning;
     }
@@ -46,7 +46,7 @@ namespace SharpGLTF.Geometry
     /// Represents an utility class to help build mesh primitives by adding triangles
     /// </summary>
     /// <typeparam name="TMaterial">The material type used by this <see cref="PrimitiveBuilder{TMaterial, TvP, TvM, TvS}"/> instance.</typeparam>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
@@ -71,14 +71,14 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
     [System.Diagnostics.DebuggerDisplay("Primitive {_Material}")]
-    public class PrimitiveBuilder<TMaterial, TvP, TvM, TvS> : IPrimitiveBuilder, IPrimitive<TMaterial>
-        where TvP : struct, IVertexGeometry
+    public class PrimitiveBuilder<TMaterial, TvG, TvM, TvS> : IPrimitiveBuilder, IPrimitive<TMaterial>
+        where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
     {
         #region lifecycle
 
-        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvP, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount, bool strict)
+        internal PrimitiveBuilder(MeshBuilder<TMaterial, TvG, TvM, TvS> mesh, TMaterial material, int primitiveVertexCount, bool strict)
         {
             this._Scrict = strict;
             this._Mesh = mesh;
@@ -92,20 +92,20 @@ namespace SharpGLTF.Geometry
 
         private readonly bool _Scrict;
 
-        private readonly MeshBuilder<TMaterial, TvP, TvM, TvS> _Mesh;
+        private readonly MeshBuilder<TMaterial, TvG, TvM, TvS> _Mesh;
 
         private readonly TMaterial _Material;
 
         private readonly int _PrimitiveVertexCount;
 
-        private readonly VertexList<VertexBuilder<TvP, TvM, TvS>> _Vertices = new VertexList<VertexBuilder<TvP, TvM, TvS>>();
+        private readonly VertexList<VertexBuilder<TvG, TvM, TvS>> _Vertices = new VertexList<VertexBuilder<TvG, TvM, TvS>>();
         private readonly List<int> _Indices = new List<int>();
 
         #endregion
 
         #region properties
 
-        public MeshBuilder<TMaterial, TvP, TvM, TvS> Mesh => _Mesh;
+        public MeshBuilder<TMaterial, TvG, TvM, TvS> Mesh => _Mesh;
 
         public TMaterial Material => _Material;
 
@@ -119,7 +119,7 @@ namespace SharpGLTF.Geometry
 
         public int VertexCount => Vertices.Count;
 
-        public IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> Vertices => _Vertices;
+        public IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> Vertices => _Vertices;
 
         public IReadOnlyList<int> Indices => _Indices;
 
@@ -138,12 +138,12 @@ namespace SharpGLTF.Geometry
         /// </summary>
         /// <param name="vertex">
         /// A vertex formed by
-        /// <typeparamref name="TvP"/>,
+        /// <typeparamref name="TvG"/>,
         /// <typeparamref name="TvM"/> and
         /// <typeparamref name="TvS"/> fragments.
         /// </param>
         /// <returns>The index of the vertex.</returns>
-        public int UseVertex(VertexBuilder<TvP, TvM, TvS> vertex)
+        public int UseVertex(VertexBuilder<TvG, TvM, TvS> vertex)
         {
             if (_Scrict) vertex.Validate();
 
@@ -154,7 +154,7 @@ namespace SharpGLTF.Geometry
         /// Adds a point.
         /// </summary>
         /// <param name="a">vertex for this point.</param>
-        public void AddPoint(VertexBuilder<TvP, TvM, TvS> a)
+        public void AddPoint(VertexBuilder<TvG, TvM, TvS> a)
         {
             Guard.IsTrue(_PrimitiveVertexCount == 1, nameof(VerticesPerPrimitive), "Points are not supported for this primitive");
 
@@ -166,7 +166,7 @@ namespace SharpGLTF.Geometry
         /// </summary>
         /// <param name="a">First corner of the line.</param>
         /// <param name="b">Second corner of the line.</param>
-        public void AddLine(VertexBuilder<TvP, TvM, TvS> a, VertexBuilder<TvP, TvM, TvS> b)
+        public void AddLine(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b)
         {
             Guard.IsTrue(_PrimitiveVertexCount == 2, nameof(VerticesPerPrimitive), "Lines are not supported for this primitive");
 
@@ -192,7 +192,7 @@ namespace SharpGLTF.Geometry
         /// <param name="a">First corner of the triangle.</param>
         /// <param name="b">Second corner of the triangle.</param>
         /// <param name="c">Third corner of the triangle.</param>
-        public void AddTriangle(VertexBuilder<TvP, TvM, TvS> a, VertexBuilder<TvP, TvM, TvS> b, VertexBuilder<TvP, TvM, TvS> c)
+        public void AddTriangle(VertexBuilder<TvG, TvM, TvS> a, VertexBuilder<TvG, TvM, TvS> b, VertexBuilder<TvG, TvM, TvS> c)
         {
             Guard.IsTrue(_PrimitiveVertexCount == 3, nameof(VerticesPerPrimitive), "Triangles are not supported for this primitive");
 
@@ -222,7 +222,7 @@ namespace SharpGLTF.Geometry
         /// Polygon triangulation is performed by a <see cref="IPolygonTriangulator"/>
         /// instance defined in <see cref="MeshBuilder{TMaterial, TvP, TvM, TvS}.Triangulator"/>
         /// </remarks>
-        public void AddPolygon(params VertexBuilder<TvP, TvM, TvS>[] points)
+        public void AddPolygon(params VertexBuilder<TvG, TvM, TvS>[] points)
         {
             Span<Vector3> vertices = stackalloc Vector3[points.Length];
 
@@ -249,7 +249,7 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvP, TvM, TvS> primitive, Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> vertexTransform)
+        internal void AddPrimitive(PrimitiveBuilder<TMaterial, TvG, TvM, TvS> primitive, Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> vertexTransform)
         {
             if (primitive == null) throw new ArgumentNullException(nameof(primitive));
 
@@ -306,9 +306,9 @@ namespace SharpGLTF.Geometry
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning
         {
-            var aa = a.ConvertTo<TvP, TvM, TvS>();
-            var bb = b.ConvertTo<TvP, TvM, TvS>();
-            var cc = c.ConvertTo<TvP, TvM, TvS>();
+            var aa = a.ConvertTo<TvG, TvM, TvS>();
+            var bb = b.ConvertTo<TvG, TvM, TvS>();
+            var cc = c.ConvertTo<TvG, TvM, TvS>();
 
             AddTriangle(aa, bb, cc);
         }
@@ -344,7 +344,7 @@ namespace SharpGLTF.Geometry
             return Schema2.PrimitiveType.TRIANGLES.GetTrianglesIndices(_Indices.Select(item => (uint)item));
         }
 
-        public void TransformVertices(Func<VertexBuilder<TvP, TvM, TvS>, VertexBuilder<TvP, TvM, TvS>> transformFunc)
+        public void TransformVertices(Func<VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>> transformFunc)
         {
             _Vertices.TransformVertices(transformFunc);
         }

+ 21 - 21
src/SharpGLTF.Toolkit/Geometry/VertexBuilder.cs

@@ -10,7 +10,7 @@ namespace SharpGLTF.Geometry
     /// <summary>
     /// Represents an individual vertex object.
     /// </summary>
-    /// <typeparam name="TvP">
+    /// <typeparam name="TvG">
     /// The vertex fragment type with Position, Normal and Tangent.
     /// Valid types are:
     /// <see cref="VertexPosition"/>,
@@ -35,21 +35,21 @@ namespace SharpGLTF.Geometry
     /// <see cref="VertexJoints16x8"/>.
     /// </typeparam>
     [System.Diagnostics.DebuggerDisplay("Vertex {Geometry} {Material} {Skinning}")]
-    public struct VertexBuilder<TvP, TvM, TvS>
-        where TvP : struct, IVertexGeometry
+    public struct VertexBuilder<TvG, TvM, TvS>
+        where TvG : struct, IVertexGeometry
         where TvM : struct, IVertexMaterial
         where TvS : struct, IVertexSkinning
     {
         #region constructors
 
-        public VertexBuilder(TvP g, TvM m, TvS s)
+        public VertexBuilder(TvG g, TvM m, TvS s)
         {
             Geometry = g;
             Material = m;
             Skinning = s;
         }
 
-        public VertexBuilder(TvP g, TvM m, params (int, float)[] bindings)
+        public VertexBuilder(TvG g, TvM m, params (int, float)[] bindings)
         {
             Geometry = g;
             Material = m;
@@ -61,52 +61,52 @@ namespace SharpGLTF.Geometry
             }
         }
 
-        public VertexBuilder(TvP g, TvM m)
+        public VertexBuilder(TvG g, TvM m)
         {
             Geometry = g;
             Material = m;
             Skinning = default;
         }
 
-        public VertexBuilder(TvP g, TvS s)
+        public VertexBuilder(TvG g, TvS s)
         {
             Geometry = g;
             Material = default;
             Skinning = s;
         }
 
-        public VertexBuilder(TvP g)
+        public VertexBuilder(TvG g)
         {
             Geometry = g;
             Material = default;
             Skinning = default;
         }
 
-        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvM, TvS) tuple)
+        public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM, TvS) tuple)
         {
-            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
+            return new VertexBuilder<TvG, TvM, TvS>(tuple.Item1, tuple.Item2, tuple.Item3);
         }
 
-        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvM) tuple)
+        public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvM) tuple)
         {
-            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
+            return new VertexBuilder<TvG, TvM, TvS>(tuple.Item1, tuple.Item2);
         }
 
-        public static implicit operator VertexBuilder<TvP, TvM, TvS>((TvP, TvS) tuple)
+        public static implicit operator VertexBuilder<TvG, TvM, TvS>((TvG, TvS) tuple)
         {
-            return new VertexBuilder<TvP, TvM, TvS>(tuple.Item1, tuple.Item2);
+            return new VertexBuilder<TvG, TvM, TvS>(tuple.Item1, tuple.Item2);
         }
 
-        public static implicit operator VertexBuilder<TvP, TvM, TvS>(TvP g)
+        public static implicit operator VertexBuilder<TvG, TvM, TvS>(TvG g)
         {
-            return new VertexBuilder<TvP, TvM, TvS>(g);
+            return new VertexBuilder<TvG, TvM, TvS>(g);
         }
 
         #endregion
 
         #region data
 
-        public TvP Geometry;
+        public TvG Geometry;
         public TvM Material;
         public TvS Skinning;
 
@@ -143,14 +143,14 @@ namespace SharpGLTF.Geometry
             return new VertexBuilder<TvPP, TvMM, TvSS>(p, m, s);
         }
 
-        public static MeshBuilder<TMaterial, TvP, TvM, TvS> CreateCompatibleMesh<TMaterial>(string name = null)
+        public static MeshBuilder<TMaterial, TvG, TvM, TvS> CreateCompatibleMesh<TMaterial>(string name = null)
         {
-            return new MeshBuilder<TMaterial, TvP, TvM, TvS>(name);
+            return new MeshBuilder<TMaterial, TvG, TvM, TvS>(name);
         }
 
-        public static MeshBuilder<TvP, TvM, TvS> CreateCompatibleMesh(string name = null)
+        public static MeshBuilder<TvG, TvM, TvS> CreateCompatibleMesh(string name = null)
         {
-            return new MeshBuilder<TvP, TvM, TvS>(name);
+            return new MeshBuilder<TvG, TvM, TvS>(name);
         }
 
         #endregion

+ 21 - 21
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexUtils.cs

@@ -11,8 +11,8 @@ namespace SharpGLTF.Geometry.VertexTypes
 {
     static class VertexUtils
     {
-        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TvP, TvM, TvS>(this IEnumerable<IReadOnlyList<VertexBuilder<TvP, TvM, TvS>>> vertexBlocks)
-            where TvP : struct, IVertexGeometry
+        public static IEnumerable<MemoryAccessor[]> CreateVertexMemoryAccessors<TvG, TvM, TvS>(this IEnumerable<IReadOnlyList<VertexBuilder<TvG, TvM, TvS>>> vertexBlocks)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
@@ -20,7 +20,7 @@ namespace SharpGLTF.Geometry.VertexTypes
             var totalCount = vertexBlocks.Sum(item => item.Count);
 
             // vertex attributes
-            var attributes = GetVertexAttributes(typeof(TvP), typeof(TvM), typeof(TvS), totalCount);
+            var attributes = GetVertexAttributes(typeof(TvG), typeof(TvM), typeof(TvS), totalCount);
 
             // create master vertex buffer
             int byteStride = attributes[0].ByteStride;
@@ -37,7 +37,7 @@ namespace SharpGLTF.Geometry.VertexTypes
 
                 foreach (var accessor in accessors)
                 {
-                    var columnFunc = GetItemValueFunc<TvP, TvM, TvS>(accessor.Attribute.Name);
+                    var columnFunc = GetItemValueFunc<TvG, TvM, TvS>(accessor.Attribute.Name);
 
                     if (accessor.Attribute.Dimensions == Schema2.DimensionType.SCALAR) accessor.AsScalarArray().Fill(block.GetScalarColumn(columnFunc));
                     if (accessor.Attribute.Dimensions == Schema2.DimensionType.VEC2) accessor.AsVector2Array().Fill(block.GetVector2Column(columnFunc));
@@ -141,12 +141,12 @@ namespace SharpGLTF.Geometry.VertexTypes
             return new MemoryAccessInfo(attribute.Name, 0, 0, 0, dimensions.Value, attribute.Encoding, attribute.Normalized);
         }
 
-        private static Func<VertexBuilder<TvP, TvM, TvS>, Object> GetItemValueFunc<TvP, TvM, TvS>(string attributeName)
-            where TvP : struct, IVertexGeometry
+        private static Func<VertexBuilder<TvG, TvM, TvS>, Object> GetItemValueFunc<TvG, TvM, TvS>(string attributeName)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
-            var finfo = GetVertexField(typeof(TvP), attributeName);
+            var finfo = GetVertexField(typeof(TvG), attributeName);
             if (finfo != null) return vertex => finfo.GetValue(vertex.Geometry);
 
             finfo = GetVertexField(typeof(TvM), attributeName);
@@ -158,40 +158,40 @@ namespace SharpGLTF.Geometry.VertexTypes
             throw new NotImplementedException();
         }
 
-        private static Single[] GetScalarColumn<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
-            where TvP : struct, IVertexGeometry
+        private static Single[] GetScalarColumn<TvG, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> vertices, Func<VertexBuilder<TvG, TvM, TvS>, Object> func)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
-            return GetColumn<TvP, TvM, TvS, Single>(vertices, func);
+            return GetColumn<TvG, TvM, TvS, Single>(vertices, func);
         }
 
-        private static Vector2[] GetVector2Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
-            where TvP : struct, IVertexGeometry
+        private static Vector2[] GetVector2Column<TvG, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> vertices, Func<VertexBuilder<TvG, TvM, TvS>, Object> func)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
-            return GetColumn<TvP, TvM, TvS, Vector2>(vertices, func);
+            return GetColumn<TvG, TvM, TvS, Vector2>(vertices, func);
         }
 
-        private static Vector3[] GetVector3Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
-            where TvP : struct, IVertexGeometry
+        private static Vector3[] GetVector3Column<TvG, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> vertices, Func<VertexBuilder<TvG, TvM, TvS>, Object> func)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
-            return GetColumn<TvP, TvM, TvS, Vector3>(vertices, func);
+            return GetColumn<TvG, TvM, TvS, Vector3>(vertices, func);
         }
 
-        private static Vector4[] GetVector4Column<TvP, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
-            where TvP : struct, IVertexGeometry
+        private static Vector4[] GetVector4Column<TvG, TvM, TvS>(this IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> vertices, Func<VertexBuilder<TvG, TvM, TvS>, Object> func)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {
-            return GetColumn<TvP, TvM, TvS, Vector4>(vertices, func);
+            return GetColumn<TvG, TvM, TvS, Vector4>(vertices, func);
         }
 
-        private static TColumn[] GetColumn<TvP, TvM, TvS, TColumn>(this IReadOnlyList<VertexBuilder<TvP, TvM, TvS>> vertices, Func<VertexBuilder<TvP, TvM, TvS>, Object> func)
-            where TvP : struct, IVertexGeometry
+        private static TColumn[] GetColumn<TvG, TvM, TvS, TColumn>(this IReadOnlyList<VertexBuilder<TvG, TvM, TvS>> vertices, Func<VertexBuilder<TvG, TvM, TvS>, Object> func)
+            where TvG : struct, IVertexGeometry
             where TvM : struct, IVertexMaterial
             where TvS : struct, IVertexSkinning
         {

+ 3 - 3
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -15,7 +15,7 @@ namespace SharpGLTF.IO
     using VERTEX = Geometry.VertexBuilder<Geometry.VertexTypes.VertexPositionNormal, Geometry.VertexTypes.VertexTexture1, Geometry.VertexTypes.VertexEmpty>;
     using VGEOMETRY = Geometry.VertexTypes.VertexPositionNormal;
     using VMATERIAL = Geometry.VertexTypes.VertexTexture1;
-    using VSKINNING = Geometry.VertexTypes.VertexEmpty;
+    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
 
     /// <summary>
     /// Tiny wavefront object writer
@@ -32,7 +32,7 @@ namespace SharpGLTF.IO
             public BYTES DiffuseTexture;
         }
 
-        private readonly Geometry.MeshBuilder<Material, VGEOMETRY, VMATERIAL, VSKINNING> _Mesh = new Geometry.MeshBuilder<Material, VGEOMETRY, VMATERIAL, VSKINNING>();
+        private readonly Geometry.MeshBuilder<Material, VGEOMETRY, VMATERIAL, VEMPTY> _Mesh = new Geometry.MeshBuilder<Material, VGEOMETRY, VMATERIAL, VEMPTY>();
 
         #endregion
 
@@ -233,7 +233,7 @@ namespace SharpGLTF.IO
 
         public void AddModel(ModelRoot model)
         {
-            foreach (var triangle in Schema2Toolkit.Triangulate<VGEOMETRY, VMATERIAL, VSKINNING>(model.DefaultScene))
+            foreach (var triangle in Schema2Toolkit.Triangulate<VGEOMETRY, VMATERIAL>(model.DefaultScene))
             {
                 var dstMaterial = default(Material);
 

+ 56 - 55
src/SharpGLTF.Toolkit/Schema2/MeshExtensions.cs

@@ -8,6 +8,11 @@ using SharpGLTF.Memory;
 
 namespace SharpGLTF.Schema2
 {
+    using Geometry;
+    using Geometry.VertexTypes;
+
+    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
+
     public static partial class Schema2Toolkit
     {
         #region meshes
@@ -265,63 +270,55 @@ namespace SharpGLTF.Schema2
 
         #region evaluation
 
-        public static IEnumerable<((TvP, TvM, TvS), (TvP, TvM, TvS), (TvP, TvM, TvS), Material)> Triangulate<TvP, TvM, TvS>(this Mesh mesh, Matrix4x4 xform)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
-            where TvS : struct, Geometry.VertexTypes.IVertexSkinning
-        {
-            var normals = mesh.GetComputedNormals();
-
-            return mesh.Primitives.SelectMany(item => item.Triangulate<TvP, TvM, TvS>(xform, normals));
-        }
-
-        public static IEnumerable<((TvP, TvM), (TvP, TvM), (TvP, TvM), Material)> Triangulate<TvP, TvM>(this Mesh mesh, Transforms.ITransform xform)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> Triangulate<TvG, TvM, TvS>(this Mesh mesh)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+            where TvS : struct, IVertexSkinning
         {
-            var normals = mesh.GetComputedNormals();
-
-            return mesh.Primitives.SelectMany(item => item.Triangulate<TvP, TvM>(xform, normals));
+            return mesh.Primitives.SelectMany(item => item.Triangulate<TvG, TvM, TvS>());
         }
 
-        public static IEnumerable<((TvP, TvM, TvS), (TvP, TvM, TvS), (TvP, TvM, TvS), Material)> Triangulate<TvP, TvM, TvS>(this MeshPrimitive prim, Matrix4x4 xform, IReadOnlyDictionary<Vector3, Vector3> defaultNormals)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
-            where TvS : struct, Geometry.VertexTypes.IVertexSkinning
+        public static IEnumerable<(VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, VertexBuilder<TvG, TvM, TvS>, Material)> Triangulate<TvG, TvM, TvS>(this MeshPrimitive prim)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+            where TvS : struct, IVertexSkinning
         {
             var vertices = prim.GetVertexColumns();
-            if (vertices.Normals == null && defaultNormals != null) vertices.ApplyNormals(defaultNormals);
-
             var triangles = prim.GetTriangleIndices();
 
+            bool hasNormals = vertices.Normals != null;
+
             foreach (var t in triangles)
             {
-                var ap = vertices.GetVertexGeometry<TvP>(t.Item1);
-                var bp = vertices.GetVertexGeometry<TvP>(t.Item2);
-                var cp = vertices.GetVertexGeometry<TvP>(t.Item3);
-
-                ap.Transform(xform);
-                bp.Transform(xform);
-                cp.Transform(xform);
+                var a = vertices.GetVertex<TvG, TvM, TvS>(t.Item1);
+                var b = vertices.GetVertex<TvG, TvM, TvS>(t.Item2);
+                var c = vertices.GetVertex<TvG, TvM, TvS>(t.Item3);
 
-                var am = vertices.GetVertexMaterial<TvM>(t.Item1);
-                var bm = vertices.GetVertexMaterial<TvM>(t.Item2);
-                var cm = vertices.GetVertexMaterial<TvM>(t.Item3);
-
-                var aj = vertices.GetVertexSkinning<TvS>(t.Item1);
-                var bj = vertices.GetVertexSkinning<TvS>(t.Item2);
-                var cj = vertices.GetVertexSkinning<TvS>(t.Item3);
+                if (!hasNormals)
+                {
+                    var n = Vector3.Cross(b.Position - a.Position, c.Position - a.Position);
+                    n = Vector3.Normalize(n);
+                    a.Geometry.SetNormal(n);
+                    b.Geometry.SetNormal(n);
+                    c.Geometry.SetNormal(n);
+                }
 
-                yield return ((ap, am, aj), (bp, bm, bj), (cp, cm, cj), prim.Material);
+                yield return (a, b, c, prim.Material);
             }
         }
 
-        public static IEnumerable<((TvP, TvM), (TvP, TvM), (TvP, TvM), Material)> Triangulate<TvP, TvM>(this MeshPrimitive prim, Transforms.ITransform xform, IReadOnlyDictionary<Vector3, Vector3> defaultNormals)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, Material)> Triangulate<TvG, TvM>(this Mesh mesh, Transforms.ITransform xform)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+        {
+            return mesh.Primitives.SelectMany(item => item.Triangulate<TvG, TvM>(xform));
+        }
+
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, Material)> Triangulate<TvG, TvM>(this MeshPrimitive prim, Transforms.ITransform xform)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
         {
             var vertices = prim.GetVertexColumns();
-            if (vertices.Normals == null && defaultNormals != null) vertices.ApplyNormals(defaultNormals);
 
             vertices.ApplyTransform(xform);
 
@@ -331,17 +328,17 @@ namespace SharpGLTF.Schema2
 
             foreach (var t in triangles)
             {
-                var a = vertices.GetVertex<TvP, TvM>(t.Item1);
-                var b = vertices.GetVertex<TvP, TvM>(t.Item2);
-                var c = vertices.GetVertex<TvP, TvM>(t.Item3);
+                var a = vertices.GetVertex<TvG, TvM>(t.Item1);
+                var b = vertices.GetVertex<TvG, TvM>(t.Item2);
+                var c = vertices.GetVertex<TvG, TvM>(t.Item3);
 
                 yield return ((a.Geometry, a.Material), (b.Geometry, b.Material), (c.Geometry, c.Material), prim.Material);
             }
         }
 
-        public static Geometry.VertexColumns GetVertexColumns(this MeshPrimitive primitive)
+        public static VertexColumns GetVertexColumns(this MeshPrimitive primitive)
         {
-            var columns = new Geometry.VertexColumns();
+            var columns = new VertexColumns();
 
             _CopyTo(primitive.VertexAccessors, columns);
 
@@ -353,7 +350,7 @@ namespace SharpGLTF.Schema2
             return columns;
         }
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, Geometry.VertexColumns dstColumns)
+        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexColumns dstColumns)
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
@@ -372,7 +369,7 @@ namespace SharpGLTF.Schema2
             if (vertexAccessors.ContainsKey("WEIGHTS_1")) dstColumns.Weights1 = vertexAccessors["WEIGHTS_1"].AsVector4Array();
         }
 
-        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, Geometry.VertexColumns.MorphTarget dstColumns)
+        private static void _CopyTo(IReadOnlyDictionary<string, Accessor> vertexAccessors, VertexColumns.MorphTarget dstColumns)
         {
             if (vertexAccessors.ContainsKey("POSITION")) dstColumns.Positions = vertexAccessors["POSITION"].AsVector3Array();
             if (vertexAccessors.ContainsKey("NORMAL")) dstColumns.Normals = vertexAccessors["NORMAL"].AsVector3Array();
@@ -426,18 +423,16 @@ namespace SharpGLTF.Schema2
             return posnrm;
         }
 
-        public static void AddMesh<TMaterial, TvP, TvM, TvS>(this Geometry.MeshBuilder<TMaterial, TvP, TvM, TvS> meshBuilder, Mesh srcMesh, Matrix4x4 xform, Func<Material, TMaterial> materialFunc)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
-            where TvS : struct, Geometry.VertexTypes.IVertexSkinning
+        public static void AddMesh<TMaterial, TvG, TvM, TvS>(this MeshBuilder<TMaterial, TvG, TvM, TvS> meshBuilder, Mesh srcMesh, Func<Material, TMaterial> materialFunc)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
+            where TvS : struct, IVertexSkinning
         {
-            var normals = srcMesh.GetComputedNormals();
-
             foreach (var srcPrim in srcMesh.Primitives)
             {
                 var dstPrim = meshBuilder.UsePrimitive(materialFunc(srcPrim.Material));
 
-                foreach (var tri in srcPrim.Triangulate<TvP, TvM, TvS>(xform, normals))
+                foreach (var tri in srcPrim.Triangulate<TvG, TvM, TvS>())
                 {
                     dstPrim.AddTriangle(tri.Item1, tri.Item2, tri.Item3);
                 }
@@ -446,6 +441,9 @@ namespace SharpGLTF.Schema2
 
         public static void SaveAsWavefront(this ModelRoot model, string filePath)
         {
+            Guard.NotNullOrEmpty(filePath, nameof(filePath));
+            Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
+
             var wf = new IO.WavefrontWriter();
             wf.AddModel(model);
             wf.WriteFiles(filePath);
@@ -453,6 +451,9 @@ namespace SharpGLTF.Schema2
 
         public static void SaveAsWavefront(this ModelRoot model, string filePath, Animation animation, float time)
         {
+            Guard.NotNullOrEmpty(filePath, nameof(filePath));
+            Guard.IsFalse(filePath.Any(c => char.IsWhiteSpace(c)), nameof(filePath), "Whitespace characters not allowed in filename");
+
             var wf = new IO.WavefrontWriter();
             wf.AddModel(model, animation, time);
             wf.WriteFiles(filePath);

+ 22 - 40
src/SharpGLTF.Toolkit/Schema2/SceneExtensions.cs

@@ -6,6 +6,10 @@ using System.Text;
 
 namespace SharpGLTF.Schema2
 {
+    using Geometry;
+    using Geometry.VertexTypes;
+    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
+
     public static partial class Schema2Toolkit
     {
         #region fluent creation
@@ -112,63 +116,41 @@ namespace SharpGLTF.Schema2
         }
 
         /// <summary>
-        /// Yield a collection of triangles representing the geometry
-        /// of the input <see cref="Scene"/> in world space.
+        /// Yields a collection of triangles representing the geometry in world space.
         /// </summary>
-        /// <typeparam name="TvP">The vertex fragment type with Position, Normal and Tangent.</typeparam>
+        /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
         /// <typeparam name="TvM">The vertex fragment type with Colors and Texture Coordinates.</typeparam>
-        /// <typeparam name="TvS">The vertex fragment type with Skin Joint Weights.</typeparam>
         /// <param name="scene">A <see cref="Scene"/> instance.</param>
+        /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
+        /// <param name="time">The animation time.</param>
         /// <returns>A collection of triangles in world space.</returns>
-        public static IEnumerable<((TvP, TvM, TvS), (TvP, TvM, TvS), (TvP, TvM, TvS), Material)> Triangulate<TvP, TvM, TvS>(this Scene scene)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
-            where TvS : struct, Geometry.VertexTypes.IVertexSkinning
-        {
-            return Node.Flatten(scene).SelectMany(item => item.Triangulate<TvP, TvM, TvS>(true));
-        }
-
-        public static IEnumerable<((TvP, TvM), (TvP, TvM), (TvP, TvM), Material)> Triangulate<TvP, TvM>(this Scene scene, Animation animation, float time)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, Material)> Triangulate<TvG, TvM>(this Scene scene, Animation animation = null, float time = 0)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
         {
-            return Node.Flatten(scene).SelectMany(item => item.Triangulate<TvP, TvM>(animation, time));
+            return Node.Flatten(scene).SelectMany(item => item.Triangulate<TvG, TvM>(animation, time));
         }
 
         /// <summary>
-        /// Yield a collection of triangles representing the geometry
-        /// of the input <see cref="Node"/> in local or world space.
+        /// Yields a collection of triangles representing the geometry in world space.
         /// </summary>
-        /// <typeparam name="TvP">The vertex fragment type with Position, Normal and Tangent.</typeparam>
+        /// <typeparam name="TvG">The vertex fragment type with Position, Normal and Tangent.</typeparam>
         /// <typeparam name="TvM">The vertex fragment type with Colors and Texture Coordinates.</typeparam>
-        /// <typeparam name="TvS">The vertex fragment type with Skin Joint Weights.</typeparam>
         /// <param name="node">A <see cref="Node"/> instance.</param>
-        /// <param name="inWorldSpace">A value indicating whether the returned triangles must be in local (false) or world (true) space.</param>
-        /// <returns>A collection of triangles in local or world space.</returns>
-        public static IEnumerable<((TvP, TvM, TvS), (TvP, TvM, TvS), (TvP, TvM, TvS), Material)> Triangulate<TvP, TvM, TvS>(this Node node, bool inWorldSpace)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
-            where TvS : struct, Geometry.VertexTypes.IVertexSkinning
-        {
-            var mesh = node.Mesh;
-            if (mesh == null) return Enumerable.Empty<((TvP, TvM, TvS), (TvP, TvM, TvS), (TvP, TvM, TvS), Material)>();
-
-            var xform = inWorldSpace ? node.WorldMatrix : Matrix4x4.Identity;
-
-            return mesh.Triangulate<TvP, TvM, TvS>(xform);
-        }
-
-        public static IEnumerable<((TvP, TvM), (TvP, TvM), (TvP, TvM), Material)> Triangulate<TvP, TvM>(this Node node, Animation animation, float time)
-            where TvP : struct, Geometry.VertexTypes.IVertexGeometry
-            where TvM : struct, Geometry.VertexTypes.IVertexMaterial
+        /// <param name="animation">An <see cref="Animation"/> instance, or null.</param>
+        /// <param name="time">The animation time.</param>
+        /// <returns>A collection of triangles in world space.</returns>
+        public static IEnumerable<(VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, Material)> Triangulate<TvG, TvM>(this Node node, Animation animation = null, float time = 0)
+            where TvG : struct, IVertexGeometry
+            where TvM : struct, IVertexMaterial
         {
             var mesh = node.Mesh;
 
-            if (mesh == null) return Enumerable.Empty<((TvP, TvM), (TvP, TvM), (TvP, TvM), Material)>();
+            if (mesh == null) return Enumerable.Empty<(VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, VertexBuilder<TvG, TvM, VEMPTY>, Material)>();
 
             var xform = node.GetMeshWorldTransform(animation, time);
 
-            return mesh.Triangulate<TvP, TvM>(xform);
+            return mesh.Triangulate<TvG, TvM>(xform);
         }
 
         #endregion

+ 5 - 0
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadPollyTest.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Text;
 
 using NUnit.Framework;
@@ -33,6 +34,10 @@ namespace SharpGLTF.Schema2.LoadAndSave
 
             Assert.NotNull(model);
 
+            var triangles = model.DefaultScene
+                .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexTexture1>(model.LogicalAnimations[0], 0.5f)
+                .ToList();
+
             // Save as GLB, and also evaluate all triangles and save as Wavefront OBJ            
             model.AttachToCurrentTest("polly_out.glb");
             model.AttachToCurrentTest("polly_out.obj");

+ 44 - 0
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSampleTests.cs

@@ -166,6 +166,8 @@ namespace SharpGLTF.Schema2.LoadAndSave
         [Test]
         public void LoadMorphTargetModel()
         {
+            TestContext.CurrentContext.AttachShowDirLink();
+
             var path = TestFiles
                 .GetSampleModelsPaths()
                 .FirstOrDefault(item => item.Contains("MorphPrimitivesTest.glb"));
@@ -180,5 +182,47 @@ namespace SharpGLTF.Schema2.LoadAndSave
             model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".obj"));
             model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".glb"));
         }
+
+        [TestCase("RiggedFigure.glb")]
+        [TestCase("RiggedSimple.glb")]
+        [TestCase("BoxAnimated.glb")]
+        [TestCase("AnimatedMorphCube.glb")]
+        [TestCase("AnimatedMorphSphere.glb")]
+        [TestCase("CesiumMan.glb")]
+        [TestCase("Monster.glb")]
+        [TestCase("BrainStem.glb")]
+        public void LoadAnimatedModels(string path)
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+
+            path = TestFiles
+                .GetSampleModelsPaths()
+                .FirstOrDefault(item => item.Contains(path));
+
+            var model = ModelRoot.Load(path);
+            Assert.NotNull(model);
+
+            path = System.IO.Path.GetFileNameWithoutExtension(path);
+
+            model.AttachToCurrentTest(path + ".glb");
+
+            var triangles = model.DefaultScene
+                .Triangulate<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(null, 0)
+                .ToArray();            
+
+            var anim = model.LogicalAnimations[0];
+
+            var duration = anim.Duration;
+
+            for(int i=0; i < 10; ++i)
+            {
+                var t = duration * i / 10;
+                int tt = (int)(t * 1000.0f);
+
+                model.AttachToCurrentTest($"{path} at {tt}.obj",anim, t);
+            }
+
+            
+        }
     }
 }

+ 14 - 0
tests/SharpGLTF.Tests/Utils.cs

@@ -58,6 +58,7 @@ namespace SharpGLTF
             }
             else if (fileName.ToLower().EndsWith(".obj"))
             {
+                fileName = fileName.Replace(" ", "_");
                 Schema2.Schema2Toolkit.SaveAsWavefront(model, fileName);
             }
 
@@ -65,6 +66,19 @@ namespace SharpGLTF
             TestContext.AddTestAttachment(fileName);
         }
 
+        public static void AttachToCurrentTest(this Schema2.ModelRoot model, string fileName, Schema2.Animation animation, float time)
+        {
+            fileName = fileName.Replace(" ", "_");
+
+            // find the output path for the current test
+            fileName = TestContext.CurrentContext.GetAttachmentPath(fileName, true);
+            
+            Schema2.Schema2Toolkit.SaveAsWavefront(model, fileName, animation, time);
+
+            // Attach the saved file to the current test
+            TestContext.AddTestAttachment(fileName);
+        }
+
         public static void AttachText(this TestContext context, string fileName, string[] lines)
         {
             fileName = context.GetAttachmentPath(fileName, true);