GPv3: Implementation of sculpt mode tools
This implements all the sculpt tools in Grease Pencil 3. UI changes in the 3D view header and keymap entries for sculpt mode are still minimal, more entries should be added once the relevant operators are supported. A set of utility functions and a shared base class `GreasePencilStrokeOperationCommon` for sculpt operations has been added to make individual operations less verbose. The `GreasePencilStrokeParams` struct bundles common arguments to reduce the amount of boilerplate code. The `foreach_editable_drawing` utility function takes care of setting up the parameters and finding the right drawings, so the tool only has to modify the data. Common features like tracking mouse movement and inverting brush influence are handled by the common base class. Most operations are then relatively simple, with the exception of the Grab and Clone operations. - __Grab__ stores a stroke mask and weights on initialization of the stroke, rather than working with the usual selection mask. - __Clone__ needs access to the clipboard, which requires exposing the clipboard in the editor API. Pull Request: https://projects.blender.org/blender/blender/pulls/120508
This commit is contained in:
@@ -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"),
|
||||
])
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<std::pair<uint, int>> 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<Clipboard>(__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<GreasePencil *>(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<uint, Material *> 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<int> 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<bke::GeometrySet> 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<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
|
||||
"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<int> clipboard_materials_remap(Main &bmain, Object &object)
|
||||
{
|
||||
using namespace blender::ed::greasepencil;
|
||||
|
||||
/* Get a list of all materials in the scene. */
|
||||
Map<uint, Material *> scene_materials;
|
||||
LISTBASE_FOREACH (Material *, material, &bmain.materials) {
|
||||
scene_materials.add(material->id.session_uid, material);
|
||||
}
|
||||
|
||||
Clipboard &clipboard = ensure_grease_pencil_clipboard();
|
||||
Array<int> 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<int> 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<bke::GeometrySet> 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<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
|
||||
"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()
|
||||
|
||||
@@ -563,6 +563,47 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
|
||||
return editable_drawings;
|
||||
}
|
||||
|
||||
Vector<MutableDrawingInfo> 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<int, int> 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<MutableDrawingInfo> editable_drawings;
|
||||
const Array<int> 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<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
|
||||
const GreasePencil &grease_pencil,
|
||||
const bool do_onion_skinning)
|
||||
|
||||
@@ -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<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
|
||||
const Scene &scene, GreasePencil &grease_pencil);
|
||||
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
|
||||
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
|
||||
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer_with_falloff(
|
||||
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
|
||||
Vector<DrawingInfo> 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<bool> vertex_group_is_locked,
|
||||
Span<bool> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ed::greasepencil::MutableDrawingInfo> 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<float2> view_positions,
|
||||
Vector<float> &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<float2> 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<bool(const GreasePencilStrokeParams ¶ms)> fn) const;
|
||||
};
|
||||
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_erase_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation();
|
||||
@@ -33,6 +130,15 @@ std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation();
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_smooth_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_thickness_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_strength_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_randomize_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_grab_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_push_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_pinch_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_twist_operation(BrushStrokeMode stroke_mode);
|
||||
std::unique_ptr<GreasePencilStrokeOperation> new_clone_operation(BrushStrokeMode stroke_mode);
|
||||
|
||||
} // namespace greasepencil
|
||||
|
||||
|
||||
@@ -331,13 +331,14 @@ struct PaintOperationExecutor {
|
||||
* stable) fit. */
|
||||
Array<float2> 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<float>::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;
|
||||
|
||||
@@ -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<float2> 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<int> 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<float2> 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<float3> 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<GreasePencilStrokeOperation> new_clone_operation(const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<CloneOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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 <iostream>
|
||||
|
||||
namespace blender::ed::sculpt_paint::greasepencil {
|
||||
|
||||
Vector<ed::greasepencil::MutableDrawingInfo> 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<GreasePencil *>(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<float2> view_positions,
|
||||
Vector<float> &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<float> 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<GreasePencil *>(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<float2> calculate_view_positions(const GreasePencilStrokeParams ¶ms,
|
||||
const IndexMask &selection)
|
||||
{
|
||||
bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
|
||||
|
||||
Array<float2> 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<bool(const GreasePencilStrokeParams ¶ms)> 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<GreasePencil *>(object.data);
|
||||
|
||||
std::atomic<bool> changed = false;
|
||||
const Vector<MutableDrawingInfo> 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
|
||||
234
source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc
Normal file
234
source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc
Normal file
@@ -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<float> weights;
|
||||
|
||||
IndexMaskMemory memory;
|
||||
};
|
||||
/* Cached point data for each affected drawing. */
|
||||
Array<PointWeights> drawing_data;
|
||||
|
||||
void foreach_grabbed_drawing(const bContext &C,
|
||||
FunctionRef<bool(const GreasePencilStrokeParams ¶ms,
|
||||
const IndexMask &mask,
|
||||
Span<float> 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<bool(
|
||||
const GreasePencilStrokeParams ¶ms, const IndexMask &mask, Span<float> 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<GreasePencil *>(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<GreasePencilDrawing &>(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<GreasePencil *>(ob_orig.data);
|
||||
|
||||
init_brush(brush);
|
||||
|
||||
this->prev_mouse_position = start_sample.mouse_position;
|
||||
|
||||
const Vector<MutableDrawingInfo> 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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
|
||||
/* Cache points under brush influence. */
|
||||
Vector<float> 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<float> 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<float3> 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<GreasePencilStrokeOperation> new_grab_operation(const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<GrabOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
|
||||
MutableSpan<float3> 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<GreasePencilStrokeOperation> new_pinch_operation(const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<PinchOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
|
||||
MutableSpan<float3> 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<GreasePencilStrokeOperation> new_push_operation(const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<PushOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> 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<float3> 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<float> 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<float> 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<float> rotations = attributes.lookup_or_add_for_write_span<float>(
|
||||
"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<GreasePencilStrokeOperation> new_randomize_operation(
|
||||
const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<RandomizeOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> 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<bool> cyclic = curves.cyclic();
|
||||
const int iterations = 2;
|
||||
|
||||
const VArray<float> influences = VArray<float>::ForFunc(
|
||||
view_positions.size(), [&](const int64_t point_) {
|
||||
return brush_influence(
|
||||
scene, brush, view_positions[point_], extension_sample, params.multi_frame_falloff);
|
||||
});
|
||||
Array<bool> selection_array(curves.points_num());
|
||||
selection.to_bools(selection_array);
|
||||
const VArray<bool> selection_varray = VArray<bool>::ForSpan(selection_array);
|
||||
|
||||
bool changed = false;
|
||||
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
|
||||
MutableSpan<float3> 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<float> 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<float> 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<float> rotations = attributes.lookup_or_add_for_write_span<float>(
|
||||
"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<GreasePencilStrokeOperation> new_smooth_operation(
|
||||
const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<SmoothOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
MutableSpan<float> 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<GreasePencilStrokeOperation> new_strength_operation(
|
||||
const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<StrengthOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
|
||||
BLI_assert(view_positions.size() == curves.points_num());
|
||||
MutableSpan<float> 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<GreasePencilStrokeOperation> new_thickness_operation(
|
||||
const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<ThicknessOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float2> view_positions = calculate_view_positions(params, selection);
|
||||
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
|
||||
MutableSpan<float3> 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<GreasePencilStrokeOperation> new_twist_operation(const BrushStrokeMode stroke_mode)
|
||||
{
|
||||
return std::make_unique<TwistOperation>(stroke_mode);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
@@ -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<float> &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<int> points_by_curve,
|
||||
const VArray<bool> &point_selection,
|
||||
const VArray<bool> &cyclic,
|
||||
int iterations,
|
||||
const VArray<float> &influence_by_point,
|
||||
bool smooth_ends,
|
||||
bool keep_shape,
|
||||
GMutableSpan attribute_data);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace blender::geometry {
|
||||
template<typename T>
|
||||
static void gaussian_blur_1D(const Span<T> src,
|
||||
const int iterations,
|
||||
const float influence,
|
||||
const VArray<float> &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<T> 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<float> &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<T>(),
|
||||
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<bool> &point_selection,
|
||||
const VArray<bool> &cyclic,
|
||||
const int iterations,
|
||||
const float influence,
|
||||
const VArray<float> &influence_by_point,
|
||||
const bool smooth_ends,
|
||||
const bool keep_shape,
|
||||
GMutableSpan attribute_data)
|
||||
{
|
||||
VArraySpan<float> influences(influence_by_point);
|
||||
|
||||
curves_to_smooth.foreach_index(GrainSize(512), [&](const int curve_i) {
|
||||
Vector<std::byte> 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<float>::ForSpan(influences.slice(range)),
|
||||
smooth_ends,
|
||||
keep_shape,
|
||||
cyclic[curve_i],
|
||||
dst_data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void smooth_curve_attribute(const IndexMask &curves_to_smooth,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
const VArray<bool> &point_selection,
|
||||
const VArray<bool> &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<float>::ForSingle(influence, points_by_curve.total_size()),
|
||||
smooth_ends,
|
||||
keep_shape,
|
||||
attribute_data);
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user