#include "../util/typedef.h" #include "../util/string.h" #include "../util/vector.h" #include "../util/file.h" #include struct MODEL_DATA { VEC3 position; VEC2 uv; VEC3 normal; }; struct MODEL { LIST vertices; LIST indices; }; enum LINE_TYPES : I32 { comment = ( '#' | ( ' ' << 8 ) ), d = ( 'd' | ( ' ' << 8 ) ), f = ( 'f' | ( ' ' << 8 ) ), illum = ( 'i' | ( 'l' << 8 ) ), Ka = ( 'K' | ( 'a' << 8 ) ), Kd = ( 'K' | ( 'd' << 8 ) ), Ke = ( 'K' | ( 'e' << 8 ) ), Ks = ( 'K' | ( 's' << 8 ) ), map_Kd = ( 'm' | ( 'a' << 8 ) ), mtllib = ( 'm' | ( 't' << 8 ) ), newmtl = ( 'n' | ( 'e' << 8 ) ), Ni = ( 'N' | ( 'i' << 8 ) ), Ns = ( 'N' | ( 's' << 8 ) ), o = ( 'o' | ( ' ' << 8 ) ), s = ( 's' | ( ' ' << 8 ) ), v = ( 'v' | ( ' ' << 8 ) ), vn = ( 'v' | ( 'n' << 8 ) ), vt = ( 'v' | ( 't' << 8 ) ), usemtl = ( 'u' | ( 's' << 8 ) ) }; static inline char* skip_space( char* s ) { while( *s == ' ' || *s == '\t' ) ++s; return s; } static inline VEC3 read_vec3( char* s ) { VEC3 temp{}; s = skip_space( s ); temp.x = strtof( s, &s ); s = skip_space( s ); temp.y = strtof( s, &s ); s = skip_space( s ); temp.z = strtof( s, &s ); return temp; } static inline LINE_TYPES get_first_two( char* s ) { I32 c0 = *s; U16 c1 = *(s + 1); return (LINE_TYPES)( c0 | c1 << 8 ); } struct MTLDATA { STR name; F32 Ns; VEC3 Ka; VEC3 Kd; VEC3 Ks; VEC3 Ke; F32 Ni; F32 d; I32 illum; STR map_Kd; }; struct MTLLIB { STR name; LIST data; }; inline I32 line_type_offset( LINE_TYPES first_two ) { switch( first_two ) { case d: case f: case o: case s: case v: return 2; case Ka: case Kd: case Ke: case Ks: case Ni: case Ns: case vn: case vt: return 3; case illum: return 6; case map_Kd: case mtllib: case newmtl: case usemtl: return 7; case comment: return 2; } } inline void mtl_parse_line( char* file, U64 idx, MTLLIB* mtl ) { LINE_TYPES first_two = get_first_two( file + idx ); char* p = file + idx + line_type_offset( first_two ); U64 mtl_idx = mtl->data.size - 1; switch( first_two ) { case newmtl: { char* end = p; while( *end && *end != '\n' ) ++end; U32 len = end - p; STR temp; temp.reserve( len + 1 ); memcpy( temp.data, p, len ); temp.data[len] = 0; mtl->data.resize( mtl->data.size + 1 ); mtl->data[mtl->data.size - 1].name = temp.data; } break; case d: mtl->data[mtl_idx].d = strtof( p, &p ); break; case Ni: mtl->data[mtl_idx].Ni = strtof( p, &p ); break; case Ns: mtl->data[mtl_idx].Ns = strtof( p, &p ); break; case illum: mtl->data[mtl_idx].illum = (I32)strtol( p, &p, 10 ); break; case Ka: mtl->data[mtl_idx].Ka = read_vec3( p ); break; case Kd: mtl->data[mtl_idx].Kd = read_vec3( p ); break; case Ke: mtl->data[mtl_idx].Ke = read_vec3( p ); break; case Ks: mtl->data[mtl_idx].Ks = read_vec3( p ); break; case map_Kd: { char* end = p; while( *end && *end != '\n' ) ++end; U32 len = end - p; STR temp; temp.reserve( len + 1 ); memcpy( temp.data, p, len ); temp.data[len] = 0; mtl->data[mtl_idx].map_Kd = temp.data; } break; case comment: break; default: { dlog( "mtl_from_file() : unhandled char combo %c%c\n", first_two & 0xFF, ( first_two >> 8 ) & 0xFF ); } break; } } inline MTLLIB mtl_from_file( const char* path, const char* name ) { U64 mtl_size; STR full_path( path ); full_path += name; char* f_mtl = (char*)file_read( full_path.data, &mtl_size ); if( !f_mtl ) { dlog( "mtl_from_file() : failed to read file %s\n", full_path.data ); return {}; } MTLLIB mtl; mtl.name = name; for( U64 i = 0; i < mtl_size - 1 && f_mtl[i]; ++i ) { if( f_mtl[i] == '\n' ) continue; mtl_parse_line( f_mtl, i, &mtl ); while( f_mtl[i] && f_mtl[i] != '\n' ) ++i; } free( (void*)f_mtl ); return mtl; } enum FACE_TYPES : U8 { V = 0, VVT = 1, VVN = 2, VVTVN = 3 }; constexpr U32 STR_LEN = 0x40; struct OBJFACE { FACE_TYPES type; LIST indices; }; struct OBJDATA { LIST uvs; LIST normals; LIST vertices; LIST mtllib; struct UseMtl { LIST face_start; LIST mtl_name; } usemtl; LIST faces; }; static inline U32 face_stride( FACE_TYPES t ) { switch( t ) { case V: return 1; case VVT: return 2; case VVN: return 2; case VVTVN: return 3; } return 0; } inline OBJFACE triangulate_face( OBJFACE ref, U32 idx, U32 stride, FACE_TYPES type ) { OBJFACE tri{}; tri.type = type; for( U32 ind = 0; ind < stride; ++ind ) tri.indices.push( ref.indices.data[ind] ); for( U32 ind = 0; ind < stride; ++ind ) tri.indices.push( ref.indices.data[idx * stride + ind] ); for( U32 ind = 0; ind < stride; ++ind ) tri.indices.push( ref.indices.data[( idx + 1 ) * stride + ind] ); return tri; } inline void obj_parse_f_line( OBJDATA* obj, char* p ) { FACE_TYPES type = (FACE_TYPES)0; bool type_set = 0; OBJFACE temp{}; while( *p && *p != '\n' ) { bool has_vt = 0; bool has_vn = 0; U32 v = strtoul( p, &p, 10 ); temp.indices.push( --v ); if( *p == '/' ) { ++p; if( *p != '/' ) { U32 vt = strtoul( p, &p, 10 ); temp.indices.push( --vt ); has_vt = 1; } if( *p == '/' ) { ++p; U32 vn = strtoul( p, &p, 10 ); temp.indices.push( --vn ); has_vn = 1; } } if( !type_set ) { type_set = 1; if( has_vt && has_vn ) type = VVTVN; else if( has_vt ) type = VVT; else if( has_vn ) type = VVN; else type = V; } while( *p && *p != ' ' && *p != '\n' ) ++p; p = skip_space( p ); } temp.type = type; U32 stride = face_stride( type ); U32 count = temp.indices.size / stride; if ( count <= 3 ) obj->faces.push( temp ); else { for( U32 idx = 1; idx < count - 1; ++idx ) obj->faces.push( triangulate_face( temp, idx, stride, type ) ); } } inline void obj_parse_line( const char* path, char* file, U64 idx, OBJDATA* obj ) { LINE_TYPES first_two = get_first_two( file + idx ); char* p = file + idx + line_type_offset( first_two ); switch( first_two ) { case f: obj_parse_f_line( obj, p ); break; case v: case vn: { if( first_two == v ) obj->vertices.push( read_vec3( p ) ); else obj->normals.push( read_vec3( p ) ); } break; case vt: { VEC2 temp{}; p = skip_space( p ); temp.x = strtof( p, &p ); p = skip_space( p ); temp.y = strtof( p, &p ); obj->uvs.push( temp ); } break; case mtllib: case usemtl: { bool is_mtllib = first_two == mtllib; char* end = p; while( *end && *end != '\n' ) ++end; U32 len = end - p; STR temp; temp.reserve( len + 1 ); memcpy( temp.data, p, len ); temp.data[len] = 0; if( !is_mtllib ) { if( obj->usemtl.mtl_name.size ) obj->usemtl.face_start.push( obj->faces.size ); obj->usemtl.mtl_name.push( temp.data ); } else obj->mtllib.push( mtl_from_file( path, temp.data ) ); } break; case comment: case o: case s: break; default: { dlog( "obj_from_file() : unhandled char combo %c%c\n", first_two & 0xFF, ( first_two >> 8 ) & 0xFF ); } break; } } inline OBJDATA obj_from_file( const char* path, const char* name ) { char* f_obj = nullptr; STR obj_path( path ); U64 obj_size = 0; obj_path += name; obj_path += ".obj"; f_obj = (char*)file_read( obj_path.data, &obj_size ); if( !f_obj ) { dlog( "obj_from_file() : failed to read obj file %s\n", obj_path.data ); return {}; } OBJDATA obj; for( U64 i = 0; i < obj_size - 1 && f_obj[i]; ++i ) { if( f_obj[i] == '\n' ) continue; obj_parse_line( path, f_obj, i, &obj ); while( f_obj[i] && f_obj[i] != '\n' ) ++i; } obj.usemtl.face_start.push( obj.faces.size ); if ( !obj.usemtl.mtl_name.size ) obj.usemtl.mtl_name.push( "default" ); free( (void*)f_obj ); return obj; } inline MODEL obj_to_model( OBJDATA obj ) { struct KEY { U32 v, vt, vn; }; struct ENTRY { KEY key; U32 idx; }; MODEL model; LIST table; table.reserve( obj.faces.size * 3 ); for( U32 f = 0; f < obj.faces.size; ++f ) { OBJFACE& face = obj.faces.data[f]; U32 stride = face_stride( face.type ); U32 count = face.indices.size / stride; for( U32 v = 0; v < count; ++v ) { KEY key = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; U32 base = v * stride; key.v = face.indices.data[base + 0]; if( face.type == VVT || face.type == VVTVN ) key.vt = face.indices.data[base + 1]; if( face.type == VVN ) key.vn = face.indices.data[base + 1]; if( face.type == VVTVN ) key.vn = face.indices.data[base + 2]; U32 idx = 0; bool found = 0; // this should be fine until ~1m triangles, right? for( U32 i = 0; i < table.size; ++i ) { ENTRY& e = table.data[i]; if( e.key.vn != key.vn || e.key.vt != key.vt || e.key.v != key.v ) continue; idx = e.idx; found = 1; break; } if( !found ) { MODEL_DATA data{}; data.position = obj.vertices.data[key.v]; if( face.type == VVTVN || face.type == VVT ) data.uv = obj.uvs.data[key.vt]; if( face.type == VVTVN || face.type == VVN ) data.normal = obj.normals.data[key.vn]; idx = model.vertices.size; model.vertices.push( data ); table.push({ key, idx }); } model.indices.push( idx ); } } return model; } inline MODEL model_from_obj( const I8* path, const I8* name ) { OBJDATA obj = obj_from_file( path, name ); return obj_to_model( obj ); }