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();
 
-            var template = Runtime.SceneTemplate.Create(srcModel.DefaultScene, false);
+            var template = Runtime.SceneTemplate.Create(srcModel.DefaultScene, true);
 
             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);
         }
 
+        public ICurveSampler<Vector3> ToFastSampler()
+        {
+            var split = _Sequence
+                .SplitByTime()
+                .Select(item => new Vector3CubicSampler(item))
+                .Cast<ICurveSampler<Vector3>>();
+
+            return new FastSampler<Vector3>(split);
+        }
+
         #endregion
     }
 
@@ -111,6 +121,16 @@ namespace SharpGLTF.Animations
             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
     }
 

+ 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();
         }
 
+        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
     }
 
@@ -113,6 +124,17 @@ namespace SharpGLTF.Animations
             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
     }
 

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

@@ -226,6 +226,44 @@ namespace SharpGLTF.Animations
             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
 
         #region interpolation utils
@@ -291,18 +329,22 @@ namespace SharpGLTF.Animations
             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;
 
-            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;
 
-            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)
@@ -319,18 +361,22 @@ namespace SharpGLTF.Animations
             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;
 
-            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;
 
-            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)

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

@@ -683,9 +683,9 @@ namespace SharpGLTF.Schema2
 
             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();
@@ -697,9 +697,9 @@ namespace SharpGLTF.Schema2
 
             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();
@@ -712,7 +712,7 @@ namespace SharpGLTF.Schema2
             switch (this.InterpolationMode)
             {
                 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();
             }
 
@@ -726,7 +726,7 @@ namespace SharpGLTF.Schema2
             switch (this.InterpolationMode)
             {
                 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();
             }
 

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

@@ -12,6 +12,99 @@ namespace SharpGLTF
     [Category("Core.Animations")]
     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.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)