sampling.js
2.76 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
// 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<string, import('./types.js').BoneOffset>}
*/
export function sampleClip(clip, elapsed) {
const t = clipPhase(clip, elapsed)
/** @type {Record<string, import('./types.js').BoneOffset>} */
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),
}
}