Browse Source

WIP mesh creation with interfaces
WIP parametric shapes
WIP curves

Vicente Penades 6 years ago
parent
commit
274b64aff5

+ 25 - 4
src/SharpGLTF.Core/Transforms/AnimationSamplerFactory.cs

@@ -147,21 +147,21 @@ namespace SharpGLTF.Transforms
 
 
         internal static Vector3 Hermite(Vector3 start, Vector3 tangentOut, Vector3 end, Vector3 tangentIn, float amount)
         internal static Vector3 Hermite(Vector3 start, Vector3 tangentOut, Vector3 end, Vector3 tangentIn, float amount)
         {
         {
-            var hermite = CalculateHermiteWeights(amount);
+            var hermite = CalculateHermiteBasis(amount);
 
 
             return (start * hermite.Item1) + (end * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4);
             return (start * hermite.Item1) + (end * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4);
         }
         }
 
 
         internal static Quaternion Hermite(Quaternion value1, Quaternion tangent1, Quaternion value2, Quaternion tangent2, float amount)
         internal static Quaternion Hermite(Quaternion value1, Quaternion tangent1, Quaternion value2, Quaternion tangent2, float amount)
         {
         {
-            var hermite = CalculateHermiteWeights(amount);
+            var hermite = CalculateHermiteBasis(amount);
 
 
             return Quaternion.Normalize((value1 * hermite.Item1) + (value2 * hermite.Item2) + (tangent1 * hermite.Item3) + (tangent2 * hermite.Item4));
             return Quaternion.Normalize((value1 * hermite.Item1) + (value2 * hermite.Item2) + (tangent1 * hermite.Item3) + (tangent2 * hermite.Item4));
         }
         }
 
 
         internal static float[] Hermite(float[] value1, float[] tangent1, float[] value2, float[] tangent2, float amount)
         internal static float[] Hermite(float[] value1, float[] tangent1, float[] value2, float[] tangent2, float amount)
         {
         {
-            var hermite = CalculateHermiteWeights(amount);
+            var hermite = CalculateHermiteBasis(amount);
 
 
             var result = new float[value1.Length];
             var result = new float[value1.Length];
 
 
@@ -183,7 +183,7 @@ namespace SharpGLTF.Transforms
         /// </summary>
         /// </summary>
         /// <param name="amount">the input amount</param>
         /// <param name="amount">the input amount</param>
         /// <returns>the output weights</returns>
         /// <returns>the output weights</returns>
-        public static (float, float, float, float) CalculateHermiteWeights(float amount)
+        public static (float, float, float, float) CalculateHermiteBasis(float amount)
         {
         {
             // http://mathworld.wolfram.com/HermitePolynomial.html
             // http://mathworld.wolfram.com/HermitePolynomial.html
 
 
@@ -206,5 +206,26 @@ namespace SharpGLTF.Transforms
 
 
             return (part1, part2, part3, part4);
             return (part1, part2, part3, part4);
         }
         }
+
+        public static (float, float, float, float) CalculateHermiteTangent(float amount)
+        {
+            // https://math.stackexchange.com/questions/1270776/how-to-find-tangent-at-any-point-along-a-cubic-hermite-spline
+
+            var squared = amount * amount;
+
+            /*
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -(6 * squared) + (6 * amount);
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+            */
+
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -part1;
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+
+            return (part1, part2, part3, part4);
+        }
     }
     }
 }
 }

+ 9 - 3
src/SharpGLTF.Toolkit/Animations/Animatable.cs

@@ -16,18 +16,24 @@ namespace SharpGLTF.Animations
 
 
         #endregion
         #endregion
 
 
+        #region properties
+
+        public IReadOnlyDictionary<string, ICurveSampler<T>> Tracks => _Tracks;
+
+        #endregion
+
         #region API
         #region API
 
 
         public T GetValueAt(string track, float value)
         public T GetValueAt(string track, float value)
         {
         {
-            return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetSample(value) : this.Default;
+            return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetPoint(value) : this.Default;
         }
         }
 
 
         public ILinearCurve<T> UseLinearCurve(string track)
         public ILinearCurve<T> UseLinearCurve(string track)
         {
         {
             if (!_Tracks.TryGetValue(track, out ICurveSampler<T> curve))
             if (!_Tracks.TryGetValue(track, out ICurveSampler<T> curve))
             {
             {
-                _Tracks[track] = CurveFactory.CreateLinearCurve<T>();
+                _Tracks[track] = curve = CurveFactory.CreateLinearCurve<T>();
             }
             }
 
 
             if (curve is ILinearCurve<T> editableCurve) return editableCurve;
             if (curve is ILinearCurve<T> editableCurve) return editableCurve;
@@ -39,7 +45,7 @@ namespace SharpGLTF.Animations
         {
         {
             if (!_Tracks.TryGetValue(track, out ICurveSampler<T> curve))
             if (!_Tracks.TryGetValue(track, out ICurveSampler<T> curve))
             {
             {
-                _Tracks[track] = CurveFactory.CreateSplineCurve<T>();
+                _Tracks[track] = curve = CurveFactory.CreateSplineCurve<T>();
             }
             }
 
 
             if (curve is ISplineCurve<T> editableCurve) return editableCurve;
             if (curve is ISplineCurve<T> editableCurve) return editableCurve;

+ 175 - 40
src/SharpGLTF.Toolkit/Animations/Curves.cs

@@ -2,13 +2,28 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
+using System.Linq;
 
 
 namespace SharpGLTF.Animations
 namespace SharpGLTF.Animations
 {
 {
     public interface ICurveSampler<T>
     public interface ICurveSampler<T>
         where T : struct
         where T : struct
     {
     {
-        T GetSample(float offset);
+        T GetPoint(float offset);
+
+        T GetTangent(float offset);
+    }
+
+    [System.Diagnostics.DebuggerDisplay("[{_Offset}] = {Sample}")]
+    public struct CurvePoint<T>
+        where T : struct
+    {
+        private readonly ICurveSampler<T> _Curve;
+        private readonly float _Offset;
+
+        public T Point => _Curve.GetPoint(_Offset);
+
+        public T Tangent => _Curve.GetTangent(_Offset);
     }
     }
 
 
     public interface ILinearCurve<T> : ICurveSampler<T>
     public interface ILinearCurve<T> : ICurveSampler<T>
@@ -21,16 +36,28 @@ namespace SharpGLTF.Animations
         T GetControlPoint(float key);
         T GetControlPoint(float key);
 
 
         void SetControlPoint(float key, T value);
         void SetControlPoint(float key, T value);
+
+        Dictionary<float, T> ToDictionary();
     }
     }
 
 
-    public interface ISplineCurve<T> : ILinearCurve<T>
+    public interface ISplineCurve<T> : ICurveSampler<T>
         where T : struct
         where T : struct
     {
     {
-        void SetControlPointIn(float key, T value);
-        void SetControlPointOut(float key, T value);
+        IReadOnlyCollection<float> Keys { get; }
+
+        void RemoveKey(float key);
+
+        T GetControlPoint(float key);
+
+        void SetControlPoint(float key, T value);
+
+        void SetCardinalPointIn(float key, T value);
+        void SetCardinalPointOut(float key, T value);
 
 
         void SetTangentIn(float key, T value);
         void SetTangentIn(float key, T value);
         void SetTangentOut(float key, T value);
         void SetTangentOut(float key, T value);
+
+        Dictionary<float, (T, T, T)> ToDictionary();
     }
     }
 
 
     public static class CurveFactory
     public static class CurveFactory
@@ -67,7 +94,7 @@ namespace SharpGLTF.Animations
         /// </summary>
         /// </summary>
         /// <param name="amount">the input amount</param>
         /// <param name="amount">the input amount</param>
         /// <returns>the output weights</returns>
         /// <returns>the output weights</returns>
-        public static (float, float, float, float) CalculateHermiteWeights(float amount)
+        public static (float, float, float, float) CalculateHermiteBasis(float amount)
         {
         {
             // http://mathworld.wolfram.com/HermitePolynomial.html
             // http://mathworld.wolfram.com/HermitePolynomial.html
 
 
@@ -88,9 +115,30 @@ namespace SharpGLTF.Animations
 
 
             return (part1, part2, part3, part4);
             return (part1, part2, part3, part4);
         }
         }
+
+        public static (float, float, float, float) CalculateHermiteTangent(float amount)
+        {
+            // https://math.stackexchange.com/questions/1270776/how-to-find-tangent-at-any-point-along-a-cubic-hermite-spline
+
+            var squared = amount * amount;
+
+            /*
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -(6 * squared) + (6 * amount);
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+            */
+
+            var part1 = (6 * squared) - (6 * amount);
+            var part2 = -part1;
+            var part3 = (3 * squared) - (4 * amount) + 1;
+            var part4 = (3 * squared) - (2 * amount);
+
+            return (part1, part2, part3, part4);
+        }
     }
     }
 
 
-    abstract class Curve<Tin, Tout> : ICurveSampler<Tout>
+    abstract class Curve<Tin, Tout>
         where Tin : struct
         where Tin : struct
         where Tout : struct
         where Tout : struct
     {
     {
@@ -110,7 +158,7 @@ namespace SharpGLTF.Animations
 
 
         #region data
         #region data
 
 
-        private SortedDictionary<float, Tin> _Keys = new SortedDictionary<float, Tin>();
+        protected SortedDictionary<float, Tin> _Keys = new SortedDictionary<float, Tin>();
 
 
         #endregion
         #endregion
 
 
@@ -133,8 +181,6 @@ namespace SharpGLTF.Animations
             return _FindSample(_Keys, offset);
             return _FindSample(_Keys, offset);
         }
         }
 
 
-        public abstract Tout GetSample(float offset);
-
         /// <summary>
         /// <summary>
         /// Given a <paramref name="sequence"/> of float+<typeparamref name="T"/> pairs and a <paramref name="offset"/> time,
         /// Given a <paramref name="sequence"/> of float+<typeparamref name="T"/> pairs and a <paramref name="offset"/> time,
         /// it finds two consecutive values and the LERP amout.
         /// it finds two consecutive values and the LERP amout.
@@ -186,6 +232,7 @@ namespace SharpGLTF.Animations
         #endregion
         #endregion
     }
     }
 
 
