Geometry Nodes: Rewrite parts of extrude mesh node
The node is still a bit non-standard in that it resizes an existing mesh rather than creating a new one, but this commit makes the extrude node a bit more similar to other mesh operations and makes other miscellaneous improvements, including: - Less use of intermediate states (compared to initial or final) - Topology map building is no longer reimplemented for the node - Attribute interpolation happens in a more familiar way - Some topology maps can be skipped if a domain is empty - More use of `IndexMask` instead of an array of indices - Logarithmic cost index mask lookup is avoided - Build index maps instead of implementing attribute propagation separately for every type Overall these changes might improve performance in a few cases, and they reduce Blender's binary size by 58 KB. Edge indices are different in some cases of the edge mode, so the test files are updated.
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include "BKE_mesh_mapping.hh"
|
||||
#include "BKE_mesh_runtime.hh"
|
||||
|
||||
#include "GEO_mesh_selection.hh"
|
||||
#include "GEO_randomize.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
@@ -74,12 +75,11 @@ struct AttributeOutputs {
|
||||
AnonymousAttributeIDPtr side_id;
|
||||
};
|
||||
|
||||
static void save_selection_as_attribute(Mesh &mesh,
|
||||
static void save_selection_as_attribute(MutableAttributeAccessor attributes,
|
||||
const AnonymousAttributeID *id,
|
||||
const eAttrDomain domain,
|
||||
const IndexMask &selection)
|
||||
{
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
BLI_assert(!attributes.contains(id));
|
||||
|
||||
SpanAttributeWriter<bool> attribute = attributes.lookup_or_add_for_write_span<bool>(id, domain);
|
||||
@@ -207,21 +207,49 @@ static MutableSpan<int> get_orig_index_layer(Mesh &mesh, const eAttrDomain domai
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* \param get_mix_indices_fn: Returns a Span of indices of the source points to mix for every
|
||||
* result point.
|
||||
*/
|
||||
template<typename T>
|
||||
void copy_with_mixing(const Span<T> src,
|
||||
const FunctionRef<Span<int>(int)> get_mix_indices_fn,
|
||||
const GroupedSpan<int> src_groups,
|
||||
const IndexMask &selection,
|
||||
MutableSpan<T> dst)
|
||||
{
|
||||
selection.foreach_segment(
|
||||
GrainSize(512), [&](const IndexMaskSegment segment, const int64_t segment_pos) {
|
||||
const IndexRange dst_range(segment_pos, segment.size());
|
||||
bke::attribute_math::DefaultPropagationMixer<T> mixer{dst.slice(dst_range)};
|
||||
for (const int i : segment.index_range()) {
|
||||
for (const int src_i : src_groups[segment[i]]) {
|
||||
mixer.mix_in(i, src[src_i]);
|
||||
}
|
||||
}
|
||||
mixer.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
static void copy_with_mixing(const GSpan src,
|
||||
const GroupedSpan<int> src_groups,
|
||||
const IndexMask &selection,
|
||||
GMutableSpan dst)
|
||||
{
|
||||
BLI_assert(selection.size() == dst.size());
|
||||
bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void copy_with_mixing(const Span<T> src,
|
||||
const GroupedSpan<int> src_groups,
|
||||
const Span<int> selection,
|
||||
MutableSpan<T> dst)
|
||||
{
|
||||
threading::parallel_for(dst.index_range(), 512, [&](const IndexRange range) {
|
||||
bke::attribute_math::DefaultPropagationMixer<T> mixer{dst.slice(range)};
|
||||
for (const int i_dst : IndexRange(range.size())) {
|
||||
const Span<int> indices = get_mix_indices_fn(range[i_dst]);
|
||||
for (const int i_src : indices) {
|
||||
mixer.mix_in(i_dst, src[i_src]);
|
||||
for (const int i : range.index_range()) {
|
||||
const int group_i = selection[i];
|
||||
for (const int i_src : src_groups[group_i]) {
|
||||
mixer.mix_in(i, src[i_src]);
|
||||
}
|
||||
}
|
||||
mixer.finalize();
|
||||
@@ -229,25 +257,57 @@ void copy_with_mixing(const Span<T> src,
|
||||
}
|
||||
|
||||
static void copy_with_mixing(const GSpan src,
|
||||
const FunctionRef<Span<int>(int)> get_mix_indices_fn,
|
||||
const GroupedSpan<int> src_groups,
|
||||
const Span<int> selection,
|
||||
GMutableSpan dst)
|
||||
{
|
||||
bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
copy_with_mixing(src.typed<T>(), get_mix_indices_fn, dst.typed<T>());
|
||||
copy_with_mixing(src.typed<T>(), src_groups, selection, dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
static Array<Vector<int>> create_vert_to_edge_map(const int vert_size,
|
||||
const Span<int2> edges,
|
||||
const int vert_offset = 0)
|
||||
using IDsByDomain = std::array<Vector<AttributeIDRef>, ATTR_DOMAIN_NUM>;
|
||||
|
||||
static IDsByDomain attribute_ids_by_domain(const AttributeAccessor attributes,
|
||||
const Set<StringRef> &skip)
|
||||
{
|
||||
Array<Vector<int>> vert_to_edge_map(vert_size);
|
||||
for (const int i : edges.index_range()) {
|
||||
vert_to_edge_map[edges[i][0] - vert_offset].append(i);
|
||||
vert_to_edge_map[edges[i][1] - vert_offset].append(i);
|
||||
IDsByDomain map;
|
||||
attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (skip.contains(id.name())) {
|
||||
return true;
|
||||
}
|
||||
map[meta_data.domain].append(id);
|
||||
return true;
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
static void gather_attributes(MutableAttributeAccessor attributes,
|
||||
const Span<AttributeIDRef> ids,
|
||||
const Span<int> indices,
|
||||
const IndexRange new_range)
|
||||
{
|
||||
for (const AttributeIDRef &id : ids) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
bke::attribute_math::gather(attribute.span, indices, attribute.span.slice(new_range));
|
||||
attribute.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static void gather_attributes(MutableAttributeAccessor attributes,
|
||||
const Span<AttributeIDRef> ids,
|
||||
const IndexMask &indices,
|
||||
const IndexRange new_range)
|
||||
{
|
||||
for (const AttributeIDRef &id : ids) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
array_utils::gather(attribute.span, indices, attribute.span.slice(new_range));
|
||||
attribute.finish();
|
||||
}
|
||||
return vert_to_edge_map;
|
||||
}
|
||||
|
||||
static void extrude_mesh_vertices(Mesh &mesh,
|
||||
@@ -274,58 +334,39 @@ static void extrude_mesh_vertices(Mesh &mesh,
|
||||
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
remove_non_propagated_attributes(attributes, propagation_info);
|
||||
remove_unsupported_vert_data(mesh);
|
||||
remove_unsupported_edge_data(mesh);
|
||||
|
||||
Set<AttributeIDRef> point_ids;
|
||||
Set<AttributeIDRef> edge_ids;
|
||||
mesh.attributes().for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (meta_data.domain == ATTR_DOMAIN_POINT) {
|
||||
if (id.name() != "position") {
|
||||
point_ids.add(id);
|
||||
}
|
||||
}
|
||||
else if (meta_data.domain == ATTR_DOMAIN_EDGE) {
|
||||
if (id.name() != ".edge_verts") {
|
||||
edge_ids.add(id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const IDsByDomain ids_by_domain = attribute_ids_by_domain(attributes,
|
||||
{"position", ".edge_verts"});
|
||||
|
||||
/* This allows parallelizing attribute mixing for new edges. */
|
||||
Array<Vector<int>> vert_to_edge_map;
|
||||
if (!edge_ids.is_empty()) {
|
||||
vert_to_edge_map = create_vert_to_edge_map(orig_vert_size, mesh.edges());
|
||||
Array<int> vert_to_edge_offsets;
|
||||
Array<int> vert_to_edge_indices;
|
||||
GroupedSpan<int> vert_to_edge_map;
|
||||
if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) {
|
||||
vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
|
||||
mesh.edges(), orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
|
||||
}
|
||||
|
||||
remove_unsupported_vert_data(mesh);
|
||||
remove_unsupported_edge_data(mesh);
|
||||
expand_mesh(mesh, selection.size(), selection.size(), 0, 0);
|
||||
|
||||
const IndexRange new_vert_range{orig_vert_size, selection.size()};
|
||||
const IndexRange new_edge_range{orig_edge_size, selection.size()};
|
||||
|
||||
MutableSpan<int2> new_edges = mesh.edges_for_write().slice(new_edge_range);
|
||||
selection.foreach_index_optimized<int>([&](const int index, const int i_selection) {
|
||||
new_edges[i_selection] = int2(index, new_vert_range[i_selection]);
|
||||
});
|
||||
selection.foreach_index_optimized<int>(
|
||||
GrainSize(4096), [&](const int index, const int i_selection) {
|
||||
new_edges[i_selection] = int2(index, new_vert_range[i_selection]);
|
||||
});
|
||||
|
||||
/* New vertices copy the attribute values from their source vertex. */
|
||||
for (const AttributeIDRef &id : point_ids) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
array_utils::gather(attribute.span, selection, attribute.span.slice(new_vert_range));
|
||||
attribute.finish();
|
||||
}
|
||||
gather_attributes(attributes, ids_by_domain[ATTR_DOMAIN_POINT], selection, new_vert_range);
|
||||
|
||||
/* New edge values are mixed from of all the edges connected to the source vertex. */
|
||||
for (const AttributeIDRef &id : edge_ids) {
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
copy_with_mixing(
|
||||
attribute.span,
|
||||
[&](const int i) { return vert_to_edge_map[selection[i]].as_span(); },
|
||||
attribute.span.slice(new_edge_range));
|
||||
attribute.span, vert_to_edge_map, selection, attribute.span.slice(new_edge_range));
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
@@ -346,11 +387,11 @@ static void extrude_mesh_vertices(Mesh &mesh,
|
||||
|
||||
if (attribute_outputs.top_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_POINT, new_vert_range);
|
||||
attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_POINT, new_vert_range);
|
||||
}
|
||||
if (attribute_outputs.side_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_EDGE, new_edge_range);
|
||||
attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_EDGE, new_edge_range);
|
||||
}
|
||||
|
||||
const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
|
||||
@@ -408,22 +449,40 @@ static void fill_quad_consistent_direction(const Span<int> other_face_verts,
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static VectorSet<int> vert_indices_from_edges(const Mesh &mesh, const Span<T> edge_indices)
|
||||
static void create_reverse_map(const IndexMask &mask, MutableSpan<int> r_map)
|
||||
{
|
||||
static_assert(is_same_any_v<T, int, int64_t>);
|
||||
const Span<int2> edges = mesh.edges();
|
||||
|
||||
VectorSet<int> vert_indices;
|
||||
vert_indices.reserve(edge_indices.size());
|
||||
for (const T i_edge : edge_indices) {
|
||||
const int2 &edge = edges[i_edge];
|
||||
vert_indices.add(edge[0]);
|
||||
vert_indices.add(edge[1]);
|
||||
}
|
||||
return vert_indices;
|
||||
#ifdef DEBUG
|
||||
r_map.fill(-1);
|
||||
#endif
|
||||
mask.foreach_index_optimized<int>(
|
||||
GrainSize(4096), [&](const int src_i, const int dst_i) { r_map[src_i] = dst_i; });
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (edge_mask.size() == edges.size()) {
|
||||
return bke::mesh::build_vert_to_edge_map(edges, verts_num, r_offsets, r_indices);
|
||||
}
|
||||
Array<int2> masked_edges(edge_mask.size());
|
||||
array_utils::gather(edges, edge_mask, masked_edges.as_mutable_span());
|
||||
|
||||
bke::mesh::build_vert_to_edge_map(masked_edges, verts_num, r_offsets, r_indices);
|
||||
|
||||
Array<int> masked_edge_to_edge(edge_mask.size());
|
||||
edge_mask.to_indices<int>(masked_edge_to_edge);
|
||||
|
||||
threading::parallel_for(r_indices.index_range(), 4096, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
r_indices[i] = masked_edge_to_edge[r_indices[i]];
|
||||
}
|
||||
});
|
||||
|
||||
return {r_offsets.as_span(), r_indices.as_span()};
|
||||
}
|
||||
static void tag_mesh_added_faces(Mesh &mesh)
|
||||
{
|
||||
const bool no_loose_vert_hint = mesh.runtime->loose_verts_cache.is_cached() &&
|
||||
@@ -465,11 +524,6 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
return;
|
||||
}
|
||||
|
||||
Array<int> edge_to_face_offsets;
|
||||
Array<int> edge_to_face_indices;
|
||||
const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
|
||||
orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices);
|
||||
|
||||
/* Find the offsets on the vertex domain for translation. This must be done before the mesh's
|
||||
* custom data layers are reallocated, in case the virtual array references one of them. */
|
||||
Array<float3> vert_offsets;
|
||||
@@ -477,7 +531,7 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
vert_offsets.reinitialize(orig_vert_size);
|
||||
bke::attribute_math::DefaultPropagationMixer<float3> mixer(vert_offsets);
|
||||
edge_selection.foreach_index([&](const int i_edge) {
|
||||
const int2 &edge = orig_edges[i_edge];
|
||||
const int2 edge = orig_edges[i_edge];
|
||||
const float3 offset = edge_offsets[i_edge];
|
||||
mixer.mix_in(edge[0], offset);
|
||||
mixer.mix_in(edge[1], offset);
|
||||
@@ -485,12 +539,11 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
mixer.finalize();
|
||||
}
|
||||
|
||||
Vector<int> edge_selection_indices(edge_selection.size());
|
||||
edge_selection.to_indices(edge_selection_indices.as_mutable_span());
|
||||
const VectorSet<int> new_vert_indices = vert_indices_from_edges<int>(mesh,
|
||||
edge_selection_indices);
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask new_verts = geometry::vert_selection_from_edge(
|
||||
orig_edges, edge_selection, orig_vert_size, memory);
|
||||
|
||||
const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()};
|
||||
const IndexRange new_vert_range{orig_vert_size, new_verts.size()};
|
||||
/* The extruded edges connect the original and duplicate edges. */
|
||||
const IndexRange connect_edge_range{orig_edges.size(), new_vert_range.size()};
|
||||
/* The duplicate edges are extruded copies of the selected edges. */
|
||||
@@ -500,7 +553,25 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
/* Every new face is a quad with four corners. */
|
||||
const IndexRange new_loop_range{orig_loop_size, new_face_range.size() * 4};
|
||||
|
||||
remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info);
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
remove_non_propagated_attributes(attributes, propagation_info);
|
||||
|
||||
const IDsByDomain ids_by_domain = attribute_ids_by_domain(
|
||||
attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
|
||||
|
||||
Array<int> edge_to_face_offsets;
|
||||
Array<int> edge_to_face_indices;
|
||||
const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
|
||||
orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices);
|
||||
|
||||
Array<int> vert_to_edge_offsets;
|
||||
Array<int> vert_to_edge_indices;
|
||||
GroupedSpan<int> vert_to_selected_edge_map;
|
||||
if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) {
|
||||
vert_to_selected_edge_map = build_vert_to_edge_map(
|
||||
orig_edges, edge_selection, orig_vert_size, vert_to_edge_offsets, vert_to_edge_indices);
|
||||
}
|
||||
|
||||
remove_unsupported_vert_data(mesh);
|
||||
remove_unsupported_edge_data(mesh);
|
||||
remove_unsupported_face_data(mesh);
|
||||
@@ -524,31 +595,36 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
offset_indices::fill_constant_group_size(4, orig_loop_size, new_face_offsets);
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
|
||||
for (const int i : connect_edges.index_range()) {
|
||||
connect_edges[i] = int2(new_vert_indices[i], new_vert_range[i]);
|
||||
}
|
||||
new_verts.foreach_index_optimized<int>(GrainSize(4096), [&](const int src, const int dst) {
|
||||
connect_edges[dst] = int2(src, new_vert_range[dst]);
|
||||
});
|
||||
|
||||
for (const int i : duplicate_edges.index_range()) {
|
||||
const int2 &orig_edge = edges[edge_selection[i]];
|
||||
const int i_new_vert_1 = new_vert_indices.index_of(orig_edge[0]);
|
||||
const int i_new_vert_2 = new_vert_indices.index_of(orig_edge[1]);
|
||||
duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
|
||||
{
|
||||
Array<int> vert_to_new_vert(orig_vert_size);
|
||||
create_reverse_map(new_verts, vert_to_new_vert);
|
||||
for (const int i : duplicate_edges.index_range()) {
|
||||
const int2 orig_edge = edges[edge_selection[i]];
|
||||
const int i_new_vert_1 = vert_to_new_vert[orig_edge[0]];
|
||||
const int i_new_vert_2 = vert_to_new_vert[orig_edge[1]];
|
||||
duplicate_edges[i] = int2(new_vert_range[i_new_vert_1], new_vert_range[i_new_vert_2]);
|
||||
}
|
||||
}
|
||||
|
||||
edge_selection.foreach_index([&](const int64_t orig_edge_index, const int64_t i) {
|
||||
const int2 &duplicate_edge = duplicate_edges[i];
|
||||
const int2 duplicate_edge = duplicate_edges[i];
|
||||
const int new_vert_1 = duplicate_edge[0];
|
||||
const int new_vert_2 = duplicate_edge[1];
|
||||
const int extrude_index_1 = new_vert_1 - orig_vert_size;
|
||||
const int extrude_index_2 = new_vert_2 - orig_vert_size;
|
||||
|
||||
const int2 orig_edge = edges[orig_edge_index];
|
||||
const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
|
||||
|
||||
/* When there was a single face connected to the new face, we can use the old one to keep
|
||||
* the face direction consistent. When there is more than one connected edge, the new face
|
||||
* the face direction consistent. When there is more than one connected face, the new face
|
||||
* direction is totally arbitrary and the only goal for the behavior is to be deterministic. */
|
||||
Span<int> connected_face_verts = {};
|
||||
Span<int> connected_face_edges = {};
|
||||
Span<int> connected_face_verts;
|
||||
Span<int> connected_face_edges;
|
||||
if (connected_faces.size() == 1) {
|
||||
const IndexRange connected_face = faces[connected_faces.first()];
|
||||
connected_face_verts = corner_verts.slice(connected_face);
|
||||
@@ -558,8 +634,8 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
connected_face_edges,
|
||||
new_corner_verts.slice(4 * i, 4),
|
||||
new_corner_edges.slice(4 * i, 4),
|
||||
new_vert_indices[extrude_index_1],
|
||||
new_vert_indices[extrude_index_2],
|
||||
orig_edge[0],
|
||||
orig_edge[1],
|
||||
new_vert_1,
|
||||
new_vert_2,
|
||||
orig_edge_index,
|
||||
@@ -568,139 +644,108 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
connect_edge_range[extrude_index_2]);
|
||||
});
|
||||
|
||||
/* Create a map of indices in the extruded vertices array to all of the indices of edges
|
||||
* in the duplicate edges array that connect to that vertex. This can be used to simplify the
|
||||
* mixing of attribute data for the connecting edges. */
|
||||
const Array<Vector<int>> new_vert_to_duplicate_edge_map = create_vert_to_edge_map(
|
||||
new_vert_range.size(), duplicate_edges, orig_vert_size);
|
||||
/* New vertices copy the attribute values from their source vertex. */
|
||||
gather_attributes(attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_verts, new_vert_range);
|
||||
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
/* Edges parallel to original edges copy the edge attributes from the original edges. */
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_EDGE], edge_selection, duplicate_edge_range);
|
||||
|
||||
attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) {
|
||||
return true;
|
||||
}
|
||||
/* Edges connected to original vertices mix values of selected connected edges. */
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
copy_with_mixing(attribute.span,
|
||||
vert_to_selected_edge_map,
|
||||
new_verts,
|
||||
attribute.span.slice(connect_edge_range));
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
switch (attribute.domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
/* New vertices copy the attribute values from their source vertex. */
|
||||
bke::attribute_math::gather(
|
||||
attribute.span, new_vert_indices, attribute.span.slice(new_vert_range));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_EDGE: {
|
||||
/* Edges parallel to original edges copy the edge attributes from the original edges. */
|
||||
GMutableSpan duplicate_data = attribute.span.slice(duplicate_edge_range);
|
||||
array_utils::gather(attribute.span, edge_selection, duplicate_data);
|
||||
/* Attribute values for new faces are a mix of values connected to its original edge. */
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_FACE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
copy_with_mixing(
|
||||
attribute.span, edge_to_face_map, edge_selection, attribute.span.slice(new_face_range));
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
/* Edges connected to original vertices mix values of selected connected edges. */
|
||||
copy_with_mixing(
|
||||
duplicate_data,
|
||||
[&](const int i) { return new_vert_to_duplicate_edge_map[i].as_span(); },
|
||||
attribute.span.slice(connect_edge_range));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
/* Attribute values for new faces are a mix of the values of faces connected to the its
|
||||
* original edge. */
|
||||
copy_with_mixing(
|
||||
attribute.span,
|
||||
[&](const int i) { return edge_to_face_map[edge_selection[i]]; },
|
||||
attribute.span.slice(new_face_range));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
/* New corners get the average value of all adjacent corners on original faces connected
|
||||
* to the original edge of their face. */
|
||||
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> new_data = data.slice(new_loop_range);
|
||||
edge_selection.foreach_index(
|
||||
GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) {
|
||||
const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
|
||||
if (connected_faces.is_empty()) {
|
||||
/* If there are no connected faces, there is no corner data to interpolate. */
|
||||
new_data.slice(4 * i_edge_selection, 4).fill(T());
|
||||
return;
|
||||
/* New corners get the average value of all adjacent corners on original faces connected
|
||||
* to the original edge of their face. */
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_CORNER]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> new_data = data.slice(new_loop_range);
|
||||
edge_selection.foreach_index(
|
||||
GrainSize(256), [&](const int64_t orig_edge_index, const int64_t i_edge_selection) {
|
||||
const Span<int> connected_faces = edge_to_face_map[orig_edge_index];
|
||||
if (connected_faces.is_empty()) {
|
||||
/* If there are no connected faces, there is no corner data to interpolate. */
|
||||
new_data.slice(4 * i_edge_selection, 4).fill(T());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Both corners on each vertical edge of the side face get the same value,
|
||||
* so there are only two unique values to mix. */
|
||||
Array<T> side_face_corner_data(2);
|
||||
bke::attribute_math::DefaultPropagationMixer<T> mixer{side_face_corner_data};
|
||||
|
||||
const int new_vert_1 = duplicate_edges[i_edge_selection][0];
|
||||
const int new_vert_2 = duplicate_edges[i_edge_selection][1];
|
||||
const int orig_vert_1 = edges[orig_edge_index][0];
|
||||
const int orig_vert_2 = edges[orig_edge_index][1];
|
||||
|
||||
/* Average the corner data from the corners that share a vertex from the
|
||||
* faces that share an edge with the extruded edge. */
|
||||
for (const int connected_face : connected_faces) {
|
||||
for (const int i_loop : faces[connected_face]) {
|
||||
if (corner_verts[i_loop] == orig_vert_1) {
|
||||
mixer.mix_in(0, data[i_loop]);
|
||||
}
|
||||
|
||||
/* Both corners on each vertical edge of the side face get the same value,
|
||||
* so there are only two unique values to mix. */
|
||||
Array<T> side_face_corner_data(2);
|
||||
bke::attribute_math::DefaultPropagationMixer<T> mixer{side_face_corner_data};
|
||||
|
||||
const int2 &duplicate_edge = duplicate_edges[i_edge_selection];
|
||||
const int new_vert_1 = duplicate_edge[0];
|
||||
const int new_vert_2 = duplicate_edge[1];
|
||||
const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size];
|
||||
const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size];
|
||||
|
||||
/* Average the corner data from the corners that share a vertex from the
|
||||
* faces that share an edge with the extruded edge. */
|
||||
for (const int i_connected_face : connected_faces.index_range()) {
|
||||
const IndexRange connected_face = faces[connected_faces[i_connected_face]];
|
||||
for (const int i_loop : IndexRange(connected_face)) {
|
||||
if (corner_verts[i_loop] == orig_vert_1) {
|
||||
mixer.mix_in(0, data[i_loop]);
|
||||
}
|
||||
if (corner_verts[i_loop] == orig_vert_2) {
|
||||
mixer.mix_in(1, data[i_loop]);
|
||||
}
|
||||
}
|
||||
if (corner_verts[i_loop] == orig_vert_2) {
|
||||
mixer.mix_in(1, data[i_loop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixer.finalize();
|
||||
mixer.finalize();
|
||||
|
||||
/* Instead of replicating the order in #fill_quad_consistent_direction here, it's
|
||||
* simpler (though probably slower) to just match the corner data based on the
|
||||
* vertex indices. */
|
||||
for (const int i : IndexRange(4 * i_edge_selection, 4)) {
|
||||
if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
|
||||
new_data[i] = side_face_corner_data.first();
|
||||
}
|
||||
else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
|
||||
new_data[i] = side_face_corner_data.last();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
/* Instead of replicating the order in #fill_quad_consistent_direction here, it's
|
||||
* simpler (though probably slower) to just match the corner data based on the
|
||||
* vertex indices. */
|
||||
for (const int i : IndexRange(4 * i_edge_selection, 4)) {
|
||||
if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
|
||||
new_data[i] = side_face_corner_data.first();
|
||||
}
|
||||
else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
|
||||
new_data[i] = side_face_corner_data.last();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
attribute.finish();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
MutableSpan<float3> new_positions = mesh.vert_positions_for_write().slice(new_vert_range);
|
||||
MutableSpan<float3> positions = mesh.vert_positions_for_write();
|
||||
MutableSpan<float3> new_positions = positions.slice(new_vert_range);
|
||||
if (edge_offsets.is_single()) {
|
||||
const float3 offset = edge_offsets.get_internal_single();
|
||||
threading::parallel_for(new_positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
new_positions[i] += offset;
|
||||
}
|
||||
new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
|
||||
new_positions[dst] = positions[src] + offset;
|
||||
});
|
||||
}
|
||||
else {
|
||||
threading::parallel_for(new_positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
new_positions[i] += vert_offsets[new_vert_indices[i]];
|
||||
}
|
||||
new_verts.foreach_index_optimized<int>(GrainSize(1024), [&](const int src, const int dst) {
|
||||
new_positions[dst] = positions[src] + vert_offsets[src];
|
||||
});
|
||||
}
|
||||
|
||||
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
|
||||
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));
|
||||
array_utils::gather(
|
||||
vert_orig_indices.as_span(), new_verts, vert_orig_indices.slice(new_vert_range));
|
||||
}
|
||||
|
||||
MutableSpan<int> edge_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_EDGE);
|
||||
@@ -716,16 +761,30 @@ static void extrude_mesh_edges(Mesh &mesh,
|
||||
|
||||
if (attribute_outputs.top_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_EDGE, duplicate_edge_range);
|
||||
attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_EDGE, duplicate_edge_range);
|
||||
}
|
||||
if (attribute_outputs.side_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, new_face_range);
|
||||
attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, new_face_range);
|
||||
}
|
||||
|
||||
tag_mesh_added_faces(mesh);
|
||||
}
|
||||
|
||||
static VectorSet<int> vert_indices_from_edges(const Mesh &mesh, const Span<int> edge_indices)
|
||||
{
|
||||
const Span<int2> edges = mesh.edges();
|
||||
|
||||
VectorSet<int> vert_indices;
|
||||
vert_indices.reserve(edge_indices.size());
|
||||
for (const int i_edge : edge_indices) {
|
||||
const int2 &edge = edges[i_edge];
|
||||
vert_indices.add(edge[0]);
|
||||
vert_indices.add(edge[1]);
|
||||
}
|
||||
return vert_indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges connected to one selected face are on the boundary of a region and will be duplicated into
|
||||
* a "side face". Edges inside a region will be duplicated to leave any original faces unchanged.
|
||||
@@ -778,15 +837,10 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
const GroupedSpan<int> edge_to_face_map = bke::mesh::build_edge_to_face_map(
|
||||
orig_faces, mesh.corner_edges(), mesh.totedge, edge_to_face_offsets, edge_to_face_indices);
|
||||
|
||||
/* All vertices that are connected to the selected faces.
|
||||
* Start the size at one vert per face to reduce unnecessary reallocation. */
|
||||
VectorSet<int> all_selected_verts;
|
||||
all_selected_verts.reserve(orig_faces.size());
|
||||
face_selection.foreach_index([&](const int i_face) {
|
||||
for (const int vert : orig_corner_verts.slice(orig_faces[i_face])) {
|
||||
all_selected_verts.add(vert);
|
||||
}
|
||||
});
|
||||
/* All vertices that are connected to the selected faces. */
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask all_selected_verts = geometry::vert_selection_from_face(
|
||||
orig_faces, face_selection, orig_corner_verts, orig_vert_size, memory);
|
||||
|
||||
/* Edges inside of an extruded region that are also attached to deselected edges. They must be
|
||||
* duplicated in order to leave the old edge attached to the unchanged deselected faces. */
|
||||
@@ -835,7 +889,7 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
}
|
||||
}
|
||||
|
||||
VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices.as_span());
|
||||
VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, boundary_edge_indices);
|
||||
/* Before adding the rest of the new vertices from the new inner edges, store the number
|
||||
* of new vertices from the boundary edges, since this is the number of connecting edges. */
|
||||
const int extruded_vert_size = new_vert_indices.size();
|
||||
@@ -860,7 +914,12 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
/* The loops that form the new side faces. */
|
||||
const IndexRange side_loop_range{orig_corner_verts.size(), side_face_range.size() * 4};
|
||||
|
||||
remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info);
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
remove_non_propagated_attributes(attributes, propagation_info);
|
||||
|
||||
const IDsByDomain ids_by_domain = attribute_ids_by_domain(
|
||||
attributes, {".corner_vert", ".corner_edge", ".edge_verts"});
|
||||
|
||||
remove_unsupported_vert_data(mesh);
|
||||
remove_unsupported_edge_data(mesh);
|
||||
remove_unsupported_face_data(mesh);
|
||||
@@ -966,107 +1025,87 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
connect_edge_range[extrude_index_2]);
|
||||
}
|
||||
|
||||
/* Create a map of indices in the extruded vertices array to all of the indices of edges
|
||||
* in the duplicate edges array that connect to that vertex. This can be used to simplify the
|
||||
* mixing of attribute data for the connecting edges. */
|
||||
const Array<Vector<int>> new_vert_to_duplicate_edge_map = create_vert_to_edge_map(
|
||||
new_vert_range.size(), boundary_edges, orig_vert_size);
|
||||
/* New vertices copy the attributes from their original vertices. */
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_vert_indices, new_vert_range);
|
||||
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
/* New faces on the side of extrusions get the values from the corresponding selected face. */
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_FACE], edge_extruded_face_indices, side_face_range);
|
||||
|
||||
attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) {
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask boundary_edge_mask = IndexMask::from_indices<int>(boundary_edge_indices,
|
||||
memory);
|
||||
|
||||
Array<int> vert_to_edge_offsets;
|
||||
Array<int> vert_to_edge_indices;
|
||||
const GroupedSpan<int> vert_to_boundary_edge_map = build_vert_to_edge_map(
|
||||
edges, boundary_edge_mask, mesh.totvert, vert_to_edge_offsets, vert_to_edge_indices);
|
||||
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
|
||||
/* Edges parallel to original edges copy the edge attributes from the original edges. */
|
||||
GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range);
|
||||
array_utils::gather(attribute.span, boundary_edge_mask, boundary_data);
|
||||
|
||||
/* Edges inside of face regions also just duplicate their source data. */
|
||||
GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range);
|
||||
bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data);
|
||||
|
||||
/* Edges connected to original vertices mix values of selected connected edges. */
|
||||
copy_with_mixing(attribute.span,
|
||||
vert_to_boundary_edge_map,
|
||||
new_vert_indices,
|
||||
attribute.span.slice(connect_edge_range));
|
||||
attribute.finish();
|
||||
}
|
||||
if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) {
|
||||
return true;
|
||||
}
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
}
|
||||
|
||||
switch (attribute.domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
/* New vertices copy the attributes from their original vertices. */
|
||||
bke::attribute_math::gather(
|
||||
attribute.span, new_vert_indices, attribute.span.slice(new_vert_range));
|
||||
break;
|
||||
/* New corners get the values from the corresponding corner on the extruded face. */
|
||||
if (!ids_by_domain[ATTR_DOMAIN_CORNER].is_empty()) {
|
||||
Array<int> orig_corners(side_loop_range.size());
|
||||
threading::parallel_for(boundary_edge_indices.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int i_boundary_edge : range) {
|
||||
const int2 &boundary_edge = boundary_edges[i_boundary_edge];
|
||||
const int new_vert_1 = boundary_edge[0];
|
||||
const int new_vert_2 = boundary_edge[1];
|
||||
const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size];
|
||||
const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size];
|
||||
|
||||
/* Retrieve the data for the first two sides of the quad from the extruded
|
||||
* face, which we generally expect to have just a small amount of sides. This
|
||||
* loop could be eliminated by adding a cache of connected loops (which would
|
||||
* also simplify some of the other code to find the correct loops on the extruded
|
||||
* face). */
|
||||
int corner_1;
|
||||
int corner_2;
|
||||
for (const int corner : faces[edge_extruded_face_indices[i_boundary_edge]]) {
|
||||
if (corner_verts[corner] == new_vert_1) {
|
||||
corner_1 = corner;
|
||||
}
|
||||
if (corner_verts[corner] == new_vert_2) {
|
||||
corner_2 = corner;
|
||||
}
|
||||
}
|
||||
|
||||
/* Instead of replicating the order in #fill_quad_consistent_direction here, it's
|
||||
* simpler (though probably slower) to just match the corner data based on the
|
||||
* vertex indices. */
|
||||
for (const int i : IndexRange(4 * i_boundary_edge, 4)) {
|
||||
if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
|
||||
orig_corners[i] = corner_1;
|
||||
}
|
||||
else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
|
||||
orig_corners[i] = corner_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
case ATTR_DOMAIN_EDGE: {
|
||||
/* Edges parallel to original edges copy the edge attributes from the original edges. */
|
||||
GMutableSpan boundary_data = attribute.span.slice(boundary_edge_range);
|
||||
bke::attribute_math::gather(attribute.span, boundary_edge_indices, boundary_data);
|
||||
|
||||
/* Edges inside of face regions also just duplicate their source data. */
|
||||
GMutableSpan new_inner_data = attribute.span.slice(new_inner_edge_range);
|
||||
bke::attribute_math::gather(attribute.span, new_inner_edge_indices, new_inner_data);
|
||||
|
||||
/* Edges connected to original vertices mix values of selected connected edges. */
|
||||
copy_with_mixing(
|
||||
boundary_data,
|
||||
[&](const int i) { return new_vert_to_duplicate_edge_map[i].as_span(); },
|
||||
attribute.span.slice(connect_edge_range));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
/* New faces on the side of extrusions get the values from the corresponding selected
|
||||
* face. */
|
||||
GMutableSpan side_data = attribute.span.slice(side_face_range);
|
||||
bke::attribute_math::gather(attribute.span, edge_extruded_face_indices, side_data);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
/* New corners get the values from the corresponding corner on the extruded face. */
|
||||
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> new_data = data.slice(side_loop_range);
|
||||
threading::parallel_for(
|
||||
boundary_edge_indices.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int i_boundary_edge : range) {
|
||||
const int2 &boundary_edge = boundary_edges[i_boundary_edge];
|
||||
const int new_vert_1 = boundary_edge[0];
|
||||
const int new_vert_2 = boundary_edge[1];
|
||||
const int orig_vert_1 = new_vert_indices[new_vert_1 - orig_vert_size];
|
||||
const int orig_vert_2 = new_vert_indices[new_vert_2 - orig_vert_size];
|
||||
|
||||
/* Retrieve the data for the first two sides of the quad from the extruded
|
||||
* face, which we generally expect to have just a small amount of sides. This
|
||||
* loop could be eliminated by adding a cache of connected loops (which would
|
||||
* also simplify some of the other code to find the correct loops on the extruded
|
||||
* face). */
|
||||
T data_1;
|
||||
T data_2;
|
||||
for (const int i_loop : faces[edge_extruded_face_indices[i_boundary_edge]]) {
|
||||
if (corner_verts[i_loop] == new_vert_1) {
|
||||
data_1 = data[i_loop];
|
||||
}
|
||||
if (corner_verts[i_loop] == new_vert_2) {
|
||||
data_2 = data[i_loop];
|
||||
}
|
||||
}
|
||||
|
||||
/* Instead of replicating the order in #fill_quad_consistent_direction here, it's
|
||||
* simpler (though probably slower) to just match the corner data based on the
|
||||
* vertex indices. */
|
||||
for (const int i : IndexRange(4 * i_boundary_edge, 4)) {
|
||||
if (ELEM(new_corner_verts[i], new_vert_1, orig_vert_1)) {
|
||||
new_data[i] = data_1;
|
||||
}
|
||||
else if (ELEM(new_corner_verts[i], new_vert_2, orig_vert_2)) {
|
||||
new_data[i] = data_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
attribute.finish();
|
||||
return true;
|
||||
});
|
||||
});
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_CORNER], orig_corners, side_loop_range);
|
||||
}
|
||||
|
||||
/* Translate vertices based on the offset. If the vertex is used by a selected edge, it will
|
||||
* have been duplicated and only the new vertex should use the offset. Otherwise the vertex might
|
||||
@@ -1074,33 +1113,27 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
MutableSpan<float3> positions = mesh.vert_positions_for_write();
|
||||
if (face_position_offsets.is_single()) {
|
||||
const float3 offset = face_position_offsets.get_internal_single();
|
||||
threading::parallel_for(
|
||||
IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) {
|
||||
for (const int i_orig : all_selected_verts.as_span().slice(range)) {
|
||||
const int i_new = new_vert_indices.index_of_try(i_orig);
|
||||
if (i_new == -1) {
|
||||
positions[i_orig] += offset;
|
||||
}
|
||||
else {
|
||||
positions[new_vert_range[i_new]] += offset;
|
||||
}
|
||||
}
|
||||
});
|
||||
all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
|
||||
const int i_new = new_vert_indices.index_of_try(orig_vert);
|
||||
if (i_new == -1) {
|
||||
positions[orig_vert] += offset;
|
||||
}
|
||||
else {
|
||||
positions[new_vert_range[i_new]] += offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
threading::parallel_for(
|
||||
IndexRange(all_selected_verts.size()), 1024, [&](const IndexRange range) {
|
||||
for (const int i_orig : all_selected_verts.as_span().slice(range)) {
|
||||
const int i_new = new_vert_indices.index_of_try(i_orig);
|
||||
const float3 offset = vert_offsets[i_orig];
|
||||
if (i_new == -1) {
|
||||
positions[i_orig] += offset;
|
||||
}
|
||||
else {
|
||||
positions[new_vert_range[i_new]] += offset;
|
||||
}
|
||||
}
|
||||
});
|
||||
all_selected_verts.foreach_index(GrainSize(1024), [&](const int orig_vert) {
|
||||
const int i_new = new_vert_indices.index_of_try(orig_vert);
|
||||
const float3 offset = vert_offsets[orig_vert];
|
||||
if (i_new == -1) {
|
||||
positions[orig_vert] += offset;
|
||||
}
|
||||
else {
|
||||
positions[new_vert_range[i_new]] += offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MutableSpan<int> vert_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_POINT);
|
||||
@@ -1130,11 +1163,11 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
||||
|
||||
if (attribute_outputs.top_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection);
|
||||
attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection);
|
||||
}
|
||||
if (attribute_outputs.side_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range);
|
||||
attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range);
|
||||
}
|
||||
|
||||
tag_mesh_added_faces(mesh);
|
||||
@@ -1169,15 +1202,10 @@ static void extrude_individual_mesh_faces(
|
||||
/* Build an array of offsets into the new data for each face. This is used to facilitate
|
||||
* parallelism later on by avoiding the need to keep track of an offset when iterating through
|
||||
* all faces. */
|
||||
int extrude_corner_size = 0;
|
||||
Array<int> group_per_face_data(face_selection.size() + 1);
|
||||
face_selection.foreach_index_optimized<int>([&](const int index, const int i_selection) {
|
||||
group_per_face_data[i_selection] = extrude_corner_size;
|
||||
extrude_corner_size += orig_faces[index].size();
|
||||
});
|
||||
|
||||
group_per_face_data.last() = extrude_corner_size;
|
||||
const OffsetIndices<int> group_per_face(group_per_face_data);
|
||||
const OffsetIndices<int> group_per_face = offset_indices::gather_selected_offsets(
|
||||
orig_faces, face_selection, group_per_face_data);
|
||||
const int extrude_corner_size = group_per_face.total_size();
|
||||
|
||||
const IndexRange new_vert_range{orig_vert_size, extrude_corner_size};
|
||||
/* One edge connects each selected vertex to a new vertex on the extruded faces. */
|
||||
@@ -1188,7 +1216,12 @@ static void extrude_individual_mesh_faces(
|
||||
const IndexRange side_face_range{orig_faces.size(), duplicate_edge_range.size()};
|
||||
const IndexRange side_loop_range{orig_loop_size, side_face_range.size() * 4};
|
||||
|
||||
remove_non_propagated_attributes(mesh.attributes_for_write(), propagation_info);
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
remove_non_propagated_attributes(attributes, propagation_info);
|
||||
|
||||
const IDsByDomain ids_by_domain = attribute_ids_by_domain(
|
||||
attributes, {"position", ".edge_verts", ".corner_vert", ".corner_edge"});
|
||||
|
||||
remove_unsupported_vert_data(mesh);
|
||||
remove_unsupported_edge_data(mesh);
|
||||
remove_unsupported_face_data(mesh);
|
||||
@@ -1199,7 +1232,8 @@ static void extrude_individual_mesh_faces(
|
||||
side_face_range.size(),
|
||||
side_loop_range.size());
|
||||
|
||||
MutableSpan<float3> new_positions = mesh.vert_positions_for_write().slice(new_vert_range);
|
||||
MutableSpan<float3> positions = mesh.vert_positions_for_write();
|
||||
MutableSpan<float3> new_positions = positions.slice(new_vert_range);
|
||||
MutableSpan<int2> edges = mesh.edges_for_write();
|
||||
MutableSpan<int2> connect_edges = edges.slice(connect_edge_range);
|
||||
MutableSpan<int2> duplicate_edges = edges.slice(duplicate_edge_range);
|
||||
@@ -1267,119 +1301,94 @@ static void extrude_individual_mesh_faces(
|
||||
}
|
||||
});
|
||||
|
||||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
/* New vertices copy the attributes from their original vertices. */
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_POINT], new_vert_indices, new_vert_range);
|
||||
|
||||
attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(id.name(), ".corner_vert", ".corner_edge", ".edge_verts")) {
|
||||
return true;
|
||||
/* The data for the duplicate edge is simply a copy of the original edge's data. */
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_EDGE], duplicate_edge_indices, duplicate_edge_range);
|
||||
|
||||
/* For extruded edges, mix the data from the two neighboring original edges of the face. */
|
||||
if (!ids_by_domain[ATTR_DOMAIN_EDGE].is_empty()) {
|
||||
Array<int2> neighbor_edges(connect_edge_range.size());
|
||||
face_selection.foreach_index(
|
||||
GrainSize(1024), [&](const int64_t index, const int64_t i_selection) {
|
||||
const IndexRange face = faces[index];
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
|
||||
for (const int i : face.index_range()) {
|
||||
const int i_prev = (i == 0) ? face.size() - 1 : i - 1;
|
||||
const int i_extrude = extrude_range[i];
|
||||
const int i_extrude_prev = extrude_range[i_prev];
|
||||
neighbor_edges[i_extrude] = int2(duplicate_edge_indices[i_extrude],
|
||||
duplicate_edge_indices[i_extrude_prev]);
|
||||
}
|
||||
});
|
||||
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_EDGE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
bke::attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> dst = data.slice(connect_edge_range);
|
||||
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const int2 neighbors = neighbor_edges[i];
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
/* Propagate selections with "or" instead of "at least half". */
|
||||
dst[i] = data[neighbors[0]] || data[neighbors[1]];
|
||||
}
|
||||
else {
|
||||
dst[i] = bke::attribute_math::mix2(0.5f, data[neighbors[0]], data[neighbors[1]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
attribute.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/* Each side face gets the values from the corresponding new face. */
|
||||
for (const AttributeIDRef &id : ids_by_domain[ATTR_DOMAIN_FACE]) {
|
||||
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
|
||||
switch (attribute.domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
/* New vertices copy the attributes from their original vertices. */
|
||||
GMutableSpan new_data = attribute.span.slice(new_vert_range);
|
||||
bke::attribute_math::gather(attribute.span, new_vert_indices, new_data);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_EDGE: {
|
||||
/* The data for the duplicate edge is simply a copy of the original edge's data. */
|
||||
GMutableSpan duplicate_data = attribute.span.slice(duplicate_edge_range);
|
||||
bke::attribute_math::gather(attribute.span, duplicate_edge_indices, duplicate_data);
|
||||
|
||||
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> connect_data = data.slice(connect_edge_range);
|
||||
face_selection.foreach_index(
|
||||
GrainSize(512), [&](const int64_t index, const int64_t i_selection) {
|
||||
const IndexRange face = faces[index];
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
|
||||
/* For the extruded edges, mix the data from the two neighboring original edges of
|
||||
* the extruded face. */
|
||||
for (const int i : face.index_range()) {
|
||||
const int i_prev = (i == 0) ? face.size() - 1 : i - 1;
|
||||
const int i_extrude = extrude_range[i];
|
||||
const int i_extrude_prev = extrude_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[i_extrude] = data[orig_edge] || data[orig_edge_prev];
|
||||
}
|
||||
else {
|
||||
connect_data[i_extrude] = bke::attribute_math::mix2(
|
||||
0.5f, data[orig_edge], data[orig_edge_prev]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
/* Each side face gets the values from the corresponding new face. */
|
||||
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> new_data = data.slice(side_face_range);
|
||||
face_selection.foreach_index(
|
||||
GrainSize(1024), [&](const int64_t face_index, const int64_t i_selection) {
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
new_data.slice(extrude_range).fill(data[face_index]);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
/* Each corner on a side face gets its value from the matching corner on an extruded
|
||||
* face. */
|
||||
bke::attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
MutableSpan<T> data = attribute.span.typed<T>();
|
||||
MutableSpan<T> new_data = data.slice(side_loop_range);
|
||||
face_selection.foreach_index(
|
||||
GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
|
||||
const IndexRange face = faces[index];
|
||||
const Span<T> face_loop_data = data.slice(face);
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
|
||||
for (const int i : face.index_range()) {
|
||||
const int i_next = (i == face.size() - 1) ? 0 : i + 1;
|
||||
const int i_extrude = extrude_range[i];
|
||||
|
||||
MutableSpan<T> side_loop_data = new_data.slice(i_extrude * 4, 4);
|
||||
|
||||
/* The two corners on each side of the side face get the data from the
|
||||
* matching corners of the extruded face. This order depends on the loop
|
||||
* filling the loop indices. */
|
||||
side_loop_data[0] = face_loop_data[i_next];
|
||||
side_loop_data[1] = face_loop_data[i];
|
||||
side_loop_data[2] = face_loop_data[i];
|
||||
side_loop_data[3] = face_loop_data[i_next];
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
bke::attribute_math::gather_to_groups(
|
||||
group_per_face, face_selection, attribute.span, attribute.span.slice(side_face_range));
|
||||
attribute.finish();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/* Each corner on a side face gets its value from the matching corner on an extruded face. */
|
||||
if (!ids_by_domain[ATTR_DOMAIN_CORNER].is_empty()) {
|
||||
Array<int> orig_corners(side_loop_range.size());
|
||||
face_selection.foreach_index(
|
||||
GrainSize(256), [&](const int64_t index, const int64_t i_selection) {
|
||||
const IndexRange face = faces[index];
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
|
||||
for (const int i : face.index_range()) {
|
||||
const IndexRange side_face(extrude_range[i] * 4, 4);
|
||||
/* The two corners on each side of the side face get the data from the
|
||||
* matching corners of the extruded face. This order depends on the loop
|
||||
* filling the loop indices. */
|
||||
const int corner = face[i];
|
||||
const int next_corner = bke::mesh::face_corner_next(face, corner);
|
||||
orig_corners[side_face[0]] = next_corner;
|
||||
orig_corners[side_face[1]] = corner;
|
||||
orig_corners[side_face[2]] = corner;
|
||||
orig_corners[side_face[3]] = next_corner;
|
||||
}
|
||||
});
|
||||
gather_attributes(
|
||||
attributes, ids_by_domain[ATTR_DOMAIN_CORNER], orig_corners, side_loop_range);
|
||||
}
|
||||
|
||||
/* Offset the new vertices. */
|
||||
face_selection.foreach_index(GrainSize(1025),
|
||||
[&](const int64_t index, const int64_t i_selection) {
|
||||
const IndexRange extrude_range = group_per_face[i_selection];
|
||||
for (float3 &position : new_positions.slice(extrude_range)) {
|
||||
position += face_offset[index];
|
||||
for (const int i : extrude_range) {
|
||||
const int src_vert = new_vert_indices[i];
|
||||
new_positions[i] = positions[src_vert] + face_offset[index];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1400,21 +1409,19 @@ static void extrude_individual_mesh_faces(
|
||||
|
||||
MutableSpan<int> face_orig_indices = get_orig_index_layer(mesh, ATTR_DOMAIN_FACE);
|
||||
if (!face_orig_indices.is_empty()) {
|
||||
MutableSpan<int> new_face_orig_indices = face_orig_indices.slice(side_face_range);
|
||||
face_selection.foreach_index(
|
||||
GrainSize(1024), [&](const int64_t face_i, const int64_t selection_i) {
|
||||
const IndexRange extrude_range = group_per_face[selection_i];
|
||||
new_face_orig_indices.slice(extrude_range).fill(face_orig_indices[face_i]);
|
||||
});
|
||||
array_utils::gather_to_groups(group_per_face,
|
||||
face_selection,
|
||||
face_orig_indices.as_span(),
|
||||
face_orig_indices.slice(side_face_range));
|
||||
}
|
||||
|
||||
if (attribute_outputs.top_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection);
|
||||
attributes, attribute_outputs.top_id.get(), ATTR_DOMAIN_FACE, face_selection);
|
||||
}
|
||||
if (attribute_outputs.side_id) {
|
||||
save_selection_as_attribute(
|
||||
mesh, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range);
|
||||
attributes, attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE, side_face_range);
|
||||
}
|
||||
|
||||
tag_mesh_added_faces(mesh);
|
||||
|
||||
Reference in New Issue
Block a user