Changeset View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
| Show All 23 Lines | |||||
| #include "DNA_mesh_types.h" | #include "DNA_mesh_types.h" | ||||
| #include "DNA_meshdata_types.h" | #include "DNA_meshdata_types.h" | ||||
| #include "DNA_pointcloud_types.h" | #include "DNA_pointcloud_types.h" | ||||
| #include "BKE_attribute_math.hh" | #include "BKE_attribute_math.hh" | ||||
| #include "BKE_bvhutils.h" | #include "BKE_bvhutils.h" | ||||
| #include "BKE_deform.h" | #include "BKE_deform.h" | ||||
| #include "BKE_geometry_set_instances.hh" | |||||
| #include "BKE_mesh.h" | #include "BKE_mesh.h" | ||||
| #include "BKE_mesh_runtime.h" | #include "BKE_mesh_runtime.h" | ||||
| #include "BKE_pointcloud.h" | #include "BKE_pointcloud.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "node_geometry_util.hh" | #include "node_geometry_util.hh" | ||||
| using blender::bke::AttributeKind; | |||||
| using blender::bke::GeometryInstanceGroup; | |||||
| static bNodeSocketTemplate geo_node_point_distribute_in[] = { | static bNodeSocketTemplate geo_node_point_distribute_in[] = { | ||||
| {SOCK_GEOMETRY, N_("Geometry")}, | {SOCK_GEOMETRY, N_("Geometry")}, | ||||
| {SOCK_FLOAT, N_("Distance Min"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, | {SOCK_FLOAT, N_("Distance Min"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, | ||||
| {SOCK_FLOAT, N_("Density Max"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, | {SOCK_FLOAT, N_("Density Max"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, | ||||
| {SOCK_STRING, N_("Density Attribute")}, | {SOCK_STRING, N_("Density Attribute")}, | ||||
| {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, | {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, | ||||
| {-1, ""}, | {-1, ""}, | ||||
| }; | }; | ||||
| Show All 35 Lines | |||||
| { | { | ||||
| /* This only updates a cache and can be considered to be logically const. */ | /* This only updates a cache and can be considered to be logically const. */ | ||||
| const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); | const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); | ||||
| const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); | const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); | ||||
| return {looptris, looptris_len}; | return {looptris, looptris_len}; | ||||
| } | } | ||||
| static void sample_mesh_surface(const Mesh &mesh, | static void sample_mesh_surface(const Mesh &mesh, | ||||
| const float4x4 &transform, | |||||
| const float base_density, | const float base_density, | ||||
| const FloatReadAttribute *density_factors, | const FloatReadAttribute *density_factors, | ||||
| const int seed, | const int seed, | ||||
| Vector<float3> &r_positions, | Vector<float3> &r_positions, | ||||
| Vector<float3> &r_bary_coords, | Vector<float3> &r_bary_coords, | ||||
| Vector<int> &r_looptri_indices) | Vector<int> &r_looptri_indices) | ||||
| { | { | ||||
| Span<MLoopTri> looptris = get_mesh_looptris(mesh); | Span<MLoopTri> looptris = get_mesh_looptris(mesh); | ||||
| for (const int looptri_index : looptris.index_range()) { | for (const int looptri_index : looptris.index_range()) { | ||||
| const MLoopTri &looptri = looptris[looptri_index]; | const MLoopTri &looptri = looptris[looptri_index]; | ||||
| const int v0_loop = looptri.tri[0]; | const int v0_loop = looptri.tri[0]; | ||||
| const int v1_loop = looptri.tri[1]; | const int v1_loop = looptri.tri[1]; | ||||
| const int v2_loop = looptri.tri[2]; | const int v2_loop = looptri.tri[2]; | ||||
| const int v0_index = mesh.mloop[v0_loop].v; | const int v0_index = mesh.mloop[v0_loop].v; | ||||
| const int v1_index = mesh.mloop[v1_loop].v; | const int v1_index = mesh.mloop[v1_loop].v; | ||||
| const int v2_index = mesh.mloop[v2_loop].v; | const int v2_index = mesh.mloop[v2_loop].v; | ||||
| const float3 v0_pos = mesh.mvert[v0_index].co; | const float3 v0_pos = transform * float3(mesh.mvert[v0_index].co); | ||||
| const float3 v1_pos = mesh.mvert[v1_index].co; | const float3 v1_pos = transform * float3(mesh.mvert[v1_index].co); | ||||
| const float3 v2_pos = mesh.mvert[v2_index].co; | const float3 v2_pos = transform * float3(mesh.mvert[v2_index].co); | ||||
HooglyBoogly: Unfortunately even non-instances will still have to do this matrix multiplication. It could be… | |||||
| float looptri_density_factor = 1.0f; | float looptri_density_factor = 1.0f; | ||||
| if (density_factors != nullptr) { | if (density_factors != nullptr) { | ||||
| const float v0_density_factor = std::max(0.0f, (*density_factors)[v0_loop]); | 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 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 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; | looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; | ||||
| } | } | ||||
| Show All 13 Lines | for (int i = 0; i < point_amount; i++) { | ||||
| interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); | interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); | ||||
| r_positions.append(point_pos); | r_positions.append(point_pos); | ||||
| r_bary_coords.append(bary_coord); | r_bary_coords.append(bary_coord); | ||||
| r_looptri_indices.append(looptri_index); | r_looptri_indices.append(looptri_index); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| BLI_NOINLINE static KDTree_3d *build_kdtree(Span<float3> positions) | BLI_NOINLINE static KDTree_3d *build_kdtree(Span<Vector<float3>> positions_array, | ||||
| const int initial_points_len) | |||||
| { | { | ||||
| KDTree_3d *kdtree = BLI_kdtree_3d_new(positions.size()); | KDTree_3d *kdtree = BLI_kdtree_3d_new(initial_points_len); | ||||
| for (const int i : positions.index_range()) { | |||||
| BLI_kdtree_3d_insert(kdtree, i, positions[i]); | int i_point = 0; | ||||
| for (const int i_instance : positions_array.index_range()) { | |||||
| Span<float3> positions = positions_array[i_instance]; | |||||
| for (const float3 position : positions) { | |||||
| BLI_kdtree_3d_insert(kdtree, i_point, position); | |||||
| i_point++; | |||||
| } | |||||
| } | } | ||||
| BLI_kdtree_3d_balance(kdtree); | BLI_kdtree_3d_balance(kdtree); | ||||
| return kdtree; | return kdtree; | ||||
| } | } | ||||
| BLI_NOINLINE static void update_elimination_mask_for_close_points( | BLI_NOINLINE static void update_elimination_mask_for_close_points( | ||||
HooglyBooglyAuthorUnsubmitted Done Inline ActionsThis function looks a little harder to parallelize now, I might have to think about restructuring this some. HooglyBoogly: This function looks a little harder to parallelize now, I might have to think about… | |||||
| Span<float3> positions, const float minimum_distance, MutableSpan<bool> elimination_mask) | Span<Vector<float3>> positions_array, | ||||
| const float minimum_distance, | |||||
| MutableSpan<bool> elimination_mask, | |||||
| const int initial_points_len) | |||||
| { | { | ||||
| if (minimum_distance <= 0.0f) { | if (minimum_distance <= 0.0f) { | ||||
| return; | return; | ||||
| } | } | ||||
| KDTree_3d *kdtree = build_kdtree(positions); | KDTree_3d *kdtree = build_kdtree(positions_array, initial_points_len); | ||||
| for (const int i : positions.index_range()) { | /* The elimination mask is a flattened array for every point, | ||||
| if (elimination_mask[i]) { | * so keep track of the index to it separately. */ | ||||
| int i_point = 0; | |||||
| for (Span<float3> positions : positions_array) { | |||||
| for (const float3 position : positions) { | |||||
| if (elimination_mask[i_point]) { | |||||
| i_point++; | |||||
| continue; | continue; | ||||
| } | } | ||||
| struct CallbackData { | struct CallbackData { | ||||
| int index; | int index; | ||||
Not Done Inline ActionsShould this be offset + i? JacquesLucke: Should this be `offset + i`? | |||||
| MutableSpan<bool> elimination_mask; | MutableSpan<bool> elimination_mask; | ||||
| } callback_data = {i, elimination_mask}; | } callback_data = {i_point, elimination_mask}; | ||||
| BLI_kdtree_3d_range_search_cb( | BLI_kdtree_3d_range_search_cb( | ||||
| kdtree, | kdtree, | ||||
| positions[i], | position, | ||||
| minimum_distance, | minimum_distance, | ||||
| [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { | [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { | ||||
| CallbackData &callback_data = *static_cast<CallbackData *>(user_data); | CallbackData &callback_data = *static_cast<CallbackData *>(user_data); | ||||
| if (index != callback_data.index) { | if (index != callback_data.index) { | ||||
| callback_data.elimination_mask[index] = true; | callback_data.elimination_mask[index] = true; | ||||
| } | } | ||||
| return true; | return true; | ||||
| }, | }, | ||||
| &callback_data); | &callback_data); | ||||
| i_point++; | |||||
| } | |||||
| } | } | ||||
| BLI_kdtree_3d_free(kdtree); | BLI_kdtree_3d_free(kdtree); | ||||
| } | } | ||||
| BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( | BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( | ||||
| const Mesh &mesh, | const Mesh &mesh, | ||||
| const FloatReadAttribute &density_factors, | const FloatReadAttribute &density_factors, | ||||
| Span<float3> bary_coords, | Span<float3> bary_coords, | ||||
| ▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | for (const int i : bary_coords.index_range()) { | ||||
| const T &v1 = data_in[loop_index_1]; | const T &v1 = data_in[loop_index_1]; | ||||
| const T &v2 = data_in[loop_index_2]; | const T &v2 = data_in[loop_index_2]; | ||||
| const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); | const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); | ||||
| data_out[i] = interpolated_value; | data_out[i] = interpolated_value; | ||||
| } | } | ||||
| } | } | ||||
| template<typename T> | |||||
| BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, | BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, | ||||
| Span<float3> bary_coords, | Span<float3> bary_coords, | ||||
| Span<int> looptri_indices, | Span<int> looptri_indices, | ||||
| const StringRef attribute_name, | const AttributeDomain source_domain, | ||||
| const ReadAttribute &attribute_in, | Span<T> source_span, | ||||
| GeometryComponent &component) | MutableSpan<T> output_span) | ||||
| { | { | ||||
| const CustomDataType data_type = attribute_in.custom_data_type(); | switch (source_domain) { | ||||
| 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<T>(); | |||||
| MutableSpan data_out = attribute_out->get_span_for_write_only<T>(); | |||||
| switch (domain) { | |||||
| case ATTR_DOMAIN_POINT: { | case ATTR_DOMAIN_POINT: { | ||||
| interpolate_attribute_point<T>(mesh, bary_coords, looptri_indices, data_in, data_out); | interpolate_attribute_point<T>(mesh, bary_coords, looptri_indices, source_span, output_span); | ||||
| break; | break; | ||||
| } | } | ||||
| case ATTR_DOMAIN_CORNER: { | case ATTR_DOMAIN_CORNER: { | ||||
| interpolate_attribute_corner<T>(mesh, bary_coords, looptri_indices, data_in, data_out); | interpolate_attribute_corner<T>( | ||||
| mesh, bary_coords, looptri_indices, source_span, output_span); | |||||
| break; | break; | ||||
| } | } | ||||
| default: { | default: { | ||||
| BLI_assert(false); | /* Not supported currently. */ | ||||
| break; | return; | ||||
| } | } | ||||
| } | } | ||||
| }); | |||||
| attribute_out.apply_span_and_save(); | |||||
| } | } | ||||
| BLI_NOINLINE static void interpolate_existing_attributes(const MeshComponent &mesh_component, | BLI_NOINLINE static void interpolate_existing_attributes( | ||||
| Span<GeometryInstanceGroup> set_groups, | |||||
| Span<int> group_start_indices, | |||||
| Map<std::string, AttributeKind> &attributes, | |||||
| GeometryComponent &component, | GeometryComponent &component, | ||||
| Span<float3> bary_coords, | Span<Vector<float3>> bary_coords_array, | ||||
| Span<int> looptri_indices) | Span<Vector<int>> looptri_indices_array) | ||||
| { | { | ||||
| const Mesh &mesh = *mesh_component.get_for_read(); | for (Map<std::string, AttributeKind>::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); | |||||
| BLI_assert(attribute_out); | |||||
| if (!attribute_out) { | |||||
| continue; | |||||
| } | |||||
| fn::GMutableSpan out_span = attribute_out->get_span_for_write_only(); | |||||
| Set<std::string> attribute_names = mesh_component.attribute_names(); | int i_set_with_mesh = 0; | ||||
| for (StringRefNull attribute_name : attribute_names) { | int i_instance = 0; | ||||
| if (ELEM(attribute_name, "position", "normal", "id")) { | for (const GeometryInstanceGroup &set_group : set_groups) { | ||||
| const GeometrySet &set = set_group.geometry_set; | |||||
| if (!set.has_mesh()) { | |||||
| continue; | continue; | ||||
| } | } | ||||
| const MeshComponent &source_component = *set.get_component_for_read<MeshComponent>(); | |||||
| const Mesh &mesh = *source_component.get_for_read(); | |||||
| ReadAttributePtr attribute_in = mesh_component.attribute_try_get_for_read(attribute_name); | /* Use a dummy read without specifying a domain or data type in order to | ||||
| interpolate_attribute( | * get the existing attribute's domain. Interpolation is done manually based | ||||
| mesh, bary_coords, looptri_indices, attribute_name, *attribute_in, component); | * 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(); | |||||
| i_set_with_mesh++; | |||||
| 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); | |||||
| BLI_assert(source_attribute); | |||||
| if (!source_attribute) { | |||||
| i_instance += set_group.transforms.size(); | |||||
Done Inline ActionsWe shouldn't assert that anymore. We might get issues where point clouds have different builtin attributes than mesh. JacquesLucke: We shouldn't assert that anymore. We might get issues where point clouds have different builtin… | |||||
| i_set_with_mesh++; | |||||
| continue; | |||||
| } | } | ||||
| fn::GSpan source_span = source_attribute->get_span(); | |||||
| BLI_NOINLINE static void compute_special_attributes(const Mesh &mesh, | attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { | ||||
| using T = decltype(dummy); | |||||
| int i_point = group_start_indices[i_set_with_mesh]; | |||||
| for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { | |||||
| Span<float3> bary_coords = bary_coords_array[i_instance].as_span(); | |||||
| Span<int> looptri_indices = looptri_indices_array[i_instance].as_span(); | |||||
| MutableSpan<T> instance_span = out_span.typed<T>().slice(i_point, bary_coords.size()); | |||||
| interpolate_attribute<T>(mesh, | |||||
| bary_coords, | |||||
| looptri_indices, | |||||
| source_domain, | |||||
| source_span.typed<T>(), | |||||
| instance_span); | |||||
| i_point += bary_coords.size(); | |||||
| i_instance++; | |||||
| } | |||||
| i_set_with_mesh++; | |||||
| }); | |||||
| } | |||||
| attribute_out.apply_span_and_save(); | |||||
| } | |||||
| } | |||||
| BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> sets, | |||||
| GeometryComponent &component, | GeometryComponent &component, | ||||
| Span<float3> bary_coords, | Span<Vector<float3>> bary_coords_array, | ||||
| Span<int> looptri_indices) | Span<Vector<int>> looptri_indices_array) | ||||
| { | { | ||||
| OutputAttributePtr id_attribute = component.attribute_try_get_for_output( | OutputAttributePtr id_attribute = component.attribute_try_get_for_output( | ||||
| "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); | "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); | ||||
| OutputAttributePtr normal_attribute = component.attribute_try_get_for_output( | OutputAttributePtr normal_attribute = component.attribute_try_get_for_output( | ||||
| "normal", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); | "normal", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); | ||||
| OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( | OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( | ||||
| "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); | "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); | ||||
| MutableSpan<int> ids = id_attribute->get_span_for_write_only<int>(); | MutableSpan<int> ids_full = id_attribute->get_span_for_write_only<int>(); | ||||
| MutableSpan<float3> normals = normal_attribute->get_span_for_write_only<float3>(); | MutableSpan<float3> normals_full = normal_attribute->get_span_for_write_only<float3>(); | ||||
| MutableSpan<float3> rotations = rotation_attribute->get_span_for_write_only<float3>(); | MutableSpan<float3> rotations_full = rotation_attribute->get_span_for_write_only<float3>(); | ||||
| int i_point = 0; | |||||
| int i_instance = 0; | |||||
Done Inline Actionssame here JacquesLucke: same here | |||||
| for (const GeometryInstanceGroup &set_group : sets) { | |||||
| const GeometrySet &set = set_group.geometry_set; | |||||
| if (!set.has_mesh()) { | |||||
| continue; | |||||
| } | |||||
| const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| Span<MLoopTri> looptris = get_mesh_looptris(mesh); | Span<MLoopTri> looptris = get_mesh_looptris(mesh); | ||||
| for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { | |||||
| Span<float3> bary_coords = bary_coords_array[i_instance].as_span(); | |||||
| Span<int> looptri_indices = looptri_indices_array[i_instance].as_span(); | |||||
| MutableSpan<int> ids = ids_full.slice(i_point, bary_coords.size()); | |||||
| MutableSpan<float3> normals = normals_full.slice(i_point, bary_coords.size()); | |||||
| MutableSpan<float3> rotations = rotations_full.slice(i_point, bary_coords.size()); | |||||
| for (const int i : bary_coords.index_range()) { | for (const int i : bary_coords.index_range()) { | ||||
| const int looptri_index = looptri_indices[i]; | const int looptri_index = looptri_indices[i]; | ||||
| const MLoopTri &looptri = looptris[looptri_index]; | const MLoopTri &looptri = looptris[looptri_index]; | ||||
| const float3 &bary_coord = bary_coords[i]; | const float3 &bary_coord = bary_coords[i]; | ||||
Done Inline ActionsIs this as_span() necessary? JacquesLucke: Is this `as_span()` necessary? | |||||
| const int v0_index = mesh.mloop[looptri.tri[0]].v; | const int v0_index = mesh.mloop[looptri.tri[0]].v; | ||||
| const int v1_index = mesh.mloop[looptri.tri[1]].v; | const int v1_index = mesh.mloop[looptri.tri[1]].v; | ||||
| const int v2_index = mesh.mloop[looptri.tri[2]].v; | const int v2_index = mesh.mloop[looptri.tri[2]].v; | ||||
| const float3 v0_pos = mesh.mvert[v0_index].co; | const float3 v0_pos = mesh.mvert[v0_index].co; | ||||
| const float3 v1_pos = mesh.mvert[v1_index].co; | const float3 v1_pos = mesh.mvert[v1_index].co; | ||||
| const float3 v2_pos = mesh.mvert[v2_index].co; | const float3 v2_pos = mesh.mvert[v2_index].co; | ||||
| ids[i] = (int)(bary_coord.hash() + (uint64_t)looptri_index); | ids[i] = (int)(bary_coord.hash()) + (uint64_t)looptri_index; | ||||
| normal_tri_v3(normals[i], v0_pos, v1_pos, v2_pos); | normal_tri_v3(normals[i], v0_pos, v1_pos, v2_pos); | ||||
| rotations[i] = normal_to_euler_rotation(normals[i]); | rotations[i] = normal_to_euler_rotation(normals[i]); | ||||
| } | } | ||||
| i_instance++; | |||||
| i_point += bary_coords.size(); | |||||
| } | |||||
| } | |||||
| id_attribute.apply_span_and_save(); | id_attribute.apply_span_and_save(); | ||||
| normal_attribute.apply_span_and_save(); | normal_attribute.apply_span_and_save(); | ||||
| rotation_attribute.apply_span_and_save(); | rotation_attribute.apply_span_and_save(); | ||||
| } | } | ||||
Done Inline ActionsDon't change the parentheses here, because it will trigger an asan overflow warning sometimes. The addition should be done on unsigned integers (which allow overflow). JacquesLucke: Don't change the parentheses here, because it will trigger an asan overflow warning sometimes. | |||||
Done Inline ActionsOops, I didn't mean to change that. Good catch. HooglyBoogly: Oops, I didn't mean to change that. Good catch. | |||||
| BLI_NOINLINE static void add_remaining_point_attributes(const MeshComponent &mesh_component, | BLI_NOINLINE static void add_remaining_point_attributes( | ||||
| Span<GeometryInstanceGroup> get_groups, | |||||
| Span<int> group_start_indices, | |||||
| Map<std::string, AttributeKind> &attributes, | |||||
Done Inline ActionsCan use a const Map? JacquesLucke: Can use a `const Map`? | |||||
| GeometryComponent &component, | GeometryComponent &component, | ||||
| Span<float3> bary_coords, | Span<Vector<float3>> bary_coords_array, | ||||
| Span<int> looptri_indices) | Span<Vector<int>> looptri_indices_array) | ||||
| { | |||||
| interpolate_existing_attributes(mesh_component, component, bary_coords, looptri_indices); | |||||
| compute_special_attributes( | |||||
| *mesh_component.get_for_read(), component, bary_coords, looptri_indices); | |||||
| } | |||||
| 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<float3> &r_positions, | |||||
| Vector<float3> &r_bary_coords, | |||||
| Vector<int> &r_looptri_indices) | |||||
| { | { | ||||
| sample_mesh_surface( | interpolate_existing_attributes(get_groups, | ||||
| mesh, max_density, nullptr, seed, r_positions, r_bary_coords, r_looptri_indices); | group_start_indices, | ||||
| Array<bool> elimination_mask(r_positions.size(), false); | attributes, | ||||
| update_elimination_mask_for_close_points(r_positions, minimum_distance, elimination_mask); | component, | ||||
| update_elimination_mask_based_on_density_factors( | bary_coords_array, | ||||
| mesh, density_factors, r_bary_coords, r_looptri_indices, elimination_mask); | looptri_indices_array); | ||||
| eliminate_points_based_on_mask(elimination_mask, r_positions, r_bary_coords, r_looptri_indices); | compute_special_attributes(get_groups, component, bary_coords_array, looptri_indices_array); | ||||
| } | } | ||||
| static void geo_node_point_distribute_exec(GeoNodeExecParams params) | static void geo_node_point_distribute_exec(GeoNodeExecParams params) | ||||
| { | { | ||||
| GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); | GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); | ||||
| GeometrySet geometry_set_out; | GeometrySet geometry_set_out; | ||||
| /* TODO: This node only needs read-only access to input instances. */ | SCOPED_TIMER(__func__); | ||||
| geometry_set = geometry_set_realize_instances(geometry_set); | |||||
| GeometryNodePointDistributeMode distribute_method = static_cast<GeometryNodePointDistributeMode>( | const GeometryNodePointDistributeMode distribute_method = | ||||
| params.node().custom1); | static_cast<GeometryNodePointDistributeMode>(params.node().custom1); | ||||
| if (!geometry_set.has_mesh()) { | if (!geometry_set.has_mesh() && !geometry_set.has_instances()) { | ||||
| params.error_message_add(NodeWarningType::Error, "Geometry must contain a mesh."); | params.error_message_add(NodeWarningType::Error, "Geometry must contain a mesh."); | ||||
| params.set_output("Geometry", std::move(geometry_set_out)); | params.set_output("Geometry", std::move(geometry_set_out)); | ||||
| return; | return; | ||||
| } | } | ||||
| const float density = params.extract_input<float>("Density Max"); | const float density = params.extract_input<float>("Density Max"); | ||||
| const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); | const std::string density_attribute_name = params.extract_input<std::string>( | ||||
| "Density Attribute"); | |||||
| if (density <= 0.0f) { | if (density <= 0.0f) { | ||||
| params.set_output("Geometry", std::move(geometry_set_out)); | params.set_output("Geometry", std::move(geometry_set_out)); | ||||
| return; | return; | ||||
| } | } | ||||
| const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); | const int seed = params.get_input<int>("Seed"); | ||||
| const Mesh *mesh_in = mesh_component.get_for_read(); | |||||
| if (mesh_in->mpoly == nullptr) { | Vector<GeometryInstanceGroup> get_groups = bke::geometry_set_gather_instances(geometry_set); | ||||
JacquesLuckeUnsubmitted Done Inline ActionsDid you want to call this geo_groups or something like that? JacquesLucke: Did you want to call this `geo_groups` or something like that?
| |||||
HooglyBooglyAuthorUnsubmitted Done Inline ActionsOops, that's a typo of set_groups HooglyBoogly: Oops, that's a typo of `set_groups` | |||||
Not Done Inline ActionsI don't really think we should warn in such cases. There are probably valid cases where in some frames there will be a mesh and in others there is not. JacquesLucke: I don't really think we should warn in such cases. There are probably valid cases where in some… | |||||
Done Inline ActionsYes, I'm fine with removing this message, best to check if there are other places that have similar errors though. HooglyBoogly: Yes, I'm fine with removing this message, best to check if there are other places that have… | |||||
| params.error_message_add(NodeWarningType::Error, "Mesh has no faces."); | int instances_len = 0; | ||||
| params.set_output("Geometry", std::move(geometry_set_out)); | for (GeometryInstanceGroup set_group : get_groups) { | ||||
| return; | const GeometrySet &set = set_group.geometry_set; | ||||
| if (set.has_mesh()) { | |||||
| instances_len += set_group.transforms.size(); | |||||
JacquesLuckeUnsubmitted Done Inline ActionsHow about you just remove all the groups from the vector above, that don't have a mesh. Then you don't need to check for it everywhere. JacquesLucke: How about you just remove all the groups from the vector above, that don't have a mesh. Then… | |||||
HooglyBooglyAuthorUnsubmitted Done Inline ActionsThat's a good idea, I'll do that. HooglyBoogly: That's a good idea, I'll do that. | |||||
| } | |||||
| } | } | ||||
| const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( | /* Store data per-instance in order to simplify attribute access after the scattering, | ||||
| density_attribute, ATTR_DOMAIN_CORNER, 1.0f); | * and to make the point elimination simpler for the poisson disk mode. Node that some | ||||
JacquesLuckeUnsubmitted Done Inline Actionstypo (note) JacquesLucke: typo (note) | |||||
| const int seed = params.get_input<int>("Seed"); | * vectors will be empty if any instances don't contain mesh data. */ | ||||
| Array<Vector<float3>> positions_array(instances_len); | |||||
| Array<Vector<float3>> bary_coords_array(instances_len); | |||||
| Array<Vector<int>> looptri_indices_array(instances_len); | |||||
| int initial_points_len = 0; | |||||
| /* The index of the current instance not including instances from sets that don't have a mesh. */ | |||||
| int i_instance = 0; | |||||
| for (const GeometryInstanceGroup &set_group : get_groups) { | |||||
JacquesLuckeUnsubmitted Done Inline ActionsLooks like it would be a good idea to extract this loop to a separate function. JacquesLucke: Looks like it would be a good idea to extract this loop to a separate function. | |||||
| const GeometrySet &set = set_group.geometry_set; | |||||
| if (!set.has_mesh()) { | |||||
| continue; | |||||
| } | |||||
| const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| for (const float4x4 &transform : set_group.transforms) { | |||||
| Vector<float3> &positions = positions_array[i_instance]; | |||||
| Vector<float3> &bary_coords = bary_coords_array[i_instance]; | |||||
| Vector<int> &looptri_indices = looptri_indices_array[i_instance]; | |||||
| Vector<float3> positions; | |||||
| Vector<float3> bary_coords; | |||||
| Vector<int> looptri_indices; | |||||
| switch (distribute_method) { | switch (distribute_method) { | ||||
| case GEO_NODE_POINT_DISTRIBUTE_RANDOM: | case GEO_NODE_POINT_DISTRIBUTE_RANDOM: { | ||||
| sample_mesh_surface( | const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( | ||||
| *mesh_in, density, &density_factors, seed, positions, bary_coords, looptri_indices); | density_attribute_name, ATTR_DOMAIN_CORNER, 1.0f); | ||||
| break; | sample_mesh_surface(mesh, | ||||
| case GEO_NODE_POINT_DISTRIBUTE_POISSON: | transform, | ||||
| const float minimum_distance = params.extract_input<float>("Distance Min"); | |||||
| sample_mesh_surface_with_minimum_distance(*mesh_in, | |||||
| density, | density, | ||||
| minimum_distance, | &density_factors, | ||||
| density_factors, | |||||
| seed, | seed, | ||||
| positions, | positions, | ||||
| bary_coords, | bary_coords, | ||||
| looptri_indices); | looptri_indices); | ||||
| break; | break; | ||||
| } | } | ||||
| const int tot_points = positions.size(); | case GEO_NODE_POINT_DISTRIBUTE_POISSON: | ||||
| sample_mesh_surface( | |||||
| mesh, transform, density, nullptr, seed, positions, bary_coords, looptri_indices); | |||||
| break; | |||||
| } | |||||
| initial_points_len += positions.size(); | |||||
| i_instance++; | |||||
| } | |||||
| } | |||||
| PointCloud *pointcloud = BKE_pointcloud_new_nomain(tot_points); | if (distribute_method == GEO_NODE_POINT_DISTRIBUTE_POISSON) { | ||||
| memcpy(pointcloud->co, positions.data(), sizeof(float3) * tot_points); | /* Unlike the other result arrays, the elimination mask in stored as a flat array for every | ||||
| for (const int i : positions.index_range()) { | * point, in order to simplify culling points from the KDTree (which needs to know about all | ||||
| *(float3 *)(pointcloud->co + i) = positions[i]; | * points at once). */ | ||||
| pointcloud->radius[i] = 0.05f; | Array<bool> elimination_mask(initial_points_len, false); | ||||
| const float minimum_distance = params.get_input<float>("Distance Min"); | |||||
| update_elimination_mask_for_close_points( | |||||
| positions_array, minimum_distance, elimination_mask, initial_points_len); | |||||
| int i_point = 0; | |||||
| i_instance = 0; | |||||
| for (const GeometryInstanceGroup &set_group : get_groups) { | |||||
| const GeometrySet &set = set_group.geometry_set; | |||||
| if (!set.has_mesh()) { | |||||
| continue; | |||||
| } | } | ||||
| const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( | |||||
| density_attribute_name, ATTR_DOMAIN_CORNER, 1.0f); | |||||
| for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { | |||||
| Vector<float3> &positions = positions_array[i_instance]; | |||||
| Vector<float3> &bary_coords = bary_coords_array[i_instance]; | |||||
| Vector<int> &looptri_indices = looptri_indices_array[i_instance]; | |||||
| update_elimination_mask_based_on_density_factors( | |||||
| mesh, | |||||
| density_factors, | |||||
| bary_coords, | |||||
| looptri_indices, | |||||
| elimination_mask.as_mutable_span().slice(i_point, positions.size())); | |||||
| /* The positions vector's size is changed, temporarily store the | |||||
| * original size to properly advance the elimination mask index. */ | |||||
| const int initial_positions_size = positions.size(); | |||||
| eliminate_points_based_on_mask(elimination_mask.as_span().slice(i_point, positions.size()), | |||||
| positions, | |||||
| bary_coords, | |||||
| looptri_indices); | |||||
| i_point += initial_positions_size; | |||||
| i_instance++; | |||||
| } | |||||
| } | |||||
| } | |||||
| int final_points_len = 0; | |||||
| Array<int> group_start_indices(get_groups.size()); | |||||
| for (const int i : positions_array.index_range()) { | |||||
| Vector<float3> &positions = positions_array[i]; | |||||
| group_start_indices[i] = final_points_len; | |||||
| final_points_len += positions.size(); | |||||
| } | |||||
| PointCloud *pointcloud = BKE_pointcloud_new_nomain(final_points_len); | |||||
| int i_point = 0; | |||||
| for (Vector<float3> &positions : positions_array) { | |||||
| memcpy(pointcloud->co + i_point, positions.data(), sizeof(float3) * positions.size()); | |||||
| i_point += positions.size(); | |||||
| } | |||||
| MutableSpan(pointcloud->radius, pointcloud->totpoint).fill(0.05f); | |||||
Done Inline ActionsCan use uninitialized_fill_n. JacquesLucke: Can use `uninitialized_fill_n`. | |||||
| PointCloudComponent &point_component = | PointCloudComponent &point_component = | ||||
| geometry_set_out.get_component_for_write<PointCloudComponent>(); | geometry_set_out.get_component_for_write<PointCloudComponent>(); | ||||
| point_component.replace(pointcloud); | point_component.replace(pointcloud); | ||||
| add_remaining_point_attributes(mesh_component, point_component, bary_coords, looptri_indices); | Map<std::string, AttributeKind> attributes; | ||||
| bke::gather_attribute_info( | |||||
| attributes, {GeometryComponentType::Mesh}, get_groups, {"position", "normal", "id"}); | |||||
| add_remaining_point_attributes(get_groups, | |||||
| group_start_indices, | |||||
| attributes, | |||||
| point_component, | |||||
| bary_coords_array, | |||||
| looptri_indices_array); | |||||
| params.set_output("Geometry", std::move(geometry_set_out)); | params.set_output("Geometry", std::move(geometry_set_out)); | ||||
| } | } | ||||
| } // namespace blender::nodes | } // namespace blender::nodes | ||||
| void register_node_type_geo_point_distribute() | void register_node_type_geo_point_distribute() | ||||
| { | { | ||||
| static bNodeType ntype; | static bNodeType ntype; | ||||
| geo_node_type_base( | geo_node_type_base( | ||||
| &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); | &ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", NODE_CLASS_GEOMETRY, 0); | ||||
| node_type_socket_templates(&ntype, geo_node_point_distribute_in, geo_node_point_distribute_out); | node_type_socket_templates(&ntype, geo_node_point_distribute_in, geo_node_point_distribute_out); | ||||
| node_type_update(&ntype, node_point_distribute_update); | node_type_update(&ntype, node_point_distribute_update); | ||||
| ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; | ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; | ||||
| ntype.draw_buttons = geo_node_point_distribute_layout; | ntype.draw_buttons = geo_node_point_distribute_layout; | ||||
| nodeRegisterType(&ntype); | nodeRegisterType(&ntype); | ||||
| } | } | ||||
Unfortunately even non-instances will still have to do this matrix multiplication. It could be templated to be skipped when the transform is a unit transform, but I would just be guessing, I haven't profiled to see if it's actually a bottleneck.