Blender V5.0
curves_sculpt_puff.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 "BKE_brush.hh"
6#include "BKE_bvhutils.hh"
7#include "BKE_context.hh"
8#include "BKE_crazyspace.hh"
9#include "BKE_mesh.hh"
10#include "BKE_mesh_runtime.hh"
11#include "BKE_paint.hh"
12
13#include "ED_screen.hh"
14#include "ED_view3d.hh"
15
16#include "DEG_depsgraph.hh"
17
18#include "DNA_brush_types.h"
19
20#include "WM_api.hh"
21
23#include "BLI_math_geom.h"
24#include "BLI_math_matrix.hh"
25#include "BLI_task.hh"
26
28
30
32
34 private:
36 CurvesBrush3D brush_3d_;
37
39 CurvesConstraintSolver constraint_solver_;
40
41 friend struct PuffOperationExecutor;
42
43 public:
44 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
45};
46
52 PuffOperation *self_ = nullptr;
54
55 Object *object_ = nullptr;
56 Curves *curves_id_ = nullptr;
58
62
64 const Brush *brush_ = nullptr;
69
71
72 const Object *surface_ob_ = nullptr;
73 const Mesh *surface_ = nullptr;
79
81
82 void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
83 {
84 UNUSED_VARS(C, stroke_extension);
85 self_ = &self;
86
88 curves_id_ = static_cast<Curves *>(object_->data);
89 curves_ = &curves_id_->geometry.wrap();
90 if (curves_->is_empty()) {
91 return;
92 }
93 if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
94 report_missing_surface(stroke_extension.reports);
95 return;
96 }
97
98 curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
101 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
102 brush_strength_ = brush_strength_get(curves_sculpt_->paint, *brush_, stroke_extension);
103 brush_pos_re_ = stroke_extension.mouse_position;
104
105 point_factors_ = *curves_->attributes().lookup_or_default<float>(
106 ".selection", bke::AttrDomain::Point, 1.0f);
108
109 const eBrushFalloffShape falloff_shape = eBrushFalloffShape(brush_->falloff_shape);
110
111 surface_ob_ = curves_id_->surface;
112 surface_ = static_cast<const Mesh *>(surface_ob_->data);
113
115
116 surface_positions_ = surface_->vert_positions();
117 surface_corner_verts_ = surface_->corner_verts();
118 surface_corner_tris_ = surface_->corner_tris();
119 corner_normals_su_ = surface_->corner_normals();
120 surface_bvh_ = surface_->bvh_corner_tris();
121
122 if (stroke_extension.is_first) {
123 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE || (U.uiflag & USER_ORBIT_SELECTION)) {
124 self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
125 *ctx_.region,
126 *ctx_.v3d,
127 *ctx_.rv3d,
128 *object_,
133 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu));
134 }
135
136 self_->constraint_solver_.initialize(*curves_,
139 curves_id_->surface_collision_distance);
140 }
141
142 Array<float> curve_weights(curves_->curves_num(), 0.0f);
143
144 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
146 }
147 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
149 }
150 else {
152 }
153
154 IndexMaskMemory memory;
155 const IndexMask curves_mask = IndexMask::from_predicate(
156 curve_selection_, GrainSize(4096), memory, [&](const int64_t curve_i) {
157 return curve_weights[curve_i] > 0.0f;
158 });
159
160 this->puff(curves_mask, curve_weights);
161
162 self_->constraint_solver_.solve_step(*curves_, curves_mask, surface_, transforms_);
163
164 curves_->tag_positions_changed();
168 }
169
171 {
172 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
174 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
175 this->find_curve_weights_projected(brush_transform, r_curve_weights);
176 }
177 }
178
179 void find_curve_weights_projected(const float4x4 &brush_transform,
180 MutableSpan<float> r_curve_weights)
181 {
182 const float4x4 brush_transform_inv = math::invert(brush_transform);
183
184 const float4x4 projection = ED_view3d_ob_project_mat_get(ctx_.rv3d, object_);
185
186 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
187 const float brush_radius_sq_re = pow2f(brush_radius_re);
188
189 const bke::crazyspace::GeometryDeformation deformation =
191 const OffsetIndices points_by_curve = curves_->points_by_curve();
192
193 curve_selection_.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
194 const IndexRange points = points_by_curve[curve_i];
195 const float3 first_pos_cu = math::transform_point(brush_transform_inv,
196 deformation.positions[points[0]]);
197 float2 prev_pos_re = ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, projection);
198 float max_weight = 0.0f;
199 for (const int point_i : points.drop_front(1)) {
200 const float3 pos_cu = math::transform_point(brush_transform_inv,
201 deformation.positions[point_i]);
202 const float2 pos_re = ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, projection);
203 BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; });
204
205 const float dist_to_brush_sq_re = dist_squared_to_line_segment_v2(
206 brush_pos_re_, prev_pos_re, pos_re);
207 if (dist_to_brush_sq_re > brush_radius_sq_re) {
208 continue;
209 }
210
211 const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
212 const float radius_falloff = BKE_brush_curve_strength(
213 brush_, dist_to_brush_re, brush_radius_re);
214 math::max_inplace(max_weight, radius_falloff);
215 }
216 math::max_inplace(r_curve_weights[curve_i], max_weight);
217 });
218 }
219
221 {
222 float3 brush_pos_wo;
224 ctx_.v3d,
225 ctx_.region,
226 math::transform_point(transforms_.curves_to_world, self_->brush_3d_.position_cu),
228 brush_pos_wo);
229 const float3 brush_pos_cu = math::transform_point(transforms_.world_to_curves, brush_pos_wo);
230 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
231
232 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
234 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
236 math::transform_point(brush_transform, brush_pos_cu), brush_radius_cu, r_curve_weights);
237 }
238 }
239
240 void find_curves_weights_spherical(const float3 &brush_pos_cu,
241 const float brush_radius_cu,
242 MutableSpan<float> r_curve_weights)
243 {
244 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
245
246 const bke::crazyspace::GeometryDeformation deformation =
248 const OffsetIndices points_by_curve = curves_->points_by_curve();
249
250 curve_selection_.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
251 const IndexRange points = points_by_curve[curve_i];
252 float max_weight = 0.0f;
253 for (const int point_i : points.drop_front(1)) {
254 const float3 &prev_pos_cu = deformation.positions[point_i - 1];
255 const float3 &pos_cu = deformation.positions[point_i];
256 const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3(
257 brush_pos_cu, prev_pos_cu, pos_cu);
258 if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
259 continue;
260 }
261
262 const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
263 const float radius_falloff = BKE_brush_curve_strength(
264 brush_, dist_to_brush_cu, brush_radius_cu);
265 math::max_inplace(max_weight, radius_falloff);
266 }
267 math::max_inplace(r_curve_weights[curve_i], max_weight);
268 });
269 }
270
271 void puff(const IndexMask &selection, const Span<float> curve_weights)
272 {
273 const OffsetIndices points_by_curve = curves_->points_by_curve();
274 MutableSpan<float3> positions_cu = curves_->positions_for_write();
275
276 selection.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
277 Vector<float> accumulated_lengths_cu;
278 for (const int curve_i : segment) {
279 const IndexRange points = points_by_curve[curve_i];
280 const int first_point_i = points[0];
281 const float3 first_pos_cu = positions_cu[first_point_i];
282 const float3 first_pos_su = math::transform_point(transforms_.curves_to_surface,
283 first_pos_cu);
284
285 /* Find the nearest position on the surface. The curve will be aligned to the normal of
286 * that point. */
287 BVHTreeNearest nearest;
288 nearest.dist_sq = FLT_MAX;
290 first_pos_su,
291 &nearest,
292 surface_bvh_.nearest_callback,
293 &surface_bvh_);
294
295 const int3 &tri = surface_corner_tris_[nearest.index];
296 const float3 closest_pos_su = nearest.co;
297 const float3 &v0_su = surface_positions_[surface_corner_verts_[tri[0]]];
298 const float3 &v1_su = surface_positions_[surface_corner_verts_[tri[1]]];
299 const float3 &v2_su = surface_positions_[surface_corner_verts_[tri[2]]];
300 float3 bary_coords;
301 interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su);
303 tri, bary_coords, corner_normals_su_);
304 const float3 normal_cu = math::normalize(
305 math::transform_direction(transforms_.surface_to_curves_normal, normal_su));
306
307 accumulated_lengths_cu.resize(points.size() - 1);
309 positions_cu.slice(points), false, accumulated_lengths_cu);
310
311 /* Align curve to the surface normal while making sure that the curve does not fold up much
312 * in the process (e.g. when the curve was pointing in the opposite direction before). */
313 for (const int i : IndexRange(points.size()).drop_front(1)) {
314 const int point_i = points[i];
315 const float3 old_pos_cu = positions_cu[point_i];
316
317 /* Compute final position of the point. */
318 const float length_param_cu = accumulated_lengths_cu[i - 1];
319 const float3 goal_pos_cu = first_pos_cu + length_param_cu * normal_cu;
320
321 const float weight = 0.01f * brush_strength_ * point_factors_[point_i] *
322 curve_weights[curve_i];
323 float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight);
324
325 /* Make sure the point does not move closer to the root point than it was initially. This
326 * makes the curve kind of "rotate up". */
327 const float old_dist_to_root_cu = math::distance(old_pos_cu, first_pos_cu);
328 const float new_dist_to_root_cu = math::distance(new_pos_cu, first_pos_cu);
329 if (new_dist_to_root_cu < old_dist_to_root_cu) {
330 const float3 offset = math::normalize(new_pos_cu - first_pos_cu);
331 new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset;
332 }
333
334 positions_cu[point_i] = new_pos_cu;
335 }
336 }
337 });
338 }
339};
340
341void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
342{
343 PuffOperationExecutor executor{C};
344 executor.execute(*this, C, stroke_extension);
345}
346
347std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation()
348{
349 return std::make_unique<PuffOperation>();
350}
351
352} // 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)
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:650
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
int BLI_bvhtree_find_nearest(const BVHTree *tree, const float co[3], BVHTreeNearest *nearest, BVHTree_NearestPointCallback callback, void *userdata)
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
void interp_weights_tri_v3(float w[3], const float v1[3], const float v2[3], const float v3[3], const float co[3])
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)
#define UNUSED_VARS(...)
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
@ 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
long long int int64_t
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
constexpr int64_t size() const
constexpr IndexRange drop_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
void resize(const int64_t new_size)
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
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)
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)
void report_missing_surface(ReportList *reports)
Vector< float4x4 > get_symmetry_brush_transforms(const eCurvesSymmetryType symmetry)
std::unique_ptr< CurvesSculptStrokeOperation > new_puff_operation()
float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension)
float3 compute_surface_point_normal(const int3 &tri, const float3 &bary_coord, Span< float3 > corner_normals)
void accumulate_lengths(const Span< T > values, const bool cyclic, MutableSpan< float > lengths)
T distance(const T &a, const T &b)
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)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
void max_inplace(T &a, const T &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
void find_curve_weights_projected_with_symmetry(MutableSpan< float > r_curve_weights)
void find_curves_weights_spherical_with_symmetry(MutableSpan< float > r_curve_weights)
void puff(const IndexMask &selection, const Span< float > curve_weights)
void find_curves_weights_spherical(const float3 &brush_pos_cu, const float brush_radius_cu, MutableSpan< float > r_curve_weights)
void find_curve_weights_projected(const float4x4 &brush_transform, MutableSpan< float > r_curve_weights)
i
Definition text_draw.cc:230
void WM_main_add_notifier(uint type, void *reference)