GPencil: Improve smooth operation

This patch makes the grease pencil smooth operation symmetric.
It also increases the performance a lot if strong smoothing is
required. Additionally there is an option for the position smooth
operation to keep the shape closer to the original for more iterations.

Since the result differs from the previous algorithm, versioning is used
to change the iterations and factor to match the old result.

Differential Revision: http://developer.blender.org/D14325
This commit is contained in:
Henrik Dick 2022-03-25 11:51:45 +01:00
parent 0c33e84020
commit d4e1458db3
Notes: blender-bot 2023-02-13 15:49:10 +01:00
Referenced by commit 5eab5713c0, Fix T97019: Regression - GPencil Shrinkwrap modifier not longer working
Referenced by issue #99248, Regression: Single-Vertex Grease Pencil strokes not affected by Push and Grab sculpt tools
Referenced by issue #97019, Regression: Grease pencil Shrinkwrap modifier no longer working
14 changed files with 443 additions and 310 deletions

View File

@ -220,30 +220,78 @@ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd,
* \param gps: Stroke to smooth
* \param i: Point index
* \param inf: Amount of smoothing to apply
* \param iterations: Radius of points to consider, equivalent to iterations
* \param smooth_caps: Apply smooth to stroke extremes
* \param keep_shape: Smooth out fine details first
* \param r_gps: Stroke to put the result into
*/
bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf, bool smooth_caps);
bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps,
int point_index,
float influence,
int iterations,
bool smooth_caps,
bool keep_shape,
struct bGPDstroke *r_gps);
/**
* Apply smooth strength to stroke point.
* \param gps: Stroke to smooth
* \param point_index: Point index
* \param influence: Amount of smoothing to apply
* \param iterations: Radius of points to consider, equivalent to iterations
* \param r_gps: Stroke to put the result into
*/
bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps,
int point_index,
float influence,
int iterations,
struct bGPDstroke *r_gps);
/**
* Apply smooth for thickness to stroke point (use pressure).
* \param gps: Stroke to smooth
* \param point_index: Point index
* \param influence: Amount of smoothing to apply
* \param iterations: Radius of points to consider, equivalent to iterations
* \param r_gps: Stroke to put the result into
*/
bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps,
int point_index,
float influence,
int iterations,
struct bGPDstroke *r_gps);
/**
* Apply smooth for UV rotation to stroke point (use pressure).
* Apply smooth for UV rotation/factor to stroke point.
* \param gps: Stroke to smooth
* \param point_index: Point index
* \param influence: Amount of smoothing to apply
* \param iterations: Radius of points to consider, equivalent to iterations
* \param r_gps: Stroke to put the result into
*/
bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence);
bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps,
int point_index,
float influence,
int iterations,
struct bGPDstroke *r_gps);
/**
* Apply smooth operation to the stroke.
* \param gps: Stroke to smooth
* \param influence: The interpolation factor for the smooth and the original stroke
* \param iterations: Radius of points to consider, equivalent to iterations
* \param smooth_position: Smooth point locations
* \param smooth_strength: Smooth point strength
* \param smooth_thickness: Smooth point thickness
* \param smooth_uv: Smooth uv rotation/factor
* \param keep_shape: Use different distribution for smooth locations to keep the shape
* \param weights: per point weights to multiply influence with (optional, can be null)
*/
void BKE_gpencil_stroke_smooth(struct bGPDstroke *gps,
const float influence,
const int iterations,
const bool smooth_position,
const bool smooth_strength,
const bool smooth_thickness,
const bool smooth_uv,
const bool keep_shape,
const float *weights);
/**
* Close grease pencil stroke.
* \param gps: Stroke to close

View File

@ -980,74 +980,116 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
/** \name Stroke Smooth Positions
* \{ */
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf, const bool smooth_caps)
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
int i,
float influence,
int iterations,
const bool smooth_caps,
const bool keep_shape,
bGPDstroke *r_gps)
{
bGPDspoint *pt = &gps->points[i];
float sco[3] = {0.0f};
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* Do nothing if not enough points to smooth out */
if (gps->totpoints <= 2) {
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
return false;
}
/* Only affect endpoints by a fraction of the normal strength,
* to prevent the stroke from shrinking too much
/* Overview of the algorithm here and in the following smooth functions:
* The smooth functions return the new attribute in question for a single point.
* The result is stored in r_gps->points[i], while the data is read from gps.
* To get a correct result, duplicate the stroke point data and read from the copy,
* while writing to the real stroke. Not doing that will result in acceptable, but
* asymmetric results.
* This algorithm works as long as all points are being smoothed. If there is
* points that should not get smoothed, use the old repeat smooth pattern with
* the parameter "iterations" set to 1 or 2. (2 matches the old algorithm).
*/
if ((!smooth_caps) && (!is_cyclic && ELEM(i, 0, gps->totpoints - 1))) {
inf *= 0.1f;
const bGPDspoint *pt = &gps->points[i];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* If smooth_caps is false, the caps will not be translated by smoothing. */
if (!smooth_caps && !is_cyclic && ELEM(i, 0, gps->totpoints - 1)) {
copy_v3_v3(&r_gps->points[i].x, &pt->x);
return true;
}
/* Compute smoothed coordinate by taking the ones nearby */
/* XXX: This is potentially slow,
* and suffers from accumulation error as earlier points are handled before later ones. */
{
/* XXX: this is hardcoded to look at 2 points on either side of the current one
* (i.e. 5 items total). */
const int steps = 2;
const float average_fac = 1.0f / (float)(steps * 2 + 1);
int step;
/* This function uses a binomial kernel, which is the discrete version of gaussian blur.
* The weight for a vertex at the relative index i is
* w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n
* All weights together sum up to 1
* This is equivalent to doing multiple iterations of averaging neighbors,
* where n = iterations * 2 and -n/2 <= j <= n/2
*
* Now the problem is that nCr(n, j + n/2) is very hard to compute for n > 500, since even
* double precision isn't sufficient. A very good robust approximation for n > 20 is
* nCr(n, j + n/2) / 2^n = sqrt(2/(pi*n)) * exp(-2*j*j/n)
*
* There is one more problem left: The old smooth algorithm was doing a more aggressive
* smooth. To solve that problem, choose a different n/2, which does not match the range and
* normalize the weights on finish. This may cause some artifacts at low values.
*
* keep_shape is a new option to stop the stroke from severly deforming.
* It uses different partially negative weights.
* w = 2 * (nCr(n, j + n/2) / 2^n) - (nCr(3*n, j + n) / 2^(3*n))
* ~ 2 * sqrt(2/(pi*n)) * exp(-2*j*j/n) - sqrt(2/(pi*3*n)) * exp(-2*j*j/(3*n))
* All weigths still sum up to 1.
* Note these weights only work because the averaging is done in relative coordinates.
*/
float sco[3] = {0.0f, 0.0f, 0.0f};
float tmp[3];
const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
(iterations * iterations) / 4 + 2 * iterations + 12;
double w = keep_shape ? 2.0 : 1.0;
double w2 = keep_shape ?
(1.0 / M_SQRT3) * exp((2 * iterations * iterations) / (double)(n_half * 3)) :
0.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
int before = i - step;
int after = i + step;
float w_before = (float)(w - w2);
float w_after = (float)(w - w2);
/* add the point itself */
madd_v3_v3fl(sco, &pt->x, average_fac);
/* n-steps before/after current point */
/* XXX: review how the endpoints are treated by this algorithm. */
/* XXX: falloff measures should also introduce some weighting variations,
* so that further-out points get less weight. */
for (step = 1; step <= steps; step++) {
bGPDspoint *pt1, *pt2;
int before = i - step;
int after = i + step;
if (is_cyclic) {
if (before < 0) {
/* Sub to end point (before is already negative). */
before = gps->totpoints + before;
CLAMP(before, 0, gps->totpoints - 1);
}
if (after > gps->totpoints - 1) {
/* Add to start point. */
after = after - gps->totpoints;
CLAMP(after, 0, gps->totpoints - 1);
}
}
else {
CLAMP_MIN(before, 0);
CLAMP_MAX(after, gps->totpoints - 1);
}
pt1 = &gps->points[before];
pt2 = &gps->points[after];
/* add both these points to the average-sum (s += p[i]/n) */
madd_v3_v3fl(sco, &pt1->x, average_fac);
madd_v3_v3fl(sco, &pt2->x, average_fac);
if (is_cyclic) {
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
after = after % gps->totpoints;
}
else {
if (before < 0) {
if (!smooth_caps) {
w_before *= -before / (float)i;
}
before = 0;
}
if (after > gps->totpoints - 1) {
if (!smooth_caps) {
w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i);
}
after = gps->totpoints - 1;
}
}
}
/* Based on influence factor, blend between original and optimal smoothed coordinate */
interp_v3_v3v3(&pt->x, &pt->x, sco, inf);
/* Add both these points in relative coordinates to the weighted average sum. */
sub_v3_v3v3(tmp, &gps->points[before].x, &pt->x);
madd_v3_v3fl(sco, tmp, w_before);
sub_v3_v3v3(tmp, &gps->points[after].x, &pt->x);
madd_v3_v3fl(sco, tmp, w_after);
total_w += w_before;
total_w += w_after;
w *= (n_half + step) / (double)(n_half + 1 - step);
w2 *= (n_half * 3 + step) / (double)(n_half * 3 + 1 - step);
}
total_w += w - w2;
/* The accumulated weight total_w should be
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
* here, but sometimes not quite. */
mul_v3_fl(sco, (float)(1.0 / total_w));
/* Shift back to global coordinates. */
add_v3_v3(sco, &pt->x);
/* Based on influence factor, blend between original and optimal smoothed coordinate. */
interp_v3_v3v3(&r_gps->points[i].x, &pt->x, sco, influence);
return true;
}
@ -1058,74 +1100,54 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf, const bo
/** \name Stroke Smooth Strength
* \{ */
bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float influence)
bool BKE_gpencil_stroke_smooth_strength(
bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps)
{
bGPDspoint *ptb = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* Do nothing if not enough points */
if ((gps->totpoints <= 2) || (point_index < 1)) {
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
return false;
}
/* Only affect endpoints by a fraction of the normal influence */
float inf = influence;
if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
inf *= 0.01f;
}
/* Limit max influence to reduce pop effect. */
CLAMP_MAX(inf, 0.98f);
float total = 0.0f;
float max_strength = 0.0f;
const int steps = 4;
const float average_fac = 1.0f / (float)(steps * 2 + 1);
int step;
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
/* add the point itself */
total += ptb->strength * average_fac;
max_strength = ptb->strength;
/* n-steps before/after current point */
for (step = 1; step <= steps; step++) {
bGPDspoint *pt1, *pt2;
int before = point_index - step;
int after = point_index + step;
const bGPDspoint *pt = &gps->points[i];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
float strength = 0.0f;
const int n_half = (iterations * iterations) / 4 + iterations;
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
int before = i - step;
int after = i + step;
float w_before = (float)w;
float w_after = (float)w;
if (is_cyclic) {
if (before < 0) {
/* Sub to end point (before is already negative). */
before = gps->totpoints + before;
CLAMP(before, 0, gps->totpoints - 1);
}
if (after > gps->totpoints - 1) {
/* Add to start point. */
after = after - gps->totpoints;
CLAMP(after, 0, gps->totpoints - 1);
}
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
after = after % gps->totpoints;
}
else {
CLAMP_MIN(before, 0);
CLAMP_MAX(after, gps->totpoints - 1);
}
pt1 = &gps->points[before];
pt2 = &gps->points[after];
/* add both these points to the average-sum (s += p[i]/n) */
total += pt1->strength * average_fac;
total += pt2->strength * average_fac;
/* Save max value. */
if (max_strength < pt1->strength) {
max_strength = pt1->strength;
}
if (max_strength < pt2->strength) {
max_strength = pt2->strength;
}
/* Add both these points in relative coordinates to the weighted average sum. */
strength += w_before * (gps->points[before].strength - pt->strength);
strength += w_after * (gps->points[after].strength - pt->strength);
total_w += w_before;
total_w += w_after;
w *= (n_half + step) / (double)(n_half + 1 - step);
}
total_w += w;
/* The accumulated weight total_w should be
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
* here, but sometimes not quite. */
strength /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
ptb->strength = interpf(ptb->strength, total, inf);
/* Clamp to maximum stroke strength to avoid weird results. */
CLAMP_MAX(ptb->strength, max_strength);
r_gps->points[i].strength = pt->strength + strength * influence;
return true;
}
@ -1136,74 +1158,55 @@ bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float
/** \name Stroke Smooth Thickness
* \{ */
bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float influence)
bool BKE_gpencil_stroke_smooth_thickness(
bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps)
{
bGPDspoint *ptb = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* Do nothing if not enough points */
if ((gps->totpoints <= 2) || (point_index < 1)) {
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
return false;
}
/* Only affect endpoints by a fraction of the normal influence */
float inf = influence;
if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
inf *= 0.01f;
}
/* Limit max influence to reduce pop effect. */
CLAMP_MAX(inf, 0.98f);
float total = 0.0f;
float max_pressure = 0.0f;
const int steps = 4;
const float average_fac = 1.0f / (float)(steps * 2 + 1);
int step;
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
/* add the point itself */
total += ptb->pressure * average_fac;
max_pressure = ptb->pressure;
/* n-steps before/after current point */
for (step = 1; step <= steps; step++) {
bGPDspoint *pt1, *pt2;
int before = point_index - step;
int after = point_index + step;
const bGPDspoint *pt = &gps->points[i];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
float pressure = 0.0f;
const int n_half = (iterations * iterations) / 4 + iterations;
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
int before = i - step;
int after = i + step;
float w_before = (float)w;
float w_after = (float)w;
if (is_cyclic) {
if (before < 0) {
/* Sub to end point (before is already negative). */
before = gps->totpoints + before;
CLAMP(before, 0, gps->totpoints - 1);
}
if (after > gps->totpoints - 1) {
/* Add to start point. */
after = after - gps->totpoints;
CLAMP(after, 0, gps->totpoints - 1);
}
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
after = after % gps->totpoints;
}
else {
CLAMP_MIN(before, 0);
CLAMP_MAX(after, gps->totpoints - 1);
}
pt1 = &gps->points[before];
pt2 = &gps->points[after];
/* add both these points to the average-sum (s += p[i]/n) */
total += pt1->pressure * average_fac;
total += pt2->pressure * average_fac;
/* Save max value. */
if (max_pressure < pt1->pressure) {
max_pressure = pt1->pressure;
}
if (max_pressure < pt2->pressure) {
max_pressure = pt2->pressure;
}
/* Add both these points in relative coordinates to the weighted average sum. */
pressure += w_before * (gps->points[before].pressure - pt->pressure);
pressure += w_after * (gps->points[after].pressure - pt->pressure);
total_w += w_before;
total_w += w_after;
w *= (n_half + step) / (double)(n_half + 1 - step);
}
total_w += w;
/* The accumulated weight total_w should be
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
* here, but sometimes not quite. */
pressure /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
ptb->pressure = interpf(ptb->pressure, total, inf);
/* Clamp to maximum stroke thickness to avoid weird results. */
CLAMP_MAX(ptb->pressure, max_pressure);
r_gps->points[i].pressure = pt->pressure + pressure * influence;
return true;
}
@ -1213,57 +1216,127 @@ bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float
/** \name Stroke Smooth UV
* \{ */
bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influence)
bool BKE_gpencil_stroke_smooth_uv(
struct bGPDstroke *gps, int i, float influence, int iterations, struct bGPDstroke *r_gps)
{
bGPDspoint *ptb = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* Do nothing if not enough points */
if (gps->totpoints <= 2) {
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
return false;
}
/* Compute theoretical optimal value */
bGPDspoint *pta, *ptc;
int before = point_index - 1;
int after = point_index + 1;
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
if (is_cyclic) {
if (before < 0) {
/* Sub to end point (before is already negative). */
before = gps->totpoints + before;
CLAMP(before, 0, gps->totpoints - 1);
const bGPDspoint *pt = &gps->points[i];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* If don't change the caps. */
if (!is_cyclic && ELEM(i, 0, gps->totpoints - 1)) {
r_gps->points[i].uv_rot = pt->uv_rot;
r_gps->points[i].uv_fac = pt->uv_fac;
return true;
}
float uv_rot = 0.0f;
float uv_fac = 0.0f;
const int n_half = iterations * iterations + iterations;
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
int before = i - step;
int after = i + step;
float w_before = (float)w;
float w_after = (float)w;
if (is_cyclic) {
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
after = after % gps->totpoints;
}
if (after > gps->totpoints - 1) {
/* Add to start point. */
after = after - gps->totpoints;
CLAMP(after, 0, gps->totpoints - 1);
else {
if (before < 0) {
w_before *= -before / (float)i;
before = 0;
}
if (after > gps->totpoints - 1) {
w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i);
after = gps->totpoints - 1;
}
}
}
else {
CLAMP_MIN(before, 0);
CLAMP_MAX(after, gps->totpoints - 1);
}
pta = &gps->points[before];
ptc = &gps->points[after];
/* the optimal value is the corresponding to the interpolation of the pressure
* at the distance of point b
*/
float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x);
/* sometimes the factor can be wrong due stroke geometry, so use middle point */
if ((fac < 0.0f) || (fac > 1.0f)) {
fac = 0.5f;
}
float optimal = interpf(ptc->uv_rot, pta->uv_rot, fac);
/* Add both these points in relative coordinates to the weighted average sum. */
uv_rot += w_before * (gps->points[before].uv_rot - pt->uv_rot);
uv_rot += w_after * (gps->points[after].uv_rot - pt->uv_rot);
uv_fac += w_before * (gps->points[before].uv_fac - pt->uv_fac);
uv_fac += w_after * (gps->points[after].uv_fac - pt->uv_fac);
/* Based on influence factor, blend between original and optimal */
ptb->uv_rot = interpf(optimal, ptb->uv_rot, influence);
CLAMP(ptb->uv_rot, -M_PI_2, M_PI_2);
total_w += w_before;
total_w += w_after;
w *= (n_half + step) / (double)(n_half + 1 - step);
}
total_w += w;
/* The accumulated weight total_w should be
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
* here, but sometimes not quite. */
uv_rot /= total_w;
uv_fac /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
r_gps->points[i].uv_rot = pt->uv_rot + uv_rot * influence;
r_gps->points[i].uv_fac = pt->uv_fac + uv_fac * influence;
return true;
}
void BKE_gpencil_stroke_smooth(bGPDstroke *gps,
const float influence,
const int iterations,
const bool smooth_position,
const bool smooth_strength,
const bool smooth_thickness,
const bool smooth_uv,
const bool keep_shape,
const float *weights)
{
if (influence <= 0 || iterations <= 0) {
return;
}
/* Make a copy of the point data to avoid directionality of the smooth operation. */
bGPDstroke gps_old = *gps;
gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points);
/* Smooth stroke. */
for (int i = 0; i < gps->totpoints; i++) {
float val = influence;
if (weights != NULL) {
val *= weights[i];
if (val <= 0.0f) {
continue;
}
}
/* TODO: Currently the weights only control the influence, but is would be much better if they
* would control the distribution used in smooth, similar to how the ends are handled. */
/* Perform smoothing. */
if (smooth_position) {
BKE_gpencil_stroke_smooth_point(&gps_old, i, val, iterations, false, keep_shape, gps);
}
if (smooth_strength) {
BKE_gpencil_stroke_smooth_strength(&gps_old, i, val, iterations, gps);
}
if (smooth_thickness) {
BKE_gpencil_stroke_smooth_thickness(&gps_old, i, val, iterations, gps);
}
if (smooth_uv) {
BKE_gpencil_stroke_smooth_uv(&gps_old, i, val, iterations, gps);
}
}
/* Free the copied points array. */
MEM_freeN(gps_old.points);
}
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points,
int totpoints,
float (*points2d)[2],
@ -3443,7 +3516,7 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
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, false);
BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f, 2, false, true, gps_a);
ratio += step;
/* In the center, reverse the ratio. */

