diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index d1419ed584d..c097e89fdc5 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4569,6 +4569,11 @@ def km_grease_pencil_paint(_params): ) items.extend([ + *_template_paint_radial_control("gpencil_paint"), + ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 0.9)]}), + ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, + {"properties": [("scalar", 1.0 / 0.9)]}), ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'INVERT')]}), diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 616824d2416..ff7d2e9247f 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -82,6 +82,8 @@ class UnifiedPaintPanel: return tool_settings.gpencil_vertex_paint elif mode == 'SCULPT_CURVES': return tool_settings.curves_sculpt + elif mode == 'PAINT_GREASE_PENCIL': + return tool_settings.gpencil_paint return None @staticmethod @@ -866,6 +868,11 @@ def brush_shared_settings(layout, context, brush, popover=False): strength = True direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'} + # Grease Pencil # + if mode == 'PAINT_GREASE_PENCIL': + size = True + strength = True + ### Draw settings. ### ups = context.scene.tool_settings.unified_paint_settings diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 3039742e8bc..82c279bc2e5 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1744,21 +1744,11 @@ class _defs_paint_grease_pencil: @ToolDef.from_fn def erase(): - def draw_settings(context, layout, _tool): - paint = context.tool_settings.gpencil_paint - brush = paint.brush - if not brush: - return - layout.prop(brush.gpencil_settings, "eraser_mode", expand=True) - if brush.gpencil_settings.eraser_mode == 'HARD': - layout.prop(brush.gpencil_settings, "use_keep_caps_eraser") - layout.prop(brush.gpencil_settings, "use_active_layer_only") return dict( idname="builtin_brush.Erase", label="Erase", icon="brush.gpencil_draw.erase", data_block='ERASE', - draw_settings=draw_settings, ) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 04ecaffb119..fd4fe024827 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -582,6 +582,62 @@ class _draw_tool_settings_context_mode: return True + @staticmethod + def PAINT_GREASE_PENCIL(context, layout, tool): + if (tool is None) or (not tool.has_datablock): + return False + + tool_settings = context.tool_settings + paint = tool_settings.gpencil_paint + + brush = paint.brush + if brush is None: + return False + + row = layout.row(align=True) + row.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True) + + grease_pencil_tool = brush.gpencil_tool + + if grease_pencil_tool == 'DRAW': + from bl_ui.properties_paint_common import ( + brush_basic__draw_color_selector, + ) + brush_basic__draw_color_selector(context, layout, brush, brush.gpencil_settings, None) + + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "size", + unified_name="use_unified_size", + pressure_name="use_pressure_size", + text="Radius", + slider=True, + header=True, + ) + + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "strength", + pressure_name="use_pressure_strength", + unified_name="use_unified_strength", + slider=True, + header=True, + ) + + if grease_pencil_tool == 'DRAW': + layout.prop(brush.gpencil_settings, "active_smooth_factor") + elif grease_pencil_tool == 'ERASE': + layout.prop(brush.gpencil_settings, "eraser_mode", expand=True) + if brush.gpencil_settings.eraser_mode == "HARD": + layout.prop(brush.gpencil_settings, "use_keep_caps_eraser") + layout.prop(brush.gpencil_settings, "use_active_layer_only") + + return True + class VIEW3D_HT_header(Header): bl_space_type = 'VIEW_3D' @@ -625,7 +681,7 @@ class VIEW3D_HT_header(Header): else: if (object_mode not in { 'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT', - 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', + 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', 'PAINT_GREASE_PENCIL', }) or has_pose_mode: show_snap = True else: @@ -8353,7 +8409,7 @@ class TOPBAR_PT_gpencil_materials(GreasePencilMaterialsPanel, Panel): @classmethod def poll(cls, context): ob = context.object - return ob and ob.type == 'GPENCIL' + return ob and (ob.type == 'GPENCIL' or ob.type == 'GREASEPENCIL') class TOPBAR_PT_gpencil_vertexcolor(GreasePencilVertexcolorPanel, Panel): diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index c26eae2739a..8e3a10083e2 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -654,11 +654,9 @@ void set_handle_position(const float3 &position, * points are referred to as the control points, and the middle points are the corresponding * handles. */ -void evaluate_segment(const float3 &point_0, - const float3 &point_1, - const float3 &point_2, - const float3 &point_3, - MutableSpan result); +template +void evaluate_segment( + const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan result); /** * Calculate all evaluated points for the Bezier curve. diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index 5ad18f1a138..00b6184b6f1 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -11,6 +11,8 @@ #include +#include "BLI_array_utils.hh" +#include "BLI_color.hh" #include "BLI_function_ref.hh" #include "BLI_map.hh" #include "BLI_math_vector_types.hh" @@ -32,32 +34,6 @@ namespace blender::bke { namespace greasepencil { -/** - * A single point for a stroke that is currently being drawn. - */ -struct StrokePoint { - float3 position; - float radius; - float opacity; - float4 color; -}; - -/** - * Stroke cache for a stroke that is currently being drawn. - */ -struct StrokeCache { - Vector points; - Vector triangles; - int mat = 0; - - void clear() - { - this->points.clear_and_shrink(); - this->triangles.clear_and_shrink(); - this->mat = 0; - } -}; - class DrawingRuntime { public: /** @@ -102,6 +78,12 @@ class Drawing : public ::GreasePencilDrawing { VArray opacities() const; MutableSpan opacities_for_write(); + /** + * Vertex colors of the points. Default is black. + */ + VArray vertex_colors() const; + MutableSpan vertex_colors_for_write(); + /** * Add a user for this drawing. When a drawing has multiple users, both users are allowed to * modify this drawings data. @@ -663,19 +645,12 @@ class GreasePencilRuntime { * Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions. */ void *batch_cache = nullptr; - bke::greasepencil::StrokeCache stroke_cache; /* The frame on which the object was evaluated (only valid for evaluated object). */ int eval_frame; public: GreasePencilRuntime() {} ~GreasePencilRuntime() {} - - /** - * A buffer for a single stroke while drawing. - */ - Span stroke_buffer() const; - bool has_stroke_buffer() const; }; } // namespace blender::bke @@ -765,15 +740,26 @@ GreasePencil *BKE_grease_pencil_copy_for_eval(const GreasePencil *grease_pencil_ BoundBox *BKE_grease_pencil_boundbox_get(Object *ob); void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *object); +int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma); int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name); Material *BKE_grease_pencil_object_material_new(Main *bmain, Object *ob, const char *name, int *r_index); +Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush); Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, Object *ob, const char *name, int *r_index); +Material *BKE_grease_pencil_brush_material_get(Brush *brush); +Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain, + Object *ob, + Brush *brush); +Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain, + Object *ob, + Brush *brush); +Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob); +Material *BKE_grease_pencil_object_material_ensure_active(Object *ob); bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference, const GreasePencil *grease_pencil); diff --git a/source/blender/blenkernel/intern/curve_bezier.cc b/source/blender/blenkernel/intern/curve_bezier.cc index 5152a6e2b97..3223944f819 100644 --- a/source/blender/blenkernel/intern/curve_bezier.cc +++ b/source/blender/blenkernel/intern/curve_bezier.cc @@ -208,25 +208,23 @@ void calculate_auto_handles(const bool cyclic, positions_right.last()); } -void evaluate_segment(const float3 &point_0, - const float3 &point_1, - const float3 &point_2, - const float3 &point_3, - MutableSpan result) +template +void evaluate_segment_ex( + const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan result) { BLI_assert(result.size() > 0); const float inv_len = 1.0f / float(result.size()); const float inv_len_squared = inv_len * inv_len; const float inv_len_cubed = inv_len_squared * inv_len; - const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len; - const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; - const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; + const T rt1 = 3.0f * (point_1 - point_0) * inv_len; + const T rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; + const T rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; - float3 q0 = point_0; - float3 q1 = rt1 + rt2 + rt3; - float3 q2 = 2.0f * rt2 + 6.0f * rt3; - float3 q3 = 6.0f * rt3; + T q0 = point_0; + T q1 = rt1 + rt2 + rt3; + T q2 = 2.0f * rt2 + 6.0f * rt3; + T q3 = 6.0f * rt3; for (const int i : result.index_range()) { result[i] = q0; q0 += q1; @@ -234,6 +232,24 @@ void evaluate_segment(const float3 &point_0, q2 += q3; } } +template<> +void evaluate_segment(const float3 &point_0, + const float3 &point_1, + const float3 &point_2, + const float3 &point_3, + MutableSpan result) +{ + evaluate_segment_ex(point_0, point_1, point_2, point_3, result); +} +template<> +void evaluate_segment(const float2 &point_0, + const float2 &point_1, + const float2 &point_2, + const float2 &point_3, + MutableSpan result) +{ + evaluate_segment_ex(point_0, point_1, point_2, point_3, result); +} void calculate_evaluated_positions(const Span positions, const Span handles_left, diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index 8b51d5b5dd3..f8dbff40178 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -41,6 +41,7 @@ #include "DNA_ID.h" #include "DNA_ID_enums.h" +#include "DNA_brush_types.h" #include "DNA_grease_pencil_types.h" #include "DNA_material_types.h" #include "DNA_modifier_types.h" @@ -223,8 +224,9 @@ IDTypeInfo IDType_ID_GP = { namespace blender::bke::greasepencil { -static const std::string ATTR_OPACITY = "opacity"; static const std::string ATTR_RADIUS = "radius"; +static const std::string ATTR_OPACITY = "opacity"; +static const std::string ATTR_VERTEX_COLOR = "vertex_color"; /* Curves attributes getters */ static int domain_num(const CurvesGeometry &curves, const eAttrDomain domain) @@ -375,6 +377,20 @@ MutableSpan Drawing::opacities_for_write() this->strokes_for_write(), ATTR_DOMAIN_POINT, ATTR_OPACITY, 1.0f); } +VArray Drawing::vertex_colors() const +{ + return *this->strokes().attributes().lookup_or_default( + ATTR_VERTEX_COLOR, ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); +} + +MutableSpan Drawing::vertex_colors_for_write() +{ + return get_mutable_attribute(this->strokes_for_write(), + ATTR_DOMAIN_POINT, + ATTR_VERTEX_COLOR, + ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); +} + void Drawing::tag_positions_changed() { this->strokes_for_write().tag_positions_changed(); @@ -1135,6 +1151,19 @@ void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *o /** \name Grease Pencil material functions * \{ */ +int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma) +{ + short *totcol = BKE_object_material_len_p(ob); + Material *read_ma = NULL; + for (short i = 0; i < *totcol; i++) { + read_ma = BKE_object_material_get(ob, i + 1); + if (ma == read_ma) { + return i; + } + } + return -1; +} + int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name) { short *totcol = BKE_object_material_len_p(ob); @@ -1166,6 +1195,18 @@ Material *BKE_grease_pencil_object_material_new(Main *bmain, return ma; } +Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush) +{ + if ((brush) && (brush->gpencil_settings) && + (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED)) + { + Material *ma = BKE_grease_pencil_brush_material_get(brush); + return ma; + } + + return BKE_object_material_get(ob, ob->actcol); +} + Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, Object *ob, const char *name, @@ -1179,6 +1220,71 @@ Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, return BKE_grease_pencil_object_material_new(bmain, ob, name, r_index); } +Material *BKE_grease_pencil_brush_material_get(Brush *brush) +{ + if (brush == nullptr) { + return nullptr; + } + if (brush->gpencil_settings == nullptr) { + return nullptr; + } + return brush->gpencil_settings->material; +} + +Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain, + Object *ob, + Brush *brush) +{ + if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { + Material *ma = BKE_grease_pencil_brush_material_get(brush); + + /* check if the material is already on object material slots and add it if missing */ + if (ma && BKE_grease_pencil_object_material_index_get(ob, ma) < 0) { + BKE_object_material_slot_add(bmain, ob); + BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF); + } + + return ma; + } + + /* Use the active material instead. */ + return BKE_object_material_get(ob, ob->actcol); +} + +Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain, + Object *ob, + Brush *brush) +{ + if (brush == nullptr) { + return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); + } + if (Material *ma = BKE_grease_pencil_object_material_ensure_from_brush(bmain, ob, brush)) { + return ma; + } + if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { + /* It is easier to just unpin a null material, instead of setting a new one. */ + brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED; + } + return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); +} + +Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob) +{ + if (Material *ma = BKE_object_material_get(ob, ob->actcol)) { + return ma; + } + return BKE_material_default_gpencil(); +} + +Material *BKE_grease_pencil_object_material_ensure_active(Object *ob) +{ + Material *ma = BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); + if (ma->gp_style == nullptr) { + BKE_gpencil_material_attr_init(ma); + } + return ma; +} + /** \} */ /* ------------------------------------------------------------------- */ @@ -1235,23 +1341,6 @@ void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil) /** \} */ -/* ------------------------------------------------------------------- */ -/** \name Grease Pencil runtime API - * \{ */ - -bool blender::bke::GreasePencilRuntime::has_stroke_buffer() const -{ - return this->stroke_cache.points.size() > 0; -} - -blender::Span blender::bke::GreasePencilRuntime:: - stroke_buffer() const -{ - return this->stroke_cache.points.as_span(); -} - -/** \} */ - /* ------------------------------------------------------------------- */ /** \name Grease Pencil data-block API * \{ */ diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index a2e4b016e45..c544874313a 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -1085,6 +1085,8 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode) return OB_MODE_EDIT; case PAINT_MODE_SCULPT_CURVES: return OB_MODE_SCULPT_CURVES; + case PAINT_MODE_GPENCIL: + return OB_MODE_PAINT_GREASE_PENCIL; case PAINT_MODE_INVALID: default: return OB_MODE_OBJECT; diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 59ca7cd4301..3b7548f814d 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -647,6 +647,20 @@ template class VArrayCommon { } return impl_->size(); } + /** + * Get the first element. + */ + T first() const + { + return (*this)[0]; + } + /** + * Get the nth last element. + */ + T last(const int64_t n = 0) const + { + return (*this)[this->size() - 1 - n]; + } /** True when the size is zero or when there is no virtual array. */ bool is_empty() const diff --git a/source/blender/draw/engines/gpencil/gpencil_layer.hh b/source/blender/draw/engines/gpencil/gpencil_layer.hh index 4af2e191549..cd085774300 100644 --- a/source/blender/draw/engines/gpencil/gpencil_layer.hh +++ b/source/blender/draw/engines/gpencil/gpencil_layer.hh @@ -36,7 +36,7 @@ class LayerModule { { /* TODO(fclem): All of this is placeholder. */ gpLayer gp_layer; - gp_layer.vertex_color_opacity = 0.0f; + // gp_layer.vertex_color_opacity = 0.0f; unused gp_layer.thickness_offset = 0.0f; gp_layer.tint = float4(1.0f, 1.0f, 1.0f, 0.0f); gp_layer.stroke_index_offset = 0.0f; diff --git a/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl b/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl index b123931642c..f931c15092b 100644 --- a/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl +++ b/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl @@ -8,7 +8,7 @@ void gpencil_color_output(vec4 stroke_col, vec4 vert_col, float vert_strength, f { /* Mix stroke with other colors. */ vec4 mixed_col = stroke_col; - mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a * gpVertexColorOpacity); + mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a); mixed_col.rgb = mix(mixed_col.rgb, gpLayerTint.rgb, gpLayerTint.a); mixed_col.a *= vert_strength * gpLayerOpacity; /** diff --git a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc index 4f7ffd2701c..7d3776f6f9d 100644 --- a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -256,13 +256,6 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets)); } - if (grease_pencil.runtime->has_stroke_buffer()) { - const int num_buffer_points = grease_pencil.runtime->stroke_buffer().size(); - total_verts_num += 1 + num_buffer_points + 1; - total_triangles_num += num_buffer_points * 2; - /* TODO: triangles for stroke buffer. */ - } - static GPUVertFormat format_edit_points_pos = {0}; if (format_edit_points_pos.attr_len == 0) { GPU_vertformat_attr_add(&format_edit_points_pos, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -316,6 +309,8 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr const VArray cyclic = curves.cyclic(); const VArray radii = drawing.radii(); const VArray opacities = drawing.opacities(); + const VArray vertex_colors = *attributes.lookup_or_default( + "vertex_color", ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); /* Assumes that if the ".selection" attribute does not exist, all points are selected. */ const VArray selection_float = *attributes.lookup_or_default( ".selection", ATTR_DOMAIN_POINT, true); @@ -358,9 +353,8 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr /* TODO: Populate fill UVs. */ s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0; - /* TODO: Populate vertex color and fill color. */ - copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); - copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); + copy_v4_v4(c_vert.vcol, vertex_colors[point_i]); + copy_v4_v4(c_vert.fcol, vertex_colors[point_i]); c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f; int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT; @@ -424,51 +418,6 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr }); } - if (grease_pencil.runtime->has_stroke_buffer()) { - Span points = grease_pencil.runtime->stroke_buffer(); - const int verts_start_offset = v_offset; - const int num_verts = 1 + points.size() + 1; - IndexRange verts_range = IndexRange(verts_start_offset, num_verts); - MutableSpan verts_slice = verts.slice(verts_range); - MutableSpan cols_slice = cols.slice(verts_range); - const int material_nr = grease_pencil.runtime->stroke_cache.mat; - - verts_slice.first().mat = -1; - for (const int i : IndexRange(points.size())) { - const int idx = i + 1; - GreasePencilStrokeVert &s_vert = verts_slice[idx]; - GreasePencilColorVert &c_vert = cols_slice[idx]; - const bke::greasepencil::StrokePoint &point = points[i]; - - copy_v3_v3(s_vert.pos, point.position); - s_vert.radius = point.radius; - s_vert.opacity = point.opacity; - s_vert.point_id = verts_range[idx]; - s_vert.stroke_id = verts_range.first(); - s_vert.mat = material_nr; - - /* TODO */ - s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(0.0f, 1.0f, 1.0f); - /* TODO */ - s_vert.u_stroke = 0; - /* TODO */ - s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0; - - /* TODO */ - copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); - copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); - - /* TODO */ - c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f; - - int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT; - GPU_indexbuf_add_tri_verts(&ibo, v_mat + 0, v_mat + 1, v_mat + 2); - GPU_indexbuf_add_tri_verts(&ibo, v_mat + 2, v_mat + 1, v_mat + 3); - } - - verts_slice.last().mat = -1; - } - /* Mark last 2 verts as invalid. */ verts[total_verts_num + 0].mat = -1; verts[total_verts_num + 1].mat = -1; diff --git a/source/blender/editors/grease_pencil/CMakeLists.txt b/source/blender/editors/grease_pencil/CMakeLists.txt index 8c7aefd2414..bf190ac68e8 100644 --- a/source/blender/editors/grease_pencil/CMakeLists.txt +++ b/source/blender/editors/grease_pencil/CMakeLists.txt @@ -11,6 +11,8 @@ set(INC ../../imbuf ../../makesrna ../../windowmanager + ../../bmesh + ../../../../extern/curve_fit_nd # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -22,9 +24,11 @@ set(SRC intern/grease_pencil_add.cc intern/grease_pencil_edit.cc intern/grease_pencil_frames.cc + intern/grease_pencil_geom.cc intern/grease_pencil_layers.cc intern/grease_pencil_ops.cc intern/grease_pencil_select.cc + intern/grease_pencil_utils.cc ) set(LIB @@ -32,6 +36,7 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::guardedalloc + extern_curve_fit_nd ) blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") 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 54db7c8eacd..3f6b4ad1961 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -237,7 +237,8 @@ void gaussian_blur_1D(const GSpan src, bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); /* Reduces unnecessary code generation. */ - if constexpr (std::is_same_v || std::is_same_v) { + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { gaussian_blur_1D(src.typed(), iterations, influence, @@ -394,56 +395,52 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot) /** \name Simplify Stroke Operator * \{ */ -/** - * An implementation of the Ramer-Douglas-Peucker algorithm. - * - * \param range: The range to simplify. - * \param epsilon: The threshold distance from the coord between two points for when a point - * in-between needs to be kept. - * \param dist_function: A function that computes the distance to a point at an index in the range. - * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. - * \param points_to_delete: Writes true to the indecies for which the points should be removed. - * \returns the total number of points to remove. - */ -int64_t ramer_douglas_peucker_simplify(const IndexRange range, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) +static float dist_to_interpolated( + float3 pos, float3 posA, float3 posB, float val, float valA, float valB) { - /* Mark all points to not be removed. */ - points_to_delete.slice(range).fill(false); - int64_t total_points_to_remove = 0; + float dist1 = math::distance_squared(posA, pos); + float dist2 = math::distance_squared(posB, pos); - Stack stack; - stack.push(range); - while (!stack.is_empty()) { - const IndexRange sub_range = stack.pop(); + if (dist1 + dist2 > 0) { + float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2)); + return math::distance(interpolated_val, val); + } + return 0.0f; +} - /* Compute the maximum distance and the corresponding distance. */ - float max_dist = -1.0f; - int max_index = -1; - for (const int64_t sub_index : sub_range.index_range().drop_front(1).drop_back(1)) { - const float dist = dist_function(sub_range, sub_index); - if (dist > max_dist) { - max_dist = dist; - max_index = sub_index; - } - } +static int64_t stroke_simplify(const IndexRange points, + const bool cyclic, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + int64_t total_points_to_delete = 0; + const Span curve_selection = points_to_delete.slice(points); + if (!curve_selection.contains(true)) { + return total_points_to_delete; + } - if (max_dist > epsilon) { - /* Found point outside the epsilon-sized strip. Repeat the search on the left & right side. - */ - stack.push(sub_range.slice(IndexRange(max_index + 1))); - stack.push(sub_range.slice(IndexRange(max_index, sub_range.size() - max_index))); - } - else { - /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ - const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); - total_points_to_remove += inside_range.size(); - points_to_delete.slice(inside_range).fill(true); + const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last()); + + const Vector selection_ranges = array_utils::find_all_ranges(curve_selection, true); + threading::parallel_for( + selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) { + for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) { + total_points_to_delete += ramer_douglas_peucker_simplify( + range.shift(points.start()), epsilon, dist_function, points_to_delete); + } + }); + + /* For cyclic curves, simplify the last segment. */ + if (cyclic && points.size() > 2 && is_last_segment_selected) { + const float dist = dist_function(points.last(1), points.first(), points.last()); + if (dist <= epsilon) { + points_to_delete[points.last()] = true; + total_points_to_delete++; } } - return total_points_to_remove; + + return total_points_to_delete; } static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) @@ -468,14 +465,31 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) } const Span positions = curves.positions(); + const VArray radii = drawing.radii(); - /* Distance function for `ramer_douglas_peucker_simplify`. */ - auto dist_func = [&](IndexRange range, int64_t index_in_range) { - const Span position_slice = positions.slice(range); - const float dist_position = dist_to_line_v3( - position_slice[index_in_range], position_slice.first(), position_slice.last()); - return dist_position; - }; + /* Distance functions for `ramer_douglas_peucker_simplify`. */ + const auto dist_function_positions = + [positions](int64_t first_index, int64_t last_index, int64_t index) { + const float dist_position = dist_to_line_v3( + positions[index], positions[first_index], positions[last_index]); + return dist_position; + }; + const auto dist_function_positions_and_radii = + [positions, radii](int64_t first_index, int64_t last_index, int64_t index) { + const float dist_position = dist_to_line_v3( + positions[index], positions[first_index], positions[last_index]); + /* We divide the distance by 2000.0f to convert from "pixels" to an actual distance. + * For some reason, grease pencil strokes the thickness of strokes in pixels rather + * than object space distance. */ + const float dist_radii = dist_to_interpolated(positions[index], + positions[first_index], + positions[last_index], + radii[index], + radii[first_index], + radii[last_index]) / + 2000.0f; + return math::max(dist_position, dist_radii); + }; const VArray cyclic = curves.cyclic(); const OffsetIndices points_by_curve = curves.points_by_curve(); @@ -486,42 +500,30 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) selection.materialize(points_to_delete); std::atomic total_points_to_delete = 0; - threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - const Span curve_selection = points_to_delete.as_span().slice(points); - if (!curve_selection.contains(true)) { - continue; + if (radii.is_single()) { + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + total_points_to_delete += stroke_simplify(points, + cyclic[curve_i], + epsilon, + dist_function_positions, + points_to_delete.as_mutable_span()); } - - const bool is_last_segment_selected = (curve_selection.first() && - curve_selection.last()); - - const Vector selection_ranges = array_utils::find_all_ranges( - curve_selection, true); - threading::parallel_for( - selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) { - for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) - { - total_points_to_delete += ramer_douglas_peucker_simplify( - range.shift(points.start()), - epsilon, - dist_func, - points_to_delete.as_mutable_span()); - } - }); - - /* For cyclic curves, simplify the last segment. */ - if (cyclic[curve_i] && curves.points_num() > 2 && is_last_segment_selected) { - const float dist = dist_to_line_v3( - positions[points.last()], positions[points.last(1)], positions[points.first()]); - if (dist <= epsilon) { - points_to_delete[points.last()] = true; - total_points_to_delete++; - } + }); + } + else if (radii.is_span()) { + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + total_points_to_delete += stroke_simplify(points, + cyclic[curve_i], + epsilon, + dist_function_positions_and_radii, + points_to_delete.as_mutable_span()); } - } - }); + }); + } if (total_points_to_delete > 0) { IndexMaskMemory memory; @@ -554,7 +556,7 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* Simplify parameters. */ - prop = RNA_def_float(ot->srna, "factor", 0.001f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); + prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc new file mode 100644 index 00000000000..588f83f0674 --- /dev/null +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc @@ -0,0 +1,141 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edgreasepencil + */ + +#include + +#include "BLI_stack.hh" + +#include "BKE_grease_pencil.hh" + +#include "ED_grease_pencil.hh" + +extern "C" { +#include "curve_fit_nd.h" +} + +namespace blender::ed::greasepencil { + +/** + * An implementation of the Ramer-Douglas-Peucker algorithm. + * + * \param range: The range to simplify. + * \param epsilon: The threshold distance from the coord between two points for when a point + * in-between needs to be kept. + * \param dist_function: A function that computes the distance to a point at an index in the range. + * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. + * \param points_to_delete: Writes true to the indecies for which the points should be removed. + * \returns the total number of points to remove. + */ +int64_t ramer_douglas_peucker_simplify( + const IndexRange range, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + /* Mark all points to not be removed. */ + points_to_delete.slice(range).fill(false); + int64_t total_points_to_remove = 0; + + Stack stack; + stack.push(range); + while (!stack.is_empty()) { + const IndexRange sub_range = stack.pop(); + /* Skip ranges with less than 3 points. All points are kept. */ + if (sub_range.size() < 3) { + continue; + } + const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); + /* Compute the maximum distance and the corresponding index. */ + float max_dist = -1.0f; + int max_index = -1; + for (const int64_t index : inside_range) { + const float dist = dist_function(sub_range.first(), sub_range.last(), index); + if (dist > max_dist) { + max_dist = dist; + max_index = index - sub_range.first(); + } + } + + if (max_dist > epsilon) { + /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat + * the search on the left & right side. */ + stack.push(sub_range.slice(0, max_index + 1)); + stack.push(sub_range.slice(max_index, sub_range.size() - max_index)); + } + else { + /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ + total_points_to_remove += inside_range.size(); + points_to_delete.slice(inside_range).fill(true); + } + } + return total_points_to_remove; +} + +Array polyline_fit_curve(Span points, + const float error_threshold, + const IndexMask &corner_mask) +{ + Array indices(corner_mask.size()); + corner_mask.to_indices(indices.as_mutable_span()); + uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast(indices.data()); + + float *r_cubic_array; + uint r_cubic_array_len; + int error = curve_fit_cubic_to_points_fl(*points.data(), + points.size(), + 2, + error_threshold, + CURVE_FIT_CALC_HIGH_QUALIY, + indicies_ptr, + indices.size(), + &r_cubic_array, + &r_cubic_array_len, + nullptr, + nullptr, + nullptr); + + if (error != 0) { + /* Some error occured. Return. */ + return {}; + } + + Span r_cubic_array_span(reinterpret_cast(r_cubic_array), + r_cubic_array_len * 3); + + Array curve_positions(r_cubic_array_span); + return curve_positions; +} + +IndexMask polyline_detect_corners(Span points, + const float radius_min, + const float radius_max, + const int samples_max, + const float angle_threshold, + IndexMaskMemory &memory) +{ + uint *r_corners; + uint r_corner_len; + const int error = curve_fit_corners_detect_fl(*points.data(), + points.size(), + float2::type_length, + radius_min, + radius_max, + samples_max, + angle_threshold, + &r_corners, + &r_corner_len); + if (error != 0) { + /* Error occured, return. */ + return IndexMask(); + } + BLI_assert(samples_max < std::numeric_limits::max()); + Span indices(reinterpret_cast(r_corners), r_corner_len); + return IndexMask::from_indices(indices, memory); +} + +} // namespace blender::ed::greasepencil diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc new file mode 100644 index 00000000000..145b9f78d37 --- /dev/null +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -0,0 +1,79 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edgreasepencil + */ + +#include "BKE_brush.hh" +#include "BKE_context.h" +#include "BKE_grease_pencil.hh" + +#include "BLI_math_vector.hh" + +#include "DNA_brush_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "ED_grease_pencil.hh" +#include "ED_view3d.hh" + +namespace blender::ed::greasepencil { + +static float3 drawing_origin(const Scene *scene, const Object *object, char align_flag) +{ + BLI_assert(object != nullptr && object->type == OB_GREASE_PENCIL); + if (align_flag & GP_PROJECT_VIEWSPACE) { + if (align_flag & GP_PROJECT_CURSOR) { + return float3(scene->cursor.location); + } + /* Use the object location. */ + return float3(object->object_to_world[3]); + } + return float3(scene->cursor.location); +} + +static float3 screen_space_to_3d( + const Scene *scene, const ARegion *region, const View3D *v3d, const Object *object, float2 co) +{ + float3 origin = drawing_origin(scene, object, scene->toolsettings->gpencil_v3d_align); + float3 r_co; + ED_view3d_win_to_3d(v3d, region, origin, co, r_co); + return r_co; +} + +float brush_radius_world_space(bContext &C, int x, int y) +{ + ARegion *region = CTX_wm_region(&C); + View3D *v3d = CTX_wm_view3d(&C); + Scene *scene = CTX_data_scene(&C); + Object *object = CTX_data_active_object(&C); + Brush *brush = scene->toolsettings->gp_paint->paint.brush; + + /* Default radius. */ + float radius = 2.0f; + if (brush == nullptr || object->type != OB_GREASE_PENCIL) { + return radius; + } + + /* Use an (arbitrary) screen space offset in the x direction to measure the size. */ + const int x_offest = 64; + const float brush_size = float(BKE_brush_size_get(scene, brush)); + + /* Get two 3d coordinates to measure the distance from. */ + const float2 screen1(x, y); + const float2 screen2(x + x_offest, y); + const float3 pos1 = screen_space_to_3d(scene, region, v3d, object, screen1); + const float3 pos2 = screen_space_to_3d(scene, region, v3d, object, screen2); + + /* Clip extreme zoom level (and avoid division by zero). */ + const float distance = math::max(math::distance(pos1, pos2), 0.001f); + + /* Calculate the radius of the brush in world space. */ + radius = (1.0f / distance) * (brush_size / 64.0f); + + return radius; +} + +} // namespace blender::ed::greasepencil diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index ac96d3b0f87..3ae9a37473b 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -12,6 +12,7 @@ #include "BKE_grease_pencil.hh" #include "BLI_generic_span.hh" +#include "BLI_index_mask.hh" #include "BLI_math_matrix_types.hh" #include "ED_keyframes_edit.hh" @@ -101,6 +102,8 @@ bool has_any_frame_selected(const bke::greasepencil::Layer &layer); void create_keyframe_edit_data_selected_frames_list(KeyframeEditData *ked, const bke::greasepencil::Layer &layer); +float brush_radius_world_space(bContext &C, int x, int y); + bool active_grease_pencil_poll(bContext *C); bool editable_grease_pencil_poll(bContext *C); bool editable_grease_pencil_point_selection_poll(bContext *C); @@ -120,7 +123,18 @@ void gaussian_blur_1D(const GSpan src, int64_t ramer_douglas_peucker_simplify(IndexRange range, float epsilon, - FunctionRef dist_function, + FunctionRef dist_function, MutableSpan dst); +Array polyline_fit_curve(Span points, + float error_threshold, + const IndexMask &corner_mask); + +IndexMask polyline_detect_corners(Span points, + float radius_min, + float radius_max, + int samples_max, + float angle_threshold, + IndexMaskMemory &memory); + } // namespace blender::ed::greasepencil 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 10f6cc7b52f..4579886d877 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -4,6 +4,7 @@ #include "BKE_context.h" #include "BKE_grease_pencil.hh" +#include "BKE_report.h" #include "DEG_depsgraph_query.hh" @@ -115,16 +116,49 @@ static void stroke_done(const bContext *C, PaintStroke *stroke) GreasePencilStrokeOperation *operation = static_cast( paint_stroke_mode_data(stroke)); operation->on_stroke_done(*C); + operation->~GreasePencilStrokeOperation(); } static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + const Scene *scene = CTX_data_scene(C); + const Object *object = CTX_data_active_object(C); + if (!object || object->type != OB_GREASE_PENCIL) { + return OPERATOR_CANCELLED; + } + + GreasePencil &grease_pencil = *static_cast(object->data); + if (!grease_pencil.has_active_layer()) { + BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer"); + return OPERATOR_CANCELLED; + } + const Paint *paint = BKE_paint_get_active_from_context(C); const Brush *brush = BKE_paint_brush_for_read(paint); if (brush == nullptr) { return OPERATOR_CANCELLED; } + const int current_frame = scene->r.cfra; + if (grease_pencil.get_active_layer()->drawing_index_at(current_frame) == -1) { + if (!IS_AUTOKEY_ON(scene)) { + BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); + return OPERATOR_CANCELLED; + } + const ToolSettings *ts = CTX_data_tool_settings(C); + bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer_for_write(); + if ((ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) != 0) { + /* For additive drawing, we duplicate the frame that's currently visible and insert it at the + * current frame. */ + grease_pencil.insert_duplicate_frame( + active_layer, active_layer.frame_key_at(current_frame), current_frame, false); + } + else { + /* Otherwise we just insert a blank keyframe. */ + grease_pencil.insert_blank_frame(active_layer, current_frame, 0, BEZT_KEYTYPE_KEYFRAME); + } + } + op->customdata = paint_stroke_new(C, op, stroke_get_location, diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc index 573e28184f9..1dc14288b88 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -2,14 +2,21 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BKE_brush.hh" +#include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_curves.hh" #include "BKE_grease_pencil.h" #include "BKE_grease_pencil.hh" +#include "BKE_material.h" #include "BKE_scene.h" +#include "BLI_length_parameterize.hh" +#include "BLI_math_geom.h" + #include "DEG_depsgraph_query.hh" +#include "ED_grease_pencil.hh" #include "ED_view3d.hh" #include "WM_api.hh" @@ -19,14 +26,80 @@ namespace blender::ed::sculpt_paint::greasepencil { +static constexpr float POINT_OVERRIDE_THRESHOLD_PX = 3.0f; +static constexpr float POINT_RESAMPLE_MIN_DISTANCE_PX = 10.0f; + +template +static inline void linear_interpolation(const T &a, const T &b, MutableSpan dst) +{ + dst.first() = a; + const float step = 1.0f / dst.size(); + for (const int i : dst.index_range().drop_front(1)) { + dst[i] = bke::attribute_math::mix2(i * step, a, b); + } +} + +/** Sample a bezier curve at a fixed resolution and return the sampled points in an array. */ +static Array sample_curve_2d(Span positions, const int64_t resolution) +{ + BLI_assert(positions.size() % 3 == 0); + const int64_t num_handles = positions.size() / 3; + const int64_t num_segments = num_handles - 1; + const int64_t num_points = num_segments * resolution; + + Array points(num_points); + const Span curve_segments = positions.drop_front(1).drop_back(1); + threading::parallel_for(IndexRange(num_segments), 32 * resolution, [&](const IndexRange range) { + for (const int64_t segment_i : range) { + IndexRange segment_range(segment_i * resolution, resolution); + bke::curves::bezier::evaluate_segment(curve_segments[segment_i * 3 + 0], + curve_segments[segment_i * 3 + 1], + curve_segments[segment_i * 3 + 2], + curve_segments[segment_i * 3 + 3], + points.as_mutable_span().slice(segment_range)); + } + }); + return points; +} + +/** Morph \a src onto \a target such that the points have the same spacing as in \a src and + * write the result to \a dst. */ +static void morph_points_to_curve(Span src, Span target, MutableSpan dst) +{ + BLI_assert(src.size() == dst.size()); + Array accumulated_lengths_src(src.size() - 1); + length_parameterize::accumulate_lengths(src, false, accumulated_lengths_src); + + Array accumulated_lengths_target(target.size() - 1); + length_parameterize::accumulate_lengths(target, false, accumulated_lengths_target); + + Array segment_indices(accumulated_lengths_src.size()); + Array segment_factors(accumulated_lengths_src.size()); + length_parameterize::sample_at_lengths( + accumulated_lengths_target, accumulated_lengths_src, segment_indices, segment_factors); + + length_parameterize::interpolate( + target, segment_indices, segment_factors, dst.drop_back(1)); + dst.last() = src.last(); +} + class PaintOperation : public GreasePencilStrokeOperation { + private: + Vector screen_space_coords_; + Vector> screen_space_curve_fitted_coords_; + Vector screen_space_smoothed_coords_; + int64_t active_smooth_index_ = 0; + + friend struct PaintOperationExecutor; public: - ~PaintOperation() override {} - 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; + + private: + void simplify_stroke(bke::greasepencil::Drawing &drawing, float epsilon_px); + void process_stroke_end(bke::greasepencil::Drawing &drawing); }; /** @@ -34,126 +107,430 @@ class PaintOperation : public GreasePencilStrokeOperation { * because it avoids passing a very large number of parameters between functions. */ struct PaintOperationExecutor { + ARegion *region_; + GreasePencil *grease_pencil_; - PaintOperationExecutor(const bContext & /*C*/) {} + Brush *brush_; + int brush_size_; + float brush_alpha_; - void execute(PaintOperation & /*self*/, const bContext &C, const InputSample &extension_sample) + BrushGpencilSettings *settings_; + float4 vertex_color_; + + bke::greasepencil::Drawing *drawing_; + + PaintOperationExecutor(const bContext &C) { - using namespace blender::bke; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); - ARegion *region = CTX_wm_region(&C); - Object *obact = CTX_data_active_object(&C); - Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); + Scene *scene = CTX_data_scene(&C); + region_ = CTX_wm_region(&C); + Object *object = CTX_data_active_object(&C); + grease_pencil_ = static_cast(object->data); - /** - * Note: We write to the evaluated object here, so that the additional copy from orig -> eval - * is not needed for every update. After the stroke is done, the result is written to the - * original object. - */ - GreasePencil &grease_pencil = *static_cast(ob_eval->data); + Paint *paint = &scene->toolsettings->gp_paint->paint; + brush_ = BKE_paint_brush(paint); + settings_ = brush_->gpencil_settings; + brush_size_ = BKE_brush_size_get(scene, brush_); + brush_alpha_ = BKE_brush_alpha_get(scene, brush_); - float4 plane{0.0f, -1.0f, 0.0f, 0.0f}; - float3 proj_pos; - ED_view3d_win_to_3d_on_plane(region, plane, extension_sample.mouse_position, false, proj_pos); + const bool use_vertex_color = (scene->toolsettings->gp_paint->mode == + GPPAINT_FLAG_USE_VERTEXCOLOR); + const bool use_vertex_color_stroke = use_vertex_color && ELEM(settings_->vertex_mode, + GPPAINT_MODE_STROKE, + GPPAINT_MODE_BOTH); + vertex_color_ = use_vertex_color_stroke ? float4(brush_->rgb[0], + brush_->rgb[1], + brush_->rgb[2], + settings_->vertex_factor) : + float4(0.0f); - bke::greasepencil::StrokePoint new_point{ - proj_pos, extension_sample.pressure * 100.0f, 1.0f, float4(1.0f)}; + // const bool use_vertex_color_fill = use_vertex_color && ELEM( + // brush->gpencil_settings->vertex_mode, GPPAINT_MODE_STROKE, GPPAINT_MODE_BOTH); - grease_pencil.runtime->stroke_cache.points.append(std::move(new_point)); + /* The object should have an active layer. */ + BLI_assert(grease_pencil_->has_active_layer()); + bke::greasepencil::Layer &active_layer = *grease_pencil_->get_active_layer_for_write(); + const int drawing_index = active_layer.drawing_index_at(scene->r.cfra); - BKE_grease_pencil_batch_cache_dirty_tag(&grease_pencil, BKE_GREASEPENCIL_BATCH_DIRTY_ALL); + /* Drawing should exist. */ + BLI_assert(drawing_index >= 0); + drawing_ = + &reinterpret_cast(grease_pencil_->drawing(drawing_index))->wrap(); + } + + float3 screen_space_to_object_space(const float2 co) + { + /* TODO: Use correct plane/projection. */ + const float4 plane{0.0f, -1.0f, 0.0f, 0.0f}; + /* TODO: Use object transform. */ + float3 proj_point; + ED_view3d_win_to_3d_on_plane(region_, plane, co, false, proj_point); + return proj_point; + } + + float radius_from_input_sample(const InputSample &sample) + { + float radius = brush_size_ / 2.0f; + if (BKE_brush_use_size_pressure(brush_)) { + radius *= BKE_curvemapping_evaluateF(settings_->curve_sensitivity, 0, sample.pressure); + } + return radius; + } + + float opacity_from_input_sample(const InputSample &sample) + { + float opacity = brush_alpha_; + if (BKE_brush_use_alpha_pressure(brush_)) { + opacity *= BKE_curvemapping_evaluateF(settings_->curve_strength, 0, sample.pressure); + } + return opacity; + } + + void process_start_sample(PaintOperation &self, + const InputSample &start_sample, + const int material_index) + { + const float2 start_coords = start_sample.mouse_position; + const float start_radius = this->radius_from_input_sample(start_sample); + const float start_opacity = this->opacity_from_input_sample(start_sample); + const ColorGeometry4f start_vertex_color = ColorGeometry4f(vertex_color_); + + self.screen_space_coords_.append(start_coords); + self.screen_space_curve_fitted_coords_.append(Vector({start_coords})); + self.screen_space_smoothed_coords_.append(start_coords); + + /* Resize the curves geometry so there is one more curve with a single point. */ + bke::CurvesGeometry &curves = drawing_->strokes_for_write(); + const int num_old_points = curves.points_num(); + curves.resize(curves.points_num() + 1, curves.curves_num() + 1); + curves.offsets_for_write().last(1) = num_old_points; + + curves.positions_for_write().last() = screen_space_to_object_space(start_coords); + drawing_->radii_for_write().last() = start_radius; + drawing_->opacities_for_write().last() = start_opacity; + drawing_->vertex_colors_for_write().last() = start_vertex_color; + + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + bke::SpanAttributeWriter materials = attributes.lookup_or_add_for_write_span( + "material_index", ATTR_DOMAIN_CURVE); + materials.span.last() = material_index; + materials.finish(); + + curves.curve_types_for_write().last() = CURVE_TYPE_POLY; + curves.update_curve_types(); + + drawing_->tag_topology_changed(); + } + + void active_smoothing(PaintOperation &self, + const IndexRange points, + const IndexRange smooth_window) + { + Span screen_space_coords_smooth_slice = self.screen_space_coords_.as_span().slice( + smooth_window); + + /* Detect corners in the current slice of coordinates. */ + const float corner_min_radius_px = 5.0f; + const float corner_max_radius_px = 30.0f; + const int64_t corner_max_samples = 64; + const float corner_angle_threshold = 0.6f; + IndexMaskMemory memory; + const IndexMask corner_mask = ed::greasepencil::polyline_detect_corners( + screen_space_coords_smooth_slice.drop_front(1).drop_back(1), + corner_min_radius_px, + corner_max_radius_px, + corner_max_samples, + corner_angle_threshold, + memory); + + /* Pre-blur the coordinates for the curve fitting. This generally leads to a better fit. */ + Array coords_pre_blur(smooth_window.size()); + const int pre_blur_iterations = 3; + ed::greasepencil::gaussian_blur_1D(screen_space_coords_smooth_slice, + pre_blur_iterations, + 1.0f, + 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; + Array curve_points = ed::greasepencil::polyline_fit_curve( + coords_pre_blur, max_error_threshold_px * settings_->active_smooth, corner_mask); + + /* Sampling the curve at a fixed resolution. */ + const int64_t sample_resolution = 32; + Array sampled_curve_points = sample_curve_2d(curve_points, sample_resolution); + + /* Morphing the coordinates onto the curve. Result is stored in a temporary array. */ + Array coords_smoothed(screen_space_coords_smooth_slice.size()); + morph_points_to_curve(screen_space_coords_smooth_slice, sampled_curve_points, coords_smoothed); + + MutableSpan smoothed_coords_slice = + self.screen_space_smoothed_coords_.as_mutable_span().slice(smooth_window); + MutableSpan positions_slice = + drawing_->strokes_for_write().positions_for_write().slice(points).slice(smooth_window); + const float converging_threshold_px = 0.1f; + bool stop_counting_converged = false; + int num_converged = 0; + for (const int64_t i : smooth_window.index_range()) { + /* Record the curve fitting of this point. */ + self.screen_space_curve_fitted_coords_[i].append(coords_smoothed[i]); + Span smoothed_coords_point = self.screen_space_curve_fitted_coords_[i]; + + /* Get the sum of all the curve fittings of this point. */ + float2 sum = smoothed_coords_point[0]; + for (const float2 v : smoothed_coords_point.drop_front(1).drop_back(1)) { + sum += v; + } + /* We compare the previous arithmetic mean to the current. Going from the back to the front, + * if a point hasn't moved by a minimum threshold, it counts as converged. */ + float2 new_pos = (sum + smoothed_coords_point.last()) / smoothed_coords_point.size(); + if (!stop_counting_converged) { + float2 prev_pos = sum / (smoothed_coords_point.size() - 1); + if (math::distance(new_pos, prev_pos) < converging_threshold_px) { + num_converged++; + } + else { + stop_counting_converged = true; + } + } + + /* Update the positions in the current cache. */ + smoothed_coords_slice[i] = new_pos; + positions_slice[i] = screen_space_to_object_space(new_pos); + } + + /* Remove all the converged points from the active window and shrink the window accordingly. */ + if (num_converged > 0) { + self.active_smooth_index_ = math::min(self.active_smooth_index_ + int64_t(num_converged), + int64_t(drawing_->strokes().points_num() - 1)); + if (self.screen_space_curve_fitted_coords_.size() - num_converged > 0) { + self.screen_space_curve_fitted_coords_.remove(0, num_converged); + } + else { + self.screen_space_curve_fitted_coords_.clear(); + } + } + } + + void process_extension_sample(PaintOperation &self, + const InputSample &extension_sample, + const int curve_index) + { + const float2 coords = extension_sample.mouse_position; + const float radius = this->radius_from_input_sample(extension_sample); + const float opacity = this->opacity_from_input_sample(extension_sample); + const ColorGeometry4f vertex_color = ColorGeometry4f(vertex_color_); + + bke::CurvesGeometry &curves = drawing_->strokes_for_write(); + + const float2 prev_coords = self.screen_space_coords_.last(); + const float prev_radius = drawing_->radii().last(); + const float prev_opacity = drawing_->opacities().last(); + const ColorGeometry4f prev_vertex_color = drawing_->vertex_colors().last(); + + /* Overwrite last point if it's very close. */ + if (math::distance(coords, prev_coords) < POINT_OVERRIDE_THRESHOLD_PX) { + curves.positions_for_write().last() = screen_space_to_object_space(coords); + drawing_->radii_for_write().last() = math::max(radius, prev_radius); + drawing_->opacities_for_write().last() = math::max(opacity, prev_opacity); + return; + } + + /* If the next sample is far away, we subdivide the segment to add more points. */ + int new_points_num = 1; + const float distance_px = math::distance(coords, prev_coords); + if (distance_px > POINT_RESAMPLE_MIN_DISTANCE_PX) { + const int subdivisions = int(math::floor(distance_px / POINT_RESAMPLE_MIN_DISTANCE_PX)) - 1; + new_points_num += subdivisions; + } + + /* Resize the curves geometry. */ + const int old_point_num = curves.points_num(); + curves.resize(curves.points_num() + new_points_num, curves.curves_num()); + curves.offsets_for_write().last() = curves.points_num(); + + /* Subdivide stroke in new_range. */ + IndexRange new_range(old_point_num, new_points_num); + Array new_screen_space_coords(new_points_num); + MutableSpan new_radii = drawing_->radii_for_write().slice(new_range); + MutableSpan new_opacities = drawing_->opacities_for_write().slice(new_range); + MutableSpan new_vertex_colors = drawing_->vertex_colors_for_write().slice( + new_range); + linear_interpolation(prev_coords, coords, new_screen_space_coords); + linear_interpolation(prev_radius, radius, new_radii); + linear_interpolation(prev_opacity, opacity, new_opacities); + linear_interpolation(prev_vertex_color, vertex_color, new_vertex_colors); + + /* Update screen space buffers with new points. */ + self.screen_space_coords_.extend(new_screen_space_coords); + self.screen_space_smoothed_coords_.extend(new_screen_space_coords); + for (float2 new_position : new_screen_space_coords) { + self.screen_space_curve_fitted_coords_.append(Vector({new_position})); + } + + /* Only start smoothing if there are enough points. */ + const int64_t min_active_smoothing_points_num = 8; + const IndexRange points = curves.points_by_curve()[curve_index]; + if (points.size() < min_active_smoothing_points_num) { + MutableSpan positions_slice = curves.positions_for_write().slice(new_range); + for (const int64_t i : new_screen_space_coords.index_range()) { + positions_slice[i] = screen_space_to_object_space(new_screen_space_coords[i]); + } + return; + } + + /* Active smoothing is done in a window at the end of the new stroke. */ + this->active_smoothing( + self, points, points.index_range().drop_front(self.active_smooth_index_)); + } + + void execute(PaintOperation &self, const InputSample &extension_sample) + { + /* New curve was created in `process_start_sample`.*/ + const int curve_index = drawing_->strokes().curves_range().last(); + this->process_extension_sample(self, extension_sample, curve_index); + drawing_->tag_topology_changed(); } }; -void PaintOperation::on_stroke_begin(const bContext & /*C*/, const InputSample & /*start_sample*/) +void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) { + Scene *scene = CTX_data_scene(&C); + Object *object = CTX_data_active_object(&C); + GreasePencil *grease_pencil = static_cast(object->data); + + Paint *paint = &scene->toolsettings->gp_paint->paint; + Brush *brush = BKE_paint_brush(paint); + + BKE_curvemapping_init(brush->gpencil_settings->curve_sensitivity); + BKE_curvemapping_init(brush->gpencil_settings->curve_strength); + 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); + + Material *material = BKE_grease_pencil_object_material_ensure_from_active_input_brush( + CTX_data_main(&C), object, brush); + const int material_index = BKE_grease_pencil_object_material_index_get(object, material); + + PaintOperationExecutor executor{C}; + executor.process_start_sample(*this, start_sample, material_index); + + DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, grease_pencil); } void PaintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) { + Object *object = CTX_data_active_object(&C); + GreasePencil *grease_pencil = static_cast(object->data); + PaintOperationExecutor executor{C}; - executor.execute(*this, C, extension_sample); + executor.execute(*this, extension_sample); + + DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, grease_pencil); +} + +static float dist_to_interpolated_2d( + float2 pos, float2 posA, float2 posB, float val, float valA, float valB) +{ + const float dist1 = math::distance_squared(posA, pos); + const float dist2 = math::distance_squared(posB, pos); + + if (dist1 + dist2 > 1e-5f) { + const float interpolated_val = math::interpolate(valB, valA, dist1 / (dist1 + dist2)); + return math::distance(interpolated_val, val); + } + return 0.0f; +} + +void PaintOperation::simplify_stroke(bke::greasepencil::Drawing &drawing, const float epsilon_px) +{ + const int stroke_index = drawing.strokes().curves_range().last(); + const IndexRange points = drawing.strokes().points_by_curve()[stroke_index]; + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const VArray radii = drawing.radii(); + + /* Distance function for `ramer_douglas_peucker_simplify`. */ + const Span positions_2d = this->screen_space_smoothed_coords_.as_span(); + const auto dist_function = [&](int64_t first_index, int64_t last_index, int64_t index) { + /* 2D coordinates are only stored for the current stroke, so offset the indices. */ + const float dist_position_px = dist_to_line_segment_v2( + positions_2d[index - points.first()], + positions_2d[first_index - points.first()], + positions_2d[last_index - points.first()]); + const float dist_radii_px = dist_to_interpolated_2d(positions_2d[index - points.first()], + positions_2d[first_index - points.first()], + positions_2d[last_index - points.first()], + radii[index], + radii[first_index], + radii[last_index]); + return math::max(dist_position_px, dist_radii_px); + }; + + Array points_to_delete(curves.points_num(), false); + int64_t total_points_to_delete = ed::greasepencil::ramer_douglas_peucker_simplify( + points, epsilon_px, dist_function, points_to_delete.as_mutable_span()); + + if (total_points_to_delete > 0) { + IndexMaskMemory memory; + curves.remove_points(IndexMask::from_bools(points_to_delete, memory)); + } +} + +void PaintOperation::process_stroke_end(bke::greasepencil::Drawing &drawing) +{ + const int stroke_index = drawing.strokes().curves_range().last(); + const IndexRange points = drawing.strokes().points_by_curve()[stroke_index]; + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const VArray radii = drawing.radii(); + + /* Remove points at the end that have a radius close to 0. */ + int64_t points_to_remove = 0; + for (int64_t index = points.last(); index >= points.first(); index--) { + if (radii[index] < 1e-5f) { + points_to_remove++; + } + else { + break; + } + } + if (points_to_remove > 0) { + curves.resize(curves.points_num() - points_to_remove, curves.curves_num()); + curves.offsets_for_write().last() = curves.points_num(); + } } void PaintOperation::on_stroke_done(const bContext &C) { using namespace blender::bke; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); Scene *scene = CTX_data_scene(&C); - Object *obact = CTX_data_active_object(&C); - Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); + Object *object = CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(object->data); - GreasePencil &grease_pencil_orig = *static_cast(obact->data); - GreasePencil &grease_pencil_eval = *static_cast(ob_eval->data); - BLI_assert(grease_pencil_orig.has_active_layer()); - const bke::greasepencil::Layer &active_layer_orig = *grease_pencil_orig.get_active_layer(); - int index_orig = active_layer_orig.drawing_index_at(scene->r.cfra); + /* Grease Pencil should have an active layer. */ + BLI_assert(grease_pencil.has_active_layer()); + bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer_for_write(); + const int drawing_index = active_layer.drawing_index_at(scene->r.cfra); - bke::greasepencil::Drawing &drawing_orig = - reinterpret_cast(grease_pencil_orig.drawing(index_orig))->wrap(); + /* Drawing should exist. */ + BLI_assert(drawing_index >= 0); + bke::greasepencil::Drawing &drawing = + reinterpret_cast(grease_pencil.drawing(drawing_index))->wrap(); - const Span stroke_points = - grease_pencil_eval.runtime->stroke_buffer(); - CurvesGeometry &curves = drawing_orig.strokes_for_write(); + const float simplifiy_threshold_px = 0.5f; + this->simplify_stroke(drawing, simplifiy_threshold_px); + this->process_stroke_end(drawing); + drawing.tag_topology_changed(); - int num_old_curves = curves.curves_num(); - int num_old_points = curves.points_num(); - curves.resize(num_old_points + stroke_points.size(), num_old_curves + 1); - - curves.offsets_for_write()[num_old_curves] = num_old_points; - curves.offsets_for_write()[num_old_curves + 1] = num_old_points + stroke_points.size(); - - const OffsetIndices points_by_curve = curves.points_by_curve(); - const IndexRange new_points_range = points_by_curve[curves.curves_num() - 1]; - const IndexRange new_curves_range = IndexRange(num_old_curves, 1); - - /* Set position, radius and opacity attribute. */ - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - MutableSpan positions = curves.positions_for_write(); - MutableSpan radii = drawing_orig.radii_for_write(); - MutableSpan opacities = drawing_orig.opacities_for_write(); - for (const int i : IndexRange(stroke_points.size())) { - const bke::greasepencil::StrokePoint &point = stroke_points[i]; - const int point_i = new_points_range[i]; - positions[point_i] = point.position; - radii[point_i] = point.radius; - opacities[point_i] = point.opacity; - } - - /* Set material index attribute. */ - int material_index = 0; - SpanAttributeWriter materials = attributes.lookup_or_add_for_write_span( - "material_index", ATTR_DOMAIN_CURVE); - - materials.span.slice(new_curves_range).fill(material_index); - - /* Set curve_type attribute. */ - curves.fill_curve_types(new_curves_range, CURVE_TYPE_POLY); - - /* Explicitly set all other attributes besides those processed above to default values. */ - Set attributes_to_skip{ - {"position", "radius", "opacity", "material_index", "curve_type"}}; - attributes.for_all( - [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { - if (attributes_to_skip.contains(id.name())) { - return true; - } - bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); - const CPPType &type = attribute.span.type(); - GMutableSpan new_data = attribute.span.slice( - attribute.domain == ATTR_DOMAIN_POINT ? new_points_range : new_curves_range); - type.fill_assign_n(type.default_value(), new_data.data(), new_data.size()); - attribute.finish(); - return true; - }); - - grease_pencil_eval.runtime->stroke_cache.clear(); - drawing_orig.tag_positions_changed(); - - materials.finish(); - - DEG_id_tag_update(&grease_pencil_orig.id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &grease_pencil_orig.id); + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &grease_pencil.id); } std::unique_ptr new_paint_operation() diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index 44d609eb387..1d237089f4a 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -16,6 +16,7 @@ #include "DNA_brush_types.h" #include "DNA_color_types.h" #include "DNA_customdata_types.h" +#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" @@ -28,6 +29,7 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_curve.h" +#include "BKE_grease_pencil.hh" #include "BKE_image.h" #include "BKE_node_runtime.hh" #include "BKE_object.h" @@ -41,6 +43,7 @@ #include "IMB_colormanagement.h" #include "IMB_imbuf_types.h" +#include "ED_grease_pencil.hh" #include "ED_image.hh" #include "ED_view3d.hh" @@ -1203,10 +1206,24 @@ static void SCULPT_layer_brush_height_preview_draw(const uint gpuattr, static bool paint_use_2d_cursor(ePaintMode mode) { - if (mode >= PAINT_MODE_TEXTURE_3D) { - return true; + switch (mode) { + case PAINT_MODE_SCULPT: + case PAINT_MODE_VERTEX: + case PAINT_MODE_WEIGHT: + return false; + case PAINT_MODE_TEXTURE_3D: + case PAINT_MODE_TEXTURE_2D: + case PAINT_MODE_SCULPT_UV: + case PAINT_MODE_VERTEX_GPENCIL: + case PAINT_MODE_SCULPT_GPENCIL: + case PAINT_MODE_WEIGHT_GPENCIL: + case PAINT_MODE_SCULPT_CURVES: + case PAINT_MODE_GPENCIL: + return true; + case PAINT_MODE_INVALID: + BLI_assert_unreachable(); } - return false; + return true; } enum PaintCursorDrawingType { @@ -1422,10 +1439,15 @@ static void paint_update_mouse_cursor(PaintCursorContext *pcontext) * with the UI (dragging a number button for e.g.), see: #102792. */ return; } - WM_cursor_set(pcontext->win, WM_CURSOR_PAINT); + if (pcontext->mode == PAINT_MODE_GPENCIL) { + WM_cursor_set(pcontext->win, WM_CURSOR_DOT); + } + else { + WM_cursor_set(pcontext->win, WM_CURSOR_PAINT); + } } -static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) +static void paint_draw_2D_view_brush_cursor_default(PaintCursorContext *pcontext) { immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha); @@ -1448,6 +1470,147 @@ static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) 40); } +static void grease_pencil_eraser_draw(PaintCursorContext *pcontext) +{ + float radius = static_cast(BKE_brush_size_get(pcontext->scene, pcontext->brush)); + + /* Redish color with alpha. */ + immUniformColor4ub(255, 100, 100, 20); + imm_draw_circle_fill_2d(pcontext->pos, pcontext->x, pcontext->y, radius, 40); + + immUnbindProgram(); + + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); + + float viewport_size[4]; + GPU_viewport_size_get_f(viewport_size); + immUniform2f("viewport_size", viewport_size[2], viewport_size[3]); + + immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f); + immUniform1i("colors_len", 0); /* "simple" mode */ + immUniform1f("dash_width", 12.0f); + immUniform1f("udash_factor", 0.5f); + + /* XXX Dashed shader gives bad results with sets of small segments + * currently, temp hack around the issue. :( */ + const int nsegments = max_ii(8, radius / 2); + imm_draw_circle_wire_2d(pcontext->pos, pcontext->x, pcontext->y, radius, nsegments); +} + +static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) +{ + using namespace blender; + if ((pcontext->region) && (pcontext->region->regiontype != RGN_TYPE_WINDOW)) { + return; + } + if (pcontext->region && !BLI_rcti_isect_pt(&pcontext->region->winrct, pcontext->x, pcontext->y)) + { + return; + } + + Object *object = CTX_data_active_object(pcontext->C); + if (object->type != OB_GREASE_PENCIL) { + return; + } + + /* default radius and color */ + float color[3] = {1.0f, 1.0f, 1.0f}; + float darkcolor[3]; + float radius = 2.0f; + + const int x = pcontext->x; + const int y = pcontext->y; + + /* for paint use paint brush size and color */ + if (pcontext->mode == PAINT_MODE_GPENCIL) { + Paint *paint = pcontext->paint; + Brush *brush = pcontext->brush; + if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) { + return; + } + + if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { + return; + } + + /* Eraser has a special shape and use a different shader program. */ + if (brush->gpencil_tool == GPAINT_TOOL_ERASE) { + grease_pencil_eraser_draw(pcontext); + return; + } + + /* Get current drawing material. */ + Material *ma = BKE_grease_pencil_object_material_from_brush_get(object, brush); + if (ma) { + MaterialGPencilStyle *gp_style = ma->gp_style; + + /* Follow user settings for the size of the draw cursor: + * - Fixed size, or + * - Brush size (i.e. stroke thickness) + */ + if ((gp_style) && ((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE) == 0) && + ((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) == 0) && + (brush->gpencil_tool == GPAINT_TOOL_DRAW)) + { + + const bool use_vertex_color = (pcontext->scene->toolsettings->gp_paint->mode == + GPPAINT_FLAG_USE_VERTEXCOLOR); + const bool use_vertex_color_stroke = use_vertex_color && + ELEM(brush->gpencil_settings->vertex_mode, + GPPAINT_MODE_STROKE, + GPPAINT_MODE_BOTH); + + radius = ed::greasepencil::brush_radius_world_space( + *pcontext->C, pcontext->x, pcontext->y); + + copy_v3_v3(color, use_vertex_color_stroke ? brush->rgb : gp_style->stroke_rgba); + } + } + } + + GPU_line_width(1.0f); + /* Inner Ring: Color from UI panel */ + immUniformColor4f(color[0], color[1], color[2], 0.8f); + imm_draw_circle_wire_2d(pcontext->pos, x, y, radius, 32); + + /* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */ + mul_v3_v3fl(darkcolor, color, 0.40f); + immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f); + imm_draw_circle_wire_2d(pcontext->pos, x, y, radius + 1, 32); + + /* Draw line for lazy mouse */ + /* TODO: No stabilize mode yet. */ + // if ((last_mouse_position) && + // (pcontext->xbrush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP)) + // { + // GPU_line_smooth(true); + // GPU_blend(GPU_BLEND_ALPHA); + + // copy_v3_v3(color, pcontext->brush->add_col); + // immUniformColor4f(color[0], color[1], color[2], 0.8f); + + // immBegin(GPU_PRIM_LINES, 2); + // immVertex2f(pos, x, y); + // immVertex2f(pos, + // last_mouse_position[0] + pcontext->region->winrct.xmin, + // last_mouse_position[1] + pcontext->region->winrct.ymin); + // immEnd(); + // } +} + +static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) +{ + switch (pcontext->mode) { + case PAINT_MODE_GPENCIL: { + grease_pencil_brush_cursor_draw(pcontext); + break; + } + default: { + paint_draw_2D_view_brush_cursor_default(pcontext); + } + } +} + static void paint_draw_legacy_3D_view_brush_cursor(PaintCursorContext *pcontext) { GPU_line_width(1.0f); @@ -1942,6 +2105,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void * /*unused*/) paint_draw_curve_cursor(pcontext.brush, &pcontext.vc); break; case PAINT_CURSOR_2D: + paint_update_mouse_cursor(&pcontext); + paint_cursor_update_rake_rotation(&pcontext); paint_cursor_check_and_draw_alpha_overlays(&pcontext); paint_cursor_update_anchored_location(&pcontext); diff --git a/source/blender/editors/sculpt_paint/paint_curve.cc b/source/blender/editors/sculpt_paint/paint_curve.cc index f3509836b5b..17fcfdd9872 100644 --- a/source/blender/editors/sculpt_paint/paint_curve.cc +++ b/source/blender/editors/sculpt_paint/paint_curve.cc @@ -686,6 +686,9 @@ static int paintcurve_draw_exec(bContext *C, wmOperator * /*op*/) case PAINT_MODE_SCULPT_CURVES: name = "SCULPT_CURVES_OT_brush_stroke"; break; + case PAINT_MODE_GPENCIL: + name = "GREASE_PENCIL_OT_brush_stroke"; + break; default: return OPERATOR_PASS_THROUGH; } diff --git a/source/blender/makesdna/DNA_grease_pencil_types.h b/source/blender/makesdna/DNA_grease_pencil_types.h index 1ee7b306a77..05a1ebcaa11 100644 --- a/source/blender/makesdna/DNA_grease_pencil_types.h +++ b/source/blender/makesdna/DNA_grease_pencil_types.h @@ -30,7 +30,6 @@ class Layer; class LayerRuntime; class LayerGroup; class LayerGroupRuntime; -struct StrokePoint; } // namespace greasepencil } // namespace blender::bke using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime; diff --git a/source/blender/makesrna/intern/rna_object.cc b/source/blender/makesrna/intern/rna_object.cc index da03be4a099..11dd4ee3868 100644 --- a/source/blender/makesrna/intern/rna_object.cc +++ b/source/blender/makesrna/intern/rna_object.cc @@ -1383,7 +1383,7 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value) Object *ob = reinterpret_cast(ptr->owner_id); Material *ma = static_cast(value.data); - if (ob->type == OB_GPENCIL_LEGACY) { + if (ELEM(ob->type, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL)) { /* GP Materials only */ return (ma->gp_style != nullptr); } diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index cc8bcdf7b99..fd39de58bbb 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -76,6 +76,7 @@ #include "ED_fileselect.hh" #include "ED_gpencil_legacy.hh" +#include "ED_grease_pencil.hh" #include "ED_numinput.hh" #include "ED_screen.hh" #include "ED_undo.hh" @@ -2356,14 +2357,24 @@ static void radial_control_set_initial_mouse(bContext *C, RadialControl *rc, con d[0] *= zoom[0]; d[1] *= zoom[1]; } + rc->scale_fac = 1.0f; /* Grease pencil draw tool needs to rescale the cursor size. If we don't do that - * the size of the radial is not equals to the actual stroke size. */ - if (rc->ptr.owner_id && GS(rc->ptr.owner_id->name) == ID_BR && rc->prop == &rna_Brush_size) { - rc->scale_fac = ED_gpencil_radial_control_scale( - C, (Brush *)rc->ptr.owner_id, rc->initial_value, event->mval); + * the size of the radial is not equal to the actual stroke size. */ + Object *object = CTX_data_active_object(C); + if (object->type == OB_GREASE_PENCIL && rc->prop == &rna_UnifiedPaintSettings_size) { + ToolSettings *ts = CTX_data_tool_settings(C); + const Brush &brush = *BKE_paint_brush_for_read(&ts->gp_paint->paint); + /* Only the draw brush needs the rescaling. */ + if (brush.gpencil_tool == GPAINT_TOOL_DRAW) { + float cursor_radius = blender::ed::greasepencil::brush_radius_world_space( + *C, event->mval[0], event->mval[1]); + rc->scale_fac = max_ff(cursor_radius, 1.0f) / max_ff(rc->initial_value, 1.0f); + } } - else { - rc->scale_fac = 1.0f; + else if (rc->ptr.owner_id && GS(rc->ptr.owner_id->name) == ID_BR && rc->prop == &rna_Brush_size) + { + Brush *brush = reinterpret_cast(rc->ptr.owner_id); + rc->scale_fac = ED_gpencil_radial_control_scale(C, brush, rc->initial_value, event->mval); } rc->initial_mouse[0] -= d[0];