diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc | 206 |
1 files changed, 198 insertions, 8 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index 2f5f7e264bc..1d3fbae5b2e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -24,6 +24,7 @@ #include "DNA_meshdata_types.h" #include "DNA_pointcloud_types.h" +#include "BKE_bvhutils.h" #include "BKE_deform.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" @@ -33,8 +34,10 @@ static bNodeSocketTemplate geo_node_point_distribute_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, - {SOCK_FLOAT, N_("Density"), 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_FLOAT, N_("Distance Min"), 0.1f, 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_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; @@ -43,11 +46,20 @@ static bNodeSocketTemplate geo_node_point_distribute_out[] = { {-1, ""}, }; +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)); +} + namespace blender::nodes { -static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, - const float density, - const FloatReadAttribute &density_factors) +static Vector<float3> random_scatter_points_from_mesh(const Mesh *mesh, + const float density, + const FloatReadAttribute &density_factors, + Vector<int> &r_ids, + const int seed) { /* 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)); @@ -71,7 +83,7 @@ static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, 3.0f; const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); - const int looptri_seed = BLI_hash_int(looptri_index); + const int looptri_seed = BLI_hash_int(looptri_index + seed); RandomNumberGenerator looptri_rng(looptri_seed); const float points_amount_fl = area * density * looptri_density_factor; @@ -84,23 +96,178 @@ static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, float3 point_pos; interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coords); points.append(point_pos); + + /* Build a hash stable even when the mesh is deformed. */ + r_ids.append(((int)(bary_coords.hash()) + looptri_index)); } } return points; } +struct RayCastAll_Data { + void *bvhdata; + + BVHTree_RayCastCallback raycast_callback; + + /** The original coordinate the result point was projected from. */ + float2 raystart; + + const Mesh *mesh; + float base_weight; + FloatReadAttribute *density_factors; + Vector<float3> *projected_points; + Vector<int> *stable_ids; + float cur_point_weight; +}; + +static void project_2d_bvh_callback(void *userdata, + int index, + const BVHTreeRay *ray, + BVHTreeRayHit *hit) +{ + struct RayCastAll_Data *data = (RayCastAll_Data *)userdata; + data->raycast_callback(data->bvhdata, index, ray, hit); + if (hit->index != -1) { + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(data->mesh)); + const MVert *mvert = data->mesh->mvert; + + const MLoopTri &looptri = looptris[index]; + const FloatReadAttribute &density_factors = data->density_factors[0]; + + const int v0_index = data->mesh->mloop[looptri.tri[0]].v; + const int v1_index = data->mesh->mloop[looptri.tri[1]].v; + const int v2_index = data->mesh->mloop[looptri.tri[2]].v; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_index]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_index]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_index]); + + /* Calculate barycentric weights for hit point. */ + float3 weights; + interp_weights_tri_v3( + weights, mvert[v0_index].co, mvert[v1_index].co, mvert[v2_index].co, hit->co); + + float point_weight = weights[0] * v0_density_factor + weights[1] * v1_density_factor + + weights[2] * v2_density_factor; + + point_weight *= data->base_weight; + + if (point_weight >= FLT_EPSILON && data->cur_point_weight <= point_weight) { + data->projected_points->append(hit->co); + + /* Build a hash stable even when the mesh is deformed. */ + data->stable_ids->append((int)data->raystart.hash()); + } + } +} + +static Vector<float3> poisson_scatter_points_from_mesh(const Mesh *mesh, + const float density, + const float minimum_distance, + const FloatReadAttribute &density_factors, + Vector<int> &r_ids, + const int seed) +{ + Vector<float3> points; + + if (minimum_distance <= FLT_EPSILON || density <= FLT_EPSILON) { + return points; + } + + /* Scatter points randomly on the mesh with higher density (5-7) times higher than desired for + * good quality possion disk distributions. */ + int quality = 5; + const int output_points_target = 1000; + points.resize(output_points_target * quality); + + const float required_area = output_points_target * + (2.0f * sqrtf(3.0f) * minimum_distance * minimum_distance); + const float point_scale_multiplier = sqrtf(required_area); + + { + const int rnd_seed = BLI_hash_int(seed); + RandomNumberGenerator point_rng(rnd_seed); + + for (int i = 0; i < points.size(); i++) { + points[i].x = point_rng.get_float() * point_scale_multiplier; + points[i].y = point_rng.get_float() * point_scale_multiplier; + points[i].z = 0.0f; + } + } + + /* Eliminate the scattered points until we get a possion distribution. */ + Vector<float3> output_points(output_points_target); + + const float3 bounds_max = float3(point_scale_multiplier, point_scale_multiplier, 0); + poisson_disk_point_elimination(&points, &output_points, 2.0f * minimum_distance, bounds_max); + Vector<float3> final_points; + r_ids.reserve(output_points_target); + final_points.reserve(output_points_target); + + /* Check if we have any points we should remove from the final possion distribition. */ + BVHTreeFromMesh treedata; + BKE_bvhtree_from_mesh_get(&treedata, const_cast<Mesh *>(mesh), BVHTREE_FROM_LOOPTRI, 2); + + float3 bb_min, bb_max; + BLI_bvhtree_get_bounding_box(treedata.tree, bb_min, bb_max); + + struct RayCastAll_Data data; + data.bvhdata = &treedata; + data.raycast_callback = treedata.raycast_callback; + data.mesh = mesh; + data.projected_points = &final_points; + data.stable_ids = &r_ids; + data.density_factors = const_cast<FloatReadAttribute *>(&density_factors); + data.base_weight = std::min( + 1.0f, density / (output_points.size() / (point_scale_multiplier * point_scale_multiplier))); + + const float max_dist = bb_max[2] - bb_min[2] + 2.0f; + const float3 dir = float3(0, 0, -1); + float3 raystart; + raystart.z = bb_max[2] + 1.0f; + + float tile_start_x_coord = bb_min[0]; + int tile_repeat_x = ceilf((bb_max[0] - bb_min[0]) / point_scale_multiplier); + + float tile_start_y_coord = bb_min[1]; + int tile_repeat_y = ceilf((bb_max[1] - bb_min[1]) / point_scale_multiplier); + + for (int x = 0; x < tile_repeat_x; x++) { + float tile_curr_x_coord = x * point_scale_multiplier + tile_start_x_coord; + for (int y = 0; y < tile_repeat_y; y++) { + float tile_curr_y_coord = y * point_scale_multiplier + tile_start_y_coord; + for (int idx = 0; idx < output_points.size(); idx++) { + raystart.x = output_points[idx].x + tile_curr_x_coord; + raystart.y = output_points[idx].y + tile_curr_y_coord; + + data.cur_point_weight = (float)idx / (float)output_points.size(); + data.raystart = raystart; + + BLI_bvhtree_ray_cast_all( + treedata.tree, raystart, dir, 0.0f, max_dist, project_2d_bvh_callback, &data); + } + } + } + + return final_points; +} + static void geo_node_point_distribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); GeometrySet geometry_set_out; + GeometryNodePointDistributeMethod distribute_method = + static_cast<GeometryNodePointDistributeMethod>(params.node().custom1); + if (!geometry_set.has_mesh()) { params.set_output("Geometry", std::move(geometry_set_out)); return; } - const float density = params.extract_input<float>("Density"); + const float density = params.extract_input<float>("Density Max"); const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); if (density <= 0.0f) { @@ -113,8 +280,21 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( density_attribute, ATTR_DOMAIN_POINT, 1.0f); + const int seed = params.get_input<int>("Seed"); - Vector<float3> points = scatter_points_from_mesh(mesh_in, density, density_factors); + Vector<int> stable_ids; + Vector<float3> points; + switch (distribute_method) { + case GEO_NODE_POINT_DISTRIBUTE_RANDOM: + points = random_scatter_points_from_mesh( + mesh_in, density, density_factors, stable_ids, seed); + break; + case GEO_NODE_POINT_DISTRIBUTE_POISSON: + const float min_dist = params.extract_input<float>("Distance Min"); + points = poisson_scatter_points_from_mesh( + mesh_in, density, min_dist, density_factors, stable_ids, seed); + break; + } PointCloud *pointcloud = BKE_pointcloud_new_nomain(points.size()); memcpy(pointcloud->co, points.data(), sizeof(float3) * points.size()); @@ -123,7 +303,16 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params) pointcloud->radius[i] = 0.05f; } - geometry_set_out.replace_pointcloud(pointcloud); + PointCloudComponent &point_component = + geometry_set_out.get_component_for_write<PointCloudComponent>(); + point_component.replace(pointcloud); + + Int32WriteAttribute stable_id_attribute = point_component.attribute_try_ensure_for_write( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + MutableSpan<int> stable_ids_span = stable_id_attribute.get_span(); + stable_ids_span.copy_from(stable_ids); + stable_id_attribute.apply_span(); + params.set_output("Geometry", std::move(geometry_set_out)); } } // namespace blender::nodes @@ -135,6 +324,7 @@ void register_node_type_geo_point_distribute() geo_node_type_base( &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_update(&ntype, node_point_distribute_update); ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; nodeRegisterType(&ntype); } |