GPencil: Smooth thickness when joining strokes

When joining two strokes in paint mode using the auto merge option, the join was very hard if the thickness was too different.

This patch adds a smooth to the join in order to get better transition.

Also fixed the problem to join existing strokes very far from actual stroke.

Some cleanup and rename of old code is included in order to make code more readable.

Reviewed By: pepeland

Differential Revision: https://developer.blender.org/D12362
This commit is contained in:
Antonio Vazquez 2021-09-03 15:24:01 +02:00
parent f9ccd26b03
commit ae334532cf
11 changed files with 111 additions and 17 deletions

View File

@ -101,7 +101,7 @@ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd,
struct bGPDstroke *gps,
const float dist,
const bool select);
bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf);
bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf);
bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence);
@ -151,7 +151,8 @@ 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);
const bool fit_thickness,
const bool smooth);
void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd,
struct bGPDlayer *gpl,
struct bGPDframe *gpf,

View File

@ -800,7 +800,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
* \param i: Point index
* \param inf: Amount of smoothing to apply
*/
bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf)
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf)
{
bGPDspoint *pt = &gps->points[i];
float sco[3] = {0.0f};
@ -3248,7 +3248,8 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps,
void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
bGPDstroke *gps_b,
const bool leave_gaps,
const bool fit_thickness)
const bool fit_thickness,
const bool smooth)
{
bGPDspoint point;
bGPDspoint *pt;
@ -3326,16 +3327,51 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, deltatime);
}
/* Ratio to apply in the points to keep the same thickness in the joined stroke using the
* destination stroke thickness. */
const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ?
(float)gps_b->thickness / (float)gps_a->thickness :
1.0f;
/* 3rd: add all points */
const int totpoints_a = gps_a->totpoints;
for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : nullptr;
gpencil_stroke_copy_point(
gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime);
}
/* Smooth the join to avoid hard thickness changes. */
if (smooth) {
const int sample_points = 8;
/* Get the segment to smooth using n points on each side of the join. */
int start = MAX2(0, totpoints_a - sample_points);
int end = MIN2(gps_a->totpoints - 1, start + (sample_points * 2));
const int len = (end - start);
float step = 1.0f / ((len / 2) + 1);
/* Calc the average pressure. */
float avg_pressure = 0.0f;
for (i = start; i < end; i++) {
pt = &gps_a->points[i];
avg_pressure += pt->pressure;
}
avg_pressure = avg_pressure / len;
/* Smooth segment thickness and position. */
float ratio = step;
for (i = start; i < end; i++) {
pt = &gps_a->points[i];
pt->pressure += (avg_pressure - pt->pressure) * ratio;
BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f);
ratio += step;
/* In the center, reverse the ratio. */
if (ratio > 1.0f) {
ratio = ratio - step - step;
step *= -1.0f;
}
}
}
}
/* Copy the stroke of the frame to all frames selected (except current). */

View File

@ -3618,7 +3618,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op)
}
elem = &strokes_list[i];
/* Join new_stroke and stroke B. */
BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true);
BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false);
elem->used = true;
}
@ -3967,7 +3967,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op)
/* perform smoothing */
if (smooth_position) {
BKE_gpencil_stroke_smooth(gps, i, factor);
BKE_gpencil_stroke_smooth_point(gps, i, factor);
}
if (smooth_strength) {
BKE_gpencil_stroke_smooth_strength(gps, i, factor);

View File

@ -1569,7 +1569,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf)
float smoothfac = 1.0f;
for (int r = 0; r < 1; r++) {
for (int i = 0; i < gps->totpoints; i++) {
BKE_gpencil_stroke_smooth(gps, i, smoothfac - reduce);
BKE_gpencil_stroke_smooth_point(gps, i, smoothfac - reduce);
}
reduce += 0.25f; /* reduce the factor */
}

View File

@ -333,7 +333,7 @@ static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps,
float reduce = 0.0f;
for (int r = 0; r < smooth_steps; r++) {
for (int i = 0; i < gps->totpoints - 1; i++) {
BKE_gpencil_stroke_smooth(gps, i, smooth_factor - reduce);
BKE_gpencil_stroke_smooth_point(gps, i, smooth_factor - reduce);
BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor);
}
reduce += 0.25f; /* reduce the factor */

View File

