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