0

I have tried for like a week to find a suitable hash function to avoid duplicate vertices on from the .obj files with no success. I have no idea how to loop though the struct (see source code) and get the correct index to index an unordered map. I don't know if that's the best approach, I'll gladly accept any solution that works. Thanks in advance for any help. It will be greatly appreciated!!

Source code:

Struct:

struct Face
{
    struct VertexConfig
    {
        int i[3];
    };

    int numConfigs = 0;
    VertexConfig configs[4];
};

Class:

class MeshResource
{
public:
    vector<float> vertices;
    vector<unsigned int> indices;
    vector<float> normals;
    vector<float> textureIndex;
    std::shared_ptr<Material> material;

    GLuint slot;
    GLint components ;
    GLenum type;
    GLsizei stride , offset ;
    GLboolean normalized = GL_FALSE;
    GLuint numIndices, vao;

    MeshResource() = default;
    MeshResource(vector<float> vertices, vector<unsigned int> indices);
    // TODO: Call cleanup from destructor
    void SetupQuad();
    void Draw();
    void DrawMesh2();
    void CreateCube(float width, float height, float depth);
    void Cleanup();
    unsigned int VAO, VBO, EBO;
    void ParseIntoFloat(string line, std::vector<float>& vector);
    void ParseIntoFloat2(string line, std::vector<float>& vector);
    void LoadMeshData(const char* FileName);
    void ParseFace(string line, int pos, std::vector<Face>& faces, bool& hasNormals, bool& hasUvs);
    void Bind();

    void SetupMesh();
private:

};

.obj parser:

 void MeshResource::LoadMeshData(const char* FileName)
{
std::ifstream file(FileName);
std::string line;

std::vector<float> vp;
std::vector<float> vt;
std::vector<float> vn;
std::vector<Face> faces;
bool hasNormals = false;
bool hasUvs = false;

while (std::getline(file, line))
{
    //check for vertices
    if (line.substr(0, 2) == "v ") 
        ParseIntoFloat(line, vp);
    //check for texture
    else if (line.substr(0, 2) == "vt") 
        ParseIntoFloat2(line, vt);
    //check for Normals
    else if (line.substr(0, 2) == "vn") 
        ParseIntoFloat(line, vn);
    //check for faces
    else if (line.substr(0, 2) == "f ") 
        MeshResource::ParseFace(line, 2, faces, hasNormals, hasUvs);
}
std::unordered_map<int, int> map;
int currentIndex = 0;
const int step = 1 + (int)hasUvs + (int)hasNormals;
const int posLength = vp.size();
const int uvLength = vt.size();
int counter = 0;

for (int i = 0; i < faces.size(); i++ )
{
    counter++;
    for (int j = 0; j < faces[i].numConfigs; j++)
    {
        int posIndex = i+j;
        int uvIndex = i+j + 1;
        int normalIndex = i+j + (int)hasUvs + 1;
        int index = vertices.size() / 8;

        int baseIndex_Position = (faces[i].configs[j].i[0] - 1) * 3;
        vertices.push_back(vp[baseIndex_Position]);
        vertices.push_back(vp[baseIndex_Position + 1]);
        vertices.push_back(vp[baseIndex_Position + 2]);

        int baseindex_Normals = (faces[i].configs[j].i[2] -1) * 3;
        vertices.push_back(vn[baseindex_Normals]);
        vertices.push_back(vn[baseindex_Normals + 1]);
        vertices.push_back(vn[baseindex_Normals + 2]);

        int baseIndex_Texture = (faces[i].configs[j].i[1] - 1) * 2;
        vertices.push_back(vt[baseIndex_Texture]);
        vertices.push_back(vt[baseIndex_Texture + 1]);

        indices.push_back(currentIndex); 
        if (j == 2)
        {
            auto temp = indices[currentIndex];
            indices[currentIndex] = indices[currentIndex-1];
            indices[currentIndex - 1] = temp;
        }
        currentIndex++;
    }       
}
SetupMesh();

}

