Browse Source

improving animation sampling performance...

Vicente Penades 6 years ago
parent
commit
fc6e3ea6ff

+ 1 - 1
examples/SharpGLTF.Runtime.MonoGame/MonoGameModelTemplate.cs

@@ -22,7 +22,7 @@ namespace SharpGLTF.Runtime
         {
         {
             srcModel.FixTextureSampler();
             srcModel.FixTextureSampler();
 
 
-            var template = Runtime.SceneTemplate.Create(srcModel.DefaultScene, false);
+            var template = Runtime.SceneTemplate.Create(srcModel.DefaultScene, true);
 
 
             var context = new LoaderContext(device);
             var context = new LoaderContext(device);
 
 

+ 20 - 0
src/SharpGLTF.Core/Animations/CubicSamplers.cs

@@ -57,6 +57,16 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }
 
 
+        public ICurveSampler<Vector3> ToFastSampler()
+        {
+            var split = _Sequence
+                .SplitByTime()
+                .Select(item => new Vector3CubicSampler(item))
+                .Cast<ICurveSampler<Vector3>>();
+
+            return new FastSampler<Vector3>(split);
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -111,6 +121,16 @@ namespace SharpGLTF.Animations
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
             return _Sequence.ToDictionary(pair => pair.Item1, pair => pair.Item2);
         }
         }
 
 
+        public ICurveSampler<Quaternion> ToFastSampler()
+        {
+            var split = _Sequence
+                .SplitByTime()
+                .Select(item => new QuaternionCubicSampler(item))
+                .Cast<ICurveSampler<Quaternion>>();
+
+            return new FastSampler<Quaternion>(split);
+        }
+
         #endregion
         #endregion
     }
     }
 
 

+ 32 - 0
src/SharpGLTF.Core/Animations/FastSampler.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace SharpGLTF.Animations
+{
+    /// <summary>
+    /// Wraps a collection of samplers split over time to speed up key retrieval.
+    /// </summary>
+    /// <typeparam name="T">The value sampled at any offset</typeparam>
+    struct FastSampler<T> : ICurveSampler<T>
+    {
+        public FastSampler(IEnumerable<ICurveSampler<T>> samplers)
+        {
+            _Samplers = samplers.ToArray();
+        }
+
+        private readonly ICurveSampler<T>[] _Samplers;
+
+        public T GetPoint(float offset)
+        {
+            if (offset < 0) offset = 0;
+
+            var index = (int)offset;
+
+            if (index >= _Samplers.Length) index = _Samplers.Length - 1;
+
+            return _Samplers[index].GetPoint(offset);
+        }
+    }
+}

+ 22 - 0
src/SharpGLTF.Core/Animations/LinearSamplers.cs

@@ -58,6 +58,17 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
+        public ICurveSampler<Vector3> ToFastSampler()
+        {
+            var linear = _Linear;
+            var split = _Sequence
+                .SplitByTime()
+                .Select(item => new Vector3LinearSampler(item, linear))
+                .Cast<ICurveSampler<Vector3>>();
+
+            return new FastSampler<Vector3>(split);
+        }
+
         #endregion
         #endregion
     }
     }
 
 
@@ -113,6 +124,17 @@ namespace SharpGLTF.Animations
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
+        public ICurveSampler<Quaternion> ToFastSampler()
+        {
+            var linear = _Linear;
+            var split = _Sequence
+                .SplitByTime()
+                .Select(item => new QuaternionLinearSampler(item, linear))
+                .Cast<ICurveSampler<Quaternion>>();
+
+            return new FastSampler<Quaternion>(split);
+        }
+
         #endregion
         #endregion
     }
     }
 
 

+ 54 - 8
src/SharpGLTF.Core/Animations/SamplerFactory.cs

@@ -226,6 +226,44 @@ namespace SharpGLTF.Animations
             return (left.Value, right.Value, amount);
             return (left.Value, right.Value, amount);
         }
         }
 
 
+        internal static IEnumerable<(float, T)[]> SplitByTime<T>(this IEnumerable<(Single Time, T Value)> sequence)
+        {
+            if (!sequence.Any()) yield break;
+
+            var segment = new List<(float, T)>();
+            int time = 0;
+
+            var last = sequence.First();
+
+            foreach (var item in sequence)
+            {
+                var t = (int)item.Time;
+
+                if (time > t) throw new InvalidOperationException("unexpected data encountered.");
+
+                while (time < t)
+                {
+                    if (segment.Count == 0 && item.Time > last.Time) segment.Add(last);
+
+                    segment.Add(item);
+                    yield return segment.ToArray();
+                    segment.Clear();
+
+                    ++time;
+                }
+
+                if (time == t)
+                {
+                    if (segment.Count == 0 && time > (int)last.Time && time < item.Time) segment.Add(last);
+                    segment.Add(item);
+                }
+
+                last = item;
+            }
+
+            if (segment.Count > 0) yield return segment.ToArray();
+        }
+
         #endregion
         #endregion
 
 
         #region interpolation utils
         #region interpolation utils
@@ -291,18 +329,22 @@ namespace SharpGLTF.Animations
             return collection;
             return collection;
         }
         }
 
 
-        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(Single, Vector3)> collection, bool isLinear = true)
+        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(Single, Vector3)> collection, bool isLinear = true, bool optimize = false)
         {
         {
             if (collection == null) return null;
             if (collection == null) return null;
 
 
-            return new Vector3LinearSampler(collection, isLinear);
+            var sampler = new Vector3LinearSampler(collection, isLinear);
+
+            return optimize ? sampler.ToFastSampler() : sampler;
         }
         }
 
 
-        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(Single, Quaternion)> collection, bool isLinear = true)
+        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(Single, Quaternion)> collection, bool isLinear = true, bool optimize = false)
         {
         {
             if (collection == null) return null;
             if (collection == null) return null;
 
 
-            return new QuaternionLinearSampler(collection, isLinear);
+            var sampler = new QuaternionLinearSampler(collection, isLinear);
+
+            return optimize ? sampler.ToFastSampler() : sampler;
         }
         }
 
 
         public static ICurveSampler<Transforms.SparseWeight8> CreateSampler(this IEnumerable<(Single, Transforms.SparseWeight8)> collection, bool isLinear = true)
         public static ICurveSampler<Transforms.SparseWeight8> CreateSampler(this IEnumerable<(Single, Transforms.SparseWeight8)> collection, bool isLinear = true)
@@ -319,18 +361,22 @@ namespace SharpGLTF.Animations
             return new ArrayLinearSampler(collection, isLinear);
             return new ArrayLinearSampler(collection, isLinear);
         }
         }
 
 
-        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(Single, (Vector3, Vector3, Vector3))> collection)
+        public static ICurveSampler<Vector3> CreateSampler(this IEnumerable<(Single, (Vector3, Vector3, Vector3))> collection, bool optimize = false)
         {
         {
             if (collection == null) return null;
             if (collection == null) return null;
 
 
-            return new Vector3CubicSampler(collection);
+            var sampler = new Vector3CubicSampler(collection);
+
+            return optimize ? sampler.ToFastSampler() : sampler;
         }
         }
 
 
-        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> collection)
+        public static ICurveSampler<Quaternion> CreateSampler(this IEnumerable<(Single, (Quaternion, Quaternion, Quaternion))> collection, bool optimize = false)
         {
         {
             if (collection == null) return null;
             if (collection == null) return null;
 
 
-            return new QuaternionCubicSampler(collection);
+            var sampler = new QuaternionCubicSampler(collection);
+
+            return optimize ? sampler.ToFastSampler() : sampler;
         }
         }
 
 
         public static ICurveSampler<Transforms.SparseWeight8> CreateSampler(this IEnumerable<(Single, (Transforms.SparseWeight8, Transforms.SparseWeight8, Transforms.SparseWeight8))> collection)
         public static ICurveSampler<Transforms.SparseWeight8> CreateSampler(this IEnumerable<(Single, (Transforms.SparseWeight8, Transforms.SparseWeight8, Transforms.SparseWeight8))> collection)

+ 8 - 8
src/SharpGLTF.Core/Schema2/gltf.Animations.cs

@@ -683,9 +683,9 @@ namespace SharpGLTF.Schema2
 
 
             switch (this.InterpolationMode)
             switch (this.InterpolationMode)
             {
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler(isolateMemory);
             }
             }
 
 
             throw new NotImplementedException();
             throw new NotImplementedException();
@@ -697,9 +697,9 @@ namespace SharpGLTF.Schema2
 
 
             switch (this.InterpolationMode)
             switch (this.InterpolationMode)
             {
             {
-                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
-                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false, isolateMemory);
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(true, isolateMemory);
+                case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler(isolateMemory);
             }
             }
 
 
             throw new NotImplementedException();
             throw new NotImplementedException();
@@ -712,7 +712,7 @@ namespace SharpGLTF.Schema2
             switch (this.InterpolationMode)
             switch (this.InterpolationMode)
             {
             {
                 case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
                 case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(true);
                 case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
                 case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
             }
 
 
@@ -726,7 +726,7 @@ namespace SharpGLTF.Schema2
             switch (this.InterpolationMode)
             switch (this.InterpolationMode)
             {
             {
                 case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
                 case AnimationInterpolationMode.STEP: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(false);
-                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler();
+                case AnimationInterpolationMode.LINEAR: return xsampler.GetLinearKeys(isolateMemory).CreateSampler(true);
                 case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
                 case AnimationInterpolationMode.CUBICSPLINE: return xsampler.GetCubicKeys(isolateMemory).CreateSampler();
             }
             }
 
 

+ 93 - 0
tests/SharpGLTF.Tests/AnimationSamplingTests.cs

@@ -12,6 +12,99 @@ namespace SharpGLTF
     [Category("Core.Animations")]
     [Category("Core.Animations")]
     public class AnimationSamplingTests
     public class AnimationSamplingTests
     {
     {
+        [Test]
+        public void TestAnimationSplit()
+        {
+            var anim0 = new[]
+            {
+                (0.1f, 1),                
+            };
+
+            var anim1 = new[]
+            {
+                (0.1f, 1),
+                (0.2f, 2)
+            };
+
+            var anim2 = new[]
+            {
+                (0.1f, 1),
+                (0.2f, 2),
+                (3.2f, 2),
+                (3.3f, 2)
+            };
+
+            var anim3 = new[]
+            {
+                (2.1f, 1),
+                (2.2f, 2),
+                (3.2f, 3),
+                (3.3f, 4),
+                (4.0f, 5),
+                (4.1f, 6),
+                (5.0f, 7),
+            };
+
+            void checkSegment(int time, (float,int)[] segment)
+            {
+                // should check all times are incremental
+                Assert.Greater(segment.Length, 1);
+                Assert.LessOrEqual(segment.First().Item1, time);
+                Assert.Greater(segment.Last().Item1, time);
+            }
+
+
+            var r0 = Animations.SamplerFactory.SplitByTime(anim0).ToArray();
+            Assert.AreEqual(1, r0.Length);
+            Assert.AreEqual(1, r0[0].Length);
+
+            var r1 = Animations.SamplerFactory.SplitByTime(anim1).ToArray();
+            Assert.AreEqual(1, r1.Length);
+            Assert.AreEqual(2, r1[0].Length);
+
+            var r2 = Animations.SamplerFactory.SplitByTime(anim2).ToArray();
+            Assert.AreEqual(4, r2.Length);
+            Assert.AreEqual(3, r2[0].Length); 
+            Assert.AreEqual(2, r2[1].Length); checkSegment(1, r2[1]);
+            Assert.AreEqual(2, r2[2].Length); checkSegment(2, r2[2]);
+            Assert.AreEqual(3, r2[3].Length); checkSegment(3, r2[3]);
+
+            var r3 = Animations.SamplerFactory.SplitByTime(anim3).ToArray();
+            Assert.AreEqual(6, r3.Length);
+            Assert.AreEqual(1, r3[0].Length); 
+            Assert.AreEqual(1, r3[1].Length); 
+            Assert.AreEqual(3, r3[2].Length); 
+            Assert.AreEqual(4, r3[3].Length); checkSegment(3, r3[3]);
+            Assert.AreEqual(4, r3[3].Length); checkSegment(4, r3[4]);
+            Assert.AreEqual(1, r3[5].Length);
+        }
+
+        [Test]
+        public void TestFastSampler()
+        {
+            var curve = Enumerable
+                .Range(0, 1000)
+                .Select(idx => (0.1f * (float)idx, new Vector3(idx, idx, idx)))
+                .ToArray();
+
+            var defaultSampler = new Animations.Vector3LinearSampler(curve, true);
+            var fastSampler = defaultSampler.ToFastSampler();
+
+            foreach(var k in curve)
+            {
+                Assert.AreEqual(k.Item2, defaultSampler.GetPoint(k.Item1));
+                Assert.AreEqual(k.Item2, fastSampler.GetPoint(k.Item1));
+            }
+
+            for(float t=0; t < 100; t+=0.232f)
+            {
+                var dv = defaultSampler.GetPoint(t);
+                var fv = fastSampler.GetPoint(t);
+
+                Assert.AreEqual(dv, fv);
+            }
+        }
+
         [TestCase(0, 0, 0, 1, 1, 1, 1, 0)]
         [TestCase(0, 0, 0, 1, 1, 1, 1, 0)]
         [TestCase(0, 0, 0.1f, 5, 0.7f, 3, 1, 0)]
         [TestCase(0, 0, 0.1f, 5, 0.7f, 3, 1, 0)]
         public void TestHermiteInterpolation1(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y, float p4x, float p4y)
         public void TestHermiteInterpolation1(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y, float p4x, float p4y)