Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFalk David <falkdavid@gmx.de>2020-11-13 23:43:00 +0300
committerFalk David <falkdavid@gmx.de>2020-11-13 23:43:00 +0300
commit0be88c7d15d2ad1af284c6283370173647ae74eb (patch)
tree5fff573c512e284547ebe0c921ecffdae2c377c4 /source/blender/blenkernel/intern/gpencil_curve.c
parent9d28353b525ecfbcca1501be72e4276dfb2bbc2a (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.c874
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);
+ }
+ }
+ }
+ }
+ }
+}
+
+/** \} */