Thread overview
Segmentation fault while reading a file
Aug 01
matheus
Aug 01
IchorDev
Aug 02
IchorDev
July 31

I decided to attempt to write my own OBJ loading library, seeing as the spec for the format is readily available online. Building with DMD and on Windows, and testing on this model, the code acts normally for a while, and then apparently segfaults after trying to call readln().

The code is as follows:

module mesh;

public import gl3n.linalg;
public import gl3n.math;
import shader;
import bindbc.opengl;
import std.conv : to;
import texture;

struct Vertex
{
    vec3 position;
    vec3 normal;
    vec2 texCoord;
}

struct Mat
{
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    vec3 emission;
    float alpha;
    float shiny;
    size_t[] diffuseTextures;
    size_t[] specularTextures;
    size_t[] ambientTextures; // won't be used in practice;
    size_t[] emissionTextures;
}

struct Texture
{
    uint id;
    string type;
}

class Mesh
{
    public:
        Vertex[] vertices;
        size_t[] indices;
        Texture[] textures;

        Mat[string] materials;

        this(Vertex[] vertices, size_t[] indices, Texture[] textures)
        {
            this.vertices = vertices.dup;
            this.indices = indices.dup;
            this.textures = textures.dup;
            setupMesh();
        }

        this(string filename)
        {
            this.readFromFile(filename);
            setupMesh();
        }
        void Draw(Shader shader)
        {
            glBindVertexArray(VAO);
            uint diffuseNR, specNR = 1;
            for(uint i = 0; i < textures.length; i++)
            {
                glActiveTexture(GL_TEXTURE0 + i);
                string name = textures[i].type;
                if(name == "diffuse_texture")
                    name ~= to!string(++diffuseNR);
                else if(name == "spec_texture")
                    name = "material." ~ name ~ to!string(++specNR);
                shader.setUniform(name.dup, i);
                glBindTexture(GL_TEXTURE_2D, textures[i].id);
            }
            glActiveTexture(GL_TEXTURE0);

            glDrawElements(GL_TRIANGLES, cast(int)indices.length, GL_UNSIGNED_INT, cast(void*)0);
            glBindVertexArray(0);
        }
    private:
        // render data
        uint VAO, VBO, EBO;
        vec3[] positions; //We ignore W for now
        vec2[] texCoords;  //We are currently working w/ 2D textures
        vec3[] normals;
        Face[] faces;
        size_t[size_t[3]] lookup_table;

        void setupMesh()
        {
            glGenBuffers(1, &VAO);
            glBindVertexArray(VAO);

            glGenBuffers(1, &VBO);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferData(GL_ARRAY_BUFFER, vertices.length * Vertex.sizeof, vertices.ptr, GL_STATIC_DRAW);

            glGenBuffers(1, &EBO);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof, indices.ptr, GL_STATIC_DRAW);

