mesh.cpp

C++ software renderer

src/mesh.cpp

5.28 KB
#include "mesh.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "texture.h"
#include "util_string.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);
        return mesh;
    }

    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);

        if (line.size == 0 || line.str[0] == '#') {
            continue;
        }
        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 (string_starts_with(line, "vt ")) {
            vec2f vt;
            sscanf(line.str, "vt %f %f", &vt.x, &vt.y);
            mesh.texcoords.push_back(vt);
        } 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 (string_starts_with(line, "f ")) {
            char *face_token = strtok(line.str + 2, " \n\r");
            while (face_token) {
                Face  idx = {0, 0, 0};
                int   v = 0, vt = 0, vn = 0;
                char *slash1 = strchr(face_token, '/');
                if (slash1) {
                    *slash1      = '\0';
                    v            = atoi(face_token);
                    char *slash2 = strchr(slash1 + 1, '/');
                    if (slash2) {
                        *slash2 = '\0';
                        vt      = atoi(slash1 + 1);
                        vn      = atoi(slash2 + 1);
                    } else {
                        vt = atoi(slash1 + 1);
                    }
                } else {
                    v = atoi(face_token);
                }
                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);
    return mesh;
}