From 392855ce5022ccfeaa52efc04b13a7208ed0dc3f Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Fri, 23 Sep 2022 18:49:54 +0200 Subject: [PATCH] GPencil: Improve Fill Tool Extend lines * Improve how the extend lines collision is calculated. * Added `S` key to switch between modes. * Now extend factor does not disable visual aids (thi sis done with checkbox). * Reduce the use of linked list and now memory array is used. * Refactor Radius functions. * Fixed bug of Radius mode when object is rotated. * Cleanup code. Differential Revision: https://developer.blender.org/D16022 --- .../startup/bl_ui/space_view3d_toolbar.py | 2 - source/blender/editors/gpencil/gpencil_fill.c | 613 ++++++++++++------ source/blender/makesdna/DNA_brush_enums.h | 4 +- source/blender/makesrna/intern/rna_brush.c | 2 +- 4 files changed, 408 insertions(+), 213 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index ecc72e1bbb8..95185c2a2f5 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1889,10 +1889,8 @@ class VIEW3D_PT_tools_grease_pencil_brush_gap_closure(View3DPanel, Panel): col.prop(gp_settings, "extend_stroke_factor", text="Size") row = col.row(align=True) - row.enabled = gp_settings.extend_stroke_factor > 0 row.prop(gp_settings, "fill_extend_mode", text="Mode") row = col.row(align=True) - row.enabled = gp_settings.extend_stroke_factor > 0 row.prop(gp_settings, "show_fill_extend", text="Visual Aids") diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 775c17e1c98..c173a30a736 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -79,6 +79,20 @@ enum { GP_DRAWFILLS_ONLY3D = (1 << 1), /* only draw 3d-strokes */ }; +/* Temporary stroke data including stroke extensions. */ +typedef struct tStroke { + /* Referenced layer. */ + bGPDlayer *gpl; + /** Referenced frame. */ + bGPDframe *gpf; + /** Referenced stroke. */ + bGPDstroke *gps; + /** Extreme Stroke A. */ + bGPDstroke *gps_ext_a; + /** Extreme Stroke B. */ + bGPDstroke *gps_ext_b; +} tStroke; + /* Temporary fill operation data `op->customdata`. */ typedef struct tGPDfill { bContext *C; @@ -115,7 +129,7 @@ typedef struct tGPDfill { /** For operations that require occlusion testing. */ struct ViewDepths *depths; /** flags */ - short flag; + int flag; /** avoid too fast events */ short oldkey; /** send to back stroke */ @@ -160,7 +174,7 @@ typedef struct tGPDfill { Image *ima; /** temp points data */ BLI_Stack *stack; - /** handle for drawing strokes while operator is running 3d stuff */ + /** handle for drawing strokes while operator is running 3d stuff. */ void *draw_handle_3d; /* Temporary size x. */ @@ -177,12 +191,28 @@ typedef struct tGPDfill { /** Factor of extension. */ float fill_extend_fac; - + /** Size of stroke_array. */ + int stroke_array_num; + /** Temp strokes array to handle strokes and stroke extensions. */ + tStroke **stroke_array; } tGPDfill; bool skip_layer_check(short fill_layer_mode, int gpl_active_index, int gpl_index); static void gpencil_draw_boundary_lines(const struct bContext *UNUSED(C), struct tGPDfill *tgpf); +/* Free temp stroke array. */ +static void stroke_array_free(tGPDfill *tgpf) +{ + if (tgpf->stroke_array) { + for (int i = 0; i < tgpf->stroke_array_num; i++) { + tStroke *stroke = tgpf->stroke_array[i]; + MEM_freeN(stroke); + } + MEM_SAFE_FREE(tgpf->stroke_array); + } + tgpf->stroke_array_num = 0; +} + /* Delete any temporary stroke. */ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_frames) { @@ -243,7 +273,8 @@ static void add_stroke_extension(bGPDframe *gpf, bGPDstroke *gps, float p1[3], f pt->pressure = 1.0f; } -static void add_endpoint_radius_help(bGPDframe *gpf, +static void add_endpoint_radius_help(tGPDfill *tgpf, + bGPDframe *gpf, bGPDstroke *gps, const float endpoint[3], const float radius, @@ -268,6 +299,11 @@ static void add_endpoint_radius_help(bGPDframe *gpf, pt->z = endpoint[2] + radius * sinf(angle); pt->strength = 1.0f; pt->pressure = 1.0f; + + /* Rotate to object rotation. */ + sub_v3_v3(&pt->x, endpoint); + mul_mat3_m4_v3(tgpf->ob->obmat, &pt->x); + add_v3_v3(&pt->x, endpoint); } } @@ -283,13 +319,20 @@ static void extrapolate_points_by_length(bGPDspoint *a, add_v3_v3v3(r_point, &b->x, ab); } -/* Cut the extended lines if collide. */ -static void gpencil_cut_extensions(tGPDfill *tgpf, const int gpl_active_index) +/* Calculate the size of the array for strokes. */ +static void gpencil_strokes_array_size(tGPDfill *tgpf) { bGPdata *gpd = tgpf->gpd; Brush *brush = tgpf->brush; BrushGpencilSettings *brush_settings = brush->gpencil_settings; + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); + BLI_assert(gpl_active != NULL); + + const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); + BLI_assert(gpl_active_index >= 0); + + tgpf->stroke_array_num = 0; LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_HIDE) { continue; @@ -306,78 +349,12 @@ static void gpencil_cut_extensions(tGPDfill *tgpf, const int gpl_active_index) if (gpf == NULL) { continue; } - int size = BLI_listbase_count(&gpf->strokes); - if (size == 0) { - continue; - } - - float diff_mat[4][4]; - BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, tgpf->ob, gpl, diff_mat); - /* Save all extend strokes in array.*/ - int tot_idx = 0; - bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * size, __func__); - - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if ((gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) == 0) { - continue; - } - gps_array[tot_idx] = gps; - tot_idx++; - } - - /* Compare all strokes. */ - for (int i = 0; i < tot_idx; i++) { - bGPDstroke *gps_a = gps_array[i]; - - bGPDspoint pt2; - float a1xy[2], a2xy[2]; - float b1xy[2], b2xy[2]; - - /* First stroke. */ - bGPDspoint *pt = &gps_a->points[0]; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a1xy[0], &a1xy[1]); - - pt = &gps_a->points[1]; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a2xy[0], &a2xy[1]); - bGPDspoint *extreme_a = &gps_a->points[1]; - - /* Loop all strokes. */ - for (int z = 0; z < tot_idx; z++) { - bGPDstroke *gps_b = gps_array[z]; - if (i == z) { - continue; - } - pt = &gps_b->points[0]; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b1xy[0], &b1xy[1]); - - pt = &gps_b->points[1]; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b2xy[0], &b2xy[1]); - bGPDspoint *extreme_b = &gps_b->points[1]; - - /* Check if extensions collide and cut the overlength. */ - if (isect_seg_seg_v2_simple(a1xy, a2xy, b1xy, b2xy)) { - float intersection[2]; - isect_line_line_v2_point(a1xy, a2xy, b1xy, b2xy, intersection); - float intersection3D[3]; - gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, intersection, intersection3D); - copy_v3_v3(&extreme_a->x, intersection3D); - copy_v3_v3(&extreme_b->x, intersection3D); - gps_a->flag |= GP_STROKE_COLLIDE; - gps_b->flag |= GP_STROKE_COLLIDE; - } - } - } - - MEM_SAFE_FREE(gps_array); + tgpf->stroke_array_num += BLI_listbase_count(&gpf->strokes); } } -/* Loop all layers create stroke extensions. */ -static void gpencil_create_extensions(tGPDfill *tgpf) +/* Load all strokes to be procesed by extend lines. */ +static void gpencil_load_array_strokes(tGPDfill *tgpf) { Object *ob = tgpf->ob; bGPdata *gpd = tgpf->gpd; @@ -390,9 +367,14 @@ static void gpencil_create_extensions(tGPDfill *tgpf) const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); BLI_assert(gpl_active_index >= 0); - float connection_dist = tgpf->fill_extend_fac * 0.1f; - GSet *connected_endpoints = BLI_gset_ptr_new(__func__); + /* Create array of strokes. */ + gpencil_strokes_array_size(tgpf); + if (tgpf->stroke_array_num == 0) { + return; + } + tgpf->stroke_array = MEM_callocN(sizeof(tStroke *) * tgpf->stroke_array_num, __func__); + int idx = 0; LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_HIDE) { continue; @@ -415,180 +397,379 @@ static void gpencil_create_extensions(tGPDfill *tgpf) if ((gps->points == NULL) || (gps->totpoints < 2)) { continue; } - if (gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) { - continue; - } /* Check if the color is visible. */ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) { continue; } - - /* Find points of high curvature. */ - float tan1[3]; - float tan2[3]; - float d1; - float d2; - float total_length = 0.0f; - for (int i = 1; i < gps->totpoints; i++) { - if (i > 1) { - copy_v3_v3(tan1, tan2); - d1 = d2; - } - bGPDspoint *pt1 = &gps->points[i - 1]; - bGPDspoint *pt2 = &gps->points[i]; - sub_v3_v3v3(tan2, &pt2->x, &pt1->x); - d2 = normalize_v3(tan2); - total_length += d2; - if (i > 1) { - if (tgpf->fill_extend_mode == GP_FILL_EMODE_RADIUS) { - continue; - } - float curvature[3]; - sub_v3_v3v3(curvature, tan2, tan1); - float k = normalize_v3(curvature); - k /= min_ff(d1, d2); - float radius = 1.0f / k; - /* - * The smaller the radius of curvature, the sharper the corner. - * The thicker the line, the larger the radius of curvature it - * takes to be visually indistinguishable from an endpoint. - */ - float min_radius = gps->thickness * 0.0001f; - - if (radius < min_radius) { - /* Extend along direction of curvature. */ - bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); - - bGPDspoint *pt = &gps_new->points[0]; - copy_v3_v3(&pt->x, &pt1->x); - pt->strength = 1.0f; - pt->pressure = 1.0f; - - pt = &gps_new->points[1]; - pt->strength = 1.0f; - pt->pressure = 1.0f; - mul_v3_fl(curvature, -connection_dist); - add_v3_v3v3(&pt->x, &pt1->x, curvature); - } - } + /* Don't include temp strokes. */ + if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) { + continue; } - if (tgpf->fill_extend_mode != GP_FILL_EMODE_RADIUS) { + tStroke *stroke = MEM_callocN(sizeof(tStroke), "temp stroke data"); + stroke->gpl = gpl; + stroke->gpf = gpf; + stroke->gps = gps; + + /* Create the extension strokes only for Lines. */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { /* Extend start. */ bGPDspoint *pt0 = &gps->points[1]; bGPDspoint *pt1 = &gps->points[0]; - bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); + stroke->gps_ext_a = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + stroke->gps_ext_a->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + stroke->gps_ext_a->fill_opacity_fac = FLT_MAX; + BLI_addtail(&gpf->strokes, stroke->gps_ext_a); - bGPDspoint *pt = &gps_new->points[0]; + bGPDspoint *pt = &stroke->gps_ext_a->points[0]; copy_v3_v3(&pt->x, &pt1->x); pt->strength = 1.0f; pt->pressure = 1.0f; - pt = &gps_new->points[1]; + pt = &stroke->gps_ext_a->points[1]; pt->strength = 1.0f; pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); /* Extend end. */ pt0 = &gps->points[gps->totpoints - 2]; pt1 = &gps->points[gps->totpoints - 1]; - gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); + stroke->gps_ext_b = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + stroke->gps_ext_b->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + stroke->gps_ext_b->fill_opacity_fac = FLT_MAX; + BLI_addtail(&gpf->strokes, stroke->gps_ext_b); - pt = &gps_new->points[0]; + pt = &stroke->gps_ext_b->points[0]; copy_v3_v3(&pt->x, &pt1->x); pt->strength = 1.0f; pt->pressure = 1.0f; - pt = &gps_new->points[1]; + pt = &stroke->gps_ext_b->points[1]; pt->strength = 1.0f; pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + } + else { + stroke->gps_ext_a = NULL; + stroke->gps_ext_b = NULL; } - /* Connect endpoints within a radius */ - if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + tgpf->stroke_array[idx] = stroke; + + idx++; + } + } + tgpf->stroke_array_num = idx; +} + +static void set_stroke_collide(bGPDstroke *gps_a, bGPDstroke *gps_b, const float connection_dist) +{ + gps_a->flag |= GP_STROKE_COLLIDE; + gps_b->flag |= GP_STROKE_COLLIDE; + + /* It uses `fill_opacity_fac` to store distance because this variable is never + * used by this type of strokes and can be used for these + * temp strokes without adding new variables to the bGPStroke struct. */ + gps_a->fill_opacity_fac = connection_dist; + gps_b->fill_opacity_fac = connection_dist; +} + +/* Cut the extended lines if collide. */ +static void gpencil_cut_extensions(tGPDfill *tgpf) +{ + const float connection_dist = tgpf->fill_extend_fac * 0.1f; + + bGPDlayer *gpl_prev = NULL; + bGPDframe *gpf_prev = NULL; + float diff_mat[4][4], inv_mat[4][4]; + + /* Allocate memory for all extend strokes. */ + bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * tgpf->stroke_array_num * 2, + __func__); + + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDframe *gpf = stroke->gpf; + if (stroke->gpl != gpl_prev) { + BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, tgpf->ob, stroke->gpl, diff_mat); + invert_m4_m4(inv_mat, diff_mat); + gpl_prev = stroke->gpl; + } + + if (gpf == gpf_prev) { + continue; + } + gpf_prev = gpf; + + /* Store all frame extend strokes in an array. */ + int tot_idx = 0; + for (int i = 0; i < tgpf->stroke_array_num; i++) { + tStroke *s = tgpf->stroke_array[i]; + if (s->gpf != gpf) { continue; } - float *stroke1_start = &gps->points[0].x; - float *stroke1_end = &gps->points[gps->totpoints - 1].x; - /* Connect the start of the stroke to its own end if the whole stroke - * isn't already so short that it's within that distance - */ - if (len_v3v3(stroke1_start, stroke1_end) < connection_dist && - total_length > connection_dist) { - add_stroke_extension(gpf, gps, stroke1_start, stroke1_end); - BLI_gset_add(connected_endpoints, stroke1_start); - BLI_gset_add(connected_endpoints, stroke1_end); + if ((s->gps_ext_a) && ((s->gps_ext_a->flag & GP_STROKE_COLLIDE) == 0)) { + gps_array[tot_idx] = s->gps_ext_a; + tot_idx++; } - for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL; - gps2 = (bGPDstroke *)(((Link *)gps2)->next)) { - /* Don't check distance to temporary extensions. */ - if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) { + if ((s->gps_ext_b) && ((s->gps_ext_b->flag & GP_STROKE_COLLIDE) == 0)) { + gps_array[tot_idx] = s->gps_ext_b; + tot_idx++; + } + } + + /* Compare all strokes. */ + for (int i = 0; i < tot_idx; i++) { + bGPDstroke *gps_a = gps_array[i]; + + bGPDspoint pt2; + float a1xy[2], a2xy[2]; + float b1xy[2], b2xy[2]; + + /* First stroke. */ + bGPDspoint *pt = &gps_a->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a1xy[0], &a1xy[1]); + + pt = &gps_a->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a2xy[0], &a2xy[1]); + bGPDspoint *extreme_a = &gps_a->points[1]; + + /* Loop all other strokes and check the intersections. */ + for (int z = 0; z < tot_idx; z++) { + bGPDstroke *gps_b = gps_array[z]; + /* Don't check stroke with itself. */ + if (i == z) { continue; } - /* Don't check endpoint distances unless the bounding boxes of the strokes - are close enough together that they can plausibly be connected. */ - if (!extended_bbox_overlap(gps->boundbox_min, - gps->boundbox_max, - gps2->boundbox_min, - gps2->boundbox_max, + /* Don't check strokes unless the bounding boxes of the strokes + * are close enough together that they can plausibly be connected. */ + if (!extended_bbox_overlap(gps_a->boundbox_min, + gps_a->boundbox_max, + gps_b->boundbox_min, + gps_b->boundbox_max, connection_dist)) { continue; } - float *stroke2_start = &gps2->points[0].x; - float *stroke2_end = &gps2->points[gps2->totpoints - 1].x; - if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) { - add_stroke_extension(gpf, gps, stroke1_start, stroke2_start); - BLI_gset_add(connected_endpoints, stroke1_start); - BLI_gset_add(connected_endpoints, stroke2_start); + pt = &gps_b->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b1xy[0], &b1xy[1]); + + pt = &gps_b->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b2xy[0], &b2xy[1]); + bGPDspoint *extreme_b = &gps_b->points[1]; + + /* Check if extreme points are near. This case is when the + * extendend lines are colinear or parallel and close together. */ + const float gap_pixsize_sq = 25.0f; + float intersection3D[3]; + if (len_squared_v2v2(a2xy, b2xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b2xy, intersection3D); + mul_m4_v3(inv_mat, intersection3D); + copy_v3_v3(&extreme_a->x, intersection3D); + copy_v3_v3(&extreme_b->x, intersection3D); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; } - if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) { - add_stroke_extension(gpf, gps, stroke1_start, stroke2_end); - BLI_gset_add(connected_endpoints, stroke1_start); - BLI_gset_add(connected_endpoints, stroke2_end); + /* Check if extensions cross. */ + if (isect_seg_seg_v2_simple(a1xy, a2xy, b1xy, b2xy)) { + float intersection[2]; + isect_line_line_v2_point(a1xy, a2xy, b1xy, b2xy, intersection); + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, intersection, intersection3D); + mul_m4_v3(inv_mat, intersection3D); + copy_v3_v3(&extreme_a->x, intersection3D); + copy_v3_v3(&extreme_b->x, intersection3D); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; } - if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) { - add_stroke_extension(gpf, gps, stroke1_end, stroke2_start); - BLI_gset_add(connected_endpoints, stroke1_end); - BLI_gset_add(connected_endpoints, stroke2_start); + /* Check if extension extreme is near of the origin of any other extension. */ + if (len_squared_v2v2(a2xy, b1xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b1xy, &extreme_a->x); + mul_m4_v3(inv_mat, &extreme_a->x); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; } - if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) { - add_stroke_extension(gpf, gps, stroke1_end, stroke2_end); - BLI_gset_add(connected_endpoints, stroke1_end); - BLI_gset_add(connected_endpoints, stroke2_end); + if (len_squared_v2v2(a1xy, b2xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, a1xy, &extreme_b->x); + mul_m4_v3(inv_mat, &extreme_b->x); + set_stroke_collide(gps_a, gps_b, connection_dist); } } + } + } + MEM_SAFE_FREE(gps_array); +} - bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start); - bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end); - add_endpoint_radius_help(gpf, gps, stroke1_start, connection_dist, start_connected); - add_endpoint_radius_help(gpf, gps, stroke1_end, connection_dist, end_connected); +/* Loop all strokes and update stroke line extensions. */ +static void gpencil_update_extensions_line(tGPDfill *tgpf) +{ + float connection_dist = tgpf->fill_extend_fac * 0.1f; + + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDstroke *gps = stroke->gps; + bGPDstroke *gps_a = stroke->gps_ext_a; + bGPDstroke *gps_b = stroke->gps_ext_b; + + /* Extend start. */ + if (((gps_a->flag & GP_STROKE_COLLIDE) == 0) || (gps_a->fill_opacity_fac > connection_dist)) { + bGPDspoint *pt0 = &gps->points[1]; + bGPDspoint *pt1 = &gps->points[0]; + bGPDspoint *pt = &gps_a->points[1]; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + gps_a->flag &= ~GP_STROKE_COLLIDE; + } + + /* Extend end. */ + if (((gps_b->flag & GP_STROKE_COLLIDE) == 0) || (gps_b->fill_opacity_fac > connection_dist)) { + bGPDspoint *pt0 = &gps->points[gps->totpoints - 2]; + bGPDspoint *pt1 = &gps->points[gps->totpoints - 1]; + bGPDspoint *pt = &gps_b->points[1]; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + gps_b->flag &= ~GP_STROKE_COLLIDE; } } - BLI_gset_free(connected_endpoints, NULL); - /* Cut overlength strokes. */ - if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { - gpencil_cut_extensions(tgpf, gpl_active_index); + gpencil_cut_extensions(tgpf); +} + +/* Loop all strokes and create stroke radius extensions. */ +static void gpencil_create_extensions_radius(tGPDfill *tgpf) +{ + float connection_dist = tgpf->fill_extend_fac * 0.1f; + GSet *connected_endpoints = BLI_gset_ptr_new(__func__); + + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDframe *gpf = stroke->gpf; + bGPDstroke *gps = stroke->gps; + + /* Find points of high curvature. */ + float tan1[3]; + float tan2[3]; + float d1; + float d2; + float total_length = 0.f; + for (int i = 1; i < gps->totpoints; i++) { + if (i > 1) { + copy_v3_v3(tan1, tan2); + d1 = d2; + } + bGPDspoint *pt1 = &gps->points[i - 1]; + bGPDspoint *pt2 = &gps->points[i]; + sub_v3_v3v3(tan2, &pt2->x, &pt1->x); + d2 = normalize_v3(tan2); + total_length += d2; + if (i > 1) { + float curvature[3]; + sub_v3_v3v3(curvature, tan2, tan1); + float k = normalize_v3(curvature); + k /= min_ff(d1, d2); + float radius = 1.f / k; + /* + * The smaller the radius of curvature, the sharper the corner. + * The thicker the line, the larger the radius of curvature it + * takes to be visually indistinguishable from an endpoint. + */ + float min_radius = gps->thickness * 0.0001f; + + if (radius < min_radius) { + /* Extend along direction of curvature. */ + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + mul_v3_fl(curvature, -connection_dist); + add_v3_v3v3(&pt->x, &pt1->x, curvature); + } + } + } + + /* Connect endpoints within a radius */ + float *stroke1_start = &gps->points[0].x; + float *stroke1_end = &gps->points[gps->totpoints - 1].x; + /* Connect the start of the stroke to its own end if the whole stroke + * isn't already so short that it's within that distance + */ + if (len_v3v3(stroke1_start, stroke1_end) < connection_dist && total_length > connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke1_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke1_end); + } + for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL; + gps2 = (bGPDstroke *)(((Link *)gps2)->next)) { + /* Don't check distance to temporary extensions. */ + if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) { + continue; + } + + /* Don't check endpoint distances unless the bounding boxes of the strokes + are close enough together that they can plausibly be connected. */ + if (!extended_bbox_overlap(gps->boundbox_min, + gps->boundbox_max, + gps2->boundbox_min, + gps2->boundbox_max, + connection_dist)) { + continue; + } + + float *stroke2_start = &gps2->points[0].x; + float *stroke2_end = &gps2->points[gps2->totpoints - 1].x; + if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_end); + } + if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_end); + } + } + + bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start); + bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end); + add_endpoint_radius_help(tgpf, gpf, gps, stroke1_start, connection_dist, start_connected); + add_endpoint_radius_help(tgpf, gpf, gps, stroke1_end, connection_dist, end_connected); } + + BLI_gset_free(connected_endpoints, NULL); } static void gpencil_update_extend(tGPDfill *tgpf) { - gpencil_delete_temp_stroke_extension(tgpf, false); + if (tgpf->stroke_array == NULL) { + gpencil_load_array_strokes(tgpf); + } - if (tgpf->fill_extend_fac > 0.0f) { - gpencil_create_extensions(tgpf); + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + gpencil_update_extensions_line(tgpf); + } + else { + gpencil_delete_temp_stroke_extension(tgpf, false); + gpencil_create_extensions_radius(tgpf); } WM_event_add_notifier(tgpf->C, NC_GPENCIL | NA_EDITED, NULL); } @@ -661,12 +842,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, col[3] = (gps->flag & GP_STROKE_TAG) ? 0.0f : 0.5f; } else if ((is_extend) && (!tgpf->is_render)) { - if ((gps->flag & GP_STROKE_COLLIDE) || (tgpf->fill_extend_mode == GP_FILL_EMODE_RADIUS)) { - copy_v4_v4(col, extend_col); - } - else { - copy_v4_v4(col, help_col); - } + copy_v4_v4(col, extend_col); } else { copy_v4_v4(col, ink); @@ -1980,7 +2156,8 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* Helper: Draw status message while the user is running the operator */ static void gpencil_fill_status_indicators(bContext *C) { - const char *status_str = TIP_("Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back"); + const char *status_str = TIP_( + "Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back, S: Switch Mode"); ED_workspace_status_text(C, status_str); } @@ -2088,6 +2265,9 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op) int totcol = tgpf->ob->totcol; + /* Extensions array */ + tgpf->stroke_array = NULL; + /* get color info */ Material *ma = BKE_gpencil_object_material_ensure_from_active_input_brush( bmain, tgpf->ob, brush); @@ -2136,6 +2316,9 @@ static void gpencil_fill_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(tgpf->sbuffer); MEM_SAFE_FREE(tgpf->depth_arr); + /* Clean temp strokes. */ + stroke_array_free(tgpf); + /* Remove any temp stroke. */ gpencil_delete_temp_stroke_extension(tgpf, true); @@ -2232,9 +2415,8 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE tgpf = op->customdata; /* Enable custom drawing handlers to show help lines */ - const bool do_extend = (tgpf->fill_extend_fac > 0.0f); - const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || - ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend)); if (help_lines) { tgpf->draw_handle_3d = ED_region_draw_cb_activate( @@ -2483,9 +2665,8 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) const bool is_inverted = (is_brush_inv && (event->modifier & KM_CTRL) == 0) || (!is_brush_inv && (event->modifier & KM_CTRL) != 0); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(tgpf->gpd); - const bool do_extend = (tgpf->fill_extend_fac > 0.0f); - const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || - ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend)); int estate = OPERATOR_RUNNING_MODAL; switch (event->type) { @@ -2619,6 +2800,22 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) } tgpf->oldkey = event->type; break; + case EVT_SKEY: + if ((do_extend) && (event->val == KM_PRESS)) { + /* Clean temp strokes. */ + stroke_array_free(tgpf); + + /* Toogle mode */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + tgpf->fill_extend_mode = GP_FILL_EMODE_RADIUS; + } + else { + tgpf->fill_extend_mode = GP_FILL_EMODE_EXTEND; + } + gpencil_delete_temp_stroke_extension(tgpf, true); + gpencil_update_extend(tgpf); + } + break; case EVT_PAGEUPKEY: case WHEELUPMOUSE: if (tgpf->oldkey == 1) { diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 97abf0fec5e..476000d8885 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -127,8 +127,8 @@ typedef enum eGP_FillDrawModes { /* BrushGpencilSettings->fill_extend_mode */ typedef enum eGP_FillExtendModes { - GP_FILL_EMODE_RADIUS = 0, - GP_FILL_EMODE_EXTEND = 1, + GP_FILL_EMODE_EXTEND = 0, + GP_FILL_EMODE_RADIUS = 1, } eGP_FillExtendModes; /* BrushGpencilSettings->fill_layer_mode */ diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index bd0e14e4b59..a50264e64db 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -287,8 +287,8 @@ static EnumPropertyItem rna_enum_gpencil_fill_draw_modes_items[] = { {0, NULL, 0, NULL, NULL}}; static EnumPropertyItem rna_enum_gpencil_fill_extend_modes_items[] = { - {GP_FILL_EMODE_RADIUS, "RADIUS", 0, "Radius", "Connect endpoints that are close together"}, {GP_FILL_EMODE_EXTEND, "EXTEND", 0, "Extend", "Extend strokes in straight lines"}, + {GP_FILL_EMODE_RADIUS, "RADIUS", 0, "Radius", "Connect endpoints that are close together"}, {0, NULL, 0, NULL, NULL}}; static EnumPropertyItem rna_enum_gpencil_fill_layers_modes_items[] = {