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:
authorAntonio Vazquez <blendergit@gmail.com>2020-11-18 23:30:43 +0300
committerAntonio Vazquez <blendergit@gmail.com>2020-11-18 23:35:06 +0300
commite9607f45d85df6df834a80f147b8c42ff12f56f2 (patch)
treeeeea0e6c742f557798d3e8011bf84c8d806c0b12 /source/blender/blenkernel
parentc126e27cdc8b28365a9d5f9fafc4d521d1eb83df (diff)
GPencil: Automerge last drawn stroke with previous strokes
This option joins any stroke with an end near the actual stroke. Now it is not limited to the last stroke, any stroke in the same layer for the actual frame can be joined. The join can join two strokes drawing a third stroke. If the end and the start of the result stroke are very small, the stroke is changed to be cyclic automatically. There is a limit distance to join the stroke, if the distance is greater than this value, the strokes are not joined. Actually, a constant, threshold distance is used, but we could expose as a parameter in the UI in the future. The tool can be used with freehand drawing or with primitives. Note: Great part of the patch is just a refactor of the old code to make it accessible and to keep code organized. Reviewed By: mendio Maniphest Tasks: T82377 Differential Revision: https://developer.blender.org/D9440
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h25
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c584
2 files changed, 609 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 6917a62053c..b107a6e72af 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -37,6 +37,7 @@ struct bGPDlayer;
struct bGPDspoint;
struct bGPDstroke;
struct bGPdata;
+struct bGPDcurve;
/* Object boundbox. */
bool BKE_gpencil_data_minmax(const struct bGPdata *gpd, float r_min[3], float r_max[3]);
@@ -115,6 +116,21 @@ bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const
bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps,
const int index_from,
const int index_to);
+struct bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(struct bGPdata *gpd,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps,
+ struct bGPDstroke *next_stroke,
+ int tag_flags,
+ bool select,
+ int limit);
+void BKE_gpencil_curve_delete_tagged_points(struct bGPdata *gpd,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps,
+ struct bGPDstroke *next_stroke,
+ struct bGPDcurve *gpc,
+ int tag_flags);
+
+void BKE_gpencil_stroke_flip(struct bGPDstroke *gps);
bool BKE_gpencil_stroke_split(struct bGPdata *gpd,
struct bGPDframe *gpf,
struct bGPDstroke *gps,
@@ -123,9 +139,18 @@ bool BKE_gpencil_stroke_split(struct bGPdata *gpd,
bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist);
float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d);
+float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps,
+ const int start_index,
+ const int end_index,
+ bool use_3d);
void BKE_gpencil_stroke_set_random_color(struct bGPDstroke *gps);
+void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a,
+ struct bGPDstroke *gps_b,
+ const bool leave_gaps,
+ const bool fit_thickness);
+
bool BKE_gpencil_convert_mesh(struct Main *bmain,
struct Depsgraph *depsgraph,
struct Scene *scene,
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c
index d2cfb36cb15..fc74439fd76 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.c
+++ b/source/blender/blenkernel/intern/gpencil_geom.c
@@ -1345,6 +1345,34 @@ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
return total_length;
}
+/** Calculate grease pencil stroke length between points. */
+float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps,
+ const int start_index,
+ const int end_index,
+ bool use_3d)
+{
+ if (!gps->points || gps->totpoints < 2 || end_index <= start_index) {
+ return 0.0f;
+ }
+
+ int index = MAX2(start_index, 0) + 1;
+ int last_index = MIN2(end_index, gps->totpoints - 1) + 1;
+
+ float *last_pt = &gps->points[index - 1].x;
+ float total_length = 0.0f;
+ for (int i = index; i < last_index; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ if (use_3d) {
+ total_length += len_v3v3(&pt->x, last_pt);
+ }
+ else {
+ total_length += len_v2v2(&pt->x, last_pt);
+ }
+ last_pt = &pt->x;
+ }
+ return total_length;
+}
+
/**
* Trim stroke to the first intersection or loop.
* \param gps: Stroke data
@@ -2640,4 +2668,560 @@ void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps)
copy_v4_v4(pt->vert_color, color);
}
}
+
+/* Flip stroke. */
+void BKE_gpencil_stroke_flip(bGPDstroke *gps)
+{
+ int end = gps->totpoints - 1;
+
+ for (int i = 0; i < gps->totpoints / 2; i++) {
+ bGPDspoint *point, *point2;
+ bGPDspoint pt;
+
+ /* save first point */
+ point = &gps->points[i];
+ pt.x = point->x;
+ pt.y = point->y;
+ pt.z = point->z;
+ pt.flag = point->flag;
+ pt.pressure = point->pressure;
+ pt.strength = point->strength;
+ pt.time = point->time;
+ copy_v4_v4(pt.vert_color, point->vert_color);
+
+ /* replace first point with last point */
+ point2 = &gps->points[end];
+ point->x = point2->x;
+ point->y = point2->y;
+ point->z = point2->z;
+ point->flag = point2->flag;
+ point->pressure = point2->pressure;
+ point->strength = point2->strength;
+ point->time = point2->time;
+ copy_v4_v4(point->vert_color, point2->vert_color);
+
+ /* replace last point with first saved before */
+ point = &gps->points[end];
+ point->x = pt.x;
+ point->y = pt.y;
+ point->z = pt.z;
+ point->flag = pt.flag;
+ point->pressure = pt.pressure;
+ point->strength = pt.strength;
+ point->time = pt.time;
+ copy_v4_v4(point->vert_color, pt.vert_color);
+
+ end--;
+ }
+}
+
+/* Temp data for storing information about an "island" of points
+ * that should be kept when splitting up a stroke. Used in:
+ * gpencil_stroke_delete_tagged_points()
+ */
+typedef struct tGPDeleteIsland {
+ int start_idx;
+ int end_idx;
+} tGPDeleteIsland;
+
+static void gpencil_stroke_join_islands(bGPdata *gpd,
+ bGPDframe *gpf,
+ bGPDstroke *gps_first,
+ bGPDstroke *gps_last)
+{
+ bGPDspoint *pt = NULL;
+ bGPDspoint *pt_final = NULL;
+ const int totpoints = gps_first->totpoints + gps_last->totpoints;
+
+ /* create new stroke */
+ bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true);
+
+ join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
+ join_stroke->totpoints = totpoints;
+ join_stroke->flag &= ~GP_STROKE_CYCLIC;
+
+ /* copy points (last before) */
+ int e1 = 0;
+ int e2 = 0;
+ float delta = 0.0f;
+
+ for (int i = 0; i < totpoints; i++) {
+ pt_final = &join_stroke->points[i];
+ if (i < gps_last->totpoints) {
+ pt = &gps_last->points[e1];
+ e1++;
+ }
+ else {
+ pt = &gps_first->points[e2];
+ e2++;
+ }
+
+ /* copy current point */
+ copy_v3_v3(&pt_final->x, &pt->x);
+ pt_final->pressure = pt->pressure;
+ pt_final->strength = pt->strength;
+ pt_final->time = delta;
+ pt_final->flag = pt->flag;
+ copy_v4_v4(pt_final->vert_color, pt->vert_color);
+
+ /* retiming with fixed time interval (we cannot determine real time) */
+ delta += 0.01f;
+ }
+
+ /* Copy over vertex weight data (if available) */
+ if ((gps_first->dvert != NULL) || (gps_last->dvert != NULL)) {
+ join_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
+ MDeformVert *dvert_src = NULL;
+ MDeformVert *dvert_dst = NULL;
+
+ /* Copy weights (last before)*/
+ e1 = 0;
+ e2 = 0;
+ for (int i = 0; i < totpoints; i++) {
+ dvert_dst = &join_stroke->dvert[i];
+ dvert_src = NULL;
+ if (i < gps_last->totpoints) {
+ if (gps_last->dvert) {
+ dvert_src = &gps_last->dvert[e1];
+ e1++;
+ }
+ }
+ else {
+ if (gps_first->dvert) {
+ dvert_src = &gps_first->dvert[e2];
+ e2++;
+ }
+ }
+
+ if ((dvert_src) && (dvert_src->dw)) {
+ dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
+ }
+ }
+ }
+
+ /* add new stroke at head */
+ BLI_addhead(&gpf->strokes, join_stroke);
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gpd, join_stroke);
+
+ /* remove first stroke */
+ BLI_remlink(&gpf->strokes, gps_first);
+ BKE_gpencil_free_stroke(gps_first);
+
+ /* remove last stroke */
+ BLI_remlink(&gpf->strokes, gps_last);
+ BKE_gpencil_free_stroke(gps_last);
+}
+
+/* Split the given stroke into several new strokes, partitioning
+ * it based on whether the stroke points have a particular flag
+ * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
+ *
+ * The algorithm used here is as follows:
+ * 1) We firstly identify the number of "islands" of non-tagged points
+ * which will all end up being in new strokes.
+ * - In the most extreme case (i.e. every other vert is a 1-vert island),
+ * we have at most n / 2 islands
+ * - Once we start having larger islands than that, the number required
+ * becomes much less
+ * 2) Each island gets converted to a new stroke
+ * If the number of points is <= limit, the stroke is deleted
+ */
+bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
+ bGPDframe *gpf,
+ bGPDstroke *gps,
+ bGPDstroke *next_stroke,
+ int tag_flags,
+ bool select,
+ int limit)
+{
+ tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2,
+ "gp_point_islands");
+ bool in_island = false;
+ int num_islands = 0;
+
+ bGPDstroke *new_stroke = NULL;
+ bGPDstroke *gps_first = NULL;
+ const bool is_cyclic = (bool)(gps->flag & GP_STROKE_CYCLIC);
+
+ /* First Pass: Identify start/end of islands */
+ bGPDspoint *pt = gps->points;
+ for (int i = 0; i < gps->totpoints; i++, pt++) {
+ if (pt->flag & tag_flags) {
+ /* selected - stop accumulating to island */
+ in_island = false;
+ }
+ else {
+ /* unselected - start of a new island? */
+ int idx;
+
+ if (in_island) {
+ /* extend existing island */
+ idx = num_islands - 1;
+ islands[idx].end_idx = i;
+ }
+ else {
+ /* start of new island */
+ in_island = true;
+ num_islands++;
+
+ idx = num_islands - 1;
+ islands[idx].start_idx = islands[idx].end_idx = i;
+ }
+ }
+ }
+
+ /* Watch out for special case where No islands = All points selected = Delete Stroke only */
+ if (num_islands) {
+ /* There are islands, so create a series of new strokes,
+ * adding them before the "next" stroke. */
+ int idx;
+
+ /* Create each new stroke... */
+ for (idx = 0; idx < num_islands; idx++) {
+ tGPDeleteIsland *island = &islands[idx];
+ new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true);
+
+ /* if cyclic and first stroke, save to join later */
+ if ((is_cyclic) && (gps_first == NULL)) {
+ gps_first = new_stroke;
+ }
+
+ new_stroke->flag &= ~GP_STROKE_CYCLIC;
+
+ /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
+ new_stroke->totpoints = island->end_idx - island->start_idx + 1;
+
+ /* Copy over the relevant point data */
+ new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints,
+ "gp delete stroke fragment");
+ memcpy(new_stroke->points,
+ gps->points + island->start_idx,
+ sizeof(bGPDspoint) * new_stroke->totpoints);
+
+ /* Copy over vertex weight data (if available) */
+ if (gps->dvert != NULL) {
+ /* Copy over the relevant vertex-weight points */
+ new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints,
+ "gp delete stroke fragment weight");
+ memcpy(new_stroke->dvert,
+ gps->dvert + island->start_idx,
+ sizeof(MDeformVert) * new_stroke->totpoints);
+
+ /* Copy weights */
+ int e = island->start_idx;
+ for (int i = 0; i < new_stroke->totpoints; i++) {
+ MDeformVert *dvert_src = &gps->dvert[e];
+ MDeformVert *dvert_dst = &new_stroke->dvert[i];
+ if (dvert_src->dw) {
+ dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
+ }
+ e++;
+ }
+ }
+ /* Each island corresponds to a new stroke.
+ * We must adjust the timings of these new strokes:
+ *
+ * Each point's timing data is a delta from stroke's inittime, so as we erase some points
+ * from the start of the stroke, we have to offset this inittime and all remaining points'
+ * delta values. This way we get a new stroke with exactly the same timing as if user had
+ * started drawing from the first non-removed point.
+ */
+ {
+ bGPDspoint *pts;
+ float delta = gps->points[island->start_idx].time;
+ int j;
+
+ new_stroke->inittime += (double)delta;
+
+ pts = new_stroke->points;
+ for (j = 0; j < new_stroke->totpoints; j++, pts++) {
+ pts->time -= delta;
+ /* set flag for select again later */
+ if (select == true) {
+ pts->flag &= ~GP_SPOINT_SELECT;
+ pts->flag |= GP_SPOINT_TAG;
+ }
+ }
+ }
+
+ /* Add new stroke to the frame or delete if below limit */
+ if ((limit > 0) && (new_stroke->totpoints <= limit)) {
+ BKE_gpencil_free_stroke(new_stroke);
+ }
+ else {
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
+
+ if (next_stroke) {
+ BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+ }
+ else {
+ BLI_addtail(&gpf->strokes, new_stroke);
+ }
+ }
+ }
+ /* if cyclic, need to join last stroke with first stroke */
+ if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) {
+ gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke);
+ }
+ }
+
+ /* free islands */
+ MEM_freeN(islands);
+
+ /* Delete the old stroke */
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+
+ return new_stroke;
+}
+
+void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd,
+ bGPDframe *gpf,
+ bGPDstroke *gps,
+ bGPDstroke *next_stroke,
+ bGPDcurve *gpc,
+ int tag_flags)
+{
+ if (gpc == NULL) {
+ return;
+ }
+ const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC;
+ const int idx_last = gpc->tot_curve_points - 1;
+ bGPDstroke *gps_first = NULL;
+ bGPDstroke *gps_last = NULL;
+
+ int idx_start = 0;
+ int idx_end = 0;
+ bool prev_selected = gpc->curve_points[0].flag & tag_flags;
+ for (int i = 1; i < gpc->tot_curve_points; i++) {
+ bool selected = gpc->curve_points[i].flag & tag_flags;
+ if (prev_selected == true && selected == false) {
+ idx_start = i;
+ }
+ /* Island ends if the current point is selected or if we reached the end of the stroke */
+ if ((prev_selected == false && selected == true) || (selected == false && i == idx_last)) {
+
+ idx_end = selected ? i - 1 : i;
+ int island_length = idx_end - idx_start + 1;
+
+ /* If an island has only a single curve point, there is no curve segment, so skip island */
+ if (island_length == 1) {
+ if (is_cyclic) {
+ if (idx_start > 0 && idx_end < idx_last) {
+ prev_selected = selected;
+ continue;
+ }
+ }
+ else {
+ prev_selected = selected;
+ continue;
+ }
+ }
+
+ bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, false, false);
+ new_stroke->points = NULL;
+ new_stroke->flag &= ~GP_STROKE_CYCLIC;
+ new_stroke->editcurve = BKE_gpencil_stroke_editcurve_new(island_length);
+
+ if (gps_first == NULL) {
+ gps_first = new_stroke;
+ }
+
+ bGPDcurve *new_gpc = new_stroke->editcurve;
+ memcpy(new_gpc->curve_points,
+ gpc->curve_points + idx_start,
+ sizeof(bGPDcurve_point) * island_length);
+
+ BKE_gpencil_editcurve_recalculate_handles(new_stroke);
+ new_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
+
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
+
+ if (next_stroke) {
+ BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+ }
+ else {
+ BLI_addtail(&gpf->strokes, new_stroke);
+ }
+
+ gps_last = new_stroke;
+ }
+ prev_selected = selected;
+ }
+
+ /* join first and last stroke if cyclic */
+ if (is_cyclic && gps_first != NULL && gps_last != NULL && gps_first != gps_last) {
+ bGPDcurve *gpc_first = gps_first->editcurve;
+ bGPDcurve *gpc_last = gps_last->editcurve;
+ int first_tot_points = gpc_first->tot_curve_points;
+ int old_tot_points = gpc_last->tot_curve_points;
+
+ gpc_last->tot_curve_points = first_tot_points + old_tot_points;
+ gpc_last->curve_points = MEM_recallocN(gpc_last->curve_points,
+ sizeof(bGPDcurve_point) * gpc_last->tot_curve_points);
+ /* copy data from first to last */
+ memcpy(gpc_last->curve_points + old_tot_points,
+ gpc_first->curve_points,
+ sizeof(bGPDcurve_point) * first_tot_points);
+
+ BKE_gpencil_editcurve_recalculate_handles(gps_last);
+ gps_last->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
+
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gpd, gps_last);
+
+ /* remove first one */
+ BLI_remlink(&gpf->strokes, gps_first);
+ BKE_gpencil_free_stroke(gps_first);
+ }
+
+ /* Delete the old stroke */
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+}
+
+/* Helper: copy point between strokes */
+static void gpencil_stroke_copy_point(bGPDstroke *gps,
+ MDeformVert *dvert,
+ bGPDspoint *point,
+ const float delta[3],
+ float pressure,
+ float strength,
+ float deltatime)
+{
+ bGPDspoint *newpoint;
+
+ gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
+ if (gps->dvert != NULL) {
+ gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1));
+ }
+ else {
+ /* If destination has weight add weight to origin. */
+ if (dvert != NULL) {
+ gps->dvert = MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1), __func__);
+ }
+ }
+
+ gps->totpoints++;
+ newpoint = &gps->points[gps->totpoints - 1];
+
+ newpoint->x = point->x * delta[0];
+ newpoint->y = point->y * delta[1];
+ newpoint->z = point->z * delta[2];
+ newpoint->flag = point->flag;
+ newpoint->pressure = pressure;
+ newpoint->strength = strength;
+ newpoint->time = point->time + deltatime;
+ copy_v4_v4(newpoint->vert_color, point->vert_color);
+
+ if (gps->dvert != NULL) {
+ MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1];
+
+ if (dvert != NULL) {
+ newdvert->totweight = dvert->totweight;
+ newdvert->dw = MEM_dupallocN(dvert->dw);
+ }
+ else {
+ newdvert->totweight = 0;
+ newdvert->dw = NULL;
+ }
+ }
+}
+
+/* Join two strokes using the shortest distance (reorder stroke if necessary ) */
+void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
+ bGPDstroke *gps_b,
+ const bool leave_gaps,
+ const bool fit_thickness)
+{
+ bGPDspoint point;
+ bGPDspoint *pt;
+ int i;
+ const float delta[3] = {1.0f, 1.0f, 1.0f};
+ float deltatime = 0.0f;
+
+ /* sanity checks */
+ if (ELEM(NULL, gps_a, gps_b)) {
+ return;
+ }
+
+ if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) {
+ return;
+ }
+
+ /* define start and end points of each stroke */
+ float start_a[3], start_b[3], end_a[3], end_b[3];
+ pt = &gps_a->points[0];
+ copy_v3_v3(start_a, &pt->x);
+
+ pt = &gps_a->points[gps_a->totpoints - 1];
+ copy_v3_v3(end_a, &pt->x);
+
+ pt = &gps_b->points[0];
+ copy_v3_v3(start_b, &pt->x);
+
+ pt = &gps_b->points[gps_b->totpoints - 1];
+ copy_v3_v3(end_b, &pt->x);
+
+ /* Check if need flip strokes. */
+ float dist = len_squared_v3v3(end_a, start_b);
+ bool flip_a = false;
+ bool flip_b = false;
+ float lowest = dist;
+
+ dist = len_squared_v3v3(end_a, end_b);
+ if (dist < lowest) {
+ lowest = dist;
+ flip_a = false;
+ flip_b = true;
+ }
+
+ dist = len_squared_v3v3(start_a, start_b);
+ if (dist < lowest) {
+ lowest = dist;
+ flip_a = true;
+ flip_b = false;
+ }
+
+ dist = len_squared_v3v3(start_a, end_b);
+ if (dist < lowest) {
+ lowest = dist;
+ flip_a = true;
+ flip_b = true;
+ }
+
+ if (flip_a) {
+ BKE_gpencil_stroke_flip(gps_a);
+ }
+ if (flip_b) {
+ BKE_gpencil_stroke_flip(gps_b);
+ }
+
+ /* don't visibly link the first and last points? */
+ if (leave_gaps) {
+ /* 1st: add one tail point to start invisible area */
+ point = gps_a->points[gps_a->totpoints - 1];
+ deltatime = point.time;
+
+ gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, 0.0f);
+
+ /* 2nd: add one head point to finish invisible area */
+ point = gps_b->points[0];
+ gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, deltatime);
+ }
+
+ const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ?
+ (float)gps_b->thickness / (float)gps_a->thickness :
+ 1.0f;
+
+ /* 3rd: add all points */
+ for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
+ MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : NULL;
+ gpencil_stroke_copy_point(
+ gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime);
+ }
+}
/** \} */