#include "modelManager.h" #include <algorithm> #include <cassert> #include <fstream> #include <iostream> #include <sstream> bool ModelManager::init(const std::string& modelPreloadPath) { std::ifstream inFile(modelPreloadPath); if (!inFile) { std::cerr << "Unable to open file: " << modelPreloadPath << '\n'; return false; } std::shared_ptr<Model> temp; while (!inFile.eof()) { std::string modelName; inFile >> modelName; if (!modelName.empty()) { std::cout << "Loading Model: " << modelName << '\n'; temp = load(modelName); if (temp == nullptr) { std::cerr << "Failed to load model: " << modelName << '\n'; return false; } // Allocate ID relative to location in the vector temp->ID = m_models.size(); // Store model name for future reference temp->name = modelName; // Store pointer to the model m_models.push_back(temp); } } VBOSort(); findBoundingSpheres(); return true; } std::shared_ptr<Model> ModelManager::load(const std::string& modelName) { std::string fileType = modelName.substr(modelName.find_last_of('.') + 1); if (fileType == "obj") { return OBJ_LOADER::load(modelName); } else if (fileType == "off") { return OFF_LOADER::load(modelName); } else { std::cerr << "File extension not supported: " << fileType << " for model: " << modelName << '\n'; return nullptr; } } std::shared_ptr<Model> ModelManager::getModel(std::size_t ID) { if (ID < m_models.size() && !m_models.empty()) { return m_models[ID]; } std::cerr << "ModelManager::getModel: ID (" << ID << ") out of range\n"; return nullptr; } std::shared_ptr<Model> ModelManager::getModel(const std::string& name) { auto model = std::find_if(begin(m_models), end(m_models), [&](const std::shared_ptr<Model>& incModel) { return incModel->name == name; }); if (model != m_models.end()) { return *model; } else { return nullptr; } } void Model::draw() const { if (vboData.vboSafe) { retainedModeDraw(); } else { immediateModeDraw(); } } void Model::immediateModeDraw() const { for (auto& face : mesh.faces) { glPushAttrib(GL_TEXTURE_BIT | GL_LIGHTING_BIT | GL_CURRENT_BIT); if (glm::length(face->colour) != 0) { glColor3ub((GLubyte)face->colour.r, (GLubyte)face->colour.g, (GLubyte)face->colour.b); } if (face->hasMaterial && !mesh.material.empty()) { glColor4fv(mesh.material[face->materialID]->diffuse); if (mesh.material[face->materialID]->textureID != 0) { glBindTexture(GL_TEXTURE_2D, mesh.material[face->materialID]->textureID); } } if (face->vertIndices.size() == 3) { glBegin(GL_TRIANGLES); } else if (face->vertIndices.size() == 4) { glBegin(GL_QUADS); } else { glBegin(GL_POLYGON); } for (std::size_t i = 0; i < face->vertIndices.size(); ++i) { glNormal3f(mesh.normals[face->normalIndices[i]].x, mesh.normals[face->normalIndices[i]].y, mesh.normals[face->normalIndices[i]].z); if (!face->texCoordsIndices.empty()) { glTexCoord2f(mesh.texCoords[face->texCoordsIndices[i]].x, mesh.texCoords[face->texCoordsIndices[i]].y); } glVertex3f(mesh.verts[face->vertIndices[i]].x, mesh.verts[face->vertIndices[i]].y, mesh.verts[face->vertIndices[i]].z); } glEnd(); glPopAttrib(); // Unbind bound texture if (mesh.material[face->materialID]->textureID != 0) { glBindTexture(GL_TEXTURE_2D, 0); } } } void Model::retainedModeDraw() const { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); // Bind verts glBindBuffer(GL_ARRAY_BUFFER, vboData.vboID); glVertexPointer(3, GL_FLOAT, 0, 0); // Bind normals glBindBuffer(GL_ARRAY_BUFFER, vboData.nboID); glNormalPointer(GL_FLOAT, 0, 0); // Bind colours glBindBuffer(GL_ARRAY_BUFFER, vboData.cboID); glColorPointer(3, GL_FLOAT, 0, 0); // Draw indices glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboData.iboID); glDrawElements(GL_TRIANGLES, vboData.totalBytes, GL_UNSIGNED_INT, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); } void ModelManager::VBOSort() { typedef GLfloat float_3[3]; float_3* tmp_vertices; float_3* tmp_normals; float_3* tmp_colours; GLuint* tmp_faces; std::size_t points_x2{6}; for (auto& model : m_models) { // Only process models who have only triangle faces and who don't have a // buffer assigned if (model->vboData.vboSafe && model->vboData.vboID == 0) { // Storing data per vert atm. std::size_t size = model->mesh.verts.size(); if (size < model->mesh.normals.size()) { size = model->mesh.normals.size(); } size *= points_x2; tmp_vertices = new float_3[size]; tmp_normals = new float_3[size]; tmp_colours = new float_3[size]; tmp_faces = new GLuint[model->mesh.faces.size() * 3]; model->vboData.totalBytes = (GLsizei)(size * 3 * sizeof(GLfloat)); int faceIndex{0}; for (auto& face : model->mesh.faces) { for (std::size_t index = 0; index < 3; ++index) { tmp_vertices[faceIndex] [0] = model->mesh .verts[face->vertIndices[index]] .x; tmp_vertices[faceIndex] [1] = model->mesh .verts[face->vertIndices[index]] .y; tmp_vertices[faceIndex] [2] = model->mesh .verts[face->vertIndices[index]] .z; tmp_normals[faceIndex] [0] = model->mesh .normals[face->normalIndices[index]] .x; tmp_normals[faceIndex] [1] = model->mesh .normals[face->normalIndices[index]] .y; tmp_normals[faceIndex] [2] = model->mesh .normals[face->normalIndices[index]] .z; tmp_colours[faceIndex][0] = model->mesh .material[face->materialID] ->diffuse[0]; tmp_colours[faceIndex][1] = model->mesh .material[face->materialID] ->diffuse[1]; tmp_colours[faceIndex][2] = model->mesh .material[face->materialID] ->diffuse[2]; tmp_faces[faceIndex] = faceIndex; ++faceIndex; } } glGenBuffers(1, &model->vboData.vboID); glGenBuffers(1, &model->vboData.iboID); glGenBuffers(1, &model->vboData.nboID); glGenBuffers(1, &model->vboData.cboID); // Vertex data glBindBuffer(GL_ARRAY_BUFFER, model->vboData.vboID); glBufferData(GL_ARRAY_BUFFER, model->vboData.totalBytes, tmp_vertices, GL_STATIC_DRAW); // Normal data glBindBuffer(GL_ARRAY_BUFFER, model->vboData.nboID); glBufferData(GL_ARRAY_BUFFER, model->vboData.totalBytes, tmp_normals, GL_STATIC_DRAW); // Colour data glBindBuffer(GL_ARRAY_BUFFER, model->vboData.cboID); glBufferData(GL_ARRAY_BUFFER, model->vboData.totalBytes, tmp_colours, GL_STATIC_DRAW); // Indices glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->vboData.iboID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(GLuint) * model->mesh.faces.size() * 3), tmp_faces, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); delete[] tmp_faces; delete[] tmp_vertices; delete[] tmp_normals; delete[] tmp_colours; tmp_faces = nullptr; tmp_vertices = nullptr; tmp_normals = nullptr; tmp_colours = nullptr; } } } void ModelManager::findBoundingSpheres() { for (auto& model : m_models) { if (model->boundingSphere.radius == 0) { // Find centre of model for (auto& vert : model->mesh.verts) { model->boundingSphere.centre += vert; } model->boundingSphere.centre /= model->mesh.verts.size(); model->boundingSphere.centreNoY = model->boundingSphere.centre; model->boundingSphere.centreNoY.y = 0; bool first{true}; bool firstNoY{true}; for (auto& vert : model->mesh.verts) { // Max float tempRadius = glm::abs( glm::distance(vert, model->boundingSphere.centre)); if (first) { model->boundingSphere.radius = tempRadius; first = false; } else if (model->boundingSphere.radius < tempRadius) { model->boundingSphere.radius = tempRadius; } // No y glm::vec3 vertNoY{vert}; vertNoY.y = 0; float tempRadiusNoY = glm::abs( glm::distance(vertNoY, model->boundingSphere.centreNoY)); if (firstNoY) { model->boundingSphere.radiusNoY = tempRadiusNoY; firstNoY = false; } else if (model->boundingSphere.radiusNoY < tempRadiusNoY) { model->boundingSphere.radiusNoY = tempRadiusNoY; } // Average model->boundingSphere.radiusAverage += glm::abs( glm::distance(vert, model->boundingSphere.centre)); } model->boundingSphere.radiusAverage /= (float) model->mesh.verts.size(); } } } namespace OBJ_LOADER { std::shared_ptr<Model> load(const std::string& modelName) { std::ifstream inFile(MODEL_PATH_OBJ + modelName); if (!inFile) { std::cerr << "Failed to open " << MODEL_PATH_OBJ << modelName << '\n'; return nullptr; } auto newModel = std::make_shared<Model>(); if (modelName.find('/') != std::string::npos) { newModel->subPath = modelName.substr(0, modelName.find_first_of('/') + 1); } std::string temp; char garbage; std::string identifier; std::size_t currentMtlID{0}; glm::vec3 vec3; glm::vec2 vec2; bool hasMaterial{false}; std::size_t tempVal; for (std::string input; std::getline(inFile, input);) { identifier = input.substr(0, input.find_first_of(' ') + 1); identifier.erase(remove(identifier.begin(), identifier.end(), ' '), identifier.end()); // Skip line if a comment or empty if (identifier != "#" && identifier != "\n" && !input.empty()) { input = input.substr(input.find_first_of(' ') + 1, input.size() - 1); std::stringstream ss(input); if (identifier == "mtllib") { ss >> temp; MTL_LOADER::load(temp, newModel); } else if (identifier == "usemtl") { // Read mtl name hasMaterial = true; ss >> temp; for (std::size_t i = 0; i < newModel->mesh.material.size(); ++i) { if (temp == newModel->mesh.material[i]->name) { currentMtlID = i; } } } else if (identifier == "v") { ss >> vec3.x; ss >> vec3.y; ss >> vec3.z; newModel->mesh.verts.push_back(vec3); } else if (identifier == "vt") { ss >> vec2.x; ss >> vec2.y; newModel->mesh.texCoords.push_back(vec2); } else if (identifier == "vn") { ss >> vec3.x; ss >> vec3.y; ss >> vec3.z; glm::normalize(vec3); newModel->mesh.normals.push_back(vec3); } else if (identifier == "vp") { ss >> vec3.x; ss >> vec3.y; ss >> vec3.z; newModel->mesh.psVerts.push_back(vec3); } else if (identifier == "f") { auto face = std::make_shared<Face>(); face->materialID = currentMtlID; face->hasMaterial = hasMaterial; ss >> temp; std::size_t n = std::count(temp.begin(), temp.end(), '/'); if (n == 0) { // Read temp data face->vertIndices.push_back(std::stoul(temp) - 1); // Read the rest of the SS while (!ss.eof()) { // Store index ss >> tempVal; face->vertIndices.push_back(tempVal - 1); } } else if (n == 1) { std::stringstream tempSS(temp); // Read temp data while (!tempSS.eof()) { // Store index tempSS >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage tempSS >> garbage; // Store texCoord tempSS >> tempVal; face->texCoordsIndices.push_back(tempVal - 1); } // Read the rest of the SS while (!ss.eof()) { // Store index ss >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage ss >> garbage; // Store texCoord ss >> tempVal; face->texCoordsIndices.push_back(tempVal - 1); } } else if (n == 2) { std::stringstream tempSS(temp); // vert normal without tex coords if (input.find("//") != std::string::npos) { // Read temp data while (!tempSS.eof()) { // Store vert index tempSS >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage tempSS >> garbage; tempSS >> garbage; // Store normal index tempSS >> tempVal; face->normalIndices.push_back(tempVal - 1); } // Read the rest of the SS while (!ss.eof()) { // Store vert index ss >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage ss >> garbage; ss >> garbage; // Store normal index ss >> tempVal; face->normalIndices.push_back(tempVal - 1); } } // else { // Read temp data while (!tempSS.eof()) { // Store vert index tempSS >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage tempSS >> garbage; // Store tex coord index tempSS >> tempVal; face->texCoordsIndices.push_back(tempVal - 1); // Remove garbage tempSS >> garbage; // Store normal index tempSS >> tempVal; face->normalIndices.push_back(tempVal - 1); } // Read the rest of the SS while (!ss.eof()) { // Store vert index ss >> tempVal; face->vertIndices.push_back(tempVal - 1); // Remove garbage ss >> garbage; // Store tex coord index ss >> tempVal; face->texCoordsIndices.push_back(tempVal - 1); // Remove garbage ss >> garbage; // Store normal index ss >> tempVal; face->normalIndices.push_back(tempVal - 1); } } } if (face->vertIndices.size() > 3 || !face->hasMaterial) { newModel->vboData.vboSafe = false; } newModel->mesh.faces.push_back(face); } else if (input == "l") { while (!ss.eof()) { // Store index ss >> tempVal; newModel->mesh.lineElements.push_back(tempVal); // Remove garbage if (!ss.eof()) { ss >> garbage; } } } } } return newModel; } } // namespace OBJ_LOADER namespace OFF_LOADER { std::shared_ptr<Model> load(const std::string& modelName) { std::ifstream inFile(MODEL_PATH_OFF + modelName); if (!inFile) { std::cerr << "Failed to open " << MODEL_PATH_OFF << modelName << '\n'; return nullptr; } auto newModel = std::make_shared<Model>(); newModel->name = modelName; newModel->vboData.vboSafe = false; std::size_t nVerts{0}, nFaces{0}, indexCount{0}, tempVal{0}; float RGB{0}; glm::vec3 verts; for (std::string input; std::getline(inFile, input);) { // Skip comments, empty lines, and optional file mark if (input != "OFF" && input[0] != '#' && !input.empty()) { if (nVerts == 0 && nFaces == 0) { std::stringstream ss(input); ss >> nVerts; ss >> nFaces; } auto face = std::make_shared<Face>(); // Push back empty vert to use as an offset (so OBJ and OFF both // start from 1) newModel->mesh.verts.push_back(verts); // Read Verts for (std::size_t i = 0; i < nVerts; ++i) { std::getline(inFile, input); std::stringstream ssV(input); ssV >> verts.x; ssV >> verts.y; ssV >> verts.z; newModel->mesh.verts.push_back(verts); } // Read indices for (std::size_t i = 0; i < nFaces; ++i) { std::getline(inFile, input); std::stringstream ssI(input); ssI >> indexCount; for (std::size_t j = 0; j < indexCount; ++j) { ssI >> tempVal; // Offset each vertex by one face->vertIndices.push_back(++tempVal); } // Read RGB if (!ssI.eof()) { // R ssI >> RGB; face->colour.r = RGB; // G ssI >> RGB; face->colour.g = RGB; // B ssI >> RGB; face->colour.b = RGB; } newModel->mesh.faces.push_back(face); } } } return newModel; } } // namespace OFF_LOADER namespace MTL_LOADER { void load(const std::string& mtlName, std::shared_ptr<Model>& model) { std::ifstream inFile(MODEL_PATH_OBJ + model->subPath + mtlName); if (!inFile) { std::cerr << "Failed to open " << MODEL_PATH_OBJ << model->subPath << mtlName << '\n'; } std::string identifier; auto newMaterial = std::make_shared<Material>(); bool firstMat{true}; for (std::string input; std::getline(inFile, input);) { identifier = input.substr(0, input.find_first_of(' ') + 1); identifier.erase(remove(identifier.begin(), identifier.end(), ' '), identifier.end()); if (identifier != "#" && !identifier.empty() && !input.empty()) { input = input.substr(input.find_first_of(' ') + 1, input.size() - 1); std::stringstream ss(input); if (identifier == "newmtl") { if (firstMat) { ss >> newMaterial->name; firstMat = false; } else { model->mesh.material.push_back(newMaterial); newMaterial = std::make_shared<Material>(); ss >> newMaterial->name; } } else if (identifier == "Ka") { ss >> newMaterial->ambient[0]; ss >> newMaterial->ambient[1]; ss >> newMaterial->ambient[2]; newMaterial->ambient[3] = 1; } else if (identifier == "Kd") { ss >> newMaterial->diffuse[0]; ss >> newMaterial->diffuse[1]; ss >> newMaterial->diffuse[2]; newMaterial->diffuse[3] = 1; } else if (identifier == "Ks") { ss >> newMaterial->specular[0]; ss >> newMaterial->specular[1]; ss >> newMaterial->specular[2]; newMaterial->specular[3] = 1; } else if (identifier == "Ns") { ss >> newMaterial->specular[3]; } else if (identifier == "d") { ss >> newMaterial->transparency; } else if (identifier == "Tr") { ss >> newMaterial->transparency; newMaterial->transparency = 1 - newMaterial->transparency; } else if (identifier == "Map_Kd" || identifier == "map_Kd") { ss >> newMaterial->mapKD; newMaterial->textureID = TextureManager::getTextureID( newMaterial->mapKD); } } } model->mesh.material.push_back(newMaterial); } } // namespace MTL_LOADER