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