From 60a6fbf5b59911cba54d30bd1105626fcc577875 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 30 Mar 2022 10:37:39 -0500 Subject: [PATCH] Curves: Port resample node to the new data-block This commit re-implements the resample curve node to use the new curves type instead of CurveEval. The largest changes come from the need to keep track of offsets into the point attribute arrays, and the fact that the attributes for all curves are stored in a flat array. Another difference is that a bit more of the logic is handled by building of the field network inputs. The idea is to let the field evaluator handle potential optimizations while making the rest of the code simpler. When resampling 1 million small poly curves,the node is about 6 times faster compared to 3.1 on my hardware (500ms to 80ms). This also adds support for Catmull Rom curve inputs. Differential Revision: https://developer.blender.org/D14435 --- source/blender/blenlib/BLI_generic_span.hh | 12 + .../nodes/geometry/node_geometry_util.hh | 10 + .../geometry/nodes/node_geo_curve_resample.cc | 741 +++++++++++++----- .../nodes/node_geo_input_spline_length.cc | 66 +- 4 files changed, 611 insertions(+), 218 deletions(-) diff --git a/source/blender/blenlib/BLI_generic_span.hh b/source/blender/blenlib/BLI_generic_span.hh index f4f93735e06..4c0bfc83ba8 100644 --- a/source/blender/blenlib/BLI_generic_span.hh +++ b/source/blender/blenlib/BLI_generic_span.hh @@ -164,6 +164,18 @@ class GMutableSpan { { return this->slice(range.start(), range.size()); } + + /** + * Copy all values from another span into this span. This invokes undefined behavior when the + * destination contains uninitialized data and T is not trivially copy constructible. + * The size of both spans is expected to be the same. + */ + void copy_from(GSpan values) + { + BLI_assert(type_ == &values.type()); + BLI_assert(size_ == values.size()); + type_->copy_assign_n(values.data(), data_, size_); + } }; } // namespace blender diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 5b7211e44b4..7af3159bbf8 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -81,4 +81,14 @@ void separate_geometry(GeometrySet &geometry_set, std::optional node_data_type_to_custom_data_type(eNodeSocketDatatype type); std::optional node_socket_to_custom_data_type(const bNodeSocket &socket); +class SplineLengthFieldInput final : public GeometryFieldInput { + public: + SplineLengthFieldInput(); + GVArray get_varray_for_context(const GeometryComponent &component, + AttributeDomain domain, + IndexMask mask) const final; + uint64_t hash() const override; + bool is_equal_to(const fn::FieldNode &other) const override; +}; + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 5a4c2ad1660..54fa56f7419 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -1,11 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_array.hh" +#include "BLI_index_mask_ops.hh" +#include "BLI_length_parameterize.hh" #include "BLI_task.hh" #include "BLI_timeit.hh" #include "BKE_attribute_math.hh" -#include "BKE_spline.hh" +#include "BKE_curves.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -23,7 +25,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input(N_("Count")).default_value(10).min(1).max(100000).supports_field(); b.add_input(N_("Length")) .default_value(0.1f) - .min(0.001f) + .min(0.01f) .supports_field() .subtype(PROP_DISTANCE); b.add_output(N_("Curve")); @@ -54,195 +56,549 @@ static void node_update(bNodeTree *ntree, bNode *node) nodeSetSocketAvailability(ntree, length_socket, mode == GEO_NODE_CURVE_RESAMPLE_LENGTH); } -struct SampleModeParam { - GeometryNodeCurveResampleMode mode; - std::optional> length; - std::optional> count; - Field selection; +/** Returns the number of evaluated points in each curve. Used to deselect curves with none. */ +class EvaluatedCountFieldInput final : public GeometryFieldInput { + public: + EvaluatedCountFieldInput() : GeometryFieldInput(CPPType::get(), "Evaluated Point Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_CURVE && domain == ATTR_DOMAIN_CURVE && + !component.is_empty()) { + const CurveComponent &curve_component = static_cast(component); + const Curves &curves_id = *curve_component.get_for_read(); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + curves.ensure_evaluated_offsets(); + return VArray::ForFunc(curves.curves_num(), [&](const int64_t index) -> int { + return curves.evaluated_points_for_curve(index).size(); + }); + } + return {}; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 234905872379865; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast(&other) != nullptr; + } }; -static SplinePtr resample_spline(const Spline &src, const int count) +/** + * Return true if the attribute should be copied/interpolated to the result curves. + * Don't output attributes that correspond to curve types that have no curves in the result. + */ +static bool interpolate_attribute_to_curves(const AttributeIDRef &attribute_id, + const std::array &type_counts) { - std::unique_ptr dst = std::make_unique(); - Spline::copy_base_settings(src, *dst); - - if (src.evaluated_edges_size() < 1 || count == 1) { - dst->resize(1); - dst->positions().first() = src.positions().first(); - dst->radii().first() = src.radii().first(); - dst->tilts().first() = src.tilts().first(); - - src.attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional src_attribute = src.attributes.get_for_read(attribute_id); - if (dst->attributes.create(attribute_id, meta_data.data_type)) { - std::optional dst_attribute = dst->attributes.get_for_write( - attribute_id); - if (dst_attribute) { - src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data()); - return true; - } - } - BLI_assert_unreachable(); - return false; - }, - ATTR_DOMAIN_POINT); - return dst; + if (!attribute_id.is_named()) { + return true; } - - dst->resize(count); - - Array uniform_samples = src.sample_uniform_index_factors(count); - - src.sample_with_index_factors( - src.evaluated_positions(), uniform_samples, dst->positions()); - - src.sample_with_index_factors( - src.interpolate_to_evaluated(src.radii()), uniform_samples, dst->radii()); - - src.sample_with_index_factors( - src.interpolate_to_evaluated(src.tilts()), uniform_samples, dst->tilts()); - - src.attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional input_attribute = src.attributes.get_for_read(attribute_id); - if (dst->attributes.create(attribute_id, meta_data.data_type)) { - std::optional output_attribute = dst->attributes.get_for_write( - attribute_id); - if (output_attribute) { - src.sample_with_index_factors(src.interpolate_to_evaluated(*input_attribute), - uniform_samples, - *output_attribute); - return true; - } - } - - BLI_assert_unreachable(); - return false; - }, - ATTR_DOMAIN_POINT); - - return dst; + if (ELEM(attribute_id.name(), + "handle_type_left", + "handle_type_right", + "handle_left", + "handle_right")) { + return type_counts[CURVE_TYPE_BEZIER] != 0; + } + if (ELEM(attribute_id.name(), "nurbs_weight")) { + return type_counts[CURVE_TYPE_NURBS] != 0; + } + return true; } -static SplinePtr resample_spline_evaluated(const Spline &src) +/** + * Return true if the attribute should be copied to poly curves. + */ +static bool interpolate_attribute_to_poly_curve(const AttributeIDRef &attribute_id) { - std::unique_ptr dst = std::make_unique(); - Spline::copy_base_settings(src, *dst); - dst->resize(src.evaluated_points_size()); + static const Set no_interpolation{{ + "handle_type_left", + "handle_type_right", + "handle_position_right", + "handle_position_left", + "nurbs_weight", + }}; + return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name())); +} - dst->positions().copy_from(src.evaluated_positions()); - dst->positions().copy_from(src.evaluated_positions()); - src.interpolate_to_evaluated(src.radii()).materialize(dst->radii()); - src.interpolate_to_evaluated(src.tilts()).materialize(dst->tilts()); +/** + * Retrieve spans from source and result attributes. + */ +static void retrieve_attribute_spans(const Span ids, + const CurveComponent &src_component, + CurveComponent &dst_component, + Vector &src, + Vector &dst, + Vector &dst_attributes) +{ + for (const int i : ids.index_range()) { + GVArray src_attribute = src_component.attribute_try_get_for_read(ids[i], ATTR_DOMAIN_POINT); + BLI_assert(src_attribute); + src.append(src_attribute.get_internal_span()); - src.attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional src_attribute = src.attributes.get_for_read(attribute_id); - if (dst->attributes.create(attribute_id, meta_data.data_type)) { - std::optional dst_attribute = dst->attributes.get_for_write(attribute_id); - if (dst_attribute) { - src.interpolate_to_evaluated(*src_attribute).materialize(dst_attribute->data()); - return true; - } + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(src_attribute.type()); + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + ids[i], ATTR_DOMAIN_POINT, data_type); + dst.append(dst_attribute.as_span()); + dst_attributes.append(std::move(dst_attribute)); + } +} + +struct AttributesForInterpolation : NonCopyable, NonMovable { + Vector src; + Vector dst; + + Vector dst_attributes; + + Vector src_no_interpolation; + Vector dst_no_interpolation; +}; + +/** + * Gather a set of all generic attribute IDs to copy to the result curves. + */ +static void gather_point_attributes_to_interpolate(const CurveComponent &src_component, + CurveComponent &dst_component, + AttributesForInterpolation &result) +{ + const Curves &dst_curves_id = *dst_component.get_for_read(); + const bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id.geometry); + const std::array type_counts = dst_curves.count_curve_types(); + + VectorSet ids; + VectorSet ids_no_interpolation; + src_component.attribute_foreach( + [&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + if (meta_data.domain != ATTR_DOMAIN_POINT) { + return true; + } + if (!interpolate_attribute_to_curves(id, type_counts)) { + return true; + } + if (interpolate_attribute_to_poly_curve(id)) { + ids.add_new(id); + } + else { + ids_no_interpolation.add_new(id); } - - BLI_assert_unreachable(); return true; - }, - ATTR_DOMAIN_POINT); + }); - return dst; + /* Position is handled differently since it has non-generic interpolation for Bezier + * curves and because the evaluated positions are cached for each evaluated point. */ + ids.remove_contained("position"); + + retrieve_attribute_spans( + ids, src_component, dst_component, result.src, result.dst, result.dst_attributes); + + /* Attributes that aren't interpolated like Bezier handles still have to be be copied + * to the result when there are any unselected curves of the corresponding type. */ + retrieve_attribute_spans(ids_no_interpolation, + src_component, + dst_component, + result.src_no_interpolation, + result.dst_no_interpolation, + result.dst_attributes); } -static std::unique_ptr resample_curve(const CurveComponent *component, - const SampleModeParam &mode_param) +/** + * Copy the provided point attribute values between all curves in the #curve_ranges index + * ranges, assuming that all curves are the same size in #src_curves and #dst_curves. + */ +template +static void copy_between_curves(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const Span curve_ranges, + const Span src, + const MutableSpan dst) { - const std::unique_ptr input_curve = curves_to_curve_eval(*component->get_for_read()); - GeometryComponentFieldContext field_context{*component, ATTR_DOMAIN_CURVE}; - const int domain_size = component->attribute_domain_size(ATTR_DOMAIN_CURVE); - - Span input_splines = input_curve->splines(); - - std::unique_ptr output_curve = std::make_unique(); - output_curve->resize(input_splines.size()); - MutableSpan output_splines = output_curve->splines(); - - if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { - fn::FieldEvaluator evaluator{field_context, domain_size}; - evaluator.add(*mode_param.count); - evaluator.add(mode_param.selection); - evaluator.evaluate(); - const VArray &cuts = evaluator.get_evaluated(0); - const VArray &selections = evaluator.get_evaluated(1); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - BLI_assert(mode_param.count); - if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { - output_splines[i] = resample_spline(*input_splines[i], std::max(cuts[i], 1)); - } - else { - output_splines[i] = input_splines[i]->copy(); - } - } - }); - } - else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { - fn::FieldEvaluator evaluator{field_context, domain_size}; - evaluator.add(*mode_param.length); - evaluator.add(mode_param.selection); - evaluator.evaluate(); - const VArray &lengths = evaluator.get_evaluated(0); - const VArray &selections = evaluator.get_evaluated(1); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { - /* Don't allow asymptotic count increase for low resolution values. */ - const float divide_length = std::max(lengths[i], 0.0001f); - const float spline_length = input_splines[i]->length(); - const int count = std::max(int(spline_length / divide_length) + 1, 1); - output_splines[i] = resample_spline(*input_splines[i], count); - } - else { - output_splines[i] = input_splines[i]->copy(); - } - } - }); - } - else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_EVALUATED) { - fn::FieldEvaluator evaluator{field_context, domain_size}; - evaluator.add(mode_param.selection); - evaluator.evaluate(); - const VArray &selections = evaluator.get_evaluated(0); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - if (selections[i] && input_splines[i]->evaluated_points_size() > 0) { - output_splines[i] = resample_spline_evaluated(*input_splines[i]); - } - else { - output_splines[i] = input_splines[i]->copy(); - } - } - }); - } - output_curve->attributes = input_curve->attributes; - return output_curve; + threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange range) { + for (const IndexRange range : curve_ranges.slice(range)) { + const IndexRange src_points = src_curves.points_for_curves(range); + const IndexRange dst_points = dst_curves.points_for_curves(range); + /* The arrays might be large, so a threaded copy might make sense here too. */ + dst.slice(dst_points).copy_from(src.slice(src_points)); + } + }); +} +static void copy_between_curves(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const Span unselected_ranges, + const GSpan src, + const GMutableSpan dst) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + copy_between_curves(src_curves, dst_curves, unselected_ranges, src.typed(), dst.typed()); + }); } -static void geometry_set_curve_resample(GeometrySet &geometry_set, - const SampleModeParam &mode_param) +/** + * Copy the size of every curve in #curve_ranges to the corresponding index in #counts. + */ +static void fill_curve_counts(const bke::CurvesGeometry &src_curves, + const Span curve_ranges, + MutableSpan counts) { - if (!geometry_set.has_curves()) { - return; + threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange ranges_range) { + for (const IndexRange curves_range : curve_ranges.slice(ranges_range)) { + for (const int i : curves_range) { + counts[i] = src_curves.points_for_curve(i).size(); + } + } + }); +} + +/** + * Turn an array of sizes into the offset at each index including all previous sizes. + */ +static void accumulate_counts_to_offsets(MutableSpan counts_to_offsets) +{ + int total = 0; + for (const int i : counts_to_offsets.index_range().drop_back(1)) { + const int count = counts_to_offsets[i]; + BLI_assert(count > 0); + counts_to_offsets[i] = total; + total += count; + } + counts_to_offsets.last() = total; +} + +/** + * Create new curves where the selected curves have been resampled with a number of uniform-length + * samples defined by the count field. Interpolate attributes to the result, with an accuracy that + * depends on the curve's resolution parameter. + * + * \warning The values provided by the #count_field must be 1 or greater. + * \warning Curves with no evaluated points must not be selected. + */ +static Curves *resample_to_uniform_count(const CurveComponent &src_component, + const Field &selection_field, + const Field &count_field) +{ + const Curves &src_curves_id = *src_component.get_for_read(); + const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); + + /* Create the new curves without any points and evaluate the final count directly + * into the offsets array, in order to be accumulated into offsets later. */ + Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + /* Directly copy curve attributes, since they stay the same (except for curve types). */ + CustomData_copy(&src_curves.curve_data, + &dst_curves.curve_data, + CD_MASK_ALL, + CD_DUPLICATE, + src_curves.curves_num()); + MutableSpan dst_offsets = dst_curves.offsets(); + + GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE}; + fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; + evaluator.set_selection(selection_field); + evaluator.add_with_destination(count_field, dst_offsets); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const Vector unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range(), nullptr); + + /* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */ + fill_curve_counts(src_curves, unselected_ranges, dst_offsets); + accumulate_counts_to_offsets(dst_offsets); + dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + + /* All resampled curves are poly curves. */ + dst_curves.curve_types().fill_indices(selection, CURVE_TYPE_POLY); + + VArray curves_cyclic = src_curves.cyclic(); + VArray curve_types = src_curves.curve_types(); + Span evaluated_positions = src_curves.evaluated_positions(); + MutableSpan dst_positions = dst_curves.positions(); + + AttributesForInterpolation attributes; + gather_point_attributes_to_interpolate(src_component, dst_component, attributes); + + src_curves.ensure_evaluated_lengths(); + + /* Sampling arbitrary attributes works by first interpolating them to the curve's standard + * "evaluated points" and then interpolating that result with the uniform samples. This is + * potentially wasteful when downsampling a curve to many fewer points. There are two possible + * solutions: only sample the necessary points for interpolation, or first sample curve + * parameter/segment indices and evaluate the curve directly. */ + Array sample_indices(dst_curves.points_num()); + Array sample_factors(dst_curves.points_num()); + + /* Use a "for each group of curves: for each attribute: for each curve" pattern to work on + * smaller sections of data that ideally fit into CPU cache better than simply one attribute at a + * time or one curve at a time. */ + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + const IndexMask sliced_selection = selection.slice(selection_range); + + Vector evaluated_buffer; + + /* Gather uniform samples based on the accumulated lengths of the original curve. */ + for (const int i_curve : sliced_selection) { + const bool cyclic = curves_cyclic[i_curve]; + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + length_parameterize::create_uniform_samples( + src_curves.evaluated_lengths_for_curve(i_curve, cyclic), + curves_cyclic[i_curve], + sample_indices.as_mutable_span().slice(dst_points), + sample_factors.as_mutable_span().slice(dst_points)); + } + + /* For every attribute, evaluate attributes from every curve in the range in the original + * curve's "evaluated points", then use linear interpolation to sample to the result. */ + for (const int i_attribute : attributes.dst.index_range()) { + attribute_math::convert_to_static_type(attributes.src[i_attribute].type(), [&](auto dummy) { + using T = decltype(dummy); + Span src = attributes.src[i_attribute].typed(); + MutableSpan dst = attributes.dst[i_attribute].typed(); + + for (const int i_curve : sliced_selection) { + const IndexRange src_points = src_curves.points_for_curve(i_curve); + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + + if (curve_types[i_curve] == CURVE_TYPE_POLY) { + length_parameterize::linear_interpolation(src.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); + } + else { + const int evaluated_size = src_curves.evaluated_points_for_curve(i_curve).size(); + evaluated_buffer.clear(); + evaluated_buffer.resize(sizeof(T) * evaluated_size); + MutableSpan evaluated = evaluated_buffer.as_mutable_span().cast(); + src_curves.interpolate_to_evaluated(i_curve, src.slice(src_points), evaluated); + + length_parameterize::linear_interpolation(evaluated.as_span(), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); + } + } + }); + } + + /* Interpolate the evaluated positions to the resampled curves. */ + for (const int i_curve : sliced_selection) { + const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve); + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + length_parameterize::linear_interpolation(evaluated_positions.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst_positions.slice(dst_points)); + } + + /* Fill the default value for non-interpolating attributes that still must be copied. */ + for (GMutableSpan dst : attributes.dst_no_interpolation) { + for (const int i_curve : sliced_selection) { + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size()); + } + } + }); + + /* Any attribute data from unselected curve points can be directly copied. */ + for (const int i : attributes.src.index_range()) { + copy_between_curves( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + for (const int i : attributes.src_no_interpolation.index_range()) { + copy_between_curves(src_curves, + dst_curves, + unselected_ranges, + attributes.src_no_interpolation[i], + attributes.dst_no_interpolation[i]); } - std::unique_ptr output_curve = resample_curve( - geometry_set.get_component_for_read(), mode_param); + /* Copy positions for unselected curves. */ + Span src_positions = src_curves.positions(); + copy_between_curves(src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); - geometry_set.replace_curves(curve_eval_to_curves(*output_curve)); + for (OutputAttribute &attribute : attributes.dst_attributes) { + attribute.save(); + } + + return dst_curves_id; +} + +/** + * Evaluate each selected curve to its implicit evaluated points. + * + * \warning Curves with no evaluated points must not be selected. + */ +static Curves *resample_to_evaluated(const CurveComponent &src_component, + const Field &selection_field) +{ + const Curves &src_curves_id = *src_component.get_for_read(); + const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); + + GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE}; + fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; + evaluator.set_selection(selection_field); + evaluator.evaluate(); + const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const Vector unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range(), nullptr); + + Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + /* Directly copy curve attributes, since they stay the same (except for curve types). */ + CustomData_copy(&src_curves.curve_data, + &dst_curves.curve_data, + CD_MASK_ALL, + CD_DUPLICATE, + src_curves.curves_num()); + /* All resampled curves are poly curves. */ + dst_curves.curve_types().fill_indices(selection, CURVE_TYPE_POLY); + MutableSpan dst_offsets = dst_curves.offsets(); + + src_curves.ensure_evaluated_offsets(); + threading::parallel_for(selection.index_range(), 4096, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + dst_offsets[i] = src_curves.evaluated_points_for_curve(i).size(); + } + }); + fill_curve_counts(src_curves, unselected_ranges, dst_offsets); + accumulate_counts_to_offsets(dst_offsets); + + dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + + /* Create the correct number of uniform-length samples for every selected curve. */ + Span evaluated_positions = src_curves.evaluated_positions(); + MutableSpan dst_positions = dst_curves.positions(); + + AttributesForInterpolation attributes; + gather_point_attributes_to_interpolate(src_component, dst_component, attributes); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + const IndexMask sliced_selection = selection.slice(selection_range); + + /* Evaluate generic point attributes directly to the result attributes. */ + for (const int i_attribute : attributes.dst.index_range()) { + attribute_math::convert_to_static_type(attributes.src[i_attribute].type(), [&](auto dummy) { + using T = decltype(dummy); + Span src = attributes.src[i_attribute].typed(); + MutableSpan dst = attributes.dst[i_attribute].typed(); + + for (const int i_curve : sliced_selection) { + const IndexRange src_points = src_curves.points_for_curve(i_curve); + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + src_curves.interpolate_to_evaluated( + i_curve, src.slice(src_points), dst.slice(dst_points)); + } + }); + } + + /* Copy the evaluated positions to the selected curves. */ + for (const int i_curve : sliced_selection) { + const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve); + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + dst_positions.slice(dst_points).copy_from(evaluated_positions.slice(src_points)); + } + + /* Fill the default value for non-interpolating attributes that still must be copied. */ + for (GMutableSpan dst : attributes.dst_no_interpolation) { + for (const int i_curve : sliced_selection) { + const IndexRange dst_points = dst_curves.points_for_curve(i_curve); + dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size()); + } + } + }); + + /* Any attribute data from unselected curve points can be directly copied. */ + for (const int i : attributes.src.index_range()) { + copy_between_curves( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + for (const int i : attributes.src_no_interpolation.index_range()) { + copy_between_curves(src_curves, + dst_curves, + unselected_ranges, + attributes.src_no_interpolation[i], + attributes.dst_no_interpolation[i]); + } + + /* Copy positions for unselected curves. */ + Span src_positions = src_curves.positions(); + copy_between_curves(src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); + + for (OutputAttribute &attribute : attributes.dst_attributes) { + attribute.save(); + } + + return dst_curves_id; +} + +/** + * Create a resampled curve point count field for both "uniform" options. + * The complexity is handled here in order to make the actual resampling functions simpler. + */ +static Field get_curve_count_field(GeoNodeExecParams params, + const GeometryNodeCurveResampleMode mode) +{ + if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { + static fn::CustomMF_SI_SO max_one_fn("Clamp Above One", + [](int value) { return std::max(1, value); }); + auto clamp_op = std::make_shared( + FieldOperation(max_one_fn, {Field(params.extract_input>("Count"))})); + + return Field(std::move(clamp_op)); + } + + if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { + static fn::CustomMF_SI_SI_SO get_count_fn( + "Length Input to Count", [](const float curve_length, const float sample_length) { + /* Find the number of sampled segments by dividing the total length by + * the sample length. Then there is one more sampled point than segment. */ + const int count = int(curve_length / sample_length) + 1; + return std::max(1, count); + }); + + auto get_count_op = std::make_shared( + FieldOperation(get_count_fn, + {Field(std::make_shared()), + params.extract_input>("Length")})); + + return Field(std::move(get_count_op)); + } + + BLI_assert_unreachable(); + return {}; +} + +/** + * Create a selection field that removes curves without any evaluated points (invalid NURBS curves) + * from the original selection provided to the node. This is here to simplify the sampling actual + * resampling code. + */ +static Field get_selection_field(GeoNodeExecParams params) +{ + static fn::CustomMF_SI_SI_SO get_selection_fn( + "Create Curve Selection", [](const bool orig_selection, const int evaluated_points_num) { + return orig_selection && evaluated_points_num > 1; + }); + + auto selection_op = std::make_shared( + FieldOperation(get_selection_fn, + {params.extract_input>("Selection"), + Field(std::make_shared())})); + + return Field(std::move(selection_op)); } static void node_geo_exec(GeoNodeExecParams params) @@ -252,25 +608,38 @@ static void node_geo_exec(GeoNodeExecParams params) const NodeGeometryCurveResample &storage = node_storage(params.node()); const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; - SampleModeParam mode_param; - mode_param.mode = mode; - mode_param.selection = params.extract_input>("Selection"); + const Field selection = get_selection_field(params); - if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { - Field count = params.extract_input>("Count"); - if (count < 1) { - params.set_default_remaining_outputs(); - return; + switch (mode) { + case GEO_NODE_CURVE_RESAMPLE_COUNT: + case GEO_NODE_CURVE_RESAMPLE_LENGTH: { + Field count = get_curve_count_field(params, mode); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curves()) { + return; + } + + Curves *result = resample_to_uniform_count( + *geometry_set.get_component_for_read(), selection, count); + + geometry_set.replace_curves(result); + }); + break; } - mode_param.count.emplace(count); - } - else if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { - Field resolution = params.extract_input>("Length"); - mode_param.length.emplace(resolution); - } + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curves()) { + return; + } - geometry_set.modify_geometry_sets( - [&](GeometrySet &geometry_set) { geometry_set_curve_resample(geometry_set, mode_param); }); + Curves *result = resample_to_evaluated( + *geometry_set.get_component_for_read(), selection); + + geometry_set.replace_curves(result); + }); + break; + } params.set_output("Curve", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc index 38bd79ff1a7..6c24f86b63b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc @@ -4,13 +4,7 @@ #include "BKE_curves.hh" -namespace blender::nodes::node_geo_input_spline_length_cc { - -static void node_declare(NodeDeclarationBuilder &b) -{ - b.add_output(N_("Length")).field_source(); - b.add_output(N_("Point Count")).field_source(); -} +namespace blender::nodes { /* -------------------------------------------------------------------- * Spline Length @@ -45,35 +39,43 @@ static VArray construct_spline_length_gvarray(const CurveComponent &compo return {}; } -class SplineLengthFieldInput final : public GeometryFieldInput { - public: - SplineLengthFieldInput() : GeometryFieldInput(CPPType::get(), "Spline Length node") - { - category_ = Category::Generated; - } +SplineLengthFieldInput::SplineLengthFieldInput() + : GeometryFieldInput(CPPType::get(), "Spline Length node") +{ + category_ = Category::Generated; +} - GVArray get_varray_for_context(const GeometryComponent &component, - const AttributeDomain domain, - IndexMask UNUSED(mask)) const final - { - if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - const CurveComponent &curve_component = static_cast(component); - return construct_spline_length_gvarray(curve_component, domain); - } - return {}; +GVArray SplineLengthFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const +{ + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast(component); + return construct_spline_length_gvarray(curve_component, domain); } + return {}; +} - uint64_t hash() const override - { - /* Some random constant hash. */ - return 3549623580; - } +uint64_t SplineLengthFieldInput::hash() const +{ + /* Some random constant hash. */ + return 3549623580; +} - bool is_equal_to(const fn::FieldNode &other) const override - { - return dynamic_cast(&other) != nullptr; - } -}; +bool SplineLengthFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + return dynamic_cast(&other) != nullptr; +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_input_spline_length_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output(N_("Length")).field_source(); + b.add_output(N_("Point Count")).field_source(); +} /* -------------------------------------------------------------------- * Spline Count