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