Commit ebf99704 authored by Axel Antoine's avatar Axel Antoine Committed by Commit Bot

Add a linear predictor

The linear predictor can be based on a first order (Using only velocity) or
second order (Using velocity and acceleration) equation to compute
the prediction.

1st order:
pred_pos = cur_pos + cur_vel / dt * pred_dt

2nd order:
pred_pos = cur_pos + cur_vel / dt * pred_dt
                   + 0.5 * (cur_vel - last_vel)/ dt * pred_dt * pred_dt

Bug: 977693
Change-Id: I4434044e9a2e0e89550d09e9a7f64302403244c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1672188
Commit-Queue: Axel Antoine <axantoine@google.com>
Reviewed-by: default avatarNavid Zolghadr <nzolghadr@chromium.org>
Reviewed-by: default avatarElla Ge <eirage@chromium.org>
Cr-Commit-Position: refs/heads/master@{#672552}
parent 08280f72
......@@ -508,6 +508,7 @@ if (!is_ios) {
"blink/prediction/input_predictor_unittest_helpers.h",
"blink/prediction/kalman_predictor_unittest.cc",
"blink/prediction/least_squares_predictor_unittest.cc",
"blink/prediction/linear_predictor_unittest.cc",
"blink/scroll_predictor_unittest.cc",
"blink/web_input_event_traits_unittest.cc",
"blink/web_input_event_unittest.cc",
......
......@@ -46,6 +46,8 @@ jumbo_source_set("blink") {
"prediction/kalman_predictor.h",
"prediction/least_squares_predictor.cc",
"prediction/least_squares_predictor.h",
"prediction/linear_predictor.cc",
"prediction/linear_predictor.h",
"scroll_predictor.cc",
"scroll_predictor.h",
"synchronous_input_handler_proxy.h",
......
......@@ -29,4 +29,40 @@ void InputPredictorTest::ValidatePredictor(
}
}
void InputPredictorTest::ValidatePredictor(
const std::vector<double>& events_x,
const std::vector<double>& events_y,
const std::vector<double>& events_ts_ms,
const std::vector<double>& prediction_ts_ms,
const std::vector<double>& predicted_x,
const std::vector<double>& predicted_y) {
predictor_->Reset();
std::vector<double> computed_x;
std::vector<double> computed_y;
size_t current_prediction_ts = 0;
for (size_t i = 0; i < events_ts_ms.size(); i++) {
InputPredictor::InputData data = {gfx::PointF(events_x[i], events_y[i]),
FromMilliseconds(events_ts_ms[i])};
predictor_->Update(data);
if (predictor_->HasPrediction()) {
InputPredictor::InputData result;
EXPECT_TRUE(predictor_->GeneratePrediction(
FromMilliseconds(prediction_ts_ms[current_prediction_ts]),
false /* is_resampling */, &result));
computed_x.push_back(result.pos.x());
computed_y.push_back(result.pos.y());
current_prediction_ts++;
}
}
EXPECT_TRUE(computed_x.size() == predicted_x.size());
if (computed_x.size() == predicted_x.size()) {
for (size_t i = 0; i < predicted_x.size(); i++) {
EXPECT_NEAR(computed_x[i], predicted_x[i], kEpsilon);
EXPECT_NEAR(computed_y[i], predicted_y[i], kEpsilon);
}
}
}
} // namespace ui
......@@ -27,6 +27,13 @@ class InputPredictorTest : public testing::Test {
const std::vector<double>& y,
const std::vector<double>& timestamp_ms);
void ValidatePredictor(const std::vector<double>& events_x,
const std::vector<double>& events_y,
const std::vector<double>& events_ts_ms,
const std::vector<double>& prediction_ts_ms,
const std::vector<double>& predicted_x,
const std::vector<double>& predicted_y);
protected:
static constexpr double kEpsilon = 0.1;
......
......@@ -4,8 +4,6 @@
#include "ui/events/blink/prediction/least_squares_predictor.h"
#include "base/trace_event/trace_event.h"
namespace ui {
namespace {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/blink/prediction/linear_predictor.h"
namespace ui {
LinearPredictor::LinearPredictor(EquationOrder order) {
equation_order_ = order;
}
LinearPredictor::~LinearPredictor() {}
const char* LinearPredictor::GetName() const {
if (equation_order_ == EquationOrder::kFirstOrder)
return "LinearFirst";
else
return "LinearSecond";
}
void LinearPredictor::Reset() {
events_queue_.clear();
}
size_t LinearPredictor::NumberOfEventsNeeded() {
return static_cast<size_t>(equation_order_);
}
void LinearPredictor::Update(const InputData& new_input) {
// The last input received is at least kMaxDeltaTime away, we consider it
// is a new trajectory
if (!events_queue_.empty() &&
new_input.time_stamp - events_queue_.back().time_stamp > kMaxTimeDelta) {
Reset();
}
// Queue the new event and keep only the number of last events needed
events_queue_.push_back(new_input);
if (events_queue_.size() > static_cast<size_t>(equation_order_)) {
events_queue_.pop_front();
}
// Compute the current velocity
if (events_queue_.size() >= static_cast<size_t>(EquationOrder::kFirstOrder)) {
// Even if cur_velocity is empty the first time, last_velocity is only
// used when 3 events are in the queue, so it will be initialized
last_velocity_.set_x(cur_velocity_.x());
last_velocity_.set_y(cur_velocity_.y());
// We have at least 2 events to compute the current velocity
// Get delta time between the last 2 events
// Note: this delta time is also used to compute the acceleration term
events_dt_ = (events_queue_.at(events_queue_.size() - 1).time_stamp -
events_queue_.at(events_queue_.size() - 2).time_stamp)
.InMillisecondsF();
// Get delta pos between the last 2 events
gfx::Vector2dF delta_pos = events_queue_.at(events_queue_.size() - 1).pos -
events_queue_.at(events_queue_.size() - 2).pos;
// Get the velocity
cur_velocity_.set_x(ScaleVector2d(delta_pos, 1.0 / events_dt_).x());
cur_velocity_.set_y(ScaleVector2d(delta_pos, 1.0 / events_dt_).y());
}
}
bool LinearPredictor::HasPrediction() const {
// Even if the current equation is second order, we still can predict a
// first order
return events_queue_.size() >=
static_cast<size_t>(EquationOrder::kFirstOrder);
}
bool LinearPredictor::GeneratePrediction(base::TimeTicks predict_time,
bool is_resampling,
InputData* result) const {
if (!HasPrediction())
return false;
float pred_dt =
(predict_time - events_queue_.back().time_stamp).InMillisecondsF();
// For resampling, we don't want to predict too far away,
// We then take the maximum of prediction time
if (is_resampling)
pred_dt = std::fmax(pred_dt, kMaxResampleTime.InMillisecondsF());
// Compute the prediction
// Note : a first order prediction is computed when only 2 events are
// available in the second order predictor
GeneratePredictionFirstOrder(pred_dt, result);
if (equation_order_ == EquationOrder::kSecondOrder &&
events_queue_.size() == static_cast<size_t>(EquationOrder::kSecondOrder))
// Add the acceleration term to the current result
GeneratePredictionSecondOrder(pred_dt, result);
return true;
}
void LinearPredictor::GeneratePredictionFirstOrder(float pred_dt,
InputData* result) const {
result->pos =
events_queue_.back().pos + ScaleVector2d(cur_velocity_, pred_dt);
}
void LinearPredictor::GeneratePredictionSecondOrder(float pred_dt,
InputData* result) const {
DCHECK(equation_order_ == EquationOrder::kSecondOrder);
// Compute the acceleration between the last two velocities
gfx::Vector2dF acceleration =
ScaleVector2d(cur_velocity_ - last_velocity_, 1.0 / events_dt_);
// Update the prediction
result->pos =
result->pos + ScaleVector2d(acceleration, 0.5 * pred_dt * pred_dt);
}
} // namespace ui
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_EVENTS_BLINK_PREDICTION_LINEAR_PREDICTOR_H_
#define UI_EVENTS_BLINK_PREDICTION_LINEAR_PREDICTOR_H_
#include <deque>
#include "ui/events/blink/prediction/input_predictor.h"
namespace ui {
// This class use a linear model for prediction
// You can choose between a first order equation:
// pred_p = last_p + velocity*pred_time
// and a second order equation:
// pred_p = last_p + velocity*pred_dt + 0.5*acceleration*pred_dt^2
class LinearPredictor : public InputPredictor {
public:
// Used to dissociate the order of the equation used but also used to
// define the number of events needed by each model
enum class EquationOrder : size_t { kFirstOrder = 2, kSecondOrder = 3 };
explicit LinearPredictor(EquationOrder order);
~LinearPredictor() override;
const char* GetName() const override;
// Reset the predictor to initial state.
void Reset() override;
// Store current input in queue.
void Update(const InputData& new_input) override;
// Return if there is enough data in the queue to generate prediction.
bool HasPrediction() const override;
// Generate the prediction based on stored points and given time_stamp.
// Return false if no prediction available.
bool GeneratePrediction(base::TimeTicks predict_time,
bool is_resampling,
InputData* result) const override;
// Return the number of events needed to compute a prediction
size_t NumberOfEventsNeeded();
private:
// Add the velocity term to the current prediction
void GeneratePredictionFirstOrder(float pred_dt, InputData* result) const;
// Add the acceleration term to the current prediction
void GeneratePredictionSecondOrder(float pred_dt, InputData* result) const;
// Store the last events received
std::deque<InputData> events_queue_;
// Store the equation order of the predictor
// The enum value also represents the number of events needed to compute the
// prediction
EquationOrder equation_order_;
// Store the current velocity of the 2 last events
gfx::Vector2dF cur_velocity_;
// Store the last velocity of the 2 last past events
gfx::Vector2dF last_velocity_;
// Store the current delta time between the last 2 events
float events_dt_;
DISALLOW_COPY_AND_ASSIGN(LinearPredictor);
};
} // namespace ui
#endif // UI_EVENTS_BLINK_PREDICTION_LINEAR_PREDICTOR_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/blink/prediction/linear_predictor.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/blink/prediction/input_predictor_unittest_helpers.h"
namespace ui {
namespace test {
class LinearPredictorFirstOrderTest : public InputPredictorTest {
public:
explicit LinearPredictorFirstOrderTest() {}
void SetUp() override {
predictor_ = std::make_unique<ui::LinearPredictor>(
LinearPredictor::EquationOrder::kFirstOrder);
}
DISALLOW_COPY_AND_ASSIGN(LinearPredictorFirstOrderTest);
};
class LinearPredictorSecondOrderTest : public InputPredictorTest {
public:
explicit LinearPredictorSecondOrderTest() {}
void SetUp() override {
predictor_ = std::make_unique<ui::LinearPredictor>(
LinearPredictor::EquationOrder::kSecondOrder);
}
DISALLOW_COPY_AND_ASSIGN(LinearPredictorSecondOrderTest);
};
// Test if the output name of the predictor is taking account of the
// equation order
TEST_F(LinearPredictorFirstOrderTest, GetName) {
ASSERT_STREQ(predictor_->GetName(), "LinearFirst");
}
// Test if the output name of the predictor is taking account of the
// equation order
TEST_F(LinearPredictorSecondOrderTest, GetName) {
ASSERT_STREQ(predictor_->GetName(), "LinearSecond");
}
// Test that the number of events required to compute a prediction is correct
TEST_F(LinearPredictorFirstOrderTest, ShouldHavePrediction) {
LinearPredictor predictor(LinearPredictor::EquationOrder::kFirstOrder);
size_t n = static_cast<size_t>(LinearPredictor::EquationOrder::kFirstOrder);
for (size_t i = 0; i < n; i++) {
EXPECT_FALSE(predictor.HasPrediction());
predictor.Update(InputPredictor::InputData());
}
EXPECT_TRUE(predictor.HasPrediction());
predictor.Reset();
EXPECT_FALSE(predictor.HasPrediction());
}
// Test that the number of events required to compute a prediction is correct
TEST_F(LinearPredictorSecondOrderTest, ShouldHavePrediction) {
LinearPredictor predictor(LinearPredictor::EquationOrder::kSecondOrder);
size_t n1 = static_cast<size_t>(LinearPredictor::EquationOrder::kFirstOrder);
size_t n2 = static_cast<size_t>(LinearPredictor::EquationOrder::kSecondOrder);
for (size_t i = 0; i < n2; i++) {
if (i < n1)
EXPECT_FALSE(predictor.HasPrediction());
else
EXPECT_TRUE(predictor.HasPrediction());
predictor.Update(InputPredictor::InputData());
}
EXPECT_TRUE(predictor.HasPrediction());
predictor.Reset();
EXPECT_FALSE(predictor.HasPrediction());
}
TEST_F(LinearPredictorFirstOrderTest, PredictedValue) {
std::vector<double> x = {10, 20};
std::vector<double> y = {5, 25};
std::vector<double> t = {17, 33};
// Compensating 23 ms
// 1st order prediction at 33 + 23 = 56 ms
std::vector<double> pred_ts = {56};
std::vector<double> pred_x = {34.37};
std::vector<double> pred_y = {53.75};
ValidatePredictor(x, y, t, pred_ts, pred_x, pred_y);
}
TEST_F(LinearPredictorSecondOrderTest, PredictedValue) {
std::vector<double> x = {0, 10, 20};
std::vector<double> y = {0, 5, 25};
std::vector<double> t = {0, 17, 33};
// Compensating 23 ms in both results
// 1st order prediction at 17 + 23 = 40 ms
// 2nd order prediction at 33 + 23 = 56 ms
std::vector<double> pred_ts = {40, 56};
std::vector<double> pred_x = {23.52, 34.98};
std::vector<double> pred_y = {11.76, 69.55};
ValidatePredictor(x, y, t, pred_ts, pred_x, pred_y);
}
// Test constant position and constant velocity
TEST_F(LinearPredictorSecondOrderTest, ConstantPositionAndVelocity) {
std::vector<double> x = {10, 10, 10, 10, 10}; // constant position
std::vector<double> y = {0, 5, 10, 15, 20}; // constant velocity
std::vector<double> t = {0, 7, 14, 21, 28}; // regular interval
// since velocity is constant, acceleration should be 0 which simplifies
// computations
// Compensating 10 ms in all results
std::vector<double> pred_ts = {17, 24, 31, 38};
std::vector<double> pred_x = {10, 10, 10, 10};
std::vector<double> pred_y = {12.14, 17.14, 22.14, 27.14};
ValidatePredictor(x, y, t, pred_ts, pred_x, pred_y);
}
} // namespace test
} // namespace ui
\ No newline at end of file
......@@ -11,6 +11,7 @@
#include "ui/events/blink/prediction/empty_predictor.h"
#include "ui/events/blink/prediction/kalman_predictor.h"
#include "ui/events/blink/prediction/least_squares_predictor.h"
#include "ui/events/blink/prediction/linear_predictor.h"
using blink::WebInputEvent;
using blink::WebGestureEvent;
......@@ -24,6 +25,8 @@ constexpr char kScrollPredictorTypeLsq[] = "lsq";
constexpr char kScrollPredictorTypeKalman[] = "kalman";
constexpr char kScrollPredictorTypeKalmanTimeFiltered[] =
"kalman_time_filtered";
constexpr char kScrollPredictorLinearFirst[] = "linearFirst";
constexpr char kScrollPredictorLinearSecond[] = "linearSecond";
} // namespace
......@@ -46,6 +49,12 @@ ScrollPredictor::ScrollPredictor(bool enable_resampling)
else if (predictor_type == kScrollPredictorTypeKalmanTimeFiltered)
predictor_ =
std::make_unique<KalmanPredictor>(true /* enable_time_filtering */);
else if (predictor_type == kScrollPredictorLinearFirst)
predictor_ = std::make_unique<LinearPredictor>(
LinearPredictor::EquationOrder::kFirstOrder);
else if (predictor_type == kScrollPredictorLinearSecond)
predictor_ = std::make_unique<LinearPredictor>(
LinearPredictor::EquationOrder::kSecondOrder);
else
predictor_ = std::make_unique<EmptyPredictor>();
}
......
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