From 416aef4e13ccc30e82ecaa691f26af54dbd5ee7e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 30 Jun 2022 15:09:13 +0200 Subject: Curves: New tools for curves sculpt mode. This commit contains various new features for curves sculpt mode that have been developed in parallel. * Selection: * Operator to select points/curves randomly. * Operator to select endpoints of curves. * Operator to grow/shrink an existing selection. * New Brushes: * Pinch: Moves points towards the brush center. * Smooth: Makes individual curves straight without changing the root or tip position. * Puff: Makes curves stand up, aligning them with the surface normal. * Density: Add or remove curves to achieve a certain density defined by a minimum distance value. * Slide: Move root points on the surface. Differential Revision: https://developer.blender.org/D15134 --- .../editors/sculpt_paint/curves_sculpt_pinch.cc | 307 +++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc (limited to 'source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc') diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc new file mode 100644 index 00000000000..db890d6a054 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_paint.h" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_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 "WM_api.h" + +/** + * The code below uses a prefix 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 { + +class PinchOperation : public CurvesSculptStrokeOperation { + private: + bool invert_pinch_; + Array segment_lengths_cu_; + + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + friend struct PinchOperationExecutor; + + public: + PinchOperation(const bool invert_pinch) : invert_pinch_(invert_pinch) + { + } + + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +struct PinchOperationExecutor { + PinchOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + VArray point_factors_; + Vector selected_curve_indices_; + IndexMask curve_selection_; + + CurvesSculptTransforms transforms_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + + float invert_factor_; + + float2 brush_pos_re_; + + PinchOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(PinchOperation &self, const bContext &C, const StrokeExtension &stroke_extension) + { + self_ = &self; + + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_->curves_num() == 0) { + return; + } + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = BKE_brush_alpha_get(ctx_.scene, brush_); + + invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f; + + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + + point_factors_ = get_point_selection(*curves_id_); + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + + brush_pos_re_ = stroke_extension.mouse_position; + const eBrushFalloffShape falloff_shape = static_cast( + brush_->falloff_shape); + + if (stroke_extension.is_first) { + this->initialize_segment_lengths(); + + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + self_->brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + *ctx_.rv3d, + *object_, + brush_pos_re_, + brush_radius_base_re_); + } + } + + Array changed_curves(curves_->curves_num(), false); + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + this->pinch_projected_with_symmetry(changed_curves); + } + else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + this->pinch_spherical_with_symmetry(changed_curves); + } + else { + BLI_assert_unreachable(); + } + + this->restore_segment_lengths(changed_curves); + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void pinch_projected_with_symmetry(MutableSpan r_changed_curves) + { + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->pinch_projected(brush_transform, r_changed_curves); + } + } + + void pinch_projected(const float4x4 &brush_transform, MutableSpan r_changed_curves) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; + float2 old_pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + + const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_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 t = safe_divide(dist_to_brush_re, brush_radius_base_re_); + const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f); + const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight); + + const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu; + float3 new_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo); + + const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo; + positions_cu[point_i] = brush_transform * new_pos_cu; + r_changed_curves[curve_i] = true; + } + } + }); + } + + void pinch_spherical_with_symmetry(MutableSpan r_changed_curves) + { + float3 brush_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * self_->brush_3d_.position_cu, + brush_pos_re_, + brush_pos_wo); + const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo; + const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->pinch_spherical(brush_transform * brush_pos_cu, brush_radius_cu, r_changed_curves); + } + } + + void pinch_spherical(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan r_changed_curves) + { + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 old_pos_cu = positions_cu[point_i]; + + const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_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 t = safe_divide(dist_to_brush_cu, brush_radius_cu); + const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f); + const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight); + positions_cu[point_i] = new_pos_cu; + + r_changed_curves[curve_i] = true; + } + } + }); + } + + void initialize_segment_lengths() + { + const Span positions_cu = curves_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_->points_num()); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_back(1)) { + const float3 &p1_cu = positions_cu[point_i]; + const float3 &p2_cu = positions_cu[point_i + 1]; + const float length_cu = math::distance(p1_cu, p2_cu); + self_->segment_lengths_cu_[point_i] = length_cu; + } + } + }); + } + + void restore_segment_lengths(const Span changed_curves) + { + const Span expected_lengths_cu = self_->segment_lengths_cu_; + MutableSpan positions_cu = curves_->positions_for_write(); + + threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + if (!changed_curves[curve_i]) { + continue; + } + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int segment_i : IndexRange(points.size() - 1)) { + const float3 &p1_cu = positions_cu[points[segment_i]]; + float3 &p2_cu = positions_cu[points[segment_i] + 1]; + const float3 direction = math::normalize(p2_cu - p1_cu); + const float expected_length_cu = expected_lengths_cu[points[segment_i]]; + p2_cu = p1_cu + direction * expected_length_cu; + } + } + }); + } +}; + +void PinchOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) +{ + PinchOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_pinch_operation(const BrushStrokeMode brush_mode, + const bContext &C) +{ + const Scene &scene = *CTX_data_scene(&C); + const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint); + + const bool invert_pinch = (brush_mode == BRUSH_STROKE_INVERT) != + ((brush.flag & BRUSH_DIR_IN) != 0); + return std::make_unique(invert_pinch); +} + +} // namespace blender::ed::sculpt_paint -- cgit v1.2.3 From 8bf9d482dacf54c72291de187676ff76845e807f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 23:00:55 +1000 Subject: Cleanup: colon after params, move text into public doc-strings, spelling --- source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc') diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc index db890d6a054..5b7359a3905 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -30,10 +30,10 @@ /** * The code below uses a prefix 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. + * `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 { -- cgit v1.2.3 From c46d4d9fad5e16daa9f50e30e6373d20b8386bbb Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 5 Jul 2022 14:56:04 +0200 Subject: Curves: move curves surface transforms to blenkernel This utility struct is useful outside of sculpting code as well. --- source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc') diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc index 5b7359a3905..689b7d22e5e 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -68,7 +68,7 @@ struct PinchOperationExecutor { Vector selected_curve_indices_; IndexMask curve_selection_; - CurvesSculptTransforms transforms_; + CurvesSurfaceTransforms transforms_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -103,7 +103,7 @@ struct PinchOperationExecutor { invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f; - transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); point_factors_ = get_point_selection(*curves_id_); curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); -- cgit v1.2.3 From 1f94b56d774440d08eb92f2a7a47b9a6a7aa7b84 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 22 Jul 2022 15:39:41 +0200 Subject: Curves: support sculpting on deformed curves Previously, curves sculpt tools only worked on original data. This was very limiting, because one could effectively only sculpt the curves when all procedural effects were turned off. This patch adds support for curves sculpting while looking the result of procedural effects (like deformation based on the surface mesh). This functionality is also known as "crazy space" support in Blender. For more details see D15407. Differential Revision: https://developer.blender.org/D15407 --- .../editors/sculpt_paint/curves_sculpt_pinch.cc | 46 +++++++++++++++------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc') diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc index 689b7d22e5e..3e43b1a6361 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -157,6 +157,9 @@ struct PinchOperationExecutor { { const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); MutableSpan positions_cu = curves_->positions_for_write(); @@ -167,11 +170,13 @@ struct PinchOperationExecutor { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + const float3 old_pos_cu = deformation.positions[point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); - const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + const float dist_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_re_); if (dist_to_brush_sq_re > brush_radius_sq_re) { continue; } @@ -182,14 +187,21 @@ struct PinchOperationExecutor { const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * point_factors_[point_i]; - const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight); - - const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu; - float3 new_pos_wo; - ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo); - - const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo; - positions_cu[point_i] = brush_transform * new_pos_cu; + const float2 new_symm_pos_re = math::interpolate(old_symm_pos_re, brush_pos_re_, weight); + + float3 new_symm_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + + const float3 new_pos_cu = brush_transform * transforms_.world_to_curves * + new_symm_pos_wo; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } } @@ -221,11 +233,14 @@ struct PinchOperationExecutor { MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = positions_cu[point_i]; + const float3 old_pos_cu = deformation.positions[point_i]; const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -239,7 +254,10 @@ struct PinchOperationExecutor { point_factors_[point_i]; const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight); - positions_cu[point_i] = new_pos_cu; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } -- cgit v1.2.3