420 lines
13 KiB
C
420 lines
13 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 "lwo/lwo2.h"
|
|
|
|
/* uncomment when debugging this module */
|
|
/*#define DEBUG_PM_LWO*/
|
|
|
|
#ifdef DEBUG_PM_LWO
|
|
#include "time.h"
|
|
#endif
|
|
|
|
/* helper functions */
|
|
static const char *lwo_lwIDToStr( unsigned int lwID ){
|
|
static char lwIDStr[5];
|
|
|
|
if ( !lwID ) {
|
|
return "n/a";
|
|
}
|
|
|
|
lwIDStr[ 0 ] = (char)( ( lwID ) >> 24 );
|
|
lwIDStr[ 1 ] = (char)( ( lwID ) >> 16 );
|
|
lwIDStr[ 2 ] = (char)( ( lwID ) >> 8 );
|
|
lwIDStr[ 3 ] = (char)( ( lwID ) );
|
|
lwIDStr[ 4 ] = '\0';
|
|
|
|
return lwIDStr;
|
|
}
|
|
|
|
/*
|
|
_lwo_canload()
|
|
validates a LightWave Object model file. btw, i use the
|
|
preceding underscore cause it's a static func referenced
|
|
by one structure only.
|
|
*/
|
|
static int _lwo_canload( PM_PARAMS_CANLOAD ){
|
|
picoMemStream_t *s;
|
|
unsigned int failID = 0;
|
|
int failpos = -1;
|
|
int ret;
|
|
|
|
/* create a new pico memorystream */
|
|
s = _pico_new_memstream( (const picoByte_t *)buffer, bufSize );
|
|
if ( s == NULL ) {
|
|
return PICO_PMV_ERROR_MEMORY;
|
|
}
|
|
|
|
ret = lwValidateObject( fileName, s, &failID, &failpos );
|
|
|
|
_pico_free_memstream( s );
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
_lwo_load()
|
|
loads a LightWave Object model file.
|
|
*/
|
|
static picoModel_t *_lwo_load( PM_PARAMS_LOAD ){
|
|
picoMemStream_t *s;
|
|
unsigned int failID = 0;
|
|
int failpos = -1;
|
|
lwObject *obj;
|
|
lwSurface *surface;
|
|
lwLayer *layer;
|
|
lwPoint *pt;
|
|
lwPolygon *pol;
|
|
lwPolVert *v;
|
|
lwVMapPt *vm;
|
|
char name[ 256 ];
|
|
int i, j, k, numverts;
|
|
|
|
picoModel_t *picoModel;
|
|
picoSurface_t *picoSurface;
|
|
picoShader_t *picoShader;
|
|
picoVec3_t xyz, normal;
|
|
picoVec2_t st;
|
|
picoColor_t color;
|
|
|
|
int defaultSTAxis[ 2 ];
|
|
picoVec2_t defaultXYZtoSTScale;
|
|
|
|
picoVertexCombinationHash_t **hashTable;
|
|
picoVertexCombinationHash_t *vertexCombinationHash;
|
|
|
|
#ifdef DEBUG_PM_LWO
|
|
clock_t load_start, load_finish, convert_start, convert_finish;
|
|
double load_elapsed, convert_elapsed;
|
|
|
|
load_start = clock();
|
|
#endif
|
|
|
|
/* do frame check */
|
|
if ( frameNum < 0 || frameNum >= 1 ) {
|
|
_pico_printf( PICO_ERROR, "Invalid or out-of-range LWO frame specified" );
|
|
return NULL;
|
|
}
|
|
|
|
/* create a new pico memorystream */
|
|
s = _pico_new_memstream( (const picoByte_t *)buffer, bufSize );
|
|
if ( s == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
obj = lwGetObject( fileName, s, &failID, &failpos );
|
|
|
|
_pico_free_memstream( s );
|
|
|
|
if ( !obj ) {
|
|
_pico_printf( PICO_ERROR, "Couldn't load LWO file, failed on ID '%s', position %d", lwo_lwIDToStr( failID ), failpos );
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG_PM_LWO
|
|
convert_start = load_finish = clock();
|
|
load_elapsed = (double)( load_finish - load_start ) / CLOCKS_PER_SEC;
|
|
#endif
|
|
|
|
/* -------------------------------------------------
|
|
pico model creation
|
|
------------------------------------------------- */
|
|
|
|
/* create a new pico model */
|
|
picoModel = PicoNewModel();
|
|
if ( picoModel == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model" );
|
|
return NULL;
|
|
}
|
|
|
|
/* do model setup */
|
|
PicoSetModelFrameNum( picoModel, frameNum );
|
|
PicoSetModelNumFrames( picoModel, 1 );
|
|
PicoSetModelName( picoModel, fileName );
|
|
PicoSetModelFileName( picoModel, fileName );
|
|
|
|
/* create all polygons from layer[ 0 ] that belong to this surface */
|
|
layer = &obj->layer[0];
|
|
|
|
/* warn the user that other layers are discarded */
|
|
if ( obj->nlayers > 1 ) {
|
|
_pico_printf( PICO_WARNING, "LWO loader discards any geometry data not in Layer 1 (%d layers found)", obj->nlayers );
|
|
}
|
|
|
|
/* initialize dummy normal */
|
|
normal[ 0 ] = normal[ 1 ] = normal[ 2 ] = 0.f;
|
|
|
|
/* setup default st map */
|
|
st[ 0 ] = st[ 1 ] = 0.f; /* st[0] holds max, st[1] holds max par one */
|
|
defaultSTAxis[ 0 ] = 0;
|
|
defaultSTAxis[ 1 ] = 1;
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
float min = layer->bbox[ i ];
|
|
float max = layer->bbox[ i + 3 ];
|
|
float size = max - min;
|
|
|
|
if ( size > st[ 0 ] ) {
|
|
defaultSTAxis[ 1 ] = defaultSTAxis[ 0 ];
|
|
defaultSTAxis[ 0 ] = i;
|
|
|
|
st[ 1 ] = st[ 0 ];
|
|
st[ 0 ] = size;
|
|
}
|
|
else if ( size > st[ 1 ] ) {
|
|
defaultSTAxis[ 1 ] = i;
|
|
st[ 1 ] = size;
|
|
}
|
|
}
|
|
defaultXYZtoSTScale[ 0 ] = 4.f / st[ 0 ];
|
|
defaultXYZtoSTScale[ 1 ] = 4.f / st[ 1 ];
|
|
|
|
/* LWO surfaces become pico surfaces */
|
|
surface = obj->surf;
|
|
while ( surface )
|
|
{
|
|
/* allocate new pico surface */
|
|
picoSurface = PicoNewSurface( picoModel );
|
|
if ( picoSurface == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model surface" );
|
|
PicoFreeModel( picoModel );
|
|
lwFreeObject( obj );
|
|
return NULL;
|
|
}
|
|
|
|
/* LWO model surfaces are all triangle meshes */
|
|
PicoSetSurfaceType( picoSurface, PICO_TRIANGLES );
|
|
|
|
/* set surface name */
|
|
PicoSetSurfaceName( picoSurface, surface->name );
|
|
|
|
/* create new pico shader */
|
|
picoShader = PicoNewShader( picoModel );
|
|
if ( picoShader == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate a new model shader" );
|
|
PicoFreeModel( picoModel );
|
|
lwFreeObject( obj );
|
|
return NULL;
|
|
}
|
|
|
|
/* detox and set shader name */
|
|
strncpy( name, surface->name, sizeof( name ) );
|
|
_pico_first_token( name );
|
|
_pico_setfext( name, "" );
|
|
_pico_unixify( name );
|
|
PicoSetShaderName( picoShader, name );
|
|
|
|
/* associate current surface with newly created shader */
|
|
PicoSetSurfaceShader( picoSurface, picoShader );
|
|
|
|
/* copy indices and vertex data */
|
|
numverts = 0;
|
|
|
|
hashTable = PicoNewVertexCombinationHashTable();
|
|
|
|
if ( hashTable == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate hash table" );
|
|
PicoFreeModel( picoModel );
|
|
lwFreeObject( obj );
|
|
return NULL;
|
|
}
|
|
|
|
for ( i = 0, pol = layer->polygon.pol; i < layer->polygon.count; i++, pol++ )
|
|
{
|
|
/* does this polygon belong to this surface? */
|
|
if ( pol->surf != surface ) {
|
|
continue;
|
|
}
|
|
|
|
/* we only support polygons of the FACE type */
|
|
if ( pol->type != ID_FACE ) {
|
|
_pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it's type != FACE (%s)", lwo_lwIDToStr( pol->type ) );
|
|
continue;
|
|
}
|
|
|
|
/* NOTE: LWO has support for non-convex polygons, do we want to store them as well? */
|
|
if ( pol->nverts != 3 ) {
|
|
_pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it has != 3 verts (%d)", pol->nverts );
|
|
continue;
|
|
}
|
|
|
|
for ( j = 0, v = pol->v; j < 3; j++, v++ )
|
|
{
|
|
pt = &layer->point.pt[ v->index ];
|
|
|
|
/* setup data */
|
|
xyz[ 0 ] = pt->pos[ 0 ];
|
|
xyz[ 1 ] = pt->pos[ 2 ];
|
|
xyz[ 2 ] = pt->pos[ 1 ];
|
|
|
|
/* doom3 lwo data doesn't seem to have smoothing-angle information */
|
|
#if 0
|
|
if ( surface->smooth <= 0 ) {
|
|
/* use face normals */
|
|
normal[ 0 ] = v->norm[ 0 ];
|
|
normal[ 1 ] = v->norm[ 2 ];
|
|
normal[ 2 ] = v->norm[ 1 ];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* smooth normals later */
|
|
normal[ 0 ] = 0;
|
|
normal[ 1 ] = 0;
|
|
normal[ 2 ] = 0;
|
|
}
|
|
|
|
st[ 0 ] = xyz[ defaultSTAxis[ 0 ] ] * defaultXYZtoSTScale[ 0 ];
|
|
st[ 1 ] = xyz[ defaultSTAxis[ 1 ] ] * defaultXYZtoSTScale[ 1 ];
|
|
|
|
color[ 0 ] = (picoByte_t)( surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF );
|
|
color[ 1 ] = (picoByte_t)( surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF );
|
|
color[ 2 ] = (picoByte_t)( surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF );
|
|
color[ 3 ] = 0xFF;
|
|
|
|
/* set from points */
|
|
for ( k = 0, vm = pt->vm; k < pt->nvmaps; k++, vm++ )
|
|
{
|
|
if ( vm->vmap->type == LWID_( 'T','X','U','V' ) ) {
|
|
/* set st coords */
|
|
st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ];
|
|
st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ];
|
|
}
|
|
else if ( vm->vmap->type == LWID_( 'R','G','B','A' ) ) {
|
|
/* set rgba */
|
|
color[ 0 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF );
|
|
color[ 1 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF );
|
|
color[ 2 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF );
|
|
color[ 3 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 3 ] * 0xFF );
|
|
}
|
|
}
|
|
|
|
/* override with polygon data */
|
|
for ( k = 0, vm = v->vm; k < v->nvmaps; k++, vm++ )
|
|
{
|
|
if ( vm->vmap->type == LWID_( 'T','X','U','V' ) ) {
|
|
/* set st coords */
|
|
st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ];
|
|
st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ];
|
|
}
|
|
else if ( vm->vmap->type == LWID_( 'R','G','B','A' ) ) {
|
|
/* set rgba */
|
|
color[ 0 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF );
|
|
color[ 1 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF );
|
|
color[ 2 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF );
|
|
color[ 3 ] = (picoByte_t)( vm->vmap->val[ vm->index ][ 3 ] * 0xFF );
|
|
}
|
|
}
|
|
|
|
/* find vertex in this surface and if we can't find it there create it */
|
|
vertexCombinationHash = PicoFindVertexCombinationInHashTable( hashTable, xyz, normal, st, color );
|
|
|
|
if ( vertexCombinationHash ) {
|
|
/* found an existing one */
|
|
PicoSetSurfaceIndex( picoSurface, ( i * 3 + j ), vertexCombinationHash->index );
|
|
}
|
|
else
|
|
{
|
|
/* it is a new one */
|
|
vertexCombinationHash = PicoAddVertexCombinationToHashTable( hashTable, xyz, normal, st, color, (picoIndex_t) numverts );
|
|
|
|
if ( vertexCombinationHash == NULL ) {
|
|
_pico_printf( PICO_ERROR, "Unable to allocate hash bucket entry table" );
|
|
PicoFreeVertexCombinationHashTable( hashTable );
|
|
PicoFreeModel( picoModel );
|
|
lwFreeObject( obj );
|
|
return NULL;
|
|
}
|
|
|
|
/* add the vertex to this surface */
|
|
PicoSetSurfaceXYZ( picoSurface, numverts, xyz );
|
|
|
|
/* set dummy normal */
|
|
PicoSetSurfaceNormal( picoSurface, numverts, normal );
|
|
|
|
/* set color */
|
|
PicoSetSurfaceColor( picoSurface, 0, numverts, color );
|
|
|
|
/* set st coords */
|
|
PicoSetSurfaceST( picoSurface, 0, numverts, st );
|
|
|
|
/* set index */
|
|
PicoSetSurfaceIndex( picoSurface, ( i * 3 + j ), (picoIndex_t) numverts );
|
|
|
|
numverts++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free the hashtable */
|
|
PicoFreeVertexCombinationHashTable( hashTable );
|
|
|
|
/* get next surface */
|
|
surface = surface->next;
|
|
}
|
|
|
|
#ifdef DEBUG_PM_LWO
|
|
load_start = convert_finish = clock();
|
|
#endif
|
|
|
|
lwFreeObject( obj );
|
|
|
|
#ifdef DEBUG_PM_LWO
|
|
load_finish = clock();
|
|
load_elapsed += (double)( load_finish - load_start ) / CLOCKS_PER_SEC;
|
|
convert_elapsed = (double)( convert_finish - convert_start ) / CLOCKS_PER_SEC;
|
|
_pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s) (loading: %-.2fs converting: %-.2fs)\n", load_elapsed + convert_elapsed, load_elapsed, convert_elapsed );
|
|
#endif
|
|
|
|
/* return the new pico model */
|
|
return picoModel;
|
|
}
|
|
|
|
/* pico file format module definition */
|
|
const picoModule_t picoModuleLWO =
|
|
{
|
|
"1.0", /* module version string */
|
|
"LightWave Object", /* module display name */
|
|
"Arnout van Meer", /* author's name */
|
|
"2003 Arnout van Meer, 2000 Ernie Wright", /* module copyright */
|
|
{
|
|
"lwo", NULL, NULL, NULL /* default extensions to use */
|
|
},
|
|
_lwo_canload, /* validation routine */
|
|
_lwo_load, /* load routine */
|
|
NULL, /* save validation routine */
|
|
NULL /* save routine */
|
|
};
|