Commit: f8cc01595d1181b9a8adcb6aa930d4cbfebdc8bf
Author: Alexander Gavrilov
Date:   Sat Jul 4 13:20:59 2020 +0300
Branches: master
https://developer.blender.org/rBf8cc01595d1181b9a8adcb6aa930d4cbfebdc8bf

Drivers: add lerp and clamp functions to namespace.

Implementation of lerp without a function requires repeating one of
the arguments, which is not ideal. To avoid that, add a new function
to the driver namespace. In addition, provide a function for clamping
between 0 and 1 to support easy clamped lerp, and a smoothstep function
from GLSL that is somewhat related.

The function implementations are added to a new bl_math module.
As an aside, add the round function and two-argument log to the
pylike expression subset.

Differential Revision: https://developer.blender.org/D8205

===================================================================

M       source/blender/blenlib/intern/expr_pylike_eval.c
M       source/blender/python/generic/CMakeLists.txt
A       source/blender/python/generic/bl_math_py_api.c
A       source/blender/python/generic/bl_math_py_api.h
M       source/blender/python/intern/bpy_driver.c
M       source/blender/python/intern/bpy_interface.c
M       tests/gtests/blenlib/BLI_expr_pylike_eval_test.cc

===================================================================

diff --git a/source/blender/blenlib/intern/expr_pylike_eval.c 
b/source/blender/blenlib/intern/expr_pylike_eval.c
index d1d84dab3f7..f8618c54ea4 100644
--- a/source/blender/blenlib/intern/expr_pylike_eval.c
+++ b/source/blender/blenlib/intern/expr_pylike_eval.c
@@ -72,6 +72,8 @@ typedef enum eOpCode {
   OPCODE_FUNC1,
   /* 2 argument function call: (a b -> func2(a,b)) */
   OPCODE_FUNC2,
+  /* 3 argument function call: (a b c -> func3(a,b,c)) */
+  OPCODE_FUNC3,
   /* Parameter access: (-> params[ival]) */
   OPCODE_PARAMETER,
   /* Minimum of multiple inputs: (a b c... -> min); ival = arg count */
@@ -92,6 +94,7 @@ typedef enum eOpCode {
 
 typedef double (*UnaryOpFunc)(double);
 typedef double (*BinaryOpFunc)(double, double);
+typedef double (*TernaryOpFunc)(double, double, double);
 
 typedef struct ExprOp {
   eOpCode opcode;
@@ -104,6 +107,7 @@ typedef struct ExprOp {
     void *ptr;
     UnaryOpFunc func1;
     BinaryOpFunc func2;
+    TernaryOpFunc func3;
   } arg;
 } ExprOp;
 
@@ -216,6 +220,11 @@ eExprPyLike_EvalStatus 
BLI_expr_pylike_eval(ExprPyLike_Parsed *expr,
         stack[sp - 2] = ops[pc].arg.func2(stack[sp - 2], stack[sp - 1]);
         sp--;
         break;
+      case OPCODE_FUNC3:
+        FAIL_IF(sp < 3);
+        stack[sp - 3] = ops[pc].arg.func3(stack[sp - 3], stack[sp - 2], 
stack[sp - 1]);
+        sp -= 2;
+        break;
       case OPCODE_MIN:
         FAIL_IF(sp < ops[pc].arg.ival);
         for (int j = 1; j < ops[pc].arg.ival; j++, sp--) {
@@ -326,6 +335,35 @@ static double op_degrees(double arg)
   return arg * 180.0 / M_PI;
 }
 
+static double op_log2(double a, double b)
+{
+  return log(a) / log(b);
+}
+
+static double op_lerp(double a, double b, double x)
+{
+  return a * (1.0 - x) + b * x;
+}
+
+static double op_clamp(double arg)
+{
+  CLAMP(arg, 0.0, 1.0);
+  return arg;
+}
+
+static double op_clamp3(double arg, double minv, double maxv)
+{
+  CLAMP(arg, minv, maxv);
+  return arg;
+}
+
+static double op_smoothstep(double a, double b, double x)
+{
+  double t = (x - a) / (b - a);
+  CLAMP(t, 0.0, 1.0);
+  return t * t * (3.0 - 2.0 * t);
+}
+
 static double op_not(double a)
 {
   return a ? 0.0 : 1.0;
@@ -390,6 +428,7 @@ static BuiltinOpDef builtin_ops[] = {
     {"floor", OPCODE_FUNC1, floor},
     {"ceil", OPCODE_FUNC1, ceil},
     {"trunc", OPCODE_FUNC1, trunc},
+    {"round", OPCODE_FUNC1, round},
     {"int", OPCODE_FUNC1, trunc},
     {"sin", OPCODE_FUNC1, sin},
     {"cos", OPCODE_FUNC1, cos},
@@ -400,9 +439,14 @@ static BuiltinOpDef builtin_ops[] = {
     {"atan2", OPCODE_FUNC2, atan2},
     {"exp", OPCODE_FUNC1, exp},
     {"log", OPCODE_FUNC1, log},
+    {"log", OPCODE_FUNC2, op_log2},
     {"sqrt", OPCODE_FUNC1, sqrt},
     {"pow", OPCODE_FUNC2, pow},
     {"fmod", OPCODE_FUNC2, fmod},
+    {"lerp", OPCODE_FUNC3, op_lerp},
+    {"clamp", OPCODE_FUNC1, op_clamp},
+    {"clamp", OPCODE_FUNC3, op_clamp3},
+    {"smoothstep", OPCODE_FUNC3, op_smoothstep},
     {NULL, OPCODE_CONST, NULL},
 };
 
@@ -514,6 +558,22 @@ static void parse_set_jump(ExprParseState *state, int jump)
   state->ops[jump - 1].jmp_offset = state->ops_count - jump;
 }
 
+/* Returns the required argument count of the given function call code. */
+static int opcode_arg_count(eOpCode code)
+{
+  switch (code) {
+    case OPCODE_FUNC1:
+      return 1;
+    case OPCODE_FUNC2:
+      return 2;
+    case OPCODE_FUNC3:
+      return 3;
+    default:
+      BLI_assert(!"unexpected opcode");
+      return -1;
+  }
+}
+
 /* Add a function call operation, applying constant folding when possible. */
 static bool parse_add_func(ExprParseState *state, eOpCode code, int args, void 
*funcptr)
 {
@@ -560,6 +620,27 @@ static bool parse_add_func(ExprParseState *state, eOpCode 
code, int args, void *
       }
       break;
 
+    case OPCODE_FUNC3:
+      CHECK_ERROR(args == 3);
+
+      if (jmp_gap >= 3 && prev_ops[-3].opcode == OPCODE_CONST &&
+          prev_ops[-2].opcode == OPCODE_CONST && prev_ops[-1].opcode == 
OPCODE_CONST) {
+        TernaryOpFunc func = funcptr;
+
+        /* volatile because some compilers overly aggressive optimize this 
call out.
+         * see D6012 for details. */
+        volatile double result = func(
+            prev_ops[-3].arg.dval, prev_ops[-2].arg.dval, 
prev_ops[-1].arg.dval);
+
+        if (fetestexcept(FE_DIVBYZERO | FE_INVALID) == 0) {
+          prev_ops[-3].arg.dval = result;
+          state->ops_count -= 2;
+          state->stack_ptr -= 2;
+          return true;
+        }
+      }
+      break;
+
     default:
       BLI_assert(false);
       return false;
@@ -755,6 +836,17 @@ static bool parse_unary(ExprParseState *state)
         if (STREQ(state->tokenbuf, builtin_ops[i].name)) {
           int args = parse_function_args(state);
 
+          /* Search for other arg count versions if necessary. */
+          if (args != opcode_arg_count(builtin_ops[i].op)) {
+            for (int j = i + 1; builtin_ops[j].name; j++) {
+              if (opcode_arg_count(builtin_ops[j].op) == args &&
+                  STREQ(builtin_ops[j].name, builtin_ops[i].name)) {
+                i = j;
+                break;
+              }
+            }
+          }
+
           return parse_add_func(state, builtin_ops[i].op, args, 
builtin_ops[i].funcptr);
         }
       }
diff --git a/source/blender/python/generic/CMakeLists.txt 
b/source/blender/python/generic/CMakeLists.txt
index 822f05bad90..785c1d66407 100644
--- a/source/blender/python/generic/CMakeLists.txt
+++ b/source/blender/python/generic/CMakeLists.txt
@@ -36,12 +36,14 @@ set(SRC
   bpy_threads.c
   idprop_py_api.c
   imbuf_py_api.c
+  bl_math_py_api.c
   py_capi_utils.c
 
   bgl.h
   blf_py_api.h
   idprop_py_api.h
   imbuf_py_api.h
+  bl_math_py_api.h
   py_capi_utils.h
 
   # header-only
diff --git a/source/blender/python/generic/bl_math_py_api.c 
b/source/blender/python/generic/bl_math_py_api.c
new file mode 100644
index 00000000000..4d5a63ffba3
--- /dev/null
+++ b/source/blender/python/generic/bl_math_py_api.c
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+/**
+ * \file
+ * \ingroup pygen
+ *
+ * This file defines the 'bl_math' module, a module for math utilities.
+ */
+
+#include <Python.h>
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "py_capi_utils.h"
+
+#include "bl_math_py_api.h"
+
+/*------------------------------------------------------------*/
+/**
+ * \name Module doc string
+ * \{ */
+
+PyDoc_STRVAR(M_Math_doc, "Miscellaneous math utilities module");
+
+/** \} */
+/*------------------------------------------------------------*/
+/**
+ * \name Python functions
+ * \{ */
+
+PyDoc_STRVAR(M_Math_clamp_doc,
+             ".. function:: clamp(value, min=0, max=1)\n"
+             "\n"
+             "   Clamps the float value between minimum and maximum. To 
avoid\n"
+             "   confusion, any call must use either one or all three 
arguments.\n"
+             "\n"
+             "   :arg value: The value to clamp.\n"
+             "   :type value: float\n"
+             "   :arg min: The minimum value, defaults to 0.\n"
+             "   :type min: float\n"
+             "   :arg max: The maximum value, defaults to 1.\n"
+             "   :type max: float\n"
+             "   :return: The clamped value.\n"
+             "   :rtype: float\n");
+static PyObject *M_Math_clamp(PyObject *UNUSED(self), PyObject *args)
+{
+  double x, minv = 0.0, maxv = 1.0;
+
+  if (PyTuple_Size(args) <= 1) {
+    if (!PyArg_ParseTuple(args, "d:clamp", &x)) {
+      return NULL;
+    }
+  }
+  else {
+    if (!PyArg_ParseTuple(args, "ddd:clamp", &x, &minv, &maxv)) {
+      return NULL;
+    }
+  }
+
+  CLAMP(x, minv, maxv);
+
+  return PyFloat_FromDouble(x);
+}
+
+PyDoc_STRVAR(M_Math_lerp_doc,
+             ".. function:: lerp(from, to, factor)\n"
+             "\n"
+             "   Linearly interpolate between two float values based on 
factor.\n"
+             "\n"
+             "   :arg from: The value to return when factor is 0.\n"
+             "   :type from: float\n"
+             "   :arg to: The value to return when factor is 1.\n"
+             "   :type to: float\n"
+             "   :arg factor: The interpolation value, normally in [0.0, 
1.0].\n"
+             "   :type factor: float\n"
+             "   :return: The interpolated value.\n"
+             "   :rtype: float\n");
+static PyObject *M_Math_lerp(PyObject *UNUSED(self), PyObject *args)
+{
+  double a, b, x;
+  if (!PyArg_ParseTuple(args, "ddd:lerp", &a, &b, &x)) {
+    return NULL;
+  }
+
+  return PyFloat_FromDouble(a * (1.0 - x) + b * x);
+}
+
+PyDoc_STRVAR(
+    M_Math_smoothstep_doc,
+    ".. function:: smoothstep(from, to, value)\n"
+    "\n"
+    "   Performs smooth interpolation between 0 and 1 as value changes between 
from and to.\n"
+    "   Outside the range the function returns the same value as the nearest 
edge.\n"
+    "\n"
+    "   :arg from: The edge value where the result is 0.\n"
+    "   :type from: float\n"
+    "   :arg to: The edge value where the result is 1.\n"
+    "   :type to: float\n"
+    "   :arg factor: The interpolation value.\n"
+    "   :type factor: float\n"
+    "   :return: The interpolated value in [0.0, 1.0].\n"
+    "   :rtype: float\n");
+static PyObject *M_Math_smoothstep(PyObject *UNUSED(self), PyObject *args)
+{
+  double a, b, x;
+  if (!PyArg_ParseTuple(args, "ddd:smoothstep", &a, &b, &x)) {
+    return NULL;
+  }
+
+  double t = (x - a) / (b - a);
+
+  CLAMP(t, 0.0, 1.0);
+
+  return PyFloat_FromDouble(t * t * (3.0 - 2.0 * t));
+}
+
+/** \} *

@@ Diff output truncated at 10240 characters. @@

_______________________________________________
Bf-blender-cvs mailing list
[email protected]
https://lists.blender.org/mailman/listinfo/bf-blender-cvs

Reply via email to