Animation Envelopes

Availability LightWave 6.0 | Component Layout, Modeler | Header lwenvel.h

A key is a structure that holds the value of an animation parameter at a specific time. An envelope is an array of keys, along with methods for interpolation (tweening) and extrapolation (what happens to the parameter value before the first key and after the last one). The Animation Envelopes global returns functions that allow you to create and manage envelopes and their keys, including a function to display an interface to the user for editing envelopes.

Other global mechanisms are built on top of envelopes. A channel contains the continuous value of a parameter as a function of time, and this is based on both the underlying envelope and on external effects, including plug-ins (channel and item motion classes, for example) that can alter channel values. And the Variant Parameters global defines a data type used by XPanel envelope controls.

See also the Motions section of the Layout commands page, as well as the commands supported by the Graph and Surface Editors.

Global Call

   LWEnvelopeFuncs *envfunc;
   envfunc = global( LWENVELOPEFUNCS_GLOBAL, GFUSE_TRANSIENT );

The global function returns a pointer to an LWEnvelopeFuncs.

   typedef struct st_LWEnvelopeFuncs {
      LWEnvelopeID    (*create)      (LWChanGroupID, const char *, int);
      void            (*destroy)     (LWEnvelopeID);
      LWChanGroupID   (*createGroup) (LWChanGroupID parent, const char *);
      void            (*destroyGroup)(LWChanGroupID);
      LWError         (*copy)        (LWEnvelopeID to, LWEnvelopeID from);
      LWError         (*load)        (LWEnvelopeID, LWLoadState *);
      LWError         (*save)        (LWEnvelopeID, LWSaveState *);
      double          (*evaluate)    (LWEnvelopeID, LWTime);
      int             (*edit)        (LWChanGroupID, LWEnvelopeID,
                                        int flags, void *data);
      int             (*envAge)      (LWEnvelopeID);
      LWEnvKeyframeID (*createKey)   (LWEnvelopeID, LWTime, double value);
      void            (*destroyKey)  (LWEnvelopeID, LWEnvKeyframeID);
      LWEnvKeyframeID (*findKey)     (LWEnvelopeID, LWTime);
      LWEnvKeyframeID (*nextKey)     (LWEnvelopeID, LWEnvKeyframeID);
      LWEnvKeyframeID (*prevKey)     (LWEnvelopeID, LWEnvKeyframeID);
      int             (*keySet)      (LWEnvelopeID, LWEnvKeyframeID,
                                        LWKeyTag, void *value);
      int             (*keyGet)      (LWEnvelopeID, LWEnvKeyframeID,
                                        LWKeyTag, void *value);
      int             (*setEnvEvent) (LWEnvelopeID, LWEnvEventFunc,
                                        void *data); 
      int             (*egSet)       (LWEnvelopeID, LWChanGroupID,
                                        int tag, void *value); 
      int             (*egGet)       (LWEnvelopeID, LWChanGroupID,
                                        int tag, void *value); 
   } LWEnvelopeFuncs;
env = create( group, name, type )
Create a new envelope. The type defines how the envelope's values are interpreted and displayed to the user in the graph editor. It can be one of the following.

LWET_FLOAT
LWET_DISTANCE
LWET_PERCENT
LWET_ANGLE

destroy( env )
Destroy an envelope created using create.
group = createGroup( parent, name )
Create a new envelope group. An envelope group is just a way to organize related envelopes.
destroyGroup( group )
Destroy an envelope group created using createGroup.
error = copy( to, from )
Copy an envelope. This is meant to be called from within a handler's copy callback.
error = load( env, loadstate )
Load an envelope. This is meant to be called from within a handler's load callback.
error = save( env, savestate )
Save an envelope. This is meant to be called from within a handler's save callback.
value = evaluate( env, time )
Returns the interpolated value of the envelope.
result = edit( group, env, flags, data )
Open the graph editor window and allow the user to edit the envelope. The flags and data arguments are currently unused.
age = envAge( env )
Returns an integer containing the number of times the envelope has been changed.
key = createKey( env, time, value )
Create a new key in an envelope.
destroyKey( env, key )
Delete a key in an envelope.
key = findKey( env, time )
Returns the key for a given time.
key = nextKey( env, key )
Returns the next key in the envelope.
key = prevKey( env, key )
Returns the previous key in the envelope.
result = keySet( env, key, tag, value )
Set a value associated with a key. This can be the value of the key itself, the shape of the key, or one of the interpolation parameters. The result is true (non-zero) if the function succeeds and false (0) if it fails. The tag describing the value can be one of the following.
LWKEY_VALUE
The value of the key.
LWKEY_SHAPE
The curve type, an integer corresponding to the options in the graph editor:
0 - TCB (Kochanek-Bartels)
1 - Hermite
2 - 1D Bezier (obsolete, equivalent to Hermit)
3 - Linear
4 - Stepped
5 - 2D Bezier
LWKEY_TENSION
LWKEY_CONTINUITY
LWKEY_BIAS
The Kochanek-Bartels blending parameters.
LWKEY_PARAM_0
LWKEY_PARAM_1
LWKEY_PARAM_2
LWKEY_PARAM_3
The curve parameters. These are the Hermite coefficients for Hermite curves, and the incoming and outgoing tangents for 2D Bezier curves.
result = keyGet( env, key, tag, value )
Get a value associated with a key. The result is true (non-zero) if the function succeeds and false (0) if it fails. The tags are the same as those for keySet, along with LWKEY_TIME, the time of the key.
result = setEnvEvent( env, event_func, data )
Set a callback for an envelope. Whenever the envelope is modified, your event_func function will be called with data as its first argument. Currently the result is false (0) if event_func is NULL and true (non-zero) otherwise.

