Blender V5.0
point_merge_by_distance.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array_utils.hh"
6#include "BLI_kdtree.h"
8#include "BLI_task.hh"
9
11
12#include "BKE_attribute_math.hh"
13#include "BKE_pointcloud.hh"
14
16#include "GEO_randomize.hh"
17
18namespace blender::geometry {
19
21 const float merge_distance,
22 const IndexMask &selection,
23 const bke::AttributeFilter &attribute_filter)
24{
25 const bke::AttributeAccessor src_attributes = src_points.attributes();
26 const Span<float3> positions = src_points.positions();
27 const int src_size = positions.size();
28
29 /* Create the KD tree based on only the selected points, to speed up merge detection and
30 * balancing. */
31 KDTree_3d *tree = BLI_kdtree_3d_new(selection.size());
33 [&](const int64_t i, const int64_t pos) { BLI_kdtree_3d_insert(tree, pos, positions[i]); });
34 BLI_kdtree_3d_balance(tree);
35
36 /* Find the duplicates in the KD tree. Because the tree only contains the selected points, the
37 * resulting indices are indices into the selection, rather than indices of the source point
38 * cloud. */
39 Array<int> selection_merge_indices(selection.size(), -1);
40 const int duplicate_count = BLI_kdtree_3d_calc_duplicates_fast(
41 tree, merge_distance, false, selection_merge_indices.data());
42 BLI_kdtree_3d_free(tree);
43
44 /* Create the new point cloud and add it to a temporary component for the attribute API. */
45 const int dst_size = src_size - duplicate_count;
46 PointCloud *dst_pointcloud = BKE_pointcloud_new_nomain(dst_size);
47 bke::MutableAttributeAccessor dst_attributes = dst_pointcloud->attributes_for_write();
48
49 /* By default, every point is just "merged" with itself. Then fill in the results of the merge
50 * finding, converting from indices into the selection to indices into the full input point
51 * cloud. */
52 Array<int> merge_indices(src_size);
54
55 selection.foreach_index([&](const int src_index, const int pos) {
56 const int merge_index = selection_merge_indices[pos];
57 if (merge_index != -1) {
58 const int src_merge_index = selection[merge_index];
59 merge_indices[src_index] = src_merge_index;
60 }
61 });
62
63 /* For every source index, find the corresponding index in the result by iterating through the
64 * source indices and counting how many merges happened before that point. */
65 int merged_points = 0;
66 Array<int> src_to_dst_indices(src_size);
67 for (const int i : IndexRange(src_size)) {
68 src_to_dst_indices[i] = i - merged_points;
69 if (merge_indices[i] != i) {
70 merged_points++;
71 }
72 }
73
74 /* In order to use a contiguous array as the storage for every destination point's source
75 * indices, first the number of source points must be counted for every result point. */
76 Array<int> point_merge_counts(dst_size, 0);
77 for (const int i : IndexRange(src_size)) {
78 const int merge_index = merge_indices[i];
79 const int dst_index = src_to_dst_indices[merge_index];
80 point_merge_counts[dst_index]++;
81 }
82
83 /* This array stores an offset into `merge_map` for every result point. */
84 Array<int> map_offsets_data(dst_size + 1);
85 int offset = 0;
86 for (const int i : IndexRange(dst_size)) {
87 map_offsets_data[i] = offset;
88 offset += point_merge_counts[i];
89 }
90 map_offsets_data.last() = offset;
91 OffsetIndices<int> map_offsets(map_offsets_data);
92
93 point_merge_counts.fill(0);
94
95 /* This array stores all of the source indices for every result point. The size is the source
96 * size because every input point is either merged with another or copied directly. */
97 Array<int> merge_map_indices(src_size);
98 for (const int i : IndexRange(src_size)) {
99 const int merge_index = merge_indices[i];
100 const int dst_index = src_to_dst_indices[merge_index];
101
102 merge_map_indices[map_offsets[dst_index].first() + point_merge_counts[dst_index]] = i;
103 point_merge_counts[dst_index]++;
104 }
105
106 Set<StringRefNull> attribute_ids = src_attributes.all_ids();
107
108 /* Transfer the ID attribute if it exists, using the ID of the first merged point. */
109 bke::GAttributeReader src_id_attribute = src_attributes.lookup("id");
110 if (src_id_attribute && src_id_attribute.domain == bke::AttrDomain::Point &&
111 src_id_attribute.varray.type().is<int>())
112 {
113 VArraySpan<int> src = src_id_attribute.varray.typed<int>();
116
117 threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) {
118 for (const int i_dst : range) {
119 dst.span[i_dst] = src[map_offsets[i_dst].first()];
120 }
121 });
122
123 dst.finish();
124 attribute_ids.remove_contained("id");
125 }
126
127 /* Transfer all other attributes. */
128 for (const StringRef id : attribute_ids) {
129 if (attribute_filter.allow_skip(id)) {
130 continue;
131 }
132
133 bke::GAttributeReader src_attribute = src_attributes.lookup(id);
134 bke::attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) {
135 using T = decltype(dummy);
136 if constexpr (!std::is_void_v<bke::attribute_math::DefaultMixer<T>>) {
137 bke::SpanAttributeWriter<T> dst_attribute =
138 dst_attributes.lookup_or_add_for_write_only_span<T>(id, bke::AttrDomain::Point);
139 VArraySpan<T> src = src_attribute.varray.typed<T>();
140
141 threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) {
142 for (const int i_dst : range) {
143 /* Create a separate mixer for every point to avoid allocating temporary buffers
144 * in the mixer the size of the result point cloud and to improve memory locality. */
145 bke::attribute_math::DefaultMixer<T> mixer{dst_attribute.span.slice(i_dst, 1)};
146
147 Span<int> src_merge_indices = merge_map_indices.as_span().slice(map_offsets[i_dst]);
148 for (const int i_src : src_merge_indices) {
149 mixer.mix_in(0, src[i_src]);
150 }
151
152 mixer.finalize();
153 }
154 });
155
156 dst_attribute.finish();
157 }
158 });
159 }
160
161 debug_randomize_point_order(dst_pointcloud);
162
163 return dst_pointcloud;
164}
165
166} // namespace blender::geometry
General operations for point clouds.
PointCloud * BKE_pointcloud_new_nomain(int totpoint)
A KD-tree for nearest neighbor search.
long long int int64_t
AttributeSet attributes
const T & last(const int64_t n=0) const
Definition BLI_array.hh:296
const T * data() const
Definition BLI_array.hh:312
void fill(const T &value) const
Definition BLI_array.hh:272
bool is() const
void remove_contained(const Key &key)
Definition BLI_set.hh:397
constexpr int64_t size() const
Definition BLI_span.hh:252
GAttributeReader lookup(const StringRef attribute_id) const
Set< StringRefNull > all_ids() const
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
void foreach_index_optimized(Fn &&fn) const
void foreach_index(Fn &&fn) const
KDTree_3d * tree
uint pos
void fill_index_range(MutableSpan< T > span, const T start=0)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
PointCloud * point_merge_by_distance(const PointCloud &src_points, const float merge_distance, const IndexMask &selection, const bke::AttributeFilter &attribute_filter)
void debug_randomize_point_order(PointCloud *pointcloud)
Definition randomize.cc:242
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
bool allow_skip(const StringRef name) const
i
Definition text_draw.cc:230