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