Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
| Show First 20 Lines • Show All 299 Lines • ▼ Show 20 Lines | void copy_with_indices(const VArray<T> &src, | ||||
| for (const int i : mask) { | for (const int i : mask) { | ||||
| dst[i] = src[indices[i]]; | dst[i] = src[indices[i]]; | ||||
| } | } | ||||
| } | } | ||||
| template<typename T> | template<typename T> | ||||
| void copy_with_indices_clamped(const VArray<T> &src, | void copy_with_indices_clamped(const VArray<T> &src, | ||||
| const IndexMask mask, | const IndexMask mask, | ||||
| const Span<int> indices, | const VArray<int> &indices, | ||||
| const MutableSpan<T> dst) | const MutableSpan<T> dst) | ||||
| { | { | ||||
| if (src.is_empty()) { | if (src.is_empty()) { | ||||
| return; | return; | ||||
| } | } | ||||
| const int max_index = src.size() - 1; | const int max_index = src.size() - 1; | ||||
| threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { | threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { | ||||
| for (const int i : range) { | for (const int i : range) { | ||||
| ▲ Show 20 Lines • Show All 265 Lines • ▼ Show 20 Lines | if (use_points_) { | ||||
| point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, domain_size); | point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, domain_size); | ||||
| point_evaluator_->add(src_field_); | point_evaluator_->add(src_field_); | ||||
| point_evaluator_->evaluate(); | point_evaluator_->evaluate(); | ||||
| point_data_ = &point_evaluator_->get_evaluated(0); | point_data_ = &point_evaluator_->get_evaluated(0); | ||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| static const GeometryComponent *find_best_match_component(const GeometrySet &geometry, | static const GeometryComponent *find_best_match_component(const GeometrySet &geometry, | ||||
HooglyBoogly: I'd call this `find_target_component`, since there's no matching going on anymore. | |||||
| const GeometryComponentType type, | |||||
| const AttributeDomain domain) | const AttributeDomain domain) | ||||
| { | { | ||||
| /* Prefer transferring from the same component type, if it exists. */ | /* Choose the other component based on a consistent order, rather than some more complicated | ||||
| if (component_is_available(geometry, type, domain)) { | * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */ | ||||
| return geometry.get_component_for_read(type); | |||||
| } | |||||
| /* If there is no component of the same type, choose the other component based on a consistent | |||||
| * order, rather than some more complicated heuristic. This is the same order visible in the | |||||
| * spreadsheet and used in the ray-cast node. */ | |||||
| static const Array<GeometryComponentType> supported_types = { | static const Array<GeometryComponentType> supported_types = { | ||||
| GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; | GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; | ||||
| for (const GeometryComponentType src_type : supported_types) { | for (const GeometryComponentType src_type : supported_types) { | ||||
| if (component_is_available(geometry, src_type, domain)) { | if (component_is_available(geometry, src_type, domain)) { | ||||
| return geometry.get_component_for_read(src_type); | return geometry.get_component_for_read(src_type); | ||||
| } | } | ||||
| } | } | ||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| /** | /** | ||||
| * Use a #FieldInput because it's necessary to know the field context in order to choose the | |||||
| * corresponding component type from the input geometry, and only a #FieldInput receives the | |||||
| * evaluation context to provide its data. | |||||
| * | |||||
| * The index-based transfer theoretically does not need realized data when there is only one | * The index-based transfer theoretically does not need realized data when there is only one | ||||
| * instance geometry set in the target. A future optimization could be removing that limitation | * instance geometry set in the target. A future optimization could be removing that limitation | ||||
| * internally. | * internally. | ||||
| */ | */ | ||||
| class IndexTransferFieldInput : public FieldInput { | class IndexTransferFunction : public fn::MultiFunction { | ||||
| GeometrySet src_geometry_; | GeometrySet src_geometry_; | ||||
| GField src_field_; | GField src_field_; | ||||
| Field<int> index_field_; | |||||
| AttributeDomain domain_; | AttributeDomain domain_; | ||||
| fn::MFSignature signature_; | |||||
| std::optional<GeometryComponentFieldContext> geometry_context_; | |||||
| std::unique_ptr<FieldEvaluator> evaluator_; | |||||
| const GVArray *src_data_ = nullptr; | |||||
| public: | public: | ||||
| IndexTransferFieldInput(GeometrySet geometry, | IndexTransferFunction(GeometrySet geometry, GField src_field, const AttributeDomain domain) | ||||
| GField src_field, | : src_geometry_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain) | ||||
| Field<int> index_field, | |||||
| const AttributeDomain domain) | |||||
| : FieldInput(src_field.cpp_type(), "Attribute Transfer node"), | |||||
| src_geometry_(std::move(geometry)), | |||||
| src_field_(std::move(src_field)), | |||||
| index_field_(std::move(index_field)), | |||||
| domain_(domain) | |||||
| { | { | ||||
| src_geometry_.ensure_owns_direct_data(); | src_geometry_.ensure_owns_direct_data(); | ||||
| category_ = Category::Generated; | |||||
| signature_ = this->create_signature(); | |||||
| this->set_signature(&signature_); | |||||
| this->evaluate_field(); | |||||
| } | } | ||||
| const GVArray *get_varray_for_context(const FieldContext &context, | fn::MFSignature create_signature() | ||||
| const IndexMask mask, | |||||
| ResourceScope &scope) const final | |||||
| { | { | ||||
| const GeometryComponentFieldContext *geometry_context = | fn::MFSignatureBuilder signature{"Attribute Transfer Index"}; | ||||
| dynamic_cast<const GeometryComponentFieldContext *>(&context); | signature.single_input<int>("Index"); | ||||
| if (geometry_context == nullptr) { | signature.single_output("Attribute", src_field_.cpp_type()); | ||||
| return nullptr; | return signature.build(); | ||||
| } | } | ||||
| FieldEvaluator index_evaluator{*geometry_context, &mask}; | void evaluate_field() | ||||
| index_evaluator.add(index_field_); | { | ||||
| index_evaluator.evaluate(); | const GeometryComponent *component = find_best_match_component(src_geometry_, domain_); | ||||
| const VArray<int> &index_varray = index_evaluator.get_evaluated<int>(0); | |||||
| /* The index virtual array is expected to be a span, since transferring the same index for | |||||
| * every element is not very useful. */ | |||||
| VArray_Span<int> indices{index_varray}; | |||||
| const GeometryComponent *component = find_best_match_component( | |||||
| src_geometry_, geometry_context->geometry_component().type(), domain_); | |||||
| if (component == nullptr) { | if (component == nullptr) { | ||||
| return nullptr; | return; | ||||
| } | |||||
| const int domain_size = component->attribute_domain_size(domain_); | |||||
| geometry_context_.emplace(GeometryComponentFieldContext(*component, domain_)); | |||||
| evaluator_ = std::make_unique<FieldEvaluator>(*geometry_context_, domain_size); | |||||
| evaluator_->add(src_field_); | |||||
| evaluator_->evaluate(); | |||||
| src_data_ = &evaluator_->get_evaluated(0); | |||||
| } | } | ||||
| GeometryComponentFieldContext target_context{*component, domain_}; | void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override | ||||
| /* A potential improvement is to only copy the necessary values | { | ||||
| * based on the indices retrieved from the index input field. */ | const VArray<int> &indices = params.readonly_single_input<int>(0, "Index"); | ||||
| FieldEvaluator target_evaluator{target_context, component->attribute_domain_size(domain_)}; | GMutableSpan dst = params.uninitialized_single_output(1, "Attribute"); | ||||
| target_evaluator.add(src_field_); | |||||
| target_evaluator.evaluate(); | |||||
| const GVArray &src_data = target_evaluator.get_evaluated(0); | |||||
| GArray dst(src_field_.cpp_type(), mask.min_array_size()); | const CPPType &type = dst.type(); | ||||
| if (src_data_ == nullptr) { | |||||
| type.fill_construct_indices(type.default_value(), dst.data(), mask); | |||||
| return; | |||||
| } | |||||
| attribute_math::convert_to_static_type(src_data.type(), [&](auto dummy) { | attribute_math::convert_to_static_type(type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | using T = decltype(dummy); | ||||
| GVArray_Typed<T> src{src_data}; | GVArray_Typed<T> src{*src_data_}; | ||||
| copy_with_indices_clamped(*src, mask, indices, dst.as_mutable_span().typed<T>()); | copy_with_indices_clamped(*src, mask, indices, dst.typed<T>()); | ||||
| }); | }); | ||||
| return &scope.construct<fn::GVArray_For_GArray>(std::move(dst)); | |||||
| } | } | ||||
| }; | }; | ||||
| static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const CustomDataType data_type) | static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const CustomDataType data_type) | ||||
| { | { | ||||
| switch (data_type) { | switch (data_type) { | ||||
| case CD_PROP_FLOAT: | case CD_PROP_FLOAT: | ||||
| return params.extract_input<Field<float>>("Attribute_001"); | return params.extract_input<Field<float>>("Attribute_001"); | ||||
| ▲ Show 20 Lines • Show All 95 Lines • ▼ Show 20 Lines | case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { | ||||
| std::move(geometry), std::move(field), domain); | std::move(geometry), std::move(field), domain); | ||||
| auto op = std::make_shared<FieldOperation>( | auto op = std::make_shared<FieldOperation>( | ||||
| FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); | FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); | ||||
| output_field = GField(std::move(op)); | output_field = GField(std::move(op)); | ||||
| break; | break; | ||||
| } | } | ||||
| case GEO_NODE_ATTRIBUTE_TRANSFER_INDEX: { | case GEO_NODE_ATTRIBUTE_TRANSFER_INDEX: { | ||||
| Field<int> indices = params.extract_input<Field<int>>("Index"); | Field<int> indices = params.extract_input<Field<int>>("Index"); | ||||
| std::shared_ptr<FieldInput> input = std::make_shared<IndexTransferFieldInput>( | auto fn = std::make_unique<IndexTransferFunction>( | ||||
| std::move(geometry), std::move(field), std::move(indices), domain); | std::move(geometry), std::move(field), domain); | ||||
| output_field = GField(std::move(input)); | auto op = std::make_shared<FieldOperation>( | ||||
| FieldOperation(std::move(fn), {std::move(indices)})); | |||||
| output_field = GField(std::move(op)); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| output_attribute_field(params, std::move(output_field)); | output_attribute_field(params, std::move(output_field)); | ||||
| } | } | ||||
| } // namespace blender::nodes | } // namespace blender::nodes | ||||
| Show All 18 Lines | |||||
I'd call this find_target_component, since there's no matching going on anymore.