RyanHub - file viewer
filename: src/render/model.c
branch: main
back to repo
// model.c

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

#define TINYOBJ_LOADER_C_IMPLEMENTATION
#include "tinyobj_loader_c.h"

void simple_file_reader(
    void* ctx,
    const char* filename,
    int is_mtl,
    const char* obj_filename,
    char** out_buf,
    size_t* out_len)
{
    (void)ctx; (void)is_mtl; (void)obj_filename;
    FILE* fp = fopen(filename, "rb");
    if (!fp) {
        *out_buf = NULL;
        *out_len = 0;
        return;
    }
    fseek(fp, 0, SEEK_END);
    long sz = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    char* buf = (char*)malloc(sz + 1);
    if (!buf) {
        fclose(fp);
        *out_buf = NULL;
        *out_len = 0;
        return;
    }
    fread(buf, 1, sz, fp);
    buf[sz] = 0;
    fclose(fp);

    *out_buf = buf;
    *out_len = sz;
}

Model* model_create(const float* vertex_data, size_t vertex_count, int* attributes, int attrib_count) {
    Model* model = malloc(sizeof(Model));
    if (!model) return NULL;

    model->vertex_count = (int)vertex_count;

    int stride = 0;
    for (int i = 0; i < attrib_count; i++) {
        stride += attributes[i];
    }
    model->vertex_stride = stride;

    glGenVertexArrays(1, &model->vao);
    glGenBuffers(1, &model->vbo);

    glBindVertexArray(model->vao);
    glBindBuffer(GL_ARRAY_BUFFER, model->vbo);
    glBufferData(GL_ARRAY_BUFFER, vertex_count * stride * sizeof(float), vertex_data, GL_STATIC_DRAW);

    int offset = 0;
    for (int i = 0; i < attrib_count; i++) {
        glVertexAttribPointer(i, attributes[i], GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)(offset * sizeof(float)));
        glEnableVertexAttribArray(i);
        offset += attributes[i];
    }

    glBindVertexArray(0);
    return model;
}

Model* model_load(const char* filepath) {
    tinyobj_attrib_t attrib;
    tinyobj_shape_t* shapes = NULL;
    size_t num_shapes = 0;
    tinyobj_material_t* materials = NULL;
    size_t num_materials = 0;

    unsigned int flags = TINYOBJ_FLAG_TRIANGULATE;

    int ret = tinyobj_parse_obj(
        &attrib,
        &shapes,
        &num_shapes,
        &materials,
        &num_materials,
        filepath,
        simple_file_reader,
        NULL,
        flags
    );


    if (ret != TINYOBJ_SUCCESS) {
        printf("Failed to load model: %s\n", filepath);
        return NULL;
    }

    size_t triangle_count = 0;
    for (size_t s = 0; s < num_shapes; s++) {
        const tinyobj_shape_t* shape = &shapes[s];
        size_t index_offset = 0;
        for (size_t f = 0; f < shape->length; f++) {
            int fv = attrib.face_num_verts[f];
            if (fv == 3) triangle_count++;
            index_offset += fv;
        }
    }
    size_t vertex_count = triangle_count * 3;

    float* vertex_data = malloc(sizeof(float) * 8 * vertex_count);

    if (!vertex_data) return NULL;

    size_t data_count = 0;
    for (size_t s = 0; s < num_shapes; s++) {
        const tinyobj_shape_t* shape = &shapes[s];
        size_t index_offset = 0;

        for (size_t f = 0; f < shape->length; f++) {
            int fv = attrib.face_num_verts[f];
            if (fv != 3) {
                index_offset += fv;
                continue;
            }
            for (int v = 0; v < 3; v++) {
                tinyobj_vertex_index_t idx = attrib.faces[index_offset + v];

                float vx = attrib.vertices[3 * idx.v_idx + 0];
                float vy = attrib.vertices[3 * idx.v_idx + 1];
                float vz = attrib.vertices[3 * idx.v_idx + 2];

                float nx = 0.0f, ny = 0.0f, nz = 1.0f;
                if (idx.vn_idx >= 0 && attrib.num_normals > 0) {
                    nx = attrib.normals[3 * idx.vn_idx + 0];
                    ny = attrib.normals[3 * idx.vn_idx + 1];
                    nz = attrib.normals[3 * idx.vn_idx + 2];
                }

                float tx = 0.0f, ty = 0.0f;
                if (idx.vt_idx >= 0 && attrib.num_texcoords > 0) {
                    tx = attrib.texcoords[2 * idx.vt_idx + 0];
                    ty = 1.0f - attrib.texcoords[2 * idx.vt_idx + 1];
                }

                vertex_data[data_count++] = vx;
                vertex_data[data_count++] = vy;
                vertex_data[data_count++] = vz;
                vertex_data[data_count++] = nx;
                vertex_data[data_count++] = ny;
                vertex_data[data_count++] = nz;
                vertex_data[data_count++] = tx;
                vertex_data[data_count++] = ty;
            }
            index_offset += fv;
        }
    }

    int attributes[] = { 3, 3, 2 };
    int num_vertices = data_count / 8;
    Model* model = model_create(vertex_data, num_vertices, attributes, 3);

    free(vertex_data);
    tinyobj_attrib_free(&attrib);
    tinyobj_shapes_free(shapes, num_shapes);
    tinyobj_materials_free(materials, num_materials);

    if (!model) {
        printf("bad model \n");
        return NULL;
    }

    return model;
}

void model_destroy(Model* model) {
    if (!model) return;
    glDeleteBuffers(1, &model->vbo);
    glDeleteVertexArrays(1, &model->vao);
    free(model);
}