offsetAt method

Offset offsetAt(
  1. Duration elapsed
)

Displacement at elapsed time since the most recent shake call. Exactly Offset.zero before any shake has ever been triggered, and again once elapsed reaches _decay (clamped, not asymptotic) — a live-driven ScreenShake uses this to know when it can stop ticking.

No direction given to shake (the default): primary uses sin, secondary uses cos at a different phase, mapped straight onto x/y — this traces a symmetric ellipse, not a straight diagonal line, and is byte-for-byte the same formula this method always used (IDEA-60 never changes this path).

With a direction: the SAME primary oscillation now runs fully ALONG that (unit) vector, and secondary runs perpendicular to it — scaled down by _perpendicularFactor — so the shake's peak displacement is visibly biased along the collision direction instead of tracing a shape-agnostic ellipse.

Implementation

Offset offsetAt(Duration elapsed) {
  if (elapsed <= Duration.zero) elapsed = Duration.zero;
  if (_decay <= Duration.zero || elapsed >= _decay) return Offset.zero;

  final t = elapsed.inMicroseconds / 1e6;
  final decaySeconds = _decay.inMicroseconds / 1e6;
  // ponytail: naive exponential envelope tuned so it's ~e^-4 (~1.8% of
  // intensity) by the end of `decay`, not a physically modeled impulse
  // response — good enough for a juice effect, revisit if a designer
  // wants a different "feel" curve.
  final envelope = math.exp(-4 * t / decaySeconds);
  final primary =
      _intensity * envelope * math.sin(2 * math.pi * _frequency * t);
  final secondary =
      _intensity * envelope * math.cos(2 * math.pi * _frequency * 1.3 * t);

  final dir = _direction;
  if (dir == null) return Offset(primary, secondary);

  final perpendicular = Offset(-dir.dy, dir.dx);
  return dir * primary + perpendicular * (secondary * _perpendicularFactor);
}