diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 877a3f7a2b5..3f421f437a2 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -674,6 +674,7 @@ class NODE_MT_category_GEO_UTILITIES_MATH(Menu): node_add_menu.add_node_type(layout, "FunctionNodeCompare") node_add_menu.add_node_type(layout, "ShaderNodeFloatCurve") node_add_menu.add_node_type(layout, "FunctionNodeFloatToInt") + node_add_menu.add_node_type(layout, "FunctionNodeHashValue") node_add_menu.add_node_type(layout, "ShaderNodeMapRange") node_add_menu.add_node_type(layout, "ShaderNodeMath") node_add_menu.add_node_type(layout, "ShaderNodeMix") diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index efc44b1f92c..b52540a1bd1 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1424,6 +1424,7 @@ void node_tree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index); #define FN_NODE_SEPARATE_MATRIX 1242 #define FN_NODE_INPUT_ROTATION 1243 #define FN_NODE_AXES_TO_ROTATION 1244 +#define FN_NODE_HASH_VALUE 1245 /** \} */ diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh index be65f0476eb..a3cf379fc68 100644 --- a/source/blender/blenlib/BLI_noise.hh +++ b/source/blender/blenlib/BLI_noise.hh @@ -4,6 +4,7 @@ #pragma once +#include "BLI_math_matrix_types.hh" #include "BLI_math_vector_types.hh" namespace blender::noise { @@ -28,6 +29,7 @@ uint32_t hash_float(float kx); uint32_t hash_float(float2 k); uint32_t hash_float(float3 k); uint32_t hash_float(float4 k); +uint32_t hash_float(const float4x4 &k); /* Hash integers to `float` between 0 and 1. */ diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc index 0344f88e4c0..5b72f2c9208 100644 --- a/source/blender/blenlib/intern/noise.cc +++ b/source/blender/blenlib/intern/noise.cc @@ -10,6 +10,7 @@ #include "BLI_math_base.hh" #include "BLI_math_base_safe.h" +#include "BLI_math_matrix_types.hh" #include "BLI_math_numbers.hh" #include "BLI_math_vector.hh" #include "BLI_noise.hh" @@ -150,6 +151,11 @@ uint32_t hash_float(float4 k) return hash(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); } +uint32_t hash_float(const float4x4 &k) +{ + return hash(hash_float(k.x), hash_float(k.y), hash_float(k.z), hash_float(k.w)); +} + /* Hashing a number of uint32_t into a float in the range [0, 1]. */ BLI_INLINE float uint_to_float_01(uint32_t k) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 78c2f2f329b..c5ada9a678b 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -277,6 +277,7 @@ DefNode(FunctionNode, FN_NODE_COMBINE_TRANSFORM, 0, "COMBINE_TRANSFORM", Combine DefNode(FunctionNode, FN_NODE_COMPARE, 0, "COMPARE", Compare, "Compare", "") DefNode(FunctionNode, FN_NODE_EULER_TO_ROTATION, 0, "EULER_TO_ROTATION", EulerToRotation, "Euler to Rotation", "") DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") +DefNode(FunctionNode, FN_NODE_HASH_VALUE, 0, "HASH_VALUE", HashValue, "Hash Value", "") DefNode(FunctionNode, FN_NODE_INPUT_BOOL, def_fn_input_bool, "INPUT_BOOL", InputBool, "Boolean", "") DefNode(FunctionNode, FN_NODE_INPUT_COLOR, def_fn_input_color, "INPUT_COLOR", InputColor, "Color", "") DefNode(FunctionNode, FN_NODE_INPUT_INT, def_fn_input_int, "INPUT_INT", InputInt, "Integer", "") diff --git a/source/blender/nodes/function/CMakeLists.txt b/source/blender/nodes/function/CMakeLists.txt index fc805fd8ada..8f00736292d 100644 --- a/source/blender/nodes/function/CMakeLists.txt +++ b/source/blender/nodes/function/CMakeLists.txt @@ -29,6 +29,7 @@ set(SRC nodes/node_fn_compare.cc nodes/node_fn_euler_to_rotation.cc nodes/node_fn_float_to_int.cc + nodes/node_fn_hash_value.cc nodes/node_fn_input_bool.cc nodes/node_fn_input_color.cc nodes/node_fn_input_int.cc diff --git a/source/blender/nodes/function/nodes/node_fn_hash_value.cc b/source/blender/nodes/function/nodes/node_fn_hash_value.cc new file mode 100644 index 00000000000..bec4d4f308a --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_hash_value.cc @@ -0,0 +1,192 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_hash.h" +#include "BLI_math_matrix_types.hh" +#include "BLI_math_quaternion.hh" +#include "BLI_noise.hh" + +#include "NOD_rna_define.hh" +#include "NOD_socket.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "node_function_util.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +namespace blender::nodes::node_fn_hash_value_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + + const bNode *node = b.node_or_null(); + if (node) { + const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); + b.add_input(data_type, "Value"); + } + b.add_input("Seed", "Seed"); + b.add_output("Hash"); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = SOCK_INT; +} + +static const mf::MultiFunction *get_multi_function(const bNode &bnode) +{ + const eNodeSocketDatatype socket_type = static_cast(bnode.custom1); + + static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle(); + + static auto fn_hash_float = mf::build::SI2_SO( + "Hash Float", + [](float a, int seed) { return noise::hash(noise::hash_float(a), seed); }, + exec_preset); + static auto fn_hash_vector = mf::build::SI2_SO( + "Hash Vector", + [](float3 a, int seed) { return noise::hash(noise::hash_float(a), seed); }, + exec_preset); + static auto fn_hash_color = mf::build::SI2_SO( + "Hash Color", + [](ColorGeometry4f a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); }, + exec_preset); + static auto fn_hash_int = mf::build::SI2_SO( + "Hash Integer", + [](int a, int seed) { return noise::hash(noise::hash(a), seed); }, + exec_preset); + static auto fn_hash_string = mf::build::SI2_SO( + "Hash String", + [](std::string a, int seed) { return noise::hash(BLI_hash_string(a.c_str()), seed); }, + exec_preset); + static auto fn_hash_rotation = mf::build::SI2_SO( + "Hash Rotation", + [](math::Quaternion a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); }, + exec_preset); + static auto fn_hash_matrix = mf::build::SI2_SO( + "Hash Matrix", + [](float4x4 a, int seed) { return noise::hash(noise::hash_float(a), seed); }, + exec_preset); + + switch (socket_type) { + case SOCK_MATRIX: + return &fn_hash_matrix; + case SOCK_ROTATION: + return &fn_hash_rotation; + case SOCK_STRING: + return &fn_hash_string; + case SOCK_FLOAT: + return &fn_hash_float; + case SOCK_VECTOR: + return &fn_hash_vector; + case SOCK_RGBA: + return &fn_hash_color; + case SOCK_INT: + return &fn_hash_int; + default: + BLI_assert_unreachable(); + return nullptr; + } +} + +class SocketSearchOp { + public: + const StringRef socket_name; + eNodeSocketDatatype socket_type; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("FunctionNodeHashValue"); + node.custom1 = socket_type; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +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_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type); + if (!ELEM(socket_type, + SOCK_BOOLEAN, + SOCK_FLOAT, + SOCK_INT, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_VECTOR, + SOCK_STRING, + SOCK_RGBA)) + { + return; + } + + if (params.in_out() == SOCK_IN) { + if (socket_type == SOCK_BOOLEAN) { + socket_type = SOCK_INT; + } + params.add_item(IFACE_("Value"), SocketSearchOp{"Value", socket_type}); + params.add_item(IFACE_("Seed"), SocketSearchOp{"Seed", SOCK_INT}); + } + else { + if (!ELEM(socket_type, SOCK_STRING)) { + const int weight = ELEM(params.other_socket().type, SOCK_INT) ? 0 : -1; + params.add_item(IFACE_("Hash"), SocketSearchOp{"Hash", SOCK_INT}, weight); + } + } +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum( + srna, + "data_type", + "Data Type", + "", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_INT, + [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) { + *r_free = true; + return enum_items_filter(rna_enum_node_socket_data_type_items, + [](const EnumPropertyItem &item) -> bool { + return ELEM(item.value, + SOCK_FLOAT, + SOCK_INT, + SOCK_MATRIX, + SOCK_ROTATION, + SOCK_VECTOR, + SOCK_STRING, + SOCK_RGBA); + }); + }); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + fn_node_type_base(&ntype, FN_NODE_HASH_VALUE, "Hash Value", NODE_CLASS_CONVERTER); + ntype.declare = node_declare; + ntype.initfunc = node_init; + 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_hash_value_cc