Skip to content

Instantly share code, notes, and snippets.

@meowstr
Created February 27, 2024 20:11
Show Gist options
  • Select an option

  • Save meowstr/0abe80e94c7917c7137b35509faf248c to your computer and use it in GitHub Desktop.

Select an option

Save meowstr/0abe80e94c7917c7137b35509faf248c to your computer and use it in GitHub Desktop.

Revisions

  1. meowstr created this gist Feb 27, 2024.
    517 changes: 517 additions & 0 deletions parser.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,517 @@
    #include "wavefront.hpp"

    #include "logging.hpp"

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

    // #define MAX_VERTEX_COUNT 4096
    // #define MAX_OBJ_COUNT 16
    #define MAX_VERTEX_COUNT 100000
    #define MAX_OBJ_COUNT 1000
    #define MAX_NAME_LENGTH 64
    #define MAX_POLYGON_LENGTH 32
    #define MAX_LINE_LENGTH 256
    #define MAX_MATERIAL_GROUP_COUNT 64

    #define MAX_MATERIAL_COUNT 32

    static int starts_with( const char * line, const char * str )
    {
    return strncmp( line, str, strlen( str ) ) == 0;
    }

    // line parsers [start] ////////////////////////////////////////////////////////

    static int parse_object_line( char * out_name, const char * line )
    {
    snprintf( out_name, MAX_NAME_LENGTH, "%s", line + strlen( "o " ) );
    return 0;
    }

    static int parse_material_lib_line( char * out_name, const char * line )
    {
    snprintf( out_name, MAX_NAME_LENGTH, "%s", line + strlen( "mtllib " ) );
    return 0;
    }

    static int parse_usemtl_line( char * out_name, const char * line )
    {
    snprintf( out_name, MAX_NAME_LENGTH, "%s", line + strlen( "usemtl " ) );
    return 0;
    }

    static int parse_position_line( float * out3, const char * line )
    {
    int count = sscanf( line, "v %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_normal_line( float * out3, const char * line )
    {
    int count = sscanf( line, "vn %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_uv_line( float * out2, const char * line )
    {
    int count = sscanf( line, "vt %f %f", out2 + 0, out2 + 1 );
    return count != 2;
    }

    static int
    parse_face_line( int * out_index_list, int * out_index_count, char * line )
    {
    int len = strlen( line );
    int count = 0;

    for ( int i = 0; i < len; i++ ) {
    if ( line[ i ] == '/' ) line[ i ] = ' ';
    }

    for ( int i = 0; i < len; i++ ) {
    if ( line[ i ] != ' ' ) continue;

    if ( count >= MAX_POLYGON_LENGTH * 3 ) {
    ERROR_LOG( "polygon limit exceeded" );
    return 1;
    }

    out_index_list[ count ] = atoi( line + i ) - 1;
    count++;
    }

    *out_index_count = count;

    return count % 3 != 0;
    }

    static int parse_newmtl_line( char * out_name, const char * line )
    {
    snprintf( out_name, MAX_NAME_LENGTH, "%s", line + strlen( "newmtl " ) );
    return 0;
    }

    static int parse_ns_line( float * out, const char * line )
    {
    int count = sscanf( line, "Ns %f", out );
    return count != 1;
    }

    static int parse_ka_line( float * out3, const char * line )
    {
    int count = sscanf( line, "Ka %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_kd_line( float * out3, const char * line )
    {
    int count = sscanf( line, "Kd %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_ks_line( float * out3, const char * line )
    {
    int count = sscanf( line, "Ks %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_ke_line( float * out3, const char * line )
    {
    int count = sscanf( line, "Ke %f %f %f", out3 + 0, out3 + 1, out3 + 2 );
    return count != 3;
    }

    static int parse_ni_line( float * out, const char * line )
    {
    int count = sscanf( line, "Ni %f", out );
    return count != 1;
    }

    static int parse_d_line( float * out, const char * line )
    {
    int count = sscanf( line, "d %f", out );
    return count != 1;
    }

    static int parse_illum_line( int * out, const char * line )
    {
    int count = sscanf( line, "illum %d", out );
    return count != 1;
    }

    static int parse_map_kd_line( char * out_name, const char * line )
    {
    snprintf( out_name, MAX_NAME_LENGTH, "%s", line + strlen( "map_Kd " ) );
    return 0;
    }

    // line parsers [end] //////////////////////////////////////////////////////////

    static void append_vertex(
    wavefront_t * out,
    float * pos_list,
    float * normal_list,
    float * uv_list,
    int * index_list,
    int index
    )
    {
    if ( out->vertex_count >= MAX_VERTEX_COUNT ) {
    ERROR_LOG( "vertex limit exceeded" );
    return;
    }

    int i = out->vertex_count;

    int pos_index = index_list[ index * 3 + 0 ];
    int uv_index = index_list[ index * 3 + 1 ];
    int normal_index = index_list[ index * 3 + 2 ];

    out->pos_list[ i * 3 + 0 ] = pos_list[ pos_index * 3 + 0 ];
    out->pos_list[ i * 3 + 1 ] = pos_list[ pos_index * 3 + 1 ];
    out->pos_list[ i * 3 + 2 ] = pos_list[ pos_index * 3 + 2 ];

    out->normal_list[ i * 3 + 0 ] = normal_list[ normal_index * 3 + 0 ];
    out->normal_list[ i * 3 + 1 ] = normal_list[ normal_index * 3 + 1 ];
    out->normal_list[ i * 3 + 2 ] = normal_list[ normal_index * 3 + 2 ];

    out->uv_list[ i * 2 + 0 ] = uv_list[ uv_index * 2 + 0 ];
    out->uv_list[ i * 2 + 1 ] = uv_list[ uv_index * 2 + 1 ];

    out->vertex_count++;
    }

    static void append_face(
    wavefront_t * out,
    float * pos_list,
    float * normal_list,
    float * uv_list,
    int * index_list,
    int index_count
    )
    {
    int poly_size = index_count / 3;

    // make triangle fan
    for ( int i = 0; i < poly_size - 2; i++ ) {
    int v1 = 0;
    int v2 = i + 1;
    int v3 = i + 2;

    append_vertex( out, pos_list, normal_list, uv_list, index_list, v1 );
    append_vertex( out, pos_list, normal_list, uv_list, index_list, v2 );
    append_vertex( out, pos_list, normal_list, uv_list, index_list, v3 );
    }
    }

    void wavefront_t::compute_bounds( float * min_vec3, float * max_vec3 )
    {
    min_vec3[ 0 ] = FLT_MAX;
    min_vec3[ 1 ] = FLT_MAX;
    min_vec3[ 2 ] = FLT_MAX;

    max_vec3[ 0 ] = -FLT_MAX;
    max_vec3[ 1 ] = -FLT_MAX;
    max_vec3[ 2 ] = -FLT_MAX;

    for ( int i = 0; i < vertex_count; i++ ) {
    min_vec3[ 0 ] = fmin( min_vec3[ 0 ], pos_list[ i * 3 + 0 ] );
    min_vec3[ 1 ] = fmin( min_vec3[ 1 ], pos_list[ i * 3 + 1 ] );
    min_vec3[ 2 ] = fmin( min_vec3[ 2 ], pos_list[ i * 3 + 2 ] );

    max_vec3[ 0 ] = fmax( max_vec3[ 0 ], pos_list[ i * 3 + 0 ] );
    max_vec3[ 1 ] = fmax( max_vec3[ 1 ], pos_list[ i * 3 + 1 ] );
    max_vec3[ 2 ] = fmax( max_vec3[ 2 ], pos_list[ i * 3 + 2 ] );
    }
    }

    static void print_header( wavefront_t * out )
    {
    float bounds[ 3 ];

    float min[ 3 ];
    float max[ 3 ];

    out->compute_bounds( min, max );
    bounds[ 0 ] = max[ 0 ] - min[ 0 ];
    bounds[ 1 ] = max[ 1 ] - min[ 1 ];
    bounds[ 2 ] = max[ 2 ] - min[ 2 ];

    INFO_LOG( "mesh description:" );
    if ( out->obj_count > 0 ) {
    INFO_LOG( " name: %s", out->obj_name_list[ 0 ] );
    }
    INFO_LOG( " vertex count: %d", out->vertex_count );
    INFO_LOG( " object count: %d", out->obj_count );
    INFO_LOG(
    " bounds: %.2f x %.2f x %.2f",
    bounds[ 0 ],
    bounds[ 1 ],
    bounds[ 2 ]
    );
    }

    static void print_mtl_header( material_t * out, const char * name )
    {
    INFO_LOG( "material description:" );
    INFO_LOG( " name: %s", name );
    INFO_LOG( " Ns: %f", out->ns );
    INFO_LOG( " Ka: %f %f %f", out->ka[ 0 ], out->ka[ 1 ], out->ka[ 2 ] );
    INFO_LOG( " Kd: %f %f %f", out->kd[ 0 ], out->kd[ 1 ], out->kd[ 2 ] );
    INFO_LOG( " Ks: %f %f %f", out->ks[ 0 ], out->ks[ 1 ], out->ks[ 2 ] );
    INFO_LOG( " Ke: %f %f %f", out->ke[ 0 ], out->ke[ 1 ], out->ke[ 2 ] );
    INFO_LOG( " Ni: %f", out->ni );
    INFO_LOG( " d: %f", out->d );
    INFO_LOG( " illum: %d", out->illum );
    INFO_LOG( " map_Kd: %s", out->map_kd ? out->map_kd : "(null)" );
    }

    int load_wavefront( wavefront_t * out, res_t res )
    {
    char line[ MAX_LINE_LENGTH ];

    int cursor = 0;

    int errors = 0;

    // intialize output
    using c_string_t = const char *;
    out->pos_list = new float[ MAX_VERTEX_COUNT * 3 ];
    out->normal_list = new float[ MAX_VERTEX_COUNT * 3 ];
    out->uv_list = new float[ MAX_VERTEX_COUNT * 2 ];
    out->vertex_count = 0;
    out->obj_offset_list = new int[ MAX_OBJ_COUNT ];
    out->obj_name_list = new c_string_t[ MAX_OBJ_COUNT ];
    out->obj_count = 0;
    out->material_lib_filename = nullptr;
    out->material_group_material_list =
    new c_string_t[ MAX_MATERIAL_GROUP_COUNT ];
    out->material_group_offset_list = new int[ MAX_MATERIAL_GROUP_COUNT ];
    out->material_group_count = 0;

    // local storage
    float * pos_list = new float[ MAX_VERTEX_COUNT * 3 ];
    float * normal_list = new float[ MAX_VERTEX_COUNT * 3 ];
    float * uv_list = new float[ MAX_VERTEX_COUNT * 2 ];
    int pos_count = 0;
    int normal_count = 0;
    int uv_count = 0;

    while ( cursor < res.size ) {
    // load next line
    int i;
    for ( i = 0; i + cursor < res.size; i++ ) {
    char c = (char) res.data[ i + cursor ];

    if ( c == '\n' ) break;

    if ( i >= MAX_LINE_LENGTH ) {
    ERROR_LOG( "line length exceeded" );
    break;
    }

    line[ i ] = c;
    }
    cursor += i + 1;
    line[ i ] = '\0';

    // parse o command
    if ( starts_with( line, "o " ) ) {
    if ( out->obj_count >= MAX_OBJ_COUNT ) {
    ERROR_LOG( "object limit exceeded" );
    continue;
    }
    char * name = new char[ MAX_NAME_LENGTH ];
    errors += parse_object_line( name, line );
    out->obj_name_list[ out->obj_count ] = name;
    out->obj_count++;
    }

    // parse v command
    if ( starts_with( line, "v " ) ) {
    if ( pos_count >= MAX_VERTEX_COUNT * 3 ) {
    ERROR_LOG( "vertex limit exceeded" );
    continue;
    }
    float pos[ 3 ];
    errors += parse_position_line( pos, line );
    pos_list[ pos_count * 3 + 0 ] = pos[ 0 ];
    pos_list[ pos_count * 3 + 1 ] = pos[ 1 ];
    pos_list[ pos_count * 3 + 2 ] = pos[ 2 ];
    pos_count++;
    }

    // parse vn command
    if ( starts_with( line, "vn " ) ) {
    if ( normal_count >= MAX_VERTEX_COUNT * 3 ) {
    ERROR_LOG( "vertex limit exceeded" );
    continue;
    }
    float normal[ 3 ];
    errors += parse_normal_line( normal, line );
    normal_list[ normal_count * 3 + 0 ] = normal[ 0 ];
    normal_list[ normal_count * 3 + 1 ] = normal[ 1 ];
    normal_list[ normal_count * 3 + 2 ] = normal[ 2 ];
    normal_count++;
    }

    // parse vt command
    if ( starts_with( line, "vt " ) ) {
    if ( uv_count >= MAX_VERTEX_COUNT * 2 ) {
    ERROR_LOG( "vertex limit exceeded" );
    continue;
    }
    float uv[ 2 ];
    errors += parse_uv_line( uv, line );
    uv_list[ uv_count * 2 + 0 ] = uv[ 0 ];
    uv_list[ uv_count * 2 + 1 ] = uv[ 1 ];
    uv_count++;
    }

    // parse f command
    if ( starts_with( line, "f " ) ) {
    int index_list[ MAX_POLYGON_LENGTH * 3 ];
    int index_count = 0;
    errors += parse_face_line( index_list, &index_count, line );
    append_face(
    out,
    pos_list,
    normal_list,
    uv_list,
    index_list,
    index_count
    );
    }

    if ( starts_with( line, "mtllib " ) ) {
    char * name = new char[ MAX_NAME_LENGTH ];
    errors += parse_material_lib_line( name, line );
    out->material_lib_filename = name;
    }

    if ( starts_with( line, "usemtl " ) ) {
    if ( out->material_group_count >= MAX_MATERIAL_GROUP_COUNT ) {
    ERROR_LOG( "material group count exceeded" );
    continue;
    }
    char * name = new char[ MAX_NAME_LENGTH ];
    errors += parse_usemtl_line( name, line );
    out->material_group_material_list[ out->material_group_count ] =
    name;
    out->material_group_offset_list[ out->material_group_count ] =
    out->vertex_count;
    out->material_group_count++;
    }
    }

    if ( errors ) {
    ERROR_LOG( "failed to load wavefront (.obj)" );
    }

    print_header( out );

    delete[] pos_list;
    delete[] normal_list;
    delete[] uv_list;

    return errors;
    }

    int load_material_lib( material_lib_t * out, res_t res )
    {
    int errors = 0;
    int cursor = 0;
    char line[ MAX_LINE_LENGTH ];

    using c_string_t = const char *;
    out->material_name_list = new c_string_t[ MAX_MATERIAL_COUNT ];
    out->material_list = new material_t[ MAX_MATERIAL_COUNT ];
    out->material_count = 0;

    // zero out all the materials
    memset( out->material_list, 0, MAX_MATERIAL_COUNT * sizeof( material_t ) );

    while ( cursor < res.size ) {
    // load next line
    int i;
    for ( i = 0; i + cursor < res.size; i++ ) {
    char c = (char) res.data[ i + cursor ];

    if ( c == '\n' ) break;

    if ( i >= MAX_LINE_LENGTH ) {
    ERROR_LOG( "line length exceeded" );
    break;
    }

    line[ i ] = c;
    }
    cursor += i + 1;
    line[ i ] = '\0';

    // parse newmtl command
    if ( starts_with( line, "newmtl " ) ) {
    if ( out->material_count >= MAX_MATERIAL_COUNT ) {
    ERROR_LOG( "material count exceeded" );
    continue;
    }
    char * newmtl = new char[ MAX_NAME_LENGTH ];
    errors += parse_newmtl_line( newmtl, line );
    out->material_name_list[ out->material_count ] = newmtl;
    out->material_count++;
    }

    // ignore lines until we have a material
    if ( out->material_count <= 0 ) continue;

    // get current material
    material_t * mat = out->material_list + ( out->material_count - 1 );

    if ( starts_with( line, "Ns " ) ) {
    errors += parse_ns_line( &mat->ns, line );
    }
    if ( starts_with( line, "Ka " ) ) {
    errors += parse_ka_line( mat->ka, line );
    }
    if ( starts_with( line, "Kd " ) ) {
    errors += parse_kd_line( mat->kd, line );
    }
    if ( starts_with( line, "Ks " ) ) {
    errors += parse_ks_line( mat->ks, line );
    }
    if ( starts_with( line, "Ke " ) ) {
    errors += parse_ke_line( mat->ke, line );
    }
    if ( starts_with( line, "Ni " ) ) {
    errors += parse_ni_line( &mat->ni, line );
    }
    if ( starts_with( line, "d " ) ) {
    errors += parse_d_line( &mat->d, line );
    }
    if ( starts_with( line, "illum " ) ) {
    errors += parse_illum_line( &mat->illum, line );
    }
    if ( starts_with( line, "map_Kd " ) ) {
    char * filename = new char[ MAX_NAME_LENGTH ];
    errors += parse_map_kd_line( filename, line );
    mat->map_kd = filename;
    }
    }

    if ( errors ) {
    ERROR_LOG( "failed to load wavefront (.mtl)" );
    }

    for ( int i = 0; i < out->material_count; i++ ) {
    print_mtl_header(
    out->material_list + i,
    out->material_name_list[ i ]
    );
    }

    return errors;
    }
    47 changes: 47 additions & 0 deletions parser.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    #pragma once

    #include "res.hpp"

    struct wavefront_t {
    float * pos_list;
    float * normal_list;
    float * uv_list;

    int vertex_count;

    int * obj_offset_list;
    const char ** obj_name_list;
    int obj_count;

    const char * material_lib_filename;

    int * material_group_offset_list;
    const char ** material_group_material_list;
    int material_group_count;

    void compute_bounds( float * min_vec3, float * max_vec3 );
    };

    struct material_t {
    float ns; // specular exponent
    float ka[ 3 ]; // ambient
    float kd[ 3 ]; // diffuse
    float ks[ 3 ]; // specular
    float ke[ 3 ]; // emmision
    float ni; // refraction index
    float d; // dissolve
    int illum; // illum model
    const char * map_kd; // diffuse texture map
    };

    struct material_lib_t {
    const char ** material_name_list;
    material_t * material_list;
    int material_count;
    };

    /// @threadsafe
    int load_wavefront( wavefront_t * out_mesh, res_t res );

    /// @threadsafe
    int load_material_lib( material_lib_t * out_lib, res_t res );