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.