Blender V4.3
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
6#include "BKE_brush.hh"
7#include "BKE_bvhutils.hh"
8#include "BKE_context.hh"
9#include "BKE_crazyspace.hh"
10#include "BKE_mesh.hh"
11#include "BKE_mesh_runtime.hh"
12#include "BKE_paint.hh"
13
14#include "ED_screen.hh"
15#include "ED_view3d.hh"
16
17#include "DEG_depsgraph.hh"
18
19#include "DNA_brush_types.h"
20
21#include "WM_api.hh"
22
24#include "BLI_math_geom.h"
25#include "BLI_math_matrix.hh"
26#include "BLI_task.hh"
27
29
31
33
35 private:
37 CurvesBrush3D brush_3d_;
38
40 CurvesConstraintSolver constraint_solver_;
41
42 friend struct PuffOperationExecutor;
43
44 public:
45 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
46};
47
53 PuffOperation *self_ = nullptr;
55
56 Object *object_ = nullptr;
57 Curves *curves_id_ = nullptr;
59
63
64 const CurvesSculpt *curves_sculpt_ = nullptr;
65 const Brush *brush_ = nullptr;
70
72
73 const Object *surface_ob_ = nullptr;
74 const Mesh *surface_ = nullptr;
80
82
83 void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
84 {
85 UNUSED_VARS(C, stroke_extension);
86 self_ = &self;
87
89 curves_id_ = static_cast<Curves *>(object_->data);
90 curves_ = &curves_id_->geometry.wrap();
91 if (curves_->curves_num() == 0) {
92 return;
93 }
94 if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
95 report_missing_surface(stroke_extension.reports);
96 return;
97 }
98
102 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
103 brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
104 brush_pos_re_ = stroke_extension.mouse_position;
105
107 ".selection", bke::AttrDomain::Point, 1.0f);
109
111
113 surface_ = static_cast<const Mesh *>(surface_ob_->data);
114
116
117 surface_positions_ = surface_->vert_positions();
118 surface_corner_verts_ = surface_->corner_verts();
119 surface_corner_tris_ = surface_->corner_tris();
120 corner_normals_su_ = surface_->corner_normals();
123
124 if (stroke_extension.is_first) {
125 if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
127 *ctx_.region,
128 *ctx_.v3d,
129 *ctx_.rv3d,
130 *object_,
133 }
134
135 self_->constraint_solver_.initialize(
137 }
138
139 Array<float> curve_weights(curves_->curves_num(), 0.0f);
140
141 if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
143 }
144 else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
146 }
147 else {
149 }
150
151 IndexMaskMemory memory;
152 const IndexMask curves_mask = IndexMask::from_predicate(
153 curve_selection_, GrainSize(4096), memory, [&](const int64_t curve_i) {
154 return curve_weights[curve_i] > 0.0f;
155 });
156
157 this->puff(curves_mask, curve_weights);
158
159 self_->constraint_solver_.solve_step(*curves_, curves_mask, surface_, transforms_);
160
165 }
166
168 {
169 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
171 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
172 this->find_curve_weights_projected(brush_transform, r_curve_weights);
173 }
174 }
175
176 void find_curve_weights_projected(const float4x4 &brush_transform,
177 MutableSpan<float> r_curve_weights)
178 {
179 const float4x4 brush_transform_inv = math::invert(brush_transform);
180
182
183 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
184 const float brush_radius_sq_re = pow2f(brush_radius_re);
185
186 const bke::crazyspace::GeometryDeformation deformation =
188 const OffsetIndices points_by_curve = curves_->points_by_curve();
189
190 curve_selection_.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
191 const IndexRange points = points_by_curve[curve_i];
192 const float3 first_pos_cu = math::transform_point(brush_transform_inv,
193 deformation.positions[points[0]]);
194 float2 prev_pos_re = ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, projection);
195 float max_weight = 0.0f;
196 for (const int point_i : points.drop_front(1)) {
197 const float3 pos_cu = math::transform_point(brush_transform_inv,
198 deformation.positions[point_i]);
199 const float2 pos_re = ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, projection);
200 BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; });
201
202 const float dist_to_brush_sq_re = dist_squared_to_line_segment_v2(
203 brush_pos_re_, prev_pos_re, pos_re);
204 if (dist_to_brush_sq_re > brush_radius_sq_re) {
205 continue;
206 }
207
208 const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
209 const float radius_falloff = BKE_brush_curve_strength(
210 brush_, dist_to_brush_re, brush_radius_re);
211 math::max_inplace(max_weight, radius_falloff);
212 }
213 math::max_inplace(r_curve_weights[curve_i], max_weight);
214 });
215 }
216
218 {
219 float3 brush_pos_wo;
221 ctx_.v3d,
222 ctx_.region,
225 brush_pos_wo);
226 const float3 brush_pos_cu = math::transform_point(transforms_.world_to_curves, brush_pos_wo);
227 const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
228
229 const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
231 for (const float4x4 &brush_transform : symmetry_brush_transforms) {
233 math::transform_point(brush_transform, brush_pos_cu), brush_radius_cu, r_curve_weights);
234 }
235 }
236
237 void find_curves_weights_spherical(const float3 &brush_pos_cu,
238 const float brush_radius_cu,
239 MutableSpan<float> r_curve_weights)
240 {
241 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
242
243 const bke::crazyspace::GeometryDeformation deformation =
245 const OffsetIndices points_by_curve = curves_->points_by_curve();
246
247 curve_selection_.foreach_index(GrainSize(256), [&](const int64_t curve_i) {
248 const IndexRange points = points_by_curve[curve_i];
249 float max_weight = 0.0f;
250 for (const int point_i : points.drop_front(1)) {
251 const float3 &prev_pos_cu = deformation.positions[point_i - 1];
252 const float3 &pos_cu = deformation.positions[point_i];
253 const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3(
254 brush_pos_cu, prev_pos_cu, pos_cu);
255 if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
256 continue;
257 }
258
259 const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
260 const float radius_falloff = BKE_brush_curve_strength(
261 brush_, dist_to_brush_cu, brush_radius_cu);
262 math::max_inplace(max_weight, radius_falloff);
263 }
264 math::max_inplace(r_curve_weights[curve_i], max_weight);
265 });
266 }
267
268 void puff(const IndexMask &selection, const Span<float> curve_weights)
269 {
270 const OffsetIndices points_by_curve = curves_->points_by_curve();
272
273 selection.foreach_segment(GrainSize(256), [&](IndexMaskSegment segment) {
274 Vector<float> accumulated_lengths_cu;
275 for (const int curve_i : segment) {
276 const IndexRange points = points_by_curve[curve_i];
277 const int first_point_i = points[0];
278 const float3 first_pos_cu = positions_cu[first_point_i];
280 first_pos_cu);
281
282 /* Find the nearest position on the surface. The curve will be aligned to the normal of
283 * that point. */
284 BVHTreeNearest nearest;
285 nearest.dist_sq = FLT_MAX;
287 first_pos_su,
288 &nearest,
290 &surface_bvh_);
291
292 const int3 &tri = surface_corner_tris_[nearest.index];
293 const float3 closest_pos_su = nearest.co;
294 const float3 &v0_su = surface_positions_[surface_corner_verts_[tri[0]]];
295 const float3 &v1_su = surface_positions_[surface_corner_verts_[tri[1]]];
296 const float3 &v2_su = surface_positions_[surface_corner_verts_[tri[2]]];
297 float3 bary_coords;
298 interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su);
300 tri, bary_coords, corner_normals_su_);
301 const float3 normal_cu = math::normalize(
303
304 accumulated_lengths_cu.resize(points.size() - 1);
306 positions_cu.slice(points), false, accumulated_lengths_cu);
307
308 /* Align curve to the surface normal while making sure that the curve does not fold up much
309 * in the process (e.g. when the curve was pointing in the opposite direction before). */
310 for (const int i : IndexRange(points.size()).drop_front(1)) {
311 const int point_i = points[i];
312 const float3 old_pos_cu = positions_cu[point_i];
313
314 /* Compute final position of the point. */
315 const float length_param_cu = accumulated_lengths_cu[i - 1];
316 const float3 goal_pos_cu = first_pos_cu + length_param_cu * normal_cu;
317
318 const float weight = 0.01f * brush_strength_ * point_factors_[point_i] *
319 curve_weights[curve_i];
320 float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight);
321
322 /* Make sure the point does not move closer to the root point than it was initially. This
323 * makes the curve kind of "rotate up". */
324 const float old_dist_to_root_cu = math::distance(old_pos_cu, first_pos_cu);
325 const float new_dist_to_root_cu = math::distance(new_pos_cu, first_pos_cu);
326 if (new_dist_to_root_cu < old_dist_to_root_cu) {
327 const float3 offset = math::normalize(new_pos_cu - first_pos_cu);
328 new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset;
329 }
330
331 positions_cu[point_i] = new_pos_cu;
332 }
333 }
334 });
335 }
336};
337
338void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
339{
340 PuffOperationExecutor executor{C};
341 executor.execute(*this, C, stroke_extension);
342}
343
344std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation()
345{
346 return std::make_unique<PuffOperation>();
347}
348
349} // 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
void free_bvhtree_from_mesh(BVHTreeFromMesh *data)
Definition bvhutils.cc:1160
BVHTree * BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, const Mesh *mesh, BVHCacheType bvh_cache_type, int tree_type)
Definition bvhutils.cc:899
@ BVHTREE_FROM_CORNER_TRIS
Object * CTX_data_active_object(const bContext *C)
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:654
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
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:517
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:289
#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:1041
eBrushFalloffShape
@ PAINT_FALLOFF_SHAPE_SPHERE
@ PAINT_FALLOFF_SHAPE_TUBE
eCurvesSymmetryType
@ CV_SCULPT_COLLISION_ENABLED
@ 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
constexpr IndexRange drop_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:574
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
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
void foreach_index(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)
void report_missing_surface(ReportList *reports)
float brush_strength_get(const Scene &scene, const Brush &brush, const StrokeExtension &stroke_extension)
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)
#define FLT_MAX
Definition stdcycles.h:14
__int64 int64_t
Definition stdint.h:89
BVHTree_NearestPointCallback nearest_callback
char falloff_shape
CurvesGeometry geometry
struct Object * surface
struct ToolSettings * toolsettings
CurvesSculpt * curves_sculpt
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 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)
void WM_main_add_notifier(uint type, void *reference)