add simple node editor(use ImGui-Node-Editor), some tweaks
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -35,3 +35,6 @@
|
||||
[submodule "Prism/vendor/efsw"]
|
||||
path = Prism/vendor/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
|
||||
|
||||
@ -719,7 +719,7 @@ namespace Prism
|
||||
m_ViewportBounds[1] = { maxBound.x, maxBound.y };
|
||||
|
||||
// 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];
|
||||
|
||||
@ -921,15 +921,19 @@ namespace Prism
|
||||
|
||||
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 newPitch = glm::asin(glm::clamp(-forward.y, -1.0f, 1.0f));
|
||||
float newYaw = -glm::atan(forward.x, -forward.z);
|
||||
float distance = glm::length(cameraPos - focusPosition);
|
||||
m_EditorCamera.SetDistance(distance);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (e.GetKeyCode())
|
||||
{
|
||||
case KeyCode::DELETE:
|
||||
if (m_SelectionContext.size())
|
||||
switch (e.GetKeyCode())
|
||||
{
|
||||
const Entity selectedEntity = m_SelectionContext[0].Entity;
|
||||
m_EditorScene->DestroyEntity(selectedEntity);
|
||||
m_SelectionContext.clear();
|
||||
m_EditorScene->SetSelectedEntity({});
|
||||
m_SceneHierarchyPanel->SetSelected({});
|
||||
case KeyCode::DELETE:
|
||||
if (m_SelectionContext.size())
|
||||
{
|
||||
const Entity selectedEntity = m_SelectionContext[0].Entity;
|
||||
m_EditorScene->DestroyEntity(selectedEntity);
|
||||
m_SelectionContext.clear();
|
||||
m_EditorScene->SetSelectedEntity({});
|
||||
m_SceneHierarchyPanel->SetSelected({});
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Input::IsKeyPressed(KeyCode::LEFT_CONTROL))
|
||||
|
||||
@ -50,6 +50,19 @@ list(APPEND SRC_SOURCE ${IMGUIZMO_SOURCE})
|
||||
# ----------- 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 -------------
|
||||
# PhysX/physx/buildtools/presets/*.xml
|
||||
@ -125,6 +138,7 @@ set(TARGET_INCLUDE_DIR
|
||||
${IMGUI_DIR}
|
||||
${IMGUIZMO_DIR}
|
||||
${IMVIEWGUIZMO_DIR}
|
||||
${IMGUI_NODE_EDITOR_DIR}
|
||||
)
|
||||
|
||||
# ------------- debug Defines -------------
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
#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/Core/Log.h"
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
1
Prism/vendor/imgui-node-editor
vendored
Submodule
1
Prism/vendor/imgui-node-editor
vendored
Submodule
Submodule Prism/vendor/imgui-node-editor added at 021aa0ea4d
Reference in New Issue
Block a user