123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- // Utilities
- import { CircularBuffer } from "../util/index.mjs";
- const HORIZON = 100; // ms
- const HISTORY = 20; // number of samples to keep
- /** @see https://android.googlesource.com/platform/frameworks/native/+/master/libs/input/VelocityTracker.cpp */
- function kineticEnergyToVelocity(work) {
- const sqrt2 = 1.41421356237;
- return (work < 0 ? -1.0 : 1.0) * Math.sqrt(Math.abs(work)) * sqrt2;
- }
- /**
- * Returns pointer velocity in px/s
- */
- export function calculateImpulseVelocity(samples) {
- // The input should be in reversed time order (most recent sample at index i=0)
- if (samples.length < 2) {
- // if 0 or 1 points, velocity is zero
- return 0;
- }
- // if (samples[1].t > samples[0].t) {
- // // Algorithm will still work, but not perfectly
- // consoleWarn('Samples provided to calculateImpulseVelocity in the wrong order')
- // }
- if (samples.length === 2) {
- // if 2 points, basic linear calculation
- if (samples[1].t === samples[0].t) {
- // consoleWarn(`Events have identical time stamps t=${samples[0].t}, setting velocity = 0`)
- return 0;
- }
- return (samples[1].d - samples[0].d) / (samples[1].t - samples[0].t);
- }
- // Guaranteed to have at least 3 points here
- // start with the oldest sample and go forward in time
- let work = 0;
- for (let i = samples.length - 1; i > 0; i--) {
- if (samples[i].t === samples[i - 1].t) {
- // consoleWarn(`Events have identical time stamps t=${samples[i].t}, skipping sample`)
- continue;
- }
- const vprev = kineticEnergyToVelocity(work); // v[i-1]
- const vcurr = (samples[i].d - samples[i - 1].d) / (samples[i].t - samples[i - 1].t); // v[i]
- work += (vcurr - vprev) * Math.abs(vcurr);
- if (i === samples.length - 1) {
- work *= 0.5;
- }
- }
- return kineticEnergyToVelocity(work) * 1000;
- }
- export function useVelocity() {
- const touches = {};
- function addMovement(e) {
- Array.from(e.changedTouches).forEach(touch => {
- const samples = touches[touch.identifier] ?? (touches[touch.identifier] = new CircularBuffer(HISTORY));
- samples.push([e.timeStamp, touch]);
- });
- }
- function endTouch(e) {
- Array.from(e.changedTouches).forEach(touch => {
- delete touches[touch.identifier];
- });
- }
- function getVelocity(id) {
- const samples = touches[id]?.values().reverse();
- if (!samples) {
- throw new Error(`No samples for touch id ${id}`);
- }
- const newest = samples[0];
- const x = [];
- const y = [];
- for (const val of samples) {
- if (newest[0] - val[0] > HORIZON) break;
- x.push({
- t: val[0],
- d: val[1].clientX
- });
- y.push({
- t: val[0],
- d: val[1].clientY
- });
- }
- return {
- x: calculateImpulseVelocity(x),
- y: calculateImpulseVelocity(y),
- get direction() {
- const {
- x,
- y
- } = this;
- const [absX, absY] = [Math.abs(x), Math.abs(y)];
- return absX > absY && x >= 0 ? 'right' : absX > absY && x <= 0 ? 'left' : absY > absX && y >= 0 ? 'down' : absY > absX && y <= 0 ? 'up' : oops();
- }
- };
- }
- return {
- addMovement,
- endTouch,
- getVelocity
- };
- }
- function oops() {
- throw new Error();
- }
- //# sourceMappingURL=touch.mjs.map
|