+    // Hermite Point
     struct _SplinePoint<T>
     struct _SplinePoint<T>
         where T : struct
         where T : struct
     {
     {
@@ -206,11 +253,17 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Single GetSample(float offset)
+        public Single GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
 
 
-            return sample.Item1 * (1 - sample.Item3) + (sample.Item2 * sample.Item3);
+            return (sample.Item1 * (1 - sample.Item3)) + (sample.Item2 * sample.Item3);
+        }
+
+        public Single GetTangent(float offset)
+        {
+            var sample = FindSample(offset);
+            return sample.Item2 - sample.Item1;
         }
         }
 
 
         public float GetControlPoint(float key)
         public float GetControlPoint(float key)
@@ -224,6 +277,11 @@ namespace SharpGLTF.Animations
             SetKey(offset, value);
             SetKey(offset, value);
         }
         }
 
 
+        public Dictionary<float, float> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => v.Value);
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -239,18 +297,30 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Single GetSample(float offset)
+        public float GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
+
+            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
 
 
-            return Hermite(sample.Item1.Point, sample.Item1.OutTangent, sample.Item2.Point, sample.Item2.InTangent, sample.Item3);
+            return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
         }
 
 
-        private static Single Hermite(Single value1, Single tangent1, Single value2, Single tangent2, float amount)
+        public float GetTangent(float offset)
         {
         {
-            var hermite = CurveFactory.CalculateHermiteWeights(amount);
+            var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
 
 
-            return (value1 * hermite.Item1) + (value2 * hermite.Item2) + (tangent1 * hermite.Item3) + (tangent2 * hermite.Item4);
+            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
+
+            return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
         }
 
 
         public float GetControlPoint(float key)
         public float GetControlPoint(float key)
@@ -266,17 +336,17 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointIn(float key, Single value)
+        public void SetCardinalPointIn(float key, Single value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
-            val.InTangent = val.Point - value;
+            val.InTangent = (val.Point - value) * 4;
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointOut(float key, Single value)
+        public void SetCardinalPointOut(float key, Single value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
-            val.OutTangent = value - val.Point;
+            val.OutTangent = (value - val.Point) * 4;
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
@@ -294,6 +364,11 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
+        public Dictionary<float, (float, float, float)> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => (v.Value.InTangent, v.Value.Point, v.Value.OutTangent));
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -309,13 +384,20 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Vector3 GetSample(float offset)
+        public Vector3 GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
 
 
             return Vector3.Lerp(sample.Item1, sample.Item2, sample.Item3);
             return Vector3.Lerp(sample.Item1, sample.Item2, sample.Item3);
         }
         }
 
 
+        public Vector3 GetTangent(float offset)
+        {
+            var sample = FindSample(offset);
+
+            return sample.Item2 - sample.Item1;
+        }
+
         public Vector3 GetControlPoint(float key)
         public Vector3 GetControlPoint(float key)
         {
         {
             var sample = FindSample(key);
             var sample = FindSample(key);
@@ -327,6 +409,11 @@ namespace SharpGLTF.Animations
             SetKey(offset, value);
             SetKey(offset, value);
         }
         }
 
 
+        public Dictionary<float, Vector3> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => v.Value);
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -342,18 +429,30 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Vector3 GetSample(float offset)
+        public Vector3 GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
+
+            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
 
 
-            return Hermite(sample.Item1.Point, sample.Item1.OutTangent, sample.Item2.Point, sample.Item2.InTangent, sample.Item3);
+            return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
         }
 
 
