GPencil: Split Curve geometry functions to new file
[blender.git] / source / blender / blenkernel / intern / gpencil_curve.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) 2008, Blender Foundation
17  * This is a new part of Blender
18  */
19
20 /** \file
21  * \ingroup bke
22  */
23
24 #include <math.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "CLG_log.h"
31
32 #include "MEM_guardedalloc.h"
33
34 #include "BLI_blenlib.h"
35 #include "BLI_math_vector.h"
36
37 #include "BLT_translation.h"
38
39 #include "DNA_gpencil_types.h"
40
41 #include "BKE_collection.h"
42 #include "BKE_curve.h"
43 #include "BKE_gpencil.h"
44 #include "BKE_gpencil_geom.h"
45 #include "BKE_main.h"
46 #include "BKE_material.h"
47 #include "BKE_object.h"
48
49 #include "DEG_depsgraph_query.h"
50
51 /* Helper: Check materials with same color. */
52 static int gpencil_check_same_material_color(Object *ob_gp, float color[4], Material **r_mat)
53 {
54   Material *ma = NULL;
55   float color_cu[4];
56   linearrgb_to_srgb_v3_v3(color_cu, color);
57   float hsv1[4];
58   rgb_to_hsv_v(color_cu, hsv1);
59   hsv1[3] = color[3];
60
61   for (int i = 1; i <= ob_gp->totcol; i++) {
62     ma = BKE_object_material_get(ob_gp, i);
63     MaterialGPencilStyle *gp_style = ma->gp_style;
64     /* Check color with small tolerance (better in HSV). */
65     float hsv2[4];
66     rgb_to_hsv_v(gp_style->fill_rgba, hsv2);
67     hsv2[3] = gp_style->fill_rgba[3];
68     if ((gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID) &&
69         (compare_v4v4(hsv1, hsv2, 0.01f))) {
70       *r_mat = ma;
71       return i - 1;
72     }
73   }
74
75   *r_mat = NULL;
76   return -1;
77 }
78
79 /* Helper: Add gpencil material using curve material as base. */
80 static Material *gpencil_add_from_curve_material(Main *bmain,
81                                                  Object *ob_gp,
82                                                  const float cu_color[4],
83                                                  const bool gpencil_lines,
84                                                  const bool fill,
85                                                  int *r_idx)
86 {
87   Material *mat_gp = BKE_gpencil_object_material_new(
88       bmain, ob_gp, (fill) ? "Material" : "Unassigned", r_idx);
89   MaterialGPencilStyle *gp_style = mat_gp->gp_style;
90
91   /* Stroke color. */
92   if (gpencil_lines) {
93     ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f);
94     gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
95   }
96   else {
97     linearrgb_to_srgb_v4(gp_style->stroke_rgba, cu_color);
98     gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
99   }
100
101   /* Fill color. */
102   linearrgb_to_srgb_v4(gp_style->fill_rgba, cu_color);
103   /* Fill is false if the original curve hasn't material assigned, so enable it. */
104   if (fill) {
105     gp_style->flag |= GP_MATERIAL_FILL_SHOW;
106   }
107
108   /* Check at least one is enabled. */
109   if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) &&
110       ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) {
111     gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
112   }
113
114   return mat_gp;
115 }
116
117 /* Helper: Create new stroke section. */
118 static void gpencil_add_new_points(bGPDstroke *gps,
119                                    float *coord_array,
120                                    float pressure,
121                                    int init,
122                                    int totpoints,
123                                    const float init_co[3],
124                                    bool last)
125 {
126   for (int i = 0; i < totpoints; i++) {
127     bGPDspoint *pt = &gps->points[i + init];
128     copy_v3_v3(&pt->x, &coord_array[3 * i]);
129     /* Be sure the last point is not on top of the first point of the curve or
130      * the close of the stroke will produce glitches. */
131     if ((last) && (i > 0) && (i == totpoints - 1)) {
132       float dist = len_v3v3(init_co, &pt->x);
133       if (dist < 0.1f) {
134         /* Interpolate between previous point and current to back slightly. */
135         bGPDspoint *pt_prev = &gps->points[i + init - 1];
136         interp_v3_v3v3(&pt->x, &pt_prev->x, &pt->x, 0.95f);
137       }
138     }
139
140     pt->pressure = pressure;
141     pt->strength = 1.0f;
142   }
143 }
144
145 /* Helper: Get the first collection that includes the object. */
146 static Collection *gpencil_get_parent_collection(Scene *scene, Object *ob)
147 {
148   Collection *mycol = NULL;
149   FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
150     LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
151       if ((mycol == NULL) && (cob->ob == ob)) {
152         mycol = collection;
153       }
154     }
155   }
156   FOREACH_SCENE_COLLECTION_END;
157
158   return mycol;
159 }
160
161 /* Helper: Convert one spline to grease pencil stroke. */
162 static void gpencil_convert_spline(Main *bmain,
163                                    Object *ob_gp,
164                                    Object *ob_cu,
165                                    const bool gpencil_lines,
166                                    const bool only_stroke,
167                                    bGPDframe *gpf,
168                                    Nurb *nu)
169 {
170   Curve *cu = (Curve *)ob_cu->data;
171   bool cyclic = true;
172
173   /* Create Stroke. */
174   bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke");
175   gps->thickness = 10.0f;
176   gps->fill_opacity_fac = 1.0f;
177   gps->hardeness = 1.0f;
178   gps->uv_scale = 1.0f;
179
180   ARRAY_SET_ITEMS(gps->aspect_ratio, 1.0f, 1.0f);
181   ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND);
182   gps->inittime = 0.0f;
183
184   gps->flag &= ~GP_STROKE_SELECT;
185   gps->flag |= GP_STROKE_3DSPACE;
186
187   gps->mat_nr = 0;
188   /* Count total points
189    * The total of points must consider that last point of each segment is equal to the first
190    * point of next segment.
191    */
192   int totpoints = 0;
193   int segments = 0;
194   int resolu = nu->resolu + 1;
195   segments = nu->pntsu;
196   if ((nu->flagu & CU_NURB_CYCLIC) == 0) {
197     segments--;
198     cyclic = false;
199   }
200   totpoints = (resolu * segments) - (segments - 1);
201
202   /* Materials
203    * Notice: The color of the material is the color of viewport and not the final shader color.
204    */
205   Material *mat_gp = NULL;
206   bool fill = true;
207   /* Check if grease pencil has a material with same color.*/
208   float color[4];
209   if ((cu->mat) && (*cu->mat)) {
210     Material *mat_cu = *cu->mat;
211     copy_v4_v4(color, &mat_cu->r);
212   }
213   else {
214     /* Gray (unassigned from SVG add-on) */
215     zero_v4(color);
216     add_v3_fl(color, 0.6f);
217     color[3] = 1.0f;
218     fill = false;
219   }
220
221   /* Special case: If the color was created by the SVG add-on and the name contains '_stroke' and
222    * there is only one color, the stroke must not be closed, fill to false and use for
223    * stroke the fill color.
224    */
225   bool do_stroke = false;
226   if (ob_cu->totcol == 1) {
227     Material *ma_stroke = BKE_object_material_get(ob_cu, 1);
228     if ((ma_stroke) && (strstr(ma_stroke->id.name, "_stroke") != NULL)) {
229       do_stroke = true;
230     }
231   }
232
233   int r_idx = gpencil_check_same_material_color(ob_gp, color, &mat_gp);
234   if ((ob_cu->totcol > 0) && (r_idx < 0)) {
235     Material *mat_curve = BKE_object_material_get(ob_cu, 1);
236     mat_gp = gpencil_add_from_curve_material(bmain, ob_gp, color, gpencil_lines, fill, &r_idx);
237
238     if ((mat_curve) && (mat_curve->gp_style != NULL)) {
239       MaterialGPencilStyle *gp_style_cur = mat_curve->gp_style;
240       MaterialGPencilStyle *gp_style_gp = mat_gp->gp_style;
241
242       copy_v4_v4(gp_style_gp->mix_rgba, gp_style_cur->mix_rgba);
243       gp_style_gp->fill_style = gp_style_cur->fill_style;
244       gp_style_gp->mix_factor = gp_style_cur->mix_factor;
245     }
246
247     /* If object has more than 1 material, use second material for stroke color. */
248     if ((!only_stroke) && (ob_cu->totcol > 1) && (BKE_object_material_get(ob_cu, 2))) {
249       mat_curve = BKE_object_material_get(ob_cu, 2);
250       if (mat_curve) {
251         linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
252         mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
253       }
254     }
255     else if ((only_stroke) || (do_stroke)) {
256       /* Also use the first color if the fill is none for stroke color. */
257       if (ob_cu->totcol > 0) {
258         mat_curve = BKE_object_material_get(ob_cu, 1);
259         if (mat_curve) {
260           copy_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
261           mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
262           /* Set fill and stroke depending of curve type (3D or 2D). */
263           if ((cu->flag & CU_3D) || ((cu->flag & (CU_FRONT | CU_BACK)) == 0)) {
264             mat_gp->gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
265             mat_gp->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
266           }
267           else {
268             mat_gp->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
269             mat_gp->gp_style->flag |= GP_MATERIAL_FILL_SHOW;
270           }
271         }
272       }
273     }
274   }
275   CLAMP_MIN(r_idx, 0);
276
277   /* Assign material index to stroke. */
278   gps->mat_nr = r_idx;
279
280   /* Add stroke to frame.*/
281   BLI_addtail(&gpf->strokes, gps);
282
283   float *coord_array = NULL;
284   float init_co[3];
285
286   switch (nu->type) {
287     case CU_POLY: {
288       /* Allocate memory for storage points. */
289       gps->totpoints = nu->pntsu;
290       gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
291       /* Increase thickness for this type. */
292       gps->thickness = 10.0f;
293
294       /* Get all curve points */
295       for (int s = 0; s < gps->totpoints; s++) {
296         BPoint *bp = &nu->bp[s];
297         bGPDspoint *pt = &gps->points[s];
298         copy_v3_v3(&pt->x, bp->vec);
299         pt->pressure = bp->radius;
300         pt->strength = 1.0f;
301       }
302       break;
303     }
304     case CU_BEZIER: {
305       /* Allocate memory for storage points. */
306       gps->totpoints = totpoints;
307       gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
308
309       int init = 0;
310       resolu = nu->resolu + 1;
311       segments = nu->pntsu;
312       if ((nu->flagu & CU_NURB_CYCLIC) == 0) {
313         segments--;
314       }
315       /* Get all interpolated curve points of Beziert */
316       for (int s = 0; s < segments; s++) {
317         int inext = (s + 1) % nu->pntsu;
318         BezTriple *prevbezt = &nu->bezt[s];
319         BezTriple *bezt = &nu->bezt[inext];
320         bool last = (bool)(s == segments - 1);
321
322         coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__);
323
324         for (int j = 0; j < 3; j++) {
325           BKE_curve_forward_diff_bezier(prevbezt->vec[1][j],
326                                         prevbezt->vec[2][j],
327                                         bezt->vec[0][j],
328                                         bezt->vec[1][j],
329                                         coord_array + j,
330                                         resolu - 1,
331                                         3 * sizeof(float));
332         }
333         /* Save first point coordinates. */
334         if (s == 0) {
335           copy_v3_v3(init_co, &coord_array[0]);
336         }
337         /* Add points to the stroke */
338         gpencil_add_new_points(gps, coord_array, bezt->radius, init, resolu, init_co, last);
339         /* Free memory. */
340         MEM_SAFE_FREE(coord_array);
341
342         /* As the last point of segment is the first point of next segment, back one array
343          * element to avoid duplicated points on the same location.
344          */
345         init += resolu - 1;
346       }
347       break;
348     }
349     case CU_NURBS: {
350       if (nu->pntsv == 1) {
351
352         int nurb_points;
353         if (nu->flagu & CU_NURB_CYCLIC) {
354           resolu++;
355           nurb_points = nu->pntsu * resolu;
356         }
357         else {
358           nurb_points = (nu->pntsu - 1) * resolu;
359         }
360         /* Get all curve points. */
361         coord_array = MEM_callocN(sizeof(float[3]) * nurb_points, __func__);
362         BKE_nurb_makeCurve(nu, coord_array, NULL, NULL, NULL, resolu, sizeof(float[3]));
363
364         /* Allocate memory for storage points. */
365         gps->totpoints = nurb_points - 1;
366         gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
367
368         /* Add points. */
369         gpencil_add_new_points(gps, coord_array, 1.0f, 0, gps->totpoints, init_co, false);
370
371         MEM_SAFE_FREE(coord_array);
372       }
373       break;
374     }
375     default: {
376       break;
377     }
378   }
379   /* Cyclic curve, close stroke. */
380   if ((cyclic) && (!do_stroke)) {
381     BKE_gpencil_stroke_close(gps);
382   }
383
384   /* Recalc fill geometry. */
385   BKE_gpencil_stroke_geometry_update(gps);
386 }
387
388 /* Convert a curve object to grease pencil stroke.
389  *
390  * \param bmain: Main thread pointer
391  * \param scene: Original scene.
392  * \param ob_gp: Grease pencil object to add strokes.
393  * \param ob_cu: Curve to convert.
394  * \param gpencil_lines: Use lines for strokes.
395  * \param use_collections: Create layers using collection names.
396  * \param only_stroke: The material must be only stroke without fill.
397  */
398 void BKE_gpencil_convert_curve(Main *bmain,
399                                Scene *scene,
400                                Object *ob_gp,
401                                Object *ob_cu,
402                                const bool gpencil_lines,
403                                const bool use_collections,
404                                const bool only_stroke)
405 {
406   if (ELEM(NULL, ob_gp, ob_cu) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) {
407     return;
408   }
409
410   Curve *cu = (Curve *)ob_cu->data;
411   bGPdata *gpd = (bGPdata *)ob_gp->data;
412   bGPDlayer *gpl = NULL;
413
414   /* If the curve is empty, cancel. */
415   if (cu->nurb.first == NULL) {
416     return;
417   }
418
419   /* Check if there is an active layer. */
420   if (use_collections) {
421     Collection *collection = gpencil_get_parent_collection(scene, ob_cu);
422     if (collection != NULL) {
423       gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2);
424       if (gpl == NULL) {
425         gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true);
426       }
427     }
428   }
429
430   if (gpl == NULL) {
431     gpl = BKE_gpencil_layer_active_get(gpd);
432     if (gpl == NULL) {
433       gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
434     }
435   }
436
437   /* Check if there is an active frame and add if needed. */
438   bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_COPY);
439
440   /* Read all splines of the curve and create a stroke for each. */
441   LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
442     gpencil_convert_spline(bmain, ob_gp, ob_cu, gpencil_lines, only_stroke, gpf, nu);
443   }
444
445   /* Tag for recalculation */
446   DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
447 }
448
449 /** \} */