From 3443c1c65e9f1753e1e656b35d08416c1fa298f2 Mon Sep 17 00:00:00 2001 From: godosa Date: Wed, 7 Oct 2026 00:14:38 +0200 Subject: worldmap-viewer: initial public history --- viewer/tools/smoke/touch.mjs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 viewer/tools/smoke/touch.mjs (limited to 'viewer/tools/smoke/touch.mjs') diff --git a/viewer/tools/smoke/touch.mjs b/viewer/tools/smoke/touch.mjs new file mode 100644 index 0000000..55405fd --- /dev/null +++ b/viewer/tools/smoke/touch.mjs @@ -0,0 +1,42 @@ +// touch.mjs — two-finger gestures on a phone: the map follows the fingers (twist turns it with them, a spread zooms +// in about them, both fingers up tilt the globe); flat maps pinch about the fingers. +export const viewport = { width: 390, height: 844 }; +const helpers = `const a = window.mapViewer, c = document.querySelector('#map canvas'), r = c.getBoundingClientRect(); + const ev = (t, id, x, y) => c.dispatchEvent(new PointerEvent(t, { clientX: r.left + x, clientY: r.top + y, bubbles: true, + pointerId: id, pointerType: 'touch', isPrimary: id === 11, button: 0, buttons: t === 'pointerup' ? 0 : 1 })); + const gesture = (from, to, steps = 10) => { // from/to: [[x, y], [x, y]] for fingers 11 and 12 + ev('pointerdown', 11, ...from[0]); ev('pointerdown', 12, ...from[1]); + for (let s = 1; s <= steps; s++) for (const [k, id] of [[0, 11], [1, 12]]) + ev('pointermove', id, from[k][0] + (to[k][0] - from[k][0]) * s / steps, from[k][1] + (to[k][1] - from[k][1]) * s / steps); + ev('pointerup', 11, ...to[0]); ev('pointerup', 12, ...to[1]); + }; + const W = r.width, H = r.height, cx = W / 2, cy = H / 2;`; +export const steps = [ + { name: 'boots', wait: 5000, expect: v => v, js: `window.mapViewer.setView('globe'); return !!window.mapViewer.view.pose;` }, + { name: 'twist clockwise 30° (about the view centre): a ground point turns ~30° clockwise with the fingers', expect: v => Math.abs(v - 30) < 6, + js: `${helpers} + a.view.setPose({ ...a.view.pose, alt: undefined, pitch: 0, range: a.view.pose.range }); + const p = a.view.pick(cx + 60, cy, false); const s0 = a.view.toScreen(p.lat, p.lon); + const R = 80, rad = d => d * Math.PI / 180, at = d => [cx + R * Math.cos(rad(d)), cy + R * Math.sin(rad(d))]; + gesture([at(180), at(0)], [at(210), at(30)]); + const s1 = a.view.toScreen(p.lat, p.lon); + return (Math.atan2(s1.y - cy, s1.x - cx) - Math.atan2(s0.y - cy, s0.x - cx)) * 180 / Math.PI;` }, + { name: 'spread to twice the distance: zoomed in ~2× (range halves)', expect: v => Math.abs(v - 0.5) < 0.08, + js: `${helpers} const r0 = a.view.pose.range; gesture([[cx - 40, cy], [cx + 40, cy]], [[cx - 80, cy], [cx + 80, cy]]); return a.view.pose.range / r0;` }, + { name: 'both fingers up: the globe tilts toward the horizon', expect: v => v > 5, + js: `${helpers} a.view.setPose({ ...a.view.pose, range: a.view.pose.range * 0.02, pitch: 0 }); const p0 = a.view.pose.pitch; gesture([[cx - 60, cy + 100], [cx + 60, cy + 100]], [[cx - 60, cy], [cx + 60, cy]]); return a.view.pose.pitch - p0;` }, + { name: 'one finger after a pinch keeps panning (no jump, no stuck drag)', expect: v => v.moved && !v.jump, + js: `${helpers} a.view.setPose({ ...a.view.pose, pitch: 0 }); + const before = { ...a.view.pose }; + ev('pointerdown', 11, cx - 40, cy); ev('pointerdown', 12, cx + 40, cy); ev('pointermove', 12, cx + 45, cy); ev('pointerup', 12, cx + 45, cy); + const mid = { ...a.view.pose }; + ev('pointermove', 11, cx - 40, cy + 30); ev('pointerup', 11, cx - 40, cy + 30); + const after = a.view.pose; + return { moved: Math.abs(after.lat - mid.lat) > 1e-4, jump: Math.abs(after.lat - mid.lat) > 20 };` }, + { name: 'flat map: spread zooms 2× about the fingers (the point between them stays put)', expect: v => Math.abs(v.z - 2) < 0.1 && v.drift < 3, + js: `${helpers} a.setView('equal_earth'); a.view.setState({ lat: 0, lon: 0, zoom: 2 }); + const z0 = a.view.getState().zoom, mx = cx + 30, my = cy + 20, p = a.view.pick(mx, my, false); + gesture([[mx - 40, my], [mx + 40, my]], [[mx - 80, my], [mx + 80, my]]); + const s = a.view.toScreen(p.lat, p.lon), z = a.view.getState().zoom / z0; a.setView('globe'); + return { z, drift: Math.hypot(s.x - mx, s.y - my) };` }, +]; -- cgit