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[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameLsq}};
const FeatureEntry::FeatureParam kResamplingInputEventsKalmanEnabled[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameKalman}};
const FeatureEntry::FeatureParam
kResamplingInputEventsKalmanHeuristicEnabled[] = {
{"predictor",
ui::input_prediction::kScrollPredictorNameKalmanHeuristic}};
const FeatureEntry::FeatureParam kResamplingInputEventsLinearFirstEnabled[] = {
{"predictor", ui::input_prediction::kScrollPredictorNameLinearFirst}};
const FeatureEntry::FeatureParam kResamplingInputEventsLinearSecondEnabled[] = {
......@@ -1178,9 +1174,6 @@ const FeatureEntry::FeatureVariation kResamplingInputEventsFeatureVariations[] =
{ui::input_prediction::kScrollPredictorNameKalman,
kResamplingInputEventsKalmanEnabled,
base::size(kResamplingInputEventsKalmanEnabled), nullptr},
{ui::input_prediction::kScrollPredictorNameKalmanHeuristic,
kResamplingInputEventsKalmanHeuristicEnabled,
base::size(kResamplingInputEventsKalmanHeuristicEnabled), nullptr},
{ui::input_prediction::kScrollPredictorNameLinearFirst,
kResamplingInputEventsLinearFirstEnabled,
base::size(kResamplingInputEventsLinearFirstEnabled), nullptr},
......
......@@ -12,6 +12,12 @@ const base::Feature kResamplingScrollEvents{"ResamplingScrollEvents",
const base::Feature kFilteringScrollPrediction{
"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",
base::FEATURE_ENABLED_BY_DEFAULT};
......
......@@ -18,6 +18,15 @@ extern const base::Feature kResamplingScrollEvents;
COMPONENT_EXPORT(BLINK_FEATURES)
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
// through to blink as mouse leave events. Traditionally these events were
// converted to mouse move events due to a number of inconsistencies on
......
......@@ -28,8 +28,8 @@ constexpr base::TimeDelta InputPredictor::kTimeInterval;
constexpr base::TimeDelta InputPredictor::kMinTimeInterval;
constexpr base::TimeDelta KalmanPredictor::kMaxTimeInQueue;
KalmanPredictor::KalmanPredictor(HeuristicsMode heuristics_mode)
: heuristics_mode_(heuristics_mode) {}
KalmanPredictor::KalmanPredictor(unsigned int prediction_options)
: prediction_options_(prediction_options) {}
KalmanPredictor::~KalmanPredictor() = default;
......@@ -84,9 +84,16 @@ std::unique_ptr<InputPredictor::InputData> KalmanPredictor::GeneratePrediction(
gfx::Vector2dF velocity = PredictVelocity();
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);
if (heuristics_mode_ == HeuristicsMode::kHeuristicsEnabled) {
if (prediction_options_ & kHeuristicsEnabled) {
float points_angle = 0.0f;
for (size_t i = 2; i < last_points_.size(); i++) {
gfx::Vector2dF first_dir =
......
......@@ -20,9 +20,16 @@ namespace ui {
// be used to predict one dimension (x, y).
class KalmanPredictor : public InputPredictor {
public:
enum class HeuristicsMode { kHeuristicsDisabled, kHeuristicsEnabled };
explicit KalmanPredictor(HeuristicsMode heuristics_mode);
// Heuristic option enables changing the influence of acceleration based on
// change of direction. Direction cut off enables discarding the prediction if
// 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;
const char* GetName() const override;
......@@ -64,9 +71,8 @@ class KalmanPredictor : public InputPredictor {
static constexpr base::TimeDelta kMaxTimeInQueue =
base::TimeDelta::FromMilliseconds(40);
// Flag to determine heuristic behavior based on the accumulated angle between
// the last set of points.
const HeuristicsMode heuristics_mode_;
// Flags to determine the enabled prediction options.
const unsigned int prediction_options_;
DISALLOW_COPY_AND_ASSIGN(KalmanPredictor);
};
......
......@@ -45,7 +45,7 @@ class KalmanPredictorTest : public InputPredictorTest {
void SetUp() override {
predictor_ = std::make_unique<ui::KalmanPredictor>(
ui::KalmanPredictor::HeuristicsMode::kHeuristicsDisabled);
ui::KalmanPredictor::PredictionOptions::kNone);
}
DISALLOW_COPY_AND_ASSIGN(KalmanPredictorTest);
......@@ -149,7 +149,7 @@ TEST_F(KalmanPredictorTest, TimeInterval) {
TEST_F(KalmanPredictorTest, HeuristicApproach) {
std::unique_ptr<InputPredictor> heuristic_predictor =
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> y_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) {
}
}
// 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 ui
......@@ -16,7 +16,6 @@ namespace input_prediction {
const char kScrollPredictorNameLsq[] = "lsq";
const char kScrollPredictorNameKalman[] = "kalman";
const char kScrollPredictorNameKalmanHeuristic[] = "kalman_heuristic";
const char kScrollPredictorNameLinearFirst[] = "linear_first";
const char kScrollPredictorNameLinearSecond[] = "linear_second";
const char kScrollPredictorNameLinearResampling[] = "linear_resampling";
......@@ -28,6 +27,9 @@ namespace {
using input_prediction::PredictorType;
}
// Set to UINT_MAX to trigger querying feature flags.
unsigned int PredictorFactory::predictor_options_ = UINT_MAX;
PredictorType PredictorFactory::GetPredictorTypeFromName(
const std::string& predictor_name) {
if (predictor_name == input_prediction::kScrollPredictorNameLinearResampling)
......@@ -36,9 +38,6 @@ PredictorType PredictorFactory::GetPredictorTypeFromName(
return PredictorType::kScrollPredictorTypeLsq;
else if (predictor_name == input_prediction::kScrollPredictorNameKalman)
return PredictorType::kScrollPredictorTypeKalman;
else if (predictor_name ==
input_prediction::kScrollPredictorNameKalmanHeuristic)
return PredictorType::kScrollPredictorTypeKalmanHeuristic;
else if (predictor_name == input_prediction::kScrollPredictorNameLinearFirst)
return PredictorType::kScrollPredictorTypeLinearFirst;
else if (predictor_name == input_prediction::kScrollPredictorNameLinearSecond)
......@@ -54,11 +53,7 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor(
else if (predictor_type == PredictorType::kScrollPredictorTypeLsq)
return std::make_unique<LeastSquaresPredictor>();
else if (predictor_type == PredictorType::kScrollPredictorTypeKalman)
return std::make_unique<KalmanPredictor>(
KalmanPredictor::HeuristicsMode::kHeuristicsDisabled);
else if (predictor_type == PredictorType::kScrollPredictorTypeKalmanHeuristic)
return std::make_unique<KalmanPredictor>(
KalmanPredictor::HeuristicsMode::kHeuristicsEnabled);
return std::make_unique<KalmanPredictor>(GetKalmanPredictorOptions());
else if (predictor_type == PredictorType::kScrollPredictorTypeLinearFirst)
return std::make_unique<LinearPredictor>(
LinearPredictor::EquationOrder::kFirstOrder);
......@@ -69,4 +64,17 @@ std::unique_ptr<InputPredictor> PredictorFactory::GetPredictor(
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
......@@ -13,7 +13,6 @@ namespace input_prediction {
extern const char kScrollPredictorNameLsq[];
extern const char kScrollPredictorNameKalman[];
extern const char kScrollPredictorNameKalmanHeuristic[];
extern const char kScrollPredictorNameLinearFirst[];
extern const char kScrollPredictorNameLinearSecond[];
extern const char kScrollPredictorNameLinearResampling[];
......@@ -22,7 +21,6 @@ extern const char kScrollPredictorNameEmpty[];
enum class PredictorType {
kScrollPredictorTypeLsq,
kScrollPredictorTypeKalman,
kScrollPredictorTypeKalmanHeuristic,
kScrollPredictorTypeLinearFirst,
kScrollPredictorTypeLinearSecond,
kScrollPredictorTypeLinearResampling,
......@@ -42,6 +40,12 @@ class PredictorFactory {
static std::unique_ptr<InputPredictor> GetPredictor(
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:
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