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 =
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
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
#define C
Definition RandGen.cpp:29
#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)
void normalize_vertex_weights(MDeformVert &dvert, const int active_vertex_group, const Span< bool > vertex_group_is_locked, const Span< bool > vertex_group_is_bone_deformed)
Array< Vector< MutableDrawingInfo > > retrieve_editable_drawings_grouped_per_frame(const Scene &scene, GreasePencil &grease_pencil)
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
VecBase< float, 2 > float2
#define FLT_MAX
Definition stdcycles.h:14
void WM_event_add_notifier(const bContext *C, uint type, void *reference)