/** * $Id$ * * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2009 Blender Foundation, Joshua Leung * All rights reserved. * * Contributor(s): Joshua Leung (full recode) * * ***** END GPL LICENSE BLOCK ***** */ #include #include #include #include #include #include "MEM_guardedalloc.h" #include "DNA_anim_types.h" #include "BLI_blenlib.h" #include "BLI_math.h" /* windows needs for M_PI */ #include "BKE_fcurve.h" #include "BKE_idprop.h" #include "BKE_utildefines.h" #ifndef DISABLE_PYTHON #include "BPY_extern.h" /* for BPY_pydriver_eval() */ #endif #define SMALL -1.0e-10 #define SELECT 1 /* ******************************** F-Modifiers ********************************* */ /* Info ------------------------------- */ /* F-Modifiers are modifiers which operate on F-Curves. However, they can also be defined * on NLA-Strips to affect all of the F-Curves referenced by the NLA-Strip. */ /* 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])) */ 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; } } 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; } } 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 */ }; /* Built-In Function Generator F-Curve Modifier --------------------------- */ /* This uses the general equation for equations: * y = amplitude * fn(phase_multiplier*x + phase_offset) + y_offset * * where amplitude, phase_multiplier/offset, y_offset are user-defined coefficients, * x is the evaluation 'time', and 'y' is the resultant value * * Functions available are * sin, cos, tan, sinc (normalised sin), natural log, square root */ static void fcm_fn_generator_new_data (void *mdata) { FMod_FunctionGenerator *data= (FMod_FunctionGenerator *)mdata; /* set amplitude and phase multiplier to 1.0f so that something is generated */ data->amplitude= 1.0f; data->phase_multiplier= 1.0f; } /* 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_fn_generator_evaluate (FCurve *fcu, FModifier *fcm, float *cvalue, float evaltime) { FMod_FunctionGenerator *data= (FMod_FunctionGenerator *)fcm->data; double arg= data->phase_multiplier*evaltime + data->phase_offset; 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->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->type); } /* execute function callback to set value if appropriate */ if (fn) { float value= (float)(data->amplitude*fn(arg) + data->value_offset); if (data->flag & FCM_GENERATOR_ADDITIVE) *cvalue += value; else *cvalue= value; } } static FModifierTypeInfo FMI_FN_GENERATOR = { FMODIFIER_TYPE_FN_GENERATOR, /* type */ sizeof(FMod_FunctionGenerator), /* size */ FMI_TYPE_GENERATE_CURVE, /* action type */ FMI_REQUIRES_NOTHING, /* requirements */ "Built-In Function", /* name */ "FMod_FunctionGenerator", /* struct name */ NULL, /* free data */ NULL, /* copy data */ fcm_fn_generator_new_data, /* new data */ NULL, /* verify */ NULL, /* evaluate time */ fcm_fn_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; /* generate noise using good ol' Blender Noise * - 0.1 is passed as the 'z' value, otherwise evaluation fails for size = phase = 1 * with evaltime being an integer (which happens when evaluating on frame by frame basis) */ noise = BLI_turbulence(data->size, evaltime, data->phase, 0.1f, data->depth); /* combine the noise with existing motion data */ 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 */ }; /* Stepped F-Curve Modifier --------------------------- */ static void fcm_stepped_new_data (void *mdata) { FMod_Stepped *data= (FMod_Stepped *)mdata; /* just need to set the step-size to 2-frames by default */ // XXX: or would 5 be more normal? data->step_size = 2.0f; } static float fcm_stepped_time (FCurve *fcu, FModifier *fcm, float cvalue, float evaltime) { FMod_Stepped *data= (FMod_Stepped *)fcm->data; int snapblock; /* check range clamping to see if we should alter the timing to achieve the desired results */ if (data->flag & FCM_STEPPED_NO_BEFORE) { if (evaltime < data->start_frame) return evaltime; } if (data->flag & FCM_STEPPED_NO_AFTER) { if (evaltime > data->end_frame) return evaltime; } /* we snap to the start of the previous closest block of 'step_size' frames * after the start offset has been discarded * - i.e. round down */ snapblock = (int)((evaltime - data->offset) / data->step_size); /* reapply the offset, and multiple the snapblock by the size of the steps to get * the new time to evaluate at */ return ((float)snapblock * data->step_size) + data->offset; } static FModifierTypeInfo FMI_STEPPED = { FMODIFIER_TYPE_STEPPED, /* type */ sizeof(FMod_Limits), /* size */ FMI_TYPE_GENERATE_CURVE, /* action type */ /* XXX... err... */ FMI_REQUIRES_RUNTIME_CHECK, /* requirements */ "Stepped", /* name */ "FMod_Stepped", /* struct name */ NULL, /* free data */ NULL, /* copy data */ fcm_stepped_new_data, /* new data */ NULL, /* verify */ fcm_stepped_time, /* evaluate time */ NULL /* 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_FN_GENERATOR; /* Built-In Function Generator F-Curve Modifier */ fmodifiersTypeInfo[3]= &FMI_ENVELOPE; /* Envelope F-Curve Modifier */ fmodifiersTypeInfo[4]= &FMI_CYCLES; /* Cycles F-Curve Modifier */ fmodifiersTypeInfo[5]= &FMI_NOISE; /* Apply-Noise F-Curve Modifier */ fmodifiersTypeInfo[6]= NULL/*&FMI_FILTER*/; /* Filter F-Curve Modifier */ // XXX unimplemented fmodifiersTypeInfo[7]= &FMI_PYTHON; /* Custom Python F-Curve Modifier */ fmodifiersTypeInfo[8]= &FMI_LIMITS; /* Limits F-Curve Modifier */ fmodifiersTypeInfo[9]= &FMI_STEPPED; /* Stepped 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 *add_fmodifier (ListBase *modifiers, int type) { FModifierTypeInfo *fmi= get_fmodifier_typeinfo(type); FModifier *fcm; /* sanity checks */ if ELEM(NULL, modifiers, fmi) return NULL; /* special checks for whether modifier can be added */ if ((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(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; } /* Make a copy of the specified F-Modifier */ FModifier *copy_fmodifier (FModifier *src) { FModifierTypeInfo *fmi= fmodifier_get_typeinfo(src); FModifier *dst; /* sanity check */ if (src == NULL) return NULL; /* copy the base data, clearing the links */ dst = MEM_dupallocN(src); dst->next = dst->prev = NULL; /* make a new copy of the F-Modifier's data */ dst->data = MEM_dupallocN(src->data); /* only do specific constraints if required */ if (fmi && fmi->copy_data) fmi->copy_data(dst, src); /* return the new modifier */ return dst; } /* Duplicate all of the F-Modifiers in the Modifier stacks */ void copy_fmodifiers (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-Modifier from the given stack */ int remove_fmodifier (ListBase *modifiers, FModifier *fcm) { FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm); /* sanity check */ if (fcm == NULL) return 0; /* 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 (modifiers) { BLI_freelinkN(modifiers, fcm); return 1; } else { // XXX this case can probably be removed some day, as it shouldn't happen... printf("remove_fmodifier() - no modifier stack given \n"); MEM_freeN(fcm); return 0; } } /* Remove and free the nth F-Modifier from the given stack */ int remove_fmodifier_index (ListBase *modifiers, int index) { FModifier *fcm= BLI_findlink(modifiers, index); return remove_fmodifier(modifiers, fcm); } /* Remove all of a given F-Curve's modifiers */ void free_fmodifiers (ListBase *modifiers) { FModifier *fcm, *fmn; /* sanity check */ if (modifiers == NULL) return; /* free each modifier in order - modifier is unlinked from list and freed */ for (fcm= modifiers->first; fcm; fcm= fmn) { fmn= fcm->next; remove_fmodifier(modifiers, fcm); } } /* Find the active F-Modifier */ FModifier *find_active_fmodifier (ListBase *modifiers) { FModifier *fcm; /* sanity checks */ if ELEM(NULL, modifiers, modifiers->first) return NULL; /* loop over modifiers until 'active' one is found */ for (fcm= modifiers->first; fcm; fcm= fcm->next) { if (fcm->flag & FMODIFIER_FLAG_ACTIVE) return fcm; } /* no modifier is active */ return NULL; } /* Set the active F-Modifier */ void set_active_fmodifier (ListBase *modifiers, FModifier *fcm) { FModifier *fm; /* sanity checks */ if ELEM(NULL, modifiers, modifiers->first) return; /* deactivate all, and set current one active */ for (fm= modifiers->first; fm; fm= fm->next) fm->flag &= ~FMODIFIER_FLAG_ACTIVE; /* make given modifier active */ if (fcm) fcm->flag |= FMODIFIER_FLAG_ACTIVE; } /* Do we have any modifiers which match certain criteria * - mtype - type of modifier (if 0, doesn't matter) * - acttype - type of action to perform (if -1, doesn't matter) */ short list_has_suitable_fmodifier (ListBase *modifiers, int mtype, short acttype) { FModifier *fcm; /* if there are no specific filtering criteria, just skip */ if ((mtype == 0) && (acttype == 0)) return (modifiers && modifiers->first); /* sanity checks */ if ELEM(NULL, modifiers, modifiers->first) return 0; /* find the first mdifier fitting these criteria */ for (fcm= modifiers->first; fcm; fcm= fcm->next) { FModifierTypeInfo *fmi= fmodifier_get_typeinfo(fcm); short mOk=1, aOk=1; /* by default 1, so that when only one test, won't fail */ /* check if applicable ones are fullfilled */ if (mtype) mOk= (fcm->type == mtype); if (acttype > -1) aOk= (fmi->acttype == acttype); /* if both are ok, we've found a hit */ if (mOk && aOk) return 1; } /* no matches */ return 0; } /* 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; /* sanity checks */ if ELEM(NULL, modifiers, modifiers->last) return evaltime; /* Starting from the end of the stack, calculate the time effects of various stacked modifiers * on the time the F-Curve should be evaluated at. * * This is done in reverse order to standard evaluation, as when this is done in standard * order, each modifier would cause jumps to other points in the curve, forcing all * previous ones to be evaluated again for them to be correct. However, if we did in the * reverse order as we have here, we can consider them a macro to micro type of waterfall * effect, which should get us the desired effects when using layered time manipulations * (such as multiple 'stepped' modifiers in sequence, causing different stepping rates) */ for (fcm= 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) evaltime= fmi->evaluate_modifier_time(fcu, fcm, cvalue, evaltime); } } /* return the modified evaltime */ return 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 */ free_fmodifiers(&fcu->modifiers); /* restore driver */ fcu->driver= driver; }