/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_attribute_math.hh" #include "BKE_mesh.h" #include "BKE_type_conversions.hh" #include "UI_interface.h" #include "UI_resources.h" #include "GEO_reverse_uv_sampler.hh" #include "NOD_socket_search_link.hh" #include "node_geometry_util.hh" namespace blender::nodes::node_geo_sample_uv_surface_cc { using geometry::ReverseUVSampler; static void node_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); b.add_input(N_("Value"), "Value_Float").hide_value().supports_field(); b.add_input(N_("Value"), "Value_Int").hide_value().supports_field(); b.add_input(N_("Value"), "Value_Vector").hide_value().supports_field(); b.add_input(N_("Value"), "Value_Color").hide_value().supports_field(); b.add_input(N_("Value"), "Value_Bool").hide_value().supports_field(); b.add_input(N_("Source UV Map")) .hide_value() .supports_field() .description(N_("The mesh UV map to sample. Should not have overlapping faces")); b.add_input(N_("Sample UV")) .supports_field() .description(N_("The coordinates to sample within the UV map")); b.add_output(N_("Value"), "Value_Float").dependent_field({7}); b.add_output(N_("Value"), "Value_Int").dependent_field({7}); b.add_output(N_("Value"), "Value_Vector").dependent_field({7}); b.add_output(N_("Value"), "Value_Color").dependent_field({7}); b.add_output(N_("Value"), "Value_Bool").dependent_field({7}); b.add_output(N_("Is Valid")) .dependent_field({7}) .description(N_("Whether the node could find a single face to sample at the UV coordinate")); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); } static void node_init(bNodeTree * /*tree*/, bNode *node) { node->custom1 = CD_PROP_FLOAT; } static void node_update(bNodeTree *ntree, bNode *node) { const eCustomDataType data_type = eCustomDataType(node->custom1); bNodeSocket *in_socket_mesh = static_cast(node->inputs.first); bNodeSocket *in_socket_float = in_socket_mesh->next; bNodeSocket *in_socket_int32 = in_socket_float->next; bNodeSocket *in_socket_vector = in_socket_int32->next; bNodeSocket *in_socket_color4f = in_socket_vector->next; bNodeSocket *in_socket_bool = in_socket_color4f->next; nodeSetSocketAvailability(ntree, in_socket_vector, data_type == CD_PROP_FLOAT3); nodeSetSocketAvailability(ntree, in_socket_float, data_type == CD_PROP_FLOAT); nodeSetSocketAvailability(ntree, in_socket_color4f, data_type == CD_PROP_COLOR); nodeSetSocketAvailability(ntree, in_socket_bool, data_type == CD_PROP_BOOL); nodeSetSocketAvailability(ntree, in_socket_int32, data_type == CD_PROP_INT32); bNodeSocket *out_socket_float = static_cast(node->outputs.first); bNodeSocket *out_socket_int32 = out_socket_float->next; bNodeSocket *out_socket_vector = out_socket_int32->next; bNodeSocket *out_socket_color4f = out_socket_vector->next; bNodeSocket *out_socket_bool = out_socket_color4f->next; nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); nodeSetSocketAvailability(ntree, out_socket_bool, data_type == CD_PROP_BOOL); nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); } static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs().take_back(2)); search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); search_link_ops_for_declarations(params, declaration.outputs().take_back(1)); const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ params.add_item(IFACE_("Value"), [type](LinkSearchOpParams ¶ms) { bNode &node = params.add_node("GeometryNodeSampleUVSurface"); node.custom1 = *type; params.update_and_connect_available_socket(node, "Value"); }); } } class SampleUVSurfaceFunction : public fn::MultiFunction { GeometrySet source_; Field src_uv_map_field_; GField src_field_; /** * Use the most complex domain for now ensuring no information is lost. In the future, it should * be possible to use the most complex domain required by the field inputs, to simplify sampling * and avoid domain conversions. */ eAttrDomain domain_ = ATTR_DOMAIN_CORNER; fn::MFSignature signature_; std::optional source_context_; std::unique_ptr source_evaluator_; const GVArray *source_data_; VArraySpan source_uv_map_; std::optional reverse_uv_sampler_; public: SampleUVSurfaceFunction(GeometrySet geometry, Field src_uv_map_field, GField src_field) : source_(std::move(geometry)), src_uv_map_field_(std::move(src_uv_map_field)), src_field_(std::move(src_field)) { source_.ensure_owns_direct_data(); signature_ = this->create_signature(); this->set_signature(&signature_); this->evaluate_source(); } fn::MFSignature create_signature() { blender::fn::MFSignatureBuilder signature{"Sample UV Surface"}; signature.single_input("Sample UV"); signature.single_output("Value", src_field_.cpp_type()); signature.single_output("Is Valid"); return signature.build(); } void call(IndexMask mask, fn::MFParams params, fn::MFContext /*context*/) const override { const VArray &sample_uvs = params.readonly_single_input(0, "Sample UV"); GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Value"); MutableSpan valid_dst = params.uninitialized_single_output_if_required(2, "Is Valid"); const CPPType &type = src_field_.cpp_type(); attribute_math::convert_to_static_type(type, [&](auto dummy) { using T = decltype(dummy); const VArray src_typed = source_data_->typed(); MutableSpan dst_typed = dst.typed(); for (const int i : mask) { const float2 sample_uv = sample_uvs[i]; const ReverseUVSampler::Result result = reverse_uv_sampler_->sample(sample_uv); const bool valid = result.type == ReverseUVSampler::ResultType::Ok; if (!dst_typed.is_empty()) { if (valid) { dst_typed[i] = attribute_math::mix3(result.bary_weights, src_typed[result.looptri->tri[0]], src_typed[result.looptri->tri[1]], src_typed[result.looptri->tri[2]]); } else { dst_typed[i] = {}; } } if (!valid_dst.is_empty()) { valid_dst[i] = valid; } } }); } private: void evaluate_source() { const Mesh &mesh = *source_.get_mesh_for_read(); source_context_.emplace(bke::MeshFieldContext{mesh, domain_}); const int domain_size = mesh.attributes().domain_size(domain_); source_evaluator_ = std::make_unique(*source_context_, domain_size); source_evaluator_->add(src_uv_map_field_); source_evaluator_->add(src_field_); source_evaluator_->evaluate(); source_uv_map_ = source_evaluator_->get_evaluated(0); source_data_ = &source_evaluator_->get_evaluated(1); reverse_uv_sampler_.emplace(source_uv_map_, mesh.looptris()); } }; static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustomDataType data_type) { switch (data_type) { case CD_PROP_FLOAT: return params.extract_input>("Value_Float"); case CD_PROP_FLOAT3: return params.extract_input>("Value_Vector"); case CD_PROP_COLOR: return params.extract_input>("Value_Color"); case CD_PROP_BOOL: return params.extract_input>("Value_Bool"); case CD_PROP_INT32: return params.extract_input>("Value_Int"); default: BLI_assert_unreachable(); } return {}; } static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) { switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { case CD_PROP_FLOAT: { params.set_output("Value_Float", Field(field)); break; } case CD_PROP_FLOAT3: { params.set_output("Value_Vector", Field(field)); break; } case CD_PROP_COLOR: { params.set_output("Value_Color", Field(field)); break; } case CD_PROP_BOOL: { params.set_output("Value_Bool", Field(field)); break; } case CD_PROP_INT32: { params.set_output("Value_Int", Field(field)); break; } default: break; } } static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry = params.extract_input("Mesh"); const eCustomDataType data_type = eCustomDataType(params.node().custom1); const Mesh *mesh = geometry.get_mesh_for_read(); if (mesh == nullptr) { params.set_default_remaining_outputs(); return; } if (mesh->totpoly == 0 && mesh->totvert != 0) { params.error_message_add(NodeWarningType::Error, TIP_("The source mesh must have faces")); params.set_default_remaining_outputs(); return; } const CPPType &float2_type = CPPType::get(); const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); Field source_uv_map = conversions.try_convert( params.extract_input>("Source UV Map"), float2_type); GField field = get_input_attribute_field(params, data_type); Field sample_uvs = conversions.try_convert( params.extract_input>("Sample UV"), float2_type); auto fn = std::make_shared( std::move(geometry), std::move(source_uv_map), std::move(field)); auto op = FieldOperation::Create(std::move(fn), {std::move(sample_uvs)}); output_attribute_field(params, GField(op, 0)); params.set_output("Is Valid", Field(op, 1)); } } // namespace blender::nodes::node_geo_sample_uv_surface_cc void register_node_type_geo_sample_uv_surface() { namespace file_ns = blender::nodes::node_geo_sample_uv_surface_cc; static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_SAMPLE_UV_SURFACE, "Sample UV Surface", NODE_CLASS_GEOMETRY); node_type_init(&ntype, file_ns::node_init); node_type_update(&ntype, file_ns::node_update); ntype.declare = file_ns::node_declare; ntype.geometry_node_execute = file_ns::node_geo_exec; ntype.draw_buttons = file_ns::node_layout; ntype.gather_link_search_ops = file_ns::node_gather_link_searches; nodeRegisterType(&ntype); }