diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index b547d95..27ca833 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -30,6 +30,7 @@ function(add_example FOLDER_NAME) file(GLOB_RECURSE SOURCE_FILES ${FOLDER_NAME}/*.cpp ${FOLDER_NAME}/*.h) add_executable(${EXAMPLE_NAME} ${SOURCE_FILES}) target_link_libraries(${EXAMPLE_NAME} PRIVATE paimon) + target_include_directories(${EXAMPLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/${FOLDER_NAME}) # Set output directory to the example's folder set_target_properties(${EXAMPLE_NAME} PROPERTIES @@ -40,7 +41,6 @@ function(add_example FOLDER_NAME) copy_example_assets(${FOLDER_NAME}) endfunction() -add_example(application) add_example(compute_shader) add_example(context) add_example(damaged_helmet) diff --git a/example/application/asset/model/test.obj b/example/application/asset/model/test.obj deleted file mode 100644 index a1f6d29..0000000 --- a/example/application/asset/model/test.obj +++ /dev/null @@ -1,4 +0,0 @@ -# Example model data -# This is a placeholder for model files -vertices: 3 -faces: 1 \ No newline at end of file diff --git a/example/application/asset/shader/test.glsl b/example/application/asset/shader/test.glsl deleted file mode 100644 index 1cc3ac0..0000000 --- a/example/application/asset/shader/test.glsl +++ /dev/null @@ -1,6 +0,0 @@ -// Example shader file for application_example -#version 460 core - -void main() { - // This is a test shader -} \ No newline at end of file diff --git a/example/application/main.cpp b/example/application/main.cpp deleted file mode 100644 index 796121b..0000000 --- a/example/application/main.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include -#include - -#include - -#include "paimon/app/application.h" -#include "paimon/app/layer.h" -#include "paimon/core/log_system.h" -#include "paimon/opengl/buffer.h" -#include "paimon/opengl/program.h" -#include "paimon/opengl/shader.h" -#include "paimon/opengl/vertex_array.h" - -using namespace paimon; - -namespace { -// hard code triangle vertices in shader for simplicity -const std::string vertex_source = R"( - #version 460 core - - layout(location = 0) in vec3 a_position; - - void main() - { - gl_Position = vec4(a_position, 1.0f); - } - )"; - -const std::string fragment_source = R"( - #version 460 core - out vec4 FragColor; - void main() - { - FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); - } - )"; -} // namespace - -class TriangleLayer : public Layer { -public: - TriangleLayer() : Layer("TriangleLayer") {} - - void onAttach() override { - // Compile shaders - m_vertexShader.compile(vertex_source); - m_fragmentShader.compile(fragment_source); - - // Link program - m_program.attach(m_vertexShader); - m_program.attach(m_fragmentShader); - m_program.link(); - - // Define the vertices of a triangle - float vertices[] = { - // positions - -0.5f, -0.5f, 0.0f, // bottom left - 0.5f, -0.5f, 0.0f, // bottom right - 0.0f, 0.5f, 0.0f // top - }; - - // Create VBO - m_vbo.set_storage(sizeof(vertices), vertices, GL_DYNAMIC_STORAGE_BIT); - m_vbo.bind(GL_ARRAY_BUFFER); - - // Setup VAO - m_vao.bind(); - - m_vao.set_vertex_buffer(0, m_vbo, 0, 3 * sizeof(float)); - m_vao.set_attribute_format(0, 3, GL_FLOAT, GL_FALSE, 0); - m_vao.set_attribute_binding(0, 0); - m_vao.enable_attribute(0); - } - - void onUpdate() override { - // Clear the colorbuffer - glClearColor(0.2f, 0.3f, 0.3f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - // Bind VAO and program - m_vao.bind(); - m_program.use(); - - // Draw a triangle - glDrawArrays(GL_TRIANGLES, 0, 3); - } - - void onDetach() override { - // Cleanup resources (RAII handles most cleanup automatically) - } - - void onEvent(Event &event) override { LOG_INFO("{}", event.toString()); } - - void onImGuiRender() override {} - -private: - Shader m_vertexShader{GL_VERTEX_SHADER}; - Shader m_fragmentShader{GL_FRAGMENT_SHADER}; - Program m_program; - Buffer m_vbo; - VertexArray m_vao; -}; - -int main() { - LogSystem::init(); - Application app({.windowConfig = {.title = "Triangle Example", - .width = 800, - .height = 600}}); - app.pushLayer(std::make_unique()); - app.run(); - return 0; -} \ No newline at end of file diff --git a/example/damaged_helmet/main.cpp b/example/damaged_helmet/main.cpp index cf75431..d269b28 100644 --- a/example/damaged_helmet/main.cpp +++ b/example/damaged_helmet/main.cpp @@ -8,6 +8,7 @@ #include "paimon/app/application.h" #include "paimon/app/panel/editor_layer.h" +#include "paimon/app/panel/interaction_layer.h" #include "paimon/core/log_system.h" #include "renderer.h" @@ -17,15 +18,14 @@ using namespace paimon; class HelmetApp : public Application { public: HelmetApp() : Application() { - // Get default scene from application - auto* scene = getScene(); - // Setup editor layer first auto* editorLayer = pushLayer(std::make_unique()); + // Setup interaction layer for camera control + auto* interactionLayer = pushLayer(std::make_unique()); + // Setup renderer with the scene and viewport auto *renderer = pushLayer(std::make_unique()); - renderer->setViewportPanel(&editorLayer->getViewportPanel()); } }; diff --git a/example/damaged_helmet/renderer.cpp b/example/damaged_helmet/renderer.cpp index 7229b11..ab27c16 100644 --- a/example/damaged_helmet/renderer.cpp +++ b/example/damaged_helmet/renderer.cpp @@ -1,7 +1,9 @@ #include "renderer.h" +#include + #include "paimon/app/application.h" -#include "paimon/app/panel/viewport_panel.h" +#include "paimon/app/event/application_event.h" using namespace paimon; @@ -10,18 +12,16 @@ Renderer::Renderer() m_renderContext(std::make_unique()), m_color_pass(*m_renderContext), m_final_pass(*m_renderContext) {} +void Renderer::onAttach() { + // Initialization if needed +} + void Renderer::onUpdate() { - auto* scene = Application::getInstance().getScene(); - if (!scene) return; + auto& scene = Application::getInstance().getScene(); // First Pass: Render to FBO - m_color_pass.draw(*m_renderContext, m_resolution, *scene); + m_color_pass.draw(*m_renderContext, m_resolution, scene); - // Set the rendered texture to viewport - if (m_viewportPanel) { - m_viewportPanel->setTexture(m_color_pass.getColorTexture()); - } - // Second Pass: Render FBO texture to screen (optional, for debugging) // m_final_pass.draw(*m_renderContext, *m_color_pass.getColorTexture(), m_resolution); } @@ -29,11 +29,28 @@ void Renderer::onUpdate() { void Renderer::onEvent(Event &event) { EventDispatcher dispatcher(event); dispatcher.dispatch([this](ViewportResizeEvent& e) { - return onViewportResize(e); + m_resolution = glm::ivec2(e.getWidth(), e.getHeight()); + return false; }); } -bool Renderer::onViewportResize(const ViewportResizeEvent& event) { - m_resolution = glm::ivec2(event.getWidth(), event.getHeight()); - return false; // Allow other layers to handle this event too +void Renderer::onImGuiRender() { + // Get the Viewport window and draw the rendered texture + if (ImGui::Begin("Viewport")) { + ImVec2 viewportSize = ImGui::GetContentRegionAvail(); + + if (viewportSize.x > 0 && viewportSize.y > 0) { + GLuint textureId = m_color_pass.getColorTexture()->get_name(); + + // Display the texture using ImGui::Image + // Note: OpenGL uses bottom-left origin, so flip UV coordinates + ImGui::Image( + (ImTextureID)(intptr_t)textureId, + viewportSize, + ImVec2(0, 1), // UV top-left (flipped) + ImVec2(1, 0) // UV bottom-right (flipped) + ); + } + } + ImGui::End(); } diff --git a/example/damaged_helmet/renderer.h b/example/damaged_helmet/renderer.h index dcf0c25..1231f7a 100644 --- a/example/damaged_helmet/renderer.h +++ b/example/damaged_helmet/renderer.h @@ -1,7 +1,6 @@ #pragma once #include "paimon/app/layer.h" -#include "paimon/app/event/application_event.h" #include "paimon/rendering/render_context.h" #include "color_pass.h" @@ -9,28 +8,18 @@ namespace paimon { -class ViewportPanel; - class Renderer : public Layer { public: Renderer(); - void onAttach() override {} + void onAttach() override; void onDetach() override {} void onUpdate() override; void onEvent(Event &event) override; - void onImGuiRender() override {} - - void setViewportPanel(ViewportPanel* viewport) { m_viewportPanel = viewport; } - -private: - bool onViewportResize(const ViewportResizeEvent& event); - + void onImGuiRender() override; private: std::unique_ptr m_renderContext; - ViewportPanel* m_viewportPanel = nullptr; - glm::ivec2 m_resolution; ColorPass m_color_pass; diff --git a/source/paimon/app/application.cpp b/source/paimon/app/application.cpp index 8f58267..ac0bd1c 100644 --- a/source/paimon/app/application.cpp +++ b/source/paimon/app/application.cpp @@ -1,48 +1,26 @@ #include "paimon/app/application.h" -#include "paimon/app/application.h" -#include "paimon/app/event/application_event.h" -#include "paimon/app/event/event.h" -#include "paimon/app/window.h" #include "paimon/config.h" -#include "paimon/core/ecs/scene.h" namespace paimon { Application* Application::s_instance = nullptr; -Application::Application(const ApplicationConfig& config) : m_running(false) { +Application::Application(const ApplicationConfig& config) { s_instance = this; // Create window m_window = Window::create(config.windowConfig, config.contextFormat); - // Set event callback - m_window->setEventCallback([this](Event& event) { - onEvent(event); - }); + m_scene = ecs::Scene::create(); m_shaderManager.load(PAIMON_SHADER_DIR); - // Create default empty scene - m_scene = std::make_unique(); - m_imguiLayer = pushLayer(std::make_unique("ImGuiLayer")); - - m_running = true; } void Application::onEvent(Event& event) { - // Handle application-level events - EventDispatcher dispatcher(event); - dispatcher.dispatch([this](WindowCloseEvent& e) { - return onWindowClose(e); - }); - dispatcher.dispatch([this](WindowResizeEvent& e) { - return onWindowResize(e); - }); - // Dispatch to layers for (auto& layer : m_layers) { layer->onEvent(event); @@ -51,7 +29,7 @@ void Application::onEvent(Event& event) { } void Application::run() { - while (m_running) { + while (!m_window->shouldClose()) { // Poll events m_window->pollEvents(); @@ -73,18 +51,4 @@ void Application::run() { } } -bool Application::onWindowClose(const WindowCloseEvent& event) { - m_running = false; - return true; -} - -bool Application::onWindowResize(const WindowResizeEvent& event) { - - // for (auto& layer : m_layers) { - // layer->onResize(event.getWidth(), event.getHeight()); - // } - - return true; -} - } // namespace paimon \ No newline at end of file diff --git a/source/paimon/app/application.h b/source/paimon/app/application.h index f51164f..70dd7f5 100644 --- a/source/paimon/app/application.h +++ b/source/paimon/app/application.h @@ -3,7 +3,6 @@ #include #include -#include "paimon/app/event/application_event.h" #include "paimon/app/event/event.h" #include "paimon/app/imgui/imgui_layer.h" #include "paimon/app/layer.h" @@ -47,25 +46,20 @@ class Application { const ShaderManager& getShaderManager() const { return m_shaderManager; } // Scene management - ecs::Scene* getScene() const { return m_scene.get(); } - -private: - bool onWindowClose(const WindowCloseEvent& event); - bool onWindowResize(const WindowResizeEvent& event); + ecs::Scene& getScene() { return *m_scene; } + const ecs::Scene& getScene() const { return *m_scene; } private: static Application* s_instance; std::unique_ptr m_window; + std::unique_ptr m_scene; + ShaderManager m_shaderManager; std::vector> m_layers; ImGuiLayer *m_imguiLayer; - - std::unique_ptr m_scene; - - bool m_running; }; } // namespace paimon \ No newline at end of file diff --git a/source/paimon/app/event/application_event.cpp b/source/paimon/app/event/application_event.cpp index a70b662..e67f9c7 100644 --- a/source/paimon/app/event/application_event.cpp +++ b/source/paimon/app/event/application_event.cpp @@ -54,100 +54,4 @@ const char* ViewportResizeEvent::getName() const { return "ViewportResize"; } int ViewportResizeEvent::getCategoryFlags() const { return EventCategoryApplication; } -WindowFocusEvent::WindowFocusEvent() {} - -std::string WindowFocusEvent::toString() const { - std::stringstream ss; - ss << "WindowFocusEvent"; - return ss.str(); -} - -EventType WindowFocusEvent::getStaticType() { return EventType::WindowFocus; } - -EventType WindowFocusEvent::getEventType() const { return getStaticType(); } - -const char* WindowFocusEvent::getName() const { return "WindowFocus"; } - -int WindowFocusEvent::getCategoryFlags() const { return EventCategoryApplication; } - -WindowLostFocusEvent::WindowLostFocusEvent() {} - -std::string WindowLostFocusEvent::toString() const { - std::stringstream ss; - ss << "WindowLostFocusEvent"; - return ss.str(); -} - -EventType WindowLostFocusEvent::getStaticType() { return EventType::WindowLostFocus; } - -EventType WindowLostFocusEvent::getEventType() const { return getStaticType(); } - -const char* WindowLostFocusEvent::getName() const { return "WindowLostFocus"; } - -int WindowLostFocusEvent::getCategoryFlags() const { return EventCategoryApplication; } - -WindowMovedEvent::WindowMovedEvent(int x, int y) : m_x(x), m_y(y) {} - -std::string WindowMovedEvent::toString() const { - std::stringstream ss; - ss << "WindowMovedEvent: " << m_x << ", " << m_y; - return ss.str(); -} - -EventType WindowMovedEvent::getStaticType() { return EventType::WindowMoved; } - -EventType WindowMovedEvent::getEventType() const { return getStaticType(); } - -const char* WindowMovedEvent::getName() const { return "WindowMoved"; } - -int WindowMovedEvent::getCategoryFlags() const { return EventCategoryApplication; } - -AppTickEvent::AppTickEvent() {} - -std::string AppTickEvent::toString() const { - std::stringstream ss; - ss << "AppTickEvent"; - return ss.str(); -} - -EventType AppTickEvent::getStaticType() { return EventType::AppTick; } - -EventType AppTickEvent::getEventType() const { return getStaticType(); } - -const char* AppTickEvent::getName() const { return "AppTick"; } - -int AppTickEvent::getCategoryFlags() const { return EventCategoryApplication; } - -AppUpdateEvent::AppUpdateEvent() {} - -std::string AppUpdateEvent::toString() const { - std::stringstream ss; - ss << "AppUpdateEvent"; - return ss.str(); -} - -EventType AppUpdateEvent::getStaticType() { return EventType::AppUpdate; } - -EventType AppUpdateEvent::getEventType() const { return getStaticType(); } - -const char* AppUpdateEvent::getName() const { return "AppUpdate"; } - -int AppUpdateEvent::getCategoryFlags() const { return EventCategoryApplication; } - -AppRenderEvent::AppRenderEvent() {} - -std::string AppRenderEvent::toString() const { - std::stringstream ss; - ss << "AppRenderEvent"; - return ss.str(); -} - -EventType AppRenderEvent::getStaticType() { return EventType::AppRender; } - -EventType AppRenderEvent::getEventType() const { return getStaticType(); } - -const char* AppRenderEvent::getName() const { return "AppRender"; } - -int AppRenderEvent::getCategoryFlags() const { return EventCategoryApplication; } - } // namespace paimon \ No newline at end of file diff --git a/source/paimon/app/event/application_event.h b/source/paimon/app/event/application_event.h index a21339a..37c6530 100644 --- a/source/paimon/app/event/application_event.h +++ b/source/paimon/app/event/application_event.h @@ -54,82 +54,4 @@ class ViewportResizeEvent : public Event { int32_t m_width, m_height; }; -class WindowFocusEvent : public Event { -public: - WindowFocusEvent(); - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; -}; - -class WindowLostFocusEvent : public Event { -public: - WindowLostFocusEvent(); - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; -}; - -class WindowMovedEvent : public Event { -public: - WindowMovedEvent(int x, int y); - - int getX() const { return m_x; } - int getY() const { return m_y; } - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; - -private: - int m_x, m_y; -}; - -class AppTickEvent : public Event { -public: - AppTickEvent(); - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; -}; - -class AppUpdateEvent : public Event { -public: - AppUpdateEvent(); - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; -}; - -class AppRenderEvent : public Event { -public: - AppRenderEvent(); - - std::string toString() const override; - - static EventType getStaticType(); - EventType getEventType() const override; - const char* getName() const override; - int getCategoryFlags() const override; -}; - } // namespace paimon \ No newline at end of file diff --git a/source/paimon/app/panel/editor_layer.h b/source/paimon/app/panel/editor_layer.h index 30b8a1f..b245723 100644 --- a/source/paimon/app/panel/editor_layer.h +++ b/source/paimon/app/panel/editor_layer.h @@ -17,8 +17,6 @@ class EditorLayer : public Layer { void onEvent(Event &event) override; void onImGuiRender() override; - ViewportPanel& getViewportPanel() { return m_viewportPanel; } - private: void setupDockingLayout(); diff --git a/source/paimon/app/panel/interaction_layer.cpp b/source/paimon/app/panel/interaction_layer.cpp new file mode 100644 index 0000000..575fae4 --- /dev/null +++ b/source/paimon/app/panel/interaction_layer.cpp @@ -0,0 +1,36 @@ +#include "paimon/app/panel/interaction_layer.h" + +#include "paimon/app/application.h" + +namespace paimon { + +InteractionLayer::InteractionLayer() + : Layer("InteractionLayer"), + m_cameraController(std::make_unique()) {} + +InteractionLayer::~InteractionLayer() = default; + +void InteractionLayer::onAttach() { + // Bind camera to controller + auto& scene = Application::getInstance().getScene(); + auto mainCamera = scene.getMainCamera(); + m_cameraController->CameraController::setTarget(mainCamera); +} + +void InteractionLayer::onDetach() {} + +void InteractionLayer::onUpdate() { + // Update camera controller + m_cameraController->update(0.016f); // ~60 FPS +} + +void InteractionLayer::onEvent(Event &event) { + // Forward input events to camera controller + m_cameraController->onEvent(event); +} + +void InteractionLayer::onImGuiRender() { + // No GUI rendering needed for interaction layer +} + +} // namespace paimon diff --git a/source/paimon/app/panel/interaction_layer.h b/source/paimon/app/panel/interaction_layer.h new file mode 100644 index 0000000..92801ad --- /dev/null +++ b/source/paimon/app/panel/interaction_layer.h @@ -0,0 +1,24 @@ +#pragma once + +#include "paimon/app/layer.h" +#include "paimon/core/camera/orbit_camera_controller.h" + +namespace paimon { + +class InteractionLayer : public Layer { +public: + InteractionLayer(); + ~InteractionLayer() override; + + void onAttach() override; + void onDetach() override; + void onUpdate() override; + void onEvent(Event &event) override; + void onImGuiRender() override; + +private: + // Camera control + std::unique_ptr m_cameraController; +}; + +} // namespace paimon diff --git a/source/paimon/app/panel/menu_panel.cpp b/source/paimon/app/panel/menu_panel.cpp index 28fb016..5d6919f 100644 --- a/source/paimon/app/panel/menu_panel.cpp +++ b/source/paimon/app/panel/menu_panel.cpp @@ -34,11 +34,9 @@ void MenuPanel::showMainMenuBar() { void MenuPanel::showFileMenu() { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New Scene", "Ctrl+N")) { - auto* scene = Application::getInstance().getScene(); - if (scene) { - // Clear current scene - scene->clear(); - } + auto& scene = Application::getInstance().getScene(); + // Clear current scene + scene.clear(); } if (ImGui::MenuItem("Load Model...", "Ctrl+O")) { @@ -47,11 +45,9 @@ void MenuPanel::showFileMenu() { nfdresult_t result = NFD_OpenDialogU8(&outPath, filters, 2, nullptr); if (result == NFD_OKAY) { - auto* scene = Application::getInstance().getScene(); - if (scene) { - LOG_INFO("Loading model: {}", outPath); - scene->load(outPath); - } + auto& scene = Application::getInstance().getScene(); + LOG_INFO("Loading model: {}", outPath); + scene.load(outPath); NFD_FreePathU8(outPath); } else if (result == NFD_ERROR) { LOG_ERROR("Error opening file dialog: {}", NFD_GetError()); diff --git a/source/paimon/app/panel/scene_panel.cpp b/source/paimon/app/panel/scene_panel.cpp index 0695952..26cc2de 100644 --- a/source/paimon/app/panel/scene_panel.cpp +++ b/source/paimon/app/panel/scene_panel.cpp @@ -16,17 +16,15 @@ ScenePanel::~ScenePanel() {} void ScenePanel::onImGuiRender() { ImGui::Begin("Scene Hierarchy"); - auto* scene = Application::getInstance().getScene(); + auto& scene = Application::getInstance().getScene(); - if (scene) { - // Iterate through all entities and find root entities (no parent) - auto view = scene->view(); - - for (auto [entity, name, parent] : view.each()) { - // Only draw root entities (entities without valid parent) - if (!parent.parent.isValid()) { - drawEntityNode(ecs::Entity(scene, entity)); - } + // Iterate through all entities and find root entities (no parent) + auto view = scene.view(); + + for (auto [entity, name, parent] : view.each()) { + // Only draw root entities (entities without valid parent) + if (!parent.parent.isValid()) { + drawEntityNode(ecs::Entity(&scene, entity)); } } @@ -38,10 +36,8 @@ void ScenePanel::onImGuiRender() { // Right-click context menu for creating entities if (ImGui::BeginPopupContextWindow("SceneContextMenu", ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems)) { if (ImGui::MenuItem("Create Empty Entity")) { - if (scene) { - auto newEntity = scene->createEntity("New Entity"); - m_selectedEntity = newEntity; - } + auto newEntity = scene.createEntity("New Entity"); + m_selectedEntity = newEntity; } ImGui::EndPopup(); } @@ -114,20 +110,16 @@ void ScenePanel::drawEntityNode(ecs::Entity entity) { // Right-click context menu on entity if (ImGui::BeginPopupContextItem()) { if (ImGui::MenuItem("Create Child Entity")) { - auto* scene = Application::getInstance().getScene(); - if (scene) { - auto child = scene->createEntity("Child Entity"); - // TODO: Set parent-child relationship - // child.setParent(entity); - } + auto& scene = Application::getInstance().getScene(); + auto child = scene.createEntity("Child Entity"); + // TODO: Set parent-child relationship + // child.setParent(entity); } if (ImGui::MenuItem("Delete Entity")) { - auto* scene = Application::getInstance().getScene(); - if (scene) { - scene->destroyEntity(entity); - if (m_selectedEntity == entity) { - m_selectedEntity = {}; - } + auto& scene = Application::getInstance().getScene(); + scene.destroyEntity(entity); + if (m_selectedEntity == entity) { + m_selectedEntity = {}; } } ImGui::EndPopup(); diff --git a/source/paimon/app/panel/viewport_panel.cpp b/source/paimon/app/panel/viewport_panel.cpp index e345586..cb6f104 100644 --- a/source/paimon/app/panel/viewport_panel.cpp +++ b/source/paimon/app/panel/viewport_panel.cpp @@ -1,11 +1,9 @@ #include "paimon/app/panel/viewport_panel.h" -#include #include #include "paimon/app/application.h" #include "paimon/app/event/application_event.h" -#include "paimon/opengl/texture.h" namespace paimon { @@ -17,6 +15,9 @@ void ViewportPanel::onImGuiRender() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("Viewport"); + // Track focus state + m_isFocused = ImGui::IsWindowFocused(); + // Get available content region ImVec2 viewportSize = ImGui::GetContentRegionAvail(); @@ -30,24 +31,7 @@ void ViewportPanel::onImGuiRender() { Application::getInstance().onEvent(event); } - // Display the texture if available - if (m_texture) { - // Get texture ID (OpenGL texture handle) - GLuint textureId = m_texture->get_name(); - - // Display the texture using ImGui::Image - // Note: ImGui uses bottom-left as origin, OpenGL uses top-left - // So we need to flip the UV coordinates - ImGui::Image( - (ImTextureID)(intptr_t)textureId, - viewportSize, - ImVec2(0, 1), // UV top-left (flipped) - ImVec2(1, 0) // UV bottom-right (flipped) - ); - } else { - // Show placeholder when no texture - ImGui::Text("No viewport texture available"); - } + // Viewport content will be rendered by Renderer in its onImGuiRender ImGui::End(); ImGui::PopStyleVar(); diff --git a/source/paimon/app/panel/viewport_panel.h b/source/paimon/app/panel/viewport_panel.h index 6d2517b..4b347b7 100644 --- a/source/paimon/app/panel/viewport_panel.h +++ b/source/paimon/app/panel/viewport_panel.h @@ -4,20 +4,18 @@ namespace paimon { -class Texture; - class ViewportPanel { public: ViewportPanel(); ~ViewportPanel(); void onImGuiRender(); - - void setTexture(const Texture* texture) { m_texture = texture; } + + bool isFocused() const { return m_isFocused; } private: - const Texture* m_texture = nullptr; - glm::ivec2 m_viewportSize; + glm::ivec2 m_viewportSize = glm::ivec2(0); + bool m_isFocused = false; }; } // namespace paimon diff --git a/source/paimon/app/window.cpp b/source/paimon/app/window.cpp index fda0a37..48ca060 100644 --- a/source/paimon/app/window.cpp +++ b/source/paimon/app/window.cpp @@ -2,103 +2,57 @@ #include -#include "paimon/core/log_system.h" -#include "paimon/app/event/application_event.h" +#include "paimon/app/application.h" #include "paimon/app/event/key_event.h" #include "paimon/app/event/mouse_event.h" +#include "paimon/core/log_system.h" using namespace paimon; -static void windowCloseCallback(GLFWwindow* window) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - WindowCloseEvent event; - win->getEventCallback()(event); - } -} - -static void windowSizeCallback(GLFWwindow* window, int width, int height) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - WindowResizeEvent event(static_cast(width), static_cast(height)); - win->getEventCallback()(event); - } -} - -static void windowFocusCallback(GLFWwindow* window, int focused) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - if (focused) { - WindowFocusEvent event; - win->getEventCallback()(event); - } else { - WindowLostFocusEvent event; - win->getEventCallback()(event); - } - } -} - -static void windowPosCallback(GLFWwindow* window, int x, int y) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - WindowMovedEvent event(x, y); - win->getEventCallback()(event); - } -} - -static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - KeyCode keyCode = static_cast(key); - if (action == GLFW_PRESS) { - KeyPressedEvent event(keyCode, 0); // repeat count 0 for now - win->getEventCallback()(event); - } else if (action == GLFW_RELEASE) { - KeyReleasedEvent event(keyCode); - win->getEventCallback()(event); - } else if (action == GLFW_REPEAT) { - KeyPressedEvent event(keyCode, 1); // repeat - win->getEventCallback()(event); - } +static void keyCallback(GLFWwindow *window, int key, int scancode, int action, + int mods) { + auto &app = Application::getInstance(); + KeyCode keyCode = static_cast(key); + if (action == GLFW_PRESS) { + KeyPressedEvent event(keyCode, 0); // repeat count 0 for now + app.onEvent(event); + } else if (action == GLFW_RELEASE) { + KeyReleasedEvent event(keyCode); + app.onEvent(event); + } else if (action == GLFW_REPEAT) { + KeyPressedEvent event(keyCode, 1); // repeat + app.onEvent(event); } } static void charCallback(GLFWwindow* window, unsigned int codepoint) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - KeyTypedEvent event(static_cast(codepoint)); - win->getEventCallback()(event); - } + auto &app = Application::getInstance(); + KeyTypedEvent event(static_cast(codepoint)); + app.onEvent(event); } static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - MouseCode mouseCode = static_cast(button); - if (action == GLFW_PRESS) { - MouseButtonPressedEvent event(mouseCode); - win->getEventCallback()(event); - } else if (action == GLFW_RELEASE) { - MouseButtonReleasedEvent event(mouseCode); - win->getEventCallback()(event); - } + auto &app = Application::getInstance(); + MouseCode mouseCode = static_cast(button); + if (action == GLFW_PRESS) { + MouseButtonPressedEvent event(mouseCode); + app.onEvent(event); + } else if (action == GLFW_RELEASE) { + MouseButtonReleasedEvent event(mouseCode); + app.onEvent(event); } } static void cursorPosCallback(GLFWwindow* window, double x, double y) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - MouseMovedEvent event(static_cast(x), static_cast(y)); - win->getEventCallback()(event); - } + auto &app = Application::getInstance(); + MouseMovedEvent event(static_cast(x), static_cast(y)); + app.onEvent(event); } static void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) { - Window* win = static_cast(glfwGetWindowUserPointer(window)); - if (win->getEventCallback()) { - MouseScrolledEvent event(static_cast(xOffset), static_cast(yOffset)); - win->getEventCallback()(event); - } + auto &app = Application::getInstance(); + MouseScrolledEvent event(static_cast(xOffset), static_cast(yOffset)); + app.onEvent(event); } using namespace paimon; @@ -173,10 +127,6 @@ std::unique_ptr Window::create(const WindowConfig &config, const Context glfwSetWindowUserPointer(window->m_window, window.get()); - glfwSetWindowCloseCallback(window->m_window, windowCloseCallback); - glfwSetWindowSizeCallback(window->m_window, windowSizeCallback); - glfwSetWindowFocusCallback(window->m_window, windowFocusCallback); - glfwSetWindowPosCallback(window->m_window, windowPosCallback); glfwSetKeyCallback(window->m_window, keyCallback); glfwSetCharCallback(window->m_window, charCallback); glfwSetMouseButtonCallback(window->m_window, mouseButtonCallback); diff --git a/source/paimon/app/window.h b/source/paimon/app/window.h index 410c1b5..3e56070 100644 --- a/source/paimon/app/window.h +++ b/source/paimon/app/window.h @@ -50,15 +50,8 @@ class Window { bool isMouseButtonPressed(MouseCode button) const; std::pair getMousePosition() const; - void setEventCallback(const std::function& callback) { m_eventCallback = callback; } - - const std::function& getEventCallback() const { return m_eventCallback; } - private: GLFWwindow *m_window = nullptr; - -public: - std::function m_eventCallback; }; } // namespace paimon \ No newline at end of file diff --git a/source/paimon/core/camera/camera_controller.h b/source/paimon/core/camera/camera_controller.h new file mode 100644 index 0000000..21da746 --- /dev/null +++ b/source/paimon/core/camera/camera_controller.h @@ -0,0 +1,25 @@ +#pragma once + +#include "paimon/core/ecs/entity.h" +#include "paimon/app/event/event.h" + +namespace paimon { + +class CameraController { +public: + CameraController() = default; + virtual ~CameraController() = default; + + virtual void onEvent(Event& event) = 0; + virtual void update(float deltaTime) = 0; + + void setTarget(ecs::Entity cameraEntity) { m_camera = cameraEntity; } + void setEnabled(bool enabled) { m_enabled = enabled; } + bool isEnabled() const { return m_enabled; } + +protected: + ecs::Entity m_camera; + bool m_enabled = true; +}; + +} // namespace paimon diff --git a/source/paimon/core/camera/orbit_camera_controller.cpp b/source/paimon/core/camera/orbit_camera_controller.cpp new file mode 100644 index 0000000..660d28d --- /dev/null +++ b/source/paimon/core/camera/orbit_camera_controller.cpp @@ -0,0 +1,210 @@ +#include "paimon/core/camera/orbit_camera_controller.h" + +#include +#include + +#include "paimon/app/event/application_event.h" +#include "paimon/app/event/event.h" +#include "paimon/app/event/mouse_event.h" +#include "paimon/app/mouse_code.h" +#include "paimon/core/ecs/components.h" +#include "paimon/core/sg/camera.h" + +namespace paimon { + +OrbitCameraController::OrbitCameraController() + : m_targetPosition(0.0f), m_distance(5.0f), m_yaw(0.0f), m_pitch(0.0f) {} + +void OrbitCameraController::onEvent(Event& event) { + if (!m_enabled || !m_camera.isValid()) { + return; + } + + EventDispatcher dispatcher(event); + + // Handle viewport resize + dispatcher.dispatch([this](const ViewportResizeEvent& e) { + onResize(glm::ivec2(e.getWidth(), e.getHeight())); + return false; // Allow other handlers to process this event + }); + + // Handle mouse button press + dispatcher.dispatch([this](const MouseButtonPressedEvent& e) { + if (e.getMouseButton() == MouseCode::Right) { + m_isRotating = true; + m_lastMousePos = glm::vec2(0.0f); // Will be initialized on first move + return true; + } + if (e.getMouseButton() == MouseCode::Middle) { + m_isPanning = true; + m_lastMousePos = glm::vec2(0.0f); // Will be initialized on first move + return true; + } + return false; + }); + + // Handle mouse button release + dispatcher.dispatch([this](const MouseButtonReleasedEvent& e) { + if (e.getMouseButton() == MouseCode::Right) { + m_isRotating = false; + return true; + } + if (e.getMouseButton() == MouseCode::Middle) { + m_isPanning = false; + return true; + } + return false; + }); + + // Handle mouse movement + dispatcher.dispatch([this](const MouseMovedEvent& e) { + glm::vec2 mousePos(e.getX(), e.getY()); + glm::vec2 mouseDelta = mousePos - m_lastMousePos; + + // Initialize on first move after button press + if (m_lastMousePos == glm::vec2(0.0f)) { + m_lastMousePos = mousePos; + return false; + } + + if (m_isRotating && glm::length(mouseDelta) > 0.01f) { + onRotate(mouseDelta); + m_lastMousePos = mousePos; + return true; + } + + if (m_isPanning && glm::length(mouseDelta) > 0.01f) { + onPan(mouseDelta); + m_lastMousePos = mousePos; + return true; + } + + m_lastMousePos = mousePos; + return false; + }); + + // Handle mouse scroll + dispatcher.dispatch([this](const MouseScrolledEvent& e) { + onZoom(e.getYOffset()); + return true; + }); +} + +void OrbitCameraController::onRotate(const glm::vec2& delta) { + // Right-click drag - rotate view + m_yaw -= delta.x * m_sensitivity; + m_pitch -= delta.y * m_sensitivity; + + // Clamp pitch angle + m_pitch = glm::clamp(m_pitch, glm::radians(m_minPitch), glm::radians(m_maxPitch)); +} + +void OrbitCameraController::onPan(const glm::vec2& delta) { + // Middle-click drag - pan view + m_targetPosition += calculatePanDelta(delta); +} + +void OrbitCameraController::onZoom(float scrollDelta) { + // Mouse wheel - zoom + m_distance *= (1.0f - scrollDelta * m_zoomSpeed); + + // Clamp distance + m_distance = glm::clamp(m_distance, m_minDistance, m_maxDistance); +} + +void OrbitCameraController::update(float deltaTime) { + if (!m_enabled || !m_camera.isValid()) { + return; + } + + updateCameraTransform(); +} + +void OrbitCameraController::updateCameraTransform() { + if (!m_camera.hasComponent()) { + return; + } + + // Calculate camera position in spherical coordinates + float cosYaw = glm::cos(m_yaw); + float sinYaw = glm::sin(m_yaw); + float cosPitch = glm::cos(m_pitch); + float sinPitch = glm::sin(m_pitch); + + glm::vec3 offset( + m_distance * cosPitch * cosYaw, + m_distance * sinPitch, + m_distance * cosPitch * sinYaw + ); + + glm::vec3 cameraPosition = m_targetPosition + offset; + glm::vec3 direction = glm::normalize(m_targetPosition - cameraPosition); + + // Calculate camera rotation (looking at target) + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 right = glm::normalize(glm::cross(direction, up)); + glm::vec3 cameraUp = glm::cross(right, direction); + + glm::mat3 rotationMatrix(right, cameraUp, -direction); + glm::quat rotation = glm::quat_cast(rotationMatrix); + + // Update Transform component + auto& transform = m_camera.getComponent(); + transform.translation = cameraPosition; + transform.rotation = rotation; + + // Update Camera component + if (m_camera.hasComponent()) { + auto& camera = m_camera.getComponent(); + camera.position = cameraPosition; + camera.direction = direction; + + // Update view matrix + camera.view = glm::lookAt(cameraPosition, m_targetPosition, cameraUp); + camera.inverseView = glm::inverse(camera.view); + } +} + +glm::vec3 OrbitCameraController::calculatePanDelta(const glm::vec2& delta) { + if (!m_camera.hasComponent()) { + return glm::vec3(0.0f); + } + + auto& transform = m_camera.getComponent(); + + // Get camera's right and up directions + glm::vec3 forward = glm::normalize(m_targetPosition - transform.translation); + glm::vec3 right = glm::normalize(glm::cross(forward, glm::vec3(0, 1, 0))); + glm::vec3 up = glm::cross(right, forward); + + // Adjust pan speed based on distance (move faster when farther away) + float speedFactor = m_distance * m_panSpeed; + + // Calculate pan vector + glm::vec3 panDelta = -right * delta.x * speedFactor + up * delta.y * speedFactor; + + return panDelta; +} + +void OrbitCameraController::onResize(const glm::ivec2& resolution) { + if (!m_camera.isValid() || !m_camera.hasComponent()) { + return; + } + + auto& cameraComp = m_camera.getComponent(); + if (!cameraComp.camera) return; + + // Calculate aspect ratio + float aspectRatio = resolution.x > 0 && resolution.y > 0 + ? static_cast(resolution.x) / static_cast(resolution.y) + : 1.0f; + + // Update aspect ratio for perspective cameras + if (cameraComp.camera->getType() == sg::Camera::Type::Perspective) { + auto* perspCam = static_cast(cameraComp.camera.get()); + perspCam->aspectRatio = aspectRatio; + cameraComp.projection = perspCam->getProjection(); + } +} + +} // namespace paimon diff --git a/source/paimon/core/camera/orbit_camera_controller.h b/source/paimon/core/camera/orbit_camera_controller.h new file mode 100644 index 0000000..2cc1e71 --- /dev/null +++ b/source/paimon/core/camera/orbit_camera_controller.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "paimon/core/camera/camera_controller.h" + +namespace paimon { + +class OrbitCameraController : public CameraController { +public: + OrbitCameraController(); + ~OrbitCameraController() override = default; + + void onEvent(Event& event) override; + void update(float deltaTime) override; + +private: + void updateCameraTransform(); + glm::vec3 calculatePanDelta(const glm::vec2& delta); + void onRotate(const glm::vec2& delta); + void onPan(const glm::vec2& delta); + void onZoom(float scrollDelta); + void onResize(const glm::ivec2& resolution); + +private: + // Camera parameters + glm::vec3 m_targetPosition = glm::vec3(0.0f); + float m_distance = 5.0f; + float m_yaw = 0.0f; // Horizontal angle + float m_pitch = 0.0f; // Vertical angle + + // Control parameters + float m_sensitivity = 0.003f; + float m_zoomSpeed = 0.1f; + float m_panSpeed = 0.005f; + + // Constraints + float m_minDistance = 0.1f; + float m_maxDistance = 100.0f; + float m_minPitch = -89.0f; + float m_maxPitch = 89.0f; + + // Input state + glm::vec2 m_lastMousePos = glm::vec2(0.0f); + bool m_isRotating = false; + bool m_isPanning = false; +}; + +} // namespace paimon diff --git a/source/paimon/core/ecs/scene.cpp b/source/paimon/core/ecs/scene.cpp index 130db57..d9a52c3 100644 --- a/source/paimon/core/ecs/scene.cpp +++ b/source/paimon/core/ecs/scene.cpp @@ -6,27 +6,6 @@ namespace paimon { namespace ecs { -Scene::Scene() { - // Initialize main camera entity - { - m_mainCamera = createEntity("MainCamera"); - m_mainCamera.addComponent(std::make_shared()); - - // Set camera position to look at the origin - auto &transform = m_mainCamera.getComponent(); - transform.translation = glm::vec3(0.0f, 0.0f, 5.0f); - } - - { - // Initialize directional light entity - m_directionalLight = createEntity("DirectionalLight"); - m_directionalLight.addComponent(std::make_shared()); - - auto &transform = m_directionalLight.getComponent(); - transform.translation = glm::vec3(0.0f, 0.0f, 3.0f); - } -} - Entity Scene::createEntity(const std::string &name) { auto entity = Entity{this, m_registry.create()}; @@ -64,5 +43,34 @@ Entity Scene::load(const std::filesystem::path &filepath) { return loader.getRootEntity(); } +std::unique_ptr Scene::create() { + auto scene = std::make_unique(); + + // Initialize main camera entity + { + auto mainCamera = scene->createEntity("MainCamera"); + mainCamera.addComponent(std::make_shared()); + + // Set camera position to look at the origin + auto &transform = mainCamera.getComponent(); + transform.translation = glm::vec3(0.0f, 0.0f, 5.0f); + + scene->setMainCamera(mainCamera); + } + + { + // Initialize directional light entity + auto directionalLight = scene->createEntity("DirectionalLight"); + directionalLight.addComponent(std::make_shared()); + + auto &transform = directionalLight.getComponent(); + transform.translation = glm::vec3(0.0f, 0.0f, 3.0f); + + scene->setDirectionalLight(directionalLight); + } + + return scene; +} + } // namespace ecs } // namespace paimon diff --git a/source/paimon/core/ecs/scene.h b/source/paimon/core/ecs/scene.h index f274ae6..6f5c13d 100644 --- a/source/paimon/core/ecs/scene.h +++ b/source/paimon/core/ecs/scene.h @@ -3,6 +3,7 @@ #include #include +#include #include "paimon/core/ecs/entity.h" #include "paimon/core/ecs/system.h" @@ -14,7 +15,7 @@ class Entity; class Scene { public: - Scene(); + Scene() = default; ~Scene() = default; Scene(const Scene &) = delete; @@ -28,9 +29,13 @@ class Scene { entt::registry &getRegistry(); const entt::registry &getRegistry() const; + void setMainCamera(Entity camera) { m_mainCamera = camera; } + Entity getMainCamera() { return m_mainCamera; } const Entity &getMainCamera() const { return m_mainCamera; } + void setDirectionalLight(Entity light) { m_directionalLight = light; } + Entity getDirectionalLight() { return m_directionalLight; } const Entity &getDirectionalLight() const { return m_directionalLight; } @@ -59,6 +64,8 @@ class Scene { Entity load(const std::filesystem::path &filepath); + static std::unique_ptr create(); + private: entt::registry m_registry;