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