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:
@@ -143,6 +143,83 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
else size.y = avail.x / aspect;
|
||||
ImGui::Image((ImTextureID) (intptr_t) sceneTex, size);
|
||||
|
||||
// ---- 骨骼关节 overlay(Endfield-Poser 范式):3D 骨架线
|
||||
// 给结构,屏幕圆点给可读性。世界→屏幕与渲染同相机,
|
||||
// Vulkan NDC y 向下与 ImGui 屏幕坐标同构 → 线性映射
|
||||
// image 矩形即可。hover 关节 = 白圈高亮 + 骨骼名
|
||||
{
|
||||
const auto &frameData = renderer.GetFrameSceneData();
|
||||
if (frameData.showBoneWires && !frameData.pieActive &&
|
||||
!frameData.boneJoints.empty() &&
|
||||
ctx.editorCamera != nullptr) {
|
||||
const ImVec2 rmin = ImGui::GetItemRectMin();
|
||||
const ImVec2 rmax = ImGui::GetItemRectMax();
|
||||
const glm::mat4 vp =
|
||||
ctx.editorCamera->GetController().GetProjection(aspect) *
|
||||
ctx.editorCamera->GetView();
|
||||
ImDrawList *dl = ImGui::GetWindowDrawList();
|
||||
dl->PushClipRect(rmin, rmax, true);
|
||||
const f32 r = frameData.boneJointPx;
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const ImVec2 mouse = io.MousePos;
|
||||
int hovered = -1;
|
||||
float hoveredD = 0.0f;
|
||||
for (size_t ji = 0; ji < frameData.boneJoints.size(); ++ji) {
|
||||
const auto &jm = frameData.boneJoints[ji];
|
||||
const glm::vec4 c = vp * glm::vec4(jm.pos, 1.0f);
|
||||
if (c.w <= 0.0f) continue; // 相机身后
|
||||
const ImVec2 p(
|
||||
rmin.x + (c.x / c.w * 0.5f + 0.5f) * size.x,
|
||||
rmin.y + (c.y / c.w * 0.5f + 0.5f) * size.y);
|
||||
// 骨骼 = 蓝(Endfield humanoid 色),普通节点 = 青
|
||||
const ImU32 fill = jm.isBone
|
||||
? IM_COL32(70, 160, 255, 230)
|
||||
: IM_COL32(70, 190, 190, 190);
|
||||
dl->AddCircleFilled(p, r, fill);
|
||||
dl->AddCircle(p, r + 1.5f,
|
||||
IM_COL32(255, 255, 255, 140), 0, 1.2f);
|
||||
// hover 命中(半径 +3px 容差,取最近)
|
||||
const float dx = p.x - mouse.x, dy = p.y - mouse.y;
|
||||
const float d = dx * dx + dy * dy;
|
||||
const float hit = (r + 3.0f) * (r + 3.0f);
|
||||
if (d <= hit && (hovered < 0 || d < hoveredD)) {
|
||||
hovered = static_cast<int>(ji);
|
||||
hoveredD = d;
|
||||
}
|
||||
}
|
||||
// hover 高亮圈 + 骨骼名 tooltip(名字查选中实体模型
|
||||
// 节点表;查不到 = 显示节点下标)
|
||||
if (hovered >= 0 && ImGui::IsItemHovered()) {
|
||||
const auto &jm = frameData.boneJoints[hovered];
|
||||
const glm::vec4 c = vp * glm::vec4(jm.pos, 1.0f);
|
||||
const ImVec2 p(
|
||||
rmin.x + (c.x / c.w * 0.5f + 0.5f) * size.x,
|
||||
rmin.y + (c.y / c.w * 0.5f + 0.5f) * size.y);
|
||||
dl->AddCircle(p, r + 3.5f,
|
||||
IM_COL32(255, 255, 255, 255), 0, 2.0f);
|
||||
const char *name = nullptr;
|
||||
if (selected != entt::null) {
|
||||
// 本段在 reg 声明之前,就地取 registry
|
||||
const auto *mrSel =
|
||||
ctx.scene->GetRegistry().try_get<
|
||||
Engine::ECS::MeshRendererComponent>(
|
||||
selected);
|
||||
if (mrSel && mrSel->model.Valid()) {
|
||||
const Model *mdl =
|
||||
ctx.assets->GetModel(mrSel->model);
|
||||
if (mdl && jm.nodeIdx < mdl->GetNodes().size())
|
||||
name = mdl->GetNodes()[jm.nodeIdx]
|
||||
.name.c_str();
|
||||
}
|
||||
}
|
||||
ImGui::SetTooltip(
|
||||
"%s%s", name ? name : "(node)",
|
||||
jm.isBone ? "" : " [non-bone]");
|
||||
}
|
||||
dl->PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 工具热键(UE 语义):W/E/R 切 Gizmo 模式、T 切
|
||||
// local/world、F 焦点选中实体。仅 Viewport 持焦时生效——
|
||||
// 输入框打字(WantTextInput)由 BlockingKeyboard 拦游戏侧,
|
||||
@@ -163,11 +240,12 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F) && tr != nullptr) {
|
||||
// F = 焦点选中实体(frame-selection):编辑器相机
|
||||
// 环绕 pivot 设为选中实体位置,沿视线平移对准(环
|
||||
// 绕距离沿用当前值,滚轮可调)。世界位姿(层级
|
||||
// 实体 = 父链组合结果)
|
||||
const bool fCombo = ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeyShift;
|
||||
// F = 焦点选中实体(frame-selection):编辑器相机
|
||||
// 环绕 pivot 设为选中实体位置,沿视线平移对准(环
|
||||
// 绕距离沿用当前值,滚轮可调)。世界位姿(层级
|
||||
// 实体 = 父链组合结果)。裸 F 才触发(组合键排他)
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F) && !fCombo && tr != nullptr) {
|
||||
ctx.editorCamera->FocusOn(tr->GetWorldPosition());
|
||||
}
|
||||
}
|
||||
@@ -179,14 +257,13 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
if (tr != nullptr && !playing) {
|
||||
// 相机矩阵:跟随渲染源(编辑器形态 = EditorCamera,游戏
|
||||
// 形态 = primary 相机实体;与 SceneRenderer::BuildFrame
|
||||
// 同一分派),参数与渲染侧一致(aspect = 渲染分辨率、
|
||||
// near/far 0.1/100)
|
||||
// 同一分派),投影参数由相机自身承载(与渲染侧一致)
|
||||
ECS::TransformComponent *camT = nullptr;
|
||||
if (Engine::Camera *cam = ViewCamera(ctx, camT);
|
||||
cam != nullptr && camT != nullptr) {
|
||||
const glm::mat4 view = cam->GetView(*camT);
|
||||
const glm::mat4 projGL = ProjVulkanToGL(
|
||||
cam->GetProjection(aspect, 0.1f, 100.0f));
|
||||
cam->GetProjection(aspect));
|
||||
|
||||
ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
|
||||
// 杆画在 Image 区域内(等比适配后的 item rect)
|
||||
@@ -285,7 +362,7 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
// Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行)
|
||||
// PIE 运行态 = 黄色 PLAYING/PAUSED 提示替代编辑状态
|
||||
{
|
||||
char tip[64];
|
||||
char tip[128];
|
||||
if (playing) {
|
||||
const bool paused = ctx.pausedState && *ctx.pausedState;
|
||||
std::snprintf(tip, sizeof(tip),
|
||||
@@ -293,7 +370,8 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
: "PLAYING - Ctrl+P to stop");
|
||||
} else {
|
||||
std::snprintf(tip, sizeof(tip),
|
||||
"Space: %s [T] Snap: Ctrl",
|
||||
"Space: %s [T] Snap: Ctrl F: Focus "
|
||||
"Ctrl+Shift+F: To Selection",
|
||||
gizmo.localSpace ? "Local" : "World");
|
||||
}
|
||||
const ImVec2 imgMin = ImGui::GetItemRectMin();
|
||||
|
||||
Reference in New Issue
Block a user