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