Blender V4.5
curves_sculpt_slide.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
8#include "BLI_math_rotation.h"
9#include "BLI_task.hh"
10#include "BLI_vector.hh"
11
12#include "DEG_depsgraph.hh"
13
14#include "BKE_attribute_math.hh"
15#include "BKE_brush.hh"
16#include "BKE_bvhutils.hh"
17#include "BKE_context.hh"
18#include "BKE_curves.hh"
19#include "BKE_mesh.hh"
20#include "BKE_mesh_sample.hh"
21#include "BKE_object.hh"
22#include "BKE_paint.hh"
23#include "BKE_report.hh"
24
25#include "DNA_curves_types.h"
26#include "DNA_screen_types.h"
27
28#include "ED_screen.hh"
29#include "ED_view3d.hh"
30
31#include "WM_api.hh"
32
34
37
39
40using geometry::ReverseUVSampler;
41
53
59
61 private:
62 float2 initial_brush_pos_re_;
64 Vector<SlideInfo> slide_info_;
66 Array<float3> initial_positions_cu_;
68 Array<float3> initial_deformed_positions_cu_;
69
71
72 public:
73 void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
74};
75
83
84 const CurvesSculpt *curves_sculpt_ = nullptr;
85 const Brush *brush_ = nullptr;
89
93
95 const Mesh *surface_orig_ = nullptr;
99
101 Mesh *surface_eval_ = nullptr;
107
111
113
115
116 std::atomic<bool> found_invalid_uv_mapping_{false};
117
119
120 void execute(SlideOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
121 {
122 UNUSED_VARS(C, stroke_extension);
123 self_ = &self;
124
126 curves_id_orig_ = static_cast<Curves *>(curves_ob_orig_->data);
127 curves_orig_ = &curves_id_orig_->geometry.wrap();
128 if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) {
129 report_missing_surface(stroke_extension.reports);
130 return;
131 }
132 if (curves_orig_->is_empty()) {
133 return;
134 }
135 if (curves_id_orig_->surface_uv_map == nullptr) {
137 return;
138 }
139 if (curves_orig_->surface_uv_coords().is_empty()) {
140 BKE_report(stroke_extension.reports,
142 "Curves do not have surface attachment information");
143 return;
144 }
145 const StringRefNull uv_map_name = curves_id_orig_->surface_uv_map;
146
147 curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
150 brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
152
153 curve_factors_ = *curves_orig_->attributes().lookup_or_default(
154 ".selection", bke::AttrDomain::Curve, 1.0f);
156
157 brush_pos_re_ = stroke_extension.mouse_position;
158
160
162 surface_orig_ = static_cast<const Mesh *>(surface_ob_orig_->data);
163 if (surface_orig_->faces_num == 0) {
164 report_empty_original_surface(stroke_extension.reports);
165 return;
166 }
168 corner_normals_orig_su_ = surface_orig_->corner_normals();
169 surface_uv_map_orig_ = *surface_orig_->attributes().lookup<float2>(uv_map_name,
171 if (surface_uv_map_orig_.is_empty()) {
173 return;
174 }
176 if (surface_ob_eval_ == nullptr) {
177 return;
178 }
180 if (surface_eval_ == nullptr) {
181 return;
182 }
183 if (surface_eval_->faces_num == 0) {
184 report_empty_evaluated_surface(stroke_extension.reports);
185 return;
186 }
188 surface_positions_eval_ = surface_eval_->vert_positions();
190 surface_uv_map_eval_ = *surface_eval_->attributes().lookup<float2>(uv_map_name,
192 if (surface_uv_map_eval_.is_empty()) {
194 return;
195 }
196 surface_bvh_eval_ = surface_eval_->bvh_corner_tris();
197
198 if (stroke_extension.is_first) {
199 self_->initial_brush_pos_re_ = brush_pos_re_;
200 /* Remember original and deformed positions of all points. Otherwise this information is lost
201 * when sliding starts, but it's still used. */
202 const bke::crazyspace::GeometryDeformation deformation =
204 self_->initial_positions_cu_ = curves_orig_->positions();
205 self_->initial_deformed_positions_cu_ = deformation.positions;
206
207 /* First find all curves to slide. When the mouse moves, only those curves will be moved. */
209 return;
210 }
211 this->slide_with_symmetry();
212
213 if (found_invalid_uv_mapping_) {
214 BKE_report(stroke_extension.reports, RPT_WARNING, "UV map or surface attachment is invalid");
215 }
216
217 curves_orig_->tag_positions_changed();
221 }
222
224 {
225 const Vector<float4x4> brush_transforms = get_symmetry_brush_transforms(
227 const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
228 const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
229 *ctx_.region,
230 *ctx_.v3d,
234 brush_radius_re);
235 if (!brush_3d.has_value()) {
236 return;
237 }
239 *ctx_.scene, math::transform_point(transforms_.curves_to_world, brush_3d->position_cu));
240
241 const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_,
243 for (const float4x4 &brush_transform : brush_transforms) {
244 self_->slide_info_.append_as();
245 SlideInfo &slide_info = self_->slide_info_.last();
246 slide_info.brush_transform = brush_transform;
247 this->find_curves_to_slide(math::transform_point(brush_transform, brush_3d->position_cu),
248 brush_3d->radius_cu,
249 reverse_uv_sampler_orig,
250 slide_info.curves_to_slide);
251 }
252 }
253
254 void find_curves_to_slide(const float3 &brush_pos_cu,
255 const float brush_radius_cu,
256 const ReverseUVSampler &reverse_uv_sampler_orig,
257 Vector<SlideCurveInfo> &r_curves_to_slide)
258 {
259 const Span<float2> surface_uv_coords = curves_orig_->surface_uv_coords();
260 const float brush_radius_sq_cu = pow2f(brush_radius_cu);
261
262 const Span<int> offsets = curves_orig_->offsets();
263 curve_selection_.foreach_segment([&](const IndexMaskSegment segment) {
264 for (const int curve_i : segment) {
265 const int first_point_i = offsets[curve_i];
266 const float3 old_pos_cu = self_->initial_deformed_positions_cu_[first_point_i];
267 const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu);
268 if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
269 /* Root point is too far away from curve center. */
270 continue;
271 }
272 const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
273 const float radius_falloff = BKE_brush_curve_strength(
274 brush_, dist_to_brush_cu, brush_radius_cu);
275
276 const float2 uv = surface_uv_coords[curve_i];
277 ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv);
278 if (result.type != ReverseUVSampler::ResultType::Ok) {
279 /* The curve does not have a valid surface attachment. */
280 found_invalid_uv_mapping_.store(true);
281 continue;
282 }
283 /* Compute the normal at the initial surface position. */
286 result.bary_weights,
288 const float3 normal_cu = math::normalize(
289 math::transform_point(transforms_.surface_to_curves_normal, point_no));
290
291 r_curves_to_slide.append({curve_i, radius_falloff, normal_cu});
292 }
293 });
294 }
295
297 {
298 const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_,
300 for (const SlideInfo &slide_info : self_->slide_info_) {
301 this->slide(slide_info.curves_to_slide, reverse_uv_sampler_orig, slide_info.brush_transform);
302 }
303 }
304
305 void slide(const Span<SlideCurveInfo> slide_curves,
306 const ReverseUVSampler &reverse_uv_sampler_orig,
307 const float4x4 &brush_transform)
308 {
309 const float4x4 brush_transform_inv = math::invert(brush_transform);
310
311 const Span<float3> positions_orig_su = surface_orig_->vert_positions();
312 const Span<int> corner_verts_orig = surface_orig_->corner_verts();
313 const OffsetIndices points_by_curve = curves_orig_->points_by_curve();
314
315 MutableSpan<float3> positions_orig_cu = curves_orig_->positions_for_write();
316 MutableSpan<float2> surface_uv_coords = curves_orig_->surface_uv_coords_for_write();
317
319
320 const float2 brush_pos_diff_re = brush_pos_re_ - self_->initial_brush_pos_re_;
321
322 /* The brush transformation has to be applied in curves space. */
323 const float4x4 world_to_surface_with_symmetry_mat = transforms_.curves_to_surface *
324 brush_transform *
325 transforms_.world_to_curves;
326
327 threading::parallel_for(slide_curves.index_range(), 256, [&](const IndexRange range) {
328 for (const SlideCurveInfo &slide_curve_info : slide_curves.slice(range)) {
329 const int curve_i = slide_curve_info.curve_i;
330 const IndexRange points = points_by_curve[curve_i];
331 const int first_point_i = points[0];
332
333 const float3 old_first_pos_eval_cu = self_->initial_deformed_positions_cu_[first_point_i];
334 const float3 old_first_symm_pos_eval_cu = math::transform_point(brush_transform_inv,
335 old_first_pos_eval_cu);
336 const float3 old_first_pos_eval_su = math::transform_point(transforms_.curves_to_surface,
337 old_first_pos_eval_cu);
338
339 const float2 old_first_symm_pos_eval_re = ED_view3d_project_float_v2_m4(
340 ctx_.region, old_first_symm_pos_eval_cu, projection);
341
342 const float radius_falloff = slide_curve_info.radius_falloff;
343 const float curve_weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
344 const float2 new_first_symm_pos_eval_re = old_first_symm_pos_eval_re +
345 curve_weight * brush_pos_diff_re;
346
347 /* Compute the ray that will be used to find the new position on the surface. */
348 float3 ray_start_wo, ray_end_wo;
349 ED_view3d_win_to_segment_clipped(ctx_.depsgraph,
350 ctx_.region,
351 ctx_.v3d,
352 new_first_symm_pos_eval_re,
353 ray_start_wo,
354 ray_end_wo,
355 true);
356 const float3 ray_start_su = math::transform_point(world_to_surface_with_symmetry_mat,
357 ray_start_wo);
358 const float3 ray_end_su = math::transform_point(world_to_surface_with_symmetry_mat,
359 ray_end_wo);
360 const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su);
361
362 /* Find the ray hit that is closest to the initial curve root position. */
363 int tri_index_eval;
364 float3 hit_pos_eval_su;
365 if (!this->find_closest_ray_hit(ray_start_su,
366 ray_direction_su,
367 old_first_pos_eval_su,
368 tri_index_eval,
369 hit_pos_eval_su))
370 {
371 continue;
372 }
373
374 /* Compute the uv of the new surface position on the evaluated mesh. */
375 const int3 &tri_eval = surface_corner_tris_eval_[tri_index_eval];
376 const float3 bary_weights_eval = bke::mesh_surface_sample::compute_bary_coord_in_triangle(
377 surface_positions_eval_, surface_corner_verts_eval_, tri_eval, hit_pos_eval_su);
378 const float2 uv = bke::attribute_math::mix3(bary_weights_eval,
379 surface_uv_map_eval_[tri_eval[0]],
380 surface_uv_map_eval_[tri_eval[1]],
381 surface_uv_map_eval_[tri_eval[2]]);
382
383 /* Try to find the same uv on the original surface. */
384 const ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv);
385 if (result.type != ReverseUVSampler::ResultType::Ok) {
386 found_invalid_uv_mapping_.store(true);
387 continue;
388 }
389 const int3 &tri_orig = surface_corner_tris_orig_[result.tri_index];
390 const float3 &bary_weights_orig = result.bary_weights;
391
392 /* Gather old and new surface normal. */
393 const float3 &initial_normal_cu = slide_curve_info.initial_normal_cu;
394 const float3 new_normal_cu = math::normalize(
395 math::transform_point(transforms_.surface_to_curves_normal,
396 geometry::compute_surface_point_normal(
397 tri_orig, result.bary_weights, corner_normals_orig_su_)));
398
399 /* Gather old and new surface position. */
400 const float3 new_first_pos_orig_su = bke::attribute_math::mix3<float3>(
401 bary_weights_orig,
402 positions_orig_su[corner_verts_orig[tri_orig[0]]],
403 positions_orig_su[corner_verts_orig[tri_orig[1]]],
404 positions_orig_su[corner_verts_orig[tri_orig[2]]]);
405 const float3 old_first_pos_orig_cu = self_->initial_positions_cu_[first_point_i];
406 const float3 new_first_pos_orig_cu = math::transform_point(transforms_.surface_to_curves,
407 new_first_pos_orig_su);
408
409 /* Actually transform curve points. */
410 const float4x4 slide_transform = this->get_slide_transform(
411 old_first_pos_orig_cu, new_first_pos_orig_cu, initial_normal_cu, new_normal_cu);
412 for (const int point_i : points) {
413 positions_orig_cu[point_i] = math::transform_point(
414 slide_transform, self_->initial_positions_cu_[point_i]);
415 }
416 surface_uv_coords[curve_i] = uv;
417 }
418 });
419 }
420
421 bool find_closest_ray_hit(const float3 &ray_start_su,
422 const float3 &ray_direction_su,
423 const float3 &point_su,
424 int &r_tri_index,
425 float3 &r_hit_pos)
426 {
427 float best_dist_sq_su = FLT_MAX;
428 int best_tri_index_eval;
429 float3 best_hit_pos_su;
431 *surface_bvh_eval_.tree,
432 ray_start_su,
433 ray_direction_su,
434 0.0f,
435 FLT_MAX,
436 [&](const int tri_index, const BVHTreeRay &ray, BVHTreeRayHit &hit) {
437 surface_bvh_eval_.raycast_callback(&surface_bvh_eval_, tri_index, &ray, &hit);
438 if (hit.index < 0) {
439 return;
440 }
441 const float3 hit_pos_su = hit.co;
442 const float dist_sq_su = math::distance_squared(hit_pos_su, point_su);
443 if (dist_sq_su < best_dist_sq_su) {
444 best_dist_sq_su = dist_sq_su;
445 best_hit_pos_su = hit_pos_su;
446 best_tri_index_eval = hit.index;
447 }
448 });
449
450 if (best_dist_sq_su == FLT_MAX) {
451 return false;
452 }
453 r_tri_index = best_tri_index_eval;
454 r_hit_pos = best_hit_pos_su;
455 return true;
456 }
457
459 const float3 &new_root_pos,
460 const float3 &old_normal,
461 const float3 &new_normal)
462 {
463 float3x3 rotation_3x3;
464 rotation_between_vecs_to_mat3(rotation_3x3.ptr(), old_normal, new_normal);
465
467 transform.location() -= old_root_pos;
468 transform = float4x4(rotation_3x3) * transform;
469 transform.location() += new_root_pos;
470 return transform;
471 }
472};
473
475{
476 SlideOperationExecutor executor{C};
477 executor.execute(*this, C, stroke_extension);
478}
479
480std::unique_ptr<CurvesSculptStrokeOperation> new_slide_operation()
481{
482 return std::make_unique<SlideOperation>();
483}
484
485} // namespace blender::ed::sculpt_paint
int BKE_brush_size_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1210
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
Definition brush.cc:1510
float BKE_brush_alpha_get(const Scene *scene, const Brush *brush)
Definition brush.cc:1269
Object * CTX_data_active_object(const bContext *C)
Low-level operations for curves.
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:641
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
MINLINE float pow2f(float x)
void rotation_between_vecs_to_mat3(float m[3][3], const float v1[3], const float v2[3])
#define UNUSED_VARS(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
eCurvesSymmetryType
@ OB_MESH
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
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:390
#define ND_DATA
Definition WM_types.hh:506
PyObject * self
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
void append(const T &value)
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override
Result sample(const float2 &query_uv) const
MatBase< 4, 4 > float4x4
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory)
void report_empty_evaluated_surface(ReportList *reports)
void remember_stroke_position(Scene &scene, const float3 &brush_position_wo)
void report_missing_uv_map_on_original_surface(ReportList *reports)
void report_missing_uv_map_on_evaluated_surface(ReportList *reports)
void report_missing_surface(ReportList *reports)
std::optional< CurvesBrush3D > sample_curves_surface_3d_brush(const Depsgraph &depsgraph, const ARegion &region, const View3D &v3d, const CurvesSurfaceTransforms &transforms, const bke::BVHTreeFromMesh &surface_bvh, const float2 &brush_pos_re, const float brush_radius_re)
void report_empty_original_surface(ReportList *reports)
Vector< float4x4 > get_symmetry_brush_transforms(const eCurvesSymmetryType symmetry)
std::unique_ptr< CurvesSculptStrokeOperation > new_slide_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)
CartesianBasis invert(const CartesianBasis &basis)
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)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
void BLI_bvhtree_ray_cast_all_cpp(const BVHTree &tree, const float3 co, const float3 dir, float radius, float hit_dist, BVHTree_RayCastCallback_CPP fn)
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
const c_style_mat & ptr() const
void execute(SlideOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
void find_curves_to_slide(const float3 &brush_pos_cu, const float brush_radius_cu, const ReverseUVSampler &reverse_uv_sampler_orig, Vector< SlideCurveInfo > &r_curves_to_slide)
void slide(const Span< SlideCurveInfo > slide_curves, const ReverseUVSampler &reverse_uv_sampler_orig, const float4x4 &brush_transform)
bool find_closest_ray_hit(const float3 &ray_start_su, const float3 &ray_direction_su, const float3 &point_su, int &r_tri_index, float3 &r_hit_pos)
float4x4 get_slide_transform(const float3 &old_root_pos, const float3 &new_root_pos, const float3 &old_normal, const float3 &new_normal)
void WM_main_add_notifier(uint type, void *reference)