Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.obj Parser + Render GLUT

Tags:

c++

glut

So I have a small .obj parser that can parse the vertices and draw it on screen:

void loadObj(char *fname)
{
    FILE *fp;
    int read;
    GLfloat x, y, z;
    char ch;
    _model = glGenLists(1);
    fp = fopen(fname, "r");
    if (!fp)
    {
        printf("can't open file %s\n", fname);
        exit(1);
    }
    glPointSize(2.0);
    glNewList(_model, GL_COMPILE);
    {
        glPushMatrix();
        glBegin(GL_POINTS);
        while (!(feof(fp)))
        {
            read = fscanf(fp, "%c %f %f %f", &ch, &x, &y, &z);
            if (read == 4 && ch == 'v')
            {
                glVertex3f(x, y, z);
            }
        }
        glEnd();
    }
    glPopMatrix();
    glEndList();
    fclose(fp);
}

void drawModel()
{

    glPushMatrix();
    glTranslatef(0, 0.00, 0.00);
    glColor3f(1.0, 0.23, 0.27);
    glScalef(10, 10, 10);
    glRotatef(_modelRot, 0, 1, 0);
    glCallList(_model);
    glPopMatrix();
}

Point is, the output is only vertices, like this:

enter image description here

How can I modify this to at-least show the 3D form between points without adding 3rd party libraries? This is something I am looking for:

enter image description here

Thanks. More code can be provided if required.

like image 653
Jishan Avatar asked Mar 04 '17 19:03

Jishan


1 Answers

Your object parser and rendering calls are not complete.

The first part of the .obj file contains vertex data. Including position, texture coordinate and normal data. The second part contains the information, how the verticies are interconnected.

  # List of geometric vertices, with (x,y,z[,w]) coordinates, w is optional and defaults to 1.0.
  v 0.123 0.234 0.345 1.0 # first vertex
  v ...                   # second vertex
  ...
  # List of texture coordinates, in (u, v [,w]) coordinates, these will vary between 0 and 1, w is optional and defaults to 0.
  vt 0.500 1 [0]  # first texture coordinate
  vt ...          # second
  ...
  # List of vertex normals in (x,y,z) form; normals might not be unit vectors.
  vn 0.707 0.000 0.707 # first normal
  vn ...               # second
  ...
  # Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement ( see below )
  vp 0.310000 3.210000 2.100000
  vp ...
  ...
  # Polygonal face element (see below)
  f 1 2 3                # face of the first, second and third vertex
  f 3/1 4/2 5/3          # face of the  third, fourth and fifth vertex with the first second and third texture coordinate
  f 6/4/1 3/5/3 7/6/5    # face of sixth, thrid and seventh vertex, fourth, fifth and sixth texture coordinate and first thrid and fifth normal
  f 7//1 8//2 9//3       # similar to the line over but without texture coordinates
  f ...
  ...

The listing is taken from here: https://en.wikipedia.org/wiki/Wavefront_.obj_file

OpenGl is not able to map the vertex data together in the way, .obj files allow it to be. So you have to make a data structure with all verticies, one for all texels and one for all normals.

Then you can parse the faces and build your vertex data, by collecting the right position, texel and normals to build a complete face.

After that step, you can use this combination to draw your primitives with GL_TRIANGLES or GL_QUADS.

Here is the loader, that I wrote a while ago for OpenGl 4.x

#pragma once

#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <glm/glm.hpp>
#include <GL/glew.h>

#include "System/Log.hpp"
#include "Graphics/Primitives/Object.hpp"
#include "Graphics/Primitives/Material.hpp"

namespace Loader {

template <class ObjectT = Graphics::Primitives::Object>
class ObjectLoader {
private:
    const std::string fullPath;
    std::vector<Graphics::Primitives::VertexGroup> objects;
    std::map<std::string, Graphics::Primitives::Material> materials;
    std::string prefixPath;

    std::vector<glm::vec3> verticies;
    std::vector<glm::vec2> texels;
    std::vector<glm::vec3> normals;

    std::vector<glm::uvec3> faces;
    std::vector<glm::vec3> index_verticies;
    std::vector<glm::vec2> index_texels;
    std::vector<glm::vec3> index_normals;

    Graphics::Primitives::Material material;
    std::map<std::string, GLuint> indexDb;

    std::string getPrefixPath();
    std::vector<std::string> explode(std::string str, char delimiter = ' ');
    glm::vec3 stringsToVec(const std::vector<std::string> parts, unsigned int begin);

    void loadMaterial(std::string fileName);
    Graphics::Primitives::VertexGroup flush();

public:

