Fix [#30855] Vertex Slide not using the active vertex
[blender-staging.git] / source / blender / editors / mesh / editmesh_slide.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  * Contributor(s): Francisco De La Cruz
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/mesh/editmesh_slide.c
24  *  \ingroup edmesh
25  */
26
27 /* Takes heavily from editmesh_loopcut.c */
28
29 #include "DNA_object_types.h"
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLI_array.h"
34 #include "BLI_math.h"
35
36 #include "BKE_context.h"
37 #include "BKE_report.h"
38 #include "BKE_tessmesh.h"
39
40 #include "BIF_gl.h"
41 #include "BIF_glutil.h"
42
43 #include "ED_screen.h"
44 #include "ED_view3d.h"
45 #include "ED_mesh.h"
46 #include "ED_space_api.h"
47
48 #include "UI_resources.h"
49
50 #include "RNA_access.h"
51 #include "RNA_define.h"
52
53 #include "WM_api.h"
54 #include "WM_types.h"
55
56 #include "mesh_intern.h"
57
58 #define VTX_SLIDE_SNAP_THRSH 0.15
59
60 /* Cusom VertexSlide Operator data */
61 typedef struct VertexSlideOp {
62         /* Starting Vertex */
63         BMVert *start_vtx;
64         BMEdge *sel_edge;
65
66         ViewContext *view_context;
67         ARegion *active_region;
68
69         /* Draw callback handle */
70         void *draw_handle;
71
72         /* Active Object */
73         Object *obj;
74
75         /* Are we in slide mode */
76         int slide_mode;
77         int snap_n_merge;
78         int snap_to_end_vtx;
79         int snap_to_mid;
80
81         /* Snap threshold */
82         float snap_threshold;
83
84         float distance;
85         float interp[3];
86
87         /* Edge Frame Count */
88         int disk_edges;
89
90         /* Edges */
91         BMEdge** edge_frame;
92
93         /* Slide Frame Endpoints */
94         float (*vtx_frame)[3];
95
96         /* Mouse Click 2d pos */
97         int m_co[2];
98
99 } VertexSlideOp;
100
101 static void vtx_slide_draw(const bContext *C, ARegion *ar, void *arg);
102 static int edbm_vert_slide_exec(bContext *C, wmOperator *op);
103 static void vtx_slide_exit(const bContext *C, wmOperator *op);
104 static void vtx_slide_set_frame(VertexSlideOp *vso);
105
106 static int vtx_slide_init(bContext *C, wmOperator *op)
107 {
108         Object *obedit = CTX_data_edit_object(C);
109         BMEditMesh *em = BMEdit_FromObject(obedit);
110         BMEditSelection *ese;
111
112         /* Custom data */
113         VertexSlideOp *vso;
114
115         const char *header_str = "Vertex Slide: Hover over an edge and left-click to select slide edge. "
116                                  "Left-Shift: Midpoint Snap, Left-Alt: Snap, Left-Ctrl: Snap&Merge";
117
118         if (!obedit) {
119                 BKE_report(op->reports, RPT_ERROR, "Vertex Slide Error: Not object in context");
120                 return FALSE;
121         }
122
123         EDBM_selectmode_flush(em);
124         ese = em->bm->selected.last;
125
126         /* Is there a starting vertex  ? */
127         if (ese == NULL || (ese->htype != BM_VERT && ese->htype != BM_EDGE)) {
128                 BKE_report(op->reports, RPT_ERROR_INVALID_INPUT, "Vertex Slide Error: Select a (single) vertex");
129                 return FALSE;
130         }
131
132         vso = MEM_callocN(sizeof(VertexSlideOp), "Vertex Slide Operator");
133         vso->view_context = MEM_callocN(sizeof(ViewContext), "Vertex Slide View Context");
134
135         op->customdata = vso;
136
137         /* Set the start vertex */
138         vso->start_vtx = (BMVert *)ese->ele;
139
140         vso->sel_edge = NULL;
141
142         /* Edges */
143         vso->edge_frame = NULL;
144
145         vso->vtx_frame = NULL;
146
147         vso->disk_edges = 0;
148
149         vso->slide_mode = FALSE;
150
151         vso->snap_n_merge = FALSE;
152
153         vso->snap_to_end_vtx = FALSE;
154
155         vso->snap_to_mid = FALSE;
156
157         vso->distance = 0.0f;
158
159         vso->snap_threshold = 0.2f;
160
161         /* Add handler for the vertex sliding */
162         WM_event_add_modal_handler(C, op);
163
164         /* Notify the viewport */
165         view3d_operator_needs_opengl(C);
166
167         /* Set the drawing region */
168         vso->active_region = CTX_wm_region(C);
169
170         /* Set the draw callback */
171         vso->draw_handle = ED_region_draw_cb_activate(vso->active_region->type, vtx_slide_draw, vso, REGION_DRAW_POST_VIEW);
172
173         ED_area_headerprint(CTX_wm_area(C), header_str);
174         
175         em_setup_viewcontext(C, vso->view_context);
176
177         /* Set the object */
178         vso->obj = obedit;
179
180         /* Init frame */
181         vtx_slide_set_frame(vso);
182
183         /* Tag for redraw */
184         ED_region_tag_redraw(vso->active_region);
185
186         return TRUE;
187 }
188
189 static void vtx_slide_confirm(bContext *C, wmOperator *op)
190 {
191         VertexSlideOp *vso = op->customdata;
192         BMEditMesh *em = BMEdit_FromObject(vso->obj);
193         BMesh* bm = em->bm;
194
195         /* Select new edge */
196         BM_edge_select_set(bm, vso->sel_edge, TRUE);
197
198         /* Invoke operator */
199         edbm_vert_slide_exec(C, op);
200
201         if(vso->snap_n_merge) {
202                 float other_d;
203                 BMVert* other = BM_edge_other_vert(vso->sel_edge, vso->start_vtx);
204                 other_d = len_v3v3(vso->interp, other->co);
205
206                 /* Only snap if within threshold */
207                 if (other_d < vso->snap_threshold) {
208                         BM_vert_select_set(bm, other, TRUE);
209                         BM_vert_select_set(bm, vso->start_vtx, TRUE);
210                         EDBM_op_callf(em, op, "pointmerge verts=%hv mergeco=%v", BM_ELEM_SELECT, other->co);
211                         EDBM_flag_disable_all(em, BM_ELEM_SELECT);
212                 } else {
213                         /* Store in historty if not merging */
214                         EDBM_editselection_store(em, &vso->start_vtx->head);            
215                 }
216         }
217         else {
218                 /* Store edit selection of the active vertex, allows other
219                  *  ops to run without reselecting */
220                 EDBM_editselection_store(em, &vso->start_vtx->head);
221         }
222
223         EDBM_selectmode_flush(em);
224         
225         /* NC_GEOM | ND_DATA & Retess */
226         EDBM_update_generic(C, em, TRUE);
227         
228         ED_region_tag_redraw(vso->active_region);
229 }
230
231 static void vtx_slide_exit(const bContext *C, wmOperator *op)
232 {
233         /* Fetch custom data */
234         VertexSlideOp *vso = op->customdata;
235
236         /* Clean-up the custom data */
237         ED_region_draw_cb_exit(vso->active_region->type, vso->draw_handle);
238
239         /* Free Custom Data
240          *
241          */
242         MEM_freeN(vso->view_context);
243
244         vso->view_context = NULL;
245
246         if (vso->edge_frame) {
247                 MEM_freeN(vso->edge_frame);
248         }
249
250         if(vso->vtx_frame) {
251                 MEM_freeN(vso->vtx_frame);
252         }
253
254         vso->edge_frame = NULL;
255
256         vso->vtx_frame = NULL;
257
258         vso->slide_mode = FALSE;
259
260         MEM_freeN(vso);
261         vso = NULL;
262
263         /* Clear the header */
264         ED_area_headerprint(CTX_wm_area(C), NULL);
265 }
266
267 static void vtx_slide_draw(const bContext *C, ARegion *UNUSED(ar), void *arg)
268 {
269         VertexSlideOp *vso = arg;
270
271         /* Have an edge to draw */
272         if (vso && vso->sel_edge) {
273                 /* Get 3d view */
274                 View3D *view3d = CTX_wm_view3d(C);
275                 const int outline_w = UI_GetThemeValuef(TH_OUTLINE_WIDTH) + 1;
276                 int i = 0;
277
278                 if (view3d && view3d->zbuf)
279                         glDisable(GL_DEPTH_TEST);
280
281                 glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT | GL_POINT_BIT);
282
283                 glPushMatrix();
284                 glMultMatrixf(vso->obj->obmat);
285
286                 glEnable(GL_BLEND);
287                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
288
289                 /* Draw selected edge
290                  * Add color offset and reduce alpha */
291                 UI_ThemeColorShadeAlpha(TH_EDGE_SELECT, 50, -50);
292
293                 glLineWidth(outline_w);
294
295                 glBegin(GL_LINES);
296                 bglVertex3fv(vso->sel_edge->v1->co);
297                 bglVertex3fv(vso->sel_edge->v2->co);
298                 glEnd();
299
300                 if (vso->slide_mode && vso->disk_edges > 0) {
301                         /* Draw intermediate edge frame */
302                         UI_ThemeColorShadeAlpha(TH_EDGE_SELECT, 50, -50);
303
304                         for (i = 0; i < vso->disk_edges; i++) {
305                                 glBegin(GL_LINES);
306                                 glVertex3fv(vso->vtx_frame[i]);
307                                 glVertex3fv(vso->interp);
308                                 glEnd();
309                         }
310                 }
311
312                 if (vso->slide_mode) {
313                         /* Draw interpolated vertex */
314                         int pt_size = UI_GetThemeValuef(TH_FACEDOT_SIZE) + 2;
315                         UI_ThemeColorShadeAlpha(TH_FACE_DOT, -90, -50);
316
317                         glPointSize(pt_size);
318
319                         bglBegin(GL_POINTS);
320                         bglVertex3fv(vso->interp);
321                         bglEnd();
322                 }
323
324                 glDisable(GL_BLEND);
325                 glPopMatrix();
326                 glPopAttrib();
327
328                 if (view3d && view3d->zbuf)
329                         glEnable(GL_DEPTH_TEST);
330         }
331 }
332
333 static BMEdge* vtx_slide_nrst_in_frame(VertexSlideOp *vso, const float mval[2])
334 {
335         BMEdge* cl_edge = NULL;
336         if (vso->disk_edges > 0) {
337                 int i = 0;
338                 BMEdge* edge = NULL;
339                 
340                 float v1_proj[3], v2_proj[3];
341                 float dist = 0;
342                 float min_dist = FLT_MAX;
343
344                 for (i = 0; i < vso->disk_edges; i++) {
345                         edge = vso->edge_frame[i];
346
347                         mul_v3_m4v3(v1_proj, vso->obj->obmat, edge->v1->co);
348                         project_float_noclip(vso->active_region, v1_proj, v1_proj);
349
350                         mul_v3_m4v3(v2_proj, vso->obj->obmat, edge->v2->co);
351                         project_float_noclip(vso->active_region, v2_proj, v2_proj);
352
353                         dist = dist_to_line_segment_v2(mval, v1_proj, v2_proj);
354                         if (dist < min_dist) {
355                                 min_dist = dist;
356                                 cl_edge = edge;
357                         }
358                 }
359         }
360         return cl_edge;
361 }
362
363 static void vtx_slide_find_edge(VertexSlideOp *vso, wmEvent *event)
364 {
365         /* Nearest edge */
366         BMEdge *nst_edge = NULL;
367
368         /* Temp Vtx */
369         BMVert *start_vtx = vso->start_vtx;
370
371         const float mval_float[] = { (float)event->mval[0], (float)event->mval[1]};
372
373         /* Set mouse coords */
374         copy_v2_v2_int(vso->view_context->mval, event->mval);
375
376         /* Find nearest edge */
377         nst_edge = vtx_slide_nrst_in_frame(vso, mval_float);
378
379         if (nst_edge) {
380                 /* Find a connected edge */
381                 if (BM_vert_in_edge(nst_edge, vso->start_vtx)) {
382                         float edge_len;
383
384                         /* Save mouse coords */
385                         copy_v2_v2_int(vso->m_co, event->mval);
386
387                         /* Set edge */
388                         vso->sel_edge = nst_edge;
389                         
390                         /* Set snap threshold to be proportional to edge length */
391                         edge_len = len_v3v3(nst_edge->v1->co, nst_edge->v2->co);
392                         vso->snap_threshold = edge_len * VTX_SLIDE_SNAP_THRSH;
393                 }
394         }
395 }
396
397 /* Updates the status of the operator - Invoked on mouse movement */
398 static void vtx_slide_update(VertexSlideOp *vso, wmEvent *event)
399 {
400         BMEdge *edge;
401
402         /* Find nearest edge */
403         edge = vso->sel_edge;
404
405         if (edge) {
406                 float edge_other_proj[3];
407                 float start_vtx_proj[3];
408                 BMVert *other;
409
410                 float interp[3];
411
412                 /* Calculate interpolation value for preview */
413                 float t_val;
414
415                 float mval_float[] = { (float)event->mval[0], (float)event->mval[1]};
416                 float closest_2d[2];
417
418                 other = BM_edge_other_vert(edge, vso->start_vtx);
419
420                 /* Project points onto screen and do interpolation in 2D */
421                 mul_v3_m4v3(start_vtx_proj, vso->obj->obmat, vso->start_vtx->co);
422                 project_float_noclip(vso->active_region, start_vtx_proj, start_vtx_proj);
423
424                 mul_v3_m4v3(edge_other_proj, vso->obj->obmat, other->co);
425                 project_float_noclip(vso->active_region, edge_other_proj, edge_other_proj);
426
427                 closest_to_line_v2(closest_2d, mval_float, start_vtx_proj, edge_other_proj);
428
429                 t_val = line_point_factor_v2(closest_2d, start_vtx_proj, edge_other_proj);
430
431                 /* Snap to mid */
432                 if (vso->snap_to_mid) {
433                         t_val = 0.5f;
434                 }
435
436                 /* Interpolate preview vertex 3D */
437                 interp_v3_v3v3(interp, vso->start_vtx->co, other->co, t_val);
438                 copy_v3_v3(vso->interp, interp);
439
440                 vso->distance = t_val;
441
442                 /* If snapping */
443                 if (vso->snap_to_end_vtx) {
444                         int start_at_v1 = edge->v1 == vso->start_vtx;
445                         float v1_d = len_v3v3(vso->interp, edge->v1->co);
446                         float v2_d = len_v3v3(vso->interp, edge->v2->co);
447
448                         if (v1_d > v2_d && v2_d < vso->snap_threshold) {
449                                 copy_v3_v3(vso->interp, edge->v2->co);
450
451                                 if (start_at_v1)
452                                         vso->distance = 1.0f;
453                                 else
454                                         vso->distance = 0.0f;
455                         }
456                         if (v2_d > v1_d && v1_d < vso->snap_threshold) {
457                                 copy_v3_v3(vso->interp, edge->v1->co);
458                                 if (start_at_v1)
459                                         vso->distance = 0.0f;
460                                 else
461                                         vso->distance = 1.0f;
462                         }
463                 }
464         }
465 }
466
467 /* Sets the outline frame */
468 static void vtx_slide_set_frame(VertexSlideOp *vso)
469 {
470         BMEdge *edge;
471         float (*vtx_frame)[3] = NULL;
472         BMEdge** edge_frame = NULL;
473         BLI_array_declare(vtx_frame);
474         BLI_array_declare(edge_frame);
475         BMIter iter;
476         BMEditMesh *em = BMEdit_FromObject(vso->obj);
477         BMesh *bm = em->bm;
478         BMVert *sel_vtx = vso->start_vtx;
479         int idx = 0;
480
481         vso->disk_edges = 0;
482
483         if (vso->edge_frame) {
484                 MEM_freeN(vso->edge_frame);
485                 vso->edge_frame = NULL;
486         }
487
488         if (vso->vtx_frame) {
489                 MEM_freeN(vso->vtx_frame);
490                 vso->vtx_frame = NULL;
491         }
492
493         /* Iterate over edges of vertex and copy them */
494         BM_ITER_INDEX(edge, &iter, bm, BM_EDGES_OF_VERT, sel_vtx, idx)
495         {
496                 BLI_array_growone(vtx_frame);
497
498                 copy_v3_v3(vtx_frame[idx], BM_edge_other_vert(edge, sel_vtx)->co);
499
500                 BLI_array_append(edge_frame, edge);
501                 vso->disk_edges++;
502         }
503
504         vso->edge_frame = edge_frame;
505         vso->vtx_frame = vtx_frame;
506
507         /* Set the interp at starting vtx */
508         copy_v3_v3(vso->interp, sel_vtx->co);
509 }
510
511 static int edbm_vert_slide_modal(bContext *C, wmOperator *op, wmEvent *event)
512 {
513         VertexSlideOp *vso = op->customdata;
514
515         /* Notify the viewport */
516         view3d_operator_needs_opengl(C);
517
518         switch (event->type) {
519                 case LEFTSHIFTKEY:
520                 {
521                         switch (event->val) {
522                                 case KM_PRESS:
523                                         vso->snap_to_mid = TRUE;
524                                         break;
525                                 case KM_RELEASE:
526                                         vso->snap_to_mid = FALSE;
527                                         break;
528                         }
529
530                         break;
531                 }
532                 case LEFTCTRLKEY:
533                 {
534                         switch (event->val) {
535                                 case KM_PRESS:
536                                         vso->snap_n_merge = TRUE;
537                                         vso->snap_to_end_vtx = TRUE;
538                                         break;
539                                 case KM_RELEASE:
540                                         vso->snap_n_merge = FALSE;
541                                         vso->snap_to_end_vtx = FALSE;
542                                         break;
543                         }
544
545                         break;
546                 }
547                 case LEFTALTKEY:
548                 {
549                         switch (event->val) {
550                                 case KM_PRESS:
551                                         vso->snap_to_end_vtx = TRUE;
552                                         break;
553                                 case KM_RELEASE:
554                                         vso->snap_to_end_vtx = FALSE;
555                                         break;
556                         }
557
558                         break;
559                 }
560                 case RIGHTMOUSE:
561                 {
562                         /* Enforce redraw */
563                         ED_region_tag_redraw(vso->active_region);
564
565                         /* Clean-up */
566                         vtx_slide_exit(C, op);
567
568                         return OPERATOR_CANCELLED;
569                 }
570                 case LEFTMOUSE:
571                 {
572                         if (event->val == KM_PRESS) {
573                                 /* Update mouse coords */
574                                 copy_v2_v2_int(vso->m_co, event->mval);
575
576                                 if (vso->slide_mode) {
577                                         vtx_slide_confirm(C, op);
578                                         /* Clean-up */
579                                         vtx_slide_exit(C, op);
580                                         return OPERATOR_FINISHED;
581                                 }
582                                 else if (vso->sel_edge) {
583                                         vso->slide_mode = TRUE;
584                                 }
585                         }
586
587                         ED_region_tag_redraw(vso->active_region);
588                         break;
589
590                 }
591                 case MOUSEMOVE:
592                 {
593                         if (!vso->slide_mode) {
594                                 vtx_slide_find_edge(vso, event);
595                         }
596                         else {
597                                 vtx_slide_update(vso, event);
598                         }
599
600                         ED_region_tag_redraw(vso->active_region);
601                         break;
602                 }
603         }
604
605         return OPERATOR_RUNNING_MODAL;
606 }
607
608 static int edbm_vert_slide_cancel(bContext *C, wmOperator *op)
609 {
610         /* Exit the modal */
611         vtx_slide_exit(C, op);
612
613         return OPERATOR_CANCELLED;
614 }
615
616 static int edbm_vert_slide_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
617 {
618         /* Initialize the operator */
619         if (vtx_slide_init(C, op))
620                 return OPERATOR_RUNNING_MODAL;
621         else
622                 return OPERATOR_CANCELLED;
623 }
624
625 /* Vertex Slide */
626 static int edbm_vert_slide_exec(bContext *C, wmOperator *op)
627 {
628         Object *obedit = CTX_data_edit_object(C);
629         BMEditMesh *em = BMEdit_FromObject(obedit);
630         BMesh *bm = em->bm;
631         BMVert *start_vert;
632         BMOperator bmop;
633         BMEditSelection *ese = em->bm->selected.last;
634
635         float distance_t = 0.0f;
636
637         /* Invoked modally? */
638         if (op->type->modal == edbm_vert_slide_modal && op->customdata) {
639                 VertexSlideOp *vso = op->customdata;
640
641                 if (bm->totedgesel > 1) {
642                         /* Reset selections */
643                         EDBM_flag_disable_all(em, BM_ELEM_SELECT);
644                         BM_edge_select_set(bm, vso->sel_edge, TRUE);
645                         BM_vert_select_set(bm, vso->start_vtx, TRUE);
646
647                         EDBM_editselection_store(em, &vso->sel_edge->head);
648                         EDBM_editselection_store(em, &vso->start_vtx->head);                    
649                         ese = em->bm->selected.last;
650                 }
651                 distance_t = vso->distance;
652                 RNA_float_set(op->ptr, "distance_t", distance_t);
653         }
654         else {
655                 /* Get Properties */
656                 distance_t = RNA_float_get(op->ptr, "distance_t");
657         }
658
659         /* Is there a starting vertex  ? */
660         if ((ese == NULL) || (ese->htype != BM_VERT && ese->htype != BM_EDGE)) {
661                 BKE_report(op->reports, RPT_ERROR_INVALID_INPUT, "Vertex Slide Error: Select a (single) vertex");
662                 return OPERATOR_CANCELLED;
663         }
664
665         start_vert = (BMVert *)ese->ele;
666
667         /* Prepare operator */
668         if (!EDBM_op_init(em, &bmop, op, "vertslide vert=%e edge=%hev distance_t=%f", start_vert, BM_ELEM_SELECT, distance_t))  {
669                 return OPERATOR_CANCELLED;
670         }
671         /* Execute operator */
672         BMO_op_exec(bm, &bmop);
673
674         /* Select the edge */
675         BMO_slot_buffer_hflag_enable(bm, &bmop, "vertout", BM_ALL, BM_ELEM_SELECT, TRUE);
676
677         /* Flush the select buffers */
678         EDBM_selectmode_flush(em);
679
680         if (!EDBM_op_finish(em, &bmop, op, TRUE)) {
681                 return OPERATOR_CANCELLED;
682         }
683
684         /* Update Geometry */
685         EDBM_update_generic(C, em, TRUE);
686
687         return OPERATOR_FINISHED;
688 }
689
690 void MESH_OT_vert_slide(wmOperatorType *ot)
691 {
692         PropertyRNA *prop;
693
694         /* identifiers */
695         ot->name = "Vertex Slide";
696         ot->idname = "MESH_OT_vert_slide";
697         ot->description = "Vertex slide";
698
699         /* api callback */
700         ot->invoke = edbm_vert_slide_invoke;
701         ot->modal = edbm_vert_slide_modal;
702         ot->cancel = edbm_vert_slide_cancel;
703         ot->poll = ED_operator_editmesh_region_view3d;
704
705         /* ot->exec = edbm_vert_slide_exec;
706          * ot->poll = ED_operator_editmesh; */
707
708         /* flags */
709         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
710
711         /* Properties for vertex slide */
712         prop = RNA_def_float(ot->srna, "distance_t", 0.0f, -FLT_MAX, FLT_MAX, "Distance", "Distance", -5.0f, 5.0f);
713         RNA_def_property_ui_range(prop, -5.0f, 5.0f, 0.1, 4);
714         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
715 }