[problem] A bigger prediction interpolation makes the prediction result less usable.
[cause] The changing velocity of pan gesture could cause overshoot for the prediction
(and sometimes in the wrong direction), which makes the scrolling jerky.
[solution] Dynamically adpat the prediction interpolation to the change of pan velocity.
When overshoot is detected, alter any prediction in the wrong direction and
decrease the prediction interpolation so that it doesn't overshoot as easily
in the next frame.
Change-Id: I71afc4d601c865785a1629a2c186d45547e6117c
application.SendNotification();
application.Render();
- Vector2 direction(Vector2::XAXIS * -5.0f);
+ Vector2 direction(Vector2::XAXIS * -1.0f);
Vector2 previousPosition( 20.0f, 20.0f );
Vector2 currentPosition( 20.0f, 10.0f );
PerformSwipeGestureSwipe(application, Vector2(1.0f, 1.0f), direction, PAN_GESTURE_UPDATE_COUNT, true);
application.SendNotification();
application.Render();
- Vector2 direction(Vector2::XAXIS * -5.0f);
+ Vector2 direction(Vector2::XAXIS * -1.0f);
Vector2 previousPosition( 20.0f, 20.0f );
Vector2 currentPosition( 20.0f, 10.0f );
PerformSwipeGestureSwipe(application, Vector2(1.0f, 1.0f), direction, PAN_GESTURE_UPDATE_COUNT, true);
eventProcessor.SetPanGesturePredictionAmount(amount);
}
+void SetPanGestureMaximumPredictionAmount( unsigned int amount )
+{
+ GestureEventProcessor& eventProcessor = ThreadLocalStorage::Get().GetGestureEventProcessor();
+ eventProcessor.SetPanGestureMaximumPredictionAmount(amount);
+}
+
+void SetPanGestureMinimumPredictionAmount( unsigned int amount )
+{
+ GestureEventProcessor& eventProcessor = ThreadLocalStorage::Get().GetGestureEventProcessor();
+ eventProcessor.SetPanGestureMinimumPredictionAmount(amount);
+}
+
+void SetPanGesturePredictionAmountAdjustment( unsigned int amount )
+{
+ GestureEventProcessor& eventProcessor = ThreadLocalStorage::Get().GetGestureEventProcessor();
+ eventProcessor.SetPanGesturePredictionAmountAdjustment(amount);
+}
+
void SetPanGestureSmoothingMode(int mode)
{
GestureEventProcessor& eventProcessor = ThreadLocalStorage::Get().GetGestureEventProcessor();
DALI_IMPORT_API void SetPanGesturePredictionMode( int mode );
/**
- * @brief Called by adaptor to set the prediction amount of the pan gesture from an environment variable
+ * @brief Called by adaptor to set the prediction amount of the pan gesture
+ * from an environment variable
*
* @param[in] amount The prediction amount in milliseconds
*/
DALI_IMPORT_API void SetPanGesturePredictionAmount(unsigned int amount);
+/**
+ * @brief Sets the upper bound of the prediction amount for clamping
+ * from an environment variable
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+DALI_IMPORT_API void SetPanGestureMaximumPredictionAmount( unsigned int amount );
+
+/**
+ * @brief Sets the lower bound of the prediction amount for clamping
+ * from an environment variable
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+DALI_IMPORT_API void SetPanGestureMinimumPredictionAmount( unsigned int amount );
+
+/**
+ * @brief Sets the prediction amount to adjust when the pan velocity is changed
+ * from an environment variable. If the pan velocity is accelerating, the prediction
+ * amount will be increased by the specified amount until it reaches the upper bound.
+ * If the pan velocity is decelerating, the prediction amount will be decreased by
+ * the specified amount until it reaches the lower bound.
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+DALI_IMPORT_API void SetPanGesturePredictionAmountAdjustment( unsigned int amount );
+
/**
* @brief Called to set how pan gestures smooth input
*
DALI_IMPORT_API void SetPanGestureSmoothingMode( int mode );
/**
- * @brief Sets the prediction amount of the pan gesture
+ * @brief Sets the smoothing amount of the pan gesture
*
* @param[in] amount The smoothing amount [0.0f,1.0f] - 0.0f would be no smoothing, 1.0f maximum smoothing
*/
mPanGestureProcessor.SetPredictionAmount(amount);
}
+void GestureEventProcessor::SetPanGestureMaximumPredictionAmount( unsigned int amount )
+{
+ mPanGestureProcessor.SetMaximumPredictionAmount(amount);
+}
+
+void GestureEventProcessor::SetPanGestureMinimumPredictionAmount( unsigned int amount )
+{
+ mPanGestureProcessor.SetMinimumPredictionAmount(amount);
+}
+
+void GestureEventProcessor::SetPanGesturePredictionAmountAdjustment( unsigned int amount )
+{
+ mPanGestureProcessor.SetPredictionAmountAdjustment(amount);
+}
+
void GestureEventProcessor::SetPanGestureSmoothingMode(int mode)
{
mPanGestureProcessor.SetSmoothingMode(mode);
*/
void SetPanGesturePredictionAmount( unsigned int amount );
+ /**
+ * @brief Sets the upper bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetPanGestureMaximumPredictionAmount( unsigned int amount );
+
+ /**
+ * @brief Sets the lower bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetPanGestureMinimumPredictionAmount( unsigned int amount );
+
+ /**
+ * @brief Sets the prediction amount to adjust when the pan velocity is changed.
+ * If the pan velocity is accelerating, the prediction amount will be increased
+ * by the specified amount until it reaches the upper bound. If the pan velocity
+ * is decelerating, the prediction amount will be decreased by the specified amount
+ * until it reaches the lower bound.
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetPanGesturePredictionAmountAdjustment( unsigned int amount );
+
/**
* @brief Called to set how pan gestures smooth input
*
mSceneObject->SetPredictionAmount(amount);
}
+void PanGestureProcessor::SetMaximumPredictionAmount(unsigned int amount)
+{
+ mSceneObject->SetMaximumPredictionAmount(amount);
+}
+
+void PanGestureProcessor::SetMinimumPredictionAmount(unsigned int amount)
+{
+ mSceneObject->SetMinimumPredictionAmount(amount);
+}
+
+void PanGestureProcessor::SetPredictionAmountAdjustment(unsigned int amount)
+{
+ mSceneObject->SetPredictionAmountAdjustment(amount);
+}
+
void PanGestureProcessor::SetSmoothingMode(int mode)
{
if( (mode < 0)
*/
void SetPredictionAmount(unsigned int amount);
+ /**
+ * @brief Sets the upper bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetMaximumPredictionAmount(unsigned int amount);
+
+ /**
+ * @brief Sets the lower bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetMinimumPredictionAmount(unsigned int amount);
+
+ /**
+ * @brief Sets the amount of prediction interpolation to adjust when the pan velocity is changed
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetPredictionAmountAdjustment(unsigned int amount);
+
/**
* Called to set the prediction mode for pan gestures
*
const PanPositionContainer::const_iterator endIter = dataContainer.end();
for ( PanPositionContainer::const_iterator iter = dataContainer.begin(); iter != endIter; ++iter )
{
- DALI_LOG_UPDATE_STATUS( "%s, %u, %.2f, %.2f\n", prefix, iter->time, iter->position.x, iter->position.y );
+ DALI_LOG_UPDATE_STATUS( "%s, %u, %.2f, %.2f, displacement: %.2f, %.2f, velocity: %.2f, %.2f, state: %d\n", prefix, iter->time, iter->position.x, iter->position.y, iter->displacement.x, iter->displacement.y, iter->velocity.x, iter->velocity.y, iter->state );
}
}
{
struct Position
{
- Position( unsigned int time, Vector2 position )
- : time( time ), position( position )
+ Position( unsigned int time, Vector2 position, Vector2 displacement, Vector2 velocity, int state )
+ : time( time ), position( position ), displacement( displacement ), velocity( velocity ), state( state )
{
}
unsigned int time;
Vector2 position;
+ Vector2 displacement;
+ Vector2 velocity;
+ int state;
};
typedef std::vector< PanGestureProfiling::Position > PanPositionContainer;
#include <dali/internal/update/gestures/scene-graph-pan-gesture.h>
// EXTERNAL INCLUDES
+#include <cmath>
// INTERNAL INCLUDES
#include <dali/internal/update/gestures/pan-gesture-profiling.h>
namespace
{
const int MAX_GESTURE_AGE = 50; ///< maximum age of a gesture before disallowing its use in algorithm
-const unsigned int DEFAULT_PREDICTION_INTERPOLATION = 0; ///< how much to interpolate pan position and displacement from last vsync time
+const float ACCELERATION_THRESHOLD = 0.1f; ///< minimum pan velocity change to trigger dynamic change of prediction amount
+const unsigned int DEFAULT_PREDICTION_INTERPOLATION = 0; ///< how much to interpolate pan position and displacement from last vsync time (in milliseconds)
+const unsigned int DEFAULT_MAX_PREDICTION_INTERPOLATION = 32; ///< the upper bound of the range to clamp the prediction interpolation
+const unsigned int DEFAULT_MIN_PREDICTION_INTERPOLATION = 0; ///< the lower bound of the range to clamp the prediction interpolation
+const unsigned int DEFAULT_PREDICTION_INTERPOLATION_ADJUSTMENT = 4; ///< the amount of prediction interpolation to adjust (in milliseconds) each time when pan velocity changes
const float DEFAULT_SMOOTHING_AMOUNT = 1.0f; ///< how much to interpolate pan position and displacement from last vsync time
} // unnamed namespace
float previousAccel = 0.0f;
unsigned int lastTime(0);
- unsigned int interpolationTime = lastVSyncTime + mPredictionAmount;
- if( interpolationTime > gestureOut.time ) // Guard against the rare case when gestureOut.time > (lastVSyncTime + mPredictionAmount)
+ unsigned int interpolationTime = lastVSyncTime + mCurrentPredictionAmount;
+ if( interpolationTime > gestureOut.time ) // Guard against the rare case when gestureOut.time > (lastVSyncTime + mCurrentPredictionAmount)
{
interpolationTime -= gestureOut.time;
}
bool justFinished ( false );
bool eventFound( false );
+ float acceleration = 0.0f;
+
// Not going through array from the beginning, using it as a circular buffer and only using unread
// values.
int eventsThisFrame = 0;
mLastEventGesture = mEventGesture;
mLastGesture = mLatestGesture;
// add new gestures and work out one full gesture for the frame
+ unsigned int previousReadPosition = 0;
while(mReadPosition != mWritePosition)
{
// Copy the gesture first
if( mProfiling )
{
- mProfiling->mRawData.push_back( PanGestureProfiling::Position( currentGesture.time, currentGesture.screen.position ) );
+ mProfiling->mRawData.push_back( PanGestureProfiling::Position( currentGesture.time, currentGesture.screen.position, currentGesture.screen.displacement, currentGesture.screen.velocity, currentGesture.state ) );
}
mEventGesture.local.position = currentGesture.local.position;
mEventGesture.local.velocity = currentGesture.local.velocity;
mEventGesture.screen.position = currentGesture.screen.position;
mEventGesture.screen.velocity = currentGesture.screen.velocity;
+
+ if(eventsThisFrame > 0)
+ {
+ acceleration = currentGesture.screen.velocity.Length() - mGestures[previousReadPosition].screen.velocity.Length();
+ }
+
if( !eventFound )
{
mEventGesture.local.displacement = currentGesture.local.displacement;
justFinished |= (currentGesture.state == Gesture::Finished || currentGesture.state == Gesture::Cancelled);
// Update our read position.
+ previousReadPosition = mReadPosition;
++eventsThisFrame;
++mReadPosition;
mReadPosition %= PAN_GESTURE_HISTORY;
{
if( mProfiling )
{
- mProfiling->mLatestData.push_back( PanGestureProfiling::Position( lastVSyncTime, mEventGesture.screen.position ) );
+ mProfiling->mLatestData.push_back( PanGestureProfiling::Position( lastVSyncTime, mEventGesture.screen.position, mEventGesture.screen.displacement, mEventGesture.screen.velocity, mEventGesture.state ) );
}
switch( mPredictionMode )
}
case PREDICTION_1:
{
- // make latest gesture equal to current gesture before interpolation
+ // Dynamically change the prediction amount according to the pan velocity acceleration.
+ if(!justStarted)
+ {
+ if(eventsThisFrame <= 1)
+ {
+ acceleration = mEventGesture.screen.velocity.Length() - mLastEventGesture.screen.velocity.Length();
+ }
+
+ // Ignore tiny velocity fluctuation to avoid unnecessary prediction amount change
+ if(fabsf(acceleration) > ACCELERATION_THRESHOLD)
+ {
+ mCurrentPredictionAmount += mPredictionAmountAdjustment * (acceleration > Math::MACHINE_EPSILON_0 ? 1.0f : -1.0f);
+ if(mCurrentPredictionAmount > mMaxPredictionAmount + mPredictionAmountAdjustment) // Guard against unsigned int overflow
+ {
+ mCurrentPredictionAmount = 0;
+ }
+ }
+ }
+ else
+ {
+ mCurrentPredictionAmount = mPredictionAmount; // Reset the prediction amount for each new gesture
+ }
+
+ mCurrentPredictionAmount = std::max(mMinPredictionAmount, std::min(mCurrentPredictionAmount, mMaxPredictionAmount));
+
+ // Calculate the delta of positions before the prediction
+ Vector2 deltaPosition = mLatestGesture.screen.position - mLastEventGesture.screen.position;
+
+ // Make latest gesture equal to current gesture before interpolation
PredictiveAlgorithm1(eventsThisFrame, mLatestGesture, mPanHistory, lastVSyncTime, nextVSyncTime);
+
+ // Calculate the delta of positions after the prediction.
+ Vector2 deltaPredictedPosition = mLatestGesture.screen.position - mLastGesture.screen.position;
+
+ // If the change in the prediction has a different sign than the change in the actual position,
+ // there is overshot (i.e. the current prediction is too large). Return the previous prediction
+ // to give the user's finger a chance to catch up with where we have panned to.
+ bool overshotXAxis = false;
+ bool overshotYAxis = false;
+ if( (deltaPosition.x > Math::MACHINE_EPSILON_0 && deltaPredictedPosition.x < Math::MACHINE_EPSILON_0 )
+ || (deltaPosition.x < Math::MACHINE_EPSILON_0 && deltaPredictedPosition.x > Math::MACHINE_EPSILON_0 ) )
+ {
+ overshotXAxis = true;
+ mLatestGesture.screen.position.x = mLastGesture.screen.position.x;
+ }
+
+ if( (deltaPosition.y > Math::MACHINE_EPSILON_0 && deltaPredictedPosition.y < Math::MACHINE_EPSILON_0 )
+ || (deltaPosition.y < Math::MACHINE_EPSILON_0 && deltaPredictedPosition.y > Math::MACHINE_EPSILON_0 ) )
+ {
+ overshotYAxis = true;
+ mLatestGesture.screen.position.y = mLastGesture.screen.position.y;
+ }
+
+ // If there is overshot in one axis, reduce the possible overshot in the other axis,
+ // and reduce the prediction amount so that it doesn't overshoot as easily next time.
+ if(overshotXAxis || overshotYAxis)
+ {
+ mCurrentPredictionAmount -= mPredictionAmountAdjustment;
+ if(mCurrentPredictionAmount > mMaxPredictionAmount + mPredictionAmountAdjustment) // Guard against unsigned int overflow
+ {
+ mCurrentPredictionAmount = 0;
+ }
+ mCurrentPredictionAmount = std::max(mMinPredictionAmount, std::min(mCurrentPredictionAmount, mMaxPredictionAmount));
+
+ if(overshotXAxis && !overshotYAxis)
+ {
+ mLatestGesture.screen.position.y = (mLastGesture.screen.position.y + mLatestGesture.screen.position.y) * 0.5f;
+ }
+
+ if(overshotYAxis && !overshotXAxis)
+ {
+ mLatestGesture.screen.position.x = (mLastGesture.screen.position.x + mLatestGesture.screen.position.x) * 0.5f;
+ }
+ }
+
updateProperties = true;
break;
}
if( mProfiling )
{
- mProfiling->mAveragedData.push_back( PanGestureProfiling::Position( mLatestGesture.time, mLatestGesture.screen.position ) );
+ mProfiling->mAveragedData.push_back( PanGestureProfiling::Position( mLatestGesture.time, mLatestGesture.screen.position, mLatestGesture.screen.displacement, mLatestGesture.screen.velocity, mLatestGesture.state ) );
}
}
mPredictionAmount = amount;
}
+void PanGesture::SetMaximumPredictionAmount(unsigned int amount)
+{
+ mMaxPredictionAmount = amount;
+}
+
+void PanGesture::SetMinimumPredictionAmount(unsigned int amount)
+{
+ mMinPredictionAmount = amount;
+}
+
+void PanGesture::SetPredictionAmountAdjustment(unsigned int amount)
+{
+ mPredictionAmountAdjustment = amount;
+}
+
void PanGesture::SetSmoothingMode(SmoothingMode mode)
{
mSmoothingMode = mode;
mInGesture( false ),
mPredictionMode(DEFAULT_PREDICTION_MODE),
mPredictionAmount(DEFAULT_PREDICTION_INTERPOLATION),
+ mCurrentPredictionAmount(DEFAULT_PREDICTION_INTERPOLATION),
+ mMaxPredictionAmount(DEFAULT_MAX_PREDICTION_INTERPOLATION),
+ mMinPredictionAmount(DEFAULT_MIN_PREDICTION_INTERPOLATION),
+ mPredictionAmountAdjustment(DEFAULT_PREDICTION_INTERPOLATION_ADJUSTMENT),
mSmoothingMode(DEFAULT_SMOOTHING_MODE),
mSmoothingAmount(DEFAULT_SMOOTHING_AMOUNT),
mProfiling( NULL )
*/
void SetPredictionAmount(unsigned int amount);
+ /**
+ * @brief Sets the upper bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetMaximumPredictionAmount(unsigned int amount);
+
+ /**
+ * @brief Sets the lower bound of the prediction amount for clamping
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetMinimumPredictionAmount(unsigned int amount);
+
+ /**
+ * @brief Sets the amount of prediction interpolation to adjust when the pan velocity is changed
+ *
+ * @param[in] amount The prediction amount in milliseconds
+ */
+ void SetPredictionAmountAdjustment(unsigned int amount);
+
/**
* @brief Sets the prediction mode of the pan gesture
*
PredictionMode mPredictionMode; ///< The pan gesture prediction mode
unsigned int mPredictionAmount; ///< how far into future to predict in milliseconds
+ unsigned int mCurrentPredictionAmount; ///< the current prediction amount used by the prediction algorithm
+ unsigned int mMaxPredictionAmount; ///< the maximum prediction amount used by the prediction algorithm
+ unsigned int mMinPredictionAmount; ///< the minimum prediction amount used by the prediction algorithm
+ unsigned int mPredictionAmountAdjustment; ///< the prediction amount to adjust in milliseconds when pan velocity changes
SmoothingMode mSmoothingMode; ///< The pan gesture prediction mode
float mSmoothingAmount; ///< How much smoothing to apply [0.0f,1.0f]
PanGestureProfiling* mProfiling; ///< NULL unless pan-gesture profiling information is required.