GPv3: Initial drawing tool #110093
@ -4569,6 +4569,11 @@ def km_grease_pencil_paint(_params):
|
|||||||
)
|
)
|
||||||
|
|
||||||
items.extend([
|
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'}, None),
|
||||||
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||||
{"properties": [("mode", 'INVERT')]}),
|
{"properties": [("mode", 'INVERT')]}),
|
||||||
|
@ -82,6 +82,8 @@ class UnifiedPaintPanel:
|
|||||||
return tool_settings.gpencil_vertex_paint
|
return tool_settings.gpencil_vertex_paint
|
||||||
elif mode == 'SCULPT_CURVES':
|
elif mode == 'SCULPT_CURVES':
|
||||||
return tool_settings.curves_sculpt
|
return tool_settings.curves_sculpt
|
||||||
|
elif mode == 'PAINT_GREASE_PENCIL':
|
||||||
|
return tool_settings.gpencil_paint
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -866,6 +868,11 @@ def brush_shared_settings(layout, context, brush, popover=False):
|
|||||||
strength = True
|
strength = True
|
||||||
direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
|
direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
|
||||||
|
|
||||||
|
# Grease Pencil #
|
||||||
|
if mode == 'PAINT_GREASE_PENCIL':
|
||||||
|
size = True
|
||||||
|
strength = True
|
||||||
|
|
||||||
### Draw settings. ###
|
### Draw settings. ###
|
||||||
ups = context.scene.tool_settings.unified_paint_settings
|
ups = context.scene.tool_settings.unified_paint_settings
|
||||||
|
|
||||||
|
@ -1744,21 +1744,11 @@ class _defs_paint_grease_pencil:
|
|||||||
|
|
||||||
@ToolDef.from_fn
|
@ToolDef.from_fn
|
||||||
def erase():
|
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(
|
return dict(
|
||||||
idname="builtin_brush.Erase",
|
idname="builtin_brush.Erase",
|
||||||
label="Erase",
|
label="Erase",
|
||||||
icon="brush.gpencil_draw.erase",
|
icon="brush.gpencil_draw.erase",
|
||||||
data_block='ERASE',
|
data_block='ERASE',
|
||||||
draw_settings=draw_settings,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -582,6 +582,62 @@ class _draw_tool_settings_context_mode:
|
|||||||
|
|
||||||
return True
|
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):
|
class VIEW3D_HT_header(Header):
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
@ -625,7 +681,7 @@ class VIEW3D_HT_header(Header):
|
|||||||
else:
|
else:
|
||||||
if (object_mode not in {
|
if (object_mode not in {
|
||||||
'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT',
|
'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:
|
}) or has_pose_mode:
|
||||||
show_snap = True
|
show_snap = True
|
||||||
else:
|
else:
|
||||||
@ -8353,7 +8409,7 @@ class TOPBAR_PT_gpencil_materials(GreasePencilMaterialsPanel, Panel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
ob = context.object
|
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):
|
class TOPBAR_PT_gpencil_vertexcolor(GreasePencilVertexcolorPanel, Panel):
|
||||||
|
@ -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
|
* points are referred to as the control points, and the middle points are the corresponding
|
||||||
* handles.
|
* handles.
|
||||||
*/
|
*/
|
||||||
void evaluate_segment(const float3 &point_0,
|
template<typename T>
|
||||||
const float3 &point_1,
|
void evaluate_segment(
|
||||||
const float3 &point_2,
|
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result);
|
||||||
const float3 &point_3,
|
|
||||||
MutableSpan<float3> result);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate all evaluated points for the Bezier curve.
|
* Calculate all evaluated points for the Bezier curve.
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "BLI_array_utils.hh"
|
||||||
|
#include "BLI_color.hh"
|
||||||
#include "BLI_function_ref.hh"
|
#include "BLI_function_ref.hh"
|
||||||
#include "BLI_map.hh"
|
#include "BLI_map.hh"
|
||||||
#include "BLI_math_vector_types.hh"
|
#include "BLI_math_vector_types.hh"
|
||||||
@ -32,32 +34,6 @@ namespace blender::bke {
|
|||||||
|
|
||||||
namespace greasepencil {
|
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 {
|
class DrawingRuntime {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@ -102,6 +78,12 @@ class Drawing : public ::GreasePencilDrawing {
|
|||||||
VArray<float> opacities() const;
|
VArray<float> opacities() const;
|
||||||
MutableSpan<float> opacities_for_write();
|
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
|
* Add a user for this drawing. When a drawing has multiple users, both users are allowed to
|
||||||
* modify this drawings data.
|
* modify this drawings data.
|
||||||
@ -663,19 +645,12 @@ class GreasePencilRuntime {
|
|||||||
* Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions.
|
* Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions.
|
||||||
*/
|
*/
|
||||||
void *batch_cache = nullptr;
|
void *batch_cache = nullptr;
|
||||||
bke::greasepencil::StrokeCache stroke_cache;
|
|
||||||
/* The frame on which the object was evaluated (only valid for evaluated object). */
|
/* The frame on which the object was evaluated (only valid for evaluated object). */
|
||||||
int eval_frame;
|
int eval_frame;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GreasePencilRuntime() {}
|
GreasePencilRuntime() {}
|
||||||
~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
|
} // 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);
|
BoundBox *BKE_grease_pencil_boundbox_get(Object *ob);
|
||||||
void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *object);
|
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);
|
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name);
|
||||||
Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
||||||
Object *ob,
|
Object *ob,
|
||||||
const char *name,
|
const char *name,
|
||||||
int *r_index);
|
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,
|
Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
||||||
Object *ob,
|
Object *ob,
|
||||||
const char *name,
|
const char *name,
|
||||||
int *r_index);
|
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,
|
bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference,
|
||||||
const GreasePencil *grease_pencil);
|
const GreasePencil *grease_pencil);
|
||||||
|
@ -208,25 +208,23 @@ void calculate_auto_handles(const bool cyclic,
|
|||||||
positions_right.last());
|
positions_right.last());
|
||||||
}
|
}
|
||||||
|
|
||||||
void evaluate_segment(const float3 &point_0,
|
template<typename T>
|
||||||
const float3 &point_1,
|
void evaluate_segment_ex(
|
||||||
const float3 &point_2,
|
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result)
|
||||||
const float3 &point_3,
|
|
||||||
MutableSpan<float3> result)
|
|
||||||
{
|
{
|
||||||
BLI_assert(result.size() > 0);
|
BLI_assert(result.size() > 0);
|
||||||
const float inv_len = 1.0f / float(result.size());
|
const float inv_len = 1.0f / float(result.size());
|
||||||
const float inv_len_squared = inv_len * inv_len;
|
const float inv_len_squared = inv_len * inv_len;
|
||||||
const float inv_len_cubed = inv_len_squared * inv_len;
|
const float inv_len_cubed = inv_len_squared * inv_len;
|
||||||
|
|
||||||
const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
|
const T 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 T 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 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
|
||||||
|
|
||||||
float3 q0 = point_0;
|
T q0 = point_0;
|
||||||
float3 q1 = rt1 + rt2 + rt3;
|
T q1 = rt1 + rt2 + rt3;
|
||||||
float3 q2 = 2.0f * rt2 + 6.0f * rt3;
|
T q2 = 2.0f * rt2 + 6.0f * rt3;
|
||||||
float3 q3 = 6.0f * rt3;
|
T q3 = 6.0f * rt3;
|
||||||
for (const int i : result.index_range()) {
|
for (const int i : result.index_range()) {
|
||||||
result[i] = q0;
|
result[i] = q0;
|
||||||
q0 += q1;
|
q0 += q1;
|
||||||
@ -234,6 +232,24 @@ void evaluate_segment(const float3 &point_0,
|
|||||||
q2 += q3;
|
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,
|
void calculate_evaluated_positions(const Span<float3> positions,
|
||||||
const Span<float3> handles_left,
|
const Span<float3> handles_left,
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include "DNA_ID.h"
|
#include "DNA_ID.h"
|
||||||
#include "DNA_ID_enums.h"
|
#include "DNA_ID_enums.h"
|
||||||
|
#include "DNA_brush_types.h"
|
||||||
#include "DNA_grease_pencil_types.h"
|
#include "DNA_grease_pencil_types.h"
|
||||||
#include "DNA_material_types.h"
|
#include "DNA_material_types.h"
|
||||||
#include "DNA_modifier_types.h"
|
#include "DNA_modifier_types.h"
|
||||||
@ -223,8 +224,9 @@ IDTypeInfo IDType_ID_GP = {
|
|||||||
|
|
||||||
namespace blender::bke::greasepencil {
|
namespace blender::bke::greasepencil {
|
||||||
|
|
||||||
static const std::string ATTR_OPACITY = "opacity";
|
|
||||||
static const std::string ATTR_RADIUS = "radius";
|
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 */
|
/* Curves attributes getters */
|
||||||
static int domain_num(const CurvesGeometry &curves, const eAttrDomain domain)
|
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);
|
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()
|
void Drawing::tag_positions_changed()
|
||||||
{
|
{
|
||||||
this->strokes_for_write().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
|
/** \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)
|
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name)
|
||||||
{
|
{
|
||||||
short *totcol = BKE_object_material_len_p(ob);
|
short *totcol = BKE_object_material_len_p(ob);
|
||||||
@ -1166,6 +1195,18 @@ Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
|||||||
return ma;
|
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,
|
Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
||||||
Object *ob,
|
Object *ob,
|
||||||
const char *name,
|
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);
|
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
|
/** \name Grease Pencil data-block API
|
||||||
* \{ */
|
* \{ */
|
||||||
|
@ -1085,6 +1085,8 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode)
|
|||||||
return OB_MODE_EDIT;
|
return OB_MODE_EDIT;
|
||||||
case PAINT_MODE_SCULPT_CURVES:
|
case PAINT_MODE_SCULPT_CURVES:
|
||||||
return OB_MODE_SCULPT_CURVES;
|
return OB_MODE_SCULPT_CURVES;
|
||||||
|
case PAINT_MODE_GPENCIL:
|
||||||
|
return OB_MODE_PAINT_GREASE_PENCIL;
|
||||||
case PAINT_MODE_INVALID:
|
case PAINT_MODE_INVALID:
|
||||||
default:
|
default:
|
||||||
return OB_MODE_OBJECT;
|
return OB_MODE_OBJECT;
|
||||||
|
@ -647,6 +647,20 @@ template<typename T> class VArrayCommon {
|
|||||||
}
|
}
|
||||||
return impl_->size();
|
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. */
|
/** True when the size is zero or when there is no virtual array. */
|
||||||
bool is_empty() const
|
bool is_empty() const
|
||||||
|
@ -36,7 +36,7 @@ class LayerModule {
|
|||||||
{
|
{
|
||||||
/* TODO(fclem): All of this is placeholder. */
|
/* TODO(fclem): All of this is placeholder. */
|
||||||
gpLayer gp_layer;
|
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.thickness_offset = 0.0f;
|
||||||
gp_layer.tint = float4(1.0f, 1.0f, 1.0f, 0.0f);
|
gp_layer.tint = float4(1.0f, 1.0f, 1.0f, 0.0f);
|
||||||
gp_layer.stroke_index_offset = 0.0f;
|
gp_layer.stroke_index_offset = 0.0f;
|
||||||
|
@ -8,7 +8,7 @@ void gpencil_color_output(vec4 stroke_col, vec4 vert_col, float vert_strength, f
|
|||||||
{
|
{
|
||||||
/* Mix stroke with other colors. */
|
/* Mix stroke with other colors. */
|
||||||
vec4 mixed_col = stroke_col;
|
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.rgb = mix(mixed_col.rgb, gpLayerTint.rgb, gpLayerTint.a);
|
||||||
mixed_col.a *= vert_strength * gpLayerOpacity;
|
mixed_col.a *= vert_strength * gpLayerOpacity;
|
||||||
/**
|
/**
|
||||||
|
@ -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));
|
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};
|
static GPUVertFormat format_edit_points_pos = {0};
|
||||||
if (format_edit_points_pos.attr_len == 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);
|
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<bool> cyclic = curves.cyclic();
|
||||||
const VArray<float> radii = drawing.radii();
|
const VArray<float> radii = drawing.radii();
|
||||||
const VArray<float> opacities = drawing.opacities();
|
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. */
|
/* Assumes that if the ".selection" attribute does not exist, all points are selected. */
|
||||||
const VArray<float> selection_float = *attributes.lookup_or_default<float>(
|
const VArray<float> selection_float = *attributes.lookup_or_default<float>(
|
||||||
".selection", ATTR_DOMAIN_POINT, true);
|
".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. */
|
/* TODO: Populate fill UVs. */
|
||||||
s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0;
|
s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0;
|
||||||
|
|
||||||
/* TODO: Populate vertex color and fill color. */
|
copy_v4_v4(c_vert.vcol, vertex_colors[point_i]);
|
||||||
copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
copy_v4_v4(c_vert.fcol, vertex_colors[point_i]);
|
||||||
copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
|
||||||
c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f;
|
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;
|
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. */
|
/* Mark last 2 verts as invalid. */
|
||||||
verts[total_verts_num + 0].mat = -1;
|
verts[total_verts_num + 0].mat = -1;
|
||||||
verts[total_verts_num + 1].mat = -1;
|
verts[total_verts_num + 1].mat = -1;
|
||||||
|
@ -11,6 +11,8 @@ set(INC
|
|||||||
../../imbuf
|
../../imbuf
|
||||||
../../makesrna
|
../../makesrna
|
||||||
../../windowmanager
|
../../windowmanager
|
||||||
|
../../bmesh
|
||||||
|
../../../../extern/curve_fit_nd
|
||||||
# RNA_prototypes.h
|
# RNA_prototypes.h
|
||||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||||
)
|
)
|
||||||
@ -22,9 +24,11 @@ set(SRC
|
|||||||
intern/grease_pencil_add.cc
|
intern/grease_pencil_add.cc
|
||||||
intern/grease_pencil_edit.cc
|
intern/grease_pencil_edit.cc
|
||||||
intern/grease_pencil_frames.cc
|
intern/grease_pencil_frames.cc
|
||||||
|
intern/grease_pencil_geom.cc
|
||||||
intern/grease_pencil_layers.cc
|
intern/grease_pencil_layers.cc
|
||||||
intern/grease_pencil_ops.cc
|
intern/grease_pencil_ops.cc
|
||||||
intern/grease_pencil_select.cc
|
intern/grease_pencil_select.cc
|
||||||
|
intern/grease_pencil_utils.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
@ -32,6 +36,7 @@ set(LIB
|
|||||||
PRIVATE bf::blenlib
|
PRIVATE bf::blenlib
|
||||||
PRIVATE bf::dna
|
PRIVATE bf::dna
|
||||||
PRIVATE bf::intern::guardedalloc
|
PRIVATE bf::intern::guardedalloc
|
||||||
|
extern_curve_fit_nd
|
||||||
)
|
)
|
||||||
|
|
||||||
blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||||
|
@ -237,7 +237,8 @@ void gaussian_blur_1D(const GSpan src,
|
|||||||
bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||||
using T = decltype(dummy);
|
using T = decltype(dummy);
|
||||||
/* Reduces unnecessary code generation. */
|
/* 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>(),
|
gaussian_blur_1D(src.typed<T>(),
|
||||||
iterations,
|
iterations,
|
||||||
influence,
|
influence,
|
||||||
@ -394,56 +395,52 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot)
|
|||||||
/** \name Simplify Stroke Operator
|
/** \name Simplify Stroke Operator
|
||||||
* \{ */
|
* \{ */
|
||||||
|
|
||||||
/**
|
static float dist_to_interpolated(
|
||||||
* An implementation of the Ramer-Douglas-Peucker algorithm.
|
float3 pos, float3 posA, float3 posB, float val, float valA, float valB)
|
||||||
*
|
|
||||||
* \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)
|
|
||||||
{
|
{
|
||||||
/* Mark all points to not be removed. */
|
float dist1 = math::distance_squared(posA, pos);
|
||||||
points_to_delete.slice(range).fill(false);
|
float dist2 = math::distance_squared(posB, pos);
|
||||||
int64_t total_points_to_remove = 0;
|
|
||||||
|
|
||||||
Stack<IndexRange> stack;
|
if (dist1 + dist2 > 0) {
|
||||||
stack.push(range);
|
float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2));
|
||||||
while (!stack.is_empty()) {
|
return math::distance(interpolated_val, val);
|
||||||
const IndexRange sub_range = stack.pop();
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compute the maximum distance and the corresponding distance. */
|
static int64_t stroke_simplify(const IndexRange points,
|
||||||
float max_dist = -1.0f;
|
const bool cyclic,
|
||||||
int max_index = -1;
|
const float epsilon,
|
||||||
for (const int64_t sub_index : sub_range.index_range().drop_front(1).drop_back(1)) {
|
const FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
|
||||||
const float dist = dist_function(sub_range, sub_index);
|
MutableSpan<bool> points_to_delete)
|
||||||
if (dist > max_dist) {
|
{
|
||||||
max_dist = dist;
|
int64_t total_points_to_delete = 0;
|
||||||
max_index = sub_index;
|
const Span<bool> curve_selection = points_to_delete.slice(points);
|
||||||
}
|
if (!curve_selection.contains(true)) {
|
||||||
}
|
return total_points_to_delete;
|
||||||
|
}
|
||||||
|
|
||||||
if (max_dist > epsilon) {
|
const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last());
|
||||||
/* Found point outside the epsilon-sized strip. Repeat the search on the left & right side.
|
|
||||||
*/
|
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(curve_selection, true);
|
||||||
stack.push(sub_range.slice(IndexRange(max_index + 1)));
|
threading::parallel_for(
|
||||||
stack.push(sub_range.slice(IndexRange(max_index, sub_range.size() - max_index)));
|
selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) {
|
||||||
}
|
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) {
|
||||||
else {
|
total_points_to_delete += ramer_douglas_peucker_simplify(
|
||||||
/* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */
|
range.shift(points.start()), epsilon, dist_function, points_to_delete);
|
||||||
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);
|
|
||||||
|
/* 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)
|
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 Span<float3> positions = curves.positions();
|
||||||
|
const VArray<float> radii = drawing.radii();
|
||||||
|
|
||||||
/* Distance function for `ramer_douglas_peucker_simplify`. */
|
/* Distance functions for `ramer_douglas_peucker_simplify`. */
|
||||||
auto dist_func = [&](IndexRange range, int64_t index_in_range) {
|
const auto dist_function_positions =
|
||||||
const Span<float3> position_slice = positions.slice(range);
|
[positions](int64_t first_index, int64_t last_index, int64_t index) {
|
||||||
const float dist_position = dist_to_line_v3(
|
const float dist_position = dist_to_line_v3(
|
||||||
position_slice[index_in_range], position_slice.first(), position_slice.last());
|
positions[index], positions[first_index], positions[last_index]);
|
||||||
return dist_position;
|
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
|
|||||||
|
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 VArray<bool> cyclic = curves.cyclic();
|
||||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
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);
|
selection.materialize(points_to_delete);
|
||||||
|
|
||||||
std::atomic<int64_t> total_points_to_delete = 0;
|
std::atomic<int64_t> total_points_to_delete = 0;
|
||||||
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
if (radii.is_single()) {
|
||||||
for (const int curve_i : range) {
|
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
||||||
const IndexRange points = points_by_curve[curve_i];
|
for (const int curve_i : range) {
|
||||||
const Span<bool> curve_selection = points_to_delete.as_span().slice(points);
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
if (!curve_selection.contains(true)) {
|
total_points_to_delete += stroke_simplify(points,
|
||||||
continue;
|
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());
|
else if (radii.is_span()) {
|
||||||
|
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
||||||
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(
|
for (const int curve_i : range) {
|
||||||
curve_selection, true);
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
threading::parallel_for(
|
total_points_to_delete += stroke_simplify(points,
|
||||||
selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) {
|
cyclic[curve_i],
|
||||||
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges))
|
epsilon,
|
||||||
{
|
dist_function_positions_and_radii,
|
||||||
total_points_to_delete += ramer_douglas_peucker_simplify(
|
points_to_delete.as_mutable_span());
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
if (total_points_to_delete > 0) {
|
if (total_points_to_delete > 0) {
|
||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
@ -554,7 +556,7 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
|
|||||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||||
|
|
||||||
/* Simplify parameters. */
|
/* 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);
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
Hans Goudey
commented
` 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
|
@ -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
Hans Goudey
commented
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
Hans Goudey
commented
This is unnecessary, the function is already in 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
Hans Goudey
commented
`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
|
@ -12,6 +12,7 @@
|
|||||||
#include "BKE_grease_pencil.hh"
|
#include "BKE_grease_pencil.hh"
|
||||||
|
|
||||||
#include "BLI_generic_span.hh"
|
#include "BLI_generic_span.hh"
|
||||||
|
#include "BLI_index_mask.hh"
|
||||||
#include "BLI_math_matrix_types.hh"
|
#include "BLI_math_matrix_types.hh"
|
||||||
|
|
||||||
#include "ED_keyframes_edit.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,
|
void create_keyframe_edit_data_selected_frames_list(KeyframeEditData *ked,
|
||||||
const bke::greasepencil::Layer &layer);
|
const bke::greasepencil::Layer &layer);
|
||||||
|
|
||||||
|
float brush_radius_world_space(bContext &C, int x, int y);
|
||||||
|
|
||||||
bool active_grease_pencil_poll(bContext *C);
|
bool active_grease_pencil_poll(bContext *C);
|
||||||
bool editable_grease_pencil_poll(bContext *C);
|
bool editable_grease_pencil_poll(bContext *C);
|
||||||
bool editable_grease_pencil_point_selection_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,
|
int64_t ramer_douglas_peucker_simplify(IndexRange range,
|
||||||
float epsilon,
|
float epsilon,
|
||||||
FunctionRef<float(IndexRange, int64_t)> dist_function,
|
FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
|
||||||
MutableSpan<bool> dst);
|
MutableSpan<bool> dst);
|
||||||
|
|
||||||
|
Array<float2> polyline_fit_curve(Span<float2> points,
|
||||||
filedescriptor marked this conversation as resolved
Outdated
Hans Goudey
commented
Suggest renaming these two functions for consistency:
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
Hans Goudey
commented
`int64_t` -> `int`?
|
|||||||
|
float angle_threshold,
|
||||||
|
IndexMaskMemory &memory);
|
||||||
|
|
||||||
} // namespace blender::ed::greasepencil
|
} // namespace blender::ed::greasepencil
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "BKE_context.h"
|
#include "BKE_context.h"
|
||||||
#include "BKE_grease_pencil.hh"
|
#include "BKE_grease_pencil.hh"
|
||||||
|
#include "BKE_report.h"
|
||||||
|
|
||||||
#include "DEG_depsgraph_query.hh"
|
#include "DEG_depsgraph_query.hh"
|
||||||
|
|
||||||
@ -115,16 +116,49 @@ static void stroke_done(const bContext *C, PaintStroke *stroke)
|
|||||||
GreasePencilStrokeOperation *operation = static_cast<GreasePencilStrokeOperation *>(
|
GreasePencilStrokeOperation *operation = static_cast<GreasePencilStrokeOperation *>(
|
||||||
paint_stroke_mode_data(stroke));
|
paint_stroke_mode_data(stroke));
|
||||||
operation->on_stroke_done(*C);
|
operation->on_stroke_done(*C);
|
||||||
|
operation->~GreasePencilStrokeOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
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 Paint *paint = BKE_paint_get_active_from_context(C);
|
||||||
const Brush *brush = BKE_paint_brush_for_read(paint);
|
const Brush *brush = BKE_paint_brush_for_read(paint);
|
||||||
if (brush == nullptr) {
|
if (brush == nullptr) {
|
||||||
return OPERATOR_CANCELLED;
|
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
Hans Goudey
commented
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->customdata = paint_stroke_new(C,
|
||||||
op,
|
op,
|
||||||
stroke_get_location,
|
stroke_get_location,
|
||||||
|
@ -2,14 +2,21 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#include "BKE_brush.hh"
|
||||||
|
#include "BKE_colortools.h"
|
||||||
#include "BKE_context.h"
|
#include "BKE_context.h"
|
||||||
#include "BKE_curves.hh"
|
#include "BKE_curves.hh"
|
||||||
#include "BKE_grease_pencil.h"
|
#include "BKE_grease_pencil.h"
|
||||||
#include "BKE_grease_pencil.hh"
|
#include "BKE_grease_pencil.hh"
|
||||||
|
#include "BKE_material.h"
|
||||||
#include "BKE_scene.h"
|
#include "BKE_scene.h"
|
||||||
|
|
||||||
|
#include "BLI_length_parameterize.hh"
|
||||||
|
#include "BLI_math_geom.h"
|
||||||
|
|
||||||
#include "DEG_depsgraph_query.hh"
|
#include "DEG_depsgraph_query.hh"
|
||||||
|
|
||||||
|
#include "ED_grease_pencil.hh"
|
||||||
#include "ED_view3d.hh"
|
#include "ED_view3d.hh"
|
||||||
|
|
||||||
#include "WM_api.hh"
|
#include "WM_api.hh"
|
||||||
@ -19,14 +26,80 @@
|
|||||||
|
|
||||||
namespace blender::ed::sculpt_paint::greasepencil {
|
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
Hans Goudey
commented
`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
Hans Goudey
commented
Rather than re-implementing this, it probably makes sense to make 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
Hans Goudey
commented
I'd suggest: 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
Hans Goudey
commented
512 * 32 is 16k, that's huge, large enough that multithreading will probably never be used. What about 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
Hans Goudey
commented
Rather than evaluating a single segment at a time from here, how about giving the whole 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.
Falk David
commented
If I am not mistaken that module currently only works with If I am not mistaken that module currently only works with `float3`s.
Hans Goudey
commented
Pretty sure you're mistaken :P It's all templated. Even the tests use Pretty sure you're mistaken :P It's all templated. Even the tests use `float2` sometimes.
Falk David
commented
I just added a 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
Hans Goudey
commented
This still looks like it has the same goal as the code in 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
Hans Goudey
commented
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 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 {
|
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:
|
public:
|
||||||
~PaintOperation() override {}
|
|
||||||
|
|
||||||
void on_stroke_begin(const bContext &C, const InputSample &start_sample) 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_extended(const bContext &C, const InputSample &extension_sample) override;
|
||||||
void on_stroke_done(const bContext &C) 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.
|
* because it avoids passing a very large number of parameters between functions.
|
||||||
*/
|
*/
|
||||||
struct PaintOperationExecutor {
|
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;
|
Scene *scene = CTX_data_scene(&C);
|
||||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
|
region_ = CTX_wm_region(&C);
|
||||||
ARegion *region = CTX_wm_region(&C);
|
Object *object = CTX_data_active_object(&C);
|
||||||
Object *obact = CTX_data_active_object(&C);
|
grease_pencil_ = static_cast<GreasePencil *>(object->data);
|
||||||
Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
|
|
||||||
|
|
||||||
/**
|
Paint *paint = &scene->toolsettings->gp_paint->paint;
|
||||||
* Note: We write to the evaluated object here, so that the additional copy from orig -> eval
|
brush_ = BKE_paint_brush(paint);
|
||||||
* is not needed for every update. After the stroke is done, the result is written to the
|
settings_ = brush_->gpencil_settings;
|
||||||
* original object.
|
brush_size_ = BKE_brush_size_get(scene, brush_);
|
||||||
*/
|
brush_alpha_ = BKE_brush_alpha_get(scene, brush_);
|
||||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob_eval->data);
|
|
||||||
|
|
||||||
float4 plane{0.0f, -1.0f, 0.0f, 0.0f};
|
const bool use_vertex_color = (scene->toolsettings->gp_paint->mode ==
|
||||||
float3 proj_pos;
|
GPPAINT_FLAG_USE_VERTEXCOLOR);
|
||||||
ED_view3d_win_to_3d_on_plane(region, plane, extension_sample.mouse_position, false, proj_pos);
|
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{
|
// const bool use_vertex_color_fill = use_vertex_color && ELEM(
|
||||||
proj_pos, extension_sample.pressure * 100.0f, 1.0f, float4(1.0f)};
|
// 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
Hans Goudey
commented
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
Hans Goudey
commented
`IndexMask corner_mask` -> `const IndexMask corner_mask`
|
|||||||
|
Array<float2> curve_points = ed::greasepencil::polyline_fit_curve(
|
||||||
filedescriptor marked this conversation as resolved
Outdated
Hans Goudey
commented
Unnecessary 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
Hans Goudey
commented
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
Hans Goudey
commented
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
Hans Goudey
commented
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
Hans Goudey
commented
How about copying this from the subdivide curves node?
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
Hans Goudey
commented
`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
Hans Goudey
commented
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)
|
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};
|
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
Hans Goudey
commented
`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
Hans Goudey
commented
`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
Hans Goudey
commented
Use simple reference capture: 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)
|
void PaintOperation::on_stroke_done(const bContext &C)
|
||||||
{
|
{
|
||||||
using namespace blender::bke;
|
using namespace blender::bke;
|
||||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
|
|
||||||
Scene *scene = CTX_data_scene(&C);
|
Scene *scene = CTX_data_scene(&C);
|
||||||
Object *obact = CTX_data_active_object(&C);
|
Object *object = CTX_data_active_object(&C);
|
||||||
Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
|
||||||
|
|
||||||
GreasePencil &grease_pencil_orig = *static_cast<GreasePencil *>(obact->data);
|
/* Grease Pencil should have an active layer. */
|
||||||
Hans Goudey
commented
Feels important not to create arrays that are the size of the entire curves geometry, but it looks like that would require changes to 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.
|
|||||||
GreasePencil &grease_pencil_eval = *static_cast<GreasePencil *>(ob_eval->data);
|
BLI_assert(grease_pencil.has_active_layer());
|
||||||
BLI_assert(grease_pencil_orig.has_active_layer());
|
bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer_for_write();
|
||||||
const bke::greasepencil::Layer &active_layer_orig = *grease_pencil_orig.get_active_layer();
|
const int drawing_index = active_layer.drawing_index_at(scene->r.cfra);
|
||||||
int index_orig = active_layer_orig.drawing_index_at(scene->r.cfra);
|
|
||||||
|
|
||||||
bke::greasepencil::Drawing &drawing_orig =
|
/* Drawing should exist. */
|
||||||
reinterpret_cast<GreasePencilDrawing *>(grease_pencil_orig.drawing(index_orig))->wrap();
|
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 =
|
const float simplifiy_threshold_px = 0.5f;
|
||||||
grease_pencil_eval.runtime->stroke_buffer();
|
this->simplify_stroke(drawing, simplifiy_threshold_px);
|
||||||
CurvesGeometry &curves = drawing_orig.strokes_for_write();
|
this->process_stroke_end(drawing);
|
||||||
|
drawing.tag_topology_changed();
|
||||||
|
|
||||||
int num_old_curves = curves.curves_num();
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
||||||
int num_old_points = curves.points_num();
|
WM_main_add_notifier(NC_GEOM | ND_DATA, &grease_pencil.id);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filedescriptor marked this conversation as resolved
Outdated
Hans Goudey
commented
Put the Put the `.radii()` in a span outside the loop.
|
|||||||
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation()
|
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation()
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "DNA_brush_types.h"
|
#include "DNA_brush_types.h"
|
||||||
#include "DNA_color_types.h"
|
#include "DNA_color_types.h"
|
||||||
#include "DNA_customdata_types.h"
|
#include "DNA_customdata_types.h"
|
||||||
|
#include "DNA_material_types.h"
|
||||||
#include "DNA_mesh_types.h"
|
#include "DNA_mesh_types.h"
|
||||||
#include "DNA_object_types.h"
|
#include "DNA_object_types.h"
|
||||||
#include "DNA_scene_types.h"
|
#include "DNA_scene_types.h"
|
||||||
@ -28,6 +29,7 @@
|
|||||||
#include "BKE_colortools.h"
|
#include "BKE_colortools.h"
|
||||||
#include "BKE_context.h"
|
#include "BKE_context.h"
|
||||||
#include "BKE_curve.h"
|
#include "BKE_curve.h"
|
||||||
|
#include "BKE_grease_pencil.hh"
|
||||||
#include "BKE_image.h"
|
#include "BKE_image.h"
|
||||||
#include "BKE_node_runtime.hh"
|
#include "BKE_node_runtime.hh"
|
||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
@ -41,6 +43,7 @@
|
|||||||
#include "IMB_colormanagement.h"
|
#include "IMB_colormanagement.h"
|
||||||
#include "IMB_imbuf_types.h"
|
#include "IMB_imbuf_types.h"
|
||||||
|
|
||||||
|
#include "ED_grease_pencil.hh"
|
||||||
#include "ED_image.hh"
|
#include "ED_image.hh"
|
||||||
#include "ED_view3d.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)
|
static bool paint_use_2d_cursor(ePaintMode mode)
|
||||||
{
|
{
|
||||||
if (mode >= PAINT_MODE_TEXTURE_3D) {
|
switch (mode) {
|
||||||
return true;
|
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 {
|
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. */
|
* with the UI (dragging a number button for e.g.), see: #102792. */
|
||||||
return;
|
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);
|
immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha);
|
||||||
|
|
||||||
@ -1448,6 +1470,147 @@ static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext)
|
|||||||
40);
|
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)
|
static void paint_draw_legacy_3D_view_brush_cursor(PaintCursorContext *pcontext)
|
||||||
{
|
{
|
||||||
GPU_line_width(1.0f);
|
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);
|
paint_draw_curve_cursor(pcontext.brush, &pcontext.vc);
|
||||||
break;
|
break;
|
||||||
case PAINT_CURSOR_2D:
|
case PAINT_CURSOR_2D:
|
||||||
|
paint_update_mouse_cursor(&pcontext);
|
||||||
|
|
||||||
paint_cursor_update_rake_rotation(&pcontext);
|
paint_cursor_update_rake_rotation(&pcontext);
|
||||||
paint_cursor_check_and_draw_alpha_overlays(&pcontext);
|
paint_cursor_check_and_draw_alpha_overlays(&pcontext);
|
||||||
paint_cursor_update_anchored_location(&pcontext);
|
paint_cursor_update_anchored_location(&pcontext);
|
||||||
|
@ -686,6 +686,9 @@ static int paintcurve_draw_exec(bContext *C, wmOperator * /*op*/)
|
|||||||
case PAINT_MODE_SCULPT_CURVES:
|
case PAINT_MODE_SCULPT_CURVES:
|
||||||
name = "SCULPT_CURVES_OT_brush_stroke";
|
name = "SCULPT_CURVES_OT_brush_stroke";
|
||||||
break;
|
break;
|
||||||
|
case PAINT_MODE_GPENCIL:
|
||||||
|
name = "GREASE_PENCIL_OT_brush_stroke";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return OPERATOR_PASS_THROUGH;
|
return OPERATOR_PASS_THROUGH;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ class Layer;
|
|||||||
class LayerRuntime;
|
class LayerRuntime;
|
||||||
class LayerGroup;
|
class LayerGroup;
|
||||||
class LayerGroupRuntime;
|
class LayerGroupRuntime;
|
||||||
struct StrokePoint;
|
|
||||||
} // namespace greasepencil
|
} // namespace greasepencil
|
||||||
} // namespace blender::bke
|
} // namespace blender::bke
|
||||||
using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime;
|
using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime;
|
||||||
|
@ -1383,7 +1383,7 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value)
|
|||||||
Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
|
Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
|
||||||
Material *ma = static_cast<Material *>(value.data);
|
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 */
|
/* GP Materials only */
|
||||||
return (ma->gp_style != nullptr);
|
return (ma->gp_style != nullptr);
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
|
|
||||||
#include "ED_fileselect.hh"
|
#include "ED_fileselect.hh"
|
||||||
#include "ED_gpencil_legacy.hh"
|
#include "ED_gpencil_legacy.hh"
|
||||||
|
#include "ED_grease_pencil.hh"
|
||||||
#include "ED_numinput.hh"
|
#include "ED_numinput.hh"
|
||||||
#include "ED_screen.hh"
|
#include "ED_screen.hh"
|
||||||
#include "ED_undo.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[0] *= zoom[0];
|
||||||
d[1] *= zoom[1];
|
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
|
/* 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. */
|
* the size of the radial is not equal to the actual stroke size. */
|
||||||
if (rc->ptr.owner_id && GS(rc->ptr.owner_id->name) == ID_BR && rc->prop == &rna_Brush_size) {
|
Object *object = CTX_data_active_object(C);
|
||||||
rc->scale_fac = ED_gpencil_radial_control_scale(
|
if (object->type == OB_GREASE_PENCIL && rc->prop == &rna_UnifiedPaintSettings_size) {
|
||||||
C, (Brush *)rc->ptr.owner_id, rc->initial_value, event->mval);
|
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 {
|
else if (rc->ptr.owner_id && GS(rc->ptr.owner_id->name) == ID_BR && rc->prop == &rna_Brush_size)
|
||||||
rc->scale_fac = 1.0f;
|
{
|
||||||
|
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];
|
rc->initial_mouse[0] -= d[0];
|
||||||
|
devide
->divide
storkes
->strokes
?