GPv3: Initial drawing tool #110093

Merged
Falk David merged 78 commits from filedescriptor/blender:gpv3-drawing-operator into main 2023-10-06 10:50:04 +02:00
25 changed files with 1275 additions and 333 deletions

View File

@ -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')]}),

View File

@ -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

View File

@ -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,
)

View File

@ -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):

View File

@ -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<float3> result);
template<typename T>
void evaluate_segment(
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result);
/**
* Calculate all evaluated points for the Bezier curve.

View File

@ -11,6 +11,8 @@
#include <atomic>
#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<StrokePoint> points;
Vector<uint3> 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<float> opacities() const;
MutableSpan<float> opacities_for_write();
/**
* Vertex colors of the points. Default is black.
*/
VArray<ColorGeometry4f> vertex_colors() const;
MutableSpan<ColorGeometry4f> 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<bke::greasepencil::StrokePoint> 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);

View File

@ -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<float3> result)
template<typename T>
void evaluate_segment_ex(
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> 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<float3> result)
{
evaluate_segment_ex<float3>(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<float2> result)
{
evaluate_segment_ex<float2>(point_0, point_1, point_2, point_3, result);
}
void calculate_evaluated_positions(const Span<float3> positions,
const Span<float3> handles_left,

View File

@ -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<float> Drawing::opacities_for_write()
this->strokes_for_write(), ATTR_DOMAIN_POINT, ATTR_OPACITY, 1.0f);
}
VArray<ColorGeometry4f> Drawing::vertex_colors() const
{
return *this->strokes().attributes().lookup_or_default<ColorGeometry4f>(
ATTR_VERTEX_COLOR, ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
}
MutableSpan<ColorGeometry4f> Drawing::vertex_colors_for_write()
{
return get_mutable_attribute<ColorGeometry4f>(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::greasepencil::StrokePoint> blender::bke::GreasePencilRuntime::
stroke_buffer() const
{
return this->stroke_cache.points.as_span();
}
/** \} */
/* ------------------------------------------------------------------- */
/** \name Grease Pencil data-block API
* \{ */

View File

@ -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;

View File

@ -647,6 +647,20 @@ template<typename T> 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

View File

@ -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;

View File

@ -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;
/**

View File

@ -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<bool> cyclic = curves.cyclic();
const VArray<float> radii = drawing.radii();
const VArray<float> opacities = drawing.opacities();
const VArray<ColorGeometry4f> vertex_colors = *attributes.lookup_or_default<ColorGeometry4f>(
"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<float> selection_float = *attributes.lookup_or_default<float>(
".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<bke::greasepencil::StrokePoint> 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<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
MutableSpan<GreasePencilColorVert> 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;

View File

@ -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}")

View File

@ -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<T, float> || std::is_same_v<T, float3>) {
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, float2> ||
std::is_same_v<T, float3>) {
gaussian_blur_1D(src.typed<T>(),
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<float(IndexRange, int64_t)> dist_function,
MutableSpan<bool> 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<IndexRange> 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<float(int64_t, int64_t, int64_t)> dist_function,
MutableSpan<bool> points_to_delete)
{
int64_t total_points_to_delete = 0;
const Span<bool> 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<IndexRange> 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<float3> positions = curves.positions();
const VArray<float> radii = drawing.radii();
/* Distance function for `ramer_douglas_peucker_simplify`. */
auto dist_func = [&](IndexRange range, int64_t index_in_range) {
const Span<float3> 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) {
filedescriptor marked this conversation as resolved Outdated
  • devide -> divide
  • storkes -> strokes?
- `devide` -> `divide` - `storkes` -> `strokes`?
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<bool> cyclic = curves.cyclic();
const OffsetIndices<int> 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<int64_t> 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<bool> 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<IndexRange> 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);
}

View File

@ -0,0 +1,141 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgreasepencil
*/
#include <limits>
#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<float(int64_t, int64_t, int64_t)> dist_function,
MutableSpan<bool> 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<IndexRange> 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<float2> polyline_fit_curve(Span<float2> points,
const float error_threshold,
const IndexMask &corner_mask)
{
Array<int32_t> indices(corner_mask.size());
corner_mask.to_indices(indices.as_mutable_span());
uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast<uint *>(indices.data());
filedescriptor marked this conversation as resolved Outdated

uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast<uint *>(indices.data());

` uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast<uint *>(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<float2> r_cubic_array_span(reinterpret_cast<float2 *>(r_cubic_array),
r_cubic_array_len * 3);
Array<float2> curve_positions(r_cubic_array_span);
return curve_positions;
}
IndexMask polyline_detect_corners(Span<float2> 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<int>::max());
Span<int> indices(reinterpret_cast<int *>(r_corners), r_corner_len);
return IndexMask::from_indices<int>(indices, memory);
}
} // namespace blender::ed::greasepencil

View File

@ -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. */
filedescriptor marked this conversation as resolved Outdated

else after return

else after return
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);
filedescriptor marked this conversation as resolved
Review

This is unnecessary, the function is already in blender::ed::greasepencil

This is unnecessary, the function is already in `blender::ed::greasepencil`
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);
filedescriptor marked this conversation as resolved Outdated

static_cast<float>(...) -> float(...)

`static_cast<float>(...)` -> `float(...)`
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

View File

@ -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<float(IndexRange, int64_t)> dist_function,
FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
MutableSpan<bool> dst);
Array<float2> polyline_fit_curve(Span<float2> points,
filedescriptor marked this conversation as resolved Outdated

Suggest renaming these two functions for consistency:

polyline_fit_curve
polyline_detect_corners

Suggest renaming these two functions for consistency: `polyline_fit_curve` `polyline_detect_corners`
float error_threshold,
const IndexMask &corner_mask);
IndexMask polyline_detect_corners(Span<float2> points,
float radius_min,
float radius_max,
int samples_max,
filedescriptor marked this conversation as resolved Outdated

int64_t -> int?

`int64_t` -> `int`?
float angle_threshold,
IndexMaskMemory &memory);
} // namespace blender::ed::greasepencil

View File

@ -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<GreasePencilStrokeOperation *>(
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<GreasePencil *>(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();
filedescriptor marked this conversation as resolved Outdated

else after return

else after return
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,

View File

@ -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<typename T>
filedescriptor marked this conversation as resolved Outdated

STOKE_CACHE_ALLOCATION_CHUNK_SIZE is unused

`STOKE_CACHE_ALLOCATION_CHUNK_SIZE` is unused
static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
filedescriptor marked this conversation as resolved Outdated

Rather than re-implementing this, it probably makes sense to make bke::curves::evaluate_segment into a template and add explicit instantiations of the float2 and float3 types in the cc file.

Rather than re-implementing this, it probably makes sense to make `bke::curves::evaluate_segment` into a template and add explicit instantiations of the float2 and float3 types in the cc file.
{
dst.first() = a;
filedescriptor marked this conversation as resolved Outdated

I'd suggest:
Span<float2> curve_points -> Span<float2> positions

I'd suggest: `Span<float2> curve_points` -> `Span<float2> positions`
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<float2> sample_curve_2d(Span<float2> positions, const int64_t resolution)
{
filedescriptor marked this conversation as resolved Outdated

512 * 32 is 16k, that's huge, large enough that multithreading will probably never be used. What about 32 * resolution?

512 * 32 is 16k, that's huge, large enough that multithreading will probably never be used. What about `32 * resolution`?
BLI_assert(positions.size() % 3 == 0);
filedescriptor marked this conversation as resolved Outdated

Rather than evaluating a single segment at a time from here, how about giving the whole curve_points span to bke::curves::bezier? That's generally aligned with the goal of that module, to handle more than one thing at a time wherever possible.

Rather than evaluating a single segment at a time from here, how about giving the whole `curve_points` span to `bke::curves::bezier`? That's generally aligned with the goal of that module, to handle more than one thing at a time wherever possible.

If I am not mistaken that module currently only works with float3s.

If I am not mistaken that module currently only works with `float3`s.

Pretty sure you're mistaken :P It's all templated. Even the tests use float2 sometimes.

Pretty sure you're mistaken :P It's all templated. Even the tests use `float2` sometimes.

I just added a threading::parallel_for for now.

I just added a `threading::parallel_for` for now.
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<float2> points(num_points);
const Span<float2> 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],
filedescriptor marked this conversation as resolved Outdated

This still looks like it has the same goal as the code in BLI_length_parameterize.hh. I'd really like that to be reused here, this is too much tricky logic to duplicate IMO.

This still looks like it has the same goal as the code in `BLI_length_parameterize.hh`. I'd really like that to be reused here, this is too much tricky logic to duplicate IMO.
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<float2> src, Span<float2> target, MutableSpan<float2> dst)
{
BLI_assert(src.size() == dst.size());
Array<float> accumulated_lengths_src(src.size() - 1);
length_parameterize::accumulate_lengths<float2>(src, false, accumulated_lengths_src);
Array<float> accumulated_lengths_target(target.size() - 1);
length_parameterize::accumulate_lengths<float2>(target, false, accumulated_lengths_target);
Array<int> segment_indices(accumulated_lengths_src.size());
Array<float> segment_factors(accumulated_lengths_src.size());
length_parameterize::sample_at_lengths(
accumulated_lengths_target, accumulated_lengths_src, segment_indices, segment_factors);
filedescriptor marked this conversation as resolved Outdated

Didn't have a ton of time to dig into this function, but on the surface it looks like it has the same goal as the code in blender::length_parameterize. If so, I'd really hope that could be reused, since this stuff is pretty fiddly, especially with floating point error (though it looks like you're doing a good job dealing with that here :)

Didn't have a ton of time to dig into this function, but on the surface it looks like it has the same goal as the code in `blender::length_parameterize`. If so, I'd really hope that could be reused, since this stuff is pretty fiddly, especially with floating point error (though it looks like you're doing a good job dealing with that here :)
length_parameterize::interpolate<float2>(
target, segment_indices, segment_factors, dst.drop_back(1));
dst.last() = src.last();
}
class PaintOperation : public GreasePencilStrokeOperation {
private:
Vector<float2> screen_space_coords_;
Vector<Vector<float2>> screen_space_curve_fitted_coords_;
Vector<float2> 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<GreasePencil *>(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<GreasePencil *>(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<GreasePencilDrawing *>(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);
filedescriptor marked this conversation as resolved Outdated

Better not to mix this data together in a single struct IMO. It's clearer if the code is consistent about handling them separately. In practice, the way this function is used, the struct is just immediately unpacked anyway.

Since this code really does have to process just one point at a time, I'd suggest just separating radius, opacity, and colors into separate functions.

Better not to mix this data together in a single struct IMO. It's clearer if the code is consistent about handling them separately. In practice, the way this function is used, the struct is just immediately unpacked anyway. Since this code really does have to process just one point at a time, I'd suggest just separating radius, opacity, and colors into separate functions.
const ColorGeometry4f start_vertex_color = ColorGeometry4f(vertex_color_);
self.screen_space_coords_.append(start_coords);
self.screen_space_curve_fitted_coords_.append(Vector<float2>({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<int> materials = attributes.lookup_or_add_for_write_span<int>(
"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<float2> 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<float2> 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;
filedescriptor marked this conversation as resolved Outdated

IndexMask corner_mask -> const IndexMask corner_mask

`IndexMask corner_mask` -> `const IndexMask corner_mask`
Array<float2> curve_points = ed::greasepencil::polyline_fit_curve(
filedescriptor marked this conversation as resolved Outdated

Unnecessary blender::?

Unnecessary `blender::`?
coords_pre_blur, max_error_threshold_px * settings_->active_smooth, corner_mask);
/* Sampling the curve at a fixed resolution. */
filedescriptor marked this conversation as resolved Outdated

I'd suggest consistently abbreviating "coordinates" to "coords"

I'd suggest consistently abbreviating "coordinates" to "coords"
const int64_t sample_resolution = 32;
Array<float2> sampled_curve_points = sample_curve_2d(curve_points, sample_resolution);
/* Morphing the coordinates onto the curve. Result is stored in a temporary array. */
Array<float2> coords_smoothed(screen_space_coords_smooth_slice.size());
morph_points_to_curve(screen_space_coords_smooth_slice, sampled_curve_points, coords_smoothed);
MutableSpan<float2> smoothed_coords_slice =
self.screen_space_smoothed_coords_.as_mutable_span().slice(smooth_window);
MutableSpan<float3> 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<float2> 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);
filedescriptor marked this conversation as resolved Outdated

static_cast<int> -> int(...)

Same for float

`static_cast<int>` -> `int(...)` Same for float
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<float2> new_screen_space_coords(new_points_num);
filedescriptor marked this conversation as resolved Outdated

Declare variables const

Declare variables const
MutableSpan<float> new_radii = drawing_->radii_for_write().slice(new_range);
MutableSpan<float> new_opacities = drawing_->opacities_for_write().slice(new_range);
filedescriptor marked this conversation as resolved
Review

How about copying this from the subdivide curves node?

template<typename T>
static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> 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);
  }
}

With the same name reused everywhere it's easy to extract to some common header later on.

How about copying this from the subdivide curves node? ``` template<typename T> static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> 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); } } ``` With the same name reused everywhere it's easy to extract to some common header later on.
MutableSpan<ColorGeometry4f> new_vertex_colors = drawing_->vertex_colors_for_write().slice(
new_range);
linear_interpolation<float2>(prev_coords, coords, new_screen_space_coords);
linear_interpolation<float>(prev_radius, radius, new_radii);
linear_interpolation<float>(prev_opacity, opacity, new_opacities);
linear_interpolation<ColorGeometry4f>(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<float2>({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<float3> 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_));
filedescriptor marked this conversation as resolved Outdated

points.index_range().drop_front(self.active_smooth_index_) maybe?

`points.index_range().drop_front(self.active_smooth_index_)` maybe?
}
void execute(PaintOperation &self, const InputSample &extension_sample)
{
/* New curve was created in `process_start_sample`.*/
filedescriptor marked this conversation as resolved Outdated

Seems like it would be simpler to mix these directly into the new slice of the attribute.

Seems like it would be simpler to mix these directly into the new slice of the attribute.
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<GreasePencil *>(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<GreasePencil *>(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);
}
filedescriptor marked this conversation as resolved Outdated

math::interpolate

`math::interpolate`
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<float> radii = drawing.radii();
/* Distance function for `ramer_douglas_peucker_simplify`. */
const Span<float2> 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<bool> 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<float> radii = drawing.radii();
/* Remove points at the end that have a radius close to 0. */
filedescriptor marked this conversation as resolved Outdated

points_range -> points (the standard name for this sort of variable)

`points_range` -> `points` (the standard name for this sort of variable)
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;
filedescriptor marked this conversation as resolved Outdated

Use simple reference capture: [&]. It should amount to the same thing here.

Use simple reference capture: `[&]`. It should amount to the same thing here.
}
}
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<GreasePencil *>(object->data);
GreasePencil &grease_pencil_orig = *static_cast<GreasePencil *>(obact->data);
GreasePencil &grease_pencil_eval = *static_cast<GreasePencil *>(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. */

Feels important not to create arrays that are the size of the entire curves geometry, but it looks like that would require changes to ramer_douglas_peucker_simplify. Could be done later.

Feels important not to create arrays that are the size of the entire curves geometry, but it looks like that would require changes to `ramer_douglas_peucker_simplify`. Could be done later.
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<GreasePencilDrawing *>(grease_pencil_orig.drawing(index_orig))->wrap();
/* Drawing should exist. */
BLI_assert(drawing_index >= 0);
bke::greasepencil::Drawing &drawing =
reinterpret_cast<GreasePencilDrawing *>(grease_pencil.drawing(drawing_index))->wrap();
const Span<bke::greasepencil::StrokePoint> 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<int> 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<float3> positions = curves.positions_for_write();
MutableSpan<float> radii = drawing_orig.radii_for_write();
MutableSpan<float> 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<int> materials = attributes.lookup_or_add_for_write_span<int>(
"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<std::string> 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);
}
filedescriptor marked this conversation as resolved Outdated

Put the .radii() in a span outside the loop.

Put the `.radii()` in a span outside the loop.
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation()

View File

@ -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<float>(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);

View File

@ -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;
}

View File

@ -30,7 +30,6 @@ class Layer;
class LayerRuntime;
class LayerGroup;
class LayerGroupRuntime;
struct StrokePoint;
} // namespace greasepencil
} // namespace blender::bke
using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime;

View File

@ -1383,7 +1383,7 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value)
Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
Material *ma = static_cast<Material *>(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);
}

View File

@ -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<Brush *>(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];