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/intern/gpencil_curve.c | |
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/intern/gpencil_curve.c')
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_curve.c | 874 |
1 files changed, 872 insertions, 2 deletions
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); + } + } + } + } + } +} + +/** \} */ |