Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Leung <aligorith@gmail.com>2009-01-17 06:12:50 +0300
committerJoshua Leung <aligorith@gmail.com>2009-01-17 06:12:50 +0300
commit44e5b7788bcbe55400bf0d91eed0752ed865b644 (patch)
tree7b69e89ff7f93127d5691f528c5dc9bb85835ee1 /source/blender/blenkernel/intern/fcurve.c
parent9bcdb4b758d96d1fe5345858f6c61a90f7c57eac (diff)
2.5: Blender "Animato" - New Animation System
Finally, here is the basic (functional) prototype of the new animation system which will allow for the infamous "everything is animatable", and which also addresses several of the more serious shortcomings of the old system. Unfortunately, this will break old animation files (especially right now, as I haven't written the version patching code yet), however, this is for the future. Highlights of the new system: * Scrapped IPO-Curves/IPO/(Action+Constraint-Channels)/Action system, and replaced it with F-Curve/Action. - F-Curves (animators from other packages will feel at home with this name) replace IPO-Curves. - The 'new' Actions, act as the containers for F-Curves, so that they can be reused. They are therefore more akin to the old 'IPO' blocks, except they do not have the blocktype restriction, so you can store materials/texture/geometry F-Curves in the same Action as Object transforms, etc. * F-Curves use RNA-paths for Data Access, hence allowing "every" (where sensible/editable that is) user-accessible setting from RNA to be animated. * Drivers are no longer mixed with Animation Data, so rigs will not be that easily broken and several dependency problems can be eliminated. (NOTE: drivers haven't been hooked up yet, but the code is in place) * F-Curve modifier system allows useful 'large-scale' manipulation of F-Curve values, including (I've only included implemented ones here): envelope deform (similar to lattices to allow broad-scale reshaping of curves), curve generator (polynomial or py-expression), cycles (replacing the old cyclic extrapolation modes, giving more control over this). (NOTE: currently this cannot be tested, as there's not access to them, but the code is all in place) * NLA system with 'tracks' (i.e. layers), and multiple strips per track. (NOTE: NLA system is not yet functional, as it's only partially coded still) There are more nice things that I will be preparing some nice docs for soon, but for now, check for more details: http://lists.blender.org/pipermail/bf-taskforce25/2009-January/000260.html So, what currently works: * I've implemented two basic operators for the 3D-view only to Insert and Delete Keyframes. These are tempolary ones only that will be replaced in due course with 'proper' code. * Object Loc/Rot/Scale can be keyframed. Also, the colour of the 'active' material (Note: this should really be for nth material instead, but that doesn't work yet in RNA) can also be keyframed into the same datablock. * Standard animation refresh (i.e. animation resulting from NLA and Action evaluation) is now done completely separate from drivers before anything else is done after a frame change. Drivers are handled after this in a separate pass, as dictated by depsgraph flags, etc. Notes: * Drivers haven't been hooked up yet * Only objects and data directly linked to objects can be animated. * Depsgraph will need further tweaks. Currently, I've only made sure that it will update some things in the most basic cases (i.e. frame change). * Animation Editors are currently broken (in terms of editing stuff). This will be my next target (priority to get Dopesheet working first, then F-Curve editor - i.e. old IPO Editor) * I've had to put in large chunks of XXX sandboxing for old animation system code all around the place. This will be cleaned up in due course, as some places need special review. In particular, the particles and sequencer code have far too many manual calls to calculate + flush animation info, which is really bad (this is a 'please explain yourselves' call to Physics coders!).
Diffstat (limited to 'source/blender/blenkernel/intern/fcurve.c')
-rw-r--r--source/blender/blenkernel/intern/fcurve.c1492
1 files changed, 1492 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c
new file mode 100644
index 00000000000..0e94faf0c4d
--- /dev/null
+++ b/source/blender/blenkernel/intern/fcurve.c
@@ -0,0 +1,1492 @@
+/* Testing code for new animation system in 2.5
+ * Copyright 2009, Joshua Leung
+ */
+
+
+#include <math.h>
+#include <stdio.h>
+#include <string.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 "BKE_fcurve.h"
+#include "BKE_curve.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 */
+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-cruve itself */
+ MEM_freeN(fcu);
+}
+
+/* ---------------------- 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);
+
+ /* 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 */
+ //fcurve_copy_driver();
+
+ /* copy modifiers */
+ fcurve_copy_modifiers(&fcu_d->modifiers, &fcu->modifiers);
+
+ /* return new data */
+ return fcu_d;
+}
+
+/* ---------------------- 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 (strcmp(fcu->rna_path, rna_path) == 0) {
+ /* now check indicies */
+ if (fcu->array_index == array_index)
+ return fcu;
+ }
+ }
+
+ /* return */
+ return NULL;
+}
+
+/* ***************************** 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;
+}
+
+/* ***************************** 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;
+ 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;
+ 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)
+{
+ 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 itself */
+void fcurve_free_driver(FCurve *fcu)
+{
+ ChannelDriver *driver;
+
+ /* sanity checks */
+ if ELEM(NULL, fcu, fcu->driver)
+ return;
+ driver= fcu->driver;
+
+ /* free RNA-paths, as these were allocated when getting the path string */
+ if (driver->rna_path) MEM_freeN(driver->rna_path);
+ if (driver->rna_path2) MEM_freeN(driver->rna_path2);
+
+ /* 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;
+}
+
+/* Driver Evaluation -------------------------- */
+
+/* Helper function to obtain a value using RNA from the specified source (for evaluating drivers)
+ * - target: used to specify which of the two driver-targets to use
+ */
+static float driver_get_driver_value (ChannelDriver *driver, short target)
+{
+ PointerRNA id_ptr, ptr;
+ PropertyRNA *prop;
+ char *path;
+ int index;
+ float value= 0.0f;
+
+ /* get RNA-pointer for the ID-block given in driver */
+ if (target == 2) {
+ /* second target */
+ RNA_id_pointer_create(driver->id2, &id_ptr);
+ path= driver->rna_path2;
+ index= driver->array_index2;
+ }
+ else {
+ /* first/main target */
+ RNA_id_pointer_create(driver->id, &id_ptr);
+ path= driver->rna_path;
+ index= driver->array_index;
+ }
+
+ /* get property to read from, and get value as appropriate */
+ if (RNA_path_resolve(&id_ptr, path, &ptr, &prop)) {
+ switch (RNA_property_type(&ptr, prop)) {
+ case PROP_BOOLEAN:
+ if (RNA_property_array_length(&ptr, prop))
+ value= (float)RNA_property_boolean_get_array(&ptr, prop, index);
+ else
+ value= (float)RNA_property_boolean_get(&ptr, prop);
+ break;
+ case PROP_INT:
+ if (RNA_property_array_length(&ptr, prop))
+ value= (float)RNA_property_int_get_array(&ptr, prop, index);
+ else
+ value= (float)RNA_property_int_get(&ptr, prop);
+ break;
+ case PROP_FLOAT:
+ if (RNA_property_array_length(&ptr, prop))
+ value= RNA_property_float_get_array(&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;
+}
+
+/* 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)
+{
+ /* check if driver can be evaluated */
+ if (driver->flag & DRIVER_FLAG_DISABLED)
+ return 0.0f;
+
+ switch (driver->type) {
+ case DRIVER_TYPE_CHANNEL: /* channel/setting drivers channel/setting */
+ return driver_get_driver_value(driver, 0);
+
+#ifndef DISABLE_PYTHON
+ case DRIVER_TYPE_PYTHON: /* expression */
+ {
+ /* 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); // XXX old func
+ return 1.0f;
+ }
+#endif /* DISABLE_PYTHON*/
+
+ case DRIVER_TYPE_ROTDIFF: /* difference of rotations of 2 bones (should be in same armature) */
+ {
+ /*
+ float q1[4], q2[4], quat[4], angle;
+
+ 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;
+ int a, 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)) {
+ /* 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)) {
+ /* 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) {
+ /* 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 */
+ "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_evaluate /* evaluate */
+};
+#endif
+
+/* Generator F-Curve Modifier --------------------------- */
+
+static void fcm_generator_free (FModifier *fcm)
+{
+ FMod_Generator *data= (FMod_Generator *)fcm->data;
+
+ /* free polynomial coefficients array */
+ if (data->poly_coefficients)
+ MEM_freeN(data->poly_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 polynomial coefficients array? */
+ if (ogen->poly_coefficients)
+ gen->poly_coefficients= MEM_dupallocN(ogen->poly_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;
+ cp= data->poly_coefficients= MEM_callocN(sizeof(float)*2, "FMod_Generator_Coefs");
+ cp[0] = 0; // y-offset
+ cp[1] = 1; // gradient
+}
+
+
+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: we don't need to do anything...) */
+ switch (data->mode) {
+ case FCM_GENERATOR_POLYNOMIAL: /* polynomial expression */
+ {
+ /* we overwrite cvalue with the sum of the polynomial */
+ float value= 0.0f, *cp = NULL;
+ unsigned int i;
+
+ /* for each coefficient, add to value, which we'll write to *cvalue in one go */
+ // TODO: could this be more efficient (i.e. without need to recalc pow() everytime)
+ cp= data->poly_coefficients;
+ for (i=0; (i <= data->poly_order) && (cp); i++, cp++)
+ value += (*cp) * pow(evaltime, i);
+
+ /* only if something changed */
+ if (data->poly_order)
+ *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 */
+ "Generator", /* name */
+ "FMod_Generator", /* struct name */
+ fcm_generator_free, /* free data */
+ fcm_generator_copy, /* copy data */
+ fcm_generator_new_data, /* new data */
+ fcm_generator_evaluate /* evaluate */
+};
+
+/* Envelope F-Curve Modifier --------------------------- */
+
+static void fcm_envelope_free (FModifier *fcm)
+{
+ FMod_Envelope *data= (FMod_Envelope *)fcm->data;
+
+ /* free envelope data array */
+ if (data->data)
+ MEM_freeN(data->data);
+}
+
+static void fcm_envelope_copy (FModifier *fcm, FModifier *src)
+{
+ FMod_Envelope *gen= (FMod_Envelope *)fcm->data;
+ FMod_Envelope *ogen= (FMod_Envelope *)src->data;
+
+ /* copy envelope data array */
+ if (ogen->data)
+ gen->data= MEM_dupallocN(ogen->data);
+}
+
+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 */
+ 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= afac*prevfed->min + bfac*fed->min;
+ max= afac*prevfed->max + bfac*fed->max;
+
+ break;
+ }
+ }
+ }
+
+ /* adjust *cvalue
+ * NOTE: env->min/max are relative to env->midval, and can be either +ve OR -ve, so we add...
+ */
+ fac= (*cvalue - min) / (max - min);
+ *cvalue= (env->midval + env->min) + (fac * (env->max - env->min));
+}
+
+static FModifierTypeInfo FMI_ENVELOPE = {
+ FMODIFIER_TYPE_ENVELOPE, /* type */
+ sizeof(FMod_Envelope), /* size */
+ "Envelope", /* name */
+ "FMod_Envelope", /* struct name */
+ fcm_envelope_free, /* free data */
+ fcm_envelope_copy, /* copy data */
+ NULL, /* new data */
+ 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
+ */
+
+static void fcm_cycles_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime)
+{
+ FMod_Cycles *data= (FMod_Cycles *)fcm->data;
+ ListBase mods = {NULL, NULL};
+ float prevkey[2], lastkey[2], cycyofs=0.0f;
+ float new_value;
+ 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;
+ }
+
+ /* 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;
+
+ /* 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 ELEM3(0, side, mode, cycles)
+ return;
+
+ /* extrapolation mode is 'cyclic' - find relative place within a cycle */
+ // FIXME: adding the more fine-grained control of extrpolation mode
+ {
+ float cycdx=0, cycdy=0, ofs=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;
+ /* check that cyclic is still enabled for the specified time */
+ if ( ((float)side * (evaltime - ofs) / cycdx) > cycles )
+ return;
+
+
+ /* 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) */
+ evaltime= (float)(fmod(evaltime-ofs, cycdx) + ofs);
+ if (evaltime < ofs) evaltime += cycdx;
+ }
+
+
+ /* store modifiers after (and including ourself) before recalculating curve with new evaltime */
+ mods= fcu->modifiers;
+ fcu->modifiers.first= fcu->modifiers.last= NULL;
+
+ /* re-enter the evaluation loop (but without the burden of evaluating any modifiers, so 'should' be relatively quick) */
+ new_value= evaluate_fcurve(fcu, evaltime);
+
+ /* restore modifiers, and set new value (don't assume everything is still ok after being re-entrant) */
+ fcu->modifiers= mods;
+ *cvalue= new_value + cycyofs;
+}
+
+static FModifierTypeInfo FMI_CYCLES = {
+ FMODIFIER_TYPE_CYCLES, /* type */
+ sizeof(FMod_Cycles), /* size */
+ "Cycles", /* name */
+ "FMod_Cycles", /* struct name */
+ NULL, /* free data */
+ NULL, /* copy data */
+ NULL, /* new data */
+ fcm_cycles_evaluate /* evaluate */
+};
+
+/* Noise F-Curve Modifier --------------------------- */
+
+#if 0 // XXX not yet implemented
+static FModifierTypeInfo FMI_NOISE = {
+ FMODIFIER_TYPE_NOISE, /* type */
+ sizeof(FMod_Noise), /* size */
+ "Noise", /* name */
+ "FMod_Noise", /* struct name */
+ NULL, /* free data */
+ NULL, /* copy data */
+ fcm_noise_new_data, /* new data */
+ fcm_noise_evaluate /* evaluate */
+};
+#endif // XXX not yet implemented
+
+/* Filter F-Curve Modifier --------------------------- */
+
+#if 0 // XXX not yet implemented
+static FModifierTypeInfo FMI_FILTER = {
+ FMODIFIER_TYPE_FILTER, /* type */
+ sizeof(FMod_Filter), /* size */
+ "Filter", /* name */
+ "FMod_Filter", /* struct name */
+ NULL, /* free data */
+ NULL, /* copy data */
+ NULL, /* new data */
+ 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 */
+ "Python", /* name */
+ "FMod_Python", /* struct name */
+ fcm_python_free, /* free data */
+ fcm_python_copy, /* copy data */
+ fcm_python_new_data, /* new data */
+ fcm_python_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 specifc 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]= NULL/*&FMI_NOISE*/; /* Apply-Noise F-Curve Modifier */ // XXX unimplemented
+ fmodifiersTypeInfo[5]= NULL/*&FMI_FILTER*/; /* Filter F-Curve Modifier */ // XXX unimplemented
+ fmodifiersTypeInfo[6]= &FMI_PYTHON; /* Custom Python 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 data */
+ fcm= MEM_callocN(sizeof(FModifier), "F-Curve Modifier");
+ BLI_addtail(&fcu->modifiers, fcm);
+
+ /* 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 checks */
+ if ELEM3(NULL, fcu, fcm, fmi)
+ return;
+
+ /* free modifier's special data */
+ if (fmi->free_data)
+ fmi->free_data(fcm);
+
+ /* remove modifier from stack */
+ BLI_freelinkN(&fcu->modifiers, 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 */
+ for (fcm= fcu->modifiers.first; fcm; fcm= fmn) {
+ fmn= fcm->next;
+ fcurve_remove_modifier(fcu, fcm);
+ }
+}
+
+/* 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)
+{
+ FPoint *fpt, *new_fpt;
+ int cfra;
+
+ /* 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;
+ }
+ if (start >= end) {
+ printf("Error: Frame range for F-Curve Modifier Baking inappropriate \n");
+ return;
+ }
+
+ /* set up sample data */
+ fpt= new_fpt= MEM_callocN(sizeof(FPoint)*(end-start+1), "FPoint FModifier Samples");
+
+ /* sample the curve at 1-frame intervals from start to end frames
+ * - assume that any ChannelDriver possibly present did not interfere in any way
+ */
+ for (cfra= start; cfra <= end; cfra++, fpt++) {
+ fpt->vec[0]= (float)cfra;
+ fpt->vec[1]= evaluate_fcurve(fcu, (float)cfra);
+ }
+
+ /* free any existing sample/keyframe data on curve, and all modifiers */
+ if (fcu->bezt) MEM_freeN(fcu->bezt);
+ if (fcu->fpt) MEM_freeN(fcu->fpt);
+ fcurve_free_modifiers(fcu);
+
+ /* store the samples */
+ fcu->fpt= new_fpt;
+ fcu->totvert= end - start + 1;
+}
+
+/* ***************************** 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
+ */
+// TODO: set up the modifier system...
+float evaluate_fcurve (FCurve *fcu, float evaltime)
+{
+ FModifier *fcm;
+ float cvalue = 0.0f;
+
+ /* if there is a driver (only if this F-Curve is acting as 'driver'), evaluate it to find value to use as "evaltime"
+ * - 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 curve-data */
+ if (fcu->bezt)
+ cvalue= fcurve_eval_keyframes(fcu, fcu->bezt, evaltime);
+ else if (fcu->fpt)
+ cvalue= fcurve_eval_samples(fcu, fcu->fpt, evaltime);
+
+ /* evaluate modifiers */
+ for (fcm= fcu->modifiers.first; fcm; fcm= fcm->next) {
+ FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm);
+
+ /* only evaluate if there's a callback for this */
+ if (fmi && fmi->evaluate_modifier) {
+ if ((fcm->flag & FMODIFIER_FLAG_DISABLED) == 0)
+ fmi->evaluate_modifier(fcu, fcm, &cvalue, evaltime);
+ }
+ }
+
+ /* 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);
+}
+