Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Need way to evaluate AnimationCurve in the job

Discussion in 'DOTS Animation' started by 5argon, May 19, 2018.

  1. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    I am using `AnimationCurve` not for animation but for calculations. It is a class so it could not go inside a job, moreover even if it can, this message would show up :

    Code (CSharp):
    1. Evaluate can only be called from the main thread.
    2. Constructors and field initializers will be executed from the loading thread when loading a scene.
    3. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
    4. UnityEngine.AnimationCurve:Evaluate(AnimationCurve, Single)
    5. FormationSt:LerpFormation(FormationSt, FormationSt, Single, Boolean) (at Assets/Scripts/Gameplay/Gameplay/FormationSt.cs:139)
    6. Job:Execute() (at Assets/Scripts/Gameplay/Gameplay/System/GameplayLayoutSystem.cs:207)
    7. Unity.Jobs.JobStruct`1:Execute(Job&, IntPtr, IntPtr, JobRanges&, Int32) (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/Managed/IJob.cs:30)
    8. Unity.Jobs.JobHandle:ScheduleBatchedJobsAndComplete(JobHandle&)
    9. Unity.Jobs.JobHandle:Complete() (at /Users/builduser/buildslave/unity/build/Runtime/Jobs/ScriptBindings/JobHandle.bindings.cs:20)
    However `animationCurve.keys` inside are already pure struct that would do well in a class/ECS. Is there any way that I can evaluate those keys in the job? Any algorithm that I have to rewrite in a job is fine too but I don't know the terms to search for. A keyframe contains :

    float m_Time;
    float m_Value;
    float m_InTangent;
    float m_OutTangent;

    int m_TangentMode;

    int m_WeightedMode;

    float m_InWeight;
    float m_OutWeight;

    Screenshot 2018-05-19 17.51.35.png

    Looking into CsReference the code already says thread safe so actually there might be a chance that Unity can already do this?

    Also just an idea, in the docs I understand why a job could not spawn an another job well. But what if a job could ask for the main thread to perform a task for it? Then we can access all of main thread-only methods in the middle of the job. (Not an expert in concurrency so I don't know what kind of problem would arise if we allow this)
     
    Last edited: May 19, 2018
    ModLunar and JakHussain like this.
  2. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    As a workaround for sampling curves in jobs I made a small extention to the AnimationCurve that pre samples 256 samples in an array. Then pass this in a native array to the job. You loose resolution, but at least you can work with curves ourside of the jobs.

    Not what you are looking for, but could work in some cases.

    Code (csharp):
    1.  
    2. public static class AnimationCurveExtention
    3.     {
    4.         public static float[] GenerateCurveArray(this AnimationCurve self)
    5.         {
    6.             float[] returnArray = new float[256];
    7.             for (int j = 0; j <= 255; j++)
    8.             {
    9.                 returnArray[j] = self.Evaluate(j / 256f);            
    10.             }              
    11.             return returnArray;
    12.         }
    13.     }
    14.  
     
  3. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    i was using similar approach to pass an animation curve tho a shader

    in a job/shader you can lerp nearest available samples to get smooth result
     
    rigidbuddy and Enrico-Monese like this.
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    That's a nice approach! Thank you!
     
  5. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    This is what I do. This only handles 2 keys. Took this off an internet post not my idea. I actually like the caching approach above better though.

    Code (csharp):
    1.  
    2. public float Evaluate(float t, Keyframe keyframe0, Keyframe keyframe1)
    3.         {
    4.             float dt = keyframe1.time - keyframe0.time;
    5.  
    6.             float m0 = keyframe0.outTangent * dt;
    7.             float m1 = keyframe1.inTangent * dt;
    8.  
    9.             float t2 = t * t;
    10.             float t3 = t2 * t;
    11.  
    12.             float a = 2 * t3 - 3 * t2 + 1;
    13.             float b = t3 - 2 * t2 + t;
    14.             float c = t3 - t2;
    15.             float d = -2 * t3 + 3 * t2;
    16.  
    17.             return a * keyframe0.value + b * m0 + c * m1 + d * keyframe1.value;
    18.         }
    19.  
     
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Hey I ended up making a solution as well based on suggestions from this thread. This struct can go in C# Jobs but be sure to construct it outside because the constructor will evaluate `AnimationCurve` on the main thread. Use it if you want.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Mathematics;
    3. using Unity.Collections;
    4.  
    5. public struct SampledAnimationCurve : System.IDisposable
    6. {
    7.     NativeArray<float> sampledFloat;
    8.     /// <param name="samples">Must be 2 or higher</param>
    9.     public SampledAnimationCurve(AnimationCurve ac, int samples)
    10.     {
    11.         sampledFloat = new NativeArray<float>(samples, Allocator.Persistent);
    12.         float timeFrom = ac.keys[0].time;
    13.         float timeTo = ac.keys[ac.keys.Length - 1].time;
    14.         float timeStep = (timeTo - timeFrom) / (samples - 1);
    15.  
    16.         for (int i = 0; i < samples; i++)
    17.         {
    18.             sampledFloat[i] = ac.Evaluate(timeFrom + (i * timeStep));
    19.         }
    20.     }
    21.  
    22.     public void Dispose()
    23.     {
    24.         sampledFloat.Dispose();
    25.     }
    26.  
    27.     /// <param name="time">Must be from 0 to 1</param>
    28.     public float EvaluateLerp(float time)
    29.     {
    30.         int len = sampledFloat.Length - 1;
    31.         float clamp01 = time < 0 ? 0 : (time > 1 ? 1 : time);
    32.         float floatIndex = (clamp01 * len);
    33.         int floorIndex = (int)math.floor(floatIndex);
    34.         if (floorIndex == len)
    35.         {
    36.             return sampledFloat[len];
    37.         }
    38.  
    39.         float lowerValue = sampledFloat[floorIndex];
    40.         float higherValue = sampledFloat[floorIndex + 1];
    41.         return math.lerp(lowerValue, higherValue, math.frac(floatIndex));
    42.     }
    43. }
     
    Last edited: May 21, 2018
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    In case someone stumbled upon this in the future, I have found that Unity's `Evaluate` is exactly Cubic Hermite Spline function described in https://en.wikipedia.org/wiki/Cubic_Hermite_spline, by naively copy that I could get equal result as AnimationCurve.Evaluate down to within 0.0001f accuracy. Tested with a unit test
    .Equals(__).Within(0.0001f)
    , in a test that randomize a curve with 100+ points of random value and tangents, then iterate linearly over them from 0~1 in a small increment (like 0.001f) each time.

    (Also it is what @snacktime said above, a b c d are each of the hermite function)

    Screenshot 2019-05-01 23.17.40.png

    So, it is possible to extract out `Keyframe` (already a struct, could all be in the job) then use Cubic Hermite Spline interpolation on a pair of keyframes on thread and get the same result, without the `AnimationCurve` class instance. Here I just move them out to NativeArray of Keyframe and they work fine in IJobParallelFor

    Screenshot 2019-05-01 23.14.39.png

    The next problem is the weight, it seems like not a classical parameter seen in any wikis so I don't know where it should go? I debugged that weight went from 0 to 1 on dragging to the right side like this, but still haven't figured out where to plug that in.

    licecap.gif


    Here's my empirical observation of Unity's weight. If someone could figure this out...

    The weight ranges from 0 to 1. In the gif, dragging the handle to the right increase the weight. Dragging stretch up do not affect the weight (however you are changing the tangent)

    Both tangents are 0, **with weight applied** and both weight are 0.3333333, it results in the same shape. I think this is the biggest hint, 0.3333333 may has to do something with the "cubic" function.

    Both tangents are 0, with weight applied and both weight are at maximum 1, the curve skewed further in X axis to meet at the center between 2 points. Indicating that, weight 1 doesn't mean unweighted like a weight function would have behave but rather really maximum possible weight.

    Both tangents are 0, with weight applied and both weight are 0, results in a linear graph as if their tangents are 1.

    Both tangents are 1, weight **do not affect** their shape at all no matter the value. It stays linear. Suggesting that weight do something to the components which became tangent, but nullified when both components are equal. I guess it did something to the cos component? Since with maximum weight the graph skewed further in X axis.

    However if the other tangent is not 1, changing the weight of the side that has tangent 1 do affect the shape. Indicating that the weight is not simply "weighting that side's tangent".
     
    Last edited: May 2, 2019
  8. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Update : I tried improving on the naive implementation (left) with matrix based using some methods from Mathematics (right).

    Screenshot 2019-05-02 18.05.50.png

    However the generated assembly looks about the same (?), maybe I will profile later how much better the matrix version could perform. (Or equivalent? The matrix multiply part ended up looking like when I was multiplying individually, maybe there is no more shortcut)

    (Naive)

    Screenshot 2019-05-02 17.35.15.png

    (Matrix)

    Screenshot 2019-05-02 18.04.40.png
     
    Last edited: May 2, 2019
    ModLunar and JakHussain like this.
  9. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    I'd recommend using BlobData for this instead of NativeArray. It makes it so you can easily reference it from an IComponentData.
     
    Opeth001, siggigg, eizenhorn and 2 others like this.
  10. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Are there any examples of BlobData usage?
     
  11. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    You could copy what BlobificationTests.cs is doing to get started.
     
    illinar likes this.
  12. JohanF_TF

    JohanF_TF

    Joined:
    May 20, 2015
    Posts:
    75
    We use AnimationCurves quite a bit and are also trying to figure out the best approach for them, so this thread has been a great help.

    My main question right now is whether to use Dynamic buffers or BlobArrays

    We started out by using DynamicBuffer<KeyFrameBufferElement>. That way we could simply convert the Animationcurve to a dynamic buffer on the entity that needed it.

    Code (CSharp):
    1. public struct AnimationCurveKeyframe : IBufferElementData
    2. {
    3.     public Keyframe keyFrame;
    4. }
    Populate a "keyframe buffer"
    Code (CSharp):
    1.  
    2. DynamicBuffer<AnimationCurveKeyframe> curveBuffer = dstManager.AddBuffer<AnimationCurveKeyframe>(entity);
    3. Keyframe[] keys = animationCurve.keys;
    4. for(int k = 0; k < keys.Length; ++k)
    5. {
    6.     Keyframe key = keys[k];
    7.     curveBuffer.Add(new AnimationCurveKeyframe
    8.     {
    9.         keyFrame = key
    10.     });
    11. }
    I then came upon this thread and saw the advice of using blobs. I fiddled around with it a bit and got that to work, and ended up with something not much more complicated than

    Code (CSharp):
    1. public struct KeyframeBlobArray
    2. {
    3.     public BlobArray<Keyframe> keys;
    4. }
    5.  
    6. public struct FooComponent : IComponentData
    7. {
    8.     public BlobAssetReference<KeyframeBlobArray> animationCurve;
    9. }
    10.  
    11. public static unsafe BlobAssetReference<KeyframeBlobArray> ConstructKeyframeBlob(Keyframe[] keyframes)
    12.     {
    13.         BlobAllocator allocator = new BlobAllocator(-1);
    14.         ref var root = ref allocator.ConstructRoot<KeyframeBlobArray>();
    15.  
    16.         allocator.Allocate(keyframes.Length, ref root.keys);
    17.  
    18.         for(int i = 0; i < keyframes.Length; ++i)
    19.         {
    20.             Keyframe k = keyframes[i];
    21.             root.keys[i] = k;
    22.         }
    23.  
    24.         BlobAssetReference<KeyframeBlobArray> keyframeBlob = allocator.CreateBlobAssetReference<KeyframeBlobArray>(Allocator.Persistent);
    25.         allocator.Dispose();
    26.  
    27.         return keyframeBlob;
    28.     }
    29.  

    Which feels better, since I can now include it in my components instead of having an attached buffer.

    What I'm trying to figure out currently is which is the better approach, technically. What are the potential pitfalls of DynamicBuffer vs BlobArray in this context, what would be the drawback with each approach? It would be nice with some typical use cases for BlobAssetReferences.

    Are there only special cases where you should use them, or can they be freely used in places where you need array-like structures in your components?
     
    Last edited: May 13, 2019
    Singtaa likes this.
  13. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    BlobArray is better for curve data.

    1. It can be shared. Commonly animation curves, clips etc are shared data. DynamicBuffer is made for having per entity uniuq arrays. BlobArrays are for immutable shared assets.
    2. BlobData is easier to consume because you can simply reference one or multiple on a single icomponentdata and safely read all of it from a job.
     
    Nyanpas, KwahuNashoba, psuong and 4 others like this.
  14. JohanF_TF

    JohanF_TF

    Joined:
    May 20, 2015
    Posts:
    75
    Don't mean to side-track this thread too much, just want to make sure I understand

    If I understand you correctly, would the general rule be to use BlobArrays for shared data, and DynamicBuffer for entity specific data? In other words, of having unique data in BlobArrays in IComponentData, adding DynamicBuffer is preferred?

    Side-note, huge thanks to you and your team (and this whole community), for spending so much time here in the forums
     
    psuong likes this.
  15. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Shared component data is really for segmenting your entities into forced chunk grouping. The name is unfortunate I think. Because really if you use it as data sharing mechanism, you will mostly shoot yourself in the foot because often you just end up with too small chunks.

    BlobData is just a reference to shared immutable data. BlobData is also easily accessable from jobs and can contain complex data.
     
    NotaNaN, Antypodish, Orimay and 4 others like this.
  16. FROS7

    FROS7

    Joined:
    Apr 8, 2015
    Posts:
    26
    Is the name unfortunate enough for Shared Component Data get a rename?
     
    NotaNaN, Vanamerax, Orimay and 2 others like this.
  17. ReadyPlayGames

    ReadyPlayGames

    Joined:
    Jan 24, 2015
    Posts:
    49
    I've spent a long time trying to get Shared Component Data to "work" because I was using it incorrectly based on the name.
     
    Orimay, Shinyclef and FROS7 like this.
  18. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Maybe ChunkComponentData?

    @5argon Could you please share your Blob based curve implementation if you've done it?
     
    Orimay and siggigg like this.
  19. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    I have benchmarked that it is currently so slower than AnimationCurve.Evaluate (like by about 20 times) on main thread evaluation, so I was afraid to share it until I have time to get it good enough.. (could barely win on multithread evaluation while AnimationCurve do it all on main thread, with tons of evaluations)

    Anyways I have opened that code's repo : https://github.com/5argon/JobAnimationCurve. There are failing tests about performance and you can see how many ticks is the target in the test log. Also there are ignored tests about weights that will fail if not ignored. All other tests verify that the answer is equal to regular AnimationCurve.
     
  20. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    I would think an optimized version would be using caching heavily. Off the top of my head...

    Start with a set precision, the eval input is normalized to the closest point at the set precision. So for any length animation curve you have a known number of points up front and you can cache all the evaluations values in a NativeArray. You could calculate the entire curve once on the first access, or do it lazily.

    I can't imagine that you need so much precision as to make it not viable memory wise.
     
  21. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    I have interest in having AnimationCurve's in jobs, so it's exciting to see @5argon has code available for us to try out. I've adjusted the code for it to run in current Entities 0.5.1. I have that adjustment available as a pull request for 5argon's repo.

    It's pretty unfortunate how unperformant the main thread evaluation is at the moment. Sometime soon, I'm going to dive through unity decompiled to see if I can find the source code for curve evaluation, and get an idea how they optimized it. (EDIT: Nvm about that decompiled idea. The C# code is just a wrapper for the pure native code.)
     
    Last edited: Feb 14, 2020
  22. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The new DOTS animation package has curve evaluation that might be worth checking out.
     
    siggigg likes this.
  23. siggigg

    siggigg

    Joined:
    Apr 11, 2018
    Posts:
    247
    It's worth noting that if you evaluate a lot of curve data, it might be worth it to pre-sample the curve into a lookup blob array. I did just that recently at work and it resulted in a massive performance increase for our use case.
     
    Antypodish likes this.
  24. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    Presampling the curve into a lookup array was best take away from this thread.
     
    Nyanpas and siggigg like this.
  25. Mockarutan

    Mockarutan

    Joined:
    May 22, 2011
    Posts:
    159
    Just got it working, it just slid straight into my code perfectly and allowed me to burst a lot of stuff that was inside ComponentSystem ForEach before. Thanks for the tips!

    It seems like they have a version of this in the DOTS animation already, interval base caching: https://docs.unity3d.com/Packages/com.unity.animation@0.3/api/Unity.Animation.AnimationCurve.html
     
    siggigg likes this.
  26. Djadjouka

    Djadjouka

    Joined:
    Nov 1, 2016
    Posts:
    9
    I managed to reproduce the full AnimationCurve (weights and wrap modes) based on the code that 5argon generously provided. It's useful if you can't have access to the new animation package. It matches the values produced by AnimationCurve at a precision of 0.0001. Unfortunately, it still runs 10 times slower than the original code when using Burst. It doesn't use any cache though so the memory impact is minimal and it can run using multiple jobs on the same data. I've also added the references I used at the top of the code if you want to dive in the maths^^
     

    Attached Files:

    Last edited: Mar 19, 2020
  27. mcurtiss

    mcurtiss

    Joined:
    Nov 29, 2012
    Posts:
    26
    Any idea on how to get a hold of the Unity.Animation package with the AnimationCurve struct? The api says to install via package manager but it isn't showing up for me, despite have preview packages enabled.
     
  28. axxessdenied

    axxessdenied

    Joined:
    Nov 29, 2016
    Posts:
    33
    add
    Code (csharp):
    1.  
    2. "com.unity.animation": "0.3.0-preview.9",
    3. "com.unity.dataflowgraph": "0.13.0-preview.3",
    4.  
    to your manifest.json file in Packages folder
     
    edalbeci and mcurtiss like this.
  29. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    How are you supposed to use and evaluate the DOTS Animation Curve properly? I think I'm using it right, but evaluating the curve results in some very odd oddness.

    Code (CSharp):
    1. public struct CarPhysics : IComponentData
    2. {
    3.   public Unity.Animation.AnimationCurve forceCurve;
    4. }
    5.  
    6. public class CarPhysicsAuthoring : MonoBehaviour, IConvertGameObjectToEntity
    7. {
    8.   public AnimationCurve forceCurve = AnimationCurve.Linear(0.0f, 1.0f, 1.0f, 0.0f);
    9.  
    10.   public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    11.   {
    12.     var physComponent = new CarPhysics
    13.     {
    14.       forceCurve = Unity.Animation.Hybrid.CurveConversion.ToDotsAnimationCurve(forceCurve)
    15.     };
    16.     dstManager.AddComponentData(entity, physComponent);
    17.   }
    18. }
    19.  
    20. public class CarPhysicsSystem : ComponentSystem
    21. {
    22.   protected override void OnUpdate()
    23.   {
    24.     Entities.ForEach((ref CarPhysics car) =>
    25.     {
    26.       // Here it only outputs the value of the first keyframe or some weird value that's on the curve somewhere
    27.       // I have no control on which condition it chooses per update
    28.       float curveValue = Unity.Animation.AnimationCurveEvaluator.Evaluate(0.1f, ref car.forceCurve);
    29.       Debug.Log(curveValue);
    30.     });
    31.   }
    32. }
    It only outputs the value of the first keyframe or some weird value that's on the curve somewhere. I have no control on which condition it chooses per update.

    In the Entity Debugger, the field forceCurve has no information associated with it. There isn't any blobs or anything associated with the DOTS curve showing up for the entity.

    I'm using the most recent versions of both the Animation package, and Entities. If this is a bug, I have no idea how to report it to the folks at Unity.
     
    Last edited: Apr 18, 2020
  30. axxessdenied

    axxessdenied

    Joined:
    Nov 29, 2016
    Posts:
    33
    Have you tried using BlobAssetReference<AnimationCurveBlob> instead of Unity.Animation.AnimationCurve ?
     
  31. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    BlobAssetReference<AnimationCurveBlob> works perfectly! Thank you!

    What even is the difference between the "DOTS" Animation Curve (Unity.Animation.AnimationCurve) and the BlobAssetReference Animation Curve in this package? Is the DOTS version supposed to have better caching abilities or something?
     
  32. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    Code (CSharp):
    1.     float EvaluateAnimationCurve(NativeArray<Keyframe> curve, float t)
    2.     {
    3.         float value = 0;
    4.  
    5.         for (int i = 0; i < curve.Length; i++)
    6.         {
    7.             int next = math.clamp(i + 1, 0, curve.Length - 1);
    8.             Keyframe start = curve[i];
    9.             Keyframe end = curve[next];
    10.  
    11.             int minCheck = math.select(0, 1, t > start.time);
    12.             int maxCheck = math.select(0, 1, t <= end.time);
    13.             int check = minCheck * maxCheck;
    14.  
    15.             float distanceTime = end.time - start.time;
    16.  
    17.             float m0 = start.outTangent * distanceTime;
    18.             float m1 = end.inTangent * distanceTime;
    19.  
    20.             float t2 = t * t;
    21.             float t3 = t2 * t;
    22.  
    23.             float a = 2 * t3 - 3 * t2 + 1;
    24.             float b = t3 - 2 * t2 + t;
    25.             float c = t3 - t2;
    26.             float d = -2 * t3 + 3 * t2;
    27.  
    28.             value += (a * start.value + b * m0 + c * m1 + d * end.value) * check;
    29.         }
    30.  
    31.         return value;
    32.     }
    I am using this somewhat naive implementation of my own in bursted jobs. Takes in NativeArray of Keyframe that can be obtained directly from AnimationCurve. plain keyframe evaluation with no wrap mode support.
     
    Next1on likes this.
  33. Timboc

    Timboc

    Joined:
    Jun 22, 2015
    Posts:
    238
    From what I can see, the new AnimationCurveBlob ignores weighting? Anyone know if that's expected in the future? @Djadjouka that class you put together is really impressive. I'm a bit surprised remapping time (or however you want to conceptualise it) for weighted points requires relatively complex recursive methods. Are there any resources (other than those in your file) that you came across about this? I had a bit of success using something like (https://medium.com/@nornagon/math-for-game-developers-parameterised-easing-9336a50c816d) but of course you end up with a discontinuity if points have different weights and I haven't yet found a decent strategy for lerping between them.
    From my understanding a weighted handle is exactly the same as the 2d location of a normal bezier handle - I *think* the bit that makes this all so tricky is re-parameterising all of this in terms of t.
    @Djadjouka would you be willing to let me utilise and distribute part of that source code within a paid Unity store asset?
    Also if anyone has any links to further relevant resources that would be much appreciated.

    (currently trying to digest https://web.archive.org/web/20161213073343/http://cagd.cs.byu.edu/~557/text/ch17.pdf & 'inversion')
     
    Last edited: May 7, 2020
  34. Soulai

    Soulai

    Joined:
    Nov 10, 2014
    Posts:
    1
    Hey Timboc, it's @Djadjouka. Thanks, I agree it could probably be optimized much more. I suggest you try caching some T values if memory is not a problem. That way you just have to do the last Bézier evaluation. It didn't work really well for me but maybe you will have a better luck depending on your requirements. This reference I included is definitely the most complete one I have found: https://pomax.github.io/bezierinfo/#matrix. I didn't read everything and I'm not great at mathematics so there's probably some connections I didn't make between all the formulas. As far as I know though, there's probably no magic way of getting rid of the Newton method. And yes you are right that the weighted handles are just the two control points in the cubic Bézier curve.

    About performance, my post wasn't completely right as it seems that starting the job when testing was an order of magnitude higher than my test so quite a large overhead. It also included some compilation for Burst so the job needs to be warmed up to have a proper benchmark. Of course, you probably won't do just an animation curve evaluation in a job so the overhead would probably be compensated by other calculations as well. When I used it in a jobified animation system, it produced results 4 times faster than the legacy animation system from Unity when using 4 cores. I can't provide the code but I suggest you try to integrate the code with Burst for your final purpose if you can. I can't say for sure because I don't know what your requirements are but I'm pretty sure the current performance should be enough.

    And yes, you're welcome to use the code or anyone else from the community (even for commercial purposes). Thanks for asking though!
     
    Timboc likes this.
  35. Timboc

    Timboc

    Joined:
    Jun 22, 2015
    Posts:
    238
    Many thanks @Soulai. After some more research I think I'm finally on to what I should be doing and the terminology I need. It might be that the Newton iterative method is faster but for now I have a preference for a more constant cost. I think what I need to be doing is solving for the roots of the bezier. For anyone else looking to do this in the future, these are absolutely invaluable:
    https://stackoverflow.com/questions...iven-x-special-case-where-x-of-control-points
    https://trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
     
    Soulai likes this.
  36. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    The internal Unity method uses a cache of the "time parameter -> segment", to remember where the last time request you made occurred, within the curve.

    So if you say:
    curve.Evaluate(time);
    Then the code searches the whole keyframe array looking for the correct segment.
    But if you then call it again:
    curve.Evaluate(time+0.001f);
    The code will first check the segment from the last query. The idea being that you generally sample curves coherently, i.e. linearly throughout time, etc. So you can take advantage of that. Most of the time, the segment you want will be the same as last time, or adjacent.

    Just remember, if you try to support this with your custom solution, that the cache must be per-thread, and not associated directly with the curve data. (unless you have an array of them and use NativeSetTheadIndex to access the correct cache.

    This is all you really need to store:
    struct Cache
    {
    int segmentIndex;
    }

    Just one int. To tell the Evaluate function which segment to check first, instead of checking them all linearly, or using a binary search (I didn't read your code - hopefully it's not linear anyway) ;-)

    If the time isn't within the cached segment, you can choose whether to look at an adjacent segment next, (depending on whether time < or > the cached segment, or just revert to a binary search of the segments lower/higher than the cached index, based on which direction you need to search.

    (When I say "segment", I mean the interval between 2 keyframes.)

    Hope it helps! (and hope it makes sense!)
     
    Last edited: Jul 2, 2020
    look001, KyryloKuzyk, evom777 and 8 others like this.
  37. Armitage1982

    Armitage1982

    Joined:
    Jul 26, 2012
    Posts:
    38
    Is there now a better solution to use a UnityEngine.AnimationCurve under DOTS?
    I saw this one https://forum.unity.com/threads/a-fast-blobcurve.985941/ but not sure how to use it.

    Unity.Animation has a converter but as I am under Unity 2021 this package is incompatible and not supported.
    So I manually imported Unity.Animation.Curves & Unity.Animation.Curves.Hybrid, which allows me to use AnimationCurveBlob (https://docs.unity3d.com/Packages/c...9/api/Unity.Animation.AnimationCurveBlob.html).
    Unfortunately, it's useless since the wrap mode (none, ping-pong, clamp) is not taken into account.

    I use AnimationCurves in conjunction with Havok and CalculateVelocityToTarget.
    For the 20 or so times I need to do this on a level, I'm close to telling myself that I'm going to keep using ISharedComponentData and do it on the Main Thread. I have some difficulty interpreting the impact of sync point in this case.
     
    Last edited: Feb 13, 2022