diff options
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil_geom.h | 25 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_geom.c | 584 | ||||
-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 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_types.h | 2 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_scene.c | 10 |
13 files changed, 908 insertions, 597 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); + } +} /** \} */ 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 diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index f65f8d32e97..85ec3dfdced 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2275,6 +2275,8 @@ typedef enum eGPencil_Flags { GP_TOOL_FLAG_THUMBNAIL_LIST = (1 << 3), /* Generate wheight data for new strokes */ GP_TOOL_FLAG_CREATE_WEIGHTS = (1 << 4), + /* Automerge with last stroke */ + GP_TOOL_FLAG_AUTOMERGE_STROKE = (1 << 5), } eGPencil_Flags; /* scene->r.simplify_gpencil */ diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index c132c434468..235042122e6 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -3273,6 +3273,16 @@ static void rna_def_tool_settings(BlenderRNA *brna) "if no vertex group selected, weight is not added"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + prop = RNA_def_property(srna, "use_gpencil_automerge_strokes", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_AUTOMERGE_STROKE); + RNA_def_property_boolean_default(prop, false); + RNA_def_property_ui_icon(prop, ICON_AUTOMERGE_OFF, 1); + RNA_def_property_ui_text( + prop, + "Automerge", + "Join by distance last drawn stroke with previous strokes in the active layer"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "gpencil_sculpt", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "gp_sculpt"); RNA_def_property_struct_type(prop, "GPencilSculptSettings"); |