// gestures.js — two-finger touch: how a pair of fingers moved between two moments. scale > 1: spread (zoom in);
// rot: degrees the pair turned, clockwise on screen (y down) positive; mid / move: their midpoint and how it moved;
// tilt: px both fingers moved vertically together (0 when they went opposite ways, as in a pinch along y).
export function twoFinger(a0, b0, a1, b1) {
const d0 = Math.hypot(b0.x - a0.x, b0.y - a0.y), d1 = Math.hypot(b1.x - a1.x, b1.y - a1.y);
let rot = (Math.atan2(b1.y - a1.y, b1.x - a1.x) - Math.atan2(b0.y - a0.y, b0.x - a0.x)) * 180 / Math.PI;
rot = ((rot + 540) % 360) - 180; // the short way round
const mid = { x: (a1.x + b1.x) / 2, y: (a1.y + b1.y) / 2 };
const move = { x: mid.x - (a0.x + b0.x) / 2, y: mid.y - (a0.y + b0.y) / 2 };
const da = a1.y - a0.y, db = b1.y - b0.y;
const tilt = da * db > 0 ? (Math.abs(da) < Math.abs(db) ? da : db) : 0; // the smaller shared move
return { scale: d0 > 0 && d1 > 0 ? d1 / d0 : 1, rot, mid, move, tilt };
}
// Touch pointers by id, in element coordinates. Two at once = a gesture. Browsers send one move per finger, so a
// shared vertical move (tilt) is pieced together: each finger's vertical travel adds up until both went the same way.
export class Touches {
constructor() { this.pts = new Map(); this.dy = new Map(); }
get size() { return this.pts.size; }
down(id, at) { this.pts.set(id, at); this.dy.clear(); }
up(id) { this.pts.delete(id); this.dy.clear(); }
pair() { return [...this.pts.values()].slice(0, 2); }
// a pointer moved: the gesture since the last move (null unless two fingers are down)
move(id, at) {
if (!this.pts.has(id)) return null;
const before = this.pair(), was = this.pts.get(id);
this.pts.set(id, at);
if (this.pts.size < 2) return null;
const g = twoFinger(...before, ...this.pair());
const ids = [...this.pts.keys()].slice(0, 2);
if (!ids.includes(id)) return { ...g, tilt: 0 };
this.dy.set(id, (this.dy.get(id) ?? 0) + at.y - was.y);
const [a, b] = ids.map(k => this.dy.get(k) ?? 0);
let tilt = 0;
if (a * b > 0) { // both went the same way: the smaller part tilts
tilt = Math.abs(a) < Math.abs(b) ? a : b;
for (const k of ids) this.dy.set(k, this.dy.get(k) - tilt);
} else if (a * b < 0) this.dy.clear(); // opposite ways: a pinch, not a tilt
return { ...g, tilt };
}
other(id) { for (const [k, v] of this.pts) if (k !== id) return { id: k, at: v }; return null; }
}