阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对), 指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis 行走基朝向与跨骨架去重、根运动通道 - 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与 字符冲量判定 - 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、 不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖 - 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增 ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target - 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板 - 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
#include "panels/ControllerEditorPanel.h"
|
||||
#include "panels/ViewportPanel.h"
|
||||
#include "panels/StatsPanel.h"
|
||||
#include "panels/LogPanel.h"
|
||||
#include "panels/GamepadPanel.h"
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
@@ -60,6 +62,8 @@ void Editor::Init(const EditorContext& ctx)
|
||||
m_Inspector = std::make_unique<InspectorPanel>();
|
||||
m_Viewport = std::make_unique<ViewportPanel>();
|
||||
m_Stats = std::make_unique<StatsPanel>();
|
||||
m_Log = std::make_unique<LogPanel>();
|
||||
m_Gamepad = std::make_unique<GamepadPanel>();
|
||||
m_AssetBrowser = std::make_unique<AssetBrowserPanel>();
|
||||
m_MaterialEditor = std::make_unique<MaterialEditorPanel>();
|
||||
m_Ctx.materialEditor = m_MaterialEditor.get();
|
||||
@@ -219,6 +223,8 @@ void Editor::Draw()
|
||||
}
|
||||
}
|
||||
if (m_ShowStats) m_Stats->Draw(m_Ctx);
|
||||
if (m_ShowLog) m_Log->Draw(m_Ctx);
|
||||
m_Gamepad->Draw(m_Ctx); // Tools 弹窗(内部 m_Open 门控)
|
||||
if (m_ShowAssetBrowser) m_AssetBrowser->Draw(m_Ctx);
|
||||
if (m_ShowMaterialEditor) m_MaterialEditor->Draw(m_Ctx);
|
||||
m_ControllerEditor->Draw(m_Ctx); // 面板内部 m_Open 门控
|
||||
@@ -236,6 +242,11 @@ bool Editor::BlockingKeyboard() const
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.WantTextInput) return true; // 打字中(改名等)不让位快捷键
|
||||
|
||||
// PIE 运行态:键盘全给游戏——编辑热键/Gizmo 在运行态本就让位
|
||||
//(ViewportPanel !playing 分支),不再因视口失焦拦截。否则点
|
||||
// Play 后焦点停在工具栏,WASD 整局全死(玩家输入无响应的根因)
|
||||
if (IsPlaying()) return false;
|
||||
|
||||
// 游戏形态(Fullscreen):WASD 全窗口生效,键盘只让位文本输入
|
||||
if (m_Ctx.renderer &&
|
||||
m_Ctx.renderer->GetDisplayMode() == kDisplayDirect)
|
||||
@@ -298,11 +309,18 @@ void Editor::DrawMenuBar()
|
||||
ImGui::MenuItem("Details", nullptr, &m_ShowInspector);
|
||||
ImGui::MenuItem("Viewport", nullptr, &m_ShowViewport);
|
||||
ImGui::MenuItem("Stats", nullptr, &m_ShowStats);
|
||||
ImGui::MenuItem("Log", nullptr, &m_ShowLog);
|
||||
ImGui::MenuItem("Content Browser", nullptr, &m_ShowAssetBrowser);
|
||||
ImGui::MenuItem("Material Editor", nullptr, &m_ShowMaterialEditor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Tools"))
|
||||
{
|
||||
ImGui::MenuItem("Gamepad Test", nullptr, &m_Gamepad->m_Open);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
@@ -428,6 +446,7 @@ void Editor::DrawDockSpace()
|
||||
ImGui::DockBuilderDockWindow("Outliner", dockLeft);
|
||||
ImGui::DockBuilderDockWindow("Viewport", dockCenter);
|
||||
ImGui::DockBuilderDockWindow("Stats", dockCenter); // Viewport 旁 tab
|
||||
ImGui::DockBuilderDockWindow("Log", dockBottom); // Content Browser 旁 tab
|
||||
ImGui::DockBuilderDockWindow("Content Browser", dockBottom);
|
||||
ImGui::DockBuilderDockWindow("Details", dockRight);
|
||||
ImGui::DockBuilderDockWindow("Material Editor", dockRight); // Details 旁 tab
|
||||
|
||||
@@ -29,6 +29,8 @@ class HierarchyPanel;
|
||||
class InspectorPanel;
|
||||
class ViewportPanel;
|
||||
class StatsPanel;
|
||||
class LogPanel;
|
||||
class GamepadPanel;
|
||||
class AssetBrowserPanel;
|
||||
class MaterialEditorPanel;
|
||||
class ControllerEditorPanel;
|
||||
@@ -126,6 +128,8 @@ private:
|
||||
std::unique_ptr<InspectorPanel> m_Inspector;
|
||||
std::unique_ptr<ViewportPanel> m_Viewport;
|
||||
std::unique_ptr<StatsPanel> m_Stats;
|
||||
std::unique_ptr<LogPanel> m_Log;
|
||||
std::unique_ptr<GamepadPanel> m_Gamepad;
|
||||
std::unique_ptr<AssetBrowserPanel> m_AssetBrowser;
|
||||
std::unique_ptr<MaterialEditorPanel> m_MaterialEditor;
|
||||
std::unique_ptr<ControllerEditorPanel> m_ControllerEditor;
|
||||
@@ -137,6 +141,7 @@ private:
|
||||
bool m_ShowInspector = true;
|
||||
bool m_ShowViewport = true;
|
||||
bool m_ShowStats = true;
|
||||
bool m_ShowLog = true;
|
||||
bool m_ShowAssetBrowser = true;
|
||||
bool m_ShowMaterialEditor = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// GamepadPanel.cpp:手柄输入可视化回显(极简手柄视图)。
|
||||
//
|
||||
// 一块画布 = 手柄俯视图:顶部扳机条 + 肩键,左上左摇杆、左下十字键、
|
||||
// 右上 ABXY(Xbox 语义色)、右下右摇杆,中央 Back/Guide/Start 小键。
|
||||
// 按下即亮蓝(面键亮语义色),无数值无卡片——位置即实体,一眼对上
|
||||
// 手里的手柄。数据源 = Window::PollGamepad(GLFW 原始轴值/按键)。
|
||||
//
|
||||
|
||||
#include "GamepadPanel.h"
|
||||
|
||||
#include <Engine/Core/Window.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
// GLFW 标准布局下标
|
||||
enum PadBtn {
|
||||
kA = 0, kB = 1, kX = 2, kY = 3,
|
||||
kLB = 4, kRB = 5, kBack = 6, kStart = 7, kGuide = 8,
|
||||
kLS = 9, kRS = 10, kUp = 11, kRight = 12, kDown = 13, kLeft = 14,
|
||||
};
|
||||
|
||||
constexpr ImU32 kLit = IM_COL32(90, 170, 250, 255); // 按下亮蓝
|
||||
constexpr ImU32 kFill = IM_COL32(52, 52, 60, 255); // 暗态填充
|
||||
constexpr ImU32 kEdge = IM_COL32(104, 104, 114, 255); // 暗态描边
|
||||
constexpr ImU32 kTxt = IM_COL32(215, 215, 220, 255); // 标签文字
|
||||
|
||||
// 块状键(肩键 / Back / Start)
|
||||
void Block(ImDrawList* dl, const ImVec2& c, float w, float h,
|
||||
const char* label, bool pressed)
|
||||
{
|
||||
const ImVec2 mn(c.x - w * 0.5f, c.y - h * 0.5f);
|
||||
const ImVec2 mx(c.x + w * 0.5f, c.y + h * 0.5f);
|
||||
dl->AddRectFilled(mn, mx, pressed ? kLit : kFill, 5.0f);
|
||||
dl->AddRect(mn, mx, pressed ? IM_COL32(160, 210, 255, 255) : kEdge,
|
||||
5.0f);
|
||||
const ImVec2 ts = ImGui::CalcTextSize(label);
|
||||
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label);
|
||||
}
|
||||
|
||||
// 圆键(Guide)
|
||||
void Circle(ImDrawList* dl, const ImVec2& c, float r, const char* label,
|
||||
bool pressed)
|
||||
{
|
||||
dl->AddCircleFilled(c, r, pressed ? kLit : kFill);
|
||||
dl->AddCircle(c, r, pressed ? IM_COL32(160, 210, 255, 255) : kEdge, 0,
|
||||
1.5f);
|
||||
const ImVec2 ts = ImGui::CalcTextSize(label);
|
||||
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label);
|
||||
}
|
||||
|
||||
// Xbox 语义色面键
|
||||
void Face(ImDrawList* dl, const ImVec2& c, float r, char label,
|
||||
bool pressed)
|
||||
{
|
||||
ImU32 col;
|
||||
switch (label)
|
||||
{
|
||||
case 'A': col = IM_COL32(32, 150, 40, 255); break;
|
||||
case 'B': col = IM_COL32(200, 45, 45, 255); break;
|
||||
case 'X': col = IM_COL32(50, 110, 220, 255); break;
|
||||
default: col = IM_COL32(225, 185, 40, 255); break;
|
||||
}
|
||||
dl->AddCircleFilled(c, r, pressed ? col : IM_COL32(col & 0xFF,
|
||||
(col >> 8) & 0xFF,
|
||||
(col >> 16) & 0xFF,
|
||||
60));
|
||||
dl->AddCircle(c, r, pressed ? col : kEdge, 0, 2.0f);
|
||||
const ImVec2 ts = ImGui::CalcTextSize(&label, &label + 1);
|
||||
dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f),
|
||||
pressed ? IM_COL32(255, 255, 255, 255) : kTxt, &label);
|
||||
}
|
||||
|
||||
// 摇杆画布(底座 + 死区圈 + 轴点)
|
||||
void Stick(ImDrawList* dl, const ImVec2& c, float r, float ax, float ay,
|
||||
float deadzone)
|
||||
{
|
||||
dl->AddCircleFilled(c, r, IM_COL32(32, 32, 37, 255));
|
||||
dl->AddCircle(c, r, kEdge, 0, 1.5f);
|
||||
dl->AddCircle(c, r * deadzone, IM_COL32(90, 140, 210, 110), 0, 1.2f);
|
||||
dl->AddLine(ImVec2(c.x - r * 0.93f, c.y), ImVec2(c.x + r * 0.93f, c.y),
|
||||
IM_COL32(50, 50, 58, 255), 1.0f);
|
||||
dl->AddLine(ImVec2(c.x, c.y - r * 0.93f), ImVec2(c.x, c.y + r * 0.93f),
|
||||
IM_COL32(50, 50, 58, 255), 1.0f);
|
||||
const float mag = std::sqrt(ax * ax + ay * ay);
|
||||
const float cl = (mag > 1.0f) ? 1.0f / mag : 1.0f;
|
||||
const ImVec2 pt(c.x + ax * cl * (r - 8.0f),
|
||||
c.y + ay * cl * (r - 8.0f));
|
||||
if (mag > deadzone)
|
||||
{
|
||||
dl->AddCircleFilled(pt, 10.0f, IM_COL32(90, 170, 250, 70));
|
||||
}
|
||||
dl->AddCircleFilled(pt, 7.0f,
|
||||
mag > deadzone ? kLit
|
||||
: IM_COL32(140, 140, 148, 255));
|
||||
}
|
||||
|
||||
// 十字键(真十字形,按下臂亮)
|
||||
void Dpad(ImDrawList* dl, const ImVec2& c, float arm, float w,
|
||||
const bool* b)
|
||||
{
|
||||
const ImVec2 l0(c.x - arm, c.y - w * 0.5f);
|
||||
const ImVec2 l1(c.x, c.y + w * 0.5f);
|
||||
const ImVec2 r0(c.x, c.y - w * 0.5f);
|
||||
const ImVec2 r1(c.x + arm, c.y + w * 0.5f);
|
||||
const ImVec2 u0(c.x - w * 0.5f, c.y - arm);
|
||||
const ImVec2 u1(c.x + w * 0.5f, c.y);
|
||||
const ImVec2 d0(c.x - w * 0.5f, c.y);
|
||||
const ImVec2 d1(c.x + w * 0.5f, c.y + arm);
|
||||
dl->AddRectFilled(l0, l1, b[kLeft] ? kLit : kFill, 4.0f);
|
||||
dl->AddRectFilled(r0, r1, b[kRight] ? kLit : kFill, 4.0f);
|
||||
dl->AddRectFilled(u0, u1, b[kUp] ? kLit : kFill, 4.0f);
|
||||
dl->AddRectFilled(d0, d1, b[kDown] ? kLit : kFill, 4.0f);
|
||||
dl->AddRect(l0, l1, kEdge, 4.0f);
|
||||
dl->AddRect(r0, r1, kEdge, 4.0f);
|
||||
dl->AddRect(u0, u1, kEdge, 4.0f);
|
||||
dl->AddRect(d0, d1, kEdge, 4.0f);
|
||||
}
|
||||
|
||||
// 线性扳机条(GLFW -1..1 → 0..1 填充)
|
||||
void Trigger(ImDrawList* dl, const ImVec2& mn, const ImVec2& mx,
|
||||
float raw)
|
||||
{
|
||||
dl->AddRectFilled(mn, mx, IM_COL32(32, 32, 37, 255), 4.0f);
|
||||
const float t = (raw + 1.0f) * 0.5f;
|
||||
if (t > 0.02f)
|
||||
{
|
||||
dl->AddRectFilled(mn, ImVec2(mn.x + (mx.x - mn.x) * t, mx.y),
|
||||
IM_COL32(235, 120, 55, 255), 4.0f);
|
||||
}
|
||||
dl->AddRect(mn, mx, kEdge, 4.0f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GamepadPanel::Draw(const EditorContext& ctx)
|
||||
{
|
||||
if (!m_Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!ImGui::Begin("Gamepad", &m_Open))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
Engine::GamepadState gp;
|
||||
const bool connected =
|
||||
ctx.app && ctx.app->GetWindow().PollGamepad(gp);
|
||||
|
||||
if (!connected)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0.60f, 0.60f, 0.60f, 1.0f),
|
||||
"○ Not connected (JOYSTICK_1)");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
ImGui::TextColored(ImVec4(0.40f, 0.85f, 0.40f, 1.0f), "● Connected");
|
||||
ImGui::Spacing();
|
||||
|
||||
// ---- 手柄画布(固定尺寸,元素 = 手柄真实位置;无背景板)----
|
||||
constexpr float kW = 440.0f, kH = 262.0f;
|
||||
constexpr float kDead = 0.15f; // = InputMap kRadialDeadZone
|
||||
ImGui::BeginChild("pad", ImVec2(kW, kH), false);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const ImVec2 o = ImGui::GetWindowPos();
|
||||
auto C = [&](float x, float y) { return ImVec2(o.x + x, o.y + y); };
|
||||
|
||||
// 顶部:线性扳机条 + 肩键
|
||||
Trigger(dl, C(52, 8), C(122, 20), gp.axes[4]); // LT
|
||||
Trigger(dl, C(kW - 122, 8), C(kW - 52, 20), gp.axes[5]); // RT
|
||||
Block(dl, C(78, 40), 76.0f, 20.0f, "LB", gp.buttons[kLB]);
|
||||
Block(dl, C(kW - 78, 40), 76.0f, 20.0f, "RB", gp.buttons[kRB]);
|
||||
|
||||
// 左上:左摇杆|左下:十字键(向右错开——真实手柄摇杆与十字不对齐)
|
||||
Stick(dl, C(76, 118), 46.0f, gp.axes[0], gp.axes[1], kDead);
|
||||
Dpad(dl, C(128, 210), 36.0f, 24.0f, gp.buttons);
|
||||
|
||||
// 中央:Back / Guide / Start
|
||||
Block(dl, C(184, 74), 40.0f, 16.0f, "Back", gp.buttons[kBack]);
|
||||
Circle(dl, C(220, 58), 13.0f, "G", gp.buttons[kGuide]);
|
||||
Block(dl, C(256, 74), 40.0f, 16.0f, "Start", gp.buttons[kStart]);
|
||||
|
||||
// 右上:ABXY 菱形|右下:右摇杆(对称错开,向中心收)
|
||||
const ImVec2 fc = C(360, 118);
|
||||
Face(dl, ImVec2(fc.x, fc.y - 24), 17.0f, 'Y', gp.buttons[kY]);
|
||||
Face(dl, ImVec2(fc.x - 24, fc.y), 17.0f, 'X', gp.buttons[kX]);
|
||||
Face(dl, ImVec2(fc.x + 24, fc.y), 17.0f, 'B', gp.buttons[kB]);
|
||||
Face(dl, ImVec2(fc.x, fc.y + 24), 17.0f, 'A', gp.buttons[kA]);
|
||||
Stick(dl, C(312, 210), 46.0f, gp.axes[2], gp.axes[3], kDead);
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// GamepadPanel:手柄输入可视化回显(Tools 菜单弹出窗,非 dock 常驻)。
|
||||
//
|
||||
// 三块:摇杆画布(原始轴值 + 引擎死区参考圈)/ 按键网格(按下高亮)/
|
||||
// 动作层回显(引擎死区+钳制后脚本真正拿到的 Move/Look/Jump)。
|
||||
// 测试用途:不接手柄时显示未连接状态。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_GAMEPADPANEL_H
|
||||
#define HELLOVULKAN_GAMEPADPANEL_H
|
||||
|
||||
#include "EditorContext.h"
|
||||
|
||||
class GamepadPanel {
|
||||
public:
|
||||
void Draw(const EditorContext& ctx);
|
||||
|
||||
bool m_Open = false; // Tools 菜单开关(窗口 X 也能关)
|
||||
};
|
||||
|
||||
#endif //HELLOVULKAN_GAMEPADPANEL_H
|
||||
@@ -198,6 +198,12 @@ namespace
|
||||
|
||||
for (u32 i = 0; i < meshes.size(); ++i)
|
||||
{
|
||||
// 「mesh」下拉的过滤:选了具体子 mesh 就只列该槽位(与渲染/
|
||||
// 物理的子引用语义一致);All meshes(-1)= 全列
|
||||
if (mr.meshIndex >= 0 && static_cast<int>(i) != mr.meshIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
|
||||
const std::string meshName =
|
||||
@@ -689,13 +695,15 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
ImGui::RadioButton("Plane", &shape, 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Trans.Plane", &shape, 2);
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
// 固定宽度而非 -1:弹窗是 AlwaysAutoResize,-1(撑满可用
|
||||
// 宽度)会拿上一帧宽度当基准 → 逐帧膨胀直到撑满屏幕
|
||||
ImGui::SetNextItemWidth(180.0f);
|
||||
ImGui::SliderFloat("half size", &half, 0.01f, 10.0f,
|
||||
"%.2f");
|
||||
ImGui::ColorEdit3("color", col);
|
||||
if (shape == 2)
|
||||
{
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::SetNextItemWidth(180.0f);
|
||||
ImGui::SliderFloat("alpha", &alpha, 0.0f, 1.0f,
|
||||
"%.2f");
|
||||
}
|
||||
@@ -1271,6 +1279,8 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
ImGui::DragFloat("Radius", &cc->radius, 0.02f, 0.05f, 5.0f);
|
||||
ImGui::DragFloat("Half Height", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f);
|
||||
ImGui::DragFloat("Mass", &cc->mass, 1.0f, 1.0f, 500.0f, "%.0f");
|
||||
// 形状附加偏移:叠加在脚底约定之上(模型 pivot 对齐用)
|
||||
ImGui::DragFloat3("Shape Offset", &cc->shapeOffset.x, 0.02f, -5.0f, 5.0f);
|
||||
// ---- Movement:扫掠运动学参数 ----
|
||||
ImGui::SeparatorText("Movement");
|
||||
ImGui::DragFloat("Max Slope (deg)", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f);
|
||||
@@ -1901,11 +1911,31 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
// 警告跳过)----
|
||||
ImGui::SeparatorText("Clips");
|
||||
const auto& files = AssetModelFiles();
|
||||
std::vector<const char*> opts;
|
||||
opts.push_back("<attach animation file...>");
|
||||
// 显示真名(AssetDisplayName:文件 stem,导入资产库的
|
||||
// 哈希 "asset" 读同目录 pathname 取原始名);重名者补
|
||||
// 父目录区分——不再把哈希路径直接摆给用户(= 显示成 id)
|
||||
std::vector<std::string> labels;
|
||||
labels.reserve(files.size());
|
||||
for (const auto& f : files)
|
||||
{
|
||||
opts.push_back(f.c_str());
|
||||
std::string label = Engine::Model::AssetDisplayName(f);
|
||||
if (std::find(labels.begin(), labels.end(), label) !=
|
||||
labels.end())
|
||||
{
|
||||
label += " (" +
|
||||
std::filesystem::path(f)
|
||||
.parent_path()
|
||||
.filename()
|
||||
.string() +
|
||||
")";
|
||||
}
|
||||
labels.push_back(std::move(label));
|
||||
}
|
||||
std::vector<const char*> opts;
|
||||
opts.push_back("<attach animation file...>");
|
||||
for (const auto& l : labels)
|
||||
{
|
||||
opts.push_back(l.c_str());
|
||||
}
|
||||
int sel = 0;
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
@@ -1925,7 +1955,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
}
|
||||
// attach 遥测(骨骼匹配结果,面板下方显示)
|
||||
s_LastAttachFile =
|
||||
std::filesystem::path(path).filename().string();
|
||||
Engine::Model::AssetDisplayName(path);
|
||||
s_LastAttachMatched =
|
||||
res.channelsMatched + res.channelsRetargeted;
|
||||
s_LastAttachRetargeted = res.channelsRetargeted;
|
||||
@@ -1938,10 +1968,9 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
||||
for (size_t i = 0; i < anim->attachedAnimPaths.size(); ++i)
|
||||
{
|
||||
const auto& p = anim->attachedAnimPaths[i];
|
||||
ImGui::BulletText("%s", std::filesystem::path(p)
|
||||
.filename()
|
||||
.string()
|
||||
.c_str());
|
||||
ImGui::BulletText("%s",
|
||||
Engine::Model::AssetDisplayName(p)
|
||||
.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
const bool doDetach = ImGui::SmallButton("x");
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// LogPanel.cpp:运行时日志面板实现。
|
||||
//
|
||||
// 数据源 = Engine::Debug::LogRing(spdlog ring sink 落的整行)。
|
||||
// 级别着色(trace 灰 / debug 青 / info 浅白 / warn 黄 / error 红 /
|
||||
// fatal 亮红带背景条)+ 级别过滤 + 文本过滤 + Auto-scroll(滚动条
|
||||
// 贴底才自动拉,上翻看历史不被拽回)。
|
||||
//
|
||||
|
||||
#include "LogPanel.h"
|
||||
|
||||
#include <Engine/Core/LogRing.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
void LogPanel::Draw(const EditorContext& ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (!ImGui::Begin("Log"))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 工具栏:清空 / 自动滚动 / 文本过滤 / 级别过滤 ----
|
||||
if (ImGui::Button("Clear"))
|
||||
{
|
||||
Engine::Debug::LogRing().Clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Auto-scroll", &m_AutoScroll);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
ImGui::InputTextWithHint("##filter", "filter...", m_Filter, sizeof(m_Filter));
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Trace", &m_ShowTrace);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Info", &m_ShowInfo);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Warn", &m_ShowWarn);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Error", &m_ShowError);
|
||||
ImGui::Separator();
|
||||
|
||||
// ---- 版本感知:有新日志且滚动条贴底 → 拉底(上翻历史不拽回)----
|
||||
const Engine::u64 ver = Engine::Debug::LogRing().Version();
|
||||
bool scrollToBottom = false;
|
||||
if (ver != m_LastVersion)
|
||||
{
|
||||
m_LastVersion = ver;
|
||||
if (m_AutoScroll)
|
||||
{
|
||||
scrollToBottom = ImGui::GetScrollY() >=
|
||||
ImGui::GetScrollMaxY() - 24.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("loglist", {0.0f, 0.0f}, false,
|
||||
ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
const auto entries = Engine::Debug::LogRing().Snapshot();
|
||||
for (const auto& e : entries)
|
||||
{
|
||||
using lv = spdlog::level::level_enum;
|
||||
bool show = false;
|
||||
switch (e.level)
|
||||
{
|
||||
case lv::trace:
|
||||
case lv::debug: show = m_ShowTrace; break;
|
||||
case lv::info: show = m_ShowInfo; break;
|
||||
case lv::warn: show = m_ShowWarn; break;
|
||||
case lv::err:
|
||||
case lv::critical: show = m_ShowError; break;
|
||||
default: show = m_ShowInfo; break;
|
||||
}
|
||||
if (!show) continue;
|
||||
if (m_Filter[0] != '\0')
|
||||
{
|
||||
// 简单子串过滤(大小写不敏感)
|
||||
std::string lower;
|
||||
lower.reserve(e.msg.size());
|
||||
for (char c : e.msg)
|
||||
{
|
||||
lower.push_back(static_cast<char>(
|
||||
std::tolower(static_cast<unsigned char>(c))));
|
||||
}
|
||||
std::string needle;
|
||||
needle.reserve(strlen(m_Filter));
|
||||
for (const char* p = m_Filter; *p; ++p)
|
||||
{
|
||||
needle.push_back(static_cast<char>(
|
||||
std::tolower(static_cast<unsigned char>(*p))));
|
||||
}
|
||||
if (lower.find(needle) == std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 渲染:级别徽标(恒彩色,固定列)+ 消息本体 ----
|
||||
// 消息色只有 warn/error/fatal 染色(info 白是惯例),颜色
|
||||
// 辨识主要靠徽标列——UE Output Log 的 Verbosity 列同位
|
||||
ImVec4 tagCol, msgCol;
|
||||
const char* tag = "INFO";
|
||||
bool bg = false;
|
||||
switch (e.level)
|
||||
{
|
||||
case lv::trace:
|
||||
tag = "TRACE"; tagCol = {0.55f, 0.55f, 0.55f, 1.0f};
|
||||
msgCol = {0.55f, 0.55f, 0.55f, 1.0f}; break;
|
||||
case lv::debug:
|
||||
tag = "DEBUG"; tagCol = {0.40f, 0.80f, 0.90f, 1.0f};
|
||||
msgCol = {0.70f, 0.70f, 0.70f, 1.0f}; break;
|
||||
case lv::info:
|
||||
tag = "INFO"; tagCol = {0.45f, 0.72f, 0.95f, 1.0f};
|
||||
msgCol = {0.90f, 0.90f, 0.90f, 1.0f}; break;
|
||||
case lv::warn:
|
||||
tag = "WARN"; tagCol = {1.00f, 0.78f, 0.20f, 1.0f};
|
||||
msgCol = {1.00f, 0.78f, 0.20f, 1.0f}; break;
|
||||
case lv::err:
|
||||
tag = "ERROR"; tagCol = {1.00f, 0.35f, 0.35f, 1.0f};
|
||||
msgCol = {1.00f, 0.35f, 0.35f, 1.0f}; break;
|
||||
default:
|
||||
tag = "FATAL"; tagCol = {1.00f, 0.15f, 0.15f, 1.0f};
|
||||
msgCol = {1.00f, 0.15f, 0.15f, 1.0f};
|
||||
bg = true; break;
|
||||
}
|
||||
if (bg)
|
||||
{
|
||||
// fatal:整行暗红背景条提级(先画背景再画文本)
|
||||
const ImVec2 p0 = ImGui::GetCursorScreenPos();
|
||||
const ImVec2 p1(p0.x + ImGui::GetContentRegionAvail().x,
|
||||
p0.y + ImGui::GetTextLineHeight());
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||
p0, p1, IM_COL32(90, 10, 10, 160));
|
||||
}
|
||||
ImGui::TextColored(tagCol, "%s", tag);
|
||||
ImGui::SameLine(40.0f); // 徽标固定列宽,消息对齐成两列
|
||||
ImGui::TextColored(msgCol, "%s", e.msg.c_str());
|
||||
}
|
||||
if (scrollToBottom)
|
||||
{
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// LogPanel:运行时日志面板(环形缓冲渲染,级别着色 + 过滤)。
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_LOGPANEL_H
|
||||
#define HELLOVULKAN_LOGPANEL_H
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
|
||||
#include "EditorContext.h"
|
||||
|
||||
class LogPanel {
|
||||
public:
|
||||
void Draw(const EditorContext& ctx);
|
||||
|
||||
private:
|
||||
bool m_ShowTrace = false;
|
||||
bool m_ShowInfo = true;
|
||||
bool m_ShowWarn = true;
|
||||
bool m_ShowError = true;
|
||||
bool m_AutoScroll = true;
|
||||
char m_Filter[128] = "";
|
||||
Engine::u64 m_LastVersion = 0;
|
||||
};
|
||||
|
||||
#endif //HELLOVULKAN_LOGPANEL_H
|
||||
@@ -173,6 +173,9 @@ void MaterialEditorPanel::DrawAssetMode(const EditorContext& ctx)
|
||||
EditParam(
|
||||
ImGui::DragFloat("Roughness", &p.factors.y, 0.01f, 0.0f, 1.0f));
|
||||
EditParam(ImGui::ColorEdit4("Base Color", &p.baseColor.x));
|
||||
// albedo 贴图 alpha 作细节叠加遮罩(脸部腮红/卧蚕等):rgb 叠加色,
|
||||
// a 强度(0 = 关闭);需 albedo 贴图自带 alpha 遮罩才有局部效果
|
||||
EditParam(ImGui::ColorEdit4("Alpha Overlay", &p.alphaOverlay.x));
|
||||
EditParam(ImGui::DragFloat("IOR", &p.surface.x, 0.01f, 1.0f, 3.0f));
|
||||
// 发光 = emissiveFactor × emissive 贴图 × Strength(Emissive 槽
|
||||
// 兜底贴图恒黑,无贴图时颜色因子是唯一入口)
|
||||
@@ -192,6 +195,23 @@ void MaterialEditorPanel::DrawAssetMode(const EditorContext& ctx)
|
||||
ImGui::TextDisabled("Transmission/Thickness need Transparent");
|
||||
}
|
||||
|
||||
// UV 镜像(材质级开关,作用于全部纹理槽):源资产 UV 朝向与引擎
|
||||
// 上左原点约定相反时贴图整体翻转,这里一键纠正。写入 misc.y/z,
|
||||
// 旧 .material 无该分量 → 缺省 0 = 关闭
|
||||
{
|
||||
bool flipU = p.misc.y > 0.5f;
|
||||
const bool c = ImGui::Checkbox("Flip U (left-right)", &flipU);
|
||||
if (c) { p.misc.y = flipU ? 1.0f : 0.0f; }
|
||||
EditParam(c);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
{
|
||||
bool flipV = p.misc.z > 0.5f;
|
||||
const bool c = ImGui::Checkbox("Flip V (up-down)", &flipV);
|
||||
if (c) { p.misc.z = flipV ? 1.0f : 0.0f; }
|
||||
EditParam(c);
|
||||
}
|
||||
|
||||
// ---- 纹理槽(变化 → 落盘 + waitIdle 重建)----
|
||||
ImGui::SeparatorText("Textures");
|
||||
static constexpr const char* kSlotNames[Engine::Material::kSlotCount] = {
|
||||
@@ -216,6 +236,13 @@ void MaterialEditorPanel::DrawAssetMode(const EditorContext& ctx)
|
||||
}
|
||||
if (ImGui::BeginPopup("tex_pick"))
|
||||
{
|
||||
// 清除入口(Unity/UE 语义):该槽置空 → 回退兜底贴图
|
||||
if (ImGui::Selectable("<none>"))
|
||||
{
|
||||
pendingSlot = static_cast<int>(s);
|
||||
pendingPath.clear();
|
||||
}
|
||||
ImGui::Separator();
|
||||
for (const auto& f : EditorMaterialUtil::AssetTextureFiles())
|
||||
{
|
||||
if (ImGui::Selectable(f.c_str()))
|
||||
@@ -313,6 +340,7 @@ void MaterialEditorPanel::DrawBuiltinMode(const EditorContext& ctx)
|
||||
ImGui::DragFloat("Metallic", &p.factors.x, 0.01f, 0.0f, 1.0f);
|
||||
ImGui::DragFloat("Roughness", &p.factors.y, 0.01f, 0.0f, 1.0f);
|
||||
ImGui::ColorEdit4("Base Color", &p.baseColor.x);
|
||||
ImGui::ColorEdit4("Alpha Overlay", &p.alphaOverlay.x);
|
||||
ImGui::DragFloat("IOR", &p.surface.x, 0.01f, 1.0f, 3.0f);
|
||||
ImGui::ColorEdit3("Emissive Color", &p.emissiveFactor.x);
|
||||
ImGui::DragFloat("Emissive Strength", &p.surface.z, 0.05f, 0.0f, 100.0f);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/ECS/Components.h"
|
||||
#include "Engine/Physics/PhysicsWorld.h"
|
||||
#include "Engine/Core/Time.h"
|
||||
#include "Engine/Core/Stats.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
@@ -208,6 +209,26 @@ void StatsPanel::Draw(const EditorContext& ctx)
|
||||
{
|
||||
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
||||
}
|
||||
|
||||
// ---- GPU 资源台账(Stats 记账中枢的唯一消费者)----
|
||||
const auto st = Engine::Stats::Get().TakeSnapshot();
|
||||
auto MB = [](u64 bytes)
|
||||
{ return static_cast<double>(bytes) / (1024.0 * 1024.0); };
|
||||
ImGui::Separator();
|
||||
ImGui::Text("textures: %llu (%.2f MB)",
|
||||
static_cast<unsigned long long>(st.textureCount),
|
||||
MB(st.textureBytes));
|
||||
ImGui::Text("meshes: %llu (%.2f MB)",
|
||||
static_cast<unsigned long long>(st.meshCount),
|
||||
MB(st.meshBytes));
|
||||
ImGui::Text("buffers: %llu (%.2f MB)",
|
||||
static_cast<unsigned long long>(st.bufferCount),
|
||||
MB(st.bufferBytes));
|
||||
ImGui::Text("allocator (VMA 1:1): %.2f MB",
|
||||
MB(st.memoryUsedBytes));
|
||||
ImGui::Text("churn: %.2f MB / %.2f MB (total %.2f / %.2f)",
|
||||
MB(st.frameAllocation), MB(st.frameDeallocation),
|
||||
MB(st.totalAllocation), MB(st.totalDeallocation));
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
@@ -234,6 +234,14 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
// PIE 运行态:输入归游戏(脚本读 GLFW 轮询),Gizmo/编辑器
|
||||
// 热键全部让位(UE Play 语义:Scene view 编辑操作冻结)
|
||||
const bool playing = ctx.playState && *ctx.playState;
|
||||
// PIE 启动瞬间:视口抢焦点(UE Play 语义——游戏视角接管)。
|
||||
// 不抢的话焦点停在工具栏 Play 按钮上,编辑器相机飞行等
|
||||
// 持焦操作下一局全失灵
|
||||
static bool s_wasPlaying = false;
|
||||
if (playing && !s_wasPlaying) {
|
||||
ImGui::SetWindowFocus();
|
||||
}
|
||||
s_wasPlaying = playing;
|
||||
if (!playing && out.focused && !ImGui::GetIO().WantTextInput &&
|
||||
!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||
// Q = 纯选择模式(UE Q 工具语义):op=0 无任何轴位,
|
||||
|
||||
Reference in New Issue
Block a user