diff --git a/CMakeLists.txt b/CMakeLists.txt index 5336516..d28305c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,19 @@ add_subdirectory(_ThirdParty/glfw) add_subdirectory(_ThirdParty/spdlog) set (ASSIMP_INSTALL OFF) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_path(UNZIP_PATH unzip.h PATHS + /usr/include + /usr/local/include) + + if(NOT UNZIP_PATH) + include_directories(/usr/include/minizip) + endif() +endif() add_subdirectory(_ThirdParty/assimp) +target_compile_options(assimp PRIVATE + -Wno-unused-but-set-variable +) add_subdirectory(_ThirdParty/glm) @@ -44,3 +56,4 @@ add_custom_target(copy_resources target_link_libraries(${PROJECT_NAME} glfw spdlog assimp glm EnTT fmt) target_include_directories(${PROJECT_NAME} PRIVATE ${ASSIMP_INCLUDE_INSTALL_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE src) diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..86523fc --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,93 @@ +// +// Created by slinky on 5/3/26. +// + +#include "Camera.h" + +#include "glm/ext/matrix_transform.hpp" +#include "glm/ext/matrix_clip_space.hpp" + +Camera::Camera(const glm::vec3 &pos, float pitch, float yaw, float aspectRatio) : _position(pos), _yaw(yaw), _pitch(pitch), _aspectRatio(aspectRatio) { + update_camera_vectors(); + update_view(); + update_projection(); +} + +const glm::mat4& Camera::view() const { + return _view; +} + +const glm::mat4& Camera::projection() const { + return _projection; +} + +const glm::vec3& Camera::position() const { + return _position; +} + +glm::vec3 Camera::front() const { + return glm::normalize(_front); +} + +glm::vec3 Camera::right() const { + return glm::normalize(_right); +} + +glm::vec3 Camera::up() const { + return glm::normalize(_up); +} + +float Camera::aspect_ratio() const { + return _aspectRatio; +} + +void Camera::rotate_pitch(float offset) { + _pitch += offset; + + if (_pitch < MIN_PITCH) { + _pitch = MIN_PITCH; + } + + if (_pitch > MAX_PITCH) { + _pitch = MAX_PITCH; + } + + update_camera_vectors(); + update_view(); +} + +void Camera::rotate_yaw(float offset) { + _yaw += offset; + update_camera_vectors(); + update_view(); +} + +void Camera::translate(const glm::vec3& translation) { + _position += translation; + update_view(); +} + +void Camera::update_aspect_ratio(float aspectRatio) { + _aspectRatio = aspectRatio; + _projection = glm::perspective(glm::radians(75.f), _aspectRatio, 0.1f, 100.0f); +} + +void Camera::update_camera_vectors() { + _front = {}; + _front.x = cos(glm::radians(_yaw)) * cos(glm::radians(_pitch)); + _front.y = sin(glm::radians(_pitch)); + _front.z = sin(glm::radians(_yaw)) * cos(glm::radians(_pitch)); + + _front = glm::normalize(_front); + + _right = glm::normalize(glm::cross(_front, WORLD_UP)); + _up = glm::normalize(glm::cross(_right, _front)); +} + +void Camera::update_projection() { + _projection = glm::perspective(glm::radians(75.f), _aspectRatio, 0.1f, 100.0f); +} + +void Camera::update_view() { + _view = glm::lookAt(_position, _position + _front, _up); +} \ No newline at end of file diff --git a/src/FreeCamera.h b/src/Camera.h similarity index 65% rename from src/FreeCamera.h rename to src/Camera.h index e521bb3..fa9b277 100644 --- a/src/FreeCamera.h +++ b/src/Camera.h @@ -12,29 +12,24 @@ constexpr float MIN_PITCH = -89.f; constexpr glm::vec3 WORLD_UP = {0.0f, 1.0f, 0.0f}; -enum CAMERA_MOVEMENT { - FORWARD, - BACKWARD, - LEFT, - RIGHT, - UP, - DOWN, -}; - -class FreeCamera { +class Camera { public: - FreeCamera() = default; - FreeCamera(const glm::vec3 &pos, float pitch, float yaw, float aspectRatio); + Camera() = default; + Camera(const glm::vec3 &pos, float pitch, float yaw, float aspectRatio); [[nodiscard]] const glm::mat4& projection() const; [[nodiscard]] const glm::mat4& view() const; [[nodiscard]] const glm::vec3& position() const; + [[nodiscard]] glm::vec3 front() const; + [[nodiscard]] glm::vec3 up() const; + [[nodiscard]] glm::vec3 right() const; + [[nodiscard]] float aspect_ratio() const; void update_aspect_ratio(float aspectRatio); void rotate_yaw(float offset); void rotate_pitch(float offset); - void move(CAMERA_MOVEMENT direction, float offset); + void translate(const glm::vec3& direction); private: void update_camera_vectors(); void update_projection(); @@ -42,9 +37,9 @@ private: glm::vec3 _position{}; - glm::vec3 front{}; - glm::vec3 up{}; - glm::vec3 right{}; + glm::vec3 _front{}; + glm::vec3 _up{}; + glm::vec3 _right{}; glm::mat4 _view{}; glm::mat4 _projection{}; diff --git a/src/Components.h b/src/Components.h index 46f367b..a2e5562 100644 --- a/src/Components.h +++ b/src/Components.h @@ -17,6 +17,7 @@ namespace Components { glm::vec3 rotation{}; glm::vec3 scale{}; glm::mat4 model = glm::identity(); + bool dirty {true}; }; struct Relationship { diff --git a/src/EditorContext.h b/src/EditorContext.h new file mode 100644 index 0000000..fb2ac49 --- /dev/null +++ b/src/EditorContext.h @@ -0,0 +1,21 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_EDITORCONTEXT_H +#define B_ENGINE_EDITORCONTEXT_H + +#include "Scene.h" +#include "GLFW/glfw3.h" + +struct EditorContext { + bool sceneViewerFocused = false; + glm::vec2 viewportSize = glm::vec2 {}; + float viewportAspectRatio = 1; + + Scene* scene {nullptr}; + GLFWwindow* window; + entt::entity selectedEntity; +}; + +#endif //B_ENGINE_EDITORCONTEXT_H diff --git a/src/FreeCamera.cpp b/src/FreeCamera.cpp deleted file mode 100644 index 3c53669..0000000 --- a/src/FreeCamera.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// -// Created by slinky on 5/3/26. -// - -#include "FreeCamera.h" - -#include "glm/ext/matrix_transform.hpp" -#include "glm/ext/matrix_clip_space.hpp" - -FreeCamera::FreeCamera(const glm::vec3 &pos, float pitch, float yaw, float aspectRatio) : _position(pos), _yaw(yaw), _pitch(pitch), _aspectRatio(aspectRatio) { - update_camera_vectors(); - update_view(); - update_projection(); -} - -const glm::mat4& FreeCamera::view() const { - return _view; -} - -const glm::mat4& FreeCamera::projection() const { - return _projection; -} - -const glm::vec3& FreeCamera::position() const { - return _position; -} - -void FreeCamera::rotate_pitch(float offset) { - _pitch += offset; - - if (_pitch < MIN_PITCH) { - _pitch = MIN_PITCH; - } - - if (_pitch > MAX_PITCH) { - _pitch = MAX_PITCH; - } - - update_camera_vectors(); - update_view(); -} - -void FreeCamera::rotate_yaw(float offset) { - _yaw += offset; - update_camera_vectors(); - update_view(); -} - -void FreeCamera::move(CAMERA_MOVEMENT direction, float offset) { - glm::vec3 finalDirection = {}; - - switch (direction) { - case FORWARD: - finalDirection = front; - break; - case BACKWARD: - finalDirection = -front; - break; - case LEFT: - finalDirection = -right; - break; - case RIGHT: - finalDirection = right; - break; - case UP: - finalDirection = up; - break; - case DOWN: - finalDirection = -up; - break; - } - - finalDirection = glm::normalize(finalDirection); - _position += finalDirection * offset; - _view = glm::lookAt(_position, _position + front, up); -} - -void FreeCamera::update_aspect_ratio(float aspectRatio) { - _aspectRatio = aspectRatio; - _projection = glm::perspective(glm::radians(75.f), _aspectRatio, 0.1f, 100.0f); -} - -void FreeCamera::update_camera_vectors() { - front = {}; - front.x = cos(glm::radians(_yaw)) * cos(glm::radians(_pitch)); - front.y = sin(glm::radians(_pitch)); - front.z = sin(glm::radians(_yaw)) * cos(glm::radians(_pitch)); - - right = glm::normalize(glm::cross(front, WORLD_UP)); - up = glm::normalize(glm::cross(right, front)); -} - -void FreeCamera::update_projection() { - _projection = glm::perspective(glm::radians(75.f), _aspectRatio, 0.1f, 100.0f); -} - -void FreeCamera::update_view() { - _view = glm::lookAt(_position, _position + front, up); -} \ No newline at end of file diff --git a/src/FreeCameraController.cpp b/src/FreeCameraController.cpp new file mode 100644 index 0000000..80a984d --- /dev/null +++ b/src/FreeCameraController.cpp @@ -0,0 +1,57 @@ +// +// Created by slinky on 5/11/26. +// + +#include "FreeCameraController.h" +#include "InputManager.h" + +void FreeCameraController::update(EditorContext &ctx) { + checkInput = ctx.sceneViewerFocused; + + if (!checkInput) return; + + if (InputManager::check_action_performed("move_forward")) { + const glm::vec3 translation = camera->front() * movement_speed; + camera->translate(translation); + } + + if (InputManager::check_action_performed("move_backward")) { + const glm::vec3 translation = camera->front() * -movement_speed; + camera->translate(translation); + } + + if (InputManager::check_action_performed("move_left")) { + const glm::vec3 translation = camera->right() * -movement_speed; + camera->translate(translation); + } + + if (InputManager::check_action_performed("move_right")) { + const glm::vec3 translation = camera->right() * movement_speed; + camera->translate(translation); + } + + if (InputManager::check_action_performed("move_up")) { + const glm::vec3 translation = camera->up() * movement_speed; + camera->translate(translation); + } + + if (InputManager::check_action_performed("move_down")) { + const glm::vec3 translation = camera->up() * -movement_speed; + camera->translate(translation); + } +} + +void FreeCameraController::on_mouse_delta(float xoff, float yoff) const { + if (!checkInput) return; + + camera->rotate_pitch(yoff * look_sensitivity); + camera->rotate_yaw(xoff * look_sensitivity); +} + +void FreeCameraController::set_camera(Camera& c) { + camera = &c; +} + +void FreeCameraController::release_camera() { + camera = nullptr; +} diff --git a/src/FreeCameraController.h b/src/FreeCameraController.h new file mode 100644 index 0000000..f722acb --- /dev/null +++ b/src/FreeCameraController.h @@ -0,0 +1,30 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_FREECAMERACONTROLLER_H +#define B_ENGINE_FREECAMERACONTROLLER_H + +#include "Camera.h" +#include "EditorContext.h" + +class FreeCameraController { +public: + FreeCameraController() = default; + + void update(EditorContext& ctx); + void on_mouse_delta(float xoff, float yoff) const; + + void set_camera(Camera& c); + void release_camera(); + + float movement_speed = .01; + float look_sensitivity = .01; +private: + Camera* camera {nullptr}; + + bool checkInput = false; +}; + + +#endif //B_ENGINE_FREECAMERACONTROLLER_H diff --git a/src/Scene.cpp b/src/Scene.cpp index 437f8bc..9cd5f41 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -18,6 +18,10 @@ entt::entity Scene::create_game_object() { } entt::entity Scene::create_game_object(entt::entity parent) { + if (parent == entt::null) { + parent = root; + } + const entt::entity entity = _registry.create(); attach_component(entity); attach_component(entity); @@ -30,10 +34,72 @@ entt::entity Scene::create_game_object(entt::entity parent) { } void Scene::update_transforms() { - + std::vector children = get_children(root); + for (auto child: children) { + update_transforms(child); + } } -void Scene::draw_scene(const ShaderProgram *program) { +void Scene::update_transforms(entt::entity entity) { + auto& transform = fetch_component(entity); + + if (!transform.dirty) + return; + + glm::mat4 translation = + glm::translate(glm::mat4(1.0f), transform.position); + + glm::mat4 rotationX = + glm::rotate(glm::mat4(1.0f), transform.rotation.x, glm::vec3(1,0,0)); + + glm::mat4 rotationY = + glm::rotate(glm::mat4(1.0f), transform.rotation.y, glm::vec3(0,1,0)); + + glm::mat4 rotationZ = + glm::rotate(glm::mat4(1.0f), transform.rotation.z, glm::vec3(0,0,1)); + + glm::mat4 scaling = + glm::scale(glm::mat4(1.0f), transform.scale); + + glm::mat4 localModel = + translation * + rotationZ * + rotationY * + rotationX * + scaling; + + auto& rel = fetch_component(entity); + if (rel.parent != entt::null) { + auto& parentTransform = fetch_component(rel.parent); + transform.model = parentTransform.model * localModel; + } else { + transform.model = localModel; + } + + transform.dirty = false; + + std::vector children = get_children(entity); + for (auto child: children) { + update_transforms(child); + } +} + +void Scene::draw_scene(ShaderProgram *program) { + program->bind(); + program->setMat4("projection", activeCamera->projection()); + program->setMat4("view", activeCamera->view()); + program->setVec3("viewPosition", activeCamera->position()); + + auto dirLights = _registry.view(); + for (const auto e : dirLights) { + auto dirLight = dirLights.get(e); + program->setVec3("lightDirection", dirLight.direction); + program->setVec3("lightAmbient", dirLight.ambient); + program->setVec3("lightDiffuse", dirLight.diffuse); + program->setVec3("lightSpecular", dirLight.specular); + break; + } + auto view = _registry.view(); for (const auto e : view) { const auto& transform = view.get(e); @@ -108,3 +174,7 @@ void Scene::add_child(entt::entity parent, entt::entity child) { childRelationship.next_sibling = entt::null; childRelationship.parent = parent; } + +void Scene::set_active_camera(Camera *camera) { + activeCamera = camera; +} diff --git a/src/Scene.h b/src/Scene.h index 2d85bd9..7f92772 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -5,6 +5,7 @@ #ifndef B_ENGINE_SCENE_H #define B_ENGINE_SCENE_H +#include "Camera.h" #include "ShaderProgram.h" #include "entt/entt.hpp" @@ -31,16 +32,26 @@ public: return _registry.get(e); } + template + bool has_component(entt::entity e) { + return _registry.all_of(e); + } + void update_transforms(); - void draw_scene(const ShaderProgram* program); + void draw_scene(ShaderProgram* program); [[nodiscard]] entt::entity get_root() const; [[nodiscard]] std::vector get_children(entt::entity parent); + + void set_active_camera(Camera *camera); private: entt::registry _registry{}; entt::entity root{entt::null}; void add_child(entt::entity parent, entt::entity child); + void update_transforms(entt::entity parent); + + Camera* activeCamera {nullptr}; }; #endif //B_ENGINE_SCENE_H \ No newline at end of file diff --git a/src/UIManager.cpp b/src/UIManager.cpp new file mode 100644 index 0000000..b81d6e7 --- /dev/null +++ b/src/UIManager.cpp @@ -0,0 +1,88 @@ +// +// Created by slinky on 5/11/26. +// + +#include "UIManager.h" + +#include + +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#include "imgui_internal.h" + +std::unordered_map> UIManager::uiPanels {}; +GLFWwindow* UIManager::window {nullptr}; +bool UIManager::updateDockspace {true}; + +void UIManager::init(GLFWwindow* _window) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ImGui::StyleColorsDark(); + ImGui_ImplGlfw_InitForOpenGL(_window, true); + ImGui_ImplOpenGL3_Init("#version 400"); + + window = _window; +} + +void UIManager::update(EditorContext& ctx) { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + + ImGui::NewFrame(); + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + 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; + + ImGui::Begin("b_engine dockspace", nullptr, window_flags); + ImGui::PopStyleVar(2); + + ImGuiID dockspace_id = ImGui::GetID("bengine_dockspace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + + if (updateDockspace) { + updateDockspace = false; + + ImGui::DockBuilderRemoveNode(dockspace_id); + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); + + ImGuiID dock_main_id = dockspace_id; + + const ImGuiID dock_id_left = + ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.2f, nullptr, &dock_main_id); + const ImGuiID dock_id_right = + ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Right, 0.25f, nullptr, &dock_main_id); + + ImGui::DockBuilderDockWindow("Hierarchy", dock_id_left); + ImGui::DockBuilderDockWindow("Inspector", dock_id_right); + ImGui::DockBuilderDockWindow("Scene", dock_main_id); + + ImGui::DockBuilderFinish(dockspace_id); + } + + for (const auto &panel: uiPanels | std::views::values) { + if (!panel) continue; + panel->update(ctx); + } + + ImGui::End(); // dockspace + } + ImGui::Render(); +} + +void UIManager::draw() { + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} \ No newline at end of file diff --git a/src/UIManager.h b/src/UIManager.h new file mode 100644 index 0000000..b2fe561 --- /dev/null +++ b/src/UIManager.h @@ -0,0 +1,42 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_UIMANAGER_H +#define B_ENGINE_UIMANAGER_H + +#include +#include +#include +#include + +#include "EditorContext.h" +#include "GLFW/glfw3.h" +#include "ui/IUIPanel.h" + +class UIManager { +public: + static void init(GLFWwindow* _window); + static void update(EditorContext& ctx); + static void draw(); + + template + static T* add_ui_panel(std::string_view id, Args &&... args) { + uiPanels[std::string(id)] = std::make_shared(std::forward(args)...); + return dynamic_cast(uiPanels[std::string(id)].get()); + } + + static std::shared_ptr get_ui_panel(std::string_view id) { + if (const auto it = uiPanels.find(std::string(id)); it != uiPanels.end()) { + return it->second; + } + return nullptr; + } +private: + static bool updateDockspace; + static GLFWwindow* window; + static std::unordered_map> uiPanels; +}; + + +#endif //B_ENGINE_UIMANAGER_H diff --git a/src/main.cpp b/src/main.cpp index b63bfb8..910fca3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,29 +5,26 @@ #include "glm/glm.hpp" #include "glm/ext/matrix_clip_space.hpp" -#include "glm/ext/matrix_transform.hpp" - -#include "entt/entt.hpp" #define STB_IMAGE_IMPLEMENTATION -#include "Components.h" -#include "FreeCamera.h" -#include "imgui.h" -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" -#include "imgui_internal.h" -#include "InputManager.h" #include "stb_image.h" -#include "Model.h" +#include "Camera.h" +#include "Components.h" +#include "EditorContext.h" +#include "FreeCameraController.h" +#include "InputManager.h" #include "ModelManager.h" #include "Scene.h" #include "ShaderManager.h" #include "ShaderProgram.h" -#include "Texture.h" #include "TextureManager.h" -#include "fmt/base.h" +#include "UIManager.h" +#include "ui/UIEntityInspector.h" +#include "ui/UIMenuBar.h" +#include "ui/UISceneGraph.h" +#include "ui/UISceneViewer.h" GLFWwindow* gWindow = nullptr; int gWindowWidth = 1920; @@ -40,11 +37,10 @@ float gAspectRatio = 0.f; float gMouseSensitivity = 0.1f; -FreeCamera gCamera {}; - +Camera gCamera {}; Scene gScene{}; - -bool gUiFirstRender = true; +EditorContext gEditorCtx{}; +FreeCameraController gCameraController{}; void glfw_error_callback(int error, const char* description); void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); @@ -53,14 +49,15 @@ void glfw_framebuffer_size_callback(GLFWwindow* window, int width, int height); void init_glfw(); void create_main_window(); void load_gl(); -void load_imgui(); void init_camera(); +void init_scene(); void load_default_textures(); void load_shaders(); void load_inputs(); void load_default_models(); void loop(); -void draw_ui(); + +void create_entity(entt::entity parent); auto xAxis = glm::vec3{1.f, 0.f, 0.f}; auto yAxis = glm::vec3{0.f, 1.f, 0.f}; @@ -75,10 +72,6 @@ int main() { load_gl(); - load_imgui(); - - init_camera(); - stbi_set_flip_vertically_on_load(true); load_default_textures(); @@ -89,15 +82,26 @@ int main() { load_default_models(); - entt::entity dirLight = gScene.create_game_object(); - auto& [direction, ambient, diffuse, specular] = gScene.attach_component(dirLight); - direction = {1, -1, 1}; - ambient = {0.3f, 0.3f, 0.3f}; - diffuse = {0.5f, 0.5f, 0.5f}; - specular = {1.0f, 1.0f, 1.0f}; + UIManager::init(gWindow); + UIMenuBar* menuBar = UIManager::add_ui_panel("menu_bar"); + menuBar->create_entity = create_entity; + UIManager::add_ui_panel("scene_viewer"); + UIManager::add_ui_panel("scene_graph"); + UIManager::add_ui_panel("entity_inspector"); - auto& tag = gScene.fetch_component(dirLight); - tag.name = "directional light"; + init_camera(); + init_scene(); + + gCameraController.set_camera(gCamera); + gCameraController.look_sensitivity = 0.2; + gCameraController.movement_speed = 0.2; + InputManager::add_mouse_listener([](float xoff, float yoff) { + gCameraController.on_mouse_delta(xoff, yoff); + }); + + gEditorCtx.scene = &gScene; + gEditorCtx.window = gWindow; + gEditorCtx.selectedEntity = entt::null; loop(); @@ -174,27 +178,23 @@ void load_gl() glEnable(GL_MULTISAMPLE); } -void load_imgui() { - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(gWindow, true); - ImGui_ImplOpenGL3_Init("#version 330"); -} - void init_camera() { - gCamera = {{5.f, 5.f, 5.f}, 0, 0, gAspectRatio}; + gCamera = {{10.f, 10.f, 10.f}, 0, 0, gAspectRatio}; gCamera.rotate_yaw(-135.f); gCamera.rotate_pitch(-35.f); +} - InputManager::add_mouse_listener([](float xoff, float yoff) { - gCamera.rotate_yaw(xoff * gMouseSensitivity); - gCamera.rotate_pitch(yoff * gMouseSensitivity); - }); +void init_scene() { + gScene.set_active_camera(&gCamera); + + const auto defaultLight = gScene.create_game_object(); + auto& [name] = gScene.fetch_component(defaultLight); + name = "Directional Light"; + auto& [direction, ambient, diffuse, specular] = gScene.attach_component(defaultLight); + direction = glm::vec3(-1, -1, -1); + ambient = glm::vec3(0.3); + diffuse = glm::vec3(0.7); + specular = glm::vec3(1.0); } void load_default_textures() { @@ -224,7 +224,6 @@ void load_inputs() { InputManager::generate_input_action("move_left", {{GLFW_KEY_A, KEY_DOWN}}); InputManager::generate_input_action("move_up", {{GLFW_KEY_SPACE, KEY_DOWN}}); InputManager::generate_input_action("move_down", {{GLFW_KEY_LEFT_SHIFT, KEY_DOWN}}); - InputManager::generate_input_action("exit_application", {{GLFW_KEY_ESCAPE, KEY_DOWN}}); } void load_default_models() { @@ -236,147 +235,47 @@ void load_default_models() { void loop() { while (!glfwWindowShouldClose(gWindow)) { + // input glfwPollEvents(); - // check inputs - /*if (InputManager::check_action_performed("exit_application")) { - glfwSetWindowShouldClose(gWindow, true); - continue; - }*/ + // update + UIManager::update(gEditorCtx); + gCameraController.update(gEditorCtx); - if (InputManager::check_action_performed("move_forward")) { - gCamera.move(FORWARD, .05); + if (gCamera.aspect_ratio() != gEditorCtx.viewportAspectRatio) { + gCamera.update_aspect_ratio(gEditorCtx.viewportAspectRatio); } - if (InputManager::check_action_performed("move_backward")) { - gCamera.move(BACKWARD, .05); + gScene.update_transforms(); + + const auto* uiScenePanel = dynamic_cast(UIManager::get_ui_panel("scene_viewer").get()); + if (uiScenePanel) { + uiScenePanel->bind_fbo(); } - if (InputManager::check_action_performed("move_left")) { - gCamera.move(LEFT, .05); - } - - if (InputManager::check_action_performed("move_right")) { - gCamera.move(RIGHT, .05); - } - - if (InputManager::check_action_performed("move_up")) { - gCamera.move(UP, .05); - } - - if (InputManager::check_action_performed("move_down")) { - gCamera.move(DOWN, .05); - } - - draw_ui(); - - // gl frame prep + // draw glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - const auto shader = ShaderManager::shaders["phong_shader"]; - shader->bind(); - shader->setMat4("projection", gCamera.projection()); - shader->setMat4("view", gCamera.view()); - shader->setVec3("viewPosition", gCamera.position()); - gScene.draw_scene(shader.get()); glBindVertexArray(0); ShaderProgram::unbind(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); - // gl end frame stuff + UIManager::draw(); + + // end frame glfwSwapBuffers(gWindow); } } -void draw_ui() { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - 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; - - ImGui::Begin("b_engine dockspace", nullptr, window_flags); - ImGui::PopStyleVar(2); - - ImGuiID dockspace_id = ImGui::GetID("bengine_dockspace"); - ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); - - if (gUiFirstRender) { - gUiFirstRender = false; - - ImGui::DockBuilderRemoveNode(dockspace_id); // Clear any previous layout - ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); - ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size); - - ImGuiID dock_main_id = dockspace_id; // The center remains for the game view - - const ImGuiID dock_id_left = - ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.2f, nullptr, &dock_main_id); - const ImGuiID dock_id_right = - ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Right, 0.25f, nullptr, &dock_main_id); - - // Assign your windows to these specific dock IDs - ImGui::DockBuilderDockWindow("Hierarchy", dock_id_left); - ImGui::DockBuilderDockWindow("Inspector", dock_id_right); - ImGui::DockBuilderDockWindow("Scene", dock_main_id); - - ImGui::DockBuilderFinish(dockspace_id); - } - - ImGui::Begin("Hierarchy"); - ImGui::Text("List of GameObjects..."); - ImGui::End(); - - ImGui::Begin("Inspector"); - ImGui::Text("Component Properties..."); - ImGui::End(); - - ImGui::Begin("Scene"); - ImGui::Text("This is where your 3D view goes!"); - ImGui::End(); - - if (ImGui::BeginMainMenuBar()) { - if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("New Scene")) { - /* Handle New File logic */ - } - if (ImGui::MenuItem("Open Scene")) { - /* Handle Open logic */ - } - ImGui::Separator(); // Adds a visual line between sections - if (ImGui::MenuItem("Save", "Ctrl+S")) { - /* Handle Save logic */ - } - if (ImGui::MenuItem("Exit", "Alt+F4")) { - glfwSetWindowShouldClose(gWindow, true); - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Scene")) { - if (ImGui::MenuItem("New Scene Object")) { - gScene.create_game_object(); - // Do other stuff here, open this entity in the entity editor, etc... - } - ImGui::EndMenu(); - } - ImGui::EndMainMenuBar(); - } - - ImGui::End(); - } - ImGui::Render(); -} \ No newline at end of file +void create_entity(entt::entity parent) { + auto entity = gScene.create_game_object(parent); + auto& drawable = gScene.attach_component(entity); + drawable.model = ModelManager::models["vette"]; + auto& transform = gScene.fetch_component(entity); + transform.scale = {0.1, 0.1, 0.1}; + transform.dirty = true; +} diff --git a/src/ui/IUIPanel.h b/src/ui/IUIPanel.h new file mode 100644 index 0000000..6aad418 --- /dev/null +++ b/src/ui/IUIPanel.h @@ -0,0 +1,18 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_IUIPANEL_H +#define B_ENGINE_IUIPANEL_H + +#include "EditorContext.h" + +class IUIPanel { +public: + virtual ~IUIPanel() = default; + virtual void update(EditorContext& context) = 0; + bool IsOpen = true; + bool HasFocus = true; +}; + +#endif //B_ENGINE_IUIPANEL_H diff --git a/src/ui/UIEntityInspector.cpp b/src/ui/UIEntityInspector.cpp new file mode 100644 index 0000000..ca55e9f --- /dev/null +++ b/src/ui/UIEntityInspector.cpp @@ -0,0 +1,63 @@ +// +// Created by slinky on 5/11/26. +// + +#include "UIEntityInspector.h" + +#include "Components.h" +#include "imgui.h" +#include "glm/gtc/type_ptr.hpp" + +void UIEntityInspector::update(EditorContext &ctx) { + ImGui::Begin("Inspector"); + + if (ctx.selectedEntity == entt::null) + { + ImGui::TextDisabled("No entity selected"); + ImGui::End(); + return; + } + + Scene* scene = ctx.scene; + auto entity = ctx.selectedEntity; + + if (scene->has_component(entity)) + { + auto& [name] = scene->fetch_component(entity); + + char buffer[256]{}; + memset(buffer, 0, sizeof(buffer)); + + strncpy(buffer, name.c_str(), sizeof(buffer) - 1); + + if (ImGui::InputText("Tag", buffer, sizeof(buffer))) + { + name = std::string(buffer); + } + } + + ImGui::Separator(); + + if (scene->has_component(entity)) + { + auto& transform = scene->fetch_component(entity); + ImGui::Text("Transform"); + + if (ImGui::DragFloat3("Position", glm::value_ptr(transform.position), 0.1f)) + { + transform.dirty = true; + } + + if (ImGui::DragFloat3("Rotation", glm::value_ptr(transform.rotation), 0.05f)) + { + transform.dirty = true; + } + + if (ImGui::DragFloat3("Scale", glm::value_ptr(transform.scale), 0.1f)) + { + transform.dirty = true; + } + } + + ImGui::End(); +} diff --git a/src/ui/UIEntityInspector.h b/src/ui/UIEntityInspector.h new file mode 100644 index 0000000..18dae4e --- /dev/null +++ b/src/ui/UIEntityInspector.h @@ -0,0 +1,17 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_UIENTITYINSPECTOR_H +#define B_ENGINE_UIENTITYINSPECTOR_H + +#include "EditorContext.h" +#include "IUIPanel.h" + +class UIEntityInspector : public IUIPanel { +public: + void update(EditorContext& ctx) override; +}; + + +#endif //B_ENGINE_UIENTITYINSPECTOR_H diff --git a/src/ui/UIMenuBar.cpp b/src/ui/UIMenuBar.cpp new file mode 100644 index 0000000..f888d41 --- /dev/null +++ b/src/ui/UIMenuBar.cpp @@ -0,0 +1,37 @@ +// +// Created by slinky on 5/11/26. +// + +#include "UIMenuBar.h" +#include "UIManager.h" + +#include "imgui.h" +#include "UISceneGraph.h" + +void UIMenuBar::update(EditorContext &context) { + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("New Scene")) { + } + if (ImGui::MenuItem("Open Scene")) { + } + ImGui::Separator(); // Adds a visual line between sections + if (ImGui::MenuItem("Save", "Ctrl+S")) { + } + if (ImGui::MenuItem("Exit", "Alt+F4")) { + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Scene")) { + if (ImGui::MenuItem("New Scene Object")) { + const auto* sceneGraph = dynamic_cast(UIManager::get_ui_panel("scene_graph").get()); + if (sceneGraph) { + create_entity(context.selectedEntity); + } + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} diff --git a/src/ui/UIMenuBar.h b/src/ui/UIMenuBar.h new file mode 100644 index 0000000..f449e75 --- /dev/null +++ b/src/ui/UIMenuBar.h @@ -0,0 +1,18 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_UIMENUBAR_H +#define B_ENGINE_UIMENUBAR_H + +#include "IUIPanel.h" + +class UIMenuBar : public IUIPanel { +public: + void update(EditorContext &context) override; + + std::function create_entity; +}; + + +#endif //B_ENGINE_UIMENUBAR_H diff --git a/src/ui/UISceneGraph.cpp b/src/ui/UISceneGraph.cpp new file mode 100644 index 0000000..baf76ea --- /dev/null +++ b/src/ui/UISceneGraph.cpp @@ -0,0 +1,53 @@ +// +// Created by slinky on 5/11/26. +// + +#include "UISceneGraph.h" + +#include "Components.h" +#include "imgui.h" +#include "Scene.h" + +void UISceneGraph::update(EditorContext &ctx) { + ImGui::Begin("Hierarchy"); + + if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + ctx.selectedEntity = entt::null; + } + + Scene* scene = ctx.scene; + if (!scene) return; + + draw_children(scene->get_root(), scene, ctx); + + ImGui::End(); +} + +void UISceneGraph::draw_children(entt::entity parent, Scene *scene, EditorContext& ctx) { + std::vector children = scene->get_children(parent); + for (const auto e : children) { + const auto& rel = scene->fetch_component(e); + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_SpanFullWidth; + + if (rel.first_child == entt::null) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + + const auto& [name] = scene->fetch_component(e); + if (ImGui::TreeNodeEx((void*)(intptr_t)e, flags, "%s", name.data())) + { + draw_children(e, scene, ctx); + ImGui::TreePop(); + } + + if (ImGui::IsItemClicked()) + { + ctx.selectedEntity = e; + } + } +} + + diff --git a/src/ui/UISceneGraph.h b/src/ui/UISceneGraph.h new file mode 100644 index 0000000..1974b6b --- /dev/null +++ b/src/ui/UISceneGraph.h @@ -0,0 +1,20 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_UISCENEGRAPH_H +#define B_ENGINE_UISCENEGRAPH_H + +#include "IUIPanel.h" +#include "Scene.h" + +#include "entt/entt.hpp" + +class UISceneGraph : public IUIPanel { +public: + void update(EditorContext& ctx) override; +private: + void draw_children(entt::entity e, Scene* scene, EditorContext& ctx); +}; + +#endif //B_ENGINE_UISCENEGRAPH_H diff --git a/src/ui/UISceneViewer.cpp b/src/ui/UISceneViewer.cpp new file mode 100644 index 0000000..264a253 --- /dev/null +++ b/src/ui/UISceneViewer.cpp @@ -0,0 +1,88 @@ +// +// Created by slinky on 5/11/26. +// + +#include "UISceneViewer.h" + +#include "GLFW/glfw3.h" + +UISceneViewer::UISceneViewer() { + create_scene_framebuffer(); + resize_scene_framebuffer(1, 1); +} + +void UISceneViewer::update(EditorContext& ctx) { + ImGui::Begin("Scene"); + + const ImVec2 viewportSize = ImGui::GetContentRegionAvail(); + if (viewportSize.x > 0.0f && viewportSize.y > 0.0f) + { + if (sceneViewportSize.x != viewportSize.x || + sceneViewportSize.y != viewportSize.y) + { + sceneViewportSize = viewportSize; + resize_scene_framebuffer((int)sceneViewportSize.x, (int)sceneViewportSize.y); + + ctx.viewportSize = {sceneViewportSize.x, sceneViewportSize.y}; + ctx.viewportAspectRatio = sceneViewportSize.x / sceneViewportSize.y; + } + + ImGui::Image( + sceneRenderTexture, + viewportSize, + ImVec2(0, 1), + ImVec2(1, 0) + ); + + bool hovered = ImGui::IsItemHovered(); + if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + capturedMouse = true; + ctx.sceneViewerFocused = true; + + glfwSetInputMode(ctx.window, + GLFW_CURSOR, + GLFW_CURSOR_DISABLED); + } + + if (capturedMouse && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) + { + capturedMouse = false; + ctx.sceneViewerFocused = false; + + glfwSetInputMode(ctx.window, + GLFW_CURSOR, + GLFW_CURSOR_NORMAL); + } + } + ImGui::End(); +} + +void UISceneViewer::bind_fbo() const { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); +} + +void UISceneViewer::create_scene_framebuffer() { + glGenFramebuffers(1, &fbo); + resize_scene_framebuffer(1, 1); +} + +void UISceneViewer::resize_scene_framebuffer(int width, int height) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glGenTextures(1, &sceneRenderTexture); + glBindTexture(GL_TEXTURE_2D, sceneRenderTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneRenderTexture, 0); + + glGenRenderbuffers(1, &rbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); +} diff --git a/src/ui/UISceneViewer.h b/src/ui/UISceneViewer.h new file mode 100644 index 0000000..f8c7f62 --- /dev/null +++ b/src/ui/UISceneViewer.h @@ -0,0 +1,38 @@ +// +// Created by slinky on 5/11/26. +// + +#ifndef B_ENGINE_UISCENEVIEWER_H +#define B_ENGINE_UISCENEVIEWER_H + +#include "glad/gl.h" +#include "imgui.h" +#include "IUIPanel.h" + +#include "Scene.h" +#include "Camera.h" + +class UISceneViewer : public IUIPanel { +public: + UISceneViewer(); + + void update(EditorContext& ctx) override; + + void bind_fbo() const; +private: + ImVec2 sceneViewportSize = { 0.0f, 0.0f }; + GLuint sceneRenderTexture = 0; + GLuint fbo = 0; + GLuint rbo = 0; + + Scene* scene = nullptr; + Camera* camera = nullptr; + + bool capturedMouse = false; + + void create_scene_framebuffer(); + void resize_scene_framebuffer(int width, int height); +}; + + +#endif //B_ENGINE_UISCENEVIEWER_H