/* * 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. */ #include "UI_interface.h" #include "UI_resources.h" #include "NOD_math_functions.hh" #include "node_geometry_util.hh" namespace blender::nodes { static void geo_node_attribute_compare_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Geometry")); b.add_input(N_("A")); b.add_input(N_("A"), "A_001"); b.add_input(N_("A"), "A_002"); b.add_input(N_("A"), "A_003").default_value({0.5, 0.5, 0.5, 1.0}); b.add_input(N_("B")); b.add_input(N_("B"), "B_001"); b.add_input(N_("B"), "B_002"); b.add_input(N_("B"), "B_003").default_value({0.5, 0.5, 0.5, 1.0}); b.add_input(N_("Threshold")).default_value(0.01f).min(0.0f); b.add_input(N_("Result")); b.add_output(N_("Geometry")); } static void geo_node_attribute_compare_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "input_type_a", 0, IFACE_("A"), ICON_NONE); uiItemR(layout, ptr, "input_type_b", 0, IFACE_("B"), ICON_NONE); } static void geo_node_attribute_compare_init(bNodeTree *UNUSED(tree), bNode *node) { NodeAttributeCompare *data = (NodeAttributeCompare *)MEM_callocN(sizeof(NodeAttributeCompare), __func__); data->operation = NODE_FLOAT_COMPARE_GREATER_THAN; data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; node->storage = data; } static bool operation_tests_equality(const NodeAttributeCompare &node_storage) { return ELEM(node_storage.operation, NODE_FLOAT_COMPARE_EQUAL, NODE_FLOAT_COMPARE_NOT_EQUAL); } static void geo_node_attribute_compare_update(bNodeTree *ntree, bNode *node) { NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node->storage; update_attribute_input_socket_availabilities( *ntree, *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); update_attribute_input_socket_availabilities( *ntree, *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); bNodeSocket *socket_threshold = (bNodeSocket *)BLI_findlink(&node->inputs, 9); nodeSetSocketAvailability(ntree, socket_threshold, operation_tests_equality(*node_storage)); } static void do_math_operation(const VArray &input_a, const VArray &input_b, const FloatCompareOperation operation, MutableSpan span_result) { const int size = input_a.size(); if (try_dispatch_float_math_fl_fl_to_bool( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { for (const int i : IndexRange(size)) { const float a = input_a[i]; const float b = input_b[i]; const bool out = math_function(a, b); span_result[i] = out; } })) { return; } /* The operation is not supported by this node currently. */ BLI_assert(false); } static void do_equal_operation_float(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const int size = input_a.size(); for (const int i : IndexRange(size)) { const float a = input_a[i]; const float b = input_b[i]; span_result[i] = compare_ff(a, b, threshold); } } static void do_equal_operation_float3(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { const float3 a = input_a[i]; const float3 b = input_b[i]; span_result[i] = len_squared_v3v3(a, b) < threshold_squared; } } static void do_equal_operation_color4f(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { const ColorGeometry4f a = input_a[i]; const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) < threshold_squared; } } static void do_equal_operation_bool(const VArray &input_a, const VArray &input_b, const float UNUSED(threshold), MutableSpan span_result) { const int size = input_a.size(); for (const int i : IndexRange(size)) { const bool a = input_a[i]; const bool b = input_b[i]; span_result[i] = a == b; } } static void do_not_equal_operation_float(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const int size = input_a.size(); for (const int i : IndexRange(size)) { const float a = input_a[i]; const float b = input_b[i]; span_result[i] = !compare_ff(a, b, threshold); } } static void do_not_equal_operation_float3(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { const float3 a = input_a[i]; const float3 b = input_b[i]; span_result[i] = len_squared_v3v3(a, b) >= threshold_squared; } } static void do_not_equal_operation_color4f(const VArray &input_a, const VArray &input_b, const float threshold, MutableSpan span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { const ColorGeometry4f a = input_a[i]; const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) >= threshold_squared; } } static void do_not_equal_operation_bool(const VArray &input_a, const VArray &input_b, const float UNUSED(threshold), MutableSpan span_result) { const int size = input_a.size(); for (const int i : IndexRange(size)) { const bool a = input_a[i]; const bool b = input_b[i]; span_result[i] = a != b; } } static CustomDataType get_data_type(GeometryComponent &component, const GeoNodeExecParams ¶ms, const NodeAttributeCompare &node_storage) { if (operation_tests_equality(node_storage)) { /* Convert the input attributes to the same data type for the equality tests. Use the higher * complexity attribute type, otherwise information necessary to the comparison may be lost. */ return bke::attribute_data_type_highest_complexity({ params.get_input_attribute_data_type("A", component, CD_PROP_FLOAT), params.get_input_attribute_data_type("B", component, CD_PROP_FLOAT), }); } /* Use float compare for every operation besides equality. */ return CD_PROP_FLOAT; } static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ std::optional result_info = component.attribute_get_meta_data(result_name); if (result_info) { return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ return params.get_highest_priority_input_domain({"A", "B"}, component, ATTR_DOMAIN_POINT); } static void attribute_compare_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node.storage; const FloatCompareOperation operation = static_cast( node_storage->operation); const std::string result_name = params.get_input("Result"); const AttributeDomain result_domain = get_result_domain(component, params, result_name); OutputAttribute_Typed attribute_result = component.attribute_try_get_for_output_only( result_name, result_domain); if (!attribute_result) { return; } const CustomDataType input_data_type = get_data_type(component, params, *node_storage); GVArray attribute_a = params.get_input_attribute( "A", component, result_domain, input_data_type, nullptr); GVArray attribute_b = params.get_input_attribute( "B", component, result_domain, input_data_type, nullptr); if (!attribute_a || !attribute_b) { /* Attribute wasn't found. */ return; } MutableSpan result_span = attribute_result.as_span(); /* Use specific types for correct equality operations, but for other operations we use implicit * conversions and float comparison. In other words, the comparison is not element-wise. */ if (operation_tests_equality(*node_storage)) { const float threshold = params.get_input("Threshold"); if (operation == NODE_FLOAT_COMPARE_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { do_equal_operation_float( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { do_equal_operation_float3( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { do_equal_operation_color4f(attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_BOOL) { do_equal_operation_bool( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } } else if (operation == NODE_FLOAT_COMPARE_NOT_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { do_not_equal_operation_float( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { do_not_equal_operation_float3( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { do_not_equal_operation_color4f(attribute_a.typed(), attribute_b.typed(), threshold, result_span); } else if (input_data_type == CD_PROP_BOOL) { do_not_equal_operation_bool( attribute_a.typed(), attribute_b.typed(), threshold, result_span); } } } else { do_math_operation( attribute_a.typed(), attribute_b.typed(), operation, result_span); } attribute_result.save(); } static void geo_node_attribute_compare_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); geometry_set = geometry_set_realize_instances(geometry_set); if (geometry_set.has()) { attribute_compare_calc(geometry_set.get_component_for_write(), params); } if (geometry_set.has()) { attribute_compare_calc(geometry_set.get_component_for_write(), params); } if (geometry_set.has()) { attribute_compare_calc(geometry_set.get_component_for_write(), params); } params.set_output("Geometry", geometry_set); } } // namespace blender::nodes void register_node_type_geo_attribute_compare() { static bNodeType ntype; geo_node_type_base( &ntype, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0); ntype.declare = blender::nodes::geo_node_attribute_compare_declare; ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec; ntype.draw_buttons = blender::nodes::geo_node_attribute_compare_layout; node_type_update(&ntype, blender::nodes::geo_node_attribute_compare_update); node_type_storage( &ntype, "NodeAttributeCompare", node_free_standard_storage, node_copy_standard_storage); node_type_init(&ntype, blender::nodes::geo_node_attribute_compare_init); nodeRegisterType(&ntype); }