From 190334b47df46f0e912c873077163e6129e4cbae Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 5 Apr 2022 15:24:12 +0200 Subject: Curves: new Grow/Shrink brush This adds a new Grow/Shrink brush which is similar to the Length brush in the old hair system. * It's possible to switch between growing and shrinking by hold down ctrl and/or by changing the direction enum. * 3d brush is supported. * Different brush falloffs are supported. * Supports scaling curves uniformly or shrinking/extrapolating them. Extrapolation is linear only in this patch. * A minimum length settings helps to avoid creating zero-sized curves. Differential Revision: https://developer.blender.org/D14474 --- source/blender/blenkernel/intern/brush.c | 1 + source/blender/blenlib/BLI_math_geom.h | 18 + source/blender/blenlib/intern/math_geom.c | 60 +++ source/blender/editors/datafiles/CMakeLists.txt | 2 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 1 + .../sculpt_paint/curves_sculpt_grow_shrink.cc | 526 +++++++++++++++++++++ .../editors/sculpt_paint/curves_sculpt_intern.hh | 3 + .../editors/sculpt_paint/curves_sculpt_ops.cc | 124 +---- source/blender/makesdna/DNA_brush_enums.h | 9 +- source/blender/makesdna/DNA_brush_types.h | 4 + source/blender/makesrna/intern/rna_brush.c | 22 +- 11 files changed, 649 insertions(+), 121 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc (limited to 'source') diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index ff07d061a20..601c867d8db 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -1558,6 +1558,7 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush) brush->curves_sculpt_settings = MEM_callocN(sizeof(BrushCurvesSculptSettings), __func__); } brush->curves_sculpt_settings->add_amount = 1; + brush->curves_sculpt_settings->minimum_length = 0.01f; } struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode) diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index 9e65093a05f..5c1b6c8d774 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -329,6 +329,24 @@ float closest_to_line_segment_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2]); + +/** + * Finds the points where two line segments are closest to each other. + * + * `lambda_*` is a value between 0 and 1 for each segment that indicates where `r_closest_*` is on + * the corresponding segment. + * + * \return Squared distance between both segments. + */ +float closest_seg_seg_v2(float r_closest_a[2], + float r_closest_b[2], + float *r_lambda_a, + float *r_lambda_b, + const float a1[2], + const float a2[2], + const float b1[2], + const float b2[2]); + /** * Point closest to v1 on line v2-v3 in 3D. * diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index 1b13493e00c..e7ccdeab80a 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -294,6 +294,66 @@ float dist_to_line_segment_v2(const float p[2], const float l1[2], const float l return sqrtf(dist_squared_to_line_segment_v2(p, l1, l2)); } +float closest_seg_seg_v2(float r_closest_a[2], + float r_closest_b[2], + float *r_lambda_a, + float *r_lambda_b, + const float a1[2], + const float a2[2], + const float b1[2], + const float b2[2]) +{ + if (isect_seg_seg_v2_simple(a1, a2, b1, b2)) { + float intersection[2]; + isect_line_line_v2_point(a1, a2, b1, b2, intersection); + copy_v2_v2(r_closest_a, intersection); + copy_v2_v2(r_closest_b, intersection); + float tmp[2]; + *r_lambda_a = closest_to_line_v2(tmp, intersection, a1, a2); + *r_lambda_b = closest_to_line_v2(tmp, intersection, b1, b2); + const float min_dist_sq = len_squared_v2v2(r_closest_a, r_closest_b); + return min_dist_sq; + } + + float p1[2], p2[2], p3[2], p4[2]; + const float lambda1 = closest_to_line_segment_v2(p1, a1, b1, b2); + const float lambda2 = closest_to_line_segment_v2(p2, a2, b1, b2); + const float lambda3 = closest_to_line_segment_v2(p3, b1, a1, a2); + const float lambda4 = closest_to_line_segment_v2(p4, b2, a1, a2); + const float dist_sq1 = len_squared_v2v2(p1, a1); + const float dist_sq2 = len_squared_v2v2(p2, a2); + const float dist_sq3 = len_squared_v2v2(p3, b1); + const float dist_sq4 = len_squared_v2v2(p4, b2); + + const float min_dist_sq = min_ffff(dist_sq1, dist_sq2, dist_sq3, dist_sq4); + if (min_dist_sq == dist_sq1) { + copy_v2_v2(r_closest_a, a1); + copy_v2_v2(r_closest_b, p1); + *r_lambda_a = 0.0f; + *r_lambda_b = lambda1; + } + else if (min_dist_sq == dist_sq2) { + copy_v2_v2(r_closest_a, a2); + copy_v2_v2(r_closest_b, p2); + *r_lambda_a = 1.0f; + *r_lambda_b = lambda2; + } + else if (min_dist_sq == dist_sq3) { + copy_v2_v2(r_closest_a, p3); + copy_v2_v2(r_closest_b, b1); + *r_lambda_a = lambda3; + *r_lambda_b = 0.0f; + } + else { + BLI_assert(min_dist_sq == dist_sq4); + copy_v2_v2(r_closest_a, p4); + copy_v2_v2(r_closest_b, b2); + *r_lambda_a = lambda4; + *r_lambda_b = 1.0f; + } + return min_dist_sq; +} + float closest_to_line_segment_v2(float r_close[2], const float p[2], const float l1[2], diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt index 0810e76fe37..ebe61d217ed 100644 --- a/source/blender/editors/datafiles/CMakeLists.txt +++ b/source/blender/editors/datafiles/CMakeLists.txt @@ -774,7 +774,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES ops.curves.sculpt_comb ops.curves.sculpt_cut ops.curves.sculpt_delete - ops.curves.sculpt_grow + ops.curves.sculpt_grow_shrink ops.generic.cursor ops.generic.select ops.generic.select_box diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index c422c8c2033..fe7683d12f5 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRC curves_sculpt_add.cc curves_sculpt_comb.cc curves_sculpt_delete.cc + curves_sculpt_grow_shrink.cc curves_sculpt_ops.cc curves_sculpt_snake_hook.cc paint_cursor.c diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc new file mode 100644 index 00000000000..d26af20799e --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc @@ -0,0 +1,526 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "curves_sculpt_intern.hh" + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_float4x4.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_paint.h" +#include "BKE_spline.hh" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "curves_sculpt_intern.hh" + +/** + * The code below uses a suffix naming convention to indicate the coordinate space: + * - `cu`: Local space of the curves object that is being edited. + * - `su`: Local space of the surface object. + * - `wo`: World space. + * - `re`: 2D coordinates within the region. + */ + +namespace blender::ed::sculpt_paint { + +using bke::CurvesGeometry; + +/** + * Utility class to wrap different grow/shrink behaviors. + * It might be useful to use this for other future brushes as well, but better see if this + * abstraction holds up for a while before using it in more places. + */ +class CurvesEffect { + public: + virtual void execute(CurvesGeometry &curves, + Span curve_indices, + Span move_distances_cu) = 0; +}; + +/** + * Make curves smaller by trimming the end off. + */ +class ShrinkCurvesEffect : public CurvesEffect { + private: + Brush &brush_; + + public: + ShrinkCurvesEffect(Brush &brush) : brush_(brush) + { + } + + void execute(CurvesGeometry &curves, + const Span curve_indices, + const Span move_distances_cu) override + { + MutableSpan positions_cu = curves.positions(); + threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + for (const int influence_i : range) { + const int curve_i = curve_indices[influence_i]; + const float move_distance_cu = move_distances_cu[influence_i]; + const IndexRange curve_points = curves.points_for_curve(curve_i); + this->shrink_curve(positions_cu, curve_points, move_distance_cu); + } + }); + } + + void shrink_curve(MutableSpan positions, + const IndexRange curve_points, + const float shrink_length) const + { + PolySpline spline; + spline.resize(curve_points.size()); + MutableSpan spline_positions = spline.positions(); + spline_positions.copy_from(positions.slice(curve_points)); + spline.mark_cache_invalid(); + const float min_length = brush_.curves_sculpt_settings->minimum_length; + const float old_length = spline.length(); + const float new_length = std::max(min_length, old_length - shrink_length); + const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f); + + Vector old_point_lengths; + old_point_lengths.append(0.0f); + for (const int i : spline_positions.index_range().drop_back(1)) { + const float3 &p1 = spline_positions[i]; + const float3 &p2 = spline_positions[i + 1]; + const float length = math::distance(p1, p2); + old_point_lengths.append(old_point_lengths.last() + length); + } + + for (const int i : spline_positions.index_range()) { + const float eval_length = old_point_lengths[i] * length_factor; + const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length); + const float index_factor = lookup.evaluated_index + lookup.factor; + float3 p; + spline.sample_with_index_factors(spline_positions, {&index_factor, 1}, {&p, 1}); + positions[curve_points[i]] = p; + } + } +}; + +/** + * Make the curves longer by extrapolating them linearly. + */ +class ExtrapolateCurvesEffect : public CurvesEffect { + void execute(CurvesGeometry &curves, + const Span curve_indices, + const Span move_distances_cu) override + { + MutableSpan positions_cu = curves.positions(); + threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + for (const int influence_i : range) { + const int curve_i = curve_indices[influence_i]; + const float move_distance_cu = move_distances_cu[influence_i]; + const IndexRange curve_points = curves.points_for_curve(curve_i); + + if (curve_points.size() <= 1) { + continue; + } + + const float3 old_last_pos_cu = positions_cu[curve_points.last()]; + /* Use some point within the curve rather than the end point to smooth out some random + * variation. */ + const float3 direction_reference_point = + positions_cu[curve_points[curve_points.size() / 2]]; + const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point); + + const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu; + this->move_last_point_and_resample(positions_cu, curve_points, new_last_pos_cu); + } + }); + } + + void move_last_point_and_resample(MutableSpan positions, + const IndexRange curve_points, + const float3 &new_last_point_position) const + { + Vector old_lengths; + old_lengths.append(0.0f); + /* Used to (1) normalize the segment sizes over time and (2) support making zero-length + * segments */ + const float extra_length = 0.001f; + for (const int segment_i : IndexRange(curve_points.size() - 1)) { + const float3 &p1 = positions[curve_points[segment_i]]; + const float3 &p2 = positions[curve_points[segment_i] + 1]; + const float length = math::distance(p1, p2); + old_lengths.append(old_lengths.last() + length + extra_length); + } + Vector point_factors; + for (float &old_length : old_lengths) { + point_factors.append(old_length / old_lengths.last()); + } + + PolySpline new_spline; + new_spline.resize(curve_points.size()); + MutableSpan new_spline_positions = new_spline.positions(); + for (const int i : IndexRange(curve_points.size() - 1)) { + new_spline_positions[i] = positions[curve_points[i]]; + } + new_spline_positions.last() = new_last_point_position; + new_spline.mark_cache_invalid(); + + for (const int i : IndexRange(curve_points.size())) { + const float factor = point_factors[i]; + const Spline::LookupResult lookup = new_spline.lookup_evaluated_factor(factor); + const float index_factor = lookup.evaluated_index + lookup.factor; + float3 p; + new_spline.sample_with_index_factors( + new_spline_positions, {&index_factor, 1}, {&p, 1}); + positions[curve_points[i]] = p; + } + } +}; + +/** + * Change the length of curves by scaling them uniformly. + */ +class ScaleCurvesEffect : public CurvesEffect { + private: + bool scale_up_; + Brush &brush_; + + public: + ScaleCurvesEffect(bool scale_up, Brush &brush) : scale_up_(scale_up), brush_(brush) + { + } + + void execute(CurvesGeometry &curves, + const Span curve_indices, + const Span move_distances_cu) override + { + MutableSpan positions_cu = curves.positions(); + threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + for (const int influence_i : range) { + const int curve_i = curve_indices[influence_i]; + const float move_distance_cu = move_distances_cu[influence_i]; + const IndexRange points = curves.points_for_curve(curve_i); + + const float old_length = this->compute_poly_curve_length(positions_cu.slice(points)); + const float length_diff = scale_up_ ? move_distance_cu : -move_distance_cu; + const float min_length = brush_.curves_sculpt_settings->minimum_length; + const float new_length = std::max(min_length, old_length + length_diff); + const float scale_factor = safe_divide(new_length, old_length); + + const float3 &root_pos_cu = positions_cu[points[0]]; + for (float3 &pos_cu : positions_cu.slice(points.drop_front(1))) { + pos_cu = (pos_cu - root_pos_cu) * scale_factor + root_pos_cu; + } + } + }); + } + + float compute_poly_curve_length(const Span positions) + { + float length = 0.0f; + const int segments_num = positions.size() - 1; + for (const int segment_i : IndexRange(segments_num)) { + const float3 &p1 = positions[segment_i]; + const float3 &p2 = positions[segment_i + 1]; + length += math::distance(p1, p2); + } + return length; + } +}; + +class CurvesEffectOperation : public CurvesSculptStrokeOperation { + private: + std::unique_ptr effect_; + float2 last_mouse_position_; + CurvesBrush3D brush_3d_; + + friend struct CurvesEffectOperationExecutor; + + public: + CurvesEffectOperation(std::unique_ptr effect) : effect_(std::move(effect)) + { + } + + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct CurvesEffectOperationExecutor { + CurvesEffectOperation *self_ = nullptr; + Depsgraph *depsgraph_ = nullptr; + Scene *scene_ = nullptr; + Object *object_ = nullptr; + ARegion *region_ = nullptr; + View3D *v3d_ = nullptr; + RegionView3D *rv3d_ = nullptr; + + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + Brush *brush_ = nullptr; + float brush_radius_re_; + float brush_radius_sq_re_; + float brush_strength_; + eBrushFalloffShape falloff_shape_; + + float4x4 curves_to_world_mat_; + float4x4 world_to_curves_mat_; + + float2 brush_pos_start_re_; + float2 brush_pos_end_re_; + + struct Influences { + Vector curve_indices; + Vector move_distances_cu; + }; + + void execute(CurvesEffectOperation &self, bContext *C, const StrokeExtension &stroke_extension) + { + BLI_SCOPED_DEFER([&]() { self.last_mouse_position_ = stroke_extension.mouse_position; }); + + self_ = &self; + depsgraph_ = CTX_data_depsgraph_pointer(C); + scene_ = CTX_data_scene(C); + object_ = CTX_data_active_object(C); + region_ = CTX_wm_region(C); + v3d_ = CTX_wm_view3d(C); + rv3d_ = CTX_wm_region_view3d(C); + + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + + CurvesSculpt &curves_sculpt = *scene_->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush(&curves_sculpt.paint); + brush_radius_re_ = BKE_brush_size_get(scene_, brush_); + brush_strength_ = BKE_brush_alpha_get(scene_, brush_); + brush_radius_sq_re_ = pow2f(brush_radius_re_); + falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape); + + curves_to_world_mat_ = object_->obmat; + world_to_curves_mat_ = curves_to_world_mat_.inverted(); + + brush_pos_start_re_ = self.last_mouse_position_; + brush_pos_end_re_ = stroke_extension.mouse_position; + + if (stroke_extension.is_first) { + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + if (std::optional brush_3d = sample_curves_3d_brush( + *C, *object_, stroke_extension.mouse_position, brush_radius_re_)) { + self.brush_3d_ = *brush_3d; + } + } + + return; + } + + /* Compute influences. */ + threading::EnumerableThreadSpecific influences_for_thread; + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { + this->gather_influences_projected(influences_for_thread); + } + else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + this->gather_influences_spherical(influences_for_thread); + } + + /* Execute effect. */ + threading::parallel_for_each(influences_for_thread, [&](const Influences &influences) { + BLI_assert(influences.curve_indices.size() == influences.move_distances_cu.size()); + self_->effect_->execute(*curves_, influences.curve_indices, influences.move_distances_cu); + }); + + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region_); + } + + void gather_influences_projected( + threading::EnumerableThreadSpecific &influences_for_thread) + { + const Span positions_cu = curves_->positions(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values); + + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + Influences &local_influences = influences_for_thread.local(); + + for (const int curve_i : curves_range) { + const IndexRange points = curves_->points_for_curve(curve_i); + const int tot_segments = points.size() - 1; + float max_move_distance_cu = 0.0f; + for (const int segment_i : IndexRange(tot_segments)) { + const float3 &p1_cu = positions_cu[points[segment_i]]; + const float3 &p2_cu = positions_cu[points[segment_i] + 1]; + + float2 p1_re, p2_re; + ED_view3d_project_float_v2_m4(region_, p1_cu, p1_re, projection.values); + ED_view3d_project_float_v2_m4(region_, p2_cu, p2_re, projection.values); + + float2 closest_on_brush_re; + float2 closest_on_segment_re; + float lambda_on_brush; + float lambda_on_segment; + const float dist_to_brush_sq_re = closest_seg_seg_v2(closest_on_brush_re, + closest_on_segment_re, + &lambda_on_brush, + &lambda_on_segment, + brush_pos_start_re_, + brush_pos_end_re_, + p1_re, + p2_re); + + if (dist_to_brush_sq_re > brush_radius_sq_re_) { + continue; + } + + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re_); + const float weight = brush_strength_ * radius_falloff; + + const float3 closest_on_segment_cu = math::interpolate(p1_cu, p2_cu, lambda_on_segment); + + float3 brush_start_pos_wo, brush_end_pos_wo; + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * closest_on_segment_cu, + brush_pos_start_re_, + brush_start_pos_wo); + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * closest_on_segment_cu, + brush_pos_end_re_, + brush_end_pos_wo); + const float3 brush_start_pos_cu = world_to_curves_mat_ * brush_start_pos_wo; + const float3 brush_end_pos_cu = world_to_curves_mat_ * brush_end_pos_wo; + + const float move_distance_cu = weight * + math::distance(brush_start_pos_cu, brush_end_pos_cu); + max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu); + } + if (max_move_distance_cu > 0.0f) { + local_influences.curve_indices.append(curve_i); + local_influences.move_distances_cu.append(max_move_distance_cu); + } + } + }); + } + + void gather_influences_spherical( + threading::EnumerableThreadSpecific &influences_for_thread) + { + const Span positions_cu = curves_->positions(); + + float3 brush_pos_start_wo, brush_pos_end_wo; + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * self_->brush_3d_.position_cu, + brush_pos_start_re_, + brush_pos_start_wo); + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * self_->brush_3d_.position_cu, + brush_pos_end_re_, + brush_pos_end_wo); + const float3 brush_pos_start_cu = world_to_curves_mat_ * brush_pos_start_wo; + const float3 brush_pos_end_cu = world_to_curves_mat_ * brush_pos_end_wo; + const float3 brush_pos_diff_cu = brush_pos_end_cu - brush_pos_start_cu; + const float brush_pos_diff_length_cu = math::length(brush_pos_diff_cu); + const float brush_radius_cu = self_->brush_3d_.radius_cu; + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + Influences &local_influences = influences_for_thread.local(); + + for (const int curve_i : curves_range) { + const IndexRange points = curves_->points_for_curve(curve_i); + const int tot_segments = points.size() - 1; + float max_move_distance_cu = 0.0f; + for (const int segment_i : IndexRange(tot_segments)) { + const float3 &p1_cu = positions_cu[points[segment_i]]; + const float3 &p2_cu = positions_cu[points[segment_i] + 1]; + + float3 closest_on_segment_cu; + float3 closest_on_brush_cu; + isect_seg_seg_v3(p1_cu, + p2_cu, + brush_pos_start_cu, + brush_pos_end_cu, + closest_on_segment_cu, + closest_on_brush_cu); + + const float dist_to_brush_sq_cu = math::distance_squared(closest_on_segment_cu, + closest_on_brush_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_cu, brush_radius_cu); + const float weight = brush_strength_ * radius_falloff; + + const float move_distance_cu = weight * brush_pos_diff_length_cu; + max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu); + } + if (max_move_distance_cu > 0.0f) { + local_influences.curve_indices.append(curve_i); + local_influences.move_distances_cu.append(max_move_distance_cu); + } + } + }); + } +}; + +void CurvesEffectOperation::on_stroke_extended(bContext *C, + const StrokeExtension &stroke_extension) +{ + CurvesEffectOperationExecutor executor; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_grow_shrink_operation( + const BrushStrokeMode brush_mode, bContext *C) +{ + Scene &scene = *CTX_data_scene(C); + Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint); + const bool use_scale_uniform = brush.curves_sculpt_settings->flag & + BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM; + const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0); + + if (use_grow) { + if (use_scale_uniform) { + return std::make_unique( + std::make_unique(true, brush)); + } + return std::make_unique(std::make_unique()); + } + if (use_scale_uniform) { + return std::make_unique( + std::make_unique(false, brush)); + } + return std::make_unique(std::make_unique(brush)); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index d021627921f..03413221907 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -5,6 +5,7 @@ #include #include "curves_sculpt_intern.h" +#include "paint_intern.h" #include "BLI_math_vector.hh" @@ -36,6 +37,8 @@ std::unique_ptr new_add_operation(); std::unique_ptr new_comb_operation(); std::unique_ptr new_delete_operation(); std::unique_ptr new_snake_hook_operation(); +std::unique_ptr new_grow_shrink_operation( + const BrushStrokeMode brush_mode, bContext *C); struct CurvesBrush3D { float3 position_cu; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index de53c118b20..893b2640427 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -74,118 +74,6 @@ using blender::bke::CurvesGeometry; /** \name * SCULPT_CURVES_OT_brush_stroke * \{ */ -/** - * Resamples the curves to a shorter length. - */ -class ShrinkOperation : public CurvesSculptStrokeOperation { - private: - float2 last_mouse_position_; - - public: - void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override - { - BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); - - if (stroke_extension.is_first) { - return; - } - - Scene &scene = *CTX_data_scene(C); - Object &object = *CTX_data_active_object(C); - ARegion *region = CTX_wm_region(C); - View3D *v3d = CTX_wm_view3d(C); - RegionView3D *rv3d = CTX_wm_region_view3d(C); - - CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt; - Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); - const float brush_radius = BKE_brush_size_get(&scene, &brush); - const float brush_strength = BKE_brush_alpha_get(&scene, &brush); - - const float4x4 ob_mat = object.obmat; - const float4x4 ob_imat = ob_mat.inverted(); - - float4x4 projection; - ED_view3d_ob_project_mat_get(rv3d, &object, projection.values); - - Curves &curves_id = *static_cast(object.data); - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - MutableSpan positions = curves.positions(); - - const float2 mouse_prev = last_mouse_position_; - const float2 mouse_cur = stroke_extension.mouse_position; - const float2 mouse_diff = mouse_cur - mouse_prev; - - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { - for (const int curve_i : curves_range) { - const IndexRange curve_points = curves.points_for_curve(curve_i); - const int last_point_i = curve_points.last(); - - const float3 old_tip_position = positions[last_point_i]; - - float2 old_tip_position_screen; - ED_view3d_project_float_v2_m4( - region, old_tip_position, old_tip_position_screen, projection.values); - - const float distance_screen = math::distance(old_tip_position_screen, mouse_prev); - if (distance_screen > brush_radius) { - continue; - } - - const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); - const float weight = brush_strength * radius_falloff; - - const float2 offset_tip_position_screen = old_tip_position_screen + weight * mouse_diff; - float3 offset_tip_position; - ED_view3d_win_to_3d(v3d, - region, - ob_mat * old_tip_position, - offset_tip_position_screen, - offset_tip_position); - offset_tip_position = ob_imat * offset_tip_position; - const float shrink_length = math::distance(offset_tip_position, old_tip_position); - - this->shrink_curve(positions, curve_points, shrink_length); - } - }); - - curves.tag_positions_changed(); - DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); - } - - void shrink_curve(MutableSpan positions, - const IndexRange curve_points, - const float shrink_length) const - { - PolySpline spline; - spline.resize(curve_points.size()); - MutableSpan spline_positions = spline.positions(); - spline_positions.copy_from(positions.slice(curve_points)); - spline.mark_cache_invalid(); - const float old_length = spline.length(); - const float new_length = std::max(0.0f, old_length - shrink_length); - const float length_factor = new_length / old_length; - - Vector old_point_lengths; - old_point_lengths.append(0.0f); - for (const int i : spline_positions.index_range().drop_back(1)) { - const float3 &p1 = spline_positions[i]; - const float3 &p2 = spline_positions[i + 1]; - const float length = math::distance(p1, p2); - old_point_lengths.append(old_point_lengths.last() + length); - } - - for (const int i : spline_positions.index_range()) { - const float eval_length = old_point_lengths[i] * length_factor; - const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - spline.sample_with_index_factors(spline_positions, {&index_factor, 1}, {&p, 1}); - positions[curve_points[i]] = p; - } - } -}; - class DensityAddOperation : public CurvesSculptStrokeOperation { private: /** Contains the root points of the curves that existed before this operation started. */ @@ -612,8 +500,10 @@ class DensityAddOperation : public CurvesSculptStrokeOperation { }; static std::unique_ptr start_brush_operation(bContext *C, - wmOperator *UNUSED(op)) + wmOperator *op) { + const BrushStrokeMode mode = static_cast(RNA_enum_get(op->ptr, "mode")); + Scene &scene = *CTX_data_scene(C); CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt; Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); @@ -626,10 +516,10 @@ static std::unique_ptr start_brush_operation(bConte return new_snake_hook_operation(); case CURVES_SCULPT_TOOL_ADD: return new_add_operation(); + case CURVES_SCULPT_TOOL_GROW_SHRINK: + return new_grow_shrink_operation(mode, C); case CURVES_SCULPT_TOOL_TEST1: return std::make_unique(); - case CURVES_SCULPT_TOOL_TEST2: - return std::make_unique(); } BLI_assert_unreachable(); return {}; @@ -674,7 +564,9 @@ static void stroke_update_step(bContext *C, stroke_extension.is_first = false; } - op_data->operation->on_stroke_extended(C, stroke_extension); + if (op_data->operation) { + op_data->operation->on_stroke_extended(C, stroke_extension); + } } static void stroke_done(const bContext *C, PaintStroke *stroke) diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 18d82448d1f..96a97648ac9 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -461,8 +461,8 @@ typedef enum eBrushCurvesSculptTool { CURVES_SCULPT_TOOL_DELETE = 1, CURVES_SCULPT_TOOL_SNAKE_HOOK = 2, CURVES_SCULPT_TOOL_ADD = 3, - CURVES_SCULPT_TOOL_TEST1 = 4, - CURVES_SCULPT_TOOL_TEST2 = 5, + CURVES_SCULPT_TOOL_GROW_SHRINK = 4, + CURVES_SCULPT_TOOL_TEST1 = 5, } eBrushCurvesSculptTool; /** When #BRUSH_ACCUMULATE is used */ @@ -608,6 +608,11 @@ typedef enum eBrushFalloffShape { PAINT_FALLOFF_SHAPE_TUBE = 1, } eBrushFalloffShape; +typedef enum eBrushCurvesSculptFlag { + BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM = (1 << 0), + BRUSH_CURVES_SCULPT_FLAG_GROW_SHRINK_INVERT = (1 << 1), +} eBrushCurvesSculptFlag; + #define MAX_BRUSH_PIXEL_RADIUS 500 #define GP_MAX_BRUSH_PIXEL_RADIUS 1000 diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 1382efca409..2d879f5afa0 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -140,6 +140,10 @@ typedef struct BrushGpencilSettings { typedef struct BrushCurvesSculptSettings { /** Number of curves added by the add brush. */ int add_amount; + /* eBrushCurvesSculptFlag. */ + uint32_t flag; + /** When shrinking curves, they shouldn't become shorter than this length. */ + float minimum_length; } BrushCurvesSculptSettings; typedef struct Brush { diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index b2b6bdbcffc..062c827b9d0 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -248,8 +248,8 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = { {CURVES_SCULPT_TOOL_DELETE, "DELETE", ICON_NONE, "Delete", ""}, {CURVES_SCULPT_TOOL_SNAKE_HOOK, "SNAKE_HOOK", ICON_NONE, "Snake Hook", ""}, {CURVES_SCULPT_TOOL_ADD, "ADD", ICON_NONE, "Add", ""}, + {CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_NONE, "Grow / Shrink", ""}, {CURVES_SCULPT_TOOL_TEST1, "TEST1", ICON_NONE, "Test 1", ""}, - {CURVES_SCULPT_TOOL_TEST2, "TEST2", ICON_NONE, "Test 2", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -883,7 +883,13 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C, default: return DummyRNA_DEFAULT_items; } - + case PAINT_MODE_SCULPT_CURVES: + switch (me->curves_sculpt_tool) { + case CURVES_SCULPT_TOOL_GROW_SHRINK: + return prop_direction_items; + default: + return DummyRNA_DEFAULT_items; + } default: return DummyRNA_DEFAULT_items; } @@ -1927,6 +1933,18 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna) prop = RNA_def_property(srna, "add_amount", PROP_INT, PROP_NONE); RNA_def_property_range(prop, 1, INT32_MAX); RNA_def_property_ui_text(prop, "Add Amount", "Number of curves added by the Add brush"); + + prop = RNA_def_property(srna, "scale_uniform", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM); + RNA_def_property_ui_text(prop, + "Scale Uniform", + "Grow or shrink curves by changing their size uniformly instead of " + "using trimming or extrapolation"); + + prop = RNA_def_property(srna, "minimum_length", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_text( + prop, "Minimum Length", "Avoid shrinking curves shorter than this length"); } static void rna_def_brush(BlenderRNA *brna) -- cgit v1.2.3