Merge branch 'master' into blender2.8
[blender.git] / source / blender / blenkernel / intern / fcurve.c
index 877f1c13da999c349ec3c0e53a76bd2a98bb89f7..2f0c70a4e124e085bfde077d142d13e0a2b2eb6d 100644 (file)
@@ -49,6 +49,8 @@
 #include "BLI_threads.h"
 #include "BLI_string_utils.h"
 #include "BLI_utildefines.h"
+#include "BLI_expr_pylike_eval.h"
+#include "BLI_alloca.h"
 
 #include "BLT_translation.h"
 
@@ -65,6 +67,8 @@
 
 #include "RNA_access.h"
 
+#include "atomic_ops.h"
+
 #ifdef WITH_PYTHON
 #include "BPY_extern.h"
 #endif
@@ -882,25 +886,44 @@ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSample
  * that the handles are correctly
  */
 
-/* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */
-bool BKE_fcurve_is_cyclic(FCurve *fcu)
+/* Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. */
+eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu)
 {
        FModifier *fcm = fcu->modifiers.first;
 
-       if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES)
-               return false;
+       if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES) {
+               return FCU_CYCLE_NONE;
+       }
 
-       if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED))
-               return false;
+       if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) {
+               return FCU_CYCLE_NONE;
+       }
 
-       if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE))
-               return false;
+       if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE)) {
+               return FCU_CYCLE_NONE;
+       }
 
        FMod_Cycles *data = (FMod_Cycles *)fcm->data;
 
-       return data && data->after_cycles == 0 && data->before_cycles == 0 &&
-           ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
-           ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET);
+       if (data && data->after_cycles == 0 && data->before_cycles == 0) {
+               if (data->before_mode == FCM_EXTRAPOLATE_CYCLIC && data->after_mode == FCM_EXTRAPOLATE_CYCLIC) {
+                       return FCU_CYCLE_PERFECT;
+               }
+
+               if (ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
+                   ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET))
+               {
+                       return FCU_CYCLE_OFFSET;
+               }
+       }
+
+       return FCU_CYCLE_NONE;
+}
+
+/* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */
+bool BKE_fcurve_is_cyclic(FCurve *fcu)
+{
+       return BKE_fcurve_get_cycle_type(fcu) != FCU_CYCLE_NONE;
 }
 
 /* Shifts 'in' by the difference in coordinates between 'to' and 'from', using 'out' as the output buffer.
@@ -1288,31 +1311,6 @@ bool driver_get_variable_property(
        return true;
 }
 
-#if 0
-/* Helper function to obtain a pointer to a Pose Channel (for evaluating drivers) */
-static bPoseChannel *dtar_get_pchan_ptr(ChannelDriver *driver, DriverTarget *dtar)
-{
-       ID *id;
-       /* sanity check */
-       if (ELEM(NULL, driver, dtar))
-               return NULL;
-
-       id = dtar_id_ensure_proxy_from(dtar->id);
-
-       /* check if the ID here is a valid object */
-       if (id && GS(id->name)) {
-               Object *ob = (Object *)id;
-
-               /* get pose, and subsequently, posechannel */
-               return BKE_pose_channel_find_name(ob->pose, dtar->pchan_name);
-       }
-       else {
-               /* cannot find a posechannel this way */
-               return NULL;
-       }
-}
-#endif
-
 static short driver_check_valid_targets(ChannelDriver *driver, DriverVar *dvar)
 {
        short valid_targets = 0;
@@ -1694,11 +1692,8 @@ void driver_free_variable_ex(ChannelDriver *driver, DriverVar *dvar)
        /* remove and free the driver variable */
        driver_free_variable(&driver->variables, dvar);
 
-#ifdef WITH_PYTHON
        /* since driver variables are cached, the expression needs re-compiling too */
-       if (driver->type == DRIVER_TYPE_PYTHON)
-               driver->flag |= DRIVER_FLAG_RENAMEVAR;
-#endif
+       BKE_driver_invalidate_expression(driver, false, true);
 }
 
 /* Copy driver variables from src_vars list to dst_vars list */
@@ -1835,11 +1830,8 @@ DriverVar *driver_add_new_variable(ChannelDriver *driver)
        /* set the default type to 'single prop' */
        driver_change_variable_type(dvar, DVAR_TYPE_SINGLE_PROP);
 
-#ifdef WITH_PYTHON
        /* since driver variables are cached, the expression needs re-compiling too */
-       if (driver->type == DRIVER_TYPE_PYTHON)
-               driver->flag |= DRIVER_FLAG_RENAMEVAR;
-#endif
+       BKE_driver_invalidate_expression(driver, false, true);
 
        /* return the target */
        return dvar;
