worldspawn/libs/picomodel/envelope.c

642 lines
13 KiB
C

/*
======================================================================
envelope.c
Envelope functions for an LWO2 reader.
Ernie Wright 16 Nov 00
====================================================================== */
#include "picointernal.h"
#include "lwo2.h"
/*
======================================================================
lwFreeEnvelope()
Free the memory used by an lwEnvelope.
====================================================================== */
void lwFreeEnvelope( lwEnvelope *env ){
if ( env ) {
if ( env->name ) {
_pico_free( env->name );
}
lwListFree( env->key, _pico_free );
lwListFree(env->cfilter, (void (*)(void *)) lwFreePlugin);
_pico_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( picoMemStream_t *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 = _pico_calloc( 1, sizeof( lwEnvelope ) );
if ( !env ) {
goto Fail;
}
/* remember where we started */
set_flen( 0 );
pos = _pico_memstream_tell( fp );
/* 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 = _pico_calloc( 1, sizeof( lwKey ) );
if ( !key ) {
goto Fail;
}
key->time = getF4( fp );
key->value = getF4( fp );
lwListInsert((void **) &env->key, key, (int (*)(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 = _pico_calloc( 1, 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 ) {
_pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
}
/* end of the ENVL chunk? */
rlen = _pico_memstream_tell( fp ) - 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 ( 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 ( 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;
return in;
case ID_BEZ2:
in = key1->param[ 1 ] * ( key1->time - key0->time );
if ( 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;
}
}