0

I'm currently working with a folder of JSON files which are collected through a tracking experiment with a drone. The data contains position, rotation and timestamp of the drone while it's moving and levitating inside the tracking system. What I'm currently doing is trying to simulate the movement of the drone inside Unity using those data. So far, I've managed to parse the position and rotation from the data to an object inside Unity and extract the timestamp to System.DateTime in Unity. However, I don't how to work with the timestamp. I want to use the timestamp to match the position and rotation of the object (i.e: at this timestamp, the drone should be at this position(x,y,z) and has the rotation(x,y,z,w)). Can someone help me with this problem, really appreciate your help :D Here is my current code:

void Update()
 {
     if (loaded)
     {
         for(int i = 0; i <= pos_data.Count; i+= 10)
         {
             Cube.transform.position = pos_data[i];
             Cube.transform.rotation = rot_data[i];
         }
     }
     else
     {
         LoadJson();
         //startTime = datetime[0];
         loaded = true;
     }
 }
 public void LoadJson()
 {
     string HeadPath = @Application.dataPath + "/Data/" + "drone_data_1.json";
     string HeadJsonhold = File.ReadAllText(HeadPath);
     var data_ = JSON.Parse(HeadJsonhold);
     
     for (int rows = 0; rows <= data_.Count; rows += 10)
     {
         pos_data.Add(new Vector3(data_[rows]["location"]["x"].AsFloat, data_[rows]["location"]["y"].AsFloat, data_[rows]["location"]["z"].AsFloat));
         rot_data.Add(new Quaternion(data_[rows]["rotation"]["x"].AsFloat, data_[rows]["rotation"]["y"].AsFloat, data_[rows]["rotation"]["z"].AsFloat, data_[rows]["rotation"]["w"].AsFloat));
         Time = System.DateTime.ParseExact(data_[rows]["Timestamp"], "yyyyMMddHHmmss",null);
         //Debug.Log(Time);
     }    
 }
Tim
  • 11
  • 2

1 Answers1

1

If I understand you correctly what you are getting are samples of a real-world drone that at some rate stores keyframes of its movement.

Now you already successfully load that json data but wonder how to animate the Unity object accordingly.


The timestamp itself you can't use at all! ^^

It most probably lies somewhere in the past ;) And you can't just assign something to Time.

What you can do, however, is take the timestamp of the first sample (I will just assume that your samples are all already ordered by the time) and calculate the difference to the next sample and so on.

Then you can use that difference in order to always interpolate between the current and next sample transforms using the given time delta.

Currently you are just doing all samples in one single frame so there won't be any animation at all.

Also just as sidenote:

for(int i = 0; i <= pos_data.Count; i+= 10)

is wrong twice:

  • a) you already skipped 10 samples when loading the data -> are you sure you now want to again skip 10 of these => In total every time skipping 100 samples?
  • b) since indices are 0 based the last accessible index would be pos_data.Count - 1 so in general when iterating Lists/arrays it should be i < pos_data.Count ;)

UPDATE: For a better solution for interpolation refer to this newer post - below data types could be adjusted to better fit the AnimationCurve approach mentioned there.


First of all I would suggest you use a better data structure and use one single list holding the information that belongs together instead of multiple parallel lists and rather load your json like e.g.

[Serializable]
public class Sample 
{
    public readonly Vector3 Position;
    public readonly Quaternion Rotation;
    public readonly float TimeDelta;

    public Sample(Vector3 position, Quaternion rotation, float timeDelta)
    {
        Position = position;
        Rotation = rotation;
        TimeDelta = timeDelta;
    }
}

And then

// Just making this serialized so you can immediately see in the Inspector
// if your data loaded correctly
[SerializeField] private readonly List<Sample> _samples = new List<Sample>();

public void LoadJson()
{
     // start fresh
     _samples.Clear();

     // See https://learn.microsoft.com/dotnet/api/system.io.path.combine
     var path = Path.Combine(Application.dataPath, "Data", "drone_data_1.json");
     var json = File.ReadAllText(path);
     var data = JSON.Parse(json);
     
     DateTime lastTime = default;

     for (var i = 0; i <= data.Count; i += 10)
     {
         // First I would pre-cache these values 
         var sample = data[i];
         var sampleLocation = sample["location"];
         var sampleRotation = sample["rotation"];
         var sampleTime = sample["Timestamp"];

         // Get your values as you did already
         var position = new Vector3(sampleLocation["x"].AsFloat, sampleLocation["y"].AsFloat, sampleLocation["z"].AsFloat));
         var rotation = new Quaternion(sampleRotation["x"].AsFloat, sampleRotation["y"].AsFloat, sampleRotation["z"].AsFloat, sampleRotation["w"].AsFloat));
         
         var time = System.DateTime.ParseExact(sampleTime, "yyyyMMddHHmmss", null);
          
         // Now for the first sample there is no deltaTime
         // for all others calculate the difference in seconds between the
         // last and current sample
         // See https://learn.microsoft.com/dotnet/csharp/language-reference/operators/conditional-operator
         var deltaTime = i == 0 ? 0f : GetDeltaSeconds(lastTime, time);
         // and of course store it for the next iteration
         lastTime = time;

         // Now you can finally add the sample to the list of samples
         // instead of having multiple parallel lists 
         _samples.Add(new Sample(position, rotation, deltaTime));
     }    
 }

