2

I'm visualising GPS data provided from the cars in races in Formula 1, and am attempting to animate their position on a path. The Formula 1 API provides vector coordinates and timestamps, but the timestamps are varied: they're updated approximately between 100 and 400 milliseconds:

timestamp                   x       y       z
2023-03-19 18:23:39.562     -1396   503     118
2023-03-19 18:23:39.842     -1443   630     118
2023-03-19 18:23:40.142     -1531   868     117
2023-03-19 18:23:40.342     -1589   1028    117
2023-03-19 18:23:40.501     -1636   1157    117
2023-03-19 18:23:40.842     -1772   1527    117
2023-03-19 18:23:41.101     -1813   1640    117
2023-03-19 18:23:41.361     -1932   1964    117
2023-03-19 18:23:41.782     -2015   2190    117
2023-03-19 18:23:42.002     -2080   2368    117
...

When visualised the data points look like this:

vector visualisation

I'm using a coroutine to Lerp between the vectors and updating on the time delta, but because the gaps are varied, it creates quite a jerky animation:

animation

My coroutine looks like this (with thanks to @derHugo for this answer):

private IEnumerator AnimationRoutine()
{
    if (alreadyAnimating) yield break;

    alreadyAnimating = true;

    var lastSample = _samples[0];
    Car.transform.position = lastSample.Position;

    yield return null;
    for (var i = 1; i < _samples.Count; i++)
    {
        var lastPosition = lastSample.Position;
        var currentSample = _samples[i];
        var targetPosition = currentSample.Position;

        var duration = currentSample.TimeDelta;
        var timePassed = 0f;
        while (timePassed < duration)
        {
            var factor = timePassed / duration;

            Car.transform.position = Vector3.Lerp(lastPosition, targetPosition, factor);
            yield return null;
            timePassed += Time.deltaTime;


        }

        Car.transform.position = targetPosition;
        lastSample = currentSample;
    }

    alreadyAnimating = false;

}

Is there a way I can maintain the time data I have but interpolate the transitions between points so they look smoother?

dave
  • 2,750
  • 1
  • 14
  • 22
  • 1
    an option might also be to have a mixed system ... only make some snapshots of positions (for later path correction) and then rather store and apply velocity snapshots basically assuming that between two snapshots there was no change in velocity .. this is of course not 100% accurate - that' what the lower frequent position snapshots can then fix ;) – derHugo Apr 17 '23 at 07:20

3 Answers3

2

What is going on here is that the set of points only defines the path of the car and not the speed of the car. You are going to need additional information to have the speed of the car along the lap. This requires time telemetry in addition to spatial telemetry.

Ideally, you take the set of (x,y,z) points and corresponding time (t) and define a spatial cubic spline curve that will allow you to interpolate between the nodes.

Vector3.Lerp is not sufficient here because the slopes do not match at the nodes and you are going to have discontinuous speed.

The spline is parametrized with some parameter t = 0 .. t_lap describing where along the lap the car is such that

float[] t_data, float[] x_data, float[] y_data, float[] z_data;
...
Spline spline_x = new Spline(t_data, x_data);
Spline spline_y = new Spline(t_data, y_data);
Spline spline_z = new Spline(t_data, z_data);

for( int i=0; i<spline.Count; i++)
{ 
    float t = (t_end/(spline.Count-1))*i;
    posVector = new Vector(
        spline_x.Value(t),
        spline_y.Value(t),
        spline_z.Value(t));
    // draw position Vector
}

You can find numerous online postings about cubic splines in C# or even my own example in Fortan

If your question is that you do not have time telemetry if you can make it up by assuming some kind of velocity profile along the track (like constant which is not realistic) then it is a different math problem.

You would need the total distance traveled between nodes (the length of the spline curve) which is going to be always greater than the straight line segment given by Vector3.Distance().

This distance is evaluated with a numerical integrator of each distance segment Δs that corresponds to a small time step Δt using the spline slope (speed) function

float speed_x = spline_x.Slope(t);
float speed_y = spline_y.Slope(t);
float speed_z = spline_z.Slope(t);
float speed = Math.SqrtF( speed_x*speed_x + speed_y*speed_y + speed_z*speed_z);

