mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-14 22:41:49 +00:00
4401 lines
92 KiB
C++
4401 lines
92 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
|
|
#include "Model_lwo.h"
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
Converted from lwobject sample prog from LW 6.5 SDK.
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeClip()
|
|
|
|
Free memory used by an lwClip.
|
|
====================================================================== */
|
|
|
|
void lwFreeClip( lwClip* clip )
|
|
{
|
|
if( clip )
|
|
{
|
|
lwListFree( clip->ifilter, ( void ( __cdecl* )( void* ) )lwFreePlugin );
|
|
lwListFree( clip->pfilter, ( void ( __cdecl* )( void* ) )lwFreePlugin );
|
|
switch( clip->type )
|
|
{
|
|
case ID_STIL:
|
|
{
|
|
if( clip->source.still.name ) Mem_Free( clip->source.still.name );
|
|
break;
|
|
}
|
|
case ID_ISEQ:
|
|
{
|
|
if( clip->source.seq.suffix ) Mem_Free( clip->source.seq.suffix );
|
|
if( clip->source.seq.prefix ) Mem_Free( clip->source.seq.prefix );
|
|
break;
|
|
}
|
|
case ID_ANIM:
|
|
{
|
|
if( clip->source.anim.server ) Mem_Free( clip->source.anim.server );
|
|
if( clip->source.anim.name ) Mem_Free( clip->source.anim.name );
|
|
break;
|
|
}
|
|
case ID_XREF:
|
|
{
|
|
if( clip->source.xref.string ) Mem_Free( clip->source.xref.string );
|
|
break;
|
|
}
|
|
case ID_STCC:
|
|
{
|
|
if( clip->source.cycle.name ) Mem_Free( clip->source.cycle.name );
|
|
break;
|
|
}
|
|
}
|
|
Mem_Free( clip );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetClip()
|
|
|
|
Read image references from a CLIP chunk in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
lwClip* lwGetClip( idFile* fp, int cksize )
|
|
{
|
|
lwClip* clip;
|
|
lwPlugin* filt;
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int pos, rlen;
|
|
|
|
|
|
/* allocate the Clip structure */
|
|
|
|
clip = ( lwClip* )Mem_ClearedAlloc( sizeof( lwClip ), TAG_MODEL );
|
|
if( !clip ) goto Fail;
|
|
|
|
clip->contrast.val = 1.0f;
|
|
clip->brightness.val = 1.0f;
|
|
clip->saturation.val = 1.0f;
|
|
clip->gamma.val = 1.0f;
|
|
|
|
/* remember where we started */
|
|
|
|
set_flen( 0 );
|
|
pos = fp->Tell();
|
|
|
|
/* index */
|
|
|
|
clip->index = getI4( fp );
|
|
|
|
/* first subchunk header */
|
|
|
|
clip->type = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( clip->type )
|
|
{
|
|
case ID_STIL:
|
|
clip->source.still.name = getS0( fp );
|
|
break;
|
|
|
|
case ID_ISEQ:
|
|
clip->source.seq.digits = getU1( fp );
|
|
clip->source.seq.flags = getU1( fp );
|
|
clip->source.seq.offset = getI2( fp );
|
|
clip->source.seq.start = getI2( fp );
|
|
clip->source.seq.end = getI2( fp );
|
|
clip->source.seq.prefix = getS0( fp );
|
|
clip->source.seq.suffix = getS0( fp );
|
|
break;
|
|
|
|
case ID_ANIM:
|
|
clip->source.anim.name = getS0( fp );
|
|
clip->source.anim.server = getS0( fp );
|
|
rlen = get_flen();
|
|
clip->source.anim.data = getbytes( fp, sz - rlen );
|
|
break;
|
|
|
|
case ID_XREF:
|
|
clip->source.xref.index = getI4( fp );
|
|
clip->source.xref.string = getS0( fp );
|
|
break;
|
|
|
|
case ID_STCC:
|
|
clip->source.cycle.lo = getI2( fp );
|
|
clip->source.cycle.hi = getI2( fp );
|
|
clip->source.cycle.name = getS0( fp );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the CLIP chunk? */
|
|
|
|
rlen = fp->Tell() - pos;
|
|
if( cksize < rlen ) goto Fail;
|
|
if( cksize == rlen )
|
|
return clip;
|
|
|
|
/* process subchunks as they're encountered */
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_TIME:
|
|
clip->start_time = getF4( fp );
|
|
clip->duration = getF4( fp );
|
|
clip->frame_rate = getF4( fp );
|
|
break;
|
|
|
|
case ID_CONT:
|
|
clip->contrast.val = getF4( fp );
|
|
clip->contrast.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_BRIT:
|
|
clip->brightness.val = getF4( fp );
|
|
clip->brightness.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_SATR:
|
|
clip->saturation.val = getF4( fp );
|
|
clip->saturation.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_HUE:
|
|
clip->hue.val = getF4( fp );
|
|
clip->hue.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_GAMM:
|
|
clip->gamma.val = getF4( fp );
|
|
clip->gamma.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_NEGA:
|
|
clip->negative = getU2( fp );
|
|
break;
|
|
|
|
case ID_IFLT:
|
|
case ID_PFLT:
|
|
filt = ( lwPlugin* )Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL );
|
|
if( !filt ) goto Fail;
|
|
|
|
filt->name = getS0( fp );
|
|
filt->flags = getU2( fp );
|
|
rlen = get_flen();
|
|
filt->data = getbytes( fp, sz - rlen );
|
|
|
|
if( id == ID_IFLT )
|
|
{
|
|
lwListAdd( ( void** )&clip->ifilter, filt );
|
|
clip->nifilters++;
|
|
}
|
|
else
|
|
{
|
|
lwListAdd( ( void** )&clip->pfilter, filt );
|
|
clip->npfilters++;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the CLIP chunk? */
|
|
|
|
rlen = fp->Tell() - pos;
|
|
if( cksize < rlen ) goto Fail;
|
|
if( cksize == rlen ) break;
|
|
|
|
/* get the next chunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
return clip;
|
|
|
|
Fail:
|
|
lwFreeClip( clip );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFindClip()
|
|
|
|
Returns an lwClip pointer, given a clip index.
|
|
====================================================================== */
|
|
|
|
lwClip* lwFindClip( lwClip* list, int index )
|
|
{
|
|
lwClip* clip;
|
|
|
|
clip = list;
|
|
while( clip )
|
|
{
|
|
if( clip->index == index ) break;
|
|
clip = clip->next;
|
|
}
|
|
return clip;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeEnvelope()
|
|
|
|
Free the memory used by an lwEnvelope.
|
|
====================================================================== */
|
|
|
|
void lwFree( void* ptr )
|
|
{
|
|
Mem_Free( ptr );
|
|
}
|
|
|
|
void lwFreeEnvelope( lwEnvelope* env )
|
|
{
|
|
if( env )
|
|
{
|
|
if( env->name ) Mem_Free( env->name );
|
|
lwListFree( env->key, lwFree );
|
|
lwListFree( env->cfilter, ( void ( __cdecl* )( void* ) )lwFreePlugin );
|
|
Mem_Free( env );
|
|
}
|
|
}
|
|
|
|
|
|
static int compare_keys( lwKey* k1, lwKey* k2 )
|
|
{
|
|
return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetEnvelope()
|
|
|
|
Read an ENVL chunk from an LWO2 file.
|
|
====================================================================== */
|
|
|
|
lwEnvelope* lwGetEnvelope( idFile* fp, int cksize )
|
|
{
|
|
lwEnvelope* env = NULL;
|
|
lwKey* key = NULL;
|
|
lwPlugin* plug = NULL;
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
float f[ 4 ];
|
|
int i, nparams, pos, rlen;
|
|
|
|
|
|
/* allocate the Envelope structure */
|
|
|
|
env = ( lwEnvelope* )Mem_ClearedAlloc( sizeof( lwEnvelope ), TAG_MODEL );
|
|
if( !env ) goto Fail;
|
|
|
|
/* remember where we started */
|
|
|
|
set_flen( 0 );
|
|
pos = fp->Tell();
|
|
|
|
/* index */
|
|
|
|
env->index = getVX( fp );
|
|
|
|
/* first subchunk header */
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
/* process subchunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_TYPE:
|
|
env->type = getU2( fp );
|
|
break;
|
|
|
|
case ID_NAME:
|
|
env->name = getS0( fp );
|
|
break;
|
|
|
|
case ID_PRE:
|
|
env->behavior[ 0 ] = getU2( fp );
|
|
break;
|
|
|
|
case ID_POST:
|
|
env->behavior[ 1 ] = getU2( fp );
|
|
break;
|
|
|
|
case ID_KEY:
|
|
key = ( lwKey* )Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL );
|
|
if( !key ) goto Fail;
|
|
key->time = getF4( fp );
|
|
key->value = getF4( fp );
|
|
lwListInsert( ( void** )&env->key, key, ( int ( __cdecl* )( void*, void* ) )compare_keys );
|
|
env->nkeys++;
|
|
break;
|
|
|
|
case ID_SPAN:
|
|
if( key == NULL ) goto Fail;
|
|
key->shape = getU4( fp );
|
|
|
|
nparams = ( sz - 4 ) / 4;
|
|
if( nparams > 4 ) nparams = 4;
|
|
for( i = 0; i < nparams; i++ )
|
|
f[ i ] = getF4( fp );
|
|
|
|
switch( key->shape )
|
|
{
|
|
case ID_TCB:
|
|
key->tension = f[ 0 ];
|
|
key->continuity = f[ 1 ];
|
|
key->bias = f[ 2 ];
|
|
break;
|
|
|
|
case ID_BEZI:
|
|
case ID_HERM:
|
|
case ID_BEZ2:
|
|
for( i = 0; i < nparams; i++ )
|
|
key->param[ i ] = f[ i ];
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ID_CHAN:
|
|
plug = ( lwPlugin* )Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL );
|
|
if( !plug ) goto Fail;
|
|
|
|
plug->name = getS0( fp );
|
|
plug->flags = getU2( fp );
|
|
plug->data = getbytes( fp, sz - get_flen() );
|
|
|
|
lwListAdd( ( void** )&env->cfilter, plug );
|
|
env->ncfilters++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the ENVL chunk? */
|
|
|
|
rlen = fp->Tell() - pos;
|
|
if( cksize < rlen ) goto Fail;
|
|
if( cksize == rlen ) break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
return env;
|
|
|
|
Fail:
|
|
lwFreeEnvelope( env );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFindEnvelope()
|
|
|
|
Returns an lwEnvelope pointer, given an envelope index.
|
|
====================================================================== */
|
|
|
|
lwEnvelope* lwFindEnvelope( lwEnvelope* list, int index )
|
|
{
|
|
lwEnvelope* env;
|
|
|
|
env = list;
|
|
while( env )
|
|
{
|
|
if( env->index == index ) break;
|
|
env = env->next;
|
|
}
|
|
return env;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
range()
|
|
|
|
Given the value v of a periodic function, returns the equivalent value
|
|
v2 in the principal interval [lo, hi]. If i isn't NULL, it receives
|
|
the number of wavelengths between v and v2.
|
|
|
|
v2 = v - i * (hi - lo)
|
|
|
|
For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1.
|
|
====================================================================== */
|
|
|
|
static float range( float v, float lo, float hi, int* i )
|
|
{
|
|
float v2, r = hi - lo;
|
|
|
|
if( r == 0.0 )
|
|
{
|
|
if( i ) *i = 0;
|
|
return lo;
|
|
}
|
|
|
|
v2 = lo + v - r * ( float ) floor( ( double ) v / r );
|
|
if( i ) *i = -( int )( ( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 ) );
|
|
|
|
return v2;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
hermite()
|
|
|
|
Calculate the Hermite coefficients.
|
|
====================================================================== */
|
|
|
|
static void hermite( float t, float* h1, float* h2, float* h3, float* h4 )
|
|
{
|
|
float t2, t3;
|
|
|
|
t2 = t * t;
|
|
t3 = t * t2;
|
|
|
|
*h2 = 3.0f * t2 - t3 - t3;
|
|
*h1 = 1.0f - *h2;
|
|
*h4 = t3 - t2;
|
|
*h3 = *h4 - t2 + t;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
bezier()
|
|
|
|
Interpolate the value of a 1D Bezier curve.
|
|
====================================================================== */
|
|
|
|
static float bezier( float x0, float x1, float x2, float x3, float t )
|
|
{
|
|
float a, b, c, t2, t3;
|
|
|
|
t2 = t * t;
|
|
t3 = t2 * t;
|
|
|
|
c = 3.0f * ( x1 - x0 );
|
|
b = 3.0f * ( x2 - x1 ) - c;
|
|
a = x3 - x0 - c - b;
|
|
|
|
return a * t3 + b * t2 + c * t + x0;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
bez2_time()
|
|
|
|
Find the t for which bezier() returns the input time. The handle
|
|
endpoints of a BEZ2 curve represent the control points, and these have
|
|
(time, value) coordinates, so time is used as both a coordinate and a
|
|
parameter for this curve type.
|
|
====================================================================== */
|
|
|
|
static float bez2_time( float x0, float x1, float x2, float x3, float time,
|
|
float* t0, float* t1 )
|
|
{
|
|
float v, t;
|
|
|
|
t = *t0 + ( *t1 - *t0 ) * 0.5f;
|
|
v = bezier( x0, x1, x2, x3, t );
|
|
if( idMath::Fabs( time - v ) > .0001f )
|
|
{
|
|
if( v > time )
|
|
*t1 = t;
|
|
else
|
|
*t0 = t;
|
|
return bez2_time( x0, x1, x2, x3, time, t0, t1 );
|
|
}
|
|
else
|
|
return t;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
bez2()
|
|
|
|
Interpolate the value of a BEZ2 curve.
|
|
====================================================================== */
|
|
|
|
static float bez2( lwKey* key0, lwKey* key1, float time )
|
|
{
|
|
float x, y, t, t0 = 0.0f, t1 = 1.0f;
|
|
|
|
if( key0->shape == ID_BEZ2 )
|
|
x = key0->time + key0->param[ 2 ];
|
|
else
|
|
x = key0->time + ( key1->time - key0->time ) / 3.0f;
|
|
|
|
t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time,
|
|
time, &t0, &t1 );
|
|
|
|
if( key0->shape == ID_BEZ2 )
|
|
y = key0->value + key0->param[ 3 ];
|
|
else
|
|
y = key0->value + key0->param[ 1 ] / 3.0f;
|
|
|
|
return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t );
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
outgoing()
|
|
|
|
Return the outgoing tangent to the curve at key0. The value returned
|
|
for the BEZ2 case is used when extrapolating a linear pre behavior and
|
|
when interpolating a non-BEZ2 span.
|
|
====================================================================== */
|
|
|
|
static float outgoing( lwKey* key0, lwKey* key1 )
|
|
{
|
|
float a, b, d, t, out;
|
|
|
|
switch( key0->shape )
|
|
{
|
|
case ID_TCB:
|
|
a = ( 1.0f - key0->tension )
|
|
* ( 1.0f + key0->continuity )
|
|
* ( 1.0f + key0->bias );
|
|
b = ( 1.0f - key0->tension )
|
|
* ( 1.0f - key0->continuity )
|
|
* ( 1.0f - key0->bias );
|
|
d = key1->value - key0->value;
|
|
|
|
if( key0->prev )
|
|
{
|
|
t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
|
|
out = t * ( a * ( key0->value - key0->prev->value ) + b * d );
|
|
}
|
|
else
|
|
out = b * d;
|
|
break;
|
|
|
|
case ID_LINE:
|
|
d = key1->value - key0->value;
|
|
if( key0->prev )
|
|
{
|
|
t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
|
|
out = t * ( key0->value - key0->prev->value + d );
|
|
}
|
|
else
|
|
out = d;
|
|
break;
|
|
|
|
case ID_BEZI:
|
|
case ID_HERM:
|
|
out = key0->param[ 1 ];
|
|
if( key0->prev )
|
|
out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time );
|
|
break;
|
|
|
|
case ID_BEZ2:
|
|
out = key0->param[ 3 ] * ( key1->time - key0->time );
|
|
if( idMath::Fabs( key0->param[ 2 ] ) > 1e-5f )
|
|
out /= key0->param[ 2 ];
|
|
else
|
|
out *= 1e5f;
|
|
break;
|
|
|
|
case ID_STEP:
|
|
default:
|
|
out = 0.0f;
|
|
break;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
incoming()
|
|
|
|
Return the incoming tangent to the curve at key1. The value returned
|
|
for the BEZ2 case is used when extrapolating a linear post behavior.
|
|
====================================================================== */
|
|
|
|
static float incoming( lwKey* key0, lwKey* key1 )
|
|
{
|
|
float a, b, d, t, in;
|
|
|
|
switch( key1->shape )
|
|
{
|
|
case ID_LINE:
|
|
d = key1->value - key0->value;
|
|
if( key1->next )
|
|
{
|
|
t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );
|
|
in = t * ( key1->next->value - key1->value + d );
|
|
}
|
|
else
|
|
in = d;
|
|
break;
|
|
|
|
case ID_TCB:
|
|
a = ( 1.0f - key1->tension )
|
|
* ( 1.0f - key1->continuity )
|
|
* ( 1.0f + key1->bias );
|
|
b = ( 1.0f - key1->tension )
|
|
* ( 1.0f + key1->continuity )
|
|
* ( 1.0f - key1->bias );
|
|
d = key1->value - key0->value;
|
|
|
|
if( key1->next )
|
|
{
|
|
t = ( key1->time - key0->time ) / ( key1->next->time - key0->time );
|
|
in = t * ( b * ( key1->next->value - key1->value ) + a * d );
|
|
}
|
|
else
|
|
in = a * d;
|
|
break;
|
|
|
|
case ID_BEZI:
|
|
case ID_HERM:
|
|
in = key1->param[ 0 ];
|
|
if( key1->next )
|
|
in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time );
|
|
break;
|
|
|
|
case ID_BEZ2:
|
|
in = key1->param[ 1 ] * ( key1->time - key0->time );
|
|
if( idMath::Fabs( key1->param[ 0 ] ) > 1e-5f )
|
|
in /= key1->param[ 0 ];
|
|
else
|
|
in *= 1e5f;
|
|
break;
|
|
|
|
case ID_STEP:
|
|
default:
|
|
in = 0.0f;
|
|
break;
|
|
}
|
|
|
|
return in;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
evalEnvelope()
|
|
|
|
Given a list of keys and a time, returns the interpolated value of the
|
|
envelope at that time.
|
|
====================================================================== */
|
|
|
|
float evalEnvelope( lwEnvelope* env, float time )
|
|
{
|
|
lwKey* key0, *key1, *skey, *ekey;
|
|
float t, h1, h2, h3, h4, in, out, offset = 0.0f;
|
|
int noff;
|
|
|
|
// Start key
|
|
skey = ekey = env->key;
|
|
|
|
/* if there's no key, the value is 0 */
|
|
if( env->nkeys == 0 || skey == NULL )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
/* if there's only one key, the value is constant */
|
|
if( env->nkeys == 1 )
|
|
{
|
|
return env->key->value;
|
|
}
|
|
|
|
/* find the last keys */
|
|
while( ekey->next != NULL )
|
|
{
|
|
ekey = ekey->next;
|
|
}
|
|
|
|
/* use pre-behavior if time is before first key time */
|
|
if( time < skey->time )
|
|
{
|
|
switch( env->behavior[ 0 ] )
|
|
{
|
|
case BEH_RESET:
|
|
return 0.0f;
|
|
|
|
case BEH_CONSTANT:
|
|
return skey->value;
|
|
|
|
case BEH_REPEAT:
|
|
time = range( time, skey->time, ekey->time, NULL );
|
|
break;
|
|
|
|
case BEH_OSCILLATE:
|
|
time = range( time, skey->time, ekey->time, &noff );
|
|
if( noff % 2 )
|
|
time = ekey->time - skey->time - time;
|
|
break;
|
|
|
|
case BEH_OFFSET:
|
|
time = range( time, skey->time, ekey->time, &noff );
|
|
offset = noff * ( ekey->value - skey->value );
|
|
break;
|
|
|
|
case BEH_LINEAR:
|
|
if( skey->next != NULL )
|
|
{
|
|
out = outgoing( skey, skey->next )
|
|
/ ( skey->next->time - skey->time );
|
|
return out * ( time - skey->time ) + skey->value;
|
|
}
|
|
else
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* use post-behavior if time is after last key time */
|
|
|
|
else if( time > ekey->time )
|
|
{
|
|
switch( env->behavior[ 1 ] )
|
|
{
|
|
case BEH_RESET:
|
|
return 0.0f;
|
|
|
|
case BEH_CONSTANT:
|
|
return ekey->value;
|
|
|
|
case BEH_REPEAT:
|
|
time = range( time, skey->time, ekey->time, NULL );
|
|
break;
|
|
|
|
case BEH_OSCILLATE:
|
|
time = range( time, skey->time, ekey->time, &noff );
|
|
if( noff % 2 )
|
|
time = ekey->time - skey->time - time;
|
|
break;
|
|
|
|
case BEH_OFFSET:
|
|
time = range( time, skey->time, ekey->time, &noff );
|
|
offset = noff * ( ekey->value - skey->value );
|
|
break;
|
|
|
|
case BEH_LINEAR:
|
|
in = incoming( ekey->prev, ekey )
|
|
/ ( ekey->time - ekey->prev->time );
|
|
return in * ( time - ekey->time ) + ekey->value;
|
|
}
|
|
}
|
|
|
|
/* get the endpoints of the interval being evaluated */
|
|
|
|
key0 = env->key;
|
|
if( key0 == NULL || key0->next == NULL )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
while( time > key0->next->time )
|
|
key0 = key0->next;
|
|
key1 = key0->next;
|
|
if( key1 == NULL )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
/* check for singularities first */
|
|
|
|
if( time == key0->time )
|
|
return key0->value + offset;
|
|
else if( time == key1->time )
|
|
return key1->value + offset;
|
|
|
|
/* get interval length, time in [0, 1] */
|
|
|
|
t = ( time - key0->time ) / ( key1->time - key0->time );
|
|
|
|
/* interpolate */
|
|
|
|
switch( key1->shape )
|
|
{
|
|
case ID_TCB:
|
|
case ID_BEZI:
|
|
case ID_HERM:
|
|
out = outgoing( key0, key1 );
|
|
in = incoming( key0, key1 );
|
|
hermite( t, &h1, &h2, &h3, &h4 );
|
|
return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset;
|
|
|
|
case ID_BEZ2:
|
|
return bez2( key0, key1, time ) + offset;
|
|
|
|
case ID_LINE:
|
|
return key0->value + t * ( key1->value - key0->value ) + offset;
|
|
|
|
case ID_STEP:
|
|
return key0->value + offset;
|
|
|
|
default:
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwListFree()
|
|
|
|
Free the items in a list.
|
|
====================================================================== */
|
|
|
|
void lwListFree( void* list, void ( *freeNode )( void* ) )
|
|
{
|
|
lwNode* node, *next;
|
|
|
|
node = ( lwNode* ) list;
|
|
while( node )
|
|
{
|
|
next = node->next;
|
|
freeNode( node );
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwListAdd()
|
|
|
|
Append a node to a list.
|
|
====================================================================== */
|
|
|
|
void lwListAdd( void** list, void* node )
|
|
{
|
|
lwNode* head = NULL, *tail = NULL;
|
|
|
|
head = *( ( lwNode** ) list );
|
|
if( head == NULL )
|
|
{
|
|
*list = node;
|
|
return;
|
|
}
|
|
while( head )
|
|
{
|
|
tail = head;
|
|
head = head->next;
|
|
}
|
|
tail->next = ( lwNode* ) node;
|
|
( ( lwNode* ) node )->prev = tail;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwListInsert()
|
|
|
|
Insert a node into a list in sorted order.
|
|
====================================================================== */
|
|
|
|
void lwListInsert( void** vlist, void* vitem, int ( *compare )( void*, void* ) )
|
|
{
|
|
lwNode** list, *item, *node, *prev;
|
|
|
|
if( !*vlist )
|
|
{
|
|
*vlist = vitem;
|
|
return;
|
|
}
|
|
|
|
list = ( lwNode** ) vlist;
|
|
item = ( lwNode* ) vitem;
|
|
node = *list;
|
|
prev = NULL;
|
|
|
|
while( node )
|
|
{
|
|
if( 0 < compare( node, item ) ) break;
|
|
prev = node;
|
|
node = node->next;
|
|
}
|
|
|
|
if( !prev )
|
|
{
|
|
*list = item;
|
|
node->prev = item;
|
|
item->next = node;
|
|
}
|
|
else if( !node )
|
|
{
|
|
prev->next = item;
|
|
item->prev = prev;
|
|
}
|
|
else
|
|
{
|
|
item->next = node;
|
|
item->prev = prev;
|
|
prev->next = item;
|
|
node->prev = item;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
flen
|
|
|
|
This accumulates a count of the number of bytes read. Callers can set
|
|
it at the beginning of a sequence of reads and then retrieve it to get
|
|
the number of bytes actually read. If one of the I/O functions fails,
|
|
flen is set to an error code, after which the I/O functions ignore
|
|
read requests until flen is reset.
|
|
====================================================================== */
|
|
|
|
#define FLEN_ERROR -9999
|
|
|
|
static int flen;
|
|
|
|
void set_flen( int i )
|
|
{
|
|
flen = i;
|
|
}
|
|
|
|
int get_flen()
|
|
{
|
|
return flen;
|
|
}
|
|
|
|
void* getbytes( idFile* fp, int size )
|
|
{
|
|
void* data;
|
|
|
|
if( flen == FLEN_ERROR ) return NULL;
|
|
if( size < 0 )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
data = Mem_ClearedAlloc( size, TAG_MODEL );
|
|
if( !data )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
if( size != fp->Read( data, size ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
Mem_Free( data );
|
|
return NULL;
|
|
}
|
|
|
|
flen += size;
|
|
return data;
|
|
}
|
|
|
|
|
|
void skipbytes( idFile* fp, int n )
|
|
{
|
|
if( flen == FLEN_ERROR ) return;
|
|
if( fp->Seek( n, FS_SEEK_CUR ) )
|
|
flen = FLEN_ERROR;
|
|
else
|
|
flen += n;
|
|
}
|
|
|
|
|
|
int getI1( idFile* fp )
|
|
{
|
|
int i, c;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
c = 0;
|
|
i = fp->Read( &c, 1 );
|
|
if( i < 0 )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
if( c > 127 ) c -= 256;
|
|
flen += 1;
|
|
return c;
|
|
}
|
|
|
|
|
|
short getI2( idFile* fp )
|
|
{
|
|
short i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
if( 2 != fp->Read( &i, 2 ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
BigRevBytes( &i, 2, 1 );
|
|
flen += 2;
|
|
return i;
|
|
}
|
|
|
|
|
|
int getI4( idFile* fp )
|
|
{
|
|
int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
if( 4 != fp->Read( &i, 4 ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
BigRevBytes( &i, 4, 1 );
|
|
flen += 4;
|
|
return i;
|
|
}
|
|
|
|
|
|
unsigned char getU1( idFile* fp )
|
|
{
|
|
int i, c;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
c = 0;
|
|
i = fp->Read( &c, 1 );
|
|
if( i < 0 )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
flen += 1;
|
|
return c;
|
|
}
|
|
|
|
|
|
unsigned short getU2( idFile* fp )
|
|
{
|
|
unsigned short i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
if( 2 != fp->Read( &i, 2 ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
BigRevBytes( &i, 2, 1 );
|
|
flen += 2;
|
|
return i;
|
|
}
|
|
|
|
|
|
unsigned int getU4( idFile* fp )
|
|
{
|
|
unsigned int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
if( 4 != fp->Read( &i, 4 ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0;
|
|
}
|
|
BigRevBytes( &i, 4, 1 );
|
|
flen += 4;
|
|
return i;
|
|
}
|
|
|
|
|
|
int getVX( idFile* fp )
|
|
{
|
|
byte c;
|
|
int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if( c != 0xFF )
|
|
{
|
|
i = c << 8;
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
return 0;
|
|
}
|
|
i |= c;
|
|
flen += 2;
|
|
}
|
|
else
|
|
{
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
return 0;
|
|
}
|
|
i = c << 16;
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
return 0;
|
|
}
|
|
i |= c << 8;
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
return 0;
|
|
}
|
|
i |= c;
|
|
flen += 4;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
float getF4( idFile* fp )
|
|
{
|
|
float f;
|
|
|
|
if( flen == FLEN_ERROR ) return 0.0f;
|
|
if( 4 != fp->Read( &f, 4 ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return 0.0f;
|
|
}
|
|
BigRevBytes( &f, 4, 1 );
|
|
flen += 4;
|
|
|
|
if( IEEE_FLT_IS_DENORMAL( f ) )
|
|
{
|
|
f = 0.0f;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
char* getS0( idFile* fp )
|
|
{
|
|
char* s;
|
|
int i, c, len, pos;
|
|
|
|
if( flen == FLEN_ERROR ) return NULL;
|
|
|
|
pos = fp->Tell();
|
|
for( i = 1; ; i++ )
|
|
{
|
|
c = 0;
|
|
if( fp->Read( &c, 1 ) == -1 )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
if( c == 0 ) break;
|
|
}
|
|
|
|
if( i == 1 )
|
|
{
|
|
if( fp->Seek( pos + 2, FS_SEEK_SET ) )
|
|
flen = FLEN_ERROR;
|
|
else
|
|
flen += 2;
|
|
return NULL;
|
|
}
|
|
|
|
len = i + ( i & 1 );
|
|
s = ( char* )Mem_ClearedAlloc( len, TAG_MODEL );
|
|
if( !s )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
if( fp->Seek( pos, FS_SEEK_SET ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
if( len != fp->Read( s, len ) )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
flen += len;
|
|
return s;
|
|
}
|
|
|
|
|
|
int sgetI1( unsigned char** bp )
|
|
{
|
|
assert( bp != NULL && *bp != NULL ); // remove compiler warning
|
|
int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
i = **bp;
|
|
if( i > 127 ) i -= 256;
|
|
flen += 1;
|
|
( *bp )++;
|
|
return i;
|
|
}
|
|
|
|
|
|
short sgetI2( unsigned char** bp )
|
|
{
|
|
assert( bp != NULL && *bp != NULL ); // remove compiler warning
|
|
short i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
memcpy( &i, *bp, 2 );
|
|
BigRevBytes( &i, 2, 1 );
|
|
flen += 2;
|
|
( *bp ) += 2;
|
|
return i;
|
|
}
|
|
|
|
|
|
int sgetI4( unsigned char** bp )
|
|
{
|
|
assert( bp != NULL && *bp != NULL ); // remove compiler warning
|
|
short i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
memcpy( &i, *bp, sizeof( i ) );
|
|
BigRevBytes( &i, 4, 1 );
|
|
flen += 4;
|
|
( *bp ) += 4;
|
|
return i;
|
|
}
|
|
|
|
|
|
unsigned char sgetU1( unsigned char** bp )
|
|
{
|
|
unsigned char c;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
c = **bp;
|
|
flen += 1;
|
|
( *bp )++;
|
|
return c;
|
|
}
|
|
|
|
|
|
unsigned short sgetU2( unsigned char** bp )
|
|
{
|
|
unsigned char* buf = *bp;
|
|
unsigned short i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
i = ( buf[ 0 ] << 8 ) | buf[ 1 ];
|
|
flen += 2;
|
|
( *bp ) += 2;
|
|
return i;
|
|
}
|
|
|
|
|
|
unsigned int sgetU4( unsigned char** bp )
|
|
{
|
|
unsigned int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
memcpy( &i, *bp, 4 );
|
|
BigRevBytes( &i, 4, 1 );
|
|
flen += 4;
|
|
( *bp ) += 4;
|
|
return i;
|
|
}
|
|
|
|
|
|
int sgetVX( unsigned char** bp )
|
|
{
|
|
unsigned char* buf = *bp;
|
|
int i;
|
|
|
|
if( flen == FLEN_ERROR ) return 0;
|
|
|
|
if( buf[ 0 ] != 0xFF )
|
|
{
|
|
i = buf[ 0 ] << 8 | buf[ 1 ];
|
|
flen += 2;
|
|
( *bp ) += 2;
|
|
}
|
|
else
|
|
{
|
|
i = ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ];
|
|
flen += 4;
|
|
( *bp ) += 4;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
float sgetF4( unsigned char** bp )
|
|
{
|
|
float f;
|
|
|
|
if( flen == FLEN_ERROR ) return 0.0f;
|
|
memcpy( &f, *bp, 4 );
|
|
BigRevBytes( &f, 4, 1 );
|
|
flen += 4;
|
|
( *bp ) += 4;
|
|
|
|
if( IEEE_FLT_IS_DENORMAL( f ) )
|
|
{
|
|
f = 0.0f;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
char* sgetS0( unsigned char** bp )
|
|
{
|
|
char* s;
|
|
unsigned char* buf = *bp;
|
|
int len;
|
|
|
|
if( flen == FLEN_ERROR ) return NULL;
|
|
|
|
len = strlen( ( const char* )buf ) + 1;
|
|
if( len == 1 )
|
|
{
|
|
flen += 2;
|
|
( *bp ) += 2;
|
|
return NULL;
|
|
}
|
|
len += len & 1;
|
|
s = ( char* )Mem_ClearedAlloc( len, TAG_MODEL );
|
|
if( !s )
|
|
{
|
|
flen = FLEN_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
memcpy( s, buf, len );
|
|
flen += len;
|
|
( *bp ) += len;
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeLayer()
|
|
|
|
Free memory used by an lwLayer.
|
|
====================================================================== */
|
|
|
|
void lwFreeLayer( lwLayer* layer )
|
|
{
|
|
if( layer )
|
|
{
|
|
if( layer->name ) Mem_Free( layer->name );
|
|
lwFreePoints( &layer->point );
|
|
lwFreePolygons( &layer->polygon );
|
|
lwListFree( layer->vmap, ( void ( __cdecl* )( void* ) )lwFreeVMap );
|
|
Mem_Free( layer );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeObject()
|
|
|
|
Free memory used by an lwObject.
|
|
====================================================================== */
|
|
|
|
void lwFreeObject( lwObject* object )
|
|
{
|
|
if( object )
|
|
{
|
|
lwListFree( object->layer, ( void ( __cdecl* )( void* ) )lwFreeLayer );
|
|
lwListFree( object->env, ( void ( __cdecl* )( void* ) )lwFreeEnvelope );
|
|
lwListFree( object->clip, ( void ( __cdecl* )( void* ) )lwFreeClip );
|
|
lwListFree( object->surf, ( void ( __cdecl* )( void* ) )lwFreeSurface );
|
|
lwFreeTags( &object->taglist );
|
|
Mem_Free( object );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetObject()
|
|
|
|
Returns the contents of a LightWave object, given its filename, or
|
|
NULL if the file couldn't be loaded. On failure, failID and failpos
|
|
can be used to diagnose the cause.
|
|
|
|
1. If the file isn't an LWO2 or an LWOB, failpos will contain 12 and
|
|
failID will be unchanged.
|
|
|
|
2. If an error occurs while reading, failID will contain the most
|
|
recently read IFF chunk ID, and failpos will contain the value
|
|
returned by fp->Tell() at the time of the failure.
|
|
|
|
3. If the file couldn't be opened, or an error occurs while reading
|
|
the first 12 bytes, both failID and failpos will be unchanged.
|
|
|
|
If you don't need this information, failID and failpos can be NULL.
|
|
====================================================================== */
|
|
|
|
lwObject* lwGetObject( const char* filename, unsigned int* failID, int* failpos )
|
|
{
|
|
idFile* fp = NULL;
|
|
lwObject* object;
|
|
lwLayer* layer;
|
|
lwNode* node;
|
|
int id, formsize, type, cksize;
|
|
int i, rlen;
|
|
|
|
fp = fileSystem->OpenFileRead( filename );
|
|
if( !fp )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* read the first 12 bytes */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
formsize = getU4( fp );
|
|
type = getU4( fp );
|
|
if( 12 != get_flen() )
|
|
{
|
|
fileSystem->CloseFile( fp );
|
|
return NULL;
|
|
}
|
|
|
|
/* is this a LW object? */
|
|
|
|
if( id != ID_FORM )
|
|
{
|
|
fileSystem->CloseFile( fp );
|
|
if( failpos ) *failpos = 12;
|
|
return NULL;
|
|
}
|
|
|
|
if( type != ID_LWO2 )
|
|
{
|
|
fileSystem->CloseFile( fp );
|
|
if( type == ID_LWOB )
|
|
return lwGetObject5( filename, failID, failpos );
|
|
else
|
|
{
|
|
if( failpos ) *failpos = 12;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* allocate an object and a default layer */
|
|
|
|
object = ( lwObject* )Mem_ClearedAlloc( sizeof( lwObject ), TAG_MODEL );
|
|
if( !object ) goto Fail;
|
|
|
|
layer = ( lwLayer* )Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL );
|
|
if( !layer ) goto Fail;
|
|
object->layer = layer;
|
|
|
|
object->timeStamp = fp->Timestamp();
|
|
|
|
/* get the first chunk header */
|
|
|
|
id = getU4( fp );
|
|
cksize = getU4( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
/* process chunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
cksize += cksize & 1;
|
|
|
|
switch( id )
|
|
{
|
|
case ID_LAYR:
|
|
if( object->nlayers > 0 )
|
|
{
|
|
layer = ( lwLayer* )Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL );
|
|
if( !layer ) goto Fail;
|
|
lwListAdd( ( void** )&object->layer, layer );
|
|
}
|
|
object->nlayers++;
|
|
|
|
set_flen( 0 );
|
|
layer->index = getU2( fp );
|
|
layer->flags = getU2( fp );
|
|
layer->pivot[ 0 ] = getF4( fp );
|
|
layer->pivot[ 1 ] = getF4( fp );
|
|
layer->pivot[ 2 ] = getF4( fp );
|
|
layer->name = getS0( fp );
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > cksize ) goto Fail;
|
|
if( rlen <= cksize - 2 )
|
|
layer->parent = getU2( fp );
|
|
rlen = get_flen();
|
|
if( rlen < cksize )
|
|
fp->Seek( cksize - rlen, FS_SEEK_CUR );
|
|
break;
|
|
|
|
case ID_PNTS:
|
|
if( !lwGetPoints( fp, cksize, &layer->point ) )
|
|
goto Fail;
|
|
break;
|
|
|
|
case ID_POLS:
|
|
if( !lwGetPolygons( fp, cksize, &layer->polygon,
|
|
layer->point.offset ) )
|
|
goto Fail;
|
|
break;
|
|
|
|
case ID_VMAP:
|
|
case ID_VMAD:
|
|
node = ( lwNode* ) lwGetVMap( fp, cksize, layer->point.offset,
|
|
layer->polygon.offset, id == ID_VMAD );
|
|
if( !node ) goto Fail;
|
|
lwListAdd( ( void** )&layer->vmap, node );
|
|
layer->nvmaps++;
|
|
break;
|
|
|
|
case ID_PTAG:
|
|
if( !lwGetPolygonTags( fp, cksize, &object->taglist,
|
|
&layer->polygon ) )
|
|
goto Fail;
|
|
break;
|
|
|
|
case ID_BBOX:
|
|
set_flen( 0 );
|
|
for( i = 0; i < 6; i++ )
|
|
layer->bbox[ i ] = getF4( fp );
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > cksize ) goto Fail;
|
|
if( rlen < cksize )
|
|
fp->Seek( cksize - rlen, FS_SEEK_CUR );
|
|
break;
|
|
|
|
case ID_TAGS:
|
|
if( !lwGetTags( fp, cksize, &object->taglist ) )
|
|
goto Fail;
|
|
break;
|
|
|
|
case ID_ENVL:
|
|
node = ( lwNode* ) lwGetEnvelope( fp, cksize );
|
|
if( !node ) goto Fail;
|
|
lwListAdd( ( void** )&object->env, node );
|
|
object->nenvs++;
|
|
break;
|
|
|
|
case ID_CLIP:
|
|
node = ( lwNode* ) lwGetClip( fp, cksize );
|
|
if( !node ) goto Fail;
|
|
lwListAdd( ( void** )&object->clip, node );
|
|
object->nclips++;
|
|
break;
|
|
|
|
case ID_SURF:
|
|
node = ( lwNode* ) lwGetSurface( fp, cksize );
|
|
if( !node ) goto Fail;
|
|
lwListAdd( ( void** )&object->surf, node );
|
|
object->nsurfs++;
|
|
break;
|
|
|
|
case ID_DESC:
|
|
case ID_TEXT:
|
|
case ID_ICON:
|
|
default:
|
|
fp->Seek( cksize, FS_SEEK_CUR );
|
|
break;
|
|
}
|
|
|
|
/* end of the file? */
|
|
|
|
if( formsize <= fp->Tell() - 8 ) break;
|
|
|
|
/* get the next chunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
cksize = getU4( fp );
|
|
if( 8 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
fileSystem->CloseFile( fp );
|
|
fp = NULL;
|
|
|
|
if( object->nlayers == 0 )
|
|
object->nlayers = 1;
|
|
|
|
layer = object->layer;
|
|
while( layer )
|
|
{
|
|
lwGetBoundingBox( &layer->point, layer->bbox );
|
|
lwGetPolyNormals( &layer->point, &layer->polygon );
|
|
if( !lwGetPointPolygons( &layer->point, &layer->polygon ) ) goto Fail;
|
|
if( !lwResolvePolySurfaces( &layer->polygon, &object->taglist,
|
|
&object->surf, &object->nsurfs ) ) goto Fail;
|
|
lwGetVertNormals( &layer->point, &layer->polygon );
|
|
if( !lwGetPointVMaps( &layer->point, layer->vmap ) ) goto Fail;
|
|
if( !lwGetPolyVMaps( &layer->polygon, layer->vmap ) ) goto Fail;
|
|
layer = layer->next;
|
|
}
|
|
|
|
return object;
|
|
|
|
Fail:
|
|
if( failID ) *failID = id;
|
|
if( fp )
|
|
{
|
|
if( failpos ) *failpos = fp->Tell();
|
|
fileSystem->CloseFile( fp );
|
|
}
|
|
lwFreeObject( object );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* IDs specific to LWOB */
|
|
|
|
#define ID_SRFS LWID_('S','R','F','S')
|
|
#define ID_FLAG LWID_('F','L','A','G')
|
|
#define ID_VLUM LWID_('V','L','U','M')
|
|
#define ID_VDIF LWID_('V','D','I','F')
|
|
#define ID_VSPC LWID_('V','S','P','C')
|
|
#define ID_RFLT LWID_('R','F','L','T')
|
|
#define ID_BTEX LWID_('B','T','E','X')
|
|
#define ID_CTEX LWID_('C','T','E','X')
|
|
#define ID_DTEX LWID_('D','T','E','X')
|
|
#define ID_LTEX LWID_('L','T','E','X')
|
|
#define ID_RTEX LWID_('R','T','E','X')
|
|
#define ID_STEX LWID_('S','T','E','X')
|
|
#define ID_TTEX LWID_('T','T','E','X')
|
|
#define ID_TFLG LWID_('T','F','L','G')
|
|
#define ID_TSIZ LWID_('T','S','I','Z')
|
|
#define ID_TCTR LWID_('T','C','T','R')
|
|
#define ID_TFAL LWID_('T','F','A','L')
|
|
#define ID_TVEL LWID_('T','V','E','L')
|
|
#define ID_TCLR LWID_('T','C','L','R')
|
|
#define ID_TVAL LWID_('T','V','A','L')
|
|
#define ID_TAMP LWID_('T','A','M','P')
|
|
#define ID_TIMG LWID_('T','I','M','G')
|
|
#define ID_TAAS LWID_('T','A','A','S')
|
|
#define ID_TREF LWID_('T','R','E','F')
|
|
#define ID_TOPC LWID_('T','O','P','C')
|
|
#define ID_SDAT LWID_('S','D','A','T')
|
|
#define ID_TFP0 LWID_('T','F','P','0')
|
|
#define ID_TFP1 LWID_('T','F','P','1')
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
add_clip()
|
|
|
|
Add a clip to the clip list. Used to store the contents of an RIMG or
|
|
TIMG surface subchunk.
|
|
====================================================================== */
|
|
|
|
static int add_clip( char* s, lwClip** clist, int* nclips )
|
|
{
|
|
lwClip* clip;
|
|
char* p;
|
|
|
|
clip = ( lwClip* )Mem_ClearedAlloc( sizeof( lwClip ), TAG_MODEL );
|
|
if( clip == NULL ) return 0;
|
|
|
|
clip->contrast.val = 1.0f;
|
|
clip->brightness.val = 1.0f;
|
|
clip->saturation.val = 1.0f;
|
|
clip->gamma.val = 1.0f;
|
|
|
|
if( ( p = strstr( s, "(sequence)" ) ) != NULL )
|
|
{
|
|
p[ -1 ] = 0;
|
|
clip->type = ID_ISEQ;
|
|
clip->source.seq.prefix = s;
|
|
clip->source.seq.digits = 3;
|
|
}
|
|
else
|
|
{
|
|
clip->type = ID_STIL;
|
|
clip->source.still.name = s;
|
|
}
|
|
|
|
( *nclips )++;
|
|
clip->index = *nclips;
|
|
|
|
lwListAdd( ( void** )clist, clip );
|
|
|
|
return clip->index;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
add_tvel()
|
|
|
|
Add a triple of envelopes to simulate the old texture velocity
|
|
parameters.
|
|
====================================================================== */
|
|
|
|
static int add_tvel( float pos[], float vel[], lwEnvelope** elist, int* nenvs )
|
|
{
|
|
lwEnvelope* env = NULL;
|
|
lwKey* key0 = NULL, *key1 = NULL;
|
|
int i;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
env = ( lwEnvelope* )Mem_ClearedAlloc( sizeof( lwEnvelope ), TAG_MODEL );
|
|
key0 = ( lwKey* )Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL );
|
|
key1 = ( lwKey* )Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL );
|
|
if( !env || !key0 || !key1 ) return 0;
|
|
|
|
key0->next = key1;
|
|
key0->value = pos[ i ];
|
|
key0->time = 0.0f;
|
|
key1->prev = key0;
|
|
key1->value = pos[ i ] + vel[ i ] * 30.0f;
|
|
key1->time = 1.0f;
|
|
key0->shape = key1->shape = ID_LINE;
|
|
|
|
env->index = *nenvs + i + 1;
|
|
env->type = 0x0301 + i;
|
|
env->name = ( char* )Mem_ClearedAlloc( 11, TAG_MODEL );
|
|
if( env->name )
|
|
{
|
|
strcpy( env->name, "Position.X" );
|
|
env->name[ 9 ] += i;
|
|
}
|
|
env->key = key0;
|
|
env->nkeys = 2;
|
|
env->behavior[ 0 ] = BEH_LINEAR;
|
|
env->behavior[ 1 ] = BEH_LINEAR;
|
|
|
|
lwListAdd( ( void** )elist, env );
|
|
}
|
|
assert( env != NULL );
|
|
|
|
*nenvs += 3;
|
|
return env->index - 2;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
get_texture()
|
|
|
|
Create a new texture for BTEX, CTEX, etc. subchunks.
|
|
====================================================================== */
|
|
|
|
static lwTexture* get_texture( char* s )
|
|
{
|
|
lwTexture* tex;
|
|
|
|
tex = ( lwTexture* )Mem_ClearedAlloc( sizeof( lwTexture ), TAG_MODEL );
|
|
if( !tex ) return NULL;
|
|
|
|
tex->tmap.size.val[ 0 ] =
|
|
tex->tmap.size.val[ 1 ] =
|
|
tex->tmap.size.val[ 2 ] = 1.0f;
|
|
tex->opacity.val = 1.0f;
|
|
tex->enabled = 1;
|
|
|
|
if( strstr( s, "Image Map" ) )
|
|
{
|
|
tex->type = ID_IMAP;
|
|
if( strstr( s, "Planar" ) ) tex->param.imap.projection = 0;
|
|
else if( strstr( s, "Cylindrical" ) ) tex->param.imap.projection = 1;
|
|
else if( strstr( s, "Spherical" ) ) tex->param.imap.projection = 2;
|
|
else if( strstr( s, "Cubic" ) ) tex->param.imap.projection = 3;
|
|
else if( strstr( s, "Front" ) ) tex->param.imap.projection = 4;
|
|
tex->param.imap.aa_strength = 1.0f;
|
|
tex->param.imap.amplitude.val = 1.0f;
|
|
Mem_Free( s );
|
|
}
|
|
else
|
|
{
|
|
tex->type = ID_PROC;
|
|
tex->param.proc.name = s;
|
|
}
|
|
|
|
return tex;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetSurface5()
|
|
|
|
Read an lwSurface from an LWOB file.
|
|
====================================================================== */
|
|
|
|
lwSurface* lwGetSurface5( idFile* fp, int cksize, lwObject* obj )
|
|
{
|
|
lwSurface* surf = NULL;
|
|
lwTexture* tex = NULL;
|
|
lwPlugin* shdr = NULL;
|
|
char* s = NULL;
|
|
float v[ 3 ];
|
|
unsigned int id, flags;
|
|
unsigned short sz;
|
|
int pos, rlen, i = 0;
|
|
|
|
|
|
/* allocate the Surface structure */
|
|
|
|
surf = ( lwSurface* )Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL );
|
|
if( !surf ) goto Fail;
|
|
|
|
/* non-zero defaults */
|
|
|
|
surf->color.rgb[ 0 ] = 0.78431f;
|
|
surf->color.rgb[ 1 ] = 0.78431f;
|
|
surf->color.rgb[ 2 ] = 0.78431f;
|
|
surf->diffuse.val = 1.0f;
|
|
surf->glossiness.val = 0.4f;
|
|
surf->bump.val = 1.0f;
|
|
surf->eta.val = 1.0f;
|
|
surf->sideflags = 1;
|
|
|
|
/* remember where we started */
|
|
|
|
set_flen( 0 );
|
|
pos = fp->Tell();
|
|
|
|
/* name */
|
|
|
|
surf->name = getS0( fp );
|
|
|
|
/* first subchunk header */
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
/* process subchunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_COLR:
|
|
surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f;
|
|
surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f;
|
|
surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f;
|
|
break;
|
|
|
|
case ID_FLAG:
|
|
flags = getU2( fp );
|
|
if( flags & 4 ) surf->smooth = 1.56207f;
|
|
if( flags & 8 ) surf->color_hilite.val = 1.0f;
|
|
if( flags & 16 ) surf->color_filter.val = 1.0f;
|
|
if( flags & 128 ) surf->dif_sharp.val = 0.5f;
|
|
if( flags & 256 ) surf->sideflags = 3;
|
|
if( flags & 512 ) surf->add_trans.val = 1.0f;
|
|
break;
|
|
|
|
case ID_LUMI:
|
|
surf->luminosity.val = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_VLUM:
|
|
surf->luminosity.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_DIFF:
|
|
surf->diffuse.val = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_VDIF:
|
|
surf->diffuse.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_SPEC:
|
|
surf->specularity.val = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_VSPC:
|
|
surf->specularity.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_GLOS:
|
|
surf->glossiness.val = ( float ) logf( ( float ) getU2( fp ) ) / 20.7944f;
|
|
break;
|
|
|
|
case ID_SMAN:
|
|
surf->smooth = getF4( fp );
|
|
break;
|
|
|
|
case ID_REFL:
|
|
surf->reflection.val.val = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_RFLT:
|
|
surf->reflection.options = getU2( fp );
|
|
break;
|
|
|
|
case ID_RIMG:
|
|
s = getS0( fp );
|
|
surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips );
|
|
surf->reflection.options = 3;
|
|
break;
|
|
|
|
case ID_RSAN:
|
|
surf->reflection.seam_angle = getF4( fp );
|
|
break;
|
|
|
|
case ID_TRAN:
|
|
surf->transparency.val.val = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_RIND:
|
|
surf->eta.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_BTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->bump.tex, tex );
|
|
break;
|
|
|
|
case ID_CTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->color.tex, tex );
|
|
break;
|
|
|
|
case ID_DTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->diffuse.tex, tex );
|
|
break;
|
|
|
|
case ID_LTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->luminosity.tex, tex );
|
|
break;
|
|
|
|
case ID_RTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->reflection.val.tex, tex );
|
|
break;
|
|
|
|
case ID_STEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->specularity.tex, tex );
|
|
break;
|
|
|
|
case ID_TTEX:
|
|
s = ( char* )getbytes( fp, sz );
|
|
tex = get_texture( s );
|
|
lwListAdd( ( void** )&surf->transparency.val.tex, tex );
|
|
break;
|
|
|
|
case ID_TFLG:
|
|
assert( tex != NULL );
|
|
flags = getU2( fp );
|
|
|
|
if( flags & 1 ) i = 0;
|
|
if( flags & 2 ) i = 1;
|
|
if( flags & 4 ) i = 2;
|
|
tex->axis = i;
|
|
if( tex->type == ID_IMAP )
|
|
tex->param.imap.axis = i;
|
|
else
|
|
tex->param.proc.axis = i;
|
|
|
|
if( flags & 8 ) tex->tmap.coord_sys = 1;
|
|
if( flags & 16 ) tex->negative = 1;
|
|
if( flags & 32 ) tex->param.imap.pblend = 1;
|
|
if( flags & 64 )
|
|
{
|
|
tex->param.imap.aa_strength = 1.0f;
|
|
tex->param.imap.aas_flags = 1;
|
|
}
|
|
break;
|
|
|
|
case ID_TSIZ:
|
|
assert( tex != NULL );
|
|
for( i = 0; i < 3; i++ )
|
|
tex->tmap.size.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TCTR:
|
|
assert( tex != NULL );
|
|
for( i = 0; i < 3; i++ )
|
|
tex->tmap.center.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFAL:
|
|
assert( tex != NULL );
|
|
for( i = 0; i < 3; i++ )
|
|
tex->tmap.falloff.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TVEL:
|
|
assert( tex != NULL );
|
|
for( i = 0; i < 3; i++ )
|
|
v[ i ] = getF4( fp );
|
|
tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v,
|
|
&obj->env, &obj->nenvs );
|
|
break;
|
|
|
|
case ID_TCLR:
|
|
assert( tex != NULL );
|
|
if( tex->type == ID_PROC )
|
|
for( i = 0; i < 3; i++ )
|
|
tex->param.proc.value[ i ] = getU1( fp ) / 255.0f;
|
|
break;
|
|
|
|
case ID_TVAL:
|
|
assert( tex != NULL );
|
|
tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_TAMP:
|
|
assert( tex != NULL );
|
|
if( tex->type == ID_IMAP )
|
|
tex->param.imap.amplitude.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TIMG:
|
|
assert( tex != NULL );
|
|
s = getS0( fp );
|
|
tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips );
|
|
break;
|
|
|
|
case ID_TAAS:
|
|
assert( tex != NULL );
|
|
tex->param.imap.aa_strength = getF4( fp );
|
|
tex->param.imap.aas_flags = 1;
|
|
break;
|
|
|
|
case ID_TREF:
|
|
assert( tex != NULL );
|
|
tex->tmap.ref_object = ( char* )getbytes( fp, sz );
|
|
break;
|
|
|
|
case ID_TOPC:
|
|
assert( tex != NULL );
|
|
tex->opacity.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFP0:
|
|
assert( tex != NULL );
|
|
if( tex->type == ID_IMAP )
|
|
tex->param.imap.wrapw.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFP1:
|
|
assert( tex != NULL );
|
|
if( tex->type == ID_IMAP )
|
|
tex->param.imap.wraph.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_SHDR:
|
|
shdr = ( lwPlugin* )Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL );
|
|
if( !shdr ) goto Fail;
|
|
shdr->name = ( char* )getbytes( fp, sz );
|
|
lwListAdd( ( void** )&surf->shader, shdr );
|
|
surf->nshaders++;
|
|
break;
|
|
|
|
case ID_SDAT:
|
|
assert( shdr != NULL );
|
|
shdr->data = getbytes( fp, sz );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the SURF chunk? */
|
|
|
|
if( cksize <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
return surf;
|
|
|
|
Fail:
|
|
if( surf ) lwFreeSurface( surf );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPolygons5()
|
|
|
|
Read polygon records from a POLS chunk in an LWOB file. The polygons
|
|
are added to the array in the lwPolygonList.
|
|
====================================================================== */
|
|
|
|
int lwGetPolygons5( idFile* fp, int cksize, lwPolygonList* plist, int ptoffset )
|
|
{
|
|
lwPolygon* pp;
|
|
lwPolVert* pv;
|
|
unsigned char* buf, *bp;
|
|
int i, j, nv, nverts, npols;
|
|
|
|
|
|
if( cksize == 0 ) return 1;
|
|
|
|
/* read the whole chunk */
|
|
|
|
set_flen( 0 );
|
|
buf = ( unsigned char* )getbytes( fp, cksize );
|
|
if( !buf ) goto Fail;
|
|
|
|
/* count the polygons and vertices */
|
|
|
|
nverts = 0;
|
|
npols = 0;
|
|
bp = buf;
|
|
|
|
while( bp < buf + cksize )
|
|
{
|
|
nv = sgetU2( &bp );
|
|
nverts += nv;
|
|
npols++;
|
|
bp += 2 * nv;
|
|
i = sgetI2( &bp );
|
|
if( i < 0 ) bp += 2; /* detail polygons */
|
|
}
|
|
|
|
if( !lwAllocPolygons( plist, npols, nverts ) )
|
|
goto Fail;
|
|
|
|
/* fill in the new polygons */
|
|
|
|
bp = buf;
|
|
pp = plist->pol + plist->offset;
|
|
pv = plist->pol[ 0 ].v + plist->voffset;
|
|
|
|
for( i = 0; i < npols; i++ )
|
|
{
|
|
nv = sgetU2( &bp );
|
|
|
|
pp->nverts = nv;
|
|
pp->type = ID_FACE;
|
|
if( !pp->v ) pp->v = pv;
|
|
for( j = 0; j < nv; j++ )
|
|
pv[ j ].index = sgetU2( &bp ) + ptoffset;
|
|
j = sgetI2( &bp );
|
|
if( j < 0 )
|
|
{
|
|
j = -j;
|
|
bp += 2;
|
|
}
|
|
j -= 1;
|
|
pp->surf.index = j;
|
|
|
|
pp++;
|
|
pv += nv;
|
|
}
|
|
|
|
Mem_Free( buf );
|
|
return 1;
|
|
|
|
Fail:
|
|
if( buf ) Mem_Free( buf );
|
|
lwFreePolygons( plist );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
getLWObject5()
|
|
|
|
Returns the contents of an LWOB, given its filename, or NULL if the
|
|
file couldn't be loaded. On failure, failID and failpos can be used
|
|
to diagnose the cause.
|
|
|
|
1. If the file isn't an LWOB, failpos will contain 12 and failID will
|
|
be unchanged.
|
|
|
|
2. If an error occurs while reading an LWOB, failID will contain the
|
|
most recently read IFF chunk ID, and failpos will contain the
|
|
value returned by fp->Tell() at the time of the failure.
|
|
|
|
3. If the file couldn't be opened, or an error occurs while reading
|
|
the first 12 bytes, both failID and failpos will be unchanged.
|
|
|
|
If you don't need this information, failID and failpos can be NULL.
|
|
====================================================================== */
|
|
|
|
lwObject* lwGetObject5( const char* filename, unsigned int* failID, int* failpos )
|
|
{
|
|
idFile* fp = NULL;
|
|
lwObject* object;
|
|
lwLayer* layer;
|
|
lwNode* node;
|
|
int id, formsize, type, cksize;
|
|
|
|
|
|
/* open the file */
|
|
|
|
//fp = fopen( filename, "rb" );
|
|
//if ( !fp ) return NULL;
|
|
|
|
/* read the first 12 bytes */
|
|
fp = fileSystem->OpenFileRead( filename );
|
|
if( !fp )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
formsize = getU4( fp );
|
|
type = getU4( fp );
|
|
if( 12 != get_flen() )
|
|
{
|
|
fileSystem->CloseFile( fp );
|
|
return NULL;
|
|
}
|
|
|
|
/* LWOB? */
|
|
|
|
if( id != ID_FORM || type != ID_LWOB )
|
|
{
|
|
fileSystem->CloseFile( fp );
|
|
if( failpos ) *failpos = 12;
|
|
return NULL;
|
|
}
|
|
|
|
/* allocate an object and a default layer */
|
|
|
|
object = ( lwObject* )Mem_ClearedAlloc( sizeof( lwObject ), TAG_MODEL );
|
|
if( !object ) goto Fail2;
|
|
|
|
layer = ( lwLayer* )Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL );
|
|
if( !layer ) goto Fail2;
|
|
object->layer = layer;
|
|
object->nlayers = 1;
|
|
|
|
/* get the first chunk header */
|
|
|
|
id = getU4( fp );
|
|
cksize = getU4( fp );
|
|
if( 0 > get_flen() ) goto Fail2;
|
|
|
|
/* process chunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
cksize += cksize & 1;
|
|
|
|
switch( id )
|
|
{
|
|
case ID_PNTS:
|
|
if( !lwGetPoints( fp, cksize, &layer->point ) )
|
|
goto Fail2;
|
|
break;
|
|
|
|
case ID_POLS:
|
|
if( !lwGetPolygons5( fp, cksize, &layer->polygon,
|
|
layer->point.offset ) )
|
|
goto Fail2;
|
|
break;
|
|
|
|
case ID_SRFS:
|
|
if( !lwGetTags( fp, cksize, &object->taglist ) )
|
|
goto Fail2;
|
|
break;
|
|
|
|
case ID_SURF:
|
|
node = ( lwNode* ) lwGetSurface5( fp, cksize, object );
|
|
if( !node ) goto Fail2;
|
|
lwListAdd( ( void** )&object->surf, node );
|
|
object->nsurfs++;
|
|
break;
|
|
|
|
default:
|
|
fp->Seek( cksize, FS_SEEK_CUR );
|
|
break;
|
|
}
|
|
|
|
/* end of the file? */
|
|
|
|
if( formsize <= fp->Tell() - 8 ) break;
|
|
|
|
/* get the next chunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
cksize = getU4( fp );
|
|
if( 8 != get_flen() ) goto Fail2;
|
|
}
|
|
|
|
fileSystem->CloseFile( fp );
|
|
fp = NULL;
|
|
|
|
lwGetBoundingBox( &layer->point, layer->bbox );
|
|
lwGetPolyNormals( &layer->point, &layer->polygon );
|
|
if( !lwGetPointPolygons( &layer->point, &layer->polygon ) ) goto Fail2;
|
|
if( !lwResolvePolySurfaces( &layer->polygon, &object->taglist,
|
|
&object->surf, &object->nsurfs ) ) goto Fail2;
|
|
lwGetVertNormals( &layer->point, &layer->polygon );
|
|
|
|
return object;
|
|
|
|
Fail2:
|
|
if( failID ) *failID = id;
|
|
if( fp )
|
|
{
|
|
if( failpos ) *failpos = fp->Tell();
|
|
fileSystem->CloseFile( fp );
|
|
}
|
|
lwFreeObject( object );
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreePoints()
|
|
|
|
Free the memory used by an lwPointList.
|
|
====================================================================== */
|
|
|
|
void lwFreePoints( lwPointList* point )
|
|
{
|
|
int i;
|
|
|
|
if( point )
|
|
{
|
|
if( point->pt )
|
|
{
|
|
for( i = 0; i < point->count; i++ )
|
|
{
|
|
if( point->pt[ i ].pol ) Mem_Free( point->pt[ i ].pol );
|
|
if( point->pt[ i ].vm ) Mem_Free( point->pt[ i ].vm );
|
|
}
|
|
Mem_Free( point->pt );
|
|
}
|
|
memset( point, 0, sizeof( lwPointList ) );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreePolygons()
|
|
|
|
Free the memory used by an lwPolygonList.
|
|
====================================================================== */
|
|
|
|
void lwFreePolygons( lwPolygonList* plist )
|
|
{
|
|
int i, j;
|
|
|
|
if( plist )
|
|
{
|
|
if( plist->pol )
|
|
{
|
|
for( i = 0; i < plist->count; i++ )
|
|
{
|
|
if( plist->pol[ i ].v )
|
|
{
|
|
for( j = 0; j < plist->pol[ i ].nverts; j++ )
|
|
if( plist->pol[ i ].v[ j ].vm )
|
|
Mem_Free( plist->pol[ i ].v[ j ].vm );
|
|
}
|
|
}
|
|
if( plist->pol[ 0 ].v )
|
|
Mem_Free( plist->pol[ 0 ].v );
|
|
Mem_Free( plist->pol );
|
|
}
|
|
memset( plist, 0, sizeof( lwPolygonList ) );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPoints()
|
|
|
|
Read point records from a PNTS chunk in an LWO2 file. The points are
|
|
added to the array in the lwPointList.
|
|
====================================================================== */
|
|
|
|
int lwGetPoints( idFile* fp, int cksize, lwPointList* point )
|
|
{
|
|
float* f;
|
|
int np, i, j;
|
|
|
|
if( cksize == 1 ) return 1;
|
|
|
|
/* extend the point array to hold the new points */
|
|
|
|
np = cksize / 12;
|
|
point->offset = point->count;
|
|
point->count += np;
|
|
lwPoint* oldpt = point->pt;
|
|
point->pt = ( lwPoint* )Mem_Alloc( point->count * sizeof( lwPoint ), TAG_MODEL );
|
|
if( !point->pt ) return 0;
|
|
if( oldpt )
|
|
{
|
|
memcpy( point->pt, oldpt, point->offset * sizeof( lwPoint ) );
|
|
Mem_Free( oldpt );
|
|
}
|
|
memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint ) );
|
|
|
|
/* read the whole chunk */
|
|
|
|
f = ( float* ) getbytes( fp, cksize );
|
|
if( !f ) return 0;
|
|
BigRevBytes( f, 4, np * 3 );
|
|
|
|
/* assign position values */
|
|
|
|
for( i = 0, j = 0; i < np; i++, j += 3 )
|
|
{
|
|
point->pt[ i ].pos[ 0 ] = f[ j ];
|
|
point->pt[ i ].pos[ 1 ] = f[ j + 1 ];
|
|
point->pt[ i ].pos[ 2 ] = f[ j + 2 ];
|
|
}
|
|
|
|
Mem_Free( f );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetBoundingBox()
|
|
|
|
Calculate the bounding box for a point list, but only if the bounding
|
|
box hasn't already been initialized.
|
|
====================================================================== */
|
|
|
|
void lwGetBoundingBox( lwPointList* point, float bbox[] )
|
|
{
|
|
int i, j;
|
|
|
|
if( point->count == 0 ) return;
|
|
|
|
for( i = 0; i < 6; i++ )
|
|
if( bbox[ i ] != 0.0f ) return;
|
|
|
|
bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f;
|
|
bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f;
|
|
for( i = 0; i < point->count; i++ )
|
|
{
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
if( bbox[ j ] > point->pt[ i ].pos[ j ] )
|
|
bbox[ j ] = point->pt[ i ].pos[ j ];
|
|
if( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] )
|
|
bbox[ j + 3 ] = point->pt[ i ].pos[ j ];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwAllocPolygons()
|
|
|
|
Allocate or extend the polygon arrays to hold new records.
|
|
====================================================================== */
|
|
|
|
int lwAllocPolygons( lwPolygonList* plist, int npols, int nverts )
|
|
{
|
|
int i;
|
|
|
|
plist->offset = plist->count;
|
|
plist->count += npols;
|
|
lwPolygon* oldpol = plist->pol;
|
|
plist->pol = ( lwPolygon* )Mem_Alloc( plist->count * sizeof( lwPolygon ), TAG_MODEL );
|
|
if( !plist->pol ) return 0;
|
|
if( oldpol )
|
|
{
|
|
memcpy( plist->pol, oldpol, plist->offset * sizeof( lwPolygon ) );
|
|
Mem_Free( oldpol );
|
|
}
|
|
memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon ) );
|
|
|
|
plist->voffset = plist->vcount;
|
|
plist->vcount += nverts;
|
|
lwPolVert* oldpolv = plist->pol[0].v;
|
|
plist->pol[0].v = ( lwPolVert* )Mem_Alloc( plist->vcount * sizeof( lwPolVert ), TAG_MODEL );
|
|
if( !plist->pol[ 0 ].v ) return 0;
|
|
if( oldpolv )
|
|
{
|
|
memcpy( plist->pol[0].v, oldpolv, plist->voffset * sizeof( lwPolVert ) );
|
|
Mem_Free( oldpolv );
|
|
}
|
|
memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert ) );
|
|
|
|
/* fix up the old vertex pointers */
|
|
|
|
for( i = 1; i < plist->offset; i++ )
|
|
plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPolygons()
|
|
|
|
Read polygon records from a POLS chunk in an LWO2 file. The polygons
|
|
are added to the array in the lwPolygonList.
|
|
====================================================================== */
|
|
|
|
int lwGetPolygons( idFile* fp, int cksize, lwPolygonList* plist, int ptoffset )
|
|
{
|
|
lwPolygon* pp;
|
|
lwPolVert* pv;
|
|
unsigned char* buf, *bp;
|
|
int i, j, flags, nv, nverts, npols;
|
|
unsigned int type;
|
|
|
|
|
|
if( cksize == 0 ) return 1;
|
|
|
|
/* read the whole chunk */
|
|
|
|
set_flen( 0 );
|
|
type = getU4( fp );
|
|
buf = ( unsigned char* )getbytes( fp, cksize - 4 );
|
|
if( cksize != get_flen() ) goto Fail;
|
|
|
|
/* count the polygons and vertices */
|
|
|
|
nverts = 0;
|
|
npols = 0;
|
|
bp = buf;
|
|
|
|
while( bp < buf + cksize - 4 )
|
|
{
|
|
nv = sgetU2( &bp );
|
|
nv &= 0x03FF;
|
|
nverts += nv;
|
|
npols++;
|
|
for( i = 0; i < nv; i++ )
|
|
j = sgetVX( &bp );
|
|
}
|
|
|
|
if( !lwAllocPolygons( plist, npols, nverts ) )
|
|
goto Fail;
|
|
|
|
/* fill in the new polygons */
|
|
|
|
bp = buf;
|
|
pp = plist->pol + plist->offset;
|
|
pv = plist->pol[ 0 ].v + plist->voffset;
|
|
|
|
for( i = 0; i < npols; i++ )
|
|
{
|
|
nv = sgetU2( &bp );
|
|
flags = nv & 0xFC00;
|
|
nv &= 0x03FF;
|
|
|
|
pp->nverts = nv;
|
|
pp->flags = flags;
|
|
pp->type = type;
|
|
if( !pp->v ) pp->v = pv;
|
|
for( j = 0; j < nv; j++ )
|
|
pp->v[ j ].index = sgetVX( &bp ) + ptoffset;
|
|
|
|
pp++;
|
|
pv += nv;
|
|
}
|
|
|
|
Mem_Free( buf );
|
|
return 1;
|
|
|
|
Fail:
|
|
if( buf ) Mem_Free( buf );
|
|
lwFreePolygons( plist );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPolyNormals()
|
|
|
|
Calculate the polygon normals. By convention, LW's polygon normals
|
|
are found as the cross product of the first and last edges. It's
|
|
undefined for one- and two-point polygons.
|
|
====================================================================== */
|
|
|
|
void lwGetPolyNormals( lwPointList* point, lwPolygonList* polygon )
|
|
{
|
|
int i, j;
|
|
float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ];
|
|
|
|
for( i = 0; i < polygon->count; i++ )
|
|
{
|
|
if( polygon->pol[ i ].nverts < 3 ) continue;
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
|
|
// FIXME: track down why indexes are way out of range
|
|
p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ];
|
|
p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ];
|
|
pn[ j ] = point->pt[ polygon->pol[ i ].v[ polygon->pol[ i ].nverts - 1 ].index ].pos[ j ];
|
|
}
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
v1[ j ] = p2[ j ] - p1[ j ];
|
|
v2[ j ] = pn[ j ] - p1[ j ];
|
|
}
|
|
|
|
cross( v1, v2, polygon->pol[ i ].norm );
|
|
normalize( polygon->pol[ i ].norm );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPointPolygons()
|
|
|
|
For each point, fill in the indexes of the polygons that share the
|
|
point. Returns 0 if any of the memory allocations fail, otherwise
|
|
returns 1.
|
|
====================================================================== */
|
|
|
|
int lwGetPointPolygons( lwPointList* point, lwPolygonList* polygon )
|
|
{
|
|
int i, j, k;
|
|
|
|
/* count the number of polygons per point */
|
|
|
|
for( i = 0; i < polygon->count; i++ )
|
|
for( j = 0; j < polygon->pol[ i ].nverts; j++ )
|
|
++point->pt[ polygon->pol[ i ].v[ j ].index ].npols;
|
|
|
|
/* alloc per-point polygon arrays */
|
|
|
|
for( i = 0; i < point->count; i++ )
|
|
{
|
|
if( point->pt[ i ].npols == 0 ) continue;
|
|
point->pt[ i ].pol = ( int* )Mem_ClearedAlloc( point->pt[ i ].npols * sizeof( int ), TAG_MODEL );
|
|
if( !point->pt[ i ].pol ) return 0;
|
|
point->pt[ i ].npols = 0;
|
|
}
|
|
|
|
/* fill in polygon array for each point */
|
|
|
|
for( i = 0; i < polygon->count; i++ )
|
|
{
|
|
for( j = 0; j < polygon->pol[ i ].nverts; j++ )
|
|
{
|
|
k = polygon->pol[ i ].v[ j ].index;
|
|
point->pt[ k ].pol[ point->pt[ k ].npols ] = i;
|
|
++point->pt[ k ].npols;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwResolvePolySurfaces()
|
|
|
|
Convert tag indexes into actual lwSurface pointers. If any polygons
|
|
point to tags for which no corresponding surface can be found, a
|
|
default surface is created.
|
|
====================================================================== */
|
|
|
|
int lwResolvePolySurfaces( lwPolygonList* polygon, lwTagList* tlist,
|
|
lwSurface** surf, int* nsurfs )
|
|
{
|
|
lwSurface** s, *st;
|
|
int i;
|
|
|
|
if( tlist->count == 0 ) return 1;
|
|
|
|
s = ( lwSurface** )Mem_ClearedAlloc( tlist->count * sizeof( lwSurface* ), TAG_MODEL );
|
|
if( !s ) return 0;
|
|
|
|
for( i = 0; i < tlist->count; i++ )
|
|
{
|
|
st = *surf;
|
|
while( st )
|
|
{
|
|
if( !strcmp( st->name, tlist->tag[ i ] ) )
|
|
{
|
|
s[ i ] = st;
|
|
break;
|
|
}
|
|
st = st->next;
|
|
}
|
|
}
|
|
|
|
intptr_t index;
|
|
for( i = 0; i < polygon->count; i++ )
|
|
{
|
|
index = polygon->pol[ i ].surf.index;
|
|
|
|
if( index < 0 || index > tlist->count ) return 0;
|
|
if( !s[ index ] )
|
|
{
|
|
s[ index ] = lwDefaultSurface();
|
|
if( !s[ index ] ) return 0;
|
|
s[ index ]->name = ( char* )Mem_ClearedAlloc( strlen( tlist->tag[ index ] ) + 1, TAG_MODEL );
|
|
if( !s[ index ]->name ) return 0;
|
|
strcpy( s[ index ]->name, tlist->tag[ index ] );
|
|
lwListAdd( ( void** )surf, s[ index ] );
|
|
*nsurfs = *nsurfs + 1;
|
|
}
|
|
polygon->pol[ i ].surf.ptr = s[ index ];
|
|
}
|
|
|
|
Mem_Free( s );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetVertNormals()
|
|
|
|
Calculate the vertex normals. For each polygon vertex, sum the
|
|
normals of the polygons that share the point. If the normals of the
|
|
current and adjacent polygons form an angle greater than the max
|
|
smoothing angle for the current polygon's surface, the normal of the
|
|
adjacent polygon is excluded from the sum. It's also excluded if the
|
|
polygons aren't in the same smoothing group.
|
|
|
|
Assumes that lwGetPointPolygons(), lwGetPolyNormals() and
|
|
lwResolvePolySurfaces() have already been called.
|
|
====================================================================== */
|
|
|
|
void lwGetVertNormals( lwPointList* point, lwPolygonList* polygon )
|
|
{
|
|
int j, k, n, g, h, p;
|
|
float a;
|
|
|
|
for( j = 0; j < polygon->count; j++ )
|
|
{
|
|
for( n = 0; n < polygon->pol[ j ].nverts; n++ )
|
|
{
|
|
for( k = 0; k < 3; k++ )
|
|
polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ];
|
|
|
|
if( polygon->pol[ j ].surf.ptr->smooth <= 0 ) continue;
|
|
|
|
p = polygon->pol[ j ].v[ n ].index;
|
|
|
|
for( g = 0; g < point->pt[ p ].npols; g++ )
|
|
{
|
|
h = point->pt[ p ].pol[ g ];
|
|
if( h == j ) continue;
|
|
|
|
if( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp )
|
|
continue;
|
|
a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm );
|
|
if( a > polygon->pol[ j ].surf.ptr->smooth ) continue;
|
|
|
|
for( k = 0; k < 3; k++ )
|
|
polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ];
|
|
}
|
|
|
|
normalize( polygon->pol[ j ].v[ n ].norm );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeTags()
|
|
|
|
Free memory used by an lwTagList.
|
|
====================================================================== */
|
|
|
|
void lwFreeTags( lwTagList* tlist )
|
|
{
|
|
int i;
|
|
|
|
if( tlist )
|
|
{
|
|
if( tlist->tag )
|
|
{
|
|
for( i = 0; i < tlist->count; i++ )
|
|
if( tlist->tag[ i ] )
|
|
{
|
|
Mem_Free( tlist->tag[ i ] );
|
|
}
|
|
Mem_Free( tlist->tag );
|
|
}
|
|
memset( tlist, 0, sizeof( lwTagList ) );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetTags()
|
|
|
|
Read tag strings from a TAGS chunk in an LWO2 file. The tags are
|
|
added to the lwTagList array.
|
|
====================================================================== */
|
|
|
|
int lwGetTags( idFile* fp, int cksize, lwTagList* tlist )
|
|
{
|
|
char* buf, *bp;
|
|
int i, len, ntags;
|
|
|
|
if( cksize == 0 ) return 1;
|
|
|
|
/* read the whole chunk */
|
|
|
|
set_flen( 0 );
|
|
buf = ( char* )getbytes( fp, cksize );
|
|
if( !buf ) return 0;
|
|
|
|
/* count the strings */
|
|
|
|
ntags = 0;
|
|
bp = buf;
|
|
while( bp < buf + cksize )
|
|
{
|
|
len = strlen( bp ) + 1;
|
|
len += len & 1;
|
|
bp += len;
|
|
++ntags;
|
|
}
|
|
|
|
/* expand the string array to hold the new tags */
|
|
|
|
tlist->offset = tlist->count;
|
|
tlist->count += ntags;
|
|
char** oldtag = tlist->tag;
|
|
tlist->tag = ( char** )Mem_Alloc( tlist->count * sizeof( char* ), TAG_MODEL );
|
|
if( !tlist->tag ) goto Fail;
|
|
if( oldtag )
|
|
{
|
|
memcpy( tlist->tag, oldtag, tlist->offset * sizeof( char* ) );
|
|
Mem_Free( oldtag );
|
|
}
|
|
memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char* ) );
|
|
|
|
/* copy the new tags to the tag array */
|
|
|
|
bp = buf;
|
|
for( i = 0; i < ntags; i++ )
|
|
tlist->tag[ i + tlist->offset ] = sgetS0( ( unsigned char** )&bp );
|
|
|
|
Mem_Free( buf );
|
|
return 1;
|
|
|
|
Fail:
|
|
if( buf ) Mem_Free( buf );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPolygonTags()
|
|
|
|
Read polygon tags from a PTAG chunk in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
int lwGetPolygonTags( idFile* fp, int cksize, lwTagList* tlist, lwPolygonList* plist )
|
|
{
|
|
unsigned int type;
|
|
int rlen = 0, i, j;
|
|
|
|
set_flen( 0 );
|
|
type = getU4( fp );
|
|
rlen = get_flen();
|
|
if( rlen < 0 ) return 0;
|
|
|
|
if( type != ID_SURF && type != ID_PART && type != ID_SMGP )
|
|
{
|
|
fp->Seek( cksize - 4, FS_SEEK_CUR );
|
|
return 1;
|
|
}
|
|
|
|
while( rlen < cksize )
|
|
{
|
|
i = getVX( fp ) + plist->offset;
|
|
j = getVX( fp ) + tlist->offset;
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > cksize ) return 0;
|
|
|
|
switch( type )
|
|
{
|
|
case ID_SURF:
|
|
plist->pol[ i ].surf.index = j;
|
|
break;
|
|
case ID_PART:
|
|
plist->pol[ i ].part = j;
|
|
break;
|
|
case ID_SMGP:
|
|
plist->pol[ i ].smoothgrp = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreePlugin()
|
|
|
|
Free the memory used by an lwPlugin.
|
|
====================================================================== */
|
|
|
|
void lwFreePlugin( lwPlugin* p )
|
|
{
|
|
if( p )
|
|
{
|
|
if( p->ord ) Mem_Free( p->ord );
|
|
if( p->name ) Mem_Free( p->name );
|
|
if( p->data ) Mem_Free( p->data );
|
|
Mem_Free( p );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeTexture()
|
|
|
|
Free the memory used by an lwTexture.
|
|
====================================================================== */
|
|
|
|
void lwFreeTexture( lwTexture* t )
|
|
{
|
|
if( t )
|
|
{
|
|
if( t->ord ) Mem_Free( t->ord );
|
|
switch( t->type )
|
|
{
|
|
case ID_IMAP:
|
|
if( t->param.imap.vmap_name ) Mem_Free( t->param.imap.vmap_name );
|
|
break;
|
|
case ID_PROC:
|
|
if( t->param.proc.name ) Mem_Free( t->param.proc.name );
|
|
if( t->param.proc.data ) Mem_Free( t->param.proc.data );
|
|
break;
|
|
case ID_GRAD:
|
|
if( t->param.grad.key ) Mem_Free( t->param.grad.key );
|
|
if( t->param.grad.ikey ) Mem_Free( t->param.grad.ikey );
|
|
break;
|
|
}
|
|
if( t->tmap.ref_object ) Mem_Free( t->tmap.ref_object );
|
|
Mem_Free( t );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeSurface()
|
|
|
|
Free the memory used by an lwSurface.
|
|
====================================================================== */
|
|
|
|
void lwFreeSurface( lwSurface* surf )
|
|
{
|
|
if( surf )
|
|
{
|
|
if( surf->name ) Mem_Free( surf->name );
|
|
if( surf->srcname ) Mem_Free( surf->srcname );
|
|
|
|
lwListFree( surf->shader, ( void ( __cdecl* )( void* ) )lwFreePlugin );
|
|
|
|
lwListFree( surf->color.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->luminosity.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->diffuse.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->specularity.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->glossiness.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->reflection.val.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->transparency.val.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->eta.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->translucency.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
lwListFree( surf->bump.tex, ( void ( __cdecl* )( void* ) )lwFreeTexture );
|
|
|
|
Mem_Free( surf );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetTHeader()
|
|
|
|
Read a texture map header from a SURF.BLOK in an LWO2 file. This is
|
|
the first subchunk in a BLOK, and its contents are common to all three
|
|
texture types.
|
|
====================================================================== */
|
|
|
|
int lwGetTHeader( idFile* fp, int hsz, lwTexture* tex )
|
|
{
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int pos, rlen;
|
|
|
|
|
|
/* remember where we started */
|
|
|
|
set_flen( 0 );
|
|
pos = fp->Tell();
|
|
|
|
/* ordinal string */
|
|
|
|
tex->ord = getS0( fp );
|
|
|
|
/* first subchunk header */
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) return 0;
|
|
|
|
/* process subchunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_CHAN:
|
|
tex->chan = getU4( fp );
|
|
break;
|
|
|
|
case ID_OPAC:
|
|
tex->opac_type = getU2( fp );
|
|
tex->opacity.val = getF4( fp );
|
|
tex->opacity.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_ENAB:
|
|
tex->enabled = getU2( fp );
|
|
break;
|
|
|
|
case ID_NEGA:
|
|
tex->negative = getU2( fp );
|
|
break;
|
|
|
|
case ID_AXIS:
|
|
tex->axis = getU2( fp );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) return 0;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the texture header subchunk? */
|
|
|
|
if( hsz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) return 0;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetTMap()
|
|
|
|
Read a texture map from a SURF.BLOK in an LWO2 file. The TMAP
|
|
defines the mapping from texture to world or object coordinates.
|
|
====================================================================== */
|
|
|
|
int lwGetTMap( idFile* fp, int tmapsz, lwTMap* tmap )
|
|
{
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int rlen, pos, i;
|
|
|
|
pos = fp->Tell();
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) return 0;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_SIZE:
|
|
for( i = 0; i < 3; i++ )
|
|
tmap->size.val[ i ] = getF4( fp );
|
|
tmap->size.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_CNTR:
|
|
for( i = 0; i < 3; i++ )
|
|
tmap->center.val[ i ] = getF4( fp );
|
|
tmap->center.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_ROTA:
|
|
for( i = 0; i < 3; i++ )
|
|
tmap->rotate.val[ i ] = getF4( fp );
|
|
tmap->rotate.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_FALL:
|
|
tmap->fall_type = getU2( fp );
|
|
for( i = 0; i < 3; i++ )
|
|
tmap->falloff.val[ i ] = getF4( fp );
|
|
tmap->falloff.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_OREF:
|
|
tmap->ref_object = getS0( fp );
|
|
break;
|
|
|
|
case ID_CSYS:
|
|
tmap->coord_sys = getU2( fp );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading the current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) return 0;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the TMAP subchunk? */
|
|
|
|
if( tmapsz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) return 0;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetImageMap()
|
|
|
|
Read an lwImageMap from a SURF.BLOK in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
int lwGetImageMap( idFile* fp, int rsz, lwTexture* tex )
|
|
{
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int rlen, pos;
|
|
|
|
pos = fp->Tell();
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) return 0;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_TMAP:
|
|
if( !lwGetTMap( fp, sz, &tex->tmap ) ) return 0;
|
|
break;
|
|
|
|
case ID_PROJ:
|
|
tex->param.imap.projection = getU2( fp );
|
|
break;
|
|
|
|
case ID_VMAP:
|
|
tex->param.imap.vmap_name = getS0( fp );
|
|
break;
|
|
|
|
case ID_AXIS:
|
|
tex->param.imap.axis = getU2( fp );
|
|
break;
|
|
|
|
case ID_IMAG:
|
|
tex->param.imap.cindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_WRAP:
|
|
tex->param.imap.wrapw_type = getU2( fp );
|
|
tex->param.imap.wraph_type = getU2( fp );
|
|
break;
|
|
|
|
case ID_WRPW:
|
|
tex->param.imap.wrapw.val = getF4( fp );
|
|
tex->param.imap.wrapw.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_WRPH:
|
|
tex->param.imap.wraph.val = getF4( fp );
|
|
tex->param.imap.wraph.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_AAST:
|
|
tex->param.imap.aas_flags = getU2( fp );
|
|
tex->param.imap.aa_strength = getF4( fp );
|
|
break;
|
|
|
|
case ID_PIXB:
|
|
tex->param.imap.pblend = getU2( fp );
|
|
break;
|
|
|
|
case ID_STCK:
|
|
tex->param.imap.stck.val = getF4( fp );
|
|
tex->param.imap.stck.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_TAMP:
|
|
tex->param.imap.amplitude.val = getF4( fp );
|
|
tex->param.imap.amplitude.eindex = getVX( fp );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading the current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) return 0;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the image map? */
|
|
|
|
if( rsz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) return 0;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetProcedural()
|
|
|
|
Read an lwProcedural from a SURF.BLOK in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
int lwGetProcedural( idFile* fp, int rsz, lwTexture* tex )
|
|
{
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int rlen, pos;
|
|
|
|
pos = fp->Tell();
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) return 0;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_TMAP:
|
|
if( !lwGetTMap( fp, sz, &tex->tmap ) ) return 0;
|
|
break;
|
|
|
|
case ID_AXIS:
|
|
tex->param.proc.axis = getU2( fp );
|
|
break;
|
|
|
|
case ID_VALU:
|
|
tex->param.proc.value[ 0 ] = getF4( fp );
|
|
if( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp );
|
|
if( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_FUNC:
|
|
tex->param.proc.name = getS0( fp );
|
|
rlen = get_flen();
|
|
tex->param.proc.data = getbytes( fp, sz - rlen );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading the current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) return 0;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the procedural block? */
|
|
|
|
if( rsz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) return 0;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetGradient()
|
|
|
|
Read an lwGradient from a SURF.BLOK in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
int lwGetGradient( idFile* fp, int rsz, lwTexture* tex )
|
|
{
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int rlen, pos, i, j, nkeys;
|
|
|
|
pos = fp->Tell();
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) return 0;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_TMAP:
|
|
if( !lwGetTMap( fp, sz, &tex->tmap ) ) return 0;
|
|
break;
|
|
|
|
case ID_PNAM:
|
|
tex->param.grad.paramname = getS0( fp );
|
|
break;
|
|
|
|
case ID_INAM:
|
|
tex->param.grad.itemname = getS0( fp );
|
|
break;
|
|
|
|
case ID_GRST:
|
|
tex->param.grad.start = getF4( fp );
|
|
break;
|
|
|
|
case ID_GREN:
|
|
tex->param.grad.end = getF4( fp );
|
|
break;
|
|
|
|
case ID_GRPT:
|
|
tex->param.grad.repeat = getU2( fp );
|
|
break;
|
|
|
|
case ID_FKEY:
|
|
nkeys = sz / sizeof( lwGradKey );
|
|
tex->param.grad.key = ( lwGradKey* )Mem_ClearedAlloc( nkeys * sizeof( lwGradKey ), TAG_MODEL );
|
|
if( !tex->param.grad.key ) return 0;
|
|
for( i = 0; i < nkeys; i++ )
|
|
{
|
|
tex->param.grad.key[ i ].value = getF4( fp );
|
|
for( j = 0; j < 4; j++ )
|
|
tex->param.grad.key[ i ].rgba[ j ] = getF4( fp );
|
|
}
|
|
break;
|
|
|
|
case ID_IKEY:
|
|
nkeys = sz / 2;
|
|
tex->param.grad.ikey = ( short* )Mem_ClearedAlloc( nkeys * sizeof( short ), TAG_MODEL );
|
|
if( !tex->param.grad.ikey ) return 0;
|
|
for( i = 0; i < nkeys; i++ )
|
|
tex->param.grad.ikey[ i ] = getU2( fp );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading the current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) return 0;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the gradient? */
|
|
|
|
if( rsz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) return 0;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetTexture()
|
|
|
|
Read an lwTexture from a SURF.BLOK in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
lwTexture* lwGetTexture( idFile* fp, int bloksz, unsigned int type )
|
|
{
|
|
lwTexture* tex;
|
|
unsigned short sz;
|
|
int ok;
|
|
|
|
tex = ( lwTexture* )Mem_ClearedAlloc( sizeof( lwTexture ), TAG_MODEL );
|
|
if( !tex ) return NULL;
|
|
|
|
tex->type = type;
|
|
tex->tmap.size.val[ 0 ] =
|
|
tex->tmap.size.val[ 1 ] =
|
|
tex->tmap.size.val[ 2 ] = 1.0f;
|
|
tex->opacity.val = 1.0f;
|
|
tex->enabled = 1;
|
|
|
|
sz = getU2( fp );
|
|
if( !lwGetTHeader( fp, sz, tex ) )
|
|
{
|
|
Mem_Free( tex );
|
|
return NULL;
|
|
}
|
|
|
|
sz = bloksz - sz - 6;
|
|
switch( type )
|
|
{
|
|
case ID_IMAP:
|
|
ok = lwGetImageMap( fp, sz, tex );
|
|
break;
|
|
case ID_PROC:
|
|
ok = lwGetProcedural( fp, sz, tex );
|
|
break;
|
|
case ID_GRAD:
|
|
ok = lwGetGradient( fp, sz, tex );
|
|
break;
|
|
default:
|
|
ok = !fp->Seek( sz, FS_SEEK_CUR );
|
|
}
|
|
|
|
if( !ok )
|
|
{
|
|
lwFreeTexture( tex );
|
|
return NULL;
|
|
}
|
|
|
|
set_flen( bloksz );
|
|
return tex;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetShader()
|
|
|
|
Read a shader record from a SURF.BLOK in an LWO2 file.
|
|
====================================================================== */
|
|
|
|
lwPlugin* lwGetShader( idFile* fp, int bloksz )
|
|
{
|
|
lwPlugin* shdr;
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
int hsz, rlen, pos;
|
|
|
|
shdr = ( lwPlugin* )Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL );
|
|
if( !shdr ) return NULL;
|
|
|
|
pos = fp->Tell();
|
|
set_flen( 0 );
|
|
hsz = getU2( fp );
|
|
shdr->ord = getS0( fp );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
while( hsz > 0 )
|
|
{
|
|
sz += sz & 1;
|
|
hsz -= sz;
|
|
if( id == ID_ENAB )
|
|
{
|
|
shdr->flags = getU2( fp );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
fp->Seek( sz, FS_SEEK_CUR );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
}
|
|
}
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_FUNC:
|
|
shdr->name = getS0( fp );
|
|
rlen = get_flen();
|
|
shdr->data = getbytes( fp, sz - rlen );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading the current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the shader block? */
|
|
|
|
if( bloksz <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
set_flen( fp->Tell() - pos );
|
|
return shdr;
|
|
|
|
Fail:
|
|
lwFreePlugin( shdr );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
compare_textures()
|
|
compare_shaders()
|
|
|
|
Callbacks for the lwListInsert() function, which is called to add
|
|
textures to surface channels and shaders to surfaces.
|
|
====================================================================== */
|
|
|
|
static int compare_textures( lwTexture* a, lwTexture* b )
|
|
{
|
|
return strcmp( a->ord, b->ord );
|
|
}
|
|
|
|
|
|
static int compare_shaders( lwPlugin* a, lwPlugin* b )
|
|
{
|
|
return strcmp( a->ord, b->ord );
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
add_texture()
|
|
|
|
Finds the surface channel (lwTParam or lwCParam) to which a texture is
|
|
applied, then calls lwListInsert().
|
|
====================================================================== */
|
|
|
|
static int add_texture( lwSurface* surf, lwTexture* tex )
|
|
{
|
|
lwTexture** list;
|
|
|
|
switch( tex->chan )
|
|
{
|
|
case ID_COLR:
|
|
list = &surf->color.tex;
|
|
break;
|
|
case ID_LUMI:
|
|
list = &surf->luminosity.tex;
|
|
break;
|
|
case ID_DIFF:
|
|
list = &surf->diffuse.tex;
|
|
break;
|
|
case ID_SPEC:
|
|
list = &surf->specularity.tex;
|
|
break;
|
|
case ID_GLOS:
|
|
list = &surf->glossiness.tex;
|
|
break;
|
|
case ID_REFL:
|
|
list = &surf->reflection.val.tex;
|
|
break;
|
|
case ID_TRAN:
|
|
list = &surf->transparency.val.tex;
|
|
break;
|
|
case ID_RIND:
|
|
list = &surf->eta.tex;
|
|
break;
|
|
case ID_TRNL:
|
|
list = &surf->translucency.tex;
|
|
break;
|
|
case ID_BUMP:
|
|
list = &surf->bump.tex;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
lwListInsert( ( void** )list, tex, ( int ( __cdecl* )( void*, void* ) )compare_textures );
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwDefaultSurface()
|
|
|
|
Allocate and initialize a surface.
|
|
====================================================================== */
|
|
|
|
lwSurface* lwDefaultSurface()
|
|
{
|
|
lwSurface* surf;
|
|
|
|
surf = ( lwSurface* )Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL );
|
|
if( !surf ) return NULL;
|
|
|
|
surf->color.rgb[ 0 ] = 0.78431f;
|
|
surf->color.rgb[ 1 ] = 0.78431f;
|
|
surf->color.rgb[ 2 ] = 0.78431f;
|
|
surf->diffuse.val = 1.0f;
|
|
surf->glossiness.val = 0.4f;
|
|
surf->bump.val = 1.0f;
|
|
surf->eta.val = 1.0f;
|
|
surf->sideflags = 1;
|
|
|
|
return surf;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetSurface()
|
|
|
|
Read an lwSurface from an LWO2 file.
|
|
====================================================================== */
|
|
|
|
lwSurface* lwGetSurface( idFile* fp, int cksize )
|
|
{
|
|
lwSurface* surf;
|
|
lwTexture* tex;
|
|
lwPlugin* shdr;
|
|
unsigned int id, type;
|
|
unsigned short sz;
|
|
int pos, rlen;
|
|
|
|
|
|
/* allocate the Surface structure */
|
|
|
|
surf = ( lwSurface* )Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL );
|
|
if( !surf ) goto Fail;
|
|
|
|
/* non-zero defaults */
|
|
|
|
surf->color.rgb[ 0 ] = 0.78431f;
|
|
surf->color.rgb[ 1 ] = 0.78431f;
|
|
surf->color.rgb[ 2 ] = 0.78431f;
|
|
surf->diffuse.val = 1.0f;
|
|
surf->glossiness.val = 0.4f;
|
|
surf->bump.val = 1.0f;
|
|
surf->eta.val = 1.0f;
|
|
surf->sideflags = 1;
|
|
|
|
/* remember where we started */
|
|
|
|
set_flen( 0 );
|
|
pos = fp->Tell();
|
|
|
|
/* names */
|
|
|
|
surf->name = getS0( fp );
|
|
surf->srcname = getS0( fp );
|
|
|
|
/* first subchunk header */
|
|
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 0 > get_flen() ) goto Fail;
|
|
|
|
/* process subchunks as they're encountered */
|
|
|
|
while( 1 )
|
|
{
|
|
sz += sz & 1;
|
|
set_flen( 0 );
|
|
|
|
switch( id )
|
|
{
|
|
case ID_COLR:
|
|
surf->color.rgb[ 0 ] = getF4( fp );
|
|
surf->color.rgb[ 1 ] = getF4( fp );
|
|
surf->color.rgb[ 2 ] = getF4( fp );
|
|
surf->color.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_LUMI:
|
|
surf->luminosity.val = getF4( fp );
|
|
surf->luminosity.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_DIFF:
|
|
surf->diffuse.val = getF4( fp );
|
|
surf->diffuse.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_SPEC:
|
|
surf->specularity.val = getF4( fp );
|
|
surf->specularity.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_GLOS:
|
|
surf->glossiness.val = getF4( fp );
|
|
surf->glossiness.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_REFL:
|
|
surf->reflection.val.val = getF4( fp );
|
|
surf->reflection.val.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_RFOP:
|
|
surf->reflection.options = getU2( fp );
|
|
break;
|
|
|
|
case ID_RIMG:
|
|
surf->reflection.cindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_RSAN:
|
|
surf->reflection.seam_angle = getF4( fp );
|
|
break;
|
|
|
|
case ID_TRAN:
|
|
surf->transparency.val.val = getF4( fp );
|
|
surf->transparency.val.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_TROP:
|
|
surf->transparency.options = getU2( fp );
|
|
break;
|
|
|
|
case ID_TIMG:
|
|
surf->transparency.cindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_RIND:
|
|
surf->eta.val = getF4( fp );
|
|
surf->eta.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_TRNL:
|
|
surf->translucency.val = getF4( fp );
|
|
surf->translucency.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_BUMP:
|
|
surf->bump.val = getF4( fp );
|
|
surf->bump.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_SMAN:
|
|
surf->smooth = getF4( fp );
|
|
break;
|
|
|
|
case ID_SIDE:
|
|
surf->sideflags = getU2( fp );
|
|
break;
|
|
|
|
case ID_CLRH:
|
|
surf->color_hilite.val = getF4( fp );
|
|
surf->color_hilite.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_CLRF:
|
|
surf->color_filter.val = getF4( fp );
|
|
surf->color_filter.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_ADTR:
|
|
surf->add_trans.val = getF4( fp );
|
|
surf->add_trans.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_SHRP:
|
|
surf->dif_sharp.val = getF4( fp );
|
|
surf->dif_sharp.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_GVAL:
|
|
surf->glow.val = getF4( fp );
|
|
surf->glow.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_LINE:
|
|
surf->line.enabled = 1;
|
|
if( sz >= 2 ) surf->line.flags = getU2( fp );
|
|
if( sz >= 6 ) surf->line.size.val = getF4( fp );
|
|
if( sz >= 8 ) surf->line.size.eindex = getVX( fp );
|
|
break;
|
|
|
|
case ID_ALPH:
|
|
surf->alpha_mode = getU2( fp );
|
|
surf->alpha = getF4( fp );
|
|
break;
|
|
|
|
case ID_AVAL:
|
|
surf->alpha = getF4( fp );
|
|
break;
|
|
|
|
case ID_BLOK:
|
|
type = getU4( fp );
|
|
|
|
switch( type )
|
|
{
|
|
case ID_IMAP:
|
|
case ID_PROC:
|
|
case ID_GRAD:
|
|
tex = lwGetTexture( fp, sz - 4, type );
|
|
if( !tex ) goto Fail;
|
|
if( !add_texture( surf, tex ) )
|
|
lwFreeTexture( tex );
|
|
set_flen( 4 + get_flen() );
|
|
break;
|
|
case ID_SHDR:
|
|
shdr = lwGetShader( fp, sz - 4 );
|
|
if( !shdr ) goto Fail;
|
|
lwListInsert( ( void** )&surf->shader, shdr, ( int ( __cdecl* )( void*, void* ) )compare_shaders );
|
|
++surf->nshaders;
|
|
set_flen( 4 + get_flen() );
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* error while reading current subchunk? */
|
|
|
|
rlen = get_flen();
|
|
if( rlen < 0 || rlen > sz ) goto Fail;
|
|
|
|
/* skip unread parts of the current subchunk */
|
|
|
|
if( rlen < sz )
|
|
fp->Seek( sz - rlen, FS_SEEK_CUR );
|
|
|
|
/* end of the SURF chunk? */
|
|
|
|
if( cksize <= fp->Tell() - pos )
|
|
break;
|
|
|
|
/* get the next subchunk header */
|
|
|
|
set_flen( 0 );
|
|
id = getU4( fp );
|
|
sz = getU2( fp );
|
|
if( 6 != get_flen() ) goto Fail;
|
|
}
|
|
|
|
return surf;
|
|
|
|
Fail:
|
|
if( surf ) lwFreeSurface( surf );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
float dot( float a[], float b[] )
|
|
{
|
|
return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ];
|
|
}
|
|
|
|
|
|
void cross( float a[], float b[], float c[] )
|
|
{
|
|
c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ];
|
|
c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ];
|
|
c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ];
|
|
}
|
|
|
|
|
|
void normalize( float v[] )
|
|
{
|
|
float r;
|
|
|
|
r = ( float ) idMath::Sqrt( dot( v, v ) );
|
|
if( r > 0 )
|
|
{
|
|
v[ 0 ] /= r;
|
|
v[ 1 ] /= r;
|
|
v[ 2 ] /= r;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
lwFreeVMap()
|
|
|
|
Free memory used by an lwVMap.
|
|
====================================================================== */
|
|
|
|
void lwFreeVMap( lwVMap* vmap )
|
|
{
|
|
if( vmap )
|
|
{
|
|
if( vmap->name ) Mem_Free( vmap->name );
|
|
if( vmap->vindex ) Mem_Free( vmap->vindex );
|
|
if( vmap->pindex ) Mem_Free( vmap->pindex );
|
|
if( vmap->val )
|
|
{
|
|
if( vmap->val[ 0 ] ) Mem_Free( vmap->val[ 0 ] );
|
|
Mem_Free( vmap->val );
|
|
}
|
|
Mem_Free( vmap );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetVMap()
|
|
|
|
Read an lwVMap from a VMAP or VMAD chunk in an LWO2.
|
|
====================================================================== */
|
|
|
|
lwVMap* lwGetVMap( idFile* fp, int cksize, int ptoffset, int poloffset,
|
|
int perpoly )
|
|
{
|
|
unsigned char* buf, *bp;
|
|
lwVMap* vmap;
|
|
float* f;
|
|
int i, j, npts, rlen;
|
|
|
|
|
|
/* read the whole chunk */
|
|
|
|
set_flen( 0 );
|
|
buf = ( unsigned char* )getbytes( fp, cksize );
|
|
if( !buf ) return NULL;
|
|
|
|
vmap = ( lwVMap* )Mem_ClearedAlloc( sizeof( lwVMap ), TAG_MODEL );
|
|
if( !vmap )
|
|
{
|
|
Mem_Free( buf );
|
|
return NULL;
|
|
}
|
|
|
|
/* initialize the vmap */
|
|
|
|
vmap->perpoly = perpoly;
|
|
|
|
bp = buf;
|
|
set_flen( 0 );
|
|
vmap->type = sgetU4( &bp );
|
|
vmap->dim = sgetU2( &bp );
|
|
vmap->name = sgetS0( &bp );
|
|
rlen = get_flen();
|
|
|
|
/* count the vmap records */
|
|
|
|
npts = 0;
|
|
while( bp < buf + cksize )
|
|
{
|
|
i = sgetVX( &bp );
|
|
if( perpoly )
|
|
i = sgetVX( &bp );
|
|
bp += vmap->dim * sizeof( float );
|
|
++npts;
|
|
}
|
|
|
|
/* allocate the vmap */
|
|
|
|
vmap->nverts = npts;
|
|
vmap->vindex = ( int* )Mem_ClearedAlloc( npts * sizeof( int ), TAG_MODEL );
|
|
if( !vmap->vindex ) goto Fail;
|
|
if( perpoly )
|
|
{
|
|
vmap->pindex = ( int* )Mem_ClearedAlloc( npts * sizeof( int ), TAG_MODEL );
|
|
if( !vmap->pindex ) goto Fail;
|
|
}
|
|
|
|
if( vmap->dim > 0 )
|
|
{
|
|
vmap->val = ( float** )Mem_ClearedAlloc( npts * sizeof( float* ), TAG_MODEL );
|
|
if( !vmap->val ) goto Fail;
|
|
f = ( float* )Mem_ClearedAlloc( npts * vmap->dim * sizeof( float ), TAG_MODEL );
|
|
if( !f ) goto Fail;
|
|
for( i = 0; i < npts; i++ )
|
|
vmap->val[ i ] = f + i * vmap->dim;
|
|
}
|
|
|
|
/* fill in the vmap values */
|
|
|
|
bp = buf + rlen;
|
|
for( i = 0; i < npts; i++ )
|
|
{
|
|
vmap->vindex[ i ] = sgetVX( &bp );
|
|
if( perpoly )
|
|
vmap->pindex[ i ] = sgetVX( &bp );
|
|
for( j = 0; j < vmap->dim; j++ )
|
|
vmap->val[ i ][ j ] = sgetF4( &bp );
|
|
}
|
|
|
|
Mem_Free( buf );
|
|
return vmap;
|
|
|
|
Fail:
|
|
if( buf ) Mem_Free( buf );
|
|
lwFreeVMap( vmap );
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPointVMaps()
|
|
|
|
Fill in the lwVMapPt structure for each point.
|
|
====================================================================== */
|
|
|
|
int lwGetPointVMaps( lwPointList* point, lwVMap* vmap )
|
|
{
|
|
lwVMap* vm;
|
|
int i, j, n;
|
|
|
|
/* count the number of vmap values for each point */
|
|
|
|
vm = vmap;
|
|
while( vm )
|
|
{
|
|
if( !vm->perpoly )
|
|
for( i = 0; i < vm->nverts; i++ )
|
|
++point->pt[ vm->vindex[ i ]].nvmaps;
|
|
vm = vm->next;
|
|
}
|
|
|
|
/* allocate vmap references for each mapped point */
|
|
|
|
for( i = 0; i < point->count; i++ )
|
|
{
|
|
if( point->pt[ i ].nvmaps )
|
|
{
|
|
point->pt[ i ].vm = ( lwVMapPt* )Mem_ClearedAlloc( point->pt[ i ].nvmaps * sizeof( lwVMapPt ), TAG_MODEL );
|
|
if( !point->pt[ i ].vm ) return 0;
|
|
point->pt[ i ].nvmaps = 0;
|
|
}
|
|
}
|
|
|
|
/* fill in vmap references for each mapped point */
|
|
|
|
vm = vmap;
|
|
while( vm )
|
|
{
|
|
if( !vm->perpoly )
|
|
{
|
|
for( i = 0; i < vm->nverts; i++ )
|
|
{
|
|
j = vm->vindex[ i ];
|
|
n = point->pt[ j ].nvmaps;
|
|
point->pt[ j ].vm[ n ].vmap = vm;
|
|
point->pt[ j ].vm[ n ].index = i;
|
|
++point->pt[ j ].nvmaps;
|
|
}
|
|
}
|
|
vm = vm->next;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
lwGetPolyVMaps()
|
|
|
|
Fill in the lwVMapPt structure for each polygon vertex.
|
|
====================================================================== */
|
|
|
|
int lwGetPolyVMaps( lwPolygonList* polygon, lwVMap* vmap )
|
|
{
|
|
lwVMap* vm;
|
|
lwPolVert* pv;
|
|
int i, j;
|
|
|
|
/* count the number of vmap values for each polygon vertex */
|
|
|
|
vm = vmap;
|
|
while( vm )
|
|
{
|
|
if( vm->perpoly )
|
|
{
|
|
for( i = 0; i < vm->nverts; i++ )
|
|
{
|
|
for( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ )
|
|
{
|
|
pv = &polygon->pol[ vm->pindex[ i ]].v[ j ];
|
|
if( vm->vindex[ i ] == pv->index )
|
|
{
|
|
++pv->nvmaps;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vm = vm->next;
|
|
}
|
|
|
|
/* allocate vmap references for each mapped vertex */
|
|
|
|
for( i = 0; i < polygon->count; i++ )
|
|
{
|
|
for( j = 0; j < polygon->pol[ i ].nverts; j++ )
|
|
{
|
|
pv = &polygon->pol[ i ].v[ j ];
|
|
if( pv->nvmaps )
|
|
{
|
|
pv->vm = ( lwVMapPt* )Mem_ClearedAlloc( pv->nvmaps * sizeof( lwVMapPt ), TAG_MODEL );
|
|
if( !pv->vm ) return 0;
|
|
pv->nvmaps = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* fill in vmap references for each mapped point */
|
|
|
|
vm = vmap;
|
|
while( vm )
|
|
{
|
|
if( vm->perpoly )
|
|
{
|
|
for( i = 0; i < vm->nverts; i++ )
|
|
{
|
|
for( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ )
|
|
{
|
|
pv = &polygon->pol[ vm->pindex[ i ]].v[ j ];
|
|
if( vm->vindex[ i ] == pv->index )
|
|
{
|
|
pv->vm[ pv->nvmaps ].vmap = vm;
|
|
pv->vm[ pv->nvmaps ].index = i;
|
|
++pv->nvmaps;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vm = vm->next;
|
|
}
|
|
|
|
return 1;
|
|
}
|