add simple node editor(use ImGui-Node-Editor), some tweaks

This commit is contained in:
2026-04-08 23:30:33 +08:00
parent 230957f728
commit d6d735900a
13 changed files with 1000 additions and 366 deletions

3
.gitmodules vendored
View File

@ -35,3 +35,6 @@
[submodule "Prism/vendor/efsw"] [submodule "Prism/vendor/efsw"]
path = Prism/vendor/efsw path = Prism/vendor/efsw
url = https://github.com/SpartanJ/efsw url = https://github.com/SpartanJ/efsw
[submodule "Prism/vendor/imgui-node-editor"]
path = Prism/vendor/imgui-node-editor
url = https://github.com/thedmd/imgui-node-editor.git

View File

@ -719,7 +719,7 @@ namespace Prism
m_ViewportBounds[1] = { maxBound.x, maxBound.y }; m_ViewportBounds[1] = { maxBound.x, maxBound.y };
// ImGuizmo // ImGuizmo
if (m_GizmoType != -1 && !m_SelectionContext.empty() && m_SceneState == SceneState::Edit) if ((m_ViewportPanelFocused || m_ViewportPanelHovered) && m_GizmoType != -1 && !m_SelectionContext.empty() && m_SceneState == SceneState::Edit)
{ {
auto& selection = m_SelectionContext[0]; auto& selection = m_SelectionContext[0];
@ -921,15 +921,19 @@ namespace Prism
if (ImViewGuizmo::Rotate(cameraPos, cameraRot, glm::vec3(0.0f), gizmoCenter)) if (ImViewGuizmo::Rotate(cameraPos, cameraRot, glm::vec3(0.0f), gizmoCenter))
{ {
if (m_ViewportPanelFocused || m_ViewportPanelHovered)
{
glm::vec3 forward = cameraRot * glm::vec3(0.0f, 0.0f, -1.0f);
float newPitch = glm::asin(glm::clamp(-forward.y, -1.0f, 1.0f));
float newYaw = -glm::atan(forward.x, -forward.z);
glm::vec3 forward = cameraRot * glm::vec3(0.0f, 0.0f, -1.0f); float distance = glm::length(cameraPos - focusPosition);
float newPitch = glm::asin(glm::clamp(-forward.y, -1.0f, 1.0f)); m_EditorCamera.SetDistance(distance);
float newYaw = -glm::atan(forward.x, -forward.z); m_EditorCamera.SetFocusPosition(focusPosition);
m_EditorCamera.SetYawPitch(newYaw, newPitch);
}
float distance = glm::length(cameraPos - focusPosition);
m_EditorCamera.SetDistance(distance);
m_EditorCamera.SetFocusPosition(focusPosition);
m_EditorCamera.SetYawPitch(newYaw, newPitch);
} }
} }
@ -994,20 +998,20 @@ namespace Prism
break; break;
} }
} }
}
switch (e.GetKeyCode()) switch (e.GetKeyCode())
{
case KeyCode::DELETE:
if (m_SelectionContext.size())
{ {
const Entity selectedEntity = m_SelectionContext[0].Entity; case KeyCode::DELETE:
m_EditorScene->DestroyEntity(selectedEntity); if (m_SelectionContext.size())
m_SelectionContext.clear(); {
m_EditorScene->SetSelectedEntity({}); const Entity selectedEntity = m_SelectionContext[0].Entity;
m_SceneHierarchyPanel->SetSelected({}); m_EditorScene->DestroyEntity(selectedEntity);
m_SelectionContext.clear();
m_EditorScene->SetSelectedEntity({});
m_SceneHierarchyPanel->SetSelected({});
}
break;
} }
break;
} }
if (Input::IsKeyPressed(KeyCode::LEFT_CONTROL)) if (Input::IsKeyPressed(KeyCode::LEFT_CONTROL))

View File

@ -50,6 +50,19 @@ list(APPEND SRC_SOURCE ${IMGUIZMO_SOURCE})
# ----------- ImViewGuizmo ------------- # ----------- ImViewGuizmo -------------
set(IMVIEWGUIZMO_DIR vendor/ImViewGuizmo) set(IMVIEWGUIZMO_DIR vendor/ImViewGuizmo)
# ----------- ImGui-Node-Editor -------------
set(IMGUI_NODE_EDITOR_DIR vendor/imgui-node-editor)
set(IMGUI_NODE_EDITOR_SOURCES
${IMGUI_NODE_EDITOR_DIR}/imgui_node_editor.cpp
${IMGUI_NODE_EDITOR_DIR}/imgui_node_editor_api.cpp
${IMGUI_NODE_EDITOR_DIR}/imgui_canvas.cpp
${IMGUI_NODE_EDITOR_DIR}/crude_json.cpp
${IMGUI_NODE_EDITOR_DIR}/examples/blueprints-example/utilities/widgets.cpp
${IMGUI_NODE_EDITOR_DIR}/examples/blueprints-example/utilities/drawing.cpp
)
list(APPEND SRC_SOURCE ${IMGUI_NODE_EDITOR_SOURCES})
# ------------- NVIDIA PhysX ------------- # ------------- NVIDIA PhysX -------------
# PhysX/physx/buildtools/presets/*.xml # PhysX/physx/buildtools/presets/*.xml
@ -125,6 +138,7 @@ set(TARGET_INCLUDE_DIR
${IMGUI_DIR} ${IMGUI_DIR}
${IMGUIZMO_DIR} ${IMGUIZMO_DIR}
${IMVIEWGUIZMO_DIR} ${IMVIEWGUIZMO_DIR}
${IMGUI_NODE_EDITOR_DIR}
) )
# ------------- debug Defines ------------- # ------------- debug Defines -------------

View File

@ -4,7 +4,9 @@
#include "AssetEditorPanel.h" #include "AssetEditorPanel.h"
#include "DefaultAssetEditors.h" #include "DefaultAssetEditors/PBRMaterialAssetEditor.h"
#include "DefaultAssetEditors/PhysicsMaterialEditor.h"
#include "DefaultAssetEditors/TextureAssetEditor.h"
#include "Prism/Asset/AssetSerializer.h" #include "Prism/Asset/AssetSerializer.h"
#include "Prism/Core/Log.h" #include "Prism/Core/Log.h"

View File

@ -1,284 +0,0 @@
//
// Created by Atdunbg on 2026/2/14.
//
#include "DefaultAssetEditors.h"
#include "Prism/Asset/AssetSerializer.h"
#include "Prism/Core/Log.h"
namespace Prism
{
// MaterialEditor
PhysicsMaterialEditor::PhysicsMaterialEditor()
: AssetEditor("Edit Physics Material") {}
void PhysicsMaterialEditor::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset) AssetSerializer::SerializeAsset(m_Asset);
m_Asset = static_cast<Ref<PhysicsMaterialAsset>>(asset);
}
void PhysicsMaterialEditor::Render()
{
if (!m_Asset)
SetOpen(false);
UI::BeginPropertyGrid();
UI::Property("Static Friction", m_Asset->StaticFriction);
UI::Property("Dynamic Friction", m_Asset->DynamicFriction);
UI::Property("Bounciness", m_Asset->Bounciness);
UI::EndPropertyGrid();
if (ImGui::Button("Save"))
AssetSerializer::SerializeAsset(m_Asset);
}
// MaterialEditor
TextureViewer::TextureViewer()
: AssetEditor("Edit Texture")
{
SetMinSize(200, 600);
SetMaxSize(500, 1000);
}
void TextureViewer::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset) AssetSerializer::SerializeAsset(m_Asset);
m_Asset = static_cast<Ref<Texture>>(asset);
}
void TextureViewer::Render()
{
if (!m_Asset)
SetOpen(false);
float textureWidth = (float)m_Asset->GetWidth();
float textureHeight = (float)m_Asset->GetHeight();
float bitsPerPixel = (float)Texture::GetBPP(m_Asset->GetFormat());
float imageSize = ImGui::GetWindowWidth() - 40;
imageSize = glm::min(imageSize, 500.0f);
ImGui::SetCursorPosX(20);
ImGui::Image((ImTextureID)m_Asset->GetRendererID(), { imageSize, imageSize });
UI::BeginPropertyGrid();
UI::Property("Width", textureWidth, 0.1f, 0.0f, 0.0f, true);
UI::Property("Height", textureHeight, 0.1f, 0.0f, 0.0f, true);
UI::Property("Bits", bitsPerPixel, 0.1f, 0.0f, 0.0f, true);
UI::EndPropertyGrid();
}
PBRMaterialEditor::PBRMaterialEditor() : AssetEditor("Material Editor")
{
}
// 辅助函数:绘制纹理拖拽槽
void DrawTextureSlot(Ref<Texture2D>& texture, const std::function<void(Ref<Texture2D>)>& onDrop)
{
static Ref<Texture2D> s_Checkerboard = nullptr;
if (!s_Checkerboard)
s_Checkerboard = AssetsManager::GetAsset<Texture2D>("assets/editor/Checkerboard.tga");
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
ImGui::Image(texture ? (ImTextureID)(intptr_t)texture->GetRendererID() : (ImTextureID)(intptr_t)s_Checkerboard->GetRendererID(),
ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::BeginDragDropTarget())
{
if (const auto data = ImGui::AcceptDragDropPayload("asset_payload"))
{
AssetHandle assetHandle = *(AssetHandle*)data->Data;
if (AssetsManager::IsAssetType(assetHandle, AssetType::Texture))
{
Ref<Texture2D> newTex = AssetsManager::GetAsset<Texture2D>(assetHandle);
if (onDrop) onDrop(newTex);
}
}
ImGui::EndDragDropTarget();
}
if (ImGui::IsItemHovered())
{
if (texture)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(texture->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureID)(intptr_t)texture->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
/*
// TODO: how to solve this
if (ImGui::IsItemClicked(ImGuiMouseButton_Left))
{
std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg");
if (!filename.empty())
{
Ref<Texture2D> newTex = Texture2D::Create(filename);
if (onDrop) onDrop(newTex);
}
}
*/
}
}
void PBRMaterialEditor::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset)
{
if (m_Asset->Handle == asset->Handle) return;
if (AssetsManager::IsAssetHandleValid(m_Asset->Handle))
AssetSerializer::SerializeAsset(m_Asset);
}
m_Asset = static_cast<Ref<PBRMaterialAsset>>(asset);
}
void PBRMaterialEditor::Render()
{
if (!m_Asset) return;
auto& material = m_Asset;
if (ImGui::BeginTabBar("MaterialProperties", ImGuiTabBarFlags_None))
{
// ==================== Parameters ====================
if (ImGui::BeginTabItem("Parameters"))
{
ImGui::Spacing();
if (ImGui::CollapsingHeader("Albedo", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::ColorEdit3("Color##Albedo", glm::value_ptr(material->AlbedoColor)))
{
m_Asset->IsDirty = true;
}
bool useAlbedoMap = (material->AlbedoTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Albedo", &useAlbedoMap))
{
material->AlbedoTexToggle = useAlbedoMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->AlbedoTexture, [&](const Ref<Texture2D>& newTex) {
material->AlbedoTexture = newTex;
material->AlbedoTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Albedo Texture");
ImGui::Unindent();
}
// Normal Map
if (ImGui::CollapsingHeader("Normal Map", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
bool useNormalMap = (material->NormalTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Normal", &useNormalMap))
{
material->NormalTexToggle = useNormalMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->NormalTexture, [&](const Ref<Texture2D>& newTex) {
material->NormalTexture = newTex;
material->NormalTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Normal Texture");
ImGui::Unindent();
}
// Metalness
if (ImGui::CollapsingHeader("Metalness", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::SliderFloat("Value##Metalness", &material->Metalness, 0.0f, 1.0f))
{
// 自动保存
m_Asset->IsDirty = true;
}
bool useMetalnessMap = (material->MetalnessTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Metalness", &useMetalnessMap))
{
material->MetalnessTexToggle = useMetalnessMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->MetalnessTexture, [&](const Ref<Texture2D>& newTex) {
material->MetalnessTexture = newTex;
material->MetalnessTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Metalness Texture");
ImGui::Unindent();
}
// Roughness
if (ImGui::CollapsingHeader("Roughness", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::SliderFloat("Value##Roughness", &material->Roughness, 0.0f, 1.0f))
{
// 自动保存
m_Asset->IsDirty = true;
}
bool useRoughnessMap = (material->RoughnessTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Roughness", &useRoughnessMap))
{
material->RoughnessTexToggle = useRoughnessMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->RoughnessTexture, [&](const Ref<Texture2D>& newTex) {
material->RoughnessTexture = newTex;
material->RoughnessTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Roughness Texture");
ImGui::Unindent();
}
ImGui::EndTabItem();
}
// ==================== 预览 ====================
if (ImGui::BeginTabItem("Preview"))
{
// 简单显示纹理预览
if (material->AlbedoTexture)
{
ImGui::Text("Albedo Preview");
ImGui::Image((ImTextureID)(intptr_t)material->AlbedoTexture->GetRendererID(), ImVec2(128, 128));
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
}

View File

@ -1,62 +0,0 @@
//
// Created by Atdunbg on 2026/2/14.
//
#ifndef PRISM_DEFAULTASSETEDITORS_H
#define PRISM_DEFAULTASSETEDITORS_H
#include "AssetEditorPanel.h"
#include "Prism/Renderer/Texture.h"
namespace Prism
{
class RenderPass;
class PhysicsMaterialEditor : public AssetEditor
{
public:
PhysicsMaterialEditor();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
void Render() override;
private:
Ref<PhysicsMaterialAsset> m_Asset;
};
class TextureViewer : public AssetEditor
{
public:
TextureViewer();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
void Render() override;
private:
Ref<Texture> m_Asset;
};
class PBRMaterialEditor : public AssetEditor
{
public:
PBRMaterialEditor();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
void Render() override;
private:
Ref<PBRMaterialAsset> m_Asset;
};
}
#endif //PRISM_DEFAULTASSETEDITORS_H

View File

@ -0,0 +1,718 @@
//
// Created by Atdunbg on 2026/2/14.
//
#include "PBRMaterialAssetEditor.h"
#include "examples/blueprints-example/utilities/widgets.h"
#include "Prism/Asset/AssetSerializer.h"
#include "Prism/Core/Log.h"
namespace Prism
{
PBRMaterialEditor::PBRMaterialEditor() : AssetEditor("Material Editor")
{
ed::Config config;
config.SettingsFile = "";
m_EditorContext = ed::CreateEditor(&config);
}
PBRMaterialEditor::~PBRMaterialEditor()
{
if (m_EditorContext)
{
ed::DestroyEditor(m_EditorContext);
m_EditorContext = nullptr;
}
}
// 辅助函数:绘制纹理拖拽槽
void DrawTextureSlot(Ref<Texture2D>& texture, const std::function<void(Ref<Texture2D>)>& onDrop)
{
static Ref<Texture2D> s_Checkerboard = nullptr;
if (!s_Checkerboard)
s_Checkerboard = AssetsManager::GetAsset<Texture2D>("assets/editor/Checkerboard.tga");
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
ImGui::Image(texture
? (ImTextureID)(intptr_t)texture->GetRendererID()
: (ImTextureID)(intptr_t)s_Checkerboard->GetRendererID(),
ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::BeginDragDropTarget())
{
if (const auto data = ImGui::AcceptDragDropPayload("asset_payload"))
{
AssetHandle assetHandle = *(AssetHandle*)data->Data;
if (AssetsManager::IsAssetType(assetHandle, AssetType::Texture))
{
Ref<Texture2D> newTex = AssetsManager::GetAsset<Texture2D>(assetHandle);
if (onDrop) onDrop(newTex);
}
}
ImGui::EndDragDropTarget();
}
if (ImGui::IsItemHovered())
{
if (texture)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(texture->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureID)(intptr_t)texture->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
/*
// TODO: how to solve this
if (ImGui::IsItemClicked(ImGuiMouseButton_Left))
{
std::string filename = FileSystem::OpenFileSelector("*.png;*.tga;*.jpg;*.jpeg");
if (!filename.empty())
{
Ref<Texture2D> newTex = Texture2D::Create(filename);
if (onDrop) onDrop(newTex);
}
}
*/
}
}
void PBRMaterialEditor::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset)
{
if (m_Asset->Handle == asset->Handle) return;
if (AssetsManager::IsAssetHandleValid(m_Asset->Handle))
AssetSerializer::SerializeAsset(m_Asset);
}
m_Asset = static_cast<Ref<PBRMaterialAsset>>(asset);
LoadAssetToGraph(); // 加载资产数据到节点图
}
void PBRMaterialEditor::Render()
{
if (!m_Asset) return;
if (ImGui::BeginTabBar("MaterialProperties", ImGuiTabBarFlags_None))
{
if (ImGui::BeginTabItem("Basic Edit panel"))
{
RenderBasicEditorPanel();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Graph Node Edit"))
{
// TODO: using ImGui Node Editor impl
RenderNodeEditorPanel();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void PBRMaterialEditor::RenderBasicEditorPanel()
{
auto& material = m_Asset;
// ==================== Parameters ====================
{
ImGui::Spacing();
if (ImGui::CollapsingHeader("Albedo", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::ColorEdit3("Color##Albedo", glm::value_ptr(material->AlbedoColor)))
{
m_Asset->IsDirty = true;
}
bool useAlbedoMap = (material->AlbedoTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Albedo", &useAlbedoMap))
{
material->AlbedoTexToggle = useAlbedoMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->AlbedoTexture, [&](const Ref<Texture2D>& newTex)
{
material->AlbedoTexture = newTex;
material->AlbedoTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Albedo Texture");
ImGui::Unindent();
}
// Normal Map
if (ImGui::CollapsingHeader("Normal Map", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
bool useNormalMap = (material->NormalTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Normal", &useNormalMap))
{
material->NormalTexToggle = useNormalMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->NormalTexture, [&](const Ref<Texture2D>& newTex)
{
material->NormalTexture = newTex;
material->NormalTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Normal Texture");
ImGui::Unindent();
}
// Metalness
if (ImGui::CollapsingHeader("Metalness", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::SliderFloat("Value##Metalness", &material->Metalness, 0.0f, 1.0f))
{
// 自动保存
m_Asset->IsDirty = true;
}
bool useMetalnessMap = (material->MetalnessTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Metalness", &useMetalnessMap))
{
material->MetalnessTexToggle = useMetalnessMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->MetalnessTexture, [&](const Ref<Texture2D>& newTex)
{
material->MetalnessTexture = newTex;
material->MetalnessTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Metalness Texture");
ImGui::Unindent();
}
// Roughness
if (ImGui::CollapsingHeader("Roughness", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Indent();
if (ImGui::SliderFloat("Value##Roughness", &material->Roughness, 0.0f, 1.0f))
{
// 自动保存
m_Asset->IsDirty = true;
}
bool useRoughnessMap = (material->RoughnessTexToggle > 0.5f);
if (ImGui::Checkbox("Use Texture##Roughness", &useRoughnessMap))
{
material->RoughnessTexToggle = useRoughnessMap ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
DrawTextureSlot(material->RoughnessTexture, [&](const Ref<Texture2D>& newTex)
{
material->RoughnessTexture = newTex;
material->RoughnessTexToggle = true;
m_Asset->IsDirty = true;
});
ImGui::SameLine();
ImGui::Text("Roughness Texture");
ImGui::Unindent();
}
}
}
ImColor PBRMaterialEditor::GetPinColor(int pinType)
{
switch (pinType)
{
case Albedo: return ImColor(220, 48, 48);
case Normal: return ImColor(68, 201, 156);
case Metallic: return ImColor(147, 226, 74);
case Roughness: return ImColor(124, 21, 153);
case AO: return ImColor(51, 150, 215);
default: return ImColor(255, 255, 255);
}
}
void PBRMaterialEditor::DrawPinIcon(bool connected, int alpha, int pinType)
{
ImColor color = GetPinColor(pinType);
color.Value.w = alpha / 255.0f;
ax::Widgets::Icon(ImVec2(24, 24), ax::Drawing::IconType::Circle, connected, color, ImColor(32, 32, 32, alpha));
}
void PBRMaterialEditor::DrawOutputPinIcon(bool connected, int alpha)
{
ax::Widgets::Icon(ImVec2(24, 24), ax::Drawing::IconType::Circle, connected, ImColor(255, 255, 255, alpha),
ImColor(32, 32, 32, alpha));
}
float PBRMaterialEditor::DrawNodeHeader(const char* title, float minWidth)
{
ImGui::TextUnformatted(title);
ImGui::Dummy(ImVec2(minWidth, 4.0f));
float headerBottomY = ImGui::GetCursorScreenPos().y;
ImGui::Dummy(ImVec2(0, 6.0f));
return headerBottomY;
}
// 节点编辑器面板
void PBRMaterialEditor::RenderNodeEditorPanel()
{
ed::SetCurrentEditor(m_EditorContext);
// 顶部工具栏
if (ImGui::Button("Add Texture Node"))
{
int newId = m_NextNodeId++;
float yOffset = m_TextureNodes.size() * 170.0f;
m_TextureNodes.push_back({newId, AssetHandle(0), 50.0f, yOffset});
m_FirstFrame = true;
}
ImGui::SameLine();
if (ImGui::Button("Compile to Asset"))
{
CompileMaterialToAsset();
m_Asset->IsDirty = true;
}
ed::Begin("MaterialNodeEditor");
if (m_FirstFrame)
{
// 定位节点
ed::SetNodePosition(ed::NodeId(OUTPUT_NODE_ID), ImVec2(400, 100));
for (auto& node : m_TextureNodes)
ed::SetNodePosition(ed::NodeId(node.ID), ImVec2(node.PosX, node.PosY));
ed::NavigateToContent();
m_FirstFrame = false;
}
// 绘制输出节点
DrawOutputNode();
// 绘制所有纹理节点
for (auto& node : m_TextureNodes)
DrawTextureNode(node.ID, node.TextureHandle);
// 绘制连线
for (const auto& link : m_Links)
{
int endPinIndex = link.EndPinID % 100;
ImColor linkColor = GetPinColor(endPinIndex);
ed::Link(ed::LinkId(link.ID), ed::PinId(link.StartPinID), ed::PinId(link.EndPinID), linkColor, 2.0f);
}
// 处理交互
HandleNodeEditorInteractions();
ed::End();
ed::SetCurrentEditor(nullptr);
}
void PBRMaterialEditor::DrawOutputNode()
{
const float nodeWidth = 240.0f;
ed::PushStyleVar(ed::StyleVar_NodePadding, ImVec4(8, 4, 8, 8));
ed::BeginNode(ed::NodeId(OUTPUT_NODE_ID));
ImVec2 headerMin = ImGui::GetCursorScreenPos();
ImGui::BeginGroup();
float headerMaxY = DrawNodeHeader("PBR Material Output", nodeWidth);
// TODO: this will add preview texture
ImGui::Dummy(ImVec2(nodeWidth, 128));
ImGui::Spacing();
// 输入引脚
auto drawInputPin = [&](PinType pinType, const char* label, auto valueWidget)
{
int pinId = InputPinID(pinType);
ed::BeginPin(ed::PinId(pinId), ed::PinKind::Input);
DrawPinIcon(IsPinLinked(pinId), 255, pinType);
ed::EndPin();
ImGui::SameLine();
ImGui::TextUnformatted(label);
if (!IsPinLinked(pinId))
{
ImGui::SameLine();
valueWidget();
}
};
drawInputPin(Albedo, "Albedo", [&]()
{
ImGui::SetNextItemWidth(80);
ImVec2 btnPos = ImGui::GetCursorScreenPos();
if (ImGui::ColorButton(
"##albedo", ImVec4(m_Asset->AlbedoColor.r, m_Asset->AlbedoColor.g, m_Asset->AlbedoColor.b, 1.0f),
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoBorder, ImVec2(20, 20)))
{
m_OpenAlbedoPicker = true;
m_AlbedoPickerPos = btnPos;
}
});
drawInputPin(Normal, "Normal", []() { ImGui::NewLine(); });
drawInputPin(Metallic, "Metallic", [&]()
{
ImGui::SetNextItemWidth(80);
if (ImGui::SliderFloat("##metallic", &m_Asset->Metalness, 0.0f, 1.0f))
m_Asset->IsDirty = true;
});
drawInputPin(Roughness, "Roughness", [&]()
{
ImGui::SetNextItemWidth(80);
if (ImGui::SliderFloat("##roughness", &m_Asset->Roughness, 0.0f, 1.0f))
m_Asset->IsDirty = true;
});
drawInputPin(AO, "AO", [&]()
{
ImGui::SetNextItemWidth(80);
float ao = 1.0f; // 如果资产没有 AO 字段可以忽略
if (ImGui::SliderFloat("##ao", &ao, 0.0f, 1.0f))
{
}
});
ImGui::EndGroup();
ImVec2 groupMin = ImGui::GetItemRectMin();
ImVec2 groupMax = ImGui::GetItemRectMax();
ed::EndNode();
// 绘制标题栏背景
float pad = 7.0f;
auto* drawList = ed::GetNodeBackgroundDrawList(ed::NodeId(OUTPUT_NODE_ID));
drawList->AddRectFilled(
ImVec2(groupMin.x - pad, headerMin.y - 3.0f),
ImVec2(groupMax.x + pad, headerMaxY),
IM_COL32(128, 16, 16, 230), 12.0f, ImDrawFlags_RoundCornersTop);
ed::PopStyleVar();
}
void PBRMaterialEditor::DrawTextureNode(int nodeId, AssetHandle& textureHandle)
{
const float nodeWidth = 120.0f;
ImGui::PushID(nodeId);
ed::PushStyleVar(ed::StyleVar_NodePadding, ImVec4(8, 4, 8, 8));
ed::BeginNode(ed::NodeId(nodeId));
ImVec2 headerMin = ImGui::GetCursorScreenPos();
ImGui::BeginGroup();
float headerMaxY = DrawNodeHeader("Texture2D", nodeWidth);
// 纹理预览
bool hasTexture = (textureHandle != 0);
float previewSize = 80.0f;
float offsetX = (nodeWidth - previewSize) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
if (hasTexture)
{
Ref<Texture2D> tex = AssetsManager::GetAsset<Texture2D>(textureHandle);
if (tex)
ImGui::Image((ImTextureID)(intptr_t)tex->GetRendererID(), ImVec2(previewSize, previewSize), ImVec2(0,1), ImVec2(1,0));
else
ImGui::Dummy(ImVec2(previewSize, previewSize));
}
else
{
ImVec2 pos = ImGui::GetCursorScreenPos();
ImDrawList* dl = ImGui::GetWindowDrawList();
// 修正:显式构造 ImVec2避免依赖运算符重载
ImVec2 pos_end = ImVec2(pos.x + previewSize, pos.y + previewSize);
dl->AddRectFilled(pos, pos_end, IM_COL32(50,50,50,255), 4.0f);
dl->AddRect(pos, pos_end, IM_COL32(90,90,90,255), 4.0f);
ImGui::Dummy(ImVec2(previewSize, previewSize));
}
// 拖拽接收
if (ImGui::BeginDragDropTarget())
{
if (const auto* payload = ImGui::AcceptDragDropPayload("asset_payload"))
{
AssetHandle handle = *(AssetHandle*)payload->Data;
if (AssetsManager::IsAssetType(handle, AssetType::Texture))
{
textureHandle = handle;
m_Asset->IsDirty = true;
}
}
ImGui::EndDragDropTarget();
}
ImGui::Spacing();
// 输出引脚(右对齐)
float pinOffset = nodeWidth - 50.0f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + pinOffset);
ImGui::TextUnformatted("Out");
ImGui::SameLine();
int outPinId = OutputPinID(nodeId);
ed::BeginPin(ed::PinId(outPinId), ed::PinKind::Output);
DrawOutputPinIcon(IsPinLinked(outPinId), 255);
ed::EndPin();
ImGui::EndGroup();
ImVec2 groupMin = ImGui::GetItemRectMin();
ImVec2 groupMax = ImGui::GetItemRectMax();
ed::EndNode();
float pad = 8.0f;
auto* bgDrawList = ed::GetNodeBackgroundDrawList(ed::NodeId(nodeId));
bgDrawList->AddRectFilled(
ImVec2(groupMin.x - pad, headerMin.y - 4.0f),
ImVec2(groupMax.x + pad, headerMaxY),
IM_COL32(0, 80, 120, 230), 12.0f, ImDrawFlags_RoundCornersTop);
ed::PopStyleVar();
ImGui::PopID();
}
void PBRMaterialEditor::HandleNodeEditorInteractions()
{
// ========== 1. 创建连线 ==========
if (ed::BeginCreate())
{
ed::PinId startId, endId;
if (ed::QueryNewLink(&startId, &endId))
{
if (startId && endId)
{
int start = (int)startId.Get();
int end = (int)endId.Get();
bool startIsOutput = (start / 100 >= 2) && (start % 100 == 1);
bool endIsInput = (end / 100 == OUTPUT_NODE_ID) && (end % 100 >= 1 && end % 100 <= 5);
if (!startIsOutput || !endIsInput)
{
std::swap(start, end);
startIsOutput = (start / 100 >= 2) && (start % 100 == 1);
endIsInput = (end / 100 == OUTPUT_NODE_ID) && (end % 100 >= 1 && end % 100 <= 5);
}
if (startIsOutput && endIsInput)
{
int pinType = end % 100;
if (!IsPinLinked(end))
{
if (ed::AcceptNewItem(GetPinColor(pinType), 2.0f))
{
m_Links.push_back({m_NextLinkId++, start, end});
m_Asset->IsDirty = true;
}
}
else
ed::RejectNewItem(ImVec4(1, 0, 0, 1), 2.0f);
}
else
ed::RejectNewItem(ImVec4(1, 0, 0, 1), 2.0f);
}
}
}
ed::EndCreate();
// ========== 2. 删除连线/节点 ==========
if (ed::BeginDelete())
{
ed::LinkId linkId;
while (ed::QueryDeletedLink(&linkId))
{
if (ed::AcceptDeletedItem())
{
int id = (int)linkId.Get();
m_Links.erase(std::remove_if(m_Links.begin(), m_Links.end(),
[id](const Link& l) { return l.ID == id; }), m_Links.end());
m_Asset->IsDirty = true;
}
}
ed::NodeId nodeId;
while (ed::QueryDeletedNode(&nodeId))
{
int id = (int)nodeId.Get();
if (id == OUTPUT_NODE_ID)
ed::RejectDeletedItem();
else if (ed::AcceptDeletedItem())
{
int outPin = OutputPinID(id);
m_Links.erase(std::remove_if(m_Links.begin(), m_Links.end(),
[outPin](const Link& l) { return l.StartPinID == outPin; }),
m_Links.end());
m_TextureNodes.erase(std::remove_if(m_TextureNodes.begin(), m_TextureNodes.end(),
[id](const TextureNode& n) { return n.ID == id; }),
m_TextureNodes.end());
m_Asset->IsDirty = true;
}
}
}
ed::EndDelete();
ed::Suspend();
if (ed::ShowBackgroundContextMenu())
ImGui::OpenPopup("NodeEditorContextMenu");
ed::NodeId contextNodeId;
if (ed::ShowNodeContextMenu(&contextNodeId))
{
m_ContextMenuNodeId = (int)contextNodeId.Get();
ImGui::OpenPopup("NodeContextMenu");
}
if (ImGui::BeginPopup("NodeEditorContextMenu"))
{
if (ImGui::MenuItem("Add Texture2D Node"))
{
int newId = m_NextNodeId++;
float yOffset = m_TextureNodes.size() * 170.0f;
m_TextureNodes.push_back({newId, AssetHandle(0), 50.0f, yOffset});
m_FirstFrame = true;
}
ImGui::EndPopup();
}
if (ImGui::BeginPopup("NodeContextMenu"))
{
int id = m_ContextMenuNodeId;
if (id != OUTPUT_NODE_ID)
{
if (ImGui::MenuItem("Delete Node"))
{
int outPin = OutputPinID(id);
m_Links.erase(std::remove_if(m_Links.begin(), m_Links.end(),
[outPin](const Link& l) { return l.StartPinID == outPin; }),
m_Links.end());
m_TextureNodes.erase(std::remove_if(m_TextureNodes.begin(), m_TextureNodes.end(),
[id](const TextureNode& n) { return n.ID == id; }),
m_TextureNodes.end());
m_Asset->IsDirty = true;
}
}
else
ImGui::TextDisabled("Cannot delete output node");
ImGui::EndPopup();
}
if (m_OpenAlbedoPicker)
{
ImGui::OpenPopup("AlbedoColorPicker");
m_OpenAlbedoPicker = false;
}
if (ImGui::BeginPopup("AlbedoColorPicker"))
{
if (ImGui::ColorPicker3("##picker", glm::value_ptr(m_Asset->AlbedoColor)))
m_Asset->IsDirty = true;
ImGui::EndPopup();
}
ed::Resume();
}
bool PBRMaterialEditor::IsPinLinked(int pinId) const
{
for (const auto& link : m_Links)
if (link.StartPinID == pinId || link.EndPinID == pinId)
return true;
return false;
}
AssetHandle PBRMaterialEditor::GetLinkedTextureHandle(int inputPinId) const
{
for (const auto& link : m_Links)
{
if (link.EndPinID == inputPinId)
{
int nodeId = link.StartPinID / 100;
for (const auto& node : m_TextureNodes)
if (node.ID == nodeId)
return node.TextureHandle;
}
}
return AssetHandle(0);
}
void PBRMaterialEditor::CompileMaterialToAsset()
{
if (!m_Asset) return;
auto getTexture = [](AssetHandle handle) -> Ref<Texture2D> {
if (handle == 0) return nullptr;
return AssetsManager::GetAsset<Texture2D>(handle);
};
m_Asset->AlbedoTexture = getTexture(GetLinkedTextureHandle(InputPinID(Albedo)));
m_Asset->AlbedoTexToggle = m_Asset->AlbedoTexture ? 1.0f : 0.0f;
m_Asset->NormalTexture = getTexture(GetLinkedTextureHandle(InputPinID(Normal)));
m_Asset->NormalTexToggle = m_Asset->NormalTexture ? 1.0f : 0.0f;
m_Asset->MetalnessTexture = getTexture(GetLinkedTextureHandle(InputPinID(Metallic)));
m_Asset->MetalnessTexToggle = m_Asset->MetalnessTexture ? 1.0f : 0.0f;
m_Asset->RoughnessTexture = getTexture(GetLinkedTextureHandle(InputPinID(Roughness)));
m_Asset->RoughnessTexToggle = m_Asset->RoughnessTexture ? 1.0f : 0.0f;
m_Asset->IsDirty = true;
}
void PBRMaterialEditor::LoadAssetToGraph()
{
if (!m_Asset) return;
m_TextureNodes.clear();
m_Links.clear();
m_NextNodeId = 2;
m_NextLinkId = 1;
// 辅助 lambda从 Ref<Texture2D> 获取 AssetHandle
auto getHandle = [](const Ref<Texture2D>& tex) -> AssetHandle {
return tex ? tex->Handle : AssetHandle(0);
};
auto addTextureNode = [&](AssetHandle handle, PinType pinType, float yPos) {
if (handle != 0)
{
int id = m_NextNodeId++;
m_TextureNodes.push_back({id, handle, 50.0f, yPos});
m_Links.push_back({m_NextLinkId++, OutputPinID(id), InputPinID(pinType)});
}
};
addTextureNode(getHandle(m_Asset->AlbedoTexture), Albedo, 0.0f);
addTextureNode(getHandle(m_Asset->NormalTexture), Normal, 150.0f);
addTextureNode(getHandle(m_Asset->MetalnessTexture), Metallic, 300.0f);
addTextureNode(getHandle(m_Asset->RoughnessTexture), Roughness, 450.0f);
m_FirstFrame = true;
}
}

View File

@ -0,0 +1,94 @@
//
// Created by Atdunbg on 2026/2/14.
//
#ifndef PRISM_PBRMATERIALASSETEDITOR_H
#define PRISM_PBRMATERIALASSETEDITOR_H
#include "../AssetEditorPanel.h"
#include "Prism/Renderer/Texture.h"
#include "imgui_node_editor.h"
namespace ed = ax::NodeEditor;
namespace Prism
{
class PBRMaterialEditor : public AssetEditor
{
public:
PBRMaterialEditor();
~PBRMaterialEditor();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
// 原有成员
Ref<PBRMaterialAsset> m_Asset;
ed::EditorContext* m_EditorContext = nullptr;
// 原有渲染函数
void Render() override;
void RenderBasicEditorPanel();
// ---------- 节点编辑器相关 ----------
void RenderNodeEditorPanel(); // 新的节点编辑器面板
void DrawOutputNode();
void DrawTextureNode(int nodeId, AssetHandle& textureHandle);
void HandleNodeEditorInteractions();
bool IsPinLinked(int pinId) const;
AssetHandle GetLinkedTextureHandle(int inputPinId) const;
void CompileMaterialToAsset(); // 将节点图数据写回 m_Asset
void LoadAssetToGraph(); // 从 m_Asset 加载数据到节点图
// 节点编辑器数据
static constexpr int OUTPUT_NODE_ID = 1;
int m_NextNodeId = 2;
int m_NextLinkId = 1;
struct TextureNode
{
int ID;
AssetHandle TextureHandle;
float PosX, PosY;
};
std::vector<TextureNode> m_TextureNodes;
struct Link
{
int ID;
int StartPinID;
int EndPinID;
};
std::vector<Link> m_Links;
// 编辑器状态
bool m_FirstFrame = true;
bool m_OpenAlbedoPicker = false;
ImVec2 m_AlbedoPickerPos;
int m_ContextMenuNodeId = 0;
// 引脚ID辅助函数
static int InputPinID(int type) { return OUTPUT_NODE_ID * 100 + type; }
static int OutputPinID(int nodeId) { return nodeId * 100 + 1; }
// 引脚类型枚举与PBR材质对应
enum PinType
{
Albedo = 1,
Normal,
Metallic,
Roughness,
AO
};
// 从 MaterialEditorPanel 借用的工具函数
static ImColor GetPinColor(int pinType);
static void DrawPinIcon(bool connected, int alpha, int pinType);
static void DrawOutputPinIcon(bool connected, int alpha);
float DrawNodeHeader(const char* title, float minWidth);
};
}
#endif //PRISM_PBRMATERIALASSETEDITOR_H

View File

@ -0,0 +1,35 @@
//
// Created by Atdunbg on 2026/4/8.
//
#include "PhysicsMaterialEditor.h"
#include "Prism/Asset/AssetSerializer.h"
namespace Prism
{
PhysicsMaterialEditor::PhysicsMaterialEditor()
: AssetEditor("Edit Physics Material") {}
void PhysicsMaterialEditor::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset) AssetSerializer::SerializeAsset(m_Asset);
m_Asset = static_cast<Ref<PhysicsMaterialAsset>>(asset);
}
void PhysicsMaterialEditor::Render()
{
if (!m_Asset)
SetOpen(false);
UI::BeginPropertyGrid();
UI::Property("Static Friction", m_Asset->StaticFriction);
UI::Property("Dynamic Friction", m_Asset->DynamicFriction);
UI::Property("Bounciness", m_Asset->Bounciness);
UI::EndPropertyGrid();
if (ImGui::Button("Save"))
AssetSerializer::SerializeAsset(m_Asset);
}
}

View File

@ -0,0 +1,32 @@
//
// Created by Atdunbg on 2026/4/8.
//
#ifndef PRISM_PHYSICSMATERIALEDITOR_H
#define PRISM_PHYSICSMATERIALEDITOR_H
#include "Prism/Core/Ref.h"
#include "Prism/Editor/AssetEditorPanel.h"
namespace Prism
{
class PhysicsMaterialAsset;
class PhysicsMaterialEditor : public AssetEditor
{
public:
PhysicsMaterialEditor();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
void Render() override;
private:
Ref<PhysicsMaterialAsset> m_Asset;
};
}
#endif //PRISM_PHYSICSMATERIALEDITOR_H

View File

@ -0,0 +1,46 @@
//
// Created by Atdunbg on 2026/4/8.
//
#include "TextureAssetEditor.h"
#include "Prism/Renderer/Texture.h"
#include "Prism/Asset/AssetSerializer.h"
namespace Prism
{
TextureViewer::TextureViewer()
: AssetEditor("Edit Texture")
{
SetMinSize(200, 600);
SetMaxSize(500, 1000);
}
void TextureViewer::SetAsset(const Ref<Asset>& asset)
{
if (m_Asset) AssetSerializer::SerializeAsset(m_Asset);
m_Asset = static_cast<Ref<Texture>>(asset);
}
void TextureViewer::Render()
{
if (!m_Asset)
SetOpen(false);
float textureWidth = (float)m_Asset->GetWidth();
float textureHeight = (float)m_Asset->GetHeight();
float bitsPerPixel = (float)Texture::GetBPP(m_Asset->GetFormat());
float imageSize = ImGui::GetWindowWidth() - 40;
imageSize = glm::min(imageSize, 500.0f);
ImGui::SetCursorPosX(20);
ImGui::Image((ImTextureID)m_Asset->GetRendererID(), {imageSize, imageSize});
UI::BeginPropertyGrid();
UI::Property("Width", textureWidth, 0.1f, 0.0f, 0.0f, true);
UI::Property("Height", textureHeight, 0.1f, 0.0f, 0.0f, true);
UI::Property("Bits", bitsPerPixel, 0.1f, 0.0f, 0.0f, true);
UI::EndPropertyGrid();
}
}

View File

@ -0,0 +1,31 @@
//
// Created by Atdunbg on 2026/4/8.
//
#ifndef PRISM_TEXTUREASSETEDITOR_H
#define PRISM_TEXTUREASSETEDITOR_H
#include "Prism/Editor/AssetEditorPanel.h"
namespace Prism
{
class Texture;
class TextureViewer : public AssetEditor
{
public:
TextureViewer();
Ref<Asset> GetAsset() override { return m_Asset; }
void SetAsset(const Ref<Asset>& asset) override;
private:
void Render() override;
private:
Ref<Texture> m_Asset;
};
}
#endif //PRISM_TEXTUREASSETEDITOR_H