From bf04513decbe443233ea61ddb61f23aad06e7ebe Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Mon, 23 Sep 2024 15:01:31 +0200 Subject: [PATCH] Geometry Nodes: new Integer Math node Provide building block support for integer operations. Manipulation of integer based data should not be limited to using float math nodes. Using float math comes with accuracy issues for larger integers and requires unnecessary type conversions. The node also adds some integer specific operations like GCM and LCD. Pull Request: https://projects.blender.org/blender/blender/pulls/110735 --- .../startup/bl_ui/node_add_menu_geometry.py | 1 + source/blender/blenkernel/BKE_node.hh | 1 + source/blender/blenlib/BLI_math_base.hh | 2 +- source/blender/makesdna/DNA_node_types.h | 21 ++ source/blender/makesrna/RNA_enum_items.hh | 1 + .../blender/makesrna/intern/rna_nodetree.cc | 61 +++++ source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/function/CMakeLists.txt | 1 + .../function/nodes/node_fn_integer_math.cc | 237 ++++++++++++++++++ .../nodes/shader/nodes/node_shader_math.cc | 2 +- 10 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 source/blender/nodes/function/nodes/node_fn_integer_math.cc diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 3f421f437a2..629e01d7022 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -670,6 +670,7 @@ class NODE_MT_category_GEO_UTILITIES_MATH(Menu): def draw(self, _context): layout = self.layout node_add_menu.add_node_type(layout, "FunctionNodeBooleanMath") + node_add_menu.add_node_type(layout, "FunctionNodeIntegerMath") node_add_menu.add_node_type(layout, "ShaderNodeClamp") node_add_menu.add_node_type(layout, "FunctionNodeCompare") node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve") diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index b52540a1bd1..270da784aaa 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1425,6 +1425,7 @@ void node_tree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index); #define FN_NODE_INPUT_ROTATION 1243 #define FN_NODE_AXES_TO_ROTATION 1244 #define FN_NODE_HASH_VALUE 1245 +#define FN_NODE_INTEGER_MATH 1246 /** \} */ diff --git a/source/blender/blenlib/BLI_math_base.hh b/source/blender/blenlib/BLI_math_base.hh index f0878ef9672..61a8e35681f 100644 --- a/source/blender/blenlib/BLI_math_base.hh +++ b/source/blender/blenlib/BLI_math_base.hh @@ -108,7 +108,7 @@ template inline T round(const T &a) */ template inline T mod_periodic(const T &a, const T &b) { - BLI_assert(b > 0); + BLI_assert(b != 0); if constexpr (std::is_integral_v) { BLI_assert(std::numeric_limits::max() - math::abs(a) >= b); return ((a % b) + b) % b; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index bbbed0095c6..1a28956fecc 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2488,6 +2488,27 @@ typedef enum NodeCompareOperation { NODE_COMPARE_COLOR_DARKER = 7, } NodeCompareOperation; +typedef enum NodeIntegerMathOperation { + NODE_INTEGER_MATH_ADD = 0, + NODE_INTEGER_MATH_SUBTRACT = 1, + NODE_INTEGER_MATH_MULTIPLY = 2, + NODE_INTEGER_MATH_DIVIDE = 3, + NODE_INTEGER_MATH_MULTIPLY_ADD = 4, + NODE_INTEGER_MATH_POWER = 5, + NODE_INTEGER_MATH_FLOORED_MODULO = 6, + NODE_INTEGER_MATH_ABSOLUTE = 7, + NODE_INTEGER_MATH_MINIMUM = 8, + NODE_INTEGER_MATH_MAXIMUM = 9, + NODE_INTEGER_MATH_GCD = 10, + NODE_INTEGER_MATH_LCM = 11, + NODE_INTEGER_MATH_NEGATE = 12, + NODE_INTEGER_MATH_SIGN = 13, + NODE_INTEGER_MATH_DIVIDE_FLOOR = 14, + NODE_INTEGER_MATH_DIVIDE_CEIL = 15, + NODE_INTEGER_MATH_DIVIDE_ROUND = 16, + NODE_INTEGER_MATH_MODULO = 17, +} NodeIntegerMathOperation; + typedef enum FloatToIntRoundingMode { FN_NODE_FLOAT_TO_INT_ROUND = 0, FN_NODE_FLOAT_TO_INT_FLOOR = 1, diff --git a/source/blender/makesrna/RNA_enum_items.hh b/source/blender/makesrna/RNA_enum_items.hh index 94740240d9c..4c122e24d6c 100644 --- a/source/blender/makesrna/RNA_enum_items.hh +++ b/source/blender/makesrna/RNA_enum_items.hh @@ -187,6 +187,7 @@ DEF_ENUM(rna_enum_node_boolean_math_items) DEF_ENUM(rna_enum_node_float_compare_items) DEF_ENUM(rna_enum_node_compare_operation_items) DEF_ENUM(rna_enum_node_filter_items) +DEF_ENUM(rna_enum_node_integer_math_items) DEF_ENUM(rna_enum_node_float_to_int_items) DEF_ENUM(rna_enum_node_map_range_items) DEF_ENUM(rna_enum_node_clamp_items) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 620a8d6a5c6..cca823d73dd 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -342,6 +342,67 @@ const EnumPropertyItem rna_enum_node_float_compare_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +const EnumPropertyItem rna_enum_node_integer_math_items[] = { + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Functions"), nullptr), + {NODE_INTEGER_MATH_ADD, "ADD", 0, "Add", "A + B"}, + {NODE_INTEGER_MATH_SUBTRACT, "SUBTRACT", 0, "Subtract", "A - B"}, + {NODE_INTEGER_MATH_MULTIPLY, "MULTIPLY", 0, "Multiply", "A * B"}, + {NODE_INTEGER_MATH_DIVIDE, "DIVIDE", 0, "Divide", "A / B"}, + {NODE_INTEGER_MATH_MULTIPLY_ADD, "MULTIPLY_ADD", 0, "Multiply Add", "A * B + C"}, + RNA_ENUM_ITEM_SEPR, + {NODE_INTEGER_MATH_ABSOLUTE, "ABSOLUTE", 0, "Absolute", "Non-negative value of A, abs(A)"}, + {NODE_INTEGER_MATH_NEGATE, "NEGATE", 0, "Negate", "-A"}, + {NODE_INTEGER_MATH_POWER, "POWER", 0, "Power", "A power B, pow(A,B)"}, + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Comparison"), nullptr), + {NODE_INTEGER_MATH_MINIMUM, + "MINIMUM", + 0, + "Minimum", + "The minimum value from A and B, min(A,B)"}, + {NODE_INTEGER_MATH_MAXIMUM, + "MAXIMUM", + 0, + "Maximum", + "The maximum value from A and B, max(A,B)"}, + {NODE_INTEGER_MATH_SIGN, "SIGN", 0, "Sign", "Return the sign of A, sign(A)"}, + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Rounding"), nullptr), + {NODE_INTEGER_MATH_DIVIDE_ROUND, + "DIVIDE_ROUND", + 0, + "Divide Round", + "Divide and round result toward zero"}, + {NODE_INTEGER_MATH_DIVIDE_FLOOR, + "DIVIDE_FLOOR", + 0, + "Divide Floor", + "Divide and floor result, the largest integer smaller than or equal A"}, + {NODE_INTEGER_MATH_DIVIDE_CEIL, + "DIVIDE_CEIL", + 0, + "Divide Ceiling", + "Divide and ceil result, the smallest integer greater than or equal A"}, + RNA_ENUM_ITEM_SEPR, + {NODE_INTEGER_MATH_FLOORED_MODULO, + "FLOORED_MODULO", + 0, + "Floored Modulo", + "Modulo that is periodic for both negative and positive operands"}, + {NODE_INTEGER_MATH_MODULO, "MODULO", 0, "Modulo", "Modulo which is the remainder of A / B"}, + RNA_ENUM_ITEM_SEPR, + {NODE_INTEGER_MATH_GCD, + "GCD", + 0, + "Greatest Common Divisor", + "The largest positive integer that divides into each of the values A and B, " + "e.g. GCD(8,12) = 4"}, + {NODE_INTEGER_MATH_LCM, + "LCM", + 0, + "Least Common Multiple", + "The smallest positive integer that is divisible by both A and B, e.g. LCM(6,10) = 30"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + const EnumPropertyItem rna_enum_node_compare_operation_items[] = { {NODE_COMPARE_LESS_THAN, "LESS_THAN", diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 349c56ee7e4..67bbde49ac0 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -285,6 +285,7 @@ DefNode(FunctionNode, FN_NODE_INPUT_ROTATION, def_fn_input_rotation, "INPUT_ROTA DefNode(FunctionNode, FN_NODE_INPUT_SPECIAL_CHARACTERS, 0, "INPUT_SPECIAL_CHARACTERS", InputSpecialCharacters, "Special Characters", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") +DefNode(FunctionNode, FN_NODE_INTEGER_MATH, 0, "INTEGER_MATH", IntegerMath, "Integer Math", "") DefNode(FunctionNode, FN_NODE_INVERT_MATRIX, 0, "INVERT_MATRIX", InvertMatrix, "Invert Matrix", "") DefNode(FunctionNode, FN_NODE_INVERT_ROTATION, 0, "INVERT_ROTATION", InvertRotation, "Invert Rotation", "") DefNode(FunctionNode, FN_NODE_MATRIX_MULTIPLY, 0, "MATRIX_MULTIPLY", MatrixMultiply, "Multiply Matrices", "") diff --git a/source/blender/nodes/function/CMakeLists.txt b/source/blender/nodes/function/CMakeLists.txt index 8f00736292d..265e77bd6be 100644 --- a/source/blender/nodes/function/CMakeLists.txt +++ b/source/blender/nodes/function/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC nodes/node_fn_input_special_characters.cc nodes/node_fn_input_string.cc nodes/node_fn_input_vector.cc + nodes/node_fn_integer_math.cc nodes/node_fn_invert_matrix.cc nodes/node_fn_invert_rotation.cc nodes/node_fn_matrix_multiply.cc diff --git a/source/blender/nodes/function/nodes/node_fn_integer_math.cc b/source/blender/nodes/function/nodes/node_fn_integer_math.cc new file mode 100644 index 00000000000..a8928a4bfd7 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_integer_math.cc @@ -0,0 +1,237 @@ +/* SPDX-FileCopyrightText: 2024 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_utf8.h" + +#include "RNA_enum_types.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_rna_define.hh" +#include "NOD_socket_search_link.hh" + +#include "node_function_util.hh" + +namespace blender::nodes::node_fn_integer_math_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + b.add_input("Value"); + b.add_input("Value", "Value_001"); + b.add_input("Value", "Value_002"); + b.add_output("Value"); +}; + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "operation", UI_ITEM_NONE, "", ICON_NONE); +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const bool one_input_ops = ELEM( + node->custom1, NODE_INTEGER_MATH_ABSOLUTE, NODE_INTEGER_MATH_SIGN, NODE_INTEGER_MATH_NEGATE); + const bool three_input_ops = ELEM(node->custom1, NODE_INTEGER_MATH_MULTIPLY_ADD); + + bNodeSocket *sockA = static_cast(node->inputs.first); + bNodeSocket *sockB = sockA->next; + bNodeSocket *sockC = sockB->next; + + bke::node_set_socket_availability(ntree, sockB, !one_input_ops); + bke::node_set_socket_availability(ntree, sockC, three_input_ops); + + node_sock_label_clear(sockA); + node_sock_label_clear(sockB); + node_sock_label_clear(sockC); + switch (node->custom1) { + case NODE_INTEGER_MATH_MULTIPLY_ADD: + node_sock_label(sockA, N_("Value")); + node_sock_label(sockB, N_("Multiplier")); + node_sock_label(sockC, N_("Addend")); + break; + } +} + +class SocketSearchOp { + public: + std::string socket_name; + NodeIntegerMathOperation operation; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("FunctionNodeIntegerMath"); + node.custom1 = NodeIntegerMathOperation(operation); + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + if (!params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type), + SOCK_INT)) + { + return; + } + + const bool is_integer = params.other_socket().type == SOCK_INT; + const int weight = is_integer ? 0 : -1; + + /* Add socket A operations. */ + for (const EnumPropertyItem *item = rna_enum_node_integer_math_items; + item->identifier != nullptr; + item++) + { + if (item->name != nullptr && item->identifier[0] != '\0') { + params.add_item(IFACE_(item->name), + SocketSearchOp{"Value", NodeIntegerMathOperation(item->value)}, + weight); + } + } +} + +static void node_label(const bNodeTree * /*ntree*/, const bNode *node, char *label, int maxlen) +{ + const char *name; + bool enum_label = RNA_enum_name(rna_enum_node_integer_math_items, node->custom1, &name); + if (!enum_label) { + name = "Unknown"; + } + BLI_strncpy(label, IFACE_(name), maxlen); +} + +static const mf::MultiFunction *get_multi_function(const bNode &bnode) +{ + NodeIntegerMathOperation operation = NodeIntegerMathOperation(bnode.custom1); + static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle(); + static auto add_fn = mf::build::SI2_SO( + "Add", [](int a, int b) { return a + b; }, exec_preset); + static auto sub_fn = mf::build::SI2_SO( + "Subtract", [](int a, int b) { return a - b; }, exec_preset); + static auto multiply_fn = mf::build::SI2_SO( + "Multiply", [](int a, int b) { return a * b; }, exec_preset); + static auto divide_fn = mf::build::SI2_SO( + "Divide", [](int a, int b) { return math::safe_divide(a, b); }, exec_preset); + static auto divide_floor_fn = mf::build::SI2_SO( + "Divide Floor", + [](int a, int b) { return int(math::floor(math::safe_divide(float(a), float(b)))); }, + exec_preset); + static auto divide_ceil_fn = mf::build::SI2_SO( + "Divide Ceil", + [](int a, int b) { return int(math::ceil(math::safe_divide(float(a), float(b)))); }, + exec_preset); + static auto divide_round_fn = mf::build::SI2_SO( + "Divide Round", + [](int a, int b) { return int(math::round(math::safe_divide(float(a), float(b)))); }, + exec_preset); + static auto pow_fn = mf::build::SI2_SO( + "Power", [](int a, int b) { return math::pow(a, b); }, exec_preset); + static auto madd_fn = mf::build::SI3_SO( + "Multiply Add", [](int a, int b, int c) { return a * b + c; }, exec_preset); + static auto floored_mod_fn = mf::build::SI2_SO( + "Floored Modulo", + [](int a, int b) { return b != 0 ? math::mod_periodic(a, b) : 0; }, + exec_preset); + static auto mod_fn = mf::build::SI2_SO( + "Modulo", [](int a, int b) { return b != 0 ? a % b : 0; }, exec_preset); + static auto abs_fn = mf::build::SI1_SO( + "Absolute", [](int a) { return math::abs(a); }, exec_preset); + static auto sign_fn = mf::build::SI1_SO( + "Sign", [](int a) { return math::sign(a); }, exec_preset); + static auto min_fn = mf::build::SI2_SO( + "Minimum", [](int a, int b) { return math::min(a, b); }, exec_preset); + static auto max_fn = mf::build::SI2_SO( + "Maximum", [](int a, int b) { return math::max(a, b); }, exec_preset); + static auto gcd_fn = mf::build::SI2_SO( + "GCD", [](int a, int b) { return std::gcd(a, b); }, exec_preset); + static auto lcm_fn = mf::build::SI2_SO( + "LCM", [](int a, int b) { return std::lcm(a, b); }, exec_preset); + static auto negate_fn = mf::build::SI1_SO( + "Negate", [](int a) { return -a; }, exec_preset); + + switch (operation) { + case NODE_INTEGER_MATH_ADD: + return &add_fn; + case NODE_INTEGER_MATH_SUBTRACT: + return &sub_fn; + case NODE_INTEGER_MATH_MULTIPLY: + return &multiply_fn; + case NODE_INTEGER_MATH_DIVIDE: + return ÷_fn; + case NODE_INTEGER_MATH_DIVIDE_FLOOR: + return ÷_floor_fn; + case NODE_INTEGER_MATH_DIVIDE_CEIL: + return ÷_ceil_fn; + case NODE_INTEGER_MATH_DIVIDE_ROUND: + return ÷_round_fn; + case NODE_INTEGER_MATH_POWER: + return &pow_fn; + case NODE_INTEGER_MATH_MULTIPLY_ADD: + return &madd_fn; + case NODE_INTEGER_MATH_FLOORED_MODULO: + return &floored_mod_fn; + case NODE_INTEGER_MATH_MODULO: + return &mod_fn; + case NODE_INTEGER_MATH_ABSOLUTE: + return &abs_fn; + case NODE_INTEGER_MATH_SIGN: + return &sign_fn; + case NODE_INTEGER_MATH_MINIMUM: + return &min_fn; + case NODE_INTEGER_MATH_MAXIMUM: + return &max_fn; + case NODE_INTEGER_MATH_GCD: + return &gcd_fn; + case NODE_INTEGER_MATH_LCM: + return &lcm_fn; + case NODE_INTEGER_MATH_NEGATE: + return &negate_fn; + } + BLI_assert_unreachable(); + return nullptr; +} + +static void node_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const mf::MultiFunction *fn = get_multi_function(builder.node()); + builder.set_matching_fn(fn); +} + +static void node_rna(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_node_enum(srna, + "operation", + "Operation", + "", + rna_enum_node_integer_math_items, + NOD_inline_enum_accessors(custom1), + NODE_INTEGER_MATH_ADD); + RNA_def_property_update_runtime(prop, rna_Node_socket_update); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_INTEGER_MATH, "Integer Math", NODE_CLASS_CONVERTER); + ntype.declare = node_declare; + ntype.labelfunc = node_label; + ntype.updatefunc = node_update; + ntype.build_multi_function = node_build_multi_function; + ntype.draw_buttons = node_layout; + ntype.gather_link_search_ops = node_gather_link_searches; + + blender::bke::node_register_type(&ntype); + + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_fn_integer_math_cc diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index e9303b24357..90e31b2b22c 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -51,7 +51,7 @@ static void sh_node_math_gather_link_searches(GatherLinkSearchOpParams ¶ms) } const bool is_geometry_node_tree = params.node_tree().type == NTREE_GEOMETRY; - const int weight = ELEM(params.other_socket().type, SOCK_FLOAT, SOCK_BOOLEAN, SOCK_INT) ? 0 : -1; + const int weight = ELEM(params.other_socket().type, SOCK_FLOAT, SOCK_BOOLEAN) ? 0 : -1; for (const EnumPropertyItem *item = rna_enum_node_math_items; item->identifier != nullptr; item++)