diff options
author | Falk David <falkdavid@gmx.de> | 2020-11-13 23:43:00 +0300 |
---|---|---|
committer | Falk David <falkdavid@gmx.de> | 2020-11-13 23:43:00 +0300 |
commit | 0be88c7d15d2ad1af284c6283370173647ae74eb (patch) | |
tree | 5fff573c512e284547ebe0c921ecffdae2c377c4 /source/blender/blenkernel | |
parent | 9d28353b525ecfbcca1501be72e4276dfb2bbc2a (diff) |
GPencil: Merge GSoC curve edit mode
Differential Revision: https://developer.blender.org/D8660
This patch is the result of the GSoC 2020 "Editing Grease Pencil Strokes
Using Curves" project. It adds a submode to greasepencil edit mode that
allows for the transformation of greasepencil strokes using bezier
curves. More information about the project can be found
here: https://wiki.blender.org/wiki/User:Filedescriptor/GSoC_2020.
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil.h | 11 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil_curve.h | 21 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil_geom.h | 31 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil.c | 112 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_curve.c | 874 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_geom.c | 84 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_modifier.c | 8 |
7 files changed, 1095 insertions, 46 deletions
diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 7d50599a8f0..6dc8d1ef06e 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -48,6 +48,7 @@ struct bGPDlayer; struct bGPDlayer_Mask; struct bGPDspoint; struct bGPDstroke; +struct bGPDcurve; struct bGPdata; #define GPENCIL_SIMPLIFY(scene) ((scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ENABLE)) @@ -89,6 +90,7 @@ struct bGPdata; void BKE_gpencil_free_point_weights(struct MDeformVert *dvert); void BKE_gpencil_free_stroke_weights(struct bGPDstroke *gps); +void BKE_gpencil_free_stroke_editcurve(struct bGPDstroke *gps); void BKE_gpencil_free_stroke(struct bGPDstroke *gps); bool BKE_gpencil_free_strokes(struct bGPDframe *gpf); void BKE_gpencil_free_frames(struct bGPDlayer *gpl); @@ -102,6 +104,7 @@ void BKE_gpencil_batch_cache_dirty_tag(struct bGPdata *gpd); void BKE_gpencil_batch_cache_free(struct bGPdata *gpd); void BKE_gpencil_stroke_sync_selection(struct bGPDstroke *gps); +void BKE_gpencil_curve_sync_selection(struct bGPDstroke *gps); struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); @@ -111,7 +114,10 @@ struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]); struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src); struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src); void BKE_gpencil_frame_copy_strokes(struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst); -struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, const bool dup_points); +struct bGPDcurve *BKE_gpencil_stroke_curve_duplicate(struct bGPDcurve *gpc_src); +struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, + const bool dup_points, + const bool dup_curve); struct bGPdata *BKE_gpencil_data_duplicate(struct Main *bmain, const struct bGPdata *gpd, @@ -160,6 +166,8 @@ struct bGPDstroke *BKE_gpencil_stroke_add_existing_style(struct bGPDframe *gpf, int totpoints, short thickness); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points); + /* Stroke and Fill - Alpha Visibility Threshold */ #define GPENCIL_ALPHA_OPACITY_THRESH 0.001f #define GPENCIL_STRENGTH_MIN 0.003f @@ -247,6 +255,7 @@ float BKE_gpencil_multiframe_falloff_calc( void BKE_gpencil_palette_ensure(struct Main *bmain, struct Scene *scene); bool BKE_gpencil_from_image(struct SpaceImage *sima, + struct bGPdata *gpd, struct bGPDframe *gpf, const float size, const bool mask); diff --git a/source/blender/blenkernel/BKE_gpencil_curve.h b/source/blender/blenkernel/BKE_gpencil_curve.h index c61427c6c4a..1821972469c 100644 --- a/source/blender/blenkernel/BKE_gpencil_curve.h +++ b/source/blender/blenkernel/BKE_gpencil_curve.h @@ -30,6 +30,10 @@ extern "C" { struct Main; struct Object; struct Scene; +struct bGPdata; +struct bGPDlayer; +struct bGPDstroke; +struct bGPDcurve; void BKE_gpencil_convert_curve(struct Main *bmain, struct Scene *scene, @@ -39,6 +43,23 @@ void BKE_gpencil_convert_curve(struct Main *bmain, const float scale_thickness, const float sample); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_generate(struct bGPDstroke *gps, + const float error_threshold, + const float corner_angle, + const float stroke_radius); +void BKE_gpencil_stroke_editcurve_update(struct bGPdata *gpd, + struct bGPDlayer *gpl, + struct bGPDstroke *gps); +void BKE_gpencil_editcurve_stroke_sync_selection(struct bGPDstroke *gps, struct bGPDcurve *gpc); +void BKE_gpencil_stroke_editcurve_sync_selection(struct bGPDstroke *gps, struct bGPDcurve *gpc); +void BKE_gpencil_strokes_selected_update_editcurve(struct bGPdata *gpd); +void BKE_gpencil_strokes_selected_sync_selection_editcurve(struct bGPdata *gpd); +void BKE_gpencil_stroke_update_geometry_from_editcurve(struct bGPDstroke *gps, + const uint resolution, + const bool is_adaptive); +void BKE_gpencil_editcurve_recalculate_handles(struct bGPDstroke *gps); +void BKE_gpencil_editcurve_subdivide(struct bGPDstroke *gps, const int cuts); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index 404cbbe1741..6917a62053c 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -51,11 +51,17 @@ void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps); /* stroke geometry utilities */ void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]); -void BKE_gpencil_stroke_simplify_adaptive(struct bGPDstroke *gps, float epsilon); -void BKE_gpencil_stroke_simplify_fixed(struct bGPDstroke *gps); -void BKE_gpencil_stroke_subdivide(struct bGPDstroke *gps, int level, int type); -bool BKE_gpencil_stroke_trim(struct bGPDstroke *gps); -void BKE_gpencil_stroke_merge_distance(struct bGPDframe *gpf, +void BKE_gpencil_stroke_simplify_adaptive(struct bGPdata *gpd, + struct bGPDstroke *gps, + float epsilon); +void BKE_gpencil_stroke_simplify_fixed(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_stroke_subdivide(struct bGPdata *gpd, + struct bGPDstroke *gps, + int level, + int type); +bool BKE_gpencil_stroke_trim(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_stroke_merge_distance(struct bGPdata *gpd, + struct bGPDframe *gpf, struct bGPDstroke *gps, const float threshold, const bool use_unselected); @@ -72,7 +78,7 @@ void BKE_gpencil_stroke_2d_flat_ref(const struct bGPDspoint *ref_points, const float scale, int *r_direction); void BKE_gpencil_stroke_fill_triangulate(struct bGPDstroke *gps); -void BKE_gpencil_stroke_geometry_update(struct bGPDstroke *gps); +void BKE_gpencil_stroke_geometry_update(struct bGPdata *gpd, struct bGPDstroke *gps); void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); void BKE_gpencil_transform(struct bGPdata *gpd, const float mat[4][4]); @@ -91,19 +97,26 @@ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd, const GPencilPointCoordinates *elem_data, const float mat[4][4]); -bool BKE_gpencil_stroke_sample(struct bGPDstroke *gps, const float dist, const bool select); +bool BKE_gpencil_stroke_sample(struct bGPdata *gpd, + struct bGPDstroke *gps, + const float dist, + const bool select); bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf); bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_close(struct bGPDstroke *gps); -void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag); +void BKE_gpencil_dissolve_points(struct bGPdata *gpd, + struct bGPDframe *gpf, + struct bGPDstroke *gps, + const short tag); bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float tip_length); bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, const int index_from, const int index_to); -bool BKE_gpencil_stroke_split(struct bGPDframe *gpf, +bool BKE_gpencil_stroke_split(struct bGPdata *gpd, + struct bGPDframe *gpf, struct bGPDstroke *gps, const int before_index, struct bGPDstroke **remaining_gps); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 4cf8e365cf6..9d3582b2df2 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -155,6 +155,11 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id BLO_write_struct_array(writer, bGPDspoint, gps->totpoints, gps->points); BLO_write_struct_array(writer, bGPDtriangle, gps->tot_triangles, gps->triangles); BKE_defvert_blend_write(writer, gps->totpoints, gps->dvert); + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + BLO_write_struct(writer, bGPDcurve, gpc); + BLO_write_struct_array(writer, bGPDcurve_point, gpc->tot_curve_points, gpc->curve_points); + } } } } @@ -222,6 +227,13 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd) /* Relink geometry*/ BLO_read_data_address(reader, &gps->triangles); + /* relink stroke edit curve. */ + BLO_read_data_address(reader, &gps->editcurve); + if (gps->editcurve != NULL) { + /* relink curve point array */ + BLO_read_data_address(reader, &gps->editcurve->curve_points); + } + /* relink weight data */ if (gps->dvert) { BLO_read_data_address(reader, &gps->dvert); @@ -341,6 +353,20 @@ void BKE_gpencil_free_stroke_weights(bGPDstroke *gps) } } +void BKE_gpencil_free_stroke_editcurve(bGPDstroke *gps) +{ + if (gps == NULL) { + return; + } + bGPDcurve *editcurve = gps->editcurve; + if (editcurve == NULL) { + return; + } + MEM_freeN(editcurve->curve_points); + MEM_freeN(editcurve); + gps->editcurve = NULL; +} + /* free stroke, doesn't unlink from any listbase */ void BKE_gpencil_free_stroke(bGPDstroke *gps) { @@ -358,6 +384,9 @@ void BKE_gpencil_free_stroke(bGPDstroke *gps) if (gps->triangles) { MEM_freeN(gps->triangles); } + if (gps->editcurve != NULL) { + BKE_gpencil_free_stroke_editcurve(gps); + } MEM_freeN(gps); } @@ -688,6 +717,13 @@ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[]) gpd->pixfactor = GP_DEFAULT_PIX_FACTOR; + gpd->curve_edit_resolution = GP_DEFAULT_CURVE_RESOLUTION; + gpd->curve_edit_threshold = GP_DEFAULT_CURVE_ERROR; + gpd->curve_edit_corner_angle = GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE; + + /* use adaptive curve resolution by default */ + gpd->flag |= GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + gpd->zdepth_offset = 0.150f; /* grid settings */ @@ -776,6 +812,8 @@ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness) gps->mat_nr = mat_idx; + gps->editcurve = NULL; + return gps; } @@ -827,6 +865,16 @@ bGPDstroke *BKE_gpencil_stroke_add_existing_style( return gps; } +bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points) +{ + bGPDcurve *new_gp_curve = (bGPDcurve *)MEM_callocN(sizeof(bGPDcurve), __func__); + new_gp_curve->tot_curve_points = tot_curve_points; + new_gp_curve->curve_points = (bGPDcurve_point *)MEM_callocN( + sizeof(bGPDcurve_point) * tot_curve_points, __func__); + + return new_gp_curve; +} + /* ************************************************** */ /* Data Duplication */ @@ -845,13 +893,28 @@ void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_d BKE_defvert_array_copy(gps_dst->dvert, gps_src->dvert, gps_src->totpoints); } +/* Make a copy of a given gpencil stroke editcurve */ +bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) +{ + bGPDcurve *gpc_dst = MEM_dupallocN(gpc_src); + + if (gpc_src->curve_points != NULL) { + gpc_dst->curve_points = MEM_dupallocN(gpc_src->curve_points); + } + + return gpc_dst; +} + /** * Make a copy of a given grease-pencil stroke. * \param gps_src: Source grease pencil strokes. * \param dup_points: Duplicate points data. + * \param dup_curve: Duplicate curve data. * \return Pointer to new stroke. */ -bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_points) +bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, + const bool dup_points, + const bool dup_curve) { bGPDstroke *gps_dst = NULL; @@ -871,6 +934,10 @@ bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_poi } } + if (dup_curve && gps_src->editcurve != NULL) { + gps_dst->editcurve = BKE_gpencil_stroke_curve_duplicate(gps_src->editcurve); + } + /* return new stroke */ return gps_dst; } @@ -898,7 +965,7 @@ bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src) BLI_listbase_clear(&gpf_dst->strokes); LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* make copy of source stroke */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); BLI_addtail(&gpf_dst->strokes, gps_dst); } @@ -923,7 +990,7 @@ void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_ds BLI_listbase_clear(&gpf_dst->strokes); LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* make copy of source stroke */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); BLI_addtail(&gpf_dst->strokes, gps_dst); } } @@ -1037,6 +1104,39 @@ void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps) } } +void BKE_gpencil_curve_sync_selection(bGPDstroke *gps) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL) { + return; + } + + gps->flag &= ~GP_STROKE_SELECT; + gpc->flag &= ~GP_CURVE_SELECT; + + bool is_selected = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (BEZT_ISSEL_ANY(bezt)) { + gpc_pt->flag |= GP_SPOINT_SELECT; + } + else { + gpc_pt->flag &= ~GP_SPOINT_SELECT; + } + + if (gpc_pt->flag & GP_SPOINT_SELECT) { + is_selected = true; + } + } + + if (is_selected) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } +} + /* ************************************************** */ /* GP Frame API */ @@ -2304,12 +2404,14 @@ void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene) /** * Create grease pencil strokes from image * \param sima: Image + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param size: Size * \param mask: Mask * \return True if done */ -bool BKE_gpencil_from_image(SpaceImage *sima, bGPDframe *gpf, const float size, const bool mask) +bool BKE_gpencil_from_image( + SpaceImage *sima, bGPdata *gpd, bGPDframe *gpf, const float size, const bool mask) { Image *image = sima->image; bool done = false; @@ -2357,7 +2459,7 @@ bool BKE_gpencil_from_image(SpaceImage *sima, bGPDframe *gpf, const float size, pt->flag |= GP_SPOINT_SELECT; } } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 6b3f752120a..59c251197eb 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -37,8 +37,10 @@ #include "BLT_translation.h" #include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" #include "BKE_collection.h" +#include "BKE_context.h" #include "BKE_curve.h" #include "BKE_gpencil.h" #include "BKE_gpencil_curve.h" @@ -47,8 +49,16 @@ #include "BKE_material.h" #include "BKE_object.h" +#include "curve_fit_nd.h" + #include "DEG_depsgraph_query.h" +#define COORD_FITTING_INFLUENCE 20.0f + +/* -------------------------------------------------------------------- */ +/** \name Convert to curve object + * \{ */ + /* Helper: Check materials with same color. */ static int gpencil_check_same_material_color(Object *ob_gp, const float color_stroke[4], @@ -295,6 +305,7 @@ static void gpencil_convert_spline(Main *bmain, bGPDframe *gpf, Nurb *nu) { + bGPdata *gpd = (bGPdata *)ob_gp->data; bool cyclic = true; /* Create Stroke. */ @@ -445,11 +456,22 @@ static void gpencil_convert_spline(Main *bmain, } if (sample > 0.0f) { - BKE_gpencil_stroke_sample(gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false); } /* Recalc fill geometry. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); +} + +static void gpencil_editstroke_deselect_all(bGPDcurve *gpc) +{ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + gpc->flag &= ~GP_CURVE_SELECT; } /** @@ -536,3 +558,851 @@ void BKE_gpencil_convert_curve(Main *bmain, } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Editcurve kernel functions + * \{ */ + +static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps, + const float stroke_radius) +{ + BLI_assert(gps->totpoints < 3); + + if (gps->totpoints == 1) { + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(1); + bGPDspoint *pt = &gps->points[0]; + bGPDcurve_point *cpt = &editcurve->curve_points[0]; + BezTriple *bezt = &cpt->bezt; + + /* Handles are twice as long as the radius of the point. */ + float offset = (pt->pressure * stroke_radius) * 2.0f; + + float tmp_vec[3]; + for (int j = 0; j < 3; j++) { + copy_v3_v3(tmp_vec, &pt->x); + /* Move handles along the x-axis away from the control point */ + tmp_vec[0] += (float)(j - 1) * offset; + copy_v3_v3(bezt->vec[j], tmp_vec); + } + + cpt->pressure = pt->pressure; + cpt->strength = pt->strength; + copy_v4_v4(cpt->vert_color, pt->vert_color); + + /* default handle type */ + bezt->h1 = HD_FREE; + bezt->h2 = HD_FREE; + + cpt->point_index = 0; + + return editcurve; + } + if (gps->totpoints == 2) { + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(2); + bGPDspoint *first_pt = &gps->points[0]; + bGPDspoint *last_pt = &gps->points[1]; + + float length = len_v3v3(&first_pt->x, &last_pt->x); + float offset = length / 3; + float dir[3]; + sub_v3_v3v3(dir, &last_pt->x, &first_pt->x); + + for (int i = 0; i < 2; i++) { + bGPDspoint *pt = &gps->points[i]; + bGPDcurve_point *cpt = &editcurve->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + + float tmp_vec[3]; + for (int j = 0; j < 3; j++) { + copy_v3_v3(tmp_vec, dir); + normalize_v3_length(tmp_vec, (float)(j - 1) * offset); + add_v3_v3v3(bezt->vec[j], &pt->x, tmp_vec); + } + + cpt->pressure = pt->pressure; + cpt->strength = pt->strength; + copy_v4_v4(cpt->vert_color, pt->vert_color); + + /* default handle type */ + bezt->h1 = HD_VECT; + bezt->h2 = HD_VECT; + + cpt->point_index = 0; + } + + return editcurve; + } + + return NULL; +} + +/** + * Creates a bGPDcurve by doing a cubic curve fitting on the grease pencil stroke points. + */ +bGPDcurve *BKE_gpencil_stroke_editcurve_generate(bGPDstroke *gps, + const float error_threshold, + const float corner_angle, + const float stroke_radius) +{ + if (gps->totpoints < 3) { + return gpencil_stroke_editcurve_generate_edgecases(gps, stroke_radius); + } +#define POINT_DIM 9 + + float *points = MEM_callocN(sizeof(float) * gps->totpoints * POINT_DIM, __func__); + float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + float tmp_vec[3]; + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + int row = i * POINT_DIM; + + /* normalize coordinate to 0..1 */ + sub_v3_v3v3(tmp_vec, &pt->x, gps->boundbox_min); + mul_v3_v3fl(&points[row], tmp_vec, COORD_FITTING_INFLUENCE / diag_length); + points[row + 3] = pt->pressure / diag_length; + + /* strength and color are already normalized */ + points[row + 4] = pt->strength / diag_length; + mul_v4_v4fl(&points[row + 5], pt->vert_color, 1.0f / diag_length); + } + + uint calc_flag = CURVE_FIT_CALC_HIGH_QUALIY; + if (gps->totpoints > 2 && gps->flag & GP_STROKE_CYCLIC) { + calc_flag |= CURVE_FIT_CALC_CYCLIC; + } + + float *r_cubic_array = NULL; + unsigned int r_cubic_array_len = 0; + unsigned int *r_cubic_orig_index = NULL; + unsigned int *r_corners_index_array = NULL; + unsigned int r_corners_index_len = 0; + int r = curve_fit_cubic_to_points_refit_fl(points, + gps->totpoints, + POINT_DIM, + error_threshold, + calc_flag, + NULL, + 0, + corner_angle, + &r_cubic_array, + &r_cubic_array_len, + &r_cubic_orig_index, + &r_corners_index_array, + &r_corners_index_len); + + if (r != 0 || r_cubic_array_len < 1) { + return NULL; + } + + uint curve_point_size = 3 * POINT_DIM; + + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(r_cubic_array_len); + + for (int i = 0; i < r_cubic_array_len; i++) { + bGPDcurve_point *cpt = &editcurve->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + float *curve_point = &r_cubic_array[i * curve_point_size]; + + for (int j = 0; j < 3; j++) { + float *bez = &curve_point[j * POINT_DIM]; + madd_v3_v3v3fl(bezt->vec[j], gps->boundbox_min, bez, diag_length / COORD_FITTING_INFLUENCE); + } + + float *ctrl_point = &curve_point[1 * POINT_DIM]; + cpt->pressure = ctrl_point[3] * diag_length; + cpt->strength = ctrl_point[4] * diag_length; + mul_v4_v4fl(cpt->vert_color, &ctrl_point[5], diag_length); + + /* default handle type */ + bezt->h1 = HD_ALIGN; + bezt->h2 = HD_ALIGN; + + cpt->point_index = r_cubic_orig_index[i]; + } + + if (r_corners_index_len > 0 && r_corners_index_array != NULL) { + int start = 0, end = r_corners_index_len; + if ((r_corners_index_len > 1) && (calc_flag & CURVE_FIT_CALC_CYCLIC) == 0) { + start = 1; + end = r_corners_index_len - 1; + } + for (int i = start; i < end; i++) { + bGPDcurve_point *cpt = &editcurve->curve_points[r_corners_index_array[i]]; + BezTriple *bezt = &cpt->bezt; + bezt->h1 = HD_FREE; + bezt->h2 = HD_FREE; + } + } + + MEM_freeN(points); + if (r_cubic_array) { + free(r_cubic_array); + } + if (r_corners_index_array) { + free(r_corners_index_array); + } + if (r_cubic_orig_index) { + free(r_cubic_orig_index); + } + +#undef POINT_DIM + return editcurve; +} + +/** + * Updates the editcurve for a stroke. Frees the old curve if one exists and generates a new one. + */ +void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps) +{ + if (gps == NULL || gps->totpoints < 0) { + return; + } + + if (gps->editcurve != NULL) { + BKE_gpencil_free_stroke_editcurve(gps); + } + + float defaultpixsize = 1000.0f / gpd->pixfactor; + float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f; + + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_generate( + gps, gpd->curve_edit_threshold, gpd->curve_edit_corner_angle, stroke_radius); + if (editcurve == NULL) { + return; + } + + gps->editcurve = editcurve; +} + +/** + * Sync the selection from stroke to editcurve + */ +void BKE_gpencil_editcurve_stroke_sync_selection(bGPDstroke *gps, bGPDcurve *gpc) +{ + if (gps->flag & GP_STROKE_SELECT) { + gpc->flag |= GP_CURVE_SELECT; + + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + bGPDspoint *pt = &gps->points[gpc_pt->point_index]; + if (pt->flag & GP_SPOINT_SELECT) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + gpencil_editstroke_deselect_all(gpc); + } +} + +/** + * Sync the selection from editcurve to stroke + */ +void BKE_gpencil_stroke_editcurve_sync_selection(bGPDstroke *gps, bGPDcurve *gpc) +{ + if (gpc->flag & GP_CURVE_SELECT) { + gps->flag |= GP_STROKE_SELECT; + + for (int i = 0; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + bGPDspoint *pt = &gps->points[gpc_pt->point_index]; + bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + 1]; + + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + pt->flag |= GP_SPOINT_SELECT; + if (gpc_pt_next->flag & GP_CURVE_POINT_SELECT) { + /* select all the points after */ + for (int j = gpc_pt->point_index + 1; j < gpc_pt_next->point_index; j++) { + bGPDspoint *pt_next = &gps->points[j]; + pt_next->flag |= GP_SPOINT_SELECT; + } + } + } + else { + pt->flag &= ~GP_SPOINT_SELECT; + /* deselect all points after */ + for (int j = gpc_pt->point_index + 1; j < gpc_pt_next->point_index; j++) { + bGPDspoint *pt_next = &gps->points[j]; + pt_next->flag &= ~GP_SPOINT_SELECT; + } + } + } + + bGPDcurve_point *gpc_first = &gpc->curve_points[0]; + bGPDcurve_point *gpc_last = &gpc->curve_points[gpc->tot_curve_points - 1]; + bGPDspoint *last_pt = &gps->points[gpc_last->point_index]; + if (gpc_last->flag & GP_CURVE_POINT_SELECT) { + last_pt->flag |= GP_SPOINT_SELECT; + } + else { + last_pt->flag &= ~GP_SPOINT_SELECT; + } + + if (gps->flag & GP_STROKE_CYCLIC) { + if (gpc_first->flag & GP_CURVE_POINT_SELECT && gpc_last->flag & GP_CURVE_POINT_SELECT) { + for (int i = gpc_last->point_index + 1; i < gps->totpoints; i++) { + bGPDspoint *pt_next = &gps->points[i]; + pt_next->flag |= GP_SPOINT_SELECT; + } + } + else { + for (int i = gpc_last->point_index + 1; i < gps->totpoints; i++) { + bGPDspoint *pt_next = &gps->points[i]; + pt_next->flag &= ~GP_SPOINT_SELECT; + } + } + } + } + else { + gps->flag &= ~GP_STROKE_SELECT; + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_SELECT; + } + } +} + +static void gpencil_interpolate_fl_from_to( + float from, float to, float *point_offset, int it, int stride) +{ + /* smooth interpolation */ + float *r = point_offset; + for (int i = 0; i <= it; i++) { + float fac = (float)i / (float)it; + fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; // smooth + *r = interpf(to, from, fac); + r = POINTER_OFFSET(r, stride); + } +} + +static void gpencil_interpolate_v4_from_to( + float from[4], float to[4], float *point_offset, int it, int stride) +{ + /* smooth interpolation */ + float *r = point_offset; + for (int i = 0; i <= it; i++) { + float fac = (float)i / (float)it; + fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; // smooth + interp_v4_v4v4(r, from, to, fac); + r = POINTER_OFFSET(r, stride); + } +} + +static float gpencil_approximate_curve_segment_arclength(bGPDcurve_point *cpt_start, + bGPDcurve_point *cpt_end) +{ + BezTriple *bezt_start = &cpt_start->bezt; + BezTriple *bezt_end = &cpt_end->bezt; + + float chord_len = len_v3v3(bezt_start->vec[1], bezt_end->vec[1]); + float net_len = len_v3v3(bezt_start->vec[1], bezt_start->vec[2]); + net_len += len_v3v3(bezt_start->vec[2], bezt_end->vec[0]); + net_len += len_v3v3(bezt_end->vec[0], bezt_end->vec[1]); + + return (chord_len + net_len) / 2.0f; +} + +static void gpencil_calculate_stroke_points_curve_segment( + bGPDcurve_point *cpt, bGPDcurve_point *cpt_next, float *points_offset, int resolu, int stride) +{ + /* sample points on all 3 axis between two curve points */ + for (uint axis = 0; axis < 3; axis++) { + BKE_curve_forward_diff_bezier(cpt->bezt.vec[1][axis], + cpt->bezt.vec[2][axis], + cpt_next->bezt.vec[0][axis], + cpt_next->bezt.vec[1][axis], + POINTER_OFFSET(points_offset, sizeof(float) * axis), + (int)resolu, + stride); + } + + /* interpolate other attributes */ + gpencil_interpolate_fl_from_to(cpt->pressure, + cpt_next->pressure, + POINTER_OFFSET(points_offset, sizeof(float) * 3), + resolu, + stride); + gpencil_interpolate_fl_from_to(cpt->strength, + cpt_next->strength, + POINTER_OFFSET(points_offset, sizeof(float) * 4), + resolu, + stride); + gpencil_interpolate_v4_from_to(cpt->vert_color, + cpt_next->vert_color, + POINTER_OFFSET(points_offset, sizeof(float) * 5), + resolu, + stride); +} + +static float *gpencil_stroke_points_from_editcurve_adaptive_resolu( + bGPDcurve_point *curve_point_array, + int curve_point_array_len, + int resolution, + bool is_cyclic, + int *r_points_len) +{ + /* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */ + const uint stride = sizeof(float[9]); + const uint cpt_last = curve_point_array_len - 1; + const uint num_segments = (is_cyclic) ? curve_point_array_len : curve_point_array_len - 1; + int *segment_point_lengths = MEM_callocN(sizeof(int) * num_segments, __func__); + + uint points_len = 1; + for (int i = 0; i < cpt_last; i++) { + bGPDcurve_point *cpt = &curve_point_array[i]; + bGPDcurve_point *cpt_next = &curve_point_array[i + 1]; + float arclen = gpencil_approximate_curve_segment_arclength(cpt, cpt_next); + int segment_resolu = (int)floorf(arclen * resolution); + CLAMP_MIN(segment_resolu, 1); + + segment_point_lengths[i] = segment_resolu; + points_len += segment_resolu; + } + + if (is_cyclic) { + bGPDcurve_point *cpt = &curve_point_array[cpt_last]; + bGPDcurve_point *cpt_next = &curve_point_array[0]; + float arclen = gpencil_approximate_curve_segment_arclength(cpt, cpt_next); + int segment_resolu = (int)floorf(arclen * resolution); + CLAMP_MIN(segment_resolu, 1); + + segment_point_lengths[cpt_last] = segment_resolu; + points_len += segment_resolu; + } + + float(*r_points)[9] = MEM_callocN((stride * points_len * (is_cyclic ? 2 : 1)), __func__); + float *points_offset = &r_points[0][0]; + int point_index = 0; + for (int i = 0; i < cpt_last; i++) { + bGPDcurve_point *cpt_curr = &curve_point_array[i]; + bGPDcurve_point *cpt_next = &curve_point_array[i + 1]; + int segment_resolu = segment_point_lengths[i]; + gpencil_calculate_stroke_points_curve_segment( + cpt_curr, cpt_next, points_offset, segment_resolu, stride); + /* update the index */ + cpt_curr->point_index = point_index; + point_index += segment_resolu; + points_offset = POINTER_OFFSET(points_offset, segment_resolu * stride); + } + + bGPDcurve_point *cpt_curr = &curve_point_array[cpt_last]; + cpt_curr->point_index = point_index; + if (is_cyclic) { + bGPDcurve_point *cpt_next = &curve_point_array[0]; + int segment_resolu = segment_point_lengths[cpt_last]; + gpencil_calculate_stroke_points_curve_segment( + cpt_curr, cpt_next, points_offset, segment_resolu, stride); + } + + MEM_freeN(segment_point_lengths); + + *r_points_len = points_len; + return (float(*))r_points; +} + +/** + * Helper: calculate the points on a curve with a fixed resolution. + */ +static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point *curve_point_array, + int curve_point_array_len, + int resolution, + bool is_cyclic, + int *r_points_len) +{ + /* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */ + const uint stride = sizeof(float[9]); + const uint array_last = curve_point_array_len - 1; + const uint resolu_stride = resolution * stride; + const uint points_len = BKE_curve_calc_coords_axis_len( + curve_point_array_len, resolution, is_cyclic, false); + + float(*r_points)[9] = MEM_callocN((stride * points_len * (is_cyclic ? 2 : 1)), __func__); + float *points_offset = &r_points[0][0]; + for (unsigned int i = 0; i < array_last; i++) { + bGPDcurve_point *cpt_curr = &curve_point_array[i]; + bGPDcurve_point *cpt_next = &curve_point_array[i + 1]; + + gpencil_calculate_stroke_points_curve_segment( + cpt_curr, cpt_next, points_offset, resolution, stride); + /* update the index */ + cpt_curr->point_index = i * resolution; + points_offset = POINTER_OFFSET(points_offset, resolu_stride); + } + + bGPDcurve_point *cpt_curr = &curve_point_array[array_last]; + cpt_curr->point_index = array_last * resolution; + if (is_cyclic) { + bGPDcurve_point *cpt_next = &curve_point_array[0]; + gpencil_calculate_stroke_points_curve_segment( + cpt_curr, cpt_next, points_offset, resolution, stride); + } + + *r_points_len = points_len; + return (float(*))r_points; +} + +/** + * Recalculate stroke points with the editcurve of the stroke. + */ +void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, + const uint resolution, + const bool adaptive) +{ + if (gps == NULL || gps->editcurve == NULL) { + return; + } + + bGPDcurve *editcurve = gps->editcurve; + bGPDcurve_point *curve_point_array = editcurve->curve_points; + int curve_point_array_len = editcurve->tot_curve_points; + if (curve_point_array_len == 0) { + return; + } + /* Handle case for single curve point. */ + if (curve_point_array_len == 1) { + bGPDcurve_point *cpt = &curve_point_array[0]; + /* resize stroke point array */ + gps->totpoints = 1; + gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints); + } + + bGPDspoint *pt = &gps->points[0]; + copy_v3_v3(&pt->x, cpt->bezt.vec[1]); + + pt->pressure = cpt->pressure; + pt->strength = cpt->strength; + + copy_v4_v4(pt->vert_color, cpt->vert_color); + + /* deselect */ + pt->flag &= ~GP_SPOINT_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + + return; + } + + bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + + int points_len = 0; + float(*points)[9] = NULL; + if (adaptive) { + points = (float(*)[9])gpencil_stroke_points_from_editcurve_adaptive_resolu( + curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len); + } + else { + points = (float(*)[9])gpencil_stroke_points_from_editcurve_fixed_resolu( + curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len); + } + + if (points == NULL || points_len == 0) { + return; + } + + /* resize stroke point array */ + gps->totpoints = points_len; + gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints); + } + + /* write new data to stroke point array */ + for (int i = 0; i < points_len; i++) { + bGPDspoint *pt = &gps->points[i]; + copy_v3_v3(&pt->x, &points[i][0]); + + pt->pressure = points[i][3]; + pt->strength = points[i][4]; + + copy_v4_v4(pt->vert_color, &points[i][5]); + + /* deselect points */ + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; + + /* free temp data */ + MEM_freeN(points); +} + +/** + * Recalculate the handles of the edit curve of a grease pencil stroke + */ +void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps) +{ + if (gps == NULL || gps->editcurve == NULL) { + return; + } + + bool changed = false; + bGPDcurve *gpc = gps->editcurve; + if (gpc->tot_curve_points < 2) { + return; + } + + if (gpc->tot_curve_points == 1) { + BKE_nurb_handle_calc( + &(gpc->curve_points[0].bezt), NULL, &(gpc->curve_points[0].bezt), false, 0); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + } + + for (int i = 1; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + bGPDcurve_point *gpc_pt_prev = &gpc->curve_points[i - 1]; + bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + 1]; + /* update handle if point or neighbour is selected */ + if (gpc_pt->flag & GP_CURVE_POINT_SELECT || gpc_pt_prev->flag & GP_CURVE_POINT_SELECT || + gpc_pt_next->flag & GP_CURVE_POINT_SELECT) { + BezTriple *bezt = &gpc_pt->bezt; + BezTriple *bezt_prev = &gpc_pt_prev->bezt; + BezTriple *bezt_next = &gpc_pt_next->bezt; + + BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, false, 0); + changed = true; + } + } + + bGPDcurve_point *gpc_first = &gpc->curve_points[0]; + bGPDcurve_point *gpc_last = &gpc->curve_points[gpc->tot_curve_points - 1]; + bGPDcurve_point *gpc_first_next = &gpc->curve_points[1]; + bGPDcurve_point *gpc_last_prev = &gpc->curve_points[gpc->tot_curve_points - 2]; + if (gps->flag & GP_STROKE_CYCLIC) { + if (gpc_first->flag & GP_CURVE_POINT_SELECT || gpc_last->flag & GP_CURVE_POINT_SELECT) { + BezTriple *bezt_first = &gpc_first->bezt; + BezTriple *bezt_last = &gpc_last->bezt; + BezTriple *bezt_first_next = &gpc_first_next->bezt; + BezTriple *bezt_last_prev = &gpc_last_prev->bezt; + + BKE_nurb_handle_calc(bezt_first, bezt_last, bezt_first_next, false, 0); + BKE_nurb_handle_calc(bezt_last, bezt_last_prev, bezt_first, false, 0); + changed = true; + } + } + else { + if (gpc_first->flag & GP_CURVE_POINT_SELECT || gpc_last->flag & GP_CURVE_POINT_SELECT) { + BezTriple *bezt_first = &gpc_first->bezt; + BezTriple *bezt_last = &gpc_last->bezt; + BezTriple *bezt_first_next = &gpc_first_next->bezt; + BezTriple *bezt_last_prev = &gpc_last_prev->bezt; + + BKE_nurb_handle_calc(bezt_first, NULL, bezt_first_next, false, 0); + BKE_nurb_handle_calc(bezt_last, bezt_last_prev, NULL, false, 0); + changed = true; + } + } + + if (changed) { + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + } +} + +/* Helper: count how many new curve points must be generated. */ +static int gpencil_editcurve_subdivide_count(bGPDcurve *gpc, bool is_cyclic) +{ + int count = 0; + for (int i = 0; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *cpt_next = &gpc->curve_points[i + 1]; + + if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) { + count++; + } + } + + if (is_cyclic) { + bGPDcurve_point *cpt = &gpc->curve_points[0]; + bGPDcurve_point *cpt_next = &gpc->curve_points[gpc->tot_curve_points - 1]; + + if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) { + count++; + } + } + + return count; +} + +static void gpencil_editcurve_subdivide_curve_segment(bGPDcurve_point *cpt_start, + bGPDcurve_point *cpt_end, + bGPDcurve_point *cpt_new) +{ + BezTriple *bezt_start = &cpt_start->bezt; + BezTriple *bezt_end = &cpt_end->bezt; + BezTriple *bezt_new = &cpt_new->bezt; + for (int axis = 0; axis < 3; axis++) { + float p0, p1, p2, p3, m0, m1, q0, q1, b; + p0 = bezt_start->vec[1][axis]; + p1 = bezt_start->vec[2][axis]; + p2 = bezt_end->vec[0][axis]; + p3 = bezt_end->vec[1][axis]; + + m0 = (p0 + p1) / 2; + q0 = (p0 + 2 * p1 + p2) / 4; + b = (p0 + 3 * p1 + 3 * p2 + p3) / 8; + q1 = (p1 + 2 * p2 + p3) / 4; + m1 = (p2 + p3) / 2; + + bezt_new->vec[0][axis] = q0; + bezt_new->vec[2][axis] = q1; + bezt_new->vec[1][axis] = b; + + bezt_start->vec[2][axis] = m0; + bezt_end->vec[0][axis] = m1; + } + + cpt_new->pressure = interpf(cpt_end->pressure, cpt_start->pressure, 0.5f); + cpt_new->strength = interpf(cpt_end->strength, cpt_start->strength, 0.5f); + interp_v4_v4v4(cpt_new->vert_color, cpt_start->vert_color, cpt_end->vert_color, 0.5f); +} + +void BKE_gpencil_editcurve_subdivide(bGPDstroke *gps, const int cuts) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 2) { + return; + } + bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + + /* repeat for number of cuts */ + for (int s = 0; s < cuts; s++) { + int old_tot_curve_points = gpc->tot_curve_points; + int new_num_curve_points = gpencil_editcurve_subdivide_count(gpc, is_cyclic); + if (new_num_curve_points == 0) { + break; + } + int new_tot_curve_points = old_tot_curve_points + new_num_curve_points; + + bGPDcurve_point *temp_curve_points = (bGPDcurve_point *)MEM_callocN( + sizeof(bGPDcurve_point) * new_tot_curve_points, __func__); + + bool prev_subdivided = false; + int j = 0; + for (int i = 0; i < old_tot_curve_points - 1; i++, j++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *cpt_next = &gpc->curve_points[i + 1]; + + if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) { + bGPDcurve_point *cpt_new = &temp_curve_points[j + 1]; + gpencil_editcurve_subdivide_curve_segment(cpt, cpt_next, cpt_new); + + memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point)); + memcpy(&temp_curve_points[j + 2], cpt_next, sizeof(bGPDcurve_point)); + + cpt_new->flag |= GP_CURVE_POINT_SELECT; + cpt_new->bezt.h1 = HD_ALIGN; + cpt_new->bezt.h2 = HD_ALIGN; + BEZT_SEL_ALL(&cpt_new->bezt); + + prev_subdivided = true; + j++; + } + else if (!prev_subdivided) { + memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point)); + prev_subdivided = false; + } + else { + prev_subdivided = false; + } + } + + if (is_cyclic) { + bGPDcurve_point *cpt = &gpc->curve_points[old_tot_curve_points - 1]; + bGPDcurve_point *cpt_next = &gpc->curve_points[0]; + + if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) { + bGPDcurve_point *cpt_new = &temp_curve_points[j + 1]; + gpencil_editcurve_subdivide_curve_segment(cpt, cpt_next, cpt_new); + + memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point)); + memcpy(&temp_curve_points[0], cpt_next, sizeof(bGPDcurve_point)); + + cpt_new->flag |= GP_CURVE_POINT_SELECT; + cpt_new->bezt.h1 = HD_ALIGN; + cpt_new->bezt.h2 = HD_ALIGN; + BEZT_SEL_ALL(&cpt_new->bezt); + } + else if (!prev_subdivided) { + memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point)); + } + } + else { + bGPDcurve_point *cpt = &gpc->curve_points[old_tot_curve_points - 1]; + memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point)); + } + + MEM_freeN(gpc->curve_points); + gpc->curve_points = temp_curve_points; + gpc->tot_curve_points = new_tot_curve_points; + } +} + +void BKE_gpencil_strokes_selected_update_editcurve(bGPdata *gpd) +{ + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + /* For all selected strokes, update edit curve. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (!BKE_gpencil_layer_is_editable(gpl)) { + continue; + } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && is_multiedit)) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip deselected stroke */ + if (!(gps->flag & GP_STROKE_SELECT)) { + continue; + } + + /* Generate the curve if there is none or the stroke was changed */ + if (gps->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Continue if curve could not be generated. */ + if (gps->editcurve == NULL) { + continue; + } + } + else if (gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + } + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gps, gps->editcurve); + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + } + } +} + +void BKE_gpencil_strokes_selected_sync_selection_editcurve(bGPdata *gpd) +{ + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + /* Sync selection for all strokes with editcurve. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (!BKE_gpencil_layer_is_editable(gpl)) { + continue; + } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && is_multiedit)) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + bGPDcurve *gpc = gps->editcurve; + if (gpc != NULL) { + /* Update the selection of every stroke that has an editcurve */ + BKE_gpencil_stroke_editcurve_sync_selection(gps, gpc); + } + } + } + } + } +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index ff7dde27db8..d2cfb36cb15 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -45,8 +45,11 @@ #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" +#include "BLT_translation.h" + #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" @@ -415,10 +418,11 @@ static void stroke_interpolate_deform_weights( /** * Resample a stroke + * \param gpd: Grease pencil data-block * \param gps: Stroke to sample * \param dist: Distance of one segment */ -bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool select) +bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select) { bGPDspoint *pt = gps->points; bGPDspoint *pt1 = NULL; @@ -515,7 +519,7 @@ bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool sel gps->totpoints = i; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return true; } @@ -628,13 +632,15 @@ bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const /** * Split stroke. + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param gps: Grease pencil original stroke * \param before_index: Position of the point to split * \param remaining_gps: Secondary stroke after split. * \return True if the split was done */ -bool BKE_gpencil_stroke_split(bGPDframe *gpf, +bool BKE_gpencil_stroke_split(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, const int before_index, bGPDstroke **remaining_gps) @@ -684,7 +690,7 @@ bool BKE_gpencil_stroke_split(bGPDframe *gpf, * Keep the end point. */ BKE_gpencil_stroke_trim_points(gps, 0, old_count); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return true; } @@ -1273,14 +1279,31 @@ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) /** * Recalc all internal geometry data for the stroke + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke */ -void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps) +void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) { if (gps == NULL) { return; } + if (gps->editcurve != NULL) { + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + /* curve geometry was updated: stroke needs recalculation */ + if (gps->flag & GP_STROKE_NEEDS_CURVE_UPDATE) { + bool is_adaptive = gpd->flag & GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, gpd->curve_edit_resolution, is_adaptive); + gps->flag &= ~GP_STROKE_NEEDS_CURVE_UPDATE; + } + } + else { + /* stroke geometry was updated: editcurve needs recalculation */ + gps->editcurve->flag |= GP_CURVE_NEEDS_STROKE_UPDATE; + } + } + if (gps->totpoints > 2) { BKE_gpencil_stroke_fill_triangulate(gps); } @@ -1326,7 +1349,7 @@ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) * Trim stroke to the first intersection or loop. * \param gps: Stroke data */ -bool BKE_gpencil_stroke_trim(bGPDstroke *gps) +bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 4) { return false; @@ -1413,7 +1436,7 @@ bool BKE_gpencil_stroke_trim(bGPDstroke *gps) MEM_SAFE_FREE(old_dvert); } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return intersect; } @@ -1509,11 +1532,12 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps) /** * Dissolve points in stroke. + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param gps: Grease pencil stroke * \param tag: Type of tag for point */ -void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag) +void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, const short tag) { bGPDspoint *pt; MDeformVert *dvert = NULL; @@ -1589,7 +1613,7 @@ void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short ta gps->totpoints = tot; /* triangles cache needs to be recalculated */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } @@ -1635,10 +1659,11 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) * * Ramer - Douglas - Peucker algorithm * by http ://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke * \param epsilon: Epsilon value to define precision of the algorithm */ -void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) +void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float epsilon) { bGPDspoint *old_points = MEM_dupallocN(gps->points); int totpoints = gps->totpoints; @@ -1735,7 +1760,7 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); @@ -1744,9 +1769,10 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) /** * Simplify alternate vertex of stroke except extremes. + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke */ -void BKE_gpencil_stroke_simplify_fixed(bGPDstroke *gps) +void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 5) { return; @@ -1800,19 +1826,20 @@ void BKE_gpencil_stroke_simplify_fixed(bGPDstroke *gps) gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); } /** - * Subdivide grease pencil stroke. - * \param gps: Grease pencil stroke + * Subdivide a stroke + * \param gpd: Grease pencil data-block + * \param gps: Stroke * \param level: Level of subdivision * \param type: Type of subdivision */ -void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) +void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int type) { bGPDspoint *temp_points; MDeformVert *temp_dverts = NULL; @@ -1921,7 +1948,7 @@ void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* Merge by distance ------------------------------------- */ @@ -1930,12 +1957,14 @@ void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) * Reduce a series of points when the distance is below a threshold. * Special case for first and last points (both are keeped) for other points, * the merge point always is at first point. + * \param gpd: Grease pencil data-block * \param gpf: Grease Pencil frame * \param gps: Grease Pencil stroke * \param threshold: Distance between points * \param use_unselected: Set to true to analyze all stroke and not only selected points */ -void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, +void BKE_gpencil_stroke_merge_distance(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, const float threshold, const bool use_unselected) @@ -2000,11 +2029,11 @@ void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, /* Dissolve tagged points */ if (tagged) { - BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); + BKE_gpencil_dissolve_points(gpd, gpf, gps, GP_SPOINT_TAG); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } typedef struct GpEdge { @@ -2093,6 +2122,7 @@ static int gpencil_walk_edge(GHash *v_table, } static void gpencil_generate_edgeloops(Object *ob, + bGPdata *gpd, bGPDframe *gpf_stroke, int stroke_mat_index, const float angle, @@ -2218,7 +2248,7 @@ static void gpencil_generate_edgeloops(Object *ob, pt->strength = 1.0f; } - BKE_gpencil_stroke_geometry_update(gps_stroke); + BKE_gpencil_stroke_geometry_update(gpd, gps_stroke); } /* Free memory. */ @@ -2397,10 +2427,10 @@ bool BKE_gpencil_convert_mesh(Main *bmain, } /* If has only 3 points subdivide. */ if (mp->totloop == 3) { - BKE_gpencil_stroke_subdivide(gps_fill, 1, GP_SUBDIV_SIMPLE); + BKE_gpencil_stroke_subdivide(gpd, gps_fill, 1, GP_SUBDIV_SIMPLE); } - BKE_gpencil_stroke_geometry_update(gps_fill); + BKE_gpencil_stroke_geometry_update(gpd, gps_fill); } } } @@ -2417,7 +2447,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); gpencil_generate_edgeloops( - ob_eval, gpf_stroke, stroke_mat_index, angle, thickness, offset, matrix, use_seams); + ob_eval, gpd, gpf_stroke, stroke_mat_index, angle, thickness, offset, matrix, use_seams); /* Tag for recalculation */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -2457,7 +2487,7 @@ void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4]) } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } @@ -2549,7 +2579,7 @@ void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } @@ -2586,7 +2616,7 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index be06638ab64..09f9e9e891c 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -698,7 +698,9 @@ void BKE_gpencil_prepare_eval_data(Depsgraph *depsgraph, Scene *scene, Object *o } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_eval); - const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_eval); + const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && + (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if ((!do_modifiers) && (!do_parent)) { return; @@ -734,8 +736,10 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) bGPdata *gpd = (bGPdata *)ob->data; const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); - const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && + const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && + (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if (!do_modifiers) { return; |