Blender V4.3
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
5#include <algorithm>
6
8
9#include "BLI_kdtree.h"
10#include "BLI_math_geom.h"
12#include "BLI_rand.hh"
13#include "BLI_vector.hh"
14
15#include "DEG_depsgraph.hh"
17
18#include "BKE_attribute_math.hh"
19#include "BKE_brush.hh"
20#include "BKE_bvhutils.hh"
21#include "BKE_colortools.hh"
22#include "BKE_context.hh"
23#include "BKE_crazyspace.hh"
24#include "BKE_curves.hh"
25#include "BKE_geometry_set.hh"
26#include "BKE_mesh.hh"
27#include "BKE_mesh_runtime.hh"
28#include "BKE_paint.hh"
29
30#include "DNA_brush_enums.h"
31#include "DNA_brush_types.h"
32#include "DNA_curves_types.h"
33#include "DNA_object_types.h"
34#include "DNA_screen_types.h"
35#include "DNA_space_types.h"
36
37#include "ED_screen.hh"
38#include "ED_view3d.hh"
39
40#include "UI_interface.hh"
41
42#include "WM_api.hh"
43
53
55
60 private:
62 float2 brush_pos_last_re_;
63
65 CurvesBrush3D brush_3d_;
66
68 CurvesConstraintSolver constraint_solver_;
69
70 Array<float> curve_lengths_;
71
72 friend struct CombOperationExecutor;
73
74 public:
75 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
76};
77
83 CombOperation *self_ = nullptr;
85
86 const CurvesSculpt *curves_sculpt_ = nullptr;
87 const Brush *brush_ = nullptr;
91
95
99
103
105
107
108 void execute(CombOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
109 {
110 self_ = &self;
111
112 BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; });
113
115 curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
117 if (curves_orig_->curves_num() == 0) {
118 return;
119 }
120
124 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
125 brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
126
128
130
132 ".selection", bke::AttrDomain::Point, 1.0f);
134
135 brush_pos_prev_re_ = self_->brush_pos_last_re_;
136 brush_pos_re_ = stroke_extension.mouse_position;
138
139 if (stroke_extension.is_first) {
140 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
142 }
143 self_->constraint_solver_.initialize(
145
146 self_->curve_lengths_.reinitialize(curves_orig_->curves_num());
147 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
148 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
150 for (const int curve_i : segment) {
151 const IndexRange points = points_by_curve[curve_i];
152 const Span<float> lengths = segment_lengths.slice(points.drop_back(1));
153 self_->curve_lengths_[curve_i] = std::accumulate(lengths.begin(), lengths.end(), 0.0f);
154 }
155 });
156 /* Combing does nothing when there is no mouse movement, so return directly. */
157 return;
158 }
159
160 Array<bool> changed_curves(curves_orig_->curves_num(), false);
161
162 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
163 this->comb_projected_with_symmetry(changed_curves);
164 }
165 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
166 this->comb_spherical_with_symmetry(changed_curves);
167 }
168 else {
170 }
171
173 static_cast<Mesh *>(curves_id_orig_->surface->data) :
174 nullptr;
175
176 IndexMaskMemory memory;
177 const IndexMask changed_curves_mask = IndexMask::from_bools(changed_curves, memory);
178 self_->constraint_solver_.solve_step(*curves_orig_, changed_curves_mask, surface, transforms_);
179
184 }
185
190 {
191 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
193 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
194 this->comb_projected(r_changed_curves, brush_transform);
195 }
196 }
197
198 void comb_projected(MutableSpan<bool> r_changed_curves, const float4x4 &brush_transform)
199 {
200 const float4x4 brush_transform_inv = math::invert(brush_transform);
201
203 const bke::crazyspace::GeometryDeformation deformation =
205 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
206
208
209 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
210 const float brush_radius_sq_re = pow2f(brush_radius_re);
211
212 CurveMapping &curve_parameter_falloff_mapping =
214 BKE_curvemapping_init(&curve_parameter_falloff_mapping);
215
216 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
217
219 for (const int curve_i : segment) {
220 bool curve_changed = false;
221 const IndexRange points = points_by_curve[curve_i];
222
223 const float total_length = self_->curve_lengths_[curve_i];
224 const float total_length_inv = math::safe_rcp(total_length);
225 float current_length = 0.0f;
226 for (const int point_i : points.drop_front(1)) {
227 current_length += segment_lengths[point_i - 1];
228
229 const float3 old_pos_cu = deformation.positions[point_i];
230 const float3 old_symm_pos_cu = math::transform_point(brush_transform_inv, old_pos_cu);
231
232 /* Find the position of the point in screen space. */
233 const float2 old_symm_pos_re = ED_view3d_project_float_v2_m4(
234 ctx_.region, old_symm_pos_cu, projection);
235
236 const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2(
237 old_symm_pos_re, brush_pos_prev_re_, brush_pos_re_);
238 if (distance_to_brush_sq_re > brush_radius_sq_re) {
239 /* Ignore the point because it's too far away. */
240 continue;
241 }
242
243 const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
244 /* A falloff that is based on how far away the point is from the stroke. */
245 const float radius_falloff = BKE_brush_curve_strength(
246 brush_, distance_to_brush_re, brush_radius_re);
247 const float curve_parameter = current_length * total_length_inv;
248 const float curve_falloff = BKE_curvemapping_evaluateF(
249 &curve_parameter_falloff_mapping, 0, curve_parameter);
250 /* Combine the falloff and brush strength. */
251 const float weight = brush_strength_ * curve_falloff * radius_falloff *
252 point_factors_[point_i];
253
254 /* Offset the old point position in screen space and transform it back into 3D space.
255 */
256 const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight;
257 float3 new_symm_pos_wo;
259 ctx_.region,
261 new_symm_pos_re,
262 new_symm_pos_wo);
263 const float3 new_pos_cu = math::transform_point(
264 brush_transform,
266
267 const float3 translation_eval = new_pos_cu - old_pos_cu;
268 const float3 translation_orig = deformation.translation_from_deformed_to_original(
269 point_i, translation_eval);
270 positions_cu_orig[point_i] += translation_orig;
271
272 curve_changed = true;
273 }
274 if (curve_changed) {
275 r_changed_curves[curve_i] = true;
276 }
277 }
278 });
279 }
280
285 {
286 float3 brush_start_wo, brush_end_wo;
288 ctx_.v3d,
289 ctx_.region,
292 brush_start_wo);
294 ctx_.v3d,
295 ctx_.region,
298 brush_end_wo);
300 brush_start_wo);
301 const float3 brush_end_cu = math::transform_point(transforms_.world_to_curves, brush_end_wo);
302
303 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
304
305 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
307 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
308 this->comb_spherical(r_changed_curves,
309 math::transform_point(brush_transform, brush_start_cu),
310 math::transform_point(brush_transform, brush_end_cu),
311 brush_radius_cu);
312 }
313 }
314
315 void comb_spherical(MutableSpan<bool> r_changed_curves,
316 const float3 &brush_start_cu,
317 const float3 &brush_end_cu,
318 const float brush_radius_cu)
319 {
321 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
322 const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
323
324 CurveMapping &curve_parameter_falloff_mapping =
326 BKE_curvemapping_init(&curve_parameter_falloff_mapping);
327
328 const bke::crazyspace::GeometryDeformation deformation =
330 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
331 const Span<float> segment_lengths = self_->constraint_solver_.segment_lengths();
332
334 for (const int curve_i : segment) {
335 bool curve_changed = false;
336 const IndexRange points = points_by_curve[curve_i];
337
338 const float total_length = self_->curve_lengths_[curve_i];
339 const float total_length_inv = math::safe_rcp(total_length);
340 float current_length = 0.0f;
341 for (const int point_i : points.drop_front(1)) {
342 current_length += segment_lengths[point_i - 1];
343
344 const float3 pos_old_cu = deformation.positions[point_i];
345
346 /* Compute distance to the brush. */
347 const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3(
348 pos_old_cu, brush_start_cu, brush_end_cu);
349 if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
350 /* Ignore the point because it's too far away. */
351 continue;
352 }
353
354 const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
355
356 /* A falloff that is based on how far away the point is from the stroke. */
357 const float radius_falloff = BKE_brush_curve_strength(
358 brush_, distance_to_brush_cu, brush_radius_cu);
359 const float curve_parameter = current_length * total_length_inv;
360 const float curve_falloff = BKE_curvemapping_evaluateF(
361 &curve_parameter_falloff_mapping, 0, curve_parameter);
362 /* Combine the falloff and brush strength. */
363 const float weight = brush_strength_ * curve_falloff * radius_falloff *
364 point_factors_[point_i];
365
366 const float3 translation_eval_cu = weight * brush_diff_cu;
367 const float3 translation_orig_cu = deformation.translation_from_deformed_to_original(
368 point_i, translation_eval_cu);
369
370 /* Update the point position. */
371 positions_cu[point_i] += translation_orig_cu;
372 curve_changed = true;
373 }
374 if (curve_changed) {
375 r_changed_curves[curve_i] = true;
376 }
377 }
378 });
379 }
380
385 {
386 std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(*ctx_.depsgraph,
387 *ctx_.region,
388 *ctx_.v3d,
389 *ctx_.rv3d,
393 if (brush_3d.has_value()) {
394 self_->brush_3d_ = *brush_3d;
395 }
396 }
397};
398
399void CombOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
400{
401 CombOperationExecutor executor{C};
402 executor.execute(*this, C, stroke_extension);
403}
404
405std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation()
406{
407 return std::make_unique<CombOperation>();
408}
409
410} // 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
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:654
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
A KD-tree for nearest neighbor search.
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:517
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:289
#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
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
eCurvesSymmetryType
@ CV_SCULPT_COLLISION_ENABLED
Object is a sort of wrapper for general info.
@ OB_MESH
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
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
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
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
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::unique_ptr< CurvesSculptStrokeOperation > new_comb_operation()
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)
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)
T safe_rcp(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
struct CurveMapping * curve_parameter_falloff
char falloff_shape
struct BrushCurvesSculptSettings * curves_sculpt_settings
CurvesGeometry geometry
struct Object * surface
struct ToolSettings * toolsettings
CurvesSculpt * curves_sculpt
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 solve_step(bke::CurvesGeometry &curves, const IndexMask &curve_selection, const Mesh *surface, const CurvesSurfaceTransforms &transforms)
void initialize(const bke::CurvesGeometry &curves, const IndexMask &curve_selection, const bool use_surface_collision)
void WM_main_add_notifier(uint type, void *reference)