Blender V5.0
curves_extrude.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_attribute.hh"
6#include "BKE_curves_utils.hh"
7
8#include "WM_api.hh"
9#include "WM_types.hh"
10
11#include "ED_curves.hh"
12
13#include "DEG_depsgraph.hh"
14
15namespace blender::ed::curves {
16
22static Span<int> compress_intervals(const OffsetIndices<int> intervals_by_curve,
23 MutableSpan<int> intervals)
24{
25 const int *src = intervals.data();
26 /* Skip the first curve, as all the data stays in the same place.
27 * -1 to drop index denoting curve's right endpoint.
28 */
29 int *dst = intervals.data() + intervals_by_curve[0].size() - 1;
30
31 for (const int curve : intervals_by_curve.index_range().drop_front(1)) {
32 const IndexRange range = intervals_by_curve[curve];
33 /* -2 one to drop index denoting curve's beginning, second one for ending. */
34 const int width = range.size() - 2;
35 std::copy_n(src + range.first() + 1, width, dst);
36 dst += width;
37 }
38 (*dst) = src[intervals_by_curve[intervals_by_curve.size() - 1].last()];
39 return {intervals.data(), dst - intervals.data() + 1};
40}
41
42static void calc_curves_extrusion(const IndexMask &selection,
43 const OffsetIndices<int> points_by_curve,
44 MutableSpan<int> copy_intervals,
45 MutableSpan<int> curves_intervals_offsets,
46 MutableSpan<bool> is_first_selected)
47{
48 int current_endpoint_index = 0;
49 curves_intervals_offsets.first() = 0;
50
52 selection,
53 points_by_curve,
54 [&](const int curve,
55 const IndexRange curve_points,
56 const Span<IndexRange> selected_point_ranges) {
57 const IndexRange first_range = selected_point_ranges.first();
58 is_first_selected[curve] = first_range.first() == curve_points.start() &&
59 first_range.size() == 1 &&
60 /* If single point curve is extruded we want the newly created
61 * point to get selected. */
62 curve_points.size() != 1;
63 current_endpoint_index += !is_first_selected[curve];
64 copy_intervals[curves_intervals_offsets[curve]] = curve_points.start();
65
66 for (const IndexRange range : selected_point_ranges) {
67 copy_intervals[current_endpoint_index++] = range.first();
68 copy_intervals[current_endpoint_index++] = range.last();
69 }
70
71 const int last_interval_index = current_endpoint_index - 1;
72 if (copy_intervals[last_interval_index] != curve_points.last() ||
73 copy_intervals[last_interval_index - 1] != copy_intervals[last_interval_index])
74 {
75 /* Append last point of the current curve if it is not extruded or extruded together with
76 * preceding points. */
77 copy_intervals[current_endpoint_index++] = curve_points.last();
78 }
79
80 curves_intervals_offsets[curve + 1] = current_endpoint_index;
81 },
82 [&](const IndexRange curves, [[maybe_unused]] const IndexRange unselected_points) {
83 for (const int curve : curves) {
84 const IndexRange curve_points = points_by_curve[curve];
85 /* Setup interval to copy full curve. */
86 is_first_selected[curve] = false;
87 copy_intervals[current_endpoint_index++] = curve_points.first();
88 copy_intervals[current_endpoint_index++] = curve_points.last();
89 curves_intervals_offsets[curve + 1] = current_endpoint_index;
90 }
91 });
92}
93
94static void calc_new_offsets(const Span<int> old_offsets,
95 const Span<int> curves_intervals_offsets,
96 MutableSpan<int> new_offsets)
97{
98 new_offsets.first() = 0;
99 const IndexRange range = old_offsets.index_range().drop_back(1).shift(1);
100 threading::parallel_for(range, 256, [&](IndexRange index_range) {
101 for (const int i : index_range) {
102 /* -1 subtracts last interval endpoint and gives number of intervals.
103 * Another -1 from number of intervals gives number of new points created for curve.
104 * Multiplied by i because -2 are accumulated for each curve.
105 */
106 new_offsets[i] = old_offsets[i] + curves_intervals_offsets[i] - 2 * i;
107 }
108 });
109}
110
114static IndexRange shift_end_by(const IndexRange &range, const int n)
115{
116 return IndexRange::from_begin_size(range.start(), range.size() + n);
117}
118
119static float clamp_to_zero(const float value)
120{
121 return math::abs(value) < 0.00001 ? 0.0 : value;
122}
123
125 const OffsetIndices<int> intervals_by_curve,
126 const OffsetIndices<int> copy_intervals,
127 const Span<bool> is_first_selected,
128 bke::CurvesGeometry &dst_curves)
129{
130 IndexMaskMemory memory;
131 const IndexMask custom_knot_curves = curves.nurbs_custom_knot_curves(memory);
132 const Span<float> src_knots = curves.nurbs_custom_knots();
133 const VArray<int8_t> orders = curves.nurbs_orders();
134 const OffsetIndices<int> src_knots_by_curve = curves.nurbs_custom_knots_by_curve();
135
137 MutableSpan<float> dst_knots = dst_curves.nurbs_custom_knots_for_write();
138
139 custom_knot_curves.foreach_index(GrainSize(64), [&](const int64_t curve) {
140 const int order = orders[curve];
141 const bool is_first_interval_selected = is_first_selected[curve];
142 Span<float> src_curve_knots = src_knots.slice(src_knots_by_curve[curve]);
143
144 Array<float> curve_span_data(src_curve_knots.size() - 1);
145 Array<int> span_multiplicity(curve_span_data.size(), 0);
146
147 int span = 0;
148 curve_span_data[span] = clamp_to_zero(src_curve_knots[1] - src_curve_knots[0]);
149 span_multiplicity[span] = 1;
150
151 for (const int i : src_curve_knots.index_range().drop_back(1).drop_front(1)) {
152 const float span_value = clamp_to_zero(src_curve_knots[i + 1] - src_curve_knots[i]);
153 const bool is_new = abs(curve_span_data[span] - span_value) >= 0.00001;
154 span += is_new;
155 curve_span_data[span] = span_value;
156 span_multiplicity[span]++;
157 }
158
159 MutableSpan<float> curve_spans = curve_span_data.as_mutable_span().slice(0, span + 1);
160
161 const IndexRange curve_intervals = intervals_by_curve[curve];
162 const Span<int> duplicated_points =
163 copy_intervals.data().slice(curve_intervals).drop_front(1).drop_back(1);
164 const int first_curve_point = copy_intervals.data()[curve_intervals.first()];
165 Vector<int> increase_span_multiplicity;
166 increase_span_multiplicity.reserve(duplicated_points.size());
167 int first_span_knot = 0;
168 span = 0;
169
170 for (const int i : duplicated_points.index_range()) {
171 const bool is_selected = bool(i % 2) != is_first_interval_selected;
172 const int point = duplicated_points[i] - first_curve_point;
173 while (first_span_knot + span_multiplicity[span] <= point) {
174 first_span_knot += span_multiplicity[span];
175 span++;
176 }
177
178 int multiplicity = point - first_span_knot;
179 int point_span = span;
180 std::array<int, 2> side_spans{point_span, point_span};
181 int side = 0;
182 for ([[maybe_unused]] const int i : IndexRange(order)) {
183 multiplicity++;
184 if (multiplicity > span_multiplicity[point_span]) {
185 point_span++;
186 multiplicity = 1;
187 }
188 if (curve_spans[point_span] == 0.0) {
189 continue;
190 }
191 side_spans[side] = point_span;
192 side = 1;
193 side_spans[side] = point_span;
194 }
195 increase_span_multiplicity.append(side_spans[is_selected]);
196 }
197 for (const int span : increase_span_multiplicity) {
198 span_multiplicity[span]++;
199 }
200
201 const OffsetIndices<int> dst_knots_by_curve = dst_curves.nurbs_custom_knots_by_curve();
202 MutableSpan<float> dst_curve_knots = dst_knots.slice(dst_knots_by_curve[curve]);
203 int knot = 0;
204 float knot_value = src_curve_knots[knot];
205 dst_curve_knots[knot++] = knot_value;
206 for (const int span : curve_spans.index_range()) {
207 for ([[maybe_unused]] const int k : IndexRange(span_multiplicity[span])) {
208 knot_value += curve_spans[span];
209 dst_curve_knots[knot++] = knot_value;
210 }
211 }
212 });
213}
214
216 const IndexMask &extruded_points)
217{
218
220
221 const int curves_num = curves.curves_num();
222
223 /* Buffer for intervals of all curves. Beginning and end of a curve can be determined only by
224 * #curve_interval_ranges. For ex. [0, 3, 4, 4, 4] indicates one copy interval for first curve
225 * [0, 3] and two for second [4, 4][4, 4]. The first curve will be copied as is without changes,
226 * in the second one (consisting only one point - 4) first point will be duplicated (extruded).
227 */
228 Array<int> copy_interval_offsets(extruded_points.size() * 2 + curves_num * 2);
229
230 /* Points to intervals for each curve in the copy_intervals array.
231 * For example above value would be [0, 3, 5]. Meaning that [0 .. 2] are indices for curve 0 in
232 * copy_intervals array, [3 .. 4] for curve 1. */
233 Array<int> curves_intervals_offsets(curves_num + 1);
234
235 /* Per curve boolean indicating if first interval in a curve is selected.
236 * Other can be calculated as in a curve two adjacent intervals can not have same selection
237 * state. */
238 Array<bool> is_first_selected(curves_num);
239
240 calc_curves_extrusion(extruded_points,
241 curves.points_by_curve(),
242 copy_interval_offsets,
243 curves_intervals_offsets,
244 is_first_selected);
245
246 MutableSpan<int> new_offsets = new_curves.offsets_for_write();
247 calc_new_offsets(curves.offsets(), curves_intervals_offsets, new_offsets);
248 new_curves.resize(new_offsets.last(), new_curves.curves_num());
249
250 const bke::AttributeAccessor src_attributes = curves.attributes();
251
252 std::array<GVArraySpan, 3> src_selection;
253 std::array<bke::GSpanAttributeWriter, 3> dst_selections;
254
255 const Span<StringRef> selection_attr_names = get_curves_selection_attribute_names(curves);
256 for (const int selection_i : selection_attr_names.index_range()) {
257 const StringRef selection_name = selection_attr_names[selection_i];
258
259 GVArray src_selection_array = *src_attributes.lookup(selection_name, bke::AttrDomain::Point);
260 if (!src_selection_array) {
261 src_selection_array = VArray<bool>::from_single(true, curves.points_num());
262 }
263
264 src_selection[selection_i] = src_selection_array;
265 dst_selections[selection_i] = ensure_selection_attribute(
266 new_curves,
268 src_selection_array.type().is<bool>() ? bke::AttrType::Bool : bke::AttrType::Float,
269 selection_name);
270 }
271
272 const OffsetIndices<int> intervals_by_curve = curves_intervals_offsets.as_span();
273 const OffsetIndices<int> copy_intervals = copy_interval_offsets.as_span().slice(
274 0, curves_intervals_offsets.last());
275
276 threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
277 for (const int curve : curves_range) {
278 const int first_index = intervals_by_curve[curve].start();
279 const int first_value = copy_intervals[first_index].start();
280 const bool first_selected = is_first_selected[curve];
281
282 for (const int i : intervals_by_curve[curve].drop_back(1)) {
283 const bool is_selected = bool((i - first_index) % 2) != first_selected;
284 const IndexRange src = shift_end_by(copy_intervals[i], 1);
285 const IndexRange dst = src.shift(new_offsets[curve] - first_value + i - first_index);
286
287 for (const int selection_i : selection_attr_names.index_range()) {
288 GMutableSpan dst_span = dst_selections[selection_i].span.slice(dst);
289 if (is_selected) {
290 GSpan src_span = src_selection[selection_i].slice(src);
291 src_selection[selection_i].type().copy_assign_n(
292 src_span.data(), dst_span.data(), src.size());
293 }
294 else {
295 fill_selection(dst_span, false);
296 }
297 }
298 }
299 }
300 });
301
302 for (const int selection_i : selection_attr_names.index_range()) {
303 dst_selections[selection_i].finish();
304 }
305
306 if (curves.nurbs_has_custom_knots()) {
307 extrude_knots(curves, intervals_by_curve, copy_intervals, is_first_selected, new_curves);
308 }
309
310 const OffsetIndices<int> compact_intervals = compress_intervals(intervals_by_curve,
311 copy_interval_offsets);
312
313 bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
314
315 for (auto &attribute : bke::retrieve_attributes_for_transfer(
316 src_attributes,
317 dst_attributes,
319 bke::attribute_filter_from_skip_ref(selection_attr_names)))
320 {
321 const CPPType &type = attribute.src.type();
322 threading::parallel_for(compact_intervals.index_range(), 512, [&](IndexRange range) {
323 for (const int i : range) {
324 const IndexRange src = shift_end_by(compact_intervals[i], 1);
325 const IndexRange dst = src.shift(i);
326 type.copy_assign_n(
327 attribute.src.slice(src).data(), attribute.dst.span.slice(dst).data(), src.size());
328 }
329 });
330 attribute.dst.finish();
331 }
332 return new_curves;
333}
334
336{
337 bool extruded = false;
338 for (Curves *curves_id : get_unique_editable_curves(*C)) {
339 const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id->selection_domain);
340 if (selection_domain != bke::AttrDomain::Point) {
341 continue;
342 }
343
344 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
345 IndexMaskMemory memory;
346 const IndexMask extruded_points = retrieve_selected_points(curves, memory);
347 if (extruded_points.is_empty()) {
348 continue;
349 }
350
351 curves_id->geometry.wrap() = extrude_curves(curves, extruded_points);
352 DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
353 extruded = true;
354 }
355 return extruded ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
356}
357
359{
360 ot->name = "Extrude";
361 ot->description = "Extrude selected control point(s)";
362 ot->idname = "CURVES_OT_extrude";
363
364 ot->exec = curves_extrude_exec;
366
368}
369
370} // namespace blender::ed::curves
@ ATTR_DOMAIN_MASK_POINT
Low-level operations for curves.
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
#define C
Definition RandGen.cpp:29
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
long long int int64_t
int64_t size() const
Definition BLI_array.hh:256
Span< T > as_span() const
Definition BLI_array.hh:243
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
const T & last(const int64_t n=0) const
Definition BLI_array.hh:296
bool is() const
constexpr int64_t first() const
constexpr IndexRange shift(int64_t n) const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
static constexpr IndexRange from_begin_size(const int64_t begin, const int64_t size)
constexpr int64_t start() const
constexpr IndexRange drop_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr T * data() const
Definition BLI_span.hh:539
constexpr T & first() const
Definition BLI_span.hh:679
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
static VArray from_single(T value, const int64_t size)
void append(const T &value)
void reserve(const int64_t min_capacity)
GAttributeReader lookup(const StringRef attribute_id) const
MutableSpan< float > nurbs_custom_knots_for_write()
void resize(int points_num, int curves_num)
MutableSpan< int > offsets_for_write()
OffsetIndices< int > nurbs_custom_knots_by_curve() const
void foreach_index(Fn &&fn) const
#define abs
void foreach_selected_point_ranges_per_curve(const IndexMask &mask, OffsetIndices< int > points_by_curve, SelectedCallback selected_fn)
bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves)
Vector< AttributeTransferData > retrieve_attributes_for_transfer(const AttributeAccessor src_attributes, MutableAttributeAccessor dst_attributes, AttrDomainMask domain_mask, const AttributeFilter &attribute_filter={})
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
static bke::CurvesGeometry extrude_curves(const bke::CurvesGeometry &curves, const IndexMask &extruded_points)
static void calc_new_offsets(const Span< int > old_offsets, const Span< int > curves_intervals_offsets, MutableSpan< int > new_offsets)
VectorSet< Curves * > get_unique_editable_curves(const bContext &C)
Definition curves_ops.cc:91
static void calc_curves_extrusion(const IndexMask &selection, const OffsetIndices< int > points_by_curve, MutableSpan< int > copy_intervals, MutableSpan< int > curves_intervals_offsets, MutableSpan< bool > is_first_selected)
static float clamp_to_zero(const float value)
bool editable_curves_in_edit_mode_poll(bContext *C)
static void extrude_knots(const bke::CurvesGeometry &curves, const OffsetIndices< int > intervals_by_curve, const OffsetIndices< int > copy_intervals, const Span< bool > is_first_selected, bke::CurvesGeometry &dst_curves)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
static Span< int > compress_intervals(const OffsetIndices< int > intervals_by_curve, MutableSpan< int > intervals)
static wmOperatorStatus curves_extrude_exec(bContext *C, wmOperator *)
static IndexRange shift_end_by(const IndexRange &range, const int n)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, bke::AttrType create_type, StringRef attribute_name)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
void CURVES_OT_extrude(wmOperatorType *ot)
T abs(const T &a)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
i
Definition text_draw.cc:230
wmOperatorType * ot
Definition wm_files.cc:4237