Geometry Nodes: Use implicit sharing in point/mesh conversion nodes #106917
|
@ -80,6 +80,13 @@ Bounds<float3> calculate_bounds_radial_primitive(float radius_top,
|
|||
int segments,
|
||||
float height);
|
||||
|
||||
/**
|
||||
* Add a layer to \a dst, sharing the data from \a src with its implicit sharing info if available.
|
||||
* Assumes that the layer exists in the source and doesn't exist in the destination.
|
||||
*/
|
||||
void share_custom_data_layer(
|
||||
const CustomData &src, CustomData &dst, AttributeIDRef id, eCustomDataType type, int size);
|
||||
|
||||
/**
|
||||
* Returns the parts of the geometry that are on the selection for the given domain. If the domain
|
||||
* is not applicable for the component, e.g. face domain for point cloud, nothing happens to that
|
||||
|
|
|
@ -45,6 +45,23 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
|
|||
node->storage = data;
|
||||
}
|
||||
|
||||
static const CustomData &mesh_custom_data_for_domain(const Mesh &mesh, const eAttrDomain domain)
|
||||
|
||||
{
|
||||
switch (domain) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
return mesh.vdata;
|
||||
case ATTR_DOMAIN_EDGE:
|
||||
return mesh.edata;
|
||||
case ATTR_DOMAIN_FACE:
|
||||
return mesh.pdata;
|
||||
case ATTR_DOMAIN_CORNER:
|
||||
return mesh.ldata;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return mesh.vdata;
|
||||
}
|
||||
}
|
||||
|
||||
static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
|
||||
Field<float3> &position_field,
|
||||
Field<float> &radius_field,
|
||||
|
@ -72,20 +89,13 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
|
|||
evaluator.add(radius_field);
|
||||
evaluator.evaluate();
|
||||
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
|
||||
const VArray<float3> positions_eval = evaluator.get_evaluated<float3>(0);
|
||||
const VArray<float> radii_eval = evaluator.get_evaluated<float>(1);
|
||||
|
||||
PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size());
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
MutableAttributeAccessor dst_attributes = pointcloud->attributes_for_write();
|
||||
|
||||
GSpanAttributeWriter position = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
"position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3);
|
||||
array_utils::gather(evaluator.get_evaluated(0), selection, position.span);
|
||||
position.finish();
|
||||
|
||||
GSpanAttributeWriter radius = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
"radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT);
|
||||
array_utils::gather(evaluator.get_evaluated(1), selection, radius.span);
|
||||
radius.finish();
|
||||
const bool share_arrays = selection.size() == domain_size;
|
||||
const bool share_position = share_arrays && domain == ATTR_DOMAIN_POINT &&
|
||||
positions_eval.is_span() &&
|
||||
positions_eval.get_internal_span() == mesh->vert_positions();
|
||||
Jacques Lucke
commented
This does a deep comparison of the positions leading to some significant new overhead. This does a deep comparison of the positions leading to some significant new overhead.
|
||||
|
||||
Map<AttributeIDRef, AttributeKind> attributes;
|
||||
geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH},
|
||||
|
@ -95,20 +105,47 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
|
|||
attributes);
|
||||
attributes.remove("position");
|
||||
|
||||
const CustomData &mesh_data = mesh_custom_data_for_domain(*mesh, domain);
|
||||
const AttributeAccessor src_attributes = mesh->attributes();
|
||||
|
||||
PointCloud *pointcloud;
|
||||
if (share_position) {
|
||||
/* Create an empty point cloud so the positions can be easily shared with a generic utility. */
|
||||
pointcloud = BKE_pointcloud_new_nomain(0);
|
||||
pointcloud->totpoint = mesh->totvert;
|
||||
CustomData_free_layer_named(&pointcloud->pdata, "position", pointcloud->totpoint);
|
||||
Jacques Lucke
commented
move one line up move one line up
|
||||
share_custom_data_layer(
|
||||
mesh_data, pointcloud->pdata, "position", CD_PROP_FLOAT3, mesh->totvert);
|
||||
}
|
||||
else {
|
||||
pointcloud = BKE_pointcloud_new_nomain(selection.size());
|
||||
array_utils::gather(positions_eval, selection, pointcloud->positions_for_write());
|
||||
}
|
||||
|
||||
MutableAttributeAccessor dst_attributes = pointcloud->attributes_for_write();
|
||||
GSpanAttributeWriter radius = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
"radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT);
|
||||
array_utils::gather(radii_eval, selection, radius.span);
|
||||
radius.finish();
|
||||
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
const eCustomDataType data_type = entry.value.data_type;
|
||||
GVArray src = src_attributes.lookup_or_default(attribute_id, domain, data_type);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
if (dst && src) {
|
||||
array_utils::gather(src, selection, dst.span);
|
||||
dst.finish();
|
||||
if (share_arrays && entry.value.domain == domain) {
|
||||
share_custom_data_layer(mesh_data, pointcloud->pdata, entry.key, data_type, mesh->totvert);
|
||||
}
|
||||
else {
|
||||
GVArray src = src_attributes.lookup_or_default(attribute_id, domain, data_type);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
if (dst && src) {
|
||||
array_utils::gather(src, selection, dst.span);
|
||||
dst.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,29 @@
|
|||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
void share_custom_data_layer(const CustomData &src,
|
||||
CustomData &dst,
|
||||
const AttributeIDRef id,
|
||||
const eCustomDataType type,
|
||||
const int size)
|
||||
{
|
||||
const std::string name = id.name();
|
||||
const int index = CustomData_get_named_layer_index(&src, type, name.c_str());
|
||||
const CustomDataLayer &src_layer = src.layers[index];
|
||||
if (id.is_anonymous()) {
|
||||
CustomData_add_layer_anonymous_with_data(
|
||||
&dst, type, &id.anonymous_id(), size, src_layer.data, src_layer.sharing_info);
|
||||
}
|
||||
else {
|
||||
CustomData_add_layer_named_with_data(
|
||||
&dst, type, src_layer.data, size, name.c_str(), src_layer.sharing_info);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
namespace blender::nodes::node_geo_points_to_vertices_cc {
|
||||
|
||||
using blender::Array;
|
||||
|
@ -20,7 +43,6 @@ static void node_declare(NodeDeclarationBuilder &b)
|
|||
b.add_output<decl::Geometry>(N_("Mesh")).propagate_all();
|
||||
}
|
||||
|
||||
/* One improvement would be to move the attribute arrays directly to the mesh when possible. */
|
||||
static void geometry_set_points_to_vertices(
|
||||
GeometrySet &geometry_set,
|
||||
Field<bool> &selection_field,
|
||||
|
@ -49,25 +71,37 @@ static void geometry_set_points_to_vertices(
|
|||
propagation_info,
|
||||
attributes);
|
||||
|
||||
Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
if (selection.size() == points->totpoint) {
|
||||
/* Create an empty mesh so the positions can be easily shared with a generic utility. */
|
||||
Mesh *mesh = BKE_mesh_new_nomain(0, 0, 0, 0);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
mesh->totvert = points->totpoint;
|
||||
CustomData_free_layer_named(&mesh->vdata, "position", mesh->totvert);
|
||||
Jacques Lucke
commented
This should probably be moved one line up. This should probably be moved one line up.
|
||||
|
||||
const AttributeAccessor src_attributes = points->attributes();
|
||||
MutableAttributeAccessor dst_attributes = mesh->attributes_for_write();
|
||||
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
const eCustomDataType data_type = entry.value.data_type;
|
||||
GVArray src = src_attributes.lookup_or_default(attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
if (dst && src) {
|
||||
src.materialize_compressed_to_uninitialized(selection, dst.span.data());
|
||||
dst.finish();
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
share_custom_data_layer(
|
||||
points->pdata, mesh->vdata, entry.key, entry.value.data_type, mesh->totvert);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
|
||||
mesh->loose_edges_tag_none();
|
||||
const AttributeAccessor src_attributes = points->attributes();
|
||||
MutableAttributeAccessor dst_attributes = mesh->attributes_for_write();
|
||||
|
||||
for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
|
||||
const AttributeIDRef attribute_id = entry.key;
|
||||
const eCustomDataType data_type = entry.value.data_type;
|
||||
GVArray src = src_attributes.lookup_or_default(attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
attribute_id, ATTR_DOMAIN_POINT, data_type);
|
||||
if (dst && src) {
|
||||
src.materialize_compressed_to_uninitialized(selection, dst.span.data());
|
||||
dst.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
I'm not quite convinced we should start using custom data here yet. Better integrate implicit sharing with the attribute api and later field evaluation + maybe virtual arrays. I started doing that a bit in my branch. See e.g.
GAttributeReader
in https://projects.blender.org/JacquesLucke/blender/src/branch/sim-bake/source/blender/blenkernel/BKE_attribute.hh.