浏览代码

Added curve cloning support [WIP]

Vicente Penades 4 年之前
父节点
当前提交
a648027971

+ 15 - 0
src/SharpGLTF.Core/Animations/CurveSamplers.Cubic.cs

@@ -10,6 +10,21 @@ namespace SharpGLTF.Animations
     {
         #region lifecycle
 
+        public IConvertibleCurve<T> Clone()
+        {
+            var traits = _Traits;
+            var clonedSequence = _Sequence.Select
+                (
+                pair =>
+                    (
+                    pair.Key,
+                    (traits.Clone(pair.Item2.TangentIn), traits.Clone(pair.Item2.Value), traits.Clone(pair.Item2.TangentOut))
+                    )
+                ).ToArray();
+
+            return new CubicSampler<T>(clonedSequence, traits);
+        }
+
         public CubicSampler(IEnumerable<(float, (T, T, T))> sequence, ISamplerTraits<T> traits)
         {
             _Sequence = sequence;

+ 7 - 0
src/SharpGLTF.Core/Animations/CurveSamplers.Fixed.cs

@@ -27,6 +27,11 @@ namespace SharpGLTF.Animations
             return new FixedSampler<T>(sequence.First().Value.Item2);
         }
 
+        public IConvertibleCurve<T> Clone()
+        {
+            return new FixedSampler<T>(_Value);
+        }
+
         private FixedSampler(T value)
         {
             _Value = value;
@@ -61,6 +66,8 @@ namespace SharpGLTF.Animations
             return new Dictionary<float, (T TangentIn, T Value, T TangentOut)> { [0] = (default, _Value, default) };
         }
 
+        
+
         #endregion
     }
 }

+ 10 - 0
src/SharpGLTF.Core/Animations/CurveSamplers.Linear.cs