private float GetDeltaSeconds(DateTime first, DateTime second)
{
    // See https://learn.microsoft.com/dotnet/api/system.datetime.op_subtraction#System_DateTime_op_Subtraction_System_DateTime_System_DateTime_
    var deltaSpan = second - first;
    // See https://learn.microsoft.com/dotnet/api/system.timespan.totalseconds#System_TimeSpan_TotalSeconds
    return (float)deltaSpan.TotalSeconds;
}

So now what to do with this information?

You now have samples (still assuming ordered by time) holding all required information to be able to interpolate between them.

I would use Coroutines instead of Update, in my eyes they are easier to understand and maintain

// Do your loading **once** in Start
private void Start()
{
    LoadJson();

    // Then start the animation routine
    // I just make it a method so you could also start it later e.g. via button etc
    StartAnimation();
}

// A flag just in case to avoid concurrent animations
private bool alreadyAnimating;

// As said just making this a method so you could also remove it from Start
// and call it in any other moment you like
public void StartAnimation()
{
    // Only start an animation if there isn't already one running
    // See https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
    if(!alreadyAnimating) StartCoroutine(AnimationRoutine());
}

private IEnumerator AnimationRoutine()
{
    // Just in case abort if there is already another animation running
    if(alreadyAnimating) yield break;

    // Block concurrent routine
    alreadyAnimating = true;

    // Initially set your object to the first sample
    var lastSample = _samples[0];
    Cube.transform.position = lastSample.Position;
    Cube.transform.rotation = lastSample.Rotation;

    // This tells Unity to "pause" the routine here, render this frame
    // and continue from here in the next frame
    yield return null;

    // then iterate through the rest of samples
    for(var i = 1; i < _samples.Count; i++)
    {
        var lastPosition = lastSample.Position;
        var lastRottaion = lastSample.Rottaion;

        var currentSample = _samples[i];
        var targetPosition = sample.Position;
        var targetRotation = sample.Rotation; 

        // How long this interpolation/animation will take
        var duration = currentSample.TimeDelta;
       
        // You never know ;)
        // See https://docs.unity3d.com/ScriptReference/Mathf.Approximately.html
        if(Mathf.Approximately(duration, 0f))
        {
            Cube.transform.position = targetPosition;
            Cube.transform.rotation = targetRotation;
            lastSample = currentSample;
            continue;
        }
     
        // And this is where the animation magic happens
        var timePassed = 0f; 
        while(timePassed < duration)
        {
            // this factor will be growing linear between 0 and 1
            var factor = timePassed / duration;

            // Interpolate between the "current" transforms (the ones it had at beginning of this iteration)
            // towards the next sample target transforms using the factor between 0 and 1
            // See https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
            Cube.transform.position = Vector3.Lerp(lastPosition, targetPosition, factor);
            // See https://docs.unity3d.com/ScriptReference/Quaternion.Slerp.html
            Cube.transform.rotation = Quaternion.Slerp(lastRotation, targetRotation, factor);

            // This tells Unity to "pause" the routine here, render this frame
            // and continue from here in the next frame
            yield return null;

            // increase by the time passed since the last frame was rendered
            timePassed += Time.deltaTime;
        }

        // just to be sure to end with clean values
        Cube.transform.position = targetPosition;
        Cube.transform.rotation = targetRotation;
        lastSample = currentSample;
    }

    // Allow the next animation to start (or restart this one)
    alreadyAnimating = false;

    // Additional stuff to do once the animation is done
}
derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Hi derHugo, First of all, I want to say thank you for your amazing answer. It really helps me to understand a lot of things which I've never thought about while solving my problem. I did try to use your solution and so far, it's working like a charm Really appreciate your help :) – Tim Feb 16 '21 at 15:27
  • Hi @derHugo, I'd like to also pass on my thanks for this answer, it's been really helpful and informative. I wondered if you might be able to offer your thoughts on my question here: https://stackoverflow.com/q/75968582/1969888 – I'm trying to factor acceleration into my Lerps for smoother interpolation between points – dave Apr 09 '23 at 07:59