Huge stuff. camera, input manager (mouse + keyboard), can move around scene now

This commit is contained in:
2026-05-04 00:41:34 -04:00
parent 9a8e618bbb
commit 3c756d6230
17 changed files with 624 additions and 119 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 4.1) 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) set(CMAKE_CXX_STANDARD 20)
@@ -26,17 +26,27 @@ add_executable(${PROJECT_NAME}
src/Mesh.h src/Mesh.h
src/Model.cpp src/Model.cpp
src/Model.h src/Model.h
src/Material.cpp
src/Material.h src/Material.h
src/ModelLoader.cpp src/ModelManager.cpp
src/ModelLoader.h src/ModelManager.h
src/ShaderProgram.cpp src/ShaderProgram.cpp
src/ShaderProgram.h src/ShaderProgram.h
src/Texture.h src/Texture.h
src/TextureLoader.cpp src/TextureManager.cpp
src/TextureLoader.h src/TextureManager.h
src/ShaderManager.cpp 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_link_libraries(${PROJECT_NAME} glfw spdlog assimp glm)
target_include_directories(${PROJECT_NAME} PRIVATE ${ASSIMP_INCLUDE_INSTALL_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${ASSIMP_INCLUDE_INSTALL_DIR})

View File

@@ -10,7 +10,7 @@ Ke 0.0 0.0 0.0
Ni 1.450000 Ni 1.450000
d 1.000000 d 1.000000
illum 2 illum 2
map_Kd backpack/diffuse.jpg map_Kd diffuse.jpg
map_Bump backpack/normal.png map_Bump normal.png
map_Ks backpack/specular.jpg map_Ks specular.jpg

View File

@@ -6,7 +6,8 @@ in vec3 Position;
in vec2 TexCoord; in vec2 TexCoord;
in vec3 Normal; in vec3 Normal;
uniform sampler2D diffuse; uniform sampler2D diffuseMap;
uniform sampler2D specularMap;
uniform vec3 viewPosition; uniform vec3 viewPosition;
@@ -15,22 +16,30 @@ uniform vec3 phongDiffuse;
uniform vec3 phongSpecular; uniform vec3 phongSpecular;
uniform float phongShininess; uniform float phongShininess;
uniform vec3 lightPosition;
uniform vec3 lightDirection; uniform vec3 lightDirection;
uniform vec3 lightAmbient; uniform vec3 lightAmbient;
uniform vec3 lightDiffuse; uniform vec3 lightDiffuse;
uniform vec3 lightSpecular; uniform vec3 lightSpecular;
void main() { void main() {
vec4 diffuseColor = texture(diffuse, TexCoord); vec3 diffuseColor = texture(diffuseMap, TexCoord).rgb;
vec3 specularColor = texture(specularMap, TexCoord).rgb;
vec3 norm = normalize(Normal); vec3 norm = normalize(Normal);
vec3 lightDir = normalize(-lightDirection); 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); 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); FragColor = vec4(result, 1.0);
} }

99
src/FreeCamera.cpp Normal file
View File

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

61
src/FreeCamera.h Normal file
View File

@@ -0,0 +1,61 @@
//
// Created by slinky on 5/3/26.
//
#ifndef B_ENGINE_CAMERA_H
#define B_ENGINE_CAMERA_H
#include <glm/glm.hpp>
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

118
src/InputManager.cpp Normal file
View File

