This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/fcurve.c
Joshua Leung 867e129135 F-Modifiers (in Nla branch):
For fun, added 'sinc' (i.e. y = sin(pi*x)/(pi*x)) as a type of builtin function usable through the generator modifier. This makes a nice 'jolt' which tapers off.
2009-06-10 05:03:27 +00:00

2389 lines
64 KiB
C

/* Testing code for new animation system in 2.5
* Copyright 2009, Joshua Leung
*/
#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <float.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "MEM_guardedalloc.h"
#include "DNA_anim_types.h"
#include "BLI_blenlib.h"
#include "BLI_arithb.h"
#include "BLI_noise.h"
#include "BKE_fcurve.h"
#include "BKE_curve.h"
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_utildefines.h"
#include "RNA_access.h"
#include "RNA_types.h"
#ifndef DISABLE_PYTHON
#include "BPY_extern.h" /* for BPY_pydriver_eval() */
#endif
#define SMALL -1.0e-10
#define SELECT 1
/* ************************** Data-Level Functions ************************* */
/* ---------------------- Freeing --------------------------- */
/* Frees the F-Curve itself too, so make sure BLI_remlink is called before calling this... */
void free_fcurve (FCurve *fcu)
{
if (fcu == NULL)
return;
/* free curve data */
if (fcu) {
if (fcu->bezt) MEM_freeN(fcu->bezt);
if (fcu->fpt) MEM_freeN(fcu->fpt);
}
/* free RNA-path, as this were allocated when getting the path string */
if (fcu->rna_path)
MEM_freeN(fcu->rna_path);
/* free extra data - i.e. modifiers, and driver */
fcurve_free_driver(fcu);
fcurve_free_modifiers(fcu);
/* free f-curve itself */
MEM_freeN(fcu);
}
/* Frees a list of F-Curves */
void free_fcurves (ListBase *list)
{
FCurve *fcu, *fcn;
/* sanity check */
if (list == NULL)
return;
/* free data - no need to call remlink before freeing each curve,
* as we store reference to next, and freeing only touches the curve
* it's given
*/
for (fcu= list->first; fcu; fcu= fcn) {
fcn= fcu->next;
free_fcurve(fcu);
}
/* clear pointers just in case */
list->first= list->last= NULL;
}
/* ---------------------- Copy --------------------------- */
/* duplicate an F-Curve */
FCurve *copy_fcurve (FCurve *fcu)
{
FCurve *fcu_d;
/* sanity check */
if (fcu == NULL)
return NULL;
/* make a copy */
fcu_d= MEM_dupallocN(fcu);
fcu_d->next= fcu_d->prev= NULL;
fcu_d->grp= NULL;
/* copy curve data */
fcu_d->bezt= MEM_dupallocN(fcu_d->bezt);
fcu_d->fpt= MEM_dupallocN(fcu_d->fpt);
/* copy rna-path */
fcu_d->rna_path= MEM_dupallocN(fcu_d->rna_path);
/* copy driver */
fcu_d->driver= fcurve_copy_driver(fcu_d->driver);
/* copy modifiers */
fcurve_copy_modifiers(&fcu_d->modifiers, &fcu->modifiers);
/* return new data */
return fcu_d;
}
/* duplicate a list of F-Curves */
void copy_fcurves (ListBase *dst, ListBase *src)
{
FCurve *dfcu, *sfcu;
/* sanity checks */
if ELEM(NULL, dst, src)
return;
/* clear destination list first */
dst->first= dst->last= NULL;
/* copy one-by-one */
for (sfcu= src->first; sfcu; sfcu= sfcu->next) {
dfcu= copy_fcurve(sfcu);
BLI_addtail(dst, dfcu);
}
}
/* ---------------------- Relink --------------------------- */
#if 0
/* uses id->newid to match pointers with other copied data
* - called after single-user or other such
*/
if (icu->driver)
ID_NEW(icu->driver->ob);
#endif
/* --------------------- Finding -------------------------- */
/* Find the F-Curve affecting the given RNA-access path + index, in the list of F-Curves provided */
FCurve *list_find_fcurve (ListBase *list, const char rna_path[], const int array_index)
{
FCurve *fcu;
/* sanity checks */
if ( ELEM(NULL, list, rna_path) || (array_index < 0) )
return NULL;
/* check paths of curves, then array indices... */
for (fcu= list->first; fcu; fcu= fcu->next) {
/* simple string-compare (this assumes that they have the same root...) */
if (fcu->rna_path && !strcmp(fcu->rna_path, rna_path)) {
/* now check indicies */
if (fcu->array_index == array_index)
return fcu;
}
}
/* return */
return NULL;
}
short on_keyframe_fcurve(FCurve *fcu, float cfra)
{
BezTriple *bezt;
unsigned i;
bezt= fcu->bezt;
for (i=0; i<fcu->totvert; i++, bezt++) {
if (IS_EQ(bezt->vec[1][0], cfra))
return 1;
}
return 0;
}
/* Calculate the extents of F-Curve's data */
void calc_fcurve_bounds (FCurve *fcu, float *xmin, float *xmax, float *ymin, float *ymax)
{
float xminv=999999999.0f, xmaxv=-999999999.0f;
float yminv=999999999.0f, ymaxv=-999999999.0f;
short foundvert=0;
unsigned int i;
if (fcu->totvert) {
if (fcu->bezt) {
/* frame range can be directly calculated from end verts */
if (xmin || xmax) {
xminv= MIN2(xminv, fcu->bezt[0].vec[1][0]);
xmaxv= MAX2(xmaxv, fcu->bezt[fcu->totvert-1].vec[1][0]);
}
/* only loop over keyframes to find extents for values if needed */
if (ymin || ymax) {
BezTriple *bezt;
for (bezt=fcu->bezt, i=0; i < fcu->totvert; bezt++, i++) {
if (bezt->vec[1][1] < yminv)
yminv= bezt->vec[1][1];
if (bezt->vec[1][1] > ymaxv)
ymaxv= bezt->vec[1][1];
}
}
}
else if (fcu->fpt) {
/* frame range can be directly calculated from end verts */
if (xmin || xmax) {
xminv= MIN2(xminv, fcu->fpt[0].vec[0]);
xmaxv= MAX2(xmaxv, fcu->fpt[fcu->totvert-1].vec[0]);
}
/* only loop over keyframes to find extents for values if needed */
if (ymin || ymax) {
FPoint *fpt;
for (fpt=fcu->fpt, i=0; i < fcu->totvert; fpt++, i++) {
if (fpt->vec[1] < yminv)
yminv= fpt->vec[1];
if (fpt->vec[1] > ymaxv)
ymaxv= fpt->vec[1];
}
}
}
foundvert=1;
}
/* minimum sizes are 1.0f */
if (foundvert) {
if (xminv == xmaxv) xmaxv += 1.0f;
if (yminv == ymaxv) ymaxv += 1.0f;
if (xmin) *xmin= xminv;
if (xmax) *xmax= xmaxv;
if (ymin) *ymin= yminv;
if (ymax) *ymax= ymaxv;
}
else {
if (xmin) *xmin= 0.0f;
if (xmax) *xmax= 0.0f;
if (ymin) *ymin= 1.0f;
if (ymax) *ymax= 1.0f;
}
}
/* Calculate the extents of F-Curve's keyframes */
void calc_fcurve_range (FCurve *fcu, float *start, float *end)
{
float min=999999999.0f, max=-999999999.0f;
short foundvert=0;
if (fcu->totvert) {
if (fcu->bezt) {
min= MIN2(min, fcu->bezt[0].vec[1][0]);
max= MAX2(max, fcu->bezt[fcu->totvert-1].vec[1][0]);
}
else if (fcu->fpt) {
min= MIN2(min, fcu->fpt[0].vec[0]);
max= MAX2(max, fcu->fpt[fcu->totvert-1].vec[0]);
}
foundvert=1;
}
/* minimum length is 1 frame */
if (foundvert) {
if (min == max) max += 1.0f;
*start= min;
*end= max;
}
else {
*start= 0.0f;
*end= 1.0f;
}
}
/* ***************************** Keyframe Column Tools ********************************* */
/* add a BezTriple to a column */
void bezt_add_to_cfra_elem (ListBase *lb, BezTriple *bezt)
{
CfraElem *ce, *cen;
for (ce= lb->first; ce; ce= ce->next) {
/* double key? */
if (ce->cfra == bezt->vec[1][0]) {
if (bezt->f2 & SELECT) ce->sel= bezt->f2;
return;
}
/* should key be inserted before this column? */
else if (ce->cfra > bezt->vec[1][0]) break;
}
/* create a new column */
cen= MEM_callocN(sizeof(CfraElem), "add_to_cfra_elem");
if (ce) BLI_insertlinkbefore(lb, ce, cen);
else BLI_addtail(lb, cen);
cen->cfra= bezt->vec[1][0];
cen->sel= bezt->f2;
}
/* ***************************** Samples Utilities ******************************* */
/* Some utilities for working with FPoints (i.e. 'sampled' animation curve data, such as
* data imported from BVH/Mocap files), which are specialised for use with high density datasets,
* which BezTriples/Keyframe data are ill equipped to do.
*/
/* Basic sampling callback which acts as a wrapper for evaluate_fcurve()
* 'data' arg here is unneeded here...
*/
float fcurve_samplingcb_evalcurve (FCurve *fcu, void *data, float evaltime)
{
/* assume any interference from drivers on the curve is intended... */
return evaluate_fcurve(fcu, evaltime);
}
/* Main API function for creating a set of sampled curve data, given some callback function
* used to retrieve the values to store.
*/
void fcurve_store_samples (FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb)
{
FPoint *fpt, *new_fpt;
int cfra;
/* sanity checks */
// TODO: make these tests report errors using reports not printf's
if ELEM(NULL, fcu, sample_cb) {
printf("Error: No F-Curve with F-Curve Modifiers to Bake\n");
return;
}
if (start >= end) {
printf("Error: Frame range for Sampled F-Curve creation is inappropriate \n");
return;
}
/* set up sample data */
fpt= new_fpt= MEM_callocN(sizeof(FPoint)*(end-start+1), "FPoint Samples");
/* use the sampling callback at 1-frame intervals from start to end frames */
for (cfra= start; cfra <= end; cfra++, fpt++) {
fpt->vec[0]= (float)cfra;
fpt->vec[1]= sample_cb(fcu, data, (float)cfra);
}
/* free any existing sample/keyframe data on curve */
if (fcu->bezt) MEM_freeN(fcu->bezt);
if (fcu->fpt) MEM_freeN(fcu->fpt);
/* store the samples */
fcu->bezt= NULL;
fcu->fpt= new_fpt;
fcu->totvert= end - start + 1;
}
/* ***************************** F-Curve Sanity ********************************* */
/* The functions here are used in various parts of Blender, usually after some editing
* of keyframe data has occurred. They ensure that keyframe data is properly ordered and
* that the handles are correctly
*/
/* This function recalculates the handles of an F-Curve
* If the BezTriples have been rearranged, sort them first before using this.
*/
void calchandles_fcurve (FCurve *fcu)
{
BezTriple *bezt, *prev, *next;
int a= fcu->totvert;
/* Error checking:
* - need at least two points
* - need bezier keys
* - only bezier-interpolation has handles (for now)
*/
if (ELEM(NULL, fcu, fcu->bezt) || (a < 2) /*|| ELEM(fcu->ipo, BEZT_IPO_CONST, BEZT_IPO_LIN)*/)
return;
/* get initial pointers */
bezt= fcu->bezt;
prev= NULL;
next= (bezt + 1);
/* loop over all beztriples, adjusting handles */
while (a--) {
/* clamp timing of handles to be on either side of beztriple */
if (bezt->vec[0][0] > bezt->vec[1][0]) bezt->vec[0][0]= bezt->vec[1][0];
if (bezt->vec[2][0] < bezt->vec[1][0]) bezt->vec[2][0]= bezt->vec[1][0];
/* calculate auto-handles */
if (fcu->flag & FCURVE_AUTO_HANDLES)
calchandleNurb(bezt, prev, next, 2); /* 2==special autohandle && keep extrema horizontal */
else
calchandleNurb(bezt, prev, next, 1); /* 1==special autohandle */
/* for automatic ease in and out */
if ((bezt->h1==HD_AUTO) && (bezt->h2==HD_AUTO)) {
/* only do this on first or last beztriple */
if ((a == 0) || (a == fcu->totvert-1)) {
/* set both handles to have same horizontal value as keyframe */
if (fcu->extend == FCURVE_EXTRAPOLATE_CONSTANT) {
bezt->vec[0][1]= bezt->vec[2][1]= bezt->vec[1][1];
}
}
}
/* advance pointers for next iteration */
prev= bezt;
if (a == 1) next= NULL;
else next++;
bezt++;
}
}
/* Use when F-Curve with handles has changed
* It treats all BezTriples with the following rules:
* - PHASE 1: do types have to be altered?
* -> Auto handles: become aligned when selection status is NOT(000 || 111)
* -> Vector handles: become 'nothing' when (one half selected AND other not)
* - PHASE 2: recalculate handles
*/
void testhandles_fcurve (FCurve *fcu)
{
BezTriple *bezt;
unsigned int a;
/* only beztriples have handles (bpoints don't though) */
if ELEM(NULL, fcu, fcu->bezt)
return;
/* loop over beztriples */
for (a=0, bezt=fcu->bezt; a < fcu->totvert; a++, bezt++) {
short flag= 0;
/* flag is initialised as selection status
* of beztriple control-points (labelled 0,1,2)
*/
if (bezt->f1 & SELECT) flag |= (1<<0); // == 1
if (bezt->f2 & SELECT) flag |= (1<<1); // == 2
if (bezt->f3 & SELECT) flag |= (1<<2); // == 4
/* one or two handles selected only */
if (ELEM(flag, 0, 7)==0) {
/* auto handles become aligned */
if (bezt->h1==HD_AUTO)
bezt->h1= HD_ALIGN;
if (bezt->h2==HD_AUTO)
bezt->h2= HD_ALIGN;
/* vector handles become 'free' when only one half selected */
if (bezt->h1==HD_VECT) {
/* only left half (1 or 2 or 1+2) */
if (flag < 4)
bezt->h1= 0;
}
if (bezt->h2==HD_VECT) {
/* only right half (4 or 2+4) */
if (flag > 3)
bezt->h2= 0;
}
}
}
/* recalculate handles */
calchandles_fcurve(fcu);
}
/* This function sorts BezTriples so that they are arranged in chronological order,
* as tools working on F-Curves expect that the BezTriples are in order.
*/
void sort_time_fcurve (FCurve *fcu)
{
short ok= 1;
/* keep adjusting order of beztriples until nothing moves (bubble-sort) */
while (ok) {
ok= 0;
/* currently, will only be needed when there are beztriples */
if (fcu->bezt) {
BezTriple *bezt;
unsigned int a;
/* loop over ALL points to adjust position in array and recalculate handles */
for (a=0, bezt=fcu->bezt; a < fcu->totvert; a++, bezt++) {
/* check if thee's a next beztriple which we could try to swap with current */
if (a < (fcu->totvert-1)) {
/* swap if one is after the other (and indicate that order has changed) */
if (bezt->vec[1][0] > (bezt+1)->vec[1][0]) {
SWAP(BezTriple, *bezt, *(bezt+1));
ok= 1;
}
/* if either one of both of the points exceeds crosses over the keyframe time... */
if ( (bezt->vec[0][0] > bezt->vec[1][0]) && (bezt->vec[2][0] < bezt->vec[1][0]) ) {
/* swap handles if they have switched sides for some reason */
SWAP(float, bezt->vec[0][0], bezt->vec[2][0]);
SWAP(float, bezt->vec[0][1], bezt->vec[2][1]);
}
else {
/* clamp handles */
if (bezt->vec[0][0] > bezt->vec[1][0])
bezt->vec[0][0]= bezt->vec[1][0];
if (bezt->vec[2][0] < bezt->vec[1][0])
bezt->vec[2][0]= bezt->vec[1][0];
}
}
}
}
}
}
/* This function tests if any BezTriples are out of order, thus requiring a sort */
short test_time_fcurve (FCurve *fcu)
{
unsigned int a;
/* sanity checks */
if (fcu == NULL)
return 0;
/* currently, only need to test beztriples */
if (fcu->bezt) {
BezTriple *bezt;
/* loop through all BezTriples, stopping when one exceeds the one after it */
for (a=0, bezt= fcu->bezt; a < (fcu->totvert - 1); a++, bezt++) {
if (bezt->vec[1][0] > (bezt+1)->vec[1][0])
return 1;
}
}
else if (fcu->fpt) {
FPoint *fpt;
/* loop through all FPoints, stopping when one exceeds the one after it */
for (a=0, fpt= fcu->fpt; a < (fcu->totvert - 1); a++, fpt++) {
if (fpt->vec[0] > (fpt+1)->vec[0])
return 1;
}
}
/* none need any swapping */
return 0;
}
/* ***************************** Drivers ********************************* */
/* Driver API --------------------------------- */
/* This frees the driver target itself */
void driver_free_target (ChannelDriver *driver, DriverTarget *dtar)
{
/* sanity checks */
if (dtar == NULL)
return;
/* free target vars */
if (dtar->rna_path)
MEM_freeN(dtar->rna_path);
/* remove the target from the driver */
if (driver)
BLI_freelinkN(&driver->targets, dtar);
else
MEM_freeN(dtar);
}
/* Add a new driver target variable */
DriverTarget *driver_add_new_target (ChannelDriver *driver)
{
DriverTarget *dtar;
/* sanity checks */
if (driver == NULL)
return NULL;
/* make a new target */
dtar= MEM_callocN(sizeof(DriverTarget), "DriverTarget");
BLI_addtail(&driver->targets, dtar);
/* give the target a 'unique' name */
strcpy(dtar->name, "var");
BLI_uniquename(&driver->targets, dtar, "var", '_', offsetof(DriverTarget, name), 64);
/* return the target */
return dtar;
}
/* This frees the driver itself */
void fcurve_free_driver(FCurve *fcu)
{
ChannelDriver *driver;
DriverTarget *dtar, *dtarn;
/* sanity checks */
if ELEM(NULL, fcu, fcu->driver)
return;
driver= fcu->driver;
/* free driver targets */
for (dtar= driver->targets.first; dtar; dtar= dtarn) {
dtarn= dtar->next;
driver_free_target(driver, dtar);
}
/* free driver itself, then set F-Curve's point to this to NULL (as the curve may still be used) */
MEM_freeN(driver);
fcu->driver= NULL;
}
/* This makes a copy of the given driver */
ChannelDriver *fcurve_copy_driver (ChannelDriver *driver)
{
ChannelDriver *ndriver;
DriverTarget *dtar;
/* sanity checks */
if (driver == NULL)
return NULL;
/* copy all data */
ndriver= MEM_dupallocN(driver);
/* copy targets */
ndriver->targets.first= ndriver->targets.last= NULL;
BLI_duplicatelist(&ndriver->targets, &driver->targets);
for (dtar= ndriver->targets.first; dtar; dtar= dtar->next) {
/* make a copy of target's rna path if available */
if (dtar->rna_path)
dtar->rna_path = MEM_dupallocN(dtar->rna_path);
}
/* return the new driver */
return ndriver;
}
/* Driver Evaluation -------------------------- */
/* Helper function to obtain a value using RNA from the specified source (for evaluating drivers) */
float driver_get_target_value (ChannelDriver *driver, DriverTarget *dtar)
{
PointerRNA id_ptr, ptr;
PropertyRNA *prop;
ID *id;
char *path;
int index;
float value= 0.0f;
/* sanity check */
if ELEM(NULL, driver, dtar)
return 0.0f;
/* get RNA-pointer for the ID-block given in target */
RNA_id_pointer_create(dtar->id, &id_ptr);
id= dtar->id;
path= dtar->rna_path;
index= dtar->array_index;
/* error check for missing pointer... */
if (id == NULL) {
printf("Error: driver doesn't have any valid target to use \n");
if (G.f & G_DEBUG) printf("\tpath = %s [%d] \n", path, index);
driver->flag |= DRIVER_FLAG_INVALID;
return 0.0f;
}
/* get property to read from, and get value as appropriate */
if (RNA_path_resolve(&id_ptr, path, &ptr, &prop)) {
switch (RNA_property_type(prop)) {
case PROP_BOOLEAN:
if (RNA_property_array_length(prop))
value= (float)RNA_property_boolean_get_index(&ptr, prop, index);
else
value= (float)RNA_property_boolean_get(&ptr, prop);
break;
case PROP_INT:
if (RNA_property_array_length(prop))
value= (float)RNA_property_int_get_index(&ptr, prop, index);
else
value= (float)RNA_property_int_get(&ptr, prop);
break;
case PROP_FLOAT:
if (RNA_property_array_length(prop))
value= RNA_property_float_get_index(&ptr, prop, index);
else
value= RNA_property_float_get(&ptr, prop);
break;
case PROP_ENUM:
value= (float)RNA_property_enum_get(&ptr, prop);
break;
default:
break;
}
}
return value;
}
/* Get two PoseChannels from the targets of the given Driver */
static void driver_get_target_pchans2 (ChannelDriver *driver, bPoseChannel **pchan1, bPoseChannel **pchan2)
{
DriverTarget *dtar;
short i = 0;
/* before doing anything */
*pchan1= NULL;
*pchan2= NULL;
/* only take the first two targets */
for (dtar= driver->targets.first; (dtar) && (i < 2); dtar=dtar->next, i++) {
PointerRNA id_ptr, ptr;
PropertyRNA *prop;
/* get RNA-pointer for the ID-block given in target */
if (dtar->id)
RNA_id_pointer_create(dtar->id, &id_ptr);
else
continue;
/* resolve path so that we have pointer to the right posechannel */
if (RNA_path_resolve(&id_ptr, dtar->rna_path, &ptr, &prop)) {
/* is pointer valid (i.e. pointing to an actual posechannel */
if ((ptr.type == &RNA_PoseChannel) && (ptr.data)) {
/* first or second target? */
if (i)
*pchan1= ptr.data;
else
*pchan2= ptr.data;
}
}
}
}
/* Evaluate an Channel-Driver to get a 'time' value to use instead of "evaltime"
* - "evaltime" is the frame at which F-Curve is being evaluated
* - has to return a float value
*/
static float evaluate_driver (ChannelDriver *driver, float evaltime)
{
DriverTarget *dtar;
/* check if driver can be evaluated */
if (driver->flag & DRIVER_FLAG_INVALID)
return 0.0f;
// TODO: the flags for individual targets need to be used too for more fine-grained support...
switch (driver->type) {
case DRIVER_TYPE_AVERAGE: /* average values of driver targets */
{
/* check how many targets there are first (i.e. just one?) */
if (driver->targets.first == driver->targets.last) {
/* just one target, so just use that */
dtar= driver->targets.first;
return driver_get_target_value(driver, dtar);
}
else {
/* more than one target, so average the values of the targets */
int tot = 0;
float value = 0.0f;
/* loop through targets, adding (hopefully we don't get any overflow!) */
for (dtar= driver->targets.first; dtar; dtar=dtar->next) {
value += driver_get_target_value(driver, dtar);
tot++;
}
/* return the average of these */
return (value / (float)tot);
}
}
break;
case DRIVER_TYPE_PYTHON: /* expression */
{
#ifndef DISABLE_PYTHON
/* check for empty or invalid expression */
if ( (driver->expression[0] == '\0') ||
(driver->flag & DRIVER_FLAG_INVALID) )
{
return 0.0f;
}
/* this evaluates the expression using Python,and returns its result:
* - on errors it reports, then returns 0.0f
*/
return BPY_pydriver_eval(driver);
#endif /* DISABLE_PYTHON*/
}
break;
case DRIVER_TYPE_ROTDIFF: /* difference of rotations of 2 bones (should ideally be in same armature) */
{
bPoseChannel *pchan, *pchan2;
float q1[4], q2[4], quat[4], angle;
/* get pose channels, and check if we've got two */
driver_get_target_pchans2(driver, &pchan, &pchan2);
if (ELEM(NULL, pchan, pchan2)) {
/* disable this driver, since it doesn't work correctly... */
driver->flag |= DRIVER_FLAG_INVALID;
/* check what the error was */
if ((pchan == NULL) && (pchan2 == NULL))
printf("Driver Evaluation Error: Rotational difference failed - first 2 targets invalid \n");
else if (pchan == NULL)
printf("Driver Evaluation Error: Rotational difference failed - first target not valid PoseChannel \n");
else if (pchan2 == NULL)
printf("Driver Evaluation Error: Rotational difference failed - second target not valid PoseChannel \n");
/* stop here... */
return 0.0f;
}
/* use the final posed locations */
Mat4ToQuat(pchan->pose_mat, q1);
Mat4ToQuat(pchan2->pose_mat, q2);
QuatInv(q1);
QuatMul(quat, q1, q2);
angle = 2.0f * (saacos(quat[0]));
angle= ABS(angle);
return (angle > M_PI) ? (float)((2.0f * M_PI) - angle) : (float)(angle);
}
break;
default:
{
/* special 'hack' - just use stored value
* This is currently used as the mechanism which allows animated settings to be able
* to be changed via the UI.
*/
return driver->curval;
}
}
/* return 0.0f, as couldn't find relevant data to use */
return 0.0f;
}
/* ***************************** Curve Calculations ********************************* */
/* The total length of the handles is not allowed to be more
* than the horizontal distance between (v1-v4).
* This is to prevent curve loops.
*/
void correct_bezpart (float *v1, float *v2, float *v3, float *v4)
{
float h1[2], h2[2], len1, len2, len, fac;
/* calculate handle deltas */
h1[0]= v1[0] - v2[0];
h1[1]= v1[1] - v2[1];
h2[0]= v4[0] - v3[0];
h2[1]= v4[1] - v3[1];
/* calculate distances:
* - len = span of time between keyframes
* - len1 = length of handle of start key
* - len2 = length of handle of end key
*/
len= v4[0]- v1[0];
len1= (float)fabs(h1[0]);
len2= (float)fabs(h2[0]);
/* if the handles have no length, no need to do any corrections */
if ((len1+len2) == 0.0f)
return;
/* the two handles cross over each other, so force them
* apart using the proportion they overlap
*/
if ((len1+len2) > len) {
fac= len / (len1+len2);
v2[0]= (v1[0] - fac*h1[0]);
v2[1]= (v1[1] - fac*h1[1]);
v3[0]= (v4[0] - fac*h2[0]);
v3[1]= (v4[1] - fac*h2[1]);
}
}
/* find root ('zero') */
int findzero (float x, float q0, float q1, float q2, float q3, float *o)
{
double c0, c1, c2, c3, a, b, c, p, q, d, t, phi;
int nr= 0;
c0= q0 - x;
c1= 3.0 * (q1 - q0);
c2= 3.0 * (q0 - 2.0*q1 + q2);
c3= q3 - q0 + 3.0 * (q1 - q2);
if (c3 != 0.0) {
a= c2/c3;
b= c1/c3;
c= c0/c3;
a= a/3;
p= b/3 - a*a;
q= (2*a*a*a - a*b + c) / 2;
d= q*q + p*p*p;
if (d > 0.0) {
t= sqrt(d);
o[0]= (float)(Sqrt3d(-q+t) + Sqrt3d(-q-t) - a);
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
else return 0;
}
else if (d == 0.0) {
t= Sqrt3d(-q);
o[0]= (float)(2*t - a);
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
o[nr]= (float)(-t-a);
if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
else return nr;
}
else {
phi= acos(-q / sqrt(-(p*p*p)));
t= sqrt(-p);
p= cos(phi/3);
q= sqrt(3 - 3*p*p);
o[0]= (float)(2*t*p - a);
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
o[nr]= (float)(-t * (p + q) - a);
if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) nr++;
o[nr]= (float)(-t * (p - q) - a);
if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
else return nr;
}
}
else {
a=c2;
b=c1;
c=c0;
if (a != 0.0) {
// discriminant
p= b*b - 4*a*c;
if (p > 0) {
p= sqrt(p);
o[0]= (float)((-b-p) / (2 * a));
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) nr++;
o[nr]= (float)((-b+p)/(2*a));
if ((o[nr] >= SMALL) && (o[nr] <= 1.000001)) return nr+1;
else return nr;
}
else if (p == 0) {
o[0]= (float)(-b / (2 * a));
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
else return 0;
}
}
else if (b != 0.0) {
o[0]= (float)(-c/b);
if ((o[0] >= SMALL) && (o[0] <= 1.000001)) return 1;
else return 0;
}
else if (c == 0.0) {
o[0]= 0.0;
return 1;
}
return 0;
}
}
void berekeny (float f1, float f2, float f3, float f4, float *o, int b)
{
float t, c0, c1, c2, c3;
int a;
c0= f1;
c1= 3.0f * (f2 - f1);
c2= 3.0f * (f1 - 2.0f*f2 + f3);
c3= f4 - f1 + 3.0f * (f2 - f3);
for (a=0; a < b; a++) {
t= o[a];
o[a]= c0 + t*c1 + t*t*c2 + t*t*t*c3;
}
}
void berekenx (float *f, float *o, int b)
{
float t, c0, c1, c2, c3;
int a;
c0= f[0];
c1= 3.0f * (f[3] - f[0]);
c2= 3.0f * (f[0] - 2.0f*f[3] + f[6]);
c3= f[9] - f[0] + 3.0f * (f[3] - f[6]);
for (a=0; a < b; a++) {
t= o[a];
o[a]= c0 + t*c1 + t*t*c2 + t*t*t*c3;
}
}
/* -------------------------- */
/* Calculate F-Curve value for 'evaltime' using BezTriple keyframes */
static float fcurve_eval_keyframes (FCurve *fcu, BezTriple *bezts, float evaltime)
{
BezTriple *bezt, *prevbezt, *lastbezt;
float v1[2], v2[2], v3[2], v4[2], opl[32], dx, fac;
unsigned int a;
int b;
float cvalue = 0.0f;
/* get pointers */
a= fcu->totvert-1;
prevbezt= bezts;
bezt= prevbezt+1;
lastbezt= prevbezt + a;
/* evaluation time at or past endpoints? */
if (prevbezt->vec[1][0] >= evaltime)
{
/* before or on first keyframe */
if ( (fcu->extend == FCURVE_EXTRAPOLATE_LINEAR) && (prevbezt->ipo != BEZT_IPO_CONST) &&
!(fcu->flag & FCURVE_DISCRETE_VALUES) )
{
/* linear or bezier interpolation */
if (prevbezt->ipo==BEZT_IPO_LIN)
{
/* Use the next center point instead of our own handle for
* linear interpolated extrapolate
*/
if (fcu->totvert == 1)
cvalue= prevbezt->vec[1][1];
else
{
bezt = prevbezt+1;
dx= prevbezt->vec[1][0] - evaltime;
fac= bezt->vec[1][0] - prevbezt->vec[1][0];
/* prevent division by zero */
if (fac) {
fac= (bezt->vec[1][1] - prevbezt->vec[1][1]) / fac;
cvalue= prevbezt->vec[1][1] - (fac * dx);
}
else
cvalue= prevbezt->vec[1][1];
}
}
else
{
/* Use the first handle (earlier) of first BezTriple to calculate the
* gradient and thus the value of the curve at evaltime
*/
dx= prevbezt->vec[1][0] - evaltime;
fac= prevbezt->vec[1][0] - prevbezt->vec[0][0];
/* prevent division by zero */
if (fac) {
fac= (prevbezt->vec[1][1] - prevbezt->vec[0][1]) / fac;
cvalue= prevbezt->vec[1][1] - (fac * dx);
}
else
cvalue= prevbezt->vec[1][1];
}
}
else
{
/* constant (BEZT_IPO_HORIZ) extrapolation or constant interpolation,
* so just extend first keyframe's value
*/
cvalue= prevbezt->vec[1][1];
}
}
else if (lastbezt->vec[1][0] <= evaltime)
{
/* after or on last keyframe */
if ( (fcu->extend == FCURVE_EXTRAPOLATE_LINEAR) && (lastbezt->ipo != BEZT_IPO_CONST) &&
!(fcu->flag & FCURVE_DISCRETE_VALUES) )
{
/* linear or bezier interpolation */
if (lastbezt->ipo==BEZT_IPO_LIN)
{
/* Use the next center point instead of our own handle for
* linear interpolated extrapolate
*/
if (fcu->totvert == 1)
cvalue= lastbezt->vec[1][1];
else
{
prevbezt = lastbezt - 1;
dx= evaltime - lastbezt->vec[1][0];
fac= lastbezt->vec[1][0] - prevbezt->vec[1][0];
/* prevent division by zero */
if (fac) {
fac= (lastbezt->vec[1][1] - prevbezt->vec[1][1]) / fac;
cvalue= lastbezt->vec[1][1] + (fac * dx);
}
else
cvalue= lastbezt->vec[1][1];
}
}
else
{
/* Use the gradient of the second handle (later) of last BezTriple to calculate the
* gradient and thus the value of the curve at evaltime
*/
dx= evaltime - lastbezt->vec[1][0];
fac= lastbezt->vec[2][0] - lastbezt->vec[1][0];
/* prevent division by zero */
if (fac) {
fac= (lastbezt->vec[2][1] - lastbezt->vec[1][1]) / fac;
cvalue= lastbezt->vec[1][1] + (fac * dx);
}
else
cvalue= lastbezt->vec[1][1];
}
}
else
{
/* constant (BEZT_IPO_HORIZ) extrapolation or constant interpolation,
* so just extend last keyframe's value
*/
cvalue= lastbezt->vec[1][1];
}
}
else
{
/* evaltime occurs somewhere in the middle of the curve */
for (a=0; prevbezt && bezt && (a < fcu->totvert-1); a++, prevbezt=bezt, bezt++)
{
/* evaltime occurs within the interval defined by these two keyframes */
if ((prevbezt->vec[1][0] <= evaltime) && (bezt->vec[1][0] >= evaltime))
{
/* value depends on interpolation mode */
if ((prevbezt->ipo == BEZT_IPO_CONST) || (fcu->flag & FCURVE_DISCRETE_VALUES))
{
/* constant (evaltime not relevant, so no interpolation needed) */
cvalue= prevbezt->vec[1][1];
}
else if (prevbezt->ipo == BEZT_IPO_LIN)
{
/* linear - interpolate between values of the two keyframes */
fac= bezt->vec[1][0] - prevbezt->vec[1][0];
/* prevent division by zero */
if (fac) {
fac= (evaltime - prevbezt->vec[1][0]) / fac;
cvalue= prevbezt->vec[1][1] + (fac * (bezt->vec[1][1] - prevbezt->vec[1][1]));
}
else
cvalue= prevbezt->vec[1][1];
}
else
{
/* bezier interpolation */
/* v1,v2 are the first keyframe and its 2nd handle */
v1[0]= prevbezt->vec[1][0];
v1[1]= prevbezt->vec[1][1];
v2[0]= prevbezt->vec[2][0];
v2[1]= prevbezt->vec[2][1];
/* v3,v4 are the last keyframe's 1st handle + the last keyframe */
v3[0]= bezt->vec[0][0];
v3[1]= bezt->vec[0][1];
v4[0]= bezt->vec[1][0];
v4[1]= bezt->vec[1][1];
/* adjust handles so that they don't overlap (forming a loop) */
correct_bezpart(v1, v2, v3, v4);
/* try to get a value for this position - if failure, try another set of points */
b= findzero(evaltime, v1[0], v2[0], v3[0], v4[0], opl);
if (b) {
berekeny(v1[1], v2[1], v3[1], v4[1], opl, 1);
cvalue= opl[0];
break;
}
}
}
}
}
/* return value */
return cvalue;
}
/* Calculate F-Curve value for 'evaltime' using FPoint samples */
static float fcurve_eval_samples (FCurve *fcu, FPoint *fpts, float evaltime)
{
FPoint *prevfpt, *lastfpt, *fpt;
float cvalue= 0.0f;
/* get pointers */
prevfpt= fpts;
lastfpt= prevfpt + fcu->totvert-1;
/* evaluation time at or past endpoints? */
if (prevfpt->vec[0] >= evaltime) {
/* before or on first sample, so just extend value */
cvalue= prevfpt->vec[1];
}
else if (lastfpt->vec[0] <= evaltime) {
/* after or on last sample, so just extend value */
cvalue= lastfpt->vec[1];
}
else {
/* find the one on the right frame (assume that these are spaced on 1-frame intervals) */
fpt= prevfpt + (int)(evaltime - prevfpt->vec[0]);
cvalue= fpt->vec[1];
}
/* return value */
return cvalue;
}
/* ******************************** F-Curve Modifiers ********************************* */
/* Template --------------------------- */
/* Each modifier defines a set of functions, which will be called at the appropriate
* times. In addition to this, each modifier should have a type-info struct, where
* its functions are attached for use.
*/
/* Template for type-info data:
* - make a copy of this when creating new modifiers, and just change the functions
* pointed to as necessary
* - although the naming of functions doesn't matter, it would help for code
* readability, to follow the same naming convention as is presented here
* - any functions that a constraint doesn't need to define, don't define
* for such cases, just use NULL
* - these should be defined after all the functions have been defined, so that
* forward-definitions/prototypes don't need to be used!
* - keep this copy #if-def'd so that future constraints can get based off this
*/
#if 0
static FModifierTypeInfo FMI_MODNAME = {
FMODIFIER_TYPE_MODNAME, /* type */
sizeof(FMod_ModName), /* size */
FMI_TYPE_SOME_ACTION, /* action type */
FMI_REQUIRES_SOME_REQUIREMENT, /* requirements */
"Modifier Name", /* name */
"FMod_ModName", /* struct name */
fcm_modname_free, /* free data */
fcm_modname_relink, /* relink data */
fcm_modname_copy, /* copy data */
fcm_modname_new_data, /* new data */
fcm_modname_verify, /* verify */
fcm_modname_time, /* evaluate time */
fcm_modname_evaluate /* evaluate */
};
#endif
/* Generator F-Curve Modifier --------------------------- */
/* Generators available:
* 1) simple polynomial generator:
* - Exanded form - (y = C[0]*(x^(n)) + C[1]*(x^(n-1)) + ... + C[n])
* - Factorised form - (y = (C[0][0]*x + C[0][1]) * (C[1][0]*x + C[1][1]) * ... * (C[n][0]*x + C[n][1]))
* 2) simple builin 'functions':
* of the form (y = C[0] * fn( C[1]*x + C[2] ) + C[3])
* where fn() can be any one of:
* sin, cos, tan, ln, sqrt
* 3) expression...
*/
static void fcm_generator_free (FModifier *fcm)
{
FMod_Generator *data= (FMod_Generator *)fcm->data;
/* free polynomial coefficients array */
if (data->coefficients)
MEM_freeN(data->coefficients);
}
static void fcm_generator_copy (FModifier *fcm, FModifier *src)
{
FMod_Generator *gen= (FMod_Generator *)fcm->data;
FMod_Generator *ogen= (FMod_Generator *)src->data;
/* copy coefficients array? */
if (ogen->coefficients)
gen->coefficients= MEM_dupallocN(ogen->coefficients);
}
static void fcm_generator_new_data (void *mdata)
{
FMod_Generator *data= (FMod_Generator *)mdata;
float *cp;
/* set default generator to be linear 0-1 (gradient = 1, y-offset = 0) */
data->poly_order= 1;
data->arraysize= 2;
cp= data->coefficients= MEM_callocN(sizeof(float)*2, "FMod_Generator_Coefs");
cp[0] = 0; // y-offset
cp[1] = 1; // gradient
}
static void fcm_generator_verify (FModifier *fcm)
{
FMod_Generator *data= (FMod_Generator *)fcm->data;
/* requirements depend on mode */
switch (data->mode) {
case FCM_GENERATOR_POLYNOMIAL: /* expanded polynomial expression */
{
/* arraysize needs to be order+1, so resize if not */
if (data->arraysize != (data->poly_order+1)) {
float *nc;
/* make new coefficients array, and copy over as much data as can fit */
nc= MEM_callocN(sizeof(float)*(data->poly_order+1), "FMod_Generator_Coefs");
if (data->coefficients) {
if (data->arraysize > (data->poly_order+1))
memcpy(nc, data->coefficients, sizeof(float)*(data->poly_order+1));
else
memcpy(nc, data->coefficients, sizeof(float)*data->arraysize);
/* free the old data */
MEM_freeN(data->coefficients);
}
/* set the new data */
data->coefficients= nc;
data->arraysize= data->poly_order+1;
}
}
break;
case FCM_GENERATOR_POLYNOMIAL_FACTORISED: /* expanded polynomial expression */
{
/* arraysize needs to be 2*order, so resize if not */
if (data->arraysize != (data->poly_order * 2)) {
float *nc;
/* make new coefficients array, and copy over as much data as can fit */
nc= MEM_callocN(sizeof(float)*(data->poly_order*2), "FMod_Generator_Coefs");
if (data->coefficients) {
if (data->arraysize > (data->poly_order * 2))
memcpy(nc, data->coefficients, sizeof(float)*(data->poly_order * 2));
else
memcpy(nc, data->coefficients, sizeof(float)*data->arraysize);
/* free the old data */
MEM_freeN(data->coefficients);
}
/* set the new data */
data->coefficients= nc;
data->arraysize= data->poly_order * 2;
}
}
break;
case FCM_GENERATOR_FUNCTION: /* builtin function */
{
/* arraysize needs to be 4*/
if (data->arraysize != 4) {
float *nc;
/* free the old data */
if (data->coefficients)
MEM_freeN(data->coefficients);
/* make new coefficients array, and init using default values */
nc= data->coefficients= MEM_callocN(sizeof(float)*4, "FMod_Generator_Coefs");
data->arraysize= 4;
nc[0]= 1.0f;
nc[1]= 1.0f;
nc[2]= 0.0f;
nc[3]= 0.0f;
}
}
break;
}
}
/* Unary 'normalised sine' function
* y = sin(PI + x) / (PI * x),
* except for x = 0 when y = 1.
*/
static double sinc (double x)
{
if (fabs(x) < 0.0001)
return 1.0;
else
return sin(M_PI * x) / (M_PI * x);
}
static void fcm_generator_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
FMod_Generator *data= (FMod_Generator *)fcm->data;
/* behaviour depends on mode
* NOTE: the data in its default state is fine too
*/
switch (data->mode) {
case FCM_GENERATOR_POLYNOMIAL: /* expanded polynomial expression */
{
/* we overwrite cvalue with the sum of the polynomial */
float *powers = MEM_callocN(sizeof(float)*data->arraysize, "Poly Powers");
float value= 0.0f;
unsigned int i;
/* for each x^n, precalculate value based on previous one first... this should be
* faster that calling pow() for each entry
*/
for (i=0; i < data->arraysize; i++) {
/* first entry is x^0 = 1, otherwise, calculate based on previous */
if (i)
powers[i]= powers[i-1] * evaltime;
else
powers[0]= 1;
}
/* for each coefficient, add to value, which we'll write to *cvalue in one go */
for (i=0; i < data->arraysize; i++)
value += data->coefficients[i] * powers[i];
/* only if something changed, write *cvalue in one go */
if (data->poly_order) {
if (data->flag & FCM_GENERATOR_ADDITIVE)
*cvalue += value;
else
*cvalue= value;
}
/* cleanup */
if (powers)
MEM_freeN(powers);
}
break;
case FCM_GENERATOR_POLYNOMIAL_FACTORISED: /* factorised polynomial */
{
float value= 1.0f, *cp=NULL;
unsigned int i;
/* for each coefficient pair, solve for that bracket before accumulating in value by multiplying */
for (cp=data->coefficients, i=0; (cp) && (i < data->poly_order); cp+=2, i++)
value *= (cp[0]*evaltime + cp[1]);
/* only if something changed, write *cvalue in one go */
if (data->poly_order) {
if (data->flag & FCM_GENERATOR_ADDITIVE)
*cvalue += value;
else
*cvalue= value;
}
}
break;
case FCM_GENERATOR_FUNCTION: /* builtin function */
{
double arg= data->coefficients[1]*evaltime + data->coefficients[2];
double (*fn)(double v) = NULL;
/* get function pointer to the func to use:
* WARNING: must perform special argument validation hereto guard against crashes
*/
switch (data->func_type)
{
/* simple ones */
case FCM_GENERATOR_FN_SIN: /* sine wave */
fn= sin;
break;
case FCM_GENERATOR_FN_COS: /* cosine wave */
fn= cos;
break;
case FCM_GENERATOR_FN_SINC: /* normalised sine wave */
fn= sinc;
break;
/* validation required */
case FCM_GENERATOR_FN_TAN: /* tangent wave */
{
/* check that argument is not on one of the discontinuities (i.e. 90deg, 270 deg, etc) */
if IS_EQ(fmod((arg - M_PI_2), M_PI), 0.0) {
if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0)
*cvalue = 0.0f; /* no value possible here */
}
else
fn= tan;
}
break;
case FCM_GENERATOR_FN_LN: /* natural log */
{
/* check that value is greater than 1? */
if (arg > 1.0f) {
fn= log;
}
else {
if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0)
*cvalue = 0.0f; /* no value possible here */
}
}
break;
case FCM_GENERATOR_FN_SQRT: /* square root */
{
/* no negative numbers */
if (arg > 0.0f) {
fn= sqrt;
}
else {
if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0)
*cvalue = 0.0f; /* no value possible here */
}
}
break;
default:
printf("Invalid Function-Generator for F-Modifier - %d \n", data->func_type);
}
/* execute function callback to set value if appropriate */
if (fn) {
float value= (float)(data->coefficients[0]*fn(arg) + data->coefficients[3]);
if (data->flag & FCM_GENERATOR_ADDITIVE)
*cvalue += value;
else
*cvalue= value;
}
}
break;
#ifndef DISABLE_PYTHON
case FCM_GENERATOR_EXPRESSION: /* py-expression */
// TODO...
break;
#endif /* DISABLE_PYTHON */
}
}
static FModifierTypeInfo FMI_GENERATOR = {
FMODIFIER_TYPE_GENERATOR, /* type */
sizeof(FMod_Generator), /* size */
FMI_TYPE_GENERATE_CURVE, /* action type */
FMI_REQUIRES_NOTHING, /* requirements */
"Generator", /* name */
"FMod_Generator", /* struct name */
fcm_generator_free, /* free data */
fcm_generator_copy, /* copy data */
fcm_generator_new_data, /* new data */
fcm_generator_verify, /* verify */
NULL, /* evaluate time */
fcm_generator_evaluate /* evaluate */
};
/* Envelope F-Curve Modifier --------------------------- */
static void fcm_envelope_free (FModifier *fcm)
{
FMod_Envelope *env= (FMod_Envelope *)fcm->data;
/* free envelope data array */
if (env->data)
MEM_freeN(env->data);
}
static void fcm_envelope_copy (FModifier *fcm, FModifier *src)
{
FMod_Envelope *env= (FMod_Envelope *)fcm->data;
FMod_Envelope *oenv= (FMod_Envelope *)src->data;
/* copy envelope data array */
if (oenv->data)
env->data= MEM_dupallocN(oenv->data);
}
static void fcm_envelope_new_data (void *mdata)
{
FMod_Envelope *env= (FMod_Envelope *)mdata;
/* set default min/max ranges */
env->min= -1.0f;
env->max= 1.0f;
}
static void fcm_envelope_verify (FModifier *fcm)
{
FMod_Envelope *env= (FMod_Envelope *)fcm->data;
/* if the are points, perform bubble-sort on them, as user may have changed the order */
if (env->data) {
// XXX todo...
}
}
static void fcm_envelope_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
FMod_Envelope *env= (FMod_Envelope *)fcm->data;
FCM_EnvelopeData *fed, *prevfed, *lastfed;
float min=0.0f, max=0.0f, fac=0.0f;
int a;
/* get pointers */
if (env->data == NULL) return;
prevfed= env->data;
fed= prevfed + 1;
lastfed= prevfed + (env->totvert-1);
/* get min/max values for envelope at evaluation time (relative to mid-value) */
if (prevfed->time >= evaltime) {
/* before or on first sample, so just extend value */
min= prevfed->min;
max= prevfed->max;
}
else if (lastfed->time <= evaltime) {
/* after or on last sample, so just extend value */
min= lastfed->min;
max= lastfed->max;
}
else {
/* evaltime occurs somewhere between segments */
// TODO: implement binary search for this to make it faster?
for (a=0; prevfed && fed && (a < env->totvert-1); a++, prevfed=fed, fed++) {
/* evaltime occurs within the interval defined by these two envelope points */
if ((prevfed->time <= evaltime) && (fed->time >= evaltime)) {
float afac, bfac, diff;
diff= fed->time - prevfed->time;
afac= (evaltime - prevfed->time) / diff;
bfac= (fed->time - evaltime) / diff;
min= bfac*prevfed->min + afac*fed->min;
max= bfac*prevfed->max + afac*fed->max;
break;
}
}
}
/* adjust *cvalue
* - fac is the ratio of how the current y-value corresponds to the reference range
* - thus, the new value is found by mapping the old range to the new!
*/
fac= (*cvalue - (env->midval + env->min)) / (env->max - env->min);
*cvalue= min + fac*(max - min);
}
static FModifierTypeInfo FMI_ENVELOPE = {
FMODIFIER_TYPE_ENVELOPE, /* type */
sizeof(FMod_Envelope), /* size */
FMI_TYPE_REPLACE_VALUES, /* action type */
0, /* requirements */
"Envelope", /* name */
"FMod_Envelope", /* struct name */
fcm_envelope_free, /* free data */
fcm_envelope_copy, /* copy data */
fcm_envelope_new_data, /* new data */
fcm_envelope_verify, /* verify */
NULL, /* evaluate time */
fcm_envelope_evaluate /* evaluate */
};
/* Cycles F-Curve Modifier --------------------------- */
/* This modifier changes evaltime to something that exists within the curve's frame-range,
* then re-evaluates modifier stack up to this point using the new time. This re-entrant behaviour
* is very likely to be more time-consuming than the original approach... (which was tighly integrated into
* the calculation code...).
*
* NOTE: this needs to be at the start of the stack to be of use, as it needs to know the extents of the keyframes/sample-data
* Possible TODO - store length of cycle information that can be initialised from the extents of the keyframes/sample-data, and adjusted
* as appropriate
*/
/* temp data used during evaluation */
typedef struct tFCMED_Cycles {
float cycyofs; /* y-offset to apply */
} tFCMED_Cycles;
static void fcm_cycles_new_data (void *mdata)
{
FMod_Cycles *data= (FMod_Cycles *)mdata;
/* turn on cycles by default */
data->before_mode= data->after_mode= FCM_EXTRAPOLATE_CYCLIC;
}
static float fcm_cycles_time (FCurve *fcu, FModifier *fcm, float cvalue, float evaltime)
{
FMod_Cycles *data= (FMod_Cycles *)fcm->data;
float prevkey[2], lastkey[2], cycyofs=0.0f;
short side=0, mode=0;
int cycles=0;
/* check if modifier is first in stack, otherwise disable ourself... */
// FIXME...
if (fcm->prev) {
fcm->flag |= FMODIFIER_FLAG_DISABLED;
return evaltime;
}
/* calculate new evaltime due to cyclic interpolation */
if (fcu && fcu->bezt) {
BezTriple *prevbezt= fcu->bezt;
BezTriple *lastbezt= prevbezt + fcu->totvert-1;
prevkey[0]= prevbezt->vec[1][0];
prevkey[1]= prevbezt->vec[1][1];
lastkey[0]= lastbezt->vec[1][0];
lastkey[1]= lastbezt->vec[1][1];
}
else if (fcu && fcu->fpt) {
FPoint *prevfpt= fcu->fpt;
FPoint *lastfpt= prevfpt + fcu->totvert-1;
prevkey[0]= prevfpt->vec[0];
prevkey[1]= prevfpt->vec[1];
lastkey[0]= lastfpt->vec[0];
lastkey[1]= lastfpt->vec[1];
}
else
return evaltime;
/* check if modifier will do anything
* 1) if in data range, definitely don't do anything
* 2) if before first frame or after last frame, make sure some cycling is in use
*/
if (evaltime < prevkey[0]) {
if (data->before_mode) {
side= -1;
mode= data->before_mode;
cycles= data->before_cycles;
}
}
else if (evaltime > lastkey[0]) {
if (data->after_mode) {
side= 1;
mode= data->after_mode;
cycles= data->after_cycles;
}
}
if ELEM(0, side, mode)
return evaltime;
/* find relative place within a cycle */
{
float cycdx=0, cycdy=0, ofs=0;
float cycle= 0;
/* ofs is start frame of cycle */
ofs= prevkey[0];
/* calculate period and amplitude (total height) of a cycle */
cycdx= lastkey[0] - prevkey[0];
cycdy= lastkey[1] - prevkey[1];
/* check if cycle is infinitely small, to be point of being impossible to use */
if (cycdx == 0)
return evaltime;
/* calculate the 'number' of the cycle */
cycle= ((float)side * (evaltime - ofs) / cycdx);
/* check that cyclic is still enabled for the specified time */
if (cycles == 0) {
/* catch this case so that we don't exit when we have cycles=0
* as this indicates infinite cycles...
*/
}
else if (cycle > (cycles+1)) {
/* we are too far away from range to evaluate
* TODO: but we should still hold last value...
*/
return evaltime;
}
/* check if 'cyclic extrapolation', and thus calculate y-offset for this cycle */
if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
cycyofs = (float)floor((evaltime - ofs) / cycdx);
cycyofs *= cycdy;
}
/* calculate where in the cycle we are (overwrite evaltime to reflect this) */
if ((mode == FCM_EXTRAPOLATE_MIRROR) && ((int)(cycle) % 2)) {
/* when 'mirror' option is used and cycle number is odd, this cycle is played in reverse
* - for 'before' extrapolation, we need to flip in a different way, otherwise values past
* then end of the curve get referenced (result of fmod will be negative, and with different phase)
*/
if (side < 0)
evaltime= (float)(prevkey[0] - fmod(evaltime-ofs, cycdx));
else
evaltime= (float)(lastkey[0] - fmod(evaltime-ofs, cycdx));
}
else {
/* the cycle is played normally... */
evaltime= (float)(fmod(evaltime-ofs, cycdx) + ofs);
}
if (evaltime < ofs) evaltime += cycdx;
}
/* store temp data if needed */
if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
tFCMED_Cycles *edata;
/* for now, this is just a float, but we could get more stuff... */
fcm->edata= edata= MEM_callocN(sizeof(tFCMED_Cycles), "tFCMED_Cycles");
edata->cycyofs= cycyofs;
}
/* return the new frame to evaluate */
return evaltime;
}
static void fcm_cycles_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
tFCMED_Cycles *edata= (tFCMED_Cycles *)fcm->edata;
/* use temp data */
if (edata) {
/* add cyclic offset - no need to check for now, otherwise the data wouldn't exist! */
*cvalue += edata->cycyofs;
/* free temp data */
MEM_freeN(edata);
fcm->edata= NULL;
}
}
static FModifierTypeInfo FMI_CYCLES = {
FMODIFIER_TYPE_CYCLES, /* type */
sizeof(FMod_Cycles), /* size */
FMI_TYPE_EXTRAPOLATION, /* action type */
FMI_REQUIRES_ORIGINAL_DATA, /* requirements */
"Cycles", /* name */
"FMod_Cycles", /* struct name */
NULL, /* free data */
NULL, /* copy data */
fcm_cycles_new_data, /* new data */
NULL /*fcm_cycles_verify*/, /* verify */
fcm_cycles_time, /* evaluate time */
fcm_cycles_evaluate /* evaluate */
};
/* Noise F-Curve Modifier --------------------------- */
static void fcm_noise_new_data (void *mdata)
{
FMod_Noise *data= (FMod_Noise *)mdata;
/* defaults */
data->size= 1.0f;
data->strength= 1.0f;
data->phase= 1.0f;
data->depth = 0;
data->modification = FCM_NOISE_MODIF_REPLACE;
}
static void fcm_noise_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
FMod_Noise *data= (FMod_Noise *)fcm->data;
float noise;
noise = BLI_turbulence(data->size, evaltime, data->phase, 0.f, data->depth);
switch (data->modification) {
case FCM_NOISE_MODIF_ADD:
*cvalue= *cvalue + noise * data->strength;
break;
case FCM_NOISE_MODIF_SUBTRACT:
*cvalue= *cvalue - noise * data->strength;
break;
case FCM_NOISE_MODIF_MULTIPLY:
*cvalue= *cvalue * noise * data->strength;
break;
case FCM_NOISE_MODIF_REPLACE:
default:
*cvalue= *cvalue + (noise - 0.5f) * data->strength;
break;
}
}
static FModifierTypeInfo FMI_NOISE = {
FMODIFIER_TYPE_NOISE, /* type */
sizeof(FMod_Noise), /* size */
FMI_TYPE_REPLACE_VALUES, /* action type */
0, /* requirements */
"Noise", /* name */
"FMod_Noise", /* struct name */
NULL, /* free data */
NULL, /* copy data */
fcm_noise_new_data, /* new data */
NULL /*fcm_noise_verify*/, /* verify */
NULL, /* evaluate time */
fcm_noise_evaluate /* evaluate */
};
/* Filter F-Curve Modifier --------------------------- */
#if 0 // XXX not yet implemented
static FModifierTypeInfo FMI_FILTER = {
FMODIFIER_TYPE_FILTER, /* type */
sizeof(FMod_Filter), /* size */
FMI_TYPE_REPLACE_VALUES, /* action type */
0, /* requirements */
"Filter", /* name */
"FMod_Filter", /* struct name */
NULL, /* free data */
NULL, /* copy data */
NULL, /* new data */
NULL /*fcm_filter_verify*/, /* verify */
NULL, /* evlauate time */
fcm_filter_evaluate /* evaluate */
};
#endif // XXX not yet implemented
/* Python F-Curve Modifier --------------------------- */
static void fcm_python_free (FModifier *fcm)
{
FMod_Python *data= (FMod_Python *)fcm->data;
/* id-properties */
IDP_FreeProperty(data->prop);
MEM_freeN(data->prop);
}
static void fcm_python_new_data (void *mdata)
{
FMod_Python *data= (FMod_Python *)mdata;
/* everything should be set correctly by calloc, except for the prop->type constant.*/
data->prop = MEM_callocN(sizeof(IDProperty), "PyFModifierProps");
data->prop->type = IDP_GROUP;
}
static void fcm_python_copy (FModifier *fcm, FModifier *src)
{
FMod_Python *pymod = (FMod_Python *)fcm->data;
FMod_Python *opymod = (FMod_Python *)src->data;
pymod->prop = IDP_CopyProperty(opymod->prop);
}
static void fcm_python_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
#ifndef DISABLE_PYTHON
//FMod_Python *data= (FMod_Python *)fcm->data;
/* FIXME... need to implement this modifier...
* It will need it execute a script using the custom properties
*/
#endif /* DISABLE_PYTHON */
}
static FModifierTypeInfo FMI_PYTHON = {
FMODIFIER_TYPE_PYTHON, /* type */
sizeof(FMod_Python), /* size */
FMI_TYPE_GENERATE_CURVE, /* action type */
FMI_REQUIRES_RUNTIME_CHECK, /* requirements */
"Python", /* name */
"FMod_Python", /* struct name */
fcm_python_free, /* free data */
fcm_python_copy, /* copy data */
fcm_python_new_data, /* new data */
NULL /*fcm_python_verify*/, /* verify */
NULL /*fcm_python_time*/, /* evaluate time */
fcm_python_evaluate /* evaluate */
};
/* Limits F-Curve Modifier --------------------------- */
static float fcm_limits_time (FCurve *fcu, FModifier *fcm, float cvalue, float evaltime)
{
FMod_Limits *data= (FMod_Limits *)fcm->data;
/* check for the time limits */
if ((data->flag & FCM_LIMIT_XMIN) && (evaltime < data->rect.xmin))
return data->rect.xmin;
if ((data->flag & FCM_LIMIT_XMAX) && (evaltime > data->rect.xmax))
return data->rect.xmax;
/* modifier doesn't change time */
return evaltime;
}
static void fcm_limits_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
{
FMod_Limits *data= (FMod_Limits *)fcm->data;
/* value limits now */
if ((data->flag & FCM_LIMIT_YMIN) && (*cvalue < data->rect.ymin))
*cvalue= data->rect.ymin;
if ((data->flag & FCM_LIMIT_YMAX) && (*cvalue > data->rect.ymax))
*cvalue= data->rect.ymax;
}
static FModifierTypeInfo FMI_LIMITS = {
FMODIFIER_TYPE_LIMITS, /* type */
sizeof(FMod_Limits), /* size */
FMI_TYPE_GENERATE_CURVE, /* action type */ /* XXX... err... */
FMI_REQUIRES_RUNTIME_CHECK, /* requirements */
"Limits", /* name */
"FMod_Limits", /* struct name */
NULL, /* free data */
NULL, /* copy data */
NULL, /* new data */
NULL, /* verify */
fcm_limits_time, /* evaluate time */
fcm_limits_evaluate /* evaluate */
};
/* F-Curve Modifier API --------------------------- */
/* All of the F-Curve Modifier api functions use FModifierTypeInfo structs to carry out
* and operations that involve F-Curve modifier specific code.
*/
/* These globals only ever get directly accessed in this file */
static FModifierTypeInfo *fmodifiersTypeInfo[FMODIFIER_NUM_TYPES];
static short FMI_INIT= 1; /* when non-zero, the list needs to be updated */
/* This function only gets called when FMI_INIT is non-zero */
static void fmods_init_typeinfo ()
{
fmodifiersTypeInfo[0]= NULL; /* 'Null' F-Curve Modifier */
fmodifiersTypeInfo[1]= &FMI_GENERATOR; /* Generator F-Curve Modifier */
fmodifiersTypeInfo[2]= &FMI_ENVELOPE; /* Envelope F-Curve Modifier */
fmodifiersTypeInfo[3]= &FMI_CYCLES; /* Cycles F-Curve Modifier */
fmodifiersTypeInfo[4]= &FMI_NOISE; /* Apply-Noise F-Curve Modifier */
fmodifiersTypeInfo[5]= NULL/*&FMI_FILTER*/; /* Filter F-Curve Modifier */ // XXX unimplemented
fmodifiersTypeInfo[6]= &FMI_PYTHON; /* Custom Python F-Curve Modifier */
fmodifiersTypeInfo[7]= &FMI_LIMITS; /* Limits F-Curve Modifier */
}
/* This function should be used for getting the appropriate type-info when only
* a F-Curve modifier type is known
*/
FModifierTypeInfo *get_fmodifier_typeinfo (int type)
{
/* initialise the type-info list? */
if (FMI_INIT) {
fmods_init_typeinfo();
FMI_INIT = 0;
}
/* only return for valid types */
if ( (type >= FMODIFIER_TYPE_NULL) &&
(type <= FMODIFIER_NUM_TYPES ) )
{
/* there shouldn't be any segfaults here... */
return fmodifiersTypeInfo[type];
}
else {
printf("No valid F-Curve Modifier type-info data available. Type = %i \n", type);
}
return NULL;
}
/* This function should always be used to get the appropriate type-info, as it
* has checks which prevent segfaults in some weird cases.
*/
FModifierTypeInfo *fmodifier_get_typeinfo (FModifier *fcm)
{
/* only return typeinfo for valid modifiers */
if (fcm)
return get_fmodifier_typeinfo(fcm->type);
else
return NULL;
}
/* API --------------------------- */
/* Add a new F-Curve Modifier to the given F-Curve of a certain type */
FModifier *fcurve_add_modifier (FCurve *fcu, int type)
{
FModifierTypeInfo *fmi= get_fmodifier_typeinfo(type);
FModifier *fcm;
/* sanity checks */
if ELEM(NULL, fcu, fmi)
return NULL;
/* special checks for whether modifier can be added */
if ((fcu->modifiers.first) && (type == FMODIFIER_TYPE_CYCLES)) {
/* cycles modifier must be first in stack, so for now, don't add if it can't be */
// TODO: perhaps there is some better way, but for now,
printf("Error: Cannot add 'Cycles' modifier to F-Curve, as 'Cycles' modifier can only be first in stack. \n");
return NULL;
}
/* add modifier itself */
fcm= MEM_callocN(sizeof(FModifier), "F-Curve Modifier");
fcm->type = type;
fcm->flag = FMODIFIER_FLAG_EXPANDED;
BLI_addtail(&fcu->modifiers, fcm);
/* add modifier's data */
fcm->data= MEM_callocN(fmi->size, fmi->structName);
/* init custom settings if necessary */
if (fmi->new_data)
fmi->new_data(fcm->data);
/* return modifier for further editing */
return fcm;
}
/* Duplicate all of the F-Curve Modifiers in the Modifier stacks */
void fcurve_copy_modifiers (ListBase *dst, ListBase *src)
{
FModifier *fcm, *srcfcm;
if ELEM(NULL, dst, src)
return;
dst->first= dst->last= NULL;
BLI_duplicatelist(dst, src);
for (fcm=dst->first, srcfcm=src->first; fcm && srcfcm; srcfcm=srcfcm->next, fcm=fcm->next) {
FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm);
/* make a new copy of the F-Modifier's data */
fcm->data = MEM_dupallocN(fcm->data);
/* only do specific constraints if required */
if (fmi && fmi->copy_data)
fmi->copy_data(fcm, srcfcm);
}
}
/* Remove and free the given F-Curve Modifier from the given F-Curve's stack */
void fcurve_remove_modifier (FCurve *fcu, FModifier *fcm)
{
FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm);
/* sanity check */
if (fcm == NULL)
return;
/* free modifier's special data (stored inside fcm->data) */
if (fcm->data) {
if (fmi && fmi->free_data)
fmi->free_data(fcm);
/* free modifier's data (fcm->data) */
MEM_freeN(fcm->data);
}
/* remove modifier from stack */
if (fcu)
BLI_freelinkN(&fcu->modifiers, fcm);
else {
// XXX this case can probably be removed some day, as it shouldn't happen...
printf("fcurve_remove_modifier() - no fcurve \n");
MEM_freeN(fcm);
}
}
/* Remove all of a given F-Curve's modifiers */
void fcurve_free_modifiers (FCurve *fcu)
{
FModifier *fcm, *fmn;
/* sanity check */
if (fcu == NULL)
return;
/* free each modifier in order - modifier is unlinked from list and freed */
for (fcm= fcu->modifiers.first; fcm; fcm= fmn) {
fmn= fcm->next;
fcurve_remove_modifier(fcu, fcm);
}
}
/* Find the active F-Curve Modifier */
FModifier *fcurve_find_active_modifier (FCurve *fcu)
{
FModifier *fcm;
/* sanity checks */
if ELEM(NULL, fcu, fcu->modifiers.first)
return NULL;
/* loop over modifiers until 'active' one is found */
for (fcm= fcu->modifiers.first; fcm; fcm= fcm->next) {
if (fcm->flag & FMODIFIER_FLAG_ACTIVE)
return fcm;
}
/* no modifier is active */
return NULL;
}
/* Set the active F-Curve Modifier */
void fcurve_set_active_modifier (FCurve *fcu, FModifier *fcm)
{
FModifier *fm;
/* sanity checks */
if ELEM(NULL, fcu, fcu->modifiers.first)
return;
/* deactivate all, and set current one active */
for (fm= fcu->modifiers.first; fm; fm= fm->next)
fm->flag &= ~FMODIFIER_FLAG_ACTIVE;
/* make given modifier active */
if (fcm)
fcm->flag |= FMODIFIER_FLAG_ACTIVE;
}
/* Evaluation API --------------------------- */
/* evaluate time modifications imposed by some F-Curve Modifiers
* - this step acts as an optimisation to prevent the F-Curve stack being evaluated
* several times by modifiers requesting the time be modified, as the final result
* would have required using the modified time
* - modifiers only ever recieve the unmodified time, as subsequent modifiers should be
* working on the 'global' result of the modified curve, not some localised segment,
* so nevaltime gets set to whatever the last time-modifying modifier likes...
* - we start from the end of the stack, as only the last one matters for now
*/
float evaluate_time_fmodifiers (ListBase *modifiers, FCurve *fcu, float cvalue, float evaltime)
{
FModifier *fcm;
float m_evaltime= evaltime;
/* sanity checks */
if ELEM(NULL, modifiers, modifiers->first)
return evaltime;
/* find the first modifier from end of stack that modifies time, and calculate the time the modifier
* would calculate time at
*/
for (fcm= fcu->modifiers.last; fcm; fcm= fcm->prev) {
FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm);
/* only evaluate if there's a callback for this */
// TODO: implement the 'influence' control feature...
if (fmi && fmi->evaluate_modifier_time) {
if ((fcm->flag & (FMODIFIER_FLAG_DISABLED|FMODIFIER_FLAG_MUTED)) == 0)
m_evaltime= fmi->evaluate_modifier_time(fcu, fcm, cvalue, evaltime);
break;
}
}
/* return the modified evaltime */
return m_evaltime;
}
/* Evalautes the given set of F-Curve Modifiers using the given data
* Should only be called after evaluate_time_fmodifiers() has been called...
*/
void evaluate_value_fmodifiers (ListBase *modifiers, FCurve *fcu, float *cvalue, float evaltime)
{
FModifier *fcm;
/* sanity checks */
if ELEM(NULL, modifiers, modifiers->first)
return;
/* evaluate modifiers */
for (fcm= modifiers->first; fcm; fcm= fcm->next) {
FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm);
/* only evaluate if there's a callback for this */
// TODO: implement the 'influence' control feature...
if (fmi && fmi->evaluate_modifier) {
if ((fcm->flag & (FMODIFIER_FLAG_DISABLED|FMODIFIER_FLAG_MUTED)) == 0)
fmi->evaluate_modifier(fcu, fcm, cvalue, evaltime);
}
}
}
/* Bake modifiers for given F-Curve to curve sample data, in the frame range defined
* by start and end (inclusive).
*/
void fcurve_bake_modifiers (FCurve *fcu, int start, int end)
{
ChannelDriver *driver;
/* sanity checks */
// TODO: make these tests report errors using reports not printf's
if ELEM(NULL, fcu, fcu->modifiers.first) {
printf("Error: No F-Curve with F-Curve Modifiers to Bake\n");
return;
}
/* temporarily, disable driver while we sample, so that they don't influence the outcome */
driver= fcu->driver;
fcu->driver= NULL;
/* bake the modifiers, by sampling the curve at each frame */
fcurve_store_samples(fcu, NULL, start, end, fcurve_samplingcb_evalcurve);
/* free the modifiers now */
fcurve_free_modifiers(fcu);
/* restore driver */
fcu->driver= driver;
}
/* ***************************** F-Curve - Evaluation ********************************* */
/* Evaluate and return the value of the given F-Curve at the specified frame ("evaltime")
* Note: this is also used for drivers
*/
float evaluate_fcurve (FCurve *fcu, float evaltime)
{
float cvalue= 0.0f;
float devaltime;
/* if there is a driver (only if this F-Curve is acting as 'driver'), evaluate it to find value to use as "evaltime"
* since drivers essentially act as alternative input (i.e. in place of 'time') for F-Curves
* - this value will also be returned as the value of the 'curve', if there are no keyframes
*/
if (fcu->driver) {
/* evaltime now serves as input for the curve */
evaltime= cvalue= evaluate_driver(fcu->driver, evaltime);
}
/* evaluate modifiers which modify time to evaluate the base curve at */
devaltime= evaluate_time_fmodifiers(&fcu->modifiers, fcu, cvalue, evaltime);
/* evaluate curve-data
* - 'devaltime' instead of 'evaltime', as this is the time that the last time-modifying
* F-Curve modifier on the stack requested the curve to be evaluated at
*/
if (fcu->bezt)
cvalue= fcurve_eval_keyframes(fcu, fcu->bezt, devaltime);
else if (fcu->fpt)
cvalue= fcurve_eval_samples(fcu, fcu->fpt, devaltime);
/* evaluate modifiers */
evaluate_value_fmodifiers(&fcu->modifiers, fcu, &cvalue, evaltime);
/* if curve can only have integral values, perform truncation (i.e. drop the decimal part)
* here so that the curve can be sampled correctly
*/
if (fcu->flag & FCURVE_INT_VALUES)
cvalue= (float)((int)cvalue);
/* return evaluated value */
return cvalue;
}
/* Calculate the value of the given F-Curve at the given frame, and set its curval */
// TODO: will this be necessary?
void calculate_fcurve (FCurve *fcu, float ctime)
{
/* calculate and set curval (evaluates driver too) */
fcu->curval= evaluate_fcurve(fcu, ctime);
}