feat(animation): attach external animation files to a model
Animation-only FBX files (no mesh, clips only) could not be used: clips are keyed by node index into the parsed model's node table, so there was no path to get them onto a different model's skeleton. Add AssetManager::AttachAnimationClips: parses the container with the CPU-only path (no GPU upload -- empty meshes cannot build a Model), remaps each clip's channels to the target model's node indices by bone name, and appends to its clip list. Channels whose bone name is absent are skipped and counted; clips with zero matching channels are dropped. Same-source attach is idempotent via a new AnimationClip::sourcePath marker; name clashes get the file stem prefixed. Inspector Animation block gains an attach slot (UE Details semantics) listing model-container files plus a bullet list of attached sources; already-attached paths are replayed idempotently when the entity is selected, so clips survive restarts. The paths persist in the scene (Animation.Attached); the clips themselves stay runtime state. Limitations (documented, by design): same-skeleton retargeting only -- bone names must match between source and target (same DCC export or Mixamo-style per-animation downloads); cross-skeleton retargeting is out of scope.
This commit is contained in:
@@ -161,6 +161,20 @@ namespace Engine
|
||||
Model* GetModel(ModelRef h) const { return m_Models.Get(h); }
|
||||
void UnloadModel(ModelRef h) { m_Models.Unload(h); }
|
||||
|
||||
// ---- 外部动画并入(UE AnimSequence 归属 Skeleton 的简化版)----
|
||||
// 把动画容器文件(无网格 FBX/GLB 等)里的 clip 按骨骼节点名
|
||||
// 重映射进目标模型的 clip 表。前提 = 源/目标骨骼命名同源(如
|
||||
// 同一 DCC 拆分/Mixamo 同角色);名字对不上的轨道跳过并计数。
|
||||
// 幂等:目标 clips 已含该源(sourcePath)时不重复并入。
|
||||
struct AnimAttachResult
|
||||
{
|
||||
u32 clipsAdded = 0;
|
||||
u32 channelsMatched = 0;
|
||||
u32 channelsSkipped = 0; // 骨骼名在目标模型找不到
|
||||
};
|
||||
AnimAttachResult AttachAnimationClips(ModelRef target,
|
||||
const std::string& animPath);
|
||||
|
||||
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
|
||||
ModelRef CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
|
||||
// 半透明平面(alpha 0-255,透明绘制队列):同参数缓存去重
|
||||
|
||||
@@ -145,6 +145,11 @@ namespace Engine::ECS {
|
||||
f32 fadeElapsed = 0.0f; // 已过渡时长
|
||||
f32 fadeDuration = 0.3f; // 过渡总时长(秒)
|
||||
|
||||
// 外部动画资产(编辑器 attach 的动画容器文件,随场景持久化)。
|
||||
// clips 本体运行时并入 Model 资产(共享);重启后由 Inspector
|
||||
// 按此列表幂等重放
|
||||
std::vector<std::string> attachedAnimPaths;
|
||||
|
||||
// 切换 clip(带 crossfade)。越界/空 clip 保护在消费端。
|
||||
void Play(int index, f32 fadeSeconds = 0.3f)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,9 @@ namespace Engine
|
||||
f32 duration = 0.0f; // 秒(已按 tps 换算)
|
||||
f32 tps = 1.0f; // ticks per second(0 按 1,assimp 惯例)
|
||||
std::vector<AnimChannel> channels;
|
||||
// 外部动画文件来源路径(运行时并入的 clip 标记;空 = 模型内嵌)。
|
||||
// 用于幂等去重:同一源文件不重复并入
|
||||
std::string sourcePath;
|
||||
};
|
||||
|
||||
// aiNode 树拍平表:骨骼 global 变换的累乘骨架(解析期 ProcessNode
|
||||
@@ -147,6 +150,15 @@ namespace Engine
|
||||
bool HasAnimations() const { return !m_Clips.empty(); }
|
||||
u32 GetBoneCount() const { return static_cast<u32>(m_Bones.size()); }
|
||||
const std::vector<AnimationClip>& GetClips() const { return m_Clips; }
|
||||
// 外部动画并入(编辑器 attach 操作):追加到 clip 表尾部,
|
||||
// 既有 clipIndex 不受影响。仅运行时内存状态(重启后由场景
|
||||
// 里 Animation.attachedAnimPaths 驱动重放)
|
||||
void AppendClips(std::vector<AnimationClip>&& extra)
|
||||
{
|
||||
m_Clips.insert(m_Clips.end(),
|
||||
std::make_move_iterator(extra.begin()),
|
||||
std::make_move_iterator(extra.end()));
|
||||
}
|
||||
const std::vector<ModelNode>& GetNodes() const { return m_Nodes; }
|
||||
const std::vector<ModelBone>& GetBones() const { return m_Bones; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user