-        private static Vector3 Hermite(Vector3 pointStart, Vector3 tangentOut, Vector3 pointEnd, Vector3 tangentIn, float amount)
+        public Vector3 GetTangent(float offset)
         {
         {
-            var hermite = CurveFactory.CalculateHermiteWeights(amount);
+            var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
+
+            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
 
 
-            return (pointStart * hermite.Item1) + (pointEnd * hermite.Item2) + (tangentOut * hermite.Item3) + (tangentIn * hermite.Item4);
+            return (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
         }
         }
 
 
         public Vector3 GetControlPoint(float key)
         public Vector3 GetControlPoint(float key)
@@ -369,17 +468,17 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointIn(float key, Vector3 value)
+        public void SetCardinalPointIn(float key, Vector3 value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
-            val.InTangent = val.Point - value;
+            val.InTangent = (val.Point - value) * 4;
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointOut(float key, Vector3 value)
+        public void SetCardinalPointOut(float key, Vector3 value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
-            val.OutTangent = value - val.Point;
+            val.OutTangent = (value - val.Point) * 4;
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
@@ -397,6 +496,11 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
+        public Dictionary<float, (Vector3, Vector3, Vector3)> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => (v.Value.InTangent, v.Value.Point, v.Value.OutTangent));
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -412,13 +516,18 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Quaternion GetSample(float offset)
+        public Quaternion GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
 
 
             return Quaternion.Slerp(sample.Item1, sample.Item2, sample.Item3);
             return Quaternion.Slerp(sample.Item1, sample.Item2, sample.Item3);
         }
         }
 
 
+        public Quaternion GetTangent(float offset)
+        {
+            throw new NotImplementedException();
+        }
+
         public Quaternion GetControlPoint(float key)
         public Quaternion GetControlPoint(float key)
         {
         {
             var sample = FindSample(key);
             var sample = FindSample(key);
@@ -430,6 +539,11 @@ namespace SharpGLTF.Animations
             SetKey(offset, value);
             SetKey(offset, value);
         }
         }
 
 
+        public Dictionary<float, Quaternion> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => v.Value);
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -445,18 +559,34 @@ namespace SharpGLTF.Animations
 
 
         #region API
         #region API
 
 
-        public override Quaternion GetSample(float offset)
+        public Quaternion GetPoint(float offset)
         {
         {
             var sample = FindSample(offset);
             var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
 
 
-            return Hermite(sample.Item1.Point, sample.Item1.OutTangent, sample.Item2.Point, sample.Item2.InTangent, sample.Item3);
+            var basis = CurveFactory.CalculateHermiteBasis(sample.Item3);
+
+            var q = (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
+
+            return Quaternion.Normalize(q);
         }
         }
 
 
-        private static Quaternion Hermite(Quaternion value1, Quaternion tangent1, Quaternion value2, Quaternion tangent2, float amount)
+        public Quaternion GetTangent(float offset)
         {
         {
-            var hermite = CurveFactory.CalculateHermiteWeights(amount);
+            var sample = FindSample(offset);
+            var pointStart = sample.Item1.Point;
+            var tangentOut = sample.Item1.OutTangent;
+            var pointEnd = sample.Item2.Point;
+            var tangentIn = sample.Item2.InTangent;
+
+            var basis = CurveFactory.CalculateHermiteTangent(sample.Item3);
 
 
-            return Quaternion.Normalize((value1 * hermite.Item1) + (value2 * hermite.Item2) + (tangent1 * hermite.Item3) + (tangent2 * hermite.Item4));
+            var q = (pointStart * basis.Item1) + (pointEnd * basis.Item2) + (tangentOut * basis.Item3) + (tangentIn * basis.Item4);
+
+            return Quaternion.Normalize(q);
         }
         }
 
 
         public Quaternion GetControlPoint(float key)
         public Quaternion GetControlPoint(float key)
@@ -472,24 +602,24 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointIn(float key, Quaternion value)
+        public void SetCardinalPointIn(float key, Quaternion value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
 
 
             var inv = Quaternion.Inverse(value);
             var inv = Quaternion.Inverse(value);
-            value = Quaternion.Concatenate(val.Point, inv);
+            value = Quaternion.Concatenate(val.Point, inv); // *4? => convert to axisradians; angle * 4, back to Q
             value = Quaternion.Normalize(value);
             value = Quaternion.Normalize(value);
 
 
             val.InTangent = value;
             val.InTangent = value;
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
-        public void SetControlPointOut(float key, Quaternion value)
+        public void SetCardinalPointOut(float key, Quaternion value)
         {
         {
             var val = GetKey(key) ?? default;
             var val = GetKey(key) ?? default;
 
 
             var inv = Quaternion.Inverse(val.Point);
             var inv = Quaternion.Inverse(val.Point);
-            value = Quaternion.Concatenate(value, inv);
+            value = Quaternion.Concatenate(value, inv); // *4? => convert to axisradians; angle * 4, back to Q
             value = Quaternion.Normalize(value);
             value = Quaternion.Normalize(value);
 
 
             val.OutTangent = value;
             val.OutTangent = value;
@@ -510,6 +640,11 @@ namespace SharpGLTF.Animations
             SetKey(key, val);
             SetKey(key, val);
         }
         }
 
 
+        public Dictionary<float, (Quaternion, Quaternion, Quaternion)> ToDictionary()
+        {
+            return _Keys.ToDictionary(k => k.Key, v => (v.Value.InTangent, v.Value.Point, v.Value.OutTangent));
+        }
+
         #endregion
         #endregion
     }
     }
 }
 }

+ 7 - 0
src/SharpGLTF.Toolkit/Geometry/PrimitiveBuilder.cs

@@ -54,6 +54,11 @@ namespace SharpGLTF.Geometry
 
 
     public interface IPrimitiveBuilder
     public interface IPrimitiveBuilder
     {
     {
+        /// <summary>
+        /// Gets the type of vertex used by this <see cref="IVertexBuilder"/>.
+        /// </summary>
+        Type VertexType { get; }
+
         (int, int, int) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c);
         (int, int, int) AddTriangle(IVertexBuilder a, IVertexBuilder b, IVertexBuilder c);
     }
     }
 
 
@@ -157,6 +162,8 @@ namespace SharpGLTF.Geometry
 
 
         public IEnumerable<(int, int, int)> Triangles => _GetTriangleIndices();
         public IEnumerable<(int, int, int)> Triangles => _GetTriangleIndices();
 
 
+        public Type VertexType => typeof(VertexBuilder<TvG, TvM, TvS>);
+
         #endregion
         #endregion
 
 
         #region API
         #region API

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

@@ -13,14 +13,14 @@ namespace SharpGLTF.Geometry
         IVertexMaterial GetMaterial();
         IVertexMaterial GetMaterial();
         IVertexSkinning GetSkinning();
         IVertexSkinning GetSkinning();
 
 
+        void SetGeometry(IVertexGeometry geometry);
+        void SetMaterial(IVertexMaterial material);
+        void SetSkinning(IVertexSkinning skinning);
+
         VertexBuilder<TvPP, TvMM, TvSS> ConvertTo<TvPP, TvMM, TvSS>()
         VertexBuilder<TvPP, TvMM, TvSS> ConvertTo<TvPP, TvMM, TvSS>()
             where TvPP : struct, IVertexGeometry
             where TvPP : struct, IVertexGeometry
             where TvMM : struct, IVertexMaterial
             where TvMM : struct, IVertexMaterial
             where TvSS : struct, IVertexSkinning;
             where TvSS : struct, IVertexSkinning;
