Blender V4.3
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 Span<IndexRange> curve_interval_ranges,
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 int *dst = intervals.data() + curve_interval_ranges[0].size();
28
29 for (const int curve : IndexRange(1, curve_interval_ranges.size() - 1)) {
30 const IndexRange range = curve_interval_ranges[curve];
31 const int width = range.size() - 1;
32 std::copy_n(src + range.first() + 1, width, dst);
33 dst += width;
34 }
35 (*dst) = src[curve_interval_ranges[curve_interval_ranges.size() - 1].last() + 1];
36 return {intervals.data(), dst - intervals.data() + 1};
37}
38
44static bool handle_range(const int curve_index,
45 const int interval_offset,
46 const Span<int> offsets,
47 int &current_interval,
48 IndexRange &range,
49 MutableSpan<int> curve_intervals,
50 MutableSpan<bool> is_first_selected)
51{
52 const int first_elem = offsets[curve_index];
53 const int last_elem = offsets[curve_index + 1] - 1;
54
55 if (current_interval == 0) {
56 is_first_selected[curve_index] = range.first() == first_elem && range.size() == 1;
57 if (!is_first_selected[curve_index]) {
58 current_interval++;
59 }
60 }
61 curve_intervals[interval_offset + current_interval] = range.first();
62 current_interval++;
63
64 bool inside_curve = last_elem >= range.last();
65 if (inside_curve) {
66 curve_intervals[interval_offset + current_interval] = range.last();
67 }
68 else {
69 curve_intervals[interval_offset + current_interval] = last_elem;
70 range = IndexRange(last_elem + 1, range.last() - last_elem);
71 }
72 current_interval++;
73 return inside_curve;
74}
75
80static void calc_curve_offset(const int curve_index,
81 int &interval_offset,
82 const Span<int> offsets,
83 MutableSpan<int> new_offsets,
84 MutableSpan<IndexRange> curve_interval_ranges)
85{
86 const int points_in_curve = (offsets[curve_index + 1] - offsets[curve_index] +
87 curve_interval_ranges[curve_index].size() - 1);
88 new_offsets[curve_index + 1] = new_offsets[curve_index] + points_in_curve;
89 interval_offset += curve_interval_ranges[curve_index].size() + 1;
90}
91
92static void finish_curve(int &curve_index,
93 int &interval_offset,
94 int last_interval,
95 int last_elem,
96 const Span<int> offsets,
97 MutableSpan<int> new_offsets,
98 MutableSpan<int> curve_intervals,
99 MutableSpan<IndexRange> curve_interval_ranges,
100 MutableSpan<bool> is_first_selected)
101{
102 if (curve_intervals[interval_offset + last_interval] != last_elem ||
103 curve_intervals[interval_offset + last_interval - 1] !=
104 curve_intervals[interval_offset + last_interval])
105 {
106 /* Append last element of the current curve if it is not extruded or extruded together with
107 * preceding points. */
108 last_interval++;
109 curve_intervals[interval_offset + last_interval] = last_elem;
110 }
111 else if (is_first_selected[curve_index] && last_interval == 1) {
112 /* Extrusion from one point. */
113 curve_intervals[interval_offset + last_interval + 1] =
114 curve_intervals[interval_offset + last_interval];
115 is_first_selected[curve_index] = false;
116 last_interval++;
117 }
118 curve_interval_ranges[curve_index] = IndexRange(interval_offset, last_interval);
119 calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
120 curve_index++;
121}
122
123static void finish_curve_or_full_copy(int &curve_index,
124 int &interval_offset,
125 int current_interval,
126 const std::optional<IndexRange> prev_range,
127 const Span<int> offsets,
128 MutableSpan<int> new_offsets,
129 MutableSpan<int> curve_intervals,
130 MutableSpan<IndexRange> curve_interval_ranges,
131 MutableSpan<bool> is_first_selected)
132{
133 const int last = offsets[curve_index + 1] - 1;
134
135 if (prev_range.has_value() && prev_range.value().last() >= offsets[curve_index]) {
136 finish_curve(curve_index,
137 interval_offset,
138 current_interval - 1,
139 last,
140 offsets,
141 new_offsets,
142 curve_intervals,
143 curve_interval_ranges,
144 is_first_selected);
145 }
146 else {
147 /* Copy full curve if previous selected point was not on this curve. */
148 const int first = offsets[curve_index];
149 curve_interval_ranges[curve_index] = IndexRange(interval_offset, 1);
150 is_first_selected[curve_index] = false;
151 curve_intervals[interval_offset] = first;
152 curve_intervals[interval_offset + 1] = last;
153 calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
154 curve_index++;
155 }
156}
157
159 const Span<int> offsets,
160 MutableSpan<int> new_offsets,
161 MutableSpan<int> curve_intervals,
162 MutableSpan<IndexRange> curve_interval_ranges,
163 MutableSpan<bool> is_first_selected)
164{
165 std::optional<IndexRange> prev_range;
166 int current_interval = 0;
167
168 int curve_index = 0;
169 int interval_offset = 0;
170 curve_intervals[interval_offset] = offsets[0];
171 new_offsets[0] = offsets[0];
172
173 selection.foreach_range([&](const IndexRange range) {
174 /* Beginning of the range outside current curve. */
175 if (range.first() > offsets[curve_index + 1] - 1) {
176 do {
177 finish_curve_or_full_copy(curve_index,
178 interval_offset,
179 current_interval,
180 prev_range,
181 offsets,
182 new_offsets,
183 curve_intervals,
184 curve_interval_ranges,
185 is_first_selected);
186 } while (range.first() > offsets[curve_index + 1] - 1);
187 current_interval = 0;
188 curve_intervals[interval_offset] = offsets[curve_index];
189 }
190
191 IndexRange range_to_handle = range;
192 while (!handle_range(curve_index,
193 interval_offset,
194 offsets,
195 current_interval,
196 range_to_handle,
197 curve_intervals,
198 is_first_selected))
199 {
200 finish_curve(curve_index,
201 interval_offset,
202 current_interval - 1,
203 offsets[curve_index + 1] - 1,
204 offsets,
205 new_offsets,
206 curve_intervals,
207 curve_interval_ranges,
208 is_first_selected);
209 current_interval = 0;
210 curve_intervals[interval_offset] = offsets[curve_index];
211 }
212 prev_range = range;
213 });
214
215 do {
216 finish_curve_or_full_copy(curve_index,
217 interval_offset,
218 current_interval,
219 prev_range,
220 offsets,
221 new_offsets,
222 curve_intervals,
223 curve_interval_ranges,
224 is_first_selected);
225 prev_range.reset();
226 } while (curve_index < offsets.size() - 1);
227}
228
229static void extrude_curves(Curves &curves_id)
230{
231 const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
232 if (selection_domain != bke::AttrDomain::Point) {
233 return;
234 }
235
236 IndexMaskMemory memory;
237 const IndexMask extruded_points = retrieve_selected_points(curves_id, memory);
238 if (extruded_points.is_empty()) {
239 return;
240 }
241
242 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
243 const Span<int> old_offsets = curves.offsets();
244
246
247 const int curves_num = curves.curves_num();
248 const int curve_intervals_size = extruded_points.size() * 2 + curves_num * 2;
249
250 MutableSpan<int> new_offsets = new_curves.offsets_for_write();
251
252 /* Buffer for intervals of all curves. Beginning and end of a curve can be determined only by
253 * #curve_interval_ranges. For ex. [0, 3, 4, 4, 4] indicates one copy interval for first curve
254 * [0, 3] and two for second [4, 4][4, 4]. The first curve will be copied as is without changes,
255 * in the second one (consisting only one point - 4) first point will be duplicated (extruded).
256 */
257 Array<int> curve_intervals(curve_intervals_size);
258
259 /* Points to intervals for each curve in the curve_intervals array.
260 * For example above value would be [{0, 1}, {2, 2}] */
261 Array<IndexRange> curve_interval_ranges(curves_num);
262
263 /* Per curve boolean indicating if first interval in a curve is selected.
264 * Other can be calculated as in a curve two adjacent intervals can not have same selection
265 * state. */
266 Array<bool> is_first_selected(curves_num);
267
268 calc_curves_extrusion(extruded_points,
269 old_offsets,
270 new_offsets,
271 curve_intervals,
272 curve_interval_ranges,
273 is_first_selected);
274
275 new_curves.resize(new_offsets.last(), new_curves.curves_num());
276
277 const bke::AttributeAccessor src_attributes = curves.attributes();
278
279 std::array<GVArraySpan, 3> src_selection;
280 std::array<bke::GSpanAttributeWriter, 3> dst_selections;
281
282 const Span<StringRef> selection_attr_names = get_curves_selection_attribute_names(curves);
283 for (const int selection_i : selection_attr_names.index_range()) {
284 const StringRef selection_name = selection_attr_names[selection_i];
285
286 GVArray src_selection_array = *src_attributes.lookup(selection_name, bke::AttrDomain::Point);
287 if (!src_selection_array) {
288 src_selection_array = VArray<bool>::ForSingle(true, curves.points_num());
289 }
290
291 src_selection[selection_i] = src_selection_array;
292 dst_selections[selection_i] = ensure_selection_attribute(
293 new_curves,
295 src_selection_array.type().is<bool>() ? CD_PROP_BOOL : CD_PROP_FLOAT,
296 selection_name);
297 }
298
299 threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
300 for (const int curve : curves_range) {
301 const int first_index = curve_interval_ranges[curve].start();
302 const int first_value = curve_intervals[first_index];
303 bool is_selected = is_first_selected[curve];
304
305 for (const int i : curve_interval_ranges[curve]) {
306 const int dest_index = new_offsets[curve] + curve_intervals[i] - first_value + i -
307 first_index;
308 const int size = curve_intervals[i + 1] - curve_intervals[i] + 1;
309
310 for (const int selection_i : selection_attr_names.index_range()) {
311 GMutableSpan dst_span = dst_selections[selection_i].span.slice(
312 IndexRange(dest_index, size));
313 if (is_selected) {
314 src_selection[selection_i].type().copy_assign_n(
315 src_selection[selection_i].slice(IndexRange(curve_intervals[i], size)).data(),
316 dst_span.data(),
317 size);
318 }
319 else {
320 fill_selection(dst_span, false);
321 }
322 }
323
324 is_selected = !is_selected;
325 }
326 }
327 });
328
329 for (const int selection_i : selection_attr_names.index_range()) {
330 dst_selections[selection_i].finish();
331 }
332
333 const Span<int> intervals = compress_intervals(curve_interval_ranges, curve_intervals);
334
335 bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
336
338 src_attributes,
339 dst_attributes,
342 {".selection", ".selection_handle_left", ".selection_handle_right"})))
343 {
344 const CPPType &type = attribute.src.type();
345 threading::parallel_for(IndexRange(intervals.size() - 1), 512, [&](IndexRange range) {
346 for (const int i : range) {
347 const int first = intervals[i];
348 const int size = intervals[i + 1] - first + 1;
349 const int dest_index = intervals[i] + i;
350 type.copy_assign_n(attribute.src.slice(IndexRange(first, size)).data(),
351 attribute.dst.span.slice(IndexRange(dest_index, size)).data(),
352 size);
353 }
354 });
355 attribute.dst.finish();
356 }
357 curves_id.geometry.wrap() = std::move(new_curves);
359}
360
362{
363 for (Curves *curves_id : get_unique_editable_curves(*C)) {
364 extrude_curves(*curves_id);
365 }
366 return OPERATOR_FINISHED;
367}
368
370{
371 ot->name = "Extrude";
372 ot->description = "Extrude selected control point(s)";
373 ot->idname = "CURVES_OT_extrude";
374
375 ot->exec = curves_extrude_exec;
377
379}
380
381} // 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:1041
@ CD_PROP_FLOAT
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
#define C
Definition RandGen.cpp:29
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
bool is() const
constexpr int64_t first() const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr T * data() const
Definition BLI_span.hh:540
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:690
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:326
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
static VArray ForSingle(T value, const int64_t size)
GAttributeReader lookup(const StringRef attribute_id) const
void resize(int points_num, int curves_num)
MutableSpan< int > offsets_for_write()
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 void extrude_curves(Curves &curves_id)
static int curves_extrude_exec(bContext *C, wmOperator *)
VectorSet< Curves * > get_unique_editable_curves(const bContext &C)
Definition curves_ops.cc:96
static void calc_curves_extrusion(const IndexMask &selection, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
static void finish_curve_or_full_copy(int &curve_index, int &interval_offset, int current_interval, const std::optional< IndexRange > prev_range, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
static Span< int > compress_intervals(const Span< IndexRange > curve_interval_ranges, MutableSpan< int > intervals)
bool editable_curves_in_edit_mode_poll(bContext *C)
static void finish_curve(int &curve_index, int &interval_offset, int last_interval, int last_elem, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< int > curve_intervals, MutableSpan< IndexRange > curve_interval_ranges, MutableSpan< bool > is_first_selected)
Span< StringRef > get_curves_selection_attribute_names(const bke::CurvesGeometry &curves)
bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, eCustomDataType create_type, StringRef attribute_name)
static void calc_curve_offset(const int curve_index, int &interval_offset, const Span< int > offsets, MutableSpan< int > new_offsets, MutableSpan< IndexRange > curve_interval_ranges)
IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
void CURVES_OT_extrude(wmOperatorType *ot)
static bool handle_range(const int curve_index, const int interval_offset, const Span< int > offsets, int &current_interval, IndexRange &range, MutableSpan< int > curve_intervals, MutableSpan< bool > is_first_selected)
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:95
GPU_SHADER_INTERFACE_INFO(overlay_edit_curve_handle_iface, "vert").flat(Type pos vertex_in(1, Type::UINT, "data") .vertex_out(overlay_edit_curve_handle_iface) .geometry_layout(PrimitiveIn Frequency::GEOMETRY storage_buf(1, Qualifier::READ, "uint", "data[]", Frequency::GEOMETRY) .push_constant(Type Frequency::GEOMETRY selection[]
CurvesGeometry geometry
char selection_domain
wmOperatorType * ot
Definition wm_files.cc:4125