View File

@ -2415,6 +2415,24 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
/* Change grease pencil smooth iterations to match old results with new algorithm. */
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
if (md->type == eGpencilModifierType_Smooth) {
SmoothGpencilModifierData *gpmd = (SmoothGpencilModifierData *)md;
if (gpmd->step == 1 && gpmd->factor <= 0.5f) {
gpmd->factor *= 2.0f;
}
else {
gpmd->step = 1 + (int)(gpmd->factor * max_ff(0.0f,
min_ff(5.1f * sqrtf(gpmd->step) - 3.0f,
gpmd->step + 2.0f)));
gpmd->factor = 1.0f;
}
}
}
}
}
/**

View File

@ -3924,31 +3924,36 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op)
GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) {
if (gps->flag & GP_STROKE_SELECT) {
for (int r = 0; r < repeat; r++) {
/* TODO use `BKE_gpencil_stroke_smooth` when the weights are better used. */
bGPDstroke gps_old = *gps;
gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points);
/* Here the iteration needs to be done outside the smooth functions,
* as there are points that don't get smoothed. */
for (int n = 0; n < repeat; n++) {
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
if ((only_selected) && ((pt->flag & GP_SPOINT_SELECT) == 0)) {
if (only_selected && (gps->points[i].flag & GP_SPOINT_SELECT) == 0) {
continue;
}
/* perform smoothing */
/* Perform smoothing. */
if (smooth_position) {
BKE_gpencil_stroke_smooth_point(gps, i, factor, false);
BKE_gpencil_stroke_smooth_point(&gps_old, i, factor, 1, false, false, gps);
}
if (smooth_strength) {
BKE_gpencil_stroke_smooth_strength(gps, i, factor);
BKE_gpencil_stroke_smooth_strength(&gps_old, i, factor, 1, gps);
}
if (smooth_thickness) {
/* thickness need to repeat process several times */
for (int r2 = 0; r2 < repeat * 2; r2++) {
BKE_gpencil_stroke_smooth_thickness(gps, i, 1.0f - factor);
}
BKE_gpencil_stroke_smooth_thickness(&gps_old, i, 1.0f - factor, 1, gps);
}
if (smooth_uv) {
BKE_gpencil_stroke_smooth_uv(gps, i, factor);
BKE_gpencil_stroke_smooth_uv(&gps_old, i, factor, 1, gps);
}
}
if (n < repeat - 1) {
memcpy(gps_old.points, gps->points, sizeof(bGPDspoint) * gps->totpoints);
}
}
MEM_freeN(gps_old.points);
}
}
GP_EDITABLE_STROKES_END(gpstroke_iter);
@ -4926,10 +4931,10 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
prop = RNA_def_int(ot->srna, "repeat", 1, 1, 50, "Repeat", "", 1, 20);
prop = RNA_def_int(ot->srna, "repeat", 2, 1, 1000, "Repeat", "", 1, 1000);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 2.0f, "Factor", "", 0.0f, 2.0f);
RNA_def_float(ot->srna, "factor", 1.0f, 0.0f, 2.0f, "Factor", "", 0.0f, 1.0f);
RNA_def_boolean(ot->srna,
"only_selected",
true,

View File

@ -1638,14 +1638,9 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf)
}
}
/* smooth stroke */
float reduce = 0.0f;
float smoothfac = 1.0f;
for (int r = 0; r < 1; r++) {
for (int i = 0; i < gps->totpoints; i++) {
BKE_gpencil_stroke_smooth_point(gps, i, smoothfac - reduce, false);
}
reduce += 0.25f; /* reduce the factor */
/* Smooth stroke. No copy of the stroke since there only a minor improvement here. */
for (int i = 0; i < gps->totpoints; i++) {
BKE_gpencil_stroke_smooth_point(gps, i, 1.0f, 2, false, true, gps);
}
/* if axis locked, reproject to plane locked */

View File

@ -310,23 +310,6 @@ static void gpencil_stroke_pair_table(bContext *C,
}
}
static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps,
float smooth_factor,
int smooth_steps)
{
if (smooth_factor == 0.0f) {
return;
}
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_point(gps, i, smooth_factor - reduce, false);
BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor);
}
reduce += 0.25f; /* reduce the factor */
}
}
/* Perform interpolation */
static void gpencil_interpolate_update_points(const bGPDstroke *gps_from,
const bGPDstroke *gps_to,
@ -553,7 +536,15 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
/* Update points position. */
gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor);
gpencil_interpolate_smooth_stroke(new_stroke, tgpi->smooth_factor, tgpi->smooth_steps);
BKE_gpencil_stroke_smooth(new_stroke,
tgpi->smooth_factor,
tgpi->smooth_steps,
true,
true,
false,
false,
true,
NULL);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
@ -1385,7 +1376,8 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
/* Update points position. */
gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
gpencil_interpolate_smooth_stroke(new_stroke, smooth_factor, smooth_steps);
BKE_gpencil_stroke_smooth(
new_stroke, smooth_factor, smooth_steps, true, true, false, false, true, NULL);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, new_stroke);

