ICT290 / src / engine / modelManager.cpp
modelManager.cpp
Raw
#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