diff options
Diffstat (limited to 'source/blender/editors')
-rw-r--r-- | source/blender/editors/gpencil/annotate_paint.c | 4 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_convert.c | 3 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_edit.c | 572 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_intern.h | 60 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_merge.c | 2 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_paint.c | 22 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_primitive.c | 25 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_utils.c | 178 | ||||
-rw-r--r-- | source/blender/editors/include/ED_gpencil.h | 18 |
9 files changed, 287 insertions, 597 deletions
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index a73bf8154d9..1b53141294e 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -42,6 +42,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_report.h" @@ -1198,7 +1199,8 @@ static void annotation_stroke_eraser_dostroke(tGPsdata *p, /* Second Pass: Remove any points that are tagged */ if (do_cull) { - gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + BKE_gpencil_stroke_delete_tagged_points( + p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } } diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 293ce370a0b..33b02d525d5 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1860,7 +1860,8 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op) if (done) { /* Delete any selected point. */ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + BKE_gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } BKE_reportf(op->reports, RPT_INFO, "Object created"); diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index f2c3804a067..8ce3d176525 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -99,8 +99,6 @@ /** \name Stroke Edit Mode Management * \{ */ -static void gpencil_flip_stroke(bGPDstroke *gps); - /* poll callback for all stroke editing operators */ static bool gpencil_stroke_edit_poll(bContext *C) { @@ -1181,7 +1179,7 @@ static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gp /* Flip stroke if it was only one point to consider extrude point as last point. */ if (gps->totpoints == 2) { - gpencil_flip_stroke(gps); + BKE_gpencil_stroke_flip(gps); } /* Calc geometry data. */ @@ -2584,372 +2582,6 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) /* ----------------------------------- */ -/* 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 - */ -void 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 *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; - bGPDstroke *new_stroke = NULL; - - /* 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); -} - -static void 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); -} - /* Split selected strokes into segments, splitting on selected points */ static int gpencil_delete_selected_points(bContext *C) { @@ -2987,12 +2619,12 @@ static int gpencil_delete_selected_points(bContext *C) if (is_curve_edit) { bGPDcurve *gpc = gps->editcurve; - gpencil_curve_delete_tagged_points( + BKE_gpencil_curve_delete_tagged_points( gpd, gpf, gps, gps->next, gpc, GP_CURVE_POINT_SELECT); } else { /* delete unwanted points by splitting stroke into several smaller ones */ - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } @@ -3828,188 +3460,6 @@ void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot) /** \name Stroke Join Operator * \{ */ -/* Helper: flip stroke */ -static void gpencil_flip_stroke(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--; - } -} - -/* 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; - } - } -} - -/* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ -static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, - bGPDstroke *gps_b, - const bool leave_gaps) -{ - 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) { - gpencil_flip_stroke(gps_a); - } - if (flip_b) { - gpencil_flip_stroke(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); - } - - /* 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, pt->strength, deltatime); - } -} - typedef struct tJoinStrokes { bGPDframe *gpf; bGPDstroke *gps; @@ -4159,7 +3609,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } elem = &strokes_list[i]; /* Join new_stroke and stroke B. */ - gpencil_stroke_join_strokes(gps_new, elem->gps, leave_gaps); + BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true); elem->used = true; } @@ -4254,8 +3704,8 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_ERROR, "Not implemented!"); } else { - /* flip stroke */ - gpencil_flip_stroke(gps); + /* Flip stroke. */ + BKE_gpencil_stroke_flip(gps); } changed = true; @@ -5108,11 +4558,11 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) } /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd_dst, gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd_src, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } } @@ -5287,11 +4737,11 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) } /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd, gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } } @@ -5487,7 +4937,7 @@ static void gpencil_cutter_dissolve(bGPdata *gpd, } } - gpencil_stroke_delete_tagged_points( + BKE_gpencil_stroke_delete_tagged_points( gpd, hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); } } diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index e4e9a3ae0ab..0bdd2033491 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -152,6 +152,31 @@ typedef struct tGPDinterpolate { NumInput num; /* numeric input */ } tGPDinterpolate; +/* Modal Operator Drawing Callbacks ------------------------ */ +void ED_gpencil_draw_fill(struct tGPDdraw *tgpw); + +/* ***************************************************** */ +/* Internal API */ + +/* Stroke Coordinates API ------------------------------ */ +/* gpencil_utils.c */ + +typedef struct GP_SpaceConversion { + struct Scene *scene; + struct Object *ob; + struct bGPdata *gpd; + struct bGPDlayer *gpl; + + struct ScrArea *area; + struct ARegion *region; + struct View2D *v2d; + + rctf *subrect; /* for using the camera rect within the 3d view */ + rctf subrect_data; + + float mat[4][4]; /* transform matrix on the strokes (introduced in [b770964]) */ +} GP_SpaceConversion; + /* Temporary primitive operation data */ typedef struct tGPDprimitive { /** main database pointer */ @@ -180,6 +205,9 @@ typedef struct tGPDprimitive { /** current brush */ struct Brush *brush; + /** Settings to pass to gp_points_to_xy(). */ + GP_SpaceConversion gsc; + /** current frame number */ int cframe; /** layer */ @@ -248,31 +276,6 @@ typedef struct tGPDprimitive { } tGPDprimitive; -/* Modal Operator Drawing Callbacks ------------------------ */ -void ED_gpencil_draw_fill(struct tGPDdraw *tgpw); - -/* ***************************************************** */ -/* Internal API */ - -/* Stroke Coordinates API ------------------------------ */ -/* gpencil_utils.c */ - -typedef struct GP_SpaceConversion { - struct Scene *scene; - struct Object *ob; - struct bGPdata *gpd; - struct bGPDlayer *gpl; - - struct ScrArea *area; - struct ARegion *region; - struct View2D *v2d; - - rctf *subrect; /* for using the camera rect within the 3d view */ - rctf subrect_data; - - float mat[4][4]; /* transform matrix on the strokes (introduced in [b770964]) */ -} GP_SpaceConversion; - bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, int x1, int y1); void gpencil_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc); @@ -343,13 +346,6 @@ struct GHash *gpencil_copybuf_validate_colormap(struct bContext *C); /* Stroke Editing ------------------------------------ */ -void gpencil_stroke_delete_tagged_points(bGPdata *gpd, - bGPDframe *gpf, - bGPDstroke *gps, - bGPDstroke *next_stroke, - int tag_flags, - bool select, - int limit); int gpencil_delete_selected_point_wrap(bContext *C); void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide); diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index f795ed01bb8..9f2bf3818a4 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -182,7 +182,7 @@ static void gpencil_dissolve_points(bContext *C) } LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + BKE_gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } CTX_DATA_END; diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index e544093cd1d..14313a50118 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1282,6 +1282,25 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) BKE_gpencil_stroke_trim(gpd, gps); } + /* Join with existing strokes. */ + if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) { + if (gps->prev != NULL) { + int pt_index = 0; + bool doit = true; + while (doit && gps) { + bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends( + p->C, &p->gsc, gpl, gpl->actframe, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index); + if (gps_target != NULL) { + gps = ED_gpencil_stroke_join_and_trim(p->gpd, p->gpf, gps, gps_target, pt_index); + } + else { + doit = false; + } + } + } + ED_gpencil_stroke_close_by_distance(gps, 0.02f); + } + /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, gps); @@ -1652,7 +1671,8 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, gpencil_stroke_soft_refine(gps); } - gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + BKE_gpencil_stroke_delete_tagged_points( + p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } gpencil_update_cache(p->gpd); } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 801dacb3e6b..b457cd819d2 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1197,6 +1197,9 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) /* set GP datablock */ tgpi->gpd = gpd; + /* Setup space conversions. */ + gpencil_point_conversion_init(C, &tgpi->gsc); + /* if brush doesn't exist, create a new set (fix damaged files from old versions) */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { BKE_brush_gpencil_paint_presets(bmain, ts, true); @@ -1347,6 +1350,28 @@ static void gpencil_primitive_interaction_end(bContext *C, } } + /* Join previous stroke. */ + if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) { + if (ELEM(tgpi->type, GP_STROKE_ARC, GP_STROKE_LINE, GP_STROKE_CURVE, GP_STROKE_POLYLINE)) { + if (gps->prev != NULL) { + int pt_index = 0; + bool doit = true; + while (doit && gps) { + bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends( + C, &tgpi->gsc, tgpi->gpl, gpf, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index); + if (gps_target != NULL) { + gps = ED_gpencil_stroke_join_and_trim(tgpi->gpd, gpf, gps, gps_target, pt_index); + } + else { + doit = false; + } + } + } + ED_gpencil_stroke_close_by_distance(gps, 0.02f); + } + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps); + } + DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE); DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 628a35f8c76..af63c5ca3b2 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -3105,3 +3105,181 @@ bool ED_gpencil_stroke_point_is_inside(bGPDstroke *gps, return hit; } + +bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C, + GP_SpaceConversion *gsc, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps, + const float radius, + int *r_index) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = CTX_data_active_object(C); + bGPDstroke *gps_rtn = NULL; + const float radius_sqr = radius * radius; + + /* calculate difference matrix object */ + float diff_mat[4][4]; + BKE_gpencil_parent_matrix_get(depsgraph, ob, gpl, diff_mat); + + /* Calculate the extremes of the stroke in 2D. */ + bGPDspoint pt_parent; + float pt2d_start[2], pt2d_end[2]; + + bGPDspoint *pt = &gps->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); + gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_start[0], &pt2d_start[1]); + + pt = &gps->points[gps->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); + gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_end[0], &pt2d_end[1]); + + /* Loop all strokes of the active frame. */ + float dist_min = FLT_MAX; + LISTBASE_FOREACH (bGPDstroke *, gps_target, &gpf->strokes) { + /* Check if the color is editable. */ + if ((gps_target == gps) || (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)) { + continue; + } + + /* Check if one of the ends is inside target stroke bounding box. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, pt2d_start, radius, diff_mat)) { + continue; + } + if (!ED_gpencil_stroke_check_collision(gsc, gps, pt2d_end, radius, diff_mat)) { + continue; + } + /* Check the distance of the ends with the ends of target stroke to avoid middle contact. + * All is done in 2D plane. */ + float pt2d_target_start[2], pt2d_target_end[2]; + + pt = &gps_target->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); + gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_start[0], &pt2d_target_start[1]); + + pt = &gps_target->points[gps_target->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); + gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]); + + if ((len_squared_v2v2(pt2d_start, pt2d_target_start) > radius_sqr) && + (len_squared_v2v2(pt2d_start, pt2d_target_end) > radius_sqr) && + (len_squared_v2v2(pt2d_end, pt2d_target_start) > radius_sqr) && + (len_squared_v2v2(pt2d_end, pt2d_target_end) > radius_sqr)) { + continue; + } + + /* Loop all points and check what is the nearest point. */ + int i; + for (i = 0, pt = gps_target->points; i < gps_target->totpoints; i++, pt++) { + /* Convert point to 2D. */ + float pt2d[2]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_parent); + gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d[0], &pt2d[1]); + + /* Check with Start point. */ + float dist = len_squared_v2v2(pt2d, pt2d_start); + if ((dist <= radius_sqr) && (dist < dist_min)) { + *r_index = i; + dist_min = dist; + gps_rtn = gps_target; + } + /* Check with End point. */ + dist = len_squared_v2v2(pt2d, pt2d_end); + if ((dist <= radius_sqr) && (dist < dist_min)) { + *r_index = i; + dist_min = dist; + gps_rtn = gps_target; + } + } + } + + return gps_rtn; +} + +/* Join two stroke using a contact point index and trimming the rest. */ +bGPDstroke *ED_gpencil_stroke_join_and_trim( + bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *gps_dst, const int pt_index) +{ + if ((gps->totpoints < 1) || (gps_dst->totpoints < 1)) { + return false; + } + BLI_assert(pt_index >= 0 && pt_index < gps_dst->totpoints); + + bGPDspoint *pt = NULL; + + /* Cannot be cyclic. */ + gps->flag &= ~GP_STROKE_CYCLIC; + gps_dst->flag &= ~GP_STROKE_CYCLIC; + + /* Trim stroke. */ + bGPDstroke *gps_final = gps_dst; + if ((pt_index > 0) && (pt_index < gps_dst->totpoints - 2)) { + /* Untag any pending operation. */ + gps_dst->flag &= ~GP_STROKE_TAG; + for (int i = 0; i < gps_dst->totpoints; i++) { + gps_dst->points[i].flag &= ~GP_SPOINT_TAG; + } + + /* Delete points of the shorter extreme */ + pt = &gps_dst->points[0]; + float dist_to_start = BKE_gpencil_stroke_segment_length(gps_dst, 0, pt_index, true); + pt = &gps_dst->points[gps_dst->totpoints - 1]; + float dist_to_end = BKE_gpencil_stroke_segment_length( + gps_dst, pt_index, gps_dst->totpoints - 1, true); + + if (dist_to_start < dist_to_end) { + for (int i = 0; i < pt_index; i++) { + gps_dst->points[i].flag |= GP_SPOINT_TAG; + } + } + else { + for (int i = pt_index + 1; i < gps_dst->totpoints; i++) { + gps_dst->points[i].flag |= GP_SPOINT_TAG; + } + } + /* Remove tagged points to trim stroke. */ + gps_final = BKE_gpencil_stroke_delete_tagged_points( + gpd, gpf, gps_dst, gps_dst->next, GP_SPOINT_TAG, false, 0); + } + + /* Join both strokes. */ + int totpoint = gps_final->totpoints; + BKE_gpencil_stroke_join(gps_final, gps, false, true); + + /* Select the join points and merge if the distance is very small. */ + pt = &gps_final->points[totpoint - 1]; + pt->flag |= GP_SPOINT_SELECT; + + pt = &gps_final->points[totpoint]; + pt->flag |= GP_SPOINT_SELECT; + BKE_gpencil_stroke_merge_distance(gpd, gpf, gps_final, 0.01f, false); + + /* Unselect all points. */ + for (int i = 0; i < gps_final->totpoints; i++) { + gps_final->points[i].flag &= ~GP_SPOINT_SELECT; + } + + /* Delete old stroke. */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + + return gps_final; +} + +/* Close if the distance between extremes is below threshold. */ +void ED_gpencil_stroke_close_by_distance(bGPDstroke *gps, const float threshold) +{ + if (gps == NULL) { + return; + } + bGPDspoint *pt_start = &gps->points[0]; + bGPDspoint *pt_end = &gps->points[gps->totpoints - 1]; + + const float threshold_sqr = threshold * threshold; + float dist_to_close = len_squared_v3v3(&pt_start->x, &pt_end->x); + if (dist_to_close < threshold_sqr) { + gps->flag |= GP_STROKE_CYCLIC; + BKE_gpencil_stroke_close(gps); + } +} diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 17aa407bd76..be2f714dfe1 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -63,6 +63,8 @@ struct bAnimContext; struct wmKeyConfig; struct wmOperator; +#define GPENCIL_MINIMUM_JOIN_DIST 20.0f + /* Reproject stroke modes. */ typedef enum eGP_ReprojectModes { /* Axis */ @@ -366,6 +368,22 @@ bool ED_gpencil_stroke_point_is_inside(struct bGPDstroke *gps, int mouse[2], const float diff_mat[4][4]); +struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C, + struct GP_SpaceConversion *gsc, + struct bGPDlayer *gpl, + struct bGPDframe *gpf, + struct bGPDstroke *gps, + const float radius, + int *r_index); + +struct bGPDstroke *ED_gpencil_stroke_join_and_trim(struct bGPdata *gpd, + struct bGPDframe *gpf, + struct bGPDstroke *gps, + struct bGPDstroke *gps_dst, + const int pt_index); + +void ED_gpencil_stroke_close_by_distance(struct bGPDstroke *gps, const float threshold); + #ifdef __cplusplus } #endif |