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