When you no longer need it, you must unhook your event callback by calling setEnvEvent again with a NULL event_func argument. (But if your callback has already been called for an LWEEVNT_DESTROY event, don't try to unhook it, since at that point the envelope no longer exists.) The data argument should be the same as it was in the original call. This argument is used to uniquely identify the owner of a callback, which is necessary because more than one event callback can be set for a given envelope. For the same reason, data should not be NULL.

result = egSet( env, group, tag, value )
Set a value associated with the envelope. The result is true (non-zero) if the function succeeds and false (0) if it fails.
LWENVTAG_VISIBLE
Invisible envelopes won't appear in the graph editor. You can use these to store internal variables. The value for this tag is an integer containing true (1) or false (0).
LWENVTAG_PREBEHAVE
LWENVTAG_POSTBEHAVE
Pre- and post-behavior setting, an integer corresponding to the options in the graph editor:
0 - Reset
1 - Constant
2 - Repeat
3 - Oscillate
4 - Offset Repeat
5 - Linear
result = egGet( env, group, tag, value )
Get a value associated with an envelope. In addition to the value types defined for egSet, you can use LWENVTAG_KEYCOUNT to get the number of keys defined for the envelope. The result is true (non-zero) if the function succeeds and false (0) if it fails.

Event Callback

The setEnvEvent function lets you set a callback that LightWave will call whenever an envelope is modified. The callback looks like this.

   typedef int (*LWEnvEventFunc) (void *data, LWEnvelopeID env,
      LWEnvEvent event, void *eventData);

data is what you passed as the third argument to the setEnvEvent function. The eventData depends on the event, which can be one of the following.

   LWEEVNT_DESTROY
   LWEEVNT_KEY_INSERT
   LWEEVNT_KEY_DELETE
   LWEEVNT_KEY_VALUE
   LWEEVNT_KEY_TIME

For the KEY events, the eventData is the LWKeyframeID. For the DESTROY event, the eventData is currently undefined and the LWEnvelopeID is invalid. When your callback is called for a DESTROY event, the envelope has already been destroyed, and you should ensure that you invalidate any of your own references to the envelope.

Example

The envelope sample shows how envelopes are interpolated. It also uses the the envelope global functions to create and examine the envelope to be interpolated.

The following code fragment finds a key for the red level of the first light at 5 seconds. If the light doesn't have a color envelope, we add it using the AddEnvelope command, and if there's no key at 5 seconds, we create it. The key value (the red level) is set to 0.75.

In order to do this, we need to find the item ID for the first light, the channel group for that light, the red channel in the channel group, the underlying envelope for the red channel, and the key in that envelope at 5 seconds, if it exists. In addition to the envelope global, we use the channel info, item info and message globals.

   #include <lwserver.h>
   #include <lwenvel.h>
   #include <lwhost.h>

   LWEnvelopeFuncs *envf;
   LWChannelInfo *chinfo;
   LWItemInfo *iteminfo;
   LWMessageFuncs *msgf;
   LWItemID id;
   LWChanGroupID group;
   LWEnvelopeID envred;
   LWEnvKeyframeID key;
   char buf[ 128 ];
   double val;

   chinfo = global( LWCHANNELINFO_GLOBAL, GFUSE_TRANSIENT );
   envf = global( LWENVELOPEFUNCS_GLOBAL, GFUSE_TRANSIENT );
   iteminfo = global( LWITEMINFO_GLOBAL, GFUSE_TRANSIENT );
   msg = global( LWMESSAGEFUNCS_GLOBAL, GFUSE_TRANSIENT );

   if ( !chinfo || !envf || !iteminfo || !msgf )
      return AFUNC_BADGLOBAL;

   id = iteminfo->first( LWI_LIGHT, NULL );
   group = iteminfo->chanGroup( id );

   envred = findEnv( group, "Color.R" );
   if ( !envred ) {
      sprintf( buf, "SelectItem %x", id );
      local->evaluate( local->data, buf );
      local->evaluate( local->data, "AddEnvelope Color.R" );
      envred = findEnv( group, "Color.R" );
   }
   if ( !envred ) {
      msgf->info( "Couldn't create an envelope for",
         iteminfo->name( id ));
      return AFUNC_OK;
   }

   val = 0.75;
   key = envf->findKey( envred, 5.0 );
   if ( !key )
      key = envf->createKey( envred, 5.0, val );
   if ( key )
      envf->keySet( envred, key, LWKEY_VALUE, &val );
   else {
      sprintf( buf, "%s.Color.R", iteminfo->name( id ));
      msg->info( "Couldn't create a key in", buf );
   }

Our findEnv function simply loops through the channels in a channel group searching for a given channel name. If a match is found, it returns the envelope ID for the channel.

   LWEnvelopeID findEnv( LWChanGroupID group, char *name )
   {
      LWChannelID chan;

      chan = chinfo->nextChannel( group, NULL );
      while ( chan ) {
         if ( !strcmp( chinfo->channelName( chan ), name ))
            return chinfo->channelEnvelope( chan );
         chan = chinfo->nextChannel( group, chan );
      }
      return NULL;
   }