    ObjectLoader(std::string fileName);

    ObjectT load();

};


template <class ObjectT>
std::string ObjectLoader<ObjectT>::getPrefixPath() {
    unsigned int lastSlash = 0;
    for(int i = fullPath.size(); i > 0; i--) {
        if(fullPath[i] == '/') {
            lastSlash = i;
            break;
        }
    }
    std::string prefixPath = fullPath.substr(0, lastSlash);
    prefixPath += "/";
    return prefixPath;
}

template <class ObjectT>
std::vector<std::string> ObjectLoader<ObjectT>::explode(std::string str, char delimiter) {
    std::vector<std::string> result;

    std::stringstream  data(str);
    std::string line;

    while(std::getline(data,line,delimiter)) {
        result.push_back(line);
    }

    return result;
}

template <class ObjectT>
glm::vec3 ObjectLoader<ObjectT>::stringsToVec(const std::vector<std::string> parts, unsigned int begin) {
    glm::vec3 result;
    if(parts.size() > begin + 2) {
        result.x = std::atof(parts[begin].c_str());
        result.y = std::atof(parts[begin+1].c_str());
        result.z = std::atof(parts[begin+2].c_str());
    } else
    if(parts.size() > begin) {
        result.x = std::atof(parts[begin].c_str());
        result.y = std::atof(parts[begin].c_str());
        result.z = std::atof(parts[begin].c_str());
    }
    return result;
}

template <class ObjectT>
void ObjectLoader<ObjectT>::loadMaterial(std::string fileName) {
    std::ifstream materialFile(fileName);
    std::string line;
    Graphics::Primitives::Material material;
    std::string materialName;
    bool initialised = false;
    while(std::getline(materialFile, line)) {
        //System::Log::msg << " " << line << std::endl;
        std::vector<std::string> parts = explode(line);
        if(parts.size() > 0) {
            if(parts[0] == "newmtl") {
                if(initialised) {
                    materials.insert(std::make_pair(materialName, material));
                    System::Log::msg << "Loaded material: " << materialName << std::endl;
                }
                materialName = parts[1];
                initialised = true;
                material = Graphics::Primitives::Material();
            } else
            if(parts[0] == "Ns") {
                if(parts.size() > 1) {
                    material.specularExponent = std::atof(parts[1].c_str());
                }
            } else
            if(parts[0] == "Ka") {
                material.ambientReflectance = stringsToVec(parts,1);
            } else
            if(parts[0] == "Kd") {
                material.diffuseReflectance = stringsToVec(parts,1);
            } else
            if(parts[0] == "Ks") {
                material.specularReflectance = stringsToVec(parts,1);
            } else
            //if(parts[0] == "Ke") {
                //No idea what this value means, maybe transmission filter aka Tf?
            //} else
            if(parts[0] == "Ni") {
                //Optical density ignored for now
            } else
            if(parts[0] == "d") {
                material.dissolve = std::atof(parts[1].c_str());
            } else
            if(parts[0] == "map_Ka") {
                material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
            } else
            if(parts[0] == "map_Kd") {
                material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
            } else
            if(parts[0] == "map_Ks") {
                material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
            }
        }
    }
    materials.insert(std::make_pair(materialName, material));
    System::Log::msg << "Loaded material: " << materialName << std::endl;
}

template <class ObjectT>
Graphics::Primitives::VertexGroup ObjectLoader<ObjectT>::flush()
{
    Graphics::Ogl::VertexArrayObject vao = Graphics::Ogl::makeVertexArrayObject(
        std::vector<Graphics::Ogl::ArrayBufferObject>({
            Graphics::Ogl::makeArrayBufferObject(index_verticies),
            Graphics::Ogl::makeArrayBufferObject(index_normals),
            Graphics::Ogl::makeArrayBufferObject(index_texels),
            Graphics::Ogl::makeIndexBufferObject(faces)
        })
    );

    System::Log::msg << "Flushing buffers: vertecies(" << index_verticies.size()
                     << "), texels(" << index_texels.size()
                     << "), normals(" << index_normals.size()
                     << "), faces(" << faces.size() << ")" << std::endl;

    Graphics::Primitives::VertexGroup obj(vao, material);
    glCheckError();

    faces.clear();
    index_verticies.clear();
    index_texels.clear();
    index_normals.clear();
    indexDb.clear();

    return obj;
}

template <class ObjectT>
ObjectLoader<ObjectT>::ObjectLoader(std::string fileName) :
    fullPath(fileName),
    prefixPath(getPrefixPath())
{}

template <class ObjectT>
ObjectT ObjectLoader<ObjectT>::load() {
    System::Log::msg << "Loading Object from file: " << fullPath << std::endl;
    std::ifstream objectFile(fullPath);
    std::string line;

    while(std::getline(objectFile,line)) {
        //System::Log::msg << line << std::endl;

        if(line[0] == '#' || line[0] == 'o' || line[0] == 'g') {
            continue;
        }
        std::vector<std::string> substrs = explode(line);
        if(substrs.size() == 0) {
            continue;
        }

        if(      substrs[0] == "v") {
            //Add new Vertex to index buffer
            glm::vec3 vertex;
            if(substrs.size() > 3) {
                vertex.x = std::stof(substrs[1]);
                vertex.y = std::stof(substrs[2]);
                vertex.z = std::stof(substrs[3]);
                verticies.push_back(vertex);
            } else {
                System::Log::err << "Vertex with less than 3 coordinates." << std::endl;
            }
        } else if(substrs[0] == "vt") {
            //Add new Texel to index buffer
            glm::vec2 texel;
            if(substrs.size() > 2) {
                texel.x= std::stof(substrs[1]);
                texel.y = std::stof(substrs[2]);
                texels.push_back(texel);
            } else {
                System::Log::err << "Texel with less than 2 coordinates." << std::endl;
            }

        } else if(substrs[0] == "vn") {
            //Add new Normal to index buffer
            glm::vec3 normal;
            if(substrs.size() > 3) {
                normal.x = std::stof(substrs[1]);
                normal.y = std::stof(substrs[2]);
                normal.z = std::stof(substrs[3]);
                normals.push_back(normal);
            } else {
                System::Log::err << "Normal with less than 3 coordinates." << std::endl;
            }

        } else if(substrs[0] == "f") {
            if(texels.size() == 0) texels.push_back(glm::vec2(0,0));
            if(normals.size() == 0) normals.push_back(glm::vec3(0,0,0));

            //Lookup in index db;
            glm::uvec3 face;
            for(unsigned int faceIndex = 1; faceIndex < 4; faceIndex++) {
                std::string vtn = substrs[faceIndex];
                try {
                    //Try to find index combination in db
                    face[faceIndex-1] = indexDb.at(substrs[faceIndex]);
                    //Index found

                } catch (std::exception e) {
                    //Index not found, now to the hard part

                    //Create new index in indexDb
                    GLuint newFace = indexDb.size();
                    face[faceIndex-1] = newFace;
                    indexDb.insert(std::make_pair(substrs[faceIndex], newFace));
                    //Create new vtn triple in buffers
                    std::vector<std::string> components = explode(substrs[faceIndex],'/');
                    if(components[1].size() == 0) components[1] = "0";
                    if(components[2].size() == 0) components[2] = "0";

                    auto clipValue = [](std::string& number) -> GLuint {
                        GLuint result = std::atoi(number.c_str());
                        if(result > 0) result--;
                        return result;
                    };

                    const GLuint vi = clipValue(components[0]);
                    const GLuint ti = clipValue(components[1]);
                    const GLuint ni = clipValue(components[2]);

                    if(verticies.size() > vi) {
                        index_verticies.push_back(verticies[vi]);
                    } else {
                        System::Log::err << "Error: Invalid vertex index. (Index="<< vi <<", LoadedVerticies=" << verticies.size() << ")" << std::endl;
                    }
                    if(texels.size() > ti) {
                        index_texels.push_back(texels[ti]);
                    }
                    if(normals.size() > ni) {
                        index_normals.push_back(normals[ni]);
                    }
                }


            }
            //Add new Face to Mesh
            faces.push_back(face);
            //System::Log::msg << "Face: " << face.x << " " << face.y << " " << face.z << std::endl;
        } else if(substrs[0] == "usemtl") {
            //Flush last mesh
            if(faces.size() > 0) {
                objects.push_back(flush());
            }
            //Use new material
            material = materials.at(substrs[1]);
            System::Log::msg << "Use Material:" << substrs[1] << std::endl;
        } else if(substrs[0] == "s") {
            //Smoothing
            //TODO:
        } else if(substrs[0] == "mtllib") {
            //Load new materials
            loadMaterial(prefixPath+substrs[1]);
        } else {
            System::Log::err << "Unknown prefix in file" << std::endl;
        }
    }
    if(faces.size() > 0) {
        objects.push_back(flush());
    }
    System::Log::msg << "Done loading object." << std::endl;
    return ObjectT(objects);
}

} // End of namespace Loader
like image 97
OutOfBound Avatar answered Oct 14 '22 07:10

OutOfBound