// 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; } }