View File

@ -1197,29 +1197,21 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
gpencil_subdivide_stroke(gpd, gps, subdivide);
}
/* Smooth stroke after subdiv - only if there's something to do for each iteration,
* the factor is reduced to get a better smoothing
* without changing too much the original stroke. */
if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) &&
(brush->gpencil_settings->draw_smoothfac > 0.0f)) {
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_point(
gps, i, brush->gpencil_settings->draw_smoothfac - reduce, false);
BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac);
}
reduce += 0.25f; /* reduce the factor */
}
/* Smooth stroke after subdiv - only if there's something to do for each iteration.
* Keep the original stroke shape as much as possible. */
const float smoothfac = brush->gpencil_settings->draw_smoothfac;
const int iterations = brush->gpencil_settings->draw_smoothlvl;
if (brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) {
BKE_gpencil_stroke_smooth(gps, smoothfac, iterations, true, true, false, false, true, NULL);
}
/* If reproject the stroke using Stroke mode, need to apply a smooth because
* the reprojection creates small jitter. */
if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) {
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_point(gps, i, sfac, false);
BKE_gpencil_stroke_smooth_strength(gps, i, sfac);
for (i = 0; i < gps->totpoints; i++) {
BKE_gpencil_stroke_smooth_point(gps, i, sfac, 2, false, true, gps);
BKE_gpencil_stroke_smooth_strength(gps, i, sfac, 2, gps);
}
}

