kg
6 年前
当前提交
46387278
共有 21 个文件被更改,包括 998 次插入 和 38 次删除
-
6Assets/UIWidgets/Tests/Gestures.cs
-
37Assets/UIWidgets/editor/editor_window.cs
-
2Assets/UIWidgets/foundation/debug.cs
-
83Assets/UIWidgets/foundation/diagnostics.cs
-
4Assets/UIWidgets/gestures/binding.cs
-
6Assets/UIWidgets/gestures/constants.cs
-
4Assets/UIWidgets/gestures/converter.cs
-
28Assets/UIWidgets/gestures/events.cs
-
45Assets/UIWidgets/gestures/recognizer.cs
-
20Assets/UIWidgets/gestures/tap.cs
-
2Assets/UIWidgets/rendering/binding.cs
-
4Assets/UIWidgets/scheduler/binding.cs
-
12Assets/UIWidgets/ui/window.cs
-
90Assets/UIWidgets/gestures/drag_details.cs
-
3Assets/UIWidgets/gestures/drag_details.cs.meta
-
186Assets/UIWidgets/gestures/lsq_resolver.cs
-
3Assets/UIWidgets/gestures/lsq_resolver.cs.meta
-
281Assets/UIWidgets/gestures/monodrag.cs
-
3Assets/UIWidgets/gestures/monodrag.cs.meta
-
214Assets/UIWidgets/gestures/velocity_tracker.cs
-
3Assets/UIWidgets/gestures/velocity_tracker.cs.meta
|
|||
using System; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.ui; |
|||
|
|||
namespace UIWidgets.gestures { |
|||
public class DragDownDetails { |
|||
public DragDownDetails( |
|||
Offset globalPosition = null |
|||
) { |
|||
this.globalPosition = globalPosition ?? Offset.zero; |
|||
} |
|||
|
|||
public readonly Offset globalPosition; |
|||
|
|||
public override string ToString() { |
|||
return this.GetType() + "(" + this.globalPosition + ")"; |
|||
} |
|||
} |
|||
|
|||
public delegate void GestureDragDownCallback(DragDownDetails details); |
|||
|
|||
public class DragStartDetails { |
|||
public DragStartDetails(DateTime sourceTimeStamp, Offset globalPosition = null) { |
|||
this.sourceTimeStamp = sourceTimeStamp; |
|||
this.globalPosition = globalPosition ?? Offset.zero; |
|||
} |
|||
|
|||
public readonly DateTime sourceTimeStamp; |
|||
|
|||
public readonly Offset globalPosition; |
|||
|
|||
public override string ToString() { |
|||
return this.GetType() + "(" + this.globalPosition + ")"; |
|||
} |
|||
} |
|||
|
|||
public delegate void GestureDragStartCallback(DragStartDetails details); |
|||
|
|||
public class DragUpdateDetails { |
|||
public DragUpdateDetails( |
|||
DateTime sourceTimeStamp, |
|||
Offset delta = null, |
|||
double? primaryDelta = null, |
|||
Offset globalPosition = null) { |
|||
this.sourceTimeStamp = sourceTimeStamp; |
|||
this.delta = delta ?? Offset.zero; |
|||
this.primaryDelta = primaryDelta; |
|||
this.globalPosition = globalPosition ?? Offset.zero; |
|||
D.assert(primaryDelta == null |
|||
|| primaryDelta == this.delta.dx && this.delta.dy == 0.0 |
|||
|| primaryDelta == this.delta.dy && this.delta.dx == 0.0); |
|||
} |
|||
|
|||
public readonly DateTime sourceTimeStamp; |
|||
|
|||
public readonly Offset delta; |
|||
|
|||
public readonly double? primaryDelta; |
|||
|
|||
public readonly Offset globalPosition; |
|||
|
|||
public override string ToString() { |
|||
return this.GetType() + "(" + this.delta + ")"; |
|||
} |
|||
} |
|||
|
|||
public delegate void GestureDragUpdateCallback(DragUpdateDetails details); |
|||
|
|||
public class DragEndDetails { |
|||
public DragEndDetails( |
|||
Velocity velocity = null, |
|||
double? primaryVelocity = null |
|||
) { |
|||
this.velocity = velocity ?? Velocity.zero; |
|||
this.primaryVelocity = primaryVelocity; |
|||
|
|||
D.assert(primaryVelocity == null |
|||
|| primaryVelocity == this.velocity.pixelsPerSecond.dx |
|||
|| primaryVelocity == this.velocity.pixelsPerSecond.dy); |
|||
} |
|||
|
|||
public readonly Velocity velocity; |
|||
|
|||
public readonly double? primaryVelocity; |
|||
|
|||
public override string ToString() { |
|||
return this.GetType() + "(" + this.velocity + ")"; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4283bd1f6f7945578b47e7ad21999e7c |
|||
timeCreated: 1536330559 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UIWidgets.foundation; |
|||
|
|||
namespace UIWidgets.gestures { |
|||
class _Vector { |
|||
internal _Vector(int size) { |
|||
this._offset = 0; |
|||
this._length = size; |
|||
this._elements = Enumerable.Repeat(0.0, size).ToList(); |
|||
} |
|||
|
|||
private _Vector(List<double> values, int offset, int length) { |
|||
this._offset = offset; |
|||
this._length = length; |
|||
this._elements = values; |
|||
} |
|||
|
|||
internal static _Vector fromVOL(List<double> values, int offset, int length) { |
|||
return new _Vector(values, offset, length); |
|||
} |
|||
|
|||
readonly int _offset; |
|||
|
|||
readonly int _length; |
|||
|
|||
readonly List<double> _elements; |
|||
|
|||
public double this[int i] { |
|||
get { return this._elements[i + this._offset]; } |
|||
set { this._elements[i + this._offset] = value; } |
|||
} |
|||
|
|||
public static double operator *(_Vector a, _Vector b) { |
|||
double result = 0.0; |
|||
for (int i = 0; i < a._length; i += 1) |
|||
result += a[i] * b[i]; |
|||
return result; |
|||
} |
|||
|
|||
public double norm() { |
|||
return Math.Sqrt(this * this); |
|||
} |
|||
} |
|||
|
|||
class _Matrix { |
|||
internal _Matrix(int rows, int cols) { |
|||
this._columns = cols; |
|||
this._elements = Enumerable.Repeat(0.0, rows * cols).ToList(); |
|||
} |
|||
|
|||
readonly int _columns; |
|||
readonly List<double> _elements; |
|||
|
|||
public double this[int row, int col] { |
|||
get { return this._elements[row * this._columns + col]; } |
|||
set { this._elements[row * this._columns + col] = value; } |
|||
} |
|||
|
|||
public _Vector getRow(int row) { |
|||
return _Vector.fromVOL( |
|||
this._elements, |
|||
row * this._columns, |
|||
this._columns |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class PolynomialFit { |
|||
public PolynomialFit(int degree) { |
|||
this.coefficients = Enumerable.Repeat(0.0, degree + 1).ToList(); |
|||
} |
|||
|
|||
public readonly List<double> coefficients; |
|||
|
|||
public double confidence; |
|||
} |
|||
|
|||
public class LeastSquaresSolver { |
|||
public LeastSquaresSolver(List<double> x, List<double> y, List<double> w) { |
|||
D.assert(x != null && y != null && w != null); |
|||
D.assert(x.Count == y.Count); |
|||
D.assert(y.Count == w.Count); |
|||
this.x = x; |
|||
this.y = y; |
|||
this.w = w; |
|||
} |
|||
|
|||
public readonly List<double> x; |
|||
|
|||
public readonly List<double> y; |
|||
|
|||
public readonly List<double> w; |
|||
|
|||
/// Fits a polynomial of the given degree to the data points.
|
|||
public PolynomialFit solve(int degree) { |
|||
if (degree > this.x.Count) { |
|||
// Not enough data to fit a curve.
|
|||
return null; |
|||
} |
|||
|
|||
PolynomialFit result = new PolynomialFit(degree); |
|||
|
|||
// Shorthands for the purpose of notation equivalence to original C++ code.
|
|||
int m = x.Count; |
|||
int n = degree + 1; |
|||
|
|||
// Expand the X vector to a matrix A, pre-multiplied by the weights.
|
|||
_Matrix a = new _Matrix(n, m); |
|||
for (int h = 0; h < m; h += 1) { |
|||
a[0, h] = this.w[h]; |
|||
for (int i = 1; i < n; i += 1) |
|||
a[i, h] = a[i - 1, h] * this.x[h]; |
|||
} |
|||
|
|||
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
|
|||
|
|||
// Orthonormal basis, column-major ordVectorer.
|
|||
_Matrix q = new _Matrix(n, m); |
|||
// Upper triangular matrix, row-major order.
|
|||
_Matrix r = new _Matrix(n, n); |
|||
for (int j = 0; j < n; j += 1) { |
|||
for (int h = 0; h < m; h += 1) |
|||
q[j, h] = a[j, h]; |
|||
for (int i = 0; i < j; i += 1) { |
|||
double dot = q.getRow(j) * q.getRow(i); |
|||
for (int h = 0; h < m; h += 1) |
|||
q[j, h] = q[j, h] - dot * q[i, h]; |
|||
} |
|||
|
|||
double norm = q.getRow(j).norm(); |
|||
if (norm < 0.000001) { |
|||
// Vectors are linearly dependent or zero so no solution.
|
|||
return null; |
|||
} |
|||
|
|||
double inverseNorm = 1.0 / norm; |
|||
for (int h = 0; h < m; h += 1) |
|||
q[j, h] = q[j, h] * inverseNorm; |
|||
for (int i = 0; i < n; i += 1) |
|||
r[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); |
|||
for (int h = 0; h < m; h += 1) |
|||
wy[h] = y[h] * w[h]; |
|||
for (int i = n - 1; i >= 0; i -= 1) { |
|||
result.coefficients[i] = q.getRow(i) * wy; |
|||
for (int j = n - 1; j > i; j -= 1) |
|||
result.coefficients[i] -= r[i, j] * result.coefficients[j]; |
|||
result.coefficients[i] /= r[i, i]; |
|||
} |
|||
|
|||
// 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 += 1) |
|||
yMean += y[h]; |
|||
yMean /= m; |
|||
|
|||
double sumSquaredError = 0.0; |
|||
double sumSquaredTotal = 0.0; |
|||
for (int h = 0; h < m; h += 1) { |
|||
double term = 1.0; |
|||
double err = y[h] - result.coefficients[0]; |
|||
for (int i = 1; i < n; i += 1) { |
|||
term *= x[h]; |
|||
err -= term * result.coefficients[i]; |
|||
} |
|||
|
|||
sumSquaredError += w[h] * w[h] * err * err; |
|||
double v = y[h] - yMean; |
|||
sumSquaredTotal += w[h] * w[h] * v * v; |
|||
} |
|||
|
|||
result.confidence = sumSquaredTotal <= 0.000001 ? 1.0 : 1.0 - (sumSquaredError / sumSquaredTotal); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 575547b88e714c13b02cc94a7fac6bb2 |
|||
timeCreated: 1536333643 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.ui; |
|||
|
|||
namespace UIWidgets.gestures { |
|||
enum _DragState { |
|||
ready, |
|||
possible, |
|||
accepted, |
|||
} |
|||
|
|||
public delegate void GestureDragEndCallback(DragEndDetails details); |
|||
|
|||
public delegate void GestureDragCancelCallback(); |
|||
|
|||
public abstract class DragGestureRecognizer : OneSequenceGestureRecognizer { |
|||
public DragGestureRecognizer(GestureBinding binding, object debugOwner = null) |
|||
: base(binding: binding, debugOwner: debugOwner) { |
|||
} |
|||
|
|||
public GestureDragDownCallback onDown; |
|||
|
|||
public GestureDragStartCallback onStart; |
|||
|
|||
public GestureDragUpdateCallback onUpdate; |
|||
|
|||
public GestureDragEndCallback onEnd; |
|||
|
|||
public GestureDragCancelCallback onCancel; |
|||
|
|||
public double? minFlingDistance; |
|||
|
|||
public double? minFlingVelocity; |
|||
|
|||
public double? maxFlingVelocity; |
|||
|
|||
_DragState _state = _DragState.ready; |
|||
Offset _initialPosition; |
|||
protected Offset _pendingDragOffset; |
|||
DateTime _lastPendingEventTimestamp; |
|||
|
|||
protected abstract bool _isFlingGesture(VelocityEstimate estimate); |
|||
protected abstract Offset _getDeltaForDetails(Offset delta); |
|||
protected abstract double? _getPrimaryValueFromOffset(Offset value); |
|||
protected abstract bool _hasSufficientPendingDragDeltaToAccept { get; } |
|||
|
|||
readonly Dictionary<int, VelocityTracker> _velocityTrackers = new Dictionary<int, VelocityTracker>(); |
|||
|
|||
public override void addPointer(PointerDownEvent evt) { |
|||
this.startTrackingPointer(evt.pointer); |
|||
this._velocityTrackers[evt.pointer] = new VelocityTracker(); |
|||
if (this._state == _DragState.ready) { |
|||
this._state = _DragState.possible; |
|||
this._initialPosition = evt.position; |
|||
this._pendingDragOffset = Offset.zero; |
|||
this._lastPendingEventTimestamp = evt.timeStamp; |
|||
if (this.onDown != null) { |
|||
this.invokeCallback<object>("onDown", |
|||
() => { |
|||
this.onDown(new DragDownDetails(globalPosition: this._initialPosition)); |
|||
return null; |
|||
}); |
|||
} |
|||
} else if (this._state == _DragState.accepted) { |
|||
this.resolve(GestureDisposition.accepted); |
|||
} |
|||
} |
|||
|
|||
protected override void handleEvent(PointerEvent evt) { |
|||
D.assert(this._state != _DragState.ready); |
|||
if (!evt.synthesized |
|||
&& (evt is PointerDownEvent || evt is PointerMoveEvent)) { |
|||
var tracker = this._velocityTrackers[evt.pointer]; |
|||
D.assert(tracker != null); |
|||
tracker.addPosition(evt.timeStamp, evt.position); |
|||
} |
|||
|
|||
if (evt is PointerMoveEvent) { |
|||
Offset delta = evt.delta; |
|||
if (this._state == _DragState.accepted) { |
|||
if (this.onUpdate != null) { |
|||
this.invokeCallback<object>("onUpdate", () => { |
|||
this.onUpdate(new DragUpdateDetails( |
|||
sourceTimeStamp: evt.timeStamp, |
|||
delta: this._getDeltaForDetails(delta), |
|||
primaryDelta: this._getPrimaryValueFromOffset(delta), |
|||
globalPosition: evt.position |
|||
)); |
|||
return null; |
|||
}); |
|||
} |
|||
} else { |
|||
this._pendingDragOffset += delta; |
|||
this._lastPendingEventTimestamp = evt.timeStamp; |
|||
if (this._hasSufficientPendingDragDeltaToAccept) { |
|||
this.resolve(GestureDisposition.accepted); |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.stopTrackingIfPointerNoLongerDown(evt); |
|||
} |
|||
|
|||
public override void acceptGesture(int pointer) { |
|||
if (this._state != _DragState.accepted) { |
|||
this._state = _DragState.accepted; |
|||
Offset delta = this._pendingDragOffset; |
|||
var timestamp = this._lastPendingEventTimestamp; |
|||
this._pendingDragOffset = Offset.zero; |
|||
this._lastPendingEventTimestamp = default(DateTime); |
|||
if (this.onStart != null) { |
|||
this.invokeCallback<object>("onStart", () => { |
|||
this.onStart(new DragStartDetails( |
|||
sourceTimeStamp: timestamp, |
|||
globalPosition: this._initialPosition |
|||
)); |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
if (delta != Offset.zero && this.onUpdate != null) { |
|||
this.invokeCallback<object>("onUpdate", () => { |
|||
this.onUpdate(new DragUpdateDetails( |
|||
sourceTimeStamp: timestamp, |
|||
delta: this._getDeltaForDetails(delta), |
|||
primaryDelta: this._getPrimaryValueFromOffset(delta), |
|||
globalPosition: this._initialPosition |
|||
)); |
|||
return null; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override void rejectGesture(int pointer) { |
|||
this.stopTrackingPointer(pointer); |
|||
} |
|||
|
|||
protected override void didStopTrackingLastPointer(int pointer) { |
|||
if (this._state == _DragState.possible) { |
|||
this.resolve(GestureDisposition.rejected); |
|||
this._state = _DragState.ready; |
|||
if (this.onCancel != null) { |
|||
this.invokeCallback<object>("onCancel", () => { |
|||
this.onCancel(); |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
bool wasAccepted = this._state == _DragState.accepted; |
|||
this._state = _DragState.ready; |
|||
if (wasAccepted && this.onEnd != null) { |
|||
var tracker = this._velocityTrackers[pointer]; |
|||
D.assert(tracker != null); |
|||
|
|||
var estimate = tracker.getVelocityEstimate(); |
|||
if (estimate != null && this._isFlingGesture(estimate)) { |
|||
Velocity velocity = new Velocity(pixelsPerSecond: estimate.pixelsPerSecond) |
|||
.clampMagnitude(this.minFlingVelocity ?? Constants.kMinFlingVelocity, |
|||
this.maxFlingVelocity ?? Constants.kMaxFlingVelocity); |
|||
this.invokeCallback<object>("onEnd", () => { |
|||
this.onEnd(new DragEndDetails( |
|||
velocity: velocity, |
|||
primaryVelocity: this._getPrimaryValueFromOffset(velocity.pixelsPerSecond) |
|||
)); |
|||
return null; |
|||
}, debugReport: () => |
|||
string.Format("{0}; fling at {1}.", estimate, velocity)); |
|||
} else { |
|||
this.invokeCallback<object>("onEnd", () => { |
|||
this.onEnd(new DragEndDetails( |
|||
velocity: Velocity.zero, |
|||
primaryVelocity: 0.0 |
|||
)); |
|||
return null; |
|||
}, debugReport: () => |
|||
estimate == null |
|||
? "Could not estimate velocity." |
|||
: estimate + "; judged to not be a fling." |
|||
); |
|||
} |
|||
} |
|||
|
|||
this._velocityTrackers.Clear(); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
this._velocityTrackers.Clear(); |
|||
base.dispose(); |
|||
} |
|||
} |
|||
|
|||
public class VerticalDragGestureRecognizer : DragGestureRecognizer { |
|||
public VerticalDragGestureRecognizer(GestureBinding binding, Object debugOwner = null) |
|||
: base(binding: binding, debugOwner: debugOwner) { |
|||
} |
|||
|
|||
protected override bool _isFlingGesture(VelocityEstimate estimate) { |
|||
double minVelocity = this.minFlingVelocity ?? Constants.kMinFlingVelocity; |
|||
double minDistance = this.minFlingDistance ?? Constants.kTouchSlop; |
|||
return Math.Abs(estimate.pixelsPerSecond.dy) > minVelocity && Math.Abs(estimate.offset.dy) > minDistance; |
|||
} |
|||
|
|||
protected override bool _hasSufficientPendingDragDeltaToAccept { |
|||
get { return Math.Abs(this._pendingDragOffset.dy) > Constants.kTouchSlop; } |
|||
} |
|||
|
|||
protected override Offset _getDeltaForDetails(Offset delta) { |
|||
return new Offset(0.0, delta.dy); |
|||
} |
|||
|
|||
protected override double? _getPrimaryValueFromOffset(Offset value) { |
|||
return value.dy; |
|||
} |
|||
|
|||
public override string debugDescription { |
|||
get { return "vertical drag"; } |
|||
} |
|||
} |
|||
|
|||
public class HorizontalDragGestureRecognizer : DragGestureRecognizer { |
|||
public HorizontalDragGestureRecognizer(GestureBinding binding, Object debugOwner = null) |
|||
: base(binding: binding, debugOwner: debugOwner) { |
|||
} |
|||
|
|||
protected override bool _isFlingGesture(VelocityEstimate estimate) { |
|||
double minVelocity = this.minFlingVelocity ?? Constants.kMinFlingVelocity; |
|||
double minDistance = this.minFlingDistance ?? Constants.kTouchSlop; |
|||
return Math.Abs(estimate.pixelsPerSecond.dx) > minVelocity && Math.Abs(estimate.offset.dx) > minDistance; |
|||
} |
|||
|
|||
protected override bool _hasSufficientPendingDragDeltaToAccept { |
|||
get { return Math.Abs(this._pendingDragOffset.dx) > Constants.kTouchSlop; } |
|||
} |
|||
|
|||
protected override Offset _getDeltaForDetails(Offset delta) { |
|||
return new Offset(delta.dx, 0.0); |
|||
} |
|||
|
|||
protected override double? _getPrimaryValueFromOffset(Offset value) { |
|||
return value.dx; |
|||
} |
|||
|
|||
public override string debugDescription { |
|||
get { return "horizontal drag"; } |
|||
} |
|||
} |
|||
|
|||
public class PanGestureRecognizer : DragGestureRecognizer { |
|||
public PanGestureRecognizer(GestureBinding binding, Object debugOwner = null) |
|||
: base(binding: binding, debugOwner: debugOwner) { |
|||
} |
|||
|
|||
protected override bool _isFlingGesture(VelocityEstimate estimate) { |
|||
double minVelocity = this.minFlingVelocity ?? Constants.kMinFlingVelocity; |
|||
double minDistance = this.minFlingDistance ?? Constants.kTouchSlop; |
|||
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity |
|||
&& estimate.offset.distanceSquared > minDistance * minDistance; |
|||
} |
|||
|
|||
protected override bool _hasSufficientPendingDragDeltaToAccept { |
|||
get { return this._pendingDragOffset.distance > Constants.kPanSlop; } |
|||
} |
|||
|
|||
protected override Offset _getDeltaForDetails(Offset delta) { |
|||
return delta; |
|||
} |
|||
|
|||
protected override double? _getPrimaryValueFromOffset(Offset value) { |
|||
return null; |
|||
} |
|||
|
|||
public override string debugDescription { |
|||
get { return "pan"; } |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 145abf9020044c79bf9227730292796d |
|||
timeCreated: 1536328579 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.ui; |
|||
|
|||
namespace UIWidgets.gestures { |
|||
public class Velocity : IEquatable<Velocity> { |
|||
public Velocity( |
|||
Offset pixelsPerSecond = null |
|||
) { |
|||
this.pixelsPerSecond = pixelsPerSecond ?? Offset.zero; |
|||
} |
|||
|
|||
public static readonly Velocity zero = new Velocity(); |
|||
|
|||
public readonly Offset pixelsPerSecond; |
|||
|
|||
public static Velocity operator -(Velocity a) { |
|||
return new Velocity(pixelsPerSecond: -a.pixelsPerSecond); |
|||
} |
|||
|
|||
public static Velocity operator -(Velocity a, Velocity b) { |
|||
return new Velocity( |
|||
pixelsPerSecond: a.pixelsPerSecond - b.pixelsPerSecond); |
|||
} |
|||
|
|||
public static Velocity operator +(Velocity a, Velocity b) { |
|||
return new Velocity( |
|||
pixelsPerSecond: a.pixelsPerSecond + b.pixelsPerSecond); |
|||
} |
|||
|
|||
public Velocity clampMagnitude(double minValue, double maxValue) { |
|||
D.assert(minValue >= 0.0); |
|||
D.assert(maxValue >= 0.0 && maxValue >= minValue); |
|||
double valueSquared = this.pixelsPerSecond.distanceSquared; |
|||
if (valueSquared > maxValue * maxValue) { |
|||
return new Velocity(pixelsPerSecond: (this.pixelsPerSecond / this.pixelsPerSecond.distance) * maxValue); |
|||
} |
|||
|
|||
if (valueSquared < minValue * minValue) { |
|||
return new Velocity(pixelsPerSecond: (this.pixelsPerSecond / this.pixelsPerSecond.distance) * minValue); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public bool Equals(Velocity other) { |
|||
if (object.ReferenceEquals(null, other)) return false; |
|||
if (object.ReferenceEquals(this, other)) return true; |
|||
return object.Equals(this.pixelsPerSecond, other.pixelsPerSecond); |
|||
} |
|||
|
|||
public override bool Equals(object obj) { |
|||
if (object.ReferenceEquals(null, obj)) return false; |
|||
if (object.ReferenceEquals(this, obj)) return true; |
|||
if (obj.GetType() != this.GetType()) return false; |
|||
return this.Equals((Velocity) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() { |
|||
return (this.pixelsPerSecond != null ? this.pixelsPerSecond.GetHashCode() : 0); |
|||
} |
|||
|
|||
public static bool operator ==(Velocity left, Velocity right) { |
|||
return object.Equals(left, right); |
|||
} |
|||
|
|||
public static bool operator !=(Velocity left, Velocity right) { |
|||
return !object.Equals(left, right); |
|||
} |
|||
|
|||
public override string ToString() { |
|||
return string.Format("Velocity({0:F1}, {1:F1})", this.pixelsPerSecond.dx, this.pixelsPerSecond.dy); |
|||
} |
|||
} |
|||
|
|||
public class VelocityEstimate { |
|||
public VelocityEstimate( |
|||
Offset pixelsPerSecond, |
|||
double confidence, |
|||
TimeSpan duration, |
|||
Offset offset |
|||
) { |
|||
D.assert(pixelsPerSecond != null); |
|||
D.assert(offset != null); |
|||
this.pixelsPerSecond = pixelsPerSecond; |
|||
this.confidence = confidence; |
|||
this.duration = duration; |
|||
this.offset = offset; |
|||
} |
|||
|
|||
public readonly Offset pixelsPerSecond; |
|||
|
|||
public readonly double confidence; |
|||
|
|||
public readonly TimeSpan duration; |
|||
|
|||
public readonly Offset offset; |
|||
|
|||
public override string ToString() { |
|||
return string.Format("VelocityEstimate({0:F1}, {1:F1}; offset: {2}, duration: {3}, confidence: {4:F1})", |
|||
this.pixelsPerSecond.dx, this.pixelsPerSecond.dy, this.offset, this.duration, this.confidence); |
|||
} |
|||
} |
|||
|
|||
class _PointAtTime { |
|||
internal _PointAtTime(Offset point, DateTime time) { |
|||
D.assert(point != null); |
|||
this.point = point; |
|||
this.time = time; |
|||
} |
|||
|
|||
public readonly Offset point; |
|||
|
|||
public readonly DateTime time; |
|||
|
|||
public override string ToString() { |
|||
return string.Format("_PointAtTime({0} at {1})", this.point, this.time); |
|||
} |
|||
} |
|||
|
|||
public class VelocityTracker { |
|||
const int _assumePointerMoveStoppedMilliseconds = 40; |
|||
const int _historySize = 20; |
|||
const int _horizonMilliseconds = 100; |
|||
const int _minSampleSize = 3; |
|||
|
|||
readonly List<_PointAtTime> _samples = Enumerable.Repeat<_PointAtTime>(null, _historySize).ToList(); |
|||
int _index = 0; |
|||
|
|||
public void addPosition(DateTime time, Offset position) { |
|||
this._index += 1; |
|||
if (this._index == _historySize) { |
|||
this._index = 0; |
|||
} |
|||
|
|||
this._samples[this._index] = new _PointAtTime(position, time); |
|||
} |
|||
|
|||
public VelocityEstimate getVelocityEstimate() { |
|||
List<double> x = new List<double>(); |
|||
List<double> y = new List<double>(); |
|||
List<double> w = new List<double>(); |
|||
List<double> time = new List<double>(); |
|||
int sampleCount = 0; |
|||
int index = this._index; |
|||
|
|||
_PointAtTime newestSample = this._samples[index]; |
|||
if (newestSample == null) { |
|||
return null; |
|||
} |
|||
|
|||
_PointAtTime previousSample = newestSample; |
|||
_PointAtTime oldestSample = newestSample; |
|||
|
|||
do { |
|||
_PointAtTime sample = this._samples[index]; |
|||
if (sample == null) |
|||
break; |
|||
|
|||
double age = (newestSample.time - sample.time).TotalMilliseconds; |
|||
double delta = Math.Abs((sample.time - previousSample.time).TotalMilliseconds); |
|||
previousSample = sample; |
|||
if (age > _horizonMilliseconds || delta > _assumePointerMoveStoppedMilliseconds) { |
|||
break; |
|||
} |
|||
|
|||
oldestSample = sample; |
|||
Offset position = sample.point; |
|||
x.Add(position.dx); |
|||
y.Add(position.dy); |
|||
w.Add(1.0); |
|||
time.Add(-age); |
|||
index = (index == 0 ? _historySize : index) - 1; |
|||
|
|||
sampleCount += 1; |
|||
} while (sampleCount < _historySize); |
|||
|
|||
if (sampleCount >= _minSampleSize) { |
|||
LeastSquaresSolver xSolver = new LeastSquaresSolver(time, x, w); |
|||
PolynomialFit xFit = xSolver.solve(2); |
|||
if (xFit != null) { |
|||
LeastSquaresSolver ySolver = new LeastSquaresSolver(time, y, w); |
|||
PolynomialFit yFit = ySolver.solve(2); |
|||
if (yFit != null) { |
|||
return new VelocityEstimate( |
|||
pixelsPerSecond: new Offset(xFit.coefficients[1] * 1000, yFit.coefficients[1] * 1000), |
|||
confidence: xFit.confidence * yFit.confidence, |
|||
duration: newestSample.time - oldestSample.time, |
|||
offset: newestSample.point - oldestSample.point |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new VelocityEstimate( |
|||
pixelsPerSecond: Offset.zero, |
|||
confidence: 1.0, |
|||
duration: newestSample.time - oldestSample.time, |
|||
offset: newestSample.point - oldestSample.point |
|||
); |
|||
} |
|||
|
|||
public Velocity getVelocity() { |
|||
VelocityEstimate estimate = this.getVelocityEstimate(); |
|||
if (estimate == null || estimate.pixelsPerSecond == Offset.zero) { |
|||
return Velocity.zero; |
|||
} |
|||
|
|||
return new Velocity(pixelsPerSecond: estimate.pixelsPerSecond); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d01877db8a7341e491a67f84ed3ab604 |
|||
timeCreated: 1536331325 |
撰写
预览
正在加载...
取消
保存
Reference in new issue