947 lines
26 KiB
C
947 lines
26 KiB
C
|
/* -----------------------------------------------------------------------------
|
||
|
|
||
|
PicoModel Library
|
||
|
|
||
|
Copyright (c) 2002, Randy Reddig & seaw0lf
|
||
|
All rights reserved.
|
||
|
|
||
|
Redistribution and use in source and binary forms, with or without modification,
|
||
|
are permitted provided that the following conditions are met:
|
||
|
|
||
|
Redistributions of source code must retain the above copyright notice, this list
|
||
|
of conditions and the following disclaimer.
|
||
|
|
||
|
Redistributions in binary form must reproduce the above copyright notice, this
|
||
|
list of conditions and the following disclaimer in the documentation and/or
|
||
|
other materials provided with the distribution.
|
||
|
|
||
|
Neither the names of the copyright holders nor the names of its contributors may
|
||
|
be used to endorse or promote products derived from this software without
|
||
|
specific prior written permission.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
----------------------------------------------------------------------------- */
|
||
|
|
||
|
/* dependencies */
|
||
|
#include "picointernal.h"
|
||
|
#include "globaldefs.h"
|
||
|
|
||
|
/* disable warnings */
|
||
|
#if GDEF_COMPILER_MSVC
|
||
|
#pragma warning( disable:4100 ) /* unref param */
|
||
|
#endif
|
||
|
|
||
|
/* todo:
|
||
|
* - '_obj_load' code crashes in a weird way after
|
||
|
* '_obj_mtl_load' for a few .mtl files
|
||
|
* - process 'mtllib' rather than using <model>.mtl
|
||
|
* - handle 'usemtl' statements
|
||
|
*/
|
||
|
/* uncomment when debugging this module */
|
||
|
/* #define DEBUG_PM_OBJ */
|
||
|
/* #define DEBUG_PM_OBJ_EX */
|
||
|
|
||
|
/* this holds temporary vertex data read by parser */
|
||
|
typedef struct SObjVertexData
|
||
|
{
|
||
|
picoVec3_t v; /* geometric vertices */
|
||
|
picoVec2_t vt; /* texture vertices */
|
||
|
picoVec3_t vn; /* vertex normals (optional) */
|
||
|
}
|
||
|
TObjVertexData;
|
||
|
|
||
|
/* _obj_canload:
|
||
|
* validates a wavefront obj model file.
|
||
|
*/
|
||
|
static int _obj_canload( PM_PARAMS_CANLOAD ){
|
||
|
picoParser_t *p;
|
||
|
|
||
|
/* check data length */
|
||
|
if ( bufSize < 30 ) {
|
||
|
return PICO_PMV_ERROR_SIZE;
|
||
|
}
|
||
|
|
||
|
/* first check file extension. we have to do this for objs */
|
||
|
/* cause there is no good way to identify the contents */
|
||
|
if ( _pico_stristr( fileName,".obj" ) != NULL ||
|
||
|
_pico_stristr( fileName,".wf" ) != NULL ) {
|
||
|
return PICO_PMV_OK;
|
||
|
}
|
||
|
/* if the extension check failed we parse through the first */
|
||
|
/* few lines in file and look for common keywords often */
|
||
|
/* appearing at the beginning of wavefront objects */
|
||
|
|
||
|
/* alllocate a new pico parser */
|
||
|
p = _pico_new_parser( (const picoByte_t *)buffer,bufSize );
|
||
|
if ( p == NULL ) {
|
||
|
return PICO_PMV_ERROR_MEMORY;
|
||
|
}
|
||
|
|
||
|
/* parse obj head line by line for type check */
|
||
|
while ( 1 )
|
||
|
{
|
||
|
/* get first token on line */
|
||
|
if ( _pico_parse_first( p ) == NULL ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* we only parse the first few lines, say 80 */
|
||
|
if ( p->curLine > 80 ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* skip empty lines */
|
||
|
if ( p->token == NULL || !strlen( p->token ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* material library keywords are teh good */
|
||
|
if ( !_pico_stricmp( p->token,"usemtl" ) ||
|
||
|
!_pico_stricmp( p->token,"mtllib" ) ||
|
||
|
!_pico_stricmp( p->token,"g" ) ||
|
||
|
!_pico_stricmp( p->token,"v" ) ) { /* v,g bit fishy, but uh... */
|
||
|
/* free the pico parser thing */
|
||
|
_pico_free_parser( p );
|
||
|
|
||
|
/* seems to be a valid wavefront obj */
|
||
|
return PICO_PMV_OK;
|
||
|
}
|
||
|
/* skip rest of line */
|
||
|
_pico_parse_skip_rest( p );
|
||
|
}
|
||
|
/* free the pico parser thing */
|
||
|
_pico_free_parser( p );
|
||
|
|
||
|
/* doesn't really look like an obj to us */
|
||
|
return PICO_PMV_ERROR;
|
||
|
}
|
||
|
|
||
|
/* SizeObjVertexData:
|
||
|
* This pretty piece of 'alloc ahead' code dynamically
|
||
|
* allocates - and reallocates as soon as required -
|
||
|
* my vertex data array in even steps.
|
||
|
*/
|
||
|
const int SIZE_OBJ_STEP = 4096;
|
||
|
|
||
|
static TObjVertexData *SizeObjVertexData(
|
||
|
TObjVertexData *vertexData, int reqEntries,
|
||
|
int *entries, int *allocated ){
|
||
|
int newAllocated;
|
||
|
|
||
|
/* sanity checks */
|
||
|
if ( reqEntries < 1 ) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if ( entries == NULL || allocated == NULL ) {
|
||
|
return NULL; /* must have */
|
||
|
|
||
|
}
|
||
|
/* no need to grow yet */
|
||
|
if ( vertexData && ( reqEntries < *allocated ) ) {
|
||
|
*entries = reqEntries;
|
||
|
return vertexData;
|
||
|
}
|
||
|
/* given vertex data ptr not allocated yet */
|
||
|
if ( vertexData == NULL ) {
|
||
|
/* how many entries to allocate */
|
||
|
newAllocated = ( reqEntries > SIZE_OBJ_STEP ) ?
|
||
|
reqEntries : SIZE_OBJ_STEP;
|
||
|
|
||
|
/* throw out an extended debug message */
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "SizeObjVertexData: allocate (%d entries)\n",
|
||
|
newAllocated );
|
||
|
#endif
|
||
|
/* first time allocation */
|
||
|
vertexData = (TObjVertexData *)
|
||
|
_pico_alloc( sizeof( TObjVertexData ) * newAllocated );
|
||
|
|
||
|
/* allocation failed */
|
||
|
if ( vertexData == NULL ) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* allocation succeeded */
|
||
|
*allocated = newAllocated;
|
||
|
*entries = reqEntries;
|
||
|
return vertexData;
|
||
|
}
|
||
|
/* given vertex data ptr needs to be resized */
|
||
|
if ( reqEntries == *allocated ) {
|
||
|
newAllocated = ( *allocated + SIZE_OBJ_STEP );
|
||
|
|
||
|
/* throw out an extended debug message */
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "SizeObjVertexData: reallocate (%d entries)\n",
|
||
|
newAllocated );
|
||
|
#endif
|
||
|
/* try to reallocate */
|
||
|
vertexData = (TObjVertexData *)
|
||
|
_pico_realloc( (void *)&vertexData,
|
||
|
sizeof( TObjVertexData ) * ( *allocated ),
|
||
|
sizeof( TObjVertexData ) * ( newAllocated ) );
|
||
|
|
||
|
/* reallocation failed */
|
||
|
if ( vertexData == NULL ) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* reallocation succeeded */
|
||
|
*allocated = newAllocated;
|
||
|
*entries = reqEntries;
|
||
|
return vertexData;
|
||
|
}
|
||
|
/* we're b0rked when we reach this */
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void FreeObjVertexData( TObjVertexData *vertexData ){
|
||
|
if ( vertexData != NULL ) {
|
||
|
free( (TObjVertexData *)vertexData );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int _obj_mtl_load( picoModel_t *model ){
|
||
|
picoShader_t *curShader = NULL;
|
||
|
picoParser_t *p;
|
||
|
picoByte_t *mtlBuffer;
|
||
|
int mtlBufSize;
|
||
|
char *fileName;
|
||
|
|
||
|
/* sanity checks */
|
||
|
if ( model == NULL || model->fileName == NULL ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* skip if we have a zero length model file name */
|
||
|
if ( !strlen( model->fileName ) ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* helper */
|
||
|
#define _obj_mtl_error_return \
|
||
|
{ \
|
||
|
_pico_free_parser( p ); \
|
||
|
_pico_free_file( mtlBuffer ); \
|
||
|
_pico_free( fileName ); \
|
||
|
return 0; \
|
||
|
}
|
||
|
/* alloc copy of model file name */
|
||
|
fileName = _pico_clone_alloc( model->fileName );
|
||
|
if ( fileName == NULL ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* change extension of model file to .mtl */
|
||
|
_pico_setfext( fileName, "mtl" );
|
||
|
|
||
|
/* load .mtl file contents */
|
||
|
_pico_load_file( fileName,&mtlBuffer,&mtlBufSize );
|
||
|
|
||
|
/* check result */
|
||
|
if ( mtlBufSize == 0 ) {
|
||
|
return 1; /* file is empty: no error */
|
||
|
}
|
||
|
if ( mtlBufSize < 0 ) {
|
||
|
return 0; /* load failed: error */
|
||
|
|
||
|
}
|
||
|
/* create a new pico parser */
|
||
|
p = _pico_new_parser( mtlBuffer, mtlBufSize );
|
||
|
if ( p == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* doo teh .mtl parse */
|
||
|
while ( 1 )
|
||
|
{
|
||
|
/* get next token in material file */
|
||
|
if ( _pico_parse( p,1 ) == NULL ) {
|
||
|
break;
|
||
|
}
|
||
|
#if 1
|
||
|
|
||
|
/* skip empty lines */
|
||
|
if ( p->token == NULL || !strlen( p->token ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* skip comment lines */
|
||
|
if ( p->token[0] == '#' ) {
|
||
|
_pico_parse_skip_rest( p );
|
||
|
continue;
|
||
|
}
|
||
|
/* new material */
|
||
|
if ( !_pico_stricmp( p->token,"newmtl" ) ) {
|
||
|
picoShader_t *shader;
|
||
|
char *name;
|
||
|
|
||
|
/* get material name */
|
||
|
name = _pico_parse( p,0 );
|
||
|
|
||
|
/* validate material name */
|
||
|
if ( name == NULL || !strlen( name ) ) {
|
||
|
_pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine );
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
/* create a new pico shader */
|
||
|
shader = PicoNewShader( model );
|
||
|
if ( shader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* set shader name */
|
||
|
PicoSetShaderName( shader,name );
|
||
|
|
||
|
/* assign pointer to current shader */
|
||
|
curShader = shader;
|
||
|
}
|
||
|
/* diffuse map name */
|
||
|
else if ( !_pico_stricmp( p->token,"map_kd" ) ) {
|
||
|
char *mapName;
|
||
|
picoShader_t *shader;
|
||
|
|
||
|
/* pointer to current shader must be valid */
|
||
|
if ( curShader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* get material's diffuse map name */
|
||
|
mapName = _pico_parse( p,0 );
|
||
|
|
||
|
/* validate map name */
|
||
|
if ( mapName == NULL || !strlen( mapName ) ) {
|
||
|
_pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine );
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
/* create a new pico shader */
|
||
|
shader = PicoNewShader( model );
|
||
|
if ( shader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
/* set shader map name */
|
||
|
PicoSetShaderMapName( shader,mapName );
|
||
|
}
|
||
|
/* dissolve factor (pseudo transparency 0..1) */
|
||
|
/* where 0 means 100% transparent and 1 means opaque */
|
||
|
else if ( !_pico_stricmp( p->token,"d" ) ) {
|
||
|
picoByte_t *diffuse;
|
||
|
float value;
|
||
|
|
||
|
|
||
|
/* get dissolve factor */
|
||
|
if ( !_pico_parse_float( p,&value ) ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* set shader transparency */
|
||
|
PicoSetShaderTransparency( curShader,value );
|
||
|
|
||
|
/* get shader's diffuse color */
|
||
|
diffuse = PicoGetShaderDiffuseColor( curShader );
|
||
|
|
||
|
/* set diffuse alpha to transparency */
|
||
|
diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
|
||
|
|
||
|
/* set shader's new diffuse color */
|
||
|
PicoSetShaderDiffuseColor( curShader,diffuse );
|
||
|
}
|
||
|
/* shininess (phong specular component) */
|
||
|
else if ( !_pico_stricmp( p->token,"ns" ) ) {
|
||
|
/* remark:
|
||
|
* - well, this is some major obj spec fuckup once again. some
|
||
|
* apps store this in 0..1 range, others use 0..100 range,
|
||
|
* even others use 0..2048 range, and again others use the
|
||
|
* range 0..128, some even use 0..1000, 0..200, 400..700,
|
||
|
* honestly, what's up with the 3d app coders? happens when
|
||
|
* you smoke too much weed i guess. -sea
|
||
|
*/
|
||
|
float value;
|
||
|
|
||
|
/* pointer to current shader must be valid */
|
||
|
if ( curShader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* get totally screwed up shininess (a random value in fact ;) */
|
||
|
if ( !_pico_parse_float( p,&value ) ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* okay, there is no way to set this correctly, so we simply */
|
||
|
/* try to guess a few ranges (most common ones i have seen) */
|
||
|
|
||
|
/* assume 0..2048 range */
|
||
|
if ( value > 1000 ) {
|
||
|
value = 128.0 * ( value / 2048.0 );
|
||
|
}
|
||
|
/* assume 0..1000 range */
|
||
|
else if ( value > 200 ) {
|
||
|
value = 128.0 * ( value / 1000.0 );
|
||
|
}
|
||
|
/* assume 0..200 range */
|
||
|
else if ( value > 100 ) {
|
||
|
value = 128.0 * ( value / 200.0 );
|
||
|
}
|
||
|
/* assume 0..100 range */
|
||
|
else if ( value > 1 ) {
|
||
|
value = 128.0 * ( value / 100.0 );
|
||
|
}
|
||
|
/* assume 0..1 range */
|
||
|
else {
|
||
|
value *= 128.0;
|
||
|
}
|
||
|
/* negative shininess is bad (yes, i have seen it...) */
|
||
|
if ( value < 0.0 ) {
|
||
|
value = 0.0;
|
||
|
}
|
||
|
|
||
|
/* set the pico shininess value in range 0..127 */
|
||
|
/* geez, .obj is such a mess... */
|
||
|
PicoSetShaderShininess( curShader,value );
|
||
|
}
|
||
|
/* kol0r ambient (wut teh fuk does "ka" stand for?) */
|
||
|
else if ( !_pico_stricmp( p->token,"ka" ) ) {
|
||
|
picoColor_t color;
|
||
|
picoVec3_t v;
|
||
|
|
||
|
/* pointer to current shader must be valid */
|
||
|
if ( curShader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* get color vector */
|
||
|
if ( !_pico_parse_vec( p,v ) ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* scale to byte range */
|
||
|
color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
|
||
|
color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
|
||
|
color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
|
||
|
color[ 3 ] = (picoByte_t)( 255 );
|
||
|
|
||
|
/* set ambient color */
|
||
|
PicoSetShaderAmbientColor( curShader,color );
|
||
|
}
|
||
|
/* kol0r diffuse */
|
||
|
else if ( !_pico_stricmp( p->token,"kd" ) ) {
|
||
|
picoColor_t color;
|
||
|
picoVec3_t v;
|
||
|
|
||
|
/* pointer to current shader must be valid */
|
||
|
if ( curShader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* get color vector */
|
||
|
if ( !_pico_parse_vec( p,v ) ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* scale to byte range */
|
||
|
color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
|
||
|
color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
|
||
|
color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
|
||
|
color[ 3 ] = (picoByte_t)( 255 );
|
||
|
|
||
|
/* set diffuse color */
|
||
|
PicoSetShaderDiffuseColor( curShader,color );
|
||
|
}
|
||
|
/* kol0r specular */
|
||
|
else if ( !_pico_stricmp( p->token,"ks" ) ) {
|
||
|
picoColor_t color;
|
||
|
picoVec3_t v;
|
||
|
|
||
|
/* pointer to current shader must be valid */
|
||
|
if ( curShader == NULL ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* get color vector */
|
||
|
if ( !_pico_parse_vec( p,v ) ) {
|
||
|
_obj_mtl_error_return;
|
||
|
}
|
||
|
|
||
|
/* scale to byte range */
|
||
|
color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
|
||
|
color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
|
||
|
color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
|
||
|
color[ 3 ] = (picoByte_t)( 255 );
|
||
|
|
||
|
/* set specular color */
|
||
|
PicoSetShaderSpecularColor( curShader,color );
|
||
|
}
|
||
|
#endif
|
||
|
/* skip rest of line */
|
||
|
_pico_parse_skip_rest( p );
|
||
|
}
|
||
|
|
||
|
/* free parser, file buffer, and file name */
|
||
|
_pico_free_parser( p );
|
||
|
_pico_free_file( mtlBuffer );
|
||
|
_pico_free( fileName );
|
||
|
|
||
|
/* return with success */
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* _obj_load:
|
||
|
* loads a wavefront obj model file.
|
||
|
*/
|
||
|
static picoModel_t *_obj_load( PM_PARAMS_LOAD ){
|
||
|
TObjVertexData *vertexData = NULL;
|
||
|
picoModel_t *model;
|
||
|
picoSurface_t *curSurface = NULL;
|
||
|
picoParser_t *p;
|
||
|
int allocated;
|
||
|
int entries;
|
||
|
int numVerts = 0;
|
||
|
int numNormals = 0;
|
||
|
int numUVs = 0;
|
||
|
int curVertex = 0;
|
||
|
int curFace = 0;
|
||
|
|
||
|
int autoGroupNumber = 0;
|
||
|
char autoGroupNameBuf[64];
|
||
|
|
||
|
#define AUTO_GROUPNAME( namebuf ) \
|
||
|
sprintf( namebuf, "__autogroup_%d", autoGroupNumber++ )
|
||
|
#define NEW_SURFACE( name ) \
|
||
|
{ \
|
||
|
picoSurface_t *newSurface; \
|
||
|
/* allocate a pico surface */ \
|
||
|
newSurface = PicoNewSurface( model ); \
|
||
|
if ( newSurface == NULL ) { \
|
||
|
_obj_error_return( "Error allocating surface" ); } \
|
||
|
/* reset face index for surface */ \
|
||
|
curFace = 0; \
|
||
|
/* if we can, assign the previous shader to this surface */ \
|
||
|
if ( curSurface ) { \
|
||
|
PicoSetSurfaceShader( newSurface, curSurface->shader ); } \
|
||
|
/* set ptr to current surface */ \
|
||
|
curSurface = newSurface; \
|
||
|
/* we use triangle meshes */ \
|
||
|
PicoSetSurfaceType( newSurface,PICO_TRIANGLES ); \
|
||
|
/* set surface name */ \
|
||
|
PicoSetSurfaceName( newSurface,name ); \
|
||
|
}
|
||
|
|
||
|
/* helper */
|
||
|
#define _obj_error_return( m ) \
|
||
|
{ \
|
||
|
_pico_printf( PICO_ERROR, "%s in OBJ %s, line %d.", m, model->fileName, p->curLine ); \
|
||
|
_pico_free_parser( p ); \
|
||
|
FreeObjVertexData( vertexData ); \
|
||
|
PicoFreeModel( model ); \
|
||
|
return NULL; \
|
||
|
}
|
||
|
/* alllocate a new pico parser */
|
||
|
p = _pico_new_parser( (const picoByte_t *)buffer,bufSize );
|
||
|
if ( p == NULL ) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* create a new pico model */
|
||
|
model = PicoNewModel();
|
||
|
if ( model == NULL ) {
|
||
|
_pico_free_parser( p );
|
||
|
return NULL;
|
||
|
}
|
||
|
/* do model setup */
|
||
|
PicoSetModelFrameNum( model,frameNum );
|
||
|
PicoSetModelName( model,fileName );
|
||
|
PicoSetModelFileName( model,fileName );
|
||
|
|
||
|
/* try loading the materials; we don't handle the result */
|
||
|
#if 1
|
||
|
_obj_mtl_load( model );
|
||
|
#endif
|
||
|
|
||
|
/* parse obj line by line */
|
||
|
while ( 1 )
|
||
|
{
|
||
|
/* get first token on line */
|
||
|
if ( _pico_parse_first( p ) == NULL ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* skip empty lines */
|
||
|
if ( p->token == NULL || !strlen( p->token ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* skip comment lines */
|
||
|
if ( p->token[0] == '#' ) {
|
||
|
_pico_parse_skip_rest( p );
|
||
|
continue;
|
||
|
}
|
||
|
/* vertex */
|
||
|
if ( !_pico_stricmp( p->token,"v" ) ) {
|
||
|
TObjVertexData *data;
|
||
|
picoVec3_t v;
|
||
|
|
||
|
vertexData = SizeObjVertexData( vertexData,numVerts + 1,&entries,&allocated );
|
||
|
if ( vertexData == NULL ) {
|
||
|
_obj_error_return( "Realloc of vertex data failed (1)" );
|
||
|
}
|
||
|
|
||
|
data = &vertexData[ numVerts++ ];
|
||
|
|
||
|
/* get and copy vertex */
|
||
|
if ( !_pico_parse_vec( p,v ) ) {
|
||
|
_obj_error_return( "Vertex parse error" );
|
||
|
}
|
||
|
|
||
|
_pico_copy_vec( v,data->v );
|
||
|
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2] );
|
||
|
#endif
|
||
|
}
|
||
|
/* uv coord */
|
||
|
else if ( !_pico_stricmp( p->token,"vt" ) ) {
|
||
|
TObjVertexData *data;
|
||
|
picoVec2_t coord;
|
||
|
|
||
|
vertexData = SizeObjVertexData( vertexData,numUVs + 1,&entries,&allocated );
|
||
|
if ( vertexData == NULL ) {
|
||
|
_obj_error_return( "Realloc of vertex data failed (2)" );
|
||
|
}
|
||
|
|
||
|
data = &vertexData[ numUVs++ ];
|
||
|
|
||
|
/* get and copy tex coord */
|
||
|
if ( !_pico_parse_vec2( p,coord ) ) {
|
||
|
_obj_error_return( "UV coord parse error" );
|
||
|
}
|
||
|
|
||
|
_pico_copy_vec2( coord,data->vt );
|
||
|
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "TexCoord: u: %f v: %f\n",coord[0],coord[1] );
|
||
|
#endif
|
||
|
}
|
||
|
/* vertex normal */
|
||
|
else if ( !_pico_stricmp( p->token,"vn" ) ) {
|
||
|
TObjVertexData *data;
|
||
|
picoVec3_t n;
|
||
|
|
||
|
vertexData = SizeObjVertexData( vertexData,numNormals + 1,&entries,&allocated );
|
||
|
if ( vertexData == NULL ) {
|
||
|
_obj_error_return( "Realloc of vertex data failed (3)" );
|
||
|
}
|
||
|
|
||
|
data = &vertexData[ numNormals++ ];
|
||
|
|
||
|
/* get and copy vertex normal */
|
||
|
if ( !_pico_parse_vec( p,n ) ) {
|
||
|
_obj_error_return( "Vertex normal parse error" );
|
||
|
}
|
||
|
|
||
|
_pico_copy_vec( n,data->vn );
|
||
|
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2] );
|
||
|
#endif
|
||
|
}
|
||
|
/* new group (for us this means a new surface) */
|
||
|
else if ( !_pico_stricmp( p->token,"g" ) ) {
|
||
|
char *groupName;
|
||
|
|
||
|
/* get first group name (ignore 2nd,3rd,etc.) */
|
||
|
groupName = _pico_parse( p,0 );
|
||
|
if ( groupName == NULL || !strlen( groupName ) ) {
|
||
|
/* some obj exporters feel like they don't need to */
|
||
|
/* supply a group name. so we gotta handle it here */
|
||
|
#if 1
|
||
|
strcpy( p->token,"default" );
|
||
|
groupName = p->token;
|
||
|
#else
|
||
|
_obj_error_return( "Invalid or missing group name" );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if ( curFace == 0 && curSurface != NULL ) {
|
||
|
PicoSetSurfaceName( curSurface,groupName );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NEW_SURFACE( groupName );
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "Group: '%s'\n",groupName );
|
||
|
#endif
|
||
|
}
|
||
|
/* face (oh jesus, hopefully this will do the job right ;) */
|
||
|
else if ( !_pico_stricmp( p->token,"f" ) ) {
|
||
|
/* okay, this is a mess. some 3d apps seem to try being unique, */
|
||
|
/* hello cinema4d & 3d exploration, feel good today?, and save */
|
||
|
/* this crap in tons of different formats. gah, those screwed */
|
||
|
/* coders. tho the wavefront obj standard defines exactly two */
|
||
|
/* ways of storing face information. so, i really won't support */
|
||
|
/* such stupid extravaganza here! */
|
||
|
|
||
|
picoVec3_t verts [ 4 ];
|
||
|
picoVec3_t normals[ 4 ];
|
||
|
picoVec2_t coords [ 4 ];
|
||
|
|
||
|
int iv [ 4 ], has_v;
|
||
|
int ivt[ 4 ], has_vt = 0;
|
||
|
int ivn[ 4 ], has_vn = 0;
|
||
|
int have_quad = 0;
|
||
|
int slashcount = 0;
|
||
|
int doubleslash = 0;
|
||
|
int i;
|
||
|
|
||
|
if ( curSurface == NULL ) {
|
||
|
_pico_printf( PICO_WARNING,"No group defined for faces, so creating an autoSurface in OBJ, line %d.",p->curLine );
|
||
|
AUTO_GROUPNAME( autoGroupNameBuf );
|
||
|
NEW_SURFACE( autoGroupNameBuf );
|
||
|
}
|
||
|
|
||
|
/* group defs *must* come before faces */
|
||
|
if ( curSurface == NULL ) {
|
||
|
_obj_error_return( "No group defined for faces" );
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "Face: " );
|
||
|
#endif
|
||
|
/* read vertex/uv/normal indices for the first three face */
|
||
|
/* vertices (cause we only support triangles) into 'i*[]' */
|
||
|
/* store the actual vertex/uv/normal data in three arrays */
|
||
|
/* called 'verts','coords' and 'normals'. */
|
||
|
for ( i = 0; i < 4; i++ )
|
||
|
{
|
||
|
char *str;
|
||
|
|
||
|
/* get next vertex index string (different */
|
||
|
/* formats are handled below) */
|
||
|
str = _pico_parse( p,0 );
|
||
|
if ( str == NULL ) {
|
||
|
/* just break for quads */
|
||
|
if ( i == 3 ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* error otherwise */
|
||
|
_obj_error_return( "Face parse error" );
|
||
|
}
|
||
|
/* if this is the fourth index string we're */
|
||
|
/* parsing we assume that we have a quad */
|
||
|
if ( i == 3 ) {
|
||
|
have_quad = 1;
|
||
|
}
|
||
|
|
||
|
/* get slash count once */
|
||
|
if ( i == 0 ) {
|
||
|
slashcount = _pico_strchcount( str,'/' );
|
||
|
doubleslash = strstr( str,"//" ) != NULL;
|
||
|
}
|
||
|
/* handle format 'v//vn' */
|
||
|
if ( doubleslash && ( slashcount == 2 ) ) {
|
||
|
has_v = has_vn = 1;
|
||
|
sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
|
||
|
}
|
||
|
/* handle format 'v/vt/vn' */
|
||
|
else if ( !doubleslash && ( slashcount == 2 ) ) {
|
||
|
has_v = has_vt = has_vn = 1;
|
||
|
sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
|
||
|
}
|
||
|
/* handle format 'v/vt' (non-standard fuckage) */
|
||
|
else if ( !doubleslash && ( slashcount == 1 ) ) {
|
||
|
has_v = has_vt = 1;
|
||
|
sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
|
||
|
}
|
||
|
/* else assume face format 'v' */
|
||
|
/* (must have been invented by some bored granny) */
|
||
|
else {
|
||
|
/* get single vertex index */
|
||
|
has_v = 1;
|
||
|
iv[ i ] = atoi( str );
|
||
|
|
||
|
/* either invalid face format or out of range */
|
||
|
if ( iv[ i ] == 0 ) {
|
||
|
_obj_error_return( "Invalid face format" );
|
||
|
}
|
||
|
}
|
||
|
/* fix useless back references */
|
||
|
/* todo: check if this works as it is supposed to */
|
||
|
|
||
|
/* assign new indices */
|
||
|
if ( iv [ i ] < 0 ) {
|
||
|
iv [ i ] = ( numVerts - iv [ i ] );
|
||
|
}
|
||
|
if ( ivt[ i ] < 0 ) {
|
||
|
ivt[ i ] = ( numUVs - ivt[ i ] );
|
||
|
}
|
||
|
if ( ivn[ i ] < 0 ) {
|
||
|
ivn[ i ] = ( numNormals - ivn[ i ] );
|
||
|
}
|
||
|
|
||
|
/* validate indices */
|
||
|
/* - commented out. index range checks will trigger
|
||
|
if (iv [ i ] < 1) iv [ i ] = 1;
|
||
|
if (ivt[ i ] < 1) ivt[ i ] = 1;
|
||
|
if (ivn[ i ] < 1) ivn[ i ] = 1;
|
||
|
*/
|
||
|
/* set vertex origin */
|
||
|
if ( has_v ) {
|
||
|
/* check vertex index range */
|
||
|
if ( iv[ i ] < 1 || iv[ i ] > numVerts ) {
|
||
|
_obj_error_return( "Vertex index out of range" );
|
||
|
}
|
||
|
|
||
|
/* get vertex data */
|
||
|
verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
|
||
|
verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
|
||
|
verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
|
||
|
}
|
||
|
/* set vertex normal */
|
||
|
if ( has_vn ) {
|
||
|
/* check normal index range */
|
||
|
if ( ivn[ i ] < 1 || ivn[ i ] > numNormals ) {
|
||
|
_obj_error_return( "Normal index out of range" );
|
||
|
}
|
||
|
|
||
|
/* get normal data */
|
||
|
normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
|
||
|
normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
|
||
|
normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
|
||
|
}
|
||
|
/* set texture coordinate */
|
||
|
if ( has_vt ) {
|
||
|
/* check uv index range */
|
||
|
if ( ivt[ i ] < 1 || ivt[ i ] > numUVs ) {
|
||
|
_obj_error_return( "UV coord index out of range" );
|
||
|
}
|
||
|
|
||
|
/* get uv coord data */
|
||
|
coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
|
||
|
coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
|
||
|
coords[ i ][ 1 ] = -coords[ i ][ 1 ];
|
||
|
}
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "(%4d",iv[ i ] );
|
||
|
if ( has_vt ) {
|
||
|
printf( " %4d",ivt[ i ] );
|
||
|
}
|
||
|
if ( has_vn ) {
|
||
|
printf( " %4d",ivn[ i ] );
|
||
|
}
|
||
|
printf( ") " );
|
||
|
#endif
|
||
|
}
|
||
|
#ifdef DEBUG_PM_OBJ_EX
|
||
|
printf( "\n" );
|
||
|
#endif
|
||
|
/* now that we have extracted all the indices and have */
|
||
|
/* read the actual data we need to assign all the crap */
|
||
|
/* to our current pico surface */
|
||
|
if ( has_v ) {
|
||
|
int max = 3;
|
||
|
if ( have_quad ) {
|
||
|
max = 4;
|
||
|
}
|
||
|
|
||
|
/* assign all surface information */
|
||
|
for ( i = 0; i < max; i++ )
|
||
|
{
|
||
|
/*if( has_v )*/ PicoSetSurfaceXYZ( curSurface, ( curVertex + i ), verts [ i ] );
|
||
|
/*if( has_vt )*/ PicoSetSurfaceST( curSurface,0,( curVertex + i ), coords [ i ] );
|
||
|
/*if( has_vn )*/ PicoSetSurfaceNormal( curSurface, ( curVertex + i ), normals[ i ] );
|
||
|
}
|
||
|
/* add our triangle (A B C) */
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 2 ),(picoIndex_t)( curVertex + 0 ) );
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 1 ),(picoIndex_t)( curVertex + 1 ) );
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 0 ),(picoIndex_t)( curVertex + 2 ) );
|
||
|
curFace++;
|
||
|
|
||
|
/* if we don't have a simple triangle, but a quad... */
|
||
|
if ( have_quad ) {
|
||
|
/* we have to add another triangle (2nd half of quad which is A C D) */
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 2 ),(picoIndex_t)( curVertex + 0 ) );
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 1 ),(picoIndex_t)( curVertex + 2 ) );
|
||
|
PicoSetSurfaceIndex( curSurface,( curFace * 3 + 0 ),(picoIndex_t)( curVertex + 3 ) );
|
||
|
curFace++;
|
||
|
}
|
||
|
/* increase vertex count */
|
||
|
curVertex += max;
|
||
|
}
|
||
|
}
|
||
|
else if ( !_pico_stricmp( p->token,"usemtl" ) ) {
|
||
|
picoShader_t *shader;
|
||
|
char *name;
|
||
|
|
||
|
/* get material name */
|
||
|
name = _pico_parse( p,0 );
|
||
|
|
||
|
if ( curFace != 0 || curSurface == NULL ) {
|
||
|
_pico_printf( PICO_WARNING,"No group defined for usemtl, so creating an autoSurface in OBJ, line %d.",p->curLine );
|
||
|
AUTO_GROUPNAME( autoGroupNameBuf );
|
||
|
NEW_SURFACE( autoGroupNameBuf );
|
||
|
}
|
||
|
|
||
|
/* validate material name */
|
||
|
if ( name == NULL || !strlen( name ) ) {
|
||
|
_pico_printf( PICO_ERROR,"Missing material name in OBJ, line %d.",p->curLine );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
shader = PicoFindShader( model, name, 1 );
|
||
|
if ( shader == NULL ) {
|
||
|
_pico_printf( PICO_WARNING,"Undefined material name in OBJ, line %d. Making a default shader.",p->curLine );
|
||
|
|
||
|
/* create a new pico shader */
|
||
|
shader = PicoNewShader( model );
|
||
|
if ( shader != NULL ) {
|
||
|
PicoSetShaderName( shader,name );
|
||
|
PicoSetShaderMapName( shader,name );
|
||
|
PicoSetSurfaceShader( curSurface, shader );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PicoSetSurfaceShader( curSurface, shader );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/* skip unparsed rest of line and continue */
|
||
|
_pico_parse_skip_rest( p );
|
||
|
}
|
||
|
/* free memory used by temporary vertexdata */
|
||
|
FreeObjVertexData( vertexData );
|
||
|
|
||
|
/* return allocated pico model */
|
||
|
return model;
|
||
|
// return NULL;
|
||
|
}
|
||
|
|
||
|
/* pico file format module definition */
|
||
|
const picoModule_t picoModuleOBJ =
|
||
|
{
|
||
|
"0.6-b", /* module version string */
|
||
|
"Wavefront ASCII", /* module display name */
|
||
|
"seaw0lf", /* author's name */
|
||
|
"2002 seaw0lf", /* module copyright */
|
||
|
{
|
||
|
"obj",NULL,NULL,NULL /* default extensions to use */
|
||
|
},
|
||
|
_obj_canload, /* validation routine */
|
||
|
_obj_load, /* load routine */
|
||
|
NULL, /* save validation routine */
|
||
|
NULL /* save routine */
|
||
|
};
|