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; | ||||
| } | } | ||||
| const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); | const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); | ||||
| Show All 12 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_all, | ||||
| 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_all.index_range()) { | |||||
| Span<float3> positions = positions_all[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( | ||||
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_all, | ||||
| Span<int> instance_start_offsets, | |||||
| 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_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<float3> positions = positions_all[i_instance]; | |||||
| const int offset = instance_start_offsets[i_instance]; | |||||
| for (const int i : positions.index_range()) { | for (const int i : positions.index_range()) { | ||||
| if (elimination_mask[i]) { | if (elimination_mask[offset]) { | ||||
JacquesLuckeUnsubmitted Not Done Inline ActionsShould this be offset + i? JacquesLucke: Should this be `offset + i`? | |||||
| continue; | continue; | ||||
| } | } | ||||
| struct CallbackData { | struct CallbackData { | ||||
| int index; | int index; | ||||
| MutableSpan<bool> elimination_mask; | MutableSpan<bool> elimination_mask; | ||||
| } callback_data = {i, elimination_mask}; | } callback_data = {offset + i, elimination_mask}; | ||||
| BLI_kdtree_3d_range_search_cb( | BLI_kdtree_3d_range_search_cb( | ||||
| kdtree, | kdtree, | ||||
| positions[i], | positions[i], | ||||
| 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); | ||||
| } | } | ||||
| } | |||||
| 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, | ||||
| Span<int> looptri_indices, | Span<int> looptri_indices, | ||||
| Show All 22 Lines | for (const int i : bary_coords.index_range()) { | ||||
| const float hash = BLI_hash_int_01(bary_coord.hash()); | const float hash = BLI_hash_int_01(bary_coord.hash()); | ||||
| if (hash > probablity) { | if (hash > probablity) { | ||||
| elimination_mask[i] = true; | elimination_mask[i] = true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| BLI_NOINLINE static void eliminate_points_based_on_mask(Span<bool> elimination_mask, | BLI_NOINLINE static void eliminate_points_based_on_mask(Span<bool> elimination_mask, | ||||
| Vector<float3> &positions, | Vector<float3> positions, | ||||
| Vector<float3> &bary_coords, | Vector<float3> bary_coords, | ||||
| Vector<int> &looptri_indices) | Vector<int> looptri_indices) | ||||
| { | { | ||||
| for (int i = positions.size() - 1; i >= 0; i--) { | for (int i = positions.size() - 1; i >= 0; i--) { | ||||
| if (elimination_mask[i]) { | if (elimination_mask[i]) { | ||||
| positions.remove_and_reorder(i); | positions.remove_and_reorder(i); | ||||
| bary_coords.remove_and_reorder(i); | bary_coords.remove_and_reorder(i); | ||||
| looptri_indices.remove_and_reorder(i); | looptri_indices.remove_and_reorder(i); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 49 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> instance_start_offsets, | |||||
| 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); | |||||
JacquesLuckeUnsubmitted 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… | |||||
| 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_instance = 0; | ||||
| for (StringRefNull attribute_name : attribute_names) { | for (const GeometryInstanceGroup &set_group : set_groups) { | ||||
| if (ELEM(attribute_name, "position", "normal", "id")) { | const GeometrySet &set = set_group.geometry_set; | ||||
| const MeshComponent &source_component = *set.get_component_for_read<MeshComponent>(); | |||||
| const Mesh &mesh = *source_component.get_for_read(); | |||||
| /* Use a dummy read without specifying a domain or data type in order to | |||||
| * get the existing attribute's domain. Interpolation is done manually based | |||||
| * on the bary coords in #interpolate_attribute. */ | |||||
| ReadAttributePtr dummy_attribute = source_component.attribute_try_get_for_read( | |||||
| attribute_name); | |||||
| if (!dummy_attribute) { | |||||
| i_instance += set_group.transforms.size(); | |||||
| continue; | continue; | ||||
| } | } | ||||
| ReadAttributePtr attribute_in = mesh_component.attribute_try_get_for_read(attribute_name); | const AttributeDomain source_domain = dummy_attribute->domain(); | ||||
| interpolate_attribute( | ReadAttributePtr source_attribute = source_component.attribute_get_for_read( | ||||
| mesh, bary_coords, looptri_indices, attribute_name, *attribute_in, component); | attribute_name, source_domain, output_data_type, nullptr); | ||||
| BLI_assert(source_attribute); | |||||
JacquesLuckeUnsubmitted Done Inline Actionssame here JacquesLucke: same here | |||||
| if (!source_attribute) { | |||||
| i_instance += set_group.transforms.size(); | |||||
| continue; | |||||
| } | } | ||||
| fn::GSpan source_span = source_attribute->get_span(); | |||||
| attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { | |||||
| using T = decltype(dummy); | |||||
| for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { | |||||
| const int offset = instance_start_offsets[i_instance]; | |||||
| Span<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(offset, bary_coords.size()); | |||||
| interpolate_attribute<T>(mesh, | |||||
| bary_coords, | |||||
| looptri_indices, | |||||
| source_domain, | |||||
| source_span.typed<T>(), | |||||
| instance_span); | |||||
| i_instance++; | |||||
| } | |||||
| }); | |||||
| } | } | ||||
| BLI_NOINLINE static void compute_special_attributes(const Mesh &mesh, | attribute_out.apply_span_and_save(); | ||||
| } | |||||
| } | |||||
| BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> sets, | |||||
| Span<int> instance_start_offsets, | |||||
| 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> result_ids = id_attribute->get_span_for_write_only<int>(); | ||||
| MutableSpan<float3> normals = normal_attribute->get_span_for_write_only<float3>(); | MutableSpan<float3> result_normals = normal_attribute->get_span_for_write_only<float3>(); | ||||
| MutableSpan<float3> rotations = rotation_attribute->get_span_for_write_only<float3>(); | MutableSpan<float3> result_rotations = rotation_attribute->get_span_for_write_only<float3>(); | ||||
| 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<MeshComponent>(); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| Span<MLoopTri> looptris = get_mesh_looptris(mesh); | Span<MLoopTri> looptris = get_mesh_looptris(mesh); | ||||
| for (const float4x4 &transform : set_group.transforms) { | |||||
| const int offset = instance_start_offsets[i_instance]; | |||||
| Span<float3> bary_coords = bary_coords_array[i_instance].as_span(); | |||||
JacquesLuckeUnsubmitted Done Inline ActionsIs this as_span() necessary? JacquesLucke: Is this `as_span()` necessary? | |||||
| Span<int> looptri_indices = looptri_indices_array[i_instance].as_span(); | |||||
| MutableSpan<int> ids = result_ids.slice(offset, bary_coords.size()); | |||||
| MutableSpan<float3> normals = result_normals.slice(offset, bary_coords.size()); | |||||
| MutableSpan<float3> 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()) { | 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]; | ||||
| 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 = float3(mesh.mvert[v0_index].co); | ||||
| const float3 v1_pos = mesh.mvert[v1_index].co; | const float3 v1_pos = float3(mesh.mvert[v1_index].co); | ||||
| const float3 v2_pos = mesh.mvert[v2_index].co; | const float3 v2_pos = float3(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; | ||||
JacquesLuckeUnsubmitted 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. | |||||
HooglyBooglyAuthorUnsubmitted Done Inline ActionsOops, I didn't mean to change that. Good catch. HooglyBoogly: Oops, I didn't mean to change that. Good catch. | |||||
| normal_tri_v3(normals[i], v0_pos, v1_pos, v2_pos); | 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]); | rotations[i] = normal_to_euler_rotation(normals[i]); | ||||
| } | } | ||||
| i_instance++; | |||||
| } | |||||
| } | |||||
| 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(); | ||||
| } | } | ||||
| BLI_NOINLINE static void add_remaining_point_attributes(const MeshComponent &mesh_component, | BLI_NOINLINE static void add_remaining_point_attributes( | ||||
| Span<GeometryInstanceGroup> set_groups, | |||||
| Span<int> instance_start_offsets, | |||||
| Map<std::string, AttributeKind> &attributes, | |||||
JacquesLuckeUnsubmitted 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); | interpolate_existing_attributes(set_groups, | ||||
| instance_start_offsets, | |||||
| attributes, | |||||
| component, | |||||
| bary_coords_array, | |||||
| looptri_indices_array); | |||||
| compute_special_attributes( | compute_special_attributes( | ||||
| *mesh_component.get_for_read(), component, bary_coords, looptri_indices); | set_groups, instance_start_offsets, component, bary_coords_array, looptri_indices_array); | ||||
| } | } | ||||
| static void sample_mesh_surface_with_minimum_distance(const Mesh &mesh, | static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, | ||||
| const float max_density, | const StringRef density_attribute_name, | ||||
| const float minimum_distance, | const float density, | ||||
| const FloatReadAttribute &density_factors, | |||||
| const int seed, | const int seed, | ||||
| Vector<float3> &r_positions, | MutableSpan<Vector<float3>> positions_all, | ||||
| Vector<float3> &r_bary_coords, | MutableSpan<Vector<float3>> bary_coords_all, | ||||
| Vector<int> &r_looptri_indices) | MutableSpan<Vector<int>> 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<MeshComponent>(); | |||||
| const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( | |||||
| density_attribute_name, ATTR_DOMAIN_CORNER, 1.0f); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| for (const float4x4 &transform : set_group.transforms) { | |||||
| Vector<float3> &positions = positions_all[i_instance]; | |||||
| Vector<float3> &bary_coords = bary_coords_all[i_instance]; | |||||
| Vector<int> &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<GeometryInstanceGroup> set_groups, | |||||
| const StringRef density_attribute_name, | |||||
| const float density, | |||||
| const int seed, | |||||
| const float minimum_distance, | |||||
| MutableSpan<Vector<float3>> positions_all, | |||||
| MutableSpan<Vector<float3>> bary_coords_all, | |||||
| MutableSpan<Vector<int>> looptri_indices_all) | |||||
| { | |||||
| Array<int> 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<MeshComponent>(); | |||||
| const Mesh &mesh = *component.get_for_read(); | |||||
| for (const float4x4 &transform : set_group.transforms) { | |||||
| Vector<float3> &positions = positions_all[i_instance]; | |||||
| Vector<float3> &bary_coords = bary_coords_all[i_instance]; | |||||
| Vector<int> &looptri_indices = looptri_indices_all[i_instance]; | |||||
| sample_mesh_surface( | sample_mesh_surface( | ||||
| mesh, max_density, nullptr, seed, r_positions, r_bary_coords, r_looptri_indices); | mesh, transform, density, nullptr, seed, positions, bary_coords, looptri_indices); | ||||
| Array<bool> elimination_mask(r_positions.size(), false); | |||||
| update_elimination_mask_for_close_points(r_positions, minimum_distance, elimination_mask); | 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<bool> 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<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_all[i_instance]; | |||||
| Vector<float3> &bary_coords = bary_coords_all[i_instance]; | |||||
| Vector<int> &looptri_indices = looptri_indices_all[i_instance]; | |||||
| const int offset = instance_start_offsets[i_instance]; | |||||
| update_elimination_mask_based_on_density_factors( | update_elimination_mask_based_on_density_factors( | ||||
| mesh, density_factors, r_bary_coords, r_looptri_indices, elimination_mask); | mesh, | ||||
| eliminate_points_based_on_mask(elimination_mask, r_positions, r_bary_coords, r_looptri_indices); | 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) | 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; | |||||
| /* TODO: This node only needs read-only access to input instances. */ | const GeometryNodePointDistributeMode distribute_method = | ||||
| geometry_set = geometry_set_realize_instances(geometry_set); | static_cast<GeometryNodePointDistributeMode>(params.node().custom1); | ||||
| GeometryNodePointDistributeMode distribute_method = static_cast<GeometryNodePointDistributeMode>( | const int seed = params.get_input<int>("Seed"); | ||||
| params.node().custom1); | const float density = params.extract_input<float>("Density Max"); | ||||
| const std::string density_attribute_name = params.extract_input<std::string>( | |||||
| "Density Attribute"); | |||||
| if (!geometry_set.has_mesh()) { | if (density <= 0.0f) { | ||||
| params.error_message_add(NodeWarningType::Error, TIP_("Geometry must contain a mesh")); | params.set_output("Geometry", std::move(GeometrySet())); | ||||
| params.set_output("Geometry", std::move(geometry_set_out)); | |||||
| return; | return; | ||||
| } | } | ||||
| const float density = params.extract_input<float>("Density Max"); | Vector<GeometryInstanceGroup> set_groups = bke::geometry_set_gather_instances(geometry_set); | ||||
| const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); | if (set_groups.is_empty()) { | ||||
| params.set_output("Geometry", std::move(GeometrySet())); | |||||
| if (density <= 0.0f) { | |||||
| params.set_output("Geometry", std::move(geometry_set_out)); | |||||
| return; | return; | ||||
| } | } | ||||
| const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); | /* Remove any set inputs that don't contain a mesh, to avoid checking later on. */ | ||||
| const Mesh *mesh_in = mesh_component.get_for_read(); | for (int i = set_groups.size() - 1; i >= 0; i--) { | ||||
| const GeometrySet &set = set_groups[i].geometry_set; | |||||
| if (!set.has_mesh()) { | |||||
| set_groups.remove_and_reorder(i); | |||||
| } | |||||
| } | |||||
| if (mesh_in->mpoly == nullptr) { | if (set_groups.is_empty()) { | ||||
| params.error_message_add(NodeWarningType::Error, TIP_("Mesh has no faces")); | params.error_message_add(NodeWarningType::Error, TIP_("Input geometry must contain a mesh")); | ||||
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.set_output("Geometry", std::move(geometry_set_out)); | params.set_output("Geometry", std::move(GeometrySet())); | ||||
| return; | return; | ||||
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?
| |||||
Done Inline ActionsOops, that's a typo of set_groups HooglyBoogly: Oops, that's a typo of `set_groups` | |||||
| } | } | ||||
| const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( | int instances_len = 0; | ||||
| density_attribute, ATTR_DOMAIN_CORNER, 1.0f); | for (GeometryInstanceGroup &set_group : set_groups) { | ||||
Done Inline Actionstypo (note) JacquesLucke: typo (note) | |||||
| const int seed = params.get_input<int>("Seed"); | instances_len += set_group.transforms.size(); | ||||
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… | |||||
Done Inline ActionsThat's a good idea, I'll do that. HooglyBoogly: That's a good idea, I'll do that. | |||||
| } | |||||
| /* 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<Vector<float3>> positions_all(instances_len); | |||||
| Array<Vector<float3>> bary_coords_all(instances_len); | |||||
| Array<Vector<int>> looptri_indices_all(instances_len); | |||||
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. | |||||
| 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( | distribute_points_random(set_groups, | ||||
| *mesh_in, density, &density_factors, seed, positions, bary_coords, looptri_indices); | density_attribute_name, | ||||
| density, | |||||
| seed, | |||||
| positions_all, | |||||
| bary_coords_all, | |||||
| looptri_indices_all); | |||||
| break; | break; | ||||
| case GEO_NODE_POINT_DISTRIBUTE_POISSON: | } | ||||
| case GEO_NODE_POINT_DISTRIBUTE_POISSON: { | |||||
| const float minimum_distance = params.extract_input<float>("Distance Min"); | const float minimum_distance = params.extract_input<float>("Distance Min"); | ||||
| sample_mesh_surface_with_minimum_distance(*mesh_in, | distribute_points_poisson_disk(set_groups, | ||||
| density_attribute_name, | |||||
| density, | density, | ||||
| minimum_distance, | |||||
| density_factors, | |||||
| seed, | seed, | ||||
| positions, | minimum_distance, | ||||
| bary_coords, | positions_all, | ||||
| looptri_indices); | bary_coords_all, | ||||
| looptri_indices_all); | |||||
| break; | break; | ||||
| } | } | ||||
| const int tot_points = positions.size(); | } | ||||
| PointCloud *pointcloud = BKE_pointcloud_new_nomain(tot_points); | int final_points_len = 0; | ||||
| memcpy(pointcloud->co, positions.data(), sizeof(float3) * tot_points); | Array<int> instance_start_offsets(set_groups.size()); | ||||
| for (const int i : positions.index_range()) { | for (const int i : positions_all.index_range()) { | ||||
| *(float3 *)(pointcloud->co + i) = positions[i]; | Vector<float3> &positions = positions_all[i]; | ||||
| pointcloud->radius[i] = 0.05f; | 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<float3> positions = positions_all[instance_index]; | |||||
| memcpy(pointcloud->co + offset, positions.data(), sizeof(float3) * positions.size()); | |||||
| } | |||||
| MutableSpan(pointcloud->radius, pointcloud->totpoint).fill(0.05f); | |||||
JacquesLuckeUnsubmitted Done Inline ActionsCan use uninitialized_fill_n. JacquesLucke: Can use `uninitialized_fill_n`. | |||||
| GeometrySet geometry_set_out = GeometrySet::create_with_pointcloud(pointcloud); | |||||
| 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); | |||||
| add_remaining_point_attributes(mesh_component, point_component, bary_coords, looptri_indices); | Map<std::string, AttributeKind> attributes; | ||||
| bke::gather_attribute_info( | |||||
| attributes, {GeometryComponentType::Mesh}, set_groups, {"position", "normal", "id"}); | |||||
| add_remaining_point_attributes(set_groups, | |||||
| instance_start_offsets, | |||||
| attributes, | |||||
| point_component, | |||||
| bary_coords_all, | |||||
| looptri_indices_all); | |||||
| params.set_output("Geometry", std::move(geometry_set_out)); | 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.