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
This commit is contained in:
Charlie Jolly
2024-09-23 15:01:31 +02:00
committed by Jacques Lucke
parent 801a73887a
commit bf04513dec
10 changed files with 326 additions and 2 deletions

View File

@@ -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")

View File

@@ -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
/** \} */

View File

@@ -108,7 +108,7 @@ template<typename T> inline T round(const T &a)
*/
template<typename T> inline T mod_periodic(const T &a, const T &b)
{
BLI_assert(b > 0);
BLI_assert(b != 0);
if constexpr (std::is_integral_v<T>) {
BLI_assert(std::numeric_limits<T>::max() - math::abs(a) >= b);
return ((a % b) + b) % b;

View File

@@ -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,

View File

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

View File

@@ -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",

View File

@@ -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", "")

View File

@@ -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

View File

@@ -0,0 +1,237 @@
/* SPDX-FileCopyrightText: 2024 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <numeric>
#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<decl::Int>("Value");
b.add_input<decl::Int>("Value", "Value_001");
b.add_input<decl::Int>("Value", "Value_002");
b.add_output<decl::Int>("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<bNodeSocket *>(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 &params)
{
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 &params)
{
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<int, int, int>(
"Add", [](int a, int b) { return a + b; }, exec_preset);
static auto sub_fn = mf::build::SI2_SO<int, int, int>(
"Subtract", [](int a, int b) { return a - b; }, exec_preset);
static auto multiply_fn = mf::build::SI2_SO<int, int, int>(
"Multiply", [](int a, int b) { return a * b; }, exec_preset);
static auto divide_fn = mf::build::SI2_SO<int, int, int>(
"Divide", [](int a, int b) { return math::safe_divide(a, b); }, exec_preset);
static auto divide_floor_fn = mf::build::SI2_SO<int, int, int>(
"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<int, int, int>(
"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<int, int, int>(
"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<int, int, int>(
"Power", [](int a, int b) { return math::pow(a, b); }, exec_preset);
static auto madd_fn = mf::build::SI3_SO<int, int, int, int>(
"Multiply Add", [](int a, int b, int c) { return a * b + c; }, exec_preset);
static auto floored_mod_fn = mf::build::SI2_SO<int, int, int>(
"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<int, int, int>(
"Modulo", [](int a, int b) { return b != 0 ? a % b : 0; }, exec_preset);
static auto abs_fn = mf::build::SI1_SO<int, int>(
"Absolute", [](int a) { return math::abs(a); }, exec_preset);
static auto sign_fn = mf::build::SI1_SO<int, int>(
"Sign", [](int a) { return math::sign(a); }, exec_preset);
static auto min_fn = mf::build::SI2_SO<int, int, int>(
"Minimum", [](int a, int b) { return math::min(a, b); }, exec_preset);
static auto max_fn = mf::build::SI2_SO<int, int, int>(
"Maximum", [](int a, int b) { return math::max(a, b); }, exec_preset);
static auto gcd_fn = mf::build::SI2_SO<int, int, int>(
"GCD", [](int a, int b) { return std::gcd(a, b); }, exec_preset);
static auto lcm_fn = mf::build::SI2_SO<int, int, int>(
"LCM", [](int a, int b) { return std::lcm(a, b); }, exec_preset);
static auto negate_fn = mf::build::SI1_SO<int, int>(
"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 &divide_fn;
case NODE_INTEGER_MATH_DIVIDE_FLOOR:
return &divide_floor_fn;
case NODE_INTEGER_MATH_DIVIDE_CEIL:
return &divide_ceil_fn;
case NODE_INTEGER_MATH_DIVIDE_ROUND:
return &divide_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

View File

@@ -51,7 +51,7 @@ static void sh_node_math_gather_link_searches(GatherLinkSearchOpParams &params)
}
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++)