Commit 989a4870 authored by Luis Sanchez Padilla's avatar Luis Sanchez Padilla Committed by Commit Bot

Add a directional cut off alternative to the kalman predictor to

prevent rubber banding.

Rubber banding, or spring effect, often happens when recent point data
evaluation generates a considerable deceleration producing velocity
direction opposite to the current direction. This could happen because
of noise in the input pipeline or a sudden stop by the input pointer.
In order to mitigate this side effect, the current velocity vector is
compared with the future velocity vector by computing the dot product
between these two. If the result of the product is zero or less, it
means that they have opposing directions and the prediction is cut off.

Change-Id: I8a863475021f8531a7f2946325d458cad6d1138c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1860329Reviewed-by: default avatarElla Ge <eirage@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Commit-Queue: Luis Sanchez Padilla <lusanpad@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#709979}
parent f1bb41bd
...@@ -1158,10 +1158,6 @@ const FeatureEntry::FeatureParam kResamplingInputEventsLSQEnabled[] = { ...@@ -1158,10 +1158,6 @@ const FeatureEntry::FeatureParam kResamplingInputEventsLSQEnabled[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameLsq}}; {"predictor", ui::input_prediction::kScrollPredictorNameLsq}};
const FeatureEntry::FeatureParam kResamplingInputEventsKalmanEnabled[] = { const FeatureEntry::FeatureParam kResamplingInputEventsKalmanEnabled[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameKalman}}; {"predictor", ui::input_prediction::kScrollPredictorNameKalman}};
const FeatureEntry::FeatureParam
kResamplingInputEventsKalmanHeuristicEnabled[] = {
{"predictor",
ui::input_prediction::kScrollPredictorNameKalmanHeuristic}};
const FeatureEntry::FeatureParam kResamplingInputEventsLinearFirstEnabled[] = { const FeatureEntry::FeatureParam kResamplingInputEventsLinearFirstEnabled[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameLinearFirst}}; {"predictor", ui::input_prediction::kScrollPredictorNameLinearFirst}};
const FeatureEntry::FeatureParam kResamplingInputEventsLinearSecondEnabled[] = { const FeatureEntry::FeatureParam kResamplingInputEventsLinearSecondEnabled[] = {
...@@ -1178,9 +1174,6 @@ const FeatureEntry::FeatureVariation kResamplingInputEventsFeatureVariations[] = ...@@ -1178,9 +1174,6 @@ const FeatureEntry::FeatureVariation kResamplingInputEventsFeatureVariations[] =
{ui::input_prediction::kScrollPredictorNameKalman, {ui::input_prediction::kScrollPredictorNameKalman,
kResamplingInputEventsKalmanEnabled, kResamplingInputEventsKalmanEnabled,
base::size(kResamplingInputEventsKalmanEnabled), nullptr}, base::size(kResamplingInputEventsKalmanEnabled), nullptr},
{ui::input_prediction::kScrollPredictorNameKalmanHeuristic,
kResamplingInputEventsKalmanHeuristicEnabled,
base::size(kResamplingInputEventsKalmanHeuristicEnabled), nullptr},
{ui::input_prediction::kScrollPredictorNameLinearFirst, {ui::input_prediction::kScrollPredictorNameLinearFirst,
kResamplingInputEventsLinearFirstEnabled, kResamplingInputEventsLinearFirstEnabled,
base::size(kResamplingInputEventsLinearFirstEnabled), nullptr}, base::size(kResamplingInputEventsLinearFirstEnabled), nullptr},
......
...@@ -12,6 +12,12 @@ const base::Feature kResamplingScrollEvents{"ResamplingScrollEvents", ...@@ -12,6 +12,12 @@ const base::Feature kResamplingScrollEvents{"ResamplingScrollEvents",
const base::Feature kFilteringScrollPrediction{ const base::Feature kFilteringScrollPrediction{
"FilteringScrollPrediction", base::FEATURE_DISABLED_BY_DEFAULT}; "FilteringScrollPrediction", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kKalmanHeuristics{"KalmanHeuristics",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kKalmanDirectionCutOff{"KalmanDirectionCutOff",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kSendMouseLeaveEvents{"SendMouseLeaveEvents", const base::Feature kSendMouseLeaveEvents{"SendMouseLeaveEvents",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -18,6 +18,15 @@ extern const base::Feature kResamplingScrollEvents; ...@@ -18,6 +18,15 @@ extern const base::Feature kResamplingScrollEvents;
COMPONENT_EXPORT(BLINK_FEATURES) COMPONENT_EXPORT(BLINK_FEATURES)
extern const base::Feature kFilteringScrollPrediction; extern const base::Feature kFilteringScrollPrediction;
// Enables changing the influence of acceleration based on change of direction.
COMPONENT_EXPORT(BLINK_FEATURES)
extern const base::Feature kKalmanHeuristics;
// Enables discarding the prediction if the predicted direction is opposite from
// the current direction.
COMPONENT_EXPORT(BLINK_FEATURES)
extern const base::Feature kKalmanDirectionCutOff;
// This feature allows native ET_MOUSE_EXIT events to be passed // This feature allows native ET_MOUSE_EXIT events to be passed
// through to blink as mouse leave events. Traditionally these events were // through to blink as mouse leave events. Traditionally these events were
// converted to mouse move events due to a number of inconsistencies on // converted to mouse move events due to a number of inconsistencies on
......
...@@ -28,8 +28,8 @@ constexpr base::TimeDelta InputPredictor::kTimeInterval; ...@@ -28,8 +28,8 @@ constexpr base::TimeDelta InputPredictor::kTimeInterval;
constexpr base::TimeDelta InputPredictor::kMinTimeInterval; constexpr base::TimeDelta InputPredictor::kMinTimeInterval;
constexpr base::TimeDelta KalmanPredictor::kMaxTimeInQueue; constexpr base::TimeDelta KalmanPredictor::kMaxTimeInQueue;
KalmanPredictor::KalmanPredictor(HeuristicsMode heuristics_mode) KalmanPredictor::KalmanPredictor(unsigned int prediction_options)
: heuristics_mode_(heuristics_mode) {} : prediction_options_(prediction_options) {}
KalmanPredictor::~KalmanPredictor() = default; KalmanPredictor::~KalmanPredictor() = default;
...@@ -84,9 +84,16 @@ std::unique_ptr<InputPredictor::InputData> KalmanPredictor::GeneratePrediction( ...@@ -84,9 +84,16 @@ std::unique_ptr<InputPredictor::InputData> KalmanPredictor::GeneratePrediction(
gfx::Vector2dF velocity = PredictVelocity(); gfx::Vector2dF velocity = PredictVelocity();
gfx::Vector2dF acceleration = PredictAcceleration(); gfx::Vector2dF acceleration = PredictAcceleration();
if (prediction_options_ & kDirectionCutOffEnabled) {
gfx::Vector2dF future_velocity =
velocity + ScaleVector2d(acceleration, pred_dt);
if (gfx::DotProduct(velocity, future_velocity) <= 0)
return nullptr;
}
position += ScaleVector2d(velocity, kVelocityInfluence * pred_dt); position += ScaleVector2d(velocity, kVelocityInfluence * pred_dt);
if (heuristics_mode_ == HeuristicsMode::kHeuristicsEnabled) { if (prediction_options_ & kHeuristicsEnabled) {
float points_angle = 0.0f; float points_angle = 0.0f;
for (size_t i = 2; i < last_points_.size(); i++) { for (size_t i = 2; i < last_points_.size(); i++) {
gfx::Vector2dF first_dir = gfx::Vector2dF first_dir =
......
...@@ -20,9 +20,16 @@ namespace ui { ...@@ -20,9 +20,16 @@ namespace ui {
// be used to predict one dimension (x, y). // be used to predict one dimension (x, y).
class KalmanPredictor : public InputPredictor { class KalmanPredictor : public InputPredictor {
public: public:
enum class HeuristicsMode { kHeuristicsDisabled, kHeuristicsEnabled }; // Heuristic option enables changing the influence of acceleration based on
// change of direction. Direction cut off enables discarding the prediction if
explicit KalmanPredictor(HeuristicsMode heuristics_mode); // the predicted direction is opposite from the current direction.
enum PredictionOptions {
kNone = 0x0,
kHeuristicsEnabled = 0x1,
kDirectionCutOffEnabled = 0x2
};
explicit KalmanPredictor(unsigned int prediction_options);
~KalmanPredictor() override; ~KalmanPredictor() override;
const char* GetName() const override; const char* GetName() const override;
...@@ -64,9 +71,8 @@ class KalmanPredictor : public InputPredictor { ...@@ -64,9 +71,8 @@ class KalmanPredictor : public InputPredictor {
static constexpr base::TimeDelta kMaxTimeInQueue = static constexpr base::TimeDelta kMaxTimeInQueue =
base::TimeDelta::FromMilliseconds(40); base::TimeDelta::FromMilliseconds(40);
// Flag to determine heuristic behavior based on the accumulated angle between // Flags to determine the enabled prediction options.
// the last set of points. const unsigned int prediction_options_;
const HeuristicsMode heuristics_mode_;
DISALLOW_COPY_AND_ASSIGN(KalmanPredictor); DISALLOW_COPY_AND_ASSIGN(KalmanPredictor);
}; };
......
...@@ -45,7 +45,7 @@ class KalmanPredictorTest : public InputPredictorTest { ...@@ -45,7 +45,7 @@ class KalmanPredictorTest : public InputPredictorTest {
void SetUp() override { void SetUp() override {
predictor_ = std::make_unique<ui::KalmanPredictor>( predictor_ = std::make_unique<ui::KalmanPredictor>(
ui::KalmanPredictor::HeuristicsMode::kHeuristicsDisabled); ui::KalmanPredictor::PredictionOptions::kNone);
} }
DISALLOW_COPY_AND_ASSIGN(KalmanPredictorTest); DISALLOW_COPY_AND_ASSIGN(KalmanPredictorTest);
...@@ -149,7 +149,7 @@ TEST_F(KalmanPredictorTest, TimeInterval) { ...@@ -149,7 +149,7 @@ TEST_F(KalmanPredictorTest, TimeInterval) {
TEST_F(KalmanPredictorTest, HeuristicApproach) { TEST_F(KalmanPredictorTest, HeuristicApproach) {
std::unique_ptr<InputPredictor> heuristic_predictor = std::unique_ptr<InputPredictor> heuristic_predictor =
std::make_unique<ui::KalmanPredictor>( std::make_unique<ui::KalmanPredictor>(
ui::KalmanPredictor::HeuristicsMode::kHeuristicsEnabled); ui::KalmanPredictor::PredictionOptions::kHeuristicsEnabled);
std::vector<double> x_stabilizer = {-40, -32, -24, -16, -8, 0}; std::vector<double> x_stabilizer = {-40, -32, -24, -16, -8, 0};
std::vector<double> y_stabilizer = {-40, -32, -24, -16, -8, 0}; std::vector<double> y_stabilizer = {-40, -32, -24, -16, -8, 0};
std::vector<double> t_stabilizer = {-40, -32, -24, -16, -8, 0}; std::vector<double> t_stabilizer = {-40, -32, -24, -16, -8, 0};
...@@ -181,5 +181,24 @@ TEST_F(KalmanPredictorTest, HeuristicApproach) { ...@@ -181,5 +181,24 @@ TEST_F(KalmanPredictorTest, HeuristicApproach) {
} }
} }
// Test the kalman predictor prevention of rubber-banding.
TEST_F(KalmanPredictorTest, DirectionalCutOff) {
predictor_ = std::make_unique<ui::KalmanPredictor>(
ui::KalmanPredictor::PredictionOptions::kDirectionCutOffEnabled);
std::vector<double> x = {98, 72, 50, 32, 18, 8, 2};
std::vector<double> y = {49, 36, 25, 16, 9, 4, 1};
std::vector<double> t = {8, 16, 24, 32, 40, 48, 56};
for (size_t i = 0; i < t.size(); i++) {
InputPredictor::InputData data = {gfx::PointF(x[i], y[i]),
FromMilliseconds(t[i])};
predictor_->Update(data);
}
// On t=64, position is (0,0), and in t=72 it is (2,1) again which means that
// direction has shifted in the opposite direction and prediction should be
// cut off.
auto result = predictor_->GeneratePrediction(FromMilliseconds(72));
EXPECT_FALSE(result);
}
} // namespace test } // namespace test
} // namespace ui } // namespace ui
...@@ -16,7 +16,6 @@ namespace input_prediction { ...@@ -16,7 +16,6 @@ namespace input_prediction {
const char kScrollPredictorNameLsq[] = "lsq"; const char kScrollPredictorNameLsq[] = "lsq";
const char kScrollPredictorNameKalman[] = "kalman"; const char kScrollPredictorNameKalman[] = "kalman";
const char kScrollPredictorNameKalmanHeuristic[] = "kalman_heuristic";
const char kScrollPredictorNameLinearFirst[] = "linear_first"; const char kScrollPredictorNameLinearFirst[] = "linear_first";
const char kScrollPredictorNameLinearSecond[] = "linear_second"; const char kScrollPredictorNameLinearSecond[] = "linear_second";
const char kScrollPredictorNameLinearResampling[] = "linear_resampling"; const char kScrollPredictorNameLinearResampling[] = "linear_resampling";
...@@ -28,6 +27,9 @@ namespace { ...@@ -28,6 +27,9 @@ namespace {
using input_prediction::PredictorType; using input_prediction::PredictorType;
} }
// Set to UINT_MAX to trigger querying feature flags.
unsigned int PredictorFactory::predictor_options_ = UINT_MAX;
PredictorType PredictorFactory::GetPredictorTypeFromName( PredictorType PredictorFactory::GetPredictorTypeFromName(
const std::string& predictor_name) { const std::string& predictor_name) {
if (predictor_name == input_prediction::kScrollPredictorNameLinearResampling) if (predictor_name == input_prediction::kScrollPredictorNameLinearResampling)
...@@ -36,9 +38,6 @@ PredictorType PredictorFactory::GetPredictorTypeFromName( ...@@ -36,9 +38,6 @@ PredictorType PredictorFactory::GetPredictorTypeFromName(
return PredictorType::kScrollPredictorTypeLsq; return PredictorType::kScrollPredictorTypeLsq;
else if (predictor_name == input_prediction::kScrollPredictorNameKalman) else if (predictor_name == input_prediction::kScrollPredictorNameKalman)
return PredictorType::kScrollPredictorTypeKalman; return PredictorType::kScrollPredictorTypeKalman;
else if (predictor_name ==
input_prediction::kScrollPredictorNameKalmanHeuristic)
return PredictorType::kScrollPredictorTypeKalmanHeuristic;
else if (predictor_name == input_prediction::kScrollPredictorNameLinearFirst) else if (predictor_name == input_prediction::kScrollPredictorNameLinearFirst)
return PredictorType::kScrollPredictorTypeLinearFirst; return PredictorType::kScrollPredictorTypeLinearFirst;
else if (predictor_name == input_prediction::kScrollPredictorNameLinearSecond) else if (predictor_name == input_prediction::kScrollPredictorNameLinearSecond)
...@@ -54,11 +53,7 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor( ...@@ -54,11 +53,7 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor(
else if (predictor_type == PredictorType::kScrollPredictorTypeLsq) else if (predictor_type == PredictorType::kScrollPredictorTypeLsq)
return std::make_unique<LeastSquaresPredictor>(); return std::make_unique<LeastSquaresPredictor>();
else if (predictor_type == PredictorType::kScrollPredictorTypeKalman) else if (predictor_type == PredictorType::kScrollPredictorTypeKalman)
return std::make_unique<KalmanPredictor>( return std::make_unique<KalmanPredictor>(GetKalmanPredictorOptions());
KalmanPredictor::HeuristicsMode::kHeuristicsDisabled);
else if (predictor_type == PredictorType::kScrollPredictorTypeKalmanHeuristic)
return std::make_unique<KalmanPredictor>(
KalmanPredictor::HeuristicsMode::kHeuristicsEnabled);
else if (predictor_type == PredictorType::kScrollPredictorTypeLinearFirst) else if (predictor_type == PredictorType::kScrollPredictorTypeLinearFirst)
return std::make_unique<LinearPredictor>( return std::make_unique<LinearPredictor>(
LinearPredictor::EquationOrder::kFirstOrder); LinearPredictor::EquationOrder::kFirstOrder);
...@@ -69,4 +64,17 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor( ...@@ -69,4 +64,17 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor(
return std::make_unique<EmptyPredictor>(); return std::make_unique<EmptyPredictor>();
} }
unsigned int PredictorFactory::GetKalmanPredictorOptions() {
if (predictor_options_ == UINT_MAX) {
predictor_options_ =
(base::FeatureList::IsEnabled(features::kKalmanHeuristics)
? KalmanPredictor::PredictionOptions::kHeuristicsEnabled
: 0) |
(base::FeatureList::IsEnabled(features::kKalmanDirectionCutOff)
? KalmanPredictor::PredictionOptions::kDirectionCutOffEnabled
: 0);
}
return predictor_options_;
}
} // namespace ui } // namespace ui
...@@ -13,7 +13,6 @@ namespace input_prediction { ...@@ -13,7 +13,6 @@ namespace input_prediction {
extern const char kScrollPredictorNameLsq[]; extern const char kScrollPredictorNameLsq[];
extern const char kScrollPredictorNameKalman[]; extern const char kScrollPredictorNameKalman[];
extern const char kScrollPredictorNameKalmanHeuristic[];
extern const char kScrollPredictorNameLinearFirst[]; extern const char kScrollPredictorNameLinearFirst[];
extern const char kScrollPredictorNameLinearSecond[]; extern const char kScrollPredictorNameLinearSecond[];
extern const char kScrollPredictorNameLinearResampling[]; extern const char kScrollPredictorNameLinearResampling[];
...@@ -22,7 +21,6 @@ extern const char kScrollPredictorNameEmpty[]; ...@@ -22,7 +21,6 @@ extern const char kScrollPredictorNameEmpty[];
enum class PredictorType { enum class PredictorType {
kScrollPredictorTypeLsq, kScrollPredictorTypeLsq,
kScrollPredictorTypeKalman, kScrollPredictorTypeKalman,
kScrollPredictorTypeKalmanHeuristic,
kScrollPredictorTypeLinearFirst, kScrollPredictorTypeLinearFirst,
kScrollPredictorTypeLinearSecond, kScrollPredictorTypeLinearSecond,
kScrollPredictorTypeLinearResampling, kScrollPredictorTypeLinearResampling,
...@@ -42,6 +40,12 @@ class PredictorFactory { ...@@ -42,6 +40,12 @@ class PredictorFactory {
static std::unique_ptr<InputPredictor> GetPredictor( static std::unique_ptr<InputPredictor> GetPredictor(
input_prediction::PredictorType predictor_type); input_prediction::PredictorType predictor_type);
// Returns the feature enabled kalman predictor options
static unsigned int GetKalmanPredictorOptions();
// Predictor options cache
static unsigned int predictor_options_;
private: private:
PredictorFactory() = delete; PredictorFactory() = delete;
~PredictorFactory() = delete; ~PredictorFactory() = delete;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment