GPv3: Initial drawing tool #110093
@ -4569,6 +4569,11 @@ def km_grease_pencil_paint(_params):
|
||||
)
|
||||
|
||||
items.extend([
|
||||
*_template_paint_radial_control("gpencil_paint"),
|
||||
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
|
||||
{"properties": [("scalar", 0.9)]}),
|
||||
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
|
||||
{"properties": [("scalar", 1.0 / 0.9)]}),
|
||||
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("mode", 'INVERT')]}),
|
||||
|
@ -82,6 +82,8 @@ class UnifiedPaintPanel:
|
||||
return tool_settings.gpencil_vertex_paint
|
||||
elif mode == 'SCULPT_CURVES':
|
||||
return tool_settings.curves_sculpt
|
||||
elif mode == 'PAINT_GREASE_PENCIL':
|
||||
return tool_settings.gpencil_paint
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@ -866,6 +868,11 @@ def brush_shared_settings(layout, context, brush, popover=False):
|
||||
strength = True
|
||||
direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
|
||||
|
||||
# Grease Pencil #
|
||||
if mode == 'PAINT_GREASE_PENCIL':
|
||||
size = True
|
||||
strength = True
|
||||
|
||||
### Draw settings. ###
|
||||
ups = context.scene.tool_settings.unified_paint_settings
|
||||
|
||||
|
@ -1744,21 +1744,11 @@ class _defs_paint_grease_pencil:
|
||||
|
||||
@ToolDef.from_fn
|
||||
def erase():
|
||||
def draw_settings(context, layout, _tool):
|
||||
paint = context.tool_settings.gpencil_paint
|
||||
brush = paint.brush
|
||||
if not brush:
|
||||
return
|
||||
layout.prop(brush.gpencil_settings, "eraser_mode", expand=True)
|
||||
if brush.gpencil_settings.eraser_mode == 'HARD':
|
||||
layout.prop(brush.gpencil_settings, "use_keep_caps_eraser")
|
||||
layout.prop(brush.gpencil_settings, "use_active_layer_only")
|
||||
return dict(
|
||||
idname="builtin_brush.Erase",
|
||||
label="Erase",
|
||||
icon="brush.gpencil_draw.erase",
|
||||
data_block='ERASE',
|
||||
draw_settings=draw_settings,
|
||||
)
|
||||
|
||||
|
||||
|
@ -582,6 +582,62 @@ class _draw_tool_settings_context_mode:
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def PAINT_GREASE_PENCIL(context, layout, tool):
|
||||
if (tool is None) or (not tool.has_datablock):
|
||||
return False
|
||||
|
||||
tool_settings = context.tool_settings
|
||||
paint = tool_settings.gpencil_paint
|
||||
|
||||
brush = paint.brush
|
||||
if brush is None:
|
||||
return False
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
|
||||
|
||||
grease_pencil_tool = brush.gpencil_tool
|
||||
|
||||
if grease_pencil_tool == 'DRAW':
|
||||
from bl_ui.properties_paint_common import (
|
||||
brush_basic__draw_color_selector,
|
||||
)
|
||||
brush_basic__draw_color_selector(context, layout, brush, brush.gpencil_settings, None)
|
||||
|
||||
UnifiedPaintPanel.prop_unified(
|
||||
layout,
|
||||
context,
|
||||
brush,
|
||||
"size",
|
||||
unified_name="use_unified_size",
|
||||
pressure_name="use_pressure_size",
|
||||
text="Radius",
|
||||
slider=True,
|
||||
header=True,
|
||||
)
|
||||
|
||||
UnifiedPaintPanel.prop_unified(
|
||||
layout,
|
||||
context,
|
||||
brush,
|
||||
"strength",
|
||||
pressure_name="use_pressure_strength",
|
||||
unified_name="use_unified_strength",
|
||||
slider=True,
|
||||
header=True,
|
||||
)
|
||||
|
||||
if grease_pencil_tool == 'DRAW':
|
||||
layout.prop(brush.gpencil_settings, "active_smooth_factor")
|
||||
elif grease_pencil_tool == 'ERASE':
|
||||
layout.prop(brush.gpencil_settings, "eraser_mode", expand=True)
|
||||
if brush.gpencil_settings.eraser_mode == "HARD":
|
||||
layout.prop(brush.gpencil_settings, "use_keep_caps_eraser")
|
||||
layout.prop(brush.gpencil_settings, "use_active_layer_only")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class VIEW3D_HT_header(Header):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
@ -625,7 +681,7 @@ class VIEW3D_HT_header(Header):
|
||||
else:
|
||||
if (object_mode not in {
|
||||
'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT',
|
||||
'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL',
|
||||
'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', 'PAINT_GREASE_PENCIL',
|
||||
}) or has_pose_mode:
|
||||
show_snap = True
|
||||
else:
|
||||
@ -8353,7 +8409,7 @@ class TOPBAR_PT_gpencil_materials(GreasePencilMaterialsPanel, Panel):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.object
|
||||
return ob and ob.type == 'GPENCIL'
|
||||
return ob and (ob.type == 'GPENCIL' or ob.type == 'GREASEPENCIL')
|
||||
|
||||
|
||||
class TOPBAR_PT_gpencil_vertexcolor(GreasePencilVertexcolorPanel, Panel):
|
||||
|
@ -654,11 +654,9 @@ void set_handle_position(const float3 &position,
|
||||
* points are referred to as the control points, and the middle points are the corresponding
|
||||
* handles.
|
||||
*/
|
||||
void evaluate_segment(const float3 &point_0,
|
||||
const float3 &point_1,
|
||||
const float3 &point_2,
|
||||
const float3 &point_3,
|
||||
MutableSpan<float3> result);
|
||||
template<typename T>
|
||||
void evaluate_segment(
|
||||
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result);
|
||||
|
||||
/**
|
||||
* Calculate all evaluated points for the Bezier curve.
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
@ -32,32 +34,6 @@ namespace blender::bke {
|
||||
|
||||
namespace greasepencil {
|
||||
|
||||
/**
|
||||
* A single point for a stroke that is currently being drawn.
|
||||
*/
|
||||
struct StrokePoint {
|
||||
float3 position;
|
||||
float radius;
|
||||
float opacity;
|
||||
float4 color;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stroke cache for a stroke that is currently being drawn.
|
||||
*/
|
||||
struct StrokeCache {
|
||||
Vector<StrokePoint> points;
|
||||
Vector<uint3> triangles;
|
||||
int mat = 0;
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->points.clear_and_shrink();
|
||||
this->triangles.clear_and_shrink();
|
||||
this->mat = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class DrawingRuntime {
|
||||
public:
|
||||
/**
|
||||
@ -102,6 +78,12 @@ class Drawing : public ::GreasePencilDrawing {
|
||||
VArray<float> opacities() const;
|
||||
MutableSpan<float> opacities_for_write();
|
||||
|
||||
/**
|
||||
* Vertex colors of the points. Default is black.
|
||||
*/
|
||||
VArray<ColorGeometry4f> vertex_colors() const;
|
||||
MutableSpan<ColorGeometry4f> vertex_colors_for_write();
|
||||
|
||||
/**
|
||||
* Add a user for this drawing. When a drawing has multiple users, both users are allowed to
|
||||
* modify this drawings data.
|
||||
@ -663,19 +645,12 @@ class GreasePencilRuntime {
|
||||
* Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions.
|
||||
*/
|
||||
void *batch_cache = nullptr;
|
||||
bke::greasepencil::StrokeCache stroke_cache;
|
||||
/* The frame on which the object was evaluated (only valid for evaluated object). */
|
||||
int eval_frame;
|
||||
|
||||
public:
|
||||
GreasePencilRuntime() {}
|
||||
~GreasePencilRuntime() {}
|
||||
|
||||
/**
|
||||
* A buffer for a single stroke while drawing.
|
||||
*/
|
||||
Span<bke::greasepencil::StrokePoint> stroke_buffer() const;
|
||||
bool has_stroke_buffer() const;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
@ -765,15 +740,26 @@ GreasePencil *BKE_grease_pencil_copy_for_eval(const GreasePencil *grease_pencil_
|
||||
BoundBox *BKE_grease_pencil_boundbox_get(Object *ob);
|
||||
void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *object);
|
||||
|
||||
int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma);
|
||||
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name);
|
||||
Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
||||
Object *ob,
|
||||
const char *name,
|
||||
int *r_index);
|
||||
Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush);
|
||||
Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
||||
Object *ob,
|
||||
const char *name,
|
||||
int *r_index);
|
||||
Material *BKE_grease_pencil_brush_material_get(Brush *brush);
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain,
|
||||
Object *ob,
|
||||
Brush *brush);
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain,
|
||||
Object *ob,
|
||||
Brush *brush);
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob);
|
||||
Material *BKE_grease_pencil_object_material_ensure_active(Object *ob);
|
||||
|
||||
bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference,
|
||||
const GreasePencil *grease_pencil);
|
||||
|
@ -208,25 +208,23 @@ void calculate_auto_handles(const bool cyclic,
|
||||
positions_right.last());
|
||||
}
|
||||
|
||||
void evaluate_segment(const float3 &point_0,
|
||||
const float3 &point_1,
|
||||
const float3 &point_2,
|
||||
const float3 &point_3,
|
||||
MutableSpan<float3> result)
|
||||
template<typename T>
|
||||
void evaluate_segment_ex(
|
||||
const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan<T> result)
|
||||
{
|
||||
BLI_assert(result.size() > 0);
|
||||
const float inv_len = 1.0f / float(result.size());
|
||||
const float inv_len_squared = inv_len * inv_len;
|
||||
const float inv_len_cubed = inv_len_squared * inv_len;
|
||||
|
||||
const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
|
||||
const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
|
||||
const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
|
||||
const T rt1 = 3.0f * (point_1 - point_0) * inv_len;
|
||||
const T rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
|
||||
const T rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
|
||||
|
||||
float3 q0 = point_0;
|
||||
float3 q1 = rt1 + rt2 + rt3;
|
||||
float3 q2 = 2.0f * rt2 + 6.0f * rt3;
|
||||
float3 q3 = 6.0f * rt3;
|
||||
T q0 = point_0;
|
||||
T q1 = rt1 + rt2 + rt3;
|
||||
T q2 = 2.0f * rt2 + 6.0f * rt3;
|
||||
T q3 = 6.0f * rt3;
|
||||
for (const int i : result.index_range()) {
|
||||
result[i] = q0;
|
||||
q0 += q1;
|
||||
@ -234,6 +232,24 @@ void evaluate_segment(const float3 &point_0,
|
||||
q2 += q3;
|
||||
}
|
||||
}
|
||||
template<>
|
||||
void evaluate_segment(const float3 &point_0,
|
||||
const float3 &point_1,
|
||||
const float3 &point_2,
|
||||
const float3 &point_3,
|
||||
MutableSpan<float3> result)
|
||||
{
|
||||
evaluate_segment_ex<float3>(point_0, point_1, point_2, point_3, result);
|
||||
}
|
||||
template<>
|
||||
void evaluate_segment(const float2 &point_0,
|
||||
const float2 &point_1,
|
||||
const float2 &point_2,
|
||||
const float2 &point_3,
|
||||
MutableSpan<float2> result)
|
||||
{
|
||||
evaluate_segment_ex<float2>(point_0, point_1, point_2, point_3, result);
|
||||
}
|
||||
|
||||
void calculate_evaluated_positions(const Span<float3> positions,
|
||||
const Span<float3> handles_left,
|
||||
|
@ -41,6 +41,7 @@
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_ID_enums.h"
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
@ -223,8 +224,9 @@ IDTypeInfo IDType_ID_GP = {
|
||||
|
||||
namespace blender::bke::greasepencil {
|
||||
|
||||
static const std::string ATTR_OPACITY = "opacity";
|
||||
static const std::string ATTR_RADIUS = "radius";
|
||||
static const std::string ATTR_OPACITY = "opacity";
|
||||
static const std::string ATTR_VERTEX_COLOR = "vertex_color";
|
||||
|
||||
/* Curves attributes getters */
|
||||
static int domain_num(const CurvesGeometry &curves, const eAttrDomain domain)
|
||||
@ -375,6 +377,20 @@ MutableSpan<float> Drawing::opacities_for_write()
|
||||
this->strokes_for_write(), ATTR_DOMAIN_POINT, ATTR_OPACITY, 1.0f);
|
||||
}
|
||||
|
||||
VArray<ColorGeometry4f> Drawing::vertex_colors() const
|
||||
{
|
||||
return *this->strokes().attributes().lookup_or_default<ColorGeometry4f>(
|
||||
ATTR_VERTEX_COLOR, ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
MutableSpan<ColorGeometry4f> Drawing::vertex_colors_for_write()
|
||||
{
|
||||
return get_mutable_attribute<ColorGeometry4f>(this->strokes_for_write(),
|
||||
ATTR_DOMAIN_POINT,
|
||||
ATTR_VERTEX_COLOR,
|
||||
ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
void Drawing::tag_positions_changed()
|
||||
{
|
||||
this->strokes_for_write().tag_positions_changed();
|
||||
@ -1135,6 +1151,19 @@ void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *o
|
||||
/** \name Grease Pencil material functions
|
||||
* \{ */
|
||||
|
||||
int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma)
|
||||
{
|
||||
short *totcol = BKE_object_material_len_p(ob);
|
||||
Material *read_ma = NULL;
|
||||
for (short i = 0; i < *totcol; i++) {
|
||||
read_ma = BKE_object_material_get(ob, i + 1);
|
||||
if (ma == read_ma) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name)
|
||||
{
|
||||
short *totcol = BKE_object_material_len_p(ob);
|
||||
@ -1166,6 +1195,18 @@ Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
||||
return ma;
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush)
|
||||
{
|
||||
if ((brush) && (brush->gpencil_settings) &&
|
||||
(brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED))
|
||||
{
|
||||
Material *ma = BKE_grease_pencil_brush_material_get(brush);
|
||||
return ma;
|
||||
}
|
||||
|
||||
return BKE_object_material_get(ob, ob->actcol);
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
||||
Object *ob,
|
||||
const char *name,
|
||||
@ -1179,6 +1220,71 @@ Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
||||
return BKE_grease_pencil_object_material_new(bmain, ob, name, r_index);
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_brush_material_get(Brush *brush)
|
||||
{
|
||||
if (brush == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (brush->gpencil_settings == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return brush->gpencil_settings->material;
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain,
|
||||
Object *ob,
|
||||
Brush *brush)
|
||||
{
|
||||
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
||||
Material *ma = BKE_grease_pencil_brush_material_get(brush);
|
||||
|
||||
/* check if the material is already on object material slots and add it if missing */
|
||||
if (ma && BKE_grease_pencil_object_material_index_get(ob, ma) < 0) {
|
||||
BKE_object_material_slot_add(bmain, ob);
|
||||
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
||||
}
|
||||
|
||||
return ma;
|
||||
}
|
||||
|
||||
/* Use the active material instead. */
|
||||
return BKE_object_material_get(ob, ob->actcol);
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain,
|
||||
Object *ob,
|
||||
Brush *brush)
|
||||
{
|
||||
if (brush == nullptr) {
|
||||
return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob);
|
||||
}
|
||||
if (Material *ma = BKE_grease_pencil_object_material_ensure_from_brush(bmain, ob, brush)) {
|
||||
return ma;
|
||||
}
|
||||
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
||||
/* It is easier to just unpin a null material, instead of setting a new one. */
|
||||
brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED;
|
||||
}
|
||||
return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob);
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob)
|
||||
{
|
||||
if (Material *ma = BKE_object_material_get(ob, ob->actcol)) {
|
||||
return ma;
|
||||
}
|
||||
return BKE_material_default_gpencil();
|
||||
}
|
||||
|
||||
Material *BKE_grease_pencil_object_material_ensure_active(Object *ob)
|
||||
{
|
||||
Material *ma = BKE_grease_pencil_object_material_ensure_from_active_input_material(ob);
|
||||
if (ma->gp_style == nullptr) {
|
||||
BKE_gpencil_material_attr_init(ma);
|
||||
}
|
||||
return ma;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
@ -1235,23 +1341,6 @@ void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** \name Grease Pencil runtime API
|
||||
* \{ */
|
||||
|
||||
bool blender::bke::GreasePencilRuntime::has_stroke_buffer() const
|
||||
{
|
||||
return this->stroke_cache.points.size() > 0;
|
||||
}
|
||||
|
||||
blender::Span<blender::bke::greasepencil::StrokePoint> blender::bke::GreasePencilRuntime::
|
||||
stroke_buffer() const
|
||||
{
|
||||
return this->stroke_cache.points.as_span();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** \name Grease Pencil data-block API
|
||||
* \{ */
|
||||
|
@ -1085,6 +1085,8 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode)
|
||||
return OB_MODE_EDIT;
|
||||
case PAINT_MODE_SCULPT_CURVES:
|
||||
return OB_MODE_SCULPT_CURVES;
|
||||
case PAINT_MODE_GPENCIL:
|
||||
return OB_MODE_PAINT_GREASE_PENCIL;
|
||||
case PAINT_MODE_INVALID:
|
||||
default:
|
||||
return OB_MODE_OBJECT;
|
||||
|
@ -647,6 +647,20 @@ template<typename T> class VArrayCommon {
|
||||
}
|
||||
return impl_->size();
|
||||
}
|
||||
/**
|
||||
* Get the first element.
|
||||
*/
|
||||
T first() const
|
||||
{
|
||||
return (*this)[0];
|
||||
}
|
||||
/**
|
||||
* Get the nth last element.
|
||||
*/
|
||||
T last(const int64_t n = 0) const
|
||||
{
|
||||
return (*this)[this->size() - 1 - n];
|
||||
}
|
||||
|
||||
/** True when the size is zero or when there is no virtual array. */
|
||||
bool is_empty() const
|
||||
|
@ -36,7 +36,7 @@ class LayerModule {
|
||||
{
|
||||
/* TODO(fclem): All of this is placeholder. */
|
||||
gpLayer gp_layer;
|
||||
gp_layer.vertex_color_opacity = 0.0f;
|
||||
// gp_layer.vertex_color_opacity = 0.0f; unused
|
||||
gp_layer.thickness_offset = 0.0f;
|
||||
gp_layer.tint = float4(1.0f, 1.0f, 1.0f, 0.0f);
|
||||
gp_layer.stroke_index_offset = 0.0f;
|
||||
|
@ -8,7 +8,7 @@ void gpencil_color_output(vec4 stroke_col, vec4 vert_col, float vert_strength, f
|
||||
{
|
||||
/* Mix stroke with other colors. */
|
||||
vec4 mixed_col = stroke_col;
|
||||
mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a * gpVertexColorOpacity);
|
||||
mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a);
|
||||
mixed_col.rgb = mix(mixed_col.rgb, gpLayerTint.rgb, gpLayerTint.a);
|
||||
mixed_col.a *= vert_strength * gpLayerOpacity;
|
||||
/**
|
||||
|
@ -256,13 +256,6 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr
|
||||
tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets));
|
||||
}
|
||||
|
||||
if (grease_pencil.runtime->has_stroke_buffer()) {
|
||||
const int num_buffer_points = grease_pencil.runtime->stroke_buffer().size();
|
||||
total_verts_num += 1 + num_buffer_points + 1;
|
||||
total_triangles_num += num_buffer_points * 2;
|
||||
/* TODO: triangles for stroke buffer. */
|
||||
}
|
||||
|
||||
static GPUVertFormat format_edit_points_pos = {0};
|
||||
if (format_edit_points_pos.attr_len == 0) {
|
||||
GPU_vertformat_attr_add(&format_edit_points_pos, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
|
||||
@ -316,6 +309,8 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
const VArray<float> radii = drawing.radii();
|
||||
const VArray<float> opacities = drawing.opacities();
|
||||
const VArray<ColorGeometry4f> vertex_colors = *attributes.lookup_or_default<ColorGeometry4f>(
|
||||
"vertex_color", ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
/* Assumes that if the ".selection" attribute does not exist, all points are selected. */
|
||||
const VArray<float> selection_float = *attributes.lookup_or_default<float>(
|
||||
".selection", ATTR_DOMAIN_POINT, true);
|
||||
@ -358,9 +353,8 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr
|
||||
/* TODO: Populate fill UVs. */
|
||||
s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0;
|
||||
|
||||
/* TODO: Populate vertex color and fill color. */
|
||||
copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
copy_v4_v4(c_vert.vcol, vertex_colors[point_i]);
|
||||
copy_v4_v4(c_vert.fcol, vertex_colors[point_i]);
|
||||
c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f;
|
||||
|
||||
int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT;
|
||||
@ -424,51 +418,6 @@ static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfr
|
||||
});
|
||||
}
|
||||
|
||||
if (grease_pencil.runtime->has_stroke_buffer()) {
|
||||
Span<bke::greasepencil::StrokePoint> points = grease_pencil.runtime->stroke_buffer();
|
||||
const int verts_start_offset = v_offset;
|
||||
const int num_verts = 1 + points.size() + 1;
|
||||
IndexRange verts_range = IndexRange(verts_start_offset, num_verts);
|
||||
MutableSpan<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
|
||||
MutableSpan<GreasePencilColorVert> cols_slice = cols.slice(verts_range);
|
||||
const int material_nr = grease_pencil.runtime->stroke_cache.mat;
|
||||
|
||||
verts_slice.first().mat = -1;
|
||||
for (const int i : IndexRange(points.size())) {
|
||||
const int idx = i + 1;
|
||||
GreasePencilStrokeVert &s_vert = verts_slice[idx];
|
||||
GreasePencilColorVert &c_vert = cols_slice[idx];
|
||||
const bke::greasepencil::StrokePoint &point = points[i];
|
||||
|
||||
copy_v3_v3(s_vert.pos, point.position);
|
||||
s_vert.radius = point.radius;
|
||||
s_vert.opacity = point.opacity;
|
||||
s_vert.point_id = verts_range[idx];
|
||||
s_vert.stroke_id = verts_range.first();
|
||||
s_vert.mat = material_nr;
|
||||
|
||||
/* TODO */
|
||||
s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(0.0f, 1.0f, 1.0f);
|
||||
/* TODO */
|
||||
s_vert.u_stroke = 0;
|
||||
/* TODO */
|
||||
s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0;
|
||||
|
||||
/* TODO */
|
||||
copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
/* TODO */
|
||||
c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f;
|
||||
|
||||
int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT;
|
||||
GPU_indexbuf_add_tri_verts(&ibo, v_mat + 0, v_mat + 1, v_mat + 2);
|
||||
GPU_indexbuf_add_tri_verts(&ibo, v_mat + 2, v_mat + 1, v_mat + 3);
|
||||
}
|
||||
|
||||
verts_slice.last().mat = -1;
|
||||
}
|
||||
|
||||
/* Mark last 2 verts as invalid. */
|
||||
verts[total_verts_num + 0].mat = -1;
|
||||
verts[total_verts_num + 1].mat = -1;
|
||||
|
@ -11,6 +11,8 @@ set(INC
|
||||
../../imbuf
|
||||
../../makesrna
|
||||
../../windowmanager
|
||||
../../bmesh
|
||||
../../../../extern/curve_fit_nd
|
||||
# RNA_prototypes.h
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
@ -22,9 +24,11 @@ set(SRC
|
||||
intern/grease_pencil_add.cc
|
||||
intern/grease_pencil_edit.cc
|
||||
intern/grease_pencil_frames.cc
|
||||
intern/grease_pencil_geom.cc
|
||||
intern/grease_pencil_layers.cc
|
||||
intern/grease_pencil_ops.cc
|
||||
intern/grease_pencil_select.cc
|
||||
intern/grease_pencil_utils.cc
|
||||
)
|
||||
|
||||
set(LIB
|
||||
@ -32,6 +36,7 @@ set(LIB
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
extern_curve_fit_nd
|
||||
)
|
||||
|
||||
blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
@ -237,7 +237,8 @@ void gaussian_blur_1D(const GSpan src,
|
||||
bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
/* Reduces unnecessary code generation. */
|
||||
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, float3>) {
|
||||
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, float2> ||
|
||||
std::is_same_v<T, float3>) {
|
||||
gaussian_blur_1D(src.typed<T>(),
|
||||
iterations,
|
||||
influence,
|
||||
@ -394,56 +395,52 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot)
|
||||
/** \name Simplify Stroke Operator
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* An implementation of the Ramer-Douglas-Peucker algorithm.
|
||||
*
|
||||
* \param range: The range to simplify.
|
||||
* \param epsilon: The threshold distance from the coord between two points for when a point
|
||||
* in-between needs to be kept.
|
||||
* \param dist_function: A function that computes the distance to a point at an index in the range.
|
||||
* The IndexRange is a subrange of \a range and the index is an index relative to the subrange.
|
||||
* \param points_to_delete: Writes true to the indecies for which the points should be removed.
|
||||
* \returns the total number of points to remove.
|
||||
*/
|
||||
int64_t ramer_douglas_peucker_simplify(const IndexRange range,
|
||||
const float epsilon,
|
||||
const FunctionRef<float(IndexRange, int64_t)> dist_function,
|
||||
MutableSpan<bool> points_to_delete)
|
||||
static float dist_to_interpolated(
|
||||
float3 pos, float3 posA, float3 posB, float val, float valA, float valB)
|
||||
{
|
||||
/* Mark all points to not be removed. */
|
||||
points_to_delete.slice(range).fill(false);
|
||||
int64_t total_points_to_remove = 0;
|
||||
float dist1 = math::distance_squared(posA, pos);
|
||||
float dist2 = math::distance_squared(posB, pos);
|
||||
|
||||
Stack<IndexRange> stack;
|
||||
stack.push(range);
|
||||
while (!stack.is_empty()) {
|
||||
const IndexRange sub_range = stack.pop();
|
||||
if (dist1 + dist2 > 0) {
|
||||
float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2));
|
||||
return math::distance(interpolated_val, val);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* Compute the maximum distance and the corresponding distance. */
|
||||
float max_dist = -1.0f;
|
||||
int max_index = -1;
|
||||
for (const int64_t sub_index : sub_range.index_range().drop_front(1).drop_back(1)) {
|
||||
const float dist = dist_function(sub_range, sub_index);
|
||||
if (dist > max_dist) {
|
||||
max_dist = dist;
|
||||
max_index = sub_index;
|
||||
}
|
||||
}
|
||||
static int64_t stroke_simplify(const IndexRange points,
|
||||
const bool cyclic,
|
||||
const float epsilon,
|
||||
const FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
|
||||
MutableSpan<bool> points_to_delete)
|
||||
{
|
||||
int64_t total_points_to_delete = 0;
|
||||
const Span<bool> curve_selection = points_to_delete.slice(points);
|
||||
if (!curve_selection.contains(true)) {
|
||||
return total_points_to_delete;
|
||||
}
|
||||
|
||||
if (max_dist > epsilon) {
|
||||
/* Found point outside the epsilon-sized strip. Repeat the search on the left & right side.
|
||||
*/
|
||||
stack.push(sub_range.slice(IndexRange(max_index + 1)));
|
||||
stack.push(sub_range.slice(IndexRange(max_index, sub_range.size() - max_index)));
|
||||
}
|
||||
else {
|
||||
/* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */
|
||||
const IndexRange inside_range = sub_range.drop_front(1).drop_back(1);
|
||||
total_points_to_remove += inside_range.size();
|
||||
points_to_delete.slice(inside_range).fill(true);
|
||||
const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last());
|
||||
|
||||
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(curve_selection, true);
|
||||
threading::parallel_for(
|
||||
selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) {
|
||||
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) {
|
||||
total_points_to_delete += ramer_douglas_peucker_simplify(
|
||||
range.shift(points.start()), epsilon, dist_function, points_to_delete);
|
||||
}
|
||||
});
|
||||
|
||||
/* For cyclic curves, simplify the last segment. */
|
||||
if (cyclic && points.size() > 2 && is_last_segment_selected) {
|
||||
const float dist = dist_function(points.last(1), points.first(), points.last());
|
||||
if (dist <= epsilon) {
|
||||
points_to_delete[points.last()] = true;
|
||||
total_points_to_delete++;
|
||||
}
|
||||
}
|
||||
return total_points_to_remove;
|
||||
|
||||
return total_points_to_delete;
|
||||
}
|
||||
|
||||
static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
|
||||
@ -468,14 +465,31 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
|
||||
}
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
const VArray<float> radii = drawing.radii();
|
||||
|
||||
/* Distance function for `ramer_douglas_peucker_simplify`. */
|
||||
auto dist_func = [&](IndexRange range, int64_t index_in_range) {
|
||||
const Span<float3> position_slice = positions.slice(range);
|
||||
const float dist_position = dist_to_line_v3(
|
||||
position_slice[index_in_range], position_slice.first(), position_slice.last());
|
||||
return dist_position;
|
||||
};
|
||||
/* Distance functions for `ramer_douglas_peucker_simplify`. */
|
||||
const auto dist_function_positions =
|
||||
[positions](int64_t first_index, int64_t last_index, int64_t index) {
|
||||
const float dist_position = dist_to_line_v3(
|
||||
positions[index], positions[first_index], positions[last_index]);
|
||||
return dist_position;
|
||||
};
|
||||
const auto dist_function_positions_and_radii =
|
||||
[positions, radii](int64_t first_index, int64_t last_index, int64_t index) {
|
||||
filedescriptor marked this conversation as resolved
Outdated
|
||||
const float dist_position = dist_to_line_v3(
|
||||
positions[index], positions[first_index], positions[last_index]);
|
||||
/* We divide the distance by 2000.0f to convert from "pixels" to an actual distance.
|
||||
* For some reason, grease pencil strokes the thickness of strokes in pixels rather
|
||||
* than object space distance. */
|
||||
const float dist_radii = dist_to_interpolated(positions[index],
|
||||
positions[first_index],
|
||||
positions[last_index],
|
||||
radii[index],
|
||||
radii[first_index],
|
||||
radii[last_index]) /
|
||||
2000.0f;
|
||||
return math::max(dist_position, dist_radii);
|
||||
};
|
||||
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
@ -486,42 +500,30 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
|
||||
selection.materialize(points_to_delete);
|
||||
|
||||
std::atomic<int64_t> total_points_to_delete = 0;
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
const Span<bool> curve_selection = points_to_delete.as_span().slice(points);
|
||||
if (!curve_selection.contains(true)) {
|
||||
continue;
|
||||
if (radii.is_single()) {
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
total_points_to_delete += stroke_simplify(points,
|
||||
cyclic[curve_i],
|
||||
epsilon,
|
||||
dist_function_positions,
|
||||
points_to_delete.as_mutable_span());
|
||||
}
|
||||
|
||||
const bool is_last_segment_selected = (curve_selection.first() &&
|
||||
curve_selection.last());
|
||||
|
||||
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(
|
||||
curve_selection, true);
|
||||
threading::parallel_for(
|
||||
selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) {
|
||||
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges))
|
||||
{
|
||||
total_points_to_delete += ramer_douglas_peucker_simplify(
|
||||
range.shift(points.start()),
|
||||
epsilon,
|
||||
dist_func,
|
||||
points_to_delete.as_mutable_span());
|
||||
}
|
||||
});
|
||||
|
||||
/* For cyclic curves, simplify the last segment. */
|
||||
if (cyclic[curve_i] && curves.points_num() > 2 && is_last_segment_selected) {
|
||||
const float dist = dist_to_line_v3(
|
||||
positions[points.last()], positions[points.last(1)], positions[points.first()]);
|
||||
if (dist <= epsilon) {
|
||||
points_to_delete[points.last()] = true;
|
||||
total_points_to_delete++;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (radii.is_span()) {
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
total_points_to_delete += stroke_simplify(points,
|
||||
cyclic[curve_i],
|
||||
epsilon,
|
||||
dist_function_positions_and_radii,
|
||||
points_to_delete.as_mutable_span());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (total_points_to_delete > 0) {
|
||||
IndexMaskMemory memory;
|
||||
@ -554,7 +556,7 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
/* Simplify parameters. */
|
||||
prop = RNA_def_float(ot->srna, "factor", 0.001f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
|
||||
prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
}
|
||||
|
||||
|
@ -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 "BLI_generic_span.hh"
|
||||
#include "BLI_index_mask.hh"
|
||||
# |
devide
->divide
storkes
->strokes
?