View File

@ -329,16 +329,16 @@ 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_point(gps, pt_index, inf, false);
BKE_gpencil_stroke_smooth_point(gps, pt_index, inf, 2, false, false, gps);
}
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf);
BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf, 2, gps);
}
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) {
BKE_gpencil_stroke_smooth_thickness(gps, pt_index, inf);
BKE_gpencil_stroke_smooth_thickness(gps, pt_index, inf, 2, gps);
}
if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) {
BKE_gpencil_stroke_smooth_uv(gps, pt_index, inf);
BKE_gpencil_stroke_smooth_uv(gps, pt_index, inf, 2, gps);
}
return true;

View File

@ -105,15 +105,15 @@ static void deformStroke(GpencilModifierData *md,
/* Apply deformed coordinates. */
pt = gps->points;
bGPDstroke gps_old = *gps;
gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points);
for (i = 0; i < gps->totpoints; i++, pt++) {
copy_v3_v3(&pt->x, vert_coords[i]);
/* Smooth stroke. */
if (mmd->smooth_factor > 0.0f) {
for (int r = 0; r < mmd->smooth_step; r++) {
BKE_gpencil_stroke_smooth_point(gps, i, mmd->smooth_factor, true);
}
}
BKE_gpencil_stroke_smooth_point(
&gps_old, i, mmd->smooth_factor, mmd->smooth_step, true, false, gps);
}
MEM_freeN(gps_old.points);
MEM_freeN(vert_coords);

