#ifndef CLIENT_RENDER_MODEL_HH_ #define CLIENT_RENDER_MODEL_HH_ #include #include #include #include #include #include #include #include #include "client/render/program.hh" #include "client/render/texture.hh" namespace client { namespace render { struct vertex { glm::vec3 position; glm::vec3 normal; glm::vec2 texture; }; struct texture_info { unsigned id; std::string type; std::string path; }; class model { private: struct mesh { private: GLuint vao; GLuint vbo; GLuint ebo; public: std::vector vertices; std::vector indices; std::vector textures; private: void setup() noexcept { glGenVertexArrays(1, &this->vao); glGenBuffers(1, &this->vbo); glGenBuffers(1, &this->ebo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, std::size(this->vertices) * sizeof(vertex), std::data(this->vertices), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, std::size(this->indices) * sizeof(GLuint), std::data(this->indices), GL_STATIC_DRAW); // The reinterpret casts might be wrong here. // positions glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), nullptr); // normals LMAO glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), reinterpret_cast(sizeof(glm::vec3))); // texture glEnableVertexAttribArray(2); glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), reinterpret_cast(sizeof(glm::vec3) + sizeof(glm::vec3))); } public: mesh(const std::vector& v, const std::vector& i, const std::vector& t) noexcept : vertices(v), indices(i), textures(t) { setup(); } // Not going to bother with textures yet. void draw(const client::render::program& program, const glm::mat4& matrix) const noexcept { glUseProgram(program); static const GLint u_matrix{ glGetUniformLocation(program, "_u_matrix")}; glBindVertexArray(this->vao); glUniformMatrix4fv(u_matrix, 1, GL_FALSE, glm::value_ptr(matrix)); glDrawElements(GL_TRIANGLES, std::size(this->indices), GL_UNSIGNED_INT, nullptr); } }; private: std::vector meshes; std::string directory; private: unsigned int texture_from_file(const std::string& path, const std::string& dir) noexcept { const std::string filename = dir + '/' + path; const client::render::texture texture{filename}; unsigned int tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0, texture.channels == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, texture.image); glGenerateMipmap(GL_TEXTURE_2D); return tex; } std::vector load_material_textures(const aiMaterial* const material, const aiTextureType type, const std::string& name) noexcept { std::vector textures; for (unsigned i = 0; i < material->GetTextureCount(type); ++i) { aiString str; material->GetTexture(type, i, &str); struct texture_info texture; texture.id = texture_from_file(str.C_Str(), directory); texture.type = name; texture.path = str.data; textures.push_back(std::move(texture)); } return textures; } mesh process_mesh(const aiMesh* mesh, const aiScene* scene) noexcept { std::vector vertices; std::vector indices; std::vector textures; for (unsigned i = 0; i < mesh->mNumVertices; ++i) { vertex v; glm::vec3 vector; vector.x = mesh->mVertices[i].x; vector.y = mesh->mVertices[i].y; vector.z = mesh->mVertices[i].z; v.position = vector; vector.x = mesh->mNormals[i].x; vector.y = mesh->mNormals[i].y; vector.z = mesh->mNormals[i].z; v.normal = vector; if (mesh->mTextureCoords[0]) { glm::vec2 vec; vec.x = mesh->mTextureCoords[0][i].x; vec.y = mesh->mTextureCoords[0][i].x; v.texture = vec; } else { v.texture = glm::vec2{0.0f}; } vector.x = mesh->mNormals[i].x; vector.y = mesh->mNormals[i].y; vector.z = mesh->mNormals[i].z; vertices.push_back(std::move(v)); } for (unsigned i = 0; i < mesh->mNumFaces; ++i) { aiFace face = mesh->mFaces[i]; for (unsigned j = 0; j < face.mNumIndices; ++j) { indices.push_back(face.mIndices[j]); } } if (mesh->mMaterialIndex >= 0) { const aiMaterial* const material = scene->mMaterials[mesh->mMaterialIndex]; std::ranges::copy(load_material_textures(material, aiTextureType_DIFFUSE, "texture_diffuse"), std::back_inserter(textures)); std::ranges::copy(load_material_textures(material, aiTextureType_SPECULAR, "texture_specular"), std::back_inserter(textures)); } return {vertices, indices, textures}; } void process_node(const aiNode* node, const aiScene* scene) noexcept { for (unsigned i = 0; i < node->mNumMeshes; ++i) { const aiMesh* const mesh = scene->mMeshes[node->mMeshes[i]]; meshes.push_back(process_mesh(mesh, scene)); } for (unsigned i = 0; i < node->mNumChildren; ++i) { process_node(node->mChildren[i], scene); } } void load(const std::string& path) noexcept { Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); if (scene == nullptr || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || scene->mRootNode == nullptr) { throw std::runtime_error("assimp error: " + std::string(importer.GetErrorString())); } directory = path; // AHHHHHHHhh process_node(scene->mRootNode, scene); } public: model(const std::string& path) noexcept { load(path); } void draw(const client::render::program& program, const glm::mat4& matrix) noexcept { for (const auto& mesh : meshes) { mesh.draw(program, matrix); } } }; } // namespace render } // namespace client #endif