Blender  V3.3
curves_sculpt_grow_shrink.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include <algorithm>
4 
6 #include "BLI_float4x4.hh"
8 #include "BLI_vector.hh"
9 
10 #include "PIL_time.h"
11 
12 #include "DEG_depsgraph.h"
13 
14 #include "BKE_attribute_math.hh"
15 #include "BKE_brush.h"
16 #include "BKE_context.h"
17 #include "BKE_curves.hh"
18 #include "BKE_paint.h"
19 
20 #include "DNA_brush_enums.h"
21 #include "DNA_brush_types.h"
22 #include "DNA_curves_types.h"
23 #include "DNA_object_types.h"
24 #include "DNA_screen_types.h"
25 #include "DNA_space_types.h"
26 
27 #include "ED_screen.h"
28 #include "ED_view3d.h"
29 
30 #include "WM_api.h"
31 
32 #include "curves_sculpt_intern.hh"
33 
42 namespace blender::ed::sculpt_paint {
43 
45 
51 class CurvesEffect {
52  public:
53  virtual ~CurvesEffect() = default;
54  virtual void execute(CurvesGeometry &curves,
55  Span<int> curve_indices,
56  Span<float> move_distances_cu) = 0;
57 };
58 
63  private:
64  const Brush &brush_;
65 
67  struct ParameterizationBuffers {
68  Array<float3> old_positions;
69  Array<float> old_lengths;
70  Array<float> sample_lengths;
72  Array<float> factors;
73 
74  void reinitialize(const int points_num)
75  {
76  this->old_positions.reinitialize(points_num);
77  this->old_lengths.reinitialize(length_parameterize::segments_num(points_num, false));
78  this->sample_lengths.reinitialize(points_num);
79  this->indices.reinitialize(points_num);
80  this->factors.reinitialize(points_num);
81  }
82  };
83 
84  public:
85  ShrinkCurvesEffect(const Brush &brush) : brush_(brush)
86  {
87  }
88 
90  const Span<int> curve_indices,
91  const Span<float> move_distances_cu) override
92  {
93  MutableSpan<float3> positions_cu = curves.positions_for_write();
94  threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
95  ParameterizationBuffers data;
96  for (const int influence_i : range) {
97  const int curve_i = curve_indices[influence_i];
98  const float move_distance_cu = move_distances_cu[influence_i];
99  const IndexRange points = curves.points_for_curve(curve_i);
100  this->shrink_curve(positions_cu.slice(points), move_distance_cu, data);
101  }
102  });
103  }
104 
105  private:
106  void shrink_curve(MutableSpan<float3> positions,
107  const float shrink_length,
108  ParameterizationBuffers &data) const
109  {
110  namespace lp = length_parameterize;
111  data.reinitialize(positions.size());
112 
113  /* Copy the old positions to facilitate mixing from neighbors for the resulting curve. */
114  data.old_positions.as_mutable_span().copy_from(positions);
115 
116  lp::accumulate_lengths<float3>(data.old_positions, false, data.old_lengths);
117 
118  const float min_length = brush_.curves_sculpt_settings->minimum_length;
119  const float old_length = data.old_lengths.last();
120  const float new_length = std::max(min_length, old_length - shrink_length);
121  const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f);
122 
123  data.sample_lengths.first() = 0.0f;
124  for (const int i : data.old_lengths.index_range()) {
125  data.sample_lengths[i + 1] = data.old_lengths[i] * length_factor;
126  }
127 
128  lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors);
129 
130  lp::interpolate<float3>(data.old_positions, data.indices, data.factors, positions);
131  }
132 };
133 
138  void execute(CurvesGeometry &curves,
139  const Span<int> curve_indices,
140  const Span<float> move_distances_cu) override
141  {
142  MutableSpan<float3> positions_cu = curves.positions_for_write();
143  threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
144  for (const int influence_i : range) {
145  const int curve_i = curve_indices[influence_i];
146  const float move_distance_cu = move_distances_cu[influence_i];
147  const IndexRange curve_points = curves.points_for_curve(curve_i);
148 
149  if (curve_points.size() <= 1) {
150  continue;
151  }
152 
153  const float3 old_last_pos_cu = positions_cu[curve_points.last()];
154  /* Use some point within the curve rather than the end point to smooth out some random
155  * variation. */
156  const float3 direction_reference_point =
157  positions_cu[curve_points.size() > 2 ? curve_points[curve_points.size() / 2] :
158  curve_points.first()];
159  const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point);
160 
161  const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu;
162  move_last_point_and_resample(positions_cu.slice(curve_points), new_last_pos_cu);
163  }
164  });
165  }
166 };
167 
172  private:
173  bool scale_up_;
174  const Brush &brush_;
175 
176  public:
177  ScaleCurvesEffect(bool scale_up, const Brush &brush) : scale_up_(scale_up), brush_(brush)
178  {
179  }
180 
182  const Span<int> curve_indices,
183  const Span<float> move_distances_cu) override
184  {
185  MutableSpan<float3> positions_cu = curves.positions_for_write();
186  threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
187  for (const int influence_i : range) {
188  const int curve_i = curve_indices[influence_i];
189  const float move_distance_cu = move_distances_cu[influence_i];
190  const IndexRange points = curves.points_for_curve(curve_i);
191 
192  const float old_length = this->compute_poly_curve_length(positions_cu.slice(points));
193  const float length_diff = scale_up_ ? move_distance_cu : -move_distance_cu;
194  const float min_length = brush_.curves_sculpt_settings->minimum_length;
195  const float new_length = std::max(min_length, old_length + length_diff);
196  const float scale_factor = safe_divide(new_length, old_length);
197 
198  const float3 &root_pos_cu = positions_cu[points[0]];
199  for (float3 &pos_cu : positions_cu.slice(points.drop_front(1))) {
200  pos_cu = (pos_cu - root_pos_cu) * scale_factor + root_pos_cu;
201  }
202  }
203  });
204  }
205 
207  {
208  float length = 0.0f;
209  const int segments_num = positions.size() - 1;
210  for (const int segment_i : IndexRange(segments_num)) {
211  const float3 &p1 = positions[segment_i];
212  const float3 &p2 = positions[segment_i + 1];
213  length += math::distance(p1, p2);
214  }
215  return length;
216  }
217 };
218 
220  private:
221  std::unique_ptr<CurvesEffect> effect_;
222  float2 last_mouse_position_;
223  CurvesBrush3D brush_3d_;
224 
226 
227  public:
228  CurvesEffectOperation(std::unique_ptr<CurvesEffect> effect) : effect_(std::move(effect))
229  {
230  }
231 
232  void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
233 };
234 
240  CurvesEffectOperation *self_ = nullptr;
242 
243  Object *object_ = nullptr;
244  Curves *curves_id_ = nullptr;
245  CurvesGeometry *curves_ = nullptr;
246 
250 
251  const Brush *brush_ = nullptr;
255 
257 
259 
262 
263  struct Influences {
266  };
267 
269  {
270  }
271 
273  const bContext &C,
274  const StrokeExtension &stroke_extension)
275  {
276  BLI_SCOPED_DEFER([&]() { self.last_mouse_position_ = stroke_extension.mouse_position; });
277 
278  self_ = &self;
279  object_ = CTX_data_active_object(&C);
280 
281  curves_id_ = static_cast<Curves *>(object_->data);
282  curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
283  if (curves_->curves_num() == 0) {
284  return;
285  }
286 
287  curve_selection_factors_ = get_curves_selection(*curves_id_);
288  curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
289 
290  const CurvesSculpt &curves_sculpt = *ctx_.scene->toolsettings->curves_sculpt;
291  brush_ = BKE_paint_brush_for_read(&curves_sculpt.paint);
292  brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
293 
294  brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
295  brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
296  brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
297 
298  falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape);
299 
300  transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
301 
302  brush_pos_start_re_ = self.last_mouse_position_;
303  brush_pos_end_re_ = stroke_extension.mouse_position;
304 
305  if (stroke_extension.is_first) {
306  if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
307  if (std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
308  *ctx_.depsgraph,
309  *ctx_.region,
310  *ctx_.v3d,
311  *ctx_.rv3d,
312  *object_,
313  stroke_extension.mouse_position,
314  brush_radius_base_re_)) {
315  self.brush_3d_ = *brush_3d;
316  }
317  }
318 
319  return;
320  }
321 
322  /* Compute influences. */
324  if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) {
325  this->gather_influences_projected(influences_for_thread);
326  }
327  else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
328  this->gather_influences_spherical(influences_for_thread);
329  }
330 
331  /* Execute effect. */
332  threading::parallel_for_each(influences_for_thread, [&](const Influences &influences) {
333  BLI_assert(influences.curve_indices.size() == influences.move_distances_cu.size());
334  self_->effect_->execute(*curves_, influences.curve_indices, influences.move_distances_cu);
335  });
336 
337  curves_->tag_positions_changed();
338  DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
339  WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
341  }
342 
345  {
346  const bke::crazyspace::GeometryDeformation deformation =
348 
349  float4x4 projection;
350  ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
351 
352  const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
353  eCurvesSymmetryType(curves_id_->symmetry));
354  Vector<float4x4> symmetry_brush_transforms_inv;
355  for (const float4x4 brush_transform : symmetry_brush_transforms) {
356  symmetry_brush_transforms_inv.append(brush_transform.inverted());
357  }
358 
359  const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
360  const float brush_radius_sq_re = pow2f(brush_radius_re);
361 
362  threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
363  Influences &local_influences = influences_for_thread.local();
364 
365  for (const int curve_i : curves_range) {
366  const IndexRange points = curves_->points_for_curve(curve_i);
367 
368  const float curve_selection_factor = curve_selection_factors_[curve_i];
369 
370  float max_move_distance_cu = 0.0f;
371  for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
372  for (const int segment_i : points.drop_back(1)) {
373  const float3 p1_cu = brush_transform_inv * deformation.positions[segment_i];
374  const float3 p2_cu = brush_transform_inv * deformation.positions[segment_i + 1];
375 
376  float2 p1_re, p2_re;
377  ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, p1_re, projection.values);
378  ED_view3d_project_float_v2_m4(ctx_.region, p2_cu, p2_re, projection.values);
379 
380  float2 closest_on_brush_re;
381  float2 closest_on_segment_re;
382  float lambda_on_brush;
383  float lambda_on_segment;
384  const float dist_to_brush_sq_re = closest_seg_seg_v2(closest_on_brush_re,
385  closest_on_segment_re,
386  &lambda_on_brush,
387  &lambda_on_segment,
388  brush_pos_start_re_,
389  brush_pos_end_re_,
390  p1_re,
391  p2_re);
392 
393  if (dist_to_brush_sq_re > brush_radius_sq_re) {
394  continue;
395  }
396 
397  const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
398  const float radius_falloff = BKE_brush_curve_strength(
399  brush_, dist_to_brush_re, brush_radius_re);
400  const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
401 
402  const float3 closest_on_segment_cu = math::interpolate(
403  p1_cu, p2_cu, lambda_on_segment);
404 
405  float3 brush_start_pos_wo, brush_end_pos_wo;
406  ED_view3d_win_to_3d(ctx_.v3d,
407  ctx_.region,
408  transforms_.curves_to_world * closest_on_segment_cu,
409  brush_pos_start_re_,
410  brush_start_pos_wo);
411  ED_view3d_win_to_3d(ctx_.v3d,
412  ctx_.region,
413  transforms_.curves_to_world * closest_on_segment_cu,
414  brush_pos_end_re_,
415  brush_end_pos_wo);
416  const float3 brush_start_pos_cu = transforms_.world_to_curves * brush_start_pos_wo;
417  const float3 brush_end_pos_cu = transforms_.world_to_curves * brush_end_pos_wo;
418 
419  const float move_distance_cu = weight *
420  math::distance(brush_start_pos_cu, brush_end_pos_cu);
421  max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
422  }
423  }
424  if (max_move_distance_cu > 0.0f) {
425  local_influences.curve_indices.append(curve_i);
426  local_influences.move_distances_cu.append(max_move_distance_cu);
427  }
428  }
429  });
430  }
431 
434  {
435  const bke::crazyspace::GeometryDeformation deformation =
436  bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_);
437 
438  float3 brush_pos_start_wo, brush_pos_end_wo;
439  ED_view3d_win_to_3d(ctx_.v3d,
440  ctx_.region,
441  transforms_.curves_to_world * self_->brush_3d_.position_cu,
442  brush_pos_start_re_,
443  brush_pos_start_wo);
444  ED_view3d_win_to_3d(ctx_.v3d,
445  ctx_.region,
446  transforms_.curves_to_world * self_->brush_3d_.position_cu,
447  brush_pos_end_re_,
448  brush_pos_end_wo);
449  const float3 brush_pos_start_cu = transforms_.world_to_curves * brush_pos_start_wo;
450  const float3 brush_pos_end_cu = transforms_.world_to_curves * brush_pos_end_wo;
451  const float3 brush_pos_diff_cu = brush_pos_end_cu - brush_pos_start_cu;
452  const float brush_pos_diff_length_cu = math::length(brush_pos_diff_cu);
453  const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
454  const float brush_radius_sq_cu = pow2f(brush_radius_cu);
455 
456  const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
457  eCurvesSymmetryType(curves_id_->symmetry));
458 
459  threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
460  Influences &local_influences = influences_for_thread.local();
461 
462  for (const int curve_i : curves_range) {
463  const IndexRange points = curves_->points_for_curve(curve_i);
464 
465  float max_move_distance_cu = 0.0f;
466 
467  const float curve_selection_factor = curve_selection_factors_[curve_i];
468 
469  for (const float4x4 &brush_transform : symmetry_brush_transforms) {
470  const float3 brush_pos_start_transformed_cu = brush_transform * brush_pos_start_cu;
471  const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu;
472 
473  for (const int segment_i : points.drop_back(1)) {
474  const float3 &p1_cu = deformation.positions[segment_i];
475  const float3 &p2_cu = deformation.positions[segment_i + 1];
476 
477  float3 closest_on_segment_cu;
478  float3 closest_on_brush_cu;
479  isect_seg_seg_v3(p1_cu,
480  p2_cu,
481  brush_pos_start_transformed_cu,
482  brush_pos_end_transformed_cu,
483  closest_on_segment_cu,
484  closest_on_brush_cu);
485 
486  const float dist_to_brush_sq_cu = math::distance_squared(closest_on_segment_cu,
487  closest_on_brush_cu);
488  if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
489  continue;
490  }
491 
492  const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
493  const float radius_falloff = BKE_brush_curve_strength(
494  brush_, dist_to_brush_cu, brush_radius_cu);
495  const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
496 
497  const float move_distance_cu = weight * brush_pos_diff_length_cu;
498  max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
499  }
500  }
501  if (max_move_distance_cu > 0.0f) {
502  local_influences.curve_indices.append(curve_i);
503  local_influences.move_distances_cu.append(max_move_distance_cu);
504  }
505  }
506  });
507  }
508 };
509 
510 void CurvesEffectOperation::on_stroke_extended(const bContext &C,
511  const StrokeExtension &stroke_extension)
512 {
514  executor.execute(*this, C, stroke_extension);
515 }
516 
517 std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
518  const BrushStrokeMode brush_mode, const bContext &C)
519 {
520  const Scene &scene = *CTX_data_scene(&C);
522  const bool use_scale_uniform = brush.curves_sculpt_settings->flag &
524  const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0);
525 
526  if (use_grow) {
527  if (use_scale_uniform) {
528  return std::make_unique<CurvesEffectOperation>(
529  std::make_unique<ScaleCurvesEffect>(true, brush));
530  }
531  return std::make_unique<CurvesEffectOperation>(std::make_unique<ExtrapolateCurvesEffect>());
532  }
533  if (use_scale_uniform) {
534  return std::make_unique<CurvesEffectOperation>(
535  std::make_unique<ScaleCurvesEffect>(false, brush));
536  }
537  return std::make_unique<CurvesEffectOperation>(std::make_unique<ShrinkCurvesEffect>(brush));
538 }
539 
540 } // namespace blender::ed::sculpt_paint
int BKE_brush_size_get(const struct Scene *scene, const struct Brush *brush)
struct Scene * CTX_data_scene(const bContext *C)
Definition: context.c:1090
struct Object * CTX_data_active_object(const bContext *C)
Definition: context.c:1353
Low-level operations for curves.
const struct Brush * BKE_paint_brush_for_read(const struct Paint *p)
#define BLI_assert(a)
Definition: BLI_assert.h:46
MINLINE float pow2f(float x)
#define BLI_SCOPED_DEFER(function_to_defer)
void DEG_id_tag_update(struct ID *id, int flag)
@ ID_RECALC_GEOMETRY
Definition: DNA_ID.h:791
@ BRUSH_DIR_IN
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
@ BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM
struct CurvesGeometry CurvesGeometry
eCurvesSymmetryType
Object is a sort of wrapper for general info.
void ED_region_tag_redraw(struct ARegion *region)
Definition: area.c:655
void ED_view3d_ob_project_mat_get(const struct RegionView3D *v3d, const struct Object *ob, float r_pmat[4][4])
void ED_view3d_win_to_3d(const struct View3D *v3d, const struct ARegion *region, const float depth_pt[3], const float mval[2], float r_out[3])
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Bright Control the brightness and contrast of the input color Vector Map an input vectors to curves
Platform independent time functions.
#define C
Definition: RandGen.cpp:25
#define NC_GEOM
Definition: WM_types.h:343
#define ND_DATA
Definition: WM_types.h:456
void reinitialize(const int64_t new_size)
Definition: BLI_array.hh:387
constexpr IndexRange index_range() const
Definition: BLI_span.hh:401
int64_t size() const
Definition: BLI_vector.hh:694
void append(const T &value)
Definition: BLI_vector.hh:433
IndexRange curves_range() const
Definition: BKE_curves.hh:795
CurvesEffectOperation(std::unique_ptr< CurvesEffect > effect)
virtual void execute(CurvesGeometry &curves, Span< int > curve_indices, Span< float > move_distances_cu)=0
void execute(CurvesGeometry &curves, const Span< int > curve_indices, const Span< float > move_distances_cu) override
ScaleCurvesEffect(bool scale_up, const Brush &brush)
float compute_poly_curve_length(const Span< float3 > positions)
void execute(CurvesGeometry &curves, const Span< int > curve_indices, const Span< float > move_distances_cu) override
Scene scene
ccl_gpu_kernel_postfix int ccl_global int * indices
GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, const Object &ob_orig)
Definition: crazyspace.cc:595
int segments_num(const int points_num, const bool cyclic)
Definition: BKE_curves.hh:462
static struct PartialUpdateUser * wrap(PartialUpdateUserImpl *user)
VArray< float > get_curves_selection(const Curves &curves_id)
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)
std::unique_ptr< CurvesSculptStrokeOperation > new_grow_shrink_operation(const BrushStrokeMode brush_mode, const bContext &C)
static IndexMask retrieve_selected_curves(const CurvesGeometry &curves, const eAttrDomain domain, Vector< int64_t > &r_indices)
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)
int segments_num(const int points_num, const bool cyclic)
void sample_at_lengths(Span< float > accumulated_segment_lengths, Span< float > sample_lengths, MutableSpan< int > r_segment_indices, MutableSpan< float > r_factors)
T clamp(const T &a, const T &min, const T &max)
T length(const vec_base< T, Size > &a)
T distance(const T &a, const T &b)
void parallel_for(IndexRange range, int64_t grain_size, const Function &function)
Definition: BLI_task.hh:51
void parallel_for_each(Range &range, const Function &function)
Definition: BLI_task.hh:39
MutableSpan< float3 > positions
BrushStrokeMode
Definition: paint_intern.h:448
@ BRUSH_STROKE_INVERT
Definition: paint_intern.h:450
char falloff_shape
struct BrushCurvesSculptSettings * curves_sculpt_settings
CurvesGeometry geometry
struct Object * surface
void * data
struct ToolSettings * toolsettings
CurvesSculpt * curves_sculpt
void gather_influences_spherical(threading::EnumerableThreadSpecific< Influences > &influences_for_thread)
void gather_influences_projected(threading::EnumerableThreadSpecific< Influences > &influences_for_thread)
void execute(CurvesEffectOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
float values[4][4]
Definition: BLI_float4x4.hh:13
float max
void WM_main_add_notifier(unsigned int type, void *reference)