Blender V5.0
sculpt_gesture.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
9#include "sculpt_gesture.hh"
10
11#include "MEM_guardedalloc.h"
12
13#include "DNA_vec_types.h"
14
15#include "BLI_bitmap_draw_2d.h"
16#include "BLI_lasso_2d.hh"
17#include "BLI_math_geom.h"
18#include "BLI_math_matrix.h"
19#include "BLI_math_matrix.hh"
21#include "BLI_math_vector.h"
22#include "BLI_math_vector.hh"
24#include "BLI_rect.h"
25
26#include "BKE_context.hh"
27#include "BKE_paint.hh"
28
29#include "ED_view3d.hh"
30
31#include "RNA_access.hh"
32#include "RNA_define.hh"
33
34#include "WM_api.hh"
35#include "WM_types.hh"
36
37#include "paint_intern.hh"
38#include "sculpt_intern.hh"
39
41
43{
44 RNA_def_boolean(ot->srna,
45 "use_front_faces_only",
46 false,
47 "Front Faces Only",
48 "Affect only faces facing towards the view");
49
50 if (shapeType == ShapeType::Line) {
51 RNA_def_boolean(ot->srna,
52 "use_limit_to_segment",
53 false,
54 "Limit to Segment",
55 "Apply the gesture action only to the area that is contained within the "
56 "segment without extending its effect to the entire line");
57 }
58}
59
60static void init_common(bContext *C, const wmOperator *op, GestureData &gesture_data)
61{
64 const Object &object = *gesture_data.vc.obact;
65
66 /* Operator properties. */
67 gesture_data.front_faces_only = RNA_boolean_get(op->ptr, "use_front_faces_only");
69
70 /* SculptSession */
71 gesture_data.ss = object.sculpt;
72
73 /* Symmetry. */
75
76 /* View Normal. */
77 const float3x3 view_inv(float4x4(gesture_data.vc.rv3d->viewinv));
78 const float3 view_dir = math::transform_direction(view_inv, {0.0f, 0.0f, 1.0f});
79
80 gesture_data.world_space_view_normal = math::normalize(view_dir);
81 gesture_data.true_view_normal = math::normalize(
82 math::transform_direction(object.world_to_object(), view_dir));
83
84 /* View Origin. */
85 gesture_data.world_space_view_origin = gesture_data.vc.rv3d->viewinv[3];
86 gesture_data.true_view_origin = gesture_data.vc.rv3d->viewinv[3];
87}
88
89static void lasso_px_cb(const int x, const int x_end, const int y, void *user_data)
90{
91 GestureData *gesture_data = static_cast<GestureData *>(user_data);
92 LassoData *lasso = &gesture_data->lasso;
93 int index = (y * lasso->width) + x;
94 const int index_end = (y * lasso->width) + x_end;
95 do {
96 lasso->mask_px[index].set();
97 } while (++index != index_end);
98}
99
100std::unique_ptr<GestureData> init_from_polyline(bContext *C, wmOperator *op)
101{
102 return init_from_lasso(C, op);
103}
104
105std::unique_ptr<GestureData> init_from_lasso(bContext *C, wmOperator *op)
106{
107 const Array<int2> mcoords = WM_gesture_lasso_path_to_array(C, op);
108 if (mcoords.size() <= 1) {
109 return nullptr;
110 }
111
112 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
113 gesture_data->shape_type = ShapeType::Lasso;
114
115 init_common(C, op, *gesture_data);
116
117 gesture_data->lasso.projviewobjmat = ED_view3d_ob_project_mat_get(gesture_data->vc.rv3d,
118 gesture_data->vc.obact);
119 BLI_lasso_boundbox(&gesture_data->lasso.boundbox, mcoords);
120 const int lasso_width = 1 + gesture_data->lasso.boundbox.xmax -
121 gesture_data->lasso.boundbox.xmin;
122 const int lasso_height = 1 + gesture_data->lasso.boundbox.ymax -
123 gesture_data->lasso.boundbox.ymin;
124 gesture_data->lasso.width = lasso_width;
125 gesture_data->lasso.mask_px.resize(lasso_width * lasso_height);
126
127 BLI_bitmap_draw_2d_poly_v2i_n(gesture_data->lasso.boundbox.xmin,
128 gesture_data->lasso.boundbox.ymin,
129 gesture_data->lasso.boundbox.xmax,
130 gesture_data->lasso.boundbox.ymax,
131 mcoords,
133 gesture_data.get());
134
135 BoundBox bb;
137 gesture_data->true_clip_planes,
138 gesture_data->vc.region,
139 gesture_data->vc.obact,
140 &gesture_data->lasso.boundbox);
141
142 gesture_data->gesture_points.reinitialize(mcoords.size());
143 for (const int i : mcoords.index_range()) {
144 gesture_data->gesture_points[i][0] = mcoords[i][0];
145 gesture_data->gesture_points[i][1] = mcoords[i][1];
146 }
147
148 return gesture_data;
149}
150
151std::unique_ptr<GestureData> init_from_box(bContext *C, wmOperator *op)
152{
153 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
154 gesture_data->shape_type = ShapeType::Box;
155
156 init_common(C, op, *gesture_data);
157
158 rcti rect;
160
161 BoundBox bb;
163 &bb, gesture_data->true_clip_planes, gesture_data->vc.region, gesture_data->vc.obact, &rect);
164
165 gesture_data->gesture_points.reinitialize(4);
166
167 gesture_data->gesture_points[0][0] = rect.xmax;
168 gesture_data->gesture_points[0][1] = rect.ymax;
169
170 gesture_data->gesture_points[1][0] = rect.xmax;
171 gesture_data->gesture_points[1][1] = rect.ymin;
172
173 gesture_data->gesture_points[2][0] = rect.xmin;
174 gesture_data->gesture_points[2][1] = rect.ymin;
175
176 gesture_data->gesture_points[3][0] = rect.xmin;
177 gesture_data->gesture_points[3][1] = rect.ymax;
178 return gesture_data;
179}
180
181static void line_plane_from_tri(float *r_plane,
182 const GestureData &gesture_data,
183 const bool flip,
184 const float3 &p1,
185 const float3 &p2,
186 const float3 &p3)
187{
188 float3 normal;
189 normal_tri_v3(normal, p1, p2, p3);
190 normal = math::normalize(
191 math::transform_direction(gesture_data.vc.obact->world_to_object(), normal));
192 if (flip) {
193 normal *= -1.0f;
194 }
195 const float3 plane_point_object_space = math::transform_point(
196 gesture_data.vc.obact->world_to_object(), p1);
197 plane_from_point_normal_v3(r_plane, plane_point_object_space, normal);
198}
199
200/* Creates 4 points in the plane defined by the line and 2 extra points with an offset relative to
201 * this plane. */
202static void line_calculate_plane_points(const GestureData &gesture_data,
203 const Span<float2> line_points,
204 std::array<float3, 4> &r_plane_points,
205 std::array<float3, 2> &r_offset_plane_points)
206{
207 const float3 depth_point = gesture_data.true_view_origin + gesture_data.true_view_normal;
209 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[0], r_plane_points[0]);
211 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[1], r_plane_points[3]);
212
213 const float3 offset_depth_point = gesture_data.true_view_origin +
214 gesture_data.true_view_normal * 10.0f;
215 ED_view3d_win_to_3d(gesture_data.vc.v3d,
216 gesture_data.vc.region,
217 offset_depth_point,
218 line_points[0],
219 r_plane_points[1]);
220 ED_view3d_win_to_3d(gesture_data.vc.v3d,
221 gesture_data.vc.region,
222 offset_depth_point,
223 line_points[1],
224 r_plane_points[2]);
225
226 float3 normal;
227 normal_tri_v3(normal, r_plane_points[0], r_plane_points[1], r_plane_points[2]);
228 r_offset_plane_points[0] = r_plane_points[0] + normal;
229 r_offset_plane_points[1] = r_plane_points[3] + normal;
230}
231
232std::unique_ptr<GestureData> init_from_line(bContext *C, const wmOperator *op)
233{
234 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
235 gesture_data->shape_type = ShapeType::Line;
236 gesture_data->line.use_side_planes = RNA_boolean_get(op->ptr, "use_limit_to_segment");
237
238 init_common(C, op, *gesture_data);
239
240 gesture_data->gesture_points.reinitialize(2);
241 gesture_data->gesture_points[0] = {float(RNA_int_get(op->ptr, "xstart")),
242 float(RNA_int_get(op->ptr, "ystart"))};
243 gesture_data->gesture_points[1] = {float(RNA_int_get(op->ptr, "xend")),
244 float(RNA_int_get(op->ptr, "yend"))};
245
246 gesture_data->line.flip = RNA_boolean_get(op->ptr, "flip");
247
248 std::array<float3, 4> plane_points;
249 std::array<float3, 2> offset_plane_points;
251 *gesture_data, gesture_data->gesture_points, plane_points, offset_plane_points);
252
253 /* Calculate line plane and normal. */
254 const bool flip = gesture_data->line.flip ^ (!gesture_data->vc.rv3d->is_persp);
255 line_plane_from_tri(gesture_data->line.true_plane,
256 *gesture_data,
257 flip,
258 plane_points[0],
259 plane_points[1],
260 plane_points[2]);
261
262 /* Calculate the side planes. */
263 line_plane_from_tri(gesture_data->line.true_side_plane[0],
264 *gesture_data,
265 false,
266 plane_points[1],
267 plane_points[0],
268 offset_plane_points[0]);
269 line_plane_from_tri(gesture_data->line.true_side_plane[1],
270 *gesture_data,
271 false,
272 plane_points[3],
273 plane_points[2],
274 offset_plane_points[1]);
275
276 return gesture_data;
277}
278
283
284static void flip_plane(float out[4], const float in[4], const char symm)
285{
286 if (symm & PAINT_SYMM_X) {
287 out[0] = -in[0];
288 }
289 else {
290 out[0] = in[0];
291 }
292 if (symm & PAINT_SYMM_Y) {
293 out[1] = -in[1];
294 }
295 else {
296 out[1] = in[1];
297 }
298 if (symm & PAINT_SYMM_Z) {
299 out[2] = -in[2];
300 }
301 else {
302 out[2] = in[2];
303 }
304
305 out[3] = in[3];
306}
307
308static void flip_for_symmetry_pass(GestureData &gesture_data, const ePaintSymmetryFlags symmpass)
309{
310 gesture_data.symmpass = symmpass;
311 for (int j = 0; j < 4; j++) {
312 flip_plane(gesture_data.clip_planes[j], gesture_data.true_clip_planes[j], symmpass);
313 }
314
315 negate_m4(gesture_data.clip_planes);
316
317 gesture_data.view_normal = symmetry_flip(gesture_data.true_view_normal, symmpass);
318 gesture_data.view_origin = symmetry_flip(gesture_data.true_view_origin, symmpass);
319 flip_plane(gesture_data.line.plane, gesture_data.line.true_plane, symmpass);
320 flip_plane(gesture_data.line.side_plane[0], gesture_data.line.true_side_plane[0], symmpass);
321 flip_plane(gesture_data.line.side_plane[1], gesture_data.line.true_side_plane[1], symmpass);
322}
323
325{
326 const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*gesture_data.vc.obact);
327 std::array<float4, 3> clip_planes;
328 copy_v4_v4(clip_planes[0], gesture_data.line.plane);
329 copy_v4_v4(clip_planes[1], gesture_data.line.side_plane[0]);
330 copy_v4_v4(clip_planes[2], gesture_data.line.side_plane[1]);
331
332 gesture_data.node_mask = bke::pbvh::search_nodes(
333 pbvh, gesture_data.node_mask_memory, [&](const bke::pbvh::Node &node) {
334 return bke::pbvh::node_frustum_contain_aabb(
335 node, Span(clip_planes).take_front(gesture_data.line.use_side_planes ? 3 : 1));
336 });
337}
338
340{
341 const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*gesture_data.vc.obact);
342 float clip_planes[4][4];
343 copy_m4_m4(clip_planes, gesture_data.clip_planes);
344 negate_m4(clip_planes);
345
346 Span planes(reinterpret_cast<float4 *>(clip_planes), 4);
347
348 gesture_data.node_mask = bke::pbvh::search_nodes(
349 pbvh, gesture_data.node_mask_memory, [&](const bke::pbvh::Node &node) {
350 switch (gesture_data.selection_type) {
351 case SelectionType::Inside:
352 return bke::pbvh::node_frustum_contain_aabb(node, planes);
353 case SelectionType::Outside:
354 /* Certain degenerate cases of a lasso shape can cause the resulting
355 * frustum planes to enclose a node's AABB, therefore we must submit it
356 * to be more thoroughly evaluated. */
357 if (gesture_data.shape_type == ShapeType::Lasso) {
358 return true;
359 }
360 return bke::pbvh::node_frustum_exclude_aabb(node, planes);
361 }
363 return false;
364 });
365}
366
367static void update_affected_nodes(GestureData &gesture_data)
368{
369 switch (gesture_data.shape_type) {
370 case ShapeType::Box:
371 case ShapeType::Lasso:
373 break;
374 case ShapeType::Line:
376 break;
377 }
378}
379
380static bool is_affected_lasso(const GestureData &gesture_data, const float3 &position)
381{
382 const float3 co_final = symmetry_flip(position, gesture_data.symmpass);
383
384 /* First project point to 2d space. */
385 const float2 scr_co_f = ED_view3d_project_float_v2_m4(
386 gesture_data.vc.region, co_final, gesture_data.lasso.projviewobjmat);
387
388 int2 screen_coords = {int(scr_co_f[0]), int(scr_co_f[1])};
389
390 /* Clip against lasso boundbox. */
391 const LassoData &lasso = gesture_data.lasso;
392 if (!BLI_rcti_isect_pt_v(&lasso.boundbox, screen_coords)) {
393 return gesture_data.selection_type == SelectionType::Outside;
394 }
395
396 screen_coords[0] -= lasso.boundbox.xmin;
397 screen_coords[1] -= lasso.boundbox.ymin;
398
399 const bool bitmap_result =
400 lasso.mask_px[screen_coords[1] * lasso.width + screen_coords[0]].test();
401 switch (gesture_data.selection_type) {
403 return bitmap_result;
405 return !bitmap_result;
406 }
408 return false;
409}
410
411bool is_affected(const GestureData &gesture_data, const float3 &position, const float3 &normal)
412{
413 const float dot = math::dot(gesture_data.view_normal, normal);
414 const bool is_affected_front_face = !(gesture_data.front_faces_only && dot < 0.0f);
415
416 if (!is_affected_front_face) {
417 return false;
418 }
419
420 switch (gesture_data.shape_type) {
421 case ShapeType::Box: {
422 const bool is_contained = isect_point_planes_v3(gesture_data.clip_planes, 4, position);
423 return ((is_contained && gesture_data.selection_type == SelectionType::Inside) ||
424 (!is_contained && gesture_data.selection_type == SelectionType::Outside));
425 }
426 case ShapeType::Lasso:
427 return is_affected_lasso(gesture_data, position);
428 case ShapeType::Line:
429 if (gesture_data.line.use_side_planes) {
430 return plane_point_side_v3(gesture_data.line.plane, position) > 0.0f &&
431 plane_point_side_v3(gesture_data.line.side_plane[0], position) > 0.0f &&
432 plane_point_side_v3(gesture_data.line.side_plane[1], position) > 0.0f;
433 }
434 return plane_point_side_v3(gesture_data.line.plane, position) > 0.0f;
435 }
436 return false;
437}
438
439void filter_factors(const GestureData &gesture_data,
440 const Span<float3> positions,
441 const Span<float3> normals,
442 const MutableSpan<float> factors)
443{
444 for (const int i : positions.index_range()) {
445 if (!is_affected(gesture_data, positions[i], normals[i])) {
446 factors[i] = 0.0f;
447 }
448 }
449}
450
451void apply(bContext &C, GestureData &gesture_data, wmOperator &op)
452{
453 const Operation *operation = gesture_data.operation;
454
455 operation->begin(C, op, gesture_data);
456
457 for (int symmpass = 0; symmpass <= gesture_data.symm; symmpass++) {
458 if (is_symmetry_iteration_valid(symmpass, gesture_data.symm)) {
459 flip_for_symmetry_pass(gesture_data, ePaintSymmetryFlags(symmpass));
460 update_affected_nodes(gesture_data);
461
462 operation->apply_for_symmetry_pass(C, gesture_data);
463 }
464 }
465
466 operation->end(C, gesture_data);
467
469}
470} // namespace blender::ed::sculpt_paint::gesture
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
void BLI_bitmap_draw_2d_poly_v2i_n(int xmin, int ymin, int xmax, int ymax, blender::Span< blender::int2 > verts, void(*callback)(int x, int x_end, int y, void *), void *user_data)
void BLI_lasso_boundbox(rcti *rect, blender::Span< blender::int2 > mcoords)
bool isect_point_planes_v3(const float(*planes)[4], int totplane, const float p[3])
void plane_from_point_normal_v3(float r_plane[4], const float plane_co[3], const float plane_no[3])
Definition math_geom.cc:217
MINLINE float plane_point_side_v3(const float plane[4], const float co[3])
float normal_tri_v3(float n[3], const float v1[3], const float v2[3], const float v3[3])
Definition math_geom.cc:41
void copy_m4_m4(float m1[4][4], const float m2[4][4])
void negate_m4(float R[4][4])
MINLINE void copy_v4_v4(float r[4], const float a[4])
bool BLI_rcti_isect_pt_v(const struct rcti *rect, const int xy[2])
ePaintSymmetryFlags
@ PAINT_SYMM_Y
@ PAINT_SYMM_X
@ PAINT_SYMM_Z
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
void ED_view3d_clipping_calc(BoundBox *bb, float planes[4][4], const ARegion *region, const Object *ob, const rcti *rect)
void ED_view3d_win_to_3d(const View3D *v3d, const ARegion *region, const float depth_pt[3], const float mval[2], float r_out[3])
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
#define C
Definition RandGen.cpp:29
BPy_StructRNA * depsgraph
int64_t size() const
Definition BLI_array.hh:256
IndexRange index_range() const
Definition BLI_array.hh:360
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
nullptr float
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
static float normals[][3]
#define in
#define out
pbvh::Tree * pbvh_get(Object &object)
Definition paint.cc:3052
IndexMask search_nodes(const Tree &pbvh, IndexMaskMemory &memory, FunctionRef< bool(const Node &)> filter_fn)
Definition pbvh.cc:2663
static void update_affected_nodes_by_line_plane(GestureData &gesture_data)
static void init_common(bContext *C, const wmOperator *op, GestureData &gesture_data)
static void lasso_px_cb(const int x, const int x_end, const int y, void *user_data)
std::unique_ptr< GestureData > init_from_box(bContext *C, wmOperator *op)
void operator_properties(wmOperatorType *ot, ShapeType shapeType)
static void flip_plane(float out[4], const float in[4], const char symm)
std::unique_ptr< GestureData > init_from_polyline(bContext *C, wmOperator *op)
static void update_affected_nodes(GestureData &gesture_data)
std::unique_ptr< GestureData > init_from_line(bContext *C, const wmOperator *op)
static void line_plane_from_tri(float *r_plane, const GestureData &gesture_data, const bool flip, const float3 &p1, const float3 &p2, const float3 &p3)
static void flip_for_symmetry_pass(GestureData &gesture_data, const ePaintSymmetryFlags symmpass)
void filter_factors(const GestureData &gesture_data, const Span< float3 > positions, const Span< float3 > normals, const MutableSpan< float > factors)
void apply(bContext &C, GestureData &gesture_data, wmOperator &op)
static bool is_affected_lasso(const GestureData &gesture_data, const float3 &position)
std::unique_ptr< GestureData > init_from_lasso(bContext *C, wmOperator *op)
static void update_affected_nodes_by_clip_planes(GestureData &gesture_data)
bool is_affected(const GestureData &gesture_data, const float3 &position, const float3 &normal)
static void line_calculate_plane_points(const GestureData &gesture_data, const Span< float2 > line_points, std::array< float3, 4 > &r_plane_points, std::array< float3, 2 > &r_offset_plane_points)
float3 symmetry_flip(const float3 &src, const ePaintSymmetryFlags symm)
bool is_symmetry_iteration_valid(const char i, const char symm)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
int RNA_int_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
ePaintSymmetryFlags SCULPT_mesh_symmetry_xyz_get(const Object &object)
Definition sculpt.cc:184
void SCULPT_tag_update_overlays(bContext *C)
Definition sculpt.cc:759
float viewinv[4][4]
RegionView3D * rv3d
Definition ED_view3d.hh:80
ARegion * region
Definition ED_view3d.hh:77
View3D * v3d
Definition ED_view3d.hh:78
Object * obact
Definition ED_view3d.hh:75
void(* end)(bContext &, GestureData &)
void(* begin)(bContext &, wmOperator &, GestureData &)
void(* apply_for_symmetry_pass)(bContext &, GestureData &)
int ymin
int ymax
int xmin
int xmax
struct PointerRNA * ptr
i
Definition text_draw.cc:230
wmOperatorType * ot
Definition wm_files.cc:4237
Array< int2 > WM_gesture_lasso_path_to_array(bContext *, wmOperator *op)
void WM_operator_properties_border_to_rcti(wmOperator *op, rcti *r_rect)