Merge remote-tracking branch 'origin/master' into blender2.8
[blender.git] / source / blender / editors / mesh / editmesh_add_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_add_gizmo.c
22  *  \ingroup edmesh
23  *
24  * Creation gizmos.
25  */
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_math.h"
30
31 #include "DNA_object_types.h"
32 #include "DNA_scene_types.h"
33
34 #include "BKE_context.h"
35 #include "BKE_editmesh.h"
36
37 #include "ED_gizmo_library.h"
38 #include "ED_gizmo_utils.h"
39 #include "ED_mesh.h"
40 #include "ED_object.h"
41 #include "ED_screen.h"
42 #include "ED_undo.h"
43 #include "ED_view3d.h"
44
45 #include "RNA_access.h"
46 #include "RNA_define.h"
47
48 #include "WM_api.h"
49 #include "WM_types.h"
50
51 #include "UI_resources.h"
52
53 #include "BLT_translation.h"
54
55 #include "mesh_intern.h"  /* own include */
56
57 /* -------------------------------------------------------------------- */
58 /** \name Helper Functions
59  * \{ */
60
61 /**
62  * When we place a shape, pick a plane.
63  *
64  * We may base this choice on context,
65  * for now pick the "ground" based on the 3D cursor's dominant plane pointing down relative to the view.
66  */
67 static void calc_initial_placement_point_from_view(
68         bContext *C, const float mval[2],
69         float r_location[3], float r_rotation[3][3])
70 {
71
72         Scene *scene = CTX_data_scene(C);
73         ARegion *ar = CTX_wm_region(C);
74         RegionView3D *rv3d = ar->regiondata;
75
76         bool use_mouse_project = true; /* TODO: make optional */
77
78         float cursor_matrix[4][4];
79         float orient_matrix[3][3];
80         ED_view3d_cursor3d_calc_mat4(scene, cursor_matrix);
81
82         float dots[3] = {
83                 dot_v3v3(rv3d->viewinv[2], cursor_matrix[0]),
84                 dot_v3v3(rv3d->viewinv[2], cursor_matrix[1]),
85                 dot_v3v3(rv3d->viewinv[2], cursor_matrix[2]),
86         };
87         const int axis = axis_dominant_v3_single(dots);
88
89         copy_v3_v3(orient_matrix[0], cursor_matrix[(axis + 1) % 3]);
90         copy_v3_v3(orient_matrix[1], cursor_matrix[(axis + 2) % 3]);
91         copy_v3_v3(orient_matrix[2], cursor_matrix[axis]);
92
93         if (dot_v3v3(rv3d->viewinv[2], orient_matrix[2]) < 0.0f) {
94                 negate_v3(orient_matrix[2]);
95         }
96         if (is_negative_m3(orient_matrix)) {
97                 swap_v3_v3(orient_matrix[0], orient_matrix[1]);
98         }
99
100         if (use_mouse_project) {
101                 float plane[4];
102                 plane_from_point_normal_v3(plane, cursor_matrix[3], orient_matrix[2]);
103                 if (ED_view3d_win_to_3d_on_plane(ar, plane, mval, true, r_location)) {
104                         copy_m3_m3(r_rotation, orient_matrix);
105                         return;
106                 }
107         }
108
109         /* fallback */
110         copy_v3_v3(r_location, cursor_matrix[3]);
111         copy_m3_m3(r_rotation, orient_matrix);
112 }
113
114 /** \} */
115
116 /* -------------------------------------------------------------------- */
117 /** \name Placement Gizmo
118  * \{ */
119
120 typedef struct GizmoPlacementGroup {
121         struct wmGizmo *cage;
122         struct {
123                 bContext *context;
124                 wmOperator *op;
125                 PropertyRNA *prop_matrix;
126         } data;
127 } GizmoPlacementGroup;
128
129 /**
130  * \warning Calling redo from property updates is not great.
131  * This is needed because changing the RNA doesn't cause a redo
132  * and we're not using operator UI which does just this.
133  */
134 static void gizmo_placement_exec(GizmoPlacementGroup *ggd)
135 {
136         wmOperator *op = ggd->data.op;
137         if (op == WM_operator_last_redo((bContext *)ggd->data.context)) {
138                 ED_undo_operator_repeat((bContext *)ggd->data.context, op);
139         }
140 }
141
142 static void gizmo_mesh_placement_update_from_op(GizmoPlacementGroup *ggd)
143 {
144         wmOperator *op = ggd->data.op;
145         UNUSED_VARS(op);
146         /* For now don't read back from the operator. */
147 #if 0
148         RNA_property_float_get_array(op->ptr, ggd->data.prop_matrix, &ggd->cage->matrix_offset[0][0]);
149 #endif
150 }
151
152 /* translate callbacks */
153 static void gizmo_placement_prop_matrix_get(
154         const wmGizmo *gz, wmGizmoProperty *gz_prop,
155         void *value_p)
156 {
157         GizmoPlacementGroup *ggd = gz->parent_gzgroup->customdata;
158         wmOperator *op = ggd->data.op;
159         float *value = value_p;
160         BLI_assert(gz_prop->type->array_length == 16);
161         UNUSED_VARS_NDEBUG(gz_prop);
162
163         if (value_p != ggd->cage->matrix_offset) {
164                 mul_m4_m4m4(value_p, ggd->cage->matrix_basis, ggd->cage->matrix_offset);
165                 RNA_property_float_get_array(op->ptr, ggd->data.prop_matrix, value);
166         }
167 }
168
169 static void gizmo_placement_prop_matrix_set(
170         const wmGizmo *gz, wmGizmoProperty *gz_prop,
171         const void *value)
172 {
173         GizmoPlacementGroup *ggd = gz->parent_gzgroup->customdata;
174         wmOperator *op = ggd->data.op;
175
176         BLI_assert(gz_prop->type->array_length == 16);
177         UNUSED_VARS_NDEBUG(gz_prop);
178
179         float mat[4][4];
180         mul_m4_m4m4(mat, ggd->cage->matrix_basis, value);
181
182         if (is_negative_m4(mat)) {
183                 negate_mat3_m4(mat);
184         }
185
186         RNA_property_float_set_array(op->ptr, ggd->data.prop_matrix, &mat[0][0]);
187
188         gizmo_placement_exec(ggd);
189 }
190
191 static bool gizmo_mesh_placement_poll(const bContext *C, wmGizmoGroupType *gzgt)
192 {
193         return ED_gizmo_poll_or_unlink_delayed_from_operator(C, gzgt, "MESH_OT_primitive_cube_add_gizmo");
194 }
195
196 static void gizmo_mesh_placement_modal_from_setup(
197         const bContext *C, wmGizmoGroup *gzgroup)
198 {
199         GizmoPlacementGroup *ggd = gzgroup->customdata;
200
201         /* Initial size. */
202         {
203                 wmGizmo *gz = ggd->cage;
204                 zero_m4(gz->matrix_offset);
205
206                 /* TODO: support zero scaled matrix in 'GIZMO_GT_cage_3d'. */
207                 gz->matrix_offset[0][0] = 0.01;
208                 gz->matrix_offset[1][1] = 0.01;
209                 gz->matrix_offset[2][2] = 0.01;
210                 gz->matrix_offset[3][3] = 1.0f;
211         }
212
213         /* Start off dragging. */
214         {
215                 wmWindow *win = CTX_wm_window(C);
216                 ARegion *ar = CTX_wm_region(C);
217                 wmGizmo *gz = ggd->cage;
218
219                 {
220                         float mat3[3][3];
221                         float location[3];
222                         calc_initial_placement_point_from_view(
223                                 (bContext *)C, (float[2]){
224                                     win->eventstate->x - ar->winrct.xmin,
225                                     win->eventstate->y - ar->winrct.ymin,
226                                 },
227                                 location, mat3);
228                         copy_m4_m3(gz->matrix_basis, mat3);
229                         copy_v3_v3(gz->matrix_basis[3], location);
230                 }
231
232                 if (1) {
233                         wmGizmoMap *gzmap = gzgroup->parent_gzmap;
234                         WM_gizmo_modal_set_from_setup(
235                                 gzmap, (bContext *)C, ggd->cage, ED_GIZMO_CAGE3D_PART_SCALE_MAX_X_MAX_Y_MAX_Z, win->eventstate);
236                 }
237         }
238 }
239
240 static void gizmo_mesh_placement_setup(const bContext *C, wmGizmoGroup *gzgroup)
241 {
242         wmOperator *op = WM_operator_last_redo(C);
243
244         if (op == NULL || !STREQ(op->type->idname, "MESH_OT_primitive_cube_add_gizmo")) {
245                 return;
246         }
247
248         struct GizmoPlacementGroup *ggd = MEM_callocN(sizeof(GizmoPlacementGroup), __func__);
249         gzgroup->customdata = ggd;
250
251         const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_3d", true);
252
253         ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, NULL);
254
255         UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, ggd->cage->color);
256
257         RNA_enum_set(ggd->cage->ptr, "transform",
258                      ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE |
259                      ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE |
260                      ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE_SIGNED);
261
262         WM_gizmo_set_flag(ggd->cage, WM_GIZMO_DRAW_VALUE, true);
263
264         ggd->data.context = (bContext *)C;
265         ggd->data.op = op;
266         ggd->data.prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
267
268         gizmo_mesh_placement_update_from_op(ggd);
269
270         /* Setup property callbacks */
271         {
272                 WM_gizmo_target_property_def_func(
273                         ggd->cage, "matrix",
274                         &(const struct wmGizmoPropertyFnParams) {
275                             .value_get_fn = gizmo_placement_prop_matrix_get,
276                             .value_set_fn = gizmo_placement_prop_matrix_set,
277                             .range_get_fn = NULL,
278                             .user_data = NULL,
279                         });
280         }
281
282         gizmo_mesh_placement_modal_from_setup(C, gzgroup);
283 }
284
285 static void gizmo_mesh_placement_draw_prepare(
286         const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
287 {
288         GizmoPlacementGroup *ggd = gzgroup->customdata;
289         if (ggd->data.op->next) {
290                 ggd->data.op = WM_operator_last_redo((bContext *)ggd->data.context);
291         }
292         gizmo_mesh_placement_update_from_op(ggd);
293 }
294
295 static void MESH_GGT_add_bounds(struct wmGizmoGroupType *gzgt)
296 {
297         gzgt->name = "Mesh Add Bounds";
298         gzgt->idname = "MESH_GGT_add_bounds";
299
300         gzgt->flag = WM_GIZMOGROUPTYPE_3D;
301
302         gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
303         gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
304
305         gzgt->poll = gizmo_mesh_placement_poll;
306         gzgt->setup = gizmo_mesh_placement_setup;
307         gzgt->draw_prepare = gizmo_mesh_placement_draw_prepare;
308 }
309
310 /** \} */
311
312 /* -------------------------------------------------------------------- */
313 /** \name Add Cube Gizmo-Operator
314  *
315  * For now we use a separate operator to add a cube,
316  * we can try to merge then however they are invoked differently
317  * and share the same BMesh creation code.
318  * \{ */
319
320
321 static int add_primitive_cube_gizmo_exec(bContext *C, wmOperator *op)
322 {
323         Object *obedit = CTX_data_edit_object(C);
324         BMEditMesh *em = BKE_editmesh_from_object(obedit);
325         float matrix[4][4];
326
327         /* Get the matrix that defines the cube bounds (as set by the gizmo cage). */
328         {
329                 PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix");
330                 if (RNA_property_is_set(op->ptr, prop_matrix)) {
331                         RNA_property_float_get_array(op->ptr, prop_matrix, &matrix[0][0]);
332                         invert_m4_m4(obedit->imat, obedit->obmat);
333                         mul_m4_m4m4(matrix, obedit->imat, matrix);
334                 }
335                 else {
336                         /* For the first update the widget may not set the matrix. */
337                         return OPERATOR_FINISHED;
338                 }
339         }
340
341         const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");
342
343         if (calc_uvs) {
344                 ED_mesh_uv_texture_ensure(obedit->data, NULL);
345         }
346
347         if (!EDBM_op_call_and_selectf(
348                 em, op, "verts.out", false,
349                 "create_cube matrix=%m4 size=%f calc_uvs=%b",
350                 matrix, 1.0f, calc_uvs))
351         {
352                 return OPERATOR_CANCELLED;
353         }
354
355         EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX);
356         EDBM_update_generic(em, true, true);
357
358         return OPERATOR_FINISHED;
359 }
360
361 static int add_primitive_cube_gizmo_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
362 {
363         View3D *v3d = CTX_wm_view3d(C);
364
365         int ret = add_primitive_cube_gizmo_exec(C, op);
366         if (ret & OPERATOR_FINISHED) {
367                 /* Setup gizmos */
368                 if (v3d && ((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0)) {
369                         wmGizmoGroupType *gzgt = WM_gizmogrouptype_find("MESH_GGT_add_bounds", false);
370                         if (!WM_gizmo_group_type_ensure_ptr(gzgt)) {
371                                 struct Main *bmain = CTX_data_main(C);
372                                 WM_gizmo_group_type_reinit_ptr(bmain, gzgt);
373                         }
374                 }
375         }
376
377         return ret;
378 }
379
380 void MESH_OT_primitive_cube_add_gizmo(wmOperatorType *ot)
381 {
382         /* identifiers */
383         ot->name = "Add Cube";
384         ot->description = "Construct a cube mesh";
385         ot->idname = "MESH_OT_primitive_cube_add_gizmo";
386
387         /* api callbacks */
388         ot->invoke = add_primitive_cube_gizmo_invoke;
389         ot->exec = add_primitive_cube_gizmo_exec;
390         ot->poll = ED_operator_editmesh_view3d;
391
392         /* flags */
393         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
394
395         ED_object_add_mesh_props(ot);
396         ED_object_add_generic_props(ot, true);
397
398         /* hidden props */
399         PropertyRNA *prop = RNA_def_float_matrix(ot->srna, "matrix", 4, 4, NULL, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f);
400         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
401
402         WM_gizmogrouptype_append(MESH_GGT_add_bounds);
403 }
404
405 /** \} */