Search Unity

Ghost car lerping issue - big jitter!

Discussion in 'Scripting' started by Barry100, Oct 4, 2020.

  1. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200
    Hi all. I am setting up a ghost car in a car game I an working on. I am saving the position and rotation every frame in fixed update to an array. I am then retrieving the array when I restart the race which I am then setting up the ghost cart with. The problem is, the ghost car jitters forward. This happens when I use lerp and smoothdamp.

    Here is my code

    Code (CSharp):
    1.     smoothTime = main.gSpeeed[i + 1] - main.ghostTimer;
    2.    
    3.    transform.eulerAngles = main.grot[i];
    4.          
    5.             transform.position = Vector3.SmoothDamp(transform.position, main.gpos[i], ref velocity, smoothTime);
    6.             i++;
    main is the main script that has the ghost cars positions and rotations in an array.

    ghosttimer is a deltatime in the main class which is ticking up per fixedupdate. Main gspeeed in the saved timestamp for the ghost car.

    Any help or suggestions would be really appreciated.
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Don't use euler angles for recording orientation. Use quaternions.

    You should not record car position in FixedUpdate, unless it is a rigidbody - results will be incorrect.

    You also shouldn't be using smoothdamp for that (it is for slowly chasing a value). Instead interpolate car position based on nearest recorded keyframes.

    It will be easier to timestamp recorded car positions instead of assuming that FixedDelta runs at fixed framerate.
     
  3. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200

    Hi @neginfinity and thanks for your reply. I was using a normal update but I thought that if people have different PC specs the update would run at different rates. This is why I tried to use fixed update. I will go update the scripts to use update and get back to you shortly. I really appreciate the help btw. Its been frustrating me all week!
     
  4. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200


    I have also updated the code to

    Code (CSharp):
    1.            transform.position = Vector3.Slerp(main.gpos[replayIndex - 1], main.gpos[replayIndex], Mathf.Clamp(replayTime, main.gSpeeed[replayIndex - 1], main.gSpeeed[replayIndex]));
    2.  
    3.          transform.eulerAngles =  AngleLerp(main.grot[replayIndex - 1], main.grot[replayIndex], Mathf.Clamp(replayTime, main.gSpeeed[replayIndex - 1], main.gSpeeed[replayIndex]));
    4.  
    It is not buttery smooth but it is much better.

    I have put this is an update and not the fixed update.
     
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Slerp is not doing what you think it is doing. It is strictly for interpolating directions and not position. You definitely shoudln't be using it.

    Also, the reason why I don't recommend to use euler angles, because they're subject to gimbal lock, which will cause artifacts if your ghost car can jump and flip in mid air. You need to store rotations as quaternions and use Quaternion.Slerp to interpolate them.

    For smooth movement between keypoints, you'd need to use a spline function, for example, catmull rom spline:
    https://en.wikipedia.org/wiki/Centripetal_Catmull–Rom_spline

    This one is not implemented in Unity Vector3 library, as far as I'm aware, but it will pass through all recorded keypoints while maintaining smooth movement.
     
  6. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200

    mm not sure about using splines.. I have looked at the catmull-rom spline and I dont think this would work for a ghost on a car game as it will not be the true path after each point is reached (unless im reading it wrong). From what I can see the spline will kind of overshoot the original path and then move to the splined path (again I could be wrong). Have you used the catmull-rom in a game previously?
     
  7. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Your trajectory recorder inserts control points at least 50 times per seconds. It won't be able to noticeably overshoot anything. And currently you're using slerp which interpolates data incorrectly in the first place.

    Yes.
     
  8. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200
    ok cool. Here is the current code

    Code (CSharp):
    1. public void FixedUpdate ()
    2.     {
    3.         try
    4.         {
    5.             transform.position = Vector3.MoveTowards(main.gpos[i], main.gpos[i +1], Mathf.Infinity);
    6.             transform.eulerAngles = AngleLerp(main.grot[i], main.grot[i + 1], (main.gSpeeed[i +1 ] - main.gSpeeed[i]));
    7.  
    8.  
    9.             i++;
    10.  
    11.  
    12.         }
    13.         catch { }
    14.      
    15.  
    16.     }
    17.  
    18.  
    19.     Vector3 AngleLerp(Vector3 StartAngle, Vector3 FinishAngle, float t)
    20.     {
    21.         float xLerp = Mathf.LerpAngle(StartAngle.x, FinishAngle.x, t);
    22.         float yLerp = Mathf.LerpAngle(StartAngle.y, FinishAngle.y, t);
    23.         float zLerp = Mathf.LerpAngle(StartAngle.z, FinishAngle.z, t);
    24.         Vector3 Lerped = new Vector3(xLerp, yLerp, zLerp);
    25.         return Lerped;
    26.     }
    This is pretty smooth but still not as smooth as I think it should be.

    I was thinking about the catmull-rom and was thinking instead of the transform array, I could replace it with a list of transforms (so i can update it easier on the fly).

    I do see your point about the 50 frames and yes your right it would be nominal and not seen. Ill try to look in to it tomorrow.

    btw I used slerp when I copied that code to put here but it wasnt kept. I have tried so many different ways of getting it smooth in the past week that at that point (and still now) any solution would be a godsend!
     
  9. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    (-_-)

    Camull-rom spline through control points you generated. As I already said.

    Also, this:
    Code (csharp):
    1.  
    2. transform.position = Vector3.MoveTowards(main.gpos[i], main.gpos[i +1], Mathf.Infinity);
    3.  
    Is fully equivalent to this:
    Code (csharp):
    1.  
    2. transform.position = main.gpos[i +1];
    3.  
    And it WILL jitter.

    Check the documentation for the last parameter.
    https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html


    And do try to find some tutorials on keyframe animation and interpolating values. Because it feels like you're trying to apply functions randomly, without full understanding of what they do.
     
  10. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200
    you are correct. I read the function and just expect it to work which of course is the round hole square peg approach.


    I did have the line above set up as below:

    Code (csharp):
    1.  
    2. transform.position = Vector3.MoveTowards(main.gpos[i], main.gpos[i +1], (main.gpspeed[i + 1] -main.gpspeed[i]) );
    3.  
    as I expected the time stamp to be the amount of time that the transform moved from one place to the next but this made everything run really slow. This is why I put infinity. Now that you point out that its the same as not putting it in, it makes sense as the movement is instant.

    I think as per the docs it should be

    Code (csharp):
    1.  
    2. transform.position = Vector3.MoveTowards(main.gpos[i], main.gpos[i +1], (main.gpspeed[i + 1] -main.gpspeed[i]) * time.deltatime);
    3.  
    Ill go read up on it. thanks for your help
     
    Last edited: Oct 5, 2020
  11. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Per docs it is
    Code (csharp):
    1.  
    2. destination = main.gpos[i+1];
    3. start = main.gpos[i];
    4. difference = desintation-start;
    5. var length = difference.length;
    6.  
    7. if (length < maximumDistance)
    8.     return destination;
    9. return start + difference * maximumDistance / length;
    10.  
    Or something along those lines.

    In your case maximum distance is infinity, and all numbers are smaller than infinity, so interpolation never happens.

    Anyway, you do not appear to understand what you're planning to do.

    like I said, your track recorder creates control points where car is. The best idea would be to timestamp it.

    Here's how it SHOULD work. Let's say that instead of adding points 50 times per frame, you record positiosn at random intervals several seconds apart. And let's say you have this:

    upload_2020-10-5_16-16-27.png
    That's "timeline" of movement. It is very similar to animation curve.
    P0, P1, P2, P3 denote points recorded at specific times. I use 1 coordinate for "position" for simplicity.

    You're currently playing back movement, and playback reached "Current Time" point, however, there's no data here. What do you do? You guess or estiamte it, using interpolation.

    Simplest approach is linear interpolation, which will result in linear movement and noticeable sharp changes in direction, but since you have bazillion points it might not be even noticeable.
    upload_2020-10-5_16-19-35.png
    How does it work....

    Well, there's function for you, Vector3.Lerp.
    https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
    You pass interpolation coefficient as 3rd parameter.
    Code (csharp):
    1.  
    2. Vector3.Lerp(p1, p2, t);
    3.  
    If you want p1, you pass 0, if you want p2, you pass 1.
    You're partway between points, how do you calculate t?

    Well... calculate distance between "CurrentTime" and recorded time of p1, and divide it by distance between recorded times of P2 and P1.
    Code (csharp):
    1.  
    2. t = (currentTime.position - p1.time)/(p2.time - p1.time)
    3.  
    Catmull rom spline will work in same fashion, except that rather than straight lines, it'll form a curve that will pass through p0, p1, p2 and p3, and to calcualte value between p1 and p2, you'll need to know p0 and p3.

    The resulting curve will be smooth, it will smoothly change speed and direction and it will pass through all recorded points.

    -------

    However, those are basic concepts you're technically supposed to know.

    If you don't know them, you need to learn them. There's probably tutorial, explanation or something. Somewwhere.

    Maybe even this will work:
    https://en.wikipedia.org/wiki/Interpolation
     
  12. Barry100

    Barry100

    Joined:
    Nov 12, 2014
    Posts:
    200
    Still jerking. I cant work out why. From what I have read, this is totally the correct way to lerp a gameobject.
    I am saving the position and rotation using invokerepeating each 0.4 seconds then playing it back at the same rate (main.stepTime) using also invokerepeating.

    main.stepTime = 0.4f

    Code (CSharp):
    1.   MainScript main;
    2.  
    3.     private void Start()
    4.     {
    5.         main = FindObjectOfType<MainScript>();
    6.  
    7.         InvokeRepeating("RepeatTime", 0, main.stepTime);
    8.     }
    9.     int x = 0;
    10.     void RepeatTime()
    11.  
    12.     { x++;
    13.         if (x < main.grot.Count)
    14.         {
    15.                  
    16.             StartCoroutine(MoveToPosition(main.gpos[x], main.grot[x]));
    17.         }
    18.      
    19.  
    20.  
    21.     }
    22.  
    23.     public IEnumerator MoveToPosition(Vector3 position,Vector3 rotation)
    24.     {
    25.  
    26.         Vector3 currentPos = transform.position;
    27.         Vector3 _rotation = transform.eulerAngles;
    28.         float currentTime = 0f;
    29.         while (currentTime < main.stepTime)
    30.         {
    31.          currentTime += Time.deltaTime;
    32.             transform.position = Vector3.Lerp(currentPos, position, currentTime / main.stepTime);
    33.             transform.eulerAngles = AngleLerp(_rotation, rotation, currentTime / main.stepTime);
    34.  
    35.  
    36.             yield return null;
    37.         }
    38.       transform.position = position;
    39.       transform.eulerAngles = _rotation;
    40.    
    41.     }
    42.  
    43.     Vector3 AngleLerp(Vector3 StartAngle, Vector3 FinishAngle, float t)
    44.     {
    45.         float xLerp = Mathf.LerpAngle(StartAngle.x, FinishAngle.x, t);
    46.         float yLerp = Mathf.LerpAngle(StartAngle.y, FinishAngle.y, t);
    47.         float zLerp = Mathf.LerpAngle(StartAngle.z, FinishAngle.z, t);
    48.         Vector3 Lerped = new Vector3(xLerp, yLerp, zLerp);
    49.         return Lerped;
    50.     }
     
  13. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    You're using InvokeRepeating to repeatedly start a coroutine on a schedule which runs over multiple frames. So that could create two potential issues from a quick glace. First would be if you end up with more than 1 of this MoveToPosition coroutine trying to move the object in the same frame. The second would be if the MoveToPosition coroutine ends considerably before the next is called, causing no movement for multiple frames when you are looking for smooth movement. Either could result in jitter or stutter movement. Add debugging to determine if either are the case.