Manvel A.
Manvel A.

Reputation: 33

Synchronize the animation timing with data (ThreeJS)

I have a data, Time in milliseconds and position(x,y,z) of object to be at that time.

msec    |poz_x  |poz_y  |poz_z
------------------------------
0       |318    |24     |3
25      |318    |24     |3
49      |318    |23     |3
70      |318    |22     |2
91      |318    |22     |2
113     |318    |21     |1
136     |318    |21     |1
e.t.c

The problem is that the time difference between actual and next data vary (coming from sensor). I'm looking for a way to do the animation in real time. If in my data I have 60 second of information, it need to animate in browser during 60 second. I have read that requestAnimationFrame( animate ); will repeat the function 60 time per second, but if my scene is heavy I imagine the frame rate will go down. In any case this can't solve my problem.

I'm looking for a robust solution that doesn't depend on the current framerate of the browser. Please help.

Upvotes: 0

Views: 963

Answers (1)

Martin Schuhfuß
Martin Schuhfuß

Reputation: 6986

There are a couple of ways to solve that, with and without libraries. And you are right, it is not quite as simple as just counting the number of ticks of the animation-loop as there is no guarantee it happens every 1/60 second. But the animation-frame callback (loop in the code below) will get a timestamp passed as first parameter that can be used to calculate animation-progress.

So, in javascript, that could be something like this:

// these are your keyframes, in a format compatible with THREE.Vector3. 
// Please note that the time `t` is expected in milliseconds here.
// (must have properties named x, y and z - otherwise the copy below doesn't work)
const keyframes = [
  {t: 0, x: 318, y: 24, z: 3},
  {t: 25, x: 318, y: 24, z: 3},
  // ... and so on
];

// find a pair of keyframes [a, b] such that `a.t < t` and `b.t > t`. 
// In other words, find the previous and next keyframe given the 
// specific time `t`. If no previous or next keyframes is found, null 
// is returned instead.
function findNearestKeyframes(t) {
  let prevKeyframe = null;
  for (let i = 0; i < keyframes.length; i++) {
    if (keyframes[i].t > t) {
      return [prevKeyframe, keyframes[i]];
    }
    prevKeyframe = keyframes[i];
  }

  return [prevKeyframe, null];
}

const tmpV3 = new THREE.Vector3();

function loop(t) {
  const [prevKeyframe, nextKeyframe] = findNearestKeyframes(t);

  // (...not handling cases where there is no prev or next here)

  // compute the progress of time between the two keyframes 
  // (0 when t === prevKeyframe.t and 1 when t === nextKeyframe.t)
  let progress = (t - prevKeyframe.t) / (nextKeyframe.t - prevKeyframe.t);

  // copy position from previous keyframe, and interpolate towards the 
  // next keyframe linearly
  tmpV3.copy(nextKeyframe);
  someObject.position
    .copy(prevKeyframe)
    .lerp(tmpV3, progress);

  // (...render scene)

  requestAnimationFrame(loop);
}

// start the animation-loop
requestAnimationFrame(loop);

EDIT: To address one question from the comments about optimizing the findNearestKeyframes-function:

Once you get to several thousands of keyframes it might make sense to optimize that a bit, yes. For something like a few hundred it wouldn't be worth the effort (i'd categorize that as premature optimization).

To optimize, you could create an index-table in order to skip over irrelevant sections of the array. For instance, you could store the index in the keyframes-array for the start of every 10 seconds or something like that, that way - when you're searching for keyframes around t = 12.328s, you could start at a higher index based on precomputed information. There are probably a lot of other algorithms and structures that you could use to speed it up.

Upvotes: 2

Related Questions