Files
Prism/Editor/Editor/EditorLayer.cpp

1156 lines
43 KiB
C++

//
// Created by sfd on 25-11-29.
//
#include "EditorLayer.h"
#include "ImGuizmo.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <filesystem>
#include "glm/gtx/matrix_decompose.hpp"
#include "Prism/Core/Input.h"
#include "Prism/Editor/PhysicsSettingsWindow.h"
#include "Prism/Physics/Physics3D.h"
#include "Prism/Renderer/Renderer2D.h"
#include "Prism/Script/ScriptEngine.h"
namespace Prism
{
enum class PropertyFlag
{
None = 0, ColorProperty = 1, SliderProperty = 2, DragProperty = 4
};
static std::tuple<glm::vec3, glm::quat, glm::vec3> GetTransformDecomposition(const glm::mat4& transform)
{
glm::vec3 scale, translation, skew;
glm::vec4 perspective;
glm::quat orientation;
glm::decompose(transform, scale, orientation, translation, skew, perspective);
return { translation, orientation, scale };
}
bool Property(const std::string& name, bool& value)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
const std::string id = "##" + name;
const bool isChanged = ImGui::Checkbox(id.c_str(), &value);
ImGui::PopItemWidth();
ImGui::NextColumn();
return isChanged;
}
bool Property(const std::string& name, float& value, const float min = -1.0f, const float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
const std::string id = "##" + name;
bool isChanged = false;
if (flags == PropertyFlag::SliderProperty)
isChanged = ImGui::SliderFloat(id.c_str(), &value, min, max);
else
isChanged = ImGui::DragFloat(id.c_str(), &value, 1.0f, min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
return isChanged;
}
bool Property(const std::string& name, glm::vec3& value, const float min = -1.0f, const float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
bool isChanged = false;
if (flags == PropertyFlag::ColorProperty)
isChanged = ImGui::ColorEdit3(id.c_str(), glm::value_ptr(value), ImGuiColorEditFlags_NoInputs);
else if (flags == PropertyFlag::SliderProperty)
isChanged = ImGui::SliderFloat3(id.c_str(), glm::value_ptr(value), min, max);
else
isChanged = ImGui::DragFloat3(id.c_str(), glm::value_ptr(value), 1.0f, min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
return isChanged;
}
bool Property(const std::string& name, glm::vec3& value, PropertyFlag flags)
{
return Property(name, value, -1.0f, 1.0f, flags);
}
bool Property(const std::string& name, glm::vec4& value, const float min = -1.0f, const float max = 1.0f, PropertyFlag flags = PropertyFlag::None)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
std::string id = "##" + name;
bool isChanged = false;
if (flags == PropertyFlag::ColorProperty)
isChanged = ImGui::ColorEdit4(id.c_str(), glm::value_ptr(value), ImGuiColorEditFlags_NoInputs);
else if (flags == PropertyFlag::SliderProperty)
isChanged = ImGui::SliderFloat4(id.c_str(), glm::value_ptr(value), min, max);
else
isChanged = ImGui::DragFloat4(id.c_str(), glm::value_ptr(value), 1.0f, min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
return isChanged;
}
bool Property(const std::string& name, glm::vec4& value, const PropertyFlag flags)
{
return Property(name, value, -1.0f, 1.0f, flags);
}
bool Property(const std::string& name, glm::vec2& value, const float min, const float max, PropertyFlag flags)
{
ImGui::Text(name.c_str());
ImGui::NextColumn();
ImGui::PushItemWidth(-1);
const std::string id = "##" + name;
bool isChanged = false;
if (flags == PropertyFlag::SliderProperty)
isChanged = ImGui::SliderFloat2(id.c_str(), glm::value_ptr(value), min, max);
else
isChanged = ImGui::DragFloat2(id.c_str(), glm::value_ptr(value), 1.0f, min, max);
ImGui::PopItemWidth();
ImGui::NextColumn();
return isChanged;
}
bool Property(const std::string& name, glm::vec2& value, const PropertyFlag flags)
{
return Property(name, value, -1.0f, 1.0f, flags);
}
static void ImGuiShowHelpMarker(const char* desc)
{
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
EditorLayer::EditorLayer()
: m_SceneType(SceneType::Model), m_EditorCamera(glm::perspectiveFov(glm::radians(45.0f), 1280.0f, 720.0f, 0.1f, 10000.0f))
{
}
EditorLayer::~EditorLayer()
{
}
void EditorLayer::OnAttach()
{
// Editor
m_CheckerboardTex = Texture2D::Create("assets/editor/Checkerboard.tga");
m_PlayButtonTex = Texture2D::Create("assets/editor/PlayButton.png");
m_EditorScene = Ref<Scene>::Create("untitled Scene", true);
ScriptEngine::SetSceneContext(m_EditorScene);
m_SceneHierarchyPanel = CreateScope<SceneHierarchyPanel>(m_EditorScene);
m_SceneHierarchyPanel->SetSelectionChangedCallback(std::bind(&EditorLayer::SelectEntity, this, std::placeholders::_1));
m_SceneHierarchyPanel->SetEntityDeletedCallback(std::bind(&EditorLayer::OnEntityDeleted, this, std::placeholders::_1));
UpdateWindowTitle("untitled Scene");
}
void EditorLayer::OnDetach()
{
m_EditorScene->OnShutdown();
}
void EditorLayer::OnUpdate(const TimeStep deltaTime)
{
switch (m_SceneState)
{
case SceneState::Edit:
{
if (m_ViewportPanelHovered || m_ViewportPanelFocused)
m_EditorCamera.OnUpdate(deltaTime);
m_EditorScene->OnRenderEditor(deltaTime, m_EditorCamera);
if (m_DrawOnTopBoundingBoxes)
{
Renderer::BeginRenderPass(SceneRenderer::GetFinalRenderPass(), false);
const auto viewProj = m_EditorCamera.GetViewProjection();
Renderer2D::BeginScene(viewProj, false);
// TODO: Renderer::DrawAABB(m_MeshEntity.GetComponent<MeshComponent>(), m_MeshEntity.GetComponent<TransformComponent>());
Renderer2D::EndScene();
Renderer::EndRenderPass();
}
if (!m_SelectionContext.empty() && false)
{
auto& selection = m_SelectionContext[0];
if (selection.Mesh && selection.Entity.HasComponent<MeshComponent>())
{
Renderer::BeginRenderPass(SceneRenderer::GetFinalRenderPass(), false);
auto viewProj = m_EditorCamera.GetViewProjection();
Renderer2D::BeginScene(viewProj, false);
glm::vec4 color = (m_SelectionMode == SelectionMode::Entity) ? glm::vec4{ 1.0f, 1.0f, 1.0f, 1.0f } : glm::vec4{ 0.2f, 0.9f, 0.2f, 1.0f };
Renderer::DrawAABB(selection.Mesh->BoundingBox, selection.Entity.GetComponent<TransformComponent>().Transformation.GetMatrix() * selection.Mesh->Transform, color);
Renderer2D::EndScene();
Renderer::EndRenderPass();
}
}
if (!m_SelectionContext.empty())
{
auto& selection = m_SelectionContext[0];
if (selection.Entity.HasComponent<BoxCollider2DComponent>())
{
const auto& size = selection.Entity.GetComponent<BoxCollider2DComponent>().Size;
auto [translation, rotationQuat, scale] = GetTransformDecomposition(selection.Entity.GetComponent<TransformComponent>().Transformation.GetMatrix());
const glm::vec3 rotation = glm::eulerAngles(rotationQuat);
Renderer::BeginRenderPass(SceneRenderer::GetFinalRenderPass(), false);
const auto viewProj = m_EditorCamera.GetViewProjection();
Renderer2D::BeginScene(viewProj, false);
Renderer2D::DrawRotatedQuad({ translation.x, translation.y }, size * 2.0f, glm::degrees(rotation.z), { 1.0f, 0.0f, 1.0f, 1.0f });
Renderer2D::EndScene();
Renderer::EndRenderPass();
}
}
break;
}
case SceneState::Play:
{
if (m_ViewportPanelFocused)
m_EditorCamera.OnUpdate(deltaTime);
m_RuntimeScene->OnUpdate(deltaTime);
m_RuntimeScene->OnRenderRuntime(deltaTime);
break;
}
case SceneState::Pause:
{
if (m_ViewportPanelFocused)
m_EditorCamera.OnUpdate(deltaTime);
m_RuntimeScene->OnRenderRuntime(deltaTime);
break;
}
}
}
void EditorLayer::OnImGuiRender()
{
static bool p_open = true;
static bool opt_fullscreen = true;
static bool opt_padding = false;
static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others.
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
if (opt_fullscreen)
{
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
}
else
{
dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
}
// When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background
// and handle the pass-thru hole, so we ask Begin() to not render a background.
if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
window_flags |= ImGuiWindowFlags_NoBackground;
// Important: note that we proceed even if Begin() returns false (aka window is collapsed).
// This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
// all active windows docked into it will lose their parent and become undocked.
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
if (!opt_padding)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpace Demo", &p_open, window_flags);
if (!opt_padding)
ImGui::PopStyleVar();
if (opt_fullscreen)
ImGui::PopStyleVar(2);
// Submit the DockSpace
// REMINDER: THIS IS A DEMO FOR ADVANCED USAGE OF DockSpace()!
// MOST REGULAR APPLICATIONS WILL SIMPLY WANT TO CALL DockSpaceOverViewport(). READ COMMENTS ABOVE.
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
{
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
}
// Show demo options and help
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("file"))
{
if (ImGui::MenuItem("New Scene"))
{
}
if (ImGui::MenuItem("Open Scene...", "Ctrl+O"))
{
OpenScene();
}
ImGui::Separator();
if (ImGui::MenuItem("Save Scene...", "Ctrl+S"))
{
SaveScene();
}
if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S"))
{
SaveSceneAs();
}
ImGui::Separator();
if (ImGui::MenuItem("Exit"))
p_open = false;
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Script"))
{
// temp
if (ImGui::MenuItem("Reload C# Assembly"))
ScriptEngine::ReloadAssembly("assets/scripts/ExampleApp.dll");
ImGui::MenuItem("Reload assembly on play", nullptr, &m_ReloadScriptOnPlay);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit"))
{
ImGui::MenuItem("Physics Settings", nullptr, &m_ShowPhysicsSettings);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Tools"))
{
// PhysX Debugger
if (ImGui::MenuItem("Connect to PVD"))
{
Physics3D::ConnectPVD();
}
if (ImGui::MenuItem("Disconnect from PVD", nullptr, nullptr, Physics3D::IsPVDConnected()))
{
Physics3D::DisconnectPVD();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
m_SceneHierarchyPanel->OnImGuiRender();
PhysicsSettingsWindow::OnImGuiRender(m_ShowPhysicsSettings);
ImGui::Begin("Materials");
if (!m_SelectionContext.empty())
{
Entity selectedEntity = m_SelectionContext.front().Entity;
if (selectedEntity.HasComponent<MeshComponent>())
{
Ref<Mesh> mesh = selectedEntity.GetComponent<MeshComponent>().Mesh;
if (mesh)
{
auto& materials = mesh->GetMaterials();
static uint32_t selectedMaterialIndex = 0;
for (uint32_t i = 0; i < materials.size(); i++)
{
const auto& materialInstance = materials[i];
ImGuiTreeNodeFlags node_flags = (selectedMaterialIndex == i ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_Leaf;
bool opened = ImGui::TreeNodeEx((void*)(&materialInstance), node_flags, "%s", materialInstance->GetName().c_str());
if (ImGui::IsItemClicked())
{
selectedMaterialIndex = i;
}
if (opened)
ImGui::TreePop();
}
ImGui::Separator();
if (selectedMaterialIndex < materials.size())
{
auto& materialInstance = materials[selectedMaterialIndex];
ImGui::Text("Shader: %s", materialInstance->GetShader()->GetName().c_str());
// Textures ------------------------------------------------------------------------------
{
// Albedo
if (ImGui::CollapsingHeader("Albedo", nullptr, ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
auto& albedoColor = materialInstance->Get<glm::vec3>("u_AlbedoColor");
bool useAlbedoMap = materialInstance->Get<float>("u_AlbedoTexToggle");
Ref<Texture2D> albedoMap = materialInstance->TryGetResource<Texture2D>("u_AlbedoTexture");
ImGui::Image(albedoMap ? (ImTextureRef)albedoMap->GetRendererID() : (ImTextureRef)m_CheckerboardTex->GetRendererID(), ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::IsItemHovered())
{
if (albedoMap)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(albedoMap->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureRef)albedoMap->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
{
albedoMap = Texture2D::Create(filename, true/*m_AlbedoInput.SRGB*/);
materialInstance->Set("u_AlbedoTexture", albedoMap);
}
}
}
ImGui::SameLine();
ImGui::BeginGroup();
if (ImGui::Checkbox("Use##AlbedoMap", &useAlbedoMap))
materialInstance->Set<float>("u_AlbedoTexToggle", useAlbedoMap ? 1.0f : 0.0f);
/*if (ImGui::Checkbox("sRGB##AlbedoMap", &m_AlbedoInput.SRGB))
{
if (m_AlbedoInput.TextureMap)
m_AlbedoInput.TextureMap = Texture2D::Create(m_AlbedoInput.TextureMap->GetPath(), m_AlbedoInput.SRGB);
}*/
ImGui::EndGroup();
ImGui::SameLine();
ImGui::ColorEdit3("Color##Albedo", glm::value_ptr(albedoColor), ImGuiColorEditFlags_NoInputs);
}
}
{
// Normals
if (ImGui::CollapsingHeader("Normals", nullptr, ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
bool useNormalMap = materialInstance->Get<float>("u_NormalTexToggle");
Ref<Texture2D> normalMap = materialInstance->TryGetResource<Texture2D>("u_NormalTexture");
ImGui::Image(normalMap ? (ImTextureRef)normalMap->GetRendererID() : (ImTextureRef)m_CheckerboardTex->GetRendererID(), ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::IsItemHovered())
{
if (normalMap)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(normalMap->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureRef)normalMap->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
{
normalMap = Texture2D::Create(filename);
materialInstance->Set("u_NormalTexture", normalMap);
}
}
}
ImGui::SameLine();
if (ImGui::Checkbox("Use##NormalMap", &useNormalMap))
materialInstance->Set<float>("u_NormalTexToggle", useNormalMap ? 1.0f : 0.0f);
}
}
{
// Metalness
if (ImGui::CollapsingHeader("Metalness", nullptr, ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
float& metalnessValue = materialInstance->Get<float>("u_Metalness");
bool useMetalnessMap = materialInstance->Get<float>("u_MetalnessTexToggle");
Ref<Texture2D> metalnessMap = materialInstance->TryGetResource<Texture2D>("u_MetalnessTexture");
ImGui::Image(metalnessMap ? (ImTextureRef)metalnessMap->GetRendererID() : (ImTextureRef)m_CheckerboardTex->GetRendererID(), ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::IsItemHovered())
{
if (metalnessMap)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(metalnessMap->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureRef)metalnessMap->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
{
metalnessMap = Texture2D::Create(filename);
materialInstance->Set("u_MetalnessTexture", metalnessMap);
}
}
}
ImGui::SameLine();
if (ImGui::Checkbox("Use##MetalnessMap", &useMetalnessMap))
materialInstance->Set<float>("u_MetalnessTexToggle", useMetalnessMap ? 1.0f : 0.0f);
ImGui::SameLine();
ImGui::SliderFloat("Value##MetalnessInput", &metalnessValue, 0.0f, 1.0f);
}
}
{
// Roughness
if (ImGui::CollapsingHeader("Roughness", nullptr, ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 10));
float& roughnessValue = materialInstance->Get<float>("u_Roughness");
bool useRoughnessMap = materialInstance->Get<float>("u_RoughnessTexToggle");
Ref<Texture2D> roughnessMap = materialInstance->TryGetResource<Texture2D>("u_RoughnessTexture");
ImGui::Image(roughnessMap ? (ImTextureRef)roughnessMap->GetRendererID() : (ImTextureRef)m_CheckerboardTex->GetRendererID(), ImVec2(64, 64));
ImGui::PopStyleVar();
if (ImGui::IsItemHovered())
{
if (roughnessMap)
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(roughnessMap->GetPath().c_str());
ImGui::PopTextWrapPos();
ImGui::Image((ImTextureRef)roughnessMap->GetRendererID(), ImVec2(384, 384));
ImGui::EndTooltip();
}
if (ImGui::IsItemClicked())
{
std::string filename = Application::Get().OpenFile("");
if (filename != "")
{
roughnessMap = Texture2D::Create(filename);
materialInstance->Set("u_RoughnessTexture", roughnessMap);
}
}
}
ImGui::SameLine();
if (ImGui::Checkbox("Use##RoughnessMap", &useRoughnessMap))
materialInstance->Set<float>("u_RoughnessTexToggle", useRoughnessMap ? 1.0f : 0.0f);
ImGui::SameLine();
ImGui::SliderFloat("Value##RoughnessInput", &roughnessValue, 0.0f, 1.0f);
}
}
}
}
}
}
ImGui::End();
ScriptEngine::OnImGuiRender();
ImGui::End();
// Editor Panel ------------------------------------------------------------------------------
ImGui::Begin("Model");
ImGui::Begin("Environment");
if (ImGui::Button("Load Environment Map"))
{
std::string filename = Application::Get().OpenFile("*.hdr");
if (!filename.empty())
m_EditorScene->SetEnvironment(Environment::Load(filename));
}
ImGui::SliderFloat("Skybox LOD", &m_EditorScene->GetSkyboxLod(), 0.0f, 11.0f);
ImGui::Columns(2);
ImGui::AlignTextToFramePadding();
auto& light = m_EditorScene->GetLight();
Property("Light Direction", light.Direction, PropertyFlag::SliderProperty);
Property("Light Radiance", light.Radiance, PropertyFlag::ColorProperty);
Property("Light Multiplier", light.Multiplier, 0.0f, 5.0f, PropertyFlag::SliderProperty);
Property("Exposure", m_EditorCamera.GetExposure(), 0.0f, 5.0f, PropertyFlag::SliderProperty);
Property("Radiance Prefiltering", m_RadiancePrefilter);
Property("Env Map Rotation", m_EnvMapRotation, -360.0f, 360.0f, PropertyFlag::SliderProperty);
if (m_SceneState == SceneState::Edit)
{
float physics2DGravity = m_EditorScene->GetPhysics2DGravity();
if (Property("Gravity", physics2DGravity, -10000.0f, 10000.0f, PropertyFlag::DragProperty))
{
m_EditorScene->SetPhysics2DGravity(physics2DGravity);
}
}
else if (m_SceneState == SceneState::Play)
{
float physics2DGravity = m_RuntimeScene->GetPhysics2DGravity();
if (Property("Gravity", physics2DGravity, -10000.0f, 10000.0f, PropertyFlag::DragProperty))
{
m_RuntimeScene->SetPhysics2DGravity(physics2DGravity);
}
}
if (Property("Show Bounding Boxes", m_UIShowBoundingBoxes))
ShowBoundingBoxes(m_UIShowBoundingBoxes, m_UIShowBoundingBoxesOnTop);
if (m_UIShowBoundingBoxes && Property("On Top", m_UIShowBoundingBoxesOnTop))
ShowBoundingBoxes(m_UIShowBoundingBoxes, m_UIShowBoundingBoxesOnTop);
const char* label = m_SelectionMode == SelectionMode::Entity ? "Entity" : "Mesh";
if (ImGui::Button(label))
{
m_SelectionMode = m_SelectionMode == SelectionMode::Entity ? SelectionMode::SubMesh : SelectionMode::Entity;
}
ImGui::Columns(1);
ImGui::End();
ImGui::Separator();
{
ImGui::Text("Mesh");
/*
auto meshComponent = m_MeshEntity.GetComponent<MeshComponent>();
std::string fullpath = meshComponent.Mesh ? meshComponent.Mesh->GetFilePath() : "None";
size_t found = fullpath.find_last_of("/\\");
std::string path = found != std::string::npos ? fullpath.substr(found + 1) : fullpath;
ImGui::Text(path.c_str());
ImGui::SameLine();
if (ImGui::Button("...##Mesh"))
{
std::string filename = Application::Get().OpenFile("");
if (!filename.empty())
{
auto newMesh = Ref<Mesh>::Create(filename);
// m_MeshMaterial.reset(new MaterialInstance(newMesh->GetMaterial()));
// m_MeshEntity->SetMaterial(m_MeshMaterial);
meshComponent.Mesh = newMesh;
}
}
*/
}
ImGui::Separator();
if (ImGui::TreeNode("Shaders"))
{
auto& shaders = Shader::GetAllShaders();
for (auto& shader : shaders)
{
if (ImGui::TreeNode(shader->GetName().c_str()))
{
const std::string buttonName = shader->GetName();
if (ImGui::Button(("Reload##" + buttonName).c_str()))
{
PM_CLIENT_INFO("Reloading Shader: {0}", buttonName);
shader->Reload();
}
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::Separator();
ImGui::End();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 4));
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.8f, 0.8f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0));
ImGui::Begin("Toolbar");
if (m_SceneState == SceneState::Edit)
{
if (ImGui::ImageButton("editbutton", (ImTextureID)(m_PlayButtonTex->GetRendererID()), ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), ImVec4(0,0,0,0), ImVec4(0.9f, 0.9f, 0.9f, 1.0f)))
{
OnScenePlay();
}
}
else if (m_SceneState == SceneState::Play)
{
if (ImGui::ImageButton("playbutton", (ImTextureID)(m_PlayButtonTex->GetRendererID()), ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), ImVec4(1.0f, 1.0f, 1.0f, 0.2f)))
{
OnSceneStop();
}
}
ImGui::SameLine();
if (ImGui::ImageButton("a", (ImTextureID)(m_PlayButtonTex->GetRendererID()), ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), ImVec4(1.0f, 1.0f, 1.0f, 0.6f)))
{
PM_CORE_INFO("PLAY!");
}
ImGui::End();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopStyleVar();
ImGui::PopStyleVar();
ImGui::PopStyleVar();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::Begin("Viewport");
m_ViewportPanelHovered = ImGui::IsWindowHovered();
m_ViewportPanelFocused = ImGui::IsWindowFocused();
auto viewportOffset = ImGui::GetCursorPos(); // includes tab bar
auto viewportSize = ImGui::GetContentRegionAvail();
SceneRenderer::SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
m_EditorScene->SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
if (m_RuntimeScene)
m_RuntimeScene->SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
m_EditorCamera.SetProjectionMatrix(glm::perspectiveFov(glm::radians(45.0f), viewportSize.x, viewportSize.y, 0.1f, 10000.0f));
m_EditorCamera.SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
ImGui::Image((ImTextureRef)SceneRenderer::GetFinalColorBufferRendererID(), viewportSize, { 0, 1 }, { 1, 0 });
static int counter = 0;
auto windowSize = ImGui::GetWindowSize();
ImVec2 minBound = ImGui::GetWindowPos();
minBound.x += viewportOffset.x;
minBound.y += viewportOffset.y;
ImVec2 maxBound = { minBound.x + windowSize.x, minBound.y + windowSize.y };
m_ViewportBounds[0] = { minBound.x, minBound.y };
m_ViewportBounds[1] = { maxBound.x, maxBound.y };
m_AllowViewportCameraEvents = ImGui::IsMouseHoveringRect(minBound, maxBound);
// ImGuizmo
if (m_GizmoType != -1 && !m_SelectionContext.empty())
{
auto& selection = m_SelectionContext[0];
const auto rw = (float)ImGui::GetWindowWidth();
const auto rh = (float)ImGui::GetWindowHeight();
ImGuizmo::SetOrthographic(false);
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, rw, rh);
bool snap = Input::IsKeyPressed(Key::LEFT_CONTROL);
Transform& entityTransform = selection.Entity.Transformation();
float snapValue = GetSnapValue();
float snapValues[3] = { snapValue, snapValue, snapValue };
if (m_SelectionMode == SelectionMode::Entity)
{
glm::mat4 transform = entityTransform.GetMatrix();
ImGuizmo::Manipulate(glm::value_ptr(m_EditorCamera.GetViewMatrix()),
glm::value_ptr(m_EditorCamera.GetProjectionMatrix()),
(ImGuizmo::OPERATION)m_GizmoType,
ImGuizmo::LOCAL,
glm::value_ptr(transform),
nullptr,
snap ? snapValues : nullptr);
auto [translation, rotation, scale] = GetTransformDecomposition(transform);
entityTransform.SetTranslation(translation);
entityTransform.SetRotation(rotation);
entityTransform.SetScale(scale);
}else
{
glm::mat4 transformBase = entityTransform.GetMatrix() * selection.Mesh->Transform;
ImGuizmo::Manipulate(glm::value_ptr(m_EditorCamera.GetViewMatrix()),
glm::value_ptr(m_EditorCamera.GetProjectionMatrix()),
(ImGuizmo::OPERATION)m_GizmoType,
ImGuizmo::LOCAL,
glm::value_ptr(transformBase),
nullptr,
snap ? snapValues : nullptr);
selection.Mesh->Transform = glm::inverse(entityTransform.GetMatrix()) * transformBase;
}
}
ImGui::End();
ImGui::PopStyleVar();
}
void EditorLayer::OnEvent(Event& e)
{
if (m_SceneState == SceneState::Edit)
{
if (m_ViewportPanelHovered)
m_EditorCamera.OnEvent(e);
m_EditorScene->OnEvent(e);
}
else if (m_SceneState == SceneState::Play)
{
m_RuntimeScene->OnEvent(e);
}
EventDispatcher dispatcher(e);
dispatcher.Dispatch<KeyPressedEvent>(PM_BIND_EVENT_FN(EditorLayer::OnKeyPressedEvent));
dispatcher.Dispatch<MouseButtonPressedEvent>(PM_BIND_EVENT_FN(EditorLayer::OnMouseButtonPressedEvent));
}
bool EditorLayer::OnKeyPressedEvent(KeyPressedEvent& e)
{
if (m_ViewportPanelFocused)
{
switch (e.GetKeyCode())
{
case KeyCode::Q:
m_GizmoType = -1;
break;
case KeyCode::W:
m_GizmoType = ImGuizmo::OPERATION::TRANSLATE;
break;
case KeyCode::E:
m_GizmoType = ImGuizmo::OPERATION::ROTATE;
break;
case KeyCode::R:
m_GizmoType = ImGuizmo::OPERATION::SCALE;
break;
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;
}
}
if (Input::IsKeyPressed(KeyCode::LEFT_CONTROL))
{
const bool IsShiftPressed = Input::IsKeyPressed(KeyCode::LEFT_SHIFT);
if (!IsShiftPressed)
{
switch (e.GetKeyCode())
{
case KeyCode::O:
OpenScene();
break;
case KeyCode::S:
SaveScene();
break;
case KeyCode::B:
// Toggle bounding boxes
m_UIShowBoundingBoxes = !m_UIShowBoundingBoxes;
ShowBoundingBoxes(m_UIShowBoundingBoxes, m_UIShowBoundingBoxesOnTop);
break;
case KeyCode::D:
if (m_SelectionContext.size())
{
const Entity selectedEntity = m_SelectionContext[0].Entity;
m_EditorScene->DuplicateEntity(selectedEntity);
}
break;
case KeyCode::G:
// Toggle grid
SceneRenderer::GetOptions().ShowGrid = !SceneRenderer::GetOptions().ShowGrid;
break;
}
}
if (IsShiftPressed) {
switch (e.GetKeyCode())
{
case KeyCode::S:
SaveSceneAs();
break;
default:
break;
}
}
// TODO: ALT TAB
}
return false;
}
bool EditorLayer::OnMouseButtonPressedEvent(MouseButtonPressedEvent& e)
{
auto [mx, my] = Input::GetMousePosition();
if (m_ViewportPanelHovered && e.GetMouseButton() == PM_MOUSE_BUTTON_LEFT && !Input::IsKeyPressed(KeyCode::LEFT_ALT) && !ImGuizmo::IsOver() && m_SceneState != SceneState::Play)
{
auto [mouseX, mouseY] = GetMouseViewportSpace();
if (mouseX > -1.0f && mouseX < 1.0f && mouseY > -1.0f && mouseY < 1.0f)
{
auto [origin, direction] = CastRay(mouseX, mouseY);
m_SelectionContext.clear();
m_EditorScene->SetSelectedEntity({});
const auto meshEntities = m_EditorScene->GetAllEntitiesWith<MeshComponent>();
for (const auto aEntity : meshEntities)
{
Entity entity = { aEntity, m_EditorScene.Raw() };
auto mesh = entity.GetComponent<MeshComponent>().Mesh;
if (!mesh)
continue;
auto& submeshes = mesh->GetSubmeshes();
float lastT = std::numeric_limits<float>::max();
for (uint32_t i = 0; i < submeshes.size(); i++)
{
auto& submesh = submeshes[i];
Ray ray = {
glm::inverse(entity.Transformation().GetMatrix() * submesh.Transform) * glm::vec4(origin, 1.0f),
glm::inverse(glm::mat3(entity.Transformation().GetMatrix()) * glm::mat3(submesh.Transform)) * direction
};
float t = 0;
if (ray.IntersectsAABB(submesh.BoundingBox, t))
{
const auto& triangleCache = mesh->GetTriangleCache(i);
for (const auto& triangle : triangleCache)
{
if (ray.IntersectsTriangle(triangle.V0.Position, triangle.V1.Position, triangle.V2.Position, t))
{
PM_CLIENT_WARN("INTERSECTION: {0}, t={1}", submesh.NodeName, t);
m_SelectionContext.push_back({ entity, &submesh, t });
break;
}
}
}
}
}
std::sort(m_SelectionContext.begin(), m_SelectionContext.end(), [](auto& a, auto& b)
{
return a.Distance < b.Distance;
});
if (!m_SelectionContext.empty())
OnSelected(m_SelectionContext[0]);
}
}
return false;
}
void EditorLayer::ShowBoundingBoxes(bool show, bool onTop)
{
SceneRenderer::GetOptions().ShowBoundingBoxes = show && !onTop;
m_DrawOnTopBoundingBoxes = show && onTop;
}
void EditorLayer::SelectEntity(Entity entity)
{
SelectedSubmesh selection;
if (entity.HasComponent<MeshComponent>())
{
if (auto& meshComp = entity.GetComponent<MeshComponent>(); meshComp.Mesh)
{
selection.Mesh = &meshComp.Mesh->GetSubmeshes()[0];
}
}
selection.Entity = entity;
m_SelectionContext.clear();
m_SelectionContext.push_back(selection);
m_EditorScene->SetSelectedEntity(entity);
}
void EditorLayer::UpdateWindowTitle(const std::string &sceneName)
{
const std::string title = sceneName + " - Prism - " + Application::GetPlatformName() + " (" + Application::GetConfigurationName() + ")";
Application::Get().GetWindow().SetTitle(title);
}
std::pair<float, float> EditorLayer::GetMouseViewportSpace() const
{
auto [mx, my] = ImGui::GetMousePos(); // Input::GetMousePosition();
mx -= m_ViewportBounds[0].x;
my -= m_ViewportBounds[0].y;
const auto viewportWidth = m_ViewportBounds[1].x - m_ViewportBounds[0].x;
const auto viewportHeight = m_ViewportBounds[1].y - m_ViewportBounds[0].y;
return { (mx / viewportWidth) * 2.0f - 1.0f, ((my / viewportHeight) * 2.0f - 1.0f) * -1.0f };
}
std::pair<glm::vec3, glm::vec3> EditorLayer::CastRay(const float mx, const float my)
{
const glm::vec4 mouseClipPos = { mx, my, -1.0f, 1.0f };
const auto inverseProj = glm::inverse(m_EditorCamera.GetProjectionMatrix());
const auto inverseView = glm::inverse(glm::mat3(m_EditorCamera.GetViewMatrix()));
const glm::vec4 ray = inverseProj * mouseClipPos;
glm::vec3 rayPos = m_EditorCamera.GetPosition();
glm::vec3 rayDir = inverseView * glm::vec3(ray);
return { rayPos, rayDir };
}
void EditorLayer::OnSelected(const SelectedSubmesh& selectionContext)
{
m_SceneHierarchyPanel->SetSelected(selectionContext.Entity);
m_EditorScene->SetSelectedEntity(selectionContext.Entity);
}
void EditorLayer::OnEntityDeleted(Entity e)
{
if (m_SelectionContext[0].Entity == e)
{
m_SelectionContext.clear();
m_EditorScene->SetSelectedEntity({});
}
}
Ray EditorLayer::CastMouseRay()
{
auto [mouseX, mouseY] = GetMouseViewportSpace();
if (mouseX > -1.0f && mouseX < 1.0f && mouseY > -1.0f && mouseY < 1.0f)
{
auto [origin, direction] = CastRay(mouseX, mouseY);
return Ray(origin, direction);
}
return Ray::Zero();
}
void EditorLayer::OpenScene()
{
const auto& app = Application::Get();
const std::string filepath = app.OpenFile("Prism Scene (*.hsc)\0*.hsc\0");
if (!filepath.empty())
{
const Ref<Scene> newScene = Ref<Scene>::Create("EditorScene", true);
SceneSerializer serializer(newScene);
serializer.Deserialize(filepath);
m_EditorScene = newScene;
const std::filesystem::path path = filepath;
UpdateWindowTitle(path.filename().string());
m_SceneHierarchyPanel->SetContext(m_EditorScene);
ScriptEngine::SetSceneContext(m_EditorScene);
m_EditorScene->SetSelectedEntity({});
m_SelectionContext.clear();
m_SceneFilePath = filepath;
}
}
void EditorLayer::SaveScene()
{
PM_CLIENT_INFO("Saving scene to: {0}", m_SceneFilePath);
SceneSerializer serializer(m_EditorScene);
serializer.Serialize(m_SceneFilePath);
}
void EditorLayer::SaveSceneAs()
{
auto& app = Application::Get();
std::string filepath = app.SaveFile("Prism Scene (*.hsc)\0*.hsc\0");
if (!filepath.empty())
{
PM_CLIENT_INFO("Saving scene to: {0}", m_SceneFilePath);
SceneSerializer serializer(m_EditorScene);
serializer.Serialize(filepath);
std::filesystem::path path = filepath;
UpdateWindowTitle(path.filename().string());
m_SceneFilePath = filepath;
}
}
void EditorLayer::OnScenePlay()
{
m_SelectionContext.clear();
m_SceneState = SceneState::Play;
if (m_ReloadScriptOnPlay)
ScriptEngine::ReloadAssembly("assets/scripts/ExampleApp.dll");
m_RuntimeScene = Ref<Scene>::Create();
m_EditorScene->CopyTo(m_RuntimeScene);
m_RuntimeScene->OnRuntimeStart();
m_SceneHierarchyPanel->SetContext(m_RuntimeScene);
}
void EditorLayer::OnSceneStop()
{
m_RuntimeScene->OnRuntimeStop();
m_SceneState = SceneState::Edit;
// Unload runtime scene
m_RuntimeScene = nullptr;
m_SelectionContext.clear();
ScriptEngine::SetSceneContext(m_EditorScene);
m_SceneHierarchyPanel->SetContext(m_EditorScene);
Input::SetCursorMode(CursorMode::Normal);
}
float EditorLayer::GetSnapValue()
{
switch (m_GizmoType)
{
case ImGuizmo::OPERATION::TRANSLATE: return 0.5f;
case ImGuizmo::OPERATION::ROTATE: return 45.0f;
case ImGuizmo::OPERATION::SCALE: return 0.5f;
}
return 0.0f;
}
}