Blender V5.0
curves_sculpt_comb.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
6
7#include "BLI_math_geom.h"
9#include "BLI_vector.hh"
10
11#include "DEG_depsgraph.hh"
13
14#include "BKE_brush.hh"
15#include "BKE_colortools.hh"
16#include "BKE_context.hh"
17#include "BKE_crazyspace.hh"
18#include "BKE_curves.hh"
19#include "BKE_geometry_set.hh"
20#include "BKE_mesh.hh"
21#include "BKE_mesh_runtime.hh"
22#include "BKE_paint.hh"
23
24#include "DNA_brush_enums.h"
25#include "DNA_brush_types.h"
26#include "DNA_curves_types.h"
27#include "DNA_object_types.h"
28#include "DNA_screen_types.h"
29
30#include "ED_screen.hh"
31#include "ED_view3d.hh"
32
33#include "WM_api.hh"
34
35#include <numeric>
36
44
46
47using blender::bke::CurvesGeometry;
48
53 private:
55 float2 brush_pos_last_re_;
56
58 CurvesBrush3D brush_3d_;
59
61 CurvesConstraintSolver constraint_solver_;
62
63 Array<float> curve_lengths_;
64
65 friend struct CombOperationExecutor;
66
67 public:
68 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
69};
70
76 CombOperation *self_ = nullptr;
78
80 const Brush *brush_ = nullptr;
84
88
92
96
98
100
101 void execute(CombOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
102 {
103 self_ = &self;
104
105 BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; });
106
108 curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
109 curves_orig_ = &curves_id_orig_->geometry.wrap();
110 if (curves_orig_->is_empty()) {
111 return;
112 }
113
114 curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
117 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
118 brush_strength_ = brush_strength_get(curves_sculpt_->paint, *brush_, stroke_extension);
119
120 const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
121
123
124 point_factors_ = *curves_orig_->attributes().lookup_or_default<float>(
125 ".selection", bke::AttrDomain::Point, 1.0f);
127
128 brush_pos_prev_re_ = self_->brush_pos_last_re_;
129 brush_pos_re_ = stroke_extension.mouse_position;
131
132 if (stroke_extension.is_first) {
133 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
135 }
136 self_->constraint_solver_.initialize(*curves_orig_,
139 curves_id_orig_->surface_collision_distance);
140
141 self_->curve_lengths_.reinitialize(curves_orig_->curves_num());
142 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
143 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
144 curve_selection_.foreach_segment(GrainSize(512), [&](const IndexMaskSegment segment) {
145 for (const int curve_i : segment) {
146 const IndexRange points = points_by_curve[curve_i];
147 const Span<float> lengths = segment_lengths.slice(points.drop_back(1));
148 self_->curve_lengths_[curve_i] = std::accumulate(lengths.begin(), lengths.end(), 0.0f);
149 }
150 });
151 /* Combing does nothing when there is no mouse movement, so return directly. */
152 return;
153 }
154
155 Array<bool> changed_curves(curves_orig_->curves_num(), false);
156
157 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
158 this->comb_projected_with_symmetry(changed_curves);
159 }
160 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
161 this->comb_spherical_with_symmetry(changed_curves);
162 }
163 else {
165 }
166
167 const Mesh *surface = curves_id_orig_->surface && curves_id_orig_->surface->type == OB_MESH ?
168 static_cast<Mesh *>(curves_id_orig_->surface->data) :
169 nullptr;
170
171 IndexMaskMemory memory;
172 const IndexMask changed_curves_mask = IndexMask::from_bools(changed_curves, memory);
173 self_->constraint_solver_.solve_step(*curves_orig_, changed_curves_mask, surface, transforms_);
174
175 curves_orig_->tag_positions_changed();
179 }
180
185 {
186 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
188 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
189 this->comb_projected(r_changed_curves, brush_transform);
190 }
191 }
192
193 void comb_projected(MutableSpan<bool> r_changed_curves, const float4x4 &brush_transform)
194 {
195 const float4x4 brush_transform_inv = math::invert(brush_transform);
196
197 MutableSpan<float3> positions_cu_orig = curves_orig_->positions_for_write();
198 const bke::crazyspace::GeometryDeformation deformation =
200 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
201
203
204 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
205 const float brush_radius_sq_re = pow2f(brush_radius_re);
206
207 CurveMapping &curve_parameter_falloff_mapping =
208 *brush_->curves_sculpt_settings->curve_parameter_falloff;
209 BKE_curvemapping_init(&curve_parameter_falloff_mapping);
210
211 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
212
213 curve_selection_.foreach_segment(GrainSize(256), [&](const IndexMaskSegment segment) {
214 for (const int curve_i : segment) {
215 bool curve_changed = false;
216 const IndexRange points = points_by_curve[curve_i];
217
218 const float total_length = self_->curve_lengths_[curve_i];
219 const float total_length_inv = math::safe_rcp(total_length);
220 float current_length = 0.0f;
221 for (const int point_i : points.drop_front(1)) {
222 current_length += segment_lengths[point_i - 1];
223
224 const float3 old_pos_cu = deformation.positions[point_i];
225 const float3 old_symm_pos_cu = math::transform_point(brush_transform_inv, old_pos_cu);
226
227 /* Find the position of the point in screen space. */
228 const float2 old_symm_pos_re = ED_view3d_project_float_v2_m4(
229 ctx_.region, old_symm_pos_cu, projection);
230
231 const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2(
232 old_symm_pos_re, brush_pos_prev_re_, brush_pos_re_);
233 if (distance_to_brush_sq_re > brush_radius_sq_re) {
234 /* Ignore the point because it's too far away. */
235 continue;
236 }
237
238 const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
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_re, brush_radius_re);
242 const float curve_parameter = current_length * total_length_inv;
243 const float curve_falloff = BKE_curvemapping_evaluateF(
244 &curve_parameter_falloff_mapping, 0, curve_parameter);
245 /* Combine the falloff and brush strength. */
246 const float weight = brush_strength_ * curve_falloff * radius_falloff *
247 point_factors_[point_i];
248
249 /* Offset the old point position in screen space and transform it back into 3D space.
250 */
251 const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight;
252 float3 new_symm_pos_wo;
254 ctx_.region,
255 math::transform_point(transforms_.curves_to_world, old_symm_pos_cu),
256 new_symm_pos_re,
257 new_symm_pos_wo);
258 const float3 new_pos_cu = math::transform_point(
259 brush_transform,
260 math::transform_point(transforms_.world_to_curves, new_symm_pos_wo));
261
262 const float3 translation_eval = new_pos_cu - old_pos_cu;
263 const float3 translation_orig = deformation.translation_from_deformed_to_original(
264 point_i, translation_eval);
265 positions_cu_orig[point_i] += translation_orig;
266
267 curve_changed = true;
268 }
269 if (curve_changed) {
270 r_changed_curves[curve_i] = true;
271 }
272 }
273 });
274 }
275
280 {
281 float3 brush_start_wo, brush_end_wo;
283 ctx_.v3d,
284 ctx_.region,
285 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
287 brush_start_wo);
289 ctx_.v3d,
290 ctx_.region,
291 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
293 brush_end_wo);
294 const float3 brush_start_cu = math::transform_point(transforms_.world_to_curves,
295 brush_start_wo);
296 const float3 brush_end_cu = math::transform_point(transforms_.world_to_curves, brush_end_wo);
297
298 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
299
300 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
302 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
303 this->comb_spherical(r_changed_curves,
304 math::transform_point(brush_transform, brush_start_cu),
305 math::transform_point(brush_transform, brush_end_cu),
306 brush_radius_cu);
307 }
308 }
309
310 void comb_spherical(MutableSpan<bool> r_changed_curves,
311 const float3 &brush_start_cu,
312 const float3 &brush_end_cu,
313 const float brush_radius_cu)
314 {
315 MutableSpan<float3> positions_cu = curves_orig_->positions_for_write();
316 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
317 const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
318
319 CurveMapping &curve_parameter_falloff_mapping =
320 *brush_->curves_sculpt_settings->curve_parameter_falloff;
321 BKE_curvemapping_init(&curve_parameter_falloff_mapping);
322
323 const bke::crazyspace::GeometryDeformation deformation =
325 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
326 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
327
328 curve_selection_.foreach_segment(GrainSize(256), [&](const IndexMaskSegment segment) {
329 for (const int curve_i : segment) {
330 bool curve_changed = false;
331 const IndexRange points = points_by_curve[curve_i];
332
333 const float total_length = self_->curve_lengths_[curve_i];
334 const float total_length_inv = math::safe_rcp(total_length);
335 float current_length = 0.0f;
336 for (const int point_i : points.drop_front(1)) {
337 current_length += segment_lengths[point_i - 1];
338
339 const float3 pos_old_cu = deformation.positions[point_i];
340
341 /* Compute distance to the brush. */
342 const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3(
343 pos_old_cu, brush_start_cu, brush_end_cu);
344 if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
345 /* Ignore the point because it's too far away. */
346 continue;
347 }
348
349 const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
350
351 /* A falloff that is based on how far away the point is from the stroke. */
352 const float radius_falloff = BKE_brush_curve_strength(
353 brush_, distance_to_brush_cu, brush_radius_cu);
354 const float curve_parameter = current_length * total_length_inv;
355 const float curve_falloff = BKE_curvemapping_evaluateF(
356 &curve_parameter_falloff_mapping, 0, curve_parameter);
357 /* Combine the falloff and brush strength. */
358 const float weight = brush_strength_ * curve_falloff * radius_falloff *
359 point_factors_[point_i];
360
361 const float3 translation_eval_cu = weight * brush_diff_cu;
362 const float3 translation_orig_cu = deformation.translation_from_deformed_to_original(
363 point_i, translation_eval_cu);
364
365 /* Update the point position. */
366 positions_cu[point_i] += translation_orig_cu;
367 curve_changed = true;
368 }
369 if (curve_changed) {
370 r_changed_curves[curve_i] = true;
371 }
372 }
373 });
374 }
375
380 {
381 std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(*ctx_.depsgraph,
382 *ctx_.region,
383 *ctx_.v3d,
384 *ctx_.rv3d,
388 if (brush_3d.has_value()) {
389 self_->brush_3d_ = *brush_3d;
392 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
393 }
394 }
395};
396
397void CombOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
398{
399 CombOperationExecutor executor{C};
400 executor.execute(*this, C, stroke_extension);
401}
402
403std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation()
404{
405 return std::make_unique<CombOperation>();
406}
407
408} // 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
float BKE_curvemapping_evaluateF(const CurveMapping *cumap, int cur, float value)
void BKE_curvemapping_init(CurveMapping *cumap)
Object * CTX_data_active_object(const bContext *C)
Low-level operations for curves.
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:650
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
MINLINE float pow2f(float x)
float dist_squared_to_line_segment_v3(const float p[3], const float l1[3], const float l2[3])
Definition math_geom.cc:519
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:291
#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
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
@ CV_SCULPT_COLLISION_ENABLED
eCurvesSymmetryType
Object is a sort of wrapper for general info.
@ OB_MESH
@ 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
PyObject * self
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
constexpr IndexRange drop_back(int64_t n) const
constexpr IndexRange drop_front(int64_t n) const
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr const T * end() const
Definition BLI_span.hh:224
constexpr const T * begin() const
Definition BLI_span.hh:220
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
std::unique_ptr< CurvesSculptStrokeOperation > new_comb_operation()
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)
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)
T safe_rcp(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
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
float3 translation_from_deformed_to_original(const int position_i, const float3 &translation) const
void comb_spherical_with_symmetry(MutableSpan< bool > r_changed_curves)
void comb_projected_with_symmetry(MutableSpan< bool > r_changed_curves)
void comb_projected(MutableSpan< bool > r_changed_curves, const float4x4 &brush_transform)
void comb_spherical(MutableSpan< bool > r_changed_curves, const float3 &brush_start_cu, const float3 &brush_end_cu, const float brush_radius_cu)
void execute(CombOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
void initialize(const bke::CurvesGeometry &curves, const IndexMask &curve_selection, const bool use_surface_collision, const float surface_collision_distance)
void WM_main_add_notifier(uint type, void *reference)