Deloryn
Deloryn

Reputation: 41

Cannot properly convert OBJ or OpenGL doesn't draw the object well

I spent hours trying to draw my .obj models in OpenGL. I figured that probably there is something wrong with my Python script (that converts obj models to C++ files). Or there is something wrong with .obj file. I don't know how to make it working properly. OpenGL draws an object, but it is completely different that the object I created in SketchUp.

In short: my script converts .obj correctly (unless I don't know other rules). At first I gather lines with vertices in one list of lines, the same with vn and vt. I gather also data about faces. Later I match appropriate v, vn and vt to the faces. Finally it generates .h and .cpp files with the data. What can be wrong?

Here's the .obj file: https://pastebin.com/g0HwpRqB

    # Alias OBJ Model File
# Exported from SketchUp, (c) 2000-2012 Trimble Navigation Limited
# File units = meters

mtllib sciany1.mtl

g Mesh1 Model

usemtl Brick_Tumbled
v 2.06118 0 0.0146645
vt -2.25413 -0.0320745
vn 0 -1 -0
v -3.02882 0 0.0146645
vt 3.31236 -0.0320745
v -3.02882 0 -9.68534
vt 3.31236 21.184
v 2.06118 0 -9.68534
vt -2.25413 21.184
f 1/1/1 2/2/1 3/3/1 4/4/1 

usemtl FrontColor
vt -119.245 0
vn 0 0 1
vt 81.1487 0
v 2.06118 2.76 0.0146645
vt 81.1487 108.661
v -3.02882 2.76 0.0146645
vt -119.245 108.661
f 2/5/2 1/6/2 5/7/2 6/8/2 

usemtl Brick_Tumbled
vt -0.0160372 0
vn 1 0 -0
vt 10.592 0
v 2.06118 2.76 -9.68534
vt 10.592 6.03675
vt -0.0160372 6.03675
f 1/9/3 4/10/3 7/11/3 5/12/3 

usemtl FrontColor
vt -81.1487 0
vn 0 0 -1
vt 119.245 0
v -3.02882 2.76 -9.68534
vt 119.245 108.661
vt -81.1487 108.661
f 4/13/4 3/14/4 8/15/4 7/16/4 

usemtl Brick_Tumbled
vt -10.592 0
vn -1 0 -0
vt 0.0160372 0
vt 0.0160372 6.03675
vt -10.592 6.03675
f 3/17/5 2/18/5 6/19/5 8/20/5 

vt -3.31236 -0.0320745
vn 0 1 -0
vt 2.25413 -0.0320745
vt 2.25413 21.184
vt -3.31236 21.184
f 6/21/6 5/22/6 7/23/6 8/24/6

Here's the generated .cpp file: https://pastebin.com/YPXnuuzP

            #include "sciany1.h"

            namespace Models {

                Sciany1 sciany1;

                Sciany1::Sciany1() {
                    vertices=Sciany1Internal::vertices;
                    normals=Sciany1Internal::normals;
                    vertexNormals=Sciany1Internal::vertexNormals;
                    texCoords=Sciany1Internal::texCoords;
                    colors=Sciany1Internal::colors;
                    vertexCount=Sciany1Internal::vertexCount;
                }

                Sciany1::~Sciany1() {
                }

                void Sciany1::drawSolid() {
                    glEnable(GL_NORMALIZE);

                    glEnableClientState(GL_VERTEX_ARRAY);
                    //glEnableClientState(GL_COLOR_ARRAY);
                    //glEnableClientState(GL_NORMAL_ARRAY);
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

                    glVertexPointer(4,GL_FLOAT,0,vertices);
                    //glColorPointer(4,GL_FLOAT,0,colors);
                    glNormalPointer(GL_FLOAT,sizeof(float)*4,vertexNormals);
                    glTexCoordPointer(2,GL_FLOAT,0,texCoords);

                    glDrawArrays(GL_TRIANGLES,0,vertexCount);

                    glDisableClientState(GL_VERTEX_ARRAY);
                    //glDisableClientState(GL_COLOR_ARRAY);
                    //glDisableClientState(GL_NORMAL_ARRAY);
                    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                }

                namespace Sciany1Internal {
                        unsigned int vertexCount=18;

                        float vertices[]={
                            2.06,0,0.01,
-3.02,0,0.01,
-3.02,0,-9.68,
-3.02,0,0.01,
2.06,0,0.01,
2.06,2.76,0.01,
2.06,0,0.01,
2.06,0,-9.68,
2.06,2.76,-9.68,
2.06,0,-9.68,
-3.02,0,-9.68,
-3.02,2.76,-9.68,
-3.02,0,-9.68,
-3.02,0,0.01,
-3.02,2.76,0.01,
-3.02,2.76,0.01,
2.06,2.76,0.01,
2.06,2.76,-9.68,


                        };

                        float colors[]={
                        };

                        float normals[]={
                            0,-1,-0,
0,-1,-0,
0,-1,-0,
0,0,1,
0,0,1,
0,0,1,
1,0,-0,
1,0,-0,
1,0,-0,
0,0,-1,
0,0,-1,
0,0,-1,
-1,0,-0,
-1,0,-0,
-1,0,-0,
0,1,-0,
0,1,-0,
0,1,-0,

                        };

                        float vertexNormals[]={

                        };

                        float texCoords[]={
                            -2.25,-0.03,
3.31,-0.03,
3.31,21.18,
-119.24,0,
81.14,0,
81.14,108.66,
-0.01,0,
10.59,0,
10.59,6.03,
-81.14,0,
119.24,0,
119.24,108.66,
-10.59,0,
0.01,0,
0.01,6.03,
-3.31,-0.03,
2.25,-0.03,
2.25,21.18,

                        };
                }
            }

Here's the generated .h file: https://pastebin.com/jaf73yf2

#ifndef SCIANY1_H
#define SCIANY1_H

//Sciany1 model made out of triangles
//Contains arrays:
//vertices - vertex positions in homogenous coordinates
//normals - vertex normals in homogenous coordinates
//texCoords - texturing coordinates
//colors - vertex colors (rgba)
//Culling GL_CW
//TBN friendly

#include "model.h"

namespace Models {
    namespace Sciany1Internal {
        extern float vertices[];
        extern float normals[];
        extern float vertexNormals[];
        extern float texCoords[];
        extern float colors[];
        extern unsigned int vertexCount;
    }

    class Sciany1: public Model {
        public:
            Sciany1();
            virtual ~Sciany1();
            virtual void drawSolid();
    };

    extern Sciany1 sciany1;
}




#endif

Here's my Python script: https://pastebin.com/LivefwgY

def round_number(string_number):
    if "\n" in string_number:
        string_number = string_number.replace("\n", "")
    if "." in string_number:
        parts = string_number.split(".")
        if len(parts[1]) > 2:
            return parts[0] + "." + parts[1][:2]
    return string_number


def line_to_good_line(line):
    processed_numbers=[]
    numbers=line.split(" ")
    for number in numbers:
        if number and number != "\n":
            processed_numbers.append(round_number(number))
    output = str.join(",", processed_numbers) + ",\n"
    return output

def add_numbers_in_the_end(lines, additional_number):
    new_lines=[]
    for line in lines:
        new_lines.append(line[:-1]+","+additional_number+",\n")
    return new_lines


def prepare_output_from_faces(data, faces):
    ready_list = []
    for face in faces:
        for number in face:
            ready_list.append(data[int(number)-1])
    return str.join("", ready_list)


def convert_to_cpp(filename):
    model_name = filename[0].upper() + filename[1:-4]
    upper_model_name = model_name.upper()
    lower_model_name = model_name.lower()

    vertices_lines = []
    normals_lines = []
    tex_coords_lines = []

    vertices_faces = []
    normals_faces = []
    tex_coords_faces = []

    vertices="" # final string output
    normals="" # final string output
    tex_coords="" # final string output

    with open(filename, "r") as file:
        lines = file.readlines()
    for line in lines:
        if line[0:2] == "v ":
            line = line_to_good_line(line[2:])
            vertices_lines.append(line)
        elif line[0:3] == "vn ":
            line = line_to_good_line(line[3:])
            normals_lines.append(line);
        elif line[0:3] == "vt ":
            line = line_to_good_line(line[3:])
            tex_coords_lines.append(line)
        elif line[0:2] == "f ":
            face = line[2:].replace("\n", "").split(" ")
            face[0] = face[0].split("/")
            face[1] = face[1].split("/")
            face[2] = face[2].split("/")
            vertex_face = [face[0][0], face[1][0], face[2][0]]
            tex_coord_face = [face[0][1], face[1][1], face[2][1]]
            normals_face = [face[0][2], face[1][2], face[2][2]]
            vertices_faces.append(vertex_face)
            tex_coords_faces.append(tex_coord_face)
            normals_faces.append(normals_face)

    vertices = prepare_output_from_faces(vertices_lines, vertices_faces)
    normals = prepare_output_from_faces(normals_lines, normals_faces)
    tex_coords = prepare_output_from_faces(tex_coords_lines, tex_coords_faces)
    vertices_number = vertices.count("\n")

    with open(lower_model_name + ".h", "w") as header_file:
        data = """
        /*
        Niniejszy program jest wolnym oprogramowaniem; możesz go
        rozprowadzać dalej i / lub modyfikować na warunkach Powszechnej
        Licencji Publicznej GNU, wydanej przez Fundację Wolnego
        Oprogramowania - według wersji 2 tej Licencji lub(według twojego
        wyboru) którejś z późniejszych wersji.

        Niniejszy program rozpowszechniany jest z nadzieją, iż będzie on
        użyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyślnej
        gwarancji PRZYDATNOŚCI HANDLOWEJ albo PRZYDATNOŚCI DO OKREŚLONYCH
        ZASTOSOWAŃ.W celu uzyskania bliższych informacji sięgnij do
        Powszechnej Licencji Publicznej GNU.

        Z pewnością wraz z niniejszym programem otrzymałeś też egzemplarz
        Powszechnej Licencji Publicznej GNU(GNU General Public License);
        jeśli nie - napisz do Free Software Foundation, Inc., 59 Temple
        Place, Fifth face, Boston, MA  02110 - 1301  USA
        */

        #ifndef {}_H
        #define {}_H

        //{} model made out of triangles
        //Contains arrays:
        //vertices - vertex positions in homogenous coordinates
        //normals - vertex normals in homogenous coordinates
        //texCoords - texturing coordinates
        //colors - vertex colors (rgba)
        //Culling GL_CW
        //TBN friendly

        #include "model.h"

        namespace Models {{
            namespace {}Internal {{
                extern float vertices[];
                extern float normals[];
                extern float vertexNormals[];
                extern float texCoords[];
                extern float colors[];
                extern unsigned int vertexCount;
            }}

            class {}: public Model {{
                public:
                    {}();
                    virtual ~{}();
                    virtual void drawSolid();
            }};

            extern {} {};
        }}




        #endif
        """.format(upper_model_name, upper_model_name,
         model_name, model_name, model_name, model_name,
         model_name, model_name, lower_model_name)
        header_file.write(data)

        with open(model_name + ".cpp", "w") as cpp_file:
            data = """
            /*
            Niniejszy program jest wolnym oprogramowaniem; możesz go
            rozprowadzać dalej i / lub modyfikować na warunkach Powszechnej
            Licencji Publicznej GNU, wydanej przez Fundację Wolnego
            Oprogramowania - według wersji 2 tej Licencji lub(według twojego
            wyboru) którejś z późniejszych wersji.

            Niniejszy program rozpowszechniany jest z nadzieją, iż będzie on
            użyteczny - jednak BEZ JAKIEJKOLWIEK GWARANCJI, nawet domyślnej
            gwarancji PRZYDATNOŚCI HANDLOWEJ albo PRZYDATNOŚCI DO OKREŚLONYCH
            ZASTOSOWAŃ.W celu uzyskania bliższych informacji sięgnij do
            Powszechnej Licencji Publicznej GNU.

            Z pewnością wraz z niniejszym programem otrzymałeś też egzemplarz
            Powszechnej Licencji Publicznej GNU(GNU General Public License);
            jeśli nie - napisz do Free Software Foundation, Inc., 59 Temple
            Place, Fifth face, Boston, MA  02110 - 1301  USA
            */

            #include "{}.h"

            namespace Models {{

                {} {};

                {}::{}() {{
                    vertices={}Internal::vertices;
                    normals={}Internal::normals;
                    vertexNormals={}Internal::vertexNormals;
                    texCoords={}Internal::texCoords;
                    colors={}Internal::colors;
                    vertexCount={}Internal::vertexCount;
                }}

                {}::~{}() {{
                }}

                void {}::drawSolid() {{
                    glEnable(GL_NORMALIZE);

                    glEnableClientState(GL_VERTEX_ARRAY);
                    //glEnableClientState(GL_COLOR_ARRAY);
                    //glEnableClientState(GL_NORMAL_ARRAY);
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

                    glVertexPointer(4,GL_FLOAT,0,vertices);
                    //glColorPointer(4,GL_FLOAT,0,colors);
                    glNormalPointer(GL_FLOAT,sizeof(float)*4,vertexNormals);
                    glTexCoordPointer(2,GL_FLOAT,0,texCoords);

                    glDrawArrays(GL_TRIANGLES,0,vertexCount);

                    glDisableClientState(GL_VERTEX_ARRAY);
                    //glDisableClientState(GL_COLOR_ARRAY);
                    //glDisableClientState(GL_NORMAL_ARRAY);
                    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                }}

                namespace {}Internal {{
                        unsigned int vertexCount={};

                        float vertices[]={{
                            {}

                        }};

                        float colors[]={{
                        }};

                        float normals[]={{
                            {}
                        }};

                        float vertexNormals[]={{

                        }};

                        float texCoords[]={{
                            {}
                        }};
                }}
            }}

            """.format(lower_model_name, model_name, lower_model_name, model_name,
            model_name, model_name, model_name, model_name, model_name, model_name,
            model_name, model_name, model_name, model_name, model_name, vertices_number,
            vertices, normals, tex_coords)
            cpp_file.write(data)


filename = input("Input the name of the obj file: ")
convert_to_cpp(filename)

Upvotes: 1

Views: 462

Answers (1)

Rabbid76
Rabbid76

Reputation: 211277

The obj file consists of vertex coordinates with 3 components (x, y, z), normal vectors with 3 components (x, y, z) and texture coordinates with 2 components (u, v).

The first paramter of glVertexPointer specifies the number of coordinates (components) per vertex and the first parameter of glTexCoordPointer specifies the number of coordinates (components) per texture attribute.

You did the the specification of the texture coordinates well, but in the definition of the vertex coordinates you specified 4 components instead of 3.

Change the definition of the array of vertex coordinates:

glVertexPointer(3, GL_FLOAT, 0, vertices);

Note, since the vertex array was defined with 4 components instead of 3, the access to the vertex coordinates is misaligned, and out of bounds at the end. This causes that the vertex coordinates of the rendered mesh seems to be arbitrary.


The 2nd parameter of glNormalPointer specifies the byte offset between consecutive normals. If stride is 0, the normals are understood to be tightly packed in the array.

The normals vectors consists of 3 components (as expected by glNormalPointer) and they are tightly packed.

Either the stride parameter has to be 0:

glNormalPointer(GL_FLOAT, 0, vertexNormals);

or it has to be 3*sizeof(float):

glNormalPointer(GL_FLOAT, sizeof(float)*3, vertexNormals);

In the function round number the attribute coordinates are rounded to 2 decimal places, but in the obj file the vertex coordinates have been submitted with an accuracy up to 5 decimal places.

Change the rounding of the numbers from 2 to 5 decimal places:

def round_number(string_number):
    if "\n" in string_number:
        string_number = string_number.replace("\n", "")
    if "." in string_number:
        parts = string_number.split(".")
        if len(parts[1]) > 5: # <---------------- 5 instead of 2
            return parts[0] + "." + parts[1][:2]
    return string_number 

Upvotes: 2

Related Questions