fixtures.js 3.53 KB
// fixtures.js —— 测试用假 rig / clip 库(不依赖 @drama/rig 已构建,纯 Node 可跑)。
// 形状严格对齐 @drama/rig §3 契约(RigDefinition / AnimationClip / TRACKS / TRACK_BONES / TRACK_DEFAULTS)。

export const TRACKS = ['base', 'lower_body', 'arm_l', 'arm_r', 'head']

export const TRACK_BONES = {
  base: ['root', 'hip', 'torso'],
  lower_body: ['thigh_l', 'shin_l', 'thigh_r', 'shin_r'],
  arm_l: ['upper_arm_l', 'lower_arm_l'],
  arm_r: ['upper_arm_r', 'lower_arm_r'],
  head: ['neck', 'head'],
}

export const TRACK_DEFAULTS = {
  base: 'base.idle',
  lower_body: 'leg.stand_together',
  arm_l: 'arm_l.down',
  arm_r: 'arm_r.down',
  head: 'head.idle',
}

/** @type {Record<string, import('@drama/rig').AnimationClip>} */
export const CLIPS = {
  'base.idle': { id: 'base.idle', track: 'base', kind: 'loop', duration: 2000, bones: { torso: [{ t: 0, dy: 0 }, { t: 0.5, dy: -4 }, { t: 1, dy: 0 }] } },

  'leg.stand_together': { id: 'leg.stand_together', track: 'lower_body', kind: 'pose', duration: 0, bones: { thigh_l: [{ t: 0, drot: 0 }], thigh_r: [{ t: 0, drot: 0 }] } },
  'leg.walk': { id: 'leg.walk', track: 'lower_body', kind: 'loop', duration: 600, bones: { thigh_l: [{ t: 0, drot: -20 }, { t: 0.5, drot: 20 }, { t: 1, drot: -20 }], thigh_r: [{ t: 0, drot: 20 }, { t: 0.5, drot: -20 }, { t: 1, drot: 20 }] } },

  'arm_l.down': { id: 'arm_l.down', track: 'arm_l', kind: 'pose', duration: 0, bones: { upper_arm_l: [{ t: 0, drot: 0 }] } },
  'arm_r.down': { id: 'arm_r.down', track: 'arm_r', kind: 'pose', duration: 0, bones: { upper_arm_r: [{ t: 0, drot: 0 }] } },
  'arm_r.point_right': { id: 'arm_r.point_right', track: 'arm_r', kind: 'pose', duration: 0, bones: { upper_arm_r: [{ t: 0, drot: 90 }] } },
  'arm_r.punch': { id: 'arm_r.punch', track: 'arm_r', kind: 'oneshot', duration: 200, bones: { upper_arm_r: [{ t: 0, drot: 0 }, { t: 1, drot: 120 }] } },

  'head.idle': { id: 'head.idle', track: 'head', kind: 'loop', duration: 3000, bones: { head: [{ t: 0, drot: 0 }] } },
  'head.talk_shake': { id: 'head.talk_shake', track: 'head', kind: 'loop', duration: 300, bones: { head: [{ t: 0, drot: -3 }, { t: 0.5, drot: 3 }, { t: 1, drot: -3 }] } },
  'head.nod': { id: 'head.nod', track: 'head', kind: 'oneshot', duration: 400, bones: { head: [{ t: 0, drot: 0 }, { t: 0.5, drot: 15 }, { t: 1, drot: 0 }] } },
}

/** @param {string} id @returns {import('@drama/rig').AnimationClip|null} */
export function getClip(id) {
  return CLIPS[id] || null
}

/** @param {Object} extra 覆盖默认字段 @returns {import('./../src/anim-state.js').ClipLib} */
export function makeClipLib(extra = {}) {
  return { getClip, TRACKS, TRACK_BONES, TRACK_DEFAULTS, ...extra }
}

/** 一个最小 humanoid 骨架:root → hip → torso → neck → head,及一条腿链。 */
export const RIG = {
  id: 'humanoid_v1',
  canvas: { width: 1024, height: 1536 },
  bones: [
    // 故意把子骨放在父骨之前,验证 core 能拓扑排序
    { name: 'head', parent: 'neck', pos: { x: 0, y: -40 }, rotation: 0 },
    { name: 'neck', parent: 'torso', pos: { x: 0, y: -60 }, rotation: 0 },
    { name: 'torso', parent: 'hip', pos: { x: 0, y: -80 }, rotation: 0 },
    { name: 'hip', parent: 'root', pos: { x: 0, y: -100 }, rotation: 0 },
    { name: 'root', parent: null, pos: { x: 0, y: 0 }, rotation: 0 },
    { name: 'thigh_r', parent: 'hip', pos: { x: 20, y: 0 }, rotation: 0 },
    { name: 'shin_r', parent: 'thigh_r', pos: { x: 0, y: 50 }, rotation: 0 },
  ],
  slots: [
    { name: 'head', bone: 'head', zOrder: 40 },
    { name: 'torso', bone: 'torso', zOrder: 20 },
  ],
}