From 84a4f2ae68d40830111ae41b25d76b165e37d611 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 8 Mar 2021 12:45:06 -0500 Subject: Geometry Nodes: Improve performance of point distribute node This commit refactors the point distribute node to skip realizing the instances created by the point instance node or the collection and object info nodes. Realizing instances is not necessary here because it copies all the mesh data and and interpolates all attributes from the instances when this operation does not need to modify the input geometry at all. In the tree leaves test file this patch improves the performance of the node by about 14%. That's not very much, the gain is likely larger for more complicated input instances with more attributes (especially attributes on different domains, where interpolation would be necessary to join all of the instances). Another possible performance improvement would be to parallelize the code in this node where possible. The point distribution code unfortunately gets quite a bit more complicated because it has to handle the complexity of having many inputs instead of just one. Note that this commit changes the randomness of the distribution in some cases, as if the seed input had changed. Differential Revision: https://developer.blender.org/D10596 --- .../geometry/nodes/node_geo_point_distribute.cc | 546 ++++++++++++++------- 1 file changed, 380 insertions(+), 166 deletions(-) (limited to 'source/blender/nodes') diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index c40cb2bb0ae..96409837491 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -29,6 +29,7 @@ #include "BKE_attribute_math.hh" #include "BKE_bvhutils.h" #include "BKE_deform.h" +#include "BKE_geometry_set_instances.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" @@ -38,6 +39,9 @@ #include "node_geometry_util.hh" +using blender::bke::AttributeKind; +using blender::bke::GeometryInstanceGroup; + static bNodeSocketTemplate geo_node_point_distribute_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, {SOCK_FLOAT, N_("Distance Min"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, @@ -89,6 +93,7 @@ static Span get_mesh_looptris(const Mesh &mesh) } static void sample_mesh_surface(const Mesh &mesh, + const float4x4 &transform, const float base_density, const FloatReadAttribute *density_factors, const int seed, @@ -106,9 +111,9 @@ static void sample_mesh_surface(const Mesh &mesh, const int v0_index = mesh.mloop[v0_loop].v; const int v1_index = mesh.mloop[v1_loop].v; const int v2_index = mesh.mloop[v2_loop].v; - const float3 v0_pos = mesh.mvert[v0_index].co; - const float3 v1_pos = mesh.mvert[v1_index].co; - const float3 v2_pos = mesh.mvert[v2_index].co; + const float3 v0_pos = transform * float3(mesh.mvert[v0_index].co); + const float3 v1_pos = transform * float3(mesh.mvert[v1_index].co); + const float3 v2_pos = transform * float3(mesh.mvert[v2_index].co); float looptri_density_factor = 1.0f; if (density_factors != nullptr) { @@ -138,47 +143,64 @@ static void sample_mesh_surface(const Mesh &mesh, } } -BLI_NOINLINE static KDTree_3d *build_kdtree(Span positions) +BLI_NOINLINE static KDTree_3d *build_kdtree(Span> positions_all, + const int initial_points_len) { - KDTree_3d *kdtree = BLI_kdtree_3d_new(positions.size()); - for (const int i : positions.index_range()) { - BLI_kdtree_3d_insert(kdtree, i, positions[i]); + KDTree_3d *kdtree = BLI_kdtree_3d_new(initial_points_len); + + int i_point = 0; + for (const Vector positions : positions_all) { + for (const float3 position : positions) { + BLI_kdtree_3d_insert(kdtree, i_point, position); + i_point++; + } } BLI_kdtree_3d_balance(kdtree); return kdtree; } BLI_NOINLINE static void update_elimination_mask_for_close_points( - Span positions, const float minimum_distance, MutableSpan elimination_mask) + Span> positions_all, + Span instance_start_offsets, + const float minimum_distance, + MutableSpan elimination_mask, + const int initial_points_len) { if (minimum_distance <= 0.0f) { return; } - KDTree_3d *kdtree = build_kdtree(positions); + KDTree_3d *kdtree = build_kdtree(positions_all, initial_points_len); - for (const int i : positions.index_range()) { - if (elimination_mask[i]) { - continue; - } + /* The elimination mask is a flattened array for every point, + * so keep track of the index to it separately. */ + for (const int i_instance : positions_all.index_range()) { + Span positions = positions_all[i_instance]; + const int offset = instance_start_offsets[i_instance]; + + for (const int i : positions.index_range()) { + if (elimination_mask[offset + i]) { + continue; + } - struct CallbackData { - int index; - MutableSpan elimination_mask; - } callback_data = {i, elimination_mask}; - - BLI_kdtree_3d_range_search_cb( - kdtree, - positions[i], - minimum_distance, - [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { - CallbackData &callback_data = *static_cast(user_data); - if (index != callback_data.index) { - callback_data.elimination_mask[index] = true; - } - return true; - }, - &callback_data); + struct CallbackData { + int index; + MutableSpan elimination_mask; + } callback_data = {offset + i, elimination_mask}; + + BLI_kdtree_3d_range_search_cb( + kdtree, + positions[i], + minimum_distance, + [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { + CallbackData &callback_data = *static_cast(user_data); + if (index != callback_data.index) { + callback_data.elimination_mask[index] = true; + } + return true; + }, + &callback_data); + } } BLI_kdtree_3d_free(kdtree); } @@ -287,73 +309,106 @@ BLI_NOINLINE static void interpolate_attribute_corner(const Mesh &mesh, } } +template BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, Span bary_coords, Span looptri_indices, - const StringRef attribute_name, - const ReadAttribute &attribute_in, - GeometryComponent &component) + const AttributeDomain source_domain, + Span source_span, + MutableSpan output_span) { - const CustomDataType data_type = attribute_in.custom_data_type(); - const AttributeDomain domain = attribute_in.domain(); - if (!ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) { - /* Not supported currently. */ - return; - } - - OutputAttributePtr attribute_out = component.attribute_try_get_for_output( - attribute_name, ATTR_DOMAIN_POINT, data_type); - if (!attribute_out) { - return; - } - - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { - using T = decltype(dummy); - - Span data_in = attribute_in.get_span(); - MutableSpan data_out = attribute_out->get_span_for_write_only(); - - switch (domain) { - case ATTR_DOMAIN_POINT: { - interpolate_attribute_point(mesh, bary_coords, looptri_indices, data_in, data_out); - break; - } - case ATTR_DOMAIN_CORNER: { - interpolate_attribute_corner(mesh, bary_coords, looptri_indices, data_in, data_out); - break; - } - default: { - BLI_assert(false); - break; - } + switch (source_domain) { + case ATTR_DOMAIN_POINT: { + interpolate_attribute_point(mesh, bary_coords, looptri_indices, source_span, output_span); + break; + } + case ATTR_DOMAIN_CORNER: { + interpolate_attribute_corner( + mesh, bary_coords, looptri_indices, source_span, output_span); + break; } - }); - attribute_out.apply_span_and_save(); + default: { + /* Not supported currently. */ + return; + } + } } -BLI_NOINLINE static void interpolate_existing_attributes(const MeshComponent &mesh_component, - GeometryComponent &component, - Span bary_coords, - Span looptri_indices) +BLI_NOINLINE static void interpolate_existing_attributes( + Span set_groups, + Span instance_start_offsets, + const Map &attributes, + GeometryComponent &component, + Span> bary_coords_array, + Span> looptri_indices_array) { - const Mesh &mesh = *mesh_component.get_for_read(); - - Set attribute_names = mesh_component.attribute_names(); - for (StringRefNull attribute_name : attribute_names) { - if (ELEM(attribute_name, "position", "normal", "id")) { + for (Map::Item entry : attributes.items()) { + StringRef attribute_name = entry.key; + const CustomDataType output_data_type = entry.value.data_type; + /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ + OutputAttributePtr attribute_out = component.attribute_try_get_for_output( + attribute_name, ATTR_DOMAIN_POINT, output_data_type); + if (!attribute_out) { continue; } - ReadAttributePtr attribute_in = mesh_component.attribute_try_get_for_read(attribute_name); - interpolate_attribute( - mesh, bary_coords, looptri_indices, attribute_name, *attribute_in, component); + fn::GMutableSpan out_span = attribute_out->get_span_for_write_only(); + + int i_instance = 0; + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + const MeshComponent &source_component = *set.get_component_for_read(); + const Mesh &mesh = *source_component.get_for_read(); + + /* Use a dummy read without specifying a domain or data type in order to + * get the existing attribute's domain. Interpolation is done manually based + * on the bary coords in #interpolate_attribute. */ + ReadAttributePtr dummy_attribute = source_component.attribute_try_get_for_read( + attribute_name); + if (!dummy_attribute) { + i_instance += set_group.transforms.size(); + continue; + } + + const AttributeDomain source_domain = dummy_attribute->domain(); + ReadAttributePtr source_attribute = source_component.attribute_get_for_read( + attribute_name, source_domain, output_data_type, nullptr); + if (!source_attribute) { + i_instance += set_group.transforms.size(); + continue; + } + fn::GSpan source_span = source_attribute->get_span(); + + attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { + using T = decltype(dummy); + + for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { + const int offset = instance_start_offsets[i_instance]; + Span bary_coords = bary_coords_array[i_instance]; + Span looptri_indices = looptri_indices_array[i_instance]; + + MutableSpan instance_span = out_span.typed().slice(offset, bary_coords.size()); + interpolate_attribute(mesh, + bary_coords, + looptri_indices, + source_domain, + source_span.typed(), + instance_span); + + i_instance++; + } + }); + } + + attribute_out.apply_span_and_save(); } } -BLI_NOINLINE static void compute_special_attributes(const Mesh &mesh, +BLI_NOINLINE static void compute_special_attributes(Span sets, + Span instance_start_offsets, GeometryComponent &component, - Span bary_coords, - Span looptri_indices) + Span> bary_coords_array, + Span> looptri_indices_array) { OutputAttributePtr id_attribute = component.attribute_try_get_for_output( "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); @@ -362,26 +417,50 @@ BLI_NOINLINE static void compute_special_attributes(const Mesh &mesh, OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - MutableSpan ids = id_attribute->get_span_for_write_only(); - MutableSpan normals = normal_attribute->get_span_for_write_only(); - MutableSpan rotations = rotation_attribute->get_span_for_write_only(); - - Span looptris = get_mesh_looptris(mesh); - for (const int i : bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const float3 &bary_coord = bary_coords[i]; - - const int v0_index = mesh.mloop[looptri.tri[0]].v; - const int v1_index = mesh.mloop[looptri.tri[1]].v; - const int v2_index = mesh.mloop[looptri.tri[2]].v; - const float3 v0_pos = mesh.mvert[v0_index].co; - const float3 v1_pos = mesh.mvert[v1_index].co; - const float3 v2_pos = mesh.mvert[v2_index].co; + MutableSpan result_ids = id_attribute->get_span_for_write_only(); + MutableSpan result_normals = normal_attribute->get_span_for_write_only(); + MutableSpan result_rotations = rotation_attribute->get_span_for_write_only(); + + int i_instance = 0; + for (const GeometryInstanceGroup &set_group : sets) { + const GeometrySet &set = set_group.geometry_set; + const MeshComponent &component = *set.get_component_for_read(); + const Mesh &mesh = *component.get_for_read(); + Span looptris = get_mesh_looptris(mesh); + + for (const float4x4 &transform : set_group.transforms) { + const int offset = instance_start_offsets[i_instance]; + + Span bary_coords = bary_coords_array[i_instance]; + Span looptri_indices = looptri_indices_array[i_instance]; + MutableSpan ids = result_ids.slice(offset, bary_coords.size()); + MutableSpan normals = result_normals.slice(offset, bary_coords.size()); + MutableSpan rotations = result_rotations.slice(offset, bary_coords.size()); + + /* Use one matrix multiplication per point instead of three (for each triangle corner). */ + float rotation_matrix[3][3]; + mat4_to_rot(rotation_matrix, transform.values); + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + const float3 v0_pos = float3(mesh.mvert[v0_index].co); + const float3 v1_pos = float3(mesh.mvert[v1_index].co); + const float3 v2_pos = float3(mesh.mvert[v2_index].co); + + ids[i] = (int)(bary_coord.hash() + (uint64_t)looptri_index); + normal_tri_v3(normals[i], v0_pos, v1_pos, v2_pos); + mul_m3_v3(rotation_matrix, normals[i]); + rotations[i] = normal_to_euler_rotation(normals[i]); + } - ids[i] = (int)(bary_coord.hash() + (uint64_t)looptri_index); - normal_tri_v3(normals[i], v0_pos, v1_pos, v2_pos); - rotations[i] = normal_to_euler_rotation(normals[i]); + i_instance++; + } } id_attribute.apply_span_and_save(); @@ -389,109 +468,244 @@ BLI_NOINLINE static void compute_special_attributes(const Mesh &mesh, rotation_attribute.apply_span_and_save(); } -BLI_NOINLINE static void add_remaining_point_attributes(const MeshComponent &mesh_component, - GeometryComponent &component, - Span bary_coords, - Span looptri_indices) +BLI_NOINLINE static void add_remaining_point_attributes( + Span set_groups, + Span instance_start_offsets, + const Map &attributes, + GeometryComponent &component, + Span> bary_coords_array, + Span> looptri_indices_array) { - interpolate_existing_attributes(mesh_component, component, bary_coords, looptri_indices); + interpolate_existing_attributes(set_groups, + instance_start_offsets, + attributes, + component, + bary_coords_array, + looptri_indices_array); compute_special_attributes( - *mesh_component.get_for_read(), component, bary_coords, looptri_indices); + set_groups, instance_start_offsets, component, bary_coords_array, looptri_indices_array); } -static void sample_mesh_surface_with_minimum_distance(const Mesh &mesh, - const float max_density, - const float minimum_distance, - const FloatReadAttribute &density_factors, - const int seed, - Vector &r_positions, - Vector &r_bary_coords, - Vector &r_looptri_indices) +static void distribute_points_random(Span set_groups, + const StringRef density_attribute_name, + const float density, + const int seed, + MutableSpan> positions_all, + MutableSpan> bary_coords_all, + MutableSpan> looptri_indices_all) { - sample_mesh_surface( - mesh, max_density, nullptr, seed, r_positions, r_bary_coords, r_looptri_indices); - Array elimination_mask(r_positions.size(), false); - update_elimination_mask_for_close_points(r_positions, minimum_distance, elimination_mask); - update_elimination_mask_based_on_density_factors( - mesh, density_factors, r_bary_coords, r_looptri_indices, elimination_mask); - eliminate_points_based_on_mask(elimination_mask, r_positions, r_bary_coords, r_looptri_indices); + /* If there is an attribute name, the default value for the densities should be zero so that + * points are only scattered where the attribute exists. Otherwise, just "ignore" the density + * factors. */ + const bool use_one_default = density_attribute_name.is_empty(); + + int i_instance = 0; + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + const MeshComponent &component = *set.get_component_for_read(); + const FloatReadAttribute density_factors = component.attribute_get_for_read( + density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); + const Mesh &mesh = *component.get_for_read(); + for (const float4x4 &transform : set_group.transforms) { + Vector &positions = positions_all[i_instance]; + Vector &bary_coords = bary_coords_all[i_instance]; + Vector &looptri_indices = looptri_indices_all[i_instance]; + sample_mesh_surface(mesh, + transform, + density, + &density_factors, + seed, + positions, + bary_coords, + looptri_indices); + i_instance++; + } + } +} + +static void distribute_points_poisson_disk(Span set_groups, + const StringRef density_attribute_name, + const float density, + const int seed, + const float minimum_distance, + MutableSpan> positions_all, + MutableSpan> bary_coords_all, + MutableSpan> looptri_indices_all) +{ + Array instance_start_offsets(positions_all.size()); + int initial_points_len = 0; + int i_instance = 0; + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + const MeshComponent &component = *set.get_component_for_read(); + const Mesh &mesh = *component.get_for_read(); + for (const float4x4 &transform : set_group.transforms) { + Vector &positions = positions_all[i_instance]; + Vector &bary_coords = bary_coords_all[i_instance]; + Vector &looptri_indices = looptri_indices_all[i_instance]; + sample_mesh_surface( + mesh, transform, density, nullptr, seed, positions, bary_coords, looptri_indices); + + instance_start_offsets[i_instance] = initial_points_len; + initial_points_len += positions.size(); + i_instance++; + } + } + + /* If there is an attribute name, the default value for the densities should be zero so that + * points are only scattered where the attribute exists. Otherwise, just "ignore" the density + * factors. */ + const bool use_one_default = density_attribute_name.is_empty(); + + /* Unlike the other result arrays, the elimination mask in stored as a flat array for every + * point, in order to simplify culling points from the KDTree (which needs to know about all + * points at once). */ + Array elimination_mask(initial_points_len, false); + update_elimination_mask_for_close_points(positions_all, + instance_start_offsets, + minimum_distance, + elimination_mask, + initial_points_len); + + i_instance = 0; + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + const MeshComponent &component = *set.get_component_for_read(); + const Mesh &mesh = *component.get_for_read(); + const FloatReadAttribute density_factors = component.attribute_get_for_read( + density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); + + for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { + Vector &positions = positions_all[i_instance]; + Vector &bary_coords = bary_coords_all[i_instance]; + Vector &looptri_indices = looptri_indices_all[i_instance]; + + const int offset = instance_start_offsets[i_instance]; + update_elimination_mask_based_on_density_factors( + mesh, + density_factors, + bary_coords, + looptri_indices, + elimination_mask.as_mutable_span().slice(offset, positions.size())); + + eliminate_points_based_on_mask(elimination_mask.as_span().slice(offset, positions.size()), + positions, + bary_coords, + looptri_indices); + + i_instance++; + } + } } static void geo_node_point_distribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); - GeometrySet geometry_set_out; - /* TODO: This node only needs read-only access to input instances. */ - geometry_set = geometry_set_realize_instances(geometry_set); + const GeometryNodePointDistributeMode distribute_method = + static_cast(params.node().custom1); - GeometryNodePointDistributeMode distribute_method = static_cast( - params.node().custom1); + const int seed = params.get_input("Seed"); + const float density = params.extract_input("Density Max"); + const std::string density_attribute_name = params.extract_input( + "Density Attribute"); - if (!geometry_set.has_mesh()) { - params.error_message_add(NodeWarningType::Error, TIP_("Geometry must contain a mesh")); - params.set_output("Geometry", std::move(geometry_set_out)); + if (density <= 0.0f) { + params.set_output("Geometry", std::move(GeometrySet())); return; } - const float density = params.extract_input("Density Max"); - const std::string density_attribute = params.extract_input("Density Attribute"); - - if (density <= 0.0f) { - params.set_output("Geometry", std::move(geometry_set_out)); + Vector set_groups = bke::geometry_set_gather_instances(geometry_set); + if (set_groups.is_empty()) { + params.set_output("Geometry", std::move(GeometrySet())); return; } - const MeshComponent &mesh_component = *geometry_set.get_component_for_read(); - const Mesh *mesh_in = mesh_component.get_for_read(); + /* Remove any set inputs that don't contain a mesh, to avoid checking later on. */ + for (int i = set_groups.size() - 1; i >= 0; i--) { + const GeometrySet &set = set_groups[i].geometry_set; + if (!set.has_mesh()) { + set_groups.remove_and_reorder(i); + } + } - if (mesh_in->mpoly == nullptr) { - params.error_message_add(NodeWarningType::Error, TIP_("Mesh has no faces")); - params.set_output("Geometry", std::move(geometry_set_out)); + if (set_groups.is_empty()) { + params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); + params.set_output("Geometry", std::move(GeometrySet())); return; } - const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read( - density_attribute, ATTR_DOMAIN_CORNER, 1.0f); - const int seed = params.get_input("Seed"); + int instances_len = 0; + for (GeometryInstanceGroup &set_group : set_groups) { + instances_len += set_group.transforms.size(); + } + + /* Store data per-instance in order to simplify attribute access after the scattering, + * and to make the point elimination simpler for the poisson disk mode. Note that some + * vectors will be empty if any instances don't contain mesh data. */ + Array> positions_all(instances_len); + Array> bary_coords_all(instances_len); + Array> looptri_indices_all(instances_len); - Vector positions; - Vector bary_coords; - Vector looptri_indices; switch (distribute_method) { - case GEO_NODE_POINT_DISTRIBUTE_RANDOM: - sample_mesh_surface( - *mesh_in, density, &density_factors, seed, positions, bary_coords, looptri_indices); + case GEO_NODE_POINT_DISTRIBUTE_RANDOM: { + distribute_points_random(set_groups, + density_attribute_name, + density, + seed, + positions_all, + bary_coords_all, + looptri_indices_all); break; - case GEO_NODE_POINT_DISTRIBUTE_POISSON: + } + case GEO_NODE_POINT_DISTRIBUTE_POISSON: { const float minimum_distance = params.extract_input("Distance Min"); - sample_mesh_surface_with_minimum_distance(*mesh_in, - density, - minimum_distance, - density_factors, - seed, - positions, - bary_coords, - looptri_indices); + distribute_points_poisson_disk(set_groups, + density_attribute_name, + density, + seed, + minimum_distance, + positions_all, + bary_coords_all, + looptri_indices_all); break; + } } - const int tot_points = positions.size(); - PointCloud *pointcloud = BKE_pointcloud_new_nomain(tot_points); - memcpy(pointcloud->co, positions.data(), sizeof(float3) * tot_points); - for (const int i : positions.index_range()) { - *(float3 *)(pointcloud->co + i) = positions[i]; - pointcloud->radius[i] = 0.05f; + int final_points_len = 0; + Array instance_start_offsets(set_groups.size()); + for (const int i : positions_all.index_range()) { + Vector &positions = positions_all[i]; + instance_start_offsets[i] = final_points_len; + final_points_len += positions.size(); } + PointCloud *pointcloud = BKE_pointcloud_new_nomain(final_points_len); + for (const int instance_index : positions_all.index_range()) { + const int offset = instance_start_offsets[instance_index]; + Span positions = positions_all[instance_index]; + memcpy(pointcloud->co + offset, positions.data(), sizeof(float3) * positions.size()); + } + + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + + GeometrySet geometry_set_out = GeometrySet::create_with_pointcloud(pointcloud); PointCloudComponent &point_component = geometry_set_out.get_component_for_write(); - point_component.replace(pointcloud); - add_remaining_point_attributes(mesh_component, point_component, bary_coords, looptri_indices); + Map attributes; + bke::gather_attribute_info( + attributes, {GeometryComponentType::Mesh}, set_groups, {"position", "normal", "id"}); + add_remaining_point_attributes(set_groups, + instance_start_offsets, + attributes, + point_component, + bary_coords_all, + looptri_indices_all); params.set_output("Geometry", std::move(geometry_set_out)); } + } // namespace blender::nodes void register_node_type_geo_point_distribute() -- cgit v1.2.3