Blender V4.5
selection.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_array_utils.hh"
10#include "BLI_index_mask.hh"
11#include "BLI_lasso_2d.hh"
12#include "BLI_math_vector.hh"
13#include "BLI_rect.h"
14
15#include "BKE_attribute.hh"
16
17#include "ED_pointcloud.hh"
18#include "ED_select_utils.hh"
19#include "ED_view3d.hh"
20
22
24
25static bool contains(const VArray<bool> &varray,
26 const IndexMask &indices_to_check,
27 const bool value)
28{
29 const CommonVArrayInfo info = varray.common_info();
31 return *static_cast<const bool *>(info.data) == value;
32 }
34 const Span<bool> span(static_cast<const bool *>(info.data), varray.size());
36 indices_to_check.index_range(),
37 4096,
38 false,
39 [&](const IndexRange range, const bool init) {
40 if (init) {
41 return init;
42 }
43 const IndexMask sliced_mask = indices_to_check.slice(range);
44 if (std::optional<IndexRange> range = sliced_mask.to_range()) {
45 return span.slice(*range).contains(value);
46 }
47 for (const int64_t segment_i : IndexRange(sliced_mask.segments_num())) {
48 const IndexMaskSegment segment = sliced_mask.segment(segment_i);
49 for (const int i : segment) {
50 if (span[i] == value) {
51 return true;
52 }
53 }
54 }
55 return false;
56 },
57 std::logical_or());
58 }
60 indices_to_check.index_range(),
61 2048,
62 false,
63 [&](const IndexRange range, const bool init) {
64 if (init) {
65 return init;
66 }
67 constexpr int64_t MaxChunkSize = 512;
68 const int64_t slice_end = range.one_after_last();
69 for (int64_t start = range.start(); start < slice_end; start += MaxChunkSize) {
70 const int64_t end = std::min<int64_t>(start + MaxChunkSize, slice_end);
71 const int64_t size = end - start;
72 const IndexMask sliced_mask = indices_to_check.slice(start, size);
73 std::array<bool, MaxChunkSize> values;
74 auto values_end = values.begin() + size;
75 varray.materialize_compressed(sliced_mask, values);
76 if (std::find(values.begin(), values_end, value) != values_end) {
77 return true;
78 }
79 }
80 return false;
81 },
82 std::logical_or());
83}
84
85static bool contains(const VArray<bool> &varray, const IndexRange range_to_check, const bool value)
86{
87 return contains(varray, IndexMask(range_to_check), value);
88}
89
91{
92 const VArray<bool> selection = *pointcloud.attributes().lookup<bool>(".selection");
93 return !selection || contains(selection, selection.index_range(), true);
94}
95
97 eCustomDataType create_type)
98{
99 const bke::AttrDomain selection_domain = bke::AttrDomain::Point;
100 const StringRef attribute_name = ".selection";
101
102 bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
103 if (attributes.contains(attribute_name)) {
104 return attributes.lookup_for_write_span(attribute_name);
105 }
106 const int domain_size = pointcloud.totpoint;
107 switch (create_type) {
108 case CD_PROP_BOOL:
109 attributes.add(attribute_name,
110 selection_domain,
113 break;
114 case CD_PROP_FLOAT:
115 attributes.add(attribute_name,
116 selection_domain,
119 break;
120 default:
122 }
123 return attributes.lookup_for_write_span(attribute_name);
124}
125
127{
128 if (selection.type().is<bool>()) {
129 index_mask::masked_fill(selection.typed<bool>(), false, mask);
130 }
131 else if (selection.type().is<float>()) {
132 index_mask::masked_fill(selection.typed<float>(), 0.0f, mask);
133 }
134}
135
137{
138 fill_selection_true(selection, IndexMask(selection.size()));
139}
140
142{
143 if (selection.type().is<bool>()) {
144 index_mask::masked_fill(selection.typed<bool>(), true, mask);
145 }
146 else if (selection.type().is<float>()) {
147 index_mask::masked_fill(selection.typed<float>(), 1.0f, mask);
148 }
149}
150
151static void invert_selection(MutableSpan<float> selection, const IndexMask &mask)
152{
153 mask.foreach_index_optimized<int64_t>(
154 GrainSize(2048), [&](const int64_t i) { selection[i] = 1.0f - selection[i]; });
155}
156
157static void invert_selection(GMutableSpan selection, const IndexMask &mask)
158{
159 if (selection.type().is<bool>()) {
160 array_utils::invert_booleans(selection.typed<bool>(), mask);
161 }
162 else if (selection.type().is<float>()) {
163 invert_selection(selection.typed<float>(), mask);
164 }
165}
166
167static void select_all(PointCloud &pointcloud, const IndexMask &mask, int action)
168{
169 if (action == SEL_SELECT) {
170 std::optional<IndexRange> range = mask.to_range();
171 if (range.has_value() && (*range == IndexRange(pointcloud.totpoint))) {
172 bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
173 /* As an optimization, just remove the selection attributes when everything is selected. */
174 attributes.remove(".selection");
175 return;
176 }
177 }
178
180 if (action == SEL_SELECT) {
181 fill_selection_true(selection.span, mask);
182 }
183 else if (action == SEL_DESELECT) {
184 fill_selection_false(selection.span, mask);
185 }
186 else if (action == SEL_INVERT) {
187 invert_selection(selection.span, mask);
188 }
189 selection.finish();
190}
191
193{
194 select_all(pointcloud, IndexRange(pointcloud.totpoint), action);
195}
196
198 const IndexMask &mask,
199 eSelectOp sel_op)
200{
201 bool changed = false;
203 if (sel_op == SEL_OP_SET) {
204 fill_selection_false(selection.span, IndexRange(selection.span.size()));
205 changed = true;
206 }
207 switch (sel_op) {
208 case SEL_OP_ADD:
209 case SEL_OP_SET:
210 fill_selection_true(selection.span, mask);
211 break;
212 case SEL_OP_SUB:
213 fill_selection_false(selection.span, mask);
214 break;
215 case SEL_OP_XOR:
216 invert_selection(selection.span, mask);
217 break;
218 default:
219 break;
220 }
221 changed |= !mask.is_empty();
222 selection.finish();
223 return changed;
224}
225
227 const ARegion &region,
228 const float4x4 &projection,
229 const rcti &rect,
230 const eSelectOp sel_op)
231{
232 const Span<float3> positions = pointcloud.positions();
233
234 IndexMaskMemory memory;
236 positions.index_range(), GrainSize(1024), memory, [&](const int point) {
237 const float2 pos_proj = ED_view3d_project_float_v2_m4(
238 &region, positions[point], projection);
239 return BLI_rcti_isect_pt_v(&rect, int2(pos_proj));
240 });
241
243}
244
246 const ARegion &region,
247 const float4x4 &projection,
248 const Span<int2> lasso_coords,
249 const eSelectOp sel_op)
250{
251 rcti bbox;
252 BLI_lasso_boundbox(&bbox, lasso_coords);
253
254 const Span<float3> positions = pointcloud.positions();
255
256 IndexMaskMemory memory;
258 positions.index_range(), GrainSize(1024), memory, [&](const int point) {
259 const float2 pos_proj = ED_view3d_project_float_v2_m4(
260 &region, positions[point], projection);
261 if (!BLI_rcti_isect_pt_v(&bbox, int2(pos_proj))) {
262 return false;
263 }
264 if (!BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
265 {
266 return false;
267 }
268 return true;
269 });
270
272}
273
275 const ARegion &region,
276 const float4x4 &projection,
277 const int2 coord,
278 const float radius,
279 const eSelectOp sel_op)
280{
281 const float radius_sq = radius * radius;
282
283 const Span<float3> positions = pointcloud.positions();
284
285 IndexMaskMemory memory;
287 positions.index_range(), GrainSize(1024), memory, [&](const int point) {
288 const float2 pos_proj = ED_view3d_project_float_v2_m4(
289 &region, positions[point], projection);
290 return math::distance_squared(pos_proj, float2(coord)) <= radius_sq;
291 });
292
294}
295
297{
298 if (a.distance_sq < b.distance_sq) {
299 return a;
300 }
301 return b;
302}
303
304std::optional<FindClosestData> find_closest_point_to_screen_co(
305 const ARegion &region,
306 const Span<float3> positions,
307 const float4x4 &projection,
308 const IndexMask &points_mask,
309 const float2 mouse_pos,
310 const float radius,
311 const FindClosestData &initial_closest)
312{
313 const float radius_sq = radius * radius;
314 const FindClosestData new_closest_data = threading::parallel_reduce(
315 points_mask.index_range(),
316 1024,
317 initial_closest,
318 [&](const IndexRange range, const FindClosestData &init) {
319 FindClosestData best_match = init;
320 points_mask.slice(range).foreach_index([&](const int point) {
321 const float3 &pos = positions[point];
322 const float2 pos_proj = ED_view3d_project_float_v2_m4(&region, pos, projection);
323
324 const float distance_proj_sq = math::distance_squared(pos_proj, mouse_pos);
325 if (distance_proj_sq > radius_sq || distance_proj_sq > best_match.distance_sq) {
326 return;
327 }
328
329 best_match = {point, distance_proj_sq};
330 });
331 return best_match;
332 },
333 closer_elem);
334
335 if (new_closest_data.distance_sq < initial_closest.distance_sq) {
336 return new_closest_data;
337 }
338
339 return {};
340}
341
343{
344 const VArray selection = *pointcloud.attributes().lookup_or_default<bool>(
345 ".selection", bke::AttrDomain::Point, true);
346 return IndexMask::from_bools(selection, memory);
347}
348
349} // namespace blender::ed::pointcloud
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
bool BLI_lasso_is_point_inside(blender::Span< blender::int2 > mcoords, int sx, int sy, int error_value)
void BLI_lasso_boundbox(rcti *rect, blender::Span< blender::int2 > mcoords)
bool BLI_rcti_isect_pt_v(const struct rcti *rect, const int xy[2])
@ CD_PROP_FLOAT
eSelectOp
@ SEL_OP_ADD
@ SEL_OP_SUB
@ SEL_OP_SET
@ SEL_OP_XOR
@ SEL_SELECT
@ SEL_INVERT
@ SEL_DESELECT
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
#define IS_CLIPPED
Definition ED_view3d.hh:252
for(;discarded_id_iter !=nullptr;discarded_id_iter=static_cast< ID * >(discarded_id_iter->next))
Definition blendfile.cc:634
long long int int64_t
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
bool is() const
const CPPType & type() const
MutableSpan< T > typed() const
constexpr int64_t one_after_last() const
constexpr int64_t start() const
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
IndexRange index_range() const
CommonVArrayInfo common_info() const
static VArray ForSingle(T value, const int64_t size)
bool contains(StringRef attribute_id) const
bool remove(const StringRef attribute_id)
bool add(const StringRef attribute_id, const AttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer)
GSpanAttributeWriter lookup_for_write_span(StringRef attribute_id)
IndexMask slice(IndexRange range) const
VecBase< float, 2 > float2
VecBase< int, 2 > int2
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
void invert_booleans(MutableSpan< bool > span)
static bool contains(const VArray< bool > &varray, const IndexMask &indices_to_check, const bool value)
Definition selection.cc:25
bool select_box(PointCloud &pointcloud, const ARegion &region, const float4x4 &projection, const rcti &rect, const eSelectOp sel_op)
Definition selection.cc:226
bool select_lasso(PointCloud &pointcloud, const ARegion &region, const float4x4 &projection, const Span< int2 > lasso_coords, const eSelectOp sel_op)
Definition selection.cc:245
bool select_circle(PointCloud &pointcloud, const ARegion &region, const float4x4 &projection, const int2 coord, const float radius, const eSelectOp sel_op)
Definition selection.cc:274
void fill_selection_true(GMutableSpan span)
Definition selection.cc:136
static void invert_selection(MutableSpan< float > selection, const IndexMask &mask)
Definition selection.cc:151
static bool apply_selection_operation(PointCloud &pointcloud, const IndexMask &mask, eSelectOp sel_op)
Definition selection.cc:197
static FindClosestData closer_elem(const FindClosestData &a, const FindClosestData &b)
Definition selection.cc:296
IndexMask retrieve_selected_points(const PointCloud &pointcloud, IndexMaskMemory &memory)
Definition selection.cc:342
bke::GSpanAttributeWriter ensure_selection_attribute(PointCloud &pointcloud, eCustomDataType create_type)
Definition selection.cc:96
void fill_selection_false(GMutableSpan selection, const IndexMask &mask)
Definition selection.cc:126
std::optional< FindClosestData > find_closest_point_to_screen_co(const ARegion &region, const Span< float3 > positions, const float4x4 &projection, const IndexMask &points_mask, const float2 mouse_pos, const float radius, const FindClosestData &initial_closest)
Definition selection.cc:304
bool has_anything_selected(const PointCloud &pointcloud)
Definition selection.cc:90
void select_all(PointCloud &pointcloud, int action)
Definition selection.cc:192
void masked_fill(MutableSpan< T > data, const T &value, const IndexMask &mask)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
Value parallel_reduce(IndexRange range, int64_t grain_size, const Value &identity, const Function &function, const Reduction &reduction)
Definition BLI_task.hh:151
MatBase< float, 4, 4 > float4x4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void init(bNodeTree *, bNode *node)
i
Definition text_draw.cc:230