Files
OpenEngine/editor/include/editor.hpp

446 lines
16 KiB
C++
Executable File

#ifndef EDITOR_HPP
#define EDITOR_HPP
#include <open_engine.hpp>
#include "open_engine/events/mouse_event.hpp"
#include "open_engine/input/input_system.hpp"
#include "open_engine/input/mouse_codes.hpp"
#include "open_engine/renderer/render_command.hpp"
#include "open_engine/renderer/renderer2d.hpp"
#include "open_engine/scene/components.hpp"
#include "panels/scene_hierarchy.hpp"
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/matrix.hpp>
#include <entt/entt.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <ImGuizmo.h>
#include <cstdint>
#include <imgui.h>
#include <vector>
#include <string>
namespace OpenEngine {
class CameraController : public NativeScriptableEntity
{
public:
void OnCreate()
{
};
void OnUpdate()
{
auto delta = Time::DeltaTime();
if (HasComponent<TransformComponent>()) {
auto& position = GetComponent<TransformComponent>().translation;
if (Input::IsKeyPressed(KeyCode::Up))
position.y += 5.0 * delta;
if (Input::IsKeyPressed(KeyCode::Down))
position.y -= 5.0 * delta;
if (Input::IsKeyPressed(KeyCode::Right))
position.x += 5.0 * delta;
if (Input::IsKeyPressed(KeyCode::Left))
position.x -= 5.0 * delta;
}
};
void OnDestroy()
{
};
};
class EditorLayer : public Layer
{
public:
EditorLayer()
: Layer("editor_layer")
{
}
~EditorLayer() {};
void OnAttach() override
{
OE_PROFILE_FUNCTION();
FramebufferSpecification specs;
specs.attachments = { FramebufferTextureFormat::RGBA8, FramebufferTextureFormat::RED_INTEGER, FramebufferTextureFormat::Depth };
specs.width = 1280;
specs.height = 720;
framebuffer = FrameBuffer::Create(specs);
scene = CreateRef<Scene>();
editor_camera = EditorCamera(30.0f, 1920.0f/1080.0f, 0.1f, 1000.0f);
/*
for (float i = 0; i < 200; i++) {
for (float y = 0; y < 200; y++) {
Entity entity = scene->CreateEntity("entity");
entities.push_back(entity);
auto& tc = entity.AddComponents<TransformComponent>();
tc.translation = { i / 10, y / 10, 0.0f };
tc.scale = { 0.1f, 0.1f, 1.0f };
auto& sprite = entity.AddComponents<SpriteRendererComponent>();
sprite.color = { i / 100.0f, y / 100.0f, 1.0f, 1.0f };
}
}
*/
scene_hierarchy.SetContext(scene);
}
void OnDetach() override
{
}
void OnUpdate() override
{
FramebufferSpecification spec = framebuffer->GetSpecification();
if (viewport_size.x > 0.0f && viewport_size.y > 0.0f &&
(spec.width != viewport_size.x || spec.height != viewport_size.y)) {
OE_PROFILE_SCOPE("Setting up Rendering");
framebuffer->Resize((uint32_t)viewport_size.x, (uint32_t)viewport_size.y);
editor_camera.SetViewportSize(viewport_size.x, viewport_size.y);
scene->OnViewportResize((uint32_t)viewport_size.x, (uint32_t)viewport_size.y);
}
framebuffer->Bind();
Renderer2D::ResetStats();
RenderCommand::SetClearColor({0.11f, 0.11f, 0.15f, 1.0f});
RenderCommand::Clear();
framebuffer->ClearBufferI(1, -1);
editor_camera.OnUpdate();
scene->OnUpdateEditor(editor_camera);
auto [mx, my] = ImGui::GetMousePos();
mx -= viewport_bounds[0].x;
my -= viewport_bounds[0].y;
auto viewport_size= viewport_bounds[1] - viewport_bounds[0];
my = viewport_size.y - my;
int mouse_x = (int)mx;
int mouse_y = (int)my;
static bool clicked = false;
// Mouse Picking
if (Input::IsMouseButtonPressed(MouseCode::ButtonLeft)
&& mouse_x >= 0 && mouse_y >= 0
&& mouse_x < (int)viewport_size.x && (int)viewport_size.y &&
!ImGuizmo::IsOver() && !Input::IsKeyPressed(KeyCode::LeftAlt)) {
if (!clicked) {
int id = framebuffer->ReadPixel(1, mouse_x, mouse_y);
selected_entity = id == -1 ?
Entity() : Entity((entt::entity)id, scene.get());
scene_hierarchy.SetSelectedEntity(selected_entity);
}
clicked = true;
} else {
clicked = false;
}
framebuffer->Unbind();
}
bool EditorKeyBinds(KeyPressedEvent& event)
{
if (event.GetRepeatCount() > 0)
return false;
bool control = Input::IsKeyPressed(Key::LeftControl) || Input::IsKeyPressed(Key::RightControl);
bool shift = Input::IsKeyPressed(Key::LeftShift) || Input::IsKeyPressed(Key::RightShift);
switch(event.GetKeyCode()) {
case KeyCode::Q: {
guizmo_operation = -1;
break;
}
case KeyCode::W: {
guizmo_operation = ImGuizmo::OPERATION::TRANSLATE;
break;
}
case KeyCode::E: {
guizmo_operation = ImGuizmo::OPERATION::ROTATE;
break;
}
case KeyCode::R: {
guizmo_operation = ImGuizmo::OPERATION::SCALE;
break;
}
default:
break;
}
return true;
};
void OnEvent(Event& event) override
{
OE_PROFILE_FUNCTION();
editor_camera.OnEvent(event);
EventDispatcher dispatcher(event);
dispatcher.Dispatch<KeyPressedEvent>(BIND_EVENT_FN(EditorLayer::EditorKeyBinds));
};
float remap(float value, float minInput, float maxInput, float minOutput, float maxOutput) {
// 1. Normalize the input to a 0.0 - 1.0 range
float t = (value - minInput) / (maxInput - minInput);
// 2. Use glm::mix to interpolate between the output range
return glm::mix(minOutput, maxOutput, t);
};
glm::vec3 ScreenToWorld(
float screenX,
float screenY,
float screenZ, // depth value (0.0 = near plane, 1.0 = far plane)
const glm::mat4& view,
const glm::mat4& projection,
int viewportWidth,
int viewportHeight)
{
// 1. Convert screen coordinates to normalized device coordinates (NDC)
// Screen space: origin at top-left, Y points down
// NDC space: origin at center, range [-1, 1] for x and y
float x = (2.0f * screenX) / viewportWidth - 1.0f;
float y = 1.0f - (2.0f * screenY) / viewportHeight; // Flip Y
float z = 2.0f * screenZ - 1.0f; // [0,1] -> [-1,1]
glm::vec4 clipCoords(x, y, z, 1.0f);
// 2. Transform from clip space to view space
glm::mat4 invProjection = glm::inverse(projection);
glm::vec4 viewCoords = invProjection * clipCoords;
// 3. Transform from view space to world space
glm::mat4 invView = glm::inverse(view);
glm::vec4 worldCoords = invView * viewCoords;
// 4. Perspective divide
if (worldCoords.w != 0.0f) {
worldCoords /= worldCoords.w;
}
return glm::vec3(worldCoords);
};
void DrawViewport()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 0, 0 });
ImGui::Begin("Viewport");
bool focused = ImGui::IsWindowFocused();
bool hovered = ImGui::IsWindowHovered();
Application::Get().GetImGuiLayer()->SetBlockEvents((!focused && !hovered) || ImGui::IsAnyItemActive());
ImVec2 viewport_panel_size = ImGui::GetContentRegionAvail();
viewport_size = { viewport_panel_size.x, viewport_panel_size.y };
ImVec2 imgui_cursor_position = ImGui::GetCursorScreenPos();
uint32_t texture_id = framebuffer->GetColorAttachmentRendererID();
ImGui::Image((void*)(uint64_t)texture_id, ImVec2{ viewport_size.x, viewport_size.y }, ImVec2{ 0, 1 }, ImVec2{ 1, 0 });
viewport_bounds[0] = { imgui_cursor_position.x, imgui_cursor_position.y };
viewport_bounds[1] = { imgui_cursor_position.x + viewport_size.x,
imgui_cursor_position.y + viewport_size.y };
DrawGuizmos();
ImGui::End();
ImGui::PopStyleVar();
};
void DrawStats()
{
auto stats = Renderer2D::GetStats();
static float time = 0;
time += Time::DeltaTime();
if (time >= 1) {
time = 0;
}
ImGui::Begin("Statistics");
ImGui::SeparatorText("Performance");
ImGui::Text("FPS: %0.0f", 1 / Time::DeltaTime());
ImGui::Text("Renderer2D:");
ImGui::Text("\t\tDraw calls: %d", stats.draw_calls);
ImGui::Text("\t\tQuad count: %d", stats.quad_count);
ImGui::Text("\t\tVertices count: %d", stats.GetTotalVertexCount());
ImGui::Text("\t\tIndices count: %d", stats.GetTotalIndexCount());
if (selected_entity) {
ImGui::SeparatorText("Entities");
ImGui::Text("Selected entity:");
ImGui::Text("\t\tname: %s", selected_entity.GetComponents<TagComponent>().tag.c_str());
ImGui::Text("\t\tid: %d", (uint32_t)selected_entity);
}
ImGui::End();
};
void DrawGuizmos()
{
Entity selected_entity = scene_hierarchy.GetSelectedEntity();
if (selected_entity && selected_entity.HasComponent<TransformComponent>() && guizmo_operation != -1) {
ImGuizmo::SetOrthographic(false);
ImGuizmo::SetDrawlist();
ImGuizmo::Enable(!editor_camera.GetMoving());
ImGuizmo::SetRect(viewport_bounds[0].x, viewport_bounds[0].y,
viewport_bounds[1].x - viewport_bounds[0].x,
viewport_bounds[1].y - viewport_bounds[0].y);
const glm::mat4& camera_projection = editor_camera.GetProjection();
glm::mat4 camera_view = editor_camera.GetViewMatrix();
auto& transform_comp = selected_entity.GetComponents<TransformComponent>();
glm::mat4 transform = transform_comp.GetTransform();
bool snap = Input::IsKeyPressed(KeyCode::LeftControl);
float snap_value = 0.1f;
if (guizmo_operation == ImGuizmo::OPERATION::ROTATE)
snap_value = 10.0f;
float snap_values[] = { snap_value, snap_value, snap_value };
ImGuizmo::Manipulate(glm::value_ptr(camera_view), glm::value_ptr(camera_projection),
(ImGuizmo::OPERATION)guizmo_operation, ImGuizmo::LOCAL,
glm::value_ptr(transform), nullptr,
snap ? snap_values : nullptr);
if (ImGuizmo::IsUsing()) {
glm::vec3 translation, rotation, scale;
Math::DecomposeTransform(transform, translation, rotation, scale);
glm::vec3 delta_rotation = rotation - transform_comp.rotation;
transform_comp.translation = translation;
transform_comp.rotation += delta_rotation;
transform_comp.scale = scale;
}
}
};
void OnImGuiRender() override
{
OE_PROFILE_FUNCTION();
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(450, 200));
{
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Save Scene As", "Ctrl+S")) {
std::string file = FileDialogs::SaveFile("useless");
OE_TRACE("saving to filename: {}", file);
if (!file.empty()) {
SceneSerializer serializer(scene);
serializer.Serialize(file);
}
}
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {
std::string file = FileDialogs::OpenFile("useless");
OE_DEBUG("loading filename: {}", file);
if (!file.empty()) {
scene = CreateRef<Scene>();
scene->OnViewportResize((uint32_t)viewport_size.x, (uint32_t)viewport_size.y);
scene_hierarchy.SetContext(scene);
SceneSerializer serializer(scene);
serializer.Deserialize(file);
}
}
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {
scene = CreateRef<Scene>();
scene->OnViewportResize((uint32_t)viewport_size.x, (uint32_t)viewport_size.y);
scene_hierarchy.SetContext(scene);
}
ImGui::Separator();
if (ImGui::MenuItem("Exit"))
Application::Get().Close();
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
}
ImGui::DockSpaceOverViewport();
DrawStats();
scene_hierarchy.OnImGuiRender();
DrawViewport();
ImGui::ShowDemoWindow();
ImGui::PopStyleVar();
};
private:
std::vector<Time::ProfilingResult> profiling_results;
glm::vec2 world_pos_cursor = { 0.0f, 0.0f };
glm::vec2 viewport_size = { 0.0f, 0.0f };
Ref<OpenEngine::FrameBuffer> framebuffer;
glm::vec2 viewport_bounds[2];
SceneHierarchy scene_hierarchy;
EditorCamera editor_camera;
int guizmo_operation = -1;
Ref<Scene> scene;
Entity selected_entity;
};
}
class EditorApp : public OpenEngine::Application
{
public:
EditorApp();
~EditorApp();
};
#endif // EDITOR_HPP
/*
#include <cstdio>
#include <string>
std::string OpenFile() {
char buffer[1024];
// This command opens a native GTK file picker and returns the path
FILE* pipe = popen("zenity --file-selection", "r");
if (!pipe) return "";
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
std::string path = buffer;
path.erase(path.find_last_not_of("\n") + 1); // Clean newline
pclose(pipe);
return path;
}
pclose(pipe);
return "";
}
*/