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

Can I use the AnimationCurve in an NativeArray?

Discussion in 'DOTS Animation' started by watsonsong, Mar 5, 2019.

  1. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I want to put the AnimationCurve as parameter to a NativeArray, but it seems can not support this operation:

    ```
    InvalidOperationException: XXXX used in NativeArray<XXXY> must be blittable.
    ```

    For the AnimationCurve will not modify during the job system running, Is there any way to use the AnimationCurve in the job system?
     
  2. krzysie7

    krzysie7

    Joined:
    Jan 4, 2019
    Posts:
    6
    AnimationCurve, while only containing a pointer to internal Unity structure, is a class (a reference type), so it cannot be used with NativeArray<>. A hacky solution is to put the curves in a normal C# array (
    var array = new AnimationCurve[length];
    ), get a GCHandle for it (
    GCHandle.Alloc(array, HandleType.Weak);
    ), and pass this handle to the job. Then you can get the array in the job using
    handle.Target as AnimationCurve[]
    . Then you can call .Evaluate() as normal (this function, at least in my Unity version - 2018.2.4, is marked as ThreadSafe and won't throw exceptions when called from a job). However this will give you wrong results if called from multiple threads as it uses an internal cache (correct me if I'm wrong, I read it somewhere on this forum). One solution for that is to put a spinLock around each evaluate (one spin lock for one curve), or otherwise ensure that only one thread is doing .Evaluate() for this specific curve at this specific time.
    Also this doesn't work with Burst at the moment to my knowledge. So overall, the perf is not the best it could be. But at least the work can be multithreaded/asynchronous.
    And lastly - remember to .Free() the GCHandle as it is not a normal C# object (is not GC'ed). You can also set new .Target for it without creating a new one. AND of course the safety system does't work if you do this so you're on your own.
     
    Deleted User likes this.
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    I made this solution long time ago. It just samples some float in equal interval from the curve (out of job) and then you could bring it with you to the job. It simply lerp between samples but they are all burst compatible.

    https://github.com/5argon/E7Unity/blob/master/SampledAnimationCurve/SampledAnimationCurve.cs

    (Not ideal for highly exponential curve, since when Y gets too steep going along X, the sample would skips a lot of details. Need some work so it truly runs along the line when sampling intervals.)
     
  4. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
    I want to know if the new ParticelSystem is support job system, for the particle system is heavy dependency on Curve data, how about the unity team deal with this issue?
     
  5. watsonsong

    watsonsong

    Joined:
    May 13, 2015
    Posts:
    555
  6. Roycon

    Roycon

    Joined:
    Jul 10, 2012
    Posts:
    50
    I am also very interested in this. Currently I'm saving my key frames in a Buffer and then rebuilding the AnimationCurve whenever i need to Evaluate

    This is obviously terrible but is working for now
     
  7. dzamani

    dzamani

    Joined:
    Feb 25, 2014
    Posts:
    122
    You could check out Blobification.cs in the last ECS package (it's inside Entities.Test folder). It shows how you could bake a struct into immutable data that will live on your componentdata.
    The goal (I think) is to have a ECSAnimationCurve that will use these Blob.
    Now, at the moment, it's not existing yet so we need to wait or use other solution in the mean time.
     
  8. Roycon

    Roycon

    Joined:
    Jul 10, 2012
    Posts:
    50
    I've seen some code elsewhere on this forum about grabbing a pointer to a class and then getting the actual class within a job

    Would that work here? and are there any rules around that?

    Its probably unsafe but would work until a ECSAnimationCurve is released

    EDIT:
    Whoops, just reread this and saw that the pointer example is above :)
     
    Last edited: Apr 10, 2019
  9. krzysie7

    krzysie7

    Joined:
    Jan 4, 2019
    Posts:
    6
    Just because I did some experiments with this lately I can say this: Unity AnimationCurve is made up of third degree polynomials between the keyframes, writing your own curve sampling is actually pretty easy, and if done with proper memory layout (structure of arrays) it is ~10x faster (in Burst) than sampling the built-in curve, nevermind rebuilding it whenever you need to Evaluate(). You can find the equations here: https://answers.unity.com/questions/464782/t-is-the-math-behind-animationcurveevaluate.html
    The sampling job looks something like this:
    Code (CSharp):
    1. [BurstCompile]
    2. public struct SampleJob : IJobParallelFor
    3. {
    4.     [ReadOnly] public NativeArray<float> leftTangents;
    5.     [ReadOnly] public NativeArray<float> rightTangents;
    6.  
    7.     [ReadOnly] public NativeArray<float> leftValues;
    8.     [ReadOnly] public NativeArray<float> rightValues;
    9.  
    10.     [ReadOnly] public NativeArray<float> leftTimes;
    11.     [ReadOnly] public NativeArray<float> rightTimes;
    12.  
    13.     [ReadOnly] public NativeArray<float> evaluationTimes;
    14.     public NativeArray<float> evaluatedValues;
    15.  
    16.     public void Execute(int i)
    17.     {
    18.         var leftTangent = leftTangents[i];
    19.         var rightTangent = rightTangents[i];
    20.         var leftValue = leftValues[i];
    21.         var rightValue = rightValues[i];
    22.         var leftTime = leftTimes[i];
    23.         var rightTime = rightTimes[i];
    24.         var evalTime = evalTimes[i];
    25.  
    26.         var pd = 1f / (rightTime - leftTime);
    27.         var td = evalTime - leftTime;
    28.         var ta = td * pd;
    29.  
    30.         evaluatedValues[i] = ((evalTime * 2 + leftTime - rightTime * 3) *
    31.   ((rightTangent + leftTangent) / 2 - (rightValue - leftValue) * pd) * ta +
    32. (evalTime + leftTime - rightTime * 2) * (rightTangent - leftTangent) / 2) * ta +
    33.              rightTangent * td + leftValue;
    34.     }
    35. }
     
    CrPxl likes this.
  10. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Forget where I got this exactly, it's what I've been using.

    Code (csharp):
    1.  
    2. float Evaluate(float t, Keyframe keyframe0, Keyframe keyframe1)
    3.         {
    4.             float distanceTime = keyframe1.time - keyframe0.time;
    5.  
    6.             float m0 = keyframe0.outTangent * distanceTime;
    7.             float m1 = keyframe1.inTangent * distanceTime;
    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.  
     
    CrPxl and Xerioz like this.