@@ -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<std::string, std::unique_ptr<InputAction>> InputManager::registeredActions = {};
static bool currentKeyStatesData[512] = {};
bool* InputManager::currentKeyStates = currentKeyStatesData;
static bool previousKeyStatesData[512] = {};
bool* InputManager::previousKeyStates = previousKeyStatesData;
std::vector<MouseDeltaCallback> InputManager::mouse_listeners = {};
void InputManager::mouse_pos_callback(GLFWwindow *window, double x, double y) {
const auto xpos = static_cast<float>(x);
const auto ypos = static_cast<float>(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<InputRequirement> 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<InputAction>(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;
}

61
src/InputManager.h Normal file
View File

@@ -0,0 +1,61 @@
//
// Created by slinky on 5/3/26.
//
#ifndef B_ENGINE_INPUTMANAGER_H
#define B_ENGINE_INPUTMANAGER_H
#include <functional>
#include <string>
#include <initializer_list>
#include <memory>
#include "GLFW/glfw3.h"
using MouseDeltaCallback = std::function<void(float, float)>;
enum KeyState {
KEY_PRESSED,
KEY_HELD,
KEY_DOWN,
KEY_RELEASED,
};
struct InputRequirement {
int key;
KeyState state;
};
struct InputAction {
std::vector<InputRequirement> requirements{};
};
class InputManager {
public:
static bool mouseInit;
static float lastMousePositionX;
static float lastMousePositionY;
static bool* currentKeyStates;
static bool* previousKeyStates;
static std::unordered_map<std::string, std::unique_ptr<InputAction>> 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<InputRequirement> 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<MouseDeltaCallback> mouse_listeners;
static void set_key_state(int key, bool state);
};
#endif //B_ENGINE_INPUTMANAGER_H

View File

@@ -1,5 +0,0 @@
//
// Created by lbmas on 4/28/2026.
//
#include "Material.h"

View File

@@ -8,7 +8,6 @@ Mesh::Mesh(const std::vector<float> &positions, const std::vector<float> &uvs, c
ebo = 0; ebo = 0;
numIndices = indices.size(); numIndices = indices.size();
// positions // positions
GLuint positionVbo = 0; GLuint positionVbo = 0;
glGenBuffers(1, &positionVbo); glGenBuffers(1, &positionVbo);

View File

@@ -18,9 +18,10 @@ public:
const std::vector<float>& normals, const std::vector<float>& normals,
const std::vector<unsigned int>& indices); const std::vector<unsigned int>& indices);
GLuint vao; GLuint vao = 0;
GLuint ebo; GLuint ebo = 0;
unsigned int numIndices; unsigned int numIndices = 0;
unsigned int materialId = 0;
}; };
#endif //B_ENGINE_MESH_H #endif //B_ENGINE_MESH_H

View File

@@ -7,15 +7,13 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <filesystem>
#include "Mesh.h" #include "Mesh.h"
#include "Material.h" #include "Material.h"
class Model struct Model
{ {
public:
static std::shared_ptr<Model> load_from_file(std::string_view filename);
std::vector<std::shared_ptr<Mesh>> meshes; std::vector<std::shared_ptr<Mesh>> meshes;
std::vector<std::shared_ptr<Material>> materials; std::vector<std::shared_ptr<Material>> materials;
}; };

View File

