[Bf-blender-cvs] [ec1b0c2014a] master: Geometry Nodes: Initial merge by distance node

Hans Goudey noreply at git.blender.org
Tue Jan 25 18:02:34 CET 2022


Commit: ec1b0c2014a8b91c27bc5478ffa1ca74b63c64e9
Author: Hans Goudey
Date:   Tue Jan 25 10:51:52 2022 -0600
Branches: master
https://developer.blender.org/rBec1b0c2014a8b91c27bc5478ffa1ca74b63c64e9

Geometry Nodes: Initial merge by distance node

This implements a merge by distance operation for point clouds.
Besides the geometry input, there are two others-- a selection
input to limit the operation to certain points, and the merge
distance. While it would be a reasonable feature, the distance
does not support a field currently, since that would make
the algorithm significantly more complex.

All attributes are merged to the merged points, with the values
mixed together. This same generic method is used for all attributes,
including `position`. The `id` attribute uses the value from the
first merged index for each point.

For the implementation, most of the effort goes into creating a
merge map to speed up attribute mixing. Some parts are inherently
single-threaded, like finding the final indices accounting for the
merged points. By far most of the time is spend balancing the
KD tree.

Mesh support will be added in the next commit.

Differential Revision: https://developer.blender.org/D13649

===================================================================

M	release/scripts/startup/nodeitems_builtins.py
M	source/blender/blenkernel/BKE_node.h
M	source/blender/blenkernel/intern/node.cc
M	source/blender/geometry/CMakeLists.txt
A	source/blender/geometry/GEO_point_merge_by_distance.hh
A	source/blender/geometry/intern/point_merge_by_distance.cc
M	source/blender/nodes/NOD_geometry.h
M	source/blender/nodes/NOD_static_types.h
M	source/blender/nodes/geometry/CMakeLists.txt
A	source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc

===================================================================

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index d7bf5d82c6a..b841cb5dd13 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -181,6 +181,7 @@ def geometry_node_items(context):
     yield NodeItem("GeometryNodeConvexHull")
     yield NodeItem("GeometryNodeDeleteGeometry")
     yield NodeItem("GeometryNodeGeometryToInstance")
+    yield NodeItem("GeometryNodeMergeByDistance")
     yield NodeItem("GeometryNodeProximity")
     yield NodeItem("GeometryNodeJoinGeometry")
     yield NodeItem("GeometryNodeRaycast")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index b2c91fafe97..16d8ba2e6dd 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1523,6 +1523,7 @@ struct TexResult;
 #define GEO_NODE_FLIP_FACES 1150
 #define GEO_NODE_SCALE_ELEMENTS 1151
 #define GEO_NODE_EXTRUDE_MESH 1152
+#define GEO_NODE_MERGE_BY_DISTANCE 1153
 
 /** \} */
 
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 074e38dea87..40d0c24c9af 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4802,6 +4802,7 @@ static void registerGeometryNodes()
   register_node_type_geo_join_geometry();
   register_node_type_geo_material_replace();
   register_node_type_geo_material_selection();
+  register_node_type_geo_merge_by_distance();
   register_node_type_geo_mesh_primitive_circle();
   register_node_type_geo_mesh_primitive_cone();
   register_node_type_geo_mesh_primitive_cube();
diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt
index de508ddc540..9e0b53f4980 100644
--- a/source/blender/geometry/CMakeLists.txt
+++ b/source/blender/geometry/CMakeLists.txt
@@ -31,9 +31,11 @@ set(INC
 
 set(SRC
   intern/mesh_to_curve_convert.cc
+  intern/point_merge_by_distance.cc
   intern/realize_instances.cc
 
   GEO_mesh_to_curve.hh
+  GEO_point_merge_by_distance.hh
   GEO_realize_instances.hh
 )
 
diff --git a/source/blender/geometry/GEO_point_merge_by_distance.hh b/source/blender/geometry/GEO_point_merge_by_distance.hh
new file mode 100644
index 00000000000..6766f3c559d
--- /dev/null
+++ b/source/blender/geometry/GEO_point_merge_by_distance.hh
@@ -0,0 +1,38 @@
+/*
+ * 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_index_mask.hh"
+
+#pragma once
+
+struct PointCloud;
+class PointCloudComponent;
+
+/** \file
+ * \ingroup geo
+ */
+
+namespace blender::geometry {
+
+/**
+ * Merge selected points into other selected points within the \a merge_distance. The merged
+ * indices favor speed over accuracy, since the results will depend on the order of the points.
+ */
+PointCloud *point_merge_by_distance(const PointCloudComponent &src_points,
+                                    const float merge_distance,
+                                    const IndexMask selection);
+
+}  // namespace blender::geometry
diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc
new file mode 100644
index 00000000000..daa08a3bce6
--- /dev/null
+++ b/source/blender/geometry/intern/point_merge_by_distance.cc
@@ -0,0 +1,183 @@
+/*
+ * 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_task.hh"
+
+#include "DNA_pointcloud_types.h"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_geometry_set.hh"
+#include "BKE_pointcloud.h"
+
+#include "GEO_point_merge_by_distance.hh"
+
+namespace blender::geometry {
+
+PointCloud *point_merge_by_distance(const PointCloudComponent &src_points,
+                                    const float merge_distance,
+                                    const IndexMask selection)
+{
+  const PointCloud &src_pointcloud = *src_points.get_for_read();
+  const int src_size = src_pointcloud.totpoint;
+  Span<float3> positions{reinterpret_cast<float3 *>(src_pointcloud.co), src_size};
+
+  /* Create the KD tree based on only the selected points, to speed up merge detection and
+   * balancing. */
+  KDTree_3d *tree = BLI_kdtree_3d_new(selection.size());
+  for (const int i : selection.index_range()) {
+    BLI_kdtree_3d_insert(tree, i, positions[selection[i]]);
+  }
+  BLI_kdtree_3d_balance(tree);
+
+  /* Find the duplicates in the KD tree. Because the tree only contains the selected points, the
+   * resulting indices are indices into the selection, rather than indices of the source point
+   * cloud. */
+  Array<int> selection_merge_indices(selection.size(), -1);
+  const int duplicate_count = BLI_kdtree_3d_calc_duplicates_fast(
+      tree, merge_distance, false, selection_merge_indices.data());
+  BLI_kdtree_3d_free(tree);
+
+  /* Create the new point cloud and add it to a temporary component for the attribute API. */
+  const int dst_size = src_size - duplicate_count;
+  PointCloud *dst_pointcloud = BKE_pointcloud_new_nomain(dst_size);
+  PointCloudComponent dst_points;
+  dst_points.replace(dst_pointcloud, GeometryOwnershipType::Editable);
+
+  /* By default, every point is just "merged" with itself. Then fill in the results of the merge
+   * finding, converting from indices into the selection to indices into the full input point
+   * cloud. */
+  Array<int> merge_indices(src_size);
+  for (const int i : merge_indices.index_range()) {
+    merge_indices[i] = i;
+  }
+  for (const int i : selection_merge_indices.index_range()) {
+    const int merge_index = selection_merge_indices[i];
+    if (merge_index != -1) {
+      const int src_merge_index = selection[merge_index];
+      const int src_index = selection[i];
+      merge_indices[src_index] = src_merge_index;
+    }
+  }
+
+  /* For every source index, find the corresponding index in the result by iterating through the
+   * source indices and counting how many merges happened before that point. */
+  int merged_points = 0;
+  Array<int> src_to_dst_indices(src_size);
+  for (const int i : IndexRange(src_size)) {
+    src_to_dst_indices[i] = i - merged_points;
+    if (merge_indices[i] != i) {
+      merged_points++;
+    }
+  }
+
+  /* In order to use a contiguous array as the storage for every destination point's source
+   * indices, first the number of source points must be counted for every result point. */
+  Array<int> point_merge_counts(dst_size, 0);
+  for (const int i : IndexRange(src_size)) {
+    const int merge_index = merge_indices[i];
+    const int dst_index = src_to_dst_indices[merge_index];
+    point_merge_counts[dst_index]++;
+  }
+
+  /* This array stores an offset into `merge_map` for every result point. */
+  Array<int> map_offsets(dst_size + 1);
+  int offset = 0;
+  for (const int i : IndexRange(dst_size)) {
+    map_offsets[i] = offset;
+    offset += point_merge_counts[i];
+  }
+  map_offsets.last() = offset;
+
+  point_merge_counts.fill(0);
+
+  /* This array stores all of the source indices for every result point. The size is the source
+   * size because every input point is either merged with another or copied directly. */
+  Array<int> merge_map(src_size);
+  for (const int i : IndexRange(src_size)) {
+    const int merge_index = merge_indices[i];
+    const int dst_index = src_to_dst_indices[merge_index];
+
+    const IndexRange point_range(map_offsets[dst_index],
+                                 map_offsets[dst_index + 1] - map_offsets[dst_index]);
+    MutableSpan<int> point_merge_indices = merge_map.as_mutable_span().slice(point_range);
+    point_merge_indices[point_merge_counts[dst_index]] = i;
+    point_merge_counts[dst_index]++;
+  }
+
+  Set<bke::AttributeIDRef> attributes = src_points.attribute_ids();
+
+  /* Transfer the ID attribute if it exists, using the ID of the first merged point. */
+  if (attributes.contains("id")) {
+    VArray<int> src = src_points.attribute_get_for_read<int>("id", ATTR_DOMAIN_POINT, 0);
+    bke::OutputAttribute_Typed<int> dst = dst_points.attribute_try_get_for_output_only<int>(
+        "id", ATTR_DOMAIN_POINT);
+    Span<int> src_ids = src.get_internal_span();
+    MutableSpan<int> dst_ids = dst.as_span();
+
+    threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) {
+      for (const int i_dst : range) {
+        const IndexRange point_range(map_offsets[i_dst],
+                                     map_offsets[i_dst + 1] - map_offsets[i_dst]);
+        dst_ids[i_dst] = src_ids[point_range.first()];
+      }
+    });
+
+    dst.save();
+    attributes.remove_contained("id");
+  }
+
+  /* Transfer all other attributes. */
+  for (const bke::AttributeIDRef &id : attributes) {
+    if (!id.should_be_kept()) {
+      continue;
+    }
+
+    bke::ReadAttributeLookup src_attribute = src_points.attribute_try_get_for_read(id);
+    attribut

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list