Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / gpencil / gpencil_merge.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  * The Original Code is Copyright (C) 2019, Blender Foundation.
17  * This is a new part of Blender
18  * Operators for merge Grease Pencil strokes
19  */
20
21 /** \file
22  * \ingroup edgpencil
23  */
24
25 #include <stdio.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_blenlib.h"
30 #include "BLI_ghash.h"
31 #include "BLI_math.h"
32
33 #include "DNA_gpencil_types.h"
34
35 #include "BKE_brush.h"
36 #include "BKE_context.h"
37 #include "BKE_gpencil.h"
38 #include "BKE_material.h"
39
40 #include "WM_api.h"
41 #include "WM_types.h"
42
43 #include "RNA_access.h"
44 #include "RNA_define.h"
45
46 #include "ED_gpencil.h"
47 #include "ED_object.h"
48 #include "ED_screen.h"
49 #include "ED_view3d.h"
50
51 #include "DEG_depsgraph.h"
52 #include "DEG_depsgraph_query.h"
53
54 #include "gpencil_intern.h"
55
56 typedef struct tGPencilPointCache {
57   float factor; /* value to sort */
58   bGPDstroke *gps;
59   float x, y, z;
60   float pressure;
61   float strength;
62 } tGPencilPointCache;
63
64 /* helper function to sort points */
65 static int gpencil_sort_points(const void *a1, const void *a2)
66 {
67   const tGPencilPointCache *ps1 = a1, *ps2 = a2;
68
69   if (ps1->factor < ps2->factor) {
70     return -1;
71   }
72   else if (ps1->factor > ps2->factor) {
73     return 1;
74   }
75
76   return 0;
77 }
78
79 static void gpencil_insert_points_to_stroke(bGPDstroke *gps,
80                                             tGPencilPointCache *points_array,
81                                             int totpoints)
82 {
83   tGPencilPointCache *point_elem = NULL;
84
85   for (int i = 0; i < totpoints; i++) {
86     point_elem = &points_array[i];
87     bGPDspoint *pt_dst = &gps->points[i];
88
89     copy_v3_v3(&pt_dst->x, &point_elem->x);
90     pt_dst->pressure = point_elem->pressure;
91     pt_dst->strength = point_elem->strength;
92     pt_dst->uv_fac = 1.0f;
93     pt_dst->uv_rot = 0;
94     pt_dst->flag |= GP_SPOINT_SELECT;
95   }
96 }
97
98 static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpoints)
99 {
100   ToolSettings *ts = CTX_data_tool_settings(C);
101   Depsgraph *depsgraph = CTX_data_depsgraph(C);
102   Object *ob = CTX_data_active_object(C);
103   bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
104
105   int cfra_eval = (int)DEG_get_ctime(depsgraph);
106
107   const bool back = RNA_boolean_get(op->ptr, "back");
108   const bool additive = RNA_boolean_get(op->ptr, "additive");
109   const bool cyclic = RNA_boolean_get(op->ptr, "cyclic");
110
111   Paint *paint = &ts->gp_paint->paint;
112   /* if not exist, create a new one */
113   if (paint->brush == NULL) {
114     /* create new brushes */
115     BKE_brush_gpencil_presets(C);
116   }
117   Brush *brush = paint->brush;
118
119   /* frame */
120   short add_frame_mode;
121   if (additive) {
122     add_frame_mode = GP_GETFRAME_ADD_COPY;
123   }
124   else {
125     add_frame_mode = GP_GETFRAME_ADD_NEW;
126   }
127   bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, add_frame_mode);
128
129   /* stroke */
130   bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
131   gps->totpoints = totpoints;
132   gps->inittime = 0.0f;
133   gps->thickness = brush->size;
134   gps->gradient_f = brush->gpencil_settings->gradient_f;
135   copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s);
136   gps->flag |= GP_STROKE_SELECT;
137   gps->flag |= GP_STROKE_3DSPACE;
138   gps->mat_nr = ob->actcol - 1;
139
140   /* allocate memory for points */
141   gps->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, "gp_stroke_points");
142   /* initialize triangle memory to dummy data */
143   gps->tot_triangles = 0;
144   gps->triangles = NULL;
145   gps->flag |= GP_STROKE_RECALC_GEOMETRY;
146
147   if (cyclic) {
148     gps->flag |= GP_STROKE_CYCLIC;
149   }
150
151   /* add new stroke to frame */
152   if (back) {
153     BLI_addhead(&gpf->strokes, gps);
154   }
155   else {
156     BLI_addtail(&gpf->strokes, gps);
157   }
158
159   return gps;
160 }
161
162 static void gpencil_get_elements_len(bContext *C, int *totstrokes, int *totpoints)
163 {
164   bGPDspoint *pt;
165   int i;
166
167   /* count number of strokes and selected points */
168   CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
169     if (gps->flag & GP_STROKE_SELECT) {
170       *totstrokes += 1;
171       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
172         if (pt->flag & GP_SPOINT_SELECT) {
173           *totpoints += 1;
174         }
175       }
176     }
177   }
178   CTX_DATA_END;
179 }
180
181 static void gpencil_dissolve_points(bContext *C)
182 {
183   bGPDstroke *gps, *gpsn;
184
185   CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
186     bGPDframe *gpf = gpl->actframe;
187     if (gpf == NULL) {
188       continue;
189     }
190
191     for (gps = gpf->strokes.first; gps; gps = gpsn) {
192       gpsn = gps->next;
193       gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false, 0);
194     }
195   }
196   CTX_DATA_END;
197 }
198
199 /* Calc a factor of each selected point and fill an array with all the data.
200  *
201  * The factor is calculated using an imaginary circle, using the angle relative
202  * to this circle and the distance to the calculated center of the selected points.
203  *
204  * All the data is saved to be sorted and used later.
205  */
206 static void gpencil_calc_points_factor(bContext *C,
207                                        const int mode,
208                                        int totpoints,
209                                        const bool clear_point,
210                                        const bool clear_stroke,
211                                        tGPencilPointCache *src_array)
212 {
213   bGPDspoint *pt;
214   int i;
215   int idx = 0;
216
217   /* create selected point array an fill it */
218   bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * totpoints, __func__);
219   bGPDspoint *pt_array = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
220
221   CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
222     bGPDframe *gpf = gpl->actframe;
223     if (gpf == NULL) {
224       continue;
225     }
226     for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
227       if (gps->flag & GP_STROKE_SELECT) {
228         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
229           if (clear_stroke) {
230             pt->flag |= GP_SPOINT_TAG;
231           }
232           else {
233             pt->flag &= ~GP_SPOINT_TAG;
234           }
235
236           if (pt->flag & GP_SPOINT_SELECT) {
237             bGPDspoint *pt2 = &pt_array[idx];
238             copy_v3_v3(&pt2->x, &pt->x);
239             pt2->pressure = pt->pressure;
240             pt2->strength = pt->strength;
241             pt->flag &= ~GP_SPOINT_SELECT;
242             if (clear_point) {
243               pt->flag |= GP_SPOINT_TAG;
244             }
245
246             /* save stroke */
247             gps_array[idx] = gps;
248
249             idx++;
250           }
251         }
252         gps->flag &= ~GP_STROKE_SELECT;
253       }
254     }
255   }
256   CTX_DATA_END;
257
258   /* project in 2d plane */
259   int direction = 0;
260   float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * totpoints, "GP Stroke temp 2d points");
261   BKE_gpencil_stroke_2d_flat(pt_array, totpoints, points2d, &direction);
262
263   /* calc center */
264   float center[2] = {0.0f, 0.0f};
265   for (i = 0; i < totpoints; i++) {
266     center[0] += points2d[i][0];
267     center[1] += points2d[i][1];
268   }
269   mul_v2_fl(center, 1.0f / totpoints);
270
271   /* calc angle and distance to center for each point */
272   const float axis[2] = {1.0f, 0.0f};
273   float v1[3];
274   for (i = 0; i < totpoints; i++) {
275     float ln = len_v2v2(center, points2d[i]);
276     sub_v2_v2v2(v1, points2d[i], center);
277     float angle = angle_signed_v2v2(axis, v1);
278     if (angle < 0.0f) {
279       angle = fabsf(angle);
280     }
281     else {
282       angle = (M_PI * 2.0) - angle;
283     }
284     tGPencilPointCache *sort_pt = &src_array[i];
285     bGPDspoint *pt2 = &pt_array[i];
286
287     copy_v3_v3(&sort_pt->x, &pt2->x);
288     sort_pt->pressure = pt2->pressure;
289     sort_pt->strength = pt2->strength;
290
291     sort_pt->gps = gps_array[i];
292
293     if (mode == GP_MERGE_STROKE) {
294       sort_pt->factor = angle;
295     }
296     else {
297       sort_pt->factor = (angle * 100000.0f) + ln;
298     }
299   }
300   MEM_SAFE_FREE(points2d);
301   MEM_SAFE_FREE(gps_array);
302   MEM_SAFE_FREE(pt_array);
303 }
304
305 /* insert a group of points in destination array */
306 static int gpencil_insert_to_array(tGPencilPointCache *src_array,
307                                    tGPencilPointCache *dst_array,
308                                    int totpoints,
309                                    bGPDstroke *gps_filter,
310                                    bool reverse,
311                                    int last)
312 {
313   tGPencilPointCache *src_elem = NULL;
314   tGPencilPointCache *dst_elem = NULL;
315   int idx = 0;
316
317   for (int i = 0; i < totpoints; i++) {
318     if (!reverse) {
319       idx = i;
320     }
321     else {
322       idx = totpoints - i - 1;
323     }
324     src_elem = &src_array[idx];
325     /* check if all points or only a stroke */
326     if ((gps_filter != NULL) && (gps_filter != src_elem->gps)) {
327       continue;
328     }
329
330     dst_elem = &dst_array[last];
331     last++;
332
333     copy_v3_v3(&dst_elem->x, &src_elem->x);
334     dst_elem->gps = src_elem->gps;
335     dst_elem->pressure = src_elem->pressure;
336     dst_elem->strength = src_elem->strength;
337     dst_elem->factor = src_elem->factor;
338   }
339
340   return last;
341 }
342
343 /* get first and last point location */
344 static void gpencil_get_extremes(
345     tGPencilPointCache *src_array, int totpoints, bGPDstroke *gps_filter, float *start, float *end)
346 {
347   tGPencilPointCache *array_pt = NULL;
348   int i;
349
350   /* find first point */
351   for (i = 0; i < totpoints; i++) {
352     array_pt = &src_array[i];
353     if (gps_filter == array_pt->gps) {
354       copy_v3_v3(start, &array_pt->x);
355       break;
356     }
357   }
358   /* find last point */
359   for (i = totpoints - 1; i >= 0; i--) {
360     array_pt = &src_array[i];
361     if (gps_filter == array_pt->gps) {
362       copy_v3_v3(end, &array_pt->x);
363       break;
364     }
365   }
366 }
367
368 static int gpencil_analyze_strokes(tGPencilPointCache *src_array,
369                                    int totstrokes,
370                                    int totpoints,
371                                    tGPencilPointCache *dst_array)
372 {
373   int i;
374   int last = 0;
375   GHash *all_strokes = BLI_ghash_ptr_new(__func__);
376   /* add first stroke to array */
377   tGPencilPointCache *sort_pt = &src_array[0];
378   bGPDstroke *gps = sort_pt->gps;
379   last = gpencil_insert_to_array(src_array, dst_array, totpoints, gps, false, last);
380   float start[3];
381   float end[3];
382   float end_prv[3];
383   gpencil_get_extremes(src_array, totpoints, gps, start, end);
384   copy_v3_v3(end_prv, end);
385   BLI_ghash_insert(all_strokes, sort_pt->gps, sort_pt->gps);
386
387   /* look for near stroke */
388   bool loop = (bool)(totstrokes > 1);
389   while (loop) {
390     bGPDstroke *gps_next = NULL;
391     GHash *strokes = BLI_ghash_ptr_new(__func__);
392     float dist_start = 0.0f;
393     float dist_end = 0.0f;
394     float dist = FLT_MAX;
395     bool reverse = false;
396
397     for (i = 0; i < totpoints; i++) {
398       sort_pt = &src_array[i];
399       /* avoid dups */
400       if (BLI_ghash_haskey(all_strokes, sort_pt->gps)) {
401         continue;
402       }
403       if (!BLI_ghash_haskey(strokes, sort_pt->gps)) {
404         gpencil_get_extremes(src_array, totpoints, sort_pt->gps, start, end);
405         /* distances to previous end */
406         dist_start = len_v3v3(end_prv, start);
407         dist_end = len_v3v3(end_prv, end);
408
409         if (dist > dist_start) {
410           gps_next = sort_pt->gps;
411           dist = dist_start;
412           reverse = false;
413         }
414         if (dist > dist_end) {
415           gps_next = sort_pt->gps;
416           dist = dist_end;
417           reverse = true;
418         }
419         BLI_ghash_insert(strokes, sort_pt->gps, sort_pt->gps);
420       }
421     }
422     BLI_ghash_free(strokes, NULL, NULL);
423
424     /* add the stroke to array */
425     if (gps->next != NULL) {
426       BLI_ghash_insert(all_strokes, gps_next, gps_next);
427       last = gpencil_insert_to_array(src_array, dst_array, totpoints, gps_next, reverse, last);
428       /* replace last end */
429       sort_pt = &dst_array[last - 1];
430       copy_v3_v3(end_prv, &sort_pt->x);
431     }
432
433     /* loop exit */
434     if (last >= totpoints) {
435       loop = false;
436     }
437   }
438
439   BLI_ghash_free(all_strokes, NULL, NULL);
440   return last;
441 }
442
443 static bool gp_strokes_merge_poll(bContext *C)
444 {
445   /* only supported with grease pencil objects */
446   Object *ob = CTX_data_active_object(C);
447   if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
448     return false;
449   }
450
451   /* check material */
452   Material *ma = NULL;
453   ma = give_current_material(ob, ob->actcol);
454   if ((ma == NULL) || (ma->gp_style == NULL)) {
455     return false;
456   }
457
458   /* check hidden or locked materials */
459   MaterialGPencilStyle *gp_style = ma->gp_style;
460   if ((gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED)) {
461     return false;
462   }
463
464   /* check layer */
465   bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
466   if ((gpl == NULL) || (gpl->flag & GP_LAYER_LOCKED) || (gpl->flag & GP_LAYER_HIDE)) {
467     return false;
468   }
469
470   /* NOTE: this is a bit slower, but is the most accurate... */
471   return (CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0) && ED_operator_view3d_active(C);
472 }
473
474 static int gp_stroke_merge_exec(bContext *C, wmOperator *op)
475 {
476   const int mode = RNA_enum_get(op->ptr, "mode");
477   const bool clear_point = RNA_boolean_get(op->ptr, "clear_point");
478   const bool clear_stroke = RNA_boolean_get(op->ptr, "clear_stroke");
479
480   Object *ob = CTX_data_active_object(C);
481   /* sanity checks */
482   if (!ob || ob->type != OB_GPENCIL) {
483     return OPERATOR_CANCELLED;
484   }
485
486   bGPdata *gpd = (bGPdata *)ob->data;
487   bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
488   if (gpl == NULL) {
489     return OPERATOR_CANCELLED;
490   }
491
492   int totstrokes = 0;
493   int totpoints = 0;
494
495   /* count number of strokes and selected points */
496   gpencil_get_elements_len(C, &totstrokes, &totpoints);
497
498   if (totpoints == 0) {
499     return OPERATOR_CANCELLED;
500   }
501
502   /* calc factor of each point and fill an array with all data */
503   tGPencilPointCache *sorted_array = NULL;
504   tGPencilPointCache *original_array = MEM_callocN(sizeof(tGPencilPointCache) * totpoints,
505                                                    __func__);
506   gpencil_calc_points_factor(C, mode, totpoints, clear_point, clear_stroke, original_array);
507
508   /* for strokes analyze strokes and load sorted array */
509   if (mode == GP_MERGE_STROKE) {
510     sorted_array = MEM_callocN(sizeof(tGPencilPointCache) * totpoints, __func__);
511     totpoints = gpencil_analyze_strokes(original_array, totstrokes, totpoints, sorted_array);
512   }
513   else {
514     /* make a copy to sort */
515     sorted_array = MEM_dupallocN(original_array);
516     /* sort by factor around center */
517     qsort(sorted_array, totpoints, sizeof(tGPencilPointCache), gpencil_sort_points);
518   }
519
520   /* prepare the new stroke */
521   bGPDstroke *gps = gpencil_prepare_stroke(C, op, totpoints);
522
523   /* copy original points to final stroke */
524   gpencil_insert_points_to_stroke(gps, sorted_array, totpoints);
525
526   /* dissolve all tagged points */
527   if ((clear_point) || (clear_stroke)) {
528     gpencil_dissolve_points(C);
529   }
530
531   /* free memory */
532   MEM_SAFE_FREE(original_array);
533   MEM_SAFE_FREE(sorted_array);
534
535   /* notifiers */
536   DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
537   WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
538
539   return OPERATOR_FINISHED;
540 }
541
542 void GPENCIL_OT_stroke_merge(wmOperatorType *ot)
543 {
544   static const EnumPropertyItem mode_type[] = {
545       {GP_MERGE_STROKE, "STROKE", 0, "Stroke", ""},
546       {GP_MERGE_POINT, "POINT", 0, "Point", ""},
547       {0, NULL, 0, NULL, NULL},
548   };
549
550   /* identifiers */
551   ot->name = "Merge Strokes";
552   ot->idname = "GPENCIL_OT_stroke_merge";
553   ot->description = "Create a new stroke with the selected stroke points";
554
555   /* api callbacks */
556   ot->exec = gp_stroke_merge_exec;
557   ot->poll = gp_strokes_merge_poll;
558
559   /* flags */
560   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
561
562   /* properties */
563   ot->prop = RNA_def_enum(ot->srna, "mode", mode_type, GP_MERGE_STROKE, "Mode", "");
564   RNA_def_boolean(
565       ot->srna, "back", 0, "Draw on Back", "Draw new stroke below all previous strokes");
566   RNA_def_boolean(ot->srna, "additive", 0, "Additive Drawing", "Add to previous drawing");
567   RNA_def_boolean(ot->srna, "cyclic", 0, "Cyclic", "Close new stroke");
568   RNA_def_boolean(ot->srna, "clear_point", 0, "Dissolve Points", "Dissolve old selected points");
569   RNA_def_boolean(ot->srna, "clear_stroke", 0, "Delete Strokes", "Delete old selected strokes");
570 }