Blender V5.0
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
67 const Brush *brush_ = nullptr;
71
73
75
77
79
81 const bContext &C,
82 const StrokeExtension &stroke_extension)
83 {
84 self_ = &self;
86
87 curves_id_ = static_cast<Curves *>(object_->data);
88 curves_ = &curves_id_->geometry.wrap();
89 if (curves_->is_empty()) {
90 return;
91 }
93 if (!selection_) {
94 return;
95 }
96
97 curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
100 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
101 brush_strength_ = brush_strength_get(curves_sculpt_->paint, *brush_, stroke_extension);
102
103 brush_pos_re_ = stroke_extension.mouse_position;
104
105 if (self.clear_selection_) {
106 if (stroke_extension.is_first) {
108 }
109 }
110
112
113 const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
114
115 selection_goal_ = self_->use_select_ ? 1.0f : 0.0f;
116
117 if (stroke_extension.is_first) {
118 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
120 }
121 }
122
123 if (selection_.domain == bke::AttrDomain::Point) {
124 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
126 }
127 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
129 }
130 }
131 else {
132 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
134 }
135 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
137 }
138 }
139
140 selection_.finish();
141
142 /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because
143 * selection is handled as a generic attribute for now. */
146 ctx_.rv3d->rflag &= ~RV3D_PAINTING;
148 }
149
151 {
152 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
154 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
155 this->paint_point_selection_projected(brush_transform, selection);
156 }
157 }
158
159 void paint_point_selection_projected(const float4x4 &brush_transform,
160 MutableSpan<float> selection)
161 {
162 const float4x4 brush_transform_inv = math::invert(brush_transform);
163
164 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
165
166 const bke::crazyspace::GeometryDeformation deformation =
168
169 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
170 const float brush_radius_sq_re = pow2f(brush_radius_re);
171
172 threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
173 for (const int point_i : point_range) {
174 const float3 pos_cu = math::transform_point(brush_transform_inv,
175 deformation.positions[point_i]);
176
177 /* Find the position of the point in screen space. */
178 const float2 pos_re = ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, projection);
179
180 const float distance_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_);
181 if (distance_to_brush_sq_re > brush_radius_sq_re) {
182 /* Ignore the point because it's too far away. */
183 continue;
184 }
185
186 const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
187 /* A falloff that is based on how far away the point is from the stroke. */
188 const float radius_falloff = BKE_brush_curve_strength(
189 brush_, distance_to_brush_re, brush_radius_re);
190 /* Combine the falloff and brush strength. */
191 const float weight = brush_strength_ * radius_falloff;
192
193 selection[point_i] = math::interpolate(selection[point_i], selection_goal_, weight);
194 }
195 });
196 }
197
199 {
200 float3 brush_wo;
202 ctx_.v3d,
203 ctx_.region,
204 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
206 brush_wo);
207 const float3 brush_cu = math::transform_point(transforms_.world_to_curves, brush_wo);
208
209 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
211
212 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
213 this->paint_point_selection_spherical(selection,
214 math::transform_point(brush_transform, brush_cu));
215 }
216 }
217
219 {
220 const bke::crazyspace::GeometryDeformation deformation =
222
223 const float brush_radius_cu = self_->brush_3d_.radius_cu;
224 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
225
226 threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
227 for (const int i : point_range) {
228 const float3 pos_old_cu = deformation.positions[i];
229
230 /* Compute distance to the brush. */
231 const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu);
232 if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
233 /* Ignore the point because it's too far away. */
234 continue;
235 }
236
237 const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
238
239 /* A falloff that is based on how far away the point is from the stroke. */
240 const float radius_falloff = BKE_brush_curve_strength(
241 brush_, distance_to_brush_cu, brush_radius_cu);
242 /* Combine the falloff and brush strength. */
243 const float weight = brush_strength_ * radius_falloff;
244
245 selection[i] = math::interpolate(selection[i], selection_goal_, weight);
246 }
247 });
248 }
249
251 {
252 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
254 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
255 this->paint_curve_selection_projected(brush_transform, selection);
256 }
257 }
258
259 void paint_curve_selection_projected(const float4x4 &brush_transform,
260 MutableSpan<float> selection)
261 {
262 const float4x4 brush_transform_inv = math::invert(brush_transform);
263
264 const bke::crazyspace::GeometryDeformation deformation =
266 const OffsetIndices points_by_curve = curves_->points_by_curve();
267
268 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
269
270 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
271 const float brush_radius_sq_re = pow2f(brush_radius_re);
272
273 threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
274 for (const int curve_i : curves_range) {
275 const float max_weight = threading::parallel_reduce(
276 points_by_curve[curve_i].drop_back(1),
277 1024,
278 0.0f,
279 [&](const IndexRange segment_range, const float init) {
280 float max_weight = init;
281 for (const int segment_i : segment_range) {
282 const float3 pos1_cu = math::transform_point(brush_transform_inv,
283 deformation.positions[segment_i]);
284 const float3 pos2_cu = math::transform_point(brush_transform_inv,
285 deformation.positions[segment_i + 1]);
286
287 const float2 pos1_re = ED_view3d_project_float_v2_m4(
288 ctx_.region, pos1_cu, projection);
289 const float2 pos2_re = ED_view3d_project_float_v2_m4(
290 ctx_.region, pos2_cu, projection);
291
292 const float distance_sq_re = dist_squared_to_line_segment_v2(
293 brush_pos_re_, pos1_re, pos2_re);
294 if (distance_sq_re > brush_radius_sq_re) {
295 continue;
296 }
297 const float radius_falloff = BKE_brush_curve_strength(
298 brush_, std::sqrt(distance_sq_re), brush_radius_re);
299 const float weight = brush_strength_ * radius_falloff;
300 max_weight = std::max(max_weight, weight);
301 }
302 return max_weight;
303 },
304 [](float a, float b) { return std::max(a, b); });
305 selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
306 }
307 });
308 }
309
311 {
312 float3 brush_wo;
314 ctx_.v3d,
315 ctx_.region,
316 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
318 brush_wo);
319 const float3 brush_cu = math::transform_point(transforms_.world_to_curves, brush_wo);
320
321 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
323
324 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
325 this->paint_curve_selection_spherical(selection,
326 math::transform_point(brush_transform, brush_cu));
327 }
328 }
329
331 {
332 const bke::crazyspace::GeometryDeformation deformation =
334 const OffsetIndices points_by_curve = curves_->points_by_curve();
335
336 const float brush_radius_cu = self_->brush_3d_.radius_cu;
337 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
338
339 threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
340 for (const int curve_i : curves_range) {
341 const float max_weight = threading::parallel_reduce(
342 points_by_curve[curve_i].drop_back(1),
343 1024,
344 0.0f,
345 [&](const IndexRange segment_range, const float init) {
346 float max_weight = init;
347 for (const int segment_i : segment_range) {
348 const float3 &pos1_cu = deformation.positions[segment_i];
349 const float3 &pos2_cu = deformation.positions[segment_i + 1];
350
351 const float distance_sq_cu = dist_squared_to_line_segment_v3(
352 brush_cu, pos1_cu, pos2_cu);
353 if (distance_sq_cu > brush_radius_sq_cu) {
354 continue;
355 }
356 const float radius_falloff = BKE_brush_curve_strength(
357 brush_, std::sqrt(distance_sq_cu), brush_radius_cu);
358 const float weight = brush_strength_ * radius_falloff;
359 max_weight = std::max(max_weight, weight);
360 }
361 return max_weight;
362 },
363 [](float a, float b) { return std::max(a, b); });
364 selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
365 }
366 });
367 }
368
370 {
371 std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(*ctx_.depsgraph,
372 *ctx_.region,
373 *ctx_.v3d,
374 *ctx_.rv3d,
375 *object_,
378 if (brush_3d.has_value()) {
379 self_->brush_3d_ = *brush_3d;
382 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
383 }
384 }
385};
386
388 const StrokeExtension &stroke_extension)
389{
391 executor.execute(*this, C, stroke_extension);
392}
393
394std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
395 const BrushStrokeMode brush_mode, const bContext &C)
396{
397 Scene &scene = *CTX_data_scene(&C);
399 const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
400 ((brush.flag & BRUSH_DIR_IN) != 0);
401 const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
402
403 return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
404}
405
406} // namespace blender::ed::sculpt_paint
float BKE_brush_radius_get(const Paint *paint, const Brush *brush)
Definition brush.cc:1272
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:650
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:645
MINLINE float pow2f(float x)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
@ 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:618
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:393
#define ND_DATA
Definition WM_types.hh:509
#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)
float brush_strength_get(const Paint &paint, const Brush &brush, const StrokeExtension &stroke_extension)
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)
void remember_stroke_position(CurvesSculpt &curves_sculpt, const float3 &brush_position_wo)
std::unique_ptr< CurvesSculptStrokeOperation > new_selection_paint_operation(const BrushStrokeMode brush_mode, const bContext &C)
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)