Pan Gesture Prediction - Added DALI_PAN_GESTURE_PREDICTION_AMOUNT environment variable
[platform/core/uifw/dali-core.git] / dali / internal / update / gestures / scene-graph-pan-gesture.cpp
index 762480c..b3340aa 100644 (file)
@@ -20,6 +20,7 @@
 // EXTERNAL INCLUDES
 
 // INTERNAL INCLUDES
+#include <dali/internal/update/gestures/pan-gesture-profiling.h>
 
 namespace Dali
 {
@@ -29,12 +30,15 @@ namespace Internal
 
 namespace SceneGraph
 {
-
 namespace
 {
-const unsigned int ARRAY_SIZE( 4u );
+const int MAX_GESTURE_AGE = 50; ///< maximum age of a gesture before disallowing its use in algorithm
+const float DEFAULT_PREDICTION_INTERPOLATION = 0.0f; ///< how much to interpolate pan position and displacement from last vsync time
 } // unnamed namespace
 
+const PanGesture::PredictionMode PanGesture::DEFAULT_PREDICTION_MODE = PanGesture::PREDICTION_2;
+const int PanGesture::NUM_PREDICTION_MODES = PanGesture::PREDICTION_2 + 1;
+
 PanGesture* PanGesture::New()
 {
   return new PanGesture();
@@ -42,85 +46,345 @@ PanGesture* PanGesture::New()
 
 PanGesture::~PanGesture()
 {
+  delete mProfiling;
 }
 
 void PanGesture::AddGesture( const Dali::PanGesture& gesture )
 {
   mGestures[ mWritePosition ] = gesture;
 
-  // Update our write position (so now this new value can be read)
-  // Note: we want to overwrite oldest gesture information even if it hasn't been read.
+  // Update our write position.
   ++mWritePosition;
-  mWritePosition %= ARRAY_SIZE;
+  mWritePosition %= PAN_GESTURE_HISTORY;
+}
+
+void PanGesture::RemoveOldHistory(PanInfoHistory& panHistory, uint currentTime, uint maxAge, uint minEvents)
+{
+  PanInfoHistoryConstIter endIter = panHistory.end();
+  PanInfoHistoryIter iter = panHistory.begin();
+  while( iter != endIter && panHistory.size() > minEvents)
+  {
+    PanInfo currentGesture = *iter;
+    if( currentTime < currentGesture.time + maxAge )
+    {
+      break;
+    }
+    iter = panHistory.erase(iter);
+    endIter = panHistory.end();
+  }
+}
+
+void PanGesture::SimpleAverageAlgorithm(bool justStarted, PanInfo& gestureOut)
+{
+  if( mInGesture )
+  {
+    if( !justStarted )
+    {
+      gestureOut.screen.position += mLastEventGesture.screen.position;
+      gestureOut.local.position += mLastEventGesture.local.position;
+      gestureOut.screen.position *= 0.5f;
+      gestureOut.local.position *= 0.5f;
+      // make current displacement relative to previous update-frame now.
+      gestureOut.screen.displacement = gestureOut.screen.position - mLastEventGesture.screen.position;
+      gestureOut.local.displacement = gestureOut.local.position - mLastEventGesture.local.position;
+    }
+  }
+}
+
+void PanGesture::PredictiveAlgorithm1(int eventsThisFrame, PanInfo& gestureOut, PanInfoHistory& panHistory, unsigned int lastVSyncTime, unsigned int nextVSyncTime)
+{
+  RemoveOldHistory(panHistory, lastVSyncTime, MAX_GESTURE_AGE, 0);
+  size_t panHistorySize = panHistory.size();
+  if( panHistorySize == 0 )
+  {
+    // cant do any prediction without a history
+    return;
+  }
+
+  PanInfoHistoryConstIter endIter = panHistory.end();
+  PanInfoHistoryIter iter = panHistory.begin();
+  Vector2 screenVelocity = gestureOut.screen.velocity;
+  Vector2 localVelocity = gestureOut.local.velocity;
+
+  bool havePreviousAcceleration = false;
+  bool previousVelocity = false;
+  float previousAccel = 0.0f;
+  unsigned int lastTime;
+  while( iter != endIter )
+  {
+    PanInfo currentGesture = *iter;
+    if( !previousVelocity )
+    {
+      // not yet set a previous velocity
+      screenVelocity = currentGesture.screen.velocity;
+      previousVelocity = true;
+      lastTime = currentGesture.time;
+      ++iter;
+      continue;
+    }
+    float velMag = currentGesture.screen.velocity.Length();
+    float velDiff = velMag - screenVelocity.Length();
+    float acceleration = 0.0f;
+    float time = (float)(currentGesture.time - lastTime);
+    if( time > Math::MACHINE_EPSILON_1 )
+    {
+      acceleration = velDiff / time;
+    }
+    float interpolationTime = (float)((int)lastVSyncTime - (int)currentGesture.time);
+    float newVelMag = 0.0f;
+    if( !havePreviousAcceleration )
+    {
+      newVelMag =  velMag + (acceleration * interpolationTime);
+      havePreviousAcceleration = true;
+    }
+    else
+    {
+      newVelMag = velMag + (((acceleration + previousAccel) * 0.5f) * interpolationTime);
+    }
+    float velMod = 1.0f;
+    if( velMag > Math::MACHINE_EPSILON_1 )
+    {
+      velMod = newVelMag / velMag;
+    }
+    gestureOut.screen.velocity = currentGesture.screen.velocity * velMod;
+    gestureOut.local.velocity = currentGesture.local.velocity * velMod;
+    screenVelocity = currentGesture.screen.velocity;
+    localVelocity = currentGesture.local.velocity;
+    previousAccel = acceleration;
+    ++iter;
+  }
+  // gestureOut's position is currently equal to the last event's position and its displacement is equal to last frame's total displacement
+  // add interpolated distance and position to current
+  float interpolationTime = (float)((int)lastVSyncTime - (int)gestureOut.time);
+  // work out interpolated velocity
+  gestureOut.screen.displacement = (gestureOut.screen.velocity * interpolationTime);
+  gestureOut.local.displacement = (gestureOut.local.velocity * interpolationTime);
+  gestureOut.screen.position += gestureOut.screen.displacement;
+  gestureOut.local.position += gestureOut.local.displacement;
+  gestureOut.time += (lastVSyncTime - gestureOut.time);
 }
 
-void PanGesture::UpdateProperties( unsigned int nextRenderTime )
+void PanGesture::PredictiveAlgorithm2(int eventsThisFrame, PanInfo& gestureOut, PanInfoHistory& panHistory, unsigned int lastVSyncTime, unsigned int nextVSyncTime)
 {
-  unsigned int time( 0u );
+  // TODO - adapt PredictiveAlgorithm1 with better smoothing, still under development
+  RemoveOldHistory(panHistory, lastVSyncTime, MAX_GESTURE_AGE, 0);
+  size_t panHistorySize = panHistory.size();
+  if( panHistorySize == 0 )
+  {
+    // cant do any prediction without a history
+    return;
+  }
+
+  PanInfoHistoryConstIter endIter = panHistory.end();
+  PanInfoHistoryIter iter = panHistory.begin();
+  Vector2 screenVelocity = gestureOut.screen.velocity;
+  Vector2 localVelocity = gestureOut.local.velocity;
+
+  bool havePreviousAcceleration = false;
+  bool previousVelocity = false;
+  float previousAccel = 0.0f;
+  unsigned int lastTime;
+  while( iter != endIter )
+  {
+    PanInfo currentGesture = *iter;
+    if( !previousVelocity )
+    {
+      // not yet set a previous velocity
+      screenVelocity = currentGesture.screen.velocity;
+      previousVelocity = true;
+      lastTime = currentGesture.time;
+      ++iter;
+      continue;
+    }
+    float previousValueWeight = (float)(MAX_GESTURE_AGE - (lastVSyncTime - lastTime)) / (float)MAX_GESTURE_AGE;
+    float velMag = currentGesture.screen.velocity.Length();
+    float velDiff = velMag - screenVelocity.Length();
+    float acceleration = 0.0f;
+    float time = (float)(currentGesture.time - lastTime);
+    if( time > Math::MACHINE_EPSILON_1 )
+    {
+      acceleration = velDiff / time;
+    }
+    float interpolationTime = (float)((int)lastVSyncTime - (int)currentGesture.time);
+    float newVelMag = 0.0f;
+    if( !havePreviousAcceleration )
+    {
+      newVelMag =  velMag + (acceleration * interpolationTime);
+      havePreviousAcceleration = true;
+    }
+    else
+    {
+      newVelMag = velMag + (((acceleration * (1.0f - previousValueWeight)) + (previousAccel * previousValueWeight)) * interpolationTime);
+    }
+    float velMod = 1.0f;
+    if( velMag > Math::MACHINE_EPSILON_1 )
+    {
+      velMod = newVelMag / velMag;
+    }
+    gestureOut.screen.velocity = currentGesture.screen.velocity * velMod;
+    gestureOut.local.velocity = currentGesture.local.velocity * velMod;
+    screenVelocity = currentGesture.screen.velocity;
+    localVelocity = currentGesture.local.velocity;
+    previousAccel = acceleration;
+    ++iter;
+  }
+  // gestureOut's position is currently equal to the last event's position and its displacement is equal to last frame's total displacement
+  // add interpolated distance and position to current
+  unsigned int timeAdvance = ((nextVSyncTime - lastVSyncTime) * mPredictionAmount);
+  unsigned int interpolationTime = (lastVSyncTime + timeAdvance) - gestureOut.time;
+  // work out interpolated velocity
+  gestureOut.screen.displacement = (gestureOut.screen.velocity * interpolationTime);
+  gestureOut.local.displacement = (gestureOut.local.velocity * interpolationTime);
+  gestureOut.screen.position += gestureOut.screen.displacement;
+  gestureOut.local.position += gestureOut.local.displacement;
+  gestureOut.time += interpolationTime;
+}
+
+bool PanGesture::UpdateProperties( unsigned int lastVSyncTime, unsigned int nextVSyncTime )
+{
+  bool propertiesUpdated( false );
+
+  if( !mInGesture )
+  {
+    // clear current pan history
+    mPanHistory.clear();
+  }
+
+  // create an event for this frame
   bool justStarted ( false );
   bool justFinished ( false );
+  bool eventFound( false );
+
+  // Not going through array from the beginning, using it as a circular buffer and only using unread
+  // values.
+  int eventsThisFrame = 0;
 
-  // If our read position is not same as write position, then there
-  // must be new values on circular buffer to read.
+  // create PanInfo to pass into prediction method
+  PanInfo nextGesture = mEventGesture;
+  // add new gestures and work out one full gesture for the frame
   while(mReadPosition != mWritePosition)
   {
-    const PanInfo& currentGesture = mGestures[mReadPosition];
+    // Copy the gesture first
+    PanInfo currentGesture(mGestures[mReadPosition]);
 
-    if ( currentGesture.time > time )
+    if( mProfiling )
     {
-      justStarted |= (currentGesture.state == Gesture::Started);
-      justFinished |= (currentGesture.state == Gesture::Finished || currentGesture.state == Gesture::Cancelled);
-
-      // use position values direct from gesture.
-      mLatestGesture.screen.position = currentGesture.screen.position;
-      mLatestGesture.local.position = currentGesture.local.position;
-
-      // change displacement to be relative to initial touch, instead of relative to last touch-frame.
-      if(currentGesture.state == Gesture::Started)
-      {
-        mLatestGesture.screen.displacement = currentGesture.screen.displacement;
-        mLatestGesture.local.displacement = currentGesture.local.displacement;
-      }
-      else
-      {
-        mLatestGesture.screen.displacement += currentGesture.screen.displacement;
-        mLatestGesture.local.displacement += currentGesture.local.displacement;
-      }
+      mProfiling->mRawData.push_back( PanGestureProfiling::Position( currentGesture.time, currentGesture.screen.position ) );
+    }
+    nextGesture.local.position = currentGesture.local.position;
+    nextGesture.local.velocity = currentGesture.local.velocity;
+    nextGesture.screen.position = currentGesture.screen.position;
+    nextGesture.screen.velocity = currentGesture.screen.velocity;
+    if( !eventFound )
+    {
+      nextGesture.local.displacement = currentGesture.local.displacement;
+      nextGesture.screen.displacement = currentGesture.screen.displacement;
+    }
+    else
+    {
+      nextGesture.local.displacement += currentGesture.local.displacement;
+      nextGesture.screen.displacement += currentGesture.screen.displacement;
+    }
+    eventFound = true;
+    nextGesture.time = currentGesture.time;
 
-      time = currentGesture.time;
+    // add event to history
+    mPanHistory.push_back(currentGesture);
+    justStarted |= (currentGesture.state == Gesture::Started);
+    if( currentGesture.state == Gesture::Started )
+    {
+      justStarted = true;
+      // clear just finished as we have started new pan
+      justFinished = false;
     }
+    justFinished |= (currentGesture.state == Gesture::Finished || currentGesture.state == Gesture::Cancelled);
 
+    // Update our read position.
+    ++eventsThisFrame;
     ++mReadPosition;
-    mReadPosition %= ARRAY_SIZE;
+    mReadPosition %= PAN_GESTURE_HISTORY;
   }
+  // set nextGesture to last gesture so it's position is correct and velocity is same as last frame
+  mEventGesture = nextGesture;
 
   mInGesture |= justStarted;
 
+  bool updateProperties = false;
+
   if ( mInGesture )
   {
-    PanInfo gesture(mLatestGesture);
+    if( mProfiling )
+    {
+      mProfiling->mLatestData.push_back( PanGestureProfiling::Position( lastVSyncTime, mEventGesture.screen.position ) );
+    }
 
-    if( !justStarted ) // only use previous frame if this is the continuing.
+    switch( mPredictionMode )
     {
-      // If previous gesture exists, then produce position as 50/50 interpolated blend of these two points.
-      gesture.screen.position += mPreviousGesture.screen.position;
-      gesture.local.position += mPreviousGesture.local.position;
-      gesture.screen.position *= 0.5f;
-      gesture.local.position *= 0.5f;
-      // make current displacement relative to previous update-frame now.
-      gesture.screen.displacement -= mPreviousGesture.screen.displacement;
-      gesture.local.displacement -= mPreviousGesture.local.displacement;
+      case NONE:
+      {
+        updateProperties = eventFound;
+        break;
+      }
+      case AVERAGE:
+      {
+        SimpleAverageAlgorithm(justStarted, nextGesture);
+        // make latest gesture equal to current gesture after averaging
+        updateProperties = true;
+        break;
+      }
+      case PREDICTION_1:
+      {
+        // make latest gesture equal to current gesture before interpolation
+        PredictiveAlgorithm1(eventsThisFrame, nextGesture, mPanHistory, lastVSyncTime, nextVSyncTime);
+        updateProperties = true;
+        break;
+      }
+      case PREDICTION_2:
+      {
+        // make latest gesture equal to current gesture before interpolation
+        PredictiveAlgorithm2(eventsThisFrame, nextGesture, mPanHistory, lastVSyncTime, nextVSyncTime);
+        updateProperties = true;
+        break;
+      }
     }
 
-    mPreviousGesture = mLatestGesture;
+    // always keep latest gesture up to date with event gesture
+    mLatestGesture = nextGesture;
 
-    mScreenPosition.Set( gesture.screen.position );
-    mScreenDisplacement.Set( gesture.screen.displacement );
-    mLocalPosition.Set( gesture.local.position );
-    mLocalDisplacement.Set( gesture.local.displacement );
+    if( updateProperties )
+    {
+      // only update properties if event received
+      // set latest gesture to raw pan info with unchanged time
+      mPanning.Set( mInGesture & !justFinished );
+      mScreenPosition.Set( nextGesture.screen.position );
+      mScreenDisplacement.Set( nextGesture.screen.displacement );
+      mLocalPosition.Set( nextGesture.local.position );
+      mLocalDisplacement.Set( nextGesture.local.displacement );
+
+      propertiesUpdated = true;
+    }
+
+    if( mProfiling )
+    {
+      mProfiling->mAveragedData.push_back( PanGestureProfiling::Position( nextGesture.time, nextGesture.screen.position ) );
+    }
   }
+  mLastEventGesture = mEventGesture;
 
   mInGesture &= ~justFinished;
+
+  if( mProfiling && justFinished )
+  {
+    mProfiling->PrintData();
+    mProfiling->ClearData();
+  }
+
+  return propertiesUpdated;
+}
+
+const GesturePropertyBool& PanGesture::GetPanningProperty() const
+{
+  return mPanning;
 }
 
 const GesturePropertyVector2& PanGesture::GetScreenPositionProperty() const
@@ -143,19 +407,41 @@ const GesturePropertyVector2& PanGesture::GetLocalDisplacementProperty() const
   return mLocalDisplacement;
 }
 
+void PanGesture::SetPredictionMode(PredictionMode mode)
+{
+  mPredictionMode = mode;
+}
+
+void PanGesture::SetPredictionAmount(float amount)
+{
+  mPredictionAmount = amount;
+}
+
+void PanGesture::EnableProfiling()
+{
+  if( !mProfiling )
+  {
+    mProfiling = new PanGestureProfiling();
+  }
+}
+
 void PanGesture::ResetDefaultProperties( BufferIndex updateBufferIndex )
 {
   mScreenPosition.Reset();
   mScreenDisplacement.Reset();
   mLocalPosition.Reset();
   mLocalDisplacement.Reset();
+  mPanning.Reset();
 }
 
 PanGesture::PanGesture()
 : mGestures(),
   mWritePosition( 0 ),
   mReadPosition( 0 ),
-  mInGesture( false )
+  mInGesture( false ),
+  mPredictionMode(DEFAULT_PREDICTION_MODE),
+  mPredictionAmount(DEFAULT_PREDICTION_INTERPOLATION),
+  mProfiling( NULL )
 {
 }