Geometry Nodes: Support original indices in extrude node

This allows using the "On Cage" feature in edit mode to interact with
original mesh elements via the newly created geometry. The original
indices are only set for new elements that copy attribute values
from original elements directly, so it can also be a helpful way
to visualize attribute propagation.

The change was simplified by refactoring the individual mode slightly
to create separate index maps for the new edges and vertices. That
simplified attribute copying a bit too.
This commit is contained in:
Hans Goudey 2022-12-09 14:19:11 -06:00
parent 46993958fd
commit d17db1d11a
1 changed files with 89 additions and 67 deletions

View File

@ -272,7 +272,10 @@ static void extrude_mesh_vertices(Mesh &mesh,
});
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
vert_orig_indices.slice_safe(new_vert_range).fill(ORIGINDEX_NONE);
if (!vert_orig_indices.is_empty()) {
array_utils::gather(
vert_orig_indices.as_span(), selection, vert_orig_indices.slice(new_vert_range));
}
MutableSpan<int> new_edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE);
new_edge_orig_indices.slice_safe(new_edge_range).fill(ORIGINDEX_NONE);
@ -608,11 +611,19 @@ static void extrude_mesh_edges(Mesh &mesh,
}
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
vert_orig_indices.slice_safe(new_vert_range).fill(ORIGINDEX_NONE);
if (!vert_orig_indices.is_empty()) {
array_utils::gather(vert_orig_indices.as_span(),
new_vert_indices.as_span(),
vert_orig_indices.slice(new_vert_range));
}
MutableSpan<int> edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE);
edge_orig_indices.slice_safe(connect_edge_range).fill(ORIGINDEX_NONE);
edge_orig_indices.slice_safe(duplicate_edge_range).fill(ORIGINDEX_NONE);
if (!edge_orig_indices.is_empty()) {
edge_orig_indices.slice(connect_edge_range).fill(ORIGINDEX_NONE);
array_utils::gather(edge_orig_indices.as_span(),
edge_selection,
edge_orig_indices.slice(duplicate_edge_range));
}
MutableSpan<int> poly_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_FACE);
poly_orig_indices.slice_safe(new_poly_range).fill(ORIGINDEX_NONE);
@ -992,15 +1003,29 @@ static void extrude_mesh_face_regions(Mesh &mesh,
}
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
vert_orig_indices.slice_safe(new_vert_range).fill(ORIGINDEX_NONE);
if (!vert_orig_indices.is_empty()) {
array_utils::gather(vert_orig_indices.as_span(),
new_vert_indices.as_span(),
vert_orig_indices.slice(new_vert_range));
}
MutableSpan<int> edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE);
edge_orig_indices.slice_safe(connect_edge_range).fill(ORIGINDEX_NONE);
edge_orig_indices.slice_safe(new_inner_edge_range).fill(ORIGINDEX_NONE);
edge_orig_indices.slice_safe(boundary_edge_range).fill(ORIGINDEX_NONE);
if (!edge_orig_indices.is_empty()) {
edge_orig_indices.slice(connect_edge_range).fill(ORIGINDEX_NONE);
array_utils::gather(edge_orig_indices.as_span(),
new_inner_edge_indices.as_span(),
edge_orig_indices.slice(new_inner_edge_range));
array_utils::gather(edge_orig_indices.as_span(),
boundary_edge_indices.as_span(),
edge_orig_indices.slice(boundary_edge_range));
}
MutableSpan<int> poly_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_FACE);
poly_orig_indices.slice_safe(side_poly_range).fill(ORIGINDEX_NONE);
if (!poly_orig_indices.is_empty()) {
array_utils::gather(poly_orig_indices.as_span(),
edge_extruded_face_indices.as_span(),
poly_orig_indices.slice(side_poly_range));
}
if (attribute_outputs.top_id) {
save_selection_as_attribute(
@ -1077,21 +1102,32 @@ static void extrude_individual_mesh_faces(Mesh &mesh,
MutableSpan<MPoly> new_polys = polys.slice(side_poly_range);
MutableSpan<MLoop> loops = mesh.loops_for_write();
/* For every selected polygon, build the faces that form the sides of the extrusion. Filling some
* of this data like the new edges or polygons could be easily split into separate loops, which
* may or may not be faster, and would involve more duplication. */
/* For every selected polygon, change it to use the new extruded vertices and the duplicate
* edges, and build the faces that form the sides of the extrusion. Build "original index"
* arrays for the new vertices and edges so they can be accessed later.
* Filling some of this data like the new edges or polygons could be easily split into
* separate loops, which may or may not be faster, but would involve more duplication. */
Array<int> new_vert_indices(extrude_corner_size);
Array<int> duplicate_edge_indices(extrude_corner_size);
threading::parallel_for(poly_selection.index_range(), 256, [&](const IndexRange range) {
for (const int i_selection : range) {
const IndexRange poly_corner_range = selected_corner_range(index_offsets, i_selection);
const MPoly &poly = polys[poly_selection[i_selection]];
Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
MutableSpan<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const int i : IndexRange(poly.totloop)) {
const int i_extrude = poly_corner_range[i];
new_vert_indices[i_extrude] = poly_loops[i].v;
duplicate_edge_indices[i_extrude] = poly_loops[i].e;
poly_loops[i].v = new_vert_range[i_extrude];
poly_loops[i].e = duplicate_edge_range[i_extrude];
}
for (const int i : IndexRange(poly.totloop)) {
const int i_next = (i == poly.totloop - 1) ? 0 : i + 1;
const MLoop &orig_loop = poly_loops[i];
const MLoop &orig_loop_next = poly_loops[i_next];
const int i_extrude = poly_corner_range[i];
const int i_extrude_next = poly_corner_range[i_next];
@ -1099,10 +1135,10 @@ static void extrude_individual_mesh_faces(Mesh &mesh,
const int new_vert = new_vert_range[i_extrude];
const int new_vert_next = new_vert_range[i_extrude_next];
const int orig_edge = orig_loop.e;
const int orig_edge = duplicate_edge_indices[i_extrude];
const int orig_vert = orig_loop.v;
const int orig_vert_next = orig_loop_next.v;
const int orig_vert = new_vert_indices[i_extrude];
const int orig_vert_next = new_vert_indices[i_extrude_next];
duplicate_edges[i_extrude] = new_edge(new_vert, new_vert_next);
@ -1142,52 +1178,39 @@ static void extrude_individual_mesh_faces(Mesh &mesh,
case ATTR_DOMAIN_POINT: {
/* New vertices copy the attributes from their original vertices. */
MutableSpan<T> new_data = data.slice(new_vert_range);
threading::parallel_for(poly_selection.index_range(), 1024, [&](const IndexRange range) {
for (const int i_selection : range) {
const MPoly &poly = polys[poly_selection[i_selection]];
Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
const int corner_offset = index_offsets[i_selection];
for (const int i : poly_loops.index_range()) {
const int orig_index = poly_loops[i].v;
new_data[corner_offset + i] = data[orig_index];
}
}
});
array_utils::gather(data.as_span(), new_vert_indices.as_span(), new_data);
break;
}
case ATTR_DOMAIN_EDGE: {
/* The data for the duplicate edge is simply a copy of the original edge's data. */
MutableSpan<T> duplicate_data = data.slice(duplicate_edge_range);
MutableSpan<T> connect_data = data.slice(connect_edge_range);
array_utils::gather(data.as_span(), duplicate_edge_indices.as_span(), duplicate_data);
MutableSpan<T> connect_data = data.slice(connect_edge_range);
threading::parallel_for(poly_selection.index_range(), 512, [&](const IndexRange range) {
for (const int i_selection : range) {
const MPoly &poly = polys[poly_selection[i_selection]];
Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
const IndexRange poly_corner_range = selected_corner_range(index_offsets,
i_selection);
/* The data for the duplicate edge is simply a copy of the original edge's data. */
for (const int i : poly_loops.index_range()) {
const int orig_index = poly_loops[i].e;
duplicate_data[poly_corner_range[i]] = data[orig_index];
}
/* For the extruded edges, mix the data from the two neighboring original edges of
* the extruded polygon. */
for (const int i : poly_loops.index_range()) {
const int i_loop_prev = (i == 0) ? poly.totloop - 1 : i - 1;
const int orig_index = poly_loops[i].e;
const int orig_index_prev = poly_loops[i_loop_prev].e;
const int i_prev = (i == 0) ? poly.totloop - 1 : i - 1;
const int i_extrude = poly_corner_range[i];
const int i_extrude_prev = poly_corner_range[i_prev];
const int orig_edge = duplicate_edge_indices[i_extrude];
const int orig_edge_prev = duplicate_edge_indices[i_extrude_prev];
if constexpr (std::is_same_v<T, bool>) {
/* Propagate selections with "or" instead of "at least half". */
connect_data[poly_corner_range[i]] = data[orig_index] || data[orig_index_prev];
connect_data[i_extrude] = data[orig_edge] || data[orig_edge_prev];
}
else {
connect_data[poly_corner_range[i]] = attribute_math::mix2(
0.5f, data[orig_index], data[orig_index_prev]);
connect_data[i_extrude] = attribute_math::mix2(
0.5f, data[orig_edge], data[orig_edge_prev]);
}
}
}
@ -1256,32 +1279,31 @@ static void extrude_individual_mesh_faces(Mesh &mesh,
});
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
vert_orig_indices.slice_safe(new_vert_range).fill(ORIGINDEX_NONE);
if (!vert_orig_indices.is_empty()) {
array_utils::gather(vert_orig_indices.as_span(),
new_vert_indices.as_span(),
vert_orig_indices.slice(new_vert_range));
}
MutableSpan<int> edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE);
edge_orig_indices.slice_safe(connect_edge_range).fill(ORIGINDEX_NONE);
edge_orig_indices.slice_safe(duplicate_edge_range).fill(ORIGINDEX_NONE);
if (!edge_orig_indices.is_empty()) {
edge_orig_indices.slice(connect_edge_range).fill(ORIGINDEX_NONE);
array_utils::gather(edge_orig_indices.as_span(),
duplicate_edge_indices.as_span(),
edge_orig_indices.slice(duplicate_edge_range));
}
MutableSpan<int> poly_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_FACE);
poly_orig_indices.slice_safe(side_poly_range).fill(ORIGINDEX_NONE);
/* Finally update each extruded polygon's loops to point to the new edges and vertices.
* This must be done last, because they were used to find original indices for attribute
* interpolation before. Alternatively an original index array could be built for each domain. */
threading::parallel_for(poly_selection.index_range(), 256, [&](const IndexRange range) {
for (const int i_selection : range) {
const IndexRange poly_corner_range = selected_corner_range(index_offsets, i_selection);
const MPoly &poly = polys[poly_selection[i_selection]];
MutableSpan<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const int i : IndexRange(poly.totloop)) {
MLoop &loop = poly_loops[i];
loop.v = new_vert_range[poly_corner_range[i]];
loop.e = duplicate_edge_range[poly_corner_range[i]];
if (!poly_orig_indices.is_empty()) {
MutableSpan<int> new_poly_orig_indices = poly_orig_indices.slice(side_poly_range);
threading::parallel_for(poly_selection.index_range(), 1024, [&](const IndexRange range) {
for (const int selection_i : range) {
const int poly_i = poly_selection[selection_i];
const IndexRange poly_corner_range = selected_corner_range(index_offsets, selection_i);
new_poly_orig_indices.slice(poly_corner_range).fill(poly_orig_indices[poly_i]);
}
}
});
});
}
if (attribute_outputs.top_id) {
save_selection_as_attribute(