void MeshResource::ParseFace(string line, int pos, std::vector<Face>& faces, bool &hasNormals, bool &hasUvs)
{   
Face f;
bool eol = false;
while (!eol)
{
    bool eow = false;
    pos = line.find(" ");
    if (line[0] == 'f')
    {
        line = line.substr(line.find(" ") + 1);
        continue;
    }
    pos = line.find(" ");
    eol = pos == -1;
    if (eol && f.numConfigs>=3)
        break;
    string word = line.substr(0,pos);
    int counter = 0;
    bool isOnLast = false;
    for (size_t i = 0; i < 3; i++)
    {

        isOnLast = false;
        int slashIndex = word.find('/');
        bool notCurrent = false;
        eow = slashIndex == -1;
        string number;
        if (eow)
        {
            slashIndex = word.length() - 1;
            number = word.substr(0);
            isOnLast = true;
        }
        else
        {
            number = word.substr(0,slashIndex);
            word = word.substr(slashIndex+1);
            if (slashIndex == 0)
                notCurrent = true;
        }
        counter++;
        if (slashIndex !=-1 && !notCurrent)
        {
            if (counter == 1)
                f.configs[f.numConfigs].i[0] = std::stoi(number);
            else if (counter == 2)
            {
                f.configs[f.numConfigs].i[1] = std::stoi(number);
                hasNormals = true;
            }
            else if (counter == 3 && word.length() > 0)
            {
                f.configs[f.numConfigs].i[2] = std::stoi(number);
                hasUvs = true;
            }
        }
        if (isOnLast)
            break;
    }
    f.numConfigs++;
    pos = line.find(" ");
    line = line.substr(pos + 1);
}
faces.push_back(f);

}

If you miss any code, let me know and I'l provide it! Thanks again! /Filip

1 Answers1

0

First, you want to use a struct to hold all the coordinates belonging to a single vertex. You could create your own struct Vertex, but I strongly recommend using the GLM library's glm::vec3f (assuming they are 3D coordinates). This library also provides specializations of std::hash, so you can then simply create an unordered map from vertex to index like so:

std::unordered_map<glm::vec3f, int> vertex_indices;

If you want to know how GLM creates a hash specialization so you can try to do that yourself, have a look at its source code.

So once you have parsed a vertex, you can check if it is already in that map, or else insert it:

int index;
glm::vec3f vertex;
...
if (auto it = vertex_indices.find(vertex); it != vertex_indices.end()) {
    // vertex already found
    ...
} else {
    vertex_indices[vertex] = index;
}

Now the trick is: what to do if the vertex already existed? You need some way to remap the indices in the f-lines in the OBJ files to your de-duplicated set of indices. You need a few more data structures to handle that.

std::vector<vertex> vertices;
std::vector<int> deduplicated_indices;
std::unordered_map<glm::vec3f, int> vertex_indices;

int index = 0;
glm::vec3f vertex;

while (std::getline(...)) {
    vertex = parseVertex(line);

    if (auto it = vertex_indices.find(vertex); it != vertex_indices.end()) {
        // vertex already found, map the file's index to our deduplicated index
        deduplicated_indices.push_back(it->second);
    } else {
        // remember this vertex
        int our_index = vertices.size();
        vertices.push_back(vertex);
        deduplicated_indices.push_back(our_index);
        vertex_indices[vertex] = our_index;
    }
}

After this, you have vector of deduplicated vertices, and the vector deduplicated_indices maps from indices in the OBJ file to indices into vertices. This way, when you read a face:

int configs[3];

// read indices from OBJ file into configs[]
...

// convert those indices to deduplicate indices
for (size_t i = 0; i < 3; ++i) {
    f.configs[i] = deduplicated_indices[configs[i]];
}
G. Sliepen
  • 7,637
  • 1
  • 15
  • 31