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