From e7ae2840a5e01e136ebf1f0375bdea7ad8dc2067 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 24 Sep 2021 11:50:02 +0200 Subject: Geometry Nodes: new Distribute Points on Faces node This adds a replacement for the deprecated Point Distribute node. Arguments for the name change can be found in T91155. Descriptions of the sockets are available in D12536. Thanks to Jarrett Johnson for the initial patch! Differential Revision: https://developer.blender.org/D12536 --- source/blender/blenkernel/BKE_geometry_set.hh | 8 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenkernel/intern/node.cc | 1 + source/blender/makesdna/DNA_node_types.h | 5 + source/blender/makesrna/intern/rna_nodetree.c | 27 + source/blender/nodes/CMakeLists.txt | 3 +- source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + .../nodes/legacy/node_geo_point_distribute.cc | 657 ++++++++++++++++++ .../nodes/node_geo_distribute_points_on_faces.cc | 753 +++++++++++++++++++++ .../geometry/nodes/node_geo_point_distribute.cc | 657 ------------------ 11 files changed, 1456 insertions(+), 658 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc delete mode 100644 source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc (limited to 'source') diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index a6c77c74b9e..5fcdbc83e25 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -715,6 +715,14 @@ class AnonymousAttributeFieldInput : public fn::FieldInput { { } + template static fn::Field Create(StrongAnonymousAttributeID anonymous_id) + { + const CPPType &type = CPPType::get(); + auto field_input = std::make_shared(std::move(anonymous_id), + type); + return fn::Field{field_input}; + } + const GVArray *get_varray_for_context(const fn::FieldContext &context, IndexMask mask, ResourceScope &scope) const override; diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 2e843e82a9f..6f2d0d9dd90 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1500,6 +1500,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_STRING_JOIN 1087 #define GEO_NODE_CURVE_PARAMETER 1088 #define GEO_NODE_CURVE_FILLET 1089 +#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 7fd681960cf..78789b2e771 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5762,6 +5762,7 @@ static void registerGeometryNodes() register_node_type_geo_curve_to_points(); register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); + register_node_type_geo_distribute_points_on_faces(); register_node_type_geo_edge_split(); register_node_type_geo_input_index(); register_node_type_geo_input_material(); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index a52816ecf57..6e4737b7d07 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1980,6 +1980,11 @@ typedef enum GeometryNodePointDistributeMode { GEO_NODE_POINT_DISTRIBUTE_POISSON = 1, } GeometryNodePointDistributeMode; +typedef enum GeometryNodeDistributePointsOnFacesMode { + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM = 0, + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON = 1, +} GeometryNodeDistributePointsOnFacesMode; + typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_EULER = 0, GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b631e76c094..dcac157d1a2 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9479,6 +9479,33 @@ static void def_geo_point_distribute(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_distribute_points_on_faces(StructRNA *srna) +{ + PropertyRNA *prop; + + static const EnumPropertyItem rna_node_geometry_distribute_points_on_faces_mode_items[] = { + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM, + "RANDOM", + 0, + "Random", + "Distribute points randomly on the surface"}, + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON, + "POISSON", + 0, + "Poisson Disk", + "Distribute the points randomly on the surface while taking a minimum distance between " + "points into account"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "distribute_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_node_geometry_distribute_points_on_faces_mode_items); + RNA_def_property_enum_default(prop, GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + RNA_def_property_ui_text(prop, "Distribution Method", "Method to use for scattering points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_color_ramp(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index e6af3ecafbc..65f0999c63b 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -146,6 +146,7 @@ set(SRC geometry/nodes/legacy/node_geo_material_assign.cc geometry/nodes/legacy/node_geo_select_by_material.cc + geometry/nodes/legacy/node_geo_point_distribute.cc geometry/nodes/node_geo_align_rotation_to_vector.cc geometry/nodes/node_geo_attribute_capture.cc @@ -196,6 +197,7 @@ set(SRC geometry/nodes/node_geo_curve_to_points.cc geometry/nodes/node_geo_curve_trim.cc geometry/nodes/node_geo_delete_geometry.cc + geometry/nodes/node_geo_distribute_points_on_faces.cc geometry/nodes/node_geo_edge_split.cc geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_input_normal.cc @@ -218,7 +220,6 @@ set(SRC geometry/nodes/node_geo_mesh_subdivide.cc geometry/nodes/node_geo_mesh_to_curve.cc geometry/nodes/node_geo_object_info.cc - geometry/nodes/node_geo_point_distribute.cc geometry/nodes/node_geo_point_instance.cc geometry/nodes/node_geo_point_rotate.cc geometry/nodes/node_geo_point_scale.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 47bc54132eb..9cd3365bd91 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -78,6 +78,7 @@ void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); void register_node_type_geo_delete_geometry(void); +void register_node_type_geo_distribute_points_on_faces(void); void register_node_type_geo_edge_split(void); void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ab673d814bb..b1c4f1d6367 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -332,6 +332,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "") +DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc new file mode 100644 index 00000000000..f95b0da86ed --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc @@ -0,0 +1,657 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_hash.h" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_geometry_set_instances.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::bke::GeometryInstanceGroup; + +namespace blender::nodes { + +static void geo_node_point_distribute_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Geometry"); + b.add_input("Distance Min").min(0.0f).max(100000.0f).subtype(PROP_DISTANCE); + b.add_input("Density Max") + .default_value(1.0f) + .min(0.0f) + .max(100000.0f) + .subtype(PROP_NONE); + b.add_input("Density Attribute"); + b.add_input("Seed").min(-10000).max(10000); + b.add_output("Geometry"); +} + +static void geo_node_point_distribute_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); +} + +static void node_point_distribute_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_min_dist = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + + nodeSetSocketAvailability(sock_min_dist, ELEM(node->custom1, GEO_NODE_POINT_DISTRIBUTE_POISSON)); +} + +/** + * Use an arbitrary choice of axes for a usable rotation attribute directly out of this node. + */ +static float3 normal_to_euler_rotation(const float3 normal) +{ + float quat[4]; + vec_to_quat(quat, normal, OB_NEGZ, OB_POSY); + float3 rotation; + quat_to_eul(rotation, quat); + return rotation; +} + +static void sample_mesh_surface(const Mesh &mesh, + const float4x4 &transform, + const float base_density, + const VArray *density_factors, + const int seed, + Vector &r_positions, + Vector &r_bary_coords, + Vector &r_looptri_indices) +{ + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int looptri_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + 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 = 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) { + const float v0_density_factor = std::max(0.0f, density_factors->get(v0_loop)); + const float v1_density_factor = std::max(0.0f, density_factors->get(v1_loop)); + const float v2_density_factor = std::max(0.0f, density_factors->get(v2_loop)); + looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; + } + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = BLI_hash_int(looptri_index + seed); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * base_density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coord = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); + r_positions.append(point_pos); + r_bary_coords.append(bary_coord); + r_looptri_indices.append(looptri_index); + } + } +} + +BLI_NOINLINE static KDTree_3d *build_kdtree(Span> positions_all, + const int initial_points_len) +{ + 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_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_all, initial_points_len); + + /* 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 = {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); +} + +BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( + const Mesh &mesh, + const VArray &density_factors, + Span bary_coords, + Span looptri_indices, + MutableSpan elimination_mask) +{ + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + for (const int i : bary_coords.index_range()) { + if (elimination_mask[i]) { + continue; + } + + const MLoopTri &looptri = looptris[looptri_indices[i]]; + const float3 bary_coord = bary_coords[i]; + + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + + const float probablity = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y + + v2_density_factor * bary_coord.z; + + const float hash = BLI_hash_int_01(bary_coord.hash()); + if (hash > probablity) { + elimination_mask[i] = true; + } + } +} + +BLI_NOINLINE static void eliminate_points_based_on_mask(Span elimination_mask, + Vector &positions, + Vector &bary_coords, + Vector &looptri_indices) +{ + for (int i = positions.size() - 1; i >= 0; i--) { + if (elimination_mask[i]) { + positions.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + } + } +} + +BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, + Span bary_coords, + Span looptri_indices, + const AttributeDomain source_domain, + const GVArray &source_data, + GMutableSpan output_data) +{ + switch (source_domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); + break; + } + default: { + /* Not supported currently. */ + return; + } + } +} + +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) +{ + for (Map::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = 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. */ + OutputAttribute attribute_out = component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, output_data_type); + if (!attribute_out) { + continue; + } + + GMutableSpan out_span = attribute_out.as_span(); + + 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(); + + std::optional attribute_info = component.attribute_get_meta_data( + attribute_id); + if (!attribute_info) { + i_instance += set_group.transforms.size(); + continue; + } + + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = source_component.attribute_get_for_read( + attribute_id, source_domain, output_data_type, nullptr); + if (!source_attribute) { + i_instance += set_group.transforms.size(); + continue; + } + + 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]; + + GMutableSpan instance_span = out_span.slice(offset, bary_coords.size()); + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span); + + i_instance++; + } + + attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { + using T = decltype(dummy); + + GVArray_Span source_span{*source_attribute}; + }); + } + + attribute_out.save(); + } +} + +BLI_NOINLINE static void compute_special_attributes(Span sets, + Span instance_start_offsets, + GeometryComponent &component, + Span> bary_coords_array, + Span> looptri_indices_array) +{ + OutputAttribute_Typed id_attribute = component.attribute_try_get_for_output_only( + "id", ATTR_DOMAIN_POINT); + OutputAttribute_Typed normal_attribute = + component.attribute_try_get_for_output_only("normal", ATTR_DOMAIN_POINT); + OutputAttribute_Typed rotation_attribute = + component.attribute_try_get_for_output_only("rotation", ATTR_DOMAIN_POINT); + + MutableSpan result_ids = id_attribute.as_span(); + MutableSpan result_normals = normal_attribute.as_span(); + MutableSpan result_rotations = rotation_attribute.as_span(); + + 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(); + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&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]); + } + + i_instance++; + } + } + + id_attribute.save(); + normal_attribute.save(); + rotation_attribute.save(); +} + +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(set_groups, + instance_start_offsets, + attributes, + component, + bary_coords_array, + looptri_indices_array); + compute_special_attributes( + set_groups, instance_start_offsets, component, bary_coords_array, looptri_indices_array); +} + +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) +{ + /* 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(); + GVArray_Typed 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 GVArray_Typed 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"); + + const GeometryNodePointDistributeMode distribute_method = + static_cast(params.node().custom1); + + const int seed = params.get_input("Seed") * 5383843; + const float density = params.extract_input("Density Max"); + const std::string density_attribute_name = params.extract_input( + "Density Attribute"); + + if (density <= 0.0f) { + params.set_output("Geometry", GeometrySet()); + return; + } + + Vector set_groups; + geometry_set_gather_instances(geometry_set, set_groups); + if (set_groups.is_empty()) { + params.set_output("Geometry", GeometrySet()); + return; + } + + /* 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 (set_groups.is_empty()) { + params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); + params.set_output("Geometry", GeometrySet()); + return; + } + + 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); + + switch (distribute_method) { + 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: { + const float minimum_distance = params.extract_input("Distance Min"); + distribute_points_poisson_disk(set_groups, + density_attribute_name, + density, + seed, + minimum_distance, + positions_all, + bary_coords_all, + looptri_indices_all); + break; + } + } + + 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(); + + Map attributes; + bke::geometry_set_gather_instances_attribute_info( + set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes); + 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() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); + node_type_update(&ntype, blender::nodes::node_point_distribute_update); + ntype.declare = blender::nodes::geo_node_point_distribute_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; + ntype.draw_buttons = blender::nodes::geo_node_point_distribute_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc new file mode 100644 index 00000000000..95987a15f32 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -0,0 +1,753 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_kdtree.h" +#include "BLI_noise.hh" +#include "BLI_rand.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_geometry_set_instances.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::bke::GeometryInstanceGroup; + +namespace blender::nodes { + +static void geo_node_point_distribute_points_on_faces_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Geometry"); + b.add_input("Distance Min").min(0.0f).subtype(PROP_DISTANCE); + b.add_input("Density Max").default_value(10.0f).min(0.0f); + b.add_input("Density").default_value(10.0f).supports_field(); + b.add_input("Density Factor") + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .supports_field(); + b.add_input("Seed"); + b.add_input("Selection").default_value(true).hide_value().supports_field(); + + b.add_output("Points"); + b.add_output("Normal").field_source(); + b.add_output("Rotation").subtype(PROP_EULER).field_source(); + b.add_output("Stable ID").field_source(); +} + +static void geo_node_point_distribute_points_on_faces_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); +} + +static void node_point_distribute_points_on_faces_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_distance_min = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_density_max = (bNodeSocket *)sock_distance_min->next; + bNodeSocket *sock_density = sock_density_max->next; + bNodeSocket *sock_density_factor = sock_density->next; + nodeSetSocketAvailability(sock_distance_min, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density_max, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + nodeSetSocketAvailability(sock_density_factor, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); +} + +/** + * Use an arbitrary choice of axes for a usable rotation attribute directly out of this node. + */ +static float3 normal_to_euler_rotation(const float3 normal) +{ + float quat[4]; + vec_to_quat(quat, normal, OB_NEGZ, OB_POSY); + float3 rotation; + quat_to_eul(rotation, quat); + return rotation; +} + +static void sample_mesh_surface(const Mesh &mesh, + const float4x4 &transform, + const float base_density, + const Span density_factors, + const int seed, + Vector &r_positions, + Vector &r_bary_coords, + Vector &r_looptri_indices) +{ + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int looptri_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + 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 = 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.is_empty()) { + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; + } + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = noise::hash(looptri_index, seed); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * base_density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coord = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); + r_positions.append(point_pos); + r_bary_coords.append(bary_coord); + r_looptri_indices.append(looptri_index); + } + } +} + +BLI_NOINLINE static KDTree_3d *build_kdtree(Span> positions_all, + const int initial_points_len) +{ + 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_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_all, initial_points_len); + + /* 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 = {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); +} + +BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( + const Mesh &mesh, + const Span density_factors, + const Span bary_coords, + const Span looptri_indices, + const MutableSpan elimination_mask) +{ + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + for (const int i : bary_coords.index_range()) { + if (elimination_mask[i]) { + continue; + } + + const MLoopTri &looptri = looptris[looptri_indices[i]]; + const float3 bary_coord = bary_coords[i]; + + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + + const float probablity = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y + + v2_density_factor * bary_coord.z; + + const float hash = noise::hash_float_to_float(bary_coord); + if (hash > probablity) { + elimination_mask[i] = true; + } + } +} + +BLI_NOINLINE static void eliminate_points_based_on_mask(const Span elimination_mask, + Vector &positions, + Vector &bary_coords, + Vector &looptri_indices) +{ + for (int i = positions.size() - 1; i >= 0; i--) { + if (elimination_mask[i]) { + positions.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + } + } +} + +BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, + const Span bary_coords, + const Span looptri_indices, + const AttributeDomain source_domain, + const GVArray &source_data, + GMutableSpan output_data) +{ + switch (source_domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); + break; + } + default: { + /* Not supported currently. */ + return; + } + } +} + +BLI_NOINLINE static void propagate_existing_attributes( + const Span set_groups, + const Span instance_start_offsets, + const Map &attributes, + GeometryComponent &component, + const Span> bary_coords_array, + const Span> looptri_indices_array) +{ + for (Map::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = 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. */ + OutputAttribute attribute_out = component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, output_data_type); + if (!attribute_out) { + continue; + } + + GMutableSpan out_span = attribute_out.as_span(); + + 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(); + + std::optional attribute_info = component.attribute_get_meta_data( + attribute_id); + if (!attribute_info) { + i_instance += set_group.transforms.size(); + continue; + } + + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = source_component.attribute_get_for_read( + attribute_id, source_domain, output_data_type, nullptr); + if (!source_attribute) { + i_instance += set_group.transforms.size(); + continue; + } + + 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]; + + GMutableSpan instance_span = out_span.slice(offset, bary_coords.size()); + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span); + + i_instance++; + } + + attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { + using T = decltype(dummy); + + GVArray_Span source_span{*source_attribute}; + }); + } + + attribute_out.save(); + } +} + +namespace { +struct AttributeOutputs { + StrongAnonymousAttributeID normal_id; + StrongAnonymousAttributeID rotation_id; + StrongAnonymousAttributeID stable_id_id; +}; +} // namespace + +BLI_NOINLINE static void compute_attribute_outputs(const Span sets, + const Span instance_start_offsets, + GeometryComponent &component, + const Span> bary_coords_array, + const Span> looptri_indices_array, + const AttributeOutputs &attribute_outputs) +{ + std::optional> id_attribute; + std::optional> normal_attribute; + std::optional> rotation_attribute; + + MutableSpan result_ids; + MutableSpan result_normals; + MutableSpan result_rotations; + + if (attribute_outputs.stable_id_id) { + id_attribute.emplace(component.attribute_try_get_for_output_only( + attribute_outputs.stable_id_id.get(), ATTR_DOMAIN_POINT)); + result_ids = id_attribute->as_span(); + } + if (attribute_outputs.normal_id) { + normal_attribute.emplace(component.attribute_try_get_for_output_only( + attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT)); + result_normals = normal_attribute->as_span(); + } + if (attribute_outputs.rotation_id) { + rotation_attribute.emplace(component.attribute_try_get_for_output_only( + attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT)); + result_rotations = rotation_attribute->as_span(); + } + + 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(); + const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&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); + + if (!result_ids.is_empty()) { + ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); + } + float3 normal; + if (!result_normals.is_empty() || !result_rotations.is_empty()) { + normal_tri_v3(normal, v0_pos, v1_pos, v2_pos); + mul_m3_v3(rotation_matrix, normal); + } + if (!result_normals.is_empty()) { + normals[i] = normal; + } + if (!result_rotations.is_empty()) { + rotations[i] = normal_to_euler_rotation(normal); + } + } + + i_instance++; + } + } + + if (id_attribute) { + id_attribute->save(); + } + if (normal_attribute) { + normal_attribute->save(); + } + if (rotation_attribute) { + rotation_attribute->save(); + } +} + +static Array calc_full_density_factors_with_selection(const MeshComponent &component, + const Field &density_field, + const Field &selection_field) +{ + const AttributeDomain attribute_domain = ATTR_DOMAIN_CORNER; + GeometryComponentFieldContext field_context{component, attribute_domain}; + const int domain_size = component.attribute_domain_size(attribute_domain); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection_mask = selection_evaluator.get_evaluated_as_mask(0); + + Array densities(domain_size, 0.0f); + + fn::FieldEvaluator density_evaluator{field_context, &selection_mask}; + density_evaluator.add_with_destination(density_field, densities.as_mutable_span()); + density_evaluator.evaluate(); + return densities; +} + +static void distribute_points_random(Span set_groups, + const Field &density_field, + const Field &selection_field, + const int seed, + MutableSpan> positions_all, + MutableSpan> bary_coords_all, + MutableSpan> looptri_indices_all) +{ + 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 Array densities = calc_full_density_factors_with_selection( + component, density_field, selection_field); + 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]; + const int instance_seed = noise::hash(seed, i_instance); + sample_mesh_surface(mesh, + transform, + 1.0f, + densities, + instance_seed, + positions, + bary_coords, + looptri_indices); + i_instance++; + } + } +} + +static void distribute_points_poisson_disk(Span set_groups, + const float minimum_distance, + const float max_density, + const Field &density_factor_field, + const Field &selection_field, + const int seed, + 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]; + const int instance_seed = noise::hash(seed, i_instance); + sample_mesh_surface(mesh, + transform, + max_density, + {}, + instance_seed, + positions, + bary_coords, + looptri_indices); + + instance_start_offsets[i_instance] = initial_points_len; + initial_points_len += positions.size(); + i_instance++; + } + } + + /* 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 Array density_factors = calc_full_density_factors_with_selection( + component, density_factor_field, selection_field); + + 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_points_on_faces_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input("Geometry"); + + const GeometryNodeDistributePointsOnFacesMode distribute_method = + static_cast(params.node().custom1); + + const int seed = params.get_input("Seed") * 5383843; + const Field selection_field = params.extract_input>("Selection"); + + Vector set_groups; + geometry_set_gather_instances(geometry_set, set_groups); + if (set_groups.is_empty()) { + params.set_output("Points", GeometrySet()); + return; + } + + /* 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 (set_groups.is_empty()) { + params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); + params.set_output("Points", GeometrySet()); + return; + } + + 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); + + switch (distribute_method) { + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM: { + const Field density_field = params.extract_input>("Density"); + distribute_points_random(set_groups, + density_field, + selection_field, + seed, + positions_all, + bary_coords_all, + looptri_indices_all); + break; + } + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON: { + const float minimum_distance = params.extract_input("Distance Min"); + const float density_max = params.extract_input("Density Max"); + const Field density_factors_field = params.extract_input>( + "Density Factor"); + distribute_points_poisson_disk(set_groups, + minimum_distance, + density_max, + density_factors_field, + selection_field, + seed, + positions_all, + bary_coords_all, + looptri_indices_all); + break; + } + } + + 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(); + + Map attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, true, attributes); + + /* Position is set separately. */ + attributes.remove("position"); + + propagate_existing_attributes(set_groups, + instance_start_offsets, + attributes, + point_component, + bary_coords_all, + looptri_indices_all); + + AttributeOutputs attribute_outputs; + if (params.output_is_required("Normal")) { + attribute_outputs.normal_id = StrongAnonymousAttributeID("normal"); + } + if (params.output_is_required("Rotation")) { + attribute_outputs.rotation_id = StrongAnonymousAttributeID("rotation"); + } + if (params.output_is_required("Stable ID")) { + attribute_outputs.stable_id_id = StrongAnonymousAttributeID("stable id"); + } + + compute_attribute_outputs(set_groups, + instance_start_offsets, + point_component, + bary_coords_all, + looptri_indices_all, + attribute_outputs); + + params.set_output("Points", std::move(geometry_set_out)); + + if (attribute_outputs.normal_id) { + params.set_output( + "Normal", + AnonymousAttributeFieldInput::Create(std::move(attribute_outputs.normal_id))); + } + if (attribute_outputs.rotation_id) { + params.set_output( + "Rotation", + AnonymousAttributeFieldInput::Create(std::move(attribute_outputs.rotation_id))); + } + if (attribute_outputs.stable_id_id) { + params.set_output( + "Stable ID", + AnonymousAttributeFieldInput::Create(std::move(attribute_outputs.stable_id_id))); + } +} + +} // namespace blender::nodes + +void register_node_type_geo_distribute_points_on_faces() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, + "Distribute Points on Faces", + NODE_CLASS_GEOMETRY, + 0); + node_type_update(&ntype, blender::nodes::node_point_distribute_points_on_faces_update); + node_type_size(&ntype, 170, 100, 320); + ntype.declare = blender::nodes::geo_node_point_distribute_points_on_faces_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_points_on_faces_exec; + ntype.draw_buttons = blender::nodes::geo_node_point_distribute_points_on_faces_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc deleted file mode 100644 index f95b0da86ed..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ /dev/null @@ -1,657 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "BLI_hash.h" -#include "BLI_kdtree.h" -#include "BLI_rand.hh" -#include "BLI_timeit.hh" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_pointcloud_types.h" - -#include "BKE_attribute_math.hh" -#include "BKE_bvhutils.h" -#include "BKE_geometry_set_instances.hh" -#include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" -#include "BKE_mesh_sample.hh" -#include "BKE_pointcloud.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "node_geometry_util.hh" - -using blender::bke::GeometryInstanceGroup; - -namespace blender::nodes { - -static void geo_node_point_distribute_declare(NodeDeclarationBuilder &b) -{ - b.add_input("Geometry"); - b.add_input("Distance Min").min(0.0f).max(100000.0f).subtype(PROP_DISTANCE); - b.add_input("Density Max") - .default_value(1.0f) - .min(0.0f) - .max(100000.0f) - .subtype(PROP_NONE); - b.add_input("Density Attribute"); - b.add_input("Seed").min(-10000).max(10000); - b.add_output("Geometry"); -} - -static void geo_node_point_distribute_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) -{ - uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); -} - -static void node_point_distribute_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - bNodeSocket *sock_min_dist = (bNodeSocket *)BLI_findlink(&node->inputs, 1); - - nodeSetSocketAvailability(sock_min_dist, ELEM(node->custom1, GEO_NODE_POINT_DISTRIBUTE_POISSON)); -} - -/** - * Use an arbitrary choice of axes for a usable rotation attribute directly out of this node. - */ -static float3 normal_to_euler_rotation(const float3 normal) -{ - float quat[4]; - vec_to_quat(quat, normal, OB_NEGZ, OB_POSY); - float3 rotation; - quat_to_eul(rotation, quat); - return rotation; -} - -static void sample_mesh_surface(const Mesh &mesh, - const float4x4 &transform, - const float base_density, - const VArray *density_factors, - const int seed, - Vector &r_positions, - Vector &r_bary_coords, - Vector &r_looptri_indices) -{ - const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), - BKE_mesh_runtime_looptri_len(&mesh)}; - - for (const int looptri_index : looptris.index_range()) { - const MLoopTri &looptri = looptris[looptri_index]; - const int v0_loop = looptri.tri[0]; - const int v1_loop = looptri.tri[1]; - const int v2_loop = looptri.tri[2]; - 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 = 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) { - const float v0_density_factor = std::max(0.0f, density_factors->get(v0_loop)); - const float v1_density_factor = std::max(0.0f, density_factors->get(v1_loop)); - const float v2_density_factor = std::max(0.0f, density_factors->get(v2_loop)); - looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; - } - const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); - - const int looptri_seed = BLI_hash_int(looptri_index + seed); - RandomNumberGenerator looptri_rng(looptri_seed); - - const float points_amount_fl = area * base_density * looptri_density_factor; - const float add_point_probability = fractf(points_amount_fl); - const bool add_point = add_point_probability > looptri_rng.get_float(); - const int point_amount = (int)points_amount_fl + (int)add_point; - - for (int i = 0; i < point_amount; i++) { - const float3 bary_coord = looptri_rng.get_barycentric_coordinates(); - float3 point_pos; - interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); - r_positions.append(point_pos); - r_bary_coords.append(bary_coord); - r_looptri_indices.append(looptri_index); - } - } -} - -BLI_NOINLINE static KDTree_3d *build_kdtree(Span> positions_all, - const int initial_points_len) -{ - 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_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_all, initial_points_len); - - /* 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 = {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); -} - -BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( - const Mesh &mesh, - const VArray &density_factors, - Span bary_coords, - Span looptri_indices, - MutableSpan elimination_mask) -{ - const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), - BKE_mesh_runtime_looptri_len(&mesh)}; - for (const int i : bary_coords.index_range()) { - if (elimination_mask[i]) { - continue; - } - - const MLoopTri &looptri = looptris[looptri_indices[i]]; - const float3 bary_coord = bary_coords[i]; - - const int v0_loop = looptri.tri[0]; - const int v1_loop = looptri.tri[1]; - const int v2_loop = looptri.tri[2]; - - const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); - const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); - const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); - - const float probablity = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y + - v2_density_factor * bary_coord.z; - - const float hash = BLI_hash_int_01(bary_coord.hash()); - if (hash > probablity) { - elimination_mask[i] = true; - } - } -} - -BLI_NOINLINE static void eliminate_points_based_on_mask(Span elimination_mask, - Vector &positions, - Vector &bary_coords, - Vector &looptri_indices) -{ - for (int i = positions.size() - 1; i >= 0; i--) { - if (elimination_mask[i]) { - positions.remove_and_reorder(i); - bary_coords.remove_and_reorder(i); - looptri_indices.remove_and_reorder(i); - } - } -} - -BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, - Span bary_coords, - Span looptri_indices, - const AttributeDomain source_domain, - const GVArray &source_data, - GMutableSpan output_data) -{ - switch (source_domain) { - case ATTR_DOMAIN_POINT: { - bke::mesh_surface_sample::sample_point_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); - break; - } - case ATTR_DOMAIN_CORNER: { - bke::mesh_surface_sample::sample_corner_attribute( - mesh, looptri_indices, bary_coords, source_data, output_data); - break; - } - case ATTR_DOMAIN_FACE: { - bke::mesh_surface_sample::sample_face_attribute( - mesh, looptri_indices, source_data, output_data); - break; - } - default: { - /* Not supported currently. */ - return; - } - } -} - -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) -{ - for (Map::Item entry : attributes.items()) { - const AttributeIDRef attribute_id = 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. */ - OutputAttribute attribute_out = component.attribute_try_get_for_output_only( - attribute_id, ATTR_DOMAIN_POINT, output_data_type); - if (!attribute_out) { - continue; - } - - GMutableSpan out_span = attribute_out.as_span(); - - 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(); - - std::optional attribute_info = component.attribute_get_meta_data( - attribute_id); - if (!attribute_info) { - i_instance += set_group.transforms.size(); - continue; - } - - const AttributeDomain source_domain = attribute_info->domain; - GVArrayPtr source_attribute = source_component.attribute_get_for_read( - attribute_id, source_domain, output_data_type, nullptr); - if (!source_attribute) { - i_instance += set_group.transforms.size(); - continue; - } - - 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]; - - GMutableSpan instance_span = out_span.slice(offset, bary_coords.size()); - interpolate_attribute( - mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span); - - i_instance++; - } - - attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { - using T = decltype(dummy); - - GVArray_Span source_span{*source_attribute}; - }); - } - - attribute_out.save(); - } -} - -BLI_NOINLINE static void compute_special_attributes(Span sets, - Span instance_start_offsets, - GeometryComponent &component, - Span> bary_coords_array, - Span> looptri_indices_array) -{ - OutputAttribute_Typed id_attribute = component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT); - OutputAttribute_Typed normal_attribute = - component.attribute_try_get_for_output_only("normal", ATTR_DOMAIN_POINT); - OutputAttribute_Typed rotation_attribute = - component.attribute_try_get_for_output_only("rotation", ATTR_DOMAIN_POINT); - - MutableSpan result_ids = id_attribute.as_span(); - MutableSpan result_normals = normal_attribute.as_span(); - MutableSpan result_rotations = rotation_attribute.as_span(); - - 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(); - const Span looptris{BKE_mesh_runtime_looptri_ensure(&mesh), - BKE_mesh_runtime_looptri_len(&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]); - } - - i_instance++; - } - } - - id_attribute.save(); - normal_attribute.save(); - rotation_attribute.save(); -} - -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(set_groups, - instance_start_offsets, - attributes, - component, - bary_coords_array, - looptri_indices_array); - compute_special_attributes( - set_groups, instance_start_offsets, component, bary_coords_array, looptri_indices_array); -} - -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) -{ - /* 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(); - GVArray_Typed 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 GVArray_Typed 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"); - - const GeometryNodePointDistributeMode distribute_method = - static_cast(params.node().custom1); - - const int seed = params.get_input("Seed") * 5383843; - const float density = params.extract_input("Density Max"); - const std::string density_attribute_name = params.extract_input( - "Density Attribute"); - - if (density <= 0.0f) { - params.set_output("Geometry", GeometrySet()); - return; - } - - Vector set_groups; - geometry_set_gather_instances(geometry_set, set_groups); - if (set_groups.is_empty()) { - params.set_output("Geometry", GeometrySet()); - return; - } - - /* 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 (set_groups.is_empty()) { - params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); - params.set_output("Geometry", GeometrySet()); - return; - } - - 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); - - switch (distribute_method) { - 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: { - const float minimum_distance = params.extract_input("Distance Min"); - distribute_points_poisson_disk(set_groups, - density_attribute_name, - density, - seed, - minimum_distance, - positions_all, - bary_coords_all, - looptri_indices_all); - break; - } - } - - 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(); - - Map attributes; - bke::geometry_set_gather_instances_attribute_info( - set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes); - 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() -{ - static bNodeType ntype; - - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); - node_type_update(&ntype, blender::nodes::node_point_distribute_update); - ntype.declare = blender::nodes::geo_node_point_distribute_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; - ntype.draw_buttons = blender::nodes::geo_node_point_distribute_layout; - nodeRegisterType(&ntype); -} -- cgit v1.2.3