Add animation controller system, material assets and skeleton visualization

- .animctrl state machine asset (params/transitions/blend trees, 1D+2D
  directional blending) with Unity Animator-style editor panel: state
  list, transitions, params, directional pad preview driving live
  CrossFade on the selected entity
- Split animation evaluation out of SceneRenderer into
  AnimationSystem/AnimationEvaluator; skin palettes with rest-pose
  fallback and blend-weight epsilon filtering
- MaterialAsset (.material) with reload-safe preview tile pool
  (DropMaterial clears dirty refs before instance destruction)
- EditorGizmoPass skeleton wires + viewport joint overlay; camera
  projection rework (baked Vulkan Y-flip, ortho support)
- Editor/game build split via HELLOVULKAN_EDITOR option
This commit is contained in:
2026-10-07 01:50:04 +08:00
parent a941207ae3
commit cc837bc605
71 changed files with 8198 additions and 548 deletions
+173
View File
@@ -0,0 +1,173 @@
//
// MaterialAssetUtil.h:材质资产编辑共用工具(Inspector / Material Editor
// 面板共享)。
//
// 单一事实来源(原 InspectorPanel.cpp 匿名 ns 实现上提):
// - .material 文件枚举缓存(inline 变量跨 TU 唯一——面板各自创建
// Override 后缓存失效即时可见)
// - Create Override:内建材质快照 → assets/materials/*.material →
// GUID 登记 → 同步加载 → 挂实体覆盖表
//
#ifndef HELLOVULKAN_MATERIALASSETUTIL_H
#define HELLOVULKAN_MATERIALASSETUTIL_H
#include <Engine/Assets/AssetManager.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Components.h>
#include <Engine/Renderer/MaterialAsset.h>
#include <Engine/Renderer/Model.h>
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <string>
#include <vector>
namespace EditorMaterialUtil {
namespace fs = std::filesystem;
// .material 文件枚举(会话级缓存 + 脏标)。inline 变量 = 所有 TU
// 共享同一份(Create Override 新建文件后置脏重扫)
inline std::vector<std::string> s_MaterialFiles;
inline bool s_MaterialFilesDirty = true;
inline void InvalidateMaterialFileList() { s_MaterialFilesDirty = true; }
// 递归收集 assets/ 下纹理文件(材质编辑器纹理槽选择器;纹理文件
// 不被编辑器创建,无需脏标)。会话级缓存
inline const std::vector<std::string>& AssetTextureFiles()
{
static std::vector<std::string> s_Files;
static bool s_Cached = false;
if (!s_Cached)
{
s_Cached = true;
if (fs::exists("assets"))
{
for (const auto& entry :
fs::recursive_directory_iterator("assets"))
{
if (!entry.is_regular_file())
{
continue;
}
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c)
{ return std::tolower(c); });
if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" ||
ext == ".tga" || ext == ".bmp" || ext == ".hdr")
{
s_Files.push_back(entry.path().generic_string());
}
}
std::sort(s_Files.begin(), s_Files.end());
}
}
return s_Files;
}
inline const std::vector<std::string>& AssetMaterialFiles()
{
if (s_MaterialFilesDirty)
{
s_MaterialFilesDirty = false;
s_MaterialFiles.clear();
if (fs::exists("assets"))
{
for (const auto& entry :
fs::recursive_directory_iterator("assets"))
{
if (!entry.is_regular_file())
{
continue;
}
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c)
{ return std::tolower(c); });
if (ext == ".material")
{
s_MaterialFiles.push_back(
entry.path().generic_string());
}
}
std::sort(s_MaterialFiles.begin(), s_MaterialFiles.end());
}
}
return s_MaterialFiles;
}
// Create Override:内建材质快照 → assets/materials/<model>_<mesh>.material
// → GUID 登记 → 同步加载 → 挂实体覆盖表。失败返回空串(不挂槽)。
// 数据段 = 内建材质只读快照(纹理路径 + 因子 + 透明语义),此后
// 该资产独立演化,内建材质不受影响
inline std::string CreateMaterialOverride(
const std::string& modelPath,
const Engine::MeshMaterialInfo& info,
Engine::u32 meshIdx,
Engine::ECS::MeshRendererComponent& mr,
Engine::Assets::AssetManager& assets)
{
// 目标文件名:<modelStem>_<meshName|idx>.material;mesh 名非
// [A-Za-z0-9] 字符折叠 '_',空名回退下标;重名追加 _2/_3/...
std::string stem = fs::path(modelPath).stem().string();
std::string leaf = info.name;
if (leaf.empty())
{
leaf = std::to_string(meshIdx);
}
for (char& c : leaf)
{
if (!std::isalnum(static_cast<unsigned char>(c)))
{
c = '_';
}
}
const fs::path dir = "assets/materials";
std::error_code ec;
fs::create_directories(dir, ec);
if (ec)
{
ENGINE_WARN("MaterialUtil: cannot create {}: {}", dir.string(),
ec.message());
return {};
}
std::string base = stem + "_" + leaf;
std::string rel = (dir / (base + ".material")).generic_string();
for (int n = 2; fs::exists(rel); ++n)
{
rel = (dir / (base + "_" + std::to_string(n) + ".material"))
.generic_string();
}
Engine::MaterialAssetData data;
data.texPaths = info.texPaths;
data.params = info.params;
data.transparent = info.transparent;
if (!Engine::MaterialAsset::Save(rel, data))
{
ENGINE_WARN("MaterialUtil: failed to save material override '{}'",
rel);
return {};
}
assets.GetOrCreateGUID(rel);
if (!assets.LoadMaterialAsset(rel).Valid())
{
ENGINE_WARN("MaterialUtil: override '{}' saved but failed to load",
rel);
return {};
}
mr.materialOverrides[meshIdx] = rel;
InvalidateMaterialFileList(); // 新文件进下拉列表
return rel;
}
} // namespace EditorMaterialUtil
#endif //HELLOVULKAN_MATERIALASSETUTIL_H