Blender V4.5
curves_sculpt_selection_paint.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 <algorithm>
6
7#include "BLI_math_geom.h"
8#include "BLI_math_matrix.hh"
9#include "BLI_task.hh"
10
11#include "DNA_brush_types.h"
12
13#include "BKE_attribute.hh"
14#include "BKE_brush.hh"
15#include "BKE_context.hh"
16#include "BKE_curves.hh"
17#include "BKE_paint.hh"
18
19#include "DEG_depsgraph.hh"
20
21#include "ED_screen.hh"
22#include "ED_view3d.hh"
23
24#include "WM_api.hh"
25
27
34
36
37using bke::CurvesGeometry;
38
40 private:
41 bool use_select_;
42 bool clear_selection_;
43
44 CurvesBrush3D brush_3d_;
45
47
48 public:
49 SelectionPaintOperation(const bool use_select, const bool clear_selection)
50 : use_select_(use_select), clear_selection_(clear_selection)
51 {
52 }
53 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
54};
55
59
60 Object *object_ = nullptr;
61 Curves *curves_id_ = nullptr;
63
65
66 const Brush *brush_ = nullptr;
70
72
74
76
78
80 const bContext &C,
81 const StrokeExtension &stroke_extension)
82 {
83 self_ = &self;
85
86 curves_id_ = static_cast<Curves *>(object_->data);
87 curves_ = &curves_id_->geometry.wrap();
88 if (curves_->is_empty()) {
89 return;
90 }
92 if (!selection_) {
93 return;
94 }
95
96 brush_ = BKE_paint_brush_for_read(&ctx_.scene->toolsettings->curves_sculpt->paint);
99 brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
100
101 brush_pos_re_ = stroke_extension.mouse_position;
102
103 if (self.clear_selection_) {
104 if (stroke_extension.is_first) {
106 }
107 }
108
110
111 const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
112
113 selection_goal_ = self_->use_select_ ? 1.0f : 0.0f;
114
115 if (stroke_extension.is_first) {
116 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
118 }
119 }
120
121 if (selection_.domain == bke::AttrDomain::Point) {
122 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
124 }
125 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
127 }
128 }
129 else {
130 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
132 }
133 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
135 }
136 }
137
138 selection_.finish();
139
140 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because
141 * selection is handled as a generic attribute for now. */
144 ctx_.rv3d->rflag &= ~RV3D_PAINTING;
146 }
147
149 {
150 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
152 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
153 this->paint_point_selection_projected(brush_transform, selection);
154 }
155 }
156
157 void paint_point_selection_projected(const float4x4 &brush_transform,
158 MutableSpan<float> selection)
159 {
160 const float4x4 brush_transform_inv = math::invert(brush_transform);
161
162 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
163
164 const bke::crazyspace::GeometryDeformation deformation =
166
167 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
168 const float brush_radius_sq_re = pow2f(brush_radius_re);
169
170 threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
171 for (const int point_i : point_range) {
172 const float3 pos_cu = math::transform_point(brush_transform_inv,
173 deformation.positions[point_i]);
174
175 /* Find the position of the point in screen space. */
176 const float2 pos_re = ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, projection);
177
178 const float distance_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_);
179 if (distance_to_brush_sq_re > brush_radius_sq_re) {
180 /* Ignore the point because it's too far away. */
181 continue;
182 }
183
184 const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
185 /* A falloff that is based on how far away the point is from the stroke. */
186 const float radius_falloff = BKE_brush_curve_strength(
187 brush_, distance_to_brush_re, brush_radius_re);
188 /* Combine the falloff and brush strength. */
189 const float weight = brush_strength_ * radius_falloff;
190
191 selection[point_i] = math::interpolate(selection[point_i], selection_goal_, weight);
192 }
193 });
194 }
195
197 {
198 float3 brush_wo;
200 ctx_.v3d,
201 ctx_.region,
202 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
204 brush_wo);
205 const float3 brush_cu = math::transform_point(transforms_.world_to_curves, brush_wo);
206
207 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
209
210 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
211 this->paint_point_selection_spherical(selection,
212 math::transform_point(brush_transform, brush_cu));
213 }
214 }
215
217 {
218 const bke::crazyspace::GeometryDeformation deformation =
220
221 const float brush_radius_cu = self_->brush_3d_.radius_cu;
222 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
223
224 threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
225 for (const int i : point_range) {
226 const float3 pos_old_cu = deformation.positions[i];
227
228 /* Compute distance to the brush. */
229 const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu);
230 if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
231 /* Ignore the point because it's too far away. */
232 continue;
233 }
234
235 const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
236
237 /* A falloff that is based on how far away the point is from the stroke. */
238 const float radius_falloff = BKE_brush_curve_strength(
239 brush_, distance_to_brush_cu, brush_radius_cu);
240 /* Combine the falloff and brush strength. */
241 const float weight = brush_strength_ * radius_falloff;
242
243 selection[i] = math::interpolate(selection[i], selection_goal_, weight);
244 }
245 });
246 }
247
249 {
250 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
252 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
253 this->paint_curve_selection_projected(brush_transform, selection);
254 }
255 }
256
257 void paint_curve_selection_projected(const float4x4 &brush_transform,
258 MutableSpan<float> selection)
259 {
260 const float4x4 brush_transform_inv = math::invert(brush_transform);
261
262 const bke::crazyspace::GeometryDeformation deformation =
264 const OffsetIndices points_by_curve = curves_->points_by_curve();
265
266 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
267
268 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
269 const float brush_radius_sq_re = pow2f(brush_radius_re);
270
271 threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
272 for (const int curve_i : curves_range) {
273 const float max_weight = threading::parallel_reduce(
274 points_by_curve[curve_i].drop_back(1),
275 1024,
276 0.0f,
277 [&](const IndexRange segment_range, const float init) {
278 float max_weight = init;
279 for (const int segment_i : segment_range) {
280 const float3 pos1_cu = math::transform_point(brush_transform_inv,
281 deformation.positions[segment_i]);
282 const float3 pos2_cu = math::transform_point(brush_transform_inv,
283 deformation.positions[segment_i + 1]);
284
285 const float2 pos1_re = ED_view3d_project_float_v2_m4(
286 ctx_.region, pos1_cu, projection);
287 const float2 pos2_re = ED_view3d_project_float_v2_m4(
288 ctx_.region, pos2_cu, projection);
289
290 const float distance_sq_re = dist_squared_to_line_segment_v2(
291 brush_pos_re_, pos1_re, pos2_re);
292 if (distance_sq_re > brush_radius_sq_re) {
293 continue;
294 }
295 const float radius_falloff = BKE_brush_curve_strength(
296 brush_, std::sqrt(distance_sq_re), brush_radius_re);
297 const float weight = brush_strength_ * radius_falloff;
298 max_weight = std::max(max_weight, weight);
299 }
300 return max_weight;
301 },
302 [](float a, float b) { return std::max(a, b); });
303 selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
304 }
305 });
306 }
307
309 {
310 float3 brush_wo;
312 ctx_.v3d,
313 ctx_.region,
314 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
316 brush_wo);
317 const float3 brush_cu = math::transform_point(transforms_.world_to_curves, brush_wo);
318
319 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
321
322 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
323 this->paint_curve_selection_spherical(selection,
324 math::transform_point(brush_transform, brush_cu));
325 }
326 }
327
329 {
330 const bke::crazyspace::GeometryDeformation deformation =
332 const OffsetIndices points_by_curve = curves_->points_by_curve();
333
334 const float brush_radius_cu = self_->brush_3d_.radius_cu;
335 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
336
337 threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
338 for (const int curve_i : curves_range) {
339 const float max_weight = threading::parallel_reduce(
340 points_by_curve[curve_i].drop_back(1),
341 1024,
342 0.0f,
343 [&](const IndexRange segment_range, const float init) {
344 float max_weight = init;
345 for (const int segment_i : segment_range) {
346 const float3 &pos1_cu = deformation.positions[segment_i];
347 const float3 &pos2_cu = deformation.positions[segment_i + 1];
348
349 const float distance_sq_cu = dist_squared_to_line_segment_v3(
350 brush_cu, pos1_cu, pos2_cu);
351 if (distance_sq_cu > brush_radius_sq_cu) {
352 continue;
353 }
354 const float radius_falloff = BKE_brush_curve_strength(
355 brush_, std::sqrt(distance_sq_cu), brush_radius_cu);
356 const float weight = brush_strength_ * radius_falloff;
357 max_weight = std::max(max_weight, weight);
358 }
359 return max_weight;
360 },
361 [](float a, float b) { return std::max(a, b); });
362 selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
363 }
364 });
365 }
366
368 {
369 std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(*ctx_.depsgraph,
370 *ctx_.region,
371 *ctx_.v3d,
372 *ctx_.rv3d,
373 *object_,
376 if (brush_3d.has_value()) {
377 self_->brush_3d_ = *brush_3d;
379 *ctx_.scene,
380 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
381 }
382 }
383};
384
386 const StrokeExtension &stroke_extension)
387{
389 executor.execute(*this, C, stroke_extension);
390}
391
392std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
393 const BrushStrokeMode brush_mode, const bContext &C)
394{
395 Scene &scene = *CTX_data_scene(&C);
397 const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
398 ((brush.flag & BRUSH_DIR_IN) != 0);
399 const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
400
401 return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
402}
403
404} // namespace blender::ed::sculpt_paint
int BKE_brush_size_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1210
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Low-level operations for curves.
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:641
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:636
MINLINE float pow2f(float x)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ BRUSH_DIR_IN
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
eCurvesSymmetryType
@ USER_ORBIT_SELECTION
@ RV3D_PAINTING
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
void ED_view3d_win_to_3d(const View3D *v3d, const ARegion *region, const float depth_pt[3], const float mval[2], float r_out[3])
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
#define C
Definition RandGen.cpp:29
#define NC_GEOM
Definition WM_types.hh:390
#define ND_DATA
Definition WM_types.hh:506
#define U
PyObject * self
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
SelectionPaintOperation(const bool use_select, const bool clear_selection)
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
void fill_selection_false(GMutableSpan selection)
void remember_stroke_position(Scene &scene, const float3 &brush_position_wo)
std::optional< CurvesBrush3D > sample_curves_3d_brush(const Depsgraph &depsgraph, const ARegion &region, const View3D &v3d, const RegionView3D &rv3d, const Object &curves_object, const float2 &brush_pos_re, const float brush_radius_re)
bke::SpanAttributeWriter< float > float_selection_ensure(Curves &curves_id)
std::unique_ptr< CurvesSculptStrokeOperation > new_selection_paint_operation(const BrushStrokeMode brush_mode, const bContext &C)
float brush_strength_get(const Scene &scene, const Brush &brush, const StrokeExtension &stroke_extension)
Vector< float4x4 > get_symmetry_brush_transforms(const eCurvesSymmetryType symmetry)
float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension)
CartesianBasis invert(const CartesianBasis &basis)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
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
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
BrushStrokeMode
@ BRUSH_STROKE_SMOOTH
@ BRUSH_STROKE_INVERT
struct ToolSettings * toolsettings
CurvesSculpt * curves_sculpt
void paint_point_selection_spherical(MutableSpan< float > selection, const float3 &brush_cu)
void paint_curve_selection_projected(const float4x4 &brush_transform, MutableSpan< float > selection)
void paint_curve_selection_spherical(MutableSpan< float > selection, const float3 &brush_cu)
void paint_point_selection_projected(const float4x4 &brush_transform, MutableSpan< float > selection)
void execute(SelectionPaintOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
void WM_main_add_notifier(uint type, void *reference)