Blender V5.0
merge_curves.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 "BLI_array_utils.hh"
6#include "BLI_stack.hh"
7
8#include "BKE_curves_utils.hh"
9#include "BKE_deform.hh"
10
11#include "GEO_merge_curves.hh"
12
13namespace blender::geometry {
14
15enum Flag {
18};
19
20template<typename Fn>
21static void foreach_connected_curve(const Span<int> connect_to_curve,
23 const int start,
24 Fn fn)
25{
26 const IndexRange range = connect_to_curve.index_range();
27
28 Stack<int> stack;
29
30 bool has_cycle = false;
31 auto push_curve = [&](const int curve_i) -> bool {
32 if ((flags[curve_i] & Inserted) != 0) {
33 return false;
34 }
35 if ((flags[curve_i] & OnStack) != 0) {
36 has_cycle = true;
37 return false;
38 }
39 stack.push(curve_i);
40 flags[curve_i] |= OnStack;
41 fn(curve_i);
42 return true;
43 };
44
45 push_curve(start);
46
47 while (!stack.is_empty()) {
48 const int current = stack.peek();
49
50 const int next = connect_to_curve[current];
51 if (range.contains(next)) {
52 if (push_curve(next)) {
53 continue;
54 }
55 }
56
57 flags[current] |= Inserted;
58 stack.pop();
59 }
60
61 UNUSED_VARS(has_cycle);
62}
63
64/* Topological sorting that puts connected curves into contiguous ranges. */
65static Vector<int> toposort_connected_curves(const Span<int> connect_to_curve)
66{
67 const IndexRange range = connect_to_curve.index_range();
68
69 Array<uint8_t> flags(connect_to_curve.size());
70
71 /* First add all open chains by finding curves without a connection. */
72 Array<bool> is_start_curve(range.size(), true);
73 for (const int curve_i : range) {
74 const int next = connect_to_curve[curve_i];
75 if (range.contains(next)) {
76 is_start_curve[next] = false;
77 }
78 }
79 /* Mark all curves that can be reached from a start curve. These must not be added before the
80 * start curve, or it can lead to gaps in curve ranges. */
81 flags.fill(0);
82 Array<bool> is_reachable(range.size(), false);
83 for (const int curve_i : range) {
84 if (is_start_curve[curve_i]) {
86 connect_to_curve, flags, curve_i, [&](const int index) { is_reachable[index] = true; });
87 }
88 }
89
90 Vector<int> sorted_curves;
91 sorted_curves.reserve(connect_to_curve.size());
92
93 flags.fill(0);
94 for (const int curve_i : range) {
95 if (is_start_curve[curve_i] || !is_reachable[curve_i]) {
97 connect_to_curve, flags, curve_i, [&](const int index) { sorted_curves.append(index); });
98 }
99 }
100
101 BLI_assert(sorted_curves.size() == range.size());
102 return sorted_curves;
103}
104
105/* TODO Add an optimized function for reversing the order of spans. */
107{
108 const CPPType &cpptype = span.type();
109 BUFFER_FOR_CPP_TYPE_VALUE(cpptype, buffer);
110 cpptype.default_construct(buffer);
111
112 for (const int i : IndexRange(span.size() / 2)) {
113 const int mirror_i = span.size() - 1 - i;
114 /* Swap. */
115 cpptype.move_assign(span[i], buffer);
116 cpptype.move_assign(span[mirror_i], span[i]);
117 cpptype.move_assign(buffer, span[mirror_i]);
118 }
119
120 cpptype.destruct(buffer);
121}
122
124 const bke::AttributeAccessor src_attributes,
125 const bke::AttrDomain domain,
126 const OffsetIndices<int> src_offsets,
127 const OffsetIndices<int> dst_offsets,
128 const Span<int> old_by_new_map,
129 const Span<bool> flip_direction,
130 bke::MutableAttributeAccessor dst_attributes)
131{
132 src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
133 if (iter.domain != domain) {
134 return;
135 }
136 if (iter.data_type == bke::AttrType::String) {
137 return;
138 }
139 const GVArray src = *iter.get(domain);
141 iter.name, domain, iter.data_type);
142 if (!dst) {
143 return;
144 }
145
146 threading::parallel_for(old_by_new_map.index_range(), 1024, [&](const IndexRange range) {
147 for (const int new_i : range) {
148 const int old_i = old_by_new_map[new_i];
149 const bool flip = flip_direction[old_i];
150
151 GMutableSpan dst_span = dst.span.slice(dst_offsets[new_i]);
152 array_utils::copy(src.slice(src_offsets[old_i]), dst_span);
153 if (flip) {
154 reverse_order(dst_span);
155 }
156 }
157 });
158
159 dst.finish();
160 });
161}
162
164 const Span<int> old_by_new_map,
165 const Span<bool> flip_direction)
166{
167 bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves);
168
172 {},
173 old_by_new_map,
174 dst_curves.attributes_for_write());
175
176 const Span<int> old_offsets = src_curves.offsets();
177 MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
178 offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
180
183 old_offsets,
184 new_offsets.as_span(),
185 old_by_new_map,
186 flip_direction,
187 dst_curves.attributes_for_write());
188 dst_curves.tag_topology_changed();
189 return dst_curves;
190}
191
192/* Build new offsets array for connected ranges. */
193static void find_connected_ranges(const bke::CurvesGeometry &src_curves,
194 const Span<int> old_by_new_map,
195 Span<int> connect_to_curve,
196 Span<bool> cyclic,
197 Vector<int> &r_joined_curve_offsets,
198 Vector<bool> &r_joined_cyclic)
199{
200 const IndexRange curves_range = src_curves.curves_range();
201
202 Array<int> new_by_old_map(old_by_new_map.size());
203 for (const int dst_i : old_by_new_map.index_range()) {
204 const int src_i = old_by_new_map[dst_i];
205 new_by_old_map[src_i] = dst_i;
206 }
207
208 r_joined_curve_offsets.reserve(curves_range.size() + 1);
209 r_joined_cyclic.reserve(curves_range.size());
210
211 int start_index = -1;
212 for (const int dst_i : curves_range) {
213 const int src_i = old_by_new_map[dst_i];
214 /* Strokes are cyclic if they are not connected and the original stroke is cyclic, or if the
215 * the last stroke of a chain is merged with the first stroke. */
216 const bool src_cyclic = cyclic[src_i];
217
218 if (start_index < 0) {
219 r_joined_curve_offsets.append(0);
220 r_joined_cyclic.append(src_cyclic);
221 start_index = dst_i;
222 }
223
224 ++r_joined_curve_offsets.last();
225
226 const int src_connect_to = connect_to_curve[src_i];
227 const bool is_connected = curves_range.contains(src_connect_to);
228 const int dst_connect_to = is_connected ? new_by_old_map[src_connect_to] : -1;
229
230 /* Check for end of chain. */
231 if (dst_connect_to != dst_i + 1) {
232 /* Set cyclic state for connected curves.
233 * Becomes cyclic if connected to the start. */
234 const bool is_chain = (is_connected || dst_i != start_index);
235 if (is_chain) {
236 r_joined_cyclic.last() = (dst_connect_to == start_index);
237 }
238 /* Start new curve. */
239 start_index = -1;
240 }
241 }
242 /* Offsets has one more entry for the overall size. */
243 r_joined_curve_offsets.append(0);
244
245 offset_indices::accumulate_counts_to_offsets(r_joined_curve_offsets);
246}
247
249 const OffsetIndices<int> old_curves_by_new)
250{
251 bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves.points_num(),
252 old_curves_by_new.size());
253 /* Copy vertex group names. */
255 dst_curves.attributes_active_index = src_curves.attributes_active_index;
256
257 /* NOTE: using the offsets as an index map means the first curve of each range is used for
258 * attributes. */
259 const Span<int> old_by_new_map = old_curves_by_new.data().drop_back(1);
264 old_by_new_map,
265 dst_curves.attributes_for_write());
266
267 const OffsetIndices old_points_by_curve = src_curves.points_by_curve();
268 MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
269 new_offsets.fill(0);
270 for (const int new_i : new_offsets.index_range().drop_back(1)) {
271 const IndexRange old_curves = old_curves_by_new[new_i];
272 new_offsets[new_i] = offset_indices::sum_group_sizes(old_points_by_curve, old_curves);
273 }
275
276 /* Point attributes copied without changes. */
277 bke::copy_attributes(src_curves.attributes(),
280 {},
281 dst_curves.attributes_for_write());
282
283 dst_curves.tag_topology_changed();
284 return dst_curves;
285}
286
288 Span<int> connect_to_curve,
289 Span<bool> flip_direction,
290 const bke::AttributeFilter & /*attribute_filter*/)
291{
292 BLI_assert(connect_to_curve.size() == src_curves.curves_num());
293 const VArraySpan<bool> src_cyclic = src_curves.cyclic();
294
295 Vector<int> old_by_new_map = toposort_connected_curves(connect_to_curve);
296
297 Vector<int> joined_curve_offsets;
298 Vector<bool> cyclic;
300 src_curves, old_by_new_map, connect_to_curve, src_cyclic, joined_curve_offsets, cyclic);
301
303 src_curves, old_by_new_map, flip_direction);
304
305 OffsetIndices joined_curves_by_new = OffsetIndices<int>(joined_curve_offsets);
306 bke::CurvesGeometry merged_curves = join_curves_ranges(ordered_curves, joined_curves_by_new);
307 merged_curves.cyclic_for_write().copy_from(cyclic);
308
313 if (src_curves.nurbs_has_custom_knots()) {
317 merged_curves);
318 }
319 return merged_curves;
320}
321
322} // namespace blender::geometry
Low-level operations for curves.
support for deformation groups and hooks.
void BKE_defgroup_copy_list(ListBase *outbase, const ListBase *inbase)
Definition deform.cc:73
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name)
#define UNUSED_VARS(...)
@ NURBS_KNOT_MODE_NORMAL
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
void fill(const T &value) const
Definition BLI_array.hh:272
void move_assign(void *src, void *dst) const
void destruct(void *ptr) const
void default_construct(void *ptr) const
const CPPType & type() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t size() const
constexpr bool contains(int64_t value) const
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr Span< T > as_span() const
Definition BLI_span.hh:661
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
bool is_empty() const
Definition BLI_stack.hh:308
void push(const T &value)
Definition BLI_stack.hh:213
int64_t size() const
void append(const T &value)
const T & last(const int64_t n=0) const
void reserve(const int64_t min_capacity)
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader get() const
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
AttributeAccessor attributes() const
MutableSpan< int > offsets_for_write()
bool nurbs_has_custom_knots() const
VArray< bool > cyclic() const
MutableSpan< bool > cyclic_for_write()
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
static ulong * next
void update_custom_knot_modes(const IndexMask &mask, const KnotsMode mode_for_regular, const KnotsMode mode_for_cyclic, bke::CurvesGeometry &curves)
auto attribute_filter_from_skip_ref(const Span< StringRef > skip)
void copy_attributes(const AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, MutableAttributeAccessor dst_attributes)
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
static Vector< int > toposort_connected_curves(const Span< int > connect_to_curve)
static bke::CurvesGeometry reorder_and_flip_curves(const bke::CurvesGeometry &src_curves, const Span< int > old_by_new_map, const Span< bool > flip_direction)
static void foreach_connected_curve(const Span< int > connect_to_curve, MutableSpan< uint8_t > flags, const int start, Fn fn)
static void reorder_and_flip_attributes_group_to_group(const bke::AttributeAccessor src_attributes, const bke::AttrDomain domain, const OffsetIndices< int > src_offsets, const OffsetIndices< int > dst_offsets, const Span< int > old_by_new_map, const Span< bool > flip_direction, bke::MutableAttributeAccessor dst_attributes)
static void reverse_order(GMutableSpan span)
static bke::CurvesGeometry join_curves_ranges(const bke::CurvesGeometry &src_curves, const OffsetIndices< int > old_curves_by_new)
static void find_connected_ranges(const bke::CurvesGeometry &src_curves, const Span< int > old_by_new_map, Span< int > connect_to_curve, Span< bool > cyclic, Vector< int > &r_joined_curve_offsets, Vector< bool > &r_joined_cyclic)
bke::CurvesGeometry curves_merge_endpoints(const bke::CurvesGeometry &src_curves, Span< int > connect_to_curve, Span< bool > flip_direction, const bke::AttributeFilter &attribute_filter)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
void gather_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask, MutableSpan< int > sizes)
int sum_group_sizes(OffsetIndices< int > offsets, const IndexMask &mask)
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
ListBase vertex_group_names
i
Definition text_draw.cc:230