From 3c756d6230be663889906e8cdb2b6de54b72f58b Mon Sep 17 00:00:00 2001 From: slinky55 Date: Mon, 4 May 2026 00:41:34 -0400 Subject: [PATCH] Huge stuff. camera, input manager (mouse + keyboard), can move around scene now --- CMakeLists.txt | 24 +- resources/backpack/backpack.mtl | 6 +- resources/standard.frag | 19 +- src/FreeCamera.cpp | 99 +++++++++ src/FreeCamera.h | 61 ++++++ src/InputManager.cpp | 118 ++++++++++ src/InputManager.h | 61 ++++++ src/Material.cpp | 5 - src/Mesh.cpp | 1 - src/Mesh.h | 7 +- src/Model.h | 6 +- src/{ModelLoader.cpp => ModelManager.cpp} | 110 ++++++++-- src/{ModelLoader.h => ModelManager.h} | 2 +- src/Texture.h | 3 + src/{TextureLoader.cpp => TextureManager.cpp} | 8 +- src/{TextureLoader.h => TextureManager.h} | 8 +- src/main.cpp | 205 ++++++++++++------ 17 files changed, 624 insertions(+), 119 deletions(-) create mode 100644 src/FreeCamera.cpp create mode 100644 src/FreeCamera.h create mode 100644 src/InputManager.cpp create mode 100644 src/InputManager.h delete mode 100644 src/Material.cpp rename src/{ModelLoader.cpp => ModelManager.cpp} (58%) rename src/{ModelLoader.h => ModelManager.h} (87%) rename src/{TextureLoader.cpp => TextureManager.cpp} (87%) rename src/{TextureLoader.h => TextureManager.h} (75%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2204a9d..d406092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 4.1) -project(b_engine VERSION 0.0.1 LANGUAGES CXX C) +project(b_engine VERSION 0.0.2 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) @@ -26,17 +26,27 @@ add_executable(${PROJECT_NAME} src/Mesh.h src/Model.cpp src/Model.h - src/Material.cpp src/Material.h - src/ModelLoader.cpp - src/ModelLoader.h + src/ModelManager.cpp + src/ModelManager.h src/ShaderProgram.cpp src/ShaderProgram.h src/Texture.h - src/TextureLoader.cpp - src/TextureLoader.h + src/TextureManager.cpp + src/TextureManager.h src/ShaderManager.cpp - src/ShaderManager.h) + src/ShaderManager.h + src/FreeCamera.cpp + src/FreeCamera.h + src/InputManager.cpp + src/InputManager.h) + +add_custom_target(copy_resources + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/resources" + "${CMAKE_CURRENT_BINARY_DIR}/resources" + COMMENT "Copying resources..." +) target_link_libraries(${PROJECT_NAME} glfw spdlog assimp glm) target_include_directories(${PROJECT_NAME} PRIVATE ${ASSIMP_INCLUDE_INSTALL_DIR}) diff --git a/resources/backpack/backpack.mtl b/resources/backpack/backpack.mtl index c862707..f8b974c 100644 --- a/resources/backpack/backpack.mtl +++ b/resources/backpack/backpack.mtl @@ -10,7 +10,7 @@ Ke 0.0 0.0 0.0 Ni 1.450000 d 1.000000 illum 2 -map_Kd backpack/diffuse.jpg -map_Bump backpack/normal.png -map_Ks backpack/specular.jpg +map_Kd diffuse.jpg +map_Bump normal.png +map_Ks specular.jpg diff --git a/resources/standard.frag b/resources/standard.frag index 2c21908..6a2ae75 100644 --- a/resources/standard.frag +++ b/resources/standard.frag @@ -6,7 +6,8 @@ in vec3 Position; in vec2 TexCoord; in vec3 Normal; -uniform sampler2D diffuse; +uniform sampler2D diffuseMap; +uniform sampler2D specularMap; uniform vec3 viewPosition; @@ -15,22 +16,30 @@ uniform vec3 phongDiffuse; uniform vec3 phongSpecular; uniform float phongShininess; +uniform vec3 lightPosition; uniform vec3 lightDirection; uniform vec3 lightAmbient; uniform vec3 lightDiffuse; uniform vec3 lightSpecular; void main() { - vec4 diffuseColor = texture(diffuse, TexCoord); + vec3 diffuseColor = texture(diffuseMap, TexCoord).rgb; + vec3 specularColor = texture(specularMap, TexCoord).rgb; vec3 norm = normalize(Normal); vec3 lightDir = normalize(-lightDirection); + vec3 viewDir = normalize(viewPosition - Position); - vec3 ambient = phongAmbient * vec3(diffuseColor); + vec3 ambient = lightAmbient * (diffuseColor * phongAmbient); float diffImpact = max(dot(norm, lightDir), 0.0); - vec3 diffuse = (phongDiffuse * vec3(diffuseColor)) * diffImpact; + vec3 diffuse = lightDiffuse * (diffImpact * diffuseColor * phongDiffuse); - vec3 result = ambient + diffuse; + vec3 reflectDir = reflect(-lightDir, norm); + float effectiveShininess = max(phongShininess, 1.0); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), effectiveShininess); + vec3 specular = lightSpecular * (spec * specularColor * phongSpecular); + + vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); } \ No newline at end of file diff --git a/src/FreeCamera.cpp b/src/FreeCamera.cpp new file mode 100644 index 0000000..3c53669 --- /dev/null +++ b/src/FreeCamera.cpp @@ -0,0 +1,99 @@ +// +// 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/FreeCamera.h b/src/FreeCamera.h new file mode 100644 index 0000000..e521bb3 --- /dev/null +++ b/src/FreeCamera.h @@ -0,0 +1,61 @@ +// +// Created by slinky on 5/3/26. +// + +#ifndef B_ENGINE_CAMERA_H +#define B_ENGINE_CAMERA_H + +#include + +constexpr float MAX_PITCH = 89.f; +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 { +public: + FreeCamera() = default; + FreeCamera(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; + + void update_aspect_ratio(float aspectRatio); + void rotate_yaw(float offset); + void rotate_pitch(float offset); + + void move(CAMERA_MOVEMENT direction, float offset); +private: + void update_camera_vectors(); + void update_projection(); + void update_view(); + + glm::vec3 _position{}; + + glm::vec3 front{}; + glm::vec3 up{}; + glm::vec3 right{}; + + glm::mat4 _view{}; + glm::mat4 _projection{}; + + float _yaw = 0; + float _pitch = 0; + + float _aspectRatio = 0; + + float _sensitivity = 0.1f; +}; + + +#endif //B_ENGINE_CAMERA_H \ No newline at end of file diff --git a/src/InputManager.cpp b/src/InputManager.cpp new file mode 100644 index 0000000..f939529 --- /dev/null +++ b/src/InputManager.cpp @@ -0,0 +1,118 @@ +// +// Created by slinky on 5/3/26. +// + +#include "InputManager.h" + +bool InputManager::mouseInit = false; +float InputManager::lastMousePositionX = 0; +float InputManager::lastMousePositionY = 0; +std::unordered_map> InputManager::registeredActions = {}; + +static bool currentKeyStatesData[512] = {}; +bool* InputManager::currentKeyStates = currentKeyStatesData; + +static bool previousKeyStatesData[512] = {}; +bool* InputManager::previousKeyStates = previousKeyStatesData; + +std::vector InputManager::mouse_listeners = {}; + +void InputManager::mouse_pos_callback(GLFWwindow *window, double x, double y) { + const auto xpos = static_cast(x); + const auto ypos = static_cast(y); + + if (!mouseInit) { + lastMousePositionX = xpos; + lastMousePositionY = ypos; + mouseInit = true; + } + + float xOff = xpos - lastMousePositionX; + float yOff = lastMousePositionY - ypos; + + lastMousePositionX = xpos; + lastMousePositionY = ypos; + + for (const auto& cb : mouse_listeners) { + cb(xOff, yOff); + } +} + +void InputManager::add_mouse_listener(MouseDeltaCallback callback) { + mouse_listeners.push_back(callback); +} + +void InputManager::key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { + if (action == GLFW_REPEAT) return; + + set_key_state(key, action == GLFW_PRESS); +} + +void InputManager::generate_input_action(std::string_view actionName, std::initializer_list requirements) { + if (registeredActions.contains(actionName.data())) { + return; + } + + InputAction action; + for (const auto& requirement : requirements) { + action.requirements.push_back(requirement); + } + const std::string key = actionName.data(); + registeredActions[key] = std::make_unique(action); +} + +bool InputManager::check_action_performed(std::string_view actionName) { + if (!registeredActions.contains(actionName.data())) { + return false; + } + + InputAction* action = registeredActions[actionName.data()].get(); + + if (!action) { + return false; + } + + for (const auto&[key, state] : action->requirements) { + switch (state) { + case KEY_DOWN: + if (!key_down(key)) + return false; + break; + case KEY_HELD: + if (!key_held(key)) + return false; + break; + case KEY_PRESSED: + if (!key_pressed(key)) + return false; + break; + case KEY_RELEASED: + if (!key_released(key)) + return false; + break; + } + } + + return true; +} + +bool InputManager::key_down(int key) { + return currentKeyStatesData[key]; +} +bool InputManager::key_up(int key) { + return !currentKeyStatesData[key]; +} +bool InputManager::key_pressed(int key) { + return !previousKeyStatesData[key] && currentKeyStatesData[key]; +} +bool InputManager::key_released(int key) { + return previousKeyStatesData[key] && !currentKeyStatesData[key]; +} +bool InputManager::key_held(int key) { + return previousKeyStatesData[key] && currentKeyStatesData[key]; +} + +void InputManager::set_key_state(int key, bool state) { + previousKeyStatesData[key] = currentKeyStatesData[key]; + currentKeyStatesData[key] = state; +} \ No newline at end of file diff --git a/src/InputManager.h b/src/InputManager.h new file mode 100644 index 0000000..17411bb --- /dev/null +++ b/src/InputManager.h @@ -0,0 +1,61 @@ +// +// Created by slinky on 5/3/26. +// + +#ifndef B_ENGINE_INPUTMANAGER_H +#define B_ENGINE_INPUTMANAGER_H + +#include +#include +#include +#include + +#include "GLFW/glfw3.h" + +using MouseDeltaCallback = std::function; + +enum KeyState { + KEY_PRESSED, + KEY_HELD, + KEY_DOWN, + KEY_RELEASED, +}; + +struct InputRequirement { + int key; + KeyState state; +}; + +struct InputAction { + std::vector requirements{}; +}; + +class InputManager { +public: + static bool mouseInit; + static float lastMousePositionX; + static float lastMousePositionY; + + static bool* currentKeyStates; + static bool* previousKeyStates; + + static std::unordered_map> registeredActions; + + static void mouse_pos_callback(GLFWwindow* window, double x, double y); + static void add_mouse_listener(MouseDeltaCallback callback); + + static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); + static void generate_input_action(std::string_view actionName, std::initializer_list requirements); + static bool check_action_performed(std::string_view actionName); + + static bool key_pressed(int key); + static bool key_held(int key); + static bool key_released(int key); + static bool key_down(int key); + static bool key_up(int key); +private: + static std::vector mouse_listeners; + static void set_key_state(int key, bool state); +}; + +#endif //B_ENGINE_INPUTMANAGER_H \ No newline at end of file diff --git a/src/Material.cpp b/src/Material.cpp deleted file mode 100644 index 7deb5f1..0000000 --- a/src/Material.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by lbmas on 4/28/2026. -// - -#include "Material.h" \ No newline at end of file diff --git a/src/Mesh.cpp b/src/Mesh.cpp index b24b865..1f94b39 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -8,7 +8,6 @@ Mesh::Mesh(const std::vector &positions, const std::vector &uvs, c ebo = 0; numIndices = indices.size(); - // positions GLuint positionVbo = 0; glGenBuffers(1, &positionVbo); diff --git a/src/Mesh.h b/src/Mesh.h index b3e594d..8c93c79 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -18,9 +18,10 @@ public: const std::vector& normals, const std::vector& indices); - GLuint vao; - GLuint ebo; - unsigned int numIndices; + GLuint vao = 0; + GLuint ebo = 0; + unsigned int numIndices = 0; + unsigned int materialId = 0; }; #endif //B_ENGINE_MESH_H \ No newline at end of file diff --git a/src/Model.h b/src/Model.h index f7e151e..ef04474 100644 --- a/src/Model.h +++ b/src/Model.h @@ -7,15 +7,13 @@ #include #include +#include #include "Mesh.h" #include "Material.h" -class Model +struct Model { -public: - static std::shared_ptr load_from_file(std::string_view filename); - std::vector> meshes; std::vector> materials; }; diff --git a/src/ModelLoader.cpp b/src/ModelManager.cpp similarity index 58% rename from src/ModelLoader.cpp rename to src/ModelManager.cpp index 8727aa9..e7dda70 100644 --- a/src/ModelLoader.cpp +++ b/src/ModelManager.cpp @@ -4,7 +4,7 @@ #include -#include "ModelLoader.h" +#include "ModelManager.h" #include #include @@ -12,15 +12,17 @@ #include -#include "TextureLoader.h" +#include "TextureManager.h" #include "glm/ext/matrix_transform.hpp" -std::unordered_map> ModelLoader::models; +std::unordered_map> ModelManager::models; void process_ai_mesh(aiMesh* aiMesh, const aiScene* scene, glm::mat4 transform, Mesh& mesh); void process_ai_node(aiNode* node, const aiScene* scene, glm::mat4 transform, Model* model); -void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat); +void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat, const std::filesystem::path& modelDirectory); +void process_ai_material_diffuse(const aiMaterial *aiMat, Material& mat, const std::filesystem::path& modelDirectory); +void process_ai_material_specular(const aiMaterial *aiMat, Material& mat, const std::filesystem::path& modelDirectory); auto zUpMatrix = glm::mat4( 1.0f, 0.0f, 0.0f, 0.0f, // Column 0 @@ -29,7 +31,7 @@ auto zUpMatrix = glm::mat4( 0.0f, 0.0f, 0.0f, 1.0f // Column 3 ); -std::shared_ptr ModelLoader::load_from_file(std::string_view _path, bool zUp) +std::shared_ptr ModelManager::load_from_file(std::string_view _path, bool zUp) { Assimp::Importer importer; @@ -46,6 +48,16 @@ std::shared_ptr ModelLoader::load_from_file(std::string_view _path, bool auto model = std::make_shared(); + std::filesystem::path modelPath = {_path}; + + model->materials.resize(scene->mNumMaterials); + for (unsigned int i = 0; i < scene->mNumMaterials; i++) { + aiMaterial* aiMat = scene->mMaterials[i]; + Material mat{}; + process_ai_material(aiMat, scene, mat, modelPath.parent_path()); + model->materials[i] = std::make_shared(mat); + } + auto transform = glm::identity(); if (zUp) { transform = zUpMatrix; @@ -58,20 +70,16 @@ std::shared_ptr ModelLoader::load_from_file(std::string_view _path, bool void process_ai_node(aiNode* node, const aiScene* scene, glm::mat4 transform, Model* model) { - auto m = node->mTransformation; - transform = transform * glm::mat4(m.a1, m.a2, m.a3, m.a4, m.b1, m.b2, m.b3, m.b4, m.c1, m.c2, m.c3, m.c4, m.d1, m.d2, m.d3, m.d4); + const auto t = node->mTransformation; + transform = transform * glm::mat4(t.a1, t.a2, t.a3, t.a4, t.b1, t.b2, t.b3, t.b4, t.c1, t.c2, t.c3, t.c4, t.d1, t.d2, t.d3, t.d4); for (unsigned int i = 0; i < node->mNumMeshes; i++) { aiMesh* aiMesh = scene->mMeshes[node->mMeshes[i]]; Mesh mesh{}; + mesh.materialId = aiMesh->mMaterialIndex; process_ai_mesh(aiMesh, scene, transform, mesh); model->meshes.push_back(std::make_shared(mesh)); - - aiMaterial* aiMat = scene->mMaterials[aiMesh->mMaterialIndex]; - Material mat{}; - process_ai_material(aiMat, scene, mat); - model->materials.push_back(std::make_shared(mat)); } for (unsigned int i = 0; i < node->mNumChildren; i++) @@ -130,7 +138,7 @@ void process_ai_mesh(aiMesh* aiMesh, const aiScene* scene, glm::mat4 transform, mesh = {positions, uvs, normals, indices}; } -void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat) { +void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat, const std::filesystem::path& modelDirectory) { aiString matName; aiReturn ret = aiMat->Get(AI_MATKEY_NAME, matName); if (ret != AI_SUCCESS) { @@ -167,14 +175,8 @@ void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat) } // now grab the textures, first diffuse - for (unsigned int i = 0; i < aiMat->GetTextureCount(aiTextureType_DIFFUSE); i++) { - aiString textureName; - aiMat->GetTexture(aiTextureType_DIFFUSE, i, &textureName); - - if (!TextureLoader::textures.contains(textureName.C_Str())) { - TextureLoader::textures[textureName.C_Str()] = TextureLoader::load_from_file(textureName.C_Str()); - } - } + process_ai_material_diffuse(aiMat, mat, modelDirectory); + process_ai_material_specular(aiMat, mat, modelDirectory); mat.name = matName.C_Str(); mat.phong.ambient = glm::vec3{ambientColor.r, ambientColor.g, ambientColor.b}; @@ -182,3 +184,69 @@ void process_ai_material(aiMaterial* aiMat, const aiScene* scene, Material& mat) mat.phong.specular = glm::vec3{specularColor.r, specularColor.g, specularColor.b}; mat.phong.shininess = shininess; } + +void process_ai_material_diffuse(const aiMaterial *aiMat, Material& mat, const std::filesystem::path& modelDirectory) { + aiString aiTextureName; + aiMat->GetTexture(aiTextureType_DIFFUSE, 0, &aiTextureName); + std::string textureName = aiTextureName.C_Str(); + + if (textureName.empty()) { + mat.diffuse = TextureManager::textures["default_diffuse"]; + return; + } + + const auto textureFilename = std::filesystem::path(textureName).filename(); + const auto texturePath = modelDirectory / textureFilename; + + const std::string finalTextureName = textureFilename.string(); + if (TextureManager::textures.contains(finalTextureName)) { + mat.diffuse = TextureManager::textures[finalTextureName]; + return; + } + + if (std::filesystem::exists(texturePath)) { + const auto texture = TextureManager::load_from_file(texturePath.string()); + if (!texture) { + mat.diffuse = TextureManager::textures["default_diffuse"]; + return; + } + + TextureManager::textures[finalTextureName] = texture; + mat.diffuse = texture; + } else { + mat.diffuse = TextureManager::textures["default_diffuse"]; + } +} + +void process_ai_material_specular(const aiMaterial *aiMat, Material& mat, const std::filesystem::path& modelDirectory) { + aiString aiTextureName; + aiMat->GetTexture(aiTextureType_SPECULAR, 0, &aiTextureName); + std::string textureName = aiTextureName.C_Str(); + + if (textureName.empty()) { + mat.specular = TextureManager::textures["default_specular"]; + return; + } + + const auto textureFilename = std::filesystem::path(textureName).filename(); + const auto texturePath = modelDirectory / textureFilename; + + const std::string finalTextureName = textureFilename.string(); + if (TextureManager::textures.contains(finalTextureName)) { + mat.specular = TextureManager::textures[finalTextureName]; + return; + } + + if (std::filesystem::exists(texturePath)) { + const auto texture = TextureManager::load_from_file(texturePath.string()); + if (!texture) { + mat.specular = TextureManager::textures["default_specular"]; + return; + } + + TextureManager::textures[finalTextureName] = texture; + mat.specular = texture; + } else { + mat.specular = TextureManager::textures["default_specular"]; + } +} \ No newline at end of file diff --git a/src/ModelLoader.h b/src/ModelManager.h similarity index 87% rename from src/ModelLoader.h rename to src/ModelManager.h index 9c04ff9..9cbd721 100644 --- a/src/ModelLoader.h +++ b/src/ModelManager.h @@ -10,7 +10,7 @@ #include "Model.h" -class ModelLoader +class ModelManager { public: static std::unordered_map> models; diff --git a/src/Texture.h b/src/Texture.h index 5aee1f6..cf7e52a 100644 --- a/src/Texture.h +++ b/src/Texture.h @@ -16,6 +16,9 @@ public: Texture(unsigned int id, std::string_view filePath, int width, int height, int channels) : id(id), filePath(filePath), width(width), height(height), channels(channels) {} + Texture(const Texture&) = delete; + Texture& operator=(const Texture&) = delete; + ~Texture() { glDeleteTextures(1, &id); } void bind() const {glBindTexture(GL_TEXTURE_2D, id);} diff --git a/src/TextureLoader.cpp b/src/TextureManager.cpp similarity index 87% rename from src/TextureLoader.cpp rename to src/TextureManager.cpp index a3233eb..497d1e6 100644 --- a/src/TextureLoader.cpp +++ b/src/TextureManager.cpp @@ -2,7 +2,7 @@ // Created by slinky on 5/1/26. // -#include "TextureLoader.h" +#include "TextureManager.h" #include "stb_image.h" @@ -10,9 +10,9 @@ #include "glad/gl.h" -std::unordered_map> TextureLoader::textures; +std::unordered_map> TextureManager::textures; -std::shared_ptr TextureLoader::load_from_file(std::string_view filePath) +std::shared_ptr TextureManager::load_from_file(std::string_view filePath) { int width, height, channels; unsigned char *data = stbi_load(filePath.data(), &width, &height, &channels, 0); @@ -41,7 +41,7 @@ std::shared_ptr TextureLoader::load_from_file(std::string_view filePath return std::make_shared(textureID, filePath, width, height, channels); } -std::shared_ptr TextureLoader::load_from_data(unsigned char* data, int width, int height, int channels) +std::shared_ptr TextureManager::load_from_data(unsigned char* data, int width, int height, int channels) { unsigned int textureID = 0; glGenTextures(1, &textureID); diff --git a/src/TextureLoader.h b/src/TextureManager.h similarity index 75% rename from src/TextureLoader.h rename to src/TextureManager.h index 0737e30..af28695 100644 --- a/src/TextureLoader.h +++ b/src/TextureManager.h @@ -2,15 +2,15 @@ // Created by slinky on 5/1/26. // -#ifndef B_ENGINE_TEXTURELOADER_H -#define B_ENGINE_TEXTURELOADER_H +#ifndef B_ENGINE_TEXTUREMANAGER_H +#define B_ENGINE_TEXTUREMANAGER_H #include #include #include "Texture.h" -class TextureLoader { +class TextureManager { public: static std::unordered_map> textures; @@ -18,4 +18,4 @@ public: static std::shared_ptr load_from_data(unsigned char* data, int width, int height, int channels); }; -#endif //B_ENGINE_TEXTURELOADER_H \ No newline at end of file +#endif //B_ENGINE_TEXTUREMANAGER_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c08b7ac..75c25d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,14 +8,16 @@ #include "glm/ext/matrix_transform.hpp" #define STB_IMAGE_IMPLEMENTATION +#include "FreeCamera.h" +#include "InputManager.h" #include "stb_image.h" #include "Model.h" -#include "ModelLoader.h" +#include "ModelManager.h" #include "ShaderManager.h" #include "ShaderProgram.h" #include "Texture.h" -#include "TextureLoader.h" +#include "TextureManager.h" GLFWwindow* gWindow = nullptr; int gWindowWidth = 800; @@ -24,9 +26,11 @@ int gWindowHeight = 600; int glVersionMajor = 0; int glVersionMinor = 0; -auto cameraPosition = glm::vec3{0, 0, 0}; -auto view = glm::mat4{0}; -auto projection = glm::mat4{0}; +float gAspectRatio = 0.f; + +float gMouseSensitivity = 0.1f; + +FreeCamera gCamera {}; void glfw_error_callback(int error, const char* description); void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); @@ -36,10 +40,16 @@ void init_glfw(); void create_main_window(); void load_gl(); void init_camera(); +void load_default_textures(); +void load_shaders(); void loop(); std::shared_ptr load_model(std::string_view _path); +auto xAxis = glm::vec3{1.f, 0.f, 0.f}; +auto yAxis = glm::vec3{0.f, 1.f, 0.f}; +auto zAxis = glm::vec3{0.f, 0.f, 1.f}; + int main() { spdlog::info("b_engine start"); @@ -49,30 +59,33 @@ int main() { load_gl(); - stbi_set_flip_vertically_on_load(true); - init_camera(); - // create a default texture - unsigned char defaultDiffuseData[4] = {static_cast(255), 255, 255, 255}; - TextureLoader::textures["default_diffuse"] = TextureLoader::load_from_data(reinterpret_cast(&defaultDiffuseData), 1, 1, 4); + stbi_set_flip_vertically_on_load(true); - // create a default specular map - unsigned char defaultSpecularData[4] = {static_cast(128), 128, 128, 255}; - TextureLoader::textures["default_specular"] = TextureLoader::load_from_data(reinterpret_cast(&defaultSpecularData), 1, 1, 4); + load_default_textures(); - ShaderManager::shaders["phong_shader"] = ShaderManager::load("./resources/standard.vert", "./resources/standard.frag"); - if (ShaderManager::shaders["phong_shader"] == nullptr) - { - std::exit(1); - } + load_shaders(); - /*ModelLoader::models["cube"] = ModelLoader::load_from_file("./resources/cube.obj", true);*/ - ModelLoader::models["backpack"] = ModelLoader::load_from_file("./resources/backpack/backpack.obj"); - /*ModelLoader::models["male"] = ModelLoader::load_from_file("./resources/male.obj");*/ + InputManager::generate_input_action("move_forward", {{GLFW_KEY_W, KEY_DOWN}}); + InputManager::generate_input_action("move_backward", {{GLFW_KEY_S, KEY_DOWN}}); + InputManager::generate_input_action("move_right", {{GLFW_KEY_D, KEY_DOWN}}); + InputManager::generate_input_action("move_left", {{GLFW_KEY_A, KEY_DOWN}}); + + InputManager::generate_input_action("exit_application", {{GLFW_KEY_ESCAPE, KEY_DOWN}}); + + spdlog::info("loading models"); + ModelManager::models["cube"] = ModelManager::load_from_file("./resources/cube.obj"); + ModelManager::models["backpack"] = ModelManager::load_from_file("./resources/backpack/backpack.obj"); + ModelManager::models["male"] = ModelManager::load_from_file("./resources/male.obj"); + spdlog::info("done"); loop(); + ShaderManager::shaders.clear(); + TextureManager::textures.clear(); + ModelManager::models.clear(); + glfwDestroyWindow(gWindow); glfwTerminate(); @@ -83,14 +96,10 @@ void glfw_error_callback(int error, const char* description) { spdlog::error("glfw error: {}", description); } -void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - glfwSetWindowShouldClose(window, GL_TRUE); - } -} - void glfw_framebuffer_size_callback(GLFWwindow *window, int width, int height) { glViewport(0, 0, width, height); + gAspectRatio = (float)width / (float)height; + gCamera.update_aspect_ratio(gAspectRatio); } void init_glfw() @@ -103,13 +112,14 @@ void init_glfw() glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_SAMPLES, 4); glfwSetErrorCallback(glfw_error_callback); } void create_main_window() { - gWindow = glfwCreateWindow(gWindowWidth, gWindowHeight, "b_engine v0.0.1", nullptr, nullptr); + gWindow = glfwCreateWindow(gWindowWidth, gWindowHeight, "b_engine v0.0.2", nullptr, nullptr); if (!gWindow) { spdlog::error("failed to create glfw window"); std::exit(1); @@ -117,8 +127,12 @@ void create_main_window() glfwMakeContextCurrent(gWindow); - glfwSetKeyCallback(gWindow, glfw_key_callback); + glfwSetKeyCallback(gWindow, InputManager::key_callback); + glfwSetCursorPosCallback(gWindow, InputManager::mouse_pos_callback); + glfwSetFramebufferSizeCallback(gWindow, glfw_framebuffer_size_callback); + + glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } void load_gl() @@ -132,63 +146,131 @@ void load_gl() { int fbWidth, fbHeight; glfwGetFramebufferSize(gWindow, &fbWidth, &fbHeight); - glViewport(0, 0, fbWidth, fbHeight); + gAspectRatio = (float)fbWidth / (float)fbHeight; } glEnable(GL_DEPTH_TEST); + glEnable(GL_MULTISAMPLE); } -void init_camera() -{ - cameraPosition = glm::vec3{10, 0, 10}; - auto cameraTarget = glm::vec3{0, 0, 0}; +void init_camera() { + gCamera = {{5.f, 5.f, 5.f}, 0, 0, gAspectRatio}; + gCamera.rotate_yaw(-135.f); + gCamera.rotate_pitch(-35.f); - view = glm::lookAt(cameraPosition, cameraTarget, glm::vec3(0.0f, 1.0f, 0.0f)); + InputManager::add_mouse_listener([](float xoff, float yoff) { + gCamera.rotate_yaw(xoff * gMouseSensitivity); + gCamera.rotate_pitch(yoff * gMouseSensitivity); + }); +} - float aspectRatio = static_cast(gWindowWidth) / static_cast(gWindowHeight); - projection = glm::perspective(glm::radians(75.f), aspectRatio, 0.1f, 100.0f); +void load_default_textures() { + spdlog::info("creating default textures"); + unsigned char defaultDiffuseData[4] = {static_cast(255), 255, 255, 255}; + TextureManager::textures["default_diffuse"] = TextureManager::load_from_data(reinterpret_cast(&defaultDiffuseData), 1, 1, 4); + + unsigned char defaultSpecularData[4] = {static_cast(64), 64, 64, 255}; + TextureManager::textures["default_specular"] = TextureManager::load_from_data(reinterpret_cast(&defaultSpecularData), 1, 1, 4); +} + +void load_shaders() { + spdlog::info("compiling shaders"); + ShaderManager::shaders["phong_shader"] = ShaderManager::load("./resources/standard.vert", "./resources/standard.frag"); + if (ShaderManager::shaders["phong_shader"] == nullptr) + { + spdlog::error("failed to compile phong shader"); + std::exit(1); + } } void loop() { - Texture* defaultDiffuse = TextureLoader::textures["default_diffuse"].get(); - Texture* defaultSpecular = TextureLoader::textures["default_specular"].get(); + std::shared_ptr defaultDiffuse = TextureManager::textures["default_diffuse"]; + std::shared_ptr defaultSpecular = TextureManager::textures["default_specular"]; + + auto model = glm::identity(); + + ShaderProgram* shader = ShaderManager::shaders["phong_shader"].get(); + + const auto cube = ModelManager::models["cube"]; + const auto backpack = ModelManager::models["backpack"]; + const auto male = ModelManager::models["male"]; + + const auto& activeModel = backpack; while (!glfwWindowShouldClose(gWindow)) { - auto model = glm::identity(); - // model = glm::translate(model, glm::vec3(0, -5.f, 0)); - // model = glm::scale(model, glm::vec3(.5f, .5f, .5f)); glfwPollEvents(); + // check inputs + if (InputManager::check_action_performed("exit_application")) { + glfwSetWindowShouldClose(gWindow, true); + continue; + } + + if (InputManager::check_action_performed("move_forward")) { + gCamera.move(CAMERA_MOVEMENT::FORWARD, .05); + } + + if (InputManager::check_action_performed("move_backward")) { + gCamera.move(CAMERA_MOVEMENT::BACKWARD, .05); + } + + if (InputManager::check_action_performed("move_left")) { + gCamera.move(CAMERA_MOVEMENT::LEFT, .05); + } + + if (InputManager::check_action_performed("move_right")) { + gCamera.move(CAMERA_MOVEMENT::RIGHT, .05); + } + + // gl frame prep glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - ShaderProgram* shader = ShaderManager::shaders["phong_shader"].get(); - - const Model* cube = ModelLoader::models["cube"].get(); - const Model* backpack = ModelLoader::models["backpack"].get(); - const Model* male = ModelLoader::models["male"].get(); - - if (shader) { - Material* mat = backpack->materials[0].get(); + // draw the stuff + if (shader && activeModel) { shader->bind(); - shader->setMat4("projection", projection); - shader->setMat4("view", view); + shader->setMat4("projection", gCamera.projection()); + shader->setMat4("view", gCamera.view()); shader->setMat4("model", model); - shader->setVec3("viewPosition", cameraPosition); + shader->setVec3("viewPosition", gCamera.position()); - shader->setVec3("phongAmbient", glm::vec3(0.3f, 0.3f, 0.3f)); - shader->setVec3("phongDiffuse", glm::vec3(0.8f, 0.8f, 0.8f)); - shader->setVec3("phongSpecular", glm::vec3(1.0f, 1.0f, 1.0f)); - shader->setFloat("phongShininess", 32.0f); - - shader->setVec3("lightDirection", glm::vec3(-.5f, -.5f, -.5f)); + shader->setVec3("lightPosition", glm::vec3{-2.f, 0, 2.0f}); + shader->setVec3("lightDirection", glm::vec3(1, -1, -1)); shader->setVec3("lightAmbient", glm::vec3(0.3f, 0.3f, 0.3f)); - shader->setVec3("lightDiffuse", glm::vec3(1.0f, 1.0f, 1.0f)); + shader->setVec3("lightDiffuse", glm::vec3(0.5f, 0.5f, 0.5f)); shader->setVec3("lightSpecular", glm::vec3(1.0f, 1.0f, 1.0f)); - for (const auto& mesh: backpack->meshes) { + for (const auto& mesh: activeModel->meshes) { + unsigned int materialId = mesh->materialId; + if (materialId >= activeModel->materials.size()) { + materialId = 0; + } + + const std::shared_ptr mat = activeModel->materials[1]; + + shader->setVec3("phongAmbient", mat->phong.ambient); + shader->setVec3("phongDiffuse", mat->phong.diffuse); + shader->setVec3("phongSpecular", mat->phong.specular); + shader->setFloat("phongShininess", mat->phong.shininess); + + glActiveTexture(GL_TEXTURE0); + auto diffuse = mat->diffuse; + if (!diffuse) { + diffuse = defaultDiffuse; + } + diffuse->bind(); + shader->setInt("diffuseMap", 0); + + glActiveTexture(GL_TEXTURE1); + auto specular = mat->specular; + if (!specular) { + specular = defaultSpecular; + } + specular->bind(); + shader->setInt("specularMap", 1); + glBindVertexArray(mesh.get()->vao); glDrawElements(GL_TRIANGLES, mesh->numIndices, GL_UNSIGNED_INT, 0); } @@ -197,6 +279,7 @@ void loop() { ShaderProgram::unbind(); } + // gl end frame stuff glfwSwapBuffers(gWindow); } } \ No newline at end of file