// sampling.js —— 关键帧采样(纯函数)。移植自原型 evaluator.js。 // // clip 的关键帧值是**相对 setup pose 的增量**(drot/dx/dy/dscale),未给的属性 // 回落默认(drot/dx/dy=0、dscale=1)。本文件只把「一个 clip + 已播时长」算成 // 「每根 bone 的当前增量」,不碰轨道叠加、不碰世界矩阵——那些在 anim-state / evaluator。 import { lerp, PROP_DEFAULT } from './matrix.js' /** * 在一组关键帧里采样某个属性(线性插值,区间外夹到首 / 末帧)。 * @param {import('./types.js').Keyframe[]} keys * @param {number} t 归一化时间 0..1 * @param {'drot'|'dx'|'dy'|'dscale'} prop * @returns {number} */ export function sampleProp(keys, t, prop) { const def = PROP_DEFAULT[prop] if (keys.length === 0) return def if (t <= keys[0].t) return keys[0][prop] ?? def const last = keys[keys.length - 1] if (t >= last.t) return last[prop] ?? def for (let i = 0; i < keys.length - 1; i++) { const k0 = keys[i] const k1 = keys[i + 1] if (t >= k0.t && t <= k1.t) { const span = k1.t - k0.t || 1 const f = (t - k0.t) / span return lerp(k0[prop] ?? def, k1[prop] ?? def, f) } } return def } /** * 归一化播放进度。pose 恒为 0;loop 取模循环;oneshot 停在末帧(夹到 1)。 * @param {import('./types.js').AnimationClip} clip * @param {number} elapsed 已播时长 ms * @returns {number} 0..1 */ export function clipPhase(clip, elapsed) { if (clip.kind === 'pose' || clip.duration <= 0) return 0 if (clip.kind === 'loop') return (elapsed % clip.duration) / clip.duration // oneshot:停在末帧 return Math.min(elapsed / clip.duration, 1) } /** * 采样一个 clip 的当前帧,返回 { boneName: BoneOffset }。 * @param {import('./types.js').AnimationClip} clip * @param {number} elapsed * @returns {Record} */ export function sampleClip(clip, elapsed) { const t = clipPhase(clip, elapsed) /** @type {Record} */ const out = {} for (const [boneName, keys] of Object.entries(clip.bones)) { out[boneName] = { drot: sampleProp(keys, t, 'drot'), dx: sampleProp(keys, t, 'dx'), dy: sampleProp(keys, t, 'dy'), dscale: sampleProp(keys, t, 'dscale'), } } return out } /** * 按权重 w 混合两个增量(交叉淡入用)。w=0 取 a,w=1 取 b。 * @param {import('./types.js').BoneOffset} a * @param {import('./types.js').BoneOffset} b * @param {number} w * @returns {import('./types.js').BoneOffset} */ export function blendOffset(a, b, w) { return { drot: lerp(a.drot, b.drot, w), dx: lerp(a.dx, b.dx, w), dy: lerp(a.dy, b.dy, w), dscale: lerp(a.dscale, b.dscale, w), } }