Blender V4.5
grease_pencil_vertex_smear.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include "BKE_context.hh"
9#include "BKE_paint.hh"
10
11#include "BLI_index_mask.hh"
12#include "BLI_math_color.hh"
13#include "BLI_math_vector.hh"
14#include "BLI_task.hh"
15#include "BLI_virtual_array.hh"
16
18
20
21struct ColorGrid {
22 /* Flat array of colors. The length of this is size^2. */
24 /* Size of the grid. Used as the width and height. Should be divisible by 2. */
25 int size;
26 /* The size of each cell in pixels (screen space). Used as the cell width and height. */
28 /* The center position of the grid (screen space). */
30
31 /* Compute the screen space position based on a grid position and a center. */
33 {
34 const float2 centered = float2(pos - this->size / 2) + float2(0.5f);
35 return (centered * this->cell_size_px) + center;
36 }
37
38 /* Compute a grid position based on a screen space position and a center. */
39 int2 coords_to_pos(const float2 coord, const float2 center) const
40 {
41 const int2 pos = int2(math::floor((coord - center) / float(this->cell_size_px)));
42 return pos + ((this->size + 1) / 2);
43 }
44
45 /* Compute a grid index (into the colors array) based on a grid position. Returns -1 if the
46 * position is out of bounds. */
47 int pos_to_index(const int2 pos) const
48 {
49 if (pos.x >= 0 && pos.x < this->size && pos.y >= 0 && pos.y < this->size) {
50 return pos.y * this->size + pos.x;
51 }
52 return -1;
53 }
54};
55
56class VertexSmearOperation : public GreasePencilStrokeOperationCommon {
58
59 public:
60 void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
61 void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
62 void on_stroke_done(const bContext &C) override;
63
64 private:
65 ColorGrid color_grid_;
66 void init_color_grid(const bContext &C, float2 start_position);
67};
68
69void VertexSmearOperation::init_color_grid(const bContext &C, const float2 start_position)
70{
71 const Scene &scene = *CTX_data_scene(&C);
73 const Brush &brush = *BKE_paint_brush(&paint);
74 const bool use_selection_masking = GPENCIL_ANY_VERTEX_MASK(
76 const float radius = brush_radius(scene, brush, 1.0f);
77
78 /* Setup grid values. */
79 /* TODO: Make this a setting. */
80 color_grid_.cell_size_px = 10.0f;
81 color_grid_.center = start_position;
82 color_grid_.size = int(math::ceil((radius * 2.0f) / color_grid_.cell_size_px));
83
84 /* Initialize the color array. */
85 const int grid_array_length = color_grid_.size * color_grid_.size;
86 color_grid_.colors.reinitialize(grid_array_length);
87 color_grid_.colors.fill(float4(0.0f));
88
89 /* Initialize grid values. */
91 IndexMaskMemory memory;
92 const IndexMask point_selection = point_mask_for_stroke_operation(
93 params, use_selection_masking, memory);
94 if (point_selection.is_empty()) {
95 return false;
96 }
97 const Array<float2> view_positions = calculate_view_positions(params, point_selection);
98 const Array<float> radii = calculate_view_radii(params, point_selection);
99 const VArray<ColorGeometry4f> vertex_colors = params.drawing.vertex_colors();
100 /* Compute the colors in the grid by averaging the vertex colors of the points that
101 * intersect each cell. */
102 Array<int> points_per_cell(grid_array_length, 0);
103 point_selection.foreach_index([&](const int point_i) {
104 const float2 view_pos = view_positions[point_i];
105 const float view_radius = radii[point_i];
106 const ColorGeometry4f color = vertex_colors[point_i];
107
108 const int bounds_size = math::floor(view_radius / color_grid_.cell_size_px) * 2 + 1;
109 const int2 bounds_center = color_grid_.coords_to_pos(view_pos, color_grid_.center);
110 const int2 bounds_min = bounds_center - (bounds_size / 2);
111 const int2 bounds_max = bounds_center + (bounds_size / 2);
112 if (!(bounds_min.x < color_grid_.size && bounds_max.x >= 0 &&
113 bounds_min.y < color_grid_.size && bounds_max.y >= 0))
114 {
115 /* Point is out of bounds. */
116 return;
117 }
118 for (int y = bounds_min.y; y <= bounds_max.y; y++) {
119 for (int x = bounds_min.x; x <= bounds_max.x; x++) {
120 const int2 grid_pos = int2(x, y);
121 const int cell_i = color_grid_.pos_to_index(grid_pos);
122 if (cell_i == -1) {
123 continue;
124 }
125 const float2 cell_pos = color_grid_.pos_to_coords(grid_pos, color_grid_.center);
126 if (math::distance_squared(cell_pos, view_pos) <= view_radius * view_radius) {
127 color_grid_.colors[cell_i] += float4(color.r, color.g, color.b, 1.0f);
128 points_per_cell[cell_i]++;
129 }
130 }
131 }
132 });
133 /* Divide by the total to get the average color per cell. */
134 for (const int cell_i : color_grid_.colors.index_range()) {
135 if (points_per_cell[cell_i] > 0) {
136 color_grid_.colors[cell_i] *= 1.0f / float(points_per_cell[cell_i]);
137 }
138 }
139 /* Don't trigger updates for the grid initialization. */
140 return false;
141 });
142}
143
145{
146 this->init_stroke(C, start_sample);
147 this->init_color_grid(C, start_sample.mouse_position);
148}
149
151 const InputSample &extension_sample)
152{
153 const Scene &scene = *CTX_data_scene(&C);
155 const Brush &brush = *BKE_paint_brush(&paint);
156 const float radius = brush_radius(scene, brush, extension_sample.pressure);
157
158 const bool use_selection_masking = GPENCIL_ANY_VERTEX_MASK(
160
162 IndexMaskMemory memory;
163 const IndexMask point_selection = point_mask_for_stroke_operation(
164 params, use_selection_masking, memory);
165 if (point_selection.is_empty()) {
166 return false;
167 }
168 const Array<float2> view_positions = calculate_view_positions(params, point_selection);
169 MutableSpan<ColorGeometry4f> vertex_colors = params.drawing.vertex_colors_for_write();
170 point_selection.foreach_index(GrainSize(1024), [&](const int64_t point_i) {
171 const float2 view_pos = view_positions[point_i];
172 const int2 grid_pos = color_grid_.coords_to_pos(view_pos, extension_sample.mouse_position);
173 const int cell_i = color_grid_.pos_to_index(grid_pos);
174 if (cell_i == -1 || color_grid_.colors[cell_i][3] == 0.0f) {
175 return;
176 }
177 const ColorGeometry4f mix_color = ColorGeometry4f(color_grid_.colors[cell_i]);
178
179 const float distance_falloff = math::clamp(
180 1.0f - (math::distance(color_grid_.center, view_pos) / radius * 2), 0.0f, 1.0f);
181 const float influence = brush_point_influence(scene,
182 brush,
183 view_pos,
184 extension_sample,
185 params.multi_frame_falloff) *
186 distance_falloff;
187 if (influence > 0.0f) {
188 ColorGeometry4f &color = vertex_colors[point_i];
189 const float alpha = color.a;
190 color = math::interpolate(color, mix_color, influence);
191 color.a = alpha;
192 }
193 });
194 return true;
195 });
196}
197
199
200std::unique_ptr<GreasePencilStrokeOperation> new_vertex_smear_operation()
201{
202 return std::make_unique<VertexSmearOperation>();
203}
204
205} // namespace blender::ed::sculpt_paint::greasepencil
Scene * CTX_data_scene(const bContext *C)
Low-level operations for grease pencil.
Paint * BKE_paint_get_active_from_context(const bContext *C)
Definition paint.cc:467
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:636
#define GPENCIL_ANY_VERTEX_MASK(flag)
eGP_vertex_SelectMaskFlag
#define C
Definition RandGen.cpp:29
long long int int64_t
void foreach_editable_drawing(const bContext &C, FunctionRef< bool(const GreasePencilStrokeParams &params, const DeltaProjectionFunc &projection_fn)> fn) const
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
void foreach_index(Fn &&fn) const
uint pos
#define this
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
Array< float > calculate_view_radii(const GreasePencilStrokeParams &params, const IndexMask &selection)
IndexMask point_mask_for_stroke_operation(const GreasePencilStrokeParams &params, bool use_selection_masking, IndexMaskMemory &memory)
Array< float2 > calculate_view_positions(const GreasePencilStrokeParams &params, const IndexMask &selection)
float brush_point_influence(const Scene &scene, const Brush &brush, const float2 &co, const InputSample &sample, float multi_frame_falloff)
std::unique_ptr< GreasePencilStrokeOperation > new_vertex_smear_operation()
float brush_radius(const Scene &scene, const Brush &brush, float pressure)
T clamp(const T &a, const T &min, const T &max)
T floor(const T &a)
T distance(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T ceil(const T &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:342
struct ToolSettings * toolsettings
char gpencil_selectmode_vertex
float2 pos_to_coords(const int2 pos, const float2 center) const
int2 coords_to_pos(const float2 coord, const float2 center) const