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