Files
BadNote/lib/editor/engine/stroke_predictor.dart

57 lines
1.7 KiB
Dart
Raw Normal View History

// Lightweight stroke prediction — extrapolates the next point from recent
// velocity so the live stroke tip leads the digitizer slightly (lower perceived
// latency). Not a full ink-stroke-modeler; intentionally small and testable.
import 'dart:ui';
class PredictedPoint {
const PredictedPoint(this.offset, this.pressure);
final Offset offset;
final double pressure;
}
class StrokePredictor {
StrokePredictor({this.lookaheadMs = 8});
/// How far ahead to project, in milliseconds of recent velocity.
final double lookaheadMs;
Offset? _prev;
double? _prevPressure;
DateTime? _prevAt;
Offset _velocity = Offset.zero;
void reset() {
_prev = null;
_prevPressure = null;
_prevAt = null;
_velocity = Offset.zero;
}
/// Feed a real sample; returns an optional predicted tip ahead of [point].
PredictedPoint? observe(Offset point, double pressure, {DateTime? at}) {
final now = at ?? DateTime.now();
if (_prev != null && _prevAt != null) {
final dtMs = now.difference(_prevAt!).inMicroseconds / 1000.0;
if (dtMs > 0.5 && dtMs < 80) {
final raw = (point - _prev!) * (1000.0 / dtMs);
// EMA blend to avoid jerky predictions.
_velocity = Offset(
_velocity.dx * 0.55 + raw.dx * 0.45,
_velocity.dy * 0.55 + raw.dy * 0.45,
);
}
}
_prev = point;
_prevPressure = pressure;
_prevAt = now;
if (_velocity.distance < 40) return null; // idle / slow — no predict
final tip = point + _velocity * (lookaheadMs / 1000.0);
return PredictedPoint(tip, pressure);
}
/// Last known pressure (for predicted tip).
double get lastPressure => _prevPressure ?? 0.5;
}