Blender V4.3
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"
20#include "BLI_math_vector.h"
21#include "BLI_math_vector.hh"
23#include "BLI_rect.h"
24#include "BLI_vector.hh"
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{
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) {
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, wmOperator *op, GestureData &gesture_data)
61{
63 gesture_data.vc = ED_view3d_viewcontext_init(C, depsgraph);
64 Object &ob = *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 = ob.sculpt;
72
73 /* Symmetry. */
75
76 /* View Normal. */
77 float mat[3][3];
78 float view_dir[3] = {0.0f, 0.0f, 1.0f};
79 copy_m3_m4(mat, gesture_data.vc.rv3d->viewinv);
80 mul_m3_v3(mat, view_dir);
81 normalize_v3_v3(gesture_data.world_space_view_normal, view_dir);
82 copy_m3_m4(mat, ob.world_to_object().ptr());
83 mul_m3_v3(mat, view_dir);
84 normalize_v3_v3(gesture_data.true_view_normal, view_dir);
85
86 /* View Origin. */
87 copy_v3_v3(gesture_data.world_space_view_origin, gesture_data.vc.rv3d->viewinv[3]);
88 copy_v3_v3(gesture_data.true_view_origin, gesture_data.vc.rv3d->viewinv[3]);
89}
90
91static void lasso_px_cb(int x, int x_end, int y, void *user_data)
92{
93 GestureData *gesture_data = static_cast<GestureData *>(user_data);
94 LassoData *lasso = &gesture_data->lasso;
95 int index = (y * lasso->width) + x;
96 int index_end = (y * lasso->width) + x_end;
97 do {
98 lasso->mask_px[index].set();
99 } while (++index != index_end);
100}
101
102std::unique_ptr<GestureData> init_from_polyline(bContext *C, wmOperator *op)
103{
104 return init_from_lasso(C, op);
105}
106
107std::unique_ptr<GestureData> init_from_lasso(bContext *C, wmOperator *op)
108{
109 const Array<int2> mcoords = WM_gesture_lasso_path_to_array(C, op);
110 if (mcoords.size() <= 1) {
111 return nullptr;
112 }
113
114 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
115 gesture_data->shape_type = ShapeType::Lasso;
116
117 init_common(C, op, *gesture_data);
118
119 gesture_data->lasso.projviewobjmat = ED_view3d_ob_project_mat_get(gesture_data->vc.rv3d,
120 gesture_data->vc.obact);
121 BLI_lasso_boundbox(&gesture_data->lasso.boundbox, mcoords);
122 const int lasso_width = 1 + gesture_data->lasso.boundbox.xmax -
123 gesture_data->lasso.boundbox.xmin;
124 const int lasso_height = 1 + gesture_data->lasso.boundbox.ymax -
125 gesture_data->lasso.boundbox.ymin;
126 gesture_data->lasso.width = lasso_width;
127 gesture_data->lasso.mask_px.resize(lasso_width * lasso_height);
128
129 BLI_bitmap_draw_2d_poly_v2i_n(gesture_data->lasso.boundbox.xmin,
130 gesture_data->lasso.boundbox.ymin,
131 gesture_data->lasso.boundbox.xmax,
132 gesture_data->lasso.boundbox.ymax,
133 mcoords,
135 gesture_data.get());
136
137 BoundBox bb;
139 gesture_data->true_clip_planes,
140 gesture_data->vc.region,
141 gesture_data->vc.obact,
142 &gesture_data->lasso.boundbox);
143
144 gesture_data->gesture_points.reinitialize(mcoords.size());
145 for (const int i : mcoords.index_range()) {
146 gesture_data->gesture_points[i][0] = mcoords[i][0];
147 gesture_data->gesture_points[i][1] = mcoords[i][1];
148 }
149
150 return gesture_data;
151}
152
153std::unique_ptr<GestureData> init_from_box(bContext *C, wmOperator *op)
154{
155 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
156 gesture_data->shape_type = ShapeType::Box;
157
158 init_common(C, op, *gesture_data);
159
160 rcti rect;
162
163 BoundBox bb;
165 &bb, gesture_data->true_clip_planes, gesture_data->vc.region, gesture_data->vc.obact, &rect);
166
167 gesture_data->gesture_points.reinitialize(4);
168
169 gesture_data->gesture_points[0][0] = rect.xmax;
170 gesture_data->gesture_points[0][1] = rect.ymax;
171
172 gesture_data->gesture_points[1][0] = rect.xmax;
173 gesture_data->gesture_points[1][1] = rect.ymin;
174
175 gesture_data->gesture_points[2][0] = rect.xmin;
176 gesture_data->gesture_points[2][1] = rect.ymin;
177
178 gesture_data->gesture_points[3][0] = rect.xmin;
179 gesture_data->gesture_points[3][1] = rect.ymax;
180 return gesture_data;
181}
182
183static void line_plane_from_tri(float *r_plane,
184 GestureData &gesture_data,
185 const bool flip,
186 const float p1[3],
187 const float p2[3],
188 const float p3[3])
189{
190 float normal[3];
191 normal_tri_v3(normal, p1, p2, p3);
192 mul_v3_mat3_m4v3(normal, gesture_data.vc.obact->world_to_object().ptr(), normal);
193 if (flip) {
194 mul_v3_fl(normal, -1.0f);
195 }
196 float plane_point_object_space[3];
197 mul_v3_m4v3(plane_point_object_space, gesture_data.vc.obact->world_to_object().ptr(), p1);
198 plane_from_point_normal_v3(r_plane, plane_point_object_space, normal);
199}
200
201/* Creates 4 points in the plane defined by the line and 2 extra points with an offset relative to
202 * this plane. */
203static void line_calculate_plane_points(GestureData &gesture_data,
204 float line_points[2][2],
205 float r_plane_points[4][3],
206 float r_offset_plane_points[2][3])
207{
208 float depth_point[3];
209 add_v3_v3v3(depth_point, gesture_data.true_view_origin, gesture_data.true_view_normal);
211 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[0], r_plane_points[0]);
213 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[1], r_plane_points[3]);
214
215 madd_v3_v3v3fl(depth_point, gesture_data.true_view_origin, gesture_data.true_view_normal, 10.0f);
217 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[0], r_plane_points[1]);
219 gesture_data.vc.v3d, gesture_data.vc.region, depth_point, line_points[1], r_plane_points[2]);
220
221 float normal[3];
222 normal_tri_v3(normal, r_plane_points[0], r_plane_points[1], r_plane_points[2]);
223 add_v3_v3v3(r_offset_plane_points[0], r_plane_points[0], normal);
224 add_v3_v3v3(r_offset_plane_points[1], r_plane_points[3], normal);
225}
226
227std::unique_ptr<GestureData> init_from_line(bContext *C, wmOperator *op)
228{
229 std::unique_ptr<GestureData> gesture_data = std::make_unique<GestureData>();
230 gesture_data->shape_type = ShapeType::Line;
231 gesture_data->line.use_side_planes = RNA_boolean_get(op->ptr, "use_limit_to_segment");
232
233 init_common(C, op, *gesture_data);
234
235 float line_points[2][2];
236 line_points[0][0] = RNA_int_get(op->ptr, "xstart");
237 line_points[0][1] = RNA_int_get(op->ptr, "ystart");
238 line_points[1][0] = RNA_int_get(op->ptr, "xend");
239 line_points[1][1] = RNA_int_get(op->ptr, "yend");
240
241 gesture_data->gesture_points.reinitialize(2);
242 gesture_data->gesture_points[0][0] = line_points[0][0];
243 gesture_data->gesture_points[0][1] = line_points[0][1];
244 gesture_data->gesture_points[1][0] = line_points[1][0];
245 gesture_data->gesture_points[1][1] = line_points[1][1];
246
247 gesture_data->line.flip = RNA_boolean_get(op->ptr, "flip");
248
249 float plane_points[4][3];
250 float offset_plane_points[2][3];
251 line_calculate_plane_points(*gesture_data, line_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 float clip_planes[3][4];
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 PBVHFrustumPlanes frustum{};
333 frustum.planes = clip_planes;
334 frustum.num_planes = gesture_data.line.use_side_planes ? 3 : 1;
335
336 gesture_data.node_mask = bke::pbvh::search_nodes(
337 pbvh, gesture_data.node_mask_memory, [&](const bke::pbvh::Node &node) {
338 return BKE_pbvh_node_frustum_contain_AABB(&node, &frustum);
339 });
340}
341
343{
344 const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*gesture_data.vc.obact);
345 float clip_planes[4][4];
346 copy_m4_m4(clip_planes, gesture_data.clip_planes);
347 negate_m4(clip_planes);
348
349 PBVHFrustumPlanes frustum{};
350 frustum.planes = clip_planes;
351 frustum.num_planes = 4;
352
353 gesture_data.node_mask = bke::pbvh::search_nodes(
354 pbvh, gesture_data.node_mask_memory, [&](const bke::pbvh::Node &node) {
355 switch (gesture_data.selection_type) {
356 case SelectionType::Inside:
357 return BKE_pbvh_node_frustum_contain_AABB(&node, &frustum);
358 case SelectionType::Outside:
359 /* Certain degenerate cases of a lasso shape can cause the resulting
360 * frustum planes to enclose a node's AABB, therefore we must submit it
361 * to be more thoroughly evaluated. */
362 if (gesture_data.shape_type == ShapeType::Lasso) {
363 return true;
364 }
365 return BKE_pbvh_node_frustum_exclude_AABB(&node, &frustum);
366 }
368 return false;
369 });
370}
371
372static void update_affected_nodes(GestureData &gesture_data)
373{
374 switch (gesture_data.shape_type) {
375 case ShapeType::Box:
376 case ShapeType::Lasso:
378 break;
379 case ShapeType::Line:
381 break;
382 }
383}
384
385static bool is_affected_lasso(const GestureData &gesture_data, const float3 &position)
386{
387 int scr_co_s[2];
388 float3 co_final = symmetry_flip(position, gesture_data.symmpass);
389
390 /* First project point to 2d space. */
391 const float2 scr_co_f = ED_view3d_project_float_v2_m4(
392 gesture_data.vc.region, co_final, gesture_data.lasso.projviewobjmat);
393
394 scr_co_s[0] = scr_co_f[0];
395 scr_co_s[1] = scr_co_f[1];
396
397 /* Clip against lasso boundbox. */
398 const LassoData &lasso = gesture_data.lasso;
399 if (!BLI_rcti_isect_pt(&lasso.boundbox, scr_co_s[0], scr_co_s[1])) {
400 return gesture_data.selection_type == SelectionType::Outside;
401 }
402
403 scr_co_s[0] -= lasso.boundbox.xmin;
404 scr_co_s[1] -= lasso.boundbox.ymin;
405
406 const bool bitmap_result = lasso.mask_px[scr_co_s[1] * lasso.width + scr_co_s[0]].test();
407 switch (gesture_data.selection_type) {
408 case SelectionType::Inside:
409 return bitmap_result;
410 case SelectionType::Outside:
411 return !bitmap_result;
412 }
414 return false;
415}
416
417bool is_affected(const GestureData &gesture_data, const float3 &position, const float3 &normal)
418{
419 float dot = math::dot(gesture_data.view_normal, normal);
420 const bool is_effected_front_face = !(gesture_data.front_faces_only && dot < 0.0f);
421
422 if (!is_effected_front_face) {
423 return false;
424 }
425
426 switch (gesture_data.shape_type) {
427 case ShapeType::Box: {
428 const bool is_contained = isect_point_planes_v3(gesture_data.clip_planes, 4, position);
429 return ((is_contained && gesture_data.selection_type == SelectionType::Inside) ||
430 (!is_contained && gesture_data.selection_type == SelectionType::Outside));
431 }
432 case ShapeType::Lasso:
433 return is_affected_lasso(gesture_data, position);
434 case ShapeType::Line:
435 if (gesture_data.line.use_side_planes) {
436 return plane_point_side_v3(gesture_data.line.plane, position) > 0.0f &&
437 plane_point_side_v3(gesture_data.line.side_plane[0], position) > 0.0f &&
438 plane_point_side_v3(gesture_data.line.side_plane[1], position) > 0.0f;
439 }
440 return plane_point_side_v3(gesture_data.line.plane, position) > 0.0f;
441 }
442 return false;
443}
444
445void filter_factors(const GestureData &gesture_data,
446 const Span<float3> positions,
447 const Span<float3> normals,
448 const MutableSpan<float> factors)
449{
450 for (const int i : positions.index_range()) {
451 if (!is_affected(gesture_data, positions[i], normals[i])) {
452 factors[i] = 0.0f;
453 }
454 }
455}
456
457void apply(bContext &C, GestureData &gesture_data, wmOperator &op)
458{
459 Operation *operation = gesture_data.operation;
460
461 operation->begin(C, op, gesture_data);
462
463 for (int symmpass = 0; symmpass <= gesture_data.symm; symmpass++) {
464 if (SCULPT_is_symmetry_iteration_valid(symmpass, gesture_data.symm)) {
465 flip_for_symmetry_pass(gesture_data, ePaintSymmetryFlags(symmpass));
466 update_affected_nodes(gesture_data);
467
468 operation->apply_for_symmetry_pass(C, gesture_data);
469 }
470 }
471
472 operation->end(C, gesture_data);
473
475}
476} // namespace blender::ed::sculpt_paint::gesture
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
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:215
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:39
void mul_m3_v3(const float M[3][3], float r[3])
void copy_m3_m4(float m1[3][3], const float m2[4][4])
void copy_m4_m4(float m1[4][4], const float m2[4][4])
void mul_v3_m4v3(float r[3], const float mat[4][4], const float vec[3])
void negate_m4(float R[4][4])
void mul_v3_mat3_m4v3(float r[3], const float mat[4][4], const float vec[3])
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE float normalize_v3_v3(float r[3], const float a[3])
MINLINE void madd_v3_v3v3fl(float r[3], const float a[3], const float b[3], float f)
bool BLI_rcti_isect_pt(const struct rcti *rect, int x, int y)
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)
int64_t size() const
Definition BLI_array.hh:245
IndexRange index_range() const
Definition BLI_array.hh:349
const Depsgraph * depsgraph
pbvh::Tree * pbvh_get(Object &object)
Definition paint.cc:2846
IndexMask search_nodes(const Tree &pbvh, IndexMaskMemory &memory, FunctionRef< bool(const Node &)> filter_fn)
Definition pbvh.cc:2647
static void update_affected_nodes_by_line_plane(GestureData &gesture_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)
static void line_calculate_plane_points(GestureData &gesture_data, float line_points[2][2], float r_plane_points[4][3], float r_offset_plane_points[2][3])
std::unique_ptr< GestureData > init_from_line(bContext *C, wmOperator *op)
static void flip_for_symmetry_pass(GestureData &gesture_data, const ePaintSymmetryFlags symmpass)
static void init_common(bContext *C, wmOperator *op, GestureData &gesture_data)
void filter_factors(const GestureData &gesture_data, const Span< float3 > positions, const Span< float3 > normals, const MutableSpan< float > factors)
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 lasso_px_cb(int x, int x_end, int y, void *user_data)
static void line_plane_from_tri(float *r_plane, GestureData &gesture_data, const bool flip, const float p1[3], const float p2[3], const float p3[3])
float3 symmetry_flip(const float3 &src, const ePaintSymmetryFlags symm)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
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:186
bool SCULPT_is_symmetry_iteration_valid(char i, char symm)
Definition sculpt.cc:713
void SCULPT_tag_update_overlays(bContext *C)
Definition sculpt.cc:735
struct SculptSession * sculpt
float viewinv[4][4]
RegionView3D * rv3d
Definition ED_view3d.hh:76
ARegion * region
Definition ED_view3d.hh:73
View3D * v3d
Definition ED_view3d.hh:74
Object * obact
Definition ED_view3d.hh:71
void(* end)(bContext &, GestureData &)
void(* begin)(bContext &, wmOperator &, GestureData &)
void(* apply_for_symmetry_pass)(bContext &, GestureData &)
int ymin
int ymax
int xmin
int xmax
StructRNA * srna
Definition WM_types.hh:1080
struct PointerRNA * ptr
wmOperatorType * ot
Definition wm_files.cc:4125
Array< int2 > WM_gesture_lasso_path_to_array(bContext *, wmOperator *op)
void WM_operator_properties_border_to_rcti(wmOperator *op, rcti *r_rect)