Blender V5.0
mesh_split_edges.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_index_mask.hh"
7#include "BLI_ordered_edge.hh"
8
9#include "BKE_attribute.hh"
10#include "BKE_attribute_math.hh"
11#include "BKE_customdata.hh"
12#include "BKE_mesh.hh"
13#include "BKE_mesh_mapping.hh"
14
15#include "GEO_mesh_selection.hh"
17#include "GEO_randomize.hh"
18
19namespace blender::geometry {
20
21static void propagate_vert_attributes(Mesh &mesh, const Span<int> new_to_old_verts_map)
22{
23 /* These types aren't supported for interpolation below. */
28 &mesh.vert_data, mesh.verts_num, mesh.verts_num + new_to_old_verts_map.size());
29 mesh.verts_num += new_to_old_verts_map.size();
30
31 bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
32 for (const StringRef id : attributes.all_ids()) {
33 const bke::AttributeMetaData meta_data = *attributes.lookup_meta_data(id);
34 if (meta_data.domain != bke::AttrDomain::Point) {
35 continue;
36 }
37 if (meta_data.data_type == bke::AttrType::String) {
38 continue;
39 }
40 bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
41 if (!attribute) {
42 continue;
43 }
45 new_to_old_verts_map,
46 attribute.span.take_back(new_to_old_verts_map.size()));
47 attribute.finish();
48 }
49 if (float3 *orco = static_cast<float3 *>(
51 {
53 new_to_old_verts_map,
54 MutableSpan(orco, mesh.verts_num).take_back(new_to_old_verts_map.size()));
55 }
56 if (int *orig_indices = static_cast<int *>(
58 {
60 Span(orig_indices, mesh.verts_num),
61 new_to_old_verts_map,
62 MutableSpan(orig_indices, mesh.verts_num).take_back(new_to_old_verts_map.size()));
63 }
64}
65
66static void propagate_edge_attributes(Mesh &mesh, const Span<int> new_to_old_edge_map)
67{
68 CustomData_realloc(&mesh.edge_data, mesh.edges_num, mesh.edges_num + new_to_old_edge_map.size());
69 mesh.edges_num += new_to_old_edge_map.size();
70
71 bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
72 for (const StringRef id : attributes.all_ids()) {
73 const bke::AttributeMetaData meta_data = *attributes.lookup_meta_data(id);
74 if (meta_data.domain != bke::AttrDomain::Edge) {
75 continue;
76 }
77 if (meta_data.data_type == bke::AttrType::String) {
78 continue;
79 }
80 if (id == ".edge_verts") {
81 /* Edge vertices are updated and combined with new edges separately. */
82 continue;
83 }
84 bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
85 if (!attribute) {
86 continue;
87 }
89 attribute.span, new_to_old_edge_map, attribute.span.take_back(new_to_old_edge_map.size()));
90 attribute.finish();
91 }
92
93 if (int *orig_indices = static_cast<int *>(
95 {
97 Span(orig_indices, mesh.edges_num),
98 new_to_old_edge_map,
99 MutableSpan(orig_indices, mesh.edges_num).take_back(new_to_old_edge_map.size()));
100 }
101}
102
110static int corner_on_edge_connected_to_vert(const Span<int> corner_verts,
111 const int corner,
112 const IndexRange face,
113 const int vert)
114{
115 if (corner_verts[corner] == vert) {
116 return corner;
117 }
118 const int other = bke::mesh::face_corner_next(face, corner);
119 BLI_assert(corner_verts[other] == vert);
120 return other;
121}
122
124
134 const Span<int> corner_verts,
135 const Span<int> corner_edges,
136 const GroupedSpan<int> edge_to_corner_map,
137 const Span<int> corner_to_face_map,
138 const BitSpan split_edges,
139 const Span<int> connected_corners,
140 const int vert)
141{
142 Vector<CornerGroup> groups;
143 /* Each corner should only be added to a single group. */
144 BitVector<> used_corners(connected_corners.size());
145 for (const int start_corner : connected_corners) {
146 CornerGroup group;
147 Vector<int> corner_stack({start_corner});
148 while (!corner_stack.is_empty()) {
149 const int corner = corner_stack.pop_last();
150 const int i = connected_corners.first_index(corner);
151 if (used_corners[i]) {
152 continue;
153 }
154 used_corners[i].set();
155 group.append(corner);
156 const int face = corner_to_face_map[corner];
157 const int prev_corner = bke::mesh::face_corner_prev(faces[face], corner);
158 /* Travel across the two edges neighboring this vertex, if they aren't split. */
159 for (const int edge : {corner_edges[corner], corner_edges[prev_corner]}) {
160 if (split_edges[edge]) {
161 continue;
162 }
163 for (const int other_corner : edge_to_corner_map[edge]) {
164 const int other_face = corner_to_face_map[other_corner];
165 if (other_face == face) {
166 /* Avoid continuing back to the same face. */
167 continue;
168 }
169 const int neighbor_corner = corner_on_edge_connected_to_vert(
170 corner_verts, other_corner, faces[other_face], vert);
171 corner_stack.append(neighbor_corner);
172 }
173 }
174 }
175 if (!group.is_empty()) {
176 groups.append(std::move(group));
177 }
178 }
179
180 return groups;
181}
182
183/* Calculate groups of corners that are contiguously connected to each input vertex.
184 * BLI_NOINLINE because MSVC 17.7 has a codegen bug here, given there is only a single call to this
185 * function, not inlining it for all platforms won't affect performance. See
186 * https://developercommunity.visualstudio.com/t/10448291 for details. */
189 const Span<int> corner_verts,
190 const Span<int> corner_edges,
191 const GroupedSpan<int> vert_to_corner_map,
192 const GroupedSpan<int> edge_to_corner_map,
193 const Span<int> corner_to_face_map,
194 const BitSpan split_edges,
195 const IndexMask &affected_verts)
196{
197 Array<Vector<CornerGroup>> corner_groups(affected_verts.size(), NoInitialization());
198 affected_verts.foreach_index(GrainSize(512), [&](const int vert, const int mask) {
199 new (&corner_groups[mask])
201 corner_verts,
202 corner_edges,
203 edge_to_corner_map,
204 corner_to_face_map,
206 vert_to_corner_map[vert],
207 vert));
208 });
209 return corner_groups;
210}
211
217
220 const BitSpan loose_edges,
221 const BitSpan split_edges,
222 const int vert)
223{
224 VertLooseEdges info;
225 for (const int edge : vert_to_edge_map[vert]) {
226 if (loose_edges[edge]) {
227 if (split_edges[edge]) {
228 info.selected.append(edge);
229 }
230 else {
231 info.unselected.append(edge);
232 }
233 }
234 }
235 return info;
236}
237
250 const IndexMask &affected_verts,
251 const Span<Vector<CornerGroup>> corner_groups,
252 const GroupedSpan<int> vert_to_edge_map,
253 const BitSpan loose_edges,
254 const BitSpan split_edges,
255 Array<int> &offset_data)
256{
257 offset_data.reinitialize(affected_verts.size() + 1);
258 MutableSpan<int> new_verts_nums = offset_data;
259 threading::parallel_for(affected_verts.index_range(), 2048, [&](const IndexRange range) {
260 /* Start with -1 for the reused vertex. None of the final sizes should be negative. */
261 new_verts_nums.slice(range).fill(-1);
262 for (const int i : range) {
263 new_verts_nums[i] += corner_groups[i].size();
264 }
265 });
266 if (!loose_edges.is_empty()) {
267 affected_verts.foreach_index(GrainSize(512), [&](const int vert, const int mask) {
268 const VertLooseEdges info = calc_vert_loose_edges(
269 vert_to_edge_map, loose_edges, split_edges, vert);
270 new_verts_nums[mask] += info.selected.size();
271 if (corner_groups[mask].is_empty()) {
272 /* Loose edges share their vertex with a corner group if possible. */
273 new_verts_nums[mask] += info.unselected.size() > 0;
274 }
275 });
276 }
278}
279
285static void update_corner_verts(const int orig_verts_num,
286 const Span<Vector<CornerGroup>> corner_groups,
287 const OffsetIndices<int> new_verts_by_affected_vert,
288 MutableSpan<int> new_corner_verts)
289{
290 threading::parallel_for(corner_groups.index_range(), 512, [&](const IndexRange range) {
291 for (const int new_vert : range) {
292 const Span<CornerGroup> groups = corner_groups[new_vert];
293 const IndexRange new_verts = new_verts_by_affected_vert[new_vert];
294 for (const int group : groups.index_range().drop_back(1)) {
295 const int new_vert = orig_verts_num + new_verts[group];
296 new_corner_verts.fill_indices(groups[group].as_span(), new_vert);
297 }
298 }
299 });
300}
301
303 const Span<int> corner_verts,
304 const Span<int> corner_to_face_map,
305 const int corner)
306{
307 const int face = corner_to_face_map[corner];
308 const int corner_next = bke::mesh::face_corner_next(faces[face], corner);
309 return OrderedEdge(corner_verts[corner], corner_verts[corner_next]);
310}
311
320 const Span<int> corner_verts,
321 const GroupedSpan<int> edge_to_corner_map,
322 const Span<int> corner_to_face_map,
323 const IndexMask &selected_edges,
324 MutableSpan<int2> edges,
325 MutableSpan<int> corner_edges,
326 MutableSpan<int> r_new_edge_offsets)
327{
328 /* Calculate the offset of new edges assuming no new edges are identical and are merged. */
329 selected_edges.foreach_index_optimized<int>(
330 GrainSize(4096), [&](const int edge, const int mask) {
331 r_new_edge_offsets[mask] = std::max<int>(edge_to_corner_map[edge].size() - 1, 0);
332 });
333 const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(r_new_edge_offsets);
334
335 Array<int2> new_edges(offsets.total_size());
336
337 /* Count the number of final new edges per edge, to use as offsets if there are duplicates. */
338 Array<int> num_edges_per_edge_merged(r_new_edge_offsets.size());
339 std::atomic<bool> found_duplicate = false;
340
341 /* The first new edge for each selected edge is reused-- we modify the existing edge in
342 * place. Simply reusing the first new edge isn't enough because deduplication might make
343 * multiple new edges reuse the original. */
344 Array<bool> is_reused(corner_verts.size(), false);
345
346 /* Calculate per-original split edge deduplication of new edges, which are stored by the
347 * corner vertices of connected faces. Update corner verts to store the updated indices. */
348 selected_edges.foreach_index(GrainSize(1024), [&](const int edge, const int mask) {
349 if (edge_to_corner_map[edge].is_empty()) {
350 /* Handle loose edges. */
351 num_edges_per_edge_merged[mask] = 0;
352 return;
353 }
354
355 const int new_edges_start = offsets[mask].start();
356 Vector<OrderedEdge> deduplication;
357 for (const int corner : edge_to_corner_map[edge]) {
358 const OrderedEdge edge = edge_from_corner(faces, corner_verts, corner_to_face_map, corner);
359 int index = deduplication.first_index_of_try(edge);
360 if (UNLIKELY(index != -1)) {
361 found_duplicate.store(true, std::memory_order_relaxed);
362 }
363 else {
364 index = deduplication.append_and_get_index(edge);
365 }
366
367 if (index == 0) {
368 is_reused[corner] = true;
369 }
370 else {
371 corner_edges[corner] = edges.size() + new_edges_start + index - 1;
372 }
373 }
374
375 const int new_edges_num = deduplication.size() - 1;
376
377 edges[edge] = int2(deduplication.first().v_low, deduplication.first().v_high);
378 new_edges.as_mutable_span()
379 .slice(new_edges_start, new_edges_num)
380 .copy_from(deduplication.as_span().drop_front(1).cast<int2>());
381
382 num_edges_per_edge_merged[mask] = new_edges_num;
383 });
384
385 if (!found_duplicate) {
386 /* No edges were merged, we can use the existing output array and offsets. */
387 return new_edges;
388 }
389
390 /* Update corner edges to remove the "holes" left by merged new edges. */
392 num_edges_per_edge_merged);
393 selected_edges.foreach_index(GrainSize(2048), [&](const int edge, const int mask) {
394 const int difference = offsets[mask].start() - offsets_merged[mask].start();
395 for (const int corner : edge_to_corner_map[edge]) {
396 if (!is_reused[corner]) {
397 corner_edges[corner] -= difference;
398 }
399 }
400 });
401
402 /* Create new edges without the empty slots for the duplicates */
403 Array<int2> new_edges_merged(offsets_merged.total_size());
404 threading::parallel_for(offsets_merged.index_range(), 1024, [&](const IndexRange range) {
405 for (const int i : range) {
406 new_edges_merged.as_mutable_span()
407 .slice(offsets_merged[i])
408 .copy_from(new_edges.as_span().slice(offsets[i].start(), offsets_merged[i].size()));
409 }
410 });
411
412 r_new_edge_offsets.copy_from(num_edges_per_edge_merged);
413 return new_edges_merged;
414}
415
417 const Span<int> corner_verts,
418 const GroupedSpan<int> edge_to_corner_map,
419 const Span<int> corner_to_face_map,
420 const IndexMask &unselected_edges,
421 MutableSpan<int2> edges)
422{
423 unselected_edges.foreach_index(GrainSize(1024), [&](const int edge) {
424 const Span<int> edge_corners = edge_to_corner_map[edge];
425 if (edge_corners.is_empty()) {
426 return;
427 }
428 const int corner = edge_corners.first();
429 const OrderedEdge new_edge = edge_from_corner(faces, corner_verts, corner_to_face_map, corner);
430 edges[edge] = int2(new_edge.v_low, new_edge.v_high);
431 });
432}
433
434static void swap_edge_vert(int2 &edge, const int old_vert, const int new_vert)
435{
436 if (edge[0] == old_vert) {
437 edge[0] = new_vert;
438 }
439 else if (edge[1] == old_vert) {
440 edge[1] = new_vert;
441 }
442}
443
449static void reassign_loose_edge_verts(const int orig_verts_num,
450 const IndexMask &affected_verts,
451 const GroupedSpan<int> vert_to_edge_map,
452 const BitSpan loose_edges,
453 const BitSpan split_edges,
454 const Span<Vector<CornerGroup>> corner_groups,
455 const OffsetIndices<int> new_verts_by_affected_vert,
456 MutableSpan<int2> edges)
457{
458 affected_verts.foreach_index(GrainSize(1024), [&](const int vert, const int mask) {
459 const IndexRange new_verts = new_verts_by_affected_vert[mask];
460 /* Account for the reuse of the original vertex by non-loose corner groups. In practice this
461 * means using the new vertices for each split loose edge until we run out of new vertices.
462 * We then expect the count to match up with the number of new vertices reserved by
463 * #calc_vert_ranges_per_old_vert. */
464 int new_vert_i = std::max<int>(corner_groups[mask].size() - 1, 0);
465 if (new_vert_i == new_verts.size()) {
466 return;
467 }
468 const VertLooseEdges vert_info = calc_vert_loose_edges(
469 vert_to_edge_map, loose_edges, split_edges, vert);
470 for (const int edge : vert_info.selected) {
471 const int new_vert = orig_verts_num + new_verts[new_vert_i];
472 swap_edge_vert(edges[edge], vert, new_vert);
473 new_vert_i++;
474 if (new_vert_i == new_verts.size()) {
475 return;
476 }
477 }
478 const int new_vert = orig_verts_num + new_verts[new_vert_i];
479 for (const int orig_edge : vert_info.unselected) {
480 swap_edge_vert(edges[orig_edge], vert, new_vert);
481 }
482 });
483}
484
490{
491 Array<int> map(offsets.total_size());
492 mask.foreach_index(GrainSize(1024), [&](const int i, const int mask) {
493 map.as_mutable_span().slice(offsets[mask]).fill(i);
494 });
495 return map;
496}
497
498void split_edges(Mesh &mesh,
499 const IndexMask &selected_edges,
500 const bke::AttributeFilter & /*attribute_filter*/)
501{
502 const int orig_verts_num = mesh.verts_num;
503 const Span<int2> orig_edges = mesh.edges();
504 const OffsetIndices faces = mesh.faces();
505
506 IndexMaskMemory memory;
507 const IndexMask affected_verts = vert_selection_from_edge(
508 orig_edges, selected_edges, orig_verts_num, memory);
509 BitVector<> selection_bits(orig_edges.size());
510 selected_edges.to_bits(selection_bits);
511 const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
512
513 const GroupedSpan<int> vert_to_corner_map = mesh.vert_to_corner_map();
514
515 Array<int> edge_to_corner_offsets;
516 Array<int> edge_to_corner_indices;
517 const GroupedSpan<int> edge_to_corner_map = bke::mesh::build_edge_to_corner_map(
518 mesh.corner_edges(), orig_edges.size(), edge_to_corner_offsets, edge_to_corner_indices);
519
520 Array<int> vert_to_edge_offsets;
521 Array<int> vert_to_edge_indices;
522 GroupedSpan<int> vert_to_edge_map;
523 if (loose_edges.count > 0) {
524 vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
525 orig_edges, orig_verts_num, vert_to_edge_offsets, vert_to_edge_indices);
526 }
527
528 const Array<int> corner_to_face_map = mesh.corner_to_face_map();
529
531 mesh.corner_verts(),
532 mesh.corner_edges(),
533 vert_to_corner_map,
534 edge_to_corner_map,
535 corner_to_face_map,
536 selection_bits,
537 affected_verts);
538
539 Array<int> vert_new_vert_offset_data;
540 const OffsetIndices new_verts_by_affected_vert = calc_vert_ranges_per_old_vert(
541 affected_verts,
542 corner_groups,
543 vert_to_edge_map,
544 loose_edges.is_loose_bits,
545 selection_bits,
546 vert_new_vert_offset_data);
547
548 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
549 update_corner_verts(orig_verts_num, corner_groups, new_verts_by_affected_vert, corner_verts);
550
551 Array<int> new_edge_offsets(selected_edges.size() + 1);
552 Array<int2> new_edges = calc_new_edges(faces,
553 corner_verts,
554 edge_to_corner_map,
555 corner_to_face_map,
556 selected_edges,
557 mesh.edges_for_write(),
558 mesh.corner_edges_for_write(),
559 new_edge_offsets);
560 const IndexMask unselected_edges = selected_edges.complement(orig_edges.index_range(), memory);
562 corner_verts,
563 edge_to_corner_map,
564 corner_to_face_map,
565 unselected_edges,
566 mesh.edges_for_write());
567
568 if (loose_edges.count > 0) {
569 reassign_loose_edge_verts(orig_verts_num,
570 affected_verts,
571 vert_to_edge_map,
572 loose_edges.is_loose_bits,
573 selection_bits,
574 corner_groups,
575 new_verts_by_affected_vert,
576 mesh.edges_for_write());
577 }
578
579 const Array<int> edge_map = offsets_to_map(selected_edges, new_edge_offsets.as_span());
580 propagate_edge_attributes(mesh, edge_map);
581 mesh.edges_for_write().take_back(new_edges.size()).copy_from(new_edges);
582
583 const Array<int> vert_map = offsets_to_map(affected_verts, new_verts_by_affected_vert);
584 propagate_vert_attributes(mesh, vert_map);
585
586 mesh.tag_edges_split();
587
590}
591
592} // namespace blender::geometry
CustomData interface, see also DNA_customdata_types.h.
void CustomData_realloc(CustomData *data, int old_size, int new_size, eCDAllocType alloctype=CD_CONSTRUCT)
void CustomData_free_layers(CustomData *data, eCustomDataType type)
void * CustomData_get_layer_for_write(CustomData *data, eCustomDataType type, int totelem)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_NOINLINE
#define UNLIKELY(x)
@ CD_MVERT_SKIN
@ CD_CLOTH_ORCO
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
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
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:419
GMutableSpan take_back(const int64_t n) const
constexpr int64_t size() const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan take_back(const int64_t n) const
Definition BLI_span.hh:640
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t first_index(const T &search_value) const
Definition BLI_span.hh:377
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
int64_t size() const
int64_t append_and_get_index(const T &value)
void append(const T &value)
bool is_empty() const
Span< T > as_span() const
const T & first() const
int64_t first_index_of_try(const T &value) const
Set< StringRefNull > all_ids() const
std::optional< AttributeMetaData > lookup_meta_data(StringRef attribute_id) const
GSpanAttributeWriter lookup_for_write_span(StringRef attribute_id)
void foreach_index_optimized(Fn &&fn) const
IndexMask complement(const IndexMask &universe, IndexMaskMemory &memory) const
void to_bits(MutableBitSpan r_bits, int64_t offset=0) const
void foreach_index(Fn &&fn) const
VecBase< int, 2 > int2
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static char faces[256]
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
void gather(GSpan src, Span< int > map, GMutableSpan dst)
GroupedSpan< int > build_edge_to_corner_map(Span< int > corner_edges, int edges_num, Array< int > &r_offsets, Array< int > &r_indices)
int face_corner_prev(const IndexRange face, const int corner)
Definition BKE_mesh.hh:306
int face_corner_next(const IndexRange face, const int corner)
Definition BKE_mesh.hh:315
GroupedSpan< int > build_vert_to_edge_map(Span< int2 > edges, int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
void debug_randomize_edge_order(Mesh *mesh)
Definition randomize.cc:131
static Array< int > offsets_to_map(const IndexMask &mask, const OffsetIndices< int > offsets)
static Vector< CornerGroup > calc_corner_groups_for_vertex(const OffsetIndices< int > faces, const Span< int > corner_verts, const Span< int > corner_edges, const GroupedSpan< int > edge_to_corner_map, const Span< int > corner_to_face_map, const BitSpan split_edges, const Span< int > connected_corners, const int vert)
static void reassign_loose_edge_verts(const int orig_verts_num, const IndexMask &affected_verts, const GroupedSpan< int > vert_to_edge_map, const BitSpan loose_edges, const BitSpan split_edges, const Span< Vector< CornerGroup > > corner_groups, const OffsetIndices< int > new_verts_by_affected_vert, MutableSpan< int2 > edges)
static Array< int2 > calc_new_edges(const OffsetIndices< int > faces, const Span< int > corner_verts, const GroupedSpan< int > edge_to_corner_map, const Span< int > corner_to_face_map, const IndexMask &selected_edges, MutableSpan< int2 > edges, MutableSpan< int > corner_edges, MutableSpan< int > r_new_edge_offsets)
static void propagate_vert_attributes(Mesh &mesh, const Span< int > new_to_old_verts_map)
Vector< int > CornerGroup
static OrderedEdge edge_from_corner(const OffsetIndices< int > faces, const Span< int > corner_verts, const Span< int > corner_to_face_map, const int corner)
static BLI_NOINLINE Array< Vector< CornerGroup > > calc_all_corner_groups(const OffsetIndices< int > faces, const Span< int > corner_verts, const Span< int > corner_edges, const GroupedSpan< int > vert_to_corner_map, const GroupedSpan< int > edge_to_corner_map, const Span< int > corner_to_face_map, const BitSpan split_edges, const IndexMask &affected_verts)
static OffsetIndices< int > calc_vert_ranges_per_old_vert(const IndexMask &affected_verts, const Span< Vector< CornerGroup > > corner_groups, const GroupedSpan< int > vert_to_edge_map, const BitSpan loose_edges, const BitSpan split_edges, Array< int > &offset_data)
void debug_randomize_vert_order(Mesh *mesh)
Definition randomize.cc:110
static VertLooseEdges calc_vert_loose_edges(const GroupedSpan< int > vert_to_edge_map, const BitSpan loose_edges, const BitSpan split_edges, const int vert)
static void swap_edge_vert(int2 &edge, const int old_vert, const int new_vert)
void split_edges(Mesh &mesh, const IndexMask &selected_edges, const bke::AttributeFilter &attribute_filter={})
static int corner_on_edge_connected_to_vert(const Span< int > corner_verts, const int corner, const IndexRange face, const int vert)
static void update_unselected_edges(const OffsetIndices< int > faces, const Span< int > corner_verts, const GroupedSpan< int > edge_to_corner_map, const Span< int > corner_to_face_map, const IndexMask &unselected_edges, MutableSpan< int2 > edges)
static void propagate_edge_attributes(Mesh &mesh, const Span< int > new_to_old_edge_map)
IndexMask vert_selection_from_edge(Span< int2 > edges, const IndexMask &edge_mask, int verts_num, IndexMaskMemory &memory)
static void update_corner_verts(const int orig_verts_num, const Span< Vector< CornerGroup > > corner_groups, const OffsetIndices< int > new_verts_by_affected_vert, MutableSpan< int > new_corner_verts)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
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
VecBase< int32_t, 2 > int2
VecBase< float, 3 > float3
CustomData edge_data
int edges_num
CustomData vert_data
int verts_num
blender::BitVector is_loose_bits
i
Definition text_draw.cc:230