Blender V4.3
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#include <numeric>
7
8#include "BLI_math_geom.h"
9#include "BLI_math_matrix.hh"
10#include "BLI_memory_utils.hh"
11#include "BLI_task.hh"
12
13#include "DNA_brush_types.h"
14
15#include "BKE_attribute.hh"
16#include "BKE_brush.hh"
17#include "BKE_context.hh"
18#include "BKE_curves.hh"
19#include "BKE_paint.hh"
20
21#include "DEG_depsgraph.hh"
22
23#include "ED_screen.hh"
24#include "ED_view3d.hh"
25
26#include "WM_api.hh"
27
29
38
39using bke::CurvesGeometry;
40
42 private:
43 bool use_select_;
44 bool clear_selection_;
45
46 CurvesBrush3D brush_3d_;
47
49
50 public:
51 SelectionPaintOperation(const bool use_select, const bool clear_selection)
52 : use_select_(use_select), clear_selection_(clear_selection)
53 {
54 }
55 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
56};
57
61
62 Object *object_ = nullptr;
63 Curves *curves_id_ = nullptr;
65
67
68 const Brush *brush_ = nullptr;
72
74
76
78
80
82 const bContext &C,
83 const StrokeExtension &stroke_extension)
84 {
85 self_ = &self;
87
88 curves_id_ = static_cast<Curves *>(object_->data);
89 curves_ = &curves_id_->geometry.wrap();
90 if (curves_->curves_num() == 0) {
91 return;
92 }
94 if (!selection_) {
95 return;
96 }
97
100 brush_radius_factor_ = brush_radius_factor(*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
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) {
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
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
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),
205 brush_pos_re_,
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(
210 eCurvesSymmetryType(curves_id_->symmetry));
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 =
221 bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
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(
253 eCurvesSymmetryType(curves_id_->symmetry));
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 =
265 bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
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),
317 brush_pos_re_,
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(
322 eCurvesSymmetryType(curves_id_->symmetry));
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 =
333 bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
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_,
376 brush_pos_re_,
377 brush_radius_base_re_);
378 if (brush_3d.has_value()) {
379 self_->brush_3d_ = *brush_3d;
380 }
381 }
382};
383
384void SelectionPaintOperation::on_stroke_extended(const bContext &C,
385 const StrokeExtension &stroke_extension)
386{
388 executor.execute(*this, C, stroke_extension);
389}
390
391std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
392 const BrushStrokeMode brush_mode, const bContext &C)
393{
394 Scene &scene = *CTX_data_scene(&C);
395 Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint);
396 const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
397 ((brush.flag & BRUSH_DIR_IN) != 0);
398 const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
399
400 return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
401}
402
403} // namespace blender::ed::sculpt_paint
int BKE_brush_size_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1075
float BKE_brush_alpha_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1153
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:654
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:649
MINLINE float pow2f(float x)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ BRUSH_DIR_IN
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
eCurvesSymmetryType
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
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 NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
PyObject * self
IndexRange points_range() const
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)
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)
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:95
BrushStrokeMode
@ BRUSH_STROKE_SMOOTH
@ BRUSH_STROKE_INVERT
char falloff_shape
CurvesGeometry geometry
struct Object * surface
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)