@@ -1868,6 +1860,8 @@ void fcurve_free_driver(FCurve *fcu)
                BPY_DECREF(driver->expr_comp);
 #endif
 
+       BLI_expr_pylike_free(driver->expr_simple);
+
        /* 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;
@@ -1885,6 +1879,7 @@ ChannelDriver *fcurve_copy_driver(const ChannelDriver *driver)
        /* copy all data */
        ndriver = MEM_dupallocN(driver);
        ndriver->expr_comp = NULL;
+       ndriver->expr_simple = NULL;
 
        /* copy variables */
        BLI_listbase_clear(&ndriver->variables); /* to get rid of refs to non-copied data (that's still used on original) */
@@ -1894,6 +1889,124 @@ ChannelDriver *fcurve_copy_driver(const ChannelDriver *driver)
        return ndriver;
 }
 
+/* Driver Expression Evaluation --------------- */
+
+static ExprPyLike_Parsed *driver_compile_simple_expr_impl(ChannelDriver *driver)
+{
+       /* Prepare parameter names. */
+       int names_len = BLI_listbase_count(&driver->variables);
+       const char **names = BLI_array_alloca(names, names_len + 1);
+       int i = 0;
+
+       names[i++] = "frame";
+
+       for (DriverVar *dvar = driver->variables.first; dvar; dvar = dvar->next) {
+               names[i++] = dvar->name;
+       }
+
+       return BLI_expr_pylike_parse(driver->expression, names, names_len + 1);
+}
+
+static bool driver_evaluate_simple_expr(ChannelDriver *driver, ExprPyLike_Parsed *expr, float *result, float time)
+{
+       /* Prepare parameter values. */
+       int vars_len = BLI_listbase_count(&driver->variables);
+       double *vars = BLI_array_alloca(vars, vars_len + 1);
+       int i = 0;
+
+       vars[i++] = time;
+
+       for (DriverVar *dvar = driver->variables.first; dvar; dvar = dvar->next) {
+               vars[i++] = driver_get_variable_value(driver, dvar);
+       }
+
+       /* Evaluate expression. */
+       double result_val;
+       eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, vars, vars_len + 1, &result_val);
+       const char *message;
+
+       switch (status) {
+               case EXPR_PYLIKE_SUCCESS:
+                       if (isfinite(result_val)) {
+                               *result = (float)result_val;
+                       }
+                       return true;
+
+               case EXPR_PYLIKE_DIV_BY_ZERO:
+               case EXPR_PYLIKE_MATH_ERROR:
+                       message = (status == EXPR_PYLIKE_DIV_BY_ZERO) ? "Division by Zero" : "Math Domain Error";
+                       fprintf(stderr, "\n%s in Driver: '%s'\n", message, driver->expression);
+
+                       driver->flag |= DRIVER_FLAG_INVALID;
+                       return true;
+
+               default:
+                       /* arriving here means a bug, not user error */
+                       printf("Error: simple driver expression evaluation failed: '%s'\n", driver->expression);
+                       return false;
+       }
+}
+
+/* Compile and cache the driver expression if necessary, with thread safety. */
+static bool driver_compile_simple_expr(ChannelDriver *driver)
+{
+       if (driver->expr_simple != NULL) {
+               return true;
+       }
+
+       if (driver->type != DRIVER_TYPE_PYTHON) {
+               return false;
+       }
+
+       /* It's safe to parse in multiple threads; at worst it'll
+        * waste some effort, but in return avoids mutex contention. */
+       ExprPyLike_Parsed *expr = driver_compile_simple_expr_impl(driver);
+
+       /* Store the result if the field is still NULL, or discard
+        * it if another thread got here first. */
+       if (atomic_cas_ptr((void **)&driver->expr_simple, NULL, expr) != NULL) {
+               BLI_expr_pylike_free(expr);
+       }
+
+       return true;
+}
+
+/* Try using the simple expression evaluator to compute the result of the driver.
+ * On success, stores the result and returns true; on failure result is set to 0. */
+static bool driver_try_evaluate_simple_expr(ChannelDriver *driver, ChannelDriver *driver_orig, float *result, float time)
+{
+       *result = 0.0f;
+
+       return driver_compile_simple_expr(driver_orig) &&
+              BLI_expr_pylike_is_valid(driver_orig->expr_simple) &&
+              driver_evaluate_simple_expr(driver, driver_orig->expr_simple, result, time);
+}
+
+/* Check if the expression in the driver conforms to the simple subset. */
+bool BKE_driver_has_simple_expression(ChannelDriver *driver)
+{
+       return driver_compile_simple_expr(driver) && BLI_expr_pylike_is_valid(driver->expr_simple);
+}
+
+/* Reset cached compiled expression data */
+void BKE_driver_invalidate_expression(ChannelDriver *driver, bool expr_changed, bool varname_changed)
+{
+       if (expr_changed || varname_changed) {
+               BLI_expr_pylike_free(driver->expr_simple);
+               driver->expr_simple = NULL;
+       }
+
+#ifdef WITH_PYTHON
+       if (expr_changed) {
+               driver->flag |= DRIVER_FLAG_RECOMPILE;
+       }
+
+       if (varname_changed) {
+               driver->flag |= DRIVER_FLAG_RENAMEVAR;
+       }
+#endif
+}
+
 /* Driver Evaluation -------------------------- */
 
 /* Evaluate a Driver Variable to get a value that contributes to the final */