-
-        // void SetGeometry(IVertexGeometry);
-        // void SetMaterial(IVertexMaterial);
-        // void SetSkinning(IVertexSkinning);
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -318,6 +318,23 @@ namespace SharpGLTF.Geometry
 
 
         IVertexSkinning IVertexBuilder.GetSkinning() { return this.Skinning; }
         IVertexSkinning IVertexBuilder.GetSkinning() { return this.Skinning; }
 
 
+        void IVertexBuilder.SetGeometry(IVertexGeometry geometry)
+        {
+            Guard.NotNull(geometry, nameof(geometry));
+            this.Geometry = geometry.GetType() == typeof(TvG) ? (TvG)geometry : geometry.ConvertTo<TvG>();
+        }
+
+        void IVertexBuilder.SetMaterial(IVertexMaterial material)
+        {
+            Guard.NotNull(material, nameof(material));
+            this.Material = material.GetType() == typeof(TvM) ? (TvM)material : material.ConvertTo<TvM>();
+        }
+
+        void IVertexBuilder.SetSkinning(IVertexSkinning skinning)
+        {
+            Guard.NotNull(skinning, nameof(skinning));
+            this.Skinning = skinning.GetType() == typeof(TvS) ? (TvS)skinning : skinning.ConvertTo<TvS>();
+        }
         #endregion
         #endregion
     }
     }
 }
 }

+ 5 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexGeometry.cs

@@ -107,6 +107,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             src.TryGetNormal(out this.Normal);
             src.TryGetNormal(out this.Normal);
         }
         }
 
 
+        public static implicit operator VertexPositionNormal((Vector3, Vector3) posnrm)
+        {
+            return new VertexPositionNormal(posnrm.Item1, posnrm.Item2);
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data

+ 5 - 0
src/SharpGLTF.Toolkit/Geometry/VertexTypes/VertexMaterial.cs

@@ -156,6 +156,11 @@ namespace SharpGLTF.Geometry.VertexTypes
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
             this.TexCoord = src.MaxTextCoords > 0 ? src.GetTexCoord(0) : Vector2.Zero;
         }
         }
 
 
+        public static implicit operator VertexColor1Texture1((Vector4,Vector2) coloruv)
+        {
+            return new VertexColor1Texture1(coloruv.Item1, coloruv.Item2);
+        }
+
         #endregion
         #endregion
 
 
         #region data
         #region data

+ 35 - 30
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -14,7 +14,7 @@ namespace SharpGLTF.Scenes
 
 
         public NodeBuilder() { }
         public NodeBuilder() { }
 
 
-        internal NodeBuilder(NodeBuilder parent)
+        private NodeBuilder(NodeBuilder parent)
         {
         {
             _Parent = parent;
             _Parent = parent;
         }
         }
@@ -28,9 +28,6 @@ namespace SharpGLTF.Scenes
         private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
         private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
 
 
         private Matrix4x4? _Matrix;
         private Matrix4x4? _Matrix;
-        private Animations.Animatable<Vector3> _Scale;
-        private Animations.Animatable<Quaternion> _Rotation;
-        private Animations.Animatable<Vector3> _Translation;
 
 
         #endregion
         #endregion
 
 
@@ -48,12 +45,20 @@ namespace SharpGLTF.Scenes
 
 
         #region properties - transform
         #region properties - transform
 
 
+        public bool HasAnimations => Scale?.Tracks.Count > 0 || Rotation?.Tracks.Count > 0 || Translation?.Tracks.Count > 0;
+
+        public Animations.Animatable<Vector3> Scale { get; private set; }
+
+        public Animations.Animatable<Quaternion> Rotation { get; private set; }
+
+        public Animations.Animatable<Vector3> Translation { get; private set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
         /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
         /// </summary>
         /// </summary>
         public Matrix4x4 LocalMatrix
         public Matrix4x4 LocalMatrix
         {
         {
-            get => Transforms.AffineTransform.Evaluate(_Matrix, _Scale?.Default, _Rotation?.Default, _Translation?.Default);
+            get => Transforms.AffineTransform.Evaluate(_Matrix, Scale?.Default, Rotation?.Default, Translation?.Default);
             set
             set
             {
             {
                 if (value == Matrix4x4.Identity)
                 if (value == Matrix4x4.Identity)
@@ -65,9 +70,9 @@ namespace SharpGLTF.Scenes
                     _Matrix = value;
                     _Matrix = value;
                 }
                 }
 
 
-                _Scale = null;
-                _Rotation = null;
-                _Translation = null;
+                Scale = null;
+                Rotation = null;
+                Translation = null;
             }
             }
         }
         }
 
 
@@ -80,7 +85,7 @@ namespace SharpGLTF.Scenes
                 ?
                 ?
                 Transforms.AffineTransform.Create(_Matrix.Value)
                 Transforms.AffineTransform.Create(_Matrix.Value)
                 :
                 :
-                Transforms.AffineTransform.Create(_Scale?.Default, _Rotation?.Default, _Translation?.Default);
+                Transforms.AffineTransform.Create(Scale?.Default, Rotation?.Default, Translation?.Default);
             set
             set
             {
             {
                 Guard.IsTrue(value.IsValid, nameof(value));
                 Guard.IsTrue(value.IsValid, nameof(value));
@@ -89,20 +94,20 @@ namespace SharpGLTF.Scenes
 
 
                 if (value.Scale != Vector3.One)
                 if (value.Scale != Vector3.One)
                 {
                 {
-                    if (_Scale == null) _Scale = new Animations.Animatable<Vector3>();
-                    _Scale.Default = value.Scale;
+                    if (Scale == null) Scale = new Animations.Animatable<Vector3>();
+                    Scale.Default = value.Scale;
                 }
                 }
 
 
                 if (value.Rotation != Quaternion.Identity)
                 if (value.Rotation != Quaternion.Identity)
                 {
                 {
-                    if (_Rotation == null) _Rotation = new Animations.Animatable<Quaternion>();
-                    _Rotation.Default = value.Rotation;
+                    if (Rotation == null) Rotation = new Animations.Animatable<Quaternion>();
+                    Rotation.Default = value.Rotation;
                 }
                 }
 
 
                 if (value.Translation != Vector3.Zero)
                 if (value.Translation != Vector3.Zero)
                 {
                 {
-                    if (_Translation == null) _Translation = new Animations.Animatable<Vector3>();
-                    _Translation.Default = value.Scale;
+                    if (Translation == null) Translation = new Animations.Animatable<Vector3>();
+                    Translation.Default = value.Scale;
                 }
                 }
             }
             }
         }
         }
