Files
Prism/Editor/Editor/EditorLayer.cpp

1248 lines
56 KiB
C++

//
// Created by sfd on 25-11-29.
//
#include "EditorLayer.h"
#include <ImGuizmo.h>
#include <filesystem>
#define IMVIEWGUIZMO_IMPLEMENTATION
#include "ImViewGuizmo.h"
#define GLM_ENABLE_EXPERIMENTAL
#include "glm/gtx/matrix_decompose.hpp"
#include "Prism/Core/Input.h"
#include "Prism/Core/Math/Math.h"
#include "Prism/Editor/AssetEditorPanel.h"
#include "Prism/Editor/PhysicsSettingsWindow.h"
#include "Prism/Physics/Physics3D.h"
#include "Prism/Renderer/Renderer2D.h"
#include "Prism/Script/ScriptEngine.h"
namespace Prism
{
EditorLayer::EditorLayer()
: m_EditorCamera(glm::perspectiveFov(glm::radians(45.0f), 1280.0f, 720.0f, 0.1f, 1000.0f)), m_SceneType(SceneType::Model)
{
}
EditorLayer::~EditorLayer()
{
}
void EditorLayer::OnAttach()
{
// Editor
m_CheckerboardTex = Texture2D::Create("assets/editor/Checkerboard.tga");
m_PlayButtonTex = Texture2D::Create("assets/editor/PlayButton.png");
m_PauseButtonTex = Texture2D::Create("assets/editor/PauseButton.png");
m_StopButtonTex = Texture2D::Create("assets/editor/StopButton.png");
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");
m_ContentBrowserPanel = CreateScope<ContentBrowserPanel>();
m_ObjectsPanel = CreateScope<ObjectsPanel>();
// OpenScene("assets/scenes/FPSDemo.scene");
NewScene();
m_CurrentScene = m_EditorScene;
AssetEditorPanel::RegisterDefaultEditors();
FileSystem::StartWatching();
}
void EditorLayer::OnDetach()
{
FileSystem::StopWatching();
m_EditorScene->OnShutdown();
}
void EditorLayer::OnUpdate(const TimeStep deltaTime)
{
auto [x, y] = GetMouseViewportSpace();
SceneRenderer::SetFocusPoint({ x * 0.5f + 0.5f, y * 0.5f + 0.5f });
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>().GetTransform() * selection.Mesh->Transform, color);
Renderer2D::EndScene();
Renderer::EndRenderPass();
}
}
if (!m_SelectionContext.empty())
{
auto& selection = m_SelectionContext[0];
if (selection.Entity.HasComponent<BoxCollider2DComponent>())
{
const auto& collider = selection.Entity.GetComponent<BoxCollider2DComponent>();
const auto& transform = selection.Entity.GetComponent<TransformComponent>();
Renderer::BeginRenderPass(SceneRenderer::GetFinalRenderPass(), false);
const auto viewProj = m_EditorCamera.GetViewProjection();
Renderer2D::BeginScene(viewProj, false);
Renderer2D::DrawRotatedRect({ transform.Translation.x + collider.Offset.x, transform.Translation.y + collider.Offset.y }, collider.Size * 2.0f, transform.Rotation.z, { 0.0f, 0.0f, 1.0f, 1.0f });
Renderer2D::EndScene();
Renderer::EndRenderPass();
}
if (selection.Entity.HasComponent<CircleCollider2DComponent>())
{
const auto& collider = selection.Entity.GetComponent<CircleCollider2DComponent>();
const TransformComponent& transform = selection.Entity.GetComponent<TransformComponent>();
Renderer::BeginRenderPass(SceneRenderer::GetFinalRenderPass(), false);
const auto viewProj = m_EditorCamera.GetViewProjection();
Renderer2D::BeginScene(viewProj, false);
Renderer2D::DrawCircle({ transform.Translation.x + collider.Offset.x, transform.Translation.y + collider.Offset.y}, collider.Radius, { 0.0f, 1.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", "Ctrl+N"))
{
NewScene();
}
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();
}
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();
}
ImGui::End();
m_SceneHierarchyPanel->OnImGuiRender();
PhysicsSettingsWindow::OnImGuiRender(m_ShowPhysicsSettings);
SceneRenderer::OnImGuiRender();
AssetEditorPanel::OnImGuiRender();
ScriptEngine::OnImGuiRender();
m_ContentBrowserPanel->OnImGuiRender();
m_ObjectsPanel->OnImGuiRender();
// Editor Panel ------------------------------------------------------------------------------
ImGui::Begin("Environment");
{
UI::PropertySlider("Skybox LOD", m_EditorScene->GetSkyboxLod(), 0.0f, 11.0f);
UI::BeginPropertyGrid();
ImGui::AlignTextToFramePadding();
auto& light = m_EditorScene->GetLight();
UI::PropertySlider("Light Direction", light.Direction, -1.0f, 1.0f);
UI::PropertyColor("Light Radiance", light.Radiance);
UI::PropertySlider("Light Multiplier", light.Multiplier, 0.0f, 5.0f);
UI::PropertySlider("Exposure", m_EditorCamera.GetExposure(), 0.0f, 5.0f);
UI::Property("Radiance Prefiltering", m_RadiancePrefilter);
UI::PropertySlider("Env Map Rotation", m_EnvMapRotation, -360.0f, 360.0f);
if (m_SceneState == SceneState::Edit)
{
float physics2DGravity = m_EditorScene->GetPhysics2DGravity();
if (UI::Property("2D World Gravity", physics2DGravity, -10000.0f, 10000.0f))
{
m_EditorScene->SetPhysics2DGravity(physics2DGravity);
}
}
else if (m_SceneState == SceneState::Play)
{
float physics2DGravity = m_RuntimeScene->GetPhysics2DGravity();
if (UI::Property("2D World Gravity", physics2DGravity, -10000.0f, 10000.0f))
{
m_RuntimeScene->SetPhysics2DGravity(physics2DGravity);
}
}
if (UI::Property("Show Bounding Boxes", m_UIShowBoundingBoxes))
ShowBoundingBoxes(m_UIShowBoundingBoxes, m_UIShowBoundingBoxesOnTop);
if (m_UIShowBoundingBoxes && UI::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;
}
UI::EndPropertyGrid();
}
ImGui::End();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 2));
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.305f, 0.31f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15f, 0.1505f, 0.151f, 0.5f));
ImGui::Begin("##tool_bar", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
{
float size = ImGui::GetWindowHeight() - 4.0F;
ImGui::SameLine((ImGui::GetWindowContentRegionMax().x / 2.0f) - (1.5f * (ImGui::GetFontSize() + ImGui::GetStyle().ItemSpacing.x)) - (size / 2.0f));
Ref<Texture2D> buttonTex = m_SceneState == SceneState::Play ? m_StopButtonTex : m_PlayButtonTex;
if (UI::ImageButton(buttonTex, ImVec2(size, size), ImVec2(0, 0), ImVec2(1, 1)))
{
if (m_SceneState == SceneState::Edit)
OnScenePlay();
else
OnSceneStop();
}
ImGui::SameLine();
if (UI::ImageButton(m_PauseButtonTex, ImVec2(size, size), ImVec2(0, 0), ImVec2(1, 1)))
{
if (m_SceneState == SceneState::Play)
{
//OnScenePause();
m_SceneState = SceneState::Pause;
}
else if (m_SceneState == SceneState::Pause)
{
//OnSceneResume();
m_SceneState = SceneState::Play;
}
}
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
ImGui::End();
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();
if (viewportSize.x != 0 && viewportSize.y != 0)
{
SceneRenderer::SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
m_EditorScene->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 });
if (ImGui::BeginDragDropTarget())
{
auto payload = ImGui::AcceptDragDropPayload("asset_payload");
if (payload)
{
int count = payload->DataSize / sizeof(AssetHandle);
for (int i = 0; i < count; i++)
{
AssetHandle assetHandle = *(((AssetHandle*)payload->Data) + i);
Ref<Asset> asset = AssetsManager::GetAsset<Asset>(assetHandle);
// We can't really support dragging and dropping scenes when we're dropping multiple assets
if (count == 1 && asset->Type == AssetType::Scene)
{
OpenScene(asset->FilePath);
}
if (asset->Type == AssetType::Mesh)
{
Entity entity = m_EditorScene->CreateEntity(asset->FileName);
entity.AddComponent<MeshComponent>(Ref<Mesh>(asset));
SelectEntity(entity);
}
}
}
ImGui::EndDragDropTarget();
}
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);
TransformComponent& entityTransform = selection.Entity.Transform();
// glm::mat4 transform = entityTransform.GetTransform();
glm::mat4 transform = m_CurrentScene->GetTransformRelativeToParent(selection.Entity);
float snapValue = GetSnapValue();
float snapValues[3] = { snapValue, snapValue, snapValue };
if (m_SelectionMode == SelectionMode::Entity)
{
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);
if (ImGuizmo::IsUsing())
{
glm::vec3 translation, rotation, scale;
Math::DecomposeTransform(transform, translation,rotation,scale);
if (Entity parent = m_CurrentScene->FindEntityByUUID(selection.Entity.GetParentUUID()))
{
glm::vec3 parentTranslation, parentRotation, parentScale;
Math::DecomposeTransform(m_CurrentScene->GetTransformRelativeToParent(parent), parentTranslation, parentRotation, parentScale);
glm::vec3 deltaRotation = (rotation - parentRotation) - entityTransform.Rotation;
entityTransform.Translation = translation - parentTranslation;
entityTransform.Rotation += deltaRotation;
entityTransform.Scale = scale;
}
else
{
glm::vec3 deltaRotation = rotation - entityTransform.Rotation;
entityTransform.Translation = translation;
entityTransform.Rotation += deltaRotation;
entityTransform.Scale = scale;
}
}
}else
{
glm::mat4 transformBase = transform * 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);
if (ImGuizmo::IsUsing())
{
selection.Mesh->Transform = glm::inverse(entityTransform.GetTransform()) * transformBase;
}
}
}
// ImZmoToolBar
{
const float iconSize = 28.0f;
const float padding = 6.0f;
const ImU32 backgroundColor = IM_COL32(30, 30, 30, 140);
const ImVec4 orangeMain = ImVec4(0.92f, 0.45f, 0.11f, 0.8f);
ImGuiChildFlags child_flags = ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
ImGuiWindowFlags Gizmo_window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding, padding));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 0));
ImGui::SetCursorPos(ImVec2(10.0f, viewportOffset.y + 10.0f));
if (m_SceneState != SceneState::Play)
{
if (ImGui::BeginChild("GizmoToolbar", ImVec2(0, 0), child_flags, Gizmo_window_flags))
{
ImDrawList *draw_list = ImGui::GetWindowDrawList();
draw_list->AddRectFilled(ImGui::GetWindowPos(),
ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowWidth(), ImGui::GetWindowPos().y + ImGui::GetWindowHeight()),
backgroundColor, 6.0f);
auto drawGizmoBtn = [&](const char *label, int type, const char *tooltip)
{
bool isActive = (m_GizmoType == type);
if (isActive)
ImGui::PushStyleColor(ImGuiCol_Button, orangeMain);
else
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (ImGui::Button(label, ImVec2(iconSize, iconSize)))
m_GizmoType = type;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort))
ImGui::SetTooltip("%s", tooltip);
ImGui::PopStyleColor();
};
drawGizmoBtn("Q", -1, "Select (Q)");
ImGui::SameLine();
drawGizmoBtn("W", (int)ImGuizmo::TRANSLATE, "Translate (W)");
ImGui::SameLine();
drawGizmoBtn("E", (int)ImGuizmo::ROTATE, "Rotate (E)");
ImGui::SameLine();
drawGizmoBtn("R", (int)ImGuizmo::SCALE, "Scale (R)");
}
ImGui::EndChild();
}
float centerX = minBound.x + viewportOffset.x + viewportSize.x * 0.5f;
float topY = minBound.y + 10.0f;
ImGui::SetNextWindowPos(ImVec2(centerX, topY), ImGuiCond_Always, ImVec2(0.5f, 0.0f));
if (ImGui::BeginChild("DebugButton", ImVec2(0, 0), child_flags, Gizmo_window_flags))
{
ImDrawList *draw_list = ImGui::GetWindowDrawList();
draw_list->AddRectFilled(ImGui::GetWindowPos(),
ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowWidth(), ImGui::GetWindowPos().y + ImGui::GetWindowHeight()),
backgroundColor, 6.0f);
// Scene control buttons
const bool toolbarEnabled = (bool)m_CurrentScene;
ImVec4 tintColor = toolbarEnabled ? ImVec4(1, 1, 1, 1) : ImVec4(1, 1, 1, 0.5f);
bool isEdit = m_SceneState == SceneState::Edit;
bool isPlay = m_SceneState == SceneState::Play;
// bool isSim = m_SceneState == SceneState::Pause;
auto drawImgBtn = [&](const char *id, Ref<Texture2D> iconRef, ImVec4 bg, auto func)
{
ImGui::PushStyleColor(ImGuiCol_Button, bg);
const auto textureID = (ImTextureID)(uintptr_t)iconRef->GetRendererID();
if (ImGui::ImageButton(id, textureID, ImVec2(iconSize - 6, iconSize - 6), {0, 0}, {1, 1}, {0, 0, 0, 0}, tintColor))
{
if (toolbarEnabled)
func();
}
ImGui::PopStyleColor();
};
if (isEdit)
{
drawImgBtn("##Play", m_PlayButtonTex, ImVec4(0, 0, 0, 0), [this]()
{ OnScenePlay(); });
}
else if (isPlay)
{
drawImgBtn("##Stop", m_StopButtonTex, ImVec4(0.8f, 0.1f, 0.1f, 0.6f), [this]()
{ OnSceneStop(); });
}
}
ImGui::EndChild();
ImGui::PopStyleVar(3);
}
// ImViewGuizmo
if (m_SceneState != SceneState::Play)
{
ImViewGuizmo::BeginFrame();
auto &style = ImViewGuizmo::GetStyle();
style.scale = 0.8f;
const float gizmoSize = 256.0f * style.scale;
const float inset = gizmoSize * 0.1f;
const ImVec2 gizmoCenter = ImVec2(
maxBound.x - gizmoSize * 0.5f + inset,
minBound.y + gizmoSize * 0.5f - inset);
glm::vec3 cameraPos = m_EditorCamera.GetPosition();
glm::vec3 focusPosition = m_EditorCamera.GetFocusPosition();
float CameraYaw = m_EditorCamera.GetYaw();
float CameraPitch = -m_EditorCamera.GetPitch();
glm::quat qYaw = glm::angleAxis(CameraYaw, glm::vec3(0.0f, 1.0f, 0.0f));
glm::quat qPitch = glm::angleAxis(CameraPitch, glm::vec3(1.0f, 0.0f, 0.0f));
glm::quat cameraRot = qYaw * qPitch;
if (ImViewGuizmo::Rotate(cameraPos, cameraRot, glm::vec3(0.0f), gizmoCenter))
{
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);
}
}
if (m_RuntimeScene)
m_RuntimeScene->SetViewportSize((uint32_t)viewportSize.x, (uint32_t)viewportSize.y);
}
}
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_ViewportPanelHovered)
{
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::F:
{
if (m_SelectionContext.size() == 0)
break;
Entity selectedEntity = m_SelectionContext[0].Entity;
m_EditorCamera.SetFocusPosition(selectedEntity.Transform().Translation);
break;
}
}
}
switch (e.GetKeyCode())
{
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::N:
NewScene();
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];
const auto transform = entity.Transform().GetTransform();
Ray ray = {
glm::inverse(transform * submesh.Transform) * glm::vec4(origin, 1.0f),
glm::inverse(glm::mat3(transform) * 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)
{
if (!entity) return;
SelectedSubmesh selection;
if (entity.HasComponent<MeshComponent>())
{
auto& meshComponent = entity.GetComponent<MeshComponent>();
if (meshComponent.Mesh && meshComponent.Mesh->Type == AssetType::Mesh)
selection.Mesh = &meshComponent.Mesh->GetSubmeshes()[0];
}
selection.Entity = entity;
m_SelectionContext.clear();
m_SelectionContext.push_back(selection);
m_EditorScene->SetSelectedEntity(entity);
m_CurrentScene = m_EditorScene;
}
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(const Entity e)
{
if (m_SelectionContext.size() > 0 && 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::NewScene()
{
m_EditorScene = Ref<Scene>::Create("Untitled Scene", true);
m_SceneHierarchyPanel->SetContext(m_EditorScene);
ScriptEngine::SetSceneContext(m_EditorScene);
UpdateWindowTitle("Untitled Scene");
m_SceneFilePath = std::string();
m_EditorCamera = EditorCamera(glm::perspectiveFov(glm::radians(45.0f), 1280.0f, 720.0f, 0.1f, 1000.0f));
m_SelectionContext.clear();
}
void EditorLayer::OpenScene()
{
const auto& app = Application::Get();
const std::string filepath = app.OpenFile("Hazel Scene (*.hsc)\0*.hsc\0");
if (!filepath.empty())
OpenScene(filepath);
}
void EditorLayer::OpenScene(const std::string& filepath)
{
const std::string fileName = std::filesystem::path(filepath).filename().string();
UpdateWindowTitle(fileName);
const Ref<Scene> newScene = Ref<Scene>::Create(fileName, true);
SceneSerializer serializer(newScene);
serializer.Deserialize(filepath);
m_EditorScene = newScene;
m_SceneFilePath = filepath;
m_SceneHierarchyPanel->SetContext(m_EditorScene);
ScriptEngine::SetSceneContext(m_EditorScene);
m_EditorScene->SetSelectedEntity({});
m_SelectionContext.clear();
m_CurrentScene = m_EditorScene;
}
void EditorLayer::SaveScene()
{
if (!m_SceneFilePath.empty())
{
PM_CLIENT_INFO("Saving scene to: {0}", m_SceneFilePath);
SceneSerializer serializer(m_EditorScene);
serializer.Serialize(m_SceneFilePath);
}
else
{
SaveSceneAs();
}
}
void EditorLayer::SaveSceneAs()
{
const auto& app = Application::Get();
const 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);
const std::filesystem::path path = filepath;
UpdateWindowTitle(path.filename().string());
m_SceneFilePath = filepath;
}else
{
PM_CLIENT_INFO("cancelled");
}
}
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_CurrentScene = m_RuntimeScene;
m_SceneHierarchyPanel->SetContext(m_CurrentScene);
m_CurrentScene->OnRuntimeStart();
}
void EditorLayer::OnSceneStop()
{
m_CurrentScene->OnRuntimeStop();
m_SceneState = SceneState::Edit;
// Unload runtime scene
m_RuntimeScene = nullptr;
m_SelectionContext.clear();
m_CurrentScene = m_EditorScene;
ScriptEngine::SetSceneContext(m_CurrentScene);
m_SceneHierarchyPanel->SetContext(m_CurrentScene);
}
float EditorLayer::GetSnapValue() const
{
switch (m_GizmoType)
{
case ImGuizmo::OPERATION::TRANSLATE: return 0.5f;
case ImGuizmo::OPERATION::ROTATE: return 45.0f;
case ImGuizmo::OPERATION::SCALE: return 0.5f;
default: break;
}
return 0.0f;
}
}