Dual Mesh: Avoid transferring position attribute twice

The node transferred position once as a generic attribute, and then set
the values again manually. This wastes processing during the attribute
transfer step. On a 1 million face grid, I observed roughly an 8%
improvement, from 231.5 to 217.1 ms average and 225.4 to 209.6 ms min.
This commit is contained in:
Hans Goudey 2022-11-27 23:23:19 -06:00
parent d96859c5b1
commit 5758d114c1
1 changed files with 32 additions and 38 deletions

View File

@ -127,7 +127,6 @@ static void copy_data_based_on_pairs(Span<T> data,
* closest face.
*/
static void transfer_attributes(
const Map<AttributeIDRef, AttributeKind> &attributes,
const Span<VertexType> vertex_types,
const bool keep_boundaries,
const Span<int> new_to_old_edges_map,
@ -136,12 +135,14 @@ static void transfer_attributes(
const AttributeAccessor src_attributes,
MutableAttributeAccessor dst_attributes)
{
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
const AttributeIDRef attribute_id = entry.key;
GAttributeReader src_attribute = src_attributes.lookup(attribute_id);
if (!src_attribute) {
continue;
}
/* Retrieve all attributes except for position which is handled manually.
* Remove anonymous attributes that don't need to be propagated.*/
Set<AttributeIDRef> attribute_ids = src_attributes.all_ids();
attribute_ids.remove("position");
attribute_ids.remove_if([](const AttributeIDRef &id) { return !id.should_be_kept(); });
for (const AttributeIDRef &id : attribute_ids) {
GAttributeReader src_attribute = src_attributes.lookup(id);
eAttrDomain out_domain;
if (src_attribute.domain == ATTR_DOMAIN_FACE) {
@ -157,7 +158,7 @@ static void transfer_attributes(
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(
src_attribute.varray.type());
GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
attribute_id, out_domain, data_type);
id, out_domain, data_type);
if (!dst_attribute) {
continue;
}
@ -605,30 +606,23 @@ static void dissolve_redundant_verts(const Span<MEdge> edges,
*
* Some special cases are needed for boundaries and non-manifold geometry.
*/
static void calc_dual_mesh(GeometrySet &geometry_set,
const MeshComponent &in_component,
const bool keep_boundaries)
static Mesh *calc_dual_mesh(const Mesh &src_mesh, const bool keep_boundaries)
{
const Mesh &mesh_in = *in_component.get_for_read();
const Span<MVert> src_verts = mesh_in.verts();
const Span<MEdge> src_edges = mesh_in.edges();
const Span<MPoly> src_polys = mesh_in.polys();
const Span<MLoop> src_loops = mesh_in.loops();
const Span<MVert> src_verts = src_mesh.verts();
const Span<MEdge> src_edges = src_mesh.edges();
const Span<MPoly> src_polys = src_mesh.polys();
const Span<MLoop> src_loops = src_mesh.loops();
Map<AttributeIDRef, AttributeKind> attributes;
geometry_set.gather_attributes_for_propagation(
{GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes);
Array<VertexType> vertex_types(mesh_in.totvert);
Array<EdgeType> edge_types(mesh_in.totedge);
calc_boundaries(mesh_in, vertex_types, edge_types);
Array<VertexType> vertex_types(src_mesh.totvert);
Array<EdgeType> edge_types(src_mesh.totedge);
calc_boundaries(src_mesh, vertex_types, edge_types);
/* Stores the indices of the polygons connected to the vertex. Because the polygons are looped
* over in order of their indices, the polygon's indices will be sorted in ascending order.
* (This can change once they are sorted using `sort_vertex_polys`). */
Array<Vector<int>> vert_to_poly_map = bke::mesh_topology::build_vert_to_poly_map(
src_polys, src_loops, src_verts.size());
Array<Array<int>> vertex_shared_edges(mesh_in.totvert);
Array<Array<int>> vertex_corners(mesh_in.totvert);
Array<Array<int>> vertex_shared_edges(src_mesh.totvert);
Array<Array<int>> vertex_corners(src_mesh.totvert);
threading::parallel_for(vert_to_poly_map.index_range(), 512, [&](IndexRange range) {
for (const int i : range) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
@ -675,8 +669,8 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
}
});
Vector<float3> vertex_positions(mesh_in.totpoly);
for (const int i : IndexRange(mesh_in.totpoly)) {
Vector<float3> vertex_positions(src_mesh.totpoly);
for (const int i : IndexRange(src_mesh.totpoly)) {
const MPoly &poly = src_polys[i];
BKE_mesh_calc_poly_center(
&poly, &src_loops[poly.loopstart], src_verts.data(), vertex_positions[i]);
@ -685,9 +679,9 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
Array<int> boundary_edge_midpoint_index;
if (keep_boundaries) {
/* Only initialize when we actually need it. */
boundary_edge_midpoint_index.reinitialize(mesh_in.totedge);
boundary_edge_midpoint_index.reinitialize(src_mesh.totedge);
/* We need to add vertices at the centers of boundary edges. */
for (const int i : IndexRange(mesh_in.totedge)) {
for (const int i : IndexRange(src_mesh.totedge)) {
if (edge_types[i] == EdgeType::Boundary) {
float3 mid;
const MEdge &edge = src_edges[i];
@ -712,7 +706,7 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
* needs to be created or not. If it's not -1 it gives the index in `new_edges` of the dual
* edge. The edges coming from preserving the boundaries only get added once anyway, so we
* don't need a hash-map for that. */
Array<int> old_to_new_edges_map(mesh_in.totedge);
Array<int> old_to_new_edges_map(src_mesh.totedge);
old_to_new_edges_map.fill(-1);
/* This is necessary to prevent duplicate edges from being created, but will likely not do
@ -726,7 +720,7 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
new_edges,
new_to_old_edges_map);
for (const int i : IndexRange(mesh_in.totvert)) {
for (const int i : IndexRange(src_mesh.totvert)) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
(!keep_boundaries && vertex_types[i] == VertexType::Boundary)) {
/* Bad vertex that we can't work with. */
@ -887,13 +881,13 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
}
Mesh *mesh_out = BKE_mesh_new_nomain(
vertex_positions.size(), new_edges.size(), 0, loops.size(), loop_lengths.size());
transfer_attributes(attributes,
vertex_types,
transfer_attributes(vertex_types,
keep_boundaries,
new_to_old_edges_map,
new_to_old_face_corners_map,
boundary_vertex_to_relevant_face_map,
mesh_in.attributes(),
src_mesh.attributes(),
mesh_out->attributes_for_write());
MutableSpan<MVert> dst_verts = mesh_out->verts_for_write();
@ -915,7 +909,7 @@ static void calc_dual_mesh(GeometrySet &geometry_set,
copy_v3_v3(dst_verts[i].co, vertex_positions[i]);
}
dst_edges.copy_from(new_edges);
geometry_set.replace_mesh(mesh_out);
return mesh_out;
}
static void node_geo_exec(GeoNodeExecParams params)
@ -923,9 +917,9 @@ static void node_geo_exec(GeoNodeExecParams params)
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
const bool keep_boundaries = params.extract_input<bool>("Keep Boundaries");
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (geometry_set.has_mesh()) {
const MeshComponent &component = *geometry_set.get_component_for_read<MeshComponent>();
calc_dual_mesh(geometry_set, component, keep_boundaries);
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
Mesh *new_mesh = calc_dual_mesh(*mesh, keep_boundaries);
geometry_set.replace_mesh(new_mesh);
}
});
params.set_output("Dual Mesh", std::move(geometry_set));