1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2020 Blender Foundation.
17 * All rights reserved.
18 */
19
20 /** \file snap3d_gizmo.c
21 * \ingroup edgizmolib
22 *
23 * \name Snap Gizmo
24 *
25 * 3D Gizmo
26 *
27 * \brief Snap gizmo which exposes the location, normal and index in the props.
28 */
29
30 #include "BLI_math.h"
31
32 #include "DNA_scene_types.h"
33
34 #include "BKE_context.h"
35
36 #include "GPU_immediate.h"
37 #include "GPU_state.h"
38
39 #include "ED_gizmo_library.h"
40 #include "ED_screen.h"
41 #include "ED_transform_snap_object_context.h"
42 #include "ED_view3d.h"
43
44 #include "UI_resources.h" /* icons */
45
46 #include "RNA_access.h"
47 #include "RNA_define.h"
48
49 #include "DEG_depsgraph_query.h"
50
51 #include "WM_api.h"
52 #include "WM_types.h"
53 #include "wm.h"
54
55 /* own includes */
56 #include "../gizmo_geometry.h"
57 #include "../gizmo_library_intern.h"
58
59 typedef struct SnapGizmo3D {
60 wmGizmo gizmo;
61 PropertyRNA *prop_prevpoint;
62 PropertyRNA *prop_location;
63 PropertyRNA *prop_normal;
64 PropertyRNA *prop_elem_index;
65 PropertyRNA *prop_snap_force;
66
67 /* We could have other snap contexts, for now only support 3D view. */
68 SnapObjectContext *snap_context_v3d;
69 int mval[2];
70
71 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
72 wmKeyMap *keymap;
73 int snap_on;
74 bool invert_snap;
75 #endif
76 int use_snap_override;
77 short snap_elem;
78 } SnapGizmo3D;
79
80 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
invert_snap(SnapGizmo3D * snap_gizmo,const wmWindowManager * wm,const wmEvent * event)81 static bool invert_snap(SnapGizmo3D *snap_gizmo, const wmWindowManager *wm, const wmEvent *event)
82 {
83 wmKeyMap *keymap = WM_keymap_active(wm, snap_gizmo->keymap);
84
85 const int snap_on = snap_gizmo->snap_on;
86 for (wmKeyMapItem *kmi = keymap->items.first; kmi; kmi = kmi->next) {
87 if (kmi->flag & KMI_INACTIVE) {
88 continue;
89 }
90
91 if (kmi->propvalue == snap_on) {
92 if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) && event->ctrl) ||
93 (ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) && event->shift) ||
94 (ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && event->alt) ||
95 ((kmi->type == EVT_OSKEY) && event->oskey)) {
96 return true;
97 }
98 }
99 }
100 return false;
101 }
102 #endif
103
104 /* -------------------------------------------------------------------- */
105 /** \name ED_gizmo_library specific API
106 * \{ */
107
ED_gizmotypes_snap_3d_draw_util(RegionView3D * rv3d,const float loc_prev[3],const float loc_curr[3],const float normal[3],const uchar color_line[4],const uchar color_point[4],const short snap_elem_type)108 void ED_gizmotypes_snap_3d_draw_util(RegionView3D *rv3d,
109 const float loc_prev[3],
110 const float loc_curr[3],
111 const float normal[3],
112 const uchar color_line[4],
113 const uchar color_point[4],
114 const short snap_elem_type)
115 {
116 if (!loc_prev && !loc_curr) {
117 return;
118 }
119
120 float view_inv[4][4];
121 copy_m4_m4(view_inv, rv3d->viewinv);
122
123 /* The size of the circle is larger than the vertex size.
124 * This prevents a drawing overlaps the other. */
125 float radius = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
126 uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
127
128 immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
129
130 if (loc_curr) {
131 immUniformColor4ubv(color_point);
132 imm_drawcircball(loc_curr, ED_view3d_pixel_size(rv3d, loc_curr) * radius, view_inv, pos);
133
134 /* draw normal if needed */
135 if (normal) {
136 immBegin(GPU_PRIM_LINES, 2);
137 immVertex3fv(pos, loc_curr);
138 immVertex3f(pos, loc_curr[0] + normal[0], loc_curr[1] + normal[1], loc_curr[2] + normal[2]);
139 immEnd();
140 }
141 }
142
143 if (loc_prev) {
144 /* Draw an "X" indicating where the previous snap point is.
145 * This is useful for indicating perpendicular snap. */
146
147 /* v1, v2, v3 and v4 indicate the coordinates of the ends of the "X". */
148 float vx[3], vy[3], v1[3], v2[3], v3[3], v4[4];
149
150 /* Multiply by 0.75f so that the final size of the "X" is close to that of
151 * the circle.
152 * (A closer value is 0.7071f, but we don't need to be exact here). */
153 float x_size = 0.75f * radius * ED_view3d_pixel_size(rv3d, loc_prev);
154
155 mul_v3_v3fl(vx, view_inv[0], x_size);
156 mul_v3_v3fl(vy, view_inv[1], x_size);
157
158 add_v3_v3v3(v1, vx, vy);
159 sub_v3_v3v3(v2, vx, vy);
160 negate_v3_v3(v3, v1);
161 negate_v3_v3(v4, v2);
162
163 add_v3_v3(v1, loc_prev);
164 add_v3_v3(v2, loc_prev);
165 add_v3_v3(v3, loc_prev);
166 add_v3_v3(v4, loc_prev);
167
168 immUniformColor4ubv(color_line);
169 immBegin(GPU_PRIM_LINES, 4);
170 immVertex3fv(pos, v3);
171 immVertex3fv(pos, v1);
172 immVertex3fv(pos, v4);
173 immVertex3fv(pos, v2);
174 immEnd();
175
176 if (loc_curr && (snap_elem_type & SCE_SNAP_MODE_EDGE_PERPENDICULAR)) {
177 /* Dashed line. */
178 immUnbindProgram();
179
180 immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
181 float viewport_size[4];
182 GPU_viewport_size_get_f(viewport_size);
183 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
184 immUniform1f("dash_width", 6.0f * U.pixelsize);
185 immUniform1f("dash_factor", 1.0f / 4.0f);
186 immUniformColor4ubv(color_line);
187
188 immBegin(GPU_PRIM_LINES, 2);
189 immVertex3fv(pos, loc_prev);
190 immVertex3fv(pos, loc_curr);
191 immEnd();
192 }
193 }
194
195 immUnbindProgram();
196 }
197
ED_gizmotypes_snap_3d_context_ensure(Scene * scene,const ARegion * region,const View3D * v3d,wmGizmo * gz)198 SnapObjectContext *ED_gizmotypes_snap_3d_context_ensure(Scene *scene,
199 const ARegion *region,
200 const View3D *v3d,
201 wmGizmo *gz)
202 {
203 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
204 if (snap_gizmo->snap_context_v3d == NULL) {
205 snap_gizmo->snap_context_v3d = ED_transform_snap_object_context_create_view3d(
206 scene, 0, region, v3d);
207 }
208 return snap_gizmo->snap_context_v3d;
209 }
210
ED_gizmotypes_snap_3d_invert_snap_get(struct wmGizmo * gz)211 bool ED_gizmotypes_snap_3d_invert_snap_get(struct wmGizmo *gz)
212 {
213 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
214 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
215 return snap_gizmo->invert_snap;
216 #else
217 return false;
218 #endif
219 }
220
ED_gizmotypes_snap_3d_toggle_set(wmGizmo * gz,bool enable)221 void ED_gizmotypes_snap_3d_toggle_set(wmGizmo *gz, bool enable)
222 {
223 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
224 snap_gizmo->use_snap_override = (int)enable;
225 }
226
ED_gizmotypes_snap_3d_toggle_clear(wmGizmo * gz)227 void ED_gizmotypes_snap_3d_toggle_clear(wmGizmo *gz)
228 {
229 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
230 snap_gizmo->use_snap_override = -1;
231 }
232
ED_gizmotypes_snap_3d_update(wmGizmo * gz,struct Depsgraph * depsgraph,const ARegion * region,const View3D * v3d,const wmWindowManager * wm,const float mval_fl[2],float r_loc[3],float r_nor[3])233 short ED_gizmotypes_snap_3d_update(wmGizmo *gz,
234 struct Depsgraph *depsgraph,
235 const ARegion *region,
236 const View3D *v3d,
237 const wmWindowManager *wm,
238 const float mval_fl[2],
239 float r_loc[3],
240 float r_nor[3])
241 {
242 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
243 Scene *scene = DEG_get_input_scene(depsgraph);
244 float co[3], no[3];
245 short snap_elem = 0;
246 int snap_elem_index[3] = {-1, -1, -1};
247 int index = -1;
248
249 if (snap_gizmo->use_snap_override != -1) {
250 if (snap_gizmo->use_snap_override == false) {
251 snap_gizmo->snap_elem = 0;
252 return 0;
253 }
254 }
255
256 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
257 if (wm && wm->winactive) {
258 snap_gizmo->invert_snap = invert_snap(snap_gizmo, wm, wm->winactive->eventstate);
259 }
260
261 if (snap_gizmo->use_snap_override == -1) {
262 const ToolSettings *ts = scene->toolsettings;
263 if (snap_gizmo->invert_snap != !(ts->snap_flag & SCE_SNAP)) {
264 snap_gizmo->snap_elem = 0;
265 return 0;
266 }
267 }
268 #else
269 UNUSED_VARS(wm);
270 #endif
271
272 wmGizmoProperty *gz_prop = WM_gizmo_target_property_find(gz, "snap_elements");
273 int snap_elements = RNA_property_enum_get(&gz_prop->ptr, gz_prop->prop);
274 if (gz_prop->prop != snap_gizmo->prop_snap_force) {
275 int snap_elements_force = RNA_property_enum_get(gz->ptr, snap_gizmo->prop_snap_force);
276 snap_elements |= snap_elements_force;
277 }
278 snap_elements &= (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE |
279 SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_EDGE_PERPENDICULAR);
280
281 if (snap_elements) {
282 float prev_co[3] = {0.0f};
283 if (RNA_property_is_set(gz->ptr, snap_gizmo->prop_prevpoint)) {
284 RNA_property_float_get_array(gz->ptr, snap_gizmo->prop_prevpoint, prev_co);
285 }
286 else {
287 snap_elements &= ~SCE_SNAP_MODE_EDGE_PERPENDICULAR;
288 }
289
290 float dist_px = 12.0f * U.pixelsize;
291
292 ED_gizmotypes_snap_3d_context_ensure(scene, region, v3d, gz);
293 snap_elem = ED_transform_snap_object_project_view3d_ex(snap_gizmo->snap_context_v3d,
294 depsgraph,
295 snap_elements,
296 &(const struct SnapObjectParams){
297 .snap_select = SNAP_ALL,
298 .use_object_edit_cage = true,
299 .use_occlusion_test = true,
300 },
301 mval_fl,
302 prev_co,
303 &dist_px,
304 co,
305 no,
306 &index,
307 NULL,
308 NULL);
309 }
310
311 if (snap_elem == 0) {
312 RegionView3D *rv3d = region->regiondata;
313 ED_view3d_win_to_3d(v3d, region, rv3d->ofs, mval_fl, co);
314 zero_v3(no);
315 }
316 else if (snap_elem == SCE_SNAP_MODE_VERTEX) {
317 snap_elem_index[0] = index;
318 }
319 else if (snap_elem &
320 (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_EDGE_PERPENDICULAR)) {
321 snap_elem_index[1] = index;
322 }
323 else if (snap_elem == SCE_SNAP_MODE_FACE) {
324 snap_elem_index[2] = index;
325 }
326
327 snap_gizmo->snap_elem = snap_elem;
328 RNA_property_float_set_array(gz->ptr, snap_gizmo->prop_location, co);
329 RNA_property_float_set_array(gz->ptr, snap_gizmo->prop_normal, no);
330 RNA_property_int_set_array(gz->ptr, snap_gizmo->prop_elem_index, snap_elem_index);
331
332 if (r_loc) {
333 copy_v3_v3(r_loc, co);
334 }
335
336 if (r_nor) {
337 copy_v3_v3(r_nor, no);
338 }
339
340 return snap_elem;
341 }
342
343 /** \} */
344
345 /* -------------------------------------------------------------------- */
346 /** \name GIZMO_GT_snap_3d
347 * \{ */
348
snap_gizmo_setup(wmGizmo * gz)349 static void snap_gizmo_setup(wmGizmo *gz)
350 {
351 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
352
353 /* For quick access to the props. */
354 snap_gizmo->prop_prevpoint = RNA_struct_find_property(gz->ptr, "prev_point");
355 snap_gizmo->prop_location = RNA_struct_find_property(gz->ptr, "location");
356 snap_gizmo->prop_normal = RNA_struct_find_property(gz->ptr, "normal");
357 snap_gizmo->prop_elem_index = RNA_struct_find_property(gz->ptr, "snap_elem_index");
358 snap_gizmo->prop_snap_force = RNA_struct_find_property(gz->ptr, "snap_elements_force");
359
360 snap_gizmo->use_snap_override = -1;
361
362 /* Prop fallback. */
363 WM_gizmo_target_property_def_rna(gz, "snap_elements", gz->ptr, "snap_elements_force", -1);
364
365 /* Flags. */
366 gz->flag |= WM_GIZMO_NO_TOOLTIP;
367 }
368
snap_gizmo_draw(const bContext * C,wmGizmo * gz)369 static void snap_gizmo_draw(const bContext *C, wmGizmo *gz)
370 {
371 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
372 if (snap_gizmo->snap_elem == 0) {
373 return;
374 }
375
376 ARegion *region = CTX_wm_region(C);
377 RegionView3D *rv3d = region->regiondata;
378
379 /* Ideally, we shouldn't assign values here.
380 * But `test_select` is not called during navigation.
381 * And `snap_elem` is not really useful in this case. */
382 if ((rv3d->rflag & RV3D_NAVIGATING) ||
383 (!(gz->state & WM_GIZMO_STATE_HIGHLIGHT) && !wm_gizmomap_modal_get(region->gizmo_map))) {
384 snap_gizmo->snap_elem = 0;
385 return;
386 }
387
388 float location[3], prev_point_stack[3], *prev_point = NULL;
389 uchar color_line[4], color_point[4];
390
391 RNA_property_float_get_array(gz->ptr, snap_gizmo->prop_location, location);
392
393 UI_GetThemeColor3ubv(TH_TRANSFORM, color_line);
394 color_line[3] = 128;
395
396 rgba_float_to_uchar(color_point, gz->color);
397
398 if (RNA_property_is_set(gz->ptr, snap_gizmo->prop_prevpoint)) {
399 RNA_property_float_get_array(gz->ptr, snap_gizmo->prop_prevpoint, prev_point_stack);
400 prev_point = prev_point_stack;
401 }
402
403 GPU_line_smooth(false);
404
405 GPU_line_width(1.0f);
406 ED_gizmotypes_snap_3d_draw_util(
407 rv3d, prev_point, location, NULL, color_line, color_point, snap_gizmo->snap_elem);
408 }
409
snap_gizmo_test_select(bContext * C,wmGizmo * gz,const int mval[2])410 static int snap_gizmo_test_select(bContext *C, wmGizmo *gz, const int mval[2])
411 {
412 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
413
414 #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK
415 wmWindowManager *wm = CTX_wm_manager(C);
416 if (snap_gizmo->keymap == NULL) {
417 snap_gizmo->keymap = WM_modalkeymap_find(wm->defaultconf, "Generic Gizmo Tweak Modal Map");
418 RNA_enum_value_from_id(snap_gizmo->keymap->modal_items, "SNAP_ON", &snap_gizmo->snap_on);
419 }
420
421 const bool invert = wm->winactive ? invert_snap(snap_gizmo, wm, wm->winactive->eventstate) :
422 false;
423 if (snap_gizmo->invert_snap == invert && snap_gizmo->mval[0] == mval[0] &&
424 snap_gizmo->mval[1] == mval[1]) {
425 /* Performance, do not update. */
426 return snap_gizmo->snap_elem ? 0 : -1;
427 }
428
429 snap_gizmo->invert_snap = invert;
430 #else
431 if (snap_gizmo->mval[0] == mval[0] && snap_gizmo->mval[1] == mval[1]) {
432 /* Performance, do not update. */
433 return snap_gizmo->snap_elem ? 0 : -1;
434 }
435 #endif
436 copy_v2_v2_int(snap_gizmo->mval, mval);
437
438 ARegion *region = CTX_wm_region(C);
439 View3D *v3d = CTX_wm_view3d(C);
440 const float mval_fl[2] = {UNPACK2(mval)};
441 short snap_elem = ED_gizmotypes_snap_3d_update(
442 gz, CTX_data_ensure_evaluated_depsgraph(C), region, v3d, NULL, mval_fl, NULL, NULL);
443
444 if (snap_elem) {
445 ED_region_tag_redraw_editor_overlays(region);
446 return 0;
447 }
448
449 return -1;
450 }
451
snap_gizmo_modal(bContext * UNUSED (C),wmGizmo * UNUSED (gz),const wmEvent * UNUSED (event),eWM_GizmoFlagTweak UNUSED (tweak_flag))452 static int snap_gizmo_modal(bContext *UNUSED(C),
453 wmGizmo *UNUSED(gz),
454 const wmEvent *UNUSED(event),
455 eWM_GizmoFlagTweak UNUSED(tweak_flag))
456 {
457 return OPERATOR_RUNNING_MODAL;
458 }
459
snap_gizmo_invoke(bContext * UNUSED (C),wmGizmo * UNUSED (gz),const wmEvent * UNUSED (event))460 static int snap_gizmo_invoke(bContext *UNUSED(C),
461 wmGizmo *UNUSED(gz),
462 const wmEvent *UNUSED(event))
463 {
464 return OPERATOR_RUNNING_MODAL;
465 }
466
snap_gizmo_free(wmGizmo * gz)467 static void snap_gizmo_free(wmGizmo *gz)
468 {
469 SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz;
470 if (snap_gizmo->snap_context_v3d) {
471 ED_transform_snap_object_context_destroy(snap_gizmo->snap_context_v3d);
472 snap_gizmo->snap_context_v3d = NULL;
473 }
474 }
475
GIZMO_GT_snap_3d(wmGizmoType * gzt)476 static void GIZMO_GT_snap_3d(wmGizmoType *gzt)
477 {
478 /* identifiers */
479 gzt->idname = "GIZMO_GT_snap_3d";
480
481 /* api callbacks */
482 gzt->setup = snap_gizmo_setup;
483 gzt->draw = snap_gizmo_draw;
484 gzt->test_select = snap_gizmo_test_select;
485 gzt->modal = snap_gizmo_modal;
486 gzt->invoke = snap_gizmo_invoke;
487 gzt->free = snap_gizmo_free;
488
489 gzt->struct_size = sizeof(SnapGizmo3D);
490
491 const EnumPropertyItem *rna_enum_snap_element_items;
492 {
493 /* Get Snap Element Items enum. */
494 bool free;
495 PointerRNA toolsettings_ptr;
496 RNA_pointer_create(NULL, &RNA_ToolSettings, NULL, &toolsettings_ptr);
497 PropertyRNA *prop = RNA_struct_find_property(&toolsettings_ptr, "snap_elements");
498 RNA_property_enum_items(
499 NULL, &toolsettings_ptr, prop, &rna_enum_snap_element_items, NULL, &free);
500
501 BLI_assert(free == false);
502 }
503
504 /* Setup. */
505 RNA_def_enum_flag(gzt->srna,
506 "snap_elements_force",
507 rna_enum_snap_element_items,
508 SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE,
509 "Snap Elements",
510 "");
511
512 RNA_def_float_vector(gzt->srna,
513 "prev_point",
514 3,
515 NULL,
516 FLT_MIN,
517 FLT_MAX,
518 "Previous Point",
519 "Point that defines the location of the perpendicular snap",
520 FLT_MIN,
521 FLT_MAX);
522
523 /* Returns. */
524 RNA_def_float_vector(gzt->srna,
525 "location",
526 3,
527 NULL,
528 FLT_MIN,
529 FLT_MAX,
530 "Location",
531 "Snap Point Location",
532 FLT_MIN,
533 FLT_MAX);
534
535 RNA_def_float_vector(gzt->srna,
536 "normal",
537 3,
538 NULL,
539 FLT_MIN,
540 FLT_MAX,
541 "Normal",
542 "Snap Point Normal",
543 FLT_MIN,
544 FLT_MAX);
545
546 RNA_def_int_vector(gzt->srna,
547 "snap_elem_index",
548 3,
549 NULL,
550 INT_MIN,
551 INT_MAX,
552 "Snap Element",
553 "Array index of face, edge and vert snapped",
554 INT_MIN,
555 INT_MAX);
556
557 /* Read/Write. */
558 WM_gizmotype_target_property_def(gzt, "snap_elements", PROP_ENUM, 1);
559 }
560
ED_gizmotypes_snap_3d(void)561 void ED_gizmotypes_snap_3d(void)
562 {
563 WM_gizmotype_append(GIZMO_GT_snap_3d);
564 }
565
566 /** \} */
567