Merge branch 'blender2.7'
[blender.git] / source / blender / editors / transform / transform_gizmo_extrude_3d.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/transform/transform_gizmo_extrude_3d.c
22  *  \ingroup edmesh
23  */
24
25 #include "BLI_utildefines.h"
26 #include "BLI_array_utils.h"
27 #include "BLI_math.h"
28 #include "BLI_listbase.h"
29
30 #include "BKE_context.h"
31 #include "BKE_global.h"
32
33 #include "RNA_access.h"
34 #include "RNA_define.h"
35
36 #include "WM_api.h"
37 #include "WM_types.h"
38 #include "WM_message.h"
39 #include "WM_toolsystem.h"
40
41 #include "ED_screen.h"
42 #include "ED_transform.h"
43 #include "ED_view3d.h"
44 #include "ED_gizmo_library.h"
45 #include "ED_gizmo_utils.h"
46
47 #include "UI_resources.h"
48
49 #include "MEM_guardedalloc.h"
50
51 /* -------------------------------------------------------------------- */
52 /** \name Extrude Gizmo
53  * \{ */
54
55 enum {
56         EXTRUDE_AXIS_NORMAL = 0,
57         EXTRUDE_AXIS_XYZ = 1,
58 };
59
60 static const float extrude_button_scale = 0.15f;
61 static const float extrude_button_offset_scale = 1.5f;
62 static const float extrude_arrow_scale = 1.0f;
63 static const float extrude_arrow_xyz_axis_scale = 1.0f;
64 static const float extrude_arrow_normal_axis_scale = 1.0f;
65 static const float extrude_dial_scale = 0.2;
66
67 static const uchar shape_plus[] = {
68         0x5f, 0xfb, 0x40, 0xee, 0x25, 0xda, 0x11, 0xbf, 0x4, 0xa0, 0x0, 0x80, 0x4, 0x5f, 0x11,
69         0x40, 0x25, 0x25, 0x40, 0x11, 0x5f, 0x4, 0x7f, 0x0, 0xa0, 0x4, 0xbf, 0x11, 0xda, 0x25,
70         0xee, 0x40, 0xfb, 0x5f, 0xff, 0x7f, 0xfb, 0xa0, 0xee, 0xbf, 0xda, 0xda, 0xbf, 0xee,
71         0xa0, 0xfb, 0x80, 0xff, 0x6e, 0xd7, 0x92, 0xd7, 0x92, 0x90, 0xd8, 0x90, 0xd8, 0x6d,
72         0x92, 0x6d, 0x92, 0x27, 0x6e, 0x27, 0x6e, 0x6d, 0x28, 0x6d, 0x28, 0x90, 0x6e,
73         0x90, 0x6e, 0xd7, 0x80, 0xff, 0x5f, 0xfb, 0x5f, 0xfb,
74 };
75
76 typedef struct GizmoExtrudeGroup {
77
78         /* XYZ & normal. */
79         wmGizmo *invoke_xyz_no[4];
80         /* Constrained & unconstrained (arrow & circle). */
81         wmGizmo *adjust[2];
82         int             adjust_axis;
83
84         /* Copied from the transform operator,
85          * use to redo with the same settings. */
86         struct {
87                 float constraint_matrix[3][3];
88                 bool  constraint_axis[3];
89                 float value[4];
90         } redo_xform;
91
92         /* Depends on object type. */
93         int normal_axis;
94
95         struct {
96                 float normal_mat3[3][3];  /* use Z axis for normal. */
97                 int orientation_type;
98         } data;
99
100         wmOperatorType *ot_extrude;
101         PropertyRNA *gzgt_axis_type_prop;
102 } GizmoExtrudeGroup;
103
104 static void gizmo_mesh_extrude_orientation_matrix_set(
105         struct GizmoExtrudeGroup *ggd, const float mat[3][3])
106 {
107         for (int i = 0; i < 3; i++) {
108                 mul_v3_v3fl(
109                         ggd->invoke_xyz_no[i]->matrix_offset[3],
110                         mat[i],
111                         (extrude_arrow_xyz_axis_scale * extrude_button_offset_scale) / extrude_button_scale);
112         }
113 }
114
115 static void gizmo_mesh_extrude_orientation_matrix_set_for_adjust(
116         struct GizmoExtrudeGroup *ggd, const float mat[3][3])
117 {
118         /* Set orientation without location. */
119         for (int j = 0; j < 3; j++) {
120                 copy_v3_v3(ggd->adjust[0]->matrix_basis[j], mat[j]);
121         }
122         /* nop when (i == 2). */
123         swap_v3_v3(ggd->adjust[0]->matrix_basis[ggd->adjust_axis], ggd->adjust[0]->matrix_basis[2]);
124 }
125
126 static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
127 {
128         struct GizmoExtrudeGroup *ggd = MEM_callocN(sizeof(GizmoExtrudeGroup), __func__);
129         gzgroup->customdata = ggd;
130
131         const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true);
132         const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_button_2d", true);
133         const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
134
135         ggd->adjust[0] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
136         ggd->adjust[1] = WM_gizmo_new_ptr(gzt_dial, gzgroup, NULL);
137         for (int i = 0; i < 4; i++) {
138                 ggd->invoke_xyz_no[i] = WM_gizmo_new_ptr(gzt_move, gzgroup, NULL);
139                 ggd->invoke_xyz_no[i]->flag |= WM_GIZMO_DRAW_OFFSET_SCALE;
140         }
141
142         {
143                 PropertyRNA *prop = RNA_struct_find_property(ggd->invoke_xyz_no[3]->ptr, "shape");
144                 for (int i = 0; i < 4; i++) {
145                         RNA_property_string_set_bytes(
146                                 ggd->invoke_xyz_no[i]->ptr, prop,
147                                 (const char *)shape_plus, ARRAY_SIZE(shape_plus));
148                 }
149         }
150
151         {
152                 const Object *obedit = CTX_data_edit_object(C);
153                 const char *op_idname = NULL;
154                 if (obedit->type == OB_MESH) {
155                         op_idname = "MESH_OT_extrude_context_move";
156                         ggd->normal_axis = 2;
157                 }
158                 else if (obedit->type == OB_ARMATURE) {
159                         op_idname = "ARMATURE_OT_extrude_move";
160                         ggd->normal_axis = 1;
161                 }
162                 else if (obedit->type == OB_CURVE) {
163                         op_idname = "CURVE_OT_extrude_move";
164                         ggd->normal_axis = 2;
165                 }
166                 else {
167                         BLI_assert(0);
168                 }
169                 ggd->ot_extrude = WM_operatortype_find(op_idname, true);
170                 ggd->gzgt_axis_type_prop = RNA_struct_type_find_property(gzgroup->type->srna, "axis_type");
171         }
172
173         for (int i = 0; i < 3; i++) {
174                 UI_GetThemeColor3fv(TH_AXIS_X + i, ggd->invoke_xyz_no[i]->color);
175         }
176         UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->invoke_xyz_no[3]->color);
177         for (int i = 0; i < 2; i++) {
178                 UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->adjust[i]->color);
179         }
180
181         for (int i = 0; i < 4; i++) {
182                 WM_gizmo_set_scale(ggd->invoke_xyz_no[i], extrude_button_scale);
183         }
184         WM_gizmo_set_scale(ggd->adjust[0], extrude_arrow_scale);
185         WM_gizmo_set_scale(ggd->adjust[1], extrude_dial_scale);
186         ggd->adjust[1]->line_width = 2.0f;
187
188         /* XYZ & normal axis extrude. */
189         for (int i = 0; i < 4; i++) {
190                 PointerRNA *ptr = WM_gizmo_operator_set(ggd->invoke_xyz_no[i], 0, ggd->ot_extrude, NULL);
191                 {
192                         bool constraint[3] = {0, 0, 0};
193                         constraint[(i < 3) ? i : ggd->normal_axis] = true;
194                         PointerRNA macroptr = RNA_pointer_get(ptr, "TRANSFORM_OT_translate");
195                         RNA_boolean_set(&macroptr, "release_confirm", true);
196                         RNA_boolean_set_array(&macroptr, "constraint_axis", constraint);
197                 }
198         }
199
200         /* Adjust extrude. */
201         for (int i = 0; i < 2; i++) {
202                 wmGizmo *gz = ggd->adjust[i];
203                 PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ggd->ot_extrude, NULL);
204                 PointerRNA macroptr = RNA_pointer_get(ptr, "TRANSFORM_OT_translate");
205                 RNA_boolean_set(&macroptr, "release_confirm", true);
206                 wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
207                 gzop->is_redo = true;
208         }
209 }
210
211 static void gizmo_mesh_extrude_refresh(const bContext *C, wmGizmoGroup *gzgroup)
212 {
213         GizmoExtrudeGroup *ggd = gzgroup->customdata;
214
215         for (int i = 0; i < 4; i++) {
216                 WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, true);
217         }
218         for (int i = 0; i < 2; i++) {
219                 WM_gizmo_set_flag(ggd->adjust[i], WM_GIZMO_HIDDEN, true);
220         }
221
222         if (G.moving) {
223                 return;
224         }
225
226         Scene *scene = CTX_data_scene(C);
227
228         int axis_type;
229         {
230                 PointerRNA ptr;
231                 bToolRef *tref = WM_toolsystem_ref_from_context((bContext *)C);
232                 WM_toolsystem_ref_properties_ensure_from_gizmo_group(tref, gzgroup->type, &ptr);
233                 axis_type = RNA_property_enum_get(&ptr, ggd->gzgt_axis_type_prop);
234         }
235
236         ggd->data.orientation_type = scene->orientation_slots[SCE_ORIENT_DEFAULT].type;
237         const bool use_normal = (
238                 (ggd->data.orientation_type != V3D_MANIP_NORMAL) ||
239                 (axis_type == EXTRUDE_AXIS_NORMAL));
240         const int axis_len_used = use_normal ? 4 : 3;
241
242         struct TransformBounds tbounds;
243
244         if (use_normal) {
245                 struct TransformBounds tbounds_normal;
246                 if (!ED_transform_calc_gizmo_stats(
247                             C, &(struct TransformCalcParams){
248                                 .orientation_type = V3D_MANIP_NORMAL + 1,
249                             }, &tbounds_normal))
250                 {
251                         unit_m3(tbounds_normal.axis);
252                 }
253                 copy_m3_m3(ggd->data.normal_mat3, tbounds_normal.axis);
254         }
255
256         /* TODO(campbell): run second since this modifies the 3D view, it should not. */
257         if (!ED_transform_calc_gizmo_stats(
258                     C, &(struct TransformCalcParams){
259                         .orientation_type = ggd->data.orientation_type + 1,
260                     }, &tbounds))
261         {
262                 return;
263         }
264
265         /* Main axis is normal. */
266         if (!use_normal) {
267                 copy_m3_m3(ggd->data.normal_mat3, tbounds.axis);
268         }
269
270         /* Offset the add icon. */
271         mul_v3_v3fl(
272                 ggd->invoke_xyz_no[3]->matrix_offset[3],
273                 ggd->data.normal_mat3[ggd->normal_axis],
274                 (extrude_arrow_normal_axis_scale * extrude_button_offset_scale) / extrude_button_scale);
275
276         /* Adjust current operator. */
277         /* Don't use 'WM_operator_last_redo' because selection actions will be ignored. */
278         wmOperator *op = CTX_wm_manager(C)->operators.last;
279         bool has_redo = (op && op->type == ggd->ot_extrude);
280         wmOperator *op_xform = has_redo ? op->macro.last : NULL;
281
282         bool adjust_is_flip = false;
283         wmGizmo *gz_adjust = NULL;
284
285         if (has_redo) {
286                 gz_adjust = ggd->adjust[1];
287                 /* We can't access this from 'ot->last_properties'
288                  * because some properties use skip-save. */
289                 RNA_float_get_array(op_xform->ptr, "constraint_matrix", &ggd->redo_xform.constraint_matrix[0][0]);
290                 RNA_boolean_get_array(op_xform->ptr, "constraint_axis", ggd->redo_xform.constraint_axis);
291                 RNA_float_get_array(op_xform->ptr, "value", ggd->redo_xform.value);
292
293                 /* Set properties for redo. */
294                 for (int i = 0; i < 3; i++) {
295                         if (ggd->redo_xform.constraint_axis[i]) {
296                                 adjust_is_flip = ggd->redo_xform.value[i] < 0.0f;
297                                 ggd->adjust_axis = i;
298                                 gz_adjust = ggd->adjust[0];
299                                 break;
300                         }
301                 }
302         }
303
304         /* Needed for normal orientation. */
305         gizmo_mesh_extrude_orientation_matrix_set(ggd, tbounds.axis);
306
307         /* Location. */
308         for (int i = 0; i < axis_len_used; i++) {
309                 WM_gizmo_set_matrix_location(ggd->invoke_xyz_no[i], tbounds.center);
310         }
311         /* Un-hide. */
312         for (int i = 0; i < axis_len_used; i++) {
313                 WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, false);
314         }
315
316         if (has_redo) {
317                 if (gz_adjust == ggd->adjust[0]) {
318                         gizmo_mesh_extrude_orientation_matrix_set_for_adjust(ggd, ggd->redo_xform.constraint_matrix);
319                         if (adjust_is_flip) {
320                                 negate_v3(ggd->adjust[0]->matrix_basis[2]);
321                         }
322                 }
323                 WM_gizmo_set_matrix_location(gz_adjust, tbounds.center);
324                 WM_gizmo_set_flag(gz_adjust, WM_GIZMO_HIDDEN, false);
325         }
326
327         /* Redo with current settings. */
328         if (has_redo) {
329                 for (int i = 0; i < 4; i++) {
330                         RNA_enum_set(
331                                 ggd->invoke_xyz_no[i]->ptr,
332                                 "draw_options",
333                                 ((gz_adjust == ggd->adjust[0]) &&
334                                  dot_v3v3(ggd->adjust[0]->matrix_basis[2],
335                                           ggd->invoke_xyz_no[i]->matrix_offset[3]) > 0.98f) ? 0 : ED_GIZMO_BUTTON_SHOW_HELPLINE);
336                 }
337         }
338         else {
339                 for (int i = 0; i < 4; i++) {
340                         RNA_enum_set(ggd->invoke_xyz_no[i]->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_HELPLINE);
341                 }
342         }
343
344         /* TODO: skip calculating axis which wont be used (above). */
345         switch (axis_type) {
346                 case EXTRUDE_AXIS_NORMAL:
347                         for (int i = 0; i < 3; i++) {
348                                 WM_gizmo_set_flag(ggd->invoke_xyz_no[i], WM_GIZMO_HIDDEN, true);
349                         }
350                         break;
351                 case EXTRUDE_AXIS_XYZ:
352                         WM_gizmo_set_flag(ggd->invoke_xyz_no[3], WM_GIZMO_HIDDEN, true);
353                         break;
354         }
355 }
356
357 static void gizmo_mesh_extrude_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
358 {
359         GizmoExtrudeGroup *ggd = gzgroup->customdata;
360         switch (ggd->data.orientation_type) {
361                 case V3D_MANIP_VIEW:
362                 {
363                         RegionView3D *rv3d = CTX_wm_region_view3d(C);
364                         float mat[3][3];
365                         copy_m3_m4(mat, rv3d->viewinv);
366                         normalize_m3(mat);
367                         gizmo_mesh_extrude_orientation_matrix_set(ggd, mat);
368                         break;
369                 }
370         }
371
372         /* Basic ordering for drawing only. */
373         {
374                 RegionView3D *rv3d = CTX_wm_region_view3d(C);
375                 LISTBASE_FOREACH (wmGizmo *, gz, &gzgroup->gizmos) {
376                         gz->temp.f = dot_v3v3(rv3d->viewinv[2], gz->matrix_offset[3]);
377                 }
378                 BLI_listbase_sort(&gzgroup->gizmos, WM_gizmo_cmp_temp_fl_reverse);
379
380                 if ((ggd->adjust[1]->flag & WM_GIZMO_HIDDEN) == 0) {
381                         copy_v3_v3(ggd->adjust[1]->matrix_basis[0], rv3d->viewinv[0]);
382                         copy_v3_v3(ggd->adjust[1]->matrix_basis[1], rv3d->viewinv[1]);
383                         copy_v3_v3(ggd->adjust[1]->matrix_basis[2], rv3d->viewinv[2]);
384                 }
385         }
386 }
387
388 static void gizmo_mesh_extrude_invoke_prepare(const bContext *UNUSED(C), wmGizmoGroup *gzgroup, wmGizmo *gz)
389 {
390         GizmoExtrudeGroup *ggd = gzgroup->customdata;
391         if (ELEM(gz, ggd->adjust[0], ggd->adjust[1])) {
392                 /* Set properties for redo. */
393                 wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
394                 PointerRNA macroptr = RNA_pointer_get(&gzop->ptr, "TRANSFORM_OT_translate");
395                 if (gz == ggd->adjust[0]) {
396                         RNA_float_set_array(&macroptr, "constraint_matrix", &ggd->redo_xform.constraint_matrix[0][0]);
397                         RNA_boolean_set_array(&macroptr, "constraint_axis", ggd->redo_xform.constraint_axis);
398                 }
399                 RNA_float_set_array(&macroptr, "value", ggd->redo_xform.value);
400         }
401         else {
402                 /* Workaround for extrude action modifying normals. */
403                 const int i = BLI_array_findindex(ggd->invoke_xyz_no, ARRAY_SIZE(ggd->invoke_xyz_no), &gz);
404                 BLI_assert(i != -1);
405                 bool use_normal_matrix = false;
406                 if (i == 3) {
407                         use_normal_matrix = true;
408                 }
409                 else if (ggd->data.orientation_type == V3D_MANIP_NORMAL) {
410                         use_normal_matrix = true;
411                 }
412                 if (use_normal_matrix) {
413                         wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
414                         PointerRNA macroptr = RNA_pointer_get(&gzop->ptr, "TRANSFORM_OT_translate");
415                         RNA_float_set_array(&macroptr, "constraint_matrix", &ggd->data.normal_mat3[0][0]);
416                 }
417         }
418 }
419
420 static void gizmo_mesh_extrude_message_subscribe(
421         const bContext *C, wmGizmoGroup *gzgroup, struct wmMsgBus *mbus)
422 {
423         GizmoExtrudeGroup *ggd = gzgroup->customdata;
424         ARegion *ar = CTX_wm_region(C);
425
426         /* Subscribe to view properties */
427         wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = {
428                 .owner = ar,
429                 .user_data = gzgroup->parent_gzmap,
430                 .notify = WM_gizmo_do_msg_notify_tag_refresh,
431         };
432
433         {
434                 WM_msg_subscribe_rna_anon_prop(mbus, TransformOrientationSlot, type, &msg_sub_value_gz_tag_refresh);
435         }
436
437
438         WM_msg_subscribe_rna_params(
439                 mbus,
440                 &(const wmMsgParams_RNA){
441                     .ptr = (PointerRNA){.type = gzgroup->type->srna},
442                     .prop = ggd->gzgt_axis_type_prop,
443                 },
444                 &msg_sub_value_gz_tag_refresh, __func__);
445 }
446
447 void VIEW3D_GGT_xform_extrude(struct wmGizmoGroupType *gzgt)
448 {
449         gzgt->name = "3D View Extrude";
450         gzgt->idname = "VIEW3D_GGT_xform_extrude";
451
452         gzgt->flag = WM_GIZMOGROUPTYPE_3D;
453
454         gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
455         gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
456
457         gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool;
458         gzgt->setup = gizmo_mesh_extrude_setup;
459         gzgt->refresh = gizmo_mesh_extrude_refresh;
460         gzgt->draw_prepare = gizmo_mesh_extrude_draw_prepare;
461         gzgt->invoke_prepare = gizmo_mesh_extrude_invoke_prepare;
462         gzgt->message_subscribe = gizmo_mesh_extrude_message_subscribe;
463
464         static const EnumPropertyItem axis_type_items[] = {
465                 {EXTRUDE_AXIS_NORMAL, "NORMAL", 0, "Normal", "Only show normal axis"},
466                 {EXTRUDE_AXIS_XYZ, "XYZ", 0, "XYZ", "Follow scene orientation"},
467                 {0, NULL, 0, NULL, NULL}
468         };
469         RNA_def_enum(gzgt->srna, "axis_type", axis_type_items, 0, "Axis Type", "");
470 }
471
472 /** \} */