// evaluator.js —— createEvaluator:世界矩阵链式求值。移植自原型 evaluator.js + humanoid-v1.js 的 indexRig。 // // 职责:吃一个 RigDefinition(来自 @drama/rig),把骨骼按「父先于子」拓扑排序, // 然后给定一个 AnimationState,逐根 bone 算出世界矩阵(角色局部空间,root 在原点): // boneWorld = parentWorld × boneLocal, boneLocal = fromTRS(setup+增量)。 // 不碰像素、不碰 slot 贴图——那是 @drama/render-pixi 的事,core 只产出 Map。 import { fromTRS, mul, decompose, degToRad, PROP_DEFAULT, matrix } from './matrix.js' import { AnimationState } from './anim-state.js' /** * 把 rig 的 bones 按「父先于子」拓扑排序(保证世界变换可一次遍历算完), * 并建 name→bone 索引。移植自原型 indexRig。环会在 visit 中被 visited 截断(rig 保证无环)。 * @param {import('./types.js').RigDefinition} rig * @returns {{boneByName:Map, orderedBones:import('./types.js').RigBone[]}} */ export function indexRig(rig) { /** @type {Map} */ const boneByName = new Map() for (const b of rig.bones) boneByName.set(b.name, b) /** @type {import('./types.js').RigBone[]} */ const ordered = [] const visited = new Set() /** @param {import('./types.js').RigBone|undefined} b */ const visit = (b) => { if (!b || visited.has(b.name)) return if (b.parent) visit(boneByName.get(b.parent)) visited.add(b.name) ordered.push(b) } for (const b of rig.bones) visit(b) return { boneByName, orderedBones: ordered } } /** * 世界变换求值。 * @param {{orderedBones:import('./types.js').RigBone[]}} rigIndex indexRig 产物 * @param {AnimationState} animState * @param {(Record|null)=} overrides * 可选覆盖,叠加到动画采样之上(姿势编辑 / 微调用)。 * @returns {Map} 角色局部空间,root 在原点 */ export function evaluateWorld(rigIndex, animState, overrides = null) { const offsets = animState.sampleBones() /** @type {Map} */ const world = new Map() for (const bone of rigIndex.orderedBones) { const off = offsets[bone.name] || PROP_DEFAULT const ov = overrides && overrides[bone.name] const rot = degToRad(bone.rotation + off.drot + (ov?.drot || 0)) const x = bone.pos.x + off.dx + (ov?.dx || 0) const y = bone.pos.y + off.dy + (ov?.dy || 0) const scale = 1 * off.dscale const local = fromTRS(x, y, rot, scale) const parentW = bone.parent ? world.get(bone.parent) : null world.set(bone.name, parentW ? mul(parentW, local) : local) } return world } /** * 求值器工厂。 * @param {import('./types.js').RigDefinition} rig * @param {import('./types.js').ClipLib=} clipLib clip 库注入(默认应由装配层注入 @drama/rig)。 * @returns {{ * state: () => AnimationState, * evaluateWorld: (animState:AnimationState, overrides?:(Record|null)) => Map, * matrix: typeof matrix, * indexRig: () => {orderedBones:import('./types.js').RigBone[]} * }} */ export function createEvaluator(rig, clipLib) { const rigIndex = indexRig(rig) return { /** 每角色一个 AnimationState。 */ state() { if (!clipLib) { throw new Error('createEvaluator: clipLib 未注入;state() 需要 clip 库(@drama/rig)') } return new AnimationState(clipLib) }, evaluateWorld(animState, overrides = null) { return evaluateWorld(rigIndex, animState, overrides) }, matrix, indexRig() { return rigIndex }, } } export { decompose }