@@ -4,7 +4,7 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "ModelLoader.h" #include "ModelManager.h"
#include <assimp/Importer.hpp> #include <assimp/Importer.hpp>
#include <assimp/postprocess.h> #include <assimp/postprocess.h>
@@ -12,15 +12,17 @@
#include <cstring> #include <cstring>
#include "TextureLoader.h" #include "TextureManager.h"
#include "glm/ext/matrix_transform.hpp" #include "glm/ext/matrix_transform.hpp"
std::unordered_map<std::string, std::shared_ptr<Model>> ModelLoader::models; std::unordered_map<std::string, std::shared_ptr<Model>> ModelManager::models;
void process_ai_mesh(aiMesh* aiMesh, const aiScene* scene, glm::mat4 transform, Mesh& mesh); 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_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( auto zUpMatrix = glm::mat4(
1.0f, 0.0f, 0.0f, 0.0f, // Column 0 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 0.0f, 0.0f, 0.0f, 1.0f // Column 3
); );
std::shared_ptr<Model> ModelLoader::load_from_file(std::string_view _path, bool zUp) std::shared_ptr<Model> ModelManager::load_from_file(std::string_view _path, bool zUp)
{ {
Assimp::Importer importer; Assimp::Importer importer;
@@ -46,6 +48,16 @@ std::shared_ptr<Model> ModelLoader::load_from_file(std::string_view _path, bool
auto model = std::make_shared<Model>(); auto model = std::make_shared<Model>();
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<Material>(mat);
}
auto transform = glm::identity<glm::mat4>(); auto transform = glm::identity<glm::mat4>();
if (zUp) { if (zUp) {
transform = zUpMatrix; transform = zUpMatrix;
@@ -58,20 +70,16 @@ std::shared_ptr<Model> ModelLoader::load_from_file(std::string_view _path, bool
void process_ai_node(aiNode* node, const aiScene* scene, glm::mat4 transform, Model* model) void process_ai_node(aiNode* node, const aiScene* scene, glm::mat4 transform, Model* model)
{ {
auto m = node->mTransformation; const auto t = 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); 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++) for (unsigned int i = 0; i < node->mNumMeshes; i++)
{ {
aiMesh* aiMesh = scene->mMeshes[node->mMeshes[i]]; aiMesh* aiMesh = scene->mMeshes[node->mMeshes[i]];
Mesh mesh{}; Mesh mesh{};
mesh.materialId = aiMesh->mMaterialIndex;
process_ai_mesh(aiMesh, scene, transform, mesh); process_ai_mesh(aiMesh, scene, transform, mesh);
model->meshes.push_back(std::make_shared<Mesh>(mesh)); model->meshes.push_back(std::make_shared<Mesh>(mesh));
aiMaterial* aiMat = scene->mMaterials[aiMesh->mMaterialIndex];
Material mat{};
process_ai_material(aiMat, scene, mat);
model->materials.push_back(std::make_shared<Material>(mat));
} }
for (unsigned int i = 0; i < node->mNumChildren; i++) 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}; 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; aiString matName;
aiReturn ret = aiMat->Get(AI_MATKEY_NAME, matName); aiReturn ret = aiMat->Get(AI_MATKEY_NAME, matName);
if (ret != AI_SUCCESS) { 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 // now grab the textures, first diffuse
for (unsigned int i = 0; i < aiMat->GetTextureCount(aiTextureType_DIFFUSE); i++) { process_ai_material_diffuse(aiMat, mat, modelDirectory);
aiString textureName; process_ai_material_specular(aiMat, mat, modelDirectory);
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());
}
}
mat.name = matName.C_Str(); mat.name = matName.C_Str();
mat.phong.ambient = glm::vec3{ambientColor.r, ambientColor.g, ambientColor.b}; 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.specular = glm::vec3{specularColor.r, specularColor.g, specularColor.b};
mat.phong.shininess = shininess; 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"];
}
}

View File

@@ -10,7 +10,7 @@
#include "Model.h" #include "Model.h"
class ModelLoader class ModelManager
{ {
public: public:
static std::unordered_map<std::string, std::shared_ptr<Model>> models; static std::unordered_map<std::string, std::shared_ptr<Model>> models;

View File

@@ -16,6 +16,9 @@ public:
Texture(unsigned int id, std::string_view filePath, int width, int height, int channels) Texture(unsigned int id, std::string_view filePath, int width, int height, int channels)
: id(id), filePath(filePath), width(width), height(height), channels(channels) {} : id(id), filePath(filePath), width(width), height(height), channels(channels) {}
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
~Texture() { glDeleteTextures(1, &id); } ~Texture() { glDeleteTextures(1, &id); }
void bind() const {glBindTexture(GL_TEXTURE_2D, id);} void bind() const {glBindTexture(GL_TEXTURE_2D, id);}

View File

@@ -2,7 +2,7 @@
// Created by slinky on 5/1/26. // Created by slinky on 5/1/26.
// //
#include "TextureLoader.h" #include "TextureManager.h"
#include "stb_image.h" #include "stb_image.h"
@@ -10,9 +10,9 @@
#include "glad/gl.h" #include "glad/gl.h"
std::unordered_map<std::string, std::shared_ptr<Texture>> TextureLoader::textures; std::unordered_map<std::string, std::shared_ptr<Texture>> TextureManager::textures;
std::shared_ptr<Texture> TextureLoader::load_from_file(std::string_view filePath) std::shared_ptr<Texture> TextureManager::load_from_file(std::string_view filePath)
{ {
int width, height, channels; int width, height, channels;
unsigned char *data = stbi_load(filePath.data(), &width, &height, &channels, 0); unsigned char *data = stbi_load(filePath.data(), &width, &height, &channels, 0);
@@ -41,7 +41,7 @@ std::shared_ptr<Texture> TextureLoader::load_from_file(std::string_view filePath
return std::make_shared<Texture>(textureID, filePath, width, height, channels); return std::make_shared<Texture>(textureID, filePath, width, height, channels);
} }
std::shared_ptr<Texture> TextureLoader::load_from_data(unsigned char* data, int width, int height, int channels) std::shared_ptr<Texture> TextureManager::load_from_data(unsigned char* data, int width, int height, int channels)
{ {
unsigned int textureID = 0; unsigned int textureID = 0;
glGenTextures(1, &textureID); glGenTextures(1, &textureID);

View File

@@ -2,15 +2,15 @@
// Created by slinky on 5/1/26. // Created by slinky on 5/1/26.
// //
#ifndef B_ENGINE_TEXTURELOADER_H #ifndef B_ENGINE_TEXTUREMANAGER_H
#define B_ENGINE_TEXTURELOADER_H #define B_ENGINE_TEXTUREMANAGER_H
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include "Texture.h" #include "Texture.h"
class TextureLoader { class TextureManager {
public: public:
static std::unordered_map<std::string, std::shared_ptr<Texture>> textures; static std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
@@ -18,4 +18,4 @@ public:
static std::shared_ptr<Texture> load_from_data(unsigned char* data, int width, int height, int channels); static std::shared_ptr<Texture> load_from_data(unsigned char* data, int width, int height, int channels);
}; };
#endif //B_ENGINE_TEXTURELOADER_H #endif //B_ENGINE_TEXTUREMANAGER_H

View File

@@ -8,14 +8,16 @@
#include "glm/ext/matrix_transform.hpp" #include "glm/ext/matrix_transform.hpp"
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "FreeCamera.h"
#include "InputManager.h"
#include "stb_image.h" #include "stb_image.h"
#include "Model.h" #include "Model.h"
#include "ModelLoader.h" #include "ModelManager.h"
#include "ShaderManager.h" #include "ShaderManager.h"
#include "ShaderProgram.h" #include "ShaderProgram.h"
#include "Texture.h" #include "Texture.h"
#include "TextureLoader.h" #include "TextureManager.h"
GLFWwindow* gWindow = nullptr; GLFWwindow* gWindow = nullptr;
int gWindowWidth = 800; int gWindowWidth = 800;
@@ -24,9 +26,11 @@ int gWindowHeight = 600;
int glVersionMajor = 0; int glVersionMajor = 0;
int glVersionMinor = 0; int glVersionMinor = 0;
auto cameraPosition = glm::vec3{0, 0, 0}; float gAspectRatio = 0.f;
auto view = glm::mat4{0};
auto projection = glm::mat4{0}; float gMouseSensitivity = 0.1f;
FreeCamera gCamera {};
void glfw_error_callback(int error, const char* description); void glfw_error_callback(int error, const char* description);
void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); 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 create_main_window();
void load_gl(); void load_gl();
void init_camera(); void init_camera();
void load_default_textures();
void load_shaders();
void loop(); void loop();
std::shared_ptr<Model> load_model(std::string_view _path); std::shared_ptr<Model> 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() { int main() {
spdlog::info("b_engine start"); spdlog::info("b_engine start");
@@ -49,30 +59,33 @@ int main() {
load_gl(); load_gl();
stbi_set_flip_vertically_on_load(true);
init_camera(); init_camera();
// create a default texture stbi_set_flip_vertically_on_load(true);
unsigned char defaultDiffuseData[4] = {static_cast<unsigned char>(255), 255, 255, 255};
TextureLoader::textures["default_diffuse"] = TextureLoader::load_from_data(reinterpret_cast<unsigned char*>(&defaultDiffuseData), 1, 1, 4);
// create a default specular map load_default_textures();
unsigned char defaultSpecularData[4] = {static_cast<unsigned char>(128), 128, 128, 255};
TextureLoader::textures["default_specular"] = TextureLoader::load_from_data(reinterpret_cast<unsigned char*>(&defaultSpecularData), 1, 1, 4);
ShaderManager::shaders["phong_shader"] = ShaderManager::load("./resources/standard.vert", "./resources/standard.frag"); load_shaders();
if (ShaderManager::shaders["phong_shader"] == nullptr)
{
std::exit(1);
}
/*ModelLoader::models["cube"] = ModelLoader::load_from_file("./resources/cube.obj", true);*/ InputManager::generate_input_action("move_forward", {{GLFW_KEY_W, KEY_DOWN}});
ModelLoader::models["backpack"] = ModelLoader::load_from_file("./resources/backpack/backpack.obj"); InputManager::generate_input_action("move_backward", {{GLFW_KEY_S, KEY_DOWN}});
/*ModelLoader::models["male"] = ModelLoader::load_from_file("./resources/male.obj");*/ 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(); loop();
ShaderManager::shaders.clear();
TextureManager::textures.clear();
ModelManager::models.clear();
glfwDestroyWindow(gWindow); glfwDestroyWindow(gWindow);
glfwTerminate(); glfwTerminate();
@@ -83,14 +96,10 @@ void glfw_error_callback(int error, const char* description) {
spdlog::error("glfw error: {}", 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) { void glfw_framebuffer_size_callback(GLFWwindow *window, int width, int height) {
glViewport(0, 0, width, height); glViewport(0, 0, width, height);
gAspectRatio = (float)width / (float)height;
gCamera.update_aspect_ratio(gAspectRatio);
} }
void init_glfw() void init_glfw()
@@ -103,13 +112,14 @@ void init_glfw()
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_SAMPLES, 4);
glfwSetErrorCallback(glfw_error_callback); glfwSetErrorCallback(glfw_error_callback);
} }
void create_main_window() 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) { if (!gWindow) {
spdlog::error("failed to create glfw window"); spdlog::error("failed to create glfw window");
std::exit(1); std::exit(1);
@@ -117,8 +127,12 @@ void create_main_window()
glfwMakeContextCurrent(gWindow); glfwMakeContextCurrent(gWindow);
glfwSetKeyCallback(gWindow, glfw_key_callback); glfwSetKeyCallback(gWindow, InputManager::key_callback);
glfwSetCursorPosCallback(gWindow, InputManager::mouse_pos_callback);
glfwSetFramebufferSizeCallback(gWindow, glfw_framebuffer_size_callback); glfwSetFramebufferSizeCallback(gWindow, glfw_framebuffer_size_callback);
glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
} }
void load_gl() void load_gl()
@@ -132,63 +146,131 @@ void load_gl()
{ {
int fbWidth, fbHeight; int fbWidth, fbHeight;
glfwGetFramebufferSize(gWindow, &fbWidth, &fbHeight); glfwGetFramebufferSize(gWindow, &fbWidth, &fbHeight);
glViewport(0, 0, fbWidth, fbHeight); gAspectRatio = (float)fbWidth / (float)fbHeight;
} }
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glEnable(GL_MULTISAMPLE);
} }
void init_camera() void init_camera() {
gCamera = {{5.f, 5.f, 5.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 load_default_textures() {
spdlog::info("creating default textures");
unsigned char defaultDiffuseData[4] = {static_cast<unsigned char>(255), 255, 255, 255};
TextureManager::textures["default_diffuse"] = TextureManager::load_from_data(reinterpret_cast<unsigned char*>(&defaultDiffuseData), 1, 1, 4);
unsigned char defaultSpecularData[4] = {static_cast<unsigned char>(64), 64, 64, 255};
TextureManager::textures["default_specular"] = TextureManager::load_from_data(reinterpret_cast<unsigned char*>(&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)
{ {
cameraPosition = glm::vec3{10, 0, 10}; spdlog::error("failed to compile phong shader");
auto cameraTarget = glm::vec3{0, 0, 0}; std::exit(1);
}
view = glm::lookAt(cameraPosition, cameraTarget, glm::vec3(0.0f, 1.0f, 0.0f));
float aspectRatio = static_cast<float>(gWindowWidth) / static_cast<float>(gWindowHeight);
projection = glm::perspective(glm::radians(75.f), aspectRatio, 0.1f, 100.0f);
} }
void loop() { void loop() {
Texture* defaultDiffuse = TextureLoader::textures["default_diffuse"].get(); std::shared_ptr<Texture> defaultDiffuse = TextureManager::textures["default_diffuse"];
Texture* defaultSpecular = TextureLoader::textures["default_specular"].get(); std::shared_ptr<Texture> defaultSpecular = TextureManager::textures["default_specular"];
while (!glfwWindowShouldClose(gWindow)) {
auto model = glm::identity<glm::mat4>(); auto model = glm::identity<glm::mat4>();
// model = glm::translate(model, glm::vec3(0, -5.f, 0));
// model = glm::scale(model, glm::vec3(.5f, .5f, .5f));
glfwPollEvents();
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(); ShaderProgram* shader = ShaderManager::shaders["phong_shader"].get();
const Model* cube = ModelLoader::models["cube"].get(); const auto cube = ModelManager::models["cube"];
const Model* backpack = ModelLoader::models["backpack"].get(); const auto backpack = ModelManager::models["backpack"];
const Model* male = ModelLoader::models["male"].get(); const auto male = ModelManager::models["male"];
if (shader) { const auto& activeModel = backpack;
Material* mat = backpack->materials[0].get();
while (!glfwWindowShouldClose(gWindow)) {
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);
// draw the stuff
if (shader && activeModel) {
shader->bind(); shader->bind();
shader->setMat4("projection", projection); shader->setMat4("projection", gCamera.projection());
shader->setMat4("view", view); shader->setMat4("view", gCamera.view());
shader->setMat4("model", model); 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("lightPosition", glm::vec3{-2.f, 0, 2.0f});
shader->setVec3("phongDiffuse", glm::vec3(0.8f, 0.8f, 0.8f)); shader->setVec3("lightDirection", glm::vec3(1, -1, -1));
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("lightAmbient", glm::vec3(0.3f, 0.3f, 0.3f)); 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)); 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<Material> 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); glBindVertexArray(mesh.get()->vao);
glDrawElements(GL_TRIANGLES, mesh->numIndices, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, mesh->numIndices, GL_UNSIGNED_INT, 0);
} }
@@ -197,6 +279,7 @@ void loop() {
ShaderProgram::unbind(); ShaderProgram::unbind();
} }
// gl end frame stuff
glfwSwapBuffers(gWindow); glfwSwapBuffers(gWindow);
} }
} }