/** * One-Euro Filter(速度自适应低通滤波器) * 参考:Casiez et al., "1€ Filter: A Simple Speed-based Low-pass Filter for Noisy Input in Interactive Systems", CHI 2012 * https://cristal.univ-lille.fr/~casiez/1euro/ * * 用法:每个轴一个实例 * const f = createOneEuroFilter({ mincutoff: 0.2, beta: 0.05, dcutoff: 1.0 }) * const out = f.filter(x, performance.now()) * f.reset() * * @param {object} [opts] * @param {number} [opts.mincutoff=0.2] - Hz,静止截止频率 * @param {number} [opts.beta=0.05] - 速度系数(越大快速运动越跟手) * @param {number} [opts.dcutoff=1.0] - Hz,速度估计的截止频率 */ export function createOneEuroFilter({ mincutoff = 0.2, beta = 0.05, dcutoff = 1.0 } = {}) { let xPrev = null // 上次 FILTERED 输出(与 Casiez 论文一致) let dxPrev = 0 // 平滑后的速度 let tPrev = null // 上次时间戳 function smoothingFactor(cutoff, dt) { const r = 2 * Math.PI * cutoff * dt return r / (r + 1) } function filter(x, t) { // 首帧:直接接受,建立基线 if (xPrev == null || tPrev == null) { xPrev = x dxPrev = 0 tPrev = t return x } const dt = Math.max((t - tPrev) / 1000, 1e-6) // ms → s // 1. 速度差分(用 filtered xPrev,与 Casiez 一致) const dx = (x - xPrev) / dt // 2. 平滑速度 const aD = smoothingFactor(dcutoff, dt) const edx = aD * dx + (1 - aD) * dxPrev // 3. 自适应截止频率 const cutoff = mincutoff + beta * Math.abs(edx) // 4. 平滑位置 const a = smoothingFactor(cutoff, dt) const result = a * x + (1 - a) * xPrev xPrev = result dxPrev = edx tPrev = t return result } function reset() { xPrev = null dxPrev = 0 tPrev = null } return { filter, reset } } /** * 取单调递增的高精度时间戳(毫秒)。 * 优先 performance.now()(sub-ms 精度),回退 Date.now()(WebView 上 ~15ms 精度)。 */ export function nowMs() { if (typeof performance !== 'undefined' && typeof performance.now === 'function') { return performance.now() } return Date.now() }