View File

@ -34,10 +34,14 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "RNA_access.h"
#include "MOD_gpencil_modifiertypes.h"
#include "MOD_gpencil_ui_common.h"
#include "MOD_gpencil_util.h"
#include "MEM_guardedalloc.h"
static void initData(GpencilModifierData *md)
{
SmoothGpencilModifierData *gpmd = (SmoothGpencilModifierData *)md;
@ -94,45 +98,40 @@ static void deformStroke(GpencilModifierData *md,
return;
}
/* smooth stroke */
if (mmd->factor > 0.0f) {
for (int r = 0; r < mmd->step; r++) {
for (int i = 0; i < gps->totpoints; i++) {
MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL;
if (mmd->factor <= 0.0f || mmd->step <= 0) {
return;
}
/* verify vertex group */
float weight = get_modifier_point_weight(
dvert, (mmd->flag & GP_SMOOTH_INVERT_VGROUP) != 0, def_nr);
if (weight < 0.0f) {
continue;
}
float *weights = NULL;
if (def_nr != -1 || use_curve) {
weights = MEM_malloc_arrayN(gps->totpoints, sizeof(*weights), __func__);
/* Calculate weights. */
for (int i = 0; i < gps->totpoints; i++) {
MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL;
/* Custom curve to modulate value. */
if (use_curve) {
float value = (float)i / (gps->totpoints - 1);
weight *= BKE_curvemapping_evaluateF(mmd->curve_intensity, 0, value);
}
/* Verify vertex group. */
float weight = get_modifier_point_weight(
dvert, (mmd->flag & GP_SMOOTH_INVERT_VGROUP) != 0, def_nr);
const float val = mmd->factor * weight;
/* perform smoothing */
if (mmd->flag & GP_SMOOTH_MOD_LOCATION) {
BKE_gpencil_stroke_smooth_point(gps, i, val, false);
}
if (mmd->flag & GP_SMOOTH_MOD_STRENGTH) {
BKE_gpencil_stroke_smooth_strength(gps, i, val);
}
if ((mmd->flag & GP_SMOOTH_MOD_THICKNESS) && (val > 0.0f)) {
/* thickness need to repeat process several times */
for (int r2 = 0; r2 < r * 10; r2++) {
BKE_gpencil_stroke_smooth_thickness(gps, i, val);
}
}
if (mmd->flag & GP_SMOOTH_MOD_UV) {
BKE_gpencil_stroke_smooth_uv(gps, i, val);
}
/* Custom curve to modulate value. */
if (use_curve && weight > 0.0f) {
float value = (float)i / (gps->totpoints - 1);
weight *= BKE_curvemapping_evaluateF(mmd->curve_intensity, 0, value);
}
weights[i] = weight;
}
}
BKE_gpencil_stroke_smooth(gps,
mmd->factor,
mmd->step,
mmd->flag & GP_SMOOTH_MOD_LOCATION,
mmd->flag & GP_SMOOTH_MOD_STRENGTH,
mmd->flag & GP_SMOOTH_MOD_THICKNESS,
mmd->flag & GP_SMOOTH_MOD_UV,
mmd->flag & GP_SMOOTH_KEEP_SHAPE,
weights);
MEM_SAFE_FREE(weights);
}
static void bakeModifier(struct Main *UNUSED(bmain),
@ -161,7 +160,7 @@ static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk,
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
uiLayout *row;
uiLayout *row, *col;
uiLayout *layout = panel->layout;
PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
@ -177,6 +176,10 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
uiItemR(layout, ptr, "factor", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "step", 0, IFACE_("Repeat"), ICON_NONE);
col = uiLayoutColumn(layout, false);
uiLayoutSetActive(col, RNA_boolean_get(ptr, "use_edit_position"));
uiItemR(col, ptr, "keep_shape", 0, NULL, ICON_NONE);
gpencil_modifier_panel_end(layout, ptr);
}

View File

@ -193,7 +193,7 @@
.vgname = "", \
.pass_index = 0, \
.flag = GP_SMOOTH_MOD_LOCATION, \
.factor = 0.5f, \
.factor = 1.0f, \
.step = 1, \
.layer_pass = 0, \
.curve_intensity = NULL, \

View File

@ -750,6 +750,7 @@ typedef enum eSmoothGpencil_Flag {
GP_SMOOTH_INVERT_LAYERPASS = (1 << 7),
GP_SMOOTH_INVERT_MATERIAL = (1 << 4),
GP_SMOOTH_CUSTOM_CURVE = (1 << 8),
GP_SMOOTH_KEEP_SHAPE = (1 << 9),
} eSmoothGpencil_Flag;
typedef struct ArmatureGpencilModifierData {

View File

@ -1355,7 +1355,8 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
/* Smoothing factor for new strokes */
prop = RNA_def_property(srna, "pen_smooth_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "draw_smoothfac");
RNA_def_property_range(prop, 0.0, 2.0f);
RNA_def_property_range(prop, 0.0, 2.0);
RNA_def_property_ui_range(prop, 0.0, 1.0, 10, 3);
RNA_def_property_ui_text(
prop,
"Smooth",
@ -1366,7 +1367,7 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
/* Iterations of the Smoothing factor */
prop = RNA_def_property(srna, "pen_smooth_steps", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "draw_smoothlvl");
RNA_def_property_range(prop, 1, 3);
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Iterations", "Number of times to smooth newly created strokes");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL);

View File

@ -1026,11 +1026,16 @@ static void rna_def_modifier_gpencilsmooth(BlenderRNA *brna)
prop = RNA_def_property(srna, "step", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "step");
RNA_def_property_range(prop, 1, 10);
RNA_def_property_range(prop, 1, 1000);
RNA_def_property_ui_text(
prop, "Step", "Number of times to apply smooth (high numbers can reduce fps)");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "keep_shape", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_SMOOTH_KEEP_SHAPE);
RNA_def_property_ui_text(prop, "Keep Shape", "Smooth the details, but keep the overall shape");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_SMOOTH_INVERT_LAYER);
RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");