Merge branch 'blender2.7'
[blender.git] / source / blender / editors / mesh / editmesh_extrude_spin_gizmo.c
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
17 /** \file
18  * \ingroup edmesh
19  */
20
21 #include "BLI_math.h"
22
23 #include "BKE_context.h"
24 #include "BKE_scene.h"
25
26 #include "RNA_define.h"
27 #include "RNA_access.h"
28 #include "RNA_enum_types.h"
29
30 #include "WM_api.h"
31 #include "WM_types.h"
32 #include "WM_message.h"
33 #include "WM_toolsystem.h"
34
35 #include "ED_gizmo_utils.h"
36 #include "ED_screen.h"
37 #include "ED_view3d.h"
38
39 #include "UI_resources.h"
40
41 #include "MEM_guardedalloc.h"
42
43 #include "mesh_intern.h"  /* own include */
44
45 #include "ED_transform.h"
46
47 #include "ED_gizmo_library.h"
48 #include "ED_undo.h"
49
50 /**
51  * Orient the handles towards the selection (can be slow with high-poly mesh!).
52  */
53 // Disable for now, issues w/ refresh and '+' icons overlap.
54 // #define USE_SELECT_CENTER
55
56 #ifdef USE_SELECT_CENTER
57 #  include "BKE_editmesh.h"
58 #endif
59
60 static const float dial_angle_partial = M_PI / 2;
61 static const float dial_angle_partial_margin = 0.92f;
62
63 #define ORTHO_AXIS_OFFSET 2
64
65 /* -------------------------------------------------------------------- */
66 /** \name Spin Tool Gizmo
67  * \{ */
68
69 typedef struct GizmoGroupData_SpinInit {
70         struct {
71                 wmGizmo *xyz_view[4];
72                 wmGizmo *icon_button[3][2];
73         } gizmos;
74
75         /* Only for view orientation. */
76         struct {
77                 float viewinv_m3[3][3];
78         } prev;
79
80         /* We could store more vars here! */
81         struct {
82                 wmOperatorType *ot_spin;
83                 PropertyRNA *gzgt_axis_prop;
84                 float orient_mat[3][3];
85 #ifdef USE_SELECT_CENTER
86                 float select_center[3];
87                 float select_center_ortho_axis[3][3];
88                 bool use_select_center;
89 #endif
90         } data;
91
92         /* Store data for invoke. */
93         struct {
94                 int ortho_axis_active;
95         } invoke;
96
97 } GizmoGroupData_SpinInit;
98
99 /* Use dials only as a visualization when hovering over the icons. */
100 #define USE_DIAL_HOVER
101
102 #define INIT_SCALE_BASE 2.3f
103 #define INIT_SCALE_BUTTON 0.15f
104
105 static const uchar shape_plus[] = {
106         0x5f, 0xfb, 0x40, 0xee, 0x25, 0xda, 0x11, 0xbf, 0x4, 0xa0, 0x0, 0x80, 0x4, 0x5f, 0x11,
107         0x40, 0x25, 0x25, 0x40, 0x11, 0x5f, 0x4, 0x7f, 0x0, 0xa0, 0x4, 0xbf, 0x11, 0xda, 0x25,
108         0xee, 0x40, 0xfb, 0x5f, 0xff, 0x7f, 0xfb, 0xa0, 0xee, 0xbf, 0xda, 0xda, 0xbf, 0xee,
109         0xa0, 0xfb, 0x80, 0xff, 0x6e, 0xd7, 0x92, 0xd7, 0x92, 0x90, 0xd8, 0x90, 0xd8, 0x6d,
110         0x92, 0x6d, 0x92, 0x27, 0x6e, 0x27, 0x6e, 0x6d, 0x28, 0x6d, 0x28, 0x90, 0x6e,
111         0x90, 0x6e, 0xd7, 0x80, 0xff, 0x5f, 0xfb, 0x5f, 0xfb,
112 };
113
114 static void gizmo_mesh_spin_init_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
115 {
116         /* alpha values for normal/highlighted states */
117         const float alpha = 0.6f;
118         const float alpha_hi = 1.0f;
119         const float scale_base = INIT_SCALE_BASE;
120         const float scale_button = INIT_SCALE_BUTTON;
121
122         GizmoGroupData_SpinInit *ggd = MEM_callocN(sizeof(*ggd), __func__);
123         gzgroup->customdata = ggd;
124         const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
125         const wmGizmoType *gzt_button = WM_gizmotype_find("GIZMO_GT_button_2d", true);
126
127         for (int i = 0; i < 3; i++) {
128                 for (int j = 0; j < 2; j++) {
129                         wmGizmo *gz = WM_gizmo_new_ptr(gzt_button, gzgroup, NULL);
130                         PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "shape");
131                         RNA_property_string_set_bytes(
132                                 gz->ptr, prop,
133                                 (const char *)shape_plus, ARRAY_SIZE(shape_plus));
134
135                         float color[4];
136                         UI_GetThemeColor3fv(TH_AXIS_X + i, color);
137                         color[3] = alpha;
138                         WM_gizmo_set_color(gz, color);
139
140                         WM_gizmo_set_scale(gz, scale_button);
141                         gz->color[3] = 0.6f;
142
143                         gz->flag |= WM_GIZMO_DRAW_OFFSET_SCALE | WM_GIZMO_OPERATOR_TOOL_INIT;
144
145                         ggd->gizmos.icon_button[i][j] = gz;
146                 }
147         }
148
149         for (int i = 0; i < ARRAY_SIZE(ggd->gizmos.xyz_view); i++) {
150                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
151                 UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, gz->color);
152                 WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_VALUE | WM_GIZMO_HIDDEN_SELECT, true);
153                 ggd->gizmos.xyz_view[i] = gz;
154         }
155
156         for (int i = 0; i < 3; i++) {
157                 wmGizmo *gz = ggd->gizmos.xyz_view[i];
158 #ifndef USE_DIAL_HOVER
159                 RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_CLIP);
160 #endif
161                 WM_gizmo_set_line_width(gz, 2.0f);
162                 float color[4];
163                 UI_GetThemeColor3fv(TH_AXIS_X + i, color);
164                 color[3] = alpha;
165                 WM_gizmo_set_color(gz, color);
166                 color[3] = alpha_hi;
167                 WM_gizmo_set_color_highlight(gz, color);
168                 WM_gizmo_set_scale(gz, INIT_SCALE_BASE);
169                 RNA_float_set(gz->ptr, "arc_partial_angle", (M_PI * 2) - (dial_angle_partial * dial_angle_partial_margin));
170         }
171
172         {
173                 wmGizmo *gz = ggd->gizmos.xyz_view[3];
174                 WM_gizmo_set_line_width(gz, 2.0f);
175                 float color[4];
176                 copy_v3_fl(color, 1.0f);
177                 color[3] = alpha;
178                 WM_gizmo_set_color(gz, color);
179                 color[3] = alpha_hi;
180                 WM_gizmo_set_color_highlight(gz, color);
181                 WM_gizmo_set_scale(gz, scale_base);
182         }
183
184
185 #ifdef USE_DIAL_HOVER
186         for (int i = 0; i < 4; i++) {
187                 wmGizmo *gz = ggd->gizmos.xyz_view[i];
188                 WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
189         }
190 #endif
191
192         ggd->data.ot_spin = WM_operatortype_find("MESH_OT_spin", true);
193         ggd->data.gzgt_axis_prop = RNA_struct_type_find_property(gzgroup->type->srna, "axis");
194 }
195
196 static void gizmo_mesh_spin_init_refresh(const bContext *C, wmGizmoGroup *gzgroup);
197
198 static void gizmo_mesh_spin_init_refresh_axis_orientation(
199         wmGizmoGroup *gzgroup,
200         int axis_index, const float axis_vec[3], const float axis_tan[3])
201 {
202         GizmoGroupData_SpinInit *ggd = gzgroup->customdata;
203         wmGizmo *gz = ggd->gizmos.xyz_view[axis_index];
204         if (axis_tan != NULL) {
205                 WM_gizmo_set_matrix_rotation_from_yz_axis(gz, axis_tan, axis_vec);
206         }
207         else {
208                 WM_gizmo_set_matrix_rotation_from_z_axis(gz, axis_vec);
209         }
210
211         /* Only for display, use icons to access. */
212 #ifndef USE_DIAL_HOVER
213         {
214                 PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ggd->data.ot_spin, NULL);
215                 RNA_float_set_array(ptr, "axis", axis_vec);
216         }
217 #endif
218         if (axis_index < 3) {
219                 for (int j = 0; j < 2; j++) {
220                         gz = ggd->gizmos.icon_button[axis_index][j];
221                         PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ggd->data.ot_spin, NULL);
222                         float axis_vec_flip[3];
223                         if (0 == j) {
224                                 negate_v3_v3(axis_vec_flip, axis_vec);
225                         }
226                         else {
227                                 copy_v3_v3(axis_vec_flip, axis_vec);
228                         }
229                         RNA_float_set_array(ptr, "axis", axis_vec_flip);
230                 }
231         }
232 }
233
234 static void gizmo_mesh_spin_init_draw_prepare(
235         const bContext *C, wmGizmoGroup *gzgroup)
236 {
237         GizmoGroupData_SpinInit *ggd = gzgroup->customdata;
238         RegionView3D *rv3d = CTX_wm_region_view3d(C);
239         float viewinv_m3[3][3];
240         copy_m3_m4(viewinv_m3, rv3d->viewinv);
241
242         {
243                 Scene *scene = CTX_data_scene(C);
244                 const TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get(scene, SCE_GIZMO_SHOW_ROTATE);
245                 switch (orient_slot->type) {
246                         case V3D_ORIENT_VIEW:
247                         {
248                                 if (!equals_m3m3(viewinv_m3, ggd->prev.viewinv_m3)) {
249                                         /* Take care calling refresh from draw_prepare,
250                                          * this should be OK because it's only adjusting the cage orientation. */
251                                         gizmo_mesh_spin_init_refresh(C, gzgroup);
252                                 }
253                                 break;
254                         }
255                 }
256         }
257
258         /* Refresh handled above when using view orientation. */
259         if (!equals_m3m3(viewinv_m3, ggd->prev.viewinv_m3)) {
260                 gizmo_mesh_spin_init_refresh_axis_orientation(gzgroup, 3, rv3d->viewinv[2], NULL);
261                 copy_m3_m4(ggd->prev.viewinv_m3, rv3d->viewinv);
262         }
263
264         /* Hack! highlight XYZ dials based on buttons */
265 #ifdef USE_DIAL_HOVER
266         {
267                 PointerRNA ptr;
268                 bToolRef *tref = WM_toolsystem_ref_from_context((bContext *)C);
269                 WM_toolsystem_ref_properties_ensure_from_gizmo_group(tref, gzgroup->type, &ptr);
270                 const int axis_flag = RNA_property_enum_get(&ptr, ggd->data.gzgt_axis_prop);
271                 for (int i = 0; i < 4; i++) {
272                         bool hide = (axis_flag & (1 << i)) == 0;
273                         wmGizmo *gz = ggd->gizmos.xyz_view[i];
274                         WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, hide);
275                         if (!hide) {
276                                 RNA_float_set(gz->ptr, "arc_partial_angle", (M_PI * 2) - (dial_angle_partial * dial_angle_partial_margin));
277                         }
278                 }
279
280                 for (int i = 0; i < 3; i++) {
281                         for (int j = 0; j < 2; j++) {
282                                 wmGizmo *gz = ggd->gizmos.icon_button[i][j];
283                                 if (gz->state & WM_GIZMO_STATE_HIGHLIGHT) {
284                                         WM_gizmo_set_flag(ggd->gizmos.xyz_view[i], WM_GIZMO_HIDDEN, false);
285                                         RNA_float_set(ggd->gizmos.xyz_view[i]->ptr, "arc_partial_angle", 0.0f);
286                                         i = 3;
287                                         break;
288                                 }
289                         }
290                 }
291         }
292 #endif
293
294 }
295
296 static void gizmo_mesh_spin_init_invoke_prepare(
297         const bContext *UNUSED(C), wmGizmoGroup *gzgroup, wmGizmo *gz)
298 {
299         /* Set the initial ortho axis. */
300         GizmoGroupData_SpinInit *ggd = gzgroup->customdata;
301         ggd->invoke.ortho_axis_active = -1;
302         for (int i = 0; i < 3; i++) {
303                 if (ELEM(gz, UNPACK2(ggd->gizmos.icon_button[i]))) {
304                         ggd->invoke.ortho_axis_active = i;
305                         break;
306                 }
307         }
308 }
309
310 static void gizmo_mesh_spin_init_refresh(const bContext *C, wmGizmoGroup *gzgroup)
311 {
312         GizmoGroupData_SpinInit *ggd = gzgroup->customdata;
313         RegionView3D *rv3d = ED_view3d_context_rv3d((bContext *)C);
314         const float *gizmo_center = NULL;
315         {
316                 Scene *scene = CTX_data_scene(C);
317                 const View3DCursor *cursor = &scene->cursor;
318                 gizmo_center = cursor->location;
319         }
320
321         for (int i = 0; i < ARRAY_SIZE(ggd->gizmos.xyz_view); i++) {
322                 wmGizmo *gz = ggd->gizmos.xyz_view[i];
323                 WM_gizmo_set_matrix_location(gz, gizmo_center);
324         }
325
326         for (int i = 0; i < ARRAY_SIZE(ggd->gizmos.icon_button); i++) {
327                 for (int j = 0; j < 2; j++) {
328                         wmGizmo *gz = ggd->gizmos.icon_button[i][j];
329                         WM_gizmo_set_matrix_location(gz, gizmo_center);
330                 }
331         }
332
333         ED_transform_calc_orientation_from_type(C, ggd->data.orient_mat);
334         for (int i = 0; i < 3; i++) {
335                 const int axis_ortho = (i + ORTHO_AXIS_OFFSET) % 3;
336                 const float *axis_ortho_vec = ggd->data.orient_mat[axis_ortho];
337 #ifdef USE_SELECT_CENTER
338                 if (ggd->data.use_select_center) {
339                         float delta[3];
340                         sub_v3_v3v3(delta, ggd->data.select_center, gizmo_center);
341                         project_plane_normalized_v3_v3v3(ggd->data.select_center_ortho_axis[i], delta, ggd->data.orient_mat[i]);
342                         if (normalize_v3(ggd->data.select_center_ortho_axis[i]) != 0.0f) {
343                                 axis_ortho_vec = ggd->data.select_center_ortho_axis[i];
344                         }
345                 }
346 #endif
347                 gizmo_mesh_spin_init_refresh_axis_orientation(
348                         gzgroup, i, ggd->data.orient_mat[i], axis_ortho_vec);
349         }
350
351         {
352                 gizmo_mesh_spin_init_refresh_axis_orientation(
353                         gzgroup, 3, rv3d->viewinv[2], NULL);
354         }
355
356
357 #ifdef USE_SELECT_CENTER
358         {
359                 Object *obedit = CTX_data_edit_object(C);
360                 BMEditMesh *em = BKE_editmesh_from_object(obedit);
361                 float select_center[3] = {0};
362                 int totsel = 0;
363
364                 BMesh *bm = em->bm;
365                 BMVert *eve;
366                 BMIter iter;
367
368                 BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
369                         if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
370                                 if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
371                                         totsel++;
372                                         add_v3_v3(select_center, eve->co);
373                                 }
374                         }
375                 }
376                 if (totsel) {
377                         mul_v3_fl(select_center, 1.0f / totsel);
378                         mul_m4_v3(obedit->obmat, select_center);
379                         copy_v3_v3(ggd->data.select_center, select_center);
380                         ggd->data.use_select_center = true;
381                 }
382                 else {
383                         ggd->data.use_select_center = false;
384                 }
385         }
386 #endif
387
388         for (int i = 0; i < ARRAY_SIZE(ggd->gizmos.icon_button); i++) {
389                 const int axis_ortho = (i + ORTHO_AXIS_OFFSET) % 3;
390                 const float *axis_ortho_vec = ggd->data.orient_mat[axis_ortho];
391                 float offset = INIT_SCALE_BASE / INIT_SCALE_BUTTON;
392                 float offset_vec[3];
393
394 #ifdef USE_SELECT_CENTER
395                 if (ggd->data.use_select_center && !is_zero_v3(ggd->data.select_center_ortho_axis[i])) {
396                         axis_ortho_vec = ggd->data.select_center_ortho_axis[i];
397                 }
398 #endif
399
400                 mul_v3_v3fl(offset_vec, axis_ortho_vec, offset);
401                 for (int j = 0; j < 2; j++) {
402                         wmGizmo *gz = ggd->gizmos.icon_button[i][j];
403                         float mat3[3][3];
404                         axis_angle_to_mat3(mat3, ggd->data.orient_mat[i], dial_angle_partial * (j ? -0.5f : 0.5f));
405                         mul_v3_m3v3(gz->matrix_offset[3], mat3, offset_vec);
406                 }
407         }
408
409         {
410                 PointerRNA ptr;
411                 bToolRef *tref = WM_toolsystem_ref_from_context((bContext *)C);
412                 WM_toolsystem_ref_properties_ensure_from_gizmo_group(tref, gzgroup->type, &ptr);
413                 const int axis_flag = RNA_property_enum_get(&ptr, ggd->data.gzgt_axis_prop);
414                 for (int i = 0; i < ARRAY_SIZE(ggd->gizmos.icon_button); i++) {
415                         for (int j = 0; j < 2; j++) {
416                                 wmGizmo *gz = ggd->gizmos.icon_button[i][j];
417                                 WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, (axis_flag & (1 << i)) == 0);
418                         }
419                 }
420         }
421
422         /* Needed to test view orientation changes. */
423         copy_m3_m4(ggd->prev.viewinv_m3, rv3d->viewinv);
424 }
425
426
427 static void gizmo_mesh_spin_init_message_subscribe(
428         const bContext *C, wmGizmoGroup *gzgroup, struct wmMsgBus *mbus)
429 {
430         GizmoGroupData_SpinInit *ggd = gzgroup->customdata;
431         Scene *scene = CTX_data_scene(C);
432         ARegion *ar = CTX_wm_region(C);
433
434         /* Subscribe to view properties */
435         wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = {
436                 .owner = ar,
437                 .user_data = gzgroup->parent_gzmap,
438                 .notify = WM_gizmo_do_msg_notify_tag_refresh,
439         };
440
441         PointerRNA scene_ptr;
442         RNA_id_pointer_create(&scene->id, &scene_ptr);
443
444         {
445                 extern PropertyRNA rna_Scene_cursor_location;
446                 const PropertyRNA *props[] = {
447                         &rna_Scene_cursor_location,
448                 };
449                 for (int i = 0; i < ARRAY_SIZE(props); i++) {
450                         WM_msg_subscribe_rna(mbus, &scene_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
451                 }
452         }
453
454         WM_msg_subscribe_rna_params(
455                 mbus,
456                 &(const wmMsgParams_RNA){
457                     .ptr = (PointerRNA){ .type = gzgroup->type->srna, },
458                     .prop = ggd->data.gzgt_axis_prop,
459                 },
460                 &msg_sub_value_gz_tag_refresh, __func__);
461
462 }
463
464 void MESH_GGT_spin(struct wmGizmoGroupType *gzgt)
465 {
466         gzgt->name = "Mesh Spin Init";
467         gzgt->idname = "MESH_GGT_spin";
468
469         gzgt->flag = WM_GIZMOGROUPTYPE_3D;
470
471         gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
472         gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
473
474         gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool;
475         gzgt->setup = gizmo_mesh_spin_init_setup;
476         gzgt->refresh = gizmo_mesh_spin_init_refresh;
477         gzgt->message_subscribe = gizmo_mesh_spin_init_message_subscribe;
478         gzgt->draw_prepare = gizmo_mesh_spin_init_draw_prepare;
479         gzgt->invoke_prepare = gizmo_mesh_spin_init_invoke_prepare;
480
481         RNA_def_enum_flag(gzgt->srna, "axis", rna_enum_axis_flag_xyz_items, (1 << 2), "Axis", "");
482 }
483
484 #undef INIT_SCALE_BASE
485 #undef INIT_SCALE_BUTTON
486
487 /** \} */
488
489 /* -------------------------------------------------------------------- */
490 /** \name Spin Redo Gizmo
491  * \{ */
492
493 /**
494  * Orient the dial so the 'arc' starts where the mouse cursor is,
495  * this is simply to keep the gizmo displaying where the cursor starts.
496  * It's not needed for practical functionality.
497  */
498 #define USE_ANGLE_Z_ORIENT
499
500 typedef struct GizmoGroupData_SpinRedo {
501         /* Translate XYZ. */
502         struct wmGizmo *translate_c;
503         /* Spin angle */
504         struct wmGizmo *angle_z;
505
506         /* Translate XY constrained ('orient_mat'). */
507         struct wmGizmo *translate_xy[2];
508         /* Rotate XY constrained ('orient_mat'). */
509         struct wmGizmo *rotate_xy[2];
510
511         /* Rotate on view axis. */
512         struct wmGizmo *rotate_view;
513
514         struct {
515                 float plane_co[3];
516                 float plane_no[3];
517         } prev;
518
519         bool is_init;
520
521         /* We could store more vars here! */
522         struct {
523                 bContext *context;
524                 wmOperatorType *ot;
525                 wmOperator *op;
526                 PropertyRNA *prop_axis_co;
527                 PropertyRNA *prop_axis_no;
528                 PropertyRNA *prop_angle;
529
530                 float rotate_axis[3];
531 #ifdef USE_ANGLE_Z_ORIENT
532                 /* Apply 'orient_mat' for the final value. */
533                 float orient_axis_relative[3];
534 #endif
535                 /* The orientation, since the operator doesn't store this, we store our own.
536                  * this is kept in sync with the operator,
537                  * rotating the orientation when it doesn't match.
538                  *
539                  * Initialize to a sensible value where possible.
540                  */
541                 float orient_mat[3][3];
542
543         } data;
544 } GizmoGroupData_SpinRedo;
545
546 /**
547  * XXX. calling redo from property updates is not great.
548  * This is needed because changing the RNA doesn't cause a redo
549  * and we're not using operator UI which does just this.
550  */
551 static void gizmo_spin_exec(GizmoGroupData_SpinRedo *ggd)
552 {
553         if (ggd->is_init) {
554                 wmGizmo *gz = ggd->angle_z;
555                 PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "click_value");
556                 RNA_property_unset(gz->ptr, prop);
557                 ggd->is_init = false;
558         }
559
560         wmOperator *op = ggd->data.op;
561         if (op == WM_operator_last_redo((bContext *)ggd->data.context)) {
562                 ED_undo_operator_repeat((bContext *)ggd->data.context, op);
563         }
564 }
565
566 static void gizmo_mesh_spin_redo_update_orient_axis(GizmoGroupData_SpinRedo *ggd, const float plane_no[3])
567 {
568         float mat[3][3];
569         rotation_between_vecs_to_mat3(mat, ggd->data.orient_mat[2], plane_no);
570         mul_m3_m3m3(ggd->data.orient_mat, mat, ggd->data.orient_mat);
571         /* Not needed, just set for numeric stability. */
572         copy_v3_v3(ggd->data.orient_mat[2], plane_no);
573 }
574
575 static void gizmo_mesh_spin_redo_update_from_op(GizmoGroupData_SpinRedo *ggd)
576 {
577         wmOperator *op = ggd->data.op;
578         float plane_co[3], plane_no[3];
579         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_co, plane_co);
580         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_no, plane_no);
581         if (UNLIKELY(normalize_v3(plane_no) == 0.0f)) {
582                 return;
583         }
584         const bool is_plane_co_eq = equals_v3v3(plane_co, ggd->prev.plane_co);
585         const bool is_plane_no_eq = equals_v3v3(plane_no, ggd->prev.plane_no);
586         if (is_plane_co_eq && is_plane_no_eq) {
587                 return;
588         }
589         copy_v3_v3(ggd->prev.plane_co, plane_co);
590         copy_v3_v3(ggd->prev.plane_no, plane_no);
591
592         if (is_plane_no_eq == false) {
593                 gizmo_mesh_spin_redo_update_orient_axis(ggd, plane_no);
594         }
595
596         for (int i = 0; i < 2; i++) {
597                 WM_gizmo_set_matrix_location(ggd->rotate_xy[i], plane_co);
598                 WM_gizmo_set_matrix_location(ggd->translate_xy[i], plane_co);
599         }
600         WM_gizmo_set_matrix_location(ggd->angle_z, plane_co);
601         WM_gizmo_set_matrix_location(ggd->rotate_view, plane_co);
602         /* translate_c location comes from the property. */
603
604         for (int i = 0; i < 2; i++) {
605                 WM_gizmo_set_matrix_rotation_from_z_axis(ggd->translate_xy[i], ggd->data.orient_mat[i]);
606                 WM_gizmo_set_matrix_rotation_from_z_axis(ggd->rotate_xy[i], ggd->data.orient_mat[i]);
607         }
608 #ifdef USE_ANGLE_Z_ORIENT
609         {
610                 float plane_tan[3];
611                 float orient_axis[3];
612                 mul_v3_m3v3(orient_axis, ggd->data.orient_mat, ggd->data.orient_axis_relative);
613                 project_plane_normalized_v3_v3v3(plane_tan, orient_axis, plane_no);
614                 if (normalize_v3(plane_tan) != 0.0f) {
615                         WM_gizmo_set_matrix_rotation_from_yz_axis(ggd->angle_z, plane_tan, plane_no);
616                 }
617                 else {
618                         WM_gizmo_set_matrix_rotation_from_z_axis(ggd->angle_z, plane_no);
619                 }
620         }
621 #else
622         WM_gizmo_set_matrix_rotation_from_z_axis(ggd->angle_z, plane_no);
623 #endif
624 }
625
626 /* depth callbacks */
627 static void gizmo_spin_prop_depth_get(
628         const wmGizmo *gz, wmGizmoProperty *gz_prop,
629         void *value_p)
630 {
631         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
632         wmOperator *op = ggd->data.op;
633         float *value = value_p;
634
635         BLI_assert(gz_prop->type->array_length == 1);
636         UNUSED_VARS_NDEBUG(gz_prop);
637
638         const float *plane_no = gz->matrix_basis[2];
639         float plane_co[3];
640         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_co, plane_co);
641
642         value[0] = dot_v3v3(plane_no, plane_co) - dot_v3v3(plane_no, gz->matrix_basis[3]);
643 }
644
645 static void gizmo_spin_prop_depth_set(
646         const wmGizmo *gz, wmGizmoProperty *gz_prop,
647         const void *value_p)
648 {
649         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
650         wmOperator *op = ggd->data.op;
651         const float *value = value_p;
652
653         BLI_assert(gz_prop->type->array_length == 1);
654         UNUSED_VARS_NDEBUG(gz_prop);
655
656         float plane_co[3], plane[4];
657         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_co, plane_co);
658         normalize_v3_v3(plane, gz->matrix_basis[2]);
659
660         plane[3] = -value[0] - dot_v3v3(plane, gz->matrix_basis[3]);
661
662         /* Keep our location, may be offset simply to be inside the viewport. */
663         closest_to_plane_normalized_v3(plane_co, plane, plane_co);
664
665         RNA_property_float_set_array(op->ptr, ggd->data.prop_axis_co, plane_co);
666
667         gizmo_spin_exec(ggd);
668 }
669
670 /* translate callbacks */
671 static void gizmo_spin_prop_translate_get(
672         const wmGizmo *gz, wmGizmoProperty *gz_prop,
673         void *value_p)
674 {
675         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
676         wmOperator *op = ggd->data.op;
677         float *value = value_p;
678
679         BLI_assert(gz_prop->type->array_length == 3);
680         UNUSED_VARS_NDEBUG(gz_prop);
681
682         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_co, value);
683 }
684
685 static void gizmo_spin_prop_translate_set(
686         const wmGizmo *gz, wmGizmoProperty *gz_prop,
687         const void *value)
688 {
689         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
690         wmOperator *op = ggd->data.op;
691
692         BLI_assert(gz_prop->type->array_length == 3);
693         UNUSED_VARS_NDEBUG(gz_prop);
694
695         RNA_property_float_set_array(op->ptr, ggd->data.prop_axis_co, value);
696
697         gizmo_spin_exec(ggd);
698 }
699
700 /* angle callbacks */
701 static void gizmo_spin_prop_axis_angle_get(
702         const wmGizmo *gz, wmGizmoProperty *gz_prop,
703         void *value_p)
704 {
705         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
706         wmOperator *op = ggd->data.op;
707         float *value = value_p;
708
709         BLI_assert(gz_prop->type->array_length == 1);
710         UNUSED_VARS_NDEBUG(gz_prop);
711
712         float plane_no[4];
713         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_no, plane_no);
714         normalize_v3(plane_no);
715
716         const float *rotate_axis = gz->matrix_basis[2];
717         float rotate_up[3];
718         ortho_v3_v3(rotate_up, rotate_axis);
719
720         float plane_no_proj[3];
721         project_plane_normalized_v3_v3v3(plane_no_proj, plane_no, rotate_axis);
722
723         if (!is_zero_v3(plane_no_proj)) {
724                 const float angle = -angle_signed_on_axis_v3v3_v3(plane_no_proj, rotate_up, rotate_axis);
725                 value[0] = angle;
726         }
727         else {
728                 value[0] = 0.0f;
729         }
730 }
731
732 static void gizmo_spin_prop_axis_angle_set(
733         const wmGizmo *gz, wmGizmoProperty *gz_prop,
734         const void *value_p)
735 {
736         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
737         wmOperator *op = ggd->data.op;
738         const float *value = value_p;
739
740         BLI_assert(gz_prop->type->array_length == 1);
741         UNUSED_VARS_NDEBUG(gz_prop);
742
743         float plane_no[4];
744         RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_no, plane_no);
745         normalize_v3(plane_no);
746
747         const float *rotate_axis = gz->matrix_basis[2];
748         float rotate_up[3];
749         ortho_v3_v3(rotate_up, rotate_axis);
750
751         float plane_no_proj[3];
752         project_plane_normalized_v3_v3v3(plane_no_proj, plane_no, rotate_axis);
753
754         if (!is_zero_v3(plane_no_proj)) {
755                 const float angle = -angle_signed_on_axis_v3v3_v3(plane_no_proj, rotate_up, rotate_axis);
756                 const float angle_delta = angle - angle_compat_rad(value[0], angle);
757                 if (angle_delta != 0.0f) {
758                         float mat[3][3];
759                         axis_angle_normalized_to_mat3(mat, rotate_axis, angle_delta);
760                         mul_m3_v3(mat, plane_no);
761
762                         /* re-normalize - seems acceptable */
763                         RNA_property_float_set_array(op->ptr, ggd->data.prop_axis_no, plane_no);
764
765                         gizmo_spin_exec(ggd);
766                 }
767         }
768 }
769
770 /* angle callbacks */
771 static void gizmo_spin_prop_angle_get(
772         const wmGizmo *gz, wmGizmoProperty *gz_prop,
773         void *value_p)
774 {
775         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
776         wmOperator *op = ggd->data.op;
777         float *value = value_p;
778
779         BLI_assert(gz_prop->type->array_length == 1);
780         UNUSED_VARS_NDEBUG(gz_prop);
781         value[0] = RNA_property_float_get(op->ptr, ggd->data.prop_angle);
782 }
783
784 static void gizmo_spin_prop_angle_set(
785         const wmGizmo *gz, wmGizmoProperty *gz_prop,
786         const void *value_p)
787 {
788         GizmoGroupData_SpinRedo *ggd = gz->parent_gzgroup->customdata;
789         wmOperator *op = ggd->data.op;
790         BLI_assert(gz_prop->type->array_length == 1);
791         UNUSED_VARS_NDEBUG(gz_prop);
792         const float *value = value_p;
793         RNA_property_float_set(op->ptr, ggd->data.prop_angle, value[0]);
794
795         gizmo_spin_exec(ggd);
796 }
797
798 static bool gizmo_mesh_spin_redo_poll(const bContext *C, wmGizmoGroupType *gzgt)
799 {
800         if (ED_gizmo_poll_or_unlink_delayed_from_operator(C, gzgt, "MESH_OT_spin")) {
801                 if (ED_gizmo_poll_or_unlink_delayed_from_tool_ex(C, gzgt, "MESH_GGT_spin")) {
802                         return true;
803                 }
804         }
805         return false;
806 }
807
808 static void gizmo_mesh_spin_redo_modal_from_setup(
809         const bContext *C, wmGizmoGroup *gzgroup)
810 {
811         /* Start off dragging. */
812         GizmoGroupData_SpinRedo *ggd = gzgroup->customdata;
813         wmWindow *win = CTX_wm_window(C);
814         wmGizmo *gz = ggd->angle_z;
815         wmGizmoMap *gzmap = gzgroup->parent_gzmap;
816
817         ggd->is_init = true;
818
819         WM_gizmo_modal_set_from_setup(
820                 gzmap, (bContext *)C, gz, 0, win->eventstate);
821 }
822
823 static void gizmo_mesh_spin_redo_setup(const bContext *C, wmGizmoGroup *gzgroup)
824 {
825         wmOperatorType *ot = WM_operatortype_find("MESH_OT_spin", true);
826         wmOperator *op = WM_operator_last_redo(C);
827
828         if ((op == NULL) || (op->type != ot)) {
829                 return;
830         }
831
832         GizmoGroupData_SpinRedo *ggd = MEM_callocN(sizeof(*ggd), __func__);
833         gzgroup->customdata = ggd;
834
835         const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true);
836         const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_move_3d", true);
837         const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
838
839         /* Rotate View Axis (rotate_view) */
840         {
841                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
842                 UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, gz->color);
843                 zero_v4(gz->color);
844                 copy_v3_fl(gz->color_hi, 1.0f);
845                 gz->color_hi[3] = 0.1f;
846                 WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_VALUE, true);
847                 RNA_enum_set(gz->ptr, "draw_options",
848                              ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_MIRROR |
849                              ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_START_Y |
850                              ED_GIZMO_DIAL_DRAW_FLAG_FILL);
851                 ggd->rotate_view = gz;
852         }
853
854         /* Translate Center (translate_c) */
855         {
856                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_move, gzgroup, NULL);
857                 UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, gz->color);
858                 gz->color[3] = 0.6f;
859                 RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_MOVE_STYLE_RING_2D);
860                 WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_VALUE, true);
861                 WM_gizmo_set_scale(gz, 0.15);
862                 WM_gizmo_set_line_width(gz, 2.0f);
863                 ggd->translate_c = gz;
864         }
865
866         /* Spin Angle (angle_z) */
867         {
868                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
869                 copy_v3_v3(gz->color, gz->color_hi);
870                 gz->color[3] = 0.5f;
871                 RNA_boolean_set(gz->ptr, "wrap_angle", false);
872                 RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_VALUE);
873                 RNA_float_set(gz->ptr, "arc_inner_factor", 0.9f);
874                 RNA_float_set(gz->ptr, "click_value", M_PI * 2);
875                 WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_VALUE, true);
876                 WM_gizmo_set_scale(gz, 2.0f);
877                 WM_gizmo_set_line_width(gz, 1.0f);
878                 ggd->angle_z = gz;
879         }
880
881         /* Translate X/Y Tangents (translate_xy) */
882         for (int i = 0; i < 2; i++) {
883                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
884                 UI_GetThemeColor3fv(TH_AXIS_X + i, gz->color);
885                 RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_NORMAL);
886                 RNA_enum_set(gz->ptr, "draw_options", 0);
887                 WM_gizmo_set_scale(gz, 1.2f);
888                 ggd->translate_xy[i] = gz;
889         }
890
891         /* Rotate X/Y Tangents (rotate_xy) */
892         for (int i = 0; i < 2; i++) {
893                 wmGizmo *gz = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
894                 UI_GetThemeColor3fv(TH_AXIS_X + i, gz->color);
895                 gz->color[3] = 0.6f;
896                 WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_VALUE, true);
897                 WM_gizmo_set_line_width(gz, 3.0f);
898                 /* show the axis instead of mouse cursor */
899                 RNA_enum_set(gz->ptr, "draw_options",
900                              ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_MIRROR |
901                              ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_START_Y |
902                              ED_GIZMO_DIAL_DRAW_FLAG_CLIP);
903                 ggd->rotate_xy[i] = gz;
904         }
905
906         {
907                 ggd->data.context = (bContext *)C;
908                 ggd->data.ot = ot;
909                 ggd->data.op = op;
910                 ggd->data.prop_axis_co = RNA_struct_type_find_property(ot->srna, "center");
911                 ggd->data.prop_axis_no = RNA_struct_type_find_property(ot->srna, "axis");
912                 ggd->data.prop_angle = RNA_struct_type_find_property(ot->srna, "angle");
913         }
914
915         /* The spin operator only knows about an axis,
916          * while the manipulator has X/Y orientation for the gizmos.
917          * Initialize the orientation from the spin gizmo if possible.
918          */
919         {
920                 ARegion *ar = CTX_wm_region(C);
921                 wmGizmoMap *gzmap = ar->gizmo_map;
922                 wmGizmoGroup *gzgroup_init = WM_gizmomap_group_find(gzmap, "MESH_GGT_spin");
923                 if (gzgroup_init) {
924                         GizmoGroupData_SpinInit *ggd_init = gzgroup_init->customdata;
925                         copy_m3_m3(ggd->data.orient_mat, ggd_init->data.orient_mat);
926                         if (ggd_init->invoke.ortho_axis_active != -1) {
927                                 copy_v3_v3(ggd->data.orient_axis_relative,
928                                            ggd_init->gizmos.xyz_view[ggd_init->invoke.ortho_axis_active]->matrix_basis[1]);
929                                 ggd_init->invoke.ortho_axis_active = -1;
930                         }
931                 }
932                 else {
933                         unit_m3(ggd->data.orient_mat);
934                 }
935         }
936
937 #ifdef USE_ANGLE_Z_ORIENT
938         {
939                 wmWindow *win = CTX_wm_window(C);
940                 View3D *v3d = CTX_wm_view3d(C);
941                 ARegion *ar = CTX_wm_region(C);
942                 const wmEvent *event = win->eventstate;
943                 float plane_co[3], plane_no[3];
944                 RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_co, plane_co);
945                 RNA_property_float_get_array(op->ptr, ggd->data.prop_axis_no, plane_no);
946
947                 gizmo_mesh_spin_redo_update_orient_axis(ggd, plane_no);
948
949                 /* Use cursor as fallback if it's not set by the 'ortho_axis_active'. */
950                 if (is_zero_v3(ggd->data.orient_axis_relative)) {
951                         float cursor_co[3];
952                         const int mval[2] = {event->x - ar->winrct.xmin, event->y - ar->winrct.ymin};
953                         float plane[4];
954                         plane_from_point_normal_v3(plane, plane_co, plane_no);
955                         if (UNLIKELY(!ED_view3d_win_to_3d_on_plane_int(ar, plane, mval, false, cursor_co))) {
956                                 ED_view3d_win_to_3d_int(v3d, ar, plane, mval, cursor_co);
957                         }
958                         sub_v3_v3v3(ggd->data.orient_axis_relative, cursor_co, plane_co);
959                 }
960
961                 if (!is_zero_v3(ggd->data.orient_axis_relative)) {
962                         normalize_v3(ggd->data.orient_axis_relative);
963                         float imat3[3][3];
964                         invert_m3_m3(imat3, ggd->data.orient_mat);
965                         mul_m3_v3(imat3, ggd->data.orient_axis_relative);
966                 }
967         }
968 #endif
969
970         gizmo_mesh_spin_redo_update_from_op(ggd);
971
972         /* Setup property callbacks */
973         {
974                 WM_gizmo_target_property_def_func(
975                         ggd->translate_c, "offset",
976                         &(const struct wmGizmoPropertyFnParams) {
977                             .value_get_fn = gizmo_spin_prop_translate_get,
978                             .value_set_fn = gizmo_spin_prop_translate_set,
979                             .range_get_fn = NULL,
980                             .user_data = NULL,
981                         });
982
983                 WM_gizmo_target_property_def_func(
984                         ggd->rotate_view, "offset",
985                         &(const struct wmGizmoPropertyFnParams) {
986                             .value_get_fn = gizmo_spin_prop_axis_angle_get,
987                             .value_set_fn = gizmo_spin_prop_axis_angle_set,
988                             .range_get_fn = NULL,
989                             .user_data = NULL,
990                         });
991
992                 for (int i = 0; i < 2; i++) {
993                         WM_gizmo_target_property_def_func(
994                                 ggd->rotate_xy[i], "offset",
995                                 &(const struct wmGizmoPropertyFnParams) {
996                                     .value_get_fn = gizmo_spin_prop_axis_angle_get,
997                                     .value_set_fn = gizmo_spin_prop_axis_angle_set,
998                                     .range_get_fn = NULL,
999                                     .user_data = NULL,
1000                                 });
1001                         WM_gizmo_target_property_def_func(
1002                                 ggd->translate_xy[i], "offset",
1003                                 &(const struct wmGizmoPropertyFnParams) {
1004                                     .value_get_fn = gizmo_spin_prop_depth_get,
1005                                     .value_set_fn = gizmo_spin_prop_depth_set,
1006                                     .range_get_fn = NULL,
1007                                     .user_data = NULL,
1008                                 });
1009                 }
1010
1011                 WM_gizmo_target_property_def_func(
1012                         ggd->angle_z, "offset",
1013                         &(const struct wmGizmoPropertyFnParams) {
1014                             .value_get_fn = gizmo_spin_prop_angle_get,
1015                             .value_set_fn = gizmo_spin_prop_angle_set,
1016                             .range_get_fn = NULL,
1017                             .user_data = NULL,
1018                         });
1019         }
1020
1021         /* Become modal as soon as it's started. */
1022         gizmo_mesh_spin_redo_modal_from_setup(C, gzgroup);
1023 }
1024
1025 static void gizmo_mesh_spin_redo_draw_prepare(
1026         const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
1027 {
1028         GizmoGroupData_SpinRedo *ggd = gzgroup->customdata;
1029         if (ggd->data.op->next) {
1030                 ggd->data.op = WM_operator_last_redo((bContext *)ggd->data.context);
1031         }
1032
1033         /* Not essential, just avoids feedback loop where matrices could shift because of float precision.
1034          * Updates in this case are also redundant. */
1035         bool is_modal = false;
1036         for (wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) {
1037                 if (gz->state & WM_GIZMO_STATE_MODAL) {
1038                         is_modal = true;
1039                         break;
1040                 }
1041         }
1042         if (!is_modal) {
1043                 gizmo_mesh_spin_redo_update_from_op(ggd);
1044         }
1045
1046         RegionView3D *rv3d = ED_view3d_context_rv3d(ggd->data.context);
1047         WM_gizmo_set_matrix_rotation_from_z_axis(ggd->translate_c, rv3d->viewinv[2]);
1048         {
1049                 float view_up[3];
1050                 project_plane_normalized_v3_v3v3(view_up, ggd->data.orient_mat[2], rv3d->viewinv[2]);
1051                 if (normalize_v3(view_up) != 0.0f) {
1052                         WM_gizmo_set_matrix_rotation_from_yz_axis(ggd->rotate_view, view_up, rv3d->viewinv[2]);
1053                 }
1054                 else {
1055                         WM_gizmo_set_matrix_rotation_from_z_axis(ggd->rotate_view, rv3d->viewinv[2]);
1056                 }
1057         }
1058 }
1059
1060 void MESH_GGT_spin_redo(struct wmGizmoGroupType *gzgt)
1061 {
1062         gzgt->name = "Mesh Spin Redo";
1063         gzgt->idname = "MESH_GGT_spin_redo";
1064
1065         gzgt->flag = WM_GIZMOGROUPTYPE_3D;
1066
1067         gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
1068         gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
1069
1070         gzgt->poll = gizmo_mesh_spin_redo_poll;
1071         gzgt->setup = gizmo_mesh_spin_redo_setup;
1072         gzgt->draw_prepare = gizmo_mesh_spin_redo_draw_prepare;
1073 }
1074
1075 /** \} */