Blender V4.3
grease_pencil_weight_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
8
10 /* Brush direction (angle) during a stroke movement. */
11 float2 brush_direction;
12 bool brush_direction_is_set;
13
16 bool get_brush_direction()
17 {
18 this->brush_direction = this->mouse_position - this->mouse_position_previous;
19
20 /* Skip tiny changes in direction, we want the bigger movements only. */
21 if (math::length_squared(this->brush_direction) < 9.0f) {
22 return this->brush_direction_is_set;
23 }
24
25 this->brush_direction = math::normalize(this->brush_direction);
26 this->brush_direction_is_set = true;
27 this->mouse_position_previous = this->mouse_position;
28
29 return true;
30 }
31
33 void apply_smear_tool(const BrushPoint &point,
34 DrawingWeightData &drawing_weight,
35 PointsTouchedByBrush &touched_points)
36 {
37 /* Find the nearest neighbors of the to-be-smeared point. */
38 KDTreeNearest_2d nearest_points[SMEAR_NEIGHBOUR_NUM];
39 const int point_num = BLI_kdtree_2d_find_nearest_n(
40 touched_points.kdtree,
41 drawing_weight.point_positions[point.drawing_point_index],
42 nearest_points,
44
45 /* For smearing a weight to point A, we look for a point B in the trail of the mouse
46 * movement, matching the last known brush angle best and with the shortest distance to A. */
47 float point_dot_product[SMEAR_NEIGHBOUR_NUM];
48 float min_distance = FLT_MAX, max_distance = -FLT_MAX;
49 int smear_point_num = 0;
50 for (const int i : IndexRange(point_num)) {
51 /* Skip the point we are about to smear. */
52 if (nearest_points[i].dist < FIND_NEAREST_POINT_EPSILON) {
53 continue;
54 }
55 const float2 direction_nearest_to_point = math::normalize(
56 drawing_weight.point_positions[point.drawing_point_index] -
57 float2(nearest_points[i].co));
58
59 /* Match point direction with brush direction. */
60 point_dot_product[i] = math::dot(direction_nearest_to_point, this->brush_direction);
61 if (point_dot_product[i] <= 0.0f) {
62 continue;
63 }
64 smear_point_num++;
65 min_distance = math::min(min_distance, nearest_points[i].dist);
66 max_distance = math::max(max_distance, nearest_points[i].dist);
67 }
68 if (smear_point_num == 0) {
69 return;
70 }
71
72 /* Find best match in angle and distance. */
73 int best_match = -1;
74 float max_score = 0.0f;
75 const float distance_normalizer = (min_distance == max_distance) ?
76 1.0f :
77 (0.95f / (max_distance - min_distance));
78 for (const int i : IndexRange(point_num)) {
79 if (point_dot_product[i] <= 0.0f) {
80 continue;
81 }
82 const float score = point_dot_product[i] *
83 (1.0f - (nearest_points[i].dist - min_distance) * distance_normalizer);
84 if (score > max_score) {
85 max_score = score;
86 best_match = i;
87 }
88 }
89 if (best_match == -1) {
90 return;
91 }
92 const float smear_weight = touched_points.weights[nearest_points[best_match].index];
93
94 apply_weight_to_point(point, smear_weight, drawing_weight);
95 }
96
97 public:
98 void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
99 {
100 using namespace blender::ed::greasepencil;
101
102 this->get_brush_settings(C, start_sample);
105
106 /* Get editable drawings grouped per frame number. When multi-frame editing is disabled, this
107 * is just one group for the current frame. When multi-frame editing is enabled, the selected
108 * keyframes are grouped per frame number. This way we can use Smear on multiple layers
109 * together instead of on every layer individually. */
110 const Scene *scene = CTX_data_scene(&C);
111 Array<Vector<MutableDrawingInfo>> drawings_per_frame =
112 retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
113
114 this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
115
116 /* Get weight data for all drawings in this frame group. */
117 for (const int frame_group : drawings_per_frame.index_range()) {
118 const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
119 this->init_weight_data_for_drawings(C, drawings, frame_group);
120 }
121 }
122
123 void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
124 {
125 using namespace blender::ed::greasepencil;
126
127 this->get_mouse_input_sample(extension_sample);
128
129 /* For the Smear tool, we use the direction of the brush during the stroke movement. The
130 * direction is derived from the current and previous mouse position. */
131 if (!this->get_brush_direction()) {
132 /* Abort when no direction is established yet. */
133 return;
134 }
135
136 /* Iterate over the drawings grouped per frame number. Collect all stroke points under the
137 * brush and smear them. */
138 std::atomic<bool> changed = false;
140 this->drawing_weight_data.index_range(), [&](const int frame_group) {
141 Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
142
143 /* For all layers at this key frame, collect the stroke points under the brush in a
144 * buffer. */
145 threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
146 for (const int point_index : drawing_weight.point_positions.index_range()) {
147 const float2 &co = drawing_weight.point_positions[point_index];
148
149 /* When the point is under the brush, add it to the brush point buffer. */
150 this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
151 }
152 });
153
154 /* Create a KDTree with all stroke points touched by the brush during the weight paint
155 * operation. */
157 drawing_weights);
158
159 /* Apply the Smear tool to all points in the brush buffer. */
160 threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
161 for (const BrushPoint &point : drawing_weight.points_in_brush) {
162 this->apply_smear_tool(point, drawing_weight, touched_points);
163
164 /* Normalize weights of bone-deformed vertex groups to 1.0f. */
165 if (this->auto_normalize) {
166 normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
167 drawing_weight.active_vertex_group,
168 drawing_weight.locked_vgroups,
169 drawing_weight.bone_deformed_vgroups);
170 }
171 }
172
173 if (!drawing_weight.points_in_brush.is_empty()) {
174 changed = true;
175 drawing_weight.points_in_brush.clear();
176 }
177 });
178
179 BLI_kdtree_2d_free(touched_points.kdtree);
180 });
181
182 if (changed) {
185 }
186 }
187
188 void on_stroke_done(const bContext & /*C*/) override {}
189};
190
191std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation()
192{
193 return std::make_unique<SmearWeightPaintOperation>();
194}
195
196} // namespace blender::ed::sculpt_paint::greasepencil
Scene * CTX_data_scene(const bContext *C)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
#define NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
int64_t size() const
Definition BLI_array.hh:245
IndexRange index_range() const
Definition BLI_array.hh:349
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 get_mouse_input_sample(const InputSample &input_sample, const float brush_widen_factor=1.0f)
PointsTouchedByBrush create_affected_points_kdtree(const Span< DrawingWeightData > drawing_weights)
void get_brush_settings(const bContext &C, const InputSample &start_sample)
void init_weight_data_for_drawings(const bContext &C, const Span< ed::greasepencil::MutableDrawingInfo > &drawings, const int frame_group)
void apply_weight_to_point(const BrushPoint &point, const float target_weight, DrawingWeightData &drawing_weight)
std::unique_ptr< GreasePencilStrokeOperation > new_weight_paint_smear_operation()
T length_squared(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T min(const T &a, const T &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T max(const T &a, const T &b)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:58
#define FLT_MAX
Definition stdcycles.h:14
void WM_event_add_notifier(const bContext *C, uint type, void *reference)