@@ -1922,13 +2035,14 @@ float driver_get_variable_value(ChannelDriver *driver, DriverVar *dvar)
 /* 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
+ * - driver_orig is where we cache Python expressions, in case of COW
  */
-float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, const float evaltime)
+float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, ChannelDriver *driver_orig, const float evaltime)
 {
        DriverVar *dvar;
 
        /* check if driver can be evaluated */
-       if (driver->flag & DRIVER_FLAG_INVALID)
+       if (driver_orig->flag & DRIVER_FLAG_INVALID)
                return 0.0f;
 
        switch (driver->type) {
@@ -1996,26 +2110,26 @@ float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, const fl
                }
                case DRIVER_TYPE_PYTHON: /* expression */
                {
-#ifdef WITH_PYTHON
                        /* check for empty or invalid expression */
-                       if ( (driver->expression[0] == '\0') ||
-                            (driver->flag & DRIVER_FLAG_INVALID) )
+                       if ( (driver_orig->expression[0] == '\0') ||
+                            (driver_orig->flag & DRIVER_FLAG_INVALID) )
                        {
                                driver->curval = 0.0f;
                        }
-                       else {
+                       else if (!driver_try_evaluate_simple_expr(driver, driver_orig, &driver->curval, evaltime)) {
+#ifdef WITH_PYTHON
                                /* this evaluates the expression using Python, and returns its result:
                                 * - on errors it reports, then returns 0.0f
                                 */
                                BLI_mutex_lock(&python_driver_lock);
 
-                               driver->curval = BPY_driver_exec(anim_rna, driver, evaltime);
+                               driver->curval = BPY_driver_exec(anim_rna, driver, driver_orig, evaltime);
 
                                BLI_mutex_unlock(&python_driver_lock);
-                       }
 #else /* WITH_PYTHON*/
-                       UNUSED_VARS(anim_rna, evaltime);
+                               UNUSED_VARS(anim_rna, evaltime);
 #endif /* WITH_PYTHON*/
+                       }
                        break;
                }
                default:
@@ -2187,24 +2301,6 @@ static void berekeny(float f1, float f2, float f3, float f4, float *o, int b)
        }
 }
 
-#if 0
-static 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;
-       }
-}
-#endif
-
 
 /* -------------------------- */
 
@@ -2705,7 +2801,7 @@ float evaluate_fcurve(FCurve *fcu, float evaltime)
        return evaluate_fcurve_ex(fcu, evaltime, 0.0);
 }
 
-float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, FCurve *fcu, float evaltime)
+float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, FCurve *fcu, ChannelDriver *driver_orig, float evaltime)
 {
        BLI_assert(fcu->driver != NULL);
        float cvalue = 0.0f;
@@ -2715,7 +2811,7 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, FCurve *fcu, float evalt
         */
        if (fcu->driver) {
                /* evaltime now serves as input for the curve */
-               evaltime = evaluate_driver(anim_rna, fcu->driver, evaltime);
+               evaltime = evaluate_driver(anim_rna, fcu->driver, driver_orig, evaltime);
 
                /* only do a default 1-1 mapping if it's unlikely that anything else will set a value... */
                if (fcu->totvert == 0) {
@@ -2762,7 +2858,7 @@ float calculate_fcurve(PathResolvedRNA *anim_rna, FCurve *fcu, float evaltime)
                /* calculate and set curval (evaluates driver too if necessary) */
                float curval;
                if (fcu->driver) {
-                       curval = evaluate_fcurve_driver(anim_rna, fcu, evaltime);
+                       curval = evaluate_fcurve_driver(anim_rna, fcu, fcu->driver, evaltime);
                }
                else {
                        curval = evaluate_fcurve(fcu, evaltime);