keyFrameInterpolator.cpp
1 /****************************************************************************
2 
3  Copyright (C) 2002-2013 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.5.2.
6 
7  http://www.libqglviewer.com - contact@libqglviewer.com
8 
9  This file may be used under the terms of the GNU General Public License
10  versions 2.0 or 3.0 as published by the Free Software Foundation and
11  appearing in the LICENSE file included in the packaging of this file.
12  In addition, as a special exception, Gilles Debunne gives you certain
13  additional rights, described in the file GPL_EXCEPTION in this package.
14 
15  libQGLViewer uses dual licensing. Commercial/proprietary software must
16  purchase a libQGLViewer Commercial License.
17 
18  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 
21 *****************************************************************************/
22 
23 #include "domUtils.h"
24 #include "qglviewer.h" // for QGLViewer::drawAxis and Camera::drawCamera
25 
26 using namespace qglviewer;
27 using namespace std;
28 
36  : frame_(NULL), period_(40), interpolationTime_(0.0), interpolationSpeed_(1.0), interpolationStarted_(false),
37  closedPath_(false), loopInterpolation_(false), pathIsValid_(false), valuesAreValid_(true), currentFrameValid_(false)
38  // #CONNECTION# Values cut pasted initFromDOMElement()
39 {
40  setFrame(frame);
41  for (int i=0; i<4; ++i)
42  currentFrame_[i] = new QMutableListIterator<KeyFrame*>(keyFrame_);
43  connect(&timer_, SIGNAL(timeout()), SLOT(update()));
44 }
45 
48 {
49  deletePath();
50  for (int i=0; i<4; ++i)
51  delete currentFrame_[i];
52 }
53 
56 {
57  if (this->frame())
58  disconnect(this, SIGNAL( interpolated() ), this->frame(), SIGNAL( interpolated() ));
59 
60  frame_ = frame;
61 
62  if (this->frame())
63  connect(this, SIGNAL( interpolated() ), this->frame(), SIGNAL( interpolated() ));
64 }
65 
72 void KeyFrameInterpolator::update()
73 {
75 
76  interpolationTime_ += interpolationSpeed() * interpolationPeriod() / 1000.0;
77 
78  if (interpolationTime() > keyFrame_.last()->time())
79  {
80  if (loopInterpolation())
81  setInterpolationTime(keyFrame_.first()->time() + interpolationTime_ - keyFrame_.last()->time());
82  else
83  {
84  // Make sure last KeyFrame is reached and displayed
85  interpolateAtTime(keyFrame_.last()->time());
87  }
88  Q_EMIT endReached();
89  }
90  else
91  if (interpolationTime() < keyFrame_.first()->time())
92  {
93  if (loopInterpolation())
94  setInterpolationTime(keyFrame_.last()->time() - keyFrame_.first()->time() + interpolationTime_);
95  else
96  {
97  // Make sure first KeyFrame is reached and displayed
98  interpolateAtTime(keyFrame_.first()->time());
100  }
101  Q_EMIT endReached();
102  }
103 }
104 
105 
127 {
128  if (period >= 0)
129  setInterpolationPeriod(period);
130 
131  if (!keyFrame_.isEmpty())
132  {
133  if ((interpolationSpeed() > 0.0) && (interpolationTime() >= keyFrame_.last()->time()))
134  setInterpolationTime(keyFrame_.first()->time());
135  if ((interpolationSpeed() < 0.0) && (interpolationTime() <= keyFrame_.first()->time()))
136  setInterpolationTime(keyFrame_.last()->time());
137  timer_.start(interpolationPeriod());
138  interpolationStarted_ = true;
139  update();
140  }
141 }
142 
143 
146 {
147  timer_.stop();
148  interpolationStarted_ = false;
149 }
150 
151 
157 {
160 }
161 
174 void KeyFrameInterpolator::addKeyFrame(const Frame* const frame, float time)
175 {
176  if (!frame)
177  return;
178 
179  if (keyFrame_.isEmpty())
180  interpolationTime_ = time;
181 
182  if ( (!keyFrame_.isEmpty()) && (keyFrame_.last()->time() > time) )
183  qWarning("Error in KeyFrameInterpolator::addKeyFrame: time is not monotone");
184  else
185  keyFrame_.append(new KeyFrame(frame, time));
186  connect(frame, SIGNAL(modified()), SLOT(invalidateValues()));
187  valuesAreValid_ = false;
188  pathIsValid_ = false;
189  currentFrameValid_ = false;
191 }
192 
200 void KeyFrameInterpolator::addKeyFrame(const Frame& frame, float time)
201 {
202  if (keyFrame_.isEmpty())
203  interpolationTime_ = time;
204 
205  if ( (!keyFrame_.isEmpty()) && (keyFrame_.last()->time() > time) )
206  qWarning("Error in KeyFrameInterpolator::addKeyFrame: time is not monotone");
207  else
208  keyFrame_.append(new KeyFrame(frame, time));
209 
210  valuesAreValid_ = false;
211  pathIsValid_ = false;
212  currentFrameValid_ = false;
214 }
215 
216 
221 void KeyFrameInterpolator::addKeyFrame(const Frame* const frame)
222 {
223  float time;
224  if (keyFrame_.isEmpty())
225  time = 0.0;
226  else
227  time = lastTime() + 1.0;
228 
229  addKeyFrame(frame, time);
230 }
231 
237 {
238  float time;
239  if (keyFrame_.isEmpty())
240  time = 0.0;
241  else
242  time = keyFrame_.last()->time() + 1.0;
243 
244  addKeyFrame(frame, time);
245 }
246 
249 {
251  qDeleteAll(keyFrame_);
252  keyFrame_.clear();
253  pathIsValid_ = false;
254  valuesAreValid_ = false;
255  currentFrameValid_ = false;
256 }
257 
258 static void drawCamera(float scale)
259 {
260  glDisable(GL_LIGHTING);
261 
262  const float halfHeight = scale * 0.07;
263  const float halfWidth = halfHeight * 1.3;
264  const float dist = halfHeight / tan(M_PI/8.0);
265 
266  const float arrowHeight = 1.5f * halfHeight;
267  const float baseHeight = 1.2f * halfHeight;
268  const float arrowHalfWidth = 0.5f * halfWidth;
269  const float baseHalfWidth = 0.3f * halfWidth;
270 
271  // Frustum outline
272  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
273  glBegin(GL_LINE_STRIP);
274  glVertex3f(-halfWidth, halfHeight,-dist);
275  glVertex3f(-halfWidth,-halfHeight,-dist);
276  glVertex3f( 0.0f, 0.0f, 0.0f);
277  glVertex3f( halfWidth,-halfHeight,-dist);
278  glVertex3f(-halfWidth,-halfHeight,-dist);
279  glEnd();
280  glBegin(GL_LINE_STRIP);
281  glVertex3f( halfWidth,-halfHeight,-dist);
282  glVertex3f( halfWidth, halfHeight,-dist);
283  glVertex3f( 0.0f, 0.0f, 0.0f);
284  glVertex3f(-halfWidth, halfHeight,-dist);
285  glVertex3f( halfWidth, halfHeight,-dist);
286  glEnd();
287 
288  // Up arrow
289  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
290  // Base
291  glBegin(GL_QUADS);
292  glVertex3f(-baseHalfWidth, halfHeight,-dist);
293  glVertex3f( baseHalfWidth, halfHeight,-dist);
294  glVertex3f( baseHalfWidth, baseHeight,-dist);
295  glVertex3f(-baseHalfWidth, baseHeight,-dist);
296  glEnd();
297 
298  // Arrow
299  glBegin(GL_TRIANGLES);
300  glVertex3f( 0.0f, arrowHeight,-dist);
301  glVertex3f(-arrowHalfWidth, baseHeight, -dist);
302  glVertex3f( arrowHalfWidth, baseHeight, -dist);
303  glEnd();
304 }
305 
337 void KeyFrameInterpolator::drawPath(int mask, int nbFrames, float scale)
338 {
339  const int nbSteps = 30;
340  if (!pathIsValid_)
341  {
342  path_.clear();
343 
344  if (keyFrame_.isEmpty())
345  return;
346 
347  if (!valuesAreValid_)
348  updateModifiedFrameValues();
349 
350  if (keyFrame_.first() == keyFrame_.last())
351  path_.push_back(Frame(keyFrame_.first()->position(), keyFrame_.first()->orientation()));
352  else
353  {
354  static Frame fr;
355  KeyFrame* kf_[4];
356  kf_[0] = keyFrame_.first();
357  kf_[1] = kf_[0];
358  int index = 1;
359  kf_[2] = (index < keyFrame_.size()) ? keyFrame_.at(index) : NULL;
360  index++;
361  kf_[3] = (index < keyFrame_.size()) ? keyFrame_.at(index) : NULL;
362 
363  while (kf_[2])
364  {
365  Vec diff = kf_[2]->position() - kf_[1]->position();
366  Vec v1 = 3.0 * diff - 2.0 * kf_[1]->tgP() - kf_[2]->tgP();
367  Vec v2 = -2.0 * diff + kf_[1]->tgP() + kf_[2]->tgP();
368 
369  // cout << kf_[0]->time() << " , " << kf_[1]->time() << " , " << kf_[2]->time() << " , " << kf_[3]->time() << endl;
370  for (int step=0; step<nbSteps; ++step)
371  {
372  float alpha = step / static_cast<float>(nbSteps);
373  fr.setPosition(kf_[1]->position() + alpha * (kf_[1]->tgP() + alpha * (v1+alpha*v2)));
374  fr.setOrientation(Quaternion::squad(kf_[1]->orientation(), kf_[1]->tgQ(), kf_[2]->tgQ(), kf_[2]->orientation(), alpha));
375  path_.push_back(fr);
376  }
377 
378  // Shift
379  kf_[0] = kf_[1];
380  kf_[1] = kf_[2];
381  kf_[2] = kf_[3];
382  index++;
383  kf_[3] = (index < keyFrame_.size()) ? keyFrame_.at(index) : NULL;
384  }
385  // Add last KeyFrame
386  path_.push_back(Frame(kf_[1]->position(), kf_[1]->orientation()));
387  }
388  pathIsValid_ = true;
389  }
390 
391  if (mask)
392  {
393  glDisable(GL_LIGHTING);
394  glLineWidth(2);
395 
396  if (mask & 1)
397  {
398  glBegin(GL_LINE_STRIP);
399  Q_FOREACH (Frame fr, path_)
400  glVertex3fv(fr.position());
401  glEnd();
402  }
403  if (mask & 6)
404  {
405  int count = 0;
406  if (nbFrames > nbSteps)
407  nbFrames = nbSteps;
408  float goal = 0.0f;
409  Q_FOREACH (Frame fr, path_)
410  if ((count++) >= goal)
411  {
412  goal += nbSteps / static_cast<float>(nbFrames);
413  glPushMatrix();
414  glMultMatrixd(fr.matrix());
415  if (mask & 2) drawCamera(scale);
416  if (mask & 4) QGLViewer::drawAxis(scale/10.0);
417  glPopMatrix();
418  }
419  }
420  }
421 }
422 
423 void KeyFrameInterpolator::updateModifiedFrameValues()
424 {
425  Quaternion prevQ = keyFrame_.first()->orientation();
426  KeyFrame* kf;
427  for (int i=0; i<keyFrame_.size(); ++i)
428  {
429  kf = keyFrame_.at(i);
430  if (kf->frame())
431  kf->updateValuesFromPointer();
432  kf->flipOrientationIfNeeded(prevQ);
433  prevQ = kf->orientation();
434  }
435 
436  KeyFrame* prev = keyFrame_.first();
437  kf = keyFrame_.first();
438  int index = 1;
439  while (kf)
440  {
441  KeyFrame* next = (index < keyFrame_.size()) ? keyFrame_.at(index) : NULL;
442  index++;
443  if (next)
444  kf->computeTangent(prev, next);
445  else
446  kf->computeTangent(prev, kf);
447  prev = kf;
448  kf = next;
449  }
450  valuesAreValid_ = true;
451 }
452 
460 {
461  const KeyFrame* const kf = keyFrame_.at(index);
462  return Frame(kf->position(), kf->orientation());
463 }
464 
469 {
470  return keyFrame_.at(index)->time();
471 }
472 
478 {
479  return lastTime() - firstTime();
480 }
481 
486 {
487  if (keyFrame_.isEmpty())
488  return 0.0;
489  else
490  return keyFrame_.first()->time();
491 }
492 
497 {
498  if (keyFrame_.isEmpty())
499  return 0.0;
500  else
501  return keyFrame_.last()->time();
502 }
503 
504 void KeyFrameInterpolator::updateCurrentKeyFrameForTime(float time)
505 {
506  // Assertion: times are sorted in monotone order.
507  // Assertion: keyFrame_ is not empty
508 
509  // TODO: Special case for loops when closed path is implemented !!
510  if (!currentFrameValid_)
511  // Recompute everything from scrach
512  currentFrame_[1]->toFront();
513 
514  while (currentFrame_[1]->peekNext()->time() > time)
515  {
516  currentFrameValid_ = false;
517  if (!currentFrame_[1]->hasPrevious())
518  break;
519  currentFrame_[1]->previous();
520  }
521 
522  if (!currentFrameValid_)
523  *currentFrame_[2] = *currentFrame_[1];
524 
525  while (currentFrame_[2]->peekNext()->time() < time)
526  {
527  currentFrameValid_ = false;
528  if (!currentFrame_[2]->hasNext())
529  break;
530  currentFrame_[2]->next();
531  }
532 
533  if (!currentFrameValid_)
534  {
535  *currentFrame_[1] = *currentFrame_[2];
536  if ((currentFrame_[1]->hasPrevious()) && (time < currentFrame_[2]->peekNext()->time()))
537  currentFrame_[1]->previous();
538 
539  *currentFrame_[0] = *currentFrame_[1];
540  if (currentFrame_[0]->hasPrevious())
541  currentFrame_[0]->previous();
542 
543  *currentFrame_[3] = *currentFrame_[2];
544  if (currentFrame_[3]->hasNext())
545  currentFrame_[3]->next();
546 
547  currentFrameValid_ = true;
548  splineCacheIsValid_ = false;
549  }
550 
551  // cout << "Time = " << time << " : " << currentFrame_[0]->peekNext()->time() << " , " <<
552  // currentFrame_[1]->peekNext()->time() << " , " << currentFrame_[2]->peekNext()->time() << " , " << currentFrame_[3]->peekNext()->time() << endl;
553 }
554 
555 void KeyFrameInterpolator::updateSplineCache()
556 {
557  Vec delta = currentFrame_[2]->peekNext()->position() - currentFrame_[1]->peekNext()->position();
558  v1 = 3.0 * delta - 2.0 * currentFrame_[1]->peekNext()->tgP() - currentFrame_[2]->peekNext()->tgP();
559  v2 = -2.0 * delta + currentFrame_[1]->peekNext()->tgP() + currentFrame_[2]->peekNext()->tgP();
560  splineCacheIsValid_ = true;
561 }
562 
571 {
572  setInterpolationTime(time);
573 
574  if ((keyFrame_.isEmpty()) || (!frame()))
575  return;
576 
577  if (!valuesAreValid_)
578  updateModifiedFrameValues();
579 
580  updateCurrentKeyFrameForTime(time);
581 
582  if (!splineCacheIsValid_)
583  updateSplineCache();
584 
585  float alpha;
586  float dt = currentFrame_[2]->peekNext()->time() - currentFrame_[1]->peekNext()->time();
587  if (dt == 0.0)
588  alpha = 0.0;
589  else
590  alpha = (time - currentFrame_[1]->peekNext()->time()) / dt;
591 
592  // Linear interpolation - debug
593  // Vec pos = alpha*(currentFrame_[2]->peekNext()->position()) + (1.0-alpha)*(currentFrame_[1]->peekNext()->position());
594  Vec pos = currentFrame_[1]->peekNext()->position() + alpha * (currentFrame_[1]->peekNext()->tgP() + alpha * (v1+alpha*v2));
595  Quaternion q = Quaternion::squad(currentFrame_[1]->peekNext()->orientation(), currentFrame_[1]->peekNext()->tgQ(),
596  currentFrame_[2]->peekNext()->tgQ(), currentFrame_[2]->peekNext()->orientation(), alpha);
598 
599  Q_EMIT interpolated();
600 }
601 
617 QDomElement KeyFrameInterpolator::domElement(const QString& name, QDomDocument& document) const
618 {
619  QDomElement de = document.createElement(name);
620  int count = 0;
621  Q_FOREACH (KeyFrame* kf, keyFrame_)
622  {
623  Frame fr(kf->position(), kf->orientation());
624  QDomElement kfNode = fr.domElement("KeyFrame", document);
625  kfNode.setAttribute("index", QString::number(count));
626  kfNode.setAttribute("time", QString::number(kf->time()));
627  de.appendChild(kfNode);
628  ++count;
629  }
630  de.setAttribute("nbKF", QString::number(keyFrame_.count()));
631  de.setAttribute("time", QString::number(interpolationTime()));
632  de.setAttribute("speed", QString::number(interpolationSpeed()));
633  de.setAttribute("period", QString::number(interpolationPeriod()));
634  DomUtils::setBoolAttribute(de, "closedPath", closedPath());
635  DomUtils::setBoolAttribute(de, "loop", loopInterpolation());
636  return de;
637 }
638 
647 void KeyFrameInterpolator::initFromDOMElement(const QDomElement& element)
648 {
649  qDeleteAll(keyFrame_);
650  keyFrame_.clear();
651  QDomElement child=element.firstChild().toElement();
652  while (!child.isNull())
653  {
654  if (child.tagName() == "KeyFrame")
655  {
656  Frame fr;
657  fr.initFromDOMElement(child);
658  float time = DomUtils::floatFromDom(child, "time", 0.0);
659  addKeyFrame(fr, time);
660  }
661 
662  child = child.nextSibling().toElement();
663  }
664 
665  // #CONNECTION# Values cut pasted from constructor
666  setInterpolationTime(DomUtils::floatFromDom(element, "time", 0.0));
667  setInterpolationSpeed(DomUtils::floatFromDom(element, "speed", 1.0));
668  setInterpolationPeriod(DomUtils::intFromDom(element, "period", 40));
669  setClosedPath(DomUtils::boolFromDom(element, "closedPath", false));
670  setLoopInterpolation(DomUtils::boolFromDom(element, "loop", false));
671 
672  // setFrame(NULL);
673  pathIsValid_ = false;
674  valuesAreValid_ = false;
675  currentFrameValid_ = false;
676 
678 }
679 
680 #ifndef DOXYGEN
681 
683 KeyFrameInterpolator::KeyFrame::KeyFrame(const Frame& fr, float t)
684  : time_(t), frame_(NULL)
685 {
686  p_ = fr.position();
687  q_ = fr.orientation();
688 }
689 
690 KeyFrameInterpolator::KeyFrame::KeyFrame(const Frame* fr, float t)
691  : time_(t), frame_(fr)
692 {
693  updateValuesFromPointer();
694 }
695 
696 void KeyFrameInterpolator::KeyFrame::updateValuesFromPointer()
697 {
698  p_ = frame()->position();
699  q_ = frame()->orientation();
700 }
701 
702 void KeyFrameInterpolator::KeyFrame::computeTangent(const KeyFrame* const prev, const KeyFrame* const next)
703 {
704  tgP_ = 0.5 * (next->position() - prev->position());
705  tgQ_ = Quaternion::squadTangent(prev->orientation(), q_, next->orientation());
706 }
707 
708 void KeyFrameInterpolator::KeyFrame::flipOrientationIfNeeded(const Quaternion& prev)
709 {
710  if (Quaternion::dot(prev, q_) < 0.0)
711  q_.negate();
712 }
713 
714 #endif //DOXYGEN
void endReached()
This signal is emitted when the interpolation reaches the first (when interpolationSpeed() is negativ...
void setInterpolationTime(float time)
Sets the interpolationTime().
void setInterpolationPeriod(int period)
Sets the interpolationPeriod().
float keyFrameTime(int index) const
Returns the time corresponding to the index keyFrame.
virtual void initFromDOMElement(const QDomElement &element)
Restores the Frame state from a QDomElement created by domElement().
Definition: frame.cpp:1011
KeyFrameInterpolator(Frame *fr=NULL)
Creates a KeyFrameInterpolator, with frame as associated frame().
float interpolationSpeed() const
Returns the current interpolation speed.
virtual QDomElement domElement(const QString &name, QDomDocument &document) const
Returns an XML QDomElement that represents the Frame.
Definition: frame.cpp:994
virtual QDomElement domElement(const QString &name, QDomDocument &document) const
Returns an XML QDomElement that represents the KeyFrameInterpolator.
void deletePath()
Removes all keyFrames from the path.
void interpolated()
This signal is emitted whenever the frame() state is interpolated.
static double dot(const Quaternion &a, const Quaternion &b)
Returns the "dot" product of a and b: a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3].
Definition: quaternion.h:270
void stopInterpolation()
Stops an interpolation started with startInterpolation().
void setFrame(Frame *const frame)
Sets the frame() associated to the KeyFrameInterpolator.
float lastTime() const
Returns the time corresponding to the last keyFrame, expressed in seconds.
void startInterpolation(int period=-1)
Starts the interpolation process.
virtual void drawPath(int mask=1, int nbFrames=6, float scale=1.0f)
Draws the path used to interpolate the frame().
const GLdouble * matrix() const
Returns the 4x4 OpenGL transformation matrix represented by the Frame.
Definition: frame.cpp:123
void setPositionAndOrientationWithConstraint(Vec &position, Quaternion &orientation)
Same as setPositionAndOrientation() but position and orientation are modified to satisfy the constrai...
Definition: frame.cpp:641
static Quaternion squadTangent(const Quaternion &before, const Quaternion &center, const Quaternion &after)
Returns a tangent Quaternion for center, defined by before and after Quaternions. ...
Definition: quaternion.cpp:513
int interpolationPeriod() const
Returns the current interpolation period, expressed in milliseconds.
static Quaternion squad(const Quaternion &a, const Quaternion &tgA, const Quaternion &tgB, const Quaternion &b, float t)
Returns the slerp interpolation of the two Quaternions a and b, at time t, using tangents tgA and tgB...
Definition: quaternion.cpp:467
void resetInterpolation()
Stops the interpolation and resets interpolationTime() to the firstTime().
The Vec class represents 3D positions and 3D vectors.
Definition: vec.h:65
void addKeyFrame(const Frame &frame)
Appends a new keyFrame to the path.
float duration() const
Returns the duration of the KeyFrameInterpolator path, expressed in seconds.
Frame * frame() const
Returns the associated Frame and that is interpolated by the KeyFrameInterpolator.
void negate()
Negates all the coefficients of the Quaternion.
Definition: quaternion.h:220
Quaternion orientation() const
Returns the orientation of the Frame, defined in the world coordinate system.
Definition: frame.cpp:546
float interpolationTime() const
Returns the current interpolation time (in seconds) along the KeyFrameInterpolator path...
float firstTime() const
Returns the time corresponding to the first keyFrame, expressed in seconds.
void setInterpolationSpeed(float speed)
Sets the interpolationSpeed().
virtual void initFromDOMElement(const QDomElement &element)
Restores the KeyFrameInterpolator state from a QDomElement created by domElement().
The Quaternion class represents 3D rotations and orientations.
Definition: quaternion.h:66
void setPosition(const Vec &position)
Sets the position() of the Frame, defined in the world coordinate system.
Definition: frame.cpp:443
Frame keyFrame(int index) const
Returns the Frame associated with the keyFrame at index index.
void setClosedPath(bool closed=true)
Sets the closedPath() value.
virtual ~KeyFrameInterpolator()
Virtual destructor.
bool loopInterpolation() const
Returns true when the interpolation is played in an infinite loop.
bool closedPath() const
Whether or not (default) the path defined by the keyFrames is a closed loop.
The Frame class represents a coordinate system, defined by a position and an orientation.
Definition: frame.h:121
void setLoopInterpolation(bool loop=true)
Sets the loopInterpolation() value.
void setOrientation(const Quaternion &orientation)
Sets the orientation() of the Frame, defined in the world coordinate system.
Definition: frame.cpp:505
Vec position() const
Returns the position of the Frame, defined in the world coordinate system.
Definition: frame.cpp:537
static void drawAxis(float length=1.0f)
Draws an XYZ axis, with a given size (default is 1.0).
Definition: qglviewer.cpp:3253
virtual void interpolateAtTime(float time)
Interpolate frame() at time time (expressed in seconds).