@ -1209,7 +1209,8 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
float reduce = 0.0f;
for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) {
for (i = 0; i < gps->totpoints - 1; i++) {
BKE_gpencil_stroke_smooth(gps, i, brush->gpencil_settings->draw_smoothfac - reduce);
BKE_gpencil_stroke_smooth_point(
gps, i, brush->gpencil_settings->draw_smoothfac - reduce);
BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac);
}
reduce += 0.25f; /* reduce the factor */
@ -1221,7 +1222,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
float ifac = (float)brush->gpencil_settings->input_samples / 10.0f;
float sfac = interpf(1.0f, 0.2f, ifac);
for (i = 0; i < gps->totpoints - 1; i++) {
BKE_gpencil_stroke_smooth(gps, i, sfac);
BKE_gpencil_stroke_smooth_point(gps, i, sfac);
BKE_gpencil_stroke_smooth_strength(gps, i, sfac);
}
}
@ -1288,11 +1289,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
/* Join with existing strokes. */
if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) {
if (gps->prev != NULL) {
BKE_gpencil_stroke_boundingbox_calc(gps);
float diff_mat[4][4], ctrl1[2], ctrl2[2];
BKE_gpencil_layer_transform_matrix_get(depsgraph, p->ob, gpl, diff_mat);
ED_gpencil_stroke_extremes_to2d(&p->gsc, diff_mat, gps, ctrl1, ctrl2);
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);
bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(p->C,
&p->gsc,
gpl,
gpl->actframe,
gps,
ctrl1,
ctrl2,
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);
}

View File

@ -1382,11 +1382,23 @@ static void gpencil_primitive_interaction_end(bContext *C,
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) {
BKE_gpencil_stroke_boundingbox_calc(gps);
float diff_mat[4][4], ctrl1[2], ctrl2[2];
BKE_gpencil_layer_transform_matrix_get(tgpi->depsgraph, tgpi->ob, tgpi->gpl, diff_mat);
ED_gpencil_stroke_extremes_to2d(&tgpi->gsc, diff_mat, gps, ctrl1, ctrl2);
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);
bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(C,
&tgpi->gsc,
tgpi->gpl,
gpf,
gps,
ctrl1,
ctrl2,
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);
}

View File

@ -337,7 +337,7 @@ static bool gpencil_brush_smooth_apply(tGP_BrushEditData *gso,
/* perform smoothing */
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
BKE_gpencil_stroke_smooth(gps, pt_index, inf);
BKE_gpencil_stroke_smooth_point(gps, pt_index, inf);
}
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf);

View File

@ -3213,11 +3213,28 @@ bool ED_gpencil_stroke_point_is_inside(const bGPDstroke *gps,
return hit;
}
/* Get extremes of stroke in 2D using current view. */
void ED_gpencil_stroke_extremes_to2d(const GP_SpaceConversion *gsc,
const float diff_mat[4][4],
bGPDstroke *gps,
float r_ctrl1[2],
float r_ctrl2[2])
{
bGPDspoint pt_dummy_ps;
gpencil_point_to_parent_space(&gps->points[0], diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl1[0], &r_ctrl1[1]);
gpencil_point_to_parent_space(&gps->points[gps->totpoints - 1], diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl2[0], &r_ctrl2[1]);
}
bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
const GP_SpaceConversion *gsc,
bGPDlayer *gpl,
bGPDframe *gpf,
bGPDstroke *gps,
const float ctrl1[2],
const float ctrl2[2],
const float radius,
int *r_index)
{
@ -3267,6 +3284,14 @@ bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
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 the distance to the original stroke extremes is too big, the stroke must not be joined. */
if ((len_squared_v2v2(ctrl1, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(ctrl1, pt2d_target_end) > radius_sqr) &&
(len_squared_v2v2(ctrl2, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(ctrl2, pt2d_target_end) > radius_sqr)) {
continue;
}
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) &&
@ -3350,7 +3375,7 @@ bGPDstroke *ED_gpencil_stroke_join_and_trim(
/* Join both strokes. */
int totpoint = gps_final->totpoints;
BKE_gpencil_stroke_join(gps_final, gps, false, true);
BKE_gpencil_stroke_join(gps_final, gps, false, true, true);
/* Select the join points and merge if the distance is very small. */
pt = &gps_final->points[totpoint - 1];

View File

@ -391,8 +391,15 @@ struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C,
struct bGPDlayer *gpl,
struct bGPDframe *gpf,
struct bGPDstroke *gps,
const float ctrl1[2],
const float ctrl2[2],
const float radius,
int *r_index);
void ED_gpencil_stroke_extremes_to2d(const struct GP_SpaceConversion *gsc,
const float diff_mat[4][4],
struct bGPDstroke *gps,
float r_ctrl1[2],
float r_ctrl2[2]);
struct bGPDstroke *ED_gpencil_stroke_join_and_trim(struct bGPdata *gpd,
struct bGPDframe *gpf,

View File

@ -132,7 +132,7 @@ static void deformStroke(GpencilModifierData *md,
const float val = mmd->factor * weight;
/* perform smoothing */
if (mmd->flag & GP_SMOOTH_MOD_LOCATION) {
BKE_gpencil_stroke_smooth(gps, i, val);
BKE_gpencil_stroke_smooth_point(gps, i, val);
}
if (mmd->flag & GP_SMOOTH_MOD_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, i, val);