فهرست منبع

WIP: adding toolkit scene and animation features.

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

+ 12 - 0
src/SharpGLTF.Core/Transforms/AffineTransform.cs

@@ -16,6 +16,16 @@ namespace SharpGLTF.Transforms
     {
         #region lifecycle
 
+        public static AffineTransform Create(Matrix4x4 matrix)
+        {
+            return new AffineTransform(matrix, null, null, null);
+        }
+
+        public static AffineTransform Create(Vector3? s, Quaternion? r, Vector3? t)
+        {
+            return new AffineTransform(null, s, r, t);
+        }
+
         internal AffineTransform(Matrix4x4? m, Vector3? s, Quaternion? r, Vector3? t)
         {
             if (m.HasValue)
@@ -58,6 +68,8 @@ namespace SharpGLTF.Transforms
 
         #region properties
 
+        public static AffineTransform Identity => new AffineTransform { Rotation = Quaternion.Identity, Scale = Vector3.One, Translation = Vector3.Zero };
+
         /// <summary>
         /// Gets the <see cref="Matrix4x4"/> transform of the current <see cref="AffineTransform"/>
         /// </summary>

+ 14 - 6
src/SharpGLTF.Core/Transforms/AnimationSamplerFactory.cs

@@ -9,7 +9,15 @@ namespace SharpGLTF.Transforms
 
     internal static class AnimationSamplerFactory
     {
-        private static (T, T, float) _GetSample<T>(this IEnumerable<(float, T)> sequence, float offset)
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <typeparam name="T">The value type</typeparam>
+        /// <param name="sequence">A sequence of float+<typeparamref name="T"/> pairs</param>
+        /// <param name="offset">the time point within the sequence</param>
+        /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP them.</returns>
+        private static (T, T, float) _FindSample<T>(this IEnumerable<(float, T)> sequence, float offset)
         {
             (float, T)? left = null;
             (float, T)? right = null;
@@ -55,7 +63,7 @@ namespace SharpGLTF.Transforms
 
             T _sampler(float offset)
             {
-                var sample = collection._GetSample(offset);
+                var sample = collection._FindSample(offset);
                 return sample.Item1;
             }
 
@@ -68,7 +76,7 @@ namespace SharpGLTF.Transforms
 
             Vector3 _sampler(float offset)
             {
-                var sample = collection._GetSample(offset);
+                var sample = collection._FindSample(offset);
                 return Vector3.Lerp(sample.Item1, sample.Item2, sample.Item3);
             }
 
@@ -81,7 +89,7 @@ namespace SharpGLTF.Transforms
 
             Quaternion _sampler(float offset)
             {
-                var sample = collection._GetSample(offset);
+                var sample = collection._FindSample(offset);
                 return Quaternion.Slerp(sample.Item1, sample.Item2, sample.Item3);
             }
 
@@ -94,7 +102,7 @@ namespace SharpGLTF.Transforms
 
             float[] _sampler(float offset)
             {
-                var sample = collection._GetSample(offset);
+                var sample = collection._FindSample(offset);
                 var result = new float[sample.Item1.Length];
 
                 for (int i = 0; i < result.Length; ++i)
@@ -129,7 +137,7 @@ namespace SharpGLTF.Transforms
 
             T _sampler(float offset)
             {
-                var sample = collection._GetSample(offset);
+                var sample = collection._FindSample(offset);
 
                 return hermiteFunc(sample.Item1.Item2, sample.Item1.Item3, sample.Item2.Item2, sample.Item2.Item1, sample.Item3);
             }

+ 27 - 0
src/SharpGLTF.Toolkit/Animations/Animatable.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Animations
+{
+    public class Animatable<T>
+        where T : struct
+    {
+        #region data
+
+        private Dictionary<string, ICurveSampler<T>> _Tracks = new Dictionary<string, ICurveSampler<T>>();
+
+        public T Default { get; set; }
+
+        #endregion
+
+        #region API
+
+        public T GetValueAt(string track, float value)
+        {
+            return _Tracks.TryGetValue(track, out ICurveSampler<T> sampler) ? sampler.GetSample(value) : this.Default;
+        }
+
+        #endregion
+    }
+}

+ 161 - 0
src/SharpGLTF.Toolkit/Animations/Curves.cs

@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Animations
+{
+    // TODO: we could support conversions between linear and cubic (with hermite regression)
+
+    public interface ICurveSampler<T>
+        where T : struct
+    {
+        T GetSample(float offset);
+    }
+
+    public abstract class Curve<Tin, Tout> : ICurveSampler<Tout>
+        where Tin : struct
+        where Tout : struct
+    {
+        #region data
+
+        private SortedDictionary<float, Tin> _Keys = new SortedDictionary<float, Tin>();
+
+        #endregion
+
+        #region properties
+
+        public IReadOnlyCollection<float> Keys => _Keys.Keys;
+
+        #endregion
+
+        #region API
+
+        protected (Tin, Tin, float) FindSample(float offset)
+        {
+            return _FindSample(_Keys, offset);
+        }
+
+        public abstract Tout GetSample(float offset);
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <typeparam name="T">The value type</typeparam>
+        /// <param name="sequence">A sequence of float+<typeparamref name="T"/> pairs</param>
+        /// <param name="offset">the time point within the sequence</param>
+        /// <returns>Two consecutive <typeparamref name="T"/> values and a float amount to LERP them.</returns>
+        private static (T, T, float) _FindSample<T>(IEnumerable<KeyValuePair<float, T>> sequence, float offset)
+        {
+            KeyValuePair<float, T>? left = null;
+            KeyValuePair<float, T>? right = null;
+            KeyValuePair<float, T>? prev = null;
+
+            if (offset < 0) offset = 0;
+
+            foreach (var item in sequence)
+            {
+                if (item.Key == offset)
+                {
+                    left = item; continue;
+                }
+
+                if (item.Key > offset)
+                {
+                    if (left == null) left = prev;
+                    right = item;
+                    break;
+                }
+
+                prev = item;
+            }
+
+            if (left == null && right == null) return (default(T), default(T), 0);
+            if (left == null) return (right.Value.Value, right.Value.Value, 0);
+            if (right == null) return (left.Value.Value, left.Value.Value, 0);
+
+            var delta = right.Value.Key - left.Value.Key;
+
+            System.Diagnostics.Debug.Assert(delta > 0);
+
+            var amount = (offset - left.Value.Key) / delta;
+
+            System.Diagnostics.Debug.Assert(amount >= 0 && amount <= 1);
+
+            return (left.Value.Value, right.Value.Value, amount);
+        }
+
+        #endregion
+    }
+
+    class Vector3LinearCurve : Curve<Vector3, Vector3>
+    {
+        public override Vector3 GetSample(float offset)
+        {
+            var sample = FindSample(offset);
+
+            return Vector3.Lerp(sample.Item1, sample.Item2, sample.Item3);
+        }
+    }
+
+    class Vector3CubicCurve : Curve<(Vector3, Vector3, Vector3), Vector3>
+    {
+        public override Vector3 GetSample(float offset)
+        {
+            var sample = FindSample(offset);
+
+            return Hermite(sample.Item1.Item2, sample.Item1.Item3, sample.Item2.Item2, sample.Item2.Item1, sample.Item3);
+        }
+
+        private static Vector3 Hermite(Vector3 value1, Vector3 tangent1, Vector3 value2, Vector3 tangent2, float amount)
+        {
+            // http://mathworld.wolfram.com/HermitePolynomial.html
+
+            var squared = amount * amount;
+            var cubed = amount * squared;
+
+            var part1 = (2.0f * cubed) - (3.0f * squared) + 1.0f;
+            var part2 = (-2.0f * cubed) + (3.0f * squared);
+            var part3 = (cubed - (2.0f * squared)) + amount;
+            var part4 = cubed - squared;
+
+            return (value1 * part1) + (value2 * part2) + (tangent1 * part3) + (tangent2 * part4);
+        }
+    }
+
+    class QuaternionLinearCurve : Curve<Quaternion, Quaternion>
+    {
+        public override Quaternion GetSample(float offset)
+        {
+            var sample = FindSample(offset);
+
+            return Quaternion.Slerp(sample.Item1, sample.Item2, sample.Item3);
+        }
+    }
+
+    class QuaternionCubicCurve : Curve<(Quaternion, Quaternion, Quaternion), Quaternion>
+    {
+        public override Quaternion GetSample(float offset)
+        {
+            var sample = FindSample(offset);
+
+            return Hermite(sample.Item1.Item2, sample.Item1.Item3, sample.Item2.Item2, sample.Item2.Item1, sample.Item3);
+        }
+
+        private static Quaternion Hermite(Quaternion value1, Quaternion tangent1, Quaternion value2, Quaternion tangent2, float amount)
+        {
+            // http://mathworld.wolfram.com/HermitePolynomial.html
+
+            var squared = amount * amount;
+            var cubed = amount * squared;
+
+            var part1 = (2.0f * cubed) - (3.0f * squared) + 1.0f;
+            var part2 = (-2.0f * cubed) + (3.0f * squared);
+            var part3 = (cubed - (2.0f * squared)) + amount;
+            var part4 = cubed - squared;
+
+            return Quaternion.Normalize((value1 * part1) + (value2 * part2) + (tangent1 * part3) + (tangent2 * part4));
+        }
+    }
+}

+ 159 - 0
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Text;
+
+namespace SharpGLTF.Scenes
+{
+    /// <summary>
+    /// Defines a node object within an armature.
+    /// </summary>
+    public class NodeBuilder
+    {
+        #region lifecycle
+
+        public NodeBuilder() { }
+
+        internal NodeBuilder(NodeBuilder parent)
+        {
+            _Parent = parent;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly NodeBuilder _Parent;
+
+        private readonly List<NodeBuilder> _Children = new List<NodeBuilder>();
+
+        private Matrix4x4? _Matrix;
+        private Animations.Animatable<Vector3> _Scale;
+        private Animations.Animatable<Quaternion> _Rotation;
+        private Animations.Animatable<Vector3> _Translation;
+
+        #endregion
+
+        #region properties - hierarchy
+
+        public String Name { get; set; }
+
+        public NodeBuilder Parent => _Parent;
+
+        public IReadOnlyList<NodeBuilder> Children => _Children;
+
+        #endregion
+
+        #region properties - transform
+
+        /// <summary>
+        /// Gets or sets the local transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
+        /// </summary>
+        public Matrix4x4 LocalMatrix
+        {
+            get => Transforms.AffineTransform.Evaluate(_Matrix, _Scale?.Default, _Rotation?.Default, _Translation?.Default);
+            set
+            {
+                if (value == Matrix4x4.Identity)
+                {
+                    _Matrix = null;
+                }
+                else
+                {
+                    _Matrix = value;
+                }
+
+                _Scale = null;
+                _Rotation = null;
+                _Translation = null;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the local Scale, Rotation and Translation of this <see cref="NodeBuilder"/>.
+        /// </summary>
+        public Transforms.AffineTransform LocalTransform
+        {
+            get => _Matrix.HasValue
+                ?
+                Transforms.AffineTransform.Create(_Matrix.Value)
+                :
+                Transforms.AffineTransform.Create(_Scale?.Default, _Rotation?.Default, _Translation?.Default);
+            set
+            {
+                Guard.IsTrue(value.IsValid, nameof(value));
+
+                _Matrix = null;
+
+                if (value.Scale != Vector3.One)
+                {
+                    if (_Scale == null) _Scale = new Animations.Animatable<Vector3>();
+                    _Scale.Default = value.Scale;
+                }
+
+                if (value.Rotation != Quaternion.Identity)
+                {
+                    if (_Rotation == null) _Rotation = new Animations.Animatable<Quaternion>();
+                    _Rotation.Default = value.Rotation;
+                }
+
+                if (value.Translation != Vector3.Zero)
+                {
+                    if (_Translation == null) _Translation = new Animations.Animatable<Vector3>();
+                    _Translation.Default = value.Scale;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the world transform <see cref="Matrix4x4"/> of this <see cref="NodeBuilder"/>.
+        /// </summary>
+        public Matrix4x4 WorldMatrix
+        {
+            get
+            {
+                var vs = this.Parent;
+                return vs == null ? LocalMatrix : Transforms.AffineTransform.LocalToWorld(vs.WorldMatrix, LocalMatrix);
+            }
+            set
+            {
+                var vs = this.Parent;
+                LocalMatrix = vs == null ? value : Transforms.AffineTransform.WorldToLocal(vs.WorldMatrix, value);
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        public NodeBuilder AddNode(string name = null)
+        {
+            var c = new NodeBuilder(this);
+            _Children.Add(c);
+            c.Name = name;
+            return c;
+        }
+
+        public Transforms.AffineTransform GetLocalTransform(string animationTrack, float time)
+        {
+            if (animationTrack == null) return this.LocalTransform;
+
+            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);
+        }
+
+        public Matrix4x4 GetWorldMatrix(string animationTrack, float time)
+        {
+            if (animationTrack == null) return this.WorldMatrix;
+
+            var vs = Parent;
+            var lm = GetLocalTransform(animationTrack, time).Matrix;
+            return vs == null ? lm : Transforms.AffineTransform.LocalToWorld(vs.GetWorldMatrix(animationTrack, time), lm);
+        }
+
+        #endregion
+    }
+}

+ 1 - 1
src/SharpGLTF.Toolkit/SharpGLTF.Toolkit.csproj

@@ -39,7 +39,7 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.2" >
+    <PackageReference Include="Microsoft.CodeQuality.Analyzers" Version="2.9.2">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
     </PackageReference>