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++)