- 新增 GLB/GLTF 模型加载与渲染,支持内嵌动画播放 - 通过 IndexedDB 持久化大体积模型二进制,规避 localStorage 限制 - 新增动作片段序列,时间轴可添加、编辑与编排多段动作 - 优化机位视角相机同步,修复角度修改不生效问题 - 扩展骨骼识别,兼容 Mixamo 前缀及 UE4 骨架
98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
import { Component, type ReactNode } from "react";
|
|
import type { CharacterRigState, MotionClip } from "../schema/directorProject";
|
|
import { PrimitiveMannequin } from "./PrimitiveMannequin";
|
|
import { UE4MannequinModel } from "./UE4MannequinModel";
|
|
import { GltfCharacterModel } from "./GltfCharacterModel";
|
|
import type { CharacterBodyType } from "./mannequin/bodyTypes";
|
|
import { getActiveMotionClipAtTime, resolveMotionClipEnd } from "./mannequin/motion";
|
|
|
|
interface CharacterModelProps {
|
|
bodyType?: CharacterBodyType;
|
|
color?: string;
|
|
onLabelAnchorYChange?: (anchorY: number) => void;
|
|
rigState?: CharacterRigState;
|
|
motionTime?: number;
|
|
characterId?: string;
|
|
/** 动作片段起点(秒):基础动作只在该时间区间内驱动角色,缺省视为时间轴开头 */
|
|
motionStartTime?: number;
|
|
/** 动作片段终点(秒):缺省视为时间轴末尾 */
|
|
motionEndTime?: number;
|
|
/** 时间轴上的动作片段序列(多段动作)。存在时按播放时间取当前片段覆盖上面的单一 motion。 */
|
|
motionClips?: MotionClip[];
|
|
/** 时间轴总时长(秒),用于解析到末尾的动作片段。 */
|
|
duration: number;
|
|
/** 当 rigState.modelAssetId 指向一个导入的 GLB/GLTF 资产时,其可访问的 url(用于渲染该角色几何)。 */
|
|
modelAssetUrl?: string;
|
|
}
|
|
|
|
class CharacterModelBoundary extends Component<
|
|
{
|
|
fallback: ReactNode;
|
|
children: ReactNode;
|
|
},
|
|
{
|
|
hasError: boolean;
|
|
}
|
|
> {
|
|
state = { hasError: false };
|
|
|
|
static getDerivedStateFromError() {
|
|
return { hasError: true };
|
|
}
|
|
|
|
render() {
|
|
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
}
|
|
}
|
|
|
|
export function CharacterModel({ bodyType, color, onLabelAnchorYChange, rigState, motionTime, characterId, motionStartTime, motionEndTime, modelAssetUrl, motionClips, duration }: CharacterModelProps) {
|
|
// 动作序列:按当前播放时间取出应生效的片段(后段覆盖前段),用它覆盖单一 motion 字段。
|
|
// 无片段 / 时间不在任何片段内 → 回退到旧的单一段落(rigState.motion + motionStartTime/EndTime)。
|
|
const activeClip = getActiveMotionClipAtTime(motionClips, motionTime ?? 0, duration);
|
|
const resolvedRigState: CharacterRigState | undefined = activeClip && rigState
|
|
? { ...rigState, motion: activeClip }
|
|
: rigState;
|
|
const resolvedStart = activeClip ? activeClip.start : motionStartTime;
|
|
const resolvedEnd = activeClip ? resolveMotionClipEnd(activeClip, duration) : motionEndTime;
|
|
|
|
const fallback = <PrimitiveMannequin bodyType={bodyType} color={color} rigState={resolvedRigState} motionTime={motionTime} characterId={characterId} motionStartTime={resolvedStart} motionEndTime={resolvedEnd} />;
|
|
|
|
// 导入的 GLB/GLTF 人形角色:几何来自资产,用通用人形骨骼映射驱动程序化动作。
|
|
if (rigState?.modelAssetId && modelAssetUrl) {
|
|
return (
|
|
<CharacterModelBoundary fallback={fallback}>
|
|
<GltfCharacterModel
|
|
url={modelAssetUrl}
|
|
bodyType={bodyType}
|
|
color={color}
|
|
onLabelAnchorYChange={onLabelAnchorYChange}
|
|
rigState={resolvedRigState}
|
|
motionTime={motionTime}
|
|
characterId={characterId}
|
|
motionStartTime={resolvedStart}
|
|
motionEndTime={resolvedEnd}
|
|
/>
|
|
</CharacterModelBoundary>
|
|
);
|
|
}
|
|
|
|
if (rigState?.rigType !== "ue4-mannequin") {
|
|
return fallback;
|
|
}
|
|
|
|
return (
|
|
<CharacterModelBoundary fallback={fallback}>
|
|
<UE4MannequinModel
|
|
bodyType={bodyType}
|
|
color={color}
|
|
onLabelAnchorYChange={onLabelAnchorYChange}
|
|
rigState={resolvedRigState}
|
|
motionTime={motionTime}
|
|
characterId={characterId}
|
|
motionStartTime={resolvedStart}
|
|
motionEndTime={resolvedEnd}
|
|
/>
|
|
</CharacterModelBoundary>
|
|
);
|
|
}
|