entity editor, scene graph viewer, scene viewer basics done. Can rotate, translate and scale selected models.

This commit is contained in:
2026-05-11 22:24:47 -05:00
parent 5d78fd672e
commit cf916951f0
22 changed files with 864 additions and 291 deletions

View File

@@ -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)

93
src/Camera.cpp Normal file
View File

@@ -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);
}

View File

@@ -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{};

View File

@@ -17,6 +17,7 @@ namespace Components {
glm::vec3 rotation{};
glm::vec3 scale{};
glm::mat4 model = glm::identity<glm::mat4>();
bool dirty {true};
};
struct Relationship {

21
src/EditorContext.h Normal file
View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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<Components::Transform>(entity);
attach_component<Components::Relationship>(entity);
@@ -30,10 +34,72 @@ entt::entity Scene::create_game_object(entt::entity parent) {
}
void Scene::update_transforms() {
std::vector<entt::entity> 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<Components::Transform>(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<Components::Relationship>(entity);
if (rel.parent != entt::null) {
auto& parentTransform = fetch_component<Components::Transform>(rel.parent);
transform.model = parentTransform.model * localModel;
} else {
transform.model = localModel;
}
transform.dirty = false;
std::vector<entt::entity> 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<Components::DirectionalLight>();
for (const auto e : dirLights) {
auto dirLight = dirLights.get<Components::DirectionalLight>(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<Components::Transform, Components::Drawable>();
for (const auto e : view) {
const auto& transform = view.get<Components::Transform>(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;
}

View File

@@ -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<T>(e);
}
template<typename T>
bool has_component(entt::entity e) {
return _registry.all_of<T>(e);
}
void update_transforms();
void draw_scene(const ShaderProgram* program);
void draw_scene(ShaderProgram* program);
[[nodiscard]] entt::entity get_root() const;
[[nodiscard]] std::vector<entt::entity> 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

88
src/UIManager.cpp Normal file
View File

@@ -0,0 +1,88 @@
//
// Created by slinky on 5/11/26.
//
#include "UIManager.h"
#include <ranges>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "imgui_internal.h"
std::unordered_map<std::string, std::shared_ptr<IUIPanel>> 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());
}

42
src/UIManager.h Normal file
View File

@@ -0,0 +1,42 @@
//
// Created by slinky on 5/11/26.
//
#ifndef B_ENGINE_UIMANAGER_H
#define B_ENGINE_UIMANAGER_H
#include <vector>
#include <memory>
#include <unordered_map>
#include <functional>
#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<typename T = IUIPanel, typename... Args>
static T* add_ui_panel(std::string_view id, Args &&... args) {
uiPanels[std::string(id)] = std::make_shared<T>(std::forward<Args>(args)...);
return dynamic_cast<T*>(uiPanels[std::string(id)].get());
}
static std::shared_ptr<IUIPanel> 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<std::string, std::shared_ptr<IUIPanel>> uiPanels;
};
#endif //B_ENGINE_UIMANAGER_H

View File

@@ -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<Components::DirectionalLight>(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<UIMenuBar>("menu_bar");
menuBar->create_entity = create_entity;
UIManager::add_ui_panel<UISceneViewer>("scene_viewer");
UIManager::add_ui_panel<UISceneGraph>("scene_graph");
UIManager::add_ui_panel<UIEntityInspector>("entity_inspector");
auto& tag = gScene.fetch_component<Components::Tag>(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<Components::Tag>(defaultLight);
name = "Directional Light";
auto& [direction, ambient, diffuse, specular] = gScene.attach_component<Components::DirectionalLight>(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<UISceneViewer*>(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();
void create_entity(entt::entity parent) {
auto entity = gScene.create_game_object(parent);
auto& drawable = gScene.attach_component<Components::Drawable>(entity);
drawable.model = ModelManager::models["vette"];
auto& transform = gScene.fetch_component<Components::Transform>(entity);
transform.scale = {0.1, 0.1, 0.1};
transform.dirty = true;
}

18
src/ui/IUIPanel.h Normal file
View File

@@ -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

View File

@@ -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<Components::Tag>(entity))
{
auto& [name] = scene->fetch_component<Components::Tag>(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<Components::Transform>(entity))
{
auto& transform = scene->fetch_component<Components::Transform>(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();
}

View File

@@ -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

37
src/ui/UIMenuBar.cpp Normal file
View File

@@ -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<UISceneGraph*>(UIManager::get_ui_panel("scene_graph").get());
if (sceneGraph) {
create_entity(context.selectedEntity);
}
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
}

18
src/ui/UIMenuBar.h Normal file
View File

@@ -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<void(entt::entity)> create_entity;
};
#endif //B_ENGINE_UIMENUBAR_H

53
src/ui/UISceneGraph.cpp Normal file
View File

@@ -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<entt::entity> children = scene->get_children(parent);
for (const auto e : children) {
const auto& rel = scene->fetch_component<Components::Relationship>(e);
ImGuiTreeNodeFlags flags =
ImGuiTreeNodeFlags_OpenOnArrow |
ImGuiTreeNodeFlags_SpanFullWidth;
if (rel.first_child == entt::null) {
flags |= ImGuiTreeNodeFlags_Leaf;
}
const auto& [name] = scene->fetch_component<Components::Tag>(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;
}
}
}

20
src/ui/UISceneGraph.h Normal file
View File

@@ -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

88
src/ui/UISceneViewer.cpp Normal file
View File

@@ -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);
}

38
src/ui/UISceneViewer.h Normal file
View File

@@ -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