Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/curves/intern/curves_ops.cc249
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_add.cc29
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_comb.cc22
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_delete.cc14
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc16
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_intern.hh21
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_ops.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_selection.cc107
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc394
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc11
11 files changed, 852 insertions, 15 deletions
diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index 5588f7440a8..efa4f884e17 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -6,18 +6,23 @@
#include <atomic>
+#include "BLI_devirtualize_parameters.hh"
#include "BLI_utildefines.h"
+#include "BLI_vector_set.hh"
#include "ED_curves.h"
#include "ED_object.h"
#include "ED_screen.h"
+#include "ED_select_utils.h"
#include "WM_api.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
+#include "BKE_geometry_set.hh"
#include "BKE_layer.h"
+#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_object.h"
@@ -37,6 +42,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
+#include "RNA_enum_types.h"
#include "RNA_prototypes.h"
/**
@@ -49,6 +55,41 @@
namespace blender::ed::curves {
+static bool object_has_editable_curves(const Main &bmain, const Object &object)
+{
+ if (object.type != OB_CURVES) {
+ return false;
+ }
+ if (!ELEM(object.mode, OB_MODE_SCULPT_CURVES, OB_MODE_EDIT)) {
+ return false;
+ }
+ if (!BKE_id_is_editable(&bmain, static_cast<const ID *>(object.data))) {
+ return false;
+ }
+ return true;
+}
+
+static VectorSet<Curves *> get_unique_editable_curves(const bContext &C)
+{
+ VectorSet<Curves *> unique_curves;
+
+ const Main &bmain = *CTX_data_main(&C);
+
+ Object *object = CTX_data_active_object(&C);
+ if (object && object_has_editable_curves(bmain, *object)) {
+ unique_curves.add_new(static_cast<Curves *>(object->data));
+ }
+
+ CTX_DATA_BEGIN (&C, Object *, object, selected_objects) {
+ if (object_has_editable_curves(bmain, *object)) {
+ unique_curves.add(static_cast<Curves *>(object->data));
+ }
+ }
+ CTX_DATA_END;
+
+ return unique_curves;
+}
+
using bke::CurvesGeometry;
namespace convert_to_particle_system {
@@ -645,6 +686,211 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot)
"How to find the point on the surface to attach to");
}
+static bool selection_poll(bContext *C)
+{
+ const Object *object = CTX_data_active_object(C);
+ if (object == nullptr) {
+ return false;
+ }
+ if (object->type != OB_CURVES) {
+ return false;
+ }
+ if (!BKE_id_is_editable(CTX_data_main(C), static_cast<const ID *>(object->data))) {
+ return false;
+ }
+ return true;
+}
+
+namespace set_selection_domain {
+
+static int curves_set_selection_domain_exec(bContext *C, wmOperator *op)
+{
+ const AttributeDomain domain = AttributeDomain(RNA_enum_get(op->ptr, "domain"));
+
+ for (Curves *curves_id : get_unique_editable_curves(*C)) {
+ if (curves_id->selection_domain == domain && (curves_id->flag & CV_SCULPT_SELECTION_ENABLED)) {
+ continue;
+ }
+
+ const AttributeDomain old_domain = AttributeDomain(curves_id->selection_domain);
+ curves_id->selection_domain = domain;
+ curves_id->flag |= CV_SCULPT_SELECTION_ENABLED;
+
+ CurveComponent component;
+ component.replace(curves_id, GeometryOwnershipType::Editable);
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
+
+ if (old_domain == ATTR_DOMAIN_POINT && domain == ATTR_DOMAIN_CURVE) {
+ VArray<float> curve_selection = curves.adapt_domain(
+ curves.selection_point_float(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ curve_selection.materialize(curves.selection_curve_float_for_write());
+ component.attribute_try_delete(".selection_point_float");
+ }
+ else if (old_domain == ATTR_DOMAIN_CURVE && domain == ATTR_DOMAIN_POINT) {
+ VArray<float> point_selection = curves.adapt_domain(
+ curves.selection_curve_float(), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT);
+ point_selection.materialize(curves.selection_point_float_for_write());
+ component.attribute_try_delete(".selection_curve_float");
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace set_selection_domain
+
+static void CURVES_OT_set_selection_domain(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ ot->name = "Set Select Mode";
+ ot->idname = __func__;
+ ot->description = "Change the mode used for selection masking in curves sculpt mode";
+
+ ot->exec = set_selection_domain::curves_set_selection_domain_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = prop = RNA_def_enum(
+ ot->srna, "domain", rna_enum_attribute_curves_domain_items, 0, "Domain", "");
+ RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+}
+
+namespace disable_selection {
+
+static int curves_disable_selection_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ for (Curves *curves_id : get_unique_editable_curves(*C)) {
+ curves_id->flag &= ~CV_SCULPT_SELECTION_ENABLED;
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace disable_selection
+
+static void CURVES_OT_disable_selection(wmOperatorType *ot)
+{
+ ot->name = "Disable Selection";
+ ot->idname = __func__;
+ ot->description = "Disable the drawing of influence of selection in sculpt mode";
+
+ ot->exec = disable_selection::curves_disable_selection_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+namespace select_all {
+
+static bool varray_contains_nonzero(const VArray<float> &data)
+{
+ bool contains_nonzero = false;
+ devirtualize_varray(data, [&](const auto array) {
+ for (const int i : data.index_range()) {
+ if (array[i] != 0.0f) {
+ contains_nonzero = true;
+ break;
+ }
+ }
+ });
+ return contains_nonzero;
+}
+
+static bool any_point_selected(const CurvesGeometry &curves)
+{
+ return varray_contains_nonzero(curves.selection_point_float());
+}
+
+static bool any_point_selected(const Span<Curves *> curves_ids)
+{
+ for (const Curves *curves_id : curves_ids) {
+ if (any_point_selected(CurvesGeometry::wrap(curves_id->geometry))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void invert_selection(MutableSpan<float> selection)
+{
+ threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
+ for (const int i : range) {
+ selection[i] = 1.0f - selection[i];
+ }
+ });
+}
+
+static int select_all_exec(bContext *C, wmOperator *op)
+{
+ int action = RNA_enum_get(op->ptr, "action");
+
+ VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C);
+
+ if (action == SEL_TOGGLE) {
+ action = any_point_selected(unique_curves) ? SEL_DESELECT : SEL_SELECT;
+ }
+
+ for (Curves *curves_id : unique_curves) {
+ if (action == SEL_SELECT) {
+ CurveComponent component;
+ component.replace(curves_id, GeometryOwnershipType::Editable);
+ component.attribute_try_delete(".selection_point_float");
+ component.attribute_try_delete(".selection_curve_float");
+ }
+ else {
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
+ MutableSpan<float> selection = curves_id->selection_domain == ATTR_DOMAIN_POINT ?
+ curves.selection_point_float_for_write() :
+ curves.selection_curve_float_for_write();
+ if (action == SEL_DESELECT) {
+ selection.fill(0.0f);
+ }
+ else if (action == SEL_INVERT) {
+ invert_selection(selection);
+ }
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace select_all
+
+static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot)
+{
+ ot->name = "(De)select All";
+ ot->idname = __func__;
+ ot->description = "(De)select all control points";
+
+ ot->exec = select_all::select_all_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ WM_operator_properties_select_all(ot);
+}
+
} // namespace blender::ed::curves
void ED_operatortypes_curves()
@@ -653,4 +899,7 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_convert_to_particle_system);
WM_operatortype_append(CURVES_OT_convert_from_particle_system);
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);
+ WM_operatortype_append(CURVES_OT_set_selection_domain);
+ WM_operatortype_append(SCULPT_CURVES_OT_select_all);
+ WM_operatortype_append(CURVES_OT_disable_selection);
}
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index d3bf28798c4..dfd8f8b1bb9 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -33,6 +33,8 @@ set(SRC
curves_sculpt_delete.cc
curves_sculpt_grow_shrink.cc
curves_sculpt_ops.cc
+ curves_sculpt_selection.cc
+ curves_sculpt_selection_paint.cc
curves_sculpt_snake_hook.cc
paint_canvas.cc
paint_cursor.c
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
index d7f4b71d2d0..fea0e862707 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
@@ -652,6 +652,8 @@ struct AddOperationExecutor {
Array<float3> new_normals_su = this->compute_normals_for_added_curves_su(added_points);
this->initialize_surface_attachment(added_points);
+ this->fill_new_selection();
+
if (interpolate_shape_) {
this->initialize_position_with_interpolation(
added_points, neighbors_per_curve, new_normals_su, new_lengths_cu);
@@ -662,6 +664,33 @@ struct AddOperationExecutor {
}
}
+ /**
+ * Select newly created points or curves in new curves if necessary.
+ */
+ void fill_new_selection()
+ {
+ switch (curves_id_->selection_domain) {
+ case ATTR_DOMAIN_CURVE: {
+ const VArray<float> selection = curves_->selection_curve_float();
+ if (selection.is_single() && selection.get_internal_single() >= 1.0f) {
+ return;
+ }
+ curves_->selection_curve_float_for_write().drop_front(tot_old_curves_).fill(1.0f);
+ break;
+ }
+ case ATTR_DOMAIN_POINT: {
+ const VArray<float> selection = curves_->selection_point_float();
+ if (selection.is_single() && selection.get_internal_single() >= 1.0f) {
+ return;
+ }
+ curves_->selection_point_float_for_write().drop_front(tot_old_points_).fill(1.0f);
+ break;
+ }
+ default:
+ BLI_assert_unreachable();
+ }
+ }
+
Array<NeighborsVector> find_curve_neighbors(const AddedPoints &added_points)
{
const int tot_added_curves = added_points.bary_coords.size();
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
index 1fcab2290e8..46332a66f60 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
@@ -96,6 +96,10 @@ struct CombOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> point_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const Object *surface_ob_ = nullptr;
const Mesh *surface_ = nullptr;
Span<MLoopTri> surface_looptris_;
@@ -142,6 +146,9 @@ struct CombOperationExecutor {
return;
}
+ point_factors_ = get_point_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
brush_pos_prev_re_ = self_->brush_pos_last_re_;
brush_pos_re_ = stroke_extension.mouse_position;
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
@@ -217,9 +224,9 @@ struct CombOperationExecutor {
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(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
+ threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<int> &local_changed_curves = r_changed_curves.local();
- for (const int curve_i : curves_range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
@@ -241,9 +248,10 @@ struct CombOperationExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_re, brush_radius_re);
/* Combine the falloff and brush strength. */
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i];
- /* Offset the old point position in screen space and transform it back into 3D space. */
+ /* Offset the old point position in screen space and transform it back into 3D space.
+ */
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
ED_view3d_win_to_3d(
@@ -304,9 +312,9 @@ struct CombOperationExecutor {
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
- threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
+ threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<int> &local_changed_curves = r_changed_curves.local();
- for (const int curve_i : curves_range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
@@ -326,7 +334,7 @@ struct CombOperationExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_cu, brush_radius_cu);
/* Combine the falloff and brush strength. */
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i];
/* Update the point position. */
positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
index 323e99df099..5a192346f39 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
@@ -70,6 +70,9 @@ struct DeleteOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const CurvesSculpt *curves_sculpt_ = nullptr;
const Brush *brush_ = nullptr;
float brush_radius_base_re_;
@@ -94,6 +97,9 @@ struct DeleteOperationExecutor {
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
+ selected_curve_indices_.clear();
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
curves_sculpt_ = scene_->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(scene_, brush_);
@@ -158,8 +164,8 @@ struct DeleteOperationExecutor {
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(curves_->curves_range(), 512, [&](IndexRange curve_range) {
- for (const int curve_i : curve_range) {
+ threading::parallel_for(curve_selection_.index_range(), 512, [&](const IndexRange range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
const float3 pos_cu = brush_transform_inv * positions_cu[points.first()];
@@ -219,8 +225,8 @@ struct DeleteOperationExecutor {
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
- threading::parallel_for(curves_->curves_range(), 512, [&](IndexRange curve_range) {
- for (const int curve_i : curve_range) {
+ threading::parallel_for(curve_selection_.index_range(), 512, [&](const IndexRange range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
index 25eb8041f4c..d87828e7437 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
@@ -280,6 +280,10 @@ struct CurvesEffectOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> curve_selection_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const Brush *brush_ = nullptr;
float brush_radius_base_re_;
float brush_radius_factor_;
@@ -318,6 +322,9 @@ struct CurvesEffectOperationExecutor {
return;
}
+ curve_selection_factors_ = get_curves_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
const CurvesSculpt &curves_sculpt = *scene_->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt.paint);
brush_strength_ = brush_strength_get(*scene_, *brush_, stroke_extension);
@@ -396,6 +403,8 @@ struct CurvesEffectOperationExecutor {
for (const int curve_i : curves_range) {
const IndexRange points = curves_->points_for_curve(curve_i);
+ const float curve_selection_factor = curve_selection_factors_[curve_i];
+
float max_move_distance_cu = 0.0f;
for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
for (const int segment_i : points.drop_back(1)) {
@@ -426,7 +435,7 @@ struct CurvesEffectOperationExecutor {
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 float weight = brush_strength_ * radius_falloff * curve_selection_factor;
const float3 closest_on_segment_cu = math::interpolate(
p1_cu, p2_cu, lambda_on_segment);
@@ -491,6 +500,9 @@ struct CurvesEffectOperationExecutor {
const IndexRange points = curves_->points_for_curve(curve_i);
float max_move_distance_cu = 0.0f;
+
+ const float curve_selection_factor = curve_selection_factors_[curve_i];
+
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float3 brush_pos_start_transformed_cu = brush_transform * brush_pos_start_cu;
const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu;
@@ -517,7 +529,7 @@ struct CurvesEffectOperationExecutor {
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 weight = brush_strength_ * radius_falloff * curve_selection_factor;
const float move_distance_cu = weight * brush_pos_diff_length_cu;
max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
index 842de234761..3635ad84c36 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -8,7 +8,10 @@
#include "paint_intern.h"
#include "BLI_math_vector.hh"
+#include "BLI_vector.hh"
+#include "BLI_virtual_array.hh"
+#include "BKE_attribute.h"
#include "BKE_curves.hh"
struct ARegion;
@@ -55,6 +58,8 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
const BrushStrokeMode brush_mode, const bContext &C);
+std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
+ const BrushStrokeMode brush_mode, const bContext &C);
struct CurvesBrush3D {
float3 position_cu;
@@ -74,4 +79,20 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
Vector<float4x4> get_symmetry_brush_transforms(eCurvesSymmetryType symmetry);
+/**
+ * Get the floating point selection on the curve domain, averaged from points if necessary.
+ */
+VArray<float> get_curves_selection(const Curves &curves_id);
+
+/**
+ * Get the floating point selection on the curve domain, copied from curves if necessary.
+ */
+VArray<float> get_point_selection(const Curves &curves_id);
+
+/**
+ * Find curves that have any point selected (a selection factor greater than zero),
+ * or curves that have their own selection factor greater than zero.
+ */
+IndexMask retrieve_selected_curves(const Curves &curves_id, Vector<int64_t> &r_indices);
+
} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 15d0f73592d..aa2ec54c73f 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -123,6 +123,8 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte
return new_add_operation(C, op.reports);
case CURVES_SCULPT_TOOL_GROW_SHRINK:
return new_grow_shrink_operation(mode, C);
+ case CURVES_SCULPT_TOOL_SELECTION_PAINT:
+ return new_selection_paint_operation(mode, C);
}
BLI_assert_unreachable();
return {};
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc
new file mode 100644
index 00000000000..9a14f0cfef0
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_index_mask_ops.hh"
+
+#include "BKE_curves.hh"
+
+#include "curves_sculpt_intern.hh"
+
+namespace blender::ed::sculpt_paint {
+
+static VArray<float> get_curves_selection(const CurvesGeometry &curves,
+ const AttributeDomain domain)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_CURVE:
+ return curves.selection_curve_float();
+ case ATTR_DOMAIN_POINT:
+ return curves.adapt_domain(
+ curves.selection_point_float(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+VArray<float> get_curves_selection(const Curves &curves_id)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return VArray<float>::ForSingle(1.0f, CurvesGeometry::wrap(curves_id.geometry).curves_num());
+ }
+ return get_curves_selection(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain));
+}
+
+static VArray<float> get_point_selection(const CurvesGeometry &curves,
+ const AttributeDomain domain)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_CURVE:
+ return curves.adapt_domain(
+ curves.selection_curve_float(), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT);
+ case ATTR_DOMAIN_POINT:
+ return curves.selection_point_float();
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+VArray<float> get_point_selection(const Curves &curves_id)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return VArray<float>::ForSingle(1.0f, CurvesGeometry::wrap(curves_id.geometry).points_num());
+ }
+ return get_point_selection(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain));
+}
+
+static IndexMask retrieve_selected_curves(const CurvesGeometry &curves,
+ const AttributeDomain domain,
+ Vector<int64_t> &r_indices)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_POINT: {
+ const VArray<float> selection = curves.selection_point_float();
+ if (selection.is_single()) {
+ return selection.get_internal_single() == 0.0f ? IndexMask(0) :
+ IndexMask(curves.curves_num());
+ }
+ return index_mask_ops::find_indices_based_on_predicate(
+ curves.curves_range(), 512, r_indices, [&](const int curve_i) {
+ for (const int i : curves.points_for_curve(curve_i)) {
+ if (selection[i] > 0.0f) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+ case ATTR_DOMAIN_CURVE: {
+ const VArray<float> selection = curves.selection_curve_float();
+ if (selection.is_single()) {
+ return selection.get_internal_single() == 0.0f ? IndexMask(0) :
+ IndexMask(curves.curves_num());
+ }
+ return index_mask_ops::find_indices_based_on_predicate(
+ curves.curves_range(), 2048, r_indices, [&](const int i) {
+ return selection[i] > 0.0f;
+ });
+ }
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+IndexMask retrieve_selected_curves(const Curves &curves_id, Vector<int64_t> &r_indices)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return CurvesGeometry::wrap(curves_id.geometry).curves_range();
+ }
+ return retrieve_selected_curves(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain),
+ r_indices);
+}
+
+} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
new file mode 100644
index 00000000000..250987466d5
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <algorithm>
+#include <numeric>
+
+#include "BLI_memory_utils.hh"
+#include "BLI_task.hh"
+
+#include "DNA_brush_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_context.h"
+#include "BKE_curves.hh"
+
+#include "DEG_depsgraph.h"
+
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "WM_api.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.
+ * wo: World space.
+ * re: 2D coordinates within the region.
+ */
+
+namespace blender::ed::sculpt_paint {
+
+using bke::CurvesGeometry;
+
+class SelectionPaintOperation : public CurvesSculptStrokeOperation {
+ private:
+ bool use_select_;
+ bool clear_selection_;
+
+ CurvesBrush3D brush_3d_;
+
+ friend struct SelectionPaintOperationExecutor;
+
+ public:
+ SelectionPaintOperation(const bool use_select, const bool clear_selection)
+ : use_select_(use_select), clear_selection_(clear_selection)
+ {
+ }
+ void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
+};
+
+struct SelectionPaintOperationExecutor {
+ SelectionPaintOperation *self_ = nullptr;
+ const bContext *C_ = nullptr;
+ const Depsgraph *depsgraph_ = nullptr;
+ const Scene *scene_ = nullptr;
+ ARegion *region_ = nullptr;
+ const View3D *v3d_ = nullptr;
+ const RegionView3D *rv3d_ = nullptr;
+
+ Object *object_ = nullptr;
+ Curves *curves_id_ = nullptr;
+ CurvesGeometry *curves_ = nullptr;
+
+ const Brush *brush_ = nullptr;
+ float brush_radius_base_re_;
+ float brush_radius_factor_;
+ float brush_strength_;
+
+ float selection_goal_;
+
+ float2 brush_pos_re_;
+
+ float4x4 curves_to_world_mat_;
+ float4x4 world_to_curves_mat_;
+
+ void execute(SelectionPaintOperation &self,
+ const bContext &C,
+ const StrokeExtension &stroke_extension)
+ {
+ 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<Curves *>(object_->data);
+ curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
+ curves_id_->flag |= CV_SCULPT_SELECTION_ENABLED;
+
+ brush_ = BKE_paint_brush_for_read(&scene_->toolsettings->curves_sculpt->paint);
+ brush_radius_base_re_ = BKE_brush_size_get(scene_, brush_);
+ brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_strength_ = BKE_brush_alpha_get(scene_, brush_);
+
+ brush_pos_re_ = stroke_extension.mouse_position;
+
+ if (self.clear_selection_) {
+ if (stroke_extension.is_first) {
+ if (curves_id_->selection_domain == ATTR_DOMAIN_POINT) {
+ curves_->selection_point_float_for_write().fill(0.0f);
+ }
+ else if (curves_id_->selection_domain == ATTR_DOMAIN_CURVE) {
+ curves_->selection_curve_float_for_write().fill(0.0f);
+ }
+ }
+ }
+
+ curves_to_world_mat_ = object_->obmat;
+ world_to_curves_mat_ = curves_to_world_mat_.inverted();
+
+ const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
+ brush_->falloff_shape);
+
+ selection_goal_ = self_->use_select_ ? 1.0f : 0.0f;
+
+ if (stroke_extension.is_first) {
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->initialize_spherical_brush_reference_point();
+ }
+ }
+
+ if (curves_id_->selection_domain == ATTR_DOMAIN_POINT) {
+ MutableSpan<float> selection = curves_->selection_point_float_for_write();
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
+ this->paint_point_selection_projected_with_symmetry(selection);
+ }
+ else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->paint_point_selection_spherical_with_symmetry(selection);
+ }
+ }
+ else {
+ MutableSpan<float> selection = curves_->selection_curve_float_for_write();
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
+ this->paint_curve_selection_projected_with_symmetry(selection);
+ }
+ else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->paint_curve_selection_spherical_with_symmetry(selection);
+ }
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because
+ * selection is handled as a generic attribute for now. */
+ 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(region_);
+ }
+
+ void paint_point_selection_projected_with_symmetry(MutableSpan<float> selection)
+ {
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_point_selection_projected(brush_transform, selection);
+ }
+ }
+
+ void paint_point_selection_projected(const float4x4 &brush_transform,
+ MutableSpan<float> selection)
+ {
+ const float4x4 brush_transform_inv = brush_transform.inverted();
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ Span<float3> positions_cu = curves_->positions();
+
+ 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(curves_->points_range(), 1024, [&](const IndexRange point_range) {
+ for (const int point_i : point_range) {
+ const float3 pos_cu = brush_transform_inv * positions_cu[point_i];
+
+ /* Find the position of the point in screen space. */
+ float2 pos_re;
+ ED_view3d_project_float_v2_m4(region_, pos_cu, pos_re, projection.values);
+
+ const float distance_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_);
+ if (distance_to_brush_sq_re > brush_radius_sq_re) {
+ /* Ignore the point because it's too far away. */
+ continue;
+ }
+
+ const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
+ /* A falloff that is based on how far away the point is from the stroke. */
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, distance_to_brush_re, brush_radius_re);
+ /* Combine the falloff and brush strength. */
+ const float weight = brush_strength_ * radius_falloff;
+
+ selection[point_i] = math::interpolate(selection[point_i], selection_goal_, weight);
+ }
+ });
+ }
+
+ void paint_point_selection_spherical_with_symmetry(MutableSpan<float> selection)
+ {
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ float3 brush_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_re_,
+ brush_wo);
+ const float3 brush_cu = world_to_curves_mat_ * brush_wo;
+
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_point_selection_spherical(selection, brush_transform * brush_cu);
+ }
+ }
+
+ void paint_point_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
+ {
+ Span<float3> positions_cu = curves_->positions();
+
+ const float brush_radius_cu = self_->brush_3d_.radius_cu;
+ const float brush_radius_sq_cu = pow2f(brush_radius_cu);
+
+ threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
+ for (const int i : point_range) {
+ const float3 pos_old_cu = positions_cu[i];
+
+ /* Compute distance to the brush. */
+ const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu);
+ if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
+ /* Ignore the point because it's too far away. */
+ continue;
+ }
+
+ const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
+
+ /* A falloff that is based on how far away the point is from the stroke. */
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, distance_to_brush_cu, brush_radius_cu);
+ /* Combine the falloff and brush strength. */
+ const float weight = brush_strength_ * radius_falloff;
+
+ selection[i] = math::interpolate(selection[i], selection_goal_, weight);
+ }
+ });
+ }
+
+ void paint_curve_selection_projected_with_symmetry(MutableSpan<float> selection)
+ {
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_curve_selection_projected(brush_transform, selection);
+ }
+ }
+
+ void paint_curve_selection_projected(const float4x4 &brush_transform,
+ MutableSpan<float> selection)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+ const float4x4 brush_transform_inv = brush_transform.inverted();
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ 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(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
+ for (const int curve_i : curves_range) {
+ const float max_weight = threading::parallel_reduce(
+ curves_->points_for_curve(curve_i).drop_back(1),
+ 1024,
+ 0.0f,
+ [&](const IndexRange segment_range, const float init) {
+ float max_weight = init;
+ for (const int segment_i : segment_range) {
+ const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i];
+ const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1];
+
+ float2 pos1_re;
+ float2 pos2_re;
+ ED_view3d_project_float_v2_m4(region_, pos1_cu, pos1_re, projection.values);
+ ED_view3d_project_float_v2_m4(region_, pos2_cu, pos2_re, projection.values);
+
+ const float distance_sq_re = dist_squared_to_line_segment_v2(
+ brush_pos_re_, pos1_re, pos2_re);
+ if (distance_sq_re > brush_radius_sq_re) {
+ continue;
+ }
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, std::sqrt(distance_sq_re), brush_radius_re);
+ const float weight = brush_strength_ * radius_falloff;
+ max_weight = std::max(max_weight, weight);
+ }
+ return max_weight;
+ },
+ [](float a, float b) { return std::max(a, b); });
+ selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
+ }
+ });
+ }
+
+ void paint_curve_selection_spherical_with_symmetry(MutableSpan<float> selection)
+ {
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ float3 brush_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_re_,
+ brush_wo);
+ const float3 brush_cu = world_to_curves_mat_ * brush_wo;
+
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_curve_selection_spherical(selection, brush_transform * brush_cu);
+ }
+ }
+
+ void paint_curve_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+
+ 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(), 1024, [&](const IndexRange curves_range) {
+ for (const int curve_i : curves_range) {
+ const float max_weight = threading::parallel_reduce(
+ curves_->points_for_curve(curve_i).drop_back(1),
+ 1024,
+ 0.0f,
+ [&](const IndexRange segment_range, const float init) {
+ float max_weight = init;
+ for (const int segment_i : segment_range) {
+ const float3 &pos1_cu = positions_cu[segment_i];
+ const float3 &pos2_cu = positions_cu[segment_i + 1];
+
+ const float distance_sq_cu = dist_squared_to_line_segment_v3(
+ brush_cu, pos1_cu, pos2_cu);
+ if (distance_sq_cu > brush_radius_sq_cu) {
+ continue;
+ }
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, std::sqrt(distance_sq_cu), brush_radius_cu);
+ const float weight = brush_strength_ * radius_falloff;
+ max_weight = std::max(max_weight, weight);
+ }
+ return max_weight;
+ },
+ [](float a, float b) { return std::max(a, b); });
+ selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
+ }
+ });
+ }
+
+ void initialize_spherical_brush_reference_point()
+ {
+ std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
+ *depsgraph_, *region_, *v3d_, *rv3d_, *object_, brush_pos_re_, brush_radius_base_re_);
+ if (brush_3d.has_value()) {
+ self_->brush_3d_ = *brush_3d;
+ }
+ }
+};
+
+void SelectionPaintOperation::on_stroke_extended(const bContext &C,
+ const StrokeExtension &stroke_extension)
+{
+ SelectionPaintOperationExecutor executor;
+ executor.execute(*this, C, stroke_extension);
+}
+
+std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
+ const BrushStrokeMode brush_mode, const bContext &C)
+{
+ Scene &scene = *CTX_data_scene(&C);
+ Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint);
+ const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
+ ((brush.flag & BRUSH_DIR_IN) != 0);
+ const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
+
+ return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
+}
+
+} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
index bcdeaaeabf2..f834d1e350f 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
@@ -90,6 +90,10 @@ struct SnakeHookOperatorExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> curve_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
float4x4 curves_to_world_mat_;
float4x4 world_to_curves_mat_;
@@ -130,6 +134,9 @@ struct SnakeHookOperatorExecutor {
return;
}
+ curve_factors_ = get_curves_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
brush_pos_prev_re_ = self.last_mouse_position_re_;
brush_pos_re_ = stroke_extension.mouse_position;
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
@@ -199,7 +206,7 @@ struct SnakeHookOperatorExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, std::sqrt(distance_to_brush_sq_re), brush_radius_re);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
@@ -265,7 +272,7 @@ struct SnakeHookOperatorExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_cu, brush_radius_cu);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu;