rasterizer

C++ software renderer

feat: MTL parsing

Arjun Choudhary contact@arjunchoudhary.com

commit: 4f739ef parent: c3fac5b

3 files changed, 121 insertions(+), 6 deletions(-)
MODsrc/mesh.cpp+90-6
MODsrc/mesh.h+12-0
MODsrc/util_string.h+19-0
MOD · src/mesh.cpp +90 -6
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -4,13 +4,17 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "globals.h"
-#include "util_math.h"
 #include "util_string.h"
+#include "texture.h"
+
+int current_material_idx = 0;
+int current_texture_idx  = 0;
 
 Mesh parse_obj(const char *filename) {
     Mesh mesh;
 
+    bool obj_has_material = false;
+
     FILE *file = fopen(filename, "r");
     if (!file) {
         printf("Failed to open file: %s\n", filename);
@@ -20,6 +24,16 @@ Mesh parse_obj(const char *filename) {
     char   line_buffer[1024];
     string line = {nullptr, 0};
 
+    char base_dir[512];
+    strcpy(base_dir, filename);
+
+    char *last_slash = strrchr(base_dir, '/');
+    if (last_slash) {
+        last_slash[1] = '\0';
+    } else {
+        base_dir[0] = '\0';
+    }
+
     while (fgets(line_buffer, sizeof(line_buffer), file)) {
         line = string_from_cstr(line_buffer);
         string_trim(&line);
@@ -27,19 +41,19 @@ Mesh parse_obj(const char *filename) {
         if (line.size == 0 || line.str[0] == '#') {
             continue;
         }
-        if (strncmp(line.str, "v ", 2) == 0) {
+        if (string_starts_with(line, "v ")) {
             vec3f v;
             sscanf(line.str, "v %f %f %f", &v.x, &v.y, &v.z);
             mesh.vertices.push_back(v);
-        } else if (strncmp(line.str, "vt ", 3) == 0) {
+        } else if (string_starts_with(line, "vt ")) {
             vec2f vt;
             sscanf(line.str, "vt %f %f", &vt.x, &vt.y);
             mesh.texcoords.push_back(vt);
-        } else if (strncmp(line.str, "vn ", 3) == 0) {
+        } else if (string_starts_with(line, "vn ")) {
             vec3f vn;
             sscanf(line.str, "vn %f %f %f", &vn.x, &vn.y, &vn.z);
             mesh.normals.push_back(vn);
-        } else if (strncmp(line.str, "f ", 2) == 0) {
+        } else if (string_starts_with(line, "f ")) {
             char *face_token = strtok(line.str + 2, " \n\r");
             while (face_token) {
                 Face  idx = {0, 0, 0};
@@ -62,9 +76,79 @@ Mesh parse_obj(const char *filename) {
                 if (v > 0) idx.vertex_idx = v - 1;
                 if (vt > 0) idx.vertex_texture_idx = vt - 1;
                 if (vn > 0) idx.vertex_normal_idx = vn - 1;
+
+                if (obj_has_material) {
+                    idx.mat_idx = current_material_idx;
+                } else {
+                    idx.mat_idx = 0;
+                }
+
                 mesh.faces.push_back(idx);
                 face_token = strtok(nullptr, " \n\r");
             }
+        } else if (string_starts_with(line, "mtllib ")) {
+            obj_has_material = true;
+
+            char mtlfile[256];
+            sscanf(line.str + 7, "%255s", mtlfile);
+            char mtlpath[512];
+            strcpy(mtlpath, base_dir);
+            strcat(mtlpath, mtlfile);
+
+            FILE *mtl = fopen(mtlpath, "r");
+            if (!mtl) {
+                printf("Failed to open MTL: %s\n", mtlpath);
+                continue;
+            }
+
+            Material current = {};
+            char     mtl_line[512];
+            while (fgets(mtl_line, sizeof(mtl_line), mtl)) {
+                string mline = string_from_cstr(mtl_line);
+                string_trim(&mline);
+
+                if (string_starts_with(mline, "newmtl ")) {
+                    if (current.name[0] != '\0') {
+                        materials.push_back(current);
+                    }
+                    memset(&current, 0, sizeof(Material));
+                    sscanf(mline.str + 7, "%127s", current.name);
+                } else if (string_starts_with(mline, "Ka ")) {
+                    sscanf(mline.str + 3, "%f %f %f", &current.ambient.x, &current.ambient.y, &current.ambient.z);
+                } else if (string_starts_with(mline, "Kd ")) {
+                    sscanf(mline.str + 3, "%f %f %f", &current.diffuse.x, &current.diffuse.y, &current.diffuse.z);
+                } else if (string_starts_with(mline, "Ks ")) {
+                    sscanf(mline.str + 3, "%f %f %f", &current.specular.x, &current.specular.y, &current.specular.z);
+                } else if (string_starts_with(mline, "map_Kd ")) {
+                    char texfile[256];
+                    sscanf(mline.str + 7, "%255s", texfile);
+                    char fulltex[512];
+                    strcpy(fulltex, base_dir);
+                    strcat(fulltex, texfile);
+
+                    Texture t = load_texture(fulltex);
+                    textures.push_back(t);
+
+                    current.texture_img_idx = (int)textures.size() - 1;
+                    strcpy(current.texture_path, fulltex);
+                }
+            }
+            if (current.name[0] != '\0') {
+                materials.push_back(current);
+            }
+            fclose(mtl);
+        } else if (string_starts_with(line, "usemtl ")) {
+            obj_has_material = true;
+
+            char matname[128];
+            sscanf(line.str + 7, "%127s", matname);
+
+            for (int i = 0; i < materials.size(); i++) {
+                if (strcmp(materials[i].name, matname) == 0) {
+                    current_material_idx = i;
+                    break;
+                }
+            }
         }
     }
     fclose(file);
MOD · src/mesh.h +12 -0
--- a/src/mesh.h
+++ b/src/mesh.h
@@ -2,6 +2,7 @@
 
 #include <vector>
 
+#include "globals.h"
 #include "util_math.h"
 
 using std::vector;
@@ -10,6 +11,7 @@ struct Face {
     int vertex_idx;
     int vertex_texture_idx;
     int vertex_normal_idx;
+    u32 mat_idx;
 };
 
 struct Transform {
@@ -28,6 +30,16 @@ struct Transform {
     }
 };
 
+struct Material {
+    char  name[128];
+    char  texture_path[512];
+    vec3f ambient;          // Ka
+    vec3f diffuse;          // Kd;
+    vec3f specular;         // Ks;
+    u32   texture_img_idx;  // 0 sentinel for missing, populated in initialize_scene()
+};
+extern vector<Material> materials;
+
 struct Mesh {
     vector<vec3f> vertices;
     vector<vec2f> texcoords;
MOD · src/util_string.h +19 -0
--- a/src/util_string.h
+++ b/src/util_string.h
@@ -37,4 +37,23 @@ inline void string_trim(string *s) {
     s->str += start;
     s->size = end - start;
 }
+
+inline bool string_starts_with(const string &s, const char *prefix) {
+    size_t len = strlen(prefix);
+    if (s.size < len) {
+        return false;
+    }
+    return strncmp(s.str, prefix, len) == 0;
+}
+
+inline bool string_equals(string a, string b) {
+    if (a.size != b.size) {
+        return false;
+    }
+    for (uint32_t i = 0; i < a.size; ++i) {
+        if (a.str[i] != b.str[i]) {
+            return false;
+        }
+    }
+    return true;
 }