Blender V5.0
curves_sculpt_grow_shrink.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_vector.hh"
8
10#include "BLI_math_geom.h"
12#include "BLI_task.hh"
13#include "BLI_vector.hh"
14
15#include "DEG_depsgraph.hh"
16
17#include "BKE_brush.hh"
18#include "BKE_context.hh"
19#include "BKE_curves.hh"
20#include "BKE_paint.hh"
21
22#include "DNA_brush_enums.h"
23#include "DNA_brush_types.h"
24#include "DNA_curves_types.h"
25#include "DNA_object_types.h"
26
27#include "ED_screen.hh"
28#include "ED_view3d.hh"
29
30#include "WM_api.hh"
31
33
41
43
44using bke::CurvesGeometry;
45
52 public:
53 virtual ~CurvesEffect() = default;
55 const IndexMask &curve_mask,
56 Span<float> move_distances_cu,
57 MutableSpan<float3> positions_cu) = 0;
58};
59
64 private:
65 const Brush &brush_;
66
68 struct ParameterizationBuffers {
69 Vector<float3> old_positions;
70 Vector<float> old_lengths;
71 Vector<float> sample_lengths;
73 Vector<float> factors;
74
75 void resize(const int points_num)
76 {
77 this->old_positions.resize(points_num);
78 this->old_lengths.resize(length_parameterize::segments_num(points_num, false));
79 this->sample_lengths.resize(points_num);
80 this->indices.resize(points_num);
81 this->factors.resize(points_num);
82 }
83 };
84
85 public:
86 ShrinkCurvesEffect(const Brush &brush) : brush_(brush) {}
87
89 const IndexMask &curve_mask,
90 const Span<float> move_distances_cu,
91 MutableSpan<float3> positions_cu) override
92 {
93 const OffsetIndices points_by_curve = curves.points_by_curve();
94 curve_mask.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
95 ParameterizationBuffers data;
96 for (const int curve_i : segment) {
97 const float move_distance_cu = move_distances_cu[curve_i];
98 const IndexRange points = points_by_curve[curve_i];
99 this->shrink_curve(positions_cu.slice(points), move_distance_cu, data);
100 }
101 });
102 }
103
104 private:
105 void shrink_curve(MutableSpan<float3> positions,
106 const float shrink_length,
107 ParameterizationBuffers &data) const
108 {
109 namespace lp = length_parameterize;
110 data.resize(positions.size());
111
112 /* Copy the old positions to facilitate mixing from neighbors for the resulting curve. */
113 data.old_positions.as_mutable_span().copy_from(positions);
114
115 lp::accumulate_lengths<float3>(data.old_positions, false, data.old_lengths);
116
117 const float min_length = brush_.curves_sculpt_settings->minimum_length;
118 const float old_length = data.old_lengths.last();
119 const float new_length = std::max(min_length, old_length - shrink_length);
120 const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f);
121
122 data.sample_lengths.first() = 0.0f;
123 for (const int i : data.old_lengths.index_range()) {
124 data.sample_lengths[i + 1] = data.old_lengths[i] * length_factor;
125 }
126
127 lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors);
128
129 lp::interpolate<float3>(data.old_positions, data.indices, data.factors, positions);
130 }
131};
132
137 void execute(CurvesGeometry &curves,
138 const IndexMask &curve_mask,
139 const Span<float> move_distances_cu,
140 MutableSpan<float3> positions_cu) override
141 {
142 const OffsetIndices points_by_curve = curves.points_by_curve();
143 curve_mask.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
144 MoveAndResampleBuffers resample_buffer;
145 for (const int curve_i : segment) {
146 const float move_distance_cu = move_distances_cu[curve_i];
147 const IndexRange points = points_by_curve[curve_i];
148 if (points.size() <= 1) {
149 continue;
150 }
151
152 const float3 old_last_pos_cu = positions_cu[points.last()];
153 /* Use some point within the curve rather than the end point to smooth out some random
154 * variation. */
155 const float3 direction_reference_point =
156 positions_cu[points.size() > 2 ? points[points.size() / 2] : points.first()];
157 const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point);
158
159 const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu;
160 move_last_point_and_resample(resample_buffer, positions_cu.slice(points), new_last_pos_cu);
161 }
162 });
163 }
164};
165
170 private:
171 bool scale_up_;
172 const Brush &brush_;
173
174 public:
175 ScaleCurvesEffect(bool scale_up, const Brush &brush) : scale_up_(scale_up), brush_(brush) {}
176
178 const IndexMask &curve_mask,
179 const Span<float> move_distances_cu,
180 MutableSpan<float3> positions_cu) override
181 {
182 const OffsetIndices points_by_curve = curves.points_by_curve();
183 curve_mask.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
184 const float move_distance_cu = move_distances_cu[curve_i];
185 const IndexRange points = points_by_curve[curve_i];
186
187 const float old_length = this->compute_poly_curve_length(positions_cu.slice(points));
188 const float length_diff = scale_up_ ? move_distance_cu : -move_distance_cu;
189 const float min_length = brush_.curves_sculpt_settings->minimum_length;
190 const float new_length = std::max(min_length, old_length + length_diff);
191 const float scale_factor = math::safe_divide(new_length, old_length);
192
193 const float3 &root_pos_cu = positions_cu[points[0]];
194 for (float3 &pos_cu : positions_cu.slice(points.drop_front(1))) {
195 pos_cu = (pos_cu - root_pos_cu) * scale_factor + root_pos_cu;
196 }
197 });
198 }
199
201 {
202 float length = 0.0f;
203 const int segments_num = positions.size() - 1;
204 for (const int segment_i : IndexRange(segments_num)) {
205 const float3 &p1 = positions[segment_i];
206 const float3 &p2 = positions[segment_i + 1];
207 length += math::distance(p1, p2);
208 }
209 return length;
210 }
211};
212
214 private:
215 std::unique_ptr<CurvesEffect> effect_;
216 float2 last_mouse_position_;
217 CurvesBrush3D brush_3d_;
218
220
221 public:
222 CurvesEffectOperation(std::unique_ptr<CurvesEffect> effect) : effect_(std::move(effect)) {}
223
224 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
225};
226
234
235 Object *object_ = nullptr;
236 Curves *curves_id_ = nullptr;
238
242
244 const Brush *brush_ = nullptr;
248
250
252
255
257
259 const bContext &C,
260 const StrokeExtension &stroke_extension)
261 {
262 BLI_SCOPED_DEFER([&]() { self.last_mouse_position_ = stroke_extension.mouse_position; });
263
264 self_ = &self;
266
267 curves_id_ = static_cast<Curves *>(object_->data);
268 curves_ = &curves_id_->geometry.wrap();
269 if (curves_->is_empty()) {
270 return;
271 }
272
273 curve_selection_factors_ = *curves_->attributes().lookup_or_default(
274 ".selection", bke::AttrDomain::Curve, 1.0f);
276
277 curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
280 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
281 brush_strength_ = brush_strength_get(curves_sculpt_->paint, *brush_, stroke_extension);
282
283 falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape);
284
286
287 brush_pos_start_re_ = self.last_mouse_position_;
288 brush_pos_end_re_ = stroke_extension.mouse_position;
289
290 if (stroke_extension.is_first) {
292 if (std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
293 *ctx_.depsgraph,
294 *ctx_.region,
295 *ctx_.v3d,
296 *ctx_.rv3d,
297 *object_,
298 stroke_extension.mouse_position,
300 {
301 self.brush_3d_ = *brush_3d;
304 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
305 }
306 }
307
308 return;
309 }
310
311 Array<float> move_distances_cu(curves_->curves_num());
312
313 /* Compute influences. */
315 this->gather_influences_projected(move_distances_cu);
316 }
318 this->gather_influences_spherical(move_distances_cu);
319 }
320
321 IndexMaskMemory memory;
322 const IndexMask curves_mask = IndexMask::from_predicate(
323 curve_selection_, GrainSize(4096), memory, [&](const int64_t curve_i) {
324 return move_distances_cu[curve_i] > 0.0f;
325 });
326
327 /* Execute effect. */
328 MutableSpan<float3> positions_cu = curves_->positions_for_write();
329 self_->effect_->execute(*curves_, curves_mask, move_distances_cu, positions_cu);
330
331 curves_->tag_positions_changed();
335 }
336
338 {
339 const bke::crazyspace::GeometryDeformation deformation =
341 const OffsetIndices points_by_curve = curves_->points_by_curve();
342
343 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
344
345 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
347 Vector<float4x4> symmetry_brush_transforms_inv;
348 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
349 symmetry_brush_transforms_inv.append(math::invert(brush_transform));
350 }
351
352 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
353 const float brush_radius_sq_re = pow2f(brush_radius_re);
354
355 curve_selection_.foreach_index(GrainSize(256), [&](int64_t curve_i) {
356 const IndexRange points = points_by_curve[curve_i];
357
358 const float curve_selection_factor = curve_selection_factors_[curve_i];
359
360 float max_move_distance_cu = 0.0f;
361 for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
362 for (const int segment_i : points.drop_back(1)) {
363 const float3 p1_cu = math::transform_point(brush_transform_inv,
364 deformation.positions[segment_i]);
365 const float3 p2_cu = math::transform_point(brush_transform_inv,
366 deformation.positions[segment_i + 1]);
367
368 const float2 p1_re = ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, projection);
369 const float2 p2_re = ED_view3d_project_float_v2_m4(ctx_.region, p2_cu, projection);
370
371 float2 closest_on_brush_re;
372 float2 closest_on_segment_re;
373 float lambda_on_brush;
374 float lambda_on_segment;
375 const float dist_to_brush_sq_re = closest_seg_seg_v2(closest_on_brush_re,
376 closest_on_segment_re,
377 &lambda_on_brush,
378 &lambda_on_segment,
381 p1_re,
382 p2_re);
383
384 if (dist_to_brush_sq_re > brush_radius_sq_re) {
385 continue;
386 }
387
388 const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
389 const float radius_falloff = BKE_brush_curve_strength(
390 brush_, dist_to_brush_re, brush_radius_re);
391 const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
392
393 const float3 closest_on_segment_cu = math::interpolate(p1_cu, p2_cu, lambda_on_segment);
394
395 float3 brush_start_pos_wo, brush_end_pos_wo;
397 ctx_.v3d,
398 ctx_.region,
399 math::transform_point(transforms_.curves_to_world, closest_on_segment_cu),
401 brush_start_pos_wo);
403 ctx_.v3d,
404 ctx_.region,
405 math::transform_point(transforms_.curves_to_world, closest_on_segment_cu),
407 brush_end_pos_wo);
408 const float3 brush_start_pos_cu = math::transform_point(transforms_.world_to_curves,
409 brush_start_pos_wo);
410 const float3 brush_end_pos_cu = math::transform_point(transforms_.world_to_curves,
411 brush_end_pos_wo);
412
413 const float move_distance_cu = weight *
414 math::distance(brush_start_pos_cu, brush_end_pos_cu);
415 max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
416 }
417 }
418 move_distances_cu[curve_i] = max_move_distance_cu;
419 });
420 }
421
423 {
424 const bke::crazyspace::GeometryDeformation deformation =
426
427 float3 brush_pos_wo = math::transform_point(transforms_.curves_to_world,
428 self_->brush_3d_.position_cu);
429
430 float3 brush_pos_start_wo, brush_pos_end_wo;
432 ctx_.v3d, ctx_.region, brush_pos_wo, brush_pos_start_re_, brush_pos_start_wo);
433 ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, brush_pos_wo, brush_pos_end_re_, brush_pos_end_wo);
434 const float3 brush_pos_start_cu = math::transform_point(transforms_.world_to_curves,
435 brush_pos_start_wo);
436 const float3 brush_pos_end_cu = math::transform_point(transforms_.world_to_curves,
437 brush_pos_end_wo);
438 const float3 brush_pos_diff_cu = brush_pos_end_cu - brush_pos_start_cu;
439 const float brush_pos_diff_length_cu = math::length(brush_pos_diff_cu);
440 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
441 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
442
443 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
445 const OffsetIndices points_by_curve = curves_->points_by_curve();
446
447 curve_selection_.foreach_index(GrainSize(256), [&](int64_t curve_i) {
448 const IndexRange points = points_by_curve[curve_i];
449
450 const float curve_selection_factor = curve_selection_factors_[curve_i];
451
452 float max_move_distance_cu = 0.0f;
453 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
454 const float3 brush_pos_start_transformed_cu = math::transform_point(brush_transform,
455 brush_pos_start_cu);
456 const float3 brush_pos_end_transformed_cu = math::transform_point(brush_transform,
457 brush_pos_end_cu);
458
459 for (const int segment_i : points.drop_back(1)) {
460 const float3 &p1_cu = deformation.positions[segment_i];
461 const float3 &p2_cu = deformation.positions[segment_i + 1];
462
463 float3 closest_on_segment_cu;
464 float3 closest_on_brush_cu;
465 isect_seg_seg_v3(p1_cu,
466 p2_cu,
467 brush_pos_start_transformed_cu,
468 brush_pos_end_transformed_cu,
469 closest_on_segment_cu,
470 closest_on_brush_cu);
471
472 const float dist_to_brush_sq_cu = math::distance_squared(closest_on_segment_cu,
473 closest_on_brush_cu);
474 if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
475 continue;
476 }
477
478 const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
479 const float radius_falloff = BKE_brush_curve_strength(
480 brush_, dist_to_brush_cu, brush_radius_cu);
481 const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
482
483 const float move_distance_cu = weight * brush_pos_diff_length_cu;
484 max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
485 }
486 }
487 move_distances_cu[curve_i] = max_move_distance_cu;
488 });
489 }
490};
491
493 const StrokeExtension &stroke_extension)
494{
496 executor.execute(*this, C, stroke_extension);
497}
498
499std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
500 const BrushStrokeMode brush_mode, const bContext &C)
501{
502 const Scene &scene = *CTX_data_scene(&C);
504 const bool use_scale_uniform = brush.curves_sculpt_settings->flag &
506 const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0);
507
508 if (use_grow) {
509 if (use_scale_uniform) {
510 return std::make_unique<CurvesEffectOperation>(
511 std::make_unique<ScaleCurvesEffect>(true, brush));
512 }
513 return std::make_unique<CurvesEffectOperation>(std::make_unique<ExtrapolateCurvesEffect>());
514 }
515 if (use_scale_uniform) {
516 return std::make_unique<CurvesEffectOperation>(
517 std::make_unique<ScaleCurvesEffect>(false, brush));
518 }
519 return std::make_unique<CurvesEffectOperation>(std::make_unique<ShrinkCurvesEffect>(brush));
520}
521
522} // namespace blender::ed::sculpt_paint
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
Definition brush.cc:1577
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
MINLINE float pow2f(float x)
float closest_seg_seg_v2(float r_closest_a[2], float r_closest_b[2], float *r_lambda_a, float *r_lambda_b, const float a1[2], const float a2[2], const float b1[2], const float b2[2])
Definition math_geom.cc:305
void isect_seg_seg_v3(const float a0[3], const float a1[3], const float b0[3], const float b1[3], float r_a[3], float r_b[3])
#define BLI_SCOPED_DEFER(function_to_defer)
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
@ BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM
eCurvesSymmetryType
Object is a sort of wrapper for general info.
@ USER_ORBIT_SELECTION
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
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
BMesh const char void * data
PyObject * self
long long int int64_t
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr int64_t size() const
Definition BLI_span.hh:252
void append(const T &value)
void resize(const int64_t new_size)
CurvesEffectOperation(std::unique_ptr< CurvesEffect > effect)
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
virtual void execute(CurvesGeometry &curves, const IndexMask &curve_mask, Span< float > move_distances_cu, MutableSpan< float3 > positions_cu)=0
void execute(CurvesGeometry &curves, const IndexMask &curve_mask, const Span< float > move_distances_cu, MutableSpan< float3 > positions_cu) override
float compute_poly_curve_length(const Span< float3 > positions)
void execute(CurvesGeometry &curves, const IndexMask &curve_mask, const Span< float > move_distances_cu, MutableSpan< float3 > positions_cu) override
void foreach_index(Fn &&fn) const
void foreach_segment(Fn &&fn) const
static ushort indices[]
float length(VecOp< float, D >) RET
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
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)
std::unique_ptr< CurvesSculptStrokeOperation > new_grow_shrink_operation(const BrushStrokeMode brush_mode, const bContext &C)
void remember_stroke_position(CurvesSculpt &curves_sculpt, const float3 &brush_position_wo)
Vector< float4x4 > get_symmetry_brush_transforms(const eCurvesSymmetryType symmetry)
float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension)
void move_last_point_and_resample(MoveAndResampleBuffers &buffer, MutableSpan< float3 > positions, const float3 &new_last_position)
int segments_num(const int points_num, const bool cyclic)
T safe_divide(const T &a, const T &b)
T distance(const T &a, const T &b)
T length(const VecBase< T, Size > &a)
CartesianBasis invert(const CartesianBasis &basis)
T interpolate(const T &a, const T &b, const FactorT &t)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
BrushStrokeMode
@ BRUSH_STROKE_INVERT
struct BrushCurvesSculptSettings * curves_sculpt_settings
struct ToolSettings * toolsettings
CurvesSculpt * curves_sculpt
void gather_influences_projected(MutableSpan< float > move_distances_cu)
void gather_influences_spherical(MutableSpan< float > move_distances_cu)
void execute(CurvesEffectOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
i
Definition text_draw.cc:230
void WM_main_add_notifier(uint type, void *reference)