diff options
23 files changed, 1023 insertions, 26 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 180dc9a894d..adaebf7ce7b 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5594,7 +5594,14 @@ def km_sculpt_curves(params): {"properties": [("mode", 'NORMAL')]}), ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'INVERT')]}), + ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, + {"properties": [("mode", 'SMOOTH')]}), + ("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}), + ("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}), + ("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None), + ("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None), *_template_paint_radial_control("curves_sculpt"), + *_template_items_select_actions(params, "sculpt_curves.select_all"), ]) return keymap diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index d5b106635ab..d7325257084 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -859,7 +859,7 @@ def brush_shared_settings(layout, context, brush, popover=False): if mode == 'SCULPT_CURVES': size = True strength = True - direction = brush.curves_sculpt_tool == 'GROW_SHRINK' + direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'} ### Draw settings. ### ups = context.scene.tool_settings.unified_paint_settings diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 5c6ca13776e..1c526cc89e4 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2316,14 +2316,58 @@ class _defs_gpencil_weight: class _defs_curves_sculpt: - @staticmethod - def generate_from_brushes(context): - return generate_from_enum_ex( - context, - idname_prefix="builtin_brush.", - icon_prefix="ops.curves.sculpt_", - type=bpy.types.Brush, - attr="curves_sculpt_tool", + @ToolDef.from_fn + def selection_paint(): + return dict( + idname="builtin_brush.selection_paint", + label="Selection Paint", + icon="ops.generic.select_paint", + data_block="SELECTION_PAINT" + ) + + @ToolDef.from_fn + def comb(): + return dict( + idname="builtin_brush.comb", + label="Comb", + icon="ops.curves.sculpt_comb", + data_block='COMB' + ) + + @ToolDef.from_fn + def add(): + return dict( + idname="builtin_brush.add", + label="Add", + icon="ops.curves.sculpt_add", + data_block='ADD' + ) + + @ToolDef.from_fn + def delete(): + return dict( + idname="builtin_brush.delete", + label="Delete", + icon="ops.curves.sculpt_delete", + data_block='DELETE' + ) + + @ToolDef.from_fn + def snake_hook(): + return dict( + idname="builtin_brush.snake_hook", + label="Snake Hook", + icon="ops.curves.sculpt_snake_hook", + data_block='SNAKE_HOOK' + ) + + @ToolDef.from_fn + def grow_shrink(): + return dict( + idname="builtin_brush.grow_shrink", + label="Grow/Shrink", + icon="ops.curves.sculpt_grow_shrink", + data_block='GROW_SHRINK' ) @@ -3076,7 +3120,21 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): ), ], 'SCULPT_CURVES': [ - _defs_curves_sculpt.generate_from_brushes, + lambda context: ( + ( + _defs_curves_sculpt.selection_paint, + None, + ) + if context is None or context.preferences.experimental.use_new_curves_tools + else () + ), + _defs_curves_sculpt.comb, + _defs_curves_sculpt.add, + _defs_curves_sculpt.delete, + _defs_curves_sculpt.snake_hook, + _defs_curves_sculpt.grow_shrink, + None, + *_tools_annotate, ], } diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 1af70895be9..116dd280b5c 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -535,6 +535,11 @@ class _draw_tool_settings_context_mode: if brush.curves_sculpt_tool == 'DELETE': layout.prop(brush, "falloff_shape", expand=True) + if brush.curves_sculpt_tool == 'SELECTION_PAINT': + layout.prop(brush, "direction", expand=True, text="") + layout.prop(brush, "falloff_shape", expand=True) + layout.popover("VIEW3D_PT_tools_brush_falloff") + class VIEW3D_HT_header(Header): bl_space_type = 'VIEW_3D' @@ -687,6 +692,24 @@ class VIEW3D_HT_header(Header): if object_mode == 'PARTICLE_EDIT': row = layout.row() row.prop(tool_settings.particle_edit, "select_mode", text="", expand=True) + elif object_mode == 'SCULPT_CURVES' and obj.type == 'CURVES': + curves = obj.data + + row = layout.row(align=True) + + experimental = context.preferences.experimental + if experimental.use_new_curves_tools: + # Combine the "use selection" toggle with the "set domain" operators + # to allow turning selection off directly. + domain = curves.selection_domain + if domain == 'POINT': + row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_BEZCIRCLE') + else: + row.operator("curves.set_selection_domain", text="", icon='CURVE_BEZCIRCLE').domain = 'POINT' + if domain == 'CURVE': + row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_PATH') + else: + row.operator("curves.set_selection_domain", text="", icon='CURVE_PATH').domain = 'CURVE' # Grease Pencil if obj and obj.type == 'GPENCIL' and context.gpencil_data: @@ -939,6 +962,7 @@ class VIEW3D_MT_editor_menus(Menu): layout.menu("VIEW3D_MT_mask") layout.menu("VIEW3D_MT_face_sets") if mode_string == 'SCULPT_CURVES': + layout.menu("VIEW3D_MT_select_sculpt_curves") layout.menu("VIEW3D_MT_sculpt_curves") else: @@ -1976,6 +2000,17 @@ class VIEW3D_MT_select_edit_curves(Menu): pass +class VIEW3D_MT_select_sculpt_curves(Menu): + bl_label = "Select" + + def draw(self, _context): + layout = self.layout + + layout.operator("sculpt_curves.select_all", text="All").action = 'SELECT' + layout.operator("sculpt_curves.select_all", text="None").action = 'DESELECT' + layout.operator("sculpt_curves.select_all", text="Invert").action = 'INVERT' + + class VIEW3D_MT_angle_control(Menu): bl_label = "Angle Control" @@ -7703,6 +7738,7 @@ classes = ( VIEW3D_MT_select_paint_mask, VIEW3D_MT_select_paint_mask_vertex, VIEW3D_MT_select_edit_curves, + VIEW3D_MT_select_sculpt_curves, VIEW3D_MT_angle_control, VIEW3D_MT_mesh_add, VIEW3D_MT_curve_add, diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index cc056dab248..445f8d46f2d 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -281,6 +281,11 @@ class CurvesGeometry : public ::CurvesGeometry { Span<float2> surface_triangle_coords() const; MutableSpan<float2> surface_triangle_coords_for_write(); + VArray<float> selection_point_float() const; + MutableSpan<float> selection_point_float_for_write(); + VArray<float> selection_curve_float() const; + MutableSpan<float> selection_curve_float_for_write(); + /** * Calculate the largest and smallest position values, only including control points * (rather than evaluated points). The existing values of `min` and `max` are taken into account. @@ -406,6 +411,11 @@ class CurvesGeometry : public ::CurvesGeometry { */ GVArray adapt_domain(const GVArray &varray, AttributeDomain from, AttributeDomain to) const; + template<typename T> + VArray<T> adapt_domain(const VArray<T> &varray, AttributeDomain from, AttributeDomain to) const + { + return this->adapt_domain(GVArray(varray), from, to).typed<T>(); + } }; namespace curves { diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 0fd58a52f81..07e50eea88c 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -37,6 +37,8 @@ static const std::string ATTR_NURBS_WEIGHT = "nurbs_weight"; static const std::string ATTR_NURBS_KNOTS_MODE = "knots_mode"; static const std::string ATTR_SURFACE_TRIANGLE_INDEX = "surface_triangle_index"; static const std::string ATTR_SURFACE_TRIANGLE_COORDINATE = "surface_triangle_coordinate"; +static const std::string ATTR_SELECTION_POINT_FLOAT = ".selection_point_float"; +static const std::string ATTR_SELECTION_CURVE_FLOAT = ".selection_curve_float"; /* -------------------------------------------------------------------- */ /** \name Constructors/Destructor @@ -438,6 +440,26 @@ MutableSpan<float2> CurvesGeometry::surface_triangle_coords_for_write() return get_mutable_attribute<float2>(*this, ATTR_DOMAIN_CURVE, ATTR_SURFACE_TRIANGLE_COORDINATE); } +VArray<float> CurvesGeometry::selection_point_float() const +{ + return get_varray_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_SELECTION_POINT_FLOAT, 1.0f); +} + +MutableSpan<float> CurvesGeometry::selection_point_float_for_write() +{ + return get_mutable_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_SELECTION_POINT_FLOAT, 1.0f); +} + +VArray<float> CurvesGeometry::selection_curve_float() const +{ + return get_varray_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_SELECTION_CURVE_FLOAT, 1.0f); +} + +MutableSpan<float> CurvesGeometry::selection_curve_float_for_write() +{ + return get_mutable_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_SELECTION_CURVE_FLOAT, 1.0f); +} + /** \} */ /* -------------------------------------------------------------------- */ 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; diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index f409d1c0442..f2cd49b6dea 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -468,6 +468,7 @@ typedef enum eBrushCurvesSculptTool { CURVES_SCULPT_TOOL_SNAKE_HOOK = 2, CURVES_SCULPT_TOOL_ADD = 3, CURVES_SCULPT_TOOL_GROW_SHRINK = 4, + CURVES_SCULPT_TOOL_SELECTION_PAINT = 5, } eBrushCurvesSculptTool; /** When #BRUSH_ACCUMULATE is used */ diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h index 2388f04cc39..45b2309c5bf 100644 --- a/source/blender/makesdna/DNA_curves_types.h +++ b/source/blender/makesdna/DNA_curves_types.h @@ -141,7 +141,12 @@ typedef struct Curves { * symmetrical geometry. */ char symmetry; - char _pad2[5]; + /** + * #AttributeDomain. The active selection mode domain. At most one selection mode can be active + * at a time. + */ + char selection_domain; + char _pad[4]; /** * Used as base mesh when curves represent e.g. hair or fur. This surface is used in edit modes. @@ -159,6 +164,7 @@ typedef struct Curves { /** #Curves.flag */ enum { HA_DS_EXPAND = (1 << 0), + CV_SCULPT_SELECTION_ENABLED = (1 << 1), }; /** #Curves.symmetry */ diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 8c42bbe2ee6..37af598729b 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -209,6 +209,7 @@ DEF_ENUM(rna_enum_attribute_type_items) DEF_ENUM(rna_enum_color_attribute_type_items) DEF_ENUM(rna_enum_attribute_type_with_auto_items) DEF_ENUM(rna_enum_attribute_domain_items) +DEF_ENUM(rna_enum_attribute_curves_domain_items) DEF_ENUM(rna_enum_color_attribute_domain_items) DEF_ENUM(rna_enum_attribute_domain_without_corner_items) DEF_ENUM(rna_enum_attribute_domain_with_auto_items) diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index 0cea693cd2a..76e1e712cad 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -106,6 +106,11 @@ const EnumPropertyItem rna_enum_color_attribute_domain_items[] = { {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", ""}, {0, NULL, 0, NULL, NULL}}; +const EnumPropertyItem rna_enum_attribute_curves_domain_items[] = { + {ATTR_DOMAIN_POINT, "POINT", 0, "Control Point", ""}, + {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", ""}, + {0, NULL, 0, NULL, NULL}}; + #ifdef RNA_RUNTIME # include "BLI_math.h" diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 2735a00ee75..46b009191ca 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -249,6 +249,7 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = { {CURVES_SCULPT_TOOL_SNAKE_HOOK, "SNAKE_HOOK", ICON_NONE, "Curves Snake Hook", ""}, {CURVES_SCULPT_TOOL_ADD, "ADD", ICON_NONE, "Add Curves", ""}, {CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_NONE, "Grow / Shrink Curves", ""}, + {CURVES_SCULPT_TOOL_SELECTION_PAINT, "SELECTION_PAINT", ICON_NONE, "Paint Selection", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -885,6 +886,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C, case PAINT_MODE_SCULPT_CURVES: switch (me->curves_sculpt_tool) { case CURVES_SCULPT_TOOL_GROW_SHRINK: + case CURVES_SCULPT_TOOL_SELECTION_PAINT: return prop_direction_items; default: return DummyRNA_DEFAULT_items; diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c index 7cf34db4cf4..caefd2f45ff 100644 --- a/source/blender/makesrna/intern/rna_curves.c +++ b/source/blender/makesrna/intern/rna_curves.c @@ -308,6 +308,18 @@ static void rna_def_curves(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Z", "Enable symmetry in the Z axis"); RNA_def_property_update(prop, 0, "rna_Curves_update_draw"); + prop = RNA_def_property(srna, "selection_domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_curves_domain_items); + RNA_def_property_ui_text(prop, "Selection Domain", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Curves_update_data"); + + prop = RNA_def_property(srna, "use_sculpt_selection", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", CV_SCULPT_SELECTION_ENABLED); + RNA_def_property_ui_text(prop, "Use Sculpt Selection", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, 0, "rna_Curves_update_draw"); + /* attributes */ rna_def_attributes_common(srna); |