Blender V4.3
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_attribute_math.hh"
18#include "BKE_brush.hh"
19#include "BKE_context.hh"
20#include "BKE_curves.hh"
21#include "BKE_paint.hh"
22
23#include "DNA_brush_enums.h"
24#include "DNA_brush_types.h"
25#include "DNA_curves_types.h"
26#include "DNA_object_types.h"
27#include "DNA_screen_types.h"
28#include "DNA_space_types.h"
29
30#include "ED_screen.hh"
31#include "ED_view3d.hh"
32
33#include "WM_api.hh"
34
36
46
47using bke::CurvesGeometry;
48
55 public:
56 virtual ~CurvesEffect() = default;
57 virtual void execute(CurvesGeometry &curves,
58 const IndexMask &curve_mask,
59 Span<float> move_distances_cu,
60 MutableSpan<float3> positions_cu) = 0;
61};
62
67 private:
68 const Brush &brush_;
69
71 struct ParameterizationBuffers {
72 Vector<float3> old_positions;
73 Vector<float> old_lengths;
74 Vector<float> sample_lengths;
75 Vector<int> indices;
76 Vector<float> factors;
77
78 void resize(const int points_num)
79 {
80 this->old_positions.resize(points_num);
81 this->old_lengths.resize(length_parameterize::segments_num(points_num, false));
82 this->sample_lengths.resize(points_num);
83 this->indices.resize(points_num);
84 this->factors.resize(points_num);
85 }
86 };
87
88 public:
89 ShrinkCurvesEffect(const Brush &brush) : brush_(brush) {}
90
91 void execute(CurvesGeometry &curves,
92 const IndexMask &curve_mask,
93 const Span<float> move_distances_cu,
94 MutableSpan<float3> positions_cu) override
95 {
96 const OffsetIndices points_by_curve = curves.points_by_curve();
97 curve_mask.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
98 ParameterizationBuffers data;
99 for (const int curve_i : segment) {
100 const float move_distance_cu = move_distances_cu[curve_i];
101 const IndexRange points = points_by_curve[curve_i];
102 this->shrink_curve(positions_cu.slice(points), move_distance_cu, data);
103 }
104 });
105 }
106
107 private:
108 void shrink_curve(MutableSpan<float3> positions,
109 const float shrink_length,
110 ParameterizationBuffers &data) const
111 {
112 namespace lp = length_parameterize;
113 data.resize(positions.size());
114
115 /* Copy the old positions to facilitate mixing from neighbors for the resulting curve. */
116 data.old_positions.as_mutable_span().copy_from(positions);
117
118 lp::accumulate_lengths<float3>(data.old_positions, false, data.old_lengths);
119
120 const float min_length = brush_.curves_sculpt_settings->minimum_length;
121 const float old_length = data.old_lengths.last();
122 const float new_length = std::max(min_length, old_length - shrink_length);
123 const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f);
124
125 data.sample_lengths.first() = 0.0f;
126 for (const int i : data.old_lengths.index_range()) {
127 data.sample_lengths[i + 1] = data.old_lengths[i] * length_factor;
128 }
129
130 lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors);
131
132 lp::interpolate<float3>(data.old_positions, data.indices, data.factors, positions);
133 }
134};
135
140 void execute(CurvesGeometry &curves,
141 const IndexMask &curve_mask,
142 const Span<float> move_distances_cu,
143 MutableSpan<float3> positions_cu) override
144 {
145 const OffsetIndices points_by_curve = curves.points_by_curve();
146 curve_mask.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
147 MoveAndResampleBuffers resample_buffer;
148 for (const int curve_i : segment) {
149 const float move_distance_cu = move_distances_cu[curve_i];
150 const IndexRange points = points_by_curve[curve_i];
151 if (points.size() <= 1) {
152 continue;
153 }
154
155 const float3 old_last_pos_cu = positions_cu[points.last()];
156 /* Use some point within the curve rather than the end point to smooth out some random
157 * variation. */
158 const float3 direction_reference_point =
159 positions_cu[points.size() > 2 ? points[points.size() / 2] : points.first()];
160 const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point);
161
162 const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu;
163 move_last_point_and_resample(resample_buffer, positions_cu.slice(points), new_last_pos_cu);
164 }
165 });
166 }
167};
168
173 private:
174 bool scale_up_;
175 const Brush &brush_;
176
177 public:
178 ScaleCurvesEffect(bool scale_up, const Brush &brush) : scale_up_(scale_up), brush_(brush) {}
179
181 const IndexMask &curve_mask,
182 const Span<float> move_distances_cu,
183 MutableSpan<float3> positions_cu) override
184 {
185 const OffsetIndices points_by_curve = curves.points_by_curve();
186 curve_mask.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
187 const float move_distance_cu = move_distances_cu[curve_i];
188 const IndexRange points = points_by_curve[curve_i];
189
190 const float old_length = this->compute_poly_curve_length(positions_cu.slice(points));
191 const float length_diff = scale_up_ ? move_distance_cu : -move_distance_cu;
192 const float min_length = brush_.curves_sculpt_settings->minimum_length;
193 const float new_length = std::max(min_length, old_length + length_diff);
194 const float scale_factor = math::safe_divide(new_length, old_length);
195
196 const float3 &root_pos_cu = positions_cu[points[0]];
197 for (float3 &pos_cu : positions_cu.slice(points.drop_front(1))) {
198 pos_cu = (pos_cu - root_pos_cu) * scale_factor + root_pos_cu;
199 }
200 });
201 }
202
204 {
205 float length = 0.0f;
206 const int segments_num = positions.size() - 1;
207 for (const int segment_i : IndexRange(segments_num)) {
208 const float3 &p1 = positions[segment_i];
209 const float3 &p2 = positions[segment_i + 1];
210 length += math::distance(p1, p2);
211 }
212 return length;
213 }
214};
215
217 private:
218 std::unique_ptr<CurvesEffect> effect_;
219 float2 last_mouse_position_;
220 CurvesBrush3D brush_3d_;
221
223
224 public:
225 CurvesEffectOperation(std::unique_ptr<CurvesEffect> effect) : effect_(std::move(effect)) {}
226
227 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
228};
229
237
238 Object *object_ = nullptr;
239 Curves *curves_id_ = nullptr;
241
245
246 const Brush *brush_ = nullptr;
250
252
254
257
259
261 const bContext &C,
262 const StrokeExtension &stroke_extension)
263 {
264 BLI_SCOPED_DEFER([&]() { self.last_mouse_position_ = stroke_extension.mouse_position; });
265
266 self_ = &self;
268
269 curves_id_ = static_cast<Curves *>(object_->data);
270 curves_ = &curves_id_->geometry.wrap();
271 if (curves_->curves_num() == 0) {
272 return;
273 }
274
276 ".selection", bke::AttrDomain::Curve, 1.0f);
278
279 const CurvesSculpt &curves_sculpt = *ctx_.scene->toolsettings->curves_sculpt;
280 brush_ = BKE_paint_brush_for_read(&curves_sculpt.paint);
281 brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
282
284 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
285 brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
286
288
290
291 brush_pos_start_re_ = self.last_mouse_position_;
292 brush_pos_end_re_ = stroke_extension.mouse_position;
293
294 if (stroke_extension.is_first) {
296 if (std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
298 *ctx_.region,
299 *ctx_.v3d,
300 *ctx_.rv3d,
301 *object_,
302 stroke_extension.mouse_position,
304 {
305 self.brush_3d_ = *brush_3d;
306 }
307 }
308
309 return;
310 }
311
312 Array<float> move_distances_cu(curves_->curves_num());
313
314 /* Compute influences. */
316 this->gather_influences_projected(move_distances_cu);
317 }
319 this->gather_influences_spherical(move_distances_cu);
320 }
321
322 IndexMaskMemory memory;
323 const IndexMask curves_mask = IndexMask::from_predicate(
324 curve_selection_, GrainSize(4096), memory, [&](const int64_t curve_i) {
325 return move_distances_cu[curve_i] > 0.0f;
326 });
327
328 /* Execute effect. */
330 self_->effect_->execute(*curves_, curves_mask, move_distances_cu, positions_cu);
331
336 }
337
339 {
340 const bke::crazyspace::GeometryDeformation deformation =
342 const OffsetIndices points_by_curve = curves_->points_by_curve();
343
345
346 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
348 Vector<float4x4> symmetry_brush_transforms_inv;
349 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
350 symmetry_brush_transforms_inv.append(math::invert(brush_transform));
351 }
352
353 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
354 const float brush_radius_sq_re = pow2f(brush_radius_re);
355
357 const IndexRange points = points_by_curve[curve_i];
358
359 const float curve_selection_factor = curve_selection_factors_[curve_i];
360
361 float max_move_distance_cu = 0.0f;
362 for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
363 for (const int segment_i : points.drop_back(1)) {
364 const float3 p1_cu = math::transform_point(brush_transform_inv,
365 deformation.positions[segment_i]);
366 const float3 p2_cu = math::transform_point(brush_transform_inv,
367 deformation.positions[segment_i + 1]);
368
369 const float2 p1_re = ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, projection);
370 const float2 p2_re = ED_view3d_project_float_v2_m4(ctx_.region, p2_cu, projection);
371
372 float2 closest_on_brush_re;
373 float2 closest_on_segment_re;
374 float lambda_on_brush;
375 float lambda_on_segment;
376 const float dist_to_brush_sq_re = closest_seg_seg_v2(closest_on_brush_re,
377 closest_on_segment_re,
378 &lambda_on_brush,
379 &lambda_on_segment,
382 p1_re,
383 p2_re);
384
385 if (dist_to_brush_sq_re > brush_radius_sq_re) {
386 continue;
387 }
388
389 const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
390 const float radius_falloff = BKE_brush_curve_strength(
391 brush_, dist_to_brush_re, brush_radius_re);
392 const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
393
394 const float3 closest_on_segment_cu = math::interpolate(p1_cu, p2_cu, lambda_on_segment);
395
396 float3 brush_start_pos_wo, brush_end_pos_wo;
398 ctx_.v3d,
399 ctx_.region,
400 math::transform_point(transforms_.curves_to_world, closest_on_segment_cu),
402 brush_start_pos_wo);
404 ctx_.v3d,
405 ctx_.region,
406 math::transform_point(transforms_.curves_to_world, closest_on_segment_cu),
408 brush_end_pos_wo);
409 const float3 brush_start_pos_cu = math::transform_point(transforms_.world_to_curves,
410 brush_start_pos_wo);
412 brush_end_pos_wo);
413
414 const float move_distance_cu = weight *
415 math::distance(brush_start_pos_cu, brush_end_pos_cu);
416 max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
417 }
418 }
419 move_distances_cu[curve_i] = max_move_distance_cu;
420 });
421 }
422
424 {
425 const bke::crazyspace::GeometryDeformation deformation =
427
429 self_->brush_3d_.position_cu);
430
431 float3 brush_pos_start_wo, brush_pos_end_wo;
433 ctx_.v3d, ctx_.region, brush_pos_wo, brush_pos_start_re_, brush_pos_start_wo);
434 ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, brush_pos_wo, brush_pos_end_re_, brush_pos_end_wo);
435 const float3 brush_pos_start_cu = math::transform_point(transforms_.world_to_curves,
436 brush_pos_start_wo);
438 brush_pos_end_wo);
439 const float3 brush_pos_diff_cu = brush_pos_end_cu - brush_pos_start_cu;
440 const float brush_pos_diff_length_cu = math::length(brush_pos_diff_cu);
441 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
442 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
443
444 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
446 const OffsetIndices points_by_curve = curves_->points_by_curve();
447
449 const IndexRange points = points_by_curve[curve_i];
450
451 const float curve_selection_factor = curve_selection_factors_[curve_i];
452
453 float max_move_distance_cu = 0.0f;
454 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
455 const float3 brush_pos_start_transformed_cu = math::transform_point(brush_transform,
456 brush_pos_start_cu);
457 const float3 brush_pos_end_transformed_cu = math::transform_point(brush_transform,
458 brush_pos_end_cu);
459
460 for (const int segment_i : points.drop_back(1)) {
461 const float3 &p1_cu = deformation.positions[segment_i];
462 const float3 &p2_cu = deformation.positions[segment_i + 1];
463
464 float3 closest_on_segment_cu;
465 float3 closest_on_brush_cu;
466 isect_seg_seg_v3(p1_cu,
467 p2_cu,
468 brush_pos_start_transformed_cu,
469 brush_pos_end_transformed_cu,
470 closest_on_segment_cu,
471 closest_on_brush_cu);
472
473 const float dist_to_brush_sq_cu = math::distance_squared(closest_on_segment_cu,
474 closest_on_brush_cu);
475 if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
476 continue;
477 }
478
479 const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
480 const float radius_falloff = BKE_brush_curve_strength(
481 brush_, dist_to_brush_cu, brush_radius_cu);
482 const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
483
484 const float move_distance_cu = weight * brush_pos_diff_length_cu;
485 max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
486 }
487 }
488 move_distances_cu[curve_i] = max_move_distance_cu;
489 });
490 }
491};
492
494 const StrokeExtension &stroke_extension)
495{
497 executor.execute(*this, C, stroke_extension);
498}
499
500std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
501 const BrushStrokeMode brush_mode, const bContext &C)
502{
503 const Scene &scene = *CTX_data_scene(&C);
504 const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
505 const bool use_scale_uniform = brush.curves_sculpt_settings->flag &
507 const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0);
508
509 if (use_grow) {
510 if (use_scale_uniform) {
511 return std::make_unique<CurvesEffectOperation>(
512 std::make_unique<ScaleCurvesEffect>(true, brush));
513 }
514 return std::make_unique<CurvesEffectOperation>(std::make_unique<ExtrapolateCurvesEffect>());
515 }
516 if (use_scale_uniform) {
517 return std::make_unique<CurvesEffectOperation>(
518 std::make_unique<ScaleCurvesEffect>(false, brush));
519 }
520 return std::make_unique<CurvesEffectOperation>(std::make_unique<ShrinkCurvesEffect>(brush));
521}
522
523} // namespace blender::ed::sculpt_paint
int BKE_brush_size_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1075
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
Definition brush.cc:1388
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
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:303
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:1041
@ 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.
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
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 NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
PyObject * self
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
void append(const T &value)
void resize(const int64_t new_size)
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type, const void *default_value=nullptr) const
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
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
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
void foreach_index(Fn &&fn) const
void foreach_segment(Fn &&fn) const
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
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)
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)
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)
BrushStrokeMode
@ BRUSH_STROKE_INVERT
__int64 int64_t
Definition stdint.h:89
char falloff_shape
struct BrushCurvesSculptSettings * curves_sculpt_settings
CurvesGeometry geometry
struct Object * surface
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)
void WM_main_add_notifier(uint type, void *reference)