/* * Copyright (c) 2007 Gero Mueller * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. */ #include "tlobj.h" #include #include /*----------------------------------------------------------------------------*/ typedef enum tlObjParsingState { OBJ_STATE_SEARCH_COMMAND, OBJ_STATE_PARSE_COMMAND, OBJ_STATE_SEARCH_PARAMETER, OBJ_STATE_PARSE_PARAMETER, OBJ_STATE_SKIP_COMMENT, OBJ_STATE_DONE } tlObjParsingState; /*----------------------------------------------------------------------------*/ typedef struct tlObjObject { char *name; unsigned int index, count; } tlObjObject; /*----------------------------------------------------------------------------*/ typedef struct vertex_map_item { int v; unsigned int vt; unsigned int vn; unsigned int index; } obj_vertex_map_item; /*----------------------------------------------------------------------------*/ struct tlObjState { tlObjObject **object_buffer; unsigned int object_buffer_size; unsigned int object_count; double *point_buffer; unsigned int point_buffer_size; unsigned int point_count; double *texcoord_buffer; unsigned int texcoord_buffer_size; unsigned int texcoord_count; double *normal_buffer; unsigned int normal_buffer_size; unsigned int normal_count; unsigned int *face_buffer; unsigned int face_buffer_size; unsigned int face_count; obj_vertex_map_item *vertex_map_buffer; unsigned int vertex_map_buffer_size; unsigned int vertex_map_count; tlObjParsingState parsing_state; tlObjParsingState previous_parsing_state; char *command_buffer; unsigned int command_buffer_size; unsigned int command_buffer_length; char *parameter_buffer; unsigned int parameter_buffer_size; unsigned int parameter_buffer_length; }; /*----------------------------------------------------------------------------*/ static int obj_is_whitespace( char c ) { if( c == ' ' ) return 1; if( c == '\n' ) return 1; if( c == '\t' ) return 1; if( c == '\r' ) return 1; return 0; } /*----------------------------------------------------------------------------*/ static int obj_state_add_point( tlObjState *state, double x, double y, double z ) { unsigned int needed_size = (state->point_count + 1) * 3 * sizeof(double); if( needed_size > state->point_buffer_size ) { unsigned int new_size = 128; double *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->point_buffer, new_size ); if( new_buffer == 0 ) return 1; state->point_buffer = new_buffer; state->point_buffer_size = new_size; } state->point_buffer[state->point_count*3] = x; state->point_buffer[state->point_count*3+1] = y; state->point_buffer[state->point_count*3+2] = z; state->point_count++; return 0; } /*----------------------------------------------------------------------------*/ static int obj_state_add_normal( tlObjState *state, double x, double y, double z ) { unsigned int needed_size = (state->normal_count + 1) * 3 * sizeof(double); if( needed_size > state->normal_buffer_size ) { unsigned int new_size = 128; double *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->normal_buffer, new_size ); if( new_buffer == NULL ) return 1; state->normal_buffer = new_buffer; state->normal_buffer_size = new_size; } state->normal_buffer[state->normal_count*3] = x; state->normal_buffer[state->normal_count*3+1] = y; state->normal_buffer[state->normal_count*3+2] = z; state->normal_count++; return 0; } /*----------------------------------------------------------------------------*/ static int obj_state_add_texcoord( tlObjState *state, double u, double v ) { unsigned int needed_size = (state->texcoord_count + 1) * 3 * sizeof(double); if( needed_size > state->texcoord_buffer_size ) { unsigned int new_size = 128; double *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->texcoord_buffer, new_size ); if( new_buffer == NULL ) return 1; state->texcoord_buffer = new_buffer; state->texcoord_buffer_size = new_size; } state->texcoord_buffer[state->texcoord_count*3] = u; state->texcoord_buffer[state->texcoord_count*3+1] = v; state->texcoord_count++; return 0; } /*----------------------------------------------------------------------------*/ static int obj_vertex_map_item_cmp( obj_vertex_map_item *a, obj_vertex_map_item *b ) { if( a->v > b->v ) return 1; else if( a->v < b->v ) return -1; if( a->vt > b->vt ) return 1; else if( a->vt < b->vt ) return -1; if( a->vn > b->vn ) return 1; else if( a->vn < b->vn ) return -1; return 0; } /*----------------------------------------------------------------------------*/ static int obj_state_map_vertex_find( tlObjState *state, unsigned int v, unsigned int vt, unsigned int vn, unsigned int *index ) { /* try to find an exiting one */ if( state->vertex_map_count > 0 ) { obj_vertex_map_item a = { v, vt, vn, 0 }; int left = 0; int center = 1; int right = state->vertex_map_count - 1; while( left <= right ) { int cmp; center = (left + right) / 2; cmp = obj_vertex_map_item_cmp( &state->vertex_map_buffer[center], &a ); if( cmp == 0 ) { *index = state->vertex_map_buffer[center].index; return 1; } else if( cmp > 0 ) right = center - 1; else left = center + 1; } } return 0; } /*----------------------------------------------------------------------------*/ static unsigned int obj_state_map_vertex_insert( tlObjState *state, unsigned int v, unsigned int vt, unsigned int vn ) { unsigned int i = 0; unsigned int left = 0; obj_vertex_map_item a = {v, vt, vn, 0 }; /* bring us close */ if( state->vertex_map_count > 8 ) { unsigned int right = state->vertex_map_count - 1; while( (right - left) > 4 ) { i = left + ((right - left) / 2); if( obj_vertex_map_item_cmp( &state->vertex_map_buffer[i], &a ) >= 0 ) right = i - 1; else left = i + 1; } } /* find first greater */ for( i = left; i < state->vertex_map_count; i++ ) { if( obj_vertex_map_item_cmp( &state->vertex_map_buffer[i], &a ) >= 0 ) break; } /* move the rest */ if( i < state->vertex_map_count ) { memmove( &state->vertex_map_buffer[i+1], &state->vertex_map_buffer[i], sizeof(obj_vertex_map_item) * (state->vertex_map_count-i) ); } /* insert new vertex */ state->vertex_map_buffer[i].v = v; state->vertex_map_buffer[i].vt = vt; state->vertex_map_buffer[i].vn = vn; state->vertex_map_buffer[i].index = state->vertex_map_count; state->vertex_map_count++; return state->vertex_map_count - 1; } /*----------------------------------------------------------------------------*/ static void obj_state_map_vertex_increase( tlObjState *state ) { /* check if there is enough room for another element */ unsigned int needed_size = (state->vertex_map_count + 1) * sizeof(obj_vertex_map_item); if( needed_size > state->vertex_map_buffer_size ) { unsigned int new_size = 128; obj_vertex_map_item *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->vertex_map_buffer, new_size ); state->vertex_map_buffer = new_buffer; state->vertex_map_buffer_size = new_size; } } /*----------------------------------------------------------------------------*/ static unsigned int obj_state_map_vertex( tlObjState *state, unsigned int v, unsigned int vt, unsigned int vn ) { unsigned int index; if( obj_state_map_vertex_find( state, v, vt, vn, &index ) ) return index; obj_state_map_vertex_increase( state ); return obj_state_map_vertex_insert( state, v, vt, vn ); } /*----------------------------------------------------------------------------*/ void obj_quicksort_vertex_map( obj_vertex_map_item *map, int left, int right ) { if( right > left ) { int i = left; int j = right; int pivot = (right+left)/2; obj_vertex_map_item tmp; while( i <= j) { while( map[i].index < pivot ) i++; while( map[j].index > pivot ) j--; if( i <= j ) { tmp = map[i]; map[i] = map[j]; map[j] = tmp; i++; j--; } } if( left < j ) obj_quicksort_vertex_map( map, left, j ); if( i < right ) obj_quicksort_vertex_map( map, i, right ); } } /*----------------------------------------------------------------------------*/ static int obj_state_add_face( tlObjState *state, unsigned int a, unsigned int b, unsigned int c ) { unsigned int needed_size = (state->face_count + 1) * 3 * sizeof(unsigned int); if( needed_size > state->face_buffer_size ) { unsigned int new_size = 128; unsigned int *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->face_buffer, new_size ); if( new_buffer == NULL ) return 1; state->face_buffer = new_buffer; state->face_buffer_size = new_size; } state->face_buffer[state->face_count*3] = a; state->face_buffer[state->face_count*3+1] = b; state->face_buffer[state->face_count*3+2] = c; state->face_count++; return 0; } /*----------------------------------------------------------------------------*/ static int obj_state_add_object( tlObjState *state, const char *name, unsigned int name_size ) { unsigned int needed_size = (state->object_count + 1) * sizeof(tlObjObject *); tlObjObject *obj = 0; if( state == NULL ) return 1; if( needed_size > state->object_buffer_size ) { unsigned int new_size = 128; tlObjObject **new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->object_buffer, new_size ); if( new_buffer == NULL ) return 1; state->object_buffer = new_buffer; state->object_buffer_size = new_size; } obj = malloc( sizeof(tlObjObject) ); obj->index = state->face_count; obj->name = malloc( name_size ); memcpy( obj->name, name, name_size ); state->object_buffer[ state->object_count ] = obj; state->object_count++; return 0; } /*----------------------------------------------------------------------------*/ static void obj_process_command( tlObjState *state ) { if( state == NULL ) return; if( strcmp( state->command_buffer, "o" ) == 0 ) { /* safe face count */ if( state->object_count > 0 ) { tlObjObject *obj = state->object_buffer[ state->object_count - 1 ]; obj->count = state->face_count - obj->index; } obj_state_add_object( state, state->parameter_buffer, state->parameter_buffer_length ); } else if( strcmp( state->command_buffer, "v" ) == 0 ) { double x, y, z; char *ptr = state->parameter_buffer; x = strtod( ptr, &ptr ); y = strtod( ptr, &ptr ); z = strtod( ptr, &ptr ); obj_state_add_point( state, x, y, z ); } else if( strcmp( state->command_buffer, "vn" ) == 0 ) { double x, y, z; char *ptr = state->parameter_buffer; x = strtod( ptr, &ptr ); y = strtod( ptr, &ptr ); z = strtod( ptr, &ptr ); obj_state_add_normal( state, x, y, z ); } else if( strcmp( state->command_buffer, "vt" ) == 0 ) { double u, v; char *ptr = state->parameter_buffer; u = strtod( ptr, &ptr ); v = strtod( ptr, &ptr ); obj_state_add_texcoord( state, u, v ); } else if( strcmp( state->command_buffer, "f" ) == 0 ) { unsigned int count = 0; int buffer[3]; char *ptr = state->parameter_buffer; while( *ptr != 0 ) { int v = 0, vn = 0, vt = 0; /* parse vertex index */ v = strtol( ptr, &ptr, 10 ); /* parse texcoord index */ if( *ptr == '/' ) { ptr++; vt = strtol( ptr, &ptr, 10 ); } /* parse normal index */ if( *ptr == '/' ) { ptr++; vn = strtol( ptr, &ptr, 10 ); } /* skip spaces */ while( *ptr == ' ' ) ptr++; count++; if( v < 0 ) v += state->point_count + 1; if( vt < 0 ) vt += state->texcoord_count + 1; if( vn < 0 ) vn += state->normal_count + 1; /* make tris from fan */ if( count < 4 ) { buffer[count-1] = obj_state_map_vertex( state, v, vt, vn ); } else { buffer[1] = buffer[2]; buffer[2] = obj_state_map_vertex( state, v, vt, vn ); } if( count > 2 ) { obj_state_add_face( state, buffer[0], buffer[1], buffer[2] ); } } } } /*----------------------------------------------------------------------------*/ static int obj_command_buffer_add( tlObjState *state, char c ) { unsigned int needed_size = state->command_buffer_length + 1; if( needed_size > state->command_buffer_size ) { unsigned int new_size = 128; char *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->command_buffer, new_size ); if( new_buffer == NULL ) return 1; state->command_buffer = new_buffer; state->command_buffer_size = new_size; } state->command_buffer[ state->command_buffer_length ] = c; state->command_buffer_length++; return 0; } /*----------------------------------------------------------------------------*/ static int obj_parameter_buffer_add( tlObjState *state, char c ) { unsigned int needed_size = state->parameter_buffer_length + 1; if( needed_size > state->parameter_buffer_size ) { unsigned int new_size = 128; char *new_buffer; while( new_size < needed_size ) new_size = new_size * 2; new_buffer = realloc( state->parameter_buffer, new_size ); if( new_buffer == NULL ) return 1; state->parameter_buffer = new_buffer; state->parameter_buffer_size = new_size; } state->parameter_buffer[ state->parameter_buffer_length ] = c; state->parameter_buffer_length++; return 0; } /*----------------------------------------------------------------------------*/ tlObjState *tlObjCreateState() { tlObjState *state = malloc( sizeof(tlObjState) ); if( state ) { memset( state, 0, sizeof(tlObjState) ); state->parsing_state = OBJ_STATE_SEARCH_COMMAND; state->previous_parsing_state = OBJ_STATE_SEARCH_COMMAND; } return state; } /*----------------------------------------------------------------------------*/ int tlObjResetState( tlObjState *state ) { unsigned int i = 0; for( i = 0; iobject_count; i++ ) { tlObjObject *obj = state->object_buffer[i]; if( obj == NULL ) continue; if( obj->name ) free( obj->name ); obj->name = NULL; free( obj ); } if( state->object_buffer ) free( state->object_buffer ); if( state->point_buffer ) free( state->point_buffer ); if( state->face_buffer ) free( state->face_buffer ); if( state->vertex_map_buffer ) free( state->vertex_map_buffer ); if( state->normal_buffer ) free( state->normal_buffer ); if( state->texcoord_buffer ) free( state->texcoord_buffer ); if( state->command_buffer ) free( state->command_buffer ); if( state->parameter_buffer ) free( state->parameter_buffer ); memset( state, 0, sizeof(tlObjState) ); state->parsing_state = OBJ_STATE_SEARCH_COMMAND; state->previous_parsing_state = OBJ_STATE_SEARCH_COMMAND; return 0; } /*----------------------------------------------------------------------------*/ void tlObjDestroyState( tlObjState *state ) { if( state ) { tlObjResetState( state ); free( state ); } } /*----------------------------------------------------------------------------*/ int tlObjParse( tlObjState *state, const char *bytes, unsigned int size, int last ) { unsigned int i = 0; if( state == NULL ) return 1; while( i < size ) { char c = bytes[i]; if( c == '#' ) { state->previous_parsing_state = state->parsing_state; state->parsing_state = OBJ_STATE_SKIP_COMMENT; i++; continue; } if( state->parsing_state == OBJ_STATE_SKIP_COMMENT ) { if( c == '\n' ) state->parsing_state = state->previous_parsing_state; i++; continue; } switch( state->parsing_state ) { case OBJ_STATE_SEARCH_COMMAND: if( obj_is_whitespace( c ) ) i++; else state->parsing_state = OBJ_STATE_PARSE_COMMAND; break; case OBJ_STATE_PARSE_COMMAND: if( obj_is_whitespace( c ) ) { state->parsing_state = OBJ_STATE_SEARCH_PARAMETER; obj_command_buffer_add( state, 0 ); continue; } obj_command_buffer_add( state, c ); i++; break; case OBJ_STATE_SEARCH_PARAMETER: /* handle missing paramter */ if( c == '\n' ) { state->parsing_state = OBJ_STATE_PARSE_PARAMETER; continue; } if( obj_is_whitespace( c ) ) i++; else state->parsing_state = OBJ_STATE_PARSE_PARAMETER; break; case OBJ_STATE_PARSE_PARAMETER: if( c == '\n' ) { /* trim */ while( obj_is_whitespace( state->parameter_buffer[ state->parameter_buffer_length-1 ] ) ) state->parameter_buffer_length--; obj_parameter_buffer_add( state, 0 ); obj_process_command( state ); state->parameter_buffer_length = 0; state->command_buffer_length = 0; state->parsing_state = OBJ_STATE_SEARCH_COMMAND; } else { obj_parameter_buffer_add( state, c ); i++; } break; default: i++; break; } } if( last != 0 ) { tlObjObject *object = NULL; /* make sure we have at least one object */ if( state->object_count == 0 ) { obj_state_add_object( state, "n/a", 4 ); state->object_buffer[ state->object_count - 1 ]->index = 0; } object = state->object_buffer[ state->object_count - 1 ]; object->count = state->face_count - object->index; } if( last ) { /* sort map, so face access is correct */ obj_quicksort_vertex_map( state->vertex_map_buffer, 0, state->vertex_map_count - 1 ); state->parsing_state = OBJ_STATE_DONE; } return 0; } /*----------------------------------------------------------------------------*/ unsigned int tlObjObjectCount( tlObjState *state ) { if( state == NULL ) return 0; if( state->parsing_state != OBJ_STATE_DONE ) return 0; return state->object_count; } /*----------------------------------------------------------------------------*/ const char *tlObjObjectName( tlObjState *state, unsigned int object ) { if( state == NULL ) return NULL; if( state->parsing_state != OBJ_STATE_DONE ) return NULL; if( object >= state->object_count ) return NULL; return state->object_buffer[object]->name; } /*----------------------------------------------------------------------------*/ unsigned int tlObjObjectFaceCount( tlObjState *state, unsigned int object ) { if( state == NULL ) return 0; if( state->parsing_state != OBJ_STATE_DONE ) return 0; if( object >= state->object_count ) return 0; return state->object_buffer[object]->count; } /*----------------------------------------------------------------------------*/ unsigned int tlObjObjectFaceIndex( tlObjState *state, unsigned int object ) { if( state == NULL ) return 0; if( state->parsing_state != OBJ_STATE_DONE ) return 0; if( object >= state->object_count ) return 0; return state->object_buffer[object]->index; } /*----------------------------------------------------------------------------*/ unsigned int tlObjVertexCount( tlObjState *state ) { if( state == NULL ) return 0; if( state->parsing_state != OBJ_STATE_DONE ) return 0; return state->vertex_map_count; } /*----------------------------------------------------------------------------*/ int tlObjGetVertexDouble( tlObjState *state, unsigned int index, double *x, double *y, double *z, double *tu, double *tv, double *nx, double *ny, double *nz ) { unsigned int v = 0, vt = 0, vn = 0; if( state == NULL ) return 1; if( index >= state->vertex_map_count ) return 1; v = state->vertex_map_buffer[index].v - 1; vt = state->vertex_map_buffer[index].vt - 1; vn = state->vertex_map_buffer[index].vn - 1; if( state->point_buffer && v < state->point_count ) { if( x ) *x = state->point_buffer[ v * 3 ]; if( y ) *y = state->point_buffer[ v * 3 + 1]; if( z ) *z = state->point_buffer[ v * 3 + 2]; } if( state->texcoord_buffer && vt < state->texcoord_count ) { if( tu ) *tu = state->texcoord_buffer[ vt * 3 ]; if( tv ) *tv = state->texcoord_buffer[ vt * 3 + 1]; } if( state->normal_buffer && vn < state->normal_count ) { if( nx ) *nx = state->normal_buffer[ vn * 3 ]; if( ny ) *ny = state->normal_buffer[ vn * 3 + 1]; if( nz ) *nz = state->normal_buffer[ vn * 3 + 2]; } return 0; } /*----------------------------------------------------------------------------*/ int tlObjGetVertex( tlObjState *state, unsigned int index, float *x, float *y, float *z, float *tu, float *tv, float *nx, float *ny, float *nz ) { unsigned int v = 0, vt = 0, vn = 0; if( state == NULL ) return 1; if( index >= state->vertex_map_count ) return 1; v = state->vertex_map_buffer[index].v - 1; vt = state->vertex_map_buffer[index].vt - 1; vn = state->vertex_map_buffer[index].vn - 1; if( state->point_buffer && v < state->point_count ) { if( x ) *x = (float)state->point_buffer[ v * 3 ]; if( y ) *y = (float)state->point_buffer[ v * 3 + 1]; if( z ) *z = (float)state->point_buffer[ v * 3 + 2]; } if( state->texcoord_buffer && vt < state->texcoord_count ) { if( tu ) *tu = (float)state->texcoord_buffer[ vt * 3 ]; if( tv ) *tv = (float)state->texcoord_buffer[ vt * 3 + 1]; } if( state->normal_buffer && vn < state->normal_count ) { if( nx ) *nx = (float)state->normal_buffer[ vn * 3 ]; if( ny ) *ny = (float)state->normal_buffer[ vn * 3 + 1]; if( nz ) *nz = (float)state->normal_buffer[ vn * 3 + 2]; } return 0; } /*----------------------------------------------------------------------------*/ unsigned int tlObjFaceCount( tlObjState *state ) { if( state == NULL ) return 0; return state->face_count; } /*----------------------------------------------------------------------------*/ int tlObjGetFaceInt( tlObjState *state, unsigned int index, unsigned int *a, unsigned int *b, unsigned int *c ) { unsigned int face = 0; if( state == NULL ) return 1; if( state->face_buffer && face <= state->face_count ) { if( a ) *a = state->face_buffer[ index * 3 ]; if( b ) *b = state->face_buffer[ index * 3 + 1 ]; if( c ) *c = state->face_buffer[ index * 3 + 2 ]; } return 0; } /*----------------------------------------------------------------------------*/ int tlObjGetFace( tlObjState *state, unsigned int index, unsigned short *a, unsigned short *b, unsigned short *c ) { unsigned int face = 0; if( state == NULL ) return 1; if( state->face_buffer && face <= state->face_count ) { if( a ) *a = state->face_buffer[ index * 3 ]; if( b ) *b = state->face_buffer[ index * 3 + 1 ]; if( c ) *c = state->face_buffer[ index * 3 + 2 ]; } return 0; } /*----------------------------------------------------------------------------*/ int tlObjCheckFileExtension( const char *filename ) { const char *ext = 0, *tmp = filename; if( filename == NULL ) return 1; while( *tmp != 0 ) { if( *tmp == '.' ) ext = tmp + 1; tmp++; } /* no extension found */ if( ext == 0 ) return 1; if( (ext[0] == 'o' || ext[0] == 'O') && (ext[1] == 'b' || ext[1] == 'B') && (ext[2] == 'j' || ext[2] == 'J') && (ext[3] == 0) ) return 0; return 1; }