MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Differential.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <glm/glm.hpp>
6
7namespace MayaFlux::Kinesis {
8
9// =============================================================================
10// General finite differences
11// =============================================================================
12
13// scalar_t<T> now comes from the PCH type system (MayaFlux::scalar_t),
14// resolving to T for plain arithmetic types and to glm_component_type<T>
15// for GlmType T. Used unqualified below via MayaFlux namespace lookup.
16
17/**
18 * @brief N-th order backward finite difference of a HistoryBuffer, scaled by dt^N
19 * @tparam N Difference order. 1 velocity, 2 acceleration, 3 jerk, 4 jounce, 5 crackle, 6 pop.
20 * @tparam T Sample type. Must support operator+, operator-, and
21 * multiplication/division by scalar_t<T>.
22 * @param history Buffer with capacity >= N + 1, [0] newest through [N] oldest used
23 * @param dt Elapsed time between consecutive samples, assumed uniform across
24 * all N intervals
25 * @return sum_{k=0}^{N} (-1)^k * C(N,k) * history[k], divided by dt^N
26 *
27 * Named wrappers below (velocity, acceleration, jerk, jounce, crackle, pop)
28 * are instantiations of this at fixed N. A caller needing N = 7 or higher
29 * calls backward_difference<7>(history, dt) directly.
30 *
31 * dt == 0 is not guarded: the correct response depends on the caller's
32 * domain, so this stays a pure computation.
33 *
34 * Uniform dt across all N intervals is an assumption, not a check.
35 * HistoryBuffer carries no per-sample timestamps.
36 *
37 * HistoryBuffer::operator[] indexes modulo capacity, not modulo the
38 * number of samples actually pushed. Querying history[k] before k
39 * samples have been pushed reads the zero initial condition.
40 */
41template <size_t N, typename T>
42[[nodiscard]] inline T backward_difference(const Memory::HistoryBuffer<T>& history, double dt) noexcept
43{
44 static_assert(N >= 1, "backward_difference<N> requires N >= 1; N = 0 is the sample itself");
45
46 using S = scalar_t<T>;
47
48 T acc = history[0];
49 double binomial = 1.0;
50
51 for (size_t k = 1; k <= N; ++k) {
52 binomial = binomial * static_cast<double>(N - k + 1) / static_cast<double>(k);
53 const double sign = (k % 2 == 0) ? 1.0 : -1.0;
54 acc = acc + static_cast<S>(sign * binomial) * history[k];
55 }
56
57 double denom = 1.0;
58 for (size_t i = 0; i < N; ++i)
59 denom *= dt;
60
61 return acc / static_cast<S>(denom);
62}
63
64/**
65 * @brief N-th order forward finite difference, expressed on a HistoryBuffer
66 * @tparam N Difference order
67 * @tparam T Sample type
68 * @param history Buffer with capacity >= N + 1
69 * @param dt Elapsed time between consecutive samples
70 * @return Forward-difference formula evaluated with the newest sample
71 * treated as the base point and older samples as the forward taps
72 *
73 * Mathematically the forward and backward difference formulas are the
74 * same stencil read in opposite temporal direction. Since a HistoryBuffer
75 * only ever exposes past samples relative to [0], this computes the
76 * forward-difference coefficients but applied to the same available data
77 * as backward_difference. The two differ only for even N in sign
78 * convention on alternating terms; provided for callers whose downstream
79 * math was derived against forward-difference tables.
80 */
81template <size_t N, typename T>
82[[nodiscard]] inline T forward_difference(const Memory::HistoryBuffer<T>& history, double dt) noexcept
83{
84 static_assert(N >= 1, "forward_difference<N> requires N >= 1");
85
86 using S = scalar_t<T>;
87
88 T acc = T {};
89 double binomial = 1.0;
90
91 for (size_t k = 0; k <= N; ++k) {
92 if (k > 0)
93 binomial = binomial * static_cast<double>(N - k + 1) / static_cast<double>(k);
94 const double sign = ((N - k) % 2 == 0) ? 1.0 : -1.0;
95 acc = acc + static_cast<S>(sign * binomial) * history[k];
96 }
97
98 double denom = 1.0;
99 for (size_t i = 0; i < N; ++i)
100 denom *= dt;
101
102 return acc / static_cast<S>(denom);
103}
104
105/**
106 * @brief Central difference approximation of the first derivative
107 * @tparam T Sample type
108 * @param history Buffer with capacity >= 3
109 * @param dt Elapsed time between consecutive samples
110 * @return (history[0] - history[2]) / (2 * dt)
111 *
112 * Second-order accurate versus the first-order accurate backward
113 * difference used by velocity(). Costs one extra sample of lag since
114 * the estimate is centered on history[1], not history[0].
115 */
116template <typename T>
117[[nodiscard]] inline T central_first_derivative(const Memory::HistoryBuffer<T>& history, double dt) noexcept
118{
119 using S = scalar_t<T>;
120 return (history[0] - history[2]) / static_cast<S>(2.0 * dt);
121}
122
123/**
124 * @brief Central difference approximation of the second derivative
125 * @tparam T Sample type
126 * @param history Buffer with capacity >= 3
127 * @param dt Elapsed time between consecutive samples
128 * @return (history[0] - 2*history[1] + history[2]) / dt^2
129 *
130 * Identical stencil to acceleration() since the standard central second
131 * difference and backward second difference coincide at this order.
132 * Provided as a named alias so call sites documenting derivation against
133 * central-difference tables do not need to reach for acceleration().
134 */
135template <typename T>
136[[nodiscard]] inline T central_second_derivative(const Memory::HistoryBuffer<T>& history, double dt) noexcept
137{
138 return backward_difference<2>(history, dt);
139}
140
141// =============================================================================
142// Named kinematic orders
143// =============================================================================
144
145/** @brief First difference: rate of change of position. Capacity >= 2. */
146template <typename T>
147[[nodiscard]] inline T velocity(const Memory::HistoryBuffer<T>& history, double dt) noexcept
148{
149 return backward_difference<1>(history, dt);
150}
151
152/** @brief Second difference: rate of change of velocity. Capacity >= 3. */
153template <typename T>
154[[nodiscard]] inline T acceleration(const Memory::HistoryBuffer<T>& history, double dt) noexcept
155{
156 return backward_difference<2>(history, dt);
157}
158
159/**
160 * @brief Third difference: rate of change of acceleration. Capacity >= 4.
161 *
162 * The gesture-relevant reading is abruptness: a stroke that suddenly
163 * changes how it is accelerating, distinct from acceleration itself
164 * which only says the speed is changing.
165 */
166template <typename T>
167[[nodiscard]] inline T jerk(const Memory::HistoryBuffer<T>& history, double dt) noexcept
168{
169 return backward_difference<3>(history, dt);
170}
171
172/** @brief Fourth difference: rate of change of jerk. Capacity >= 5. */
173template <typename T>
174[[nodiscard]] inline T jounce(const Memory::HistoryBuffer<T>& history, double dt) noexcept
175{
176 return backward_difference<4>(history, dt);
177}
178
179/** @brief Fifth difference: rate of change of jounce. Capacity >= 6. */
180template <typename T>
181[[nodiscard]] inline T crackle(const Memory::HistoryBuffer<T>& history, double dt) noexcept
182{
183 return backward_difference<5>(history, dt);
184}
185
186/** @brief Sixth difference: rate of change of crackle. Capacity >= 7. */
187template <typename T>
188[[nodiscard]] inline T pop(const Memory::HistoryBuffer<T>& history, double dt) noexcept
189{
190 return backward_difference<6>(history, dt);
191}
192
193// =============================================================================
194// Smoothed differences
195// =============================================================================
196
197/**
198 * @brief Velocity averaged over a short window rather than a single interval
199 * @tparam T Sample type
200 * @param history Buffer with capacity >= window + 1
201 * @param dt Elapsed time between consecutive samples
202 * @param window Number of intervals to average over, minimum 1
203 * @return Mean of the per-interval first differences across the window
204 *
205 * A single backward_difference<1> reading is sensitive to jitter on any
206 * one sample pair. Averaging several consecutive one-step differences
207 * trades responsiveness for stability, useful when the source stream
208 * (tablet, camera-derived tracking) carries sensor noise that would
209 * otherwise alias into a spurious high-frequency acceleration or jerk
210 * reading downstream.
211 */
212template <typename T>
213[[nodiscard]] inline T smoothed_velocity(const Memory::HistoryBuffer<T>& history, double dt, size_t window) noexcept
214{
215 using S = scalar_t<T>;
216 window = window < 1 ? 1 : window;
217 const auto s_dt = static_cast<S>(dt);
218 T acc = (history[0] - history[1]) / s_dt;
219 for (size_t i = 1; i < window; ++i)
220 acc = acc + (history[i] - history[i + 1]) / s_dt;
221 return acc / static_cast<S>(window);
222}
223
224/**
225 * @brief Simple moving average over the newest @p window samples
226 * @tparam T Sample type
227 * @param history Buffer with capacity >= window
228 * @param window Number of samples to average, minimum 1
229 * @return Mean of history[0..window-1]
230 *
231 * Not a derivative. Included alongside the differential family because
232 * a common pattern is smoothing the raw signal before differentiating it
233 * rather than smoothing the derivative after the fact; the two produce
234 * different noise characteristics and callers should be able to reach
235 * for either without leaving this file.
236 */
237template <typename T>
238[[nodiscard]] inline T moving_average(const Memory::HistoryBuffer<T>& history, size_t window) noexcept
239{
240 using S = scalar_t<T>;
241 window = window < 1 ? 1 : window;
242 T acc = history[0];
243 for (size_t i = 1; i < window; ++i)
244 acc = acc + history[i];
245 return acc / static_cast<S>(window);
246}
247
248// =============================================================================
249// Magnitude and direction (scalar T)
250// =============================================================================
251
252/**
253 * @brief Absolute value of a scalar first difference
254 * @param history Buffer with capacity >= 2
255 * @param dt Elapsed time between samples 0 and 1
256 * @return |velocity|, direction discarded
257 */
258[[nodiscard]] inline double speed(const Memory::HistoryBuffer<double>& history, double dt) noexcept
259{
260 return std::abs(velocity(history, dt));
261}
262
263/**
264 * @brief Sign of a scalar first difference
265 * @param history Buffer with capacity >= 2
266 * @param dt Elapsed time between samples 0 and 1
267 * @return -1.0, 0.0, or 1.0
268 */
269[[nodiscard]] inline double direction_sign(const Memory::HistoryBuffer<double>& history, double dt) noexcept
270{
271 const double v = velocity(history, dt);
272 return (v > 0.0) ? 1.0 : ((v < 0.0) ? -1.0 : 0.0);
273}
274
275// =============================================================================
276// Magnitude and direction (glm::vec2 / glm::vec3)
277// =============================================================================
278
279/**
280 * @brief Magnitude of a vec2 first difference
281 * @param history Buffer with capacity >= 2
282 * @param dt Elapsed time between samples 0 and 1
283 * @return Euclidean speed
284 */
285[[nodiscard]] inline float speed(const Memory::HistoryBuffer<glm::vec2>& history, double dt) noexcept
286{
287 return glm::length(velocity(history, dt));
288}
289
290/**
291 * @brief Magnitude of a vec3 first difference
292 * @param history Buffer with capacity >= 2
293 * @param dt Elapsed time between samples 0 and 1
294 * @return Euclidean speed
295 */
296[[nodiscard]] inline float speed(const Memory::HistoryBuffer<glm::vec3>& history, double dt) noexcept
297{
298 return glm::length(velocity(history, dt));
299}
300
301/**
302 * @brief Unit direction of a vec2 first difference
303 * @param history Buffer with capacity >= 2
304 * @param dt Elapsed time between samples 0 and 1
305 * @return Normalized velocity, or the zero vector when speed is
306 * below 1e-6 to avoid dividing by zero on a stationary point
307 */
308[[nodiscard]] inline glm::vec2 heading_vector(const Memory::HistoryBuffer<glm::vec2>& history, double dt) noexcept
309{
310 const glm::vec2 v = velocity(history, dt);
311 const float len = glm::length(v);
312 return (len > 1e-6F) ? (v / len) : glm::vec2(0.0F);
313}
314
315/**
316 * @brief Unit direction of a vec3 first difference
317 * @param history Buffer with capacity >= 2
318 * @param dt Elapsed time between samples 0 and 1
319 * @return Normalized velocity, or the zero vector when speed is
320 * below 1e-6 to avoid dividing by zero on a stationary point
321 */
322[[nodiscard]] inline glm::vec3 heading_vector(const Memory::HistoryBuffer<glm::vec3>& history, double dt) noexcept
323{
324 const glm::vec3 v = velocity(history, dt);
325 const float len = glm::length(v);
326 return (len > 1e-6F) ? (v / len) : glm::vec3(0.0F);
327}
328
329// =============================================================================
330// Angular quantities (glm::vec2)
331// =============================================================================
332
333/**
334 * @brief Heading angle of a 2D vector
335 * @param v Any vector, typically a velocity
336 * @return atan2(v.y, v.x) in radians, range (-pi, pi]
337 */
338[[nodiscard]] inline float heading(const glm::vec2& v) noexcept
339{
340 return std::atan2(v.y, v.x);
341}
342
343/**
344 * @brief Shortest signed angular difference between two headings
345 * @param to Target angle in radians
346 * @param from Source angle in radians
347 * @return Signed difference in (-pi, pi], wrapped correctly across the seam
348 *
349 * Plain subtraction (to - from) is wrong whenever the pair straddles the
350 * -pi/pi boundary, e.g. from = 3.0, to = -3.0 is a small turn, not a turn
351 * of nearly 2*pi. This wraps into the shortest equivalent angle first.
352 */
353[[nodiscard]] inline float angular_delta(float to, float from) noexcept
354{
355 float delta = to - from;
356 const float two_pi = 2.0F * std::numbers::pi_v<float>;
357 delta = std::fmod(delta + std::numbers::pi_v<float>, two_pi);
358 if (delta < 0.0F)
359 delta += two_pi;
360 return delta - std::numbers::pi_v<float>;
361}
362
363/**
364 * @brief Angular velocity from consecutive headings in a HistoryBuffer
365 * @param headings Buffer of heading angles in radians, capacity >= 2
366 * @param dt Elapsed time between samples 0 and 1
367 * @return Signed rate of heading change in radians per second, wrapped
368 * correctly across the -pi/pi seam
369 *
370 * Takes a HistoryBuffer<float> of already-computed headings rather than
371 * positions directly, since heading is itself derived (see heading()
372 * above) and keeping this function ignorant of that derivation keeps it
373 * reusable for any angle-producing source, not only motion.
374 */
375[[nodiscard]] inline float angular_velocity(const Memory::HistoryBuffer<float>& headings, double dt) noexcept
376{
377 return angular_delta(headings[0], headings[1]) / static_cast<float>(dt);
378}
379
380/**
381 * @brief 2D scalar cross product, sign of turn direction
382 * @param a First vector
383 * @param b Second vector
384 * @return a.x*b.y - a.y*b.x. Positive is CCW from a to b, negative CW.
385 */
386[[nodiscard]] inline float cross_2d(const glm::vec2& a, const glm::vec2& b) noexcept
387{
388 return a.x * b.y - a.y * b.x;
389}
390
391/**
392 * @brief Signed curvature from velocity and acceleration
393 * @param vel Velocity vector
394 * @param accel Acceleration vector
395 * @param min_speed Speed below which curvature is reported as 0 rather
396 * than computed. Default 1e-3, not 1e-6: curvature divides by
397 * speed^3, so at real input scales (e.g. normalized 0..1 tablet
398 * coordinates per second, where a slow-moving stroke is speed
399 * 0.05-0.5) a guard near machine epsilon never fires, and the
400 * cubic denominator still amplifies ordinary acceleration noise
401 * into numbers in the thousands. The threshold has to be picked
402 * relative to the caller's actual speed scale, not left at a
403 * constant close to zero; 1e-3 is a reasonable default for
404 * normalized 0..1 spatial data but a caller working in different
405 * units (pixels, millimeters) should pass one appropriate to that
406 * scale rather than rely on this default.
407 * @return (vx*ay - vy*ax) / |v|^3, or 0 when speed is below min_speed
408 *
409 * dtheta/ds rather than dtheta/dt: speed-independent, so a sharp corner
410 * drawn slowly and the same corner drawn fast report the same curvature,
411 * unlike angular_velocity which conflates turn sharpness with pace.
412 * Positive is a leftward (CCW) bend, negative rightward. Even with a
413 * correctly scaled min_speed, curvature remains numerically sensitive
414 * near that threshold since it is still a cubic denominator; treat
415 * curvature values from low-speed samples as low-confidence regardless
416 * of whether they cleared the guard.
417 */
418[[nodiscard]] inline float curvature(const glm::vec2& vel, const glm::vec2& accel, float min_speed = 1e-3F) noexcept
419{
420 const float speed_sq = glm::dot(vel, vel);
421 const float spd = std::sqrt(speed_sq);
422 if (spd < min_speed)
423 return 0.0F;
424 return cross_2d(vel, accel) / (speed_sq * spd);
425}
426
427/**
428 * @brief Signed curvature computed directly from a position HistoryBuffer
429 * @param history Buffer of positions with capacity >= 3
430 * @param dt Elapsed time between consecutive samples
431 * @param min_speed Forwarded to curvature(); see its doc for why the
432 * default is scale-relative rather than machine-epsilon.
433 * @return curvature(velocity(history, dt), acceleration(history, dt), min_speed)
434 *
435 * Convenience wrapper chaining the two differences a caller would
436 * otherwise compute separately before calling curvature() above.
437 */
438[[nodiscard]] inline float curvature_from_history(const Memory::HistoryBuffer<glm::vec2>& history, double dt, float min_speed = 1e-3F) noexcept
439{
440 return curvature(velocity(history, dt), acceleration(history, dt), min_speed);
441}
442
443// =============================================================================
444// Windowed path-shape measures (glm::vec2)
445// =============================================================================
446
447/**
448 * @brief Path length accumulated across the newest @p window positions
449 * @param history Buffer of positions, capacity >= window
450 * @param window Number of samples spanning window - 1 segments, minimum 2
451 * @return Sum of consecutive segment lengths
452 */
453[[nodiscard]] inline float path_length(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
454{
455 window = window < 2 ? 2 : window;
456 float len = 0.0F;
457 for (size_t i = 0; i + 1 < window; ++i)
458 len += glm::length(history[i] - history[i + 1]);
459 return len;
460}
461
462/**
463 * @brief Net displacement across the newest @p window positions
464 * @param history Buffer of positions, capacity >= window
465 * @param window Number of samples, minimum 2
466 * @return Straight-line distance from history[window-1] to history[0]
467 */
468[[nodiscard]] inline float net_displacement(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
469{
470 window = window < 2 ? 2 : window;
471 return glm::length(history[0] - history[window - 1]);
472}
473
474/**
475 * @brief Straightness of a path over a window, 1.0 is a straight line
476 * @param history Buffer of positions, capacity >= window
477 * @param window Number of samples, minimum 2
478 * @return net_displacement / path_length, or 0 when path_length is
479 * below 1e-6 to avoid dividing by a stationary point
480 *
481 * A gesture that doubles back on itself drives this toward 0 even
482 * though individual segment speeds may be high; a gesture that moves
483 * directly toward one point holds this near 1.0 regardless of speed.
484 */
485[[nodiscard]] inline float straightness(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
486{
487 const float len = path_length(history, window);
488 if (len < 1e-6F)
489 return 0.0F;
490 return net_displacement(history, window) / len;
491}
492
493/**
494 * @brief Total absolute turning accumulated across a window
495 * @param history Buffer of positions, capacity >= window
496 * @param window Number of samples, minimum 3
497 * @return Sum of |angular_delta| between consecutive segment headings
498 *
499 * A jitteriness measure distinct from curvature at a point: a path that
500 * wiggles back and forth accumulates large total turning even if its net
501 * curvature at any single sample is small, since positive and negative
502 * bends do not cancel here the way they would in a single derivative.
503 */
504[[nodiscard]] inline float total_turning(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
505{
506 window = window < 3 ? 3 : window;
507 float total = 0.0F;
508 float prev_heading = heading(history[0] - history[1]);
509 for (size_t i = 1; i + 1 < window; ++i) {
510 const float h = heading(history[i] - history[i + 1]);
511 total += std::abs(angular_delta(h, prev_heading));
512 prev_heading = h;
513 }
514 return total;
515}
516
517/**
518 * @brief Mean position across a window
519 * @param history Buffer of positions, capacity >= window
520 * @param window Number of samples, minimum 1
521 * @return Centroid of history[0..window-1]
522 */
523[[nodiscard]] inline glm::vec2 centroid(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
524{
525 return moving_average(history, window);
526}
527
528/**
529 * @brief Bounding radius of a window around its centroid
530 * @param history Buffer of positions, capacity >= window
531 * @param window Number of samples, minimum 1
532 * @return Maximum distance from centroid to any sample in the window
533 *
534 * A cheap containment/extent measure: how large a circle a gesture
535 * currently occupies, independent of how much path length it traced
536 * to get there. A tight scribble and a single large slow circle can
537 * have similar path_length but very different spread.
538 */
539[[nodiscard]] inline float spread_radius(const Memory::HistoryBuffer<glm::vec2>& history, size_t window) noexcept
540{
541 const glm::vec2 c = centroid(history, window);
542 float max_dist = 0.0F;
543 for (size_t i = 0; i < window; ++i)
544 max_dist = std::max(max_dist, glm::length(history[i] - c));
545 return max_dist;
546}
547
548// =============================================================================
549// Convenience overloads: raw sample spans, HistoryBuffer built inline
550//
551// Every function above takes a HistoryBuffer<T>& because that is the
552// correct long-lived object for a caller pushing one sample per frame.
553// These overloads exist for the other common shape: a caller already
554// holding a handful of samples in a plain span (a small stack array
555// pulled off a queue, a window sliced from a larger recording, values
556// read straight out of TabletContext::frame) who does not want to stand
557// up a HistoryBuffer just to call one of the functions above once.
558//
559// Ordering matches HistoryBuffer convention: samples[0] is newest,
560// samples[k] is k steps back. A HistoryBuffer is constructed with
561// capacity equal to samples.size() and pushed in reverse so that
562// history[0] ends up holding samples[0], matching what the caller
563// would get from pushing samples live in newest-last arrival order.
564// =============================================================================
565
566/**
567 * @brief Build a HistoryBuffer<T> from a newest-first span
568 * @tparam T Sample type
569 * @param samples Span with samples[0] newest, samples[N-1] oldest
570 * @return HistoryBuffer<T> of capacity samples.size() with matching contents
571 *
572 * Exposed directly since several call sites below only need this step
573 * once before calling a HistoryBuffer<T>& overload repeatedly, e.g.
574 * comparing several difference orders against the same window without
575 * rebuilding it per call.
576 */
577template <typename T>
578[[nodiscard]] inline Memory::HistoryBuffer<T> to_history(std::span<const T> samples) noexcept
579{
580 Memory::HistoryBuffer<T> history(samples.size());
581 for (size_t i = samples.size(); i-- > 0;)
582 history.push(samples[i]);
583 return history;
584}
585
586/** @brief Convenience overload of backward_difference<N> over a raw span. */
587template <size_t N, typename T>
588[[nodiscard]] inline T backward_difference(std::span<const T> samples, double dt) noexcept
589{
590 return backward_difference<N>(to_history(samples), dt);
591}
592
593/** @brief Convenience overload of forward_difference<N> over a raw span. */
594template <size_t N, typename T>
595[[nodiscard]] inline T forward_difference(std::span<const T> samples, double dt) noexcept
596{
597 return forward_difference<N>(to_history(samples), dt);
598}
599
600/** @brief Convenience overload of central_first_derivative over a raw span. */
601template <typename T>
602[[nodiscard]] inline T central_first_derivative(std::span<const T> samples, double dt) noexcept
603{
604 return central_first_derivative(to_history(samples), dt);
605}
606
607/** @brief Convenience overload of central_second_derivative over a raw span. */
608template <typename T>
609[[nodiscard]] inline T central_second_derivative(std::span<const T> samples, double dt) noexcept
610{
611 return central_second_derivative(to_history(samples), dt);
612}
613
614/** @brief Convenience overload of velocity over a raw span, samples[0] newest. */
615template <typename T>
616[[nodiscard]] inline T velocity(std::span<const T> samples, double dt) noexcept
617{
618 return velocity(to_history(samples), dt);
619}
620
621/** @brief Convenience overload of acceleration over a raw span, samples[0] newest. */
622template <typename T>
623[[nodiscard]] inline T acceleration(std::span<const T> samples, double dt) noexcept
624{
625 return acceleration(to_history(samples), dt);
626}
627
628/** @brief Convenience overload of jerk over a raw span, samples[0] newest. */
629template <typename T>
630[[nodiscard]] inline T jerk(std::span<const T> samples, double dt) noexcept
631{
632 return jerk(to_history(samples), dt);
633}
634
635/** @brief Convenience overload of jounce over a raw span, samples[0] newest. */
636template <typename T>
637[[nodiscard]] inline T jounce(std::span<const T> samples, double dt) noexcept
638{
639 return jounce(to_history(samples), dt);
640}
641
642/** @brief Convenience overload of crackle over a raw span, samples[0] newest. */
643template <typename T>
644[[nodiscard]] inline T crackle(std::span<const T> samples, double dt) noexcept
645{
646 return crackle(to_history(samples), dt);
647}
648
649/** @brief Convenience overload of pop over a raw span, samples[0] newest. */
650template <typename T>
651[[nodiscard]] inline T pop(std::span<const T> samples, double dt) noexcept
652{
653 return pop(to_history(samples), dt);
654}
655
656/** @brief Convenience overload of smoothed_velocity over a raw span, samples[0] newest. */
657template <typename T>
658[[nodiscard]] inline T smoothed_velocity(std::span<const T> samples, double dt, size_t window) noexcept
659{
660 return smoothed_velocity(to_history(samples), dt, window);
661}
662
663/** @brief Convenience overload of moving_average over a raw span, samples[0] newest. */
664template <typename T>
665[[nodiscard]] inline T moving_average(std::span<const T> samples, size_t window) noexcept
666{
667 return moving_average(to_history(samples), window);
668}
669
670/** @brief Convenience overload of speed(double) over a raw span, samples[0] newest. */
671[[nodiscard]] inline double speed(std::span<const double> samples, double dt) noexcept
672{
673 return speed(to_history(samples), dt);
674}
675
676/** @brief Convenience overload of direction_sign over a raw span, samples[0] newest. */
677[[nodiscard]] inline double direction_sign(std::span<const double> samples, double dt) noexcept
678{
679 return direction_sign(to_history(samples), dt);
680}
681
682/** @brief Convenience overload of speed(vec2) over a raw span, samples[0] newest. */
683[[nodiscard]] inline float speed(std::span<const glm::vec2> samples, double dt) noexcept
684{
685 return speed(to_history(samples), dt);
686}
687
688/** @brief Convenience overload of speed(vec3) over a raw span, samples[0] newest. */
689[[nodiscard]] inline float speed(std::span<const glm::vec3> samples, double dt) noexcept
690{
691 return speed(to_history(samples), dt);
692}
693
694/** @brief Convenience overload of heading_vector(vec2) over a raw span, samples[0] newest. */
695[[nodiscard]] inline glm::vec2 heading_vector(std::span<const glm::vec2> samples, double dt) noexcept
696{
697 return heading_vector(to_history(samples), dt);
698}
699
700/** @brief Convenience overload of heading_vector(vec3) over a raw span, samples[0] newest. */
701[[nodiscard]] inline glm::vec3 heading_vector(std::span<const glm::vec3> samples, double dt) noexcept
702{
703 return heading_vector(to_history(samples), dt);
704}
705
706/** @brief Convenience overload of angular_velocity over a raw span of headings, samples[0] newest. */
707[[nodiscard]] inline float angular_velocity(std::span<const float> headings, double dt) noexcept
708{
709 return angular_velocity(to_history(headings), dt);
710}
711
712/** @brief Convenience overload of curvature_from_history over a raw span of positions, samples[0] newest. */
713[[nodiscard]] inline float curvature_from_history(std::span<const glm::vec2> samples, double dt, float min_speed = 1e-3F) noexcept
714{
715 return curvature_from_history(to_history(samples), dt, min_speed);
716}
717
718/** @brief Convenience overload of path_length over a raw span of positions, samples[0] newest. */
719[[nodiscard]] inline float path_length(std::span<const glm::vec2> samples, size_t window) noexcept
720{
721 return path_length(to_history(samples), window);
722}
723
724/** @brief Convenience overload of net_displacement over a raw span of positions, samples[0] newest. */
725[[nodiscard]] inline float net_displacement(std::span<const glm::vec2> samples, size_t window) noexcept
726{
727 return net_displacement(to_history(samples), window);
728}
729
730/** @brief Convenience overload of straightness over a raw span of positions, samples[0] newest. */
731[[nodiscard]] inline float straightness(std::span<const glm::vec2> samples, size_t window) noexcept
732{
733 return straightness(to_history(samples), window);
734}
735
736/** @brief Convenience overload of total_turning over a raw span of positions, samples[0] newest. */
737[[nodiscard]] inline float total_turning(std::span<const glm::vec2> samples, size_t window) noexcept
738{
739 return total_turning(to_history(samples), window);
740}
741
742/** @brief Convenience overload of centroid over a raw span of positions, samples[0] newest. */
743[[nodiscard]] inline glm::vec2 centroid(std::span<const glm::vec2> samples, size_t window) noexcept
744{
745 return centroid(to_history(samples), window);
746}
747
748/** @brief Convenience overload of spread_radius over a raw span of positions, samples[0] newest. */
749[[nodiscard]] inline float spread_radius(std::span<const glm::vec2> samples, size_t window) noexcept
750{
751 return spread_radius(to_history(samples), window);
752}
753
754} // namespace MayaFlux::Kinesis
#define N(method_name, full_type_name)
Definition Creator.hpp:106
uint32_t h
Definition InkPress.cpp:28
size_t a
size_t b
float k
void push(const T &value)
Push new value to front of history.
History buffer for difference equations and recursive relations.
double direction_sign(const Memory::HistoryBuffer< double > &history, double dt) noexcept
Sign of a scalar first difference.
T acceleration(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Second difference: rate of change of velocity.
T forward_difference(const Memory::HistoryBuffer< T > &history, double dt) noexcept
N-th order forward finite difference, expressed on a HistoryBuffer.
T moving_average(const Memory::HistoryBuffer< T > &history, size_t window) noexcept
Simple moving average over the newest window samples.
glm::vec2 centroid(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Mean position across a window.
float angular_delta(float to, float from) noexcept
Shortest signed angular difference between two headings.
T velocity(const Memory::HistoryBuffer< T > &history, double dt) noexcept
First difference: rate of change of position.
float spread_radius(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Bounding radius of a window around its centroid.
float curvature_from_history(const Memory::HistoryBuffer< glm::vec2 > &history, double dt, float min_speed=1e-3F) noexcept
Signed curvature computed directly from a position HistoryBuffer.
T smoothed_velocity(const Memory::HistoryBuffer< T > &history, double dt, size_t window) noexcept
Velocity averaged over a short window rather than a single interval.
float cross_2d(const glm::vec2 &a, const glm::vec2 &b) noexcept
2D scalar cross product, sign of turn direction
float curvature(const glm::vec2 &vel, const glm::vec2 &accel, float min_speed=1e-3F) noexcept
Signed curvature from velocity and acceleration.
T central_first_derivative(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Central difference approximation of the first derivative.
float straightness(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Straightness of a path over a window, 1.0 is a straight line.
float path_length(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Path length accumulated across the newest window positions.
T crackle(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Fifth difference: rate of change of jounce.
T pop(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Sixth difference: rate of change of crackle.
double speed(const Memory::HistoryBuffer< double > &history, double dt) noexcept
Absolute value of a scalar first difference.
T central_second_derivative(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Central difference approximation of the second derivative.
Memory::HistoryBuffer< T > to_history(std::span< const T > samples) noexcept
Build a HistoryBuffer<T> from a newest-first span.
float total_turning(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Total absolute turning accumulated across a window.
float net_displacement(const Memory::HistoryBuffer< glm::vec2 > &history, size_t window) noexcept
Net displacement across the newest window positions.
float heading(const glm::vec2 &v) noexcept
Heading angle of a 2D vector.
float angular_velocity(const Memory::HistoryBuffer< float > &headings, double dt) noexcept
Angular velocity from consecutive headings in a HistoryBuffer.
T backward_difference(const Memory::HistoryBuffer< T > &history, double dt) noexcept
N-th order backward finite difference of a HistoryBuffer, scaled by dt^N.
T jounce(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Fourth difference: rate of change of jerk.
T jerk(const Memory::HistoryBuffer< T > &history, double dt) noexcept
Third difference: rate of change of acceleration.
glm::vec2 heading_vector(const Memory::HistoryBuffer< glm::vec2 > &history, double dt) noexcept
Unit direction of a vec2 first difference.