topfans/frontend/composables/useOneEuroFilter.js

75 lines
2.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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()
}