Files
ttstd-3d-director/src/editor/runtime/mannequin/motion.test.ts
T
tongtong 38dc27501a feat(编辑器): 支持导入 GLB/GLTF 模型并驱动角色动画
- 新增 GLB/GLTF 模型加载与渲染,支持内嵌动画播放
- 通过 IndexedDB 持久化大体积模型二进制,规避 localStorage 限制
- 新增动作片段序列,时间轴可添加、编辑与编排多段动作
- 优化机位视角相机同步,修复角度修改不生效问题
- 扩展骨骼识别,兼容 Mixamo 前缀及 UE4 骨架
2026-09-18 18:23:13 +08:00

64 lines
2.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { getActiveMotionClipAtTime, resolveMotionClipEnd } from "./motion";
import type { MotionClip } from "../../schema/directorProject";
function clip(partial: Partial<MotionClip> & Pick<MotionClip, "id" | "start" | "end">): MotionClip {
return {
kind: "walk",
speed: 1,
amplitude: 1,
enabled: true,
...partial,
};
}
describe("getActiveMotionClipAtTime", () => {
it("returns null when there are no clips", () => {
expect(getActiveMotionClipAtTime(undefined, 1, 10)).toBeNull();
expect(getActiveMotionClipAtTime([], 1, 10)).toBeNull();
});
it("returns the clip covering the current time", () => {
const a = clip({ id: "a", start: 0, end: 2 });
const b = clip({ id: "b", kind: "run", start: 2, end: 5 });
expect(getActiveMotionClipAtTime([a, b], 1, 5)?.id).toBe("a");
expect(getActiveMotionClipAtTime([a, b], 3, 5)?.id).toBe("b");
});
it("later clips win on overlap (last covering segment wins)", () => {
const a = clip({ id: "a", start: 0, end: 5 });
const b = clip({ id: "b", start: 2, end: 6 });
expect(getActiveMotionClipAtTime([a, b], 3, 6)?.id).toBe("b");
});
it("treats an open-ended clip (Infinity) as lasting to the timeline end", () => {
const a = clip({ id: "a", start: 0, end: Number.POSITIVE_INFINITY });
expect(getActiveMotionClipAtTime([a], 999, 10)?.id).toBe("a");
});
it("returns null for disabled / none clips (character stays static)", () => {
const disabled = clip({ id: "a", start: 0, end: 5, enabled: false });
const none = clip({ id: "b", start: 0, end: 5, kind: "none" });
expect(getActiveMotionClipAtTime([disabled], 1, 5)).toBeNull();
expect(getActiveMotionClipAtTime([none], 1, 5)).toBeNull();
});
it("returns null outside any clip", () => {
const a = clip({ id: "a", start: 1, end: 4 });
expect(getActiveMotionClipAtTime([a], 0, 5)).toBeNull();
expect(getActiveMotionClipAtTime([a], 5, 5)).toBeNull();
});
});
describe("resolveMotionClipEnd", () => {
it("keeps a finite end within bounds", () => {
const a = clip({ id: "a", start: 1, end: 4 });
expect(resolveMotionClipEnd(a, 10)).toBe(4);
});
it("resolves an open-ended end to the timeline duration", () => {
const a = clip({ id: "a", start: 1, end: Number.POSITIVE_INFINITY });
expect(resolveMotionClipEnd(a, 10)).toBe(10);
});
});