float distInterval = Δt / speed;
    

The full mathematical treatment is rather involved, so I am not sure you want to go down this path, you just a quick estimate suffices here.


I found a similar answer of mine here about smoothly inerpolating between airplane data in 3D.


There is also a [Math.NET] Spline object you can use.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
2

Today I would go a different approach and use AnimationCurves and initially when loading your data fill them completely in like e.g.

// extract your key frames - might consider a different way to store them in the first place ;)
var keysX = new Keyframe[_samples.Length];
var keysY = new Keyframe[_samples.Length];
var keysZ = new Keyframe[_samples.Length];

var time = 0f;

for(var i = 0; i < _samples.Length; i++)
{
    var position = _samples.Position;
    
    keysX[i] = new Keyframe[time, position.x];
    keysX[i] = new Keyframe[time, position.y];
    keysX[i] = new Keyframe[time, position.z];

    // would even be easier to track the actual time rather than delta then
    time += _samples.TimeDelta;
}

// From the keyframes construct curves
var animationCurveX = new AnimationCurve(keysX);
var animationCurveY = new AnimationCurve(keysY);
var animationCurveZ = new AnimationCurve(keysZ);

And then later you can simply use the time and AnimationCurve.Evaluate in order to get already correctly interpolated values. See also Keyframe for more advanced constructor options with tangents etc - alternatively let's simply smooth them out via AnimationCurve.SmoothTangents

// smoothing tangents 
for(var i = 0; i < _samples.Length; i++)
{
    animationCurveX.SmoothTangents(i, 0f);
    animationCurveY.SmoothTangents(i, 0f);
    animationCurveZ.SmoothTangents(i, 0f);
}

And then you can simply evaluate the position at any time and have it already interpolated

private IEnumerator AnimationRoutine()
{
    if (alreadyAnimating) yield break;

    alreadyAnimating = true;

    var keys = animationCurveX.keys;

    var endTime = keys[keys.Length - 1].time;

    for(var time = 0f; time < endTime; time += Time.deltaTime /*optionally add a factor here to play slower or faster*/)
    {
        car.transform.position = new Vector3(animationCurveX.Evaluate(time), animationCurveY.Evaluate(time), animationCurveZ.Evaluate(time));

        yield return null;
    }

    car.transform.position = new Vector3(animationCurveX.Evaluate(endTime), animationCurveY.Evaluate(endTime), animationCurveZ.Evaluate(endTime));

    alreadyAnimating = false;
}
derHugo
  • 83,094
  • 9
  • 75
  • 115
1

You calculate the average speed of the object between two points and move the sphere accordingly but not the acceleration. You'll want to keep track of the sphere's speed in order to apply linear acceleration between two points. Accumulating the acceleration into the sphere's speed and applying that speed into its position over time should make it much less choppy.

To calculate average acceleration, you'll need to divide the velocity at point B subtracted by the velocity at point A by the distance: (v2 - v1) / d. To know the speed of the sphere by the time it reaches point B, it's just distance over time.

pvc pipe
  • 63
  • 7
  • Thanks @pvc pipe for this, I think it's exactly what I need. Are you able to provide a bit more context on putting it into a Lerp? I've added the following: `var duration = currentSample.TimeDelta;`. `var distance = Vector3.Distance(lastPosition, targetPosition);`. `var speed = distance / duration;`. `var acceleration = (speed - lastSpeed) / distance;`. Can you explain how to accumulate the acceleration into the sphere's speed? – dave Apr 09 '23 at 11:33
  • @dave You could keep linear interpolation to move the object along but you'll have to calculate the percentage of how far it moves along every iteration in the loop. Instead, you could create another Vector3 for velocity in the X, Y and Z planes. Every iteration, you can add the acceleration into the velocity vector and add the speed into the sphere's position after determining how much it accelerates and travels by multiplying the acceleration with the delta. Think gravity: 9.81 m/s and you multiply that by seconds to get how much an object has accelerated. – pvc pipe Apr 09 '23 at 17:17