-
-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathpinch-scale.js
More file actions
100 lines (92 loc) · 3.64 KB
/
Copy pathpinch-scale.js
File metadata and controls
100 lines (92 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Pure pinch-to-resize math for WebXR AR placement (no DOM, no THREE).
//
// A two-finger pinch during an immersive-ar session resizes the placed agent,
// matching the native Scene Viewer / Quick Look gesture users already know.
// The state machine is deliberately tiny and allocation-free so the session
// controller (src/ar/webxr.js) can drive it from raw touch points every frame:
//
// const p = createPinchState();
// pinchStart(p, dist, currentScale) // two fingers down
// pinchMove(p, dist) → new scale // fingers move (null = not pinching)
// pinchEnd(p) → final scale // a finger lifts (null = wasn't pinching)
//
// Scale is the ratio of the current finger distance to the distance at pinch
// start, applied to the scale the content already had — so consecutive pinches
// compose naturally instead of snapping back to 1. Clamped to sane bounds: a
// life-size avatar squeezed to a desk figurine (MIN) or grown to a statue (MAX).
export const PINCH_SCALE_MIN = 0.25;
export const PINCH_SCALE_MAX = 4;
/** Minimum finger distance (px) considered a real pinch — rejects palm noise. */
export const PINCH_DEADZONE_PX = 24;
export function createPinchState() {
return { active: false, startDist: 0, baseScale: 1, scale: 1 };
}
/**
* Begin a pinch. Ignored (returns false) when the finger spread is inside the
* dead zone or inputs are not finite — the caller keeps treating input as taps.
*
* @param {ReturnType<createPinchState>} p
* @param {number} dist Distance between the two touch points, px.
* @param {number} baseScale The content's current uniform scale.
* @returns {boolean} true when the pinch is engaged.
*/
export function pinchStart(p, dist, baseScale) {
if (!Number.isFinite(dist) || dist < PINCH_DEADZONE_PX) return false;
const base = Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1;
p.active = true;
p.startDist = dist;
p.baseScale = base;
p.scale = base;
return true;
}
/**
* Advance an engaged pinch. Returns the new clamped scale, or null when no
* pinch is engaged or the distance is degenerate (caller applies nothing).
*
* @param {ReturnType<createPinchState>} p
* @param {number} dist Current distance between the two touch points, px.
* @returns {number|null}
*/
export function pinchMove(p, dist) {
if (!p.active || !Number.isFinite(dist) || dist <= 0 || p.startDist <= 0) return null;
const next = p.baseScale * (dist / p.startDist);
p.scale = Math.min(PINCH_SCALE_MAX, Math.max(PINCH_SCALE_MIN, next));
return p.scale;
}
/**
* End the pinch (a finger lifted). Returns the final scale to persist, or
* null when no pinch was engaged (a plain tap — nothing to save).
*
* @param {ReturnType<createPinchState>} p
* @returns {number|null}
*/
export function pinchEnd(p) {
if (!p.active) return null;
p.active = false;
return p.scale;
}
/**
* Clamp a persisted pin scale for rendering. Anything non-finite or
* non-positive (legacy pins, absent column) renders at natural size.
*
* @param {unknown} v Raw anchor_scale from the API (may be null/undefined).
* @returns {number}
*/
export function clampPinScale(v) {
const n = Number(v);
if (!Number.isFinite(n) || n <= 0) return 1;
return Math.min(PINCH_SCALE_MAX, Math.max(PINCH_SCALE_MIN, n));
}
/**
* Euclidean distance between two touch points, in CSS pixels — the raw
* geometry both AR pinch lanes (WebXR's dom-overlay and camera-mode's plain
* canvas) feed into pinchStart/pinchMove.
*
* @param {TouchList|Touch[]} touches
* @returns {number}
*/
export function touchDist(touches) {
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
return Math.hypot(dx, dy);
}