@@ -10,6 +10,16 @@ namespace SharpGLTF.Animations
     {
         #region lifecycle
 
+        public IConvertibleCurve<T> Clone()
+        {
+            var traits = _Traits;
+            var clonedSequence = _Sequence
+                .Select(pair => (pair.Key, traits.Clone(pair.Value)))
+                .ToArray();
+
+            return new LinearSampler<T>(clonedSequence, traits);
+        }
+
         public LinearSampler(IEnumerable<(float, T)> sequence, ISamplerTraits<T> traits)
         {
             _Sequence = sequence;

+ 10 - 0
src/SharpGLTF.Core/Animations/CurveSamplers.Step.cs

@@ -11,6 +11,16 @@ namespace SharpGLTF.Animations
     {
         #region lifecycle
 
+        public IConvertibleCurve<T> Clone()
+        {
+            var traits = _Traits;
+            var clonedSequence = _Sequence
+                .Select(pair => (pair.Key, traits.Clone(pair.Value)))
+                .ToArray();
+
+            return new StepSampler<T>(clonedSequence, traits);
+        }
+
         public StepSampler(IEnumerable<(float, T)> sequence, ISamplerTraits<T> traits)
         {
             _Sequence = sequence;

+ 6 - 0
src/SharpGLTF.Core/Animations/Interfaces.cs

@@ -32,6 +32,12 @@ namespace SharpGLTF.Animations
         /// </summary>
         int MaxDegree { get; }
 
+        /// <summary>
+        /// Creates a clone of this curve.
+        /// </summary>
+        /// <returns>A new curve.</returns>
+        IConvertibleCurve<T> Clone();
+
         /// <summary>
         /// Gets a STEP interpolated curve. Use only when <see cref="MaxDegree"/> is 0.
         /// </summary>

+ 19 - 6
src/SharpGLTF.Toolkit/Animations/AnimatableProperty.cs

@@ -3,7 +3,6 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
-using System.Text;
 
 namespace SharpGLTF.Animations
 {
@@ -110,8 +109,17 @@ namespace SharpGLTF.Animations
                 return;
             }
 
+            Guard.IsFalse(curve is CurveBuilder<T>, "Use UseTrackBuilder() instead");
+
+            // clone curve
+
+            var convertible = curve as IConvertibleCurve<T>;
+            curve = convertible.Clone() as ICurveSampler<T>;
+
+            // validate
+
             curve.GetPoint(0); // make a single evaluation to ensure it's an evaluable curve.
-            Guard.IsTrue(curve is IConvertibleCurve<T>, nameof(curve), $"Provided {nameof(ICurveSampler<T>)} {nameof(curve)} must implement {nameof(IConvertibleCurve<T>)} interface.");
+            Guard.NotNull(curve, $"Provided {nameof(ICurveSampler<T>)} {nameof(curve)} must implement {nameof(IConvertibleCurve<T>)} interface.");
 
             // insert track
             if (_Tracks == null) _Tracks = new Dictionary<string, ICurveSampler<T>>();
@@ -123,17 +131,22 @@ namespace SharpGLTF.Animations
         {
             Guard.NotNullOrEmpty(track, nameof(track));
 
-            if (_Tracks == null || !_Tracks.TryGetValue(track, out ICurveSampler<T> sampler))
+            if (_Tracks == null) _Tracks = new Dictionary<string, ICurveSampler<T>>();
+
+            if (!_Tracks.TryGetValue(track, out ICurveSampler<T> sampler))
             {
                 sampler = CurveFactory.CreateCurveBuilder<T>();
-                SetTrack(track, sampler);
+                _Tracks[track] = sampler;
             }
 
             if (sampler is CurveBuilder<T> builder) return builder;
 
-            throw new NotImplementedException($"Underlaying curve must be of type CurveBuilder<{nameof(T)}>");
+            // convert to builder
+
+            builder = CurveFactory.CreateCurveBuilder(sampler);
+            _Tracks[track] = builder;
 
-            // TODO: CurveFactory.CreateCurveBuilder(sampler);
+            return builder;
         }
 
         #endregion

+ 73 - 49
src/SharpGLTF.Toolkit/Animations/CurveBuilder.cs

@@ -13,6 +13,7 @@ namespace SharpGLTF.Animations
     public abstract class CurveBuilder<T> :
         ICurveSampler<T>,
         IConvertibleCurve<T>
+        where T : struct
     {
         #region lifecycle
 
@@ -24,10 +25,15 @@ namespace SharpGLTF.Animations
 
             foreach (var kvp in other._Keys)
             {
-                this._Keys[kvp.Key] = kvp.Value;
+                this._Keys[kvp.Key] = kvp.Value.Clone(CloneValue);
             }
         }
 
+        IConvertibleCurve<T> IConvertibleCurve<T>.Clone()
+        {
+            return this.Clone();
+        }
+
         public abstract CurveBuilder<T> Clone();
 
         #endregion
@@ -196,66 +202,73 @@ namespace SharpGLTF.Animations
         {
             if (curve is IConvertibleCurve<T> convertible)
             {
-                if (convertible.MaxDegree == 0)
-                {
-                    var step = convertible.ToStepCurve();
-                    foreach (var p in step) this.SetPoint(p.Key, p.Value, false);
+                SetCurve(convertible);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
 
-                    #if DEBUG
-                    foreach (var p in step)
-                    {
-                        var dstKey = _Keys[p.Key];
-                        System.Diagnostics.Debug.Assert(dstKey.Degree <= 1);
-                        System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, p.Value));
-                    }
-                    #endif
+        public void SetCurve(IConvertibleCurve<T> convertible)
+        {
+            if (convertible.MaxDegree == 0)
+            {
+                var step = convertible.ToStepCurve();
+                foreach (var p in step) this.SetPoint(p.Key, p.Value, false);
 
-                    return;
+                #if DEBUG
+                foreach (var p in step)
+                {
+                    var dstKey = _Keys[p.Key];
+                    System.Diagnostics.Debug.Assert(dstKey.Degree <= 1);
+                    System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, p.Value));
                 }
+                #endif
 
-                if (convertible.MaxDegree == 1)
-                {
-                    var linear = convertible.ToLinearCurve();
-                    foreach (var p in linear) this.SetPoint(p.Key, p.Value);
+                return;
+            }
 
-                    #if DEBUG
-                    foreach (var p in linear)
-                    {
-                        var dstKey = _Keys[p.Key];
-                        System.Diagnostics.Debug.Assert(dstKey.Degree <= 1);
-                        System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, p.Value));
-                    }
-                    #endif
+            if (convertible.MaxDegree == 1)
+            {
+                var linear = convertible.ToLinearCurve();
+                foreach (var p in linear) this.SetPoint(p.Key, p.Value);
 
-                    return;
+                #if DEBUG
+                foreach (var p in linear)
+                {
+                    var dstKey = _Keys[p.Key];
+                    System.Diagnostics.Debug.Assert(dstKey.Degree <= 1);
+                    System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, p.Value));
                 }
+                #endif
 
-                if (convertible.MaxDegree == 3)
-                {
-                    var spline = convertible.ToSplineCurve();
-                    foreach (var ppp in spline)
-                    {
-                        this.SetPoint(ppp.Key, ppp.Value.Value);
-                        this.SetIncomingTangent(ppp.Key, ppp.Value.TangentIn);
-                        this.SetOutgoingTangent(ppp.Key, ppp.Value.TangentOut);
-                    }
+                return;
+            }
 
-                    #if DEBUG
-                    foreach (var ppp in spline)
-                    {
-                        var dstKey = _Keys[ppp.Key];
-                        System.Diagnostics.Debug.Assert(dstKey.Degree == 3);
-                        System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, ppp.Value.Value));
-                        System.Diagnostics.Debug.Assert(AreEqual(dstKey.IncomingTangent, ppp.Value.TangentIn));
-                        System.Diagnostics.Debug.Assert(AreEqual(dstKey.OutgoingTangent, ppp.Value.TangentOut));
-                    }
-                    #endif
+            if (convertible.MaxDegree == 3)
+            {
+                var spline = convertible.ToSplineCurve();
+                foreach (var ppp in spline)
+                {
+                    this.SetPoint(ppp.Key, ppp.Value.Value);
+                    this.SetIncomingTangent(ppp.Key, ppp.Value.TangentIn);
+                    this.SetOutgoingTangent(ppp.Key, ppp.Value.TangentOut);
+                }
 
-                    return;
+                #if DEBUG
+                foreach (var ppp in spline)
+                {
+                    var dstKey = _Keys[ppp.Key];
+                    System.Diagnostics.Debug.Assert(dstKey.Degree == 3);
+                    System.Diagnostics.Debug.Assert(AreEqual(dstKey.Point, ppp.Value.Value));
+                    System.Diagnostics.Debug.Assert(AreEqual(dstKey.IncomingTangent, ppp.Value.TangentIn));
+                    System.Diagnostics.Debug.Assert(AreEqual(dstKey.OutgoingTangent, ppp.Value.TangentOut));
                 }
-            }
+                #endif
 
