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