// 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 = 12}); /// 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; }