Commit 5e9b2fba authored by Kris Giesing's avatar Kris Giesing Committed by kgiesing

Style pass on velocity tracker and event recorder

parent 24ea0f0d
// Copyright 2015 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.
import "dart:math" as math;
import "dart:typed_data";
class Vector {
Vector(int size)
: _offset = 0, _length = size, _elem = new Float64List(size);
class _Vector {
_Vector(int size)
: _offset = 0, _length = size, _elements = new Float64List(size);
Vector.fromValues(List<double> values)
: _offset = 0, _length = values.length, _elem = values;
_Vector.fromValues(List<double> values)
: _offset = 0, _length = values.length, _elements = values;
Vector.fromVOL(List<double> values, int offset, int length)
: _offset = offset, _length = length, _elem = values;
_Vector.fromVOL(List<double> values, int offset, int length)
: _offset = offset, _length = length, _elements = values;
int get length => _length;
operator [](int i) => _elem[i + _offset];
operator []=(int i, double value) => _elem[i + _offset] = value;
operator [](int i) => _elements[i + _offset];
operator []=(int i, double value) => _elements[i + _offset] = value;
operator *(Vector a) {
operator *(_Vector a) {
double result = 0.0;
for (int i = 0; i < _length; i++) {
result += this[i] * a[i];
......@@ -38,28 +42,32 @@ class Vector {
final int _offset;
final int _length;
final List<double> _elem;
final List<double> _elements;
}
class Matrix {
Matrix(int rows, int cols)
class _Matrix {
_Matrix(int rows, int cols)
: _rows = rows,
_cols = cols,
_elem = new Float64List(rows * cols);
_columns = cols,
_elements = new Float64List(rows * cols);
double get(int row, int col) => _elem[row * _cols + col];
double get(int row, int col) => _elements[row * _columns + col];
void set(int row, int col, double value) {
_elem[row * _cols + col] = value;
_elements[row * _columns + col] = value;
}
Vector getRow(int row) => new Vector.fromVOL(_elem, row * _cols, _cols);
_Vector getRow(int row) => new _Vector.fromVOL(
_elements,
row * _columns,
_columns
);
String toString() {
String result = "";
for (int i = 0; i < _rows; i++) {
if (i > 0)
result += "; ";
for (int j = 0; j < _cols; j++) {
for (int j = 0; j < _columns; j++) {
if (j > 0)
result += ", ";
result += get(i, j).toString();
......@@ -69,8 +77,8 @@ class Matrix {
}
final int _rows;
final int _cols;
final List<double> _elem;
final int _columns;
final List<double> _elements;
}
class PolynomialFit {
......@@ -99,10 +107,9 @@ class LeastSquaresSolver {
// Shorthands for the purpose of notation equivalence to original C++ code
final int m = x.length;
final int n = degree + 1;
final List<double> out_b = result.coefficients;
// Expand the X vector to a matrix A, pre-multiplied by the weights.
Matrix a = new Matrix(n, m);
_Matrix a = new _Matrix(n, m);
for (int h = 0; h < m; h++) {
a.set(0, h, w[h]);
for (int i = 1; i < n; i++) {
......@@ -113,15 +120,15 @@ class LeastSquaresSolver {
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major ordVectorer.
Matrix q = new Matrix(n, m);
_Matrix q = new _Matrix(n, m);
// Upper triangular matrix, row-major order.
Matrix r = new Matrix(n, n);
_Matrix r = new _Matrix(n, n);
for (int j = 0; j < n; j++) {
for (int h = 0; h < m; h++) {
q.set(j, h, a.get(j, h));
}
for (int i = 0; i < j; i++) {
double dot = q.getRow(j)*q.getRow(i);
double dot = q.getRow(j) * q.getRow(i);
for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) - dot * q.get(i, h));
}
......@@ -133,56 +140,57 @@ class LeastSquaresSolver {
return null;
}
double invNorm = 1.0 / norm;
double inverseNorm = 1.0 / norm;
for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) * invNorm);
q.set(j, h, q.get(j, h) * inverseNorm);
}
for (int i = 0; i < n; i++) {
r.set(j, i, i < j ? 0.0 : q.getRow(j)*a.getRow(i));
r.set(j, i, i < j ? 0.0 : q.getRow(j) * a.getRow(i));
}
}
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients.
Vector wy = new Vector(m);
_Vector wy = new _Vector(m);
for (int h = 0; h < m; h++) {
wy[h] = y[h] * w[h];
}
for (int i = n; i-- != 0;) {
out_b[i] = q.getRow(i) * wy;
result.coefficients[i] = q.getRow(i) * wy;
for (int j = n - 1; j > i; j--) {
out_b[i] -= r.get(i, j) * out_b[j];
result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
}
out_b[i] /= r.get(i, i);
result.coefficients[i] /= r.get(i, i);
}
// Calculate the coefficient of determination as 1 - (SSerr / SStot) where
// SSerr is the residual sum of squares (variance of the error),
// and SStot is the total sum of squares (variance of the data) where each
// has been weighted.
double ymean = 0.0;
// Calculate the coefficient of determination (confidence) as:
// 1 - (sumSquaredError / sumSquaredTotal)
// where sumSquaredError is the residual sum of squares (variance of the
// error), and sumSquaredTotal is the total sum of squares (variance of the
// data) where each has been weighted.
double yMean = 0.0;
for (int h = 0; h < m; h++) {
ymean += y[h];
yMean += y[h];
}
ymean /= m;
yMean /= m;
double sserr = 0.0;
double sstot = 0.0;
double sumSquaredError = 0.0;
double sumSquaredTotal = 0.0;
for (int h = 0; h < m; h++) {
double err = y[h] - out_b[0];
double err = y[h] - result.coefficients[0];
double term = 1.0;
for (int i = 1; i < n; i++) {
term *= x[h];
err -= term * out_b[i];
err -= term * result.coefficients[i];
}
sserr += w[h] * w[h] * err * err;
double v = y[h] - ymean;
sstot += w[h] * w[h] * v * v;
sumSquaredError += w[h] * w[h] * err * err;
double v = y[h] - yMean;
sumSquaredTotal += w[h] * w[h] * v * v;
}
double det = sstot > 0.000001 ? 1.0 - (sserr / sstot) : 1.0;
result.confidence = det;
result.confidence = sumSquaredTotal > 0.000001 ?
1.0 - (sumSquaredError / sumSquaredTotal) :
1.0;
return result;
}
......
// Copyright 2015 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.
import 'dart:ui' as ui;
import 'lsq_solver.dart';
......@@ -10,62 +14,62 @@ class GestureVelocity {
final double y;
}
class Estimator {
class _Estimator {
int degree;
double time;
List<double> xcoeff;
List<double> ycoeff;
List<double> xCoefficients;
List<double> yCoefficients;
double confidence;
String toString() {
String result = "Estimator(degree: " + degree.toString();
result += ", time: " + time.toString();
result += ", confidence: " + confidence.toString();
result += ", xcoeff: " + (new Vector.fromValues(xcoeff)).toString();
result += ", ycoeff: " + (new Vector.fromValues(ycoeff)).toString();
result += ", xCoefficients: " + xCoefficients.toString();
result += ", yCoefficients: " + yCoefficients.toString();
return result;
}
}
abstract class VelocityTrackerStrategy {
abstract class _VelocityTrackerStrategy {
void addMovement(double timeStamp, double x, double y);
bool getEstimator(Estimator estimator);
bool getEstimator(_Estimator estimator);
void clear();
}
enum Weighting {
WEIGHTING_NONE,
WEIGHTING_DELTA,
WEIGHTING_CENTRAL,
WEIGHTING_RECENT
enum _Weighting {
weightingNone,
weightingDelta,
weightingCentral,
weightingRecent
}
class Movement {
double event_time = 0.0;
class _Movement {
double eventTime = 0.0;
ui.Point position = ui.Point.origin;
}
class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
static const int kHistorySize = 20;
static const int kHorizonMS = 100;
static const int kHorizonMilliseconds = 100;
LeastSquaresVelocityTrackerStrategy(this.degree, this.weighting)
: _index = 0, _movements = new List<Movement>(kHistorySize);
_LeastSquaresVelocityTrackerStrategy(this.degree, this.weighting)
: _index = 0, _movements = new List<_Movement>(kHistorySize);
final int degree;
final Weighting weighting;
final List<Movement> _movements;
final _Weighting weighting;
final List<_Movement> _movements;
int _index;
void addMovement(double timeStamp, double x, double y) {
if (++_index == kHistorySize)
_index = 0;
Movement movement = _getMovement(_index);
movement.event_time = timeStamp;
_Movement movement = _getMovement(_index);
movement.eventTime = timeStamp;
movement.position = new ui.Point(x, y);
}
bool getEstimator(Estimator estimator) {
bool getEstimator(_Estimator estimator) {
// Iterate over movement samples in reverse time order and collect samples.
List<double> x = new List<double>();
List<double> y = new List<double>();
......@@ -73,12 +77,12 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
List<double> time = new List<double>();
int m = 0;
int index = _index;
Movement newest_movement = _getMovement(index);
_Movement newestMovement = _getMovement(index);
do {
Movement movement = _getMovement(index);
_Movement movement = _getMovement(index);
double age = newest_movement.event_time - movement.event_time;
if (age > kHorizonMS)
double age = newestMovement.eventTime - movement.eventTime;
if (age > kHorizonMilliseconds)
break;
ui.Point position = movement.position;
......@@ -104,9 +108,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
LeastSquaresSolver ySolver = new LeastSquaresSolver(time, y, w);
PolynomialFit yFit = ySolver.solve(n);
if (yFit != null) {
estimator.xcoeff = xFit.coefficients;
estimator.ycoeff = yFit.coefficients;
estimator.time = newest_movement.event_time;
estimator.xCoefficients = xFit.coefficients;
estimator.yCoefficients = yFit.coefficients;
estimator.time = newestMovement.eventTime;
estimator.degree = n;
estimator.confidence = xFit.confidence * yFit.confidence;
return true;
......@@ -116,9 +120,9 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
// No velocity data available for this pointer, but we do have its current
// position.
estimator.xcoeff = [ x[0] ];
estimator.ycoeff = [ y[0] ];
estimator.time = newest_movement.event_time;
estimator.xCoefficients = [ x[0] ];
estimator.yCoefficients = [ y[0] ];
estimator.time = newestMovement.eventTime;
estimator.degree = 0;
estimator.confidence = 1.0;
return true;
......@@ -130,7 +134,7 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
double _chooseWeight(int index) {
switch (weighting) {
case Weighting.WEIGHTING_DELTA:
case _Weighting.weightingDelta:
// Weight points based on how much time elapsed between them and the next
// point so that points that "cover" a shorter time span are weighed less.
// delta 0ms: 0.5
......@@ -138,61 +142,61 @@ class LeastSquaresVelocityTrackerStrategy extends VelocityTrackerStrategy {
if (index == _index) {
return 1.0;
}
int next_index = (index + 1) % kHistorySize;
double delta_millis = _movements[next_index].event_time -
_movements[index].event_time;
if (delta_millis < 0)
int nextIndex = (index + 1) % kHistorySize;
double deltaMilliseconds = _movements[nextIndex].eventTime -
_movements[index].eventTime;
if (deltaMilliseconds < 0)
return 0.5;
if (delta_millis < 10)
return 0.5 + delta_millis * 0.05;
if (deltaMilliseconds < 10)
return 0.5 + deltaMilliseconds * 0.05;
return 1.0;
case Weighting.WEIGHTING_CENTRAL:
case _Weighting.weightingCentral:
// Weight points based on their age, weighing very recent and very old
// points less.
// age 0ms: 0.5
// age 10ms: 1.0
// age 50ms: 1.0
// age 60ms: 0.5
double age_millis = _movements[_index].event_time -
_movements[index].event_time;
if (age_millis < 0)
double ageMilliseconds = _movements[_index].eventTime -
_movements[index].eventTime;
if (ageMilliseconds < 0)
return 0.5;
if (age_millis < 10)
return 0.5 + age_millis * 0.05;
if (age_millis < 50)
if (ageMilliseconds < 10)
return 0.5 + ageMilliseconds * 0.05;
if (ageMilliseconds < 50)
return 1.0;
if (age_millis < 60)
return 0.5 + (60 - age_millis) * 0.05;
if (ageMilliseconds < 60)
return 0.5 + (60 - ageMilliseconds) * 0.05;
return 0.5;
case Weighting.WEIGHTING_RECENT:
case _Weighting.weightingRecent:
// Weight points based on their age, weighing older points less.
// age 0ms: 1.0
// age 50ms: 1.0
// age 100ms: 0.5
double age_millis = _movements[_index].event_time -
_movements[index].event_time;
if (age_millis < 50) {
double ageMilliseconds = _movements[_index].eventTime -
_movements[index].eventTime;
if (ageMilliseconds < 50) {
return 1.0;
}
if (age_millis < 100) {
return 0.5 + (100 - age_millis) * 0.01;
if (ageMilliseconds < 100) {
return 0.5 + (100 - ageMilliseconds) * 0.01;
}
return 0.5;
case Weighting.WEIGHTING_NONE:
case _Weighting.weightingNone:
default:
return 1.0;
}
}
Movement _getMovement(int i) {
Movement result = _movements[i];
_Movement _getMovement(int i) {
_Movement result = _movements[i];
if (result == null) {
result = new Movement();
result = new _Movement();
_movements[i] = result;
}
return result;
......@@ -206,7 +210,7 @@ class VelocityTracker {
VelocityTracker() : _lastTimeStamp = 0.0, _strategy = _createStrategy();
double _lastTimeStamp;
VelocityTrackerStrategy _strategy;
_VelocityTrackerStrategy _strategy;
void addPosition(double timeStamp, double x, double y) {
if ((timeStamp - _lastTimeStamp) >= kAssumePointerMoveStoppedTimeMs)
......@@ -216,19 +220,19 @@ class VelocityTracker {
}
GestureVelocity getVelocity() {
Estimator estimator = new Estimator();
_Estimator estimator = new _Estimator();
if (_strategy.getEstimator(estimator) && estimator.degree >= 1) {
// convert from pixels/ms to pixels/s
return new GestureVelocity(
isValid: true,
x: estimator.xcoeff[1]*1000,
y: estimator.ycoeff[1]*1000
x: estimator.xCoefficients[1] * 1000,
y: estimator.yCoefficients[1] * 1000
);
}
return new GestureVelocity(isValid: false, x: 0.0, y: 0.0);
}
static VelocityTrackerStrategy _createStrategy() {
return new LeastSquaresVelocityTrackerStrategy(2, Weighting.WEIGHTING_NONE);
static _VelocityTrackerStrategy _createStrategy() {
return new _LeastSquaresVelocityTrackerStrategy(2, _Weighting.weightingNone);
}
}
......@@ -13,8 +13,14 @@ enum EventRecorderMode {
record
}
typedef void EventsReady(List<PointerInputEvent> events);
typedef void EventsReadyCallback(List<PointerInputEvent> events);
/// EventRecorder is a utility widget that allows input events occurring
/// on the child to be recorded. The widget is initially in the "stop" state
/// by default. When in the "record" state, all pointer input events
/// occurring on the child are recorded into a buffer. When the "stop" state
/// is entered again, the onEventsReady callback is invoked with a list of
/// the recorded events.
class EventRecorder extends StatefulComponent {
EventRecorder({
Key key,
......@@ -25,32 +31,29 @@ class EventRecorder extends StatefulComponent {
final Widget child;
final EventRecorderMode mode;
final EventsReady onEventsReady;
final EventsReadyCallback onEventsReady;
_EventRecorderState createState() => new _EventRecorderState();
}
class _EventRecorderState extends State<EventRecorder> {
EventRecorderMode _mode;
List<PointerInputEvent> _events = new List<PointerInputEvent>();
void initState() {
super.initState();
_mode = config.mode;
}
void didUpdateConfig(EventRecorder oldConfig) {
if (_mode == EventRecorderMode.record &&
if (oldConfig.mode == EventRecorderMode.record &&
config.mode == EventRecorderMode.stop) {
config.onEventsReady(_events);
_events.clear();
}
_mode = config.mode;
}
void _recordEvent(PointerInputEvent event) {
if (_mode == EventRecorderMode.record) {
if (config.mode == EventRecorderMode.record) {
_events.add(event);
}
}
......
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