mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-29 23:51:49 +00:00
4141 lines
90 KiB
C++
4141 lines
90 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 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 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "framework/FileSystem.h"
|
|
|
|
#include "renderer/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 ) );
|
|
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 ) );
|
|
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;
|
|
lwKey *key = NULL;
|
|
lwPlugin *plug;
|
|
unsigned int id;
|
|
unsigned short sz;
|
|
float f[ 4 ];
|
|
int i, nparams, pos, rlen;
|
|
|
|
|
|
/* allocate the Envelope structure */
|
|
|
|
env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ) );
|
|
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 ) );
|
|
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 ) 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 ) );
|
|
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;
|
|
|
|
|
|
/* if there's no key, the value is 0 */
|
|
|
|
if ( env->nkeys == 0 ) return 0.0f;
|
|
|
|
/* if there's only one key, the value is constant */
|
|
|
|
if ( env->nkeys == 1 )
|
|
return env->key->value;
|
|
|
|
/* find the first and last keys */
|
|
|
|
skey = ekey = env->key;
|
|
while ( ekey->next ) 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:
|
|
out = outgoing( skey, skey->next )
|
|
/ ( skey->next->time - skey->time );
|
|
return out * ( time - skey->time ) + skey->value;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
while ( time > key0->next->time )
|
|
key0 = key0->next;
|
|
key1 = key0->next;
|
|
|
|
/* 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, *tail;
|
|
|
|
head = *(( lwNode ** ) list );
|
|
if ( !head ) {
|
|
*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( void ) { 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 );
|
|
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 ( FLOAT_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 );
|
|
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 )
|
|
{
|
|
int i;
|
|
|
|
if ( flen == FLEN_ERROR ) return 0;
|
|
i = **bp;
|
|
if ( i > 127 ) i -= 256;
|
|
flen += 1;
|
|
*bp += 1;
|
|
return i;
|
|
}
|
|
|
|
|
|
short sgetI2( unsigned char **bp )
|
|
{
|
|
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 )
|
|
{
|
|
int i;
|
|
|
|
if ( flen == FLEN_ERROR ) return 0;
|
|
memcpy( &i, *bp, 4 );
|
|
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 += 1;
|
|
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 ( FLOAT_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 );
|
|
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 ) );
|
|
if ( !object ) goto Fail;
|
|
|
|
layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ) );
|
|
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 ) );
|
|
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 ) );
|
|
if ( !clip ) 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)" ))) {
|
|
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 += 1;
|
|
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;
|
|
lwKey *key0, *key1;
|
|
int i;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ) );
|
|
key0 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ) );
|
|
key1 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ) );
|
|
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 );
|
|
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 );
|
|
}
|
|
|
|
*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 ) );
|
|
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;
|
|
lwTexture *tex = NULL;
|
|
lwPlugin *shdr = NULL;
|
|
char *s;
|
|
float v[ 3 ];
|
|
unsigned int id, flags;
|
|
unsigned short sz;
|
|
int pos, rlen, i;
|
|
|
|
|
|
/* allocate the Surface structure */
|
|
|
|
surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ) );
|
|
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:
|
|
flags = getU2( fp );
|
|
|
|
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:
|
|
for ( i = 0; i < 3; i++ )
|
|
tex->tmap.size.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TCTR:
|
|
for ( i = 0; i < 3; i++ )
|
|
tex->tmap.center.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFAL:
|
|
for ( i = 0; i < 3; i++ )
|
|
tex->tmap.falloff.val[ i ] = getF4( fp );
|
|
break;
|
|
|
|
case ID_TVEL:
|
|
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:
|
|
if ( tex->type == ID_PROC )
|
|
for ( i = 0; i < 3; i++ )
|
|
tex->param.proc.value[ i ] = getU1( fp ) / 255.0f;
|
|
break;
|
|
|
|
case ID_TVAL:
|
|
tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f;
|
|
break;
|
|
|
|
case ID_TAMP:
|
|
if ( tex->type == ID_IMAP )
|
|
tex->param.imap.amplitude.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TIMG:
|
|
s = getS0( fp );
|
|
tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips );
|
|
break;
|
|
|
|
case ID_TAAS:
|
|
tex->param.imap.aa_strength = getF4( fp );
|
|
tex->param.imap.aas_flags = 1;
|
|
break;
|
|
|
|
case ID_TREF:
|
|
tex->tmap.ref_object = (char*)getbytes( fp, sz );
|
|
break;
|
|
|
|
case ID_TOPC:
|
|
tex->opacity.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFP0:
|
|
if ( tex->type == ID_IMAP )
|
|
tex->param.imap.wrapw.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_TFP1:
|
|
if ( tex->type == ID_IMAP )
|
|
tex->param.imap.wraph.val = getF4( fp );
|
|
break;
|
|
|
|
case ID_SHDR:
|
|
shdr = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ) );
|
|
if ( !shdr ) goto Fail;
|
|
shdr->name = (char*)getbytes( fp, sz );
|
|
lwListAdd( (void**)&surf->shader, shdr );
|
|
surf->nshaders++;
|
|
break;
|
|
|
|
case ID_SDAT:
|
|
if ( !shdr ) goto Fail;
|
|
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, nv, nverts, npols;
|
|
ptrdiff_t j;
|
|
|
|
|
|
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 = ( lwSurface * ) 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 ) );
|
|
if ( !object ) goto Fail2;
|
|
|
|
layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ) );
|
|
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 ) );
|
|
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 ) );
|
|
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 ) );
|
|
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 ) );
|
|
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;
|
|
ptrdiff_t index;
|
|
|
|
if ( tlist->count == 0 ) return 1;
|
|
|
|
s = (lwSurface**)Mem_ClearedAlloc( tlist->count * sizeof( lwSurface * ) );
|
|
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;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < polygon->count; i++ ) {
|
|
index = ( ptrdiff_t ) polygon->pol[ i ].surf;
|
|
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 );
|
|
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 = 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->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->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 * ) );
|
|
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;
|
|
ptrdiff_t 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 = ( lwSurface * ) 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 ) );
|
|
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 ) );
|
|
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 ) );
|
|
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 ) );
|
|
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( void )
|
|
{
|
|
lwSurface *surf;
|
|
|
|
surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ) );
|
|
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 ) );
|
|
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 ) );
|
|
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 ) );
|
|
if ( !vmap->vindex ) goto Fail;
|
|
if ( perpoly ) {
|
|
vmap->pindex = (int*)Mem_ClearedAlloc( npts * sizeof( int ) );
|
|
if ( !vmap->pindex ) goto Fail;
|
|
}
|
|
|
|
if ( vmap->dim > 0 ) {
|
|
vmap->val = (float**)Mem_ClearedAlloc( npts * sizeof( float * ) );
|
|
if ( !vmap->val ) goto Fail;
|
|
f = (float*)Mem_ClearedAlloc( npts * vmap->dim * sizeof( float ) );
|
|
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 ) );
|
|
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 ) );
|
|
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;
|
|
}
|