From dc5654ba9afa4afb50ed416457ec423a46a4531f Mon Sep 17 00:00:00 2001 From: Claire Schwarzer Date: Wed, 8 Jul 2026 14:24:34 +0200 Subject: [PATCH] restructure v2 --- CMakeLists.txt | 14 +- .../{assets => Assets}/export_embedded_fbx.py | 0 engine/{assets => Assets}/modelLoader.cpp | 4 +- engine/{assets => Assets}/modelLoader.h | 4 +- engine/Backend/Camera.cpp | 107 +++++++ engine/Backend/Camera.h | 51 ++++ engine/Backend/Mesh.cpp | 101 +++++++ engine/Backend/Mesh.h | 50 ++++ engine/Backend/Model.cpp | 240 ++++++++++++++++ engine/Backend/Model.h | 39 +++ engine/Backend/Shader.cpp | 176 ++++++++++++ engine/Backend/Shader.h | 34 +++ engine/Backend/Texture.cpp | 47 ++++ engine/Backend/Texture.h | 12 + engine/camera.h | 136 --------- engine/mesh.h | 147 ---------- engine/model.h | 260 ------------------ engine/shader.h | 186 ------------- engine/texture.h | 53 ---- game/game.cpp | 40 ++- 20 files changed, 910 insertions(+), 791 deletions(-) rename engine/{assets => Assets}/export_embedded_fbx.py (100%) rename engine/{assets => Assets}/modelLoader.cpp (90%) rename engine/{assets => Assets}/modelLoader.h (63%) create mode 100644 engine/Backend/Camera.cpp create mode 100644 engine/Backend/Camera.h create mode 100644 engine/Backend/Mesh.cpp create mode 100644 engine/Backend/Mesh.h create mode 100644 engine/Backend/Model.cpp create mode 100644 engine/Backend/Model.h create mode 100644 engine/Backend/Shader.cpp create mode 100644 engine/Backend/Shader.h create mode 100644 engine/Backend/Texture.cpp create mode 100644 engine/Backend/Texture.h delete mode 100644 engine/camera.h delete mode 100644 engine/mesh.h delete mode 100644 engine/model.h delete mode 100644 engine/shader.h delete mode 100644 engine/texture.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dbfe28..c645a4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(SOURCES main.cpp game/game.cpp - engine/assets/modelLoader.cpp + engine/Assets/modelLoader.cpp vendor/glad/glad.c vendor/stb/stb_image.cpp engine/Backend/Backend.cpp + engine/Backend/Model.cpp ) set(HEADERS @@ -24,7 +25,16 @@ set(HEADERS engine/mesh.h engine/camera.h game/game.h - engine/assets/modelLoader.h + engine/Assets/modelLoader.h + engine/Backend/Model.h + engine/Backend/Mesh.cpp + engine/Backend/Mesh.h + engine/Backend/Shader.cpp + engine/Backend/Shader.h + engine/Backend/Texture.cpp + engine/Backend/Texture.h + engine/Backend/Camera.cpp + engine/Backend/Camera.h ) # Create the executable diff --git a/engine/assets/export_embedded_fbx.py b/engine/Assets/export_embedded_fbx.py similarity index 100% rename from engine/assets/export_embedded_fbx.py rename to engine/Assets/export_embedded_fbx.py diff --git a/engine/assets/modelLoader.cpp b/engine/Assets/modelLoader.cpp similarity index 90% rename from engine/assets/modelLoader.cpp rename to engine/Assets/modelLoader.cpp index 4a79775..94cc881 100644 --- a/engine/assets/modelLoader.cpp +++ b/engine/Assets/modelLoader.cpp @@ -1,7 +1,9 @@ #include "modelLoader.h" -namespace engine::assets +#include + +namespace engine::Assets { Model loadBlendModel(const char *path) { diff --git a/engine/assets/modelLoader.h b/engine/Assets/modelLoader.h similarity index 63% rename from engine/assets/modelLoader.h rename to engine/Assets/modelLoader.h index d010bd1..c6d721b 100644 --- a/engine/assets/modelLoader.h +++ b/engine/Assets/modelLoader.h @@ -1,8 +1,8 @@ #pragma once -#include "engine/model.h" +#include "engine/Backend/Model.h" -namespace engine::assets +namespace engine::Assets { Model loadBlendModel(const char *path); Model loadModel(const char *path); diff --git a/engine/Backend/Camera.cpp b/engine/Backend/Camera.cpp new file mode 100644 index 0000000..368ac93 --- /dev/null +++ b/engine/Backend/Camera.cpp @@ -0,0 +1,107 @@ + +#include "Camera.h" + +#include + +// constructor with vectors +Camera::Camera(glm::vec3 newPosition, glm::vec3 up, float newYaw, float newPitch) : front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM) +{ + position = newPosition; + worldUp = up; + yaw = newYaw; + pitch = newPitch; + updateCameraVectors(); +} +// constructor with scalar values +Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float newYaw, float newPitch) : front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM) +{ + position = glm::vec3(posX, posY, posZ); + worldUp = glm::vec3(upX, upY, upZ); + yaw = newYaw; + pitch = newPitch; + updateCameraVectors(); +} + +// returns the view matrix calculated using Euler Angles and the LookAt Matrix +glm::mat4 Camera::GetViewMatrix() +{ + return glm::lookAt(position, position + front, up); +} + +// processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) +void Camera::processKeyboard(Camera_Movement direction, float deltaTime) +{ + float velocity = movementSpeed * deltaTime; + if (sprinting) + velocity *= 8; + + if (direction == FORWARD) + position += front * velocity; + if (direction == BACKWARD) + position -= front * velocity; + if (direction == LEFT) + position -= right * velocity; + if (direction == RIGHT) + position += right * velocity; + if (direction == SPRINT) + sprinting = !sprinting; +} + +// processes input received from a mouse input system. Expects the offset value in both the x and y direction. +void Camera::processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch) +{ + xoffset *= mouseSensitivity; + yoffset *= mouseSensitivity; + + yaw += xoffset; + pitch += yoffset; + + // make sure that when pitch is out of bounds, screen doesn't get flipped + if (constrainPitch) + { + if (pitch > 89.0f) + pitch = 89.0f; + + if (pitch < -89.0f) + pitch = -89.0f; + } + + // update Front, Right and Up Vectors using the updated Euler angles + updateCameraVectors(); +} + +// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis +void Camera::processMouseScroll(float yoffset) +{ + zoom -= (float)yoffset; + if (zoom < 1.0f) + zoom = 1.0f; + if (zoom > 45.0f) + zoom = 45.0f; +} + +void Camera::Update(Shader shader, int screenWidth, int screenHeight) +{ + glm::mat4 projection = glm::perspective(glm::radians(zoom), (float)screenWidth / (float)screenHeight, 0.1f, 100.0f); + + shader.setMat4("projection", projection); + shader.setMat4("view", GetViewMatrix()); + shader.setVec3("viewPos", position); +} + +// calculates the front vector from the Camera's (updated) Euler Angles +void Camera::updateCameraVectors() +{ + // calculate the new Front vector + glm::vec3 newFront; + newFront.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); + newFront.y = sin(glm::radians(pitch)); + newFront.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); + front = glm::normalize(newFront); + + // also re-calculate the Right and Up vector + right = glm::normalize(glm::cross(front, worldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. + up = glm::normalize(glm::cross(right, front)); +} + + diff --git a/engine/Backend/Camera.h b/engine/Backend/Camera.h new file mode 100644 index 0000000..a1911b1 --- /dev/null +++ b/engine/Backend/Camera.h @@ -0,0 +1,51 @@ + +#pragma once + +#include "Shader.h" + +#include +#include + +// Movement keys/buttons +enum Camera_Movement { + FORWARD, + BACKWARD, + LEFT, + RIGHT, + SPRINT +}; + +// Default camera values +const float YAW = -90.0f; +const float PITCH = 0.0f; +const float SPEED = 5.5f; +const float SENSITIVITY = 0.1f; +const float ZOOM = 45.0f; + +class Camera +{ +public: + glm::vec3 position, front, up, right, worldUp; + float yaw, pitch; + float movementSpeed, mouseSensitivity, zoom; + bool sprinting = false; + + // constructor with vectors + Camera(glm::vec3 newPosition = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float newYaw = YAW, float newPitch = PITCH); // constructor with scalar values + Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float newYaw, float newPitch); + // returns the view matrix calculated using Euler Angles and the LookAt Matrix + glm::mat4 GetViewMatrix(); + // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) + void processKeyboard(Camera_Movement direction, float deltaTime); + // processes input received from a mouse input system. Expects the offset value in both the x and y direction. + void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true); + + // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis + void processMouseScroll(float yoffset); + void Update(Shader shader, int screenWidth, int screenHeight); + +private: + // calculates the front vector from the Camera's (updated) Euler Angles + void updateCameraVectors(); +}; + diff --git a/engine/Backend/Mesh.cpp b/engine/Backend/Mesh.cpp new file mode 100644 index 0000000..7babc14 --- /dev/null +++ b/engine/Backend/Mesh.cpp @@ -0,0 +1,101 @@ + +#include "Mesh.h" + +#include // holds all OpenGL type declarations +#include +#include +#include + +// constructor +Mesh::Mesh(vector vertices, vector indices, vector textures) +{ + this->vertices = vertices; + this->indices = indices; + this->textures = textures; + + // now that we have all the required data, set the vertex buffers and its attribute pointers. + setupMesh(); +} + +// render the mesh +void Mesh::Draw(Shader &shader) +{ + // bind appropriate textures + unsigned int diffuseNr = 1; + unsigned int specularNr = 1; + unsigned int normalNr = 1; + unsigned int heightNr = 1; + for(unsigned int i = 0; i < textures.size(); i++) + { + glActiveTexture(GL_TEXTURE0 + i); // active proper texture unit before binding + // retrieve texture number (the N in diffuse_textureN) + string number; + string name = textures[i].type; + if(name == "texture_diffuse") + number = std::to_string(diffuseNr++); + else if(name == "texture_specular") + number = std::to_string(specularNr++); // transfer unsigned int to string + else if(name == "texture_normal") + number = std::to_string(normalNr++); // transfer unsigned int to string + else if(name == "texture_height") + number = std::to_string(heightNr++); // transfer unsigned int to string + + // now set the sampler to the correct texture unit + glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i); + // and finally bind the texture + glBindTexture(GL_TEXTURE_2D, textures[i].id); + } + + // draw mesh + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, static_cast(indices.size()), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + + // always good practice to set everything back to defaults once configured. + glActiveTexture(GL_TEXTURE0); +} + +// initializes all the buffer objects/arrays +void Mesh::setupMesh() +{ + // create buffers/arrays + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + glBindVertexArray(VAO); + // load data into vertex buffers + glBindBuffer(GL_ARRAY_BUFFER, VBO); + // A great thing about structs is that their memory layout is sequential for all its items. + // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which + // again translates to 3/2 floats which translates to a byte array. + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); + + // set the vertex attribute pointers + // vertex Positions + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); + // vertex normals + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); + // vertex texture coords + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); + // vertex tangent + glEnableVertexAttribArray(3); + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent)); + // vertex bitangent + glEnableVertexAttribArray(4); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent)); + // ids + glEnableVertexAttribArray(5); + glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, m_BoneIDs)); + + // weights + glEnableVertexAttribArray(6); + glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, m_Weights)); + glBindVertexArray(0); +} diff --git a/engine/Backend/Mesh.h b/engine/Backend/Mesh.h new file mode 100644 index 0000000..c21ae40 --- /dev/null +++ b/engine/Backend/Mesh.h @@ -0,0 +1,50 @@ + +#pragma once + +#include +#include +using namespace std; + +#define MAX_BONE_INFLUENCE 4 + +struct Vertex { + // position + glm::vec3 Position; + // normal + glm::vec3 Normal; + // texCoords + glm::vec2 TexCoords; + // tangent + glm::vec3 Tangent; + // bitangent + glm::vec3 Bitangent; + //bone indexes which will influence this vertex + int m_BoneIDs[MAX_BONE_INFLUENCE]; + //weights from each bone + float m_Weights[MAX_BONE_INFLUENCE]; +}; + +struct Texture { + unsigned int id; + string type; + string path; +}; + +class Mesh { +public: + // mesh Data + vector vertices; + vector indices; + vector textures; + unsigned int VAO; + + Mesh(vector vertices, vector indices, vector textures); + void Draw(Shader &shader); + +private: + // render data + unsigned int VBO, EBO; + + // initializes all the buffer objects/arrays + void setupMesh(); +}; \ No newline at end of file diff --git a/engine/Backend/Model.cpp b/engine/Backend/Model.cpp new file mode 100644 index 0000000..8b908f2 --- /dev/null +++ b/engine/Backend/Model.cpp @@ -0,0 +1,240 @@ + +#include "Model.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +using namespace std; + + +// constructor, expects a filepath to a 3D model. +Model::Model(string const &path, bool gamma) : gammaCorrection(gamma) +{ + loadModel(path); +} + +// draws the model, and thus all its meshes +void Model::Draw(Shader &shader) +{ + for(unsigned int i = 0; i < meshes.size(); i++) + meshes[i].Draw(shader); +} + +// loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector. +void Model::loadModel(string const &path) +{ + // read file via ASSIMP + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); + // check for errors + if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero + { + cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl; + return; + } + // retrieve the directory path of the filepath + directory = path.substr(0, path.find_last_of('/')); + + // process ASSIMP's root node recursively + processNode(scene->mRootNode, scene); +} + +// processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any). +void Model::processNode(aiNode *node, const aiScene *scene) +{ + // process each mesh located at the current node + for(unsigned int i = 0; i < node->mNumMeshes; i++) + { + // the node object only contains indices to index the actual objects in the scene. + // the scene contains all the data, node is just to keep stuff organized (like relations between nodes). + aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; + meshes.push_back(processMesh(mesh, scene)); + } + // after we've processed all of the meshes (if any) we then recursively process each of the children nodes + for(unsigned int i = 0; i < node->mNumChildren; i++) + { + processNode(node->mChildren[i], scene); + } + +} + +Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene) +{ + // data to fill + vector vertices; + vector indices; + vector textures; + + // walk through each of the mesh's vertices + for(unsigned int i = 0; i < mesh->mNumVertices; i++) + { + Vertex vertex; + glm::vec3 vector; // we declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first. + // positions + vector.x = mesh->mVertices[i].x; + vector.y = mesh->mVertices[i].y; + vector.z = mesh->mVertices[i].z; + vertex.Position = vector; + // normals + if (mesh->HasNormals()) + { + vector.x = mesh->mNormals[i].x; + vector.y = mesh->mNormals[i].y; + vector.z = mesh->mNormals[i].z; + vertex.Normal = vector; + } + // texture coordinates + if(mesh->mTextureCoords[0]) // does the mesh contain texture coordinates? + { + glm::vec2 vec; + // a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't + // use models where a vertex can have multiple texture coordinates so we always take the first set (0). + vec.x = mesh->mTextureCoords[0][i].x; + vec.y = mesh->mTextureCoords[0][i].y; + vertex.TexCoords = vec; + // tangent + vector.x = mesh->mTangents[i].x; + vector.y = mesh->mTangents[i].y; + vector.z = mesh->mTangents[i].z; + vertex.Tangent = vector; + // bitangent + vector.x = mesh->mBitangents[i].x; + vector.y = mesh->mBitangents[i].y; + vector.z = mesh->mBitangents[i].z; + vertex.Bitangent = vector; + } + else + vertex.TexCoords = glm::vec2(0.0f, 0.0f); + + vertices.push_back(vertex); + } + // now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices. + for(unsigned int i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + // retrieve all indices of the face and store them in the indices vector + for(unsigned int j = 0; j < face.mNumIndices; j++) + indices.push_back(face.mIndices[j]); + } + // process materials + aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + // we assume a convention for sampler names in the shaders. Each diffuse texture should be named + // as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER. + // Same applies to other texture as the following list summarizes: + // diffuse: texture_diffuseN + // specular: texture_specularN + // normal: texture_normalN + + // 1. diffuse maps + vector diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse", true); + textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); + // 2. specular maps + vector specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular"); + textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); + // 3. normal maps + std::vector normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal"); + textures.insert(textures.end(), normalMaps.begin(), normalMaps.end()); + // 4. height maps + std::vector heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height"); + textures.insert(textures.end(), heightMaps.begin(), heightMaps.end()); + + // return a mesh object created from the extracted mesh data + return Mesh(vertices, indices, textures); +} + +// checks all material textures of a given type and loads the textures if they're not loaded yet. +// the required info is returned as a Texture struct. +vector Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName, bool fallback) +{ + vector textures; + for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) + { + aiString str; + mat->GetTexture(type, i, &str); + // check if texture was loaded before and if so, continue to next iteration: skip loading a new texture + bool skip = false; + for(unsigned int j = 0; j < textures_loaded.size(); j++) + { + if(std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) + { + textures.push_back(textures_loaded[j]); + skip = true; // a texture with the same filepath has already been loaded, continue to next one. (optimization) + break; + } + } + if(!skip) + { // if texture hasn't been loaded already, load it + Texture texture; + texture.id = TextureFromFile(str.C_Str(), this->directory); + texture.type = typeName; + texture.path = str.C_Str(); + textures.push_back(texture); + textures_loaded.push_back(texture); // store it as texture loaded for entire model, to ensure we won't unnecessary load duplicate textures. + } + } + + // Load DefaultTexture if there isn't one already + if (mat->GetTextureCount(type) == 0) { + string defaultPath = "DefaultTexture.jpg"; + + Texture texture; + texture.id = TextureFromFile(defaultPath.c_str(), "assets/"); + texture.type = typeName; + texture.path = defaultPath; + textures.push_back(texture); + textures_loaded.push_back(texture); + } + + return textures; +} + + + +unsigned inline int TextureFromFile(const char *path, const string &directory, bool gamma) +{ + string filename = string(path); + filename = directory + '/' + filename; + + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrComponents; + unsigned char *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0); + if (data) + { + GLenum format; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + return textureID; +} diff --git a/engine/Backend/Model.h b/engine/Backend/Model.h new file mode 100644 index 0000000..b45a72a --- /dev/null +++ b/engine/Backend/Model.h @@ -0,0 +1,39 @@ + +#pragma once + +#include +#include + +#include +#include + +class Model +{ +public: + // model data + vector textures_loaded; // stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once. + vector meshes; + string directory; + bool gammaCorrection; + + // constructor, expects a filepath to a 3D model. + Model(string const &path, bool gamma = false); + + // draws the model, and thus all its meshes + void Draw(Shader &shader); + +private: + // loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector. + void loadModel(string const &path); + + // processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any). + void processNode(aiNode *node, const aiScene *scene); + + Mesh processMesh(aiMesh *mesh, const aiScene *scene); + + // checks all material textures of a given type and loads the textures if they're not loaded yet. + // the required info is returned as a Texture struct. + vector loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName, bool fallback = false); +}; + +unsigned inline int TextureFromFile(const char *path, const string &directory, bool gamma = false); diff --git a/engine/Backend/Shader.cpp b/engine/Backend/Shader.cpp new file mode 100644 index 0000000..78c7934 --- /dev/null +++ b/engine/Backend/Shader.cpp @@ -0,0 +1,176 @@ + +#include "Shader.h" + +#include +#include +#include + + +Shader::Shader(const char* vertexPath, const char* fragmentPath) +{ + std::string vertexCode, fragmentCode; + std::ifstream vertexFile, fragmentFile; + + vertexFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); + fragmentFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); + + try + { + // Open the files + vertexFile.open(vertexPath); + fragmentFile.open(fragmentPath); + std::stringstream vertexStream, fragmentStream; + + // Save the contents to our streams + vertexStream << vertexFile.rdbuf(); + fragmentStream << fragmentFile.rdbuf(); + + // Close files + vertexFile.close(); + fragmentFile.close(); + + // Save the contents to our strings + vertexCode = vertexStream.str(); + fragmentCode = fragmentStream.str(); + + // Fix encoding issues, specifically for Linux + //vertexCode.erase(std::remove(vertexCode.begin(), vertexCode.end(), '\r'), vertexCode.end()); + //fragmentCode.erase(std::remove(fragmentCode.begin(), fragmentCode.end(), '\r'), fragmentCode.end()); + } + catch (std::ifstream::failure& e) + { + std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl; + } + + const char* vertexShaderSource = vertexCode.c_str(); + const char* fragmentShaderSource = fragmentCode.c_str(); + + unsigned int vertexShader; + vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); + glCompileShader(vertexShader); + checkCompileErrors(vertexShader, "VERTEX"); + + unsigned int fragmentShader; + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); + glCompileShader(fragmentShader); + checkCompileErrors(fragmentShader, "FRAGMENT"); + + ID = glCreateProgram(); + glAttachShader(ID, vertexShader); + glAttachShader(ID, fragmentShader); + glLinkProgram(ID); + checkCompileErrors(ID, "PROGRAM"); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); +} + +void Shader::Use() const +{ + glUseProgram(ID); +}; + +// Setters +void Shader::setBool(const std::string &name, bool value) const +{ + glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); +} +// ------------------------------------------------------------------------ +void Shader::setInt(const std::string &name, int value) const +{ + glUniform1i(glGetUniformLocation(ID, name.c_str()), value); +} +// ------------------------------------------------------------------------ +void Shader::setFloat(const std::string &name, float value) const +{ + glUniform1f(glGetUniformLocation(ID, name.c_str()), value); +} +// ------------------------------------------------------------------------ +void Shader::setVec2(const std::string &name, const glm::vec2 &value) const +{ + glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec2(const std::string &name, float x, float y) const +{ + glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y); +} +// ------------------------------------------------------------------------ +void Shader::setVec3(const std::string &name, const glm::vec3 &value) const +{ + glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec3(const std::string &name, float x, float y, float z) const +{ + glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z); +} +// ------------------------------------------------------------------------ +void Shader::setArr(const std::string &name, const GLint value[], const int &count) const +{ + glUniform1iv(glGetUniformLocation(ID, name.c_str()), count, value); +} +// ------------------------------------------------------------------------ +void Shader::setVec4(const std::string &name, const glm::vec4 &value) const +{ + glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec4(const std::string &name, float x, float y, float z, float w) const +{ + glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w); +} +// ------------------------------------------------------------------------ +void Shader::setMat2(const std::string &name, const glm::mat2 &mat) const +{ + glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); +} +// ------------------------------------------------------------------------ +void Shader::setMat3(const std::string &name, const glm::mat3 &mat) const +{ + glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); +} +// ------------------------------------------------------------------------ +void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const +{ + glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); +} + +void Shader::setDecal(int index, GLint tex, float opacity, const glm::vec4& uvCoords) const { + std::string indexStr = std::to_string(index); + + GLint texLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].tex").c_str()); + if (texLoc != -1) glUniform1i(texLoc, tex); + + GLint opLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].opacity").c_str()); + if (opLoc != -1) glUniform1f(opLoc, opacity); + + GLint uvLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].uvCoords").c_str()); + if (uvLoc != -1) { + glUniform4fv(uvLoc, 1, &uvCoords[0]); + } +} + +void Shader::checkCompileErrors(unsigned int shader, std::string type) +{ + int success; + char infoLog[1024]; + if (type != "PROGRAM") + { + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) + { + glGetShaderInfoLog(shader, 1024, NULL, infoLog); + std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; + } + } + else + { + glGetProgramiv(shader, GL_LINK_STATUS, &success); + if (!success) + { + glGetProgramInfoLog(shader, 1024, NULL, infoLog); + std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; + } + } +} + diff --git a/engine/Backend/Shader.h b/engine/Backend/Shader.h new file mode 100644 index 0000000..334b768 --- /dev/null +++ b/engine/Backend/Shader.h @@ -0,0 +1,34 @@ + +#pragma once + +#include +#include +#include + +class Shader +{ +public: + unsigned int ID; + + Shader(const char* vertexPath, const char* fragmentPath); + void Use() const; + + // Setters; + void setBool(const std::string &name, bool value) const; + void setInt(const std::string &name, int value) const; + void setFloat(const std::string &name, float value) const; + void setVec2(const std::string &name, const glm::vec2 &value) const; + void setVec2(const std::string &name, float x, float y) const; + void setVec3(const std::string &name, const glm::vec3 &value) const; + void setVec3(const std::string &name, float x, float y, float z) const; + void setArr(const std::string &name, const GLint value[], const int &count) const; + void setVec4(const std::string &name, const glm::vec4 &value) const; + void setVec4(const std::string &name, float x, float y, float z, float w) const; + void setMat2(const std::string &name, const glm::mat2 &mat) const; + void setMat3(const std::string &name, const glm::mat3 &mat) const; + void setMat4(const std::string &name, const glm::mat4 &mat) const; + void setDecal(int index, GLint tex, float opacity, const glm::vec4& uvCoords) const; + +private: + void checkCompileErrors(unsigned int shader, std::string type); +}; diff --git a/engine/Backend/Texture.cpp b/engine/Backend/Texture.cpp new file mode 100644 index 0000000..50fd112 --- /dev/null +++ b/engine/Backend/Texture.cpp @@ -0,0 +1,47 @@ + +#include "Texture.h" +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +Texture::Texture(const char* texturePath, bool flip) +{ + // Create the Texture and set our ID + glGenTextures(1, &ID); + glBindTexture(GL_TEXTURE_2D, ID); + + // set the texture wrapping/filtering options (on the currently bound texture object) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Grab the texture from our path + if (flip) + stbi_set_flip_vertically_on_load(true); + + unsigned char *data = stbi_load(texturePath, &width, &height, &nrChannels, 0); + if (data) + { + GLenum format = (nrChannels == 4) ? GL_RGBA : GL_RGB; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + } + else + { + std::cout << "Failed to load texture" << std::endl; + } + + if (flip) + stbi_set_flip_vertically_on_load(false); + + stbi_image_free(data); +} + +void Texture::Draw() const +{ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, ID); +} diff --git a/engine/Backend/Texture.h b/engine/Backend/Texture.h new file mode 100644 index 0000000..5869cd5 --- /dev/null +++ b/engine/Backend/Texture.h @@ -0,0 +1,12 @@ + +#pragma once + +class Texture +{ +public: + unsigned int ID; + int width, height, nrChannels; + + Texture(const char* texturePath, bool flip = false); + void Draw() const; +}; diff --git a/engine/camera.h b/engine/camera.h deleted file mode 100644 index c9586f1..0000000 --- a/engine/camera.h +++ /dev/null @@ -1,136 +0,0 @@ - -#ifndef ENGINE2026_CAMERA_H -#define ENGINE2026_CAMERA_H - -#include -#include -#include - -// Movement keys/buttons -enum Camera_Movement { - FORWARD, - BACKWARD, - LEFT, - RIGHT, - SPRINT -}; - -// Default camera values -const float YAW = -90.0f; -const float PITCH = 0.0f; -const float SPEED = 5.5f; -const float SENSITIVITY = 0.1f; -const float ZOOM = 45.0f; - -class Camera -{ -public: - glm::vec3 position, front, up, right, worldUp; - float yaw, pitch; - float movementSpeed, mouseSensitivity, zoom; - bool sprinting = false; - - // constructor with vectors - Camera(glm::vec3 newPosition = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float newYaw = YAW, float newPitch = PITCH) : front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM) - { - position = newPosition; - worldUp = up; - yaw = newYaw; - pitch = newPitch; - updateCameraVectors(); - } - // constructor with scalar values - Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float newYaw, float newPitch) : front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM) - { - position = glm::vec3(posX, posY, posZ); - worldUp = glm::vec3(upX, upY, upZ); - yaw = newYaw; - pitch = newPitch; - updateCameraVectors(); - } - - // returns the view matrix calculated using Euler Angles and the LookAt Matrix - glm::mat4 GetViewMatrix() - { - return glm::lookAt(position, position + front, up); - } - - // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) - void processKeyboard(Camera_Movement direction, float deltaTime) - { - float velocity = movementSpeed * deltaTime; - if (sprinting) - velocity *= 8; - - if (direction == FORWARD) - position += front * velocity; - if (direction == BACKWARD) - position -= front * velocity; - if (direction == LEFT) - position -= right * velocity; - if (direction == RIGHT) - position += right * velocity; - if (direction == SPRINT) - sprinting = !sprinting; - } - - // processes input received from a mouse input system. Expects the offset value in both the x and y direction. - void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) - { - xoffset *= mouseSensitivity; - yoffset *= mouseSensitivity; - - yaw += xoffset; - pitch += yoffset; - - // make sure that when pitch is out of bounds, screen doesn't get flipped - if (constrainPitch) - { - if (pitch > 89.0f) - pitch = 89.0f; - - if (pitch < -89.0f) - pitch = -89.0f; - } - - // update Front, Right and Up Vectors using the updated Euler angles - updateCameraVectors(); - } - - // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis - void processMouseScroll(float yoffset) - { - zoom -= (float)yoffset; - if (zoom < 1.0f) - zoom = 1.0f; - if (zoom > 45.0f) - zoom = 45.0f; - } - - void Update(Shader shader, int screenWidth, int screenHeight) - { - glm::mat4 projection = glm::perspective(glm::radians(zoom), (float)screenWidth / (float)screenHeight, 0.1f, 100.0f); - - shader.setMat4("projection", projection); - shader.setMat4("view", GetViewMatrix()); - shader.setVec3("viewPos", position); - } - -private: - // calculates the front vector from the Camera's (updated) Euler Angles - void updateCameraVectors() - { - // calculate the new Front vector - glm::vec3 newFront; - newFront.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); - newFront.y = sin(glm::radians(pitch)); - newFront.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); - front = glm::normalize(newFront); - - // also re-calculate the Right and Up vector - right = glm::normalize(glm::cross(front, worldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. - up = glm::normalize(glm::cross(right, front)); - } -}; - -#endif \ No newline at end of file diff --git a/engine/mesh.h b/engine/mesh.h deleted file mode 100644 index e121767..0000000 --- a/engine/mesh.h +++ /dev/null @@ -1,147 +0,0 @@ - -#ifndef ENGINE2026_MESH_H -#define ENGINE2026_MESH_H - -#include // holds all OpenGL type declarations - -#include -#include - -#include - -#include -#include -using namespace std; - -#define MAX_BONE_INFLUENCE 4 - -struct Vertex { - // position - glm::vec3 Position; - // normal - glm::vec3 Normal; - // texCoords - glm::vec2 TexCoords; - // tangent - glm::vec3 Tangent; - // bitangent - glm::vec3 Bitangent; - //bone indexes which will influence this vertex - int m_BoneIDs[MAX_BONE_INFLUENCE]; - //weights from each bone - float m_Weights[MAX_BONE_INFLUENCE]; -}; - -struct Texture { - unsigned int id; - string type; - string path; -}; - -class Mesh { -public: - // mesh Data - vector vertices; - vector indices; - vector textures; - unsigned int VAO; - - // constructor - Mesh(vector vertices, vector indices, vector textures) - { - this->vertices = vertices; - this->indices = indices; - this->textures = textures; - - // now that we have all the required data, set the vertex buffers and its attribute pointers. - setupMesh(); - } - - // render the mesh - void Draw(Shader &shader) - { - // bind appropriate textures - unsigned int diffuseNr = 1; - unsigned int specularNr = 1; - unsigned int normalNr = 1; - unsigned int heightNr = 1; - for(unsigned int i = 0; i < textures.size(); i++) - { - glActiveTexture(GL_TEXTURE0 + i); // active proper texture unit before binding - // retrieve texture number (the N in diffuse_textureN) - string number; - string name = textures[i].type; - if(name == "texture_diffuse") - number = std::to_string(diffuseNr++); - else if(name == "texture_specular") - number = std::to_string(specularNr++); // transfer unsigned int to string - else if(name == "texture_normal") - number = std::to_string(normalNr++); // transfer unsigned int to string - else if(name == "texture_height") - number = std::to_string(heightNr++); // transfer unsigned int to string - - // now set the sampler to the correct texture unit - glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i); - // and finally bind the texture - glBindTexture(GL_TEXTURE_2D, textures[i].id); - } - - // draw mesh - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES, static_cast(indices.size()), GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - - // always good practice to set everything back to defaults once configured. - glActiveTexture(GL_TEXTURE0); - } - -private: - // render data - unsigned int VBO, EBO; - - // initializes all the buffer objects/arrays - void setupMesh() - { - // create buffers/arrays - glGenVertexArrays(1, &VAO); - glGenBuffers(1, &VBO); - glGenBuffers(1, &EBO); - - glBindVertexArray(VAO); - // load data into vertex buffers - glBindBuffer(GL_ARRAY_BUFFER, VBO); - // A great thing about structs is that their memory layout is sequential for all its items. - // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which - // again translates to 3/2 floats which translates to a byte array. - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); - - // set the vertex attribute pointers - // vertex Positions - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); - // vertex normals - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); - // vertex texture coords - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); - // vertex tangent - glEnableVertexAttribArray(3); - glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent)); - // vertex bitangent - glEnableVertexAttribArray(4); - glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent)); - // ids - glEnableVertexAttribArray(5); - glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, m_BoneIDs)); - - // weights - glEnableVertexAttribArray(6); - glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, m_Weights)); - glBindVertexArray(0); - } -}; -#endif //ENGINE2026_MESH_H diff --git a/engine/model.h b/engine/model.h deleted file mode 100644 index 5da461f..0000000 --- a/engine/model.h +++ /dev/null @@ -1,260 +0,0 @@ - -#ifndef ENGINE2026_MODEL_H -#define ENGINE2026_MODEL_H - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -unsigned int TextureFromFile(const char *path, const string &directory, bool gamma = false); - -class Model -{ -public: - // model data - vector textures_loaded; // stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once. - vector meshes; - string directory; - bool gammaCorrection; - - // constructor, expects a filepath to a 3D model. - Model(string const &path, bool gamma = false) : gammaCorrection(gamma) - { - loadModel(path); - } - - // draws the model, and thus all its meshes - void Draw(Shader &shader) - { - for(unsigned int i = 0; i < meshes.size(); i++) - meshes[i].Draw(shader); - } - -private: - // loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector. - void loadModel(string const &path) - { - // read file via ASSIMP - Assimp::Importer importer; - const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); - // check for errors - if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero - { - cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl; - return; - } - // retrieve the directory path of the filepath - directory = path.substr(0, path.find_last_of('/')); - - // process ASSIMP's root node recursively - processNode(scene->mRootNode, scene); - } - - // processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any). - void processNode(aiNode *node, const aiScene *scene) - { - // process each mesh located at the current node - for(unsigned int i = 0; i < node->mNumMeshes; i++) - { - // the node object only contains indices to index the actual objects in the scene. - // the scene contains all the data, node is just to keep stuff organized (like relations between nodes). - aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; - meshes.push_back(processMesh(mesh, scene)); - } - // after we've processed all of the meshes (if any) we then recursively process each of the children nodes - for(unsigned int i = 0; i < node->mNumChildren; i++) - { - processNode(node->mChildren[i], scene); - } - - } - - Mesh processMesh(aiMesh *mesh, const aiScene *scene) - { - // data to fill - vector vertices; - vector indices; - vector textures; - - // walk through each of the mesh's vertices - for(unsigned int i = 0; i < mesh->mNumVertices; i++) - { - Vertex vertex; - glm::vec3 vector; // we declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first. - // positions - vector.x = mesh->mVertices[i].x; - vector.y = mesh->mVertices[i].y; - vector.z = mesh->mVertices[i].z; - vertex.Position = vector; - // normals - if (mesh->HasNormals()) - { - vector.x = mesh->mNormals[i].x; - vector.y = mesh->mNormals[i].y; - vector.z = mesh->mNormals[i].z; - vertex.Normal = vector; - } - // texture coordinates - if(mesh->mTextureCoords[0]) // does the mesh contain texture coordinates? - { - glm::vec2 vec; - // a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't - // use models where a vertex can have multiple texture coordinates so we always take the first set (0). - vec.x = mesh->mTextureCoords[0][i].x; - vec.y = mesh->mTextureCoords[0][i].y; - vertex.TexCoords = vec; - // tangent - vector.x = mesh->mTangents[i].x; - vector.y = mesh->mTangents[i].y; - vector.z = mesh->mTangents[i].z; - vertex.Tangent = vector; - // bitangent - vector.x = mesh->mBitangents[i].x; - vector.y = mesh->mBitangents[i].y; - vector.z = mesh->mBitangents[i].z; - vertex.Bitangent = vector; - } - else - vertex.TexCoords = glm::vec2(0.0f, 0.0f); - - vertices.push_back(vertex); - } - // now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices. - for(unsigned int i = 0; i < mesh->mNumFaces; i++) - { - aiFace face = mesh->mFaces[i]; - // retrieve all indices of the face and store them in the indices vector - for(unsigned int j = 0; j < face.mNumIndices; j++) - indices.push_back(face.mIndices[j]); - } - // process materials - aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - // we assume a convention for sampler names in the shaders. Each diffuse texture should be named - // as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER. - // Same applies to other texture as the following list summarizes: - // diffuse: texture_diffuseN - // specular: texture_specularN - // normal: texture_normalN - - // 1. diffuse maps - vector diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse", true); - textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); - // 2. specular maps - vector specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular"); - textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); - // 3. normal maps - std::vector normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal"); - textures.insert(textures.end(), normalMaps.begin(), normalMaps.end()); - // 4. height maps - std::vector heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height"); - textures.insert(textures.end(), heightMaps.begin(), heightMaps.end()); - - // return a mesh object created from the extracted mesh data - return Mesh(vertices, indices, textures); - } - - // checks all material textures of a given type and loads the textures if they're not loaded yet. - // the required info is returned as a Texture struct. - vector loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName, bool fallback = false) - { - vector textures; - for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) - { - aiString str; - mat->GetTexture(type, i, &str); - // check if texture was loaded before and if so, continue to next iteration: skip loading a new texture - bool skip = false; - for(unsigned int j = 0; j < textures_loaded.size(); j++) - { - if(std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) - { - textures.push_back(textures_loaded[j]); - skip = true; // a texture with the same filepath has already been loaded, continue to next one. (optimization) - break; - } - } - if(!skip) - { // if texture hasn't been loaded already, load it - Texture texture; - texture.id = TextureFromFile(str.C_Str(), this->directory); - texture.type = typeName; - texture.path = str.C_Str(); - textures.push_back(texture); - textures_loaded.push_back(texture); // store it as texture loaded for entire model, to ensure we won't unnecessary load duplicate textures. - } - } - - // Load DefaultTexture if there isn't one already - if (mat->GetTextureCount(type) == 0) { - string defaultPath = "DefaultTexture.jpg"; - - Texture texture; - texture.id = TextureFromFile(defaultPath.c_str(), "assets/"); - texture.type = typeName; - texture.path = defaultPath; - textures.push_back(texture); - textures_loaded.push_back(texture); - } - - return textures; - } -}; - - -unsigned inline int TextureFromFile(const char *path, const string &directory, bool gamma) -{ - string filename = string(path); - filename = directory + '/' + filename; - - unsigned int textureID; - glGenTextures(1, &textureID); - - int width, height, nrComponents; - unsigned char *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0); - if (data) - { - GLenum format; - if (nrComponents == 1) - format = GL_RED; - else if (nrComponents == 3) - format = GL_RGB; - else if (nrComponents == 4) - format = GL_RGBA; - - glBindTexture(GL_TEXTURE_2D, textureID); - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - stbi_image_free(data); - } - else - { - std::cout << "Texture failed to load at path: " << path << std::endl; - stbi_image_free(data); - } - - return textureID; -} -#endif //ENGINE2026_MODEL_H \ No newline at end of file diff --git a/engine/shader.h b/engine/shader.h deleted file mode 100644 index c2ce31e..0000000 --- a/engine/shader.h +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef SHADER_H -#define SHADER_H - -#include - -#include -#include -#include -#include - -class Shader -{ -public: - // Compiled Shader Program ID - unsigned int ID; - - Shader(const char* vertexPath, const char* fragmentPath) - { - std::string vertexCode, fragmentCode; - std::ifstream vertexFile, fragmentFile; - - vertexFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); - fragmentFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); - - try - { - // Open the files - vertexFile.open(vertexPath); - fragmentFile.open(fragmentPath); - std::stringstream vertexStream, fragmentStream; - - // Save the contents to our streams - vertexStream << vertexFile.rdbuf(); - fragmentStream << fragmentFile.rdbuf(); - - // Close files - vertexFile.close(); - fragmentFile.close(); - - // Save the contents to our strings - vertexCode = vertexStream.str(); - fragmentCode = fragmentStream.str(); - - // Fix encoding issues, specifically for Linux - //vertexCode.erase(std::remove(vertexCode.begin(), vertexCode.end(), '\r'), vertexCode.end()); - //fragmentCode.erase(std::remove(fragmentCode.begin(), fragmentCode.end(), '\r'), fragmentCode.end()); - } - catch (std::ifstream::failure& e) - { - std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl; - } - - const char* vertexShaderSource = vertexCode.c_str(); - const char* fragmentShaderSource = fragmentCode.c_str(); - - unsigned int vertexShader; - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); - glCompileShader(vertexShader); - checkCompileErrors(vertexShader, "VERTEX"); - - unsigned int fragmentShader; - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); - glCompileShader(fragmentShader); - checkCompileErrors(fragmentShader, "FRAGMENT"); - - ID = glCreateProgram(); - glAttachShader(ID, vertexShader); - glAttachShader(ID, fragmentShader); - glLinkProgram(ID); - checkCompileErrors(ID, "PROGRAM"); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - } - - void Use() - { - glUseProgram(ID); - }; - - // Setters - void setBool(const std::string &name, bool value) const - { - glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); - } - // ------------------------------------------------------------------------ - void setInt(const std::string &name, int value) const - { - glUniform1i(glGetUniformLocation(ID, name.c_str()), value); - } - // ------------------------------------------------------------------------ - void setFloat(const std::string &name, float value) const - { - glUniform1f(glGetUniformLocation(ID, name.c_str()), value); - } - // ------------------------------------------------------------------------ - void setVec2(const std::string &name, const glm::vec2 &value) const - { - glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); - } - void setVec2(const std::string &name, float x, float y) const - { - glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y); - } - // ------------------------------------------------------------------------ - void setVec3(const std::string &name, const glm::vec3 &value) const - { - glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); - } - void setVec3(const std::string &name, float x, float y, float z) const - { - glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z); - } - // ------------------------------------------------------------------------ - void setArr(const std::string &name, const GLint value[], const int &count) const - { - glUniform1iv(glGetUniformLocation(ID, name.c_str()), count, value); - } - // ------------------------------------------------------------------------ - void setVec4(const std::string &name, const glm::vec4 &value) const - { - glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); - } - void setVec4(const std::string &name, float x, float y, float z, float w) const - { - glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w); - } - // ------------------------------------------------------------------------ - void setMat2(const std::string &name, const glm::mat2 &mat) const - { - glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); - } - // ------------------------------------------------------------------------ - void setMat3(const std::string &name, const glm::mat3 &mat) const - { - glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); - } - // ------------------------------------------------------------------------ - void setMat4(const std::string &name, const glm::mat4 &mat) const - { - glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); - } - - void setDecal(int index, GLint tex, float opacity, const glm::vec4& uvCoords) const { - std::string indexStr = std::to_string(index); - - GLint texLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].tex").c_str()); - if (texLoc != -1) glUniform1i(texLoc, tex); - - GLint opLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].opacity").c_str()); - if (opLoc != -1) glUniform1f(opLoc, opacity); - - GLint uvLoc = glGetUniformLocation(ID, ("decals[" + indexStr + "].uvCoords").c_str()); - if (uvLoc != -1) { - glUniform4fv(uvLoc, 1, &uvCoords[0]); - } - } -private: - void checkCompileErrors(unsigned int shader, std::string type) - { - int success; - char infoLog[1024]; - if (type != "PROGRAM") - { - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) - { - glGetShaderInfoLog(shader, 1024, NULL, infoLog); - std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; - } - } - else - { - glGetProgramiv(shader, GL_LINK_STATUS, &success); - if (!success) - { - glGetProgramInfoLog(shader, 1024, NULL, infoLog); - std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl; - } - } - } -}; - -#endif \ No newline at end of file diff --git a/engine/texture.h b/engine/texture.h deleted file mode 100644 index 21b20fc..0000000 --- a/engine/texture.h +++ /dev/null @@ -1,53 +0,0 @@ - -#include - -#include -#define STB_IMAGE_IMPLEMENTATION -#include - -class Texture -{ -public: - unsigned int ID; - int width, height, nrChannels; - - Texture(const char* texturePath, bool flip = false) - { - // Create the Texture and set our ID - glGenTextures(1, &ID); - glBindTexture(GL_TEXTURE_2D, ID); - - // set the texture wrapping/filtering options (on the currently bound texture object) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - // Grab the texture from our path - if (flip) - stbi_set_flip_vertically_on_load(true); - - unsigned char *data = stbi_load(texturePath, &width, &height, &nrChannels, 0); - if (data) - { - GLenum format = (nrChannels == 4) ? GL_RGBA : GL_RGB; - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - } - else - { - std::cout << "Failed to load texture" << std::endl; - } - - if (flip) - stbi_set_flip_vertically_on_load(false); - - stbi_image_free(data); - } - - void Draw() const - { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, ID); - } -}; diff --git a/game/game.cpp b/game/game.cpp index 95a4c8f..d4078c4 100644 --- a/game/game.cpp +++ b/game/game.cpp @@ -1,23 +1,54 @@ -#include +#include +#include +#include + #include +#include + +#include namespace game { + const int SCREEN_WIDTH = 1366; + const int SCREEN_HEIGHT = 768; + + std::optional shader; + std::optional model; + std::optional camera; + + float lastX = SCREEN_WIDTH / 2.0f; + float lastY = SCREEN_HEIGHT / 2.0f; + bool firstMouse = true; + bool Init() { + shader.emplace("assets/shaders/basicVertex.vert", "assets/shaders/basicFragment.frag"); + shader->Use(); + + camera.emplace(glm::vec3(0.0f, 0.0f, 0.0f)); + model.emplace("assets/models/sponza/Sponza.gltf"); + return true; } void PreFrame() { + glClearColor(0.2f, 0.3f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + shader->Use(); + + camera->Update(shader.value(), SCREEN_WIDTH, SCREEN_HEIGHT); } void Render() { - glClearColor(0.2f, 0.3f, 0.3f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + glm::mat4 modelTransform = glm::mat4(1.0f); + modelTransform = glm::translate(modelTransform, glm::vec3(0.0f, 0.0f, 0.0f)); + modelTransform = glm::scale(modelTransform, glm::vec3(0.02f, 0.02f, 0.02f)); + shader->setMat4("model", modelTransform); + model->Draw(shader.value()); } void Update() @@ -34,4 +65,5 @@ namespace game { } -} \ No newline at end of file +} +