From 11f6c65e61a22ea9423962d6165f54ec29e221a8 Mon Sep 17 00:00:00 2001 From: Mattias Fredriksson Date: Wed, 9 Nov 2022 10:50:51 -0600 Subject: Geometry Nodes: Trim curve node selection input and corrections Correct trim for cyclical curves mentioned in T101379, splitting the curves if the start/endpoint is at the 'loop point'. Correct implementation based on comments in D14481, request was made to use 'foreach_curve_by_type' to computing the point lookups. Included corrections from D16066 as it may not be a adopted solution. Exposed selection input by adding it as input to the node. Note: This is disabled for 3.4 to avoid making UI changes in Bcon3. Differential Revision: https://developer.blender.org/D16161 --- source/blender/blenkernel/BKE_curves_utils.hh | 342 +++++-- source/blender/blenkernel/intern/curves_utils.cc | 29 + source/blender/geometry/GEO_trim_curves.hh | 20 +- source/blender/geometry/intern/trim_curves.cc | 980 ++++++++------------- .../nodes/geometry/nodes/node_geo_curve_trim.cc | 47 +- 5 files changed, 663 insertions(+), 755 deletions(-) diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index f9155023db7..670b25bf80f 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -67,86 +67,228 @@ struct CurvePoint : public CurveSegment { }; /** - * Cyclical index range. Iterates the interval [start, end). + * Cyclical index range. Allows iteration over a plain 'IndexRange' interval on form [start, end) + * while also supporting treating the underlying array as a cyclic array where the last index is + * followed by the first nidex in the 'cyclical' range. The cyclical index range can then be + * considered a combination of the intervals separated by the last index of the underlying array, + * namely [start, range_size) and [0, end) where start/end is the indices iterated between and + * range_size is the size of the underlying array. To cycle the underlying array the interval + * [0, range_size) can be iterated over an arbitrary amount of times inbetween. */ class IndexRangeCyclic { /* Index to the start and end of the iterated range. */ - int64_t start_ = 0; - int64_t end_ = 0; - /* Index for the start and end of the entire iterable range which contains the iterated range - * (e.g. the point range for an individual spline/curve within the entire Curves point domain). + int start_ = 0; + int end_ = 0; + /* Size of the underlying iterable range. */ - int64_t range_start_ = 0; - int64_t range_end_ = 0; + int range_size_ = 0; /* Number of times the range end is passed when the range is iterated. */ - int64_t cycles_ = 0; - - constexpr IndexRangeCyclic(int64_t begin, - int64_t end, - int64_t iterable_range_start, - int64_t iterable_range_end, - int64_t cycles) - : start_(begin), - end_(end), - range_start_(iterable_range_start), - range_end_(iterable_range_end), - cycles_(cycles) - { - } + int cycles_ = 0; public: constexpr IndexRangeCyclic() = default; ~IndexRangeCyclic() = default; - constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range, int64_t cycles) - : start_(start), - end_(end), - range_start_(iterable_range.first()), - range_end_(iterable_range.one_after_last()), - cycles_(cycles) + constexpr IndexRangeCyclic(const int start, + const int end, + const int iterable_range_size, + const int cycles) + : start_(start), end_(end), range_size_(iterable_range_size), cycles_(cycles) { } /** * Create an iterator over the cyclical interval [start_index, end_index). */ - constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range) + constexpr IndexRangeCyclic(const int start, const int end, const int iterable_range_size) : start_(start), - end_(end == iterable_range.one_after_last() ? iterable_range.first() : end), - range_start_(iterable_range.first()), - range_end_(iterable_range.one_after_last()), + end_(end == iterable_range_size ? 0 : end), + range_size_(iterable_range_size), cycles_(end < start) { } /** - * Increment the range by adding the given number of indices to the beginning of the range. + * Create a cyclical iterator of the specified size. + * + * \param start_point: Point on the curve that define the starting point of the interval. + * \param iterator_size: Number of elements to iterate (size of the iterated cyclical range). + * \param iterable_range_size: Size of the underlying range (superset to the cyclical range). + */ + static IndexRangeCyclic get_range_from_size(const int start_index, + const int iterator_size, + const int iterable_range_size) + { + BLI_assert(start_index >= 0); + BLI_assert(iterator_size >= 0); + BLI_assert(iterable_range_size > 0); + const int num_until_loop = iterable_range_size - start_index; + if (iterator_size < num_until_loop) { + return IndexRangeCyclic(start_index, start_index + iterator_size, iterable_range_size, 0); + } + + const int num_remaining = iterator_size - num_until_loop; + const int num_full_cycles = num_remaining / + iterable_range_size; /* Integer division (rounded down). */ + const int end_index = num_remaining - num_full_cycles * iterable_range_size; + return IndexRangeCyclic(start_index, end_index, iterable_range_size, num_full_cycles + 1); + } + + /** + * Create a cyclical iterator for all control points within the interval [start_point, end_point] + * including any control point at the start or end point. + * + * \param start_point: Point on the curve that define the starting point of the interval. + * \param end_point: Point on the curve that define the end point of the interval (included). + * \param iterable_range_size: Size of the underlying range (superset to the cyclical range). + */ + static IndexRangeCyclic get_range_between_endpoints(const CurvePoint start_point, + const CurvePoint end_point, + const int iterable_range_size) + { + BLI_assert(iterable_range_size > 0); + const int start_index = start_point.parameter == 0.0 ? start_point.index : + start_point.next_index; + int end_index = end_point.parameter == 0.0 ? end_point.index : end_point.next_index; + int cycles; + + if (end_point.is_controlpoint()) { + BLI_assert(end_index < iterable_range_size); + ++end_index; + if (end_index == iterable_range_size) { + end_index = 0; + } + /* end_point < start_point but parameter is irrelevant (end_point is controlpoint), and loop + * when equal due to increment. */ + cycles = end_index <= start_index; + } + else { + cycles = end_point < start_point || end_index < start_index; + } + return IndexRangeCyclic(start_index, end_index, iterable_range_size, cycles); + } + + /** + * Next index within the iterable range. + */ + template constexpr IndexT next_index(const IndexT index, const bool cyclic) + { + static_assert((is_same_any_v), "Expected signed integer type."); + const IndexT next_index = index + 1; + if (next_index == this->size_range()) { + return cyclic ? 0 : index; + } + return next_index; + } + + /** + * Previous index within the iterable range. + */ + template constexpr IndexT previous_index(const IndexT index, const bool cyclic) + { + static_assert((is_same_any_v), "Expected signed integer type."); + const IndexT prev_index = index - 1; + if (prev_index < 0) { + return cyclic ? this->size_range() - 1 : 0; + } + return prev_index; + } + + /** + * Increment the range by adding `n` loops to the range. This invokes undefined behavior when n + * is negative. */ - constexpr IndexRangeCyclic push_forward(int n) + constexpr IndexRangeCyclic push_loop(const int n = 1) const + { + return {this->start_, this->end_, this->range_size_, this->cycles_ + n}; + } + + /** + * Increment the range by adding the given number of indices to the beginning of the iterated + * range. This invokes undefined behavior when n is negative. + */ + constexpr IndexRangeCyclic push_front(const int n = 1) const { BLI_assert(n >= 0); - int64_t nstart = start_ - n; - int64_t cycles = cycles_; - if (nstart < range_start_) { + int new_start = this->start_ - n; + int num_cycles = this->cycles_; + if (new_start < 0) { + const int new_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_start + this->size_range() * new_cycles; + const bool underflow = remainder < 0; + new_start = remainder + (underflow ? this->size_range() : 0); + num_cycles += new_cycles + int(underflow); + } + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || + (new_start <= this->end_ || (this->end_ == 0 && new_start < this->size_range()))); + return {new_start, this->end_, this->range_size_, num_cycles}; + } - cycles += (int64_t)(n / (range_end_ - range_start_)) + (end_ < nstart) - (end_ < start_); + /** + * Increment the range by adding the given number of indices to the end of the iterated range. + * This invokes undefined behavior when n is negative. + */ + constexpr IndexRangeCyclic push_back(const int n = 1) const + { + BLI_assert(n >= 0); + int new_end = this->end_ + n; + int num_cycles = this->cycles_; + if (this->size_range() <= new_end) { + const int new_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_end - this->size_range() * new_cycles; + const bool overflow = remainder >= this->size_range(); + new_end = remainder - (overflow ? this->size_range() : 0); + num_cycles += new_cycles + int(overflow); } - return {nstart, end_, range_start_, range_end_, cycles}; + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || (this->start_ <= new_end || new_end == 0)); + return {this->start_, new_end, this->range_size_, num_cycles}; } + /** - * Increment the range by adding the given number of indices to the end of the range. + * Returns a new range with n indices removed from the beginning of the range. + * This invokes undefined behavior. */ - constexpr IndexRangeCyclic push_backward(int n) + constexpr IndexRangeCyclic drop_front(const int n = 1) const { BLI_assert(n >= 0); - int64_t new_end = end_ + n; - int64_t cycles = cycles_; - if (range_end_ <= new_end) { - cycles += (int64_t)(n / (range_end_ - range_start_)) + (new_end < start_) - (end_ < start_); + int new_start = this->start_ + n; + int num_cycles = this->cycles_; + if (this->size_range() <= new_start) { + const int dropped_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_start - this->size_range() * dropped_cycles; + const bool overflow = remainder >= this->size_range(); + new_start = remainder - (overflow ? this->size_range() : 0); + num_cycles -= dropped_cycles + int(overflow); } - return {start_, new_end, range_start_, range_end_, cycles}; + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || + (new_start <= this->end_ || (this->end_ == 0 && new_start < this->size_range()))); + return {new_start, this->end_, this->range_size_, num_cycles}; + } + + /** + * Returns a new range with n indices removed from the end of the range. + * This invokes undefined behavior when n is negative or n is larger then the underlying range. + */ + constexpr IndexRangeCyclic drop_back(const int n = 1) const + { + BLI_assert(n >= 0); + int new_end = this->end_ - n; + int num_cycles = this->cycles_; + if (0 >= new_end) { + const int dropped_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_end + this->size_range() * dropped_cycles; + const bool underflow = remainder < 0; + new_end = remainder + (underflow ? this->size_range() : 0); + num_cycles -= dropped_cycles + int(underflow); + } + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || (this->start_ <= new_end || new_end == 0)); + return {this->start_, new_end, this->range_size_, num_cycles}; } /** @@ -154,7 +296,7 @@ class IndexRangeCyclic { */ constexpr IndexRange curve_range() const { - return IndexRange(range_start_, total_size()); + return IndexRange(0, this->size_range()); } /** @@ -162,7 +304,7 @@ class IndexRangeCyclic { */ constexpr IndexRange range_before_loop() const { - return IndexRange(start_, size_before_loop()); + return IndexRange(this->start_, this->size_before_loop()); } /** @@ -170,88 +312,104 @@ class IndexRangeCyclic { */ constexpr IndexRange range_after_loop() const { - return IndexRange(range_start_, size_after_loop()); + return IndexRange(0, this->size_after_loop()); } /** - * Size of the entire iterable range. + * Number of elements in the underlying iterable range. */ - constexpr int64_t total_size() const + constexpr int size_range() const { - return range_end_ - range_start_; + return this->range_size_; } /** * Number of elements between the first element in the range up to the last element in the curve. */ - constexpr int64_t size_before_loop() const + constexpr int size_before_loop() const { - return range_end_ - start_; + return this->range_size_ - this->start_; } /** * Number of elements between the first element in the iterable range up to the last element in * the range. */ - constexpr int64_t size_after_loop() const + constexpr int size_after_loop() const { - return end_ - range_start_; + return this->end_; } /** - * Get number of elements iterated by the cyclical index range. + * Number of elements iterated by the cyclical index range. */ - constexpr int64_t size() const + constexpr int size() const { - if (cycles_ > 0) { - return size_before_loop() + end_ + (cycles_ - 1) * (range_end_ - range_start_); + if (this->cycles_ > 0) { + return this->size_before_loop() + this->end_ + (this->cycles_ - 1) * this->range_size_; } else { - return end_ - start_; + return int(this->end_ - this->start_); } } /** * Return the number of times the iterator will cycle before ending. */ - constexpr int64_t cycles() const + constexpr int cycles() const + { + return this->cycles_; + } + + constexpr int first() const { - return cycles_; + return this->start_; } - constexpr int64_t first() const + constexpr int last() const { - return start_; + BLI_assert(this->size() > 0); + return int(this->end_ - 1); } - constexpr int64_t one_after_last() const + constexpr int one_after_last() const { - return end_; + return this->end_; + } + + constexpr bool operator==(const IndexRangeCyclic &other) const + { + return this->start_ == other.start_ && this->end_ == other.end_ && + this->cycles_ == other.cycles_ && this->range_size_ == other.range_size_; + } + constexpr bool operator!=(const IndexRangeCyclic &other) const + { + return !this->operator==(other); } struct CyclicIterator; /* Forward declaration */ constexpr CyclicIterator begin() const { - return CyclicIterator(range_start_, range_end_, start_, 0); + return CyclicIterator(this->range_size_, this->start_, 0); } constexpr CyclicIterator end() const { - return CyclicIterator(range_start_, range_end_, end_, cycles_); + return CyclicIterator(this->range_size_, this->end_, this->cycles_); } struct CyclicIterator { - int64_t index_, begin_, end_, cycles_; + int index_, range_end_, cycles_; - constexpr CyclicIterator(int64_t range_begin, int64_t range_end, int64_t index, int64_t cycles) - : index_(index), begin_(range_begin), end_(range_end), cycles_(cycles) + constexpr CyclicIterator(const int range_end, const int index, const int cycles) + : index_(index), range_end_(range_end), cycles_(cycles) { - BLI_assert(range_begin <= index && index <= range_end); + BLI_assert(0 <= index && index <= range_end); } constexpr CyclicIterator(const CyclicIterator ©) - : index_(copy.index_), begin_(copy.begin_), end_(copy.end_), cycles_(copy.cycles_) + : index_(copy.index_), range_end_(copy.range_end_), cycles_(copy.cycles_) { } ~CyclicIterator() = default; @@ -261,37 +419,36 @@ class IndexRangeCyclic { if (this == ©) { return *this; } - index_ = copy.index_; - begin_ = copy.begin_; - end_ = copy.end_; - cycles_ = copy.cycles_; + this->index_ = copy.index_; + this->range_end_ = copy.range_end_; + this->cycles_ = copy.cycles_; return *this; } constexpr CyclicIterator &operator++() { - index_++; - if (index_ == end_) { - index_ = begin_; - cycles_++; + this->index_++; + if (this->index_ == this->range_end_) { + this->index_ = 0; + this->cycles_++; } return *this; } - void increment(int64_t n) + void increment(const int n) { for (int i = 0; i < n; i++) { ++*this; } } - constexpr const int64_t &operator*() const + constexpr const int &operator*() const { - return index_; + return this->index_; } constexpr bool operator==(const CyclicIterator &other) const { - return index_ == other.index_ && cycles_ == other.cycles_; + return this->index_ == other.index_ && this->cycles_ == other.cycles_; } constexpr bool operator!=(const CyclicIterator &other) const { @@ -386,6 +543,12 @@ IndexMask indices_for_type(const VArray &types, const IndexMask selection, Vector &r_indices); +void indices_for_each_type(const VArray &types, + const std::array &counts, + const IndexMask selection, + std::array &r_type_masks, + std::array, CURVE_TYPES_NUM> &r_type_indices); + void foreach_curve_by_type(const VArray &types, const std::array &type_counts, IndexMask selection, @@ -394,6 +557,15 @@ void foreach_curve_by_type(const VArray &types, FunctionRef bezier_fn, FunctionRef nurbs_fn); +/** + * Same as 'by_type' but index mask for each curve type is pre-computed. + */ +void foreach_curve_by_type_mask(const std::array &curve_type_mask, + FunctionRef catmull_rom_fn, + FunctionRef poly_fn, + FunctionRef bezier_fn, + FunctionRef nurbs_fn); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index f5a69a995a3..2b13a7d3c8a 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -128,6 +128,18 @@ IndexMask indices_for_type(const VArray &types, selection, 4096, r_indices, [&](const int index) { return types_span[index] == type; }); } +void indices_for_each_type(const VArray &types, + const std::array &counts, + const IndexMask selection, + std::array &r_type_masks, + std::array, CURVE_TYPES_NUM> &r_type_indices) +{ + for (const int64_t curve_type : IndexRange(CURVE_TYPES_NUM)) { + r_type_masks[curve_type] = indices_for_type( + types, counts, CurveType(curve_type), selection, r_type_indices[curve_type]); + } +} + void foreach_curve_by_type(const VArray &types, const std::array &counts, const IndexMask selection, @@ -150,4 +162,21 @@ void foreach_curve_by_type(const VArray &types, call_if_not_empty(CURVE_TYPE_NURBS, nurbs_fn); } +void foreach_curve_by_type_mask(const std::array &curve_type_mask, + FunctionRef catmull_rom_fn, + FunctionRef poly_fn, + FunctionRef bezier_fn, + FunctionRef nurbs_fn) +{ + auto call_if_not_empty = [&](const IndexMask curve_type_mask, FunctionRef fn) { + if (!curve_type_mask.is_empty()) { + fn(curve_type_mask); + } + }; + call_if_not_empty(curve_type_mask[0], catmull_rom_fn); + call_if_not_empty(curve_type_mask[1], poly_fn); + call_if_not_empty(curve_type_mask[2], bezier_fn); + call_if_not_empty(curve_type_mask[3], nurbs_fn); +} + } // namespace blender::bke::curves diff --git a/source/blender/geometry/GEO_trim_curves.hh b/source/blender/geometry/GEO_trim_curves.hh index d2ff770f225..197ef79c25b 100644 --- a/source/blender/geometry/GEO_trim_curves.hh +++ b/source/blender/geometry/GEO_trim_curves.hh @@ -3,6 +3,7 @@ #pragma once #include "BLI_span.hh" +#include "DNA_node_types.h" #include "BKE_curves.hh" #include "BKE_curves_utils.hh" @@ -16,21 +17,8 @@ namespace blender::geometry { */ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, IndexMask selection, - Span start_points, - Span end_points); - -/** - * Find the point(s) and piecewise segment corresponding to the given distance along the length of - * the curve. Returns points on the evaluated curve for Catmull-Rom and NURBS splines. - * - * \param curves: Curve geometry to sample. - * \param lengths: Distance along the curve on form [0.0, length] to determine the point for. - * \param curve_indices: Curve index to lookup for each 'length', negative index are set to 0. - * \param normalized_factors: If true, 'lengths' are normalized to the interval [0.0, 1.0]. - */ -Array lookup_curve_points(const bke::CurvesGeometry &curves, - Span lengths, - Span curve_indices, - bool normalized_factors); + const VArray &starts, + const VArray &ends, + GeometryNodeCurveSampleMode mode); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index f2adc3b58d3..0e23a3d53f0 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -17,61 +17,6 @@ namespace blender::geometry { -/* -------------------------------------------------------------------- */ -/** \name Curve Enums - * \{ */ - -#define CURVE_TYPE_AS_MASK(curve_type) ((CurveTypeMask)(1 << int(curve_type))) - -enum CurveTypeMask { - CURVE_TYPE_MASK_CATMULL_ROM = (1 << 0), - CURVE_TYPE_MASK_POLY = (1 << 1), - CURVE_TYPE_MASK_BEZIER = (1 << 2), - CURVE_TYPE_MASK_NURBS = (1 << 3), - CURVE_TYPE_MASK_ALL = (1 << 4) - 1 -}; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name #IndexRangeCyclic Utilities - * \{ */ - -/** - * Create a cyclical iterator for all control points within the interval [start_point, end_point] - * including any control point at the start or end point. - * - * \param start_point: Point on the curve that define the starting point of the interval. - * \param end_point: Point on the curve that define the end point of the interval (included). - * \param points: #IndexRange for the curve points. - */ -static bke::curves::IndexRangeCyclic get_range_between_endpoints( - const bke::curves::CurvePoint start_point, - const bke::curves::CurvePoint end_point, - const IndexRange points) -{ - const int64_t start_index = start_point.parameter == 0.0 ? start_point.index : - start_point.next_index; - int64_t end_index = end_point.parameter == 0.0 ? end_point.index : end_point.next_index; - int64_t cycles; - - if (end_point.is_controlpoint()) { - ++end_index; - if (end_index > points.last()) { - end_index = points.one_after_last(); - } - /* end_point < start_point but parameter is irrelevant (end_point is controlpoint), and loop - * when equal due to increment. */ - cycles = end_index <= start_index; - } - else { - cycles = end_point < start_point || end_index < start_index; - } - return bke::curves::IndexRangeCyclic(start_index, end_index, points, cycles); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Lookup Curve Points * \{ */ @@ -88,11 +33,11 @@ static bke::curves::IndexRangeCyclic get_range_between_endpoints( * \param num_curve_points: Total number of control points in the curve. * \return: Point on the piecewise segment matching the given distance. */ -static bke::curves::CurvePoint lookup_curve_point(const Span lengths, - const float sample_length, - const bool cyclic, - const int resolution, - const int num_curve_points) +static bke::curves::CurvePoint lookup_point_uniform_spacing(const Span lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { BLI_assert(!cyclic || lengths.size() / resolution >= 2); const int last_index = num_curve_points - 1; @@ -117,7 +62,7 @@ static bke::curves::CurvePoint lookup_curve_point(const Span lengths, /** * Find the point on the 'evaluated' polygonal curve. */ -static bke::curves::CurvePoint lookup_evaluated_point(const Span lengths, +static bke::curves::CurvePoint lookup_point_polygonal(const Span lengths, const float sample_length, const bool cyclic, const int evaluated_size) @@ -142,7 +87,7 @@ static bke::curves::CurvePoint lookup_evaluated_point(const Span lengths, /** * Find the point on a Bezier curve using the 'bezier_offsets' cache. */ -static bke::curves::CurvePoint lookup_bezier_point(const Span bezier_offsets, +static bke::curves::CurvePoint lookup_point_bezier(const Span bezier_offsets, const Span lengths, const float sample_length, const bool cyclic, @@ -166,197 +111,73 @@ static bke::curves::CurvePoint lookup_bezier_point(const Span bezier_offset const int right = left == last_index ? 0 : left + 1; const int prev_offset = left == 0 ? 0 : bezier_offsets[int64_t(left) - 1]; - const float offset_in_segment = eval_factor + eval_index - prev_offset; + const float offset_in_segment = eval_factor + (eval_index - prev_offset); const int segment_resolution = bezier_offsets[left] - prev_offset; const float parameter = std::clamp(offset_in_segment / segment_resolution, 0.0f, 1.0f); return {{left, right}, parameter}; } -Array lookup_curve_points(const bke::CurvesGeometry &curves, - const Span lengths, - const Span curve_indices, - const bool normalized_factors) -{ - BLI_assert(lengths.size() == curve_indices.size()); - BLI_assert(*std::max_element(curve_indices.begin(), curve_indices.end()) < curves.curves_num()); - - const VArray cyclic = curves.cyclic(); - const VArray resolution = curves.resolution(); - const VArray curve_types = curves.curve_types(); - - /* Compute curve lengths! */ - curves.ensure_evaluated_lengths(); - curves.ensure_evaluated_offsets(); - - /* Find the curve points referenced by the input! */ - Array lookups(curve_indices.size()); - threading::parallel_for(curve_indices.index_range(), 128, [&](const IndexRange range) { - for (const int64_t lookup_index : range) { - const int64_t curve_i = curve_indices[lookup_index]; - - const int point_count = curves.points_num_for_curve(curve_i); - if (curve_i < 0 || point_count == 1) { - lookups[lookup_index] = {{0, 0}, 0.0f}; - continue; - } - - const Span accumulated_lengths = curves.evaluated_lengths_for_curve(curve_i, - cyclic[curve_i]); - BLI_assert(accumulated_lengths.size() > 0); - - const float sample_length = normalized_factors ? - lengths[lookup_index] * accumulated_lengths.last() : - lengths[lookup_index]; - - const CurveType curve_type = (CurveType)curve_types[curve_i]; - - switch (curve_type) { - case CURVE_TYPE_BEZIER: { - if (bke::curves::bezier::has_vector_handles( - point_count, - curves.evaluated_points_for_curve(curve_i).size(), - cyclic[curve_i], - resolution[curve_i])) { - const Span bezier_offsets = curves.bezier_evaluated_offsets_for_curve(curve_i); - lookups[lookup_index] = lookup_bezier_point( - bezier_offsets, accumulated_lengths, sample_length, cyclic[curve_i], point_count); - } - else { - lookups[lookup_index] = lookup_curve_point(accumulated_lengths, - sample_length, - cyclic[curve_i], - resolution[curve_i], - point_count); - } - break; - } - case CURVE_TYPE_CATMULL_ROM: { - lookups[lookup_index] = lookup_curve_point(accumulated_lengths, - sample_length, - cyclic[curve_i], - resolution[curve_i], - point_count); - break; - } - case CURVE_TYPE_NURBS: - case CURVE_TYPE_POLY: - default: { - /* Handle general case as an "evaluated" or polygonal curve. */ - BLI_assert(resolution[curve_i] > 0); - lookups[lookup_index] = lookup_evaluated_point( - accumulated_lengths, - sample_length, - cyclic[curve_i], - curves.evaluated_points_for_curve(curve_i).size()); - break; - } - } - } - }); - return lookups; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Transfer Curve Domain - * \{ */ - -/** - * Determine curve type(s) for the copied curves given the supported set of types and knot modes. - * If a curve type is not supported the default type is set. - */ -static void determine_copyable_curve_types( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const IndexMask selection_inverse, - const CurveTypeMask supported_curve_type_mask, - const int8_t default_curve_type = int8_t(CURVE_TYPE_POLY)) -{ - const VArray src_curve_types = src_curves.curve_types(); - const VArray src_knot_modes = src_curves.nurbs_knots_modes(); - MutableSpan dst_curve_types = dst_curves.curve_types_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange selection_range) { - for (const int64_t curve_i : selection.slice(selection_range)) { - if (supported_curve_type_mask & CURVE_TYPE_AS_MASK(src_curve_types[curve_i])) { - dst_curve_types[curve_i] = src_curve_types[curve_i]; - } - else { - dst_curve_types[curve_i] = default_curve_type; - } - } - }); - - array_utils::copy(src_curve_types, selection_inverse, dst_curve_types); -} - -/** - * Determine if a curve is treated as an evaluated curve. Curves which inherently do not support - * trimming are discretized (e.g. NURBS). - */ -static bool copy_as_evaluated_curve(const int8_t src_type, const int8_t dst_type) +static bke::curves::CurvePoint lookup_point_bezier(const bke::CurvesGeometry &src_curves, + const int64_t curve_index, + const Span accumulated_lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { - return src_type != CURVE_TYPE_POLY && dst_type == CURVE_TYPE_POLY; + if (bke::curves::bezier::has_vector_handles( + num_curve_points, + src_curves.evaluated_points_for_curve(curve_index).size(), + cyclic, + resolution)) { + const Span bezier_offsets = src_curves.bezier_evaluated_offsets_for_curve(curve_index); + return lookup_point_bezier( + bezier_offsets, accumulated_lengths, sample_length, cyclic, num_curve_points); + } + else { + return lookup_point_uniform_spacing( + accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + } } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Specialized Curve Constructors - * \{ */ - -static void compute_trim_result_offsets(const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const IndexMask inverse_selection, - const Span start_points, - const Span end_points, - const VArray dst_curve_types, - MutableSpan dst_curve_offsets, - Vector &r_curve_indices, - Vector &r_point_curve_indices) +static bke::curves::CurvePoint lookup_curve_point(const bke::CurvesGeometry &src_curves, + const CurveType curve_type, + const int64_t curve_index, + const Span accumulated_lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { - BLI_assert(r_curve_indices.size() == 0); - BLI_assert(r_point_curve_indices.size() == 0); - const VArray cyclic = src_curves.cyclic(); - const VArray curve_types = src_curves.curve_types(); - r_curve_indices.reserve(selection.size()); - - for (const int64_t curve_i : selection) { - - int64_t src_point_count; - - if (copy_as_evaluated_curve(curve_types[curve_i], dst_curve_types[curve_i])) { - src_point_count = src_curves.evaluated_points_for_curve(curve_i).size(); - } - else { - src_point_count = int64_t(src_curves.points_num_for_curve(curve_i)); - } - BLI_assert(src_point_count > 0); + if (num_curve_points == 1) { + return {{0, 0}, 0.0f}; + } - if (start_points[curve_i] == end_points[curve_i]) { - dst_curve_offsets[curve_i] = 1; - r_point_curve_indices.append(curve_i); - } - else { - const bke::curves::IndexRangeCyclic point_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_point_count}); - const int count = point_range.size() + !start_points[curve_i].is_controlpoint() + - !end_points[curve_i].is_controlpoint(); - dst_curve_offsets[curve_i] = count; - r_curve_indices.append(curve_i); - } - BLI_assert(dst_curve_offsets[curve_i] > 0); + if (curve_type == CURVE_TYPE_CATMULL_ROM) { + return lookup_point_uniform_spacing( + accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + } + else if (curve_type == CURVE_TYPE_BEZIER) { + return lookup_point_bezier(src_curves, + curve_index, + accumulated_lengths, + sample_length, + cyclic, + resolution, + num_curve_points); + } + else if (curve_type == CURVE_TYPE_POLY) { + return lookup_point_polygonal(accumulated_lengths, sample_length, cyclic, num_curve_points); + } + else { + /* Handle evaluated curve. */ + BLI_assert(resolution > 0); + return lookup_point_polygonal(accumulated_lengths, + sample_length, + cyclic, + src_curves.evaluated_points_for_curve(curve_index).size()); } - threading::parallel_for( - inverse_selection.index_range(), 4096, [&](const IndexRange selection_range) { - for (const int64_t curve_i : inverse_selection.slice(selection_range)) { - dst_curve_offsets[curve_i] = src_curves.points_num_for_curve(curve_i); - } - }); - bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); } /** \} */ @@ -367,41 +188,42 @@ static void compute_trim_result_offsets(const bke::CurvesGeometry &src_curves, static void fill_bezier_data(bke::CurvesGeometry &dst_curves, const IndexMask selection) { - if (dst_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { - MutableSpan handle_positions_left = dst_curves.handle_positions_left_for_write(); - MutableSpan handle_positions_right = dst_curves.handle_positions_right_for_write(); - MutableSpan handle_types_left = dst_curves.handle_types_left_for_write(); - MutableSpan handle_types_right = dst_curves.handle_types_right_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange points = dst_curves.points_for_curve(curve_i); - handle_types_right.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); - handle_types_left.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); - handle_positions_left.slice(points).fill({0.0f, 0.0f, 0.0f}); - handle_positions_right.slice(points).fill({0.0f, 0.0f, 0.0f}); - } - }); + if (!dst_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + return; } + MutableSpan handle_positions_left = dst_curves.handle_positions_left_for_write(); + MutableSpan handle_positions_right = dst_curves.handle_positions_right_for_write(); + MutableSpan handle_types_left = dst_curves.handle_types_left_for_write(); + MutableSpan handle_types_right = dst_curves.handle_types_right_for_write(); + + threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { + for (const int64_t curve_i : selection.slice(range)) { + const IndexRange points = dst_curves.points_for_curve(curve_i); + handle_types_right.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); + handle_types_left.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); + handle_positions_left.slice(points).fill({0.0f, 0.0f, 0.0f}); + handle_positions_right.slice(points).fill({0.0f, 0.0f, 0.0f}); + } + }); } static void fill_nurbs_data(bke::CurvesGeometry &dst_curves, const IndexMask selection) { - if (dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { - bke::curves::fill_points(dst_curves, selection, 0.0f, dst_curves.nurbs_weights_for_write()); + if (!dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + return; } + bke::curves::fill_points(dst_curves, selection, 0.0f, dst_curves.nurbs_weights_for_write()); } template static int64_t copy_point_data_between_endpoints(const Span src_data, MutableSpan dst_data, const bke::curves::IndexRangeCyclic src_range, - const int64_t src_index, int64_t dst_index) { int64_t increment; if (src_range.cycles()) { increment = src_range.size_before_loop(); - dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_index, increment)); + dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_range.first(), increment)); dst_index += increment; increment = src_range.size_after_loop(); @@ -411,7 +233,7 @@ static int64_t copy_point_data_between_endpoints(const Span src_data, } else { increment = src_range.one_after_last() - src_range.first(); - dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_index, increment)); + dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_range.first(), increment)); dst_index += increment; } return dst_index; @@ -465,82 +287,6 @@ static bke::curves::bezier::Insertion knot_insert_bezier( /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Sample Single Point - * \{ */ - -template -static void sample_linear(const Span src_data, - MutableSpan dst_data, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_data[dst_range.first()] = src_data[index]; - } - else { - dst_data[dst_range.first()] = attribute_math::mix2( - sample_point.parameter, src_data[sample_point.index], src_data[sample_point.next_index]); - } -} - -template -static void sample_catmull_rom(const Span src_data, - MutableSpan dst_data, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point, - const bool src_cyclic) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_data[dst_range.first()] = src_data[index]; - } - else { - dst_data[dst_range.first()] = interpolate_catmull_rom(src_data, sample_point, src_cyclic); - } -} - -static void sample_bezier(const Span src_positions, - const Span src_handles_l, - const Span src_handles_r, - const Span src_types_l, - const Span src_types_r, - MutableSpan dst_positions, - MutableSpan dst_handles_l, - MutableSpan dst_handles_r, - MutableSpan dst_types_l, - MutableSpan dst_types_r, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_positions[dst_range.first()] = src_positions[index]; - dst_handles_l[dst_range.first()] = src_handles_l[index]; - dst_handles_r[dst_range.first()] = src_handles_r[index]; - dst_types_l[dst_range.first()] = src_types_l[index]; - dst_types_r[dst_range.first()] = src_types_r[index]; - } - else { - bke::curves::bezier::Insertion insertion_point = knot_insert_bezier( - src_positions, src_handles_l, src_handles_r, sample_point); - dst_positions[dst_range.first()] = insertion_point.position; - dst_handles_l[dst_range.first()] = insertion_point.left_handle; - dst_handles_r[dst_range.first()] = insertion_point.right_handle; - dst_types_l[dst_range.first()] = BEZIER_HANDLE_FREE; - dst_types_r[dst_range.first()] = BEZIER_HANDLE_FREE; - } -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Sample Curve Interval (Trim) * \{ */ @@ -561,19 +307,18 @@ static void sample_bezier(const Span src_positions, template static void sample_interval_linear(const Span src_data, MutableSpan dst_data, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point) { - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); if (start_point.is_controlpoint()) { /* 'start_point' is included in the copy iteration. */ if constexpr (!include_start_point) { /* Skip first. */ - ++src_index; + src_range = src_range.drop_front(); } } else if constexpr (!include_start_point) { @@ -586,8 +331,11 @@ static void sample_interval_linear(const Span src_data, ++dst_index; } - dst_index = copy_point_data_between_endpoints( - src_data, dst_data, src_range, src_index, dst_index); + dst_index = copy_point_data_between_endpoints(src_data, dst_data, src_range, dst_index); + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } /* Handle last case */ if (end_point.is_controlpoint()) { @@ -603,27 +351,18 @@ static void sample_interval_linear(const Span src_data, BLI_assert(dst_index == dst_range.one_after_last()); } -template +template static void sample_interval_catmull_rom(const Span src_data, MutableSpan dst_data, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point, const bool src_cyclic) { - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); if (start_point.is_controlpoint()) { - /* 'start_point' is included in the copy iteration. */ - if constexpr (!include_start_point) { - /* Skip first. */ - ++src_index; - } - } - else if constexpr (!include_start_point) { - /* Do nothing (excluded). */ } else { /* General case, sample 'start_point' */ @@ -631,8 +370,11 @@ static void sample_interval_catmull_rom(const Span src_data, ++dst_index; } - dst_index = copy_point_data_between_endpoints( - src_data, dst_data, src_range, src_index, dst_index); + dst_index = copy_point_data_between_endpoints(src_data, dst_data, src_range, dst_index); + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } /* Handle last case */ if (end_point.is_controlpoint()) { @@ -658,20 +400,19 @@ static void sample_interval_bezier(const Span src_positions, MutableSpan dst_handles_r, MutableSpan dst_types_l, MutableSpan dst_types_r, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point) { bke::curves::bezier::Insertion start_point_insert; - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); bool start_point_trimmed = false; if (start_point.is_controlpoint()) { /* The 'start_point' control point is included in the copy iteration. */ if constexpr (!include_start_point) { - ++src_index; /* Skip first! */ + src_range = src_range.drop_front(); } } else if constexpr (!include_start_point) { @@ -696,7 +437,7 @@ static void sample_interval_bezier(const Span src_positions, src_range.one_after_last() - src_range.first(); const IndexRange dst_range_to_end(dst_index, increment); - const IndexRange src_range_to_end(src_index, increment); + const IndexRange src_range_to_end(src_range.first(), increment); dst_positions.slice(dst_range_to_end).copy_from(src_positions.slice(src_range_to_end)); dst_handles_l.slice(dst_range_to_end).copy_from(src_handles_l.slice(src_range_to_end)); dst_handles_r.slice(dst_range_to_end).copy_from(src_handles_r.slice(src_range_to_end)); @@ -704,6 +445,11 @@ static void sample_interval_bezier(const Span src_positions, dst_types_r.slice(dst_range_to_end).copy_from(src_types_r.slice(src_range_to_end)); dst_index += increment; + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } + increment = src_range.size_after_loop(); if (src_range.cycles() && increment > 0) { const IndexRange dst_range_looped(dst_index, increment); @@ -768,174 +514,6 @@ static void sample_interval_bezier(const Span src_positions, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Convert to Point Curves - * \{ */ - -static void convert_point_polygonal_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_linear( - src_positions.slice(src_points), dst_positions, dst_points, sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_linear(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_curves.points_for_curve(curve_i), - sample_points[curve_i]); - }); - } - } - }); - - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_catmull_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - const VArray src_cyclic = src_curves.cyclic(); - - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_catmull_rom(src_positions.slice(src_points), - dst_positions, - dst_points, - sample_points[curve_i], - src_cyclic[curve_i]); - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_catmull_rom(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_points, - sample_points[curve_i], - src_cyclic[curve_i]); - }); - } - } - }); - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_bezier_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - const VArraySpan src_types_l{src_curves.handle_types_left()}; - const VArraySpan src_types_r{src_curves.handle_types_right()}; - const Span src_handles_l = src_curves.handle_positions_left(); - const Span src_handles_r = src_curves.handle_positions_right(); - - MutableSpan dst_positions = dst_curves.positions_for_write(); - MutableSpan dst_types_l = dst_curves.handle_types_left_for_write(); - MutableSpan dst_types_r = dst_curves.handle_types_right_for_write(); - MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write(); - MutableSpan dst_handles_r = dst_curves.handle_positions_right_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_bezier(src_positions.slice(src_points), - src_handles_l.slice(src_points), - src_handles_r.slice(src_points), - src_types_l.slice(src_points), - src_types_r.slice(src_points), - dst_positions, - dst_handles_l, - dst_handles_r, - dst_types_l, - dst_types_r, - dst_points, - sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_linear(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_points, - sample_points[curve_i]); - }); - } - } - }); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_evaluated_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span evaluated_sample_points, - MutableSpan transfer_attributes) -{ - const Span src_eval_positions = src_curves.evaluated_positions(); - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - const IndexRange src_evaluated_points = src_curves.evaluated_points_for_curve(curve_i); - - sample_linear(src_eval_positions.slice(src_evaluated_points), - dst_positions, - dst_points, - evaluated_sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - GArray evaluated_data(CPPType::get(), src_evaluated_points.size()); - GMutableSpan evaluated_span = evaluated_data.as_mutable_span(); - src_curves.interpolate_to_evaluated( - curve_i, attribute.src.slice(src_curves.points_for_curve(curve_i)), evaluated_span); - sample_linear(evaluated_span.typed(), - attribute.dst.span.typed(), - dst_points, - evaluated_sample_points[curve_i]); - }); - } - } - }); - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Trim Curves * \{ */ @@ -945,6 +523,7 @@ static void trim_attribute_linear(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { for (bke::AttributeTransferData &attribute : transfer_attributes) { @@ -955,11 +534,9 @@ static void trim_attribute_linear(const bke::CurvesGeometry &src_curves, for (const int64_t curve_i : selection.slice(range)) { const IndexRange src_points = src_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_linear(attribute.src.template typed().slice(src_points), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_curves.points_for_curve(curve_i), start_points[curve_i], end_points[curve_i]); @@ -974,6 +551,7 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -984,11 +562,9 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_linear(src_positions.slice(src_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); @@ -996,8 +572,13 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, }); fill_bezier_data(dst_curves, selection); fill_nurbs_data(dst_curves, selection); - trim_attribute_linear( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + trim_attribute_linear(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); } static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, @@ -1005,6 +586,7 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -1016,11 +598,9 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_catmull_rom(src_positions.slice(src_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i], @@ -1039,11 +619,9 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_catmull_rom(attribute.src.template typed().slice(src_points), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i], @@ -1059,6 +637,7 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -1078,8 +657,6 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_bezier(src_positions.slice(src_points), src_handles_l.slice(src_points), src_handles_r.slice(src_points), @@ -1090,15 +667,20 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, dst_handles_r, dst_types_l, dst_types_r, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); } }); fill_nurbs_data(dst_curves, selection); - trim_attribute_linear( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + trim_attribute_linear(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); } static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, @@ -1106,6 +688,7 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_eval_positions = src_curves.evaluated_positions(); @@ -1116,11 +699,9 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, const IndexRange dst_points = dst_curves.points_for_curve(curve_i); const IndexRange src_evaluated_points = src_curves.evaluated_points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_evaluated_points.size()}); sample_interval_linear(src_eval_positions.slice(src_evaluated_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); @@ -1141,11 +722,9 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, GMutableSpan evaluated_span = evaluated_data.as_mutable_span(); src_curves.interpolate_to_evaluated( curve_i, attribute.src.slice(src_curves.points_for_curve(curve_i)), evaluated_span); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_evaluated_points.size()}); sample_interval_linear(evaluated_span.typed(), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_curves.points_for_curve(curve_i), start_points[curve_i], end_points[curve_i]); @@ -1155,52 +734,206 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, } } +/* -------------------------------------------------------------------- */ +/** \name Compute trim parameters + * \{ */ + +static float trim_sample_length(const Span accumulated_lengths, + const float sample_length, + const GeometryNodeCurveSampleMode mode) +{ + float length = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? + sample_length * accumulated_lengths.last() : + sample_length; + return std::clamp(length, 0.0f, accumulated_lengths.last()); +} + +/** + * Compute the selection for the given curve type. Tracks indices for splitting the selection if + * there are curves reduced to a single point. + */ +void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, + const IndexMask selection, + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode, + MutableSpan dst_curve_size, + MutableSpan dst_curve_types, + MutableSpan start_points, + MutableSpan end_points, + MutableSpan src_ranges) +{ + const VArray src_cyclic = curves.cyclic(); + const VArray resolution = curves.resolution(); + const VArray curve_types = curves.curve_types(); + + /* Compute. */ + threading::parallel_for( + selection.index_range(), 128, [&](const IndexRange selection_range) { + for (const int64_t curve_i : selection.slice(selection_range)) { + CurveType curve_type = CurveType(curve_types[curve_i]); + + int point_count; + if (curve_type == CURVE_TYPE_NURBS) { + dst_curve_types[curve_i] = CURVE_TYPE_POLY; + point_count = curves.evaluated_points_for_curve(curve_i).size(); + } + else { + dst_curve_types[curve_i] = curve_type; + point_count = curves.points_num_for_curve(curve_i); + } + if (point_count == 1) { + /* Single point. */ + dst_curve_size[curve_i] = 1; + src_ranges[curve_i] = bke::curves::IndexRangeCyclic(0, 0, 1, 1); + start_points[curve_i] = {0, 0, 0.0f}; + end_points[curve_i] = {0, 0, 0.0f}; + continue; + } + + const bool cyclic = src_cyclic[curve_i]; + const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); + BLI_assert(lengths.size() > 0); + + const float start_length = trim_sample_length(lengths, starts[curve_i], mode); + float end_length; + + bool equal_sample_point; + if (cyclic) { + end_length = trim_sample_length(lengths, ends[curve_i], mode); + const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; + const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; + equal_sample_point = cyclic_start == cyclic_end; + } + else { + end_length = ends[curve_i] <= starts[curve_i] ? + start_length : + trim_sample_length(lengths, ends[curve_i], mode); + equal_sample_point = start_length == end_length; + } + + start_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + start_length, + cyclic, + resolution[curve_i], + point_count); + if (equal_sample_point) { + end_points[curve_i] = start_points[curve_i]; + if (end_length <= start_length) { + /* Single point. */ + dst_curve_size[curve_i] = 1; + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( + start_points[curve_i].index, + start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ + point_count); + } + else { + /* Split. */ + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count) + .push_loop(); + const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + } + else { + /* General case. */ + end_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + end_length, + cyclic, + resolution[curve_i], + point_count); + + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count); + const int count = src_ranges[curve_i].size() + + !start_points[curve_i].is_controlpoint() + + !end_points[curve_i].is_controlpoint(); + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + BLI_assert(dst_curve_size[curve_i] > 0); + } + }); +} + +/** \} */ + bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, - const Span start_points, - const Span end_points) + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode) { BLI_assert(selection.size() > 0); - BLI_assert(selection.last() <= start_points.size()); - BLI_assert(start_points.size() == end_points.size()); + BLI_assert(selection.last() <= src_curves.curves_num()); + BLI_assert(starts.size() == src_curves.curves_num()); + BLI_assert(starts.size() == ends.size()); + src_curves.ensure_evaluated_lengths(); - src_curves.ensure_evaluated_offsets(); Vector inverse_selection_indices; const IndexMask inverse_selection = selection.invert(src_curves.curves_range(), inverse_selection_indices); - /* Create trim curves. */ + /* Create destination curves. */ bke::CurvesGeometry dst_curves(0, src_curves.curves_num()); - determine_copyable_curve_types(src_curves, - dst_curves, - selection, - inverse_selection, - (CurveTypeMask)(CURVE_TYPE_MASK_CATMULL_ROM | - CURVE_TYPE_MASK_POLY | CURVE_TYPE_MASK_BEZIER)); - - Vector curve_indices; - Vector point_curve_indices; - compute_trim_result_offsets(src_curves, - selection, - inverse_selection, - start_points, - end_points, - dst_curves.curve_types(), - dst_curves.offsets_for_write(), - curve_indices, - point_curve_indices); - /* Finalize by updating the geometry container. */ + MutableSpan dst_curve_offsets = dst_curves.offsets_for_write(); + MutableSpan dst_curve_types = dst_curves.curve_types_for_write(); + Array start_points(src_curves.curves_num()); + Array end_points(src_curves.curves_num()); + Array src_ranges(src_curves.curves_num()); + + if (src_curves.has_curve_with_type({CURVE_TYPE_BEZIER, CURVE_TYPE_NURBS})) { + src_curves.ensure_evaluated_offsets(); + if (src_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + src_curves.evaluated_positions(); + } + } + + /* Compute destiation curves. */ + compute_curve_trim_parameters(src_curves, + selection, + starts, + ends, + mode, + dst_curve_offsets, + dst_curve_types, + start_points, + end_points, + src_ranges); + + /* Transfer copied curves parameters. */ + const VArray src_curve_types = src_curves.curve_types(); + threading::parallel_for( + inverse_selection.index_range(), 4096, [&](const IndexRange selection_range) { + for (const int64_t curve_i : inverse_selection.slice(selection_range)) { + dst_curve_offsets[curve_i] = src_curves.points_num_for_curve(curve_i); + dst_curve_types[curve_i] = src_curve_types[curve_i]; + } + }); + /* Finalize and update the geometry container. */ + bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); dst_curves.update_curve_types(); /* Populate curve domain. */ const bke::AttributeAccessor src_attributes = src_curves.attributes(); bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); - bke::copy_attribute_domain(src_attributes, - dst_attributes, - selection, - ATTR_DOMAIN_CURVE, - {"cyclic", "curve_type", "nurbs_order", "knots_mode"}); + Set transfer_curve_skip = {"cyclic", "curve_type", "nurbs_order", "knots_mode"}; + if (dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + /* If a NURBS curve is copied keep */ + transfer_curve_skip.remove("nurbs_order"); + transfer_curve_skip.remove("knots_mode"); + } + bke::copy_attribute_domain( + src_attributes, dst_attributes, selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); /* Fetch custom point domain attributes for transfer (copy). */ Vector transfer_attributes = bke::retrieve_attributes_for_transfer( @@ -1214,61 +947,55 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, "handle_type_right", "nurbs_weight"}); - auto trim_catmull = [&](IndexMask selection) { - trim_catmull_rom_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_catmull = [&](const IndexMask selection) { + trim_catmull_rom_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_poly = [&](IndexMask selection) { - trim_polygonal_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_poly = [&](const IndexMask selection) { + trim_polygonal_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_bezier = [&](IndexMask selection) { - trim_bezier_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_bezier = [&](const IndexMask selection) { + trim_bezier_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_evaluated = [&](IndexMask selection) { + auto trim_evaluated = [&](const IndexMask selection) { /* Ensure evaluated positions are available. */ src_curves.ensure_evaluated_offsets(); src_curves.evaluated_positions(); - trim_evaluated_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); - }; - - auto single_point_catmull = [&](IndexMask selection) { - convert_point_catmull_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_poly = [&](IndexMask selection) { - convert_point_polygonal_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_bezier = [&](IndexMask selection) { - convert_point_bezier_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_evaluated = [&](IndexMask selection) { - convert_point_evaluated_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); + trim_evaluated_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; /* Populate point domain. */ bke::curves::foreach_curve_by_type(src_curves.curve_types(), src_curves.curve_type_counts(), - curve_indices.as_span(), + selection, trim_catmull, trim_poly, trim_bezier, trim_evaluated); - if (point_curve_indices.size()) { - bke::curves::foreach_curve_by_type(src_curves.curve_types(), - src_curves.curve_type_counts(), - point_curve_indices.as_span(), - single_point_catmull, - single_point_poly, - single_point_bezier, - single_point_evaluated); - } /* Cleanup/close context */ for (bke::AttributeTransferData &attribute : transfer_attributes) { attribute.dst.finish(); @@ -1276,14 +1003,21 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, /* Copy unselected */ if (!inverse_selection.is_empty()) { + transfer_curve_skip.remove("cyclic"); bke::copy_attribute_domain( - src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE); + src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); /* Trim curves are no longer cyclic. If all curves are trimmed, this will be set implicitly. */ dst_curves.cyclic_for_write().fill_indices(selection, false); + Set copy_point_skip; + if (!dst_curves.has_curve_with_type(CURVE_TYPE_NURBS) && + src_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + copy_point_skip.add("nurbs_weight"); + } + /* Copy point domain. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, copy_point_skip)) { bke::curves::copy_point_data( src_curves, dst_curves, inverse_selection, attribute.src, attribute.dst.span); attribute.dst.finish(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 15c89d78276..ea02fb9709f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -19,6 +19,7 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) static void node_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + // b.add_input(N_("Selection")).default_value(true).hide_value().supports_field(); b.add_input(N_("Start")) .min(0.0f) .max(1.0f) @@ -64,6 +65,7 @@ static void node_update(bNodeTree *ntree, bNode *node) const NodeGeometryCurveTrim &storage = node_storage(*node); const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; + // bNodeSocket *start_fac = static_cast(node->inputs.first)->next->next; bNodeSocket *start_fac = static_cast(node->inputs.first)->next; bNodeSocket *end_fac = start_fac->next; bNodeSocket *start_len = end_fac->next; @@ -109,6 +111,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) static void geometry_set_curve_trim(GeometrySet &geometry_set, const GeometryNodeCurveSampleMode mode, + Field &selection_field, Field &start_field, Field &end_field) { @@ -123,41 +126,21 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; + evaluator.add(selection_field); evaluator.add(start_field); evaluator.add(end_field); evaluator.evaluate(); - const VArray starts = evaluator.get_evaluated(0); - const VArray ends = evaluator.get_evaluated(1); - - const VArray cyclic = src_curves.cyclic(); - - /* If node length input is on form [0, 1] instead of [0, length]*/ - const bool normalized_length_lookup = mode == GEO_NODE_CURVE_SAMPLE_FACTOR; - - /* Stack start + end field. */ - Vector length_factors(src_curves.curves_num() * 2); - Vector lookup_indices(src_curves.curves_num() * 2); - threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { - for (const int64_t curve_i : curve_range) { - const bool negative_trim = !cyclic[curve_i] && starts[curve_i] > ends[curve_i]; - length_factors[curve_i] = starts[curve_i]; - length_factors[curve_i + src_curves.curves_num()] = negative_trim ? starts[curve_i] : - ends[curve_i]; - lookup_indices[curve_i] = curve_i; - lookup_indices[curve_i + src_curves.curves_num()] = curve_i; - } - }); - /* Create curve trim lookup table. */ - Array point_lookups = geometry::lookup_curve_points( - src_curves, length_factors, lookup_indices, normalized_length_lookup); + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + const VArray starts = evaluator.get_evaluated(1); + const VArray ends = evaluator.get_evaluated(2); - bke::CurvesGeometry dst_curves = geometry::trim_curves( - src_curves, - src_curves.curves_range().as_span(), - point_lookups.as_span().slice(0, src_curves.curves_num()), - point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num())); + if (selection.is_empty()) { + return; + } + bke::CurvesGeometry dst_curves = geometry::trim_curves( + src_curves, selection, starts, ends, mode); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(src_curves_id, *dst_curves_id); geometry_set.replace_curves(dst_curves_id); @@ -171,18 +154,20 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Curve"); GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + // Field selection_field = params.extract_input>("Selection"); + Field selection_field = fn::make_constant_field(true); if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { Field start_field = params.extract_input>("Start"); Field end_field = params.extract_input>("End"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start_field, end_field); + geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); }); } else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { Field start_field = params.extract_input>("Start_001"); Field end_field = params.extract_input>("End_001"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start_field, end_field); + geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); }); } -- cgit v1.2.3