This source file includes following definitions.
- VectorDot
- VectorNorm
- CreateStrategy
- strategy_
- strategy_
- Clear
- ClearPointers
- AddMovement
- AddMovement
- GetVelocity
- AddMovement
- GetEstimator
- weighting_
- Clear
- SolveLeastSquares
- ClearPointers
- GetEstimator
- ChooseWeight
- Clear
- ClearPointers
- AddMovement
- GetEstimator
- InitState
- UpdateState
- PopulateEstimator
#include "ui/events/gesture_detection/velocity_tracker.h"
#include <cmath>
#include "base/logging.h"
#include "ui/events/gesture_detection/motion_event.h"
using base::TimeDelta;
using base::TimeTicks;
namespace ui {
class VelocityTrackerStrategy {
public:
virtual ~VelocityTrackerStrategy() {}
virtual void Clear() = 0;
virtual void ClearPointers(BitSet32 id_bits) = 0;
virtual void AddMovement(const base::TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) = 0;
virtual bool GetEstimator(uint32_t id, Estimator* out_estimator) const = 0;
protected:
VelocityTrackerStrategy() {}
};
namespace {
COMPILE_ASSERT(MotionEvent::MAX_POINTER_ID < 32, max_pointer_id_too_large);
struct Position {
float x, y;
};
struct Estimator {
enum { MAX_DEGREE = 4 };
TimeTicks time;
float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 1];
uint32_t degree;
float confidence;
inline void Clear() {
time = TimeTicks();
degree = 0;
confidence = 0;
for (size_t i = 0; i <= MAX_DEGREE; i++) {
xcoeff[i] = 0;
ycoeff[i] = 0;
}
}
};
const TimeDelta ASSUME_POINTER_STOPPED_TIME = TimeDelta::FromMilliseconds(40);
static float VectorDot(const float* a, const float* b, uint32_t m) {
float r = 0;
while (m--) {
r += *(a++) * *(b++);
}
return r;
}
static float VectorNorm(const float* a, uint32_t m) {
float r = 0;
while (m--) {
float t = *(a++);
r += t * t;
}
return sqrtf(r);
}
class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
public:
enum Weighting {
WEIGHTING_NONE,
WEIGHTING_DELTA,
WEIGHTING_CENTRAL,
WEIGHTING_RECENT,
};
enum { HISTORY_SIZE = 20 };
LeastSquaresVelocityTrackerStrategy(uint32_t degree,
Weighting weighting = WEIGHTING_NONE);
virtual ~LeastSquaresVelocityTrackerStrategy();
virtual void Clear() OVERRIDE;
virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
virtual void AddMovement(const TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) OVERRIDE;
virtual bool GetEstimator(uint32_t id,
Estimator* out_estimator) const OVERRIDE;
private:
static const TimeDelta HORIZON;
struct Movement {
TimeTicks event_time;
BitSet32 id_bits;
Position positions[VelocityTracker::MAX_POINTERS];
inline const Position& GetPosition(uint32_t id) const {
return positions[id_bits.get_index_of_bit(id)];
}
};
float ChooseWeight(uint32_t index) const;
const uint32_t degree_;
const Weighting weighting_;
uint32_t index_;
Movement movements_[HISTORY_SIZE];
};
class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
public:
explicit IntegratingVelocityTrackerStrategy(uint32_t degree);
virtual ~IntegratingVelocityTrackerStrategy();
virtual void Clear() OVERRIDE;
virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
virtual void AddMovement(const TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) OVERRIDE;
virtual bool GetEstimator(uint32_t id,
Estimator* out_estimator) const OVERRIDE;
private:
struct State {
TimeTicks update_time;
uint32_t degree;
float xpos, xvel, xaccel;
float ypos, yvel, yaccel;
};
const uint32_t degree_;
BitSet32 pointer_id_bits_;
State mPointerState[MotionEvent::MAX_POINTER_ID + 1];
void InitState(State& state,
const TimeTicks& event_time,
float xpos,
float ypos) const;
void UpdateState(State& state,
const TimeTicks& event_time,
float xpos,
float ypos) const;
void PopulateEstimator(const State& state, Estimator* out_estimator) const;
};
VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) {
switch (strategy) {
case VelocityTracker::LSQ1:
return new LeastSquaresVelocityTrackerStrategy(1);
case VelocityTracker::LSQ2:
return new LeastSquaresVelocityTrackerStrategy(2);
case VelocityTracker::LSQ3:
return new LeastSquaresVelocityTrackerStrategy(3);
case VelocityTracker::WLSQ2_DELTA:
return new LeastSquaresVelocityTrackerStrategy(
2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
case VelocityTracker::WLSQ2_CENTRAL:
return new LeastSquaresVelocityTrackerStrategy(
2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
case VelocityTracker::WLSQ2_RECENT:
return new LeastSquaresVelocityTrackerStrategy(
2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
case VelocityTracker::INT1:
return new IntegratingVelocityTrackerStrategy(1);
case VelocityTracker::INT2:
return new IntegratingVelocityTrackerStrategy(2);
}
NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy;
return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT);
}
}
VelocityTracker::VelocityTracker()
: current_pointer_id_bits_(0),
active_pointer_id_(-1),
strategy_(CreateStrategy(STRATEGY_DEFAULT)) {}
VelocityTracker::VelocityTracker(Strategy strategy)
: current_pointer_id_bits_(0),
active_pointer_id_(-1),
strategy_(CreateStrategy(strategy)) {}
VelocityTracker::~VelocityTracker() {}
void VelocityTracker::Clear() {
current_pointer_id_bits_.clear();
active_pointer_id_ = -1;
strategy_->Clear();
}
void VelocityTracker::ClearPointers(BitSet32 id_bits) {
BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value);
current_pointer_id_bits_ = remaining_id_bits;
if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) {
active_pointer_id_ = !remaining_id_bits.is_empty()
? remaining_id_bits.first_marked_bit()
: -1;
}
strategy_->ClearPointers(id_bits);
}
void VelocityTracker::AddMovement(const TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) {
while (id_bits.count() > MAX_POINTERS)
id_bits.clear_last_marked_bit();
if ((current_pointer_id_bits_.value & id_bits.value) &&
event_time >= (last_event_time_ + ASSUME_POINTER_STOPPED_TIME)) {
strategy_->Clear();
}
last_event_time_ = event_time;
current_pointer_id_bits_ = id_bits;
if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_))
active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit();
strategy_->AddMovement(event_time, id_bits, positions);
}
void VelocityTracker::AddMovement(const MotionEvent& event) {
int32_t actionMasked = event.GetAction();
switch (actionMasked) {
case MotionEvent::ACTION_DOWN:
Clear();
break;
case MotionEvent::ACTION_POINTER_DOWN: {
BitSet32 downIdBits;
downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex()));
ClearPointers(downIdBits);
break;
}
case MotionEvent::ACTION_MOVE:
break;
default:
return;
}
size_t pointer_count = event.GetPointerCount();
if (pointer_count > MAX_POINTERS) {
pointer_count = MAX_POINTERS;
}
BitSet32 id_bits;
for (size_t i = 0; i < pointer_count; i++) {
id_bits.mark_bit(event.GetPointerId(i));
}
uint32_t pointer_index[MAX_POINTERS];
for (size_t i = 0; i < pointer_count; i++) {
pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i));
}
Position positions[MAX_POINTERS];
size_t historySize = event.GetHistorySize();
for (size_t h = 0; h < historySize; h++) {
for (size_t i = 0; i < pointer_count; i++) {
uint32_t index = pointer_index[i];
positions[index].x = event.GetHistoricalX(i, h);
positions[index].y = event.GetHistoricalY(i, h);
}
AddMovement(event.GetHistoricalEventTime(h), id_bits, positions);
}
for (size_t i = 0; i < pointer_count; i++) {
uint32_t index = pointer_index[i];
positions[index].x = event.GetX(i);
positions[index].y = event.GetY(i);
}
AddMovement(event.GetEventTime(), id_bits, positions);
}
bool VelocityTracker::GetVelocity(uint32_t id,
float* out_vx,
float* out_vy) const {
Estimator estimator;
if (GetEstimator(id, &estimator) && estimator.degree >= 1) {
*out_vx = estimator.xcoeff[1];
*out_vy = estimator.ycoeff[1];
return true;
}
*out_vx = 0;
*out_vy = 0;
return false;
}
void LeastSquaresVelocityTrackerStrategy::AddMovement(
const TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) {
if (++index_ == HISTORY_SIZE) {
index_ = 0;
}
Movement& movement = movements_[index_];
movement.event_time = event_time;
movement.id_bits = id_bits;
uint32_t count = id_bits.count();
for (uint32_t i = 0; i < count; i++) {
movement.positions[i] = positions[i];
}
}
bool VelocityTracker::GetEstimator(uint32_t id,
Estimator* out_estimator) const {
return strategy_->GetEstimator(id, out_estimator);
}
const TimeDelta LeastSquaresVelocityTrackerStrategy::HORIZON =
TimeDelta::FromMilliseconds(100);
LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
uint32_t degree,
Weighting weighting)
: degree_(degree), weighting_(weighting) {
DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
Clear();
}
LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
void LeastSquaresVelocityTrackerStrategy::Clear() {
index_ = 0;
movements_[0].id_bits.clear();
}
static bool SolveLeastSquares(const float* x,
const float* y,
const float* w,
uint32_t m,
uint32_t n,
float* out_b,
float* out_det) {
#if defined(COMPILER_MSVC)
enum {
M_ARRAY_LENGTH = LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE,
N_ARRAY_LENGTH = Estimator::MAX_DEGREE
};
DCHECK_LE(m, static_cast<uint32_t>(M_ARRAY_LENGTH));
DCHECK_LE(n, static_cast<uint32_t>(N_ARRAY_LENGTH));
#else
const uint32_t M_ARRAY_LENGTH = m;
const uint32_t N_ARRAY_LENGTH = n;
#endif
float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH];
for (uint32_t h = 0; h < m; h++) {
a[0][h] = w[h];
for (uint32_t i = 1; i < n; i++) {
a[i][h] = a[i - 1][h] * x[h];
}
}
float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH];
float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH];
for (uint32_t j = 0; j < n; j++) {
for (uint32_t h = 0; h < m; h++) {
q[j][h] = a[j][h];
}
for (uint32_t i = 0; i < j; i++) {
float dot = VectorDot(&q[j][0], &q[i][0], m);
for (uint32_t h = 0; h < m; h++) {
q[j][h] -= dot * q[i][h];
}
}
float norm = VectorNorm(&q[j][0], m);
if (norm < 0.000001f) {
return false;
}
float invNorm = 1.0f / norm;
for (uint32_t h = 0; h < m; h++) {
q[j][h] *= invNorm;
}
for (uint32_t i = 0; i < n; i++) {
r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m);
}
}
float wy[M_ARRAY_LENGTH];
for (uint32_t h = 0; h < m; h++) {
wy[h] = y[h] * w[h];
}
for (uint32_t i = n; i-- != 0;) {
out_b[i] = VectorDot(&q[i][0], wy, m);
for (uint32_t j = n - 1; j > i; j--) {
out_b[i] -= r[i][j] * out_b[j];
}
out_b[i] /= r[i][i];
}
float ymean = 0;
for (uint32_t h = 0; h < m; h++) {
ymean += y[h];
}
ymean /= m;
float sserr = 0;
float sstot = 0;
for (uint32_t h = 0; h < m; h++) {
float err = y[h] - out_b[0];
float term = 1;
for (uint32_t i = 1; i < n; i++) {
term *= x[h];
err -= term * out_b[i];
}
sserr += w[h] * w[h] * err * err;
float var = y[h] - ymean;
sstot += w[h] * w[h] * var * var;
}
*out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
return true;
}
void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value);
movements_[index_].id_bits = remaining_id_bits;
}
bool LeastSquaresVelocityTrackerStrategy::GetEstimator(
uint32_t id,
Estimator* out_estimator) const {
out_estimator->Clear();
float x[HISTORY_SIZE];
float y[HISTORY_SIZE];
float w[HISTORY_SIZE];
float time[HISTORY_SIZE];
uint32_t m = 0;
uint32_t index = index_;
const Movement& newest_movement = movements_[index_];
do {
const Movement& movement = movements_[index];
if (!movement.id_bits.has_bit(id))
break;
TimeDelta age = newest_movement.event_time - movement.event_time;
if (age > HORIZON)
break;
const Position& position = movement.GetPosition(id);
x[m] = position.x;
y[m] = position.y;
w[m] = ChooseWeight(index);
time[m] = -age.InSecondsF();
index = (index == 0 ? HISTORY_SIZE : index) - 1;
} while (++m < HISTORY_SIZE);
if (m == 0)
return false;
uint32_t degree = degree_;
if (degree > m - 1)
degree = m - 1;
if (degree >= 1) {
float xdet, ydet;
uint32_t n = degree + 1;
if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) &&
SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) {
out_estimator->time = newest_movement.event_time;
out_estimator->degree = degree;
out_estimator->confidence = xdet * ydet;
return true;
}
}
out_estimator->xcoeff[0] = x[0];
out_estimator->ycoeff[0] = y[0];
out_estimator->time = newest_movement.event_time;
out_estimator->degree = 0;
out_estimator->confidence = 1;
return true;
}
float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const {
switch (weighting_) {
case WEIGHTING_DELTA: {
if (index == index_) {
return 1.0f;
}
uint32_t next_index = (index + 1) % HISTORY_SIZE;
float delta_millis =
static_cast<float>((movements_[next_index].event_time -
movements_[index].event_time).InMillisecondsF());
if (delta_millis < 0)
return 0.5f;
if (delta_millis < 10)
return 0.5f + delta_millis * 0.05;
return 1.0f;
}
case WEIGHTING_CENTRAL: {
float age_millis =
static_cast<float>((movements_[index_].event_time -
movements_[index].event_time).InMillisecondsF());
if (age_millis < 0)
return 0.5f;
if (age_millis < 10)
return 0.5f + age_millis * 0.05;
if (age_millis < 50)
return 1.0f;
if (age_millis < 60)
return 0.5f + (60 - age_millis) * 0.05;
return 0.5f;
}
case WEIGHTING_RECENT: {
float age_millis =
static_cast<float>((movements_[index_].event_time -
movements_[index].event_time).InMillisecondsF());
if (age_millis < 50) {
return 1.0f;
}
if (age_millis < 100) {
return 0.5f + (100 - age_millis) * 0.01f;
}
return 0.5f;
}
case WEIGHTING_NONE:
default:
return 1.0f;
}
}
IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(
uint32_t degree)
: degree_(degree) {
DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
}
IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {}
void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); }
void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
pointer_id_bits_.value &= ~id_bits.value;
}
void IntegratingVelocityTrackerStrategy::AddMovement(
const TimeTicks& event_time,
BitSet32 id_bits,
const Position* positions) {
uint32_t index = 0;
for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) {
uint32_t id = iter_id_bits.clear_first_marked_bit();
State& state = mPointerState[id];
const Position& position = positions[index++];
if (pointer_id_bits_.has_bit(id))
UpdateState(state, event_time, position.x, position.y);
else
InitState(state, event_time, position.x, position.y);
}
pointer_id_bits_ = id_bits;
}
bool IntegratingVelocityTrackerStrategy::GetEstimator(
uint32_t id,
Estimator* out_estimator) const {
out_estimator->Clear();
if (pointer_id_bits_.has_bit(id)) {
const State& state = mPointerState[id];
PopulateEstimator(state, out_estimator);
return true;
}
return false;
}
void IntegratingVelocityTrackerStrategy::InitState(State& state,
const TimeTicks& event_time,
float xpos,
float ypos) const {
state.update_time = event_time;
state.degree = 0;
state.xpos = xpos;
state.xvel = 0;
state.xaccel = 0;
state.ypos = ypos;
state.yvel = 0;
state.yaccel = 0;
}
void IntegratingVelocityTrackerStrategy::UpdateState(
State& state,
const TimeTicks& event_time,
float xpos,
float ypos) const {
const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2);
const float FILTER_TIME_CONSTANT = 0.010f;
if (event_time <= state.update_time + MIN_TIME_DELTA)
return;
float dt = static_cast<float>((event_time - state.update_time).InSecondsF());
state.update_time = event_time;
float xvel = (xpos - state.xpos) / dt;
float yvel = (ypos - state.ypos) / dt;
if (state.degree == 0) {
state.xvel = xvel;
state.yvel = yvel;
state.degree = 1;
} else {
float alpha = dt / (FILTER_TIME_CONSTANT + dt);
if (degree_ == 1) {
state.xvel += (xvel - state.xvel) * alpha;
state.yvel += (yvel - state.yvel) * alpha;
} else {
float xaccel = (xvel - state.xvel) / dt;
float yaccel = (yvel - state.yvel) / dt;
if (state.degree == 1) {
state.xaccel = xaccel;
state.yaccel = yaccel;
state.degree = 2;
} else {
state.xaccel += (xaccel - state.xaccel) * alpha;
state.yaccel += (yaccel - state.yaccel) * alpha;
}
state.xvel += (state.xaccel * dt) * alpha;
state.yvel += (state.yaccel * dt) * alpha;
}
}
state.xpos = xpos;
state.ypos = ypos;
}
void IntegratingVelocityTrackerStrategy::PopulateEstimator(
const State& state,
Estimator* out_estimator) const {
out_estimator->time = state.update_time;
out_estimator->confidence = 1.0f;
out_estimator->degree = state.degree;
out_estimator->xcoeff[0] = state.xpos;
out_estimator->xcoeff[1] = state.xvel;
out_estimator->xcoeff[2] = state.xaccel / 2;
out_estimator->ycoeff[0] = state.ypos;
out_estimator->ycoeff[1] = state.yvel;
out_estimator->ycoeff[2] = state.yaccel / 2;
}
}