Blender V4.3
node_geo_extrude_mesh.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_task.hh"
7#include "BLI_vector_set.hh"
8
9#include "DNA_mesh_types.h"
10#include "DNA_meshdata_types.h"
11
12#include "BKE_attribute_math.hh"
13#include "BKE_customdata.hh"
14#include "BKE_deform.hh"
15#include "BKE_mesh.hh"
16#include "BKE_mesh_mapping.hh"
17#include "BKE_mesh_runtime.hh"
18
19#include "GEO_mesh_selection.hh"
20#include "GEO_randomize.hh"
21
22#include "NOD_rna_define.hh"
23
24#include "UI_interface.hh"
25#include "UI_resources.hh"
26
27#include "node_geometry_util.hh"
28
30
32
34{
35 b.add_input<decl::Geometry>("Mesh").supported_type(GeometryComponent::Type::Mesh);
36 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
37 b.add_input<decl::Vector>("Offset")
39 .implicit_field_on_all(implicit_field_inputs::normal)
40 .hide_value();
41 b.add_input<decl::Float>("Offset Scale").default_value(1.0f).field_on_all();
42 auto &individual =
43 b.add_input<decl::Bool>("Individual").default_value(true).make_available([](bNode &node) {
44 node_storage(node).mode = GEO_NODE_EXTRUDE_MESH_FACES;
45 });
46 b.add_output<decl::Geometry>("Mesh").propagate_all();
47 b.add_output<decl::Bool>("Top").field_on_all().translation_context(BLT_I18NCONTEXT_ID_NODETREE);
48 b.add_output<decl::Bool>("Side").field_on_all();
49
50 const bNode *node = b.node_or_null();
51 if (node != nullptr) {
52 const NodeGeometryExtrudeMesh &storage = node_storage(*node);
54 individual.available(mode == GEO_NODE_EXTRUDE_MESH_FACES);
55 }
56}
57
58static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
59{
60 uiLayoutSetPropSep(layout, true);
61 uiLayoutSetPropDecorate(layout, false);
62 uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
63}
64
65static void node_init(bNodeTree * /*tree*/, bNode *node)
66{
67 NodeGeometryExtrudeMesh *data = MEM_cnew<NodeGeometryExtrudeMesh>(__func__);
69 node->storage = data;
70}
71
73 std::optional<std::string> top_id;
74 std::optional<std::string> side_id;
75};
76
78 const StringRef id,
79 const AttrDomain domain,
80 const IndexMask &selection)
81{
82 BLI_assert(!attributes.contains(id));
83
85 selection.to_bools(attribute.span);
86 attribute.finish();
87}
88
90 const AttributeFilter &attribute_filter)
91{
92 Vector<std::string> names_to_remove;
93 const Set<StringRefNull> all_names = attributes.all_ids();
94 for (const StringRefNull name : all_names) {
95 if (attribute_filter.allow_skip(name)) {
96 names_to_remove.append(name);
97 }
98 }
99 for (const StringRef id : names_to_remove) {
100 attributes.remove(id);
101 }
102}
103
111
116
121
130
131static void expand_mesh(Mesh &mesh,
132 const int vert_expand,
133 const int edge_expand,
134 const int face_expand,
135 const int loop_expand)
136{
137 /* Remove types that aren't supported for interpolation in this node. */
138 if (vert_expand != 0) {
139 const int old_verts_num = mesh.verts_num;
140 mesh.verts_num += vert_expand;
141 CustomData_realloc(&mesh.vert_data, old_verts_num, mesh.verts_num);
142 }
143 if (edge_expand != 0) {
144 if (mesh.edges_num == 0) {
145 mesh.attributes_for_write().add(
146 ".edge_verts", AttrDomain::Edge, CD_PROP_INT32_2D, bke::AttributeInitConstruct());
147 }
148 const int old_edges_num = mesh.edges_num;
149 mesh.edges_num += edge_expand;
150 CustomData_realloc(&mesh.edge_data, old_edges_num, mesh.edges_num);
151 }
152 if (face_expand != 0) {
153 const int old_faces_num = mesh.faces_num;
154 mesh.faces_num += face_expand;
155 CustomData_realloc(&mesh.face_data, old_faces_num, mesh.faces_num);
157 &mesh.runtime->face_offsets_sharing_info,
158 old_faces_num == 0 ? 0 : (old_faces_num + 1),
159 mesh.faces_num + 1);
160 /* Set common values for convenience. */
161 mesh.face_offset_indices[0] = 0;
162 mesh.face_offset_indices[mesh.faces_num] = mesh.corners_num + loop_expand;
163 }
164 if (loop_expand != 0) {
165 if (mesh.corners_num == 0) {
166 mesh.attributes_for_write().add(
167 ".corner_vert", AttrDomain::Corner, CD_PROP_INT32, bke::AttributeInitConstruct());
168 mesh.attributes_for_write().add(
169 ".corner_edge", AttrDomain::Corner, CD_PROP_INT32, bke::AttributeInitConstruct());
170 }
171 const int old_loops_num = mesh.corners_num;
172 mesh.corners_num += loop_expand;
173 CustomData_realloc(&mesh.corner_data, old_loops_num, mesh.corners_num);
174 }
175}
176
178{
179 switch (domain) {
180 case AttrDomain::Point:
181 return mesh.vert_data;
182 case AttrDomain::Edge:
183 return mesh.edge_data;
184 case AttrDomain::Face:
185 return mesh.face_data;
186 case AttrDomain::Corner:
187 return mesh.corner_data;
188 default:
190 return mesh.vert_data;
191 }
192}
193
194static std::optional<MutableSpan<int>> get_orig_index_layer(Mesh &mesh, const AttrDomain domain)
195{
196 const bke::AttributeAccessor attributes = mesh.attributes();
197 CustomData &custom_data = mesh_custom_data_for_domain(mesh, domain);
198 if (int *orig_indices = static_cast<int *>(CustomData_get_layer_for_write(
199 &custom_data, CD_ORIGINDEX, attributes.domain_size(domain))))
200 {
201 return MutableSpan<int>(orig_indices, attributes.domain_size(domain));
202 }
203 return std::nullopt;
204}
205
206template<typename T>
208 const GroupedSpan<int> src_groups,
209 const IndexMask &selection,
210 MutableSpan<T> dst)
211{
212 selection.foreach_segment(
213 GrainSize(512), [&](const IndexMaskSegment segment, const int64_t segment_pos) {
214 const IndexRange dst_range(segment_pos, segment.size());
216 for (const int i : segment.index_range()) {
217 for (const int src_i : src_groups[segment[i]]) {
218 mixer.mix_in(i, src[src_i]);
219 }
220 }
221 mixer.finalize();
222 });
223}
224
225static void copy_with_mixing(const GSpan src,
226 const GroupedSpan<int> src_groups,
227 const IndexMask &selection,
228 GMutableSpan dst)
229{
230 BLI_assert(selection.size() == dst.size());
232 using T = decltype(dummy);
233 copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
234 });
235}
236
237template<typename T>
239 const GroupedSpan<int> src_groups,
240 const Span<int> selection,
241 MutableSpan<T> dst)
242{
243 threading::parallel_for(dst.index_range(), 512, [&](const IndexRange range) {
244 bke::attribute_math::DefaultPropagationMixer<T> mixer{dst.slice(range)};
245 for (const int i : range.index_range()) {
246 const int group_i = selection[i];
247 for (const int i_src : src_groups[group_i]) {
248 mixer.mix_in(i, src[i_src]);
249 }
250 }
251 mixer.finalize();
252 });
253}
254
255static void copy_with_mixing(const GSpan src,
256 const GroupedSpan<int> src_groups,
257 const Span<int> selection,
258 GMutableSpan dst)
259{
261 using T = decltype(dummy);
262 copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
263 });
264}
265
266using IDsByDomain = std::array<Vector<StringRef>, ATTR_DOMAIN_NUM>;
267
269 const Set<StringRef> &skip)
270{
271 IDsByDomain ids_by_domain;
272 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
273 if (iter.data_type == CD_PROP_STRING) {
274 return;
275 }
276 if (skip.contains(iter.name)) {
277 return;
278 }
279 ids_by_domain[int(iter.domain)].append(iter.name);
280 });
281 return ids_by_domain;
282}
283
284static bool is_empty_domain(const AttributeAccessor attributes,
285 const Set<StringRef> &skip,
286 const AttrDomain domain)
287{
288 bool is_empty = true;
289 attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
290 if (iter.data_type == CD_PROP_STRING) {
291 return;
292 }
293 if (iter.domain != domain) {
294 return;
295 }
296 if (skip.contains(iter.name)) {
297 return;
298 }
299 is_empty = false;
300 iter.stop();
301 });
302 return is_empty;
303}
304
306 const Span<StringRef> ids,
307 const Span<int> indices,
308 const IndexRange new_range)
309{
310 for (const StringRef id : ids) {
312 bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range));
313 attribute.finish();
314 }
315}
316
318 const Span<StringRef> ids,
319 const IndexMask &indices,
320 const IndexRange new_range)
321{
322 for (const StringRef id : ids) {
324 array_utils::gather(attribute.span, indices, attribute.span.slice(new_range));
325 attribute.finish();
326 }
327}
328
329static void gather_vert_attributes(Mesh &mesh,
330 const Span<StringRef> ids,
331 const Span<int> indices,
332 const IndexRange new_range)
333{
334 Set<StringRef> vertex_group_names;
336 vertex_group_names.add(group->name);
337 }
338
339 if (!vertex_group_names.is_empty() && !mesh.deform_verts().is_empty()) {
340 MutableSpan<MDeformVert> dverts = mesh.deform_verts_for_write();
341 bke::gather_deform_verts(dverts, indices, dverts.slice(new_range));
342 }
343
344 MutableAttributeAccessor attributes = mesh.attributes_for_write();
345 for (const StringRef id : ids) {
346 if (!vertex_group_names.contains(id)) {
348 bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range));
349 attribute.finish();
350 }
351 }
352}
353
354static void gather_vert_attributes(Mesh &mesh,
355 const Span<StringRef> ids,
356 const IndexMask &indices,
357 const IndexRange new_range)
358{
359 Set<StringRef> vertex_group_names;
361 vertex_group_names.add(group->name);
362 }
363
364 if (!vertex_group_names.is_empty() && !mesh.deform_verts().is_empty()) {
365 MutableSpan<MDeformVert> dverts = mesh.deform_verts_for_write();
366 bke::gather_deform_verts(dverts, indices, dverts.slice(new_range));
367 }
368
369 MutableAttributeAccessor attributes = mesh.attributes_for_write();
370 for (const StringRef id : ids) {
371 if (!vertex_group_names.contains(id)) {
373 array_utils::gather(attribute.span, indices, attribute.span.slice(new_range));
374 attribute.finish();
375 }
376 }
377}
378
379static void extrude_mesh_vertices(Mesh &mesh,
380 const Field<bool> &selection_field,
381 const Field<float3> &offset_field,
382 const AttributeOutputs &attribute_outputs,
383 const AttributeFilter &attribute_filter)
384{
385 const int orig_vert_size = mesh.verts_num;
386 const int orig_edge_size = mesh.edges_num;
387
388 /* Use an array for the result of the evaluation because the mesh is reallocated before
389 * the vertices are moved, and the evaluated result might reference an attribute. */
390 Array<float3> offsets(orig_vert_size);
391 const bke::MeshFieldContext context{mesh, AttrDomain::Point};
392 FieldEvaluator evaluator{context, mesh.verts_num};
393 evaluator.add_with_destination(offset_field, offsets.as_mutable_span());
394 evaluator.set_selection(selection_field);
395 evaluator.evaluate();
397 if (selection.is_empty()) {
398 return;
399 }
400
401 MutableAttributeAccessor attributes = mesh.attributes_for_write();
402 remove_non_propagated_attributes(attributes, attribute_filter);
403
404 const IDsByDomain ids_by_domain = attribute_ids_by_domain(attributes,
405 {"position", ".edge_verts"});
406
407 Array<int> vert_to_edge_offsets;
408 Array<int> vert_to_edge_indices;
409 GroupedSpan<int> vert_to_edge_map;
410 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
411 vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
412 mesh.edges(), orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
413 }
414
417 expand_mesh(mesh, selection.size(), selection.size(), 0, 0);
418
419 const IndexRange new_vert_range{orig_vert_size, selection.size()};
420 const IndexRange new_edge_range{orig_edge_size, selection.size()};
421
422 MutableSpan<int2> new_edges = mesh.edges_for_write().slice(new_edge_range);
423 selection.foreach_index_optimized<int>(
424 GrainSize(4096), [&](const int index, const int i_selection) {
425 new_edges[i_selection] = int2(index, new_vert_range[i_selection]);
426 });
427
428 /* New vertices copy the attribute values from their source vertex. */
429 gather_vert_attributes(mesh, ids_by_domain[int(AttrDomain::Point)], selection, new_vert_range);
430
431 /* New edge values are mixed from of all the edges connected to the source vertex. */
432 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
435 attribute.span, vert_to_edge_map, selection, attribute.span.slice(new_edge_range));
436 attribute.finish();
437 }
438
439 MutableSpan<float3> positions = mesh.vert_positions_for_write();
440 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
441 selection.foreach_index_optimized<int>(GrainSize(1024), [&](const int index, const int i) {
442 new_positions[i] = positions[index] + offsets[index];
443 });
444
445 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
446 array_utils::gather(indices->as_span(), selection, indices->slice(new_vert_range));
447 }
448 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
449 indices->slice(new_edge_range).fill(ORIGINDEX_NONE);
450 }
451
452 if (attribute_outputs.top_id) {
454 attributes, *attribute_outputs.top_id, AttrDomain::Point, new_vert_range);
455 }
456 if (attribute_outputs.side_id) {
458 attributes, *attribute_outputs.side_id, AttrDomain::Edge, new_edge_range);
459 }
460
461 const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
462 mesh.runtime->loose_verts_cache.data().count == 0;
463 const bool no_overlapping_hint = mesh.no_overlapping_topology();
465 if (no_loose_vert_hint) {
466 mesh.tag_loose_verts_none();
467 }
468 if (no_overlapping_hint) {
469 mesh.tag_overlapping_none();
470 }
471}
472
473static void fill_quad_consistent_direction(const Span<int> other_face_verts,
474 const Span<int> other_face_edges,
475 MutableSpan<int> new_corner_verts,
476 MutableSpan<int> new_corner_edges,
477 const int vert_connected_to_face_1,
478 const int vert_connected_to_face_2,
479 const int vert_across_from_face_1,
480 const int vert_across_from_face_2,
481 const int edge_connected_to_face,
482 const int connecting_edge_1,
483 const int edge_across_from_face,
484 const int connecting_edge_2)
485{
486 /* Find the loop on the face connected to the new quad that uses the duplicate edge. */
487 bool start_with_connecting_edge = true;
488 for (const int i : other_face_edges.index_range()) {
489 if (other_face_edges[i] == edge_connected_to_face) {
490 start_with_connecting_edge = other_face_verts[i] == vert_connected_to_face_1;
491 break;
492 }
493 }
494 if (start_with_connecting_edge) {
495 new_corner_verts[0] = vert_connected_to_face_1;
496 new_corner_edges[0] = connecting_edge_1;
497 new_corner_verts[1] = vert_across_from_face_1;
498 new_corner_edges[1] = edge_across_from_face;
499 new_corner_verts[2] = vert_across_from_face_2;
500 new_corner_edges[2] = connecting_edge_2;
501 new_corner_verts[3] = vert_connected_to_face_2;
502 new_corner_edges[3] = edge_connected_to_face;
503 }
504 else {
505 new_corner_verts[0] = vert_connected_to_face_1;
506 new_corner_edges[0] = edge_connected_to_face;
507 new_corner_verts[1] = vert_connected_to_face_2;
508 new_corner_edges[1] = connecting_edge_2;
509 new_corner_verts[2] = vert_across_from_face_2;
510 new_corner_edges[2] = edge_across_from_face;
511 new_corner_verts[3] = vert_across_from_face_1;
512 new_corner_edges[3] = connecting_edge_1;
513 }
514}
515
517 const IndexMask &edge_mask,
518 const int verts_num,
519 Array<int> &r_offsets,
520 Array<int> &r_indices)
521{
522 if (edge_mask.size() == edges.size()) {
523 return bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices);
524 }
525 Array<int2> masked_edges(edge_mask.size());
526 array_utils::gather(edges, edge_mask, masked_edges.as_mutable_span());
527
528 bke::mesh::build_vert_to_edge_map(masked_edges, verts_num, r_offsets, r_indices);
529
530 Array<int> masked_edge_to_edge(edge_mask.size());
531 edge_mask.to_indices<int>(masked_edge_to_edge);
532
533 threading::parallel_for(r_indices.index_range(), 4096, [&](const IndexRange range) {
534 for (const int i : range) {
535 r_indices[i] = masked_edge_to_edge[r_indices[i]];
536 }
537 });
538
539 return {r_offsets.as_span(), r_indices.as_span()};
540}
541static void tag_mesh_added_faces(Mesh &mesh)
542{
543 const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
544 mesh.runtime->loose_verts_cache.data().count == 0;
545 const bool no_loose_edge_hint = mesh.runtime->loose_edges_cache.is_cached() &&
546 mesh.runtime->loose_edges_cache.data().count == 0;
547 const bool no_overlapping_hint = mesh.no_overlapping_topology();
549 if (no_loose_vert_hint) {
550 mesh.tag_loose_verts_none();
551 }
552 if (no_loose_edge_hint) {
553 mesh.tag_loose_edges_none();
554 }
555 if (no_overlapping_hint) {
556 mesh.tag_overlapping_none();
557 }
558}
559
560static void extrude_mesh_edges(Mesh &mesh,
561 const Field<bool> &selection_field,
562 const Field<float3> &offset_field,
563 const AttributeOutputs &attribute_outputs,
564 const AttributeFilter &attribute_filter)
565{
566 const int orig_vert_size = mesh.verts_num;
567 const Span<int2> orig_edges = mesh.edges();
568 const OffsetIndices orig_faces = mesh.faces();
569 const int orig_loop_size = mesh.corners_num;
570
571 const bke::MeshFieldContext edge_context{mesh, AttrDomain::Edge};
572 FieldEvaluator edge_evaluator{edge_context, mesh.edges_num};
573 edge_evaluator.set_selection(selection_field);
574 edge_evaluator.add(offset_field);
575 edge_evaluator.evaluate();
576 const IndexMask edge_selection = edge_evaluator.get_evaluated_selection_as_mask();
577 const VArray<float3> edge_offsets = edge_evaluator.get_evaluated<float3>(0);
578 if (edge_selection.is_empty()) {
579 return;
580 }
581
582 /* Find the offsets on the vertex domain for translation. This must be done before the mesh's
583 * custom data layers are reallocated, in case the virtual array references one of them. */
584 Array<float3> vert_offsets;
585 if (!edge_offsets.is_single()) {
586 vert_offsets.reinitialize(orig_vert_size);
588 edge_selection.foreach_index([&](const int i_edge) {
589 const int2 edge = orig_edges[i_edge];
590 const float3 offset = edge_offsets[i_edge];
591 mixer.mix_in(edge[0], offset);
592 mixer.mix_in(edge[1], offset);
593 });
594 mixer.finalize();
595 }
596
597 IndexMaskMemory memory;
599 orig_edges, edge_selection, orig_vert_size, memory);
600
601 const IndexRange new_vert_range{orig_vert_size, new_verts.size()};
602 /* The extruded edges connect the original and duplicate edges. */
603 const IndexRange connect_edge_range{orig_edges.size(), new_vert_range.size()};
604 /* The duplicate edges are extruded copies of the selected edges. */
605 const IndexRange duplicate_edge_range = connect_edge_range.after(edge_selection.size());
606 /* There is a new face for every selected edge. */
607 const IndexRange new_face_range{orig_faces.size(), edge_selection.size()};
608 /* Every new face is a quad with four corners. */
609 const IndexRange new_loop_range{orig_loop_size, new_face_range.size() * 4};
610
611 MutableAttributeAccessor attributes = mesh.attributes_for_write();
612 remove_non_propagated_attributes(attributes, attribute_filter);
613
614 Array<int> edge_to_face_offsets;
615 Array<int> edge_to_face_indices;
617 orig_faces, mesh.corner_edges(), mesh.edges_num, edge_to_face_offsets, edge_to_face_indices);
618
619 Array<int> vert_to_edge_offsets;
620 Array<int> vert_to_edge_indices;
621 GroupedSpan<int> vert_to_selected_edge_map;
622 if (!is_empty_domain(attributes, {".edge_verts"}, AttrDomain::Edge)) {
623 vert_to_selected_edge_map = build_vert_to_edge_map(
624 orig_edges, edge_selection, orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
625 }
626
631 expand_mesh(mesh,
632 new_vert_range.size(),
633 connect_edge_range.size() + duplicate_edge_range.size(),
634 new_face_range.size(),
635 new_loop_range.size());
636
637 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
638 attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
639
640 MutableSpan<int2> edges = mesh.edges_for_write();
641 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
642 MutableSpan<int2> duplicate_edges = edges.slice(duplicate_edge_range);
643 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
644 MutableSpan<int> new_face_offsets = face_offsets.slice(new_face_range);
645 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
646 MutableSpan<int> new_corner_verts = corner_verts.slice(new_loop_range);
647 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
648 MutableSpan<int> new_corner_edges = corner_edges.slice(new_loop_range);
649
650 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
651 const OffsetIndices faces = mesh.faces();
652
653 new_verts.foreach_index_optimized<int>(GrainSize(4096), [&](const int src, const int dst) {
654 connect_edges[dst] = int2(src, new_vert_range[dst]);
655 });
656
657 {
658 Array<int> vert_to_new_vert(orig_vert_size);
659 index_mask::build_reverse_map<int>(new_verts, vert_to_new_vert);
660 for (const int i : duplicate_edges.index_range()) {
661 const int2 orig_edge = edges[edge_selection[i]];
662 const int i_new_vert_1 = vert_to_new_vert[orig_edge[0]];
663 const int i_new_vert_2 = vert_to_new_vert[orig_edge[1]];
664 duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
665 }
666 }
667
668 edge_selection.foreach_index([&](const int64_t orig_edge_index, const int64_t i) {
669 const int2 duplicate_edge = duplicate_edges[i];
670 const int new_vert_1 = duplicate_edge[0];
671 const int new_vert_2 = duplicate_edge[1];
672 const int extrude_index_1 = new_vert_1 - orig_vert_size;
673 const int extrude_index_2 = new_vert_2 - orig_vert_size;
674
675 const int2 orig_edge = edges[orig_edge_index];
676 const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
677
678 /* When there was a single face connected to the new face, we can use the old one to keep
679 * the face direction consistent. When there is more than one connected face, the new face
680 * direction is totally arbitrary and the only goal for the behavior is to be deterministic. */
681 Span<int> connected_face_verts;
682 Span<int> connected_face_edges;
683 if (connected_faces.size() == 1) {
684 const IndexRange connected_face = faces[connected_faces.first()];
685 connected_face_verts = corner_verts.slice(connected_face);
686 connected_face_edges = corner_edges.slice(connected_face);
687 }
688 fill_quad_consistent_direction(connected_face_verts,
689 connected_face_edges,
690 new_corner_verts.slice(4 * i, 4),
691 new_corner_edges.slice(4 * i, 4),
692 orig_edge[0],
693 orig_edge[1],
694 new_vert_1,
695 new_vert_2,
696 orig_edge_index,
697 connect_edge_range[extrude_index_1],
698 duplicate_edge_range[i],
699 connect_edge_range[extrude_index_2]);
700 });
701
702 /* New vertices copy the attribute values from their source vertex. */
703 gather_vert_attributes(mesh, ids_by_domain[int(AttrDomain::Point)], new_verts, new_vert_range);
704
705 /* Edges parallel to original edges copy the edge attributes from the original edges. */
707 attributes, ids_by_domain[int(AttrDomain::Edge)], edge_selection, duplicate_edge_range);
708
709 /* Edges connected to original vertices mix values of selected connected edges. */
710 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
713 vert_to_selected_edge_map,
714 new_verts,
715 attribute.span.slice(connect_edge_range));
716 attribute.finish();
717 }
718
719 /* Attribute values for new faces are a mix of values connected to its original edge. */
720 for (const StringRef id : ids_by_domain[int(AttrDomain::Face)]) {
723 attribute.span, edge_to_face_map, edge_selection, attribute.span.slice(new_face_range));
724 attribute.finish();
725 }
726
727 /* New corners get the average value of all adjacent corners on original faces connected
728 * to the original edge of their face. */
729 for (const StringRef id : ids_by_domain[int(AttrDomain::Corner)]) {
731 bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
732 using T = decltype(dummy);
733 MutableSpan<T> data = attribute.span.typed<T>();
734 MutableSpan<T> new_data = data.slice(new_loop_range);
735 edge_selection.foreach_index(
736 GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) {
737 const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
738 if (connected_faces.is_empty()) {
739 /* If there are no connected faces, there is no corner data to interpolate. */
740 new_data.slice(4 * i_edge_selection, 4).fill(T());
741 return;
742 }
743
744 /* Both corners on each vertical edge of the side face get the same value,
745 * so there are only two unique values to mix. */
746 Array<T> side_face_corner_data(2);
747 bke::attribute_math::DefaultPropagationMixer<T> mixer{side_face_corner_data};
748
749 const int new_vert_1 = duplicate_edges[i_edge_selection][0];
750 const int new_vert_2 = duplicate_edges[i_edge_selection][1];
751 const int orig_vert_1 = edges[orig_edge_index][0];
752 const int orig_vert_2 = edges[orig_edge_index][1];
753
754 /* Average the corner data from the corners that share a vertex from the
755 * faces that share an edge with the extruded edge. */
756 for (const int connected_face : connected_faces) {
757 for (const int i_loop : faces[connected_face]) {
758 if (corner_verts[i_loop] == orig_vert_1) {
759 mixer.mix_in(0, data[i_loop]);
760 }
761 if (corner_verts[i_loop] == orig_vert_2) {
762 mixer.mix_in(1, data[i_loop]);
763 }
764 }
765 }
766
767 mixer.finalize();
768
769 /* Instead of replicating the order in #fill_quad_consistent_direction here, it's
770 * simpler (though probably slower) to just match the corner data based on the
771 * vertex indices. */
772 for (const int i : IndexRange(4 * i_edge_selection, 4)) {
773 if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
774 new_data[i] = side_face_corner_data.first();
775 }
776 else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
777 new_data[i] = side_face_corner_data.last();
778 }
779 }
780 });
781 });
782
783 attribute.finish();
784 }
785
786 MutableSpan<float3> positions = mesh.vert_positions_for_write();
787 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
788 if (edge_offsets.is_single()) {
789 const float3 offset = edge_offsets.get_internal_single();
790 new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
791 new_positions[dst] = positions[src] + offset;
792 });
793 }
794 else {
795 new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
796 new_positions[dst] = positions[src] + vert_offsets[src];
797 });
798 }
799
800 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
801 array_utils::gather(indices->as_span(), new_verts, indices->slice(new_vert_range));
802 }
803 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
804 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
805 array_utils::gather(indices->as_span(), edge_selection, indices->slice(duplicate_edge_range));
806 }
807 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
808 indices->slice(new_face_range).fill(ORIGINDEX_NONE);
809 }
810
811 if (attribute_outputs.top_id) {
813 attributes, *attribute_outputs.top_id, AttrDomain::Edge, duplicate_edge_range);
814 }
815 if (attribute_outputs.side_id) {
817 attributes, *attribute_outputs.side_id, AttrDomain::Face, new_face_range);
818 }
819
821}
822
823static VectorSet<int> vert_indices_from_edges(const Mesh &mesh, const Span<int> edge_indices)
824{
825 const Span<int2> edges = mesh.edges();
826
827 VectorSet<int> vert_indices;
828 vert_indices.reserve(edge_indices.size());
829 for (const int i_edge : edge_indices) {
830 const int2 &edge = edges[i_edge];
831 vert_indices.add(edge[0]);
832 vert_indices.add(edge[1]);
833 }
834 return vert_indices;
835}
836
842 const Field<bool> &selection_field,
843 const Field<float3> &offset_field,
844 const AttributeOutputs &attribute_outputs,
845 const AttributeFilter &attribute_filter)
846{
847 const int orig_vert_size = mesh.verts_num;
848 const Span<int2> orig_edges = mesh.edges();
849 const OffsetIndices orig_faces = mesh.faces();
850 const Span<int> orig_corner_verts = mesh.corner_verts();
851 const int orig_loop_size = orig_corner_verts.size();
852
853 const bke::MeshFieldContext face_context{mesh, AttrDomain::Face};
854 FieldEvaluator face_evaluator{face_context, mesh.faces_num};
855 face_evaluator.set_selection(selection_field);
856 face_evaluator.add(offset_field);
857 face_evaluator.evaluate();
858 const IndexMask face_selection = face_evaluator.get_evaluated_selection_as_mask();
859 const VArray<float3> face_position_offsets = face_evaluator.get_evaluated<float3>(0);
860 if (face_selection.is_empty()) {
861 return;
862 }
863
864 Array<bool> face_selection_array(orig_faces.size());
865 face_selection.to_bools(face_selection_array);
866
867 /* Mix the offsets from the face domain to the vertex domain. Evaluate on the face domain above
868 * in order to be consistent with the selection, and to use the face normals rather than vertex
869 * normals as an offset, for example. */
870 Array<float3> vert_offsets;
871 if (!face_position_offsets.is_single()) {
872 vert_offsets.reinitialize(orig_vert_size);
874 face_selection.foreach_index([&](const int i_face) {
875 const float3 offset = face_position_offsets[i_face];
876 for (const int vert : orig_corner_verts.slice(orig_faces[i_face])) {
877 mixer.mix_in(vert, offset);
878 }
879 });
880 mixer.finalize();
881 }
882
883 /* All of the faces (selected and deselected) connected to each edge. */
884 Array<int> edge_to_face_offsets;
885 Array<int> edge_to_face_indices;
887 orig_faces, mesh.corner_edges(), mesh.edges_num, edge_to_face_offsets, edge_to_face_indices);
888
889 /* All vertices that are connected to the selected faces. */
890 IndexMaskMemory memory;
891 const IndexMask all_selected_verts = geometry::vert_selection_from_face(
892 orig_faces, face_selection, orig_corner_verts, orig_vert_size, memory);
893
894 /* Edges inside of an extruded region that are also attached to deselected edges. They must be
895 * duplicated in order to leave the old edge attached to the unchanged deselected faces. */
896 VectorSet<int> new_inner_edge_indices;
897 /* Edges inside of an extruded region. Their vertices should be translated
898 * with the offset, but the edges themselves should not be duplicated. */
899 Vector<int> inner_edge_indices;
900 /* The extruded face corresponding to each boundary edge (and each boundary face). */
901 Vector<int> edge_extruded_face_indices;
902 /* Edges on the outside of selected regions, either because there are no
903 * other connected faces, or because all of the other faces aren't selected. */
904 VectorSet<int> boundary_edge_indices;
905 for (const int i_edge : orig_edges.index_range()) {
906 const Span<int> faces = edge_to_face_map[i_edge];
907
908 int i_selected_face = -1;
909 int deselected_face_count = 0;
910 int selected_face_count = 0;
911 for (const int i_other_face : faces) {
912 if (face_selection_array[i_other_face]) {
913 selected_face_count++;
914 i_selected_face = i_other_face;
915 }
916 else {
917 deselected_face_count++;
918 }
919 }
920
921 if (selected_face_count == 1) {
922 /* If there is only one selected face connected to the edge,
923 * the edge should be extruded to form a "side face". */
924 boundary_edge_indices.add_new(i_edge);
925 edge_extruded_face_indices.append(i_selected_face);
926 }
927 else if (selected_face_count > 1) {
928 /* The edge is inside an extruded region of faces. */
929 if (deselected_face_count > 0) {
930 /* Add edges that are also connected to deselected edges to a separate list. */
931 new_inner_edge_indices.add_new(i_edge);
932 }
933 else {
934 /* Otherwise, just keep track of edges inside the region so that
935 * we can reattach them to duplicated vertices if necessary. */
936 inner_edge_indices.append(i_edge);
937 }
938 }
939 }
940
941 VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices);
942 /* Before adding the rest of the new vertices from the new inner edges, store the number
943 * of new vertices from the boundary edges, since this is the number of connecting edges. */
944 const int extruded_vert_size = new_vert_indices.size();
945
946 /* The vertices attached to duplicate inner edges also have to be duplicated. */
947 for (const int i_edge : new_inner_edge_indices) {
948 const int2 &edge = orig_edges[i_edge];
949 new_vert_indices.add(edge[0]);
950 new_vert_indices.add(edge[1]);
951 }
952
953 /* New vertices forming the duplicated boundary edges and the ends of the new inner edges. */
954 const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()};
955 /* One edge connects each selected vertex to a new vertex on the extruded faces. */
956 const IndexRange connect_edge_range{orig_edges.size(), extruded_vert_size};
957 /* Each selected edge is duplicated to form a single edge on the extrusion. */
958 const IndexRange boundary_edge_range = connect_edge_range.after(boundary_edge_indices.size());
959 /* Duplicated edges inside regions that were connected to deselected faces. */
960 const IndexRange new_inner_edge_range = boundary_edge_range.after(new_inner_edge_indices.size());
961 /* Each edge selected for extrusion is extruded into a single face. */
962 const IndexRange side_face_range{orig_faces.size(), boundary_edge_indices.size()};
963 /* The loops that form the new side faces. */
964 const IndexRange side_loop_range{orig_corner_verts.size(), side_face_range.size() * 4};
965
966 MutableAttributeAccessor attributes = mesh.attributes_for_write();
967 remove_non_propagated_attributes(attributes, attribute_filter);
968
973 expand_mesh(mesh,
974 new_vert_range.size(),
975 connect_edge_range.size() + boundary_edge_range.size() + new_inner_edge_range.size(),
976 side_face_range.size(),
977 side_loop_range.size());
978
979 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
980 attributes, {".corner_vert", ".corner_edge", ".edge_verts"});
981
982 MutableSpan<int2> edges = mesh.edges_for_write();
983 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
984 MutableSpan<int2> boundary_edges = edges.slice(boundary_edge_range);
985 MutableSpan<int2> new_inner_edges = edges.slice(new_inner_edge_range);
986 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
987 MutableSpan<int> new_face_offsets = face_offsets.slice(side_face_range);
988 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
989 MutableSpan<int> new_corner_verts = corner_verts.slice(side_loop_range);
990 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
991 MutableSpan<int> new_corner_edges = corner_edges.slice(side_loop_range);
992
993 /* Initialize the new side faces. */
994 if (!new_face_offsets.is_empty()) {
995 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
996 }
997 const OffsetIndices faces = mesh.faces();
998
999 /* Initialize the edges that form the sides of the extrusion. */
1000 for (const int i : connect_edges.index_range()) {
1001 connect_edges[i] = int2(new_vert_indices[i], new_vert_range[i]);
1002 }
1003
1004 /* Initialize the edges that form the top of the extrusion. */
1005 for (const int i : boundary_edges.index_range()) {
1006 const int2 &orig_edge = edges[boundary_edge_indices[i]];
1007 const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]);
1008 const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]);
1009 boundary_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
1010 }
1011
1012 /* Initialize the new edges inside of extrude regions. */
1013 for (const int i : new_inner_edge_indices.index_range()) {
1014 const int2 &orig_edge = edges[new_inner_edge_indices[i]];
1015 const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]);
1016 const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]);
1017 new_inner_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
1018 }
1019
1020 /* Connect original edges inside face regions to any new vertices, if necessary. */
1021 for (const int i : inner_edge_indices) {
1022 int2 &edge = edges[i];
1023 const int i_new_vert_1 = new_vert_indices.index_of_try(edge[0]);
1024 const int i_new_vert_2 = new_vert_indices.index_of_try(edge[1]);
1025 if (i_new_vert_1 != -1) {
1026 edge[0] = new_vert_range[i_new_vert_1];
1027 }
1028 if (i_new_vert_2 != -1) {
1029 edge[1] = new_vert_range[i_new_vert_2];
1030 }
1031 }
1032
1033 /* Connect the selected faces to the extruded or duplicated edges and the new vertices. */
1034 face_selection.foreach_index([&](const int i_face) {
1035 for (const int corner : faces[i_face]) {
1036 const int i_new_vert = new_vert_indices.index_of_try(corner_verts[corner]);
1037 if (i_new_vert != -1) {
1038 corner_verts[corner] = new_vert_range[i_new_vert];
1039 }
1040 const int i_boundary_edge = boundary_edge_indices.index_of_try(corner_edges[corner]);
1041 if (i_boundary_edge != -1) {
1042 corner_edges[corner] = boundary_edge_range[i_boundary_edge];
1043 /* Skip the next check, an edge cannot be both a boundary edge and an inner edge. */
1044 continue;
1045 }
1046 const int i_new_inner_edge = new_inner_edge_indices.index_of_try(corner_edges[corner]);
1047 if (i_new_inner_edge != -1) {
1048 corner_edges[corner] = new_inner_edge_range[i_new_inner_edge];
1049 }
1050 }
1051 });
1052
1053 /* Create the faces on the sides of extruded regions. */
1054 for (const int i : boundary_edge_indices.index_range()) {
1055 const int2 &boundary_edge = boundary_edges[i];
1056 const int new_vert_1 = boundary_edge[0];
1057 const int new_vert_2 = boundary_edge[1];
1058 const int extrude_index_1 = new_vert_1 - orig_vert_size;
1059 const int extrude_index_2 = new_vert_2 - orig_vert_size;
1060
1061 const IndexRange extrude_face = faces[edge_extruded_face_indices[i]];
1062
1063 fill_quad_consistent_direction(corner_verts.slice(extrude_face),
1064 corner_edges.slice(extrude_face),
1065 new_corner_verts.slice(4 * i, 4),
1066 new_corner_edges.slice(4 * i, 4),
1067 new_vert_1,
1068 new_vert_2,
1069 new_vert_indices[extrude_index_1],
1070 new_vert_indices[extrude_index_2],
1071 boundary_edge_range[i],
1072 connect_edge_range[extrude_index_1],
1073 boundary_edge_indices[i],
1074 connect_edge_range[extrude_index_2]);
1075 }
1076
1077 /* New vertices copy the attributes from their original vertices. */
1079 mesh, ids_by_domain[int(AttrDomain::Point)], new_vert_indices, new_vert_range);
1080
1081 /* New faces on the side of extrusions get the values from the corresponding selected face. */
1082 gather_attributes(attributes,
1083 ids_by_domain[int(AttrDomain::Face)],
1084 edge_extruded_face_indices,
1085 side_face_range);
1086
1087 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
1088 IndexMaskMemory memory;
1089 const IndexMask boundary_edge_mask = IndexMask::from_indices<int>(boundary_edge_indices,
1090 memory);
1091
1092 Array<int> vert_to_edge_offsets;
1093 Array<int> vert_to_edge_indices;
1094 const GroupedSpan<int> vert_to_boundary_edge_map = build_vert_to_edge_map(
1095 edges, boundary_edge_mask, mesh.verts_num, vert_to_edge_offsets, vert_to_edge_indices);
1096
1097 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
1099
1100 /* Edges parallel to original edges copy the edge attributes from the original edges. */
1101 GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range);
1102 array_utils::gather(attribute.span, boundary_edge_mask, boundary_data);
1103
1104 /* Edges inside of face regions also just duplicate their source data. */
1105 GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range);
1106 bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data);
1107
1108 /* Edges connected to original vertices mix values of selected connected edges. */
1110 vert_to_boundary_edge_map,
1111 new_vert_indices,
1112 attribute.span.slice(connect_edge_range));
1113 attribute.finish();
1114 }
1115 }
1116
1117 /* New corners get the values from the corresponding corner on the extruded face. */
1118 if (!ids_by_domain[int(AttrDomain::Corner)].is_empty()) {
1119 Array<int> orig_corners(side_loop_range.size());
1120 threading::parallel_for(boundary_edge_indices.index_range(), 256, [&](const IndexRange range) {
1121 for (const int i_boundary_edge : range) {
1122 const int2 &boundary_edge = boundary_edges[i_boundary_edge];
1123 const int new_vert_1 = boundary_edge[0];
1124 const int new_vert_2 = boundary_edge[1];
1125 const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size];
1126 const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size];
1127
1128 /* Retrieve the data for the first two sides of the quad from the extruded
1129 * face, which we generally expect to have just a small amount of sides. This
1130 * loop could be eliminated by adding a cache of connected loops (which would
1131 * also simplify some of the other code to find the correct loops on the extruded
1132 * face). */
1133 int corner_1;
1134 int corner_2;
1135 for (const int corner : faces[edge_extruded_face_indices[i_boundary_edge]]) {
1136 if (corner_verts[corner] == new_vert_1) {
1137 corner_1 = corner;
1138 }
1139 if (corner_verts[corner] == new_vert_2) {
1140 corner_2 = corner;
1141 }
1142 }
1143
1144 /* Instead of replicating the order in #fill_quad_consistent_direction here, it's
1145 * simpler (though probably slower) to just match the corner data based on the
1146 * vertex indices. */
1147 for (const int i : IndexRange(4 * i_boundary_edge, 4)) {
1148 if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
1149 orig_corners[i] = corner_1;
1150 }
1151 else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
1152 orig_corners[i] = corner_2;
1153 }
1154 }
1155 }
1156 });
1158 attributes, ids_by_domain[int(AttrDomain::Corner)], orig_corners, side_loop_range);
1159 }
1160
1161 /* Translate vertices based on the offset. If the vertex is used by a selected edge, it will
1162 * have been duplicated and only the new vertex should use the offset. Otherwise the vertex might
1163 * still need an offset, but it was reused on the inside of a region of extruded faces. */
1164 MutableSpan<float3> positions = mesh.vert_positions_for_write();
1165 if (face_position_offsets.is_single()) {
1166 const float3 offset = face_position_offsets.get_internal_single();
1167 all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
1168 const int i_new = new_vert_indices.index_of_try(orig_vert);
1169 if (i_new == -1) {
1170 positions[orig_vert] += offset;
1171 }
1172 else {
1173 positions[new_vert_range[i_new]] += offset;
1174 }
1175 });
1176 }
1177 else {
1178 all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
1179 const int i_new = new_vert_indices.index_of_try(orig_vert);
1180 const float3 offset = vert_offsets[orig_vert];
1181 if (i_new == -1) {
1182 positions[orig_vert] += offset;
1183 }
1184 else {
1185 positions[new_vert_range[i_new]] += offset;
1186 }
1187 });
1188 }
1189
1190 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
1191 array_utils::gather(
1192 indices->as_span(), new_vert_indices.as_span(), indices->slice(new_vert_range));
1193 }
1194 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
1195 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
1196 array_utils::gather(indices->as_span(),
1197 new_inner_edge_indices.as_span(),
1198 indices->slice(new_inner_edge_range));
1199 array_utils::gather(
1200 indices->as_span(), boundary_edge_indices.as_span(), indices->slice(boundary_edge_range));
1201 }
1202 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
1203 array_utils::gather(
1204 indices->as_span(), edge_extruded_face_indices.as_span(), indices->slice(side_face_range));
1205 }
1206
1207 if (attribute_outputs.top_id) {
1209 attributes, *attribute_outputs.top_id, AttrDomain::Face, face_selection);
1210 }
1211 if (attribute_outputs.side_id) {
1213 attributes, *attribute_outputs.side_id, AttrDomain::Face, side_face_range);
1214 }
1215
1217}
1218
1220 const Field<bool> &selection_field,
1221 const Field<float3> &offset_field,
1222 const AttributeOutputs &attribute_outputs,
1223 const AttributeFilter &attribute_filter)
1224{
1225 const int orig_vert_size = mesh.verts_num;
1226 const int orig_edge_size = mesh.edges_num;
1227 const OffsetIndices orig_faces = mesh.faces();
1228 const Span<int> orig_corner_verts = mesh.corner_verts();
1229 const int orig_loop_size = orig_corner_verts.size();
1230
1231 /* Use an array for the result of the evaluation because the mesh is reallocated before
1232 * the vertices are moved, and the evaluated result might reference an attribute. */
1233 Array<float3> face_offset(orig_faces.size());
1234 const bke::MeshFieldContext face_context{mesh, AttrDomain::Face};
1235 FieldEvaluator face_evaluator{face_context, mesh.faces_num};
1236 face_evaluator.set_selection(selection_field);
1237 face_evaluator.add_with_destination(offset_field, face_offset.as_mutable_span());
1238 face_evaluator.evaluate();
1239 const IndexMask face_selection = face_evaluator.get_evaluated_selection_as_mask();
1240 if (face_selection.is_empty()) {
1241 return;
1242 }
1243
1244 /* Build an array of offsets into the new data for each face. This is used to facilitate
1245 * parallelism later on by avoiding the need to keep track of an offset when iterating through
1246 * all faces. */
1247 Array<int> group_per_face_data(face_selection.size() + 1);
1249 orig_faces, face_selection, group_per_face_data);
1250 const int extrude_corner_size = group_per_face.total_size();
1251
1252 const IndexRange new_vert_range{orig_vert_size, extrude_corner_size};
1253 /* One edge connects each selected vertex to a new vertex on the extruded faces. */
1254 const IndexRange connect_edge_range{orig_edge_size, extrude_corner_size};
1255 /* Each selected edge is duplicated to form a single edge on the extrusion. */
1256 const IndexRange duplicate_edge_range = connect_edge_range.after(extrude_corner_size);
1257 /* Each edge selected for extrusion is extruded into a single face. */
1258 const IndexRange side_face_range{orig_faces.size(), duplicate_edge_range.size()};
1259 const IndexRange side_loop_range{orig_loop_size, side_face_range.size() * 4};
1260
1261 MutableAttributeAccessor attributes = mesh.attributes_for_write();
1262 remove_non_propagated_attributes(attributes, attribute_filter);
1263
1268 expand_mesh(mesh,
1269 new_vert_range.size(),
1270 connect_edge_range.size() + duplicate_edge_range.size(),
1271 side_face_range.size(),
1272 side_loop_range.size());
1273
1274 const IDsByDomain ids_by_domain = attribute_ids_by_domain(
1275 attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
1276
1277 MutableSpan<float3> positions = mesh.vert_positions_for_write();
1278 MutableSpan<float3> new_positions = positions.slice(new_vert_range);
1279 MutableSpan<int2> edges = mesh.edges_for_write();
1280 MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
1281 MutableSpan<int2> duplicate_edges = edges.slice(duplicate_edge_range);
1282 MutableSpan<int> face_offsets = mesh.face_offsets_for_write();
1283 MutableSpan<int> new_face_offsets = face_offsets.slice(side_face_range);
1284 MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
1285 MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
1286
1287 offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
1288 const OffsetIndices faces = mesh.faces();
1289
1290 /* For every selected face, change it to use the new extruded vertices and the duplicate
1291 * edges, and build the faces that form the sides of the extrusion. Build "original index"
1292 * arrays for the new vertices and edges so they can be accessed later.
1293 *
1294 * Filling some of this data like the new edges or faces could be easily split into
1295 * separate loops, which may or may not be faster, but would involve more duplication. */
1296 Array<int> new_vert_indices(extrude_corner_size);
1297 Array<int> duplicate_edge_indices(extrude_corner_size);
1298 face_selection.foreach_index(
1299 GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
1300 const IndexRange extrude_range = group_per_face[i_selection];
1301
1302 const IndexRange face = faces[index];
1303 MutableSpan<int> face_verts = corner_verts.slice(face);
1304 MutableSpan<int> face_edges = corner_edges.slice(face);
1305
1306 for (const int i : face.index_range()) {
1307 const int i_extrude = extrude_range[i];
1308 new_vert_indices[i_extrude] = face_verts[i];
1309 duplicate_edge_indices[i_extrude] = face_edges[i];
1310
1311 face_verts[i] = new_vert_range[i_extrude];
1312 face_edges[i] = duplicate_edge_range[i_extrude];
1313 }
1314
1315 for (const int i : face.index_range()) {
1316 const int i_next = (i == face.size() - 1) ? 0 : i + 1;
1317 const int i_extrude = extrude_range[i];
1318 const int i_extrude_next = extrude_range[i_next];
1319
1320 const int i_duplicate_edge = duplicate_edge_range[i_extrude];
1321 const int new_vert = new_vert_range[i_extrude];
1322 const int new_vert_next = new_vert_range[i_extrude_next];
1323
1324 const int orig_edge = duplicate_edge_indices[i_extrude];
1325
1326 const int orig_vert = new_vert_indices[i_extrude];
1327 const int orig_vert_next = new_vert_indices[i_extrude_next];
1328
1329 duplicate_edges[i_extrude] = int2(new_vert, new_vert_next);
1330
1331 MutableSpan<int> side_face_verts = corner_verts.slice(side_loop_range[i_extrude * 4], 4);
1332 MutableSpan<int> side_face_edges = corner_edges.slice(side_loop_range[i_extrude * 4], 4);
1333 side_face_verts[0] = new_vert_next;
1334 side_face_edges[0] = i_duplicate_edge;
1335 side_face_verts[1] = new_vert;
1336 side_face_edges[1] = connect_edge_range[i_extrude];
1337 side_face_verts[2] = orig_vert;
1338 side_face_edges[2] = orig_edge;
1339 side_face_verts[3] = orig_vert_next;
1340 side_face_edges[3] = connect_edge_range[i_extrude_next];
1341
1342 connect_edges[i_extrude] = int2(orig_vert, new_vert);
1343 }
1344 });
1345
1346 /* New vertices copy the attributes from their original vertices. */
1348 mesh, ids_by_domain[int(AttrDomain::Point)], new_vert_indices, new_vert_range);
1349
1350 /* The data for the duplicate edge is simply a copy of the original edge's data. */
1351 gather_attributes(attributes,
1352 ids_by_domain[int(AttrDomain::Edge)],
1353 duplicate_edge_indices,
1354 duplicate_edge_range);
1355
1356 /* For extruded edges, mix the data from the two neighboring original edges of the face. */
1357 if (!ids_by_domain[int(AttrDomain::Edge)].is_empty()) {
1358 Array<int2> neighbor_edges(connect_edge_range.size());
1359 face_selection.foreach_index(
1360 GrainSize(1024), [&](const int64_t index, const int64_t i_selection) {
1361 const IndexRange face = faces[index];
1362 const IndexRange extrude_range = group_per_face[i_selection];
1363
1364 for (const int i : face.index_range()) {
1365 const int i_prev = (i == 0) ? face.size() - 1 : i - 1;
1366 const int i_extrude = extrude_range[i];
1367 const int i_extrude_prev = extrude_range[i_prev];
1368 neighbor_edges[i_extrude] = int2(duplicate_edge_indices[i_extrude],
1369 duplicate_edge_indices[i_extrude_prev]);
1370 }
1371 });
1372
1373 for (const StringRef id : ids_by_domain[int(AttrDomain::Edge)]) {
1375 bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
1376 using T = decltype(dummy);
1377 MutableSpan<T> data = attribute.span.typed<T>();
1378 MutableSpan<T> dst = data.slice(connect_edge_range);
1379 threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
1380 for (const int i : range) {
1381 const int2 neighbors = neighbor_edges[i];
1382 if constexpr (std::is_same_v<T, bool>) {
1383 /* Propagate selections with "or" instead of "at least half". */
1384 dst[i] = data[neighbors[0]] || data[neighbors[1]];
1385 }
1386 else {
1387 dst[i] = bke::attribute_math::mix2(0.5f, data[neighbors[0]], data[neighbors[1]]);
1388 }
1389 }
1390 });
1391 });
1392 attribute.finish();
1393 }
1394 }
1395
1396 /* Each side face gets the values from the corresponding new face. */
1397 for (const StringRef id : ids_by_domain[int(AttrDomain::Face)]) {
1398 GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
1400 group_per_face, face_selection, attribute.span, attribute.span.slice(side_face_range));
1401 attribute.finish();
1402 }
1403
1404 /* Each corner on a side face gets its value from the matching corner on an extruded face. */
1405 if (!ids_by_domain[int(AttrDomain::Corner)].is_empty()) {
1406 Array<int> orig_corners(side_loop_range.size());
1407 face_selection.foreach_index(
1408 GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
1409 const IndexRange face = faces[index];
1410 const IndexRange extrude_range = group_per_face[i_selection];
1411
1412 for (const int i : face.index_range()) {
1413 const IndexRange side_face(extrude_range[i] * 4, 4);
1414 /* The two corners on each side of the side face get the data from the
1415 * matching corners of the extruded face. This order depends on the loop
1416 * filling the loop indices. */
1417 const int corner = face[i];
1418 const int next_corner = bke::mesh::face_corner_next(face, corner);
1419 orig_corners[side_face[0]] = next_corner;
1420 orig_corners[side_face[1]] = corner;
1421 orig_corners[side_face[2]] = corner;
1422 orig_corners[side_face[3]] = next_corner;
1423 }
1424 });
1426 attributes, ids_by_domain[int(AttrDomain::Corner)], orig_corners, side_loop_range);
1427 }
1428
1429 /* Offset the new vertices. */
1430 face_selection.foreach_index(GrainSize(1025),
1431 [&](const int64_t index, const int64_t i_selection) {
1432 const IndexRange extrude_range = group_per_face[i_selection];
1433 for (const int i : extrude_range) {
1434 const int src_vert = new_vert_indices[i];
1435 new_positions[i] = positions[src_vert] + face_offset[index];
1436 }
1437 });
1438
1439 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Point)) {
1440 array_utils::gather(
1441 indices->as_span(), new_vert_indices.as_span(), indices->slice(new_vert_range));
1442 }
1443 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Edge)) {
1444 indices->slice(connect_edge_range).fill(ORIGINDEX_NONE);
1445 array_utils::gather(indices->as_span(),
1446 duplicate_edge_indices.as_span(),
1447 indices->slice(duplicate_edge_range));
1448 }
1449 if (std::optional<MutableSpan<int>> indices = get_orig_index_layer(mesh, AttrDomain::Face)) {
1450 array_utils::gather_to_groups(
1451 group_per_face, face_selection, indices->as_span(), indices->slice(side_face_range));
1452 }
1453
1454 if (attribute_outputs.top_id) {
1456 attributes, *attribute_outputs.top_id, AttrDomain::Face, face_selection);
1457 }
1458 if (attribute_outputs.side_id) {
1460 attributes, *attribute_outputs.side_id, AttrDomain::Face, side_face_range);
1461 }
1462
1464}
1465
1467{
1468 GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
1469 Field<bool> selection = params.extract_input<Field<bool>>("Selection");
1470 Field<float3> offset_field = params.extract_input<Field<float3>>("Offset");
1471 Field<float> scale_field = params.extract_input<Field<float>>("Offset Scale");
1472 const NodeGeometryExtrudeMesh &storage = node_storage(params.node());
1474
1475 /* Create a combined field from the offset and the scale so the field evaluator
1476 * can take care of the multiplication and to simplify each extrude function. */
1477 static auto multiply_fn = mf::build::SI2_SO<float3, float, float3>(
1478 "Scale",
1479 [](const float3 &offset, const float scale) { return offset * scale; },
1480 mf::build::exec_presets::AllSpanOrSingle());
1481 const Field<float3> final_offset{
1482 FieldOperation::Create(multiply_fn, {std::move(offset_field), std::move(scale_field)})};
1483
1484 AttributeOutputs attribute_outputs;
1485 attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
1486 attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side");
1487
1488 const bool extrude_individual = mode == GEO_NODE_EXTRUDE_MESH_FACES &&
1489 params.extract_input<bool>("Individual");
1490
1491 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Mesh");
1492
1493 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
1494 if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
1495
1496 switch (mode) {
1499 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1500 break;
1502 extrude_mesh_edges(*mesh, selection, final_offset, attribute_outputs, attribute_filter);
1503 break;
1505 if (extrude_individual) {
1507 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1508 }
1509 else {
1511 *mesh, selection, final_offset, attribute_outputs, attribute_filter);
1512 }
1513 break;
1514 }
1515 }
1516
1518 }
1519 });
1520
1521 params.set_output("Mesh", std::move(geometry_set));
1522}
1523
1524static void node_rna(StructRNA *srna)
1525{
1526 static const EnumPropertyItem mode_items[] = {
1527 {GEO_NODE_EXTRUDE_MESH_VERTICES, "VERTICES", 0, "Vertices", ""},
1528 {GEO_NODE_EXTRUDE_MESH_EDGES, "EDGES", 0, "Edges", ""},
1529 {GEO_NODE_EXTRUDE_MESH_FACES, "FACES", 0, "Faces", ""},
1530 {0, nullptr, 0, nullptr, nullptr},
1531 };
1532
1533 RNA_def_node_enum(srna,
1534 "mode",
1535 "Mode",
1536 "",
1537 mode_items,
1540}
1541
1542static void node_register()
1543{
1544 static blender::bke::bNodeType ntype;
1546 ntype.declare = node_declare;
1547 ntype.initfunc = node_init;
1550 &ntype, "NodeGeometryExtrudeMesh", node_free_standard_storage, node_copy_standard_storage);
1551 ntype.draw_buttons = node_layout;
1553
1554 node_rna(ntype.rna_ext.srna);
1555}
1556NOD_REGISTER_NODE(node_register)
1557
1558} // namespace blender::nodes::node_geo_extrude_mesh_cc
#define ATTR_DOMAIN_NUM
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, int totelem)
#define ORIGINDEX_NONE
void * CustomData_get_layer_for_write(CustomData *data, eCustomDataType type, int totelem)
support for deformation groups and hooks.
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define GEO_NODE_EXTRUDE_MESH
Definition BKE_node.hh:1281
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
#define BLT_I18NCONTEXT_ID_NODETREE
@ CD_MLOOPTANGENT
@ CD_MVERT_SKIN
@ CD_CUSTOMLOOPNORMAL
@ CD_PROP_INT32_2D
@ CD_PROP_INT32
@ CD_FREESTYLE_EDGE
@ CD_FREESTYLE_FACE
@ CD_GRID_PAINT_MASK
@ CD_CLOTH_ORCO
@ CD_PROP_STRING
GeometryNodeExtrudeMeshMode
@ GEO_NODE_EXTRUDE_MESH_FACES
@ GEO_NODE_EXTRUDE_MESH_VERTICES
@ GEO_NODE_EXTRUDE_MESH_EDGES
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
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
@ PROP_TRANSLATION
Definition RNA_types.hh:164
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
AttributeSet attributes
static IndexMask from_indices(Span< T > indices, IndexMaskMemory &memory)
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
IndexRange index_range() const
Definition BLI_array.hh:349
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
const CPPType & type() const
constexpr int64_t size() const
constexpr IndexRange after(int64_t n) const
constexpr IndexRange index_range() const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
constexpr bool is_empty() const
Definition BLI_span.hh:510
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
bool contains(const Key &key) const
Definition BLI_set.hh:291
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:572
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr const T & first() const
Definition BLI_span.hh:316
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
int64_t index_of(const Key &key) const
void reserve(const int64_t n)
bool add(const Key &key)
void add_new(const Key &key)
int64_t index_of_try(const Key &key) const
IndexRange index_range() const
int64_t size() const
void append(const T &value)
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
Set< StringRefNull > all_ids() const
int domain_size(const AttrDomain domain) const
bool contains(const StringRef attribute_id) const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
bool remove(const StringRef attribute_id)
GSpanAttributeWriter lookup_for_write_span(StringRef attribute_id)
void set_selection(Field< bool > selection)
Definition FN_field.hh:385
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:822
int add_with_destination(GField field, GVMutableArray dst)
Definition field.cc:743
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:450
void to_indices(MutableSpan< T > r_indices) const
void foreach_index_optimized(Fn &&fn) const
void to_bools(MutableSpan< bool > r_bools) const
void foreach_index(Fn &&fn) const
static std::shared_ptr< FieldOperation > Create(std::shared_ptr< const mf::MultiFunction > function, Vector< GField > inputs={})
Definition FN_field.hh:244
local_group_size(16, 16) .push_constant(Type b
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
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)
void gather_to_groups(OffsetIndices< int > dst_offsets, const IndexMask &src_selection, GSpan src, GMutableSpan dst)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
typename DefaultPropagationMixerStruct< T >::type DefaultPropagationMixer
GroupedSpan< int > build_edge_to_face_map(OffsetIndices< int > faces, Span< int > corner_edges, int edges_num, Array< int > &r_offsets, Array< int > &r_indices)
int face_corner_next(const IndexRange face, const int corner)
Definition BKE_mesh.hh:252
GroupedSpan< int > build_vert_to_edge_map(Span< int2 > edges, int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
void gather_deform_verts(Span< MDeformVert > src, Span< int > indices, MutableSpan< MDeformVert > dst)
Definition deform.cc:1811
void debug_randomize_mesh_order(Mesh *mesh)
Definition randomize.cc:220
IndexMask vert_selection_from_face(OffsetIndices< int > faces, const IndexMask &face_mask, Span< int > corner_verts, int verts_num, IndexMaskMemory &memory)
IndexMask vert_selection_from_edge(Span< int2 > edges, const IndexMask &edge_mask, int verts_num, IndexMaskMemory &memory)
void resize_trivial_array(T **data, const ImplicitSharingInfo **sharing_info, int64_t old_size, int64_t new_size)
void build_reverse_map(const IndexMask &mask, MutableSpan< T > r_map)
Definition index_mask.cc:31
void index(const bNode &, void *r_value)
void normal(const bNode &, void *r_value)
static void node_declare(NodeDeclarationBuilder &b)
static void extrude_individual_mesh_faces(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void extrude_mesh_face_regions(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void extrude_mesh_edges(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
std::array< Vector< StringRef >, ATTR_DOMAIN_NUM > IDsByDomain
static void gather_vert_attributes(Mesh &mesh, const Span< StringRef > ids, const Span< int > indices, const IndexRange new_range)
static IDsByDomain attribute_ids_by_domain(const AttributeAccessor attributes, const Set< StringRef > &skip)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static VectorSet< int > vert_indices_from_edges(const Mesh &mesh, const Span< int > edge_indices)
static bool is_empty_domain(const AttributeAccessor attributes, const Set< StringRef > &skip, const AttrDomain domain)
static CustomData & mesh_custom_data_for_domain(Mesh &mesh, const AttrDomain domain)
static void extrude_mesh_vertices(Mesh &mesh, const Field< bool > &selection_field, const Field< float3 > &offset_field, const AttributeOutputs &attribute_outputs, const AttributeFilter &attribute_filter)
static void remove_non_propagated_attributes(MutableAttributeAccessor attributes, const AttributeFilter &attribute_filter)
static std::optional< MutableSpan< int > > get_orig_index_layer(Mesh &mesh, const AttrDomain domain)
static void save_selection_as_attribute(MutableAttributeAccessor attributes, const StringRef id, const AttrDomain domain, const IndexMask &selection)
static void fill_quad_consistent_direction(const Span< int > other_face_verts, const Span< int > other_face_edges, MutableSpan< int > new_corner_verts, MutableSpan< int > new_corner_edges, const int vert_connected_to_face_1, const int vert_connected_to_face_2, const int vert_across_from_face_1, const int vert_across_from_face_2, const int edge_connected_to_face, const int connecting_edge_1, const int edge_across_from_face, const int connecting_edge_2)
static void gather_attributes(MutableAttributeAccessor attributes, const Span< StringRef > ids, const Span< int > indices, const IndexRange new_range)
static void node_geo_exec(GeoNodeExecParams params)
void copy_with_mixing(const Span< T > src, const GroupedSpan< int > src_groups, const IndexMask &selection, MutableSpan< T > dst)
static void expand_mesh(Mesh &mesh, const int vert_expand, const int edge_expand, const int face_expand, const int loop_expand)
static GroupedSpan< int > build_vert_to_edge_map(const Span< int2 > edges, const IndexMask &edge_mask, const int verts_num, Array< int > &r_offsets, Array< int > &r_indices)
static void node_init(bNodeTree *, bNode *node)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
void fill_constant_group_size(int size, int start_offset, MutableSpan< int > offsets)
OffsetIndices< int > gather_selected_offsets(OffsetIndices< int > src_offsets, const IndexMask &selection, int start_offset, MutableSpan< int > dst_offsets)
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
VecBase< int32_t, 2 > int2
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
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[]
__int64 int64_t
Definition stdint.h:89
StructRNA * srna
Definition RNA_types.hh:780
int corners_num
CustomData edge_data
int edges_num
MeshRuntimeHandle * runtime
CustomData corner_data
CustomData face_data
ListBase vertex_group_names
int * face_offset_indices
CustomData vert_data
int faces_num
int verts_num
void * storage
bool allow_skip(const StringRef name) const
void modify_geometry_sets(ForeachSubGeometryCallback callback)
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126