            // Memory Layout go brrrrrrrrrrrrrrrrrrrrrr
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * float.sizeof, cast(void*)0);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * float.sizeof, cast(void*)(Vertex.normal.offsetof));
            glEnableVertexAttribArray(1);
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * float.sizeof, cast(void*)(Vertex.texCoord.offsetof));
            glEnableVertexAttribArray(2);
        }

        void readFromFile(in string filename)
        {
            bool smooth = false;
            import std.stdio;
            import std.exception : ErrnoException;
            import std.uni;
            import std.conv : to;
            import std.array;

            import std.algorithm;
            File file;
            try
            {
                file = File(filename, "r");
            }
            catch(ErrnoException e)
            {
                writeln("Failed to open file \'" ~ filename ~ "\': " ~ e.msg);
                return;
            }

            {
                scope(exit) file.close();
                char[] buf;
                while(file.readln(buf))
                {
                    if(buf.startsWith("v "))
                    {
                        vec3 vertex;
                        size_t i = 2;
                        string temp;
                        static foreach (x; ["x", "y", "z"])
                        {
                            for(; i < buf.length && !buf[i].isNumber; i++) {}
                            for(; i < buf.length && (buf[i].isNumber || buf[i] == '.'); i++) { temp ~= buf[i]; }
                            mixin("vertex." ~ x ~ " = to!float(temp);");
                            temp = "";
                        }

                        positions ~= vertex;
                    }
                    else if(buf.startsWith("vn"))
                    {
                        vec3 normal;
                        size_t i = 2;
                        string temp;
                        static foreach (x; ["x", "y", "z"])
                        {
                            for(;i < buf.length && !buf[i].isNumber; i++) {}
                            for(; i < buf.length && (buf[i].isNumber || buf[i] == '.'); i++) { temp ~= buf[i]; }
                            mixin("normal." ~ x ~ " = to!float(temp);");
                            temp = "";
                        }
                        normals ~= normal;
                    }
                    else if(buf.startsWith("vt"))
                    {
                        vec2 tex;
                        size_t i = 2;
                        string temp;
                        static foreach (x; ["x", "y"])
                        {
                            for(;i < buf.length && !buf[i].isNumber; i++) {}
                            for(; i < buf.length && (buf[i].isNumber || buf[i] == '.'); i++) { temp ~= buf[i]; }
                            mixin("tex." ~ x ~ " = to!float(temp);");
                            temp = "";
                        }
                        texCoords ~= tex;
                    }
                    else if(buf.startsWith("f ")) // Handle Faces
                    {
                        Face face;
                        Vertex vertex;
                        size_t i = 2;
                        string temp;
                        while(i < buf.length-1) // -1 because otherwise the loop runs an extra time and `to` fails to convert a blank string into a `size_t`
                        {
                            face.vertices.length++;
                            uint j = 0;
                            static foreach (x; ["positions", "texCoords", "normals"])
                            {
                                for(; i < buf.length && !buf[i].isNumber; i++) {}
                                "1".writeln;
                                for(; i < buf.length && buf[i].isNumber; i++) { temp ~= buf[i]; }
                                "2".writeln;
                                mixin("vertex." ~ x[0 .. $-1] ~ " = " ~ x ~ "[to!size_t(temp) - 1];");
                                "3".writeln;
                                face.vertices[$-1][j] = to!size_t(temp) - 1;
                                "4".writeln;
                                ++j;
                                temp = "";
                            }
                            vertex = vertex.init;
                        }
                        face.smooth = smooth;
                        faces ~= face;
                        "END".writeln;
                    }
                    else if(buf.startsWith("s")) // smoothing group
                    {
                        uint i = 1;
                        for(; i < buf.length && !buf[i].isAlphaNum; i++) {}
                        if(buf[i] == '0' || (i + 2 < buf.length && buf[i .. i + 3] == "off"))
                            smooth = false;
                        else if(buf[i].isNumber)
                            smooth = true;
                        else
                            throw new Exception("Corrupted object file: " ~ filename);
                    }
                    else if(buf.startsWith("mtllib")) // Handle material data
                    {
                        foreach(fil; buf[6 .. $].split!isWhite)
                        {
                            if(fil.length == 0)
                                continue;
                            else
                            {

                                File mtl;
                                try
                                {
                                    mtl = File(filename, "r");
                                }
                                catch (ErrnoException e)
                                {
                                    writeln("Could not open MTL file \'" ~ filename ~ "\': " ~ e.msg);
                                    return;
                                }
                                {
                                    scope(exit) mtl.close();
                                    char[] mtlbuf;
                                    string key;
                                    Loop: while(mtl.readln(mtlbuf))
                                    {
                                        if(mtlbuf.startsWith("newmtl"))
                                        {
                                           key = join(mtlbuf[6 .. $].split!isWhite).idup; // Name of the material
                                            "here".writeln;
                                        }
                                        static foreach(vec; ["ambient", "diffuse", "specular", "emission"])
                                        {
                                            if(mtlbuf.startsWith("K" ~ vec[0])) // Ambient Color
                                            {
                                                ubyte j = 0;
                                                foreach(val; mtlbuf[2 .. $].split!isWhite)
                                                {
                                                    final switch(j)
                                                    {
                                                        case 0:
                                                            mixin("materials[key]." ~ vec ~ ".x = to!float(val);");
                                                            break;
                                                        case 1:
                                                            mixin("materials[key]." ~ vec ~ ".y = to!float(val);");
                                                            break;
                                                        case 2:
                                                            mixin("materials[key]." ~ vec ~ ".z = to!float(val);");
                                                            break;
                                                    }
                                                    ++j;
                                                }
                                                continue Loop;
                                            }
                                            if(mtlbuf.startsWith("map_K" ~ vec[0])) //Texture Maps
                                            {
                                                string fname = mtlbuf[5 .. $].split!isWhite.join.idup;
                                                int width, hight, nrChannels;
                                                newTexture(fname, width, hight, nrChannels);
                                                mixin("materials[key]."  ~ vec ~ "Textures ~= textures.length - 1;");
                                            }
                                        }
                                        if(mtlbuf.startsWith("d")) //Alpha
                                        {
                                            materials[key].alpha = mtlbuf[1 .. $].split!isWhite.join.to!float;
                                        }
                                        else if(mtlbuf.startsWith("Tr"))
                                        {
                                            materials[key].alpha = 1 - mtlbuf[1 .. $].split!isWhite.join.to!float;
                                        }
                                        else if(mtlbuf.startsWith("Ns")) // Shininess
                                        {
                                            materials[key].shiny = mtlbuf[2 .. $].split!isWhite.join.to!float;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Process the face & Vertex data to create a vertex array and an EBO.
            foreach(face; faces)
            {
                foreach(v; face.vertices)
                {
                    vertices ~= Vertex(positions[v[0]], normals[v[2]], texCoords[v[1]]);
                    lookup_table[v] = vertices.length - 1;
                    indices ~= lookup_table[v];
                }
            }
        }
}

struct Face
{
    size_t[3][] vertices;
    bool smooth = false;
}

The various writeln statements were my attempt to find the cause of the SEGFAULT, and it apparently occurs at some point while executing readln() as part of the condition of the while loop. Am I doing something wrong that is leading to memory being illegally accessed, or is readln bugging out because the file (backpack.obj contained in the linked .zip file) is too large?

Thanks in advance.

August 01
On Wednesday, 31 July 2024 at 23:06:30 UTC, Ruby The Roobster wrote:
> ... or is `readln` bugging out because the file (backpack.obj contained in the linked .zip file) is too large?
> ...

I don't have I compiler in hand to try your code at moment, but about your concern over the size of the file, you could try a simple object[1]:

# cube.obj
#

g cube

v  0.0  0.0  0.0
v  0.0  0.0  1.0
v  0.0  1.0  0.0
v  0.0  1.0  1.0
v  1.0  0.0  0.0
v  1.0  0.0  1.0
v  1.0  1.0  0.0
v  1.0  1.0  1.0

vn  0.0  0.0  1.0
vn  0.0  0.0 -1.0
vn  0.0  1.0  0.0
vn  0.0 -1.0  0.0
vn  1.0  0.0  0.0
vn -1.0  0.0  0.0

f  1//2  7//2  5//2
f  1//2  3//2  7//2
f  1//6  4//6  3//6
f  1//6  2//6  4//6
f  3//3  8//3  7//3
f  3//3  4//3  8//3
f  5//5  7//5  8//5
f  5//5  8//5  6//5
f  1//4  5//4  6//4
f  1//4  6//4  2//4
f  2//1  6//1  8//1
f  2//1  8//1  4//1

Matheus.

[1] https://cs.wellesley.edu/~cs307/readings/obj-ojects.html
August 01
Nevermind. The segfault happened because I accidentally used the Mesh class before loading OpenGL.  I don't know if it works as intended, but it no longer crashes.


August 01

Hey just a heads up, you might wanna use readText and lineSplitter just so you don’t have to deal with file handles. Also you can use something like formattedRead for parsing formatted floats more easily.
Also please only use in if you have -preview=in and you know what it’s for. You were probably looking for const, because in with -preview=in is not useful for strings.

August 01

On Thursday, 1 August 2024 at 07:03:04 UTC, IchorDev wrote:

>

Hey just a heads up, you might wanna use readText and lineSplitter just so you don’t have to deal with file handles.
...

Thank you. I am not very well acquainted with the standard library, and this cleans up things significantly.

Question: Is there a good guide to Phobos anywhere? I would like to learn the more commonly used algorithms / convenience functions, so I don't have to look through the docs trying to find what I want, and so that I don't keep having to re-invent the wheel when trying to work with data.

August 02

On Thursday, 1 August 2024 at 14:42:36 UTC, Ruby The Roobster wrote:

>

Thank you. I am not very well acquainted with the standard library, and this cleans up things significantly.

Question: Is there a good guide to Phobos anywhere? I would like to learn the more commonly used algorithms / convenience functions, so I don't have to look through the docs trying to find what I want, and so that I don't keep having to re-invent the wheel when trying to work with data.

I’m actually not sure. I think the best stuff is usually the examples in the documentation. If you’re searching for whether something might be in Phobos, try seeing if there’s a module that sounds right in the index. In general I just peruse different modules until I see a function that fits what I need. It also depends on how functional you like your code, there’s a lot of map/filter/etc. stuff that I regularly write my own ad-hoc code for.