@@ -138,44 +143,44 @@ namespace SharpGLTF.Scenes
 
 
         public Animations.Animatable<Vector3> UseScale()
         public Animations.Animatable<Vector3> UseScale()
         {
         {
-            if (_Scale == null)
+            if (Scale == null)
             {
             {
-                _Scale = new Animations.Animatable<Vector3>();
-                _Scale.Default = Vector3.One;
+                Scale = new Animations.Animatable<Vector3>();
+                Scale.Default = Vector3.One;
             }
             }
 
 
-            return _Scale;
+            return Scale;
         }
         }
 
 
         public Animations.Animatable<Quaternion> UseRotation()
         public Animations.Animatable<Quaternion> UseRotation()
         {
         {
-            if (_Rotation == null)
+            if (Rotation == null)
             {
             {
-                _Rotation = new Animations.Animatable<Quaternion>();
-                _Rotation.Default = Quaternion.Identity;
+                Rotation = new Animations.Animatable<Quaternion>();
+                Rotation.Default = Quaternion.Identity;
             }
             }
 
 
-            return _Rotation;
+            return Rotation;
         }
         }
 
 
         public Animations.Animatable<Vector3> UseTranslation()
         public Animations.Animatable<Vector3> UseTranslation()
         {
         {
-            if (_Translation == null)
+            if (Translation == null)
             {
             {
-                _Translation = new Animations.Animatable<Vector3>();
-                _Translation.Default = Vector3.One;
+                Translation = new Animations.Animatable<Vector3>();
+                Translation.Default = Vector3.One;
             }
             }
 
 
-            return _Translation;
+            return Translation;
         }
         }
 
 
         public Transforms.AffineTransform GetLocalTransform(string animationTrack, float time)
         public Transforms.AffineTransform GetLocalTransform(string animationTrack, float time)
         {
         {
             if (animationTrack == null) return this.LocalTransform;
             if (animationTrack == null) return this.LocalTransform;
 
 
-            var scale = _Scale?.GetValueAt(animationTrack, time);
-            var rotation = _Rotation?.GetValueAt(animationTrack, time);
-            var translation = _Translation?.GetValueAt(animationTrack, time);
+            var scale = Scale?.GetValueAt(animationTrack, time);
+            var rotation = Rotation?.GetValueAt(animationTrack, time);
+            var translation = Translation?.GetValueAt(animationTrack, time);
 
 
             return Transforms.AffineTransform.Create(scale, rotation, translation);
             return Transforms.AffineTransform.Create(scale, rotation, translation);
         }
         }

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

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
+using System.Numerics;
 
 
 namespace SharpGLTF.Scenes
 namespace SharpGLTF.Scenes
 {
 {
@@ -79,7 +80,21 @@ namespace SharpGLTF.Scenes
             var dstNode = container.CreateNode(srcNode.Name);
             var dstNode = container.CreateNode(srcNode.Name);
             _Nodes[srcNode] = dstNode;
             _Nodes[srcNode] = dstNode;
 
 
-            // assign animation here
+            if (srcNode.HasAnimations)
+            {
+                dstNode.LocalTransform = srcNode.LocalTransform;
+
+                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);
+
+            }
+            else
+            {
+                dstNode.LocalMatrix = srcNode.LocalMatrix;
+            }
 
 
             foreach (var c in srcNode.Children) CreateArmature(dstNode, c);
             foreach (var c in srcNode.Children) CreateArmature(dstNode, c);
         }
         }

+ 63 - 0
src/SharpGLTF.Toolkit/Schema2/AnimationExtensions.cs

@@ -15,6 +15,69 @@ namespace SharpGLTF.Schema2
             return animation ?? root.CreateAnimation(name);
             return animation ?? root.CreateAnimation(name);
         }
         }
 
 
+        public static Node WithScaleAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> curve)
+        {
+            var animation = node
+                .LogicalParent
+                .UseAnimation(animationName);
+
+            if (curve is Animations.ISplineCurve<Vector3> spline)
+            {
+                animation.CreateScaleChannel(node, spline.ToDictionary());
+                return node;
+            }
+
+            if (curve is Animations.ILinearCurve<Vector3> linear)
+            {
+                animation.CreateScaleChannel(node, linear.ToDictionary());
+                return node;
+            }
+
+            throw new ArgumentException("Not supported", nameof(curve));
+        }
+
+        public static Node WithTranslationAnimation(this Node node, string animationName, Animations.ICurveSampler<Vector3> curve)
+        {
+            var animation = node
+                .LogicalParent
+                .UseAnimation(animationName);
+
+            if (curve is Animations.ISplineCurve<Vector3> spline)
+            {
+                animation.CreateTranslationChannel(node, spline.ToDictionary());
+                return node;
+            }
+
+            if (curve is Animations.ILinearCurve<Vector3> linear)
+            {
+                animation.CreateTranslationChannel(node, linear.ToDictionary());
+                return node;
+            }
+
+            throw new ArgumentException("Not supported", nameof(curve));
+        }
+
+        public static Node WithRotationAnimation(this Node node, string animationName, Animations.ICurveSampler<Quaternion> curve)
+        {
+            var animation = node
+                .LogicalParent
+                .UseAnimation(animationName);
+
+            if (curve is Animations.ISplineCurve<Quaternion> spline)
+            {
+                animation.CreateRotationChannel(node, spline.ToDictionary());
+                return node;
+            }
+
+            if (curve is Animations.ILinearCurve<Quaternion> linear)
+            {
+                animation.CreateRotationChannel(node, linear.ToDictionary());
+                return node;
+            }
+
+            throw new ArgumentException("Not supported", nameof(curve));
+        }
+
         public static Node WithScaleAnimation(this Node node, string animationName, params (Single, Vector3)[] keyframes)
         public static Node WithScaleAnimation(this Node node, string animationName, params (Single, Vector3)[] keyframes)
         {
         {
             return node.WithScaleAnimation(animationName, keyframes.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2));
             return node.WithScaleAnimation(animationName, keyframes.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2));

+ 17 - 4
tests/SharpGLTF.Tests/AnimationSamplingTests.cs

@@ -24,7 +24,7 @@ namespace SharpGLTF
 
 
             for (float amount = 0; amount <= 1; amount += 0.01f)
             for (float amount = 0; amount <= 1; amount += 0.01f)
             {
             {
-                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteWeights(amount);
+                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(amount);
 
 
                 var p = Vector2.Zero;
                 var p = Vector2.Zero;
 
 
@@ -36,10 +36,23 @@ namespace SharpGLTF
                 ppp.Add(p);
                 ppp.Add(p);
             }
             }
 
 
+            // now lets calculate an arbitrary point and tangent
+
+            float k = 0.3f;
+
+            var hb = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(k);
+            var ht = Transforms.AnimationSamplerFactory.CalculateHermiteTangent(k);
+
+            var pp = p1 * hb.Item1 + p4 * hb.Item2 + (p2 - p1) * 4 * hb.Item3 + (p4 - p3) * 4 * hb.Item4;
+            var pt = p1 * ht.Item1 + p4 * ht.Item2 + (p2 - p1) * 4 * ht.Item3 + (p4 - p3) * 4 * ht.Item4;
+
+            // plotting
+
             var series1 = ppp.ToPointSeries();
             var series1 = ppp.ToPointSeries();
             var series2 = new[] { p1, p2, p3, p4 }.ToLineSeries();
             var series2 = new[] { p1, p2, p3, p4 }.ToLineSeries();
+            var series3 = new[] { pp, pp + pt }.ToLineSeries();
 
 
-            new[] { series1, series2 }.AttachToCurrentTest("plot.png");
+            new[] { series1, series2, series3 }.AttachToCurrentTest("plot.png");
         }
         }
 
 
         [Test]
         [Test]
@@ -54,7 +67,7 @@ namespace SharpGLTF
 
 
             for (float amount = 0; amount <= 1; amount += 0.01f)
             for (float amount = 0; amount <= 1; amount += 0.01f)
             {
             {
-                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteWeights(amount);
+                var hermite = Transforms.AnimationSamplerFactory.CalculateHermiteBasis(amount);
 
 
                 var p = Vector2.Zero;
                 var p = Vector2.Zero;
 
 
@@ -70,7 +83,7 @@ namespace SharpGLTF
             var series2 = new[] { p1, p2, p3, p4 }.ToLineSeries();
             var series2 = new[] { p1, p2, p3, p4 }.ToLineSeries();
 
 
             new[] { series1, series2 }.AttachToCurrentTest("plot.png");
             new[] { series1, series2 }.AttachToCurrentTest("plot.png");
-        }
+        }        
 
 
         private static (float, (Vector3, Vector3, Vector3))[] _TransAnim = new []
         private static (float, (Vector3, Vector3, Vector3))[] _TransAnim = new []
         {
         {

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

@@ -19,7 +19,55 @@ namespace SharpGLTF.Scenes
     public class SceneBuilderTests
     public class SceneBuilderTests
     {
     {
         [Test]
         [Test]
-        public void CreateSceneWithRandomCubes()
+        public void CreateCubeScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            var mesh = new Cube<Materials.MaterialBuilder>(new Materials.MaterialBuilder())
+                .ToMesh(Matrix4x4.Identity);
+
+            var scene = new SceneBuilder();
+
+            scene.AddMesh(mesh, Matrix4x4.Identity);
+
+            scene.AttachToCurrentTest("cubes.glb");
+        }
+
+        [Test]
+        public void CreateAnimatedCubeScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLinks();
+
+            var mesh = new Cube<Materials.MaterialBuilder>(new Materials.MaterialBuilder())
+                .ToMesh(Matrix4x4.Identity);
+
+            var pivot = new NodeBuilder();
+
+            var tcurve = pivot.UseTranslation().UseSplineCurve("default");
+            tcurve.SetControlPoint(0, Vector3.Zero);
+
+            tcurve.SetControlPoint(1, new Vector3(10, 0, 0));
+            tcurve.SetCardinalPointOut(1, new Vector3(10, 10, 0));
+            
+            tcurve.SetControlPoint(2, new Vector3(10, 0, 0));
+            tcurve.SetCardinalPointIn(2, new Vector3(20, 10, 0));
+
+            tcurve.SetControlPoint(3, new Vector3(20, 0, 0));
+
+            tcurve.SetControlPoint(4, new Vector3(10, -10, 0));
+
+            var scene = new SceneBuilder();
+
+            scene.AddMesh(mesh, pivot);
+
+            scene.AttachToCurrentTest("animated.glb");
+            scene.AttachToCurrentTest("animated.gltf");
+        }
+
+        [Test]
+        public void CreateSceneWithRandomShapes()
         {
         {
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachShowDirLink();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
             TestContext.CurrentContext.AttachGltfValidatorLinks();
@@ -41,22 +89,24 @@ namespace SharpGLTF.Scenes
             {
             {
                 // create mesh
                 // create mesh
                 var m = materials[rnd.Next(0, 10)];
                 var m = materials[rnd.Next(0, 10)];
-                var cube = VPOSNRM.CreateCompatibleMesh("cube");
-                cube.VertexPreprocessor.SetDebugPreprocessors();
-                cube.AddCube(m, Matrix4x4.Identity);
-                cube.Validate();
+                var s = VPOSNRM.CreateCompatibleMesh("shape");
+                s.VertexPreprocessor.SetDebugPreprocessors();
 
 
-                // create transform
+                if ((i & 1) == 0) s.AddCube(m, Matrix4x4.Identity);
+                else s.AddSphere(m, 0.5f, Matrix4x4.Identity);
+
+                s.Validate();
+
+                // create random transform
                 var r = rnd.NextVector3() * 5;
                 var r = rnd.NextVector3() * 5;
                 var xform = Matrix4x4.CreateFromYawPitchRoll(r.X, r.Y, r.Z) * Matrix4x4.CreateTranslation(rnd.NextVector3() * 25);
                 var xform = Matrix4x4.CreateFromYawPitchRoll(r.X, r.Y, r.Z) * Matrix4x4.CreateTranslation(rnd.NextVector3() * 25);
 
 
-                scene.AddMesh(cube, xform);                
+                scene.AddMesh(s, xform);                
             }
             }
 
 
             // save the model as GLB
             // save the model as GLB
 
 
-            scene.AttachToCurrentTest("cubes.glb");
+            scene.AttachToCurrentTest("shapes.glb");
         }
         }
-
     }
     }
 }
 }

+ 163 - 64
tests/SharpGLTF.Tests/Schema2/Authoring/SolidMeshUtils.cs

@@ -3,32 +3,63 @@ using System.Collections.Generic;
 using System.Numerics;
 using System.Numerics;
 using System.Text;
 using System.Text;
 
 
+using SharpGLTF.Geometry;
+using SharpGLTF.Geometry.VertexTypes;
+
 namespace SharpGLTF.Schema2.Authoring
 namespace SharpGLTF.Schema2.Authoring
 {
 {
-    using Geometry;
+    using VERTEX = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexEmpty>;
 
 
-    using VEMPTY = Geometry.VertexTypes.VertexEmpty;
-    using VPOS = Geometry.VertexTypes.VertexPosition;
-    using VPOSNRM = Geometry.VertexTypes.VertexPositionNormal;
-    using VTEX1 = Geometry.VertexTypes.VertexTexture1;    
+    interface IParametricShape<TMaterial>
+    {
+        void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform);
+    }
 
 
-    static class SolidMeshUtils
+    class Cube<TMaterial> : IParametricShape<TMaterial>
     {
     {
-        public static void AddCube<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Matrix4x4 xform)
+        #region lifecycle
+
+        public Cube(TMaterial material)
         {
         {
-            var p = meshBuilder.UsePrimitive(material);
+            _Front = _Back = _Left = _Right = _Top = _Bottom = material;
+        }
+
+        #endregion
 
 
-            p._AddCubeFace(Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ, xform);
-            p._AddCubeFace(-Vector3.UnitX, Vector3.UnitZ, Vector3.UnitY, xform);
+        #region data
 
 
-            p._AddCubeFace(Vector3.UnitY, Vector3.UnitZ, Vector3.UnitX, xform);
-            p._AddCubeFace(-Vector3.UnitY, Vector3.UnitX, Vector3.UnitZ, xform);
+        private Vector3 _Size = Vector3.One;
 
 
-            p._AddCubeFace(Vector3.UnitZ, Vector3.UnitX, Vector3.UnitY, xform);
-            p._AddCubeFace(-Vector3.UnitZ, Vector3.UnitY, Vector3.UnitX, xform);
+        private TMaterial _Front;
+        private TMaterial _Back;
+
+        private TMaterial _Left;
+        private TMaterial _Right;
+
+        private TMaterial _Top;
+        private TMaterial _Bottom;
+
+        #endregion
+
+        #region API
+
+        public void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
+        {
+            var x = Vector3.UnitX * _Size.X * 0.5f;
+            var y = Vector3.UnitY * _Size.Y * 0.5f;
+            var z = Vector3.UnitZ * _Size.Z * 0.5f;
+
+            _AddCubeFace(meshBuilder.UsePrimitive(_Right), x, y, z, xform);
+            _AddCubeFace(meshBuilder.UsePrimitive(_Left), -x, z, y, xform);
+
+            _AddCubeFace(meshBuilder.UsePrimitive(_Top), y, z, x, xform);
+            _AddCubeFace(meshBuilder.UsePrimitive(_Bottom), -y, x, z, xform);
+
+            _AddCubeFace(meshBuilder.UsePrimitive(_Front), z, x, y, xform);
+            _AddCubeFace(meshBuilder.UsePrimitive(_Back), -z, y, x, xform);
         }
         }
 
 
-        private static void _AddCubeFace<TMaterial>(this PrimitiveBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> primitiveBuilder, Vector3 origin, Vector3 axisX, Vector3 axisY, Matrix4x4 xform)
+        private static void _AddCubeFace(IPrimitiveBuilder primitiveBuilder, Vector3 origin, Vector3 axisX, Vector3 axisY, Matrix4x4 xform)
         {
         {
             var p1 = Vector3.Transform(origin - axisX - axisY, xform);
             var p1 = Vector3.Transform(origin - axisX - axisY, xform);
             var p2 = Vector3.Transform(origin + axisX - axisY, xform);
             var p2 = Vector3.Transform(origin + axisX - axisY, xform);
@@ -38,72 +69,109 @@ namespace SharpGLTF.Schema2.Authoring
 
 
             primitiveBuilder.AddConvexPolygon
             primitiveBuilder.AddConvexPolygon
                 (
                 (
-                new VPOSNRM(p1, n),
-                new VPOSNRM(p2, n),
-                new VPOSNRM(p3, n),
-                new VPOSNRM(p4, n)
+                new VERTEX( (p1, n), (Vector4.One, Vector2.Zero)  ),
+                new VERTEX( (p2, n), (Vector4.One, Vector2.UnitX) ),
+                new VERTEX( (p3, n), (Vector4.One, Vector2.One)   ),
+                new VERTEX( (p4, n), (Vector4.One, Vector2.UnitY) )
                 );
                 );
         }
         }
 
 
-        public static void AddSphere<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Single radius, Matrix4x4 xform)
+        public MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty> ToMesh(Matrix4x4 xform)
+        {
+            var mesh = new MeshBuilder<TMaterial, VertexPositionNormal, VertexColor1Texture1, VertexEmpty>();
+
+            AddTo(mesh, xform);
+
+            return mesh;
+        }
+
+        #endregion
+    }
+
+    class IcoSphere<TMaterial> : IParametricShape<TMaterial>
+    {
+        #region lifecycle
+
+        public IcoSphere(TMaterial material, float radius = 0.5f)
+        {
+            _Material = material;
+            _Radius = radius;
+        }
+        
+        #endregion
+
+        #region data
+
+        private float _Radius = 0.5f;
+        private TMaterial _Material;
+        private int _Subdivision = 3;
+
+        #endregion
+
+        #region API        
+
+        public void AddTo(IMeshBuilder<TMaterial> meshBuilder, Matrix4x4 xform)
         {
         {
             // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
             // http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
 
 
             var t = 1 + (float)(Math.Sqrt(5.0) / 2);
             var t = 1 + (float)(Math.Sqrt(5.0) / 2);
 
 
-            var v0 = new Vector3(-1, t, 0) * radius;
-            var v1 = new Vector3(1, t, 0) * radius;
-            var v2 = new Vector3(-1, -t, 0) * radius;
-            var v3 = new Vector3(1, -t, 0) * radius;
+            var v0 = new Vector3(-1, t, 0) * _Radius;
+            var v1 = new Vector3(1, t, 0) * _Radius;
+            var v2 = new Vector3(-1, -t, 0) * _Radius;
+            var v3 = new Vector3(1, -t, 0) * _Radius;
 
 
-            var v4 = new Vector3(0, -1, t) * radius;
-            var v5 = new Vector3(0, 1, t) * radius;
-            var v6 = new Vector3(0, -1, -t) * radius;
-            var v7 = new Vector3(0, 1, -t) * radius;
+            var v4 = new Vector3(0, -1, t) * _Radius;
+            var v5 = new Vector3(0, 1, t) * _Radius;
+            var v6 = new Vector3(0, -1, -t) * _Radius;
+            var v7 = new Vector3(0, 1, -t) * _Radius;
 
 
-            var v8 = new Vector3(t, 0, -1) * radius;
-            var v9 = new Vector3(t, 0, 1) * radius;
-            var v10 = new Vector3(-t, 0, -1) * radius;
-            var v11 = new Vector3(-t, 0, 1) * radius;
+            var v8 = new Vector3(t, 0, -1) * _Radius;
+            var v9 = new Vector3(t, 0, 1) * _Radius;
+            var v10 = new Vector3(-t, 0, -1) * _Radius;
+            var v11 = new Vector3(-t, 0, 1) * _Radius;
+
+            var prim = meshBuilder.UsePrimitive(_Material);
 
 
             // 5 faces around point 0
             // 5 faces around point 0
-            meshBuilder._AddSphereTriangle(material, xform, v0, v11, v5, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v0, v5, v1, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v0, v1, v7, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v0, v7, v10, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v0, v10, v11, 3);
+            _AddSphereFace(prim, xform, v0, v11, v5, _Subdivision);
+            _AddSphereFace(prim, xform, v0, v5, v1, _Subdivision);
+            _AddSphereFace(prim, xform, v0, v1, v7, _Subdivision);
+            _AddSphereFace(prim, xform, v0, v7, v10, _Subdivision);
+            _AddSphereFace(prim, xform, v0, v10, v11, _Subdivision);
 
 
             // 5 adjacent faces
             // 5 adjacent faces
-            meshBuilder._AddSphereTriangle(material, xform, v1, v5, v9, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v5, v11, v4, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v11, v10, v2, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v10, v7, v6, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v7, v1, v8, 3);
+            _AddSphereFace(prim, xform, v1, v5, v9, _Subdivision);
+            _AddSphereFace(prim, xform, v5, v11, v4, _Subdivision);
+            _AddSphereFace(prim, xform, v11, v10, v2, _Subdivision);
+            _AddSphereFace(prim, xform, v10, v7, v6, _Subdivision);
+            _AddSphereFace(prim, xform, v7, v1, v8, _Subdivision);
 
 
             // 5 faces around point 3
             // 5 faces around point 3
-            meshBuilder._AddSphereTriangle(material, xform, v3, v9, v4, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v3, v4, v2, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v3, v2, v6, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v3, v6, v8, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v3, v8, v9, 3);
+            _AddSphereFace(prim, xform, v3, v9, v4, _Subdivision);
+            _AddSphereFace(prim, xform, v3, v4, v2, _Subdivision);
+            _AddSphereFace(prim, xform, v3, v2, v6, _Subdivision);
+            _AddSphereFace(prim, xform, v3, v6, v8, _Subdivision);
+            _AddSphereFace(prim, xform, v3, v8, v9, _Subdivision);
 
 
             // 5 adjacent faces
             // 5 adjacent faces
-            meshBuilder._AddSphereTriangle(material, xform, v4, v9, v5, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v2, v4, v11, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v6, v2, v10, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v8, v6, v7, 3);
-            meshBuilder._AddSphereTriangle(material, xform, v9, v8, v1, 3);
+            _AddSphereFace(prim, xform, v4, v9, v5, _Subdivision);
+            _AddSphereFace(prim, xform, v2, v4, v11, _Subdivision);
+            _AddSphereFace(prim, xform, v6, v2, v10, _Subdivision);
+            _AddSphereFace(prim, xform, v8, v6, v7, _Subdivision);
+            _AddSphereFace(prim, xform, v9, v8, v1, _Subdivision);
         }
         }
 
 
-        private static void _AddSphereTriangle<TMaterial>(this MeshBuilder<TMaterial, VPOSNRM, VEMPTY, VEMPTY> meshBuilder, TMaterial material, Matrix4x4 xform, Vector3 a, Vector3 b, Vector3 c, int iterations = 0)
+        private static void _AddSphereFace(IPrimitiveBuilder primitiveBuilder, Matrix4x4 xform, Vector3 a, Vector3 b, Vector3 c, int iterations = 0)
         {
         {
-            if (iterations <=0)
+            if (iterations <= 0)
             {
             {
-                var aa = new VPOSNRM(Vector3.Transform(a, xform), Vector3.Normalize(Vector3.TransformNormal(a, xform)));
-                var bb = new VPOSNRM(Vector3.Transform(b, xform), Vector3.Normalize(Vector3.TransformNormal(b, xform)));
-                var cc = new VPOSNRM(Vector3.Transform(c, xform), Vector3.Normalize(Vector3.TransformNormal(c, xform)));
+                var tt = (a + b + c) / 3.0f;
 
 
-                meshBuilder.UsePrimitive(material).AddTriangle(aa, bb, cc);
+                var aa = _CreateVertex(a, xform);
+                var bb = _CreateVertex(b, xform);
+                var cc = _CreateVertex(c, xform);
+                primitiveBuilder.AddTriangle(aa, bb, cc);
                 return;
                 return;
             }
             }
 
 
@@ -114,22 +182,53 @@ namespace SharpGLTF.Schema2.Authoring
             var ca = Vector3.Normalize(c + a) * c.Length();
             var ca = Vector3.Normalize(c + a) * c.Length();
 
 
             // central triangle
             // central triangle
-            _AddSphereTriangle(meshBuilder, material, xform, ab, bc, ca, iterations);
+            _AddSphereFace(primitiveBuilder, xform, ab, bc, ca, iterations);
 
 
             // vertex triangles
             // vertex triangles
-            _AddSphereTriangle(meshBuilder, material, xform, a, ab, ca, iterations);
-            _AddSphereTriangle(meshBuilder, material, xform, b, bc, ab, iterations);
-            _AddSphereTriangle(meshBuilder, material, xform, c, ca, bc, iterations);
+            _AddSphereFace(primitiveBuilder, xform, a, ab, ca, iterations);
+            _AddSphereFace(primitiveBuilder, xform, b, bc, ab, iterations);
+            _AddSphereFace(primitiveBuilder, xform, c, ca, bc, iterations);
+        }
+
+        private static VERTEX _CreateVertex(Vector3 position, Matrix4x4 xform)
+        {
+            var v = new VERTEX();
+
+            v.Geometry.Position = Vector3.Transform(position, xform);
+            v.Geometry.Normal = Vector3.Normalize(Vector3.TransformNormal(position, xform));
+            v.Material.Color = Vector4.One;
+            v.Material.TexCoord = Vector2.Zero;
+
+            return v;
+        }
+
+        #endregion
+    }
+
+    static class SolidMeshUtils
+    {
+        public static void AddCube<TMaterial>(this IMeshBuilder<TMaterial> meshBuilder, TMaterial material, Matrix4x4 xform)
+        {
+            var cube = new Cube<TMaterial>(material);
+
+            cube.AddTo(meshBuilder, xform);
+        }
+
+        public static void AddSphere<TMaterial>(this IMeshBuilder<TMaterial> meshBuilder, TMaterial material, Single radius, Matrix4x4 xform)
+        {
+            var sphere = new IcoSphere<TMaterial>(material, radius);
+
+            sphere.AddTo(meshBuilder, xform);
         }
         }
 
 
-        public static MeshBuilder<VPOS, VTEX1> CreateTerrainMesh(int width, int length, Func<int,int,float> heightFunction, string terrainColorImagePath)
+        public static MeshBuilder<VertexPosition, VertexTexture1> CreateTerrainMesh(int width, int length, Func<int,int,float> heightFunction, string terrainColorImagePath)
         {
         {
             // we create a new material to use with the terrain mesh
             // we create a new material to use with the terrain mesh
             var material = new Materials.MaterialBuilder("TerrainMaterial")
             var material = new Materials.MaterialBuilder("TerrainMaterial")
                 .WithChannelImage(Materials.KnownChannels.BaseColor, terrainColorImagePath);
                 .WithChannelImage(Materials.KnownChannels.BaseColor, terrainColorImagePath);
 
 
             // we create a MeshBuilder
             // we create a MeshBuilder
-            var terrainMesh = new MeshBuilder<VPOS, VTEX1>("terrain");
+            var terrainMesh = new MeshBuilder<VertexPosition, VertexTexture1>("terrain");
 
 
             var texScale = new Vector2(width, length);
             var texScale = new Vector2(width, length);
 
 

+ 12 - 0
tests/SharpGLTF.Tests/ToolkitUtils.cs

@@ -24,5 +24,17 @@ namespace SharpGLTF
                 primitive.AddTriangle(a, b, c);
                 primitive.AddTriangle(a, b, c);
             }
             }
         }
         }
+
+        public static void AddConvexPolygon(this IPrimitiveBuilder primitive, params IVertexBuilder[] vertices)
+        {
+            for (int i = 2; i < vertices.Length; ++i)
+            {
+                var a = vertices[0];
+                var b = vertices[i - 1];
+                var c = vertices[i];
+
+                primitive.AddTriangle(a, b, c);
+            }
+        }
     }
     }
 }
 }