diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 290f3768f46..fe0be500cc1 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4693,6 +4693,12 @@ def km_grease_pencil_sculpt_mode(params): {"properties": [("scalar", 0.9)]}), ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, {"properties": [("scalar", 1.0 / 0.9)]}), + # Invoke sculpt operator + ("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), + ("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', + "ctrl": True}, {"properties": [("mode", 'INVERT')]}), + ("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', + "shift": True}, {"properties": [("mode", 'SMOOTH')]}), *_template_paint_radial_control("gpencil_sculpt_paint"), ]) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index ae31c8c6a9d..95da89f0106 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -474,9 +474,14 @@ class _draw_tool_settings_context_mode: ) # direction - if not capabilities.has_direction: + if brush.gpencil_sculpt_tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}: layout.row().prop(brush, "direction", expand=True, text="") + # Brush falloff + layout.popover("VIEW3D_PT_tools_brush_falloff") + + # Active layer only switch + layout.prop(brush.gpencil_settings, "use_active_layer_only") return True @staticmethod @@ -894,11 +899,11 @@ class VIEW3D_HT_header(Header): row = layout.row(align=True) row.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE') - if object_mode in {'PAINT_GPENCIL', 'EDIT', 'WEIGHT_GPENCIL'}: + if object_mode in {'PAINT_GPENCIL', 'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}: row = layout.row(align=True) row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="") - if object_mode in {'EDIT', 'WEIGHT_GPENCIL'}: + if object_mode in {'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}: sub = row.row(align=True) sub.enabled = tool_settings.use_grease_pencil_multi_frame_editing sub.popover( @@ -975,6 +980,35 @@ class VIEW3D_HT_header(Header): text="Multiframe", ) + # Grease Pencil + if obj and obj.type == 'GREASEPENCIL': + if object_mode == 'PAINT_GREASE_PENCIL': + row = layout.row() + sub = row.row(align=True) + sub.prop(tool_settings, "use_gpencil_draw_onback", text="", icon='MOD_OPACITY') + sub.separator(factor=0.4) + sub.prop(tool_settings, "use_gpencil_automerge_strokes", text="") + sub.separator(factor=0.4) + sub.prop(tool_settings, "use_gpencil_weight_data_add", text="", icon='WPAINT_HLT') + sub.separator(factor=0.4) + sub.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE') + + # Select mode for Editing + if object_mode == 'EDIT': + row = layout.row(align=True) + row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='POINT') + row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='STROKE') + + subrow = row.row(align=True) + subrow.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='SEGMENT') + + # Select mode for Sculpt + if object_mode == 'SCULPT_GPENCIL': + row = layout.row(align=True) + row.prop(tool_settings, "use_gpencil_select_mask_point", text="") + row.prop(tool_settings, "use_gpencil_select_mask_stroke", text="") + row.prop(tool_settings, "use_gpencil_select_mask_segment", text="") + overlay = view.overlay VIEW3D_MT_editor_menus.draw_collapsible(context, layout) @@ -1188,7 +1222,12 @@ class VIEW3D_MT_editor_menus(Menu): mode_string = context.mode edit_object = context.edit_object gp_edit = obj and obj.type == 'GPENCIL' and obj.mode in { - 'EDIT_GPENCIL', 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', + 'EDIT_GPENCIL', + 'PAINT_GPENCIL', + 'SCULPT_GPENCIL', + 'SCULPT_GREASE_PENCIL', + 'WEIGHT_GPENCIL', + 'VERTEX_GPENCIL', } tool_settings = context.tool_settings @@ -1196,18 +1235,16 @@ class VIEW3D_MT_editor_menus(Menu): # Select Menu if gp_edit: - if mode_string not in {'PAINT_GPENCIL', 'WEIGHT_GPENCIL'}: - if ( - mode_string == 'SCULPT_GPENCIL' and - (tool_settings.use_gpencil_select_mask_point or - tool_settings.use_gpencil_select_mask_stroke or - tool_settings.use_gpencil_select_mask_segment) - ): - layout.menu("VIEW3D_MT_select_edit_gpencil") - elif mode_string == 'EDIT_GPENCIL': - layout.menu("VIEW3D_MT_select_edit_gpencil") - elif mode_string == 'VERTEX_GPENCIL': - layout.menu("VIEW3D_MT_select_edit_gpencil") + use_gpencil_masking = (tool_settings.use_gpencil_select_mask_point or + tool_settings.use_gpencil_select_mask_stroke or + tool_settings.use_gpencil_select_mask_segment) + if mode_string in { + 'EDIT_GPENCIL', + 'VERTEX_GPENCIL'} or ( + mode_string in { + 'SCULPT_GPENCIL', + 'SCULPT_GREASE_PENCIL'} and use_gpencil_masking): + layout.menu("VIEW3D_MT_select_edit_gpencil") elif mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}: mesh = obj.data if mesh.use_paint_mask: diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index 73bc3aa6c19..96d914b7b89 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -2193,18 +2193,20 @@ static void GREASE_PENCIL_OT_separate(wmOperatorType *ot) * \{ */ /* Global clipboard for Grease Pencil curves. */ -struct Clipboard { +static struct Clipboard { bke::CurvesGeometry curves; /* We store the material uid's of the copied curves, so we can match those when pasting the * clipboard into another object. */ Vector> materials; int materials_in_source_num; -}; +} *grease_pencil_clipboard = nullptr; -static Clipboard &get_grease_pencil_clipboard() +static Clipboard &ensure_grease_pencil_clipboard() { - static Clipboard clipboard; - return clipboard; + if (grease_pencil_clipboard == nullptr) { + grease_pencil_clipboard = MEM_new(__func__); + } + return *grease_pencil_clipboard; } static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op) @@ -2217,8 +2219,6 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op) GreasePencil &grease_pencil = *static_cast(object->data); const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back"); - Clipboard &clipboard = get_grease_pencil_clipboard(); - /* Get active layer in the target object. */ if (!grease_pencil.has_active_layer()) { BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer"); @@ -2251,60 +2251,8 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op) selection_in_target.finish(); }); - /* Get a list of all materials in the scene. */ - Map scene_materials; - LISTBASE_FOREACH (Material *, material, &bmain->materials) { - scene_materials.add(material->id.session_uid, material); - } + clipboard_paste_strokes(*bmain, *object, *target_drawing, paste_on_back); - /* Map the materials used in the clipboard curves to the materials in the target object. */ - Array clipboard_material_remap(clipboard.materials_in_source_num, 0); - for (const int i : clipboard.materials.index_range()) { - /* Check if the material name exists in the scene. */ - int target_index; - uint material_id = clipboard.materials[i].first; - Material *material = scene_materials.lookup_default(material_id, nullptr); - if (!material) { - /* Material is removed, so create a new material. */ - BKE_grease_pencil_object_material_new(bmain, object, nullptr, &target_index); - clipboard_material_remap[clipboard.materials[i].second] = target_index; - continue; - } - - /* Find or add the material to the target object. */ - target_index = BKE_object_material_ensure(bmain, object, material); - clipboard_material_remap[clipboard.materials[i].second] = target_index; - } - - /* Get the index range of the pasted curves in the target layer. */ - IndexRange pasted_curves_range = paste_on_back ? - IndexRange(0, clipboard.curves.curves_num()) : - IndexRange(target_drawing->strokes().curves_num(), - clipboard.curves.curves_num()); - - /* Append the geometry from the clipboard to the target layer. */ - Curves *clipboard_curves = curves_new_nomain(clipboard.curves); - Curves *target_curves = curves_new_nomain(std::move(target_drawing->strokes_for_write())); - Array geometry_sets = { - bke::GeometrySet::from_curves(paste_on_back ? clipboard_curves : target_curves), - bke::GeometrySet::from_curves(paste_on_back ? target_curves : clipboard_curves)}; - bke::GeometrySet joined_curves = geometry::join_geometries(geometry_sets, {}); - target_drawing->strokes_for_write() = std::move( - joined_curves.get_curves_for_write()->geometry.wrap()); - - /* Remap the material indices of the pasted curves to the target object material indices. */ - bke::MutableAttributeAccessor attributes = - target_drawing->strokes_for_write().attributes_for_write(); - bke::SpanAttributeWriter material_indices = attributes.lookup_or_add_for_write_span( - "material_index", bke::AttrDomain::Curve); - if (material_indices) { - for (const int i : pasted_curves_range) { - material_indices.span[i] = clipboard_material_remap[material_indices.span[i]]; - } - material_indices.finish(); - } - - target_drawing->tag_topology_changed(); DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); @@ -2319,7 +2267,7 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op) const bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get( scene->toolsettings); - Clipboard &clipboard = get_grease_pencil_clipboard(); + Clipboard &clipboard = ensure_grease_pencil_clipboard(); bool anything_copied = false; int num_copied = 0; @@ -2400,8 +2348,7 @@ static bool grease_pencil_paste_strokes_poll(bContext *C) } /* Check for curves in the Grease Pencil clipboard. */ - Clipboard &clipboard = get_grease_pencil_clipboard(); - return (clipboard.curves.curves_num() > 0); + return (grease_pencil_clipboard && grease_pencil_clipboard->curves.curves_num() > 0); } static void GREASE_PENCIL_OT_paste(wmOperatorType *ot) @@ -2439,6 +2386,94 @@ static void GREASE_PENCIL_OT_copy(wmOperatorType *ot) /** \} */ +void clipboard_free() +{ + if (grease_pencil_clipboard) { + MEM_delete(grease_pencil_clipboard); + grease_pencil_clipboard = nullptr; + } +} + +const bke::CurvesGeometry &clipboard_curves() +{ + using namespace blender::ed::greasepencil; + return ensure_grease_pencil_clipboard().curves; +} + +static Array clipboard_materials_remap(Main &bmain, Object &object) +{ + using namespace blender::ed::greasepencil; + + /* Get a list of all materials in the scene. */ + Map scene_materials; + LISTBASE_FOREACH (Material *, material, &bmain.materials) { + scene_materials.add(material->id.session_uid, material); + } + + Clipboard &clipboard = ensure_grease_pencil_clipboard(); + Array clipboard_material_remap(clipboard.materials_in_source_num, 0); + for (const int i : clipboard.materials.index_range()) { + /* Check if the material name exists in the scene. */ + int target_index; + uint material_id = clipboard.materials[i].first; + Material *material = scene_materials.lookup_default(material_id, nullptr); + if (!material) { + /* Material is removed, so create a new material. */ + BKE_grease_pencil_object_material_new(&bmain, &object, nullptr, &target_index); + clipboard_material_remap[clipboard.materials[i].second] = target_index; + continue; + } + + /* Find or add the material to the target object. */ + target_index = BKE_object_material_ensure(&bmain, &object, material); + clipboard_material_remap[clipboard.materials[i].second] = target_index; + } + + return clipboard_material_remap; +} + +IndexRange clipboard_paste_strokes(Main &bmain, + Object &object, + bke::greasepencil::Drawing &drawing, + const bool paste_back) +{ + const bke::CurvesGeometry &clipboard_curves = ed::greasepencil::clipboard_curves(); + + /* Get a list of all materials in the scene. */ + const Array clipboard_material_remap = ed::greasepencil::clipboard_materials_remap(bmain, + object); + + /* Get the index range of the pasted curves in the target layer. */ + const IndexRange pasted_curves_range = paste_back ? + IndexRange(0, clipboard_curves.curves_num()) : + IndexRange(drawing.strokes().curves_num(), + clipboard_curves.curves_num()); + + /* Append the geometry from the clipboard to the target layer. */ + Curves *clipboard_id = bke::curves_new_nomain(clipboard_curves); + Curves *target_id = curves_new_nomain(std::move(drawing.strokes_for_write())); + const Array geometry_sets = { + bke::GeometrySet::from_curves(paste_back ? clipboard_id : target_id), + bke::GeometrySet::from_curves(paste_back ? target_id : clipboard_id)}; + bke::GeometrySet joined_curves = geometry::join_geometries(geometry_sets, {}); + drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap()); + + /* Remap the material indices of the pasted curves to the target object material indices. */ + bke::MutableAttributeAccessor attributes = drawing.strokes_for_write().attributes_for_write(); + bke::SpanAttributeWriter material_indices = attributes.lookup_or_add_for_write_span( + "material_index", bke::AttrDomain::Curve); + if (material_indices) { + for (const int i : pasted_curves_range) { + material_indices.span[i] = clipboard_material_remap[material_indices.span[i]]; + } + material_indices.finish(); + } + + drawing.tag_topology_changed(); + + return pasted_curves_range; +} + } // namespace blender::ed::greasepencil void ED_operatortypes_grease_pencil_edit() diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc index e6fdd6ecaac..3a1fdb617b1 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -563,6 +563,47 @@ Vector retrieve_editable_drawings_from_layer( return editable_drawings; } +Vector retrieve_editable_drawings_from_layer_with_falloff( + const Scene &scene, + GreasePencil &grease_pencil, + const blender::bke::greasepencil::Layer &layer) +{ + using namespace blender::bke::greasepencil; + const int current_frame = scene.r.cfra; + const ToolSettings *toolsettings = scene.toolsettings; + const bool use_multi_frame_editing = (toolsettings->gpencil_flags & + GP_USE_MULTI_FRAME_EDITING) != 0; + const bool use_multi_frame_falloff = use_multi_frame_editing && + (toolsettings->gp_sculpt.flag & + GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + const int layer_index = *grease_pencil.get_layer_index(layer); + int center_frame; + std::pair minmax_frame; + if (use_multi_frame_falloff) { + BKE_curvemapping_init(toolsettings->gp_sculpt.cur_falloff); + minmax_frame = get_minmax_selected_frame_numbers(grease_pencil, current_frame); + center_frame = math::clamp(current_frame, minmax_frame.first, minmax_frame.second); + } + + Vector editable_drawings; + const Array frame_numbers = get_editable_frames_for_layer( + layer, current_frame, use_multi_frame_editing); + for (const int frame_number : frame_numbers) { + if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) { + const float falloff = use_multi_frame_falloff ? + get_multi_frame_falloff(frame_number, + center_frame, + minmax_frame.first, + minmax_frame.second, + toolsettings->gp_sculpt.cur_falloff) : + 1.0f; + editable_drawings.append({*drawing, layer_index, frame_number, falloff}); + } + } + + return editable_drawings; +} + Vector retrieve_visible_drawings(const Scene &scene, const GreasePencil &grease_pencil, const bool do_onion_skinning) diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 8c887bc00f4..4efbd4b298b 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -34,7 +34,8 @@ struct ViewContext; namespace blender { namespace bke { enum class AttrDomain : int8_t; -} +class CurvesGeometry; +} // namespace bke } // namespace blender enum { @@ -62,6 +63,7 @@ void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf); void GREASE_PENCIL_OT_stroke_cutter(wmOperatorType *ot); void ED_undosys_type_grease_pencil(UndoType *undo_type); + /** * Get the selection mode for Grease Pencil selection operators: point, stroke, segment. */ @@ -256,6 +258,8 @@ Array> retrieve_editable_drawings_grouped_per_frame( const Scene &scene, GreasePencil &grease_pencil); Vector retrieve_editable_drawings_from_layer( const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer); +Vector retrieve_editable_drawings_from_layer_with_falloff( + const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer); Vector retrieve_visible_drawings(const Scene &scene, const GreasePencil &grease_pencil, bool do_onion_skinning); @@ -381,4 +385,16 @@ void normalize_vertex_weights(MDeformVert &dvert, Span vertex_group_is_locked, Span vertex_group_is_bone_deformed); +void clipboard_free(); +const bke::CurvesGeometry &clipboard_curves(); +/** + * Paste curves from the clipboard into the drawing. + * \param paste_back Render behind existing curves by inserting curves at the front. + * \return Index range of the new curves in the drawing after pasting. + */ +IndexRange clipboard_paste_strokes(Main &bmain, + Object &object, + bke::greasepencil::Drawing &drawing, + bool paste_back); + } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 178c30e19f9..2ad34c5097f 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -43,6 +43,16 @@ set(SRC grease_pencil_draw_ops.cc grease_pencil_erase.cc grease_pencil_paint.cc + grease_pencil_sculpt_clone.cc + grease_pencil_sculpt_common.cc + grease_pencil_sculpt_grab.cc + grease_pencil_sculpt_pinch.cc + grease_pencil_sculpt_push.cc + grease_pencil_sculpt_randomize.cc + grease_pencil_sculpt_smooth.cc + grease_pencil_sculpt_strength.cc + grease_pencil_sculpt_thickness.cc + grease_pencil_sculpt_twist.cc grease_pencil_tint.cc grease_pencil_weight_average.cc grease_pencil_weight_blur.cc diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc index 60a0bb553f6..0e9216df944 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -27,6 +27,7 @@ #include "WM_message.hh" #include "WM_toolsystem.hh" +#include "curves_sculpt_intern.hh" #include "grease_pencil_intern.hh" #include "paint_intern.hh" @@ -208,30 +209,31 @@ static bool grease_pencil_sculpt_paint_poll(bContext *C) return true; } -static GreasePencilStrokeOperation *grease_pencil_sculpt_paint_operation(bContext &C) +static GreasePencilStrokeOperation *grease_pencil_sculpt_paint_operation( + bContext &C, const BrushStrokeMode stroke_mode) { const Scene &scene = *CTX_data_scene(&C); const GpSculptPaint &gp_sculptpaint = *scene.toolsettings->gp_sculptpaint; const Brush &brush = *BKE_paint_brush_for_read(&gp_sculptpaint.paint); switch (eBrushGPSculptTool(brush.gpencil_sculpt_tool)) { case GPSCULPT_TOOL_SMOOTH: - return nullptr; + return greasepencil::new_smooth_operation(stroke_mode).release(); case GPSCULPT_TOOL_THICKNESS: - return nullptr; + return greasepencil::new_thickness_operation(stroke_mode).release(); case GPSCULPT_TOOL_STRENGTH: - return nullptr; + return greasepencil::new_strength_operation(stroke_mode).release(); case GPSCULPT_TOOL_GRAB: - return nullptr; + return greasepencil::new_grab_operation(stroke_mode).release(); case GPSCULPT_TOOL_PUSH: - return nullptr; + return greasepencil::new_push_operation(stroke_mode).release(); case GPSCULPT_TOOL_TWIST: - return nullptr; + return greasepencil::new_twist_operation(stroke_mode).release(); case GPSCULPT_TOOL_PINCH: - return nullptr; + return greasepencil::new_pinch_operation(stroke_mode).release(); case GPSCULPT_TOOL_RANDOMIZE: - return nullptr; + return greasepencil::new_randomize_operation(stroke_mode).release(); case GPSCULPT_TOOL_CLONE: - return nullptr; + return greasepencil::new_clone_operation(stroke_mode).release(); } return nullptr; } @@ -240,7 +242,8 @@ static bool grease_pencil_sculpt_paint_test_start(bContext *C, wmOperator *op, const float mouse[2]) { - GreasePencilStrokeOperation *operation = grease_pencil_sculpt_paint_operation(*C); + const BrushStrokeMode stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode")); + GreasePencilStrokeOperation *operation = grease_pencil_sculpt_paint_operation(*C, stroke_mode); if (operation) { stroke_start(*C, *op, float2(mouse), *operation); return true; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh index d6bfe13f02e..d114adf447a 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh +++ b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh @@ -4,9 +4,18 @@ #pragma once +#include "DNA_scene_types.h" +#include "ED_grease_pencil.hh" + #include "paint_intern.hh" -#include "BLI_math_vector.hh" +namespace blender::bke::greasepencil { +class Drawing; +class Layer; +} // namespace blender::bke::greasepencil +namespace blender::bke::crazyspace { +struct GeometryDeformation; +} namespace blender::ed::sculpt_paint { @@ -25,6 +34,94 @@ class GreasePencilStrokeOperation { namespace greasepencil { +/* Get list of drawings the tool should be operating on. */ +Vector get_drawings_for_sculpt(const bContext &C); + +/* Make sure the brush has all necessary grease pencil settings. */ +void init_brush(Brush &brush); + +/* Index mask of all points within the brush radius. */ +IndexMask brush_influence_mask(const Scene &scene, + const Brush &brush, + const float2 &mouse_position, + float pressure, + float multi_frame_falloff, + const IndexMask &selection, + Span view_positions, + Vector &influences, + IndexMaskMemory &memory); + +/* Influence value at point co for the brush. */ +float brush_influence(const Scene &scene, + const Brush &brush, + const float2 &co, + const InputSample &sample, + float multi_frame_falloff); + +/* True if influence of the brush should be inverted. */ +bool is_brush_inverted(const Brush &brush, BrushStrokeMode stroke_mode); + +/* Common parameters for stroke callbacks that can be passed to utility functions. */ +struct GreasePencilStrokeParams { + const ToolSettings &toolsettings; + const ARegion ®ion; + Object &ob_orig; + Object &ob_eval; + const bke::greasepencil::Layer &layer; + int layer_index; + int frame_number; + float multi_frame_falloff; + ed::greasepencil::DrawingPlacement placement; + bke::greasepencil::Drawing &drawing; + + /* Note: accessing region in worker threads will return null, + * this has to be done on the main thread and passed explicitly. */ + static GreasePencilStrokeParams from_context(const Scene &scene, + const Depsgraph &depsgraph, + const ARegion ®ion, + const View3D &view3d, + Object &object, + int layer_index, + int frame_number, + float multi_frame_falloff, + bke::greasepencil::Drawing &drawing); +}; + +/* Point index mask for a drawing based on selection tool settings. */ +IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, IndexMaskMemory &memory); + +bke::crazyspace::GeometryDeformation get_drawing_deformation( + const GreasePencilStrokeParams ¶ms); + +/* Project points from layer space into 2D view space. */ +Array calculate_view_positions(const GreasePencilStrokeParams ¶ms, + const IndexMask &selection); + +/* Stroke operation base class that performs various common initializations. */ +class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation { + public: + using MutableDrawingInfo = blender::ed::greasepencil::MutableDrawingInfo; + using DrawingPlacement = ed::greasepencil::DrawingPlacement; + + BrushStrokeMode stroke_mode; + + /* Previous mouse position for computing the direction. */ + float2 prev_mouse_position; + + GreasePencilStrokeOperationCommon(const BrushStrokeMode stroke_mode) : stroke_mode(stroke_mode) + { + } + + bool is_inverted(const Brush &brush) const; + float2 mouse_delta(const InputSample &input_sample) const; + + void init_stroke(const bContext &C, const InputSample &start_sample); + void stroke_extended(const InputSample &extension_sample); + + void foreach_editable_drawing( + const bContext &C, FunctionRef fn) const; +}; + std::unique_ptr new_paint_operation(); std::unique_ptr new_erase_operation(); std::unique_ptr new_tint_operation(); @@ -33,6 +130,15 @@ std::unique_ptr new_weight_paint_draw_operation( std::unique_ptr new_weight_paint_blur_operation(); std::unique_ptr new_weight_paint_average_operation(); std::unique_ptr new_weight_paint_smear_operation(); +std::unique_ptr new_smooth_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_thickness_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_strength_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_randomize_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_grab_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_push_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_pinch_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_twist_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_clone_operation(BrushStrokeMode stroke_mode); } // namespace greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc index bc4eb19693e..c8f7828fd19 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -331,13 +331,14 @@ struct PaintOperationExecutor { * stable) fit. */ Array coords_pre_blur(smooth_window.size()); const int pre_blur_iterations = 3; - geometry::gaussian_blur_1D(coords_to_smooth, - pre_blur_iterations, - settings_->active_smooth, - true, - true, - false, - coords_pre_blur.as_mutable_span()); + geometry::gaussian_blur_1D( + coords_to_smooth, + pre_blur_iterations, + VArray::ForSingle(settings_->active_smooth, smooth_window.size()), + true, + true, + false, + coords_pre_blur.as_mutable_span()); /* Curve fitting. The output will be a set of handles (float2 triplets) in a flat array. */ const float max_error_threshold_px = 5.0f; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc new file mode 100644 index 00000000000..e482d2dd76b --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc @@ -0,0 +1,90 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_curves.hh" +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class CloneOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +static float2 arithmetic_mean(Span values) +{ + return std::accumulate(values.begin(), values.end(), float2(0)) / values.size(); +} + +void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + Main &bmain = *CTX_data_main(&C); + Object &object = *CTX_data_active_object(&C); + + this->init_stroke(C, start_sample); + + /* Note: Only one copy is created at the beginning of each stroke. + * GPv2 supposedly has 2 modes: + * - Stamp: Clone on stroke start and then transform (the transform part doesn't work) + * - Continuous: Create multiple copies during the stroke (disabled) + * + * Here we only have the GPv2 behavior that actually works for now. */ + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + const IndexRange pasted_curves = ed::greasepencil::clipboard_paste_strokes( + bmain, object, params.drawing, false); + if (pasted_curves.is_empty()) { + return false; + } + + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + const OffsetIndices pasted_points_by_curve = curves.points_by_curve().slice( + pasted_curves); + const IndexRange pasted_points = IndexRange::from_begin_size( + pasted_points_by_curve[0].start(), + pasted_points_by_curve.total_size() - pasted_points_by_curve[0].start()); + + Array view_positions = calculate_view_positions(params, pasted_points); + const float2 center = arithmetic_mean(view_positions.as_mutable_span().slice(pasted_points)); + const float2 &mouse_delta = start_sample.mouse_position - center; + + MutableSpan positions = curves.positions_for_write(); + threading::parallel_for(pasted_points, 4096, [&](const IndexRange range) { + for (const int point_i : range) { + positions[point_i] = params.placement.project(view_positions[point_i] + mouse_delta); + } + }); + params.drawing.tag_positions_changed(); + + return true; + }); +} + +void CloneOperation::on_stroke_extended(const bContext & /*C*/, + const InputSample &extension_sample) +{ + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_clone_operation(const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc new file mode 100644 index 00000000000..dc4871e0a58 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc @@ -0,0 +1,284 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_brush.hh" +#include "BKE_colortools.hh" +#include "BKE_context.hh" +#include "BKE_crazyspace.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" + +#include "BLI_index_mask.hh" +#include "BLI_math_vector.hh" +#include "BLI_task.hh" + +#include "DEG_depsgraph_query.hh" + +#include "DNA_brush_types.h" +#include "DNA_node_tree_interface_types.h" +#include "DNA_screen_types.h" +#include "DNA_view3d_types.h" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "grease_pencil_intern.hh" + +#include + +namespace blender::ed::sculpt_paint::greasepencil { + +Vector get_drawings_for_sculpt(const bContext &C) +{ + using namespace blender::bke::greasepencil; + + const Scene &scene = *CTX_data_scene(&C); + Object &ob_orig = *CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(ob_orig.data); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool active_layer_only = ((brush.gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) != + 0); + + if (active_layer_only) { + /* Apply only to the drawing at the current frame of the active layer. */ + if (!grease_pencil.has_active_layer()) { + return {}; + } + const Layer &active_layer = *grease_pencil.get_active_layer(); + return ed::greasepencil::retrieve_editable_drawings_from_layer_with_falloff( + scene, grease_pencil, active_layer); + } + + /* Apply to all editable drawings. */ + return ed::greasepencil::retrieve_editable_drawings_with_falloff(scene, grease_pencil); +} + +void init_brush(Brush &brush) +{ + if (brush.gpencil_settings == nullptr) { + BKE_brush_init_gpencil_settings(&brush); + } + BLI_assert(brush.gpencil_settings != nullptr); + BKE_curvemapping_init(brush.gpencil_settings->curve_strength); + BKE_curvemapping_init(brush.gpencil_settings->curve_sensitivity); + BKE_curvemapping_init(brush.gpencil_settings->curve_jitter); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_pressure); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_strength); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_uv); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_hue); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_saturation); + BKE_curvemapping_init(brush.gpencil_settings->curve_rand_value); +} + +static float brush_radius(const Scene &scene, const Brush &brush, const float pressure = 1.0f) +{ + float radius = BKE_brush_size_get(&scene, &brush); + if (BKE_brush_use_size_pressure(&brush)) { + radius *= BKE_curvemapping_evaluateF(brush.gpencil_settings->curve_sensitivity, 0, pressure); + } + return radius; +} + +float brush_influence(const Scene &scene, + const Brush &brush, + const float2 &co, + const InputSample &sample, + const float multi_frame_falloff) +{ + const float radius = brush_radius(scene, brush, sample.pressure); + /* Basic strength factor from brush settings. */ + const float brush_pressure = BKE_brush_use_alpha_pressure(&brush) ? sample.pressure : 1.0f; + const float influence_base = BKE_brush_alpha_get(&scene, &brush) * brush_pressure * + multi_frame_falloff; + + /* Distance falloff. */ + const int2 mval_i = int2(math::round(sample.mouse_position)); + const float distance = math::distance(mval_i, int2(co)); + /* Apply Brush curve. */ + const float brush_falloff = BKE_brush_curve_strength(&brush, distance, radius); + + return influence_base * brush_falloff; +} + +IndexMask brush_influence_mask(const Scene &scene, + const Brush &brush, + const float2 &mouse_position, + const float pressure, + const float multi_frame_falloff, + const IndexMask &selection, + const Span view_positions, + Vector &influences, + IndexMaskMemory &memory) +{ + if (selection.is_empty()) { + return {}; + } + + const float radius = brush_radius(scene, brush, pressure); + const float radius_squared = radius * radius; + const float brush_pressure = BKE_brush_use_alpha_pressure(&brush) ? pressure : 1.0f; + const float influence_base = BKE_brush_alpha_get(&scene, &brush) * brush_pressure * + multi_frame_falloff; + const int2 mval_i = int2(math::round(mouse_position)); + + Array all_influences(selection.min_array_size()); + const IndexMask influence_mask = IndexMask::from_predicate( + selection, GrainSize(4096), memory, [&](const int point) { + /* Distance falloff. */ + const float distance_squared = math::distance_squared(int2(view_positions[point]), mval_i); + if (distance_squared > radius_squared) { + all_influences[point] = 0.0f; + return false; + } + /* Apply Brush curve. */ + const float brush_falloff = BKE_brush_curve_strength( + &brush, math::sqrt(distance_squared), radius); + all_influences[point] = influence_base * brush_falloff; + return all_influences[point] > 0.0f; + }); + influences.reinitialize(influence_mask.size()); + array_utils::gather(all_influences.as_span(), influence_mask, influences.as_mutable_span()); + + return influence_mask; +} + +bool is_brush_inverted(const Brush &brush, const BrushStrokeMode stroke_mode) +{ + /* The basic setting is the brush's setting. During runtime, the user can hold down the Ctrl key + * to invert the basic behavior. */ + return bool(brush.flag & BRUSH_DIR_IN) ^ (stroke_mode == BrushStrokeMode::BRUSH_STROKE_INVERT); +} + +GreasePencilStrokeParams GreasePencilStrokeParams::from_context( + const Scene &scene, + const Depsgraph &depsgraph, + const ARegion ®ion, + const View3D &view3d, + Object &object, + const int layer_index, + const int frame_number, + const float multi_frame_falloff, + bke::greasepencil::Drawing &drawing) +{ + Object &ob_eval = *DEG_get_evaluated_object(&depsgraph, &object); + GreasePencil &grease_pencil = *static_cast(object.data); + + const bke::greasepencil::Layer &layer = *grease_pencil.layers()[layer_index]; + ed::greasepencil::DrawingPlacement placement(scene, region, view3d, ob_eval, layer); + + return {*scene.toolsettings, + region, + object, + ob_eval, + layer, + layer_index, + frame_number, + multi_frame_falloff, + std::move(placement), + drawing}; +} + +IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, IndexMaskMemory &memory) +{ + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(params.toolsettings.gpencil_selectmode_sculpt)); + return (is_masking ? ed::greasepencil::retrieve_editable_and_selected_points( + params.ob_eval, params.drawing, memory) : + params.drawing.strokes().points_range()); +} + +bke::crazyspace::GeometryDeformation get_drawing_deformation( + const GreasePencilStrokeParams ¶ms) +{ + return bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( + ¶ms.ob_eval, params.ob_orig, params.layer_index, params.frame_number); +} + +Array calculate_view_positions(const GreasePencilStrokeParams ¶ms, + const IndexMask &selection) +{ + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); + + Array view_positions(deformation.positions.size()); + + /* Compute screen space positions. */ + const float4x4 transform = params.layer.to_world_space(params.ob_eval); + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + eV3DProjStatus result = ED_view3d_project_float_global( + ¶ms.region, + math::transform_point(transform, deformation.positions[point_i]), + view_positions[point_i], + V3D_PROJ_TEST_NOP); + if (result != V3D_PROJ_RET_OK) { + view_positions[point_i] = float2(0); + } + }); + + return view_positions; +} + +bool GreasePencilStrokeOperationCommon::is_inverted(const Brush &brush) const +{ + return is_brush_inverted(brush, this->stroke_mode); +} + +float2 GreasePencilStrokeOperationCommon::mouse_delta(const InputSample &input_sample) const +{ + return input_sample.mouse_position - this->prev_mouse_position; +} + +void GreasePencilStrokeOperationCommon::foreach_editable_drawing( + const bContext &C, FunctionRef fn) const +{ + using namespace blender::bke::greasepencil; + + const Scene &scene = *CTX_data_scene(&C); + const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); + const View3D &view3d = *CTX_wm_view3d(&C); + const ARegion ®ion = *CTX_wm_region(&C); + Object &object = *CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(object.data); + + std::atomic changed = false; + const Vector drawings = get_drawings_for_sculpt(C); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( + scene, + depsgraph, + region, + view3d, + object, + info.layer_index, + info.frame_number, + info.multi_frame_falloff, + info.drawing); + if (fn(params)) { + changed = true; + } + }); + + if (changed) { + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil); + } +} + +void GreasePencilStrokeOperationCommon::init_stroke(const bContext &C, + const InputSample &start_sample) +{ + Paint &paint = *BKE_paint_get_active_from_context(&C); + Brush &brush = *BKE_paint_brush(&paint); + + init_brush(brush); + + this->prev_mouse_position = start_sample.mouse_position; +} + +void GreasePencilStrokeOperationCommon::stroke_extended(const InputSample &extension_sample) +{ + this->prev_mouse_position = extension_sample.mouse_position; +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc new file mode 100644 index 00000000000..71f23735b0e --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc @@ -0,0 +1,234 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_crazyspace.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "BLI_index_mask.hh" +#include "BLI_math_matrix.hh" +#include "BLI_task.hh" + +#include "DEG_depsgraph_query.hh" + +#include "DNA_grease_pencil_types.h" +#include "DNA_view3d_types.h" +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class GrabOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + using MutableDrawingInfo = blender::ed::greasepencil::MutableDrawingInfo; + using DrawingPlacement = ed::greasepencil::DrawingPlacement; + + /* Cached point mask and influence for a particular drawing. */ + struct PointWeights { + int layer_index; + int frame_number; + float multi_frame_falloff; + + /* Layer space to view space projection at the start of the stroke. */ + float4x4 layer_to_win; + /* Points that are grabbed at the beginning of the stroke. */ + IndexMask point_mask; + /* Influence weights for grabbed points. */ + Vector weights; + + IndexMaskMemory memory; + }; + /* Cached point data for each affected drawing. */ + Array drawing_data; + + void foreach_grabbed_drawing(const bContext &C, + FunctionRef weights)> fn) const; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void GrabOperation::foreach_grabbed_drawing( + const bContext &C, + FunctionRef weights)> fn) + const +{ + const Scene &scene = *CTX_data_scene(&C); + const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); + const ARegion ®ion = *CTX_wm_region(&C); + const View3D &view3d = *CTX_wm_view3d(&C); + Object &object = *CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(object.data); + + bool changed = false; + threading::parallel_for_each(this->drawing_data.index_range(), [&](const int i) { + const PointWeights &data = this->drawing_data[i]; + if (data.point_mask.is_empty()) { + return; + } + const bke::greasepencil::Layer &layer = *grease_pencil.layers()[data.layer_index]; + /* If a new frame is created, could be impossible find the stroke. */ + const int drawing_index = layer.drawing_index_at(data.frame_number); + if (drawing_index < 0) { + return; + } + GreasePencilDrawingBase &drawing_base = *grease_pencil.drawing(drawing_index); + if (drawing_base.type != GP_DRAWING) { + return; + } + bke::greasepencil::Drawing &drawing = + reinterpret_cast(drawing_base).wrap(); + + GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( + scene, + depsgraph, + region, + view3d, + object, + data.layer_index, + data.frame_number, + data.multi_frame_falloff, + drawing); + if (fn(params, data.point_mask, data.weights)) { + changed = true; + } + }); + + if (changed) { + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil); + } +} + +void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + const ARegion ®ion = *CTX_wm_region(&C); + const View3D &view3d = *CTX_wm_view3d(&C); + const RegionView3D &rv3d = *CTX_wm_region_view3d(&C); + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + Brush &brush = *BKE_paint_brush(&paint); + const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); + Object &ob_orig = *CTX_data_active_object(&C); + Object &ob_eval = *DEG_get_evaluated_object(&depsgraph, &ob_orig); + GreasePencil &grease_pencil = *static_cast(ob_orig.data); + + init_brush(brush); + + this->prev_mouse_position = start_sample.mouse_position; + + const Vector drawings = get_drawings_for_sculpt(C); + this->drawing_data.reinitialize(drawings.size()); + threading::parallel_for_each(drawings.index_range(), [&](const int i) { + const MutableDrawingInfo &info = drawings[i]; + BLI_assert(info.layer_index >= 0); + PointWeights &data = this->drawing_data[i]; + + const bke::greasepencil::Layer &layer = *grease_pencil.layers()[info.layer_index]; + const int drawing_index = layer.drawing_index_at(info.frame_number); + BLI_assert(drawing_index >= 0); + BLI_assert(grease_pencil.get_drawing_at(layer, info.frame_number) == &info.drawing); + + ed::greasepencil::DrawingPlacement placement(scene, region, view3d, ob_eval, layer); + GreasePencilStrokeParams params = {*scene.toolsettings, + region, + ob_orig, + ob_eval, + layer, + info.layer_index, + info.frame_number, + info.multi_frame_falloff, + std::move(placement), + info.drawing}; + + IndexMaskMemory selection_memory; + IndexMask selection = point_selection_mask(params, selection_memory); + + Array view_positions = calculate_view_positions(params, selection); + + /* Cache points under brush influence. */ + Vector weights; + IndexMask point_mask = brush_influence_mask(scene, + brush, + start_sample.mouse_position, + start_sample.pressure, + info.multi_frame_falloff, + selection, + view_positions, + weights, + data.memory); + + if (point_mask.is_empty()) { + /* Set empty point mask to skip. */ + data.point_mask = {}; + return; + } + data.layer_index = info.layer_index; + data.frame_number = info.frame_number; + data.multi_frame_falloff = info.multi_frame_falloff; + data.layer_to_win = ED_view3d_ob_project_mat_get(&rv3d, &ob_eval) * + layer.to_object_space(ob_eval); + data.point_mask = std::move(point_mask); + data.weights = std::move(weights); + }); +} + +void GrabOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const ARegion ®ion = *CTX_wm_region(&C); + const RegionView3D &rv3d = *CTX_wm_region_view3d(&C); + + this->foreach_grabbed_drawing( + C, + [&](const GreasePencilStrokeParams ¶ms, + const IndexMask &mask, + const Span weights) { + /* Crazyspace deformation. */ + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); + + /* Transform mouse delta into layer space. */ + const float2 mouse_delta_win = this->mouse_delta(extension_sample); + const float3 layer_origin = params.layer.to_world_space(params.ob_eval).location(); + const float zfac = ED_view3d_calc_zfac(&rv3d, layer_origin); + float3 mouse_delta; + ED_view3d_win_to_delta(®ion, mouse_delta_win, zfac, mouse_delta); + + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + MutableSpan positions = curves.positions_for_write(); + mask.foreach_index(GrainSize(1024), [&](const int point_i, const int index) { + /* Translate the point with the influence factor. */ + const float3 new_pos_layer = deformation.positions[point_i] + + mouse_delta * weights[index]; + const float3 new_pos_world = math::transform_point( + params.layer.to_world_space(params.ob_eval), new_pos_layer); + float2 new_pos_view; + ED_view3d_project_float_global(®ion, new_pos_world, new_pos_view, V3D_PROJ_TEST_NOP); + positions[point_i] = params.placement.project(new_pos_view); + }); + + params.drawing.tag_positions_changed(); + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_grab_operation(const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc new file mode 100644 index 00000000000..b22fcd6f338 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc @@ -0,0 +1,79 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class PinchOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void PinchOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool invert = this->is_inverted(brush); + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + MutableSpan positions = curves.positions_for_write(); + + const float2 target = extension_sample.mouse_position; + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + + const float scale_offset = influence * influence / 25.0f; + const float scale = invert ? 1.0 + scale_offset : 1.0f - scale_offset; + positions[point_i] = params.placement.project(target + (co - target) * scale); + }); + + params.drawing.tag_positions_changed(); + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_pinch_operation(const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc new file mode 100644 index 00000000000..7f1531f782d --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc @@ -0,0 +1,76 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class PushOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void PushOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void PushOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + MutableSpan positions = curves.positions_for_write(); + + const float2 mouse_delta = this->mouse_delta(extension_sample); + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + + positions[point_i] = params.placement.project(co + mouse_delta * influence); + }); + + params.drawing.tag_positions_changed(); + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_push_operation(const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc new file mode 100644 index 00000000000..279b80a13c9 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc @@ -0,0 +1,153 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_hash.h" +#include "BLI_math_vector.hh" +#include "BLI_rand.hh" +#include "BLI_task.hh" + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +/* Use a hash to generate random numbers. */ +static float hash_rng(uint32_t seed1, uint32_t seed2, int index) +{ + return BLI_hash_int_01(BLI_hash_int_3d(seed1, seed2, uint32_t(index))); +} + +class RandomizeOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + /* Get a different seed value for each stroke. */ + uint32_t unique_seed() const; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +uint32_t RandomizeOperation::unique_seed() const +{ + return RandomNumberGenerator::from_random_seed().get_uint32(); +} + +void RandomizeOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag; + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + const uint32_t seed = this->unique_seed(); + + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + + bool changed = false; + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { + MutableSpan positions = curves.positions_for_write(); + + /* Jitter is applied perpendicular to the mouse movement vector. */ + const float2 forward = math::normalize(this->mouse_delta(extension_sample)); + const float2 sideways = float2(-forward.y, forward.x); + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + const float noise = 2.0f * hash_rng(seed, 5678, point_i) - 1.0f; + positions[point_i] = params.placement.project(co + sideways * influence * noise); + }); + + params.drawing.tag_positions_changed(); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) { + MutableSpan opacities = params.drawing.opacities_for_write(); + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f; + opacities[point_i] = math::clamp(opacities[point_i] + influence * noise, 0.0f, 1.0f); + }); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) { + const MutableSpan radii = params.drawing.radii_for_write(); + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f; + radii[point_i] = math::max(radii[point_i] + influence * noise * 0.001f, 0.0f); + }); + curves.tag_radii_changed(); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) { + bke::SpanAttributeWriter rotations = attributes.lookup_or_add_for_write_span( + "rotation", bke::AttrDomain::Point); + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f; + rotations.span[point_i] = math::clamp( + rotations.span[point_i] + influence * noise, -float(M_PI_2), float(M_PI_2)); + }); + rotations.finish(); + changed = true; + } + return changed; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_randomize_operation( + const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc new file mode 100644 index 00000000000..9d73cbfcd1f --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc @@ -0,0 +1,140 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute.hh" + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "BLI_virtual_array.hh" +#include "DNA_brush_enums.h" + +#include "GEO_smooth_curves.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class SmoothOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void SmoothOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void SmoothOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag; + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray cyclic = curves.cyclic(); + const int iterations = 2; + + const VArray influences = VArray::ForFunc( + view_positions.size(), [&](const int64_t point_) { + return brush_influence( + scene, brush, view_positions[point_], extension_sample, params.multi_frame_falloff); + }); + Array selection_array(curves.points_num()); + selection.to_bools(selection_array); + const VArray selection_varray = VArray::ForSpan(selection_array); + + bool changed = false; + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { + MutableSpan positions = curves.positions_for_write(); + geometry::smooth_curve_attribute(curves.curves_range(), + points_by_curve, + selection_varray, + cyclic, + iterations, + influences, + false, + false, + positions); + params.drawing.tag_positions_changed(); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) { + MutableSpan opacities = params.drawing.opacities_for_write(); + geometry::smooth_curve_attribute(curves.curves_range(), + points_by_curve, + selection_varray, + cyclic, + iterations, + influences, + true, + false, + opacities); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) { + const MutableSpan radii = params.drawing.radii_for_write(); + geometry::smooth_curve_attribute(curves.curves_range(), + points_by_curve, + selection_varray, + cyclic, + iterations, + influences, + true, + false, + radii); + curves.tag_radii_changed(); + changed = true; + } + if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) { + bke::SpanAttributeWriter rotations = attributes.lookup_or_add_for_write_span( + "rotation", bke::AttrDomain::Point); + geometry::smooth_curve_attribute(curves.curves_range(), + points_by_curve, + selection_varray, + cyclic, + iterations, + influences, + true, + false, + rotations.span); + rotations.finish(); + changed = true; + } + return changed; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_smooth_operation( + const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc new file mode 100644 index 00000000000..3038f4ccecd --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc @@ -0,0 +1,73 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "BKE_context.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class StrengthOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void StrengthOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void StrengthOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool invert = this->is_inverted(brush); + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + MutableSpan opacities = params.drawing.opacities_for_write(); + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + float &opacity = opacities[point_i]; + const float influence = brush_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + /* Brush influence mapped to opacity by a factor of 0.125. */ + const float delta_opacity = (invert ? -influence : influence) * 0.125f; + opacity = std::clamp(opacity + delta_opacity, 0.0f, 1.0f); + }); + + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_strength_operation( + const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc new file mode 100644 index 00000000000..78be495bca4 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc @@ -0,0 +1,77 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class ThicknessOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void ThicknessOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void ThicknessOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool invert = this->is_inverted(brush); + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + BLI_assert(view_positions.size() == curves.points_num()); + MutableSpan radii = params.drawing.radii_for_write(); + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + float &radius = radii[point_i]; + const float influence = brush_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + /* Factor 1/1000 is used to map arbitrary influence value to a sensible radius. */ + const float delta_radius = (invert ? -influence : influence) * 0.001f; + radius = std::max(radius + delta_radius, 0.0f); + }); + + curves.tag_radii_changed(); + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_thickness_operation( + const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc new file mode 100644 index 00000000000..1c62480f709 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc @@ -0,0 +1,86 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class TwistOperation : public GreasePencilStrokeOperationCommon { + public: + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +static float2 rotate_by_angle(const float2 &vec, const float angle) +{ + const float cos_angle = math::cos(angle); + const float sin_angle = math::sin(angle); + return float2(vec.x * cos_angle - vec.y * sin_angle, vec.x * sin_angle + vec.y * cos_angle); +} + +void TwistOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); +} + +void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool invert = this->is_inverted(brush); + + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory selection_memory; + const IndexMask selection = point_selection_mask(params, selection_memory); + if (selection.is_empty()) { + return false; + } + + Array view_positions = calculate_view_positions(params, selection); + bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + MutableSpan positions = curves.positions_for_write(); + + const float2 mouse_pos = extension_sample.mouse_position; + + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float2 &co = view_positions[point_i]; + const float influence = brush_influence( + scene, brush, co, extension_sample, params.multi_frame_falloff); + if (influence <= 0.0f) { + return; + } + + const float angle = DEG2RADF(invert ? -1.0f : 1.0f) * influence; + positions[point_i] = params.placement.project(rotate_by_angle(co - mouse_pos, angle) + + mouse_pos); + }); + + params.drawing.tag_positions_changed(); + return true; + }); + this->stroke_extended(extension_sample); +} + +std::unique_ptr new_twist_operation(const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/geometry/GEO_smooth_curves.hh b/source/blender/geometry/GEO_smooth_curves.hh index 310e38b6f24..d710c0815ef 100644 --- a/source/blender/geometry/GEO_smooth_curves.hh +++ b/source/blender/geometry/GEO_smooth_curves.hh @@ -14,13 +14,14 @@ namespace blender::geometry { * 1D Gaussian-like smoothing function. * * \param iterations: Number of times to repeat the smoothing. + * \param influence: Influence factor for each point. * \param smooth_ends: Smooth the first and last value. * \param keep_shape: Changes the gaussian kernel to avoid severe deformations. * \param is_cyclic: Propagate smoothing across the ends of the input as if they were connected. */ void gaussian_blur_1D(const GSpan src, int iterations, - const float influence, + const VArray &influence_by_point, const bool smooth_ends, const bool keep_shape, const bool is_cyclic, @@ -39,4 +40,17 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth, bool keep_shape, GMutableSpan attribute_data); +/** + * Smooths the \a attribute_data using a 1D gaussian blur. + */ +void smooth_curve_attribute(const IndexMask &curves_to_smooth, + const OffsetIndices points_by_curve, + const VArray &point_selection, + const VArray &cyclic, + int iterations, + const VArray &influence_by_point, + bool smooth_ends, + bool keep_shape, + GMutableSpan attribute_data); + } // namespace blender::geometry diff --git a/source/blender/geometry/intern/smooth_curves.cc b/source/blender/geometry/intern/smooth_curves.cc index d908cfedb0b..3038a5cd0bd 100644 --- a/source/blender/geometry/intern/smooth_curves.cc +++ b/source/blender/geometry/intern/smooth_curves.cc @@ -18,7 +18,7 @@ namespace blender::geometry { template static void gaussian_blur_1D(const Span src, const int iterations, - const float influence, + const VArray &influence_by_point, const bool smooth_ends, const bool keep_shape, const bool is_cyclic, @@ -133,19 +133,21 @@ static void gaussian_blur_1D(const Span src, } /* Normalize the weights. */ - threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { - for (const int64_t index : range) { - if (!is_end_and_fixed(index)) { - total_weight[index] += w - w2; - dst[index] = src[index] + influence * dst[index] / total_weight[index]; + devirtualize_varray(influence_by_point, [&](const auto influence_by_point) { + threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) { + for (const int64_t index : range) { + if (!is_end_and_fixed(index)) { + total_weight[index] += w - w2; + dst[index] = src[index] + influence_by_point[index] * dst[index] / total_weight[index]; + } } - } + }); }); } void gaussian_blur_1D(const GSpan src, const int iterations, - const float influence, + const VArray &influence_by_point, const bool smooth_ends, const bool keep_shape, const bool is_cyclic, @@ -160,7 +162,7 @@ void gaussian_blur_1D(const GSpan src, { gaussian_blur_1D(src.typed(), iterations, - influence, + influence_by_point, smooth_ends, keep_shape, is_cyclic, @@ -174,11 +176,13 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth, const VArray &point_selection, const VArray &cyclic, const int iterations, - const float influence, + const VArray &influence_by_point, const bool smooth_ends, const bool keep_shape, GMutableSpan attribute_data) { + VArraySpan influences(influence_by_point); + curves_to_smooth.foreach_index(GrainSize(512), [&](const int curve_i) { Vector orig_data; const IndexRange points = points_by_curve[curve_i]; @@ -196,10 +200,36 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth, dst_data.type().copy_assign_n(dst_data.data(), orig_data.data(), range.size()); const GSpan src_data(dst_data.type(), orig_data.data(), range.size()); - gaussian_blur_1D( - src_data, iterations, influence, smooth_ends, keep_shape, cyclic[curve_i], dst_data); + gaussian_blur_1D(src_data, + iterations, + VArray::ForSpan(influences.slice(range)), + smooth_ends, + keep_shape, + cyclic[curve_i], + dst_data); }); }); } +void smooth_curve_attribute(const IndexMask &curves_to_smooth, + const OffsetIndices points_by_curve, + const VArray &point_selection, + const VArray &cyclic, + const int iterations, + const float influence, + const bool smooth_ends, + const bool keep_shape, + GMutableSpan attribute_data) +{ + smooth_curve_attribute(curves_to_smooth, + points_by_curve, + point_selection, + cyclic, + iterations, + VArray::ForSingle(influence, points_by_curve.total_size()), + smooth_ends, + keep_shape, + attribute_data); +} + } // namespace blender::geometry diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 034562c12dd..8ce636f5ff9 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -328,6 +328,7 @@ typedef enum eGP_Sculpt_Mode_Flag { /* apply brush to uv data */ GP_SCULPT_FLAGMODE_APPLY_UV = (1 << 3), } eGP_Sculpt_Mode_Flag; +ENUM_OPERATORS(eGP_Sculpt_Mode_Flag, GP_SCULPT_FLAGMODE_APPLY_UV) typedef enum eAutomasking_flag { BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0), diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index 1565ba302a6..861683a4a04 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -1013,6 +1013,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C, return rna_enum_dummy_DEFAULT_items; } case PaintMode::SculptGPencil: + case PaintMode::SculptGreasePencil: switch (me->gpencil_sculpt_tool) { case GPSCULPT_TOOL_THICKNESS: case GPSCULPT_TOOL_STRENGTH: diff --git a/source/blender/windowmanager/intern/wm_init_exit.cc b/source/blender/windowmanager/intern/wm_init_exit.cc index beff6791718..a67017683bb 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.cc +++ b/source/blender/windowmanager/intern/wm_init_exit.cc @@ -86,6 +86,7 @@ #include "ED_anim_api.hh" #include "ED_asset.hh" #include "ED_gpencil_legacy.hh" +#include "ED_grease_pencil.hh" #include "ED_keyframes_edit.hh" #include "ED_keyframing.hh" #include "ED_node.hh" @@ -581,6 +582,7 @@ void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_ BKE_mask_clipboard_free(); BKE_vfont_clipboard_free(); ED_node_clipboard_free(); + ed::greasepencil::clipboard_free(); UV_clipboard_free(); wm_clipboard_free();