-            throw new NotImplementedException();
+                return;
+            }
         }
 
         public void SetCurve(Schema2.IAnimationSampler<T> curve)
@@ -532,6 +545,17 @@ namespace SharpGLTF.Animations
             OutgoingTangent = outgoing;
         }
 
+        public _CurveNode<T> Clone(Func<T, T> cloneValue)
+        {
+            return new _CurveNode<T>
+            {
+                Degree = this.Degree,
+                IncomingTangent = cloneValue(this.IncomingTangent),
+                Point = cloneValue(this.Point),
+                OutgoingTangent = cloneValue(this.OutgoingTangent)
+            };
+        }
+
         #endregion
 
         #region data

+ 28 - 0
src/SharpGLTF.Toolkit/Animations/CurveFactory.cs

@@ -30,6 +30,34 @@ namespace SharpGLTF.Animations
             if (curve is SparseCurveBuilder sscb) return sscb.Clone() as CurveBuilder<T>;
             if (curve is SegmentCurveBuilder xscb) return xscb.Clone() as CurveBuilder<T>;
 
+            if (typeof(T) == typeof(Vector3))
+            {
+                var cb = new Vector3CurveBuilder();
+                cb.SetCurve(curve as ICurveSampler<Vector3>);
+                return cb as CurveBuilder<T>;
+            }
+
+            if (typeof(T) == typeof(Quaternion))
+            {
+                var cb = new QuaternionCurveBuilder();
+                cb.SetCurve(curve as ICurveSampler<Quaternion>);
+                return cb as CurveBuilder<T>;
+            }
+
+            if (typeof(T) == typeof(SEGMENT))
+            {
+                var cb = new SegmentCurveBuilder();
+                cb.SetCurve(curve as ICurveSampler<SEGMENT>);
+                return cb as CurveBuilder<T>;
+            }
+
+            if (typeof(T) == typeof(SPARSE))
+            {
+                var cb = new SparseCurveBuilder();
+                cb.SetCurve(curve as ICurveSampler<SPARSE>);
+                return cb as CurveBuilder<T>;
+            }
+
             throw new InvalidOperationException($"{typeof(T).Name} not supported.");
         }
     }

+ 27 - 18
src/SharpGLTF.Toolkit/Scenes/NodeBuilder.cs

@@ -366,11 +366,20 @@ namespace SharpGLTF.Scenes
             return UseTranslation().UseTrackBuilder(animationTrack);
         }
 
-        public void SetScaleTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseScale().SetTrack(track, curve); }
+        public void SetScaleTrack(string track, Animations.ICurveSampler<Vector3> curve)
+        {
+            UseScale().SetTrack(track, curve);
+        }
 
-        public void SetTranslationTrack(string track, Animations.ICurveSampler<Vector3> curve) { UseTranslation().SetTrack(track, curve); }
+        public void SetTranslationTrack(string track, Animations.ICurveSampler<Vector3> curve)
+        {
+            UseTranslation().SetTrack(track, curve);
+        }
 
-        public void SetRotationTrack(string track, Animations.ICurveSampler<Quaternion> curve) { UseRotation().SetTrack(track, curve); }
+        public void SetRotationTrack(string track, Animations.ICurveSampler<Quaternion> curve)
+        {
+            UseRotation().SetTrack(track, curve);
+        }
 
         public TRANSFORM GetLocalTransform(string animationTrack, float time)
         {
@@ -425,12 +434,12 @@ namespace SharpGLTF.Scenes
         {
             Guard.NotNull(keyframes, nameof(keyframes));
 
-            var track = this.UseTranslation(animTrack);
+            var items = keyframes
+                .OrderBy(item => item.Key)
+                .Select(item => (item.Key, item.Value));
+                // no need to collapse, since SetTrack already clones the curve.
 
-            foreach (var kf in keyframes)
-            {
-                track.SetPoint(kf.Key, kf.Value);
-            }
+            this.UseTranslation().SetTrack(animTrack, Animations.CurveSampler.CreateSampler(items));
 
             return this;
         }
@@ -439,12 +448,12 @@ namespace SharpGLTF.Scenes
         {
             Guard.NotNull(keyframes, nameof(keyframes));
 
-            var track = this.UseRotation(animTrack);
+            var items = keyframes
+                .OrderBy(item => item.Key)
+                .Select(item => (item.Key, item.Value));
+            // no need to collapse, since SetTrack already clones the curve.
 
-            foreach (var kf in keyframes)
-            {
-                track.SetPoint(kf.Key, kf.Value);
-            }
+            this.UseRotation().SetTrack(animTrack, Animations.CurveSampler.CreateSampler(items));
 
             return this;
         }
@@ -453,12 +462,12 @@ namespace SharpGLTF.Scenes
         {
             Guard.NotNull(keyframes, nameof(keyframes));
 
-            var track = this.UseScale(animTrack);
+            var items = keyframes
+                .OrderBy(item => item.Key)
+                .Select(item => (item.Key, item.Value));
+            // no need to collapse, since SetTrack already clones the curve.
 
-            foreach (var kf in keyframes)
-            {
-                track.SetPoint(kf.Key, kf.Value);
-            }
+            this.UseScale().SetTrack(animTrack, Animations.CurveSampler.CreateSampler(items));
 
             return this;
         }

+ 2 - 0
tests/SharpGLTF.Toolkit.Tests/Geometry/MeshBuilderAdvancedTests.cs

@@ -147,6 +147,8 @@ namespace SharpGLTF.Geometry
 
             var pivot = new NodeBuilder("RootNode").WithLocalTranslation("track1", keyframes);
 
+            Assert.AreEqual(4, pivot.UseTranslation("track1").Keys.Count);
+
             // create scene
 
             var scene = new SceneBuilder();