// // Created by sfd on 25-11-29. // #include "EditorLayer.h" #include "ImGuizmo.h" #define GLM_ENABLE_EXPERIMENTAL #include #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 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::Create("untitled Scene", true); ScriptEngine::SetSceneContext(m_EditorScene); m_SceneHierarchyPanel = CreateScope(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(), m_MeshEntity.GetComponent()); Renderer2D::EndScene(); Renderer::EndRenderPass(); } if (!m_SelectionContext.empty() && false) { auto& selection = m_SelectionContext[0]; if (selection.Mesh && selection.Entity.HasComponent()) { 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().Transformation.GetMatrix() * selection.Mesh->Transform, color); Renderer2D::EndScene(); Renderer::EndRenderPass(); } } if (!m_SelectionContext.empty()) { auto& selection = m_SelectionContext[0]; if (selection.Entity.HasComponent()) { const auto& size = selection.Entity.GetComponent().Size; auto [translation, rotationQuat, scale] = GetTransformDecomposition(selection.Entity.GetComponent().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()) { Ref mesh = selectedEntity.GetComponent().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("u_AlbedoColor"); bool useAlbedoMap = materialInstance->Get("u_AlbedoTexToggle"); Ref albedoMap = materialInstance->TryGetResource("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("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("u_NormalTexToggle"); Ref normalMap = materialInstance->TryGetResource("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("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("u_Metalness"); bool useMetalnessMap = materialInstance->Get("u_MetalnessTexToggle"); Ref metalnessMap = materialInstance->TryGetResource("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("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("u_Roughness"); bool useRoughnessMap = materialInstance->Get("u_RoughnessTexToggle"); Ref roughnessMap = materialInstance->TryGetResource("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("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(); 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::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(PM_BIND_EVENT_FN(EditorLayer::OnKeyPressedEvent)); dispatcher.Dispatch(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(); for (const auto aEntity : meshEntities) { Entity entity = { aEntity, m_EditorScene.Raw() }; auto mesh = entity.GetComponent().Mesh; if (!mesh) continue; auto& submeshes = mesh->GetSubmeshes(); float lastT = std::numeric_limits::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()) { if (auto& meshComp = entity.GetComponent(); 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 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 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 newScene = Ref::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::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; } }