1248 lines
56 KiB
C++
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;
|
|
}
|
|
}
|