GPencil: Simplify sample modifier improvements.

1. Now handles cyclic strokes correctly.

2.  Added a sharp threshold value to allow preservation of sharp corners.

Reviewed By: Antonio Vazquez (antoniov), Aleš Jelovčan (frogstomp)

Ref D14044
This commit is contained in:
YimingWu
2022-02-22 12:54:35 +08:00
parent 0f2e0a25e1
commit 2234bfbcdb
10 changed files with 94 additions and 35 deletions

View File

@@ -208,11 +208,13 @@ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd,
* \param gpd: Grease pencil data-block
* \param gps: Stroke to sample
* \param dist: Distance of one segment
* \param sharp_threshold: Threshold for preserving sharp corners
*/
bool BKE_gpencil_stroke_sample(struct bGPdata *gpd,
struct bGPDstroke *gps,
float dist,
bool select);
const float dist,
const bool select,
const float sharp_threshold);
/**
* Apply smooth position to stroke point.
* \param gps: Stroke to smooth

View File

@@ -443,7 +443,7 @@ static void gpencil_convert_spline(Main *bmain,
}
if (sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd, gps, sample, false);
BKE_gpencil_stroke_sample(gpd, gps, sample, false, 0);
}
/* Recalc fill geometry. */

View File

@@ -202,8 +202,8 @@ static int stroke_march_next_point(const bGPDstroke *gps,
int next_point_index = index_next_pt;
bGPDspoint *pt = nullptr;
if (!(next_point_index < gps->totpoints)) {
return -1;
if (next_point_index == gps->totpoints) {
next_point_index = 0;
}
copy_v3_v3(step_start, current);
@@ -211,15 +211,33 @@ static int stroke_march_next_point(const bGPDstroke *gps,
copy_v3_v3(point, &pt->x);
remaining_till_next = len_v3v3(point, step_start);
while (remaining_till_next < remaining_march) {
while (remaining_till_next < remaining_march && next_point_index) {
remaining_march -= remaining_till_next;
pt = &gps->points[next_point_index];
if (pt->flag & GP_SPOINT_TEMP_TAG) {
pt = &gps->points[next_point_index];
copy_v3_v3(result, &pt->x);
*pressure = gps->points[next_point_index].pressure;
*strength = gps->points[next_point_index].strength;
memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float[4]));
*index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1);
*index_to = next_point_index;
*ratio_result = 1.0f;
next_point_index++;
return next_point_index == 0 ? gps->totpoints : next_point_index;
}
next_point_index++;
copy_v3_v3(point, &pt->x);
copy_v3_v3(step_start, point);
next_point_index++;
if (!(next_point_index < gps->totpoints)) {
next_point_index = gps->totpoints - 1;
break;
if (gps->flag & GP_STROKE_CYCLIC) {
next_point_index = 0;
}
else {
next_point_index = gps->totpoints - 1;
break;
}
}
pt = &gps->points[next_point_index];
copy_v3_v3(point, &pt->x);
@@ -232,35 +250,37 @@ static int stroke_march_next_point(const bGPDstroke *gps,
*strength = gps->points[next_point_index].strength;
memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float[4]));
*index_from = next_point_index - 1;
*index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1);
*index_to = next_point_index;
*ratio_result = 1.0f;
return 0;
}
*index_from = next_point_index == 0 ? (gps->totpoints - 1) : (next_point_index - 1);
*index_to = next_point_index;
float ratio = remaining_march / remaining_till_next;
interp_v3_v3v3(result, step_start, point, ratio);
*ratio_result = ratio;
*pressure = interpf(
gps->points[next_point_index].pressure, gps->points[next_point_index - 1].pressure, ratio);
gps->points[next_point_index].pressure, gps->points[*index_from].pressure, ratio);
*strength = interpf(
gps->points[next_point_index].strength, gps->points[next_point_index - 1].strength, ratio);
gps->points[next_point_index].strength, gps->points[*index_from].strength, ratio);
interp_v4_v4v4(vert_color,
gps->points[next_point_index - 1].vert_color,
gps->points[*index_from].vert_color,
gps->points[next_point_index].vert_color,
ratio);
*index_from = next_point_index - 1;
*index_to = next_point_index;
*ratio_result = ratio;
return next_point_index;
return next_point_index == 0 ? gps->totpoints : next_point_index;
}
static int stroke_march_next_point_no_interp(const bGPDstroke *gps,
const int index_next_pt,
const float *current,
const float dist,
const float sharp_threshold,
float *result)
{
float remaining_till_next = 0.0f;
@@ -270,8 +290,8 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps,
int next_point_index = index_next_pt;
bGPDspoint *pt = nullptr;
if (!(next_point_index < gps->totpoints)) {
return -1;
if (next_point_index == gps->totpoints) {
next_point_index = 0;
}
copy_v3_v3(step_start, current);
@@ -279,15 +299,29 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps,
copy_v3_v3(point, &pt->x);
remaining_till_next = len_v3v3(point, step_start);
while (remaining_till_next < remaining_march) {
while (remaining_till_next < remaining_march && next_point_index) {
remaining_march -= remaining_till_next;
pt = &gps->points[next_point_index];
if (next_point_index < gps->totpoints - 1 &&
angle_v3v3v3(&gps->points[next_point_index - 1].x,
&gps->points[next_point_index].x,
&gps->points[next_point_index + 1].x) < sharp_threshold) {
copy_v3_v3(result, &pt->x);
pt->flag |= GP_SPOINT_TEMP_TAG;
next_point_index++;
return next_point_index == 0 ? gps->totpoints : next_point_index;
}
next_point_index++;
copy_v3_v3(point, &pt->x);
copy_v3_v3(step_start, point);
next_point_index++;
if (!(next_point_index < gps->totpoints)) {
next_point_index = gps->totpoints - 1;
break;
if (gps->flag & GP_STROKE_CYCLIC) {
next_point_index = 0;
}
else {
next_point_index = gps->totpoints - 1;
break;
}
}
pt = &gps->points[next_point_index];
copy_v3_v3(point, &pt->x);
@@ -296,15 +330,16 @@ static int stroke_march_next_point_no_interp(const bGPDstroke *gps,
if (remaining_till_next < remaining_march) {
pt = &gps->points[next_point_index];
copy_v3_v3(result, &pt->x);
/* Stroke marching only terminates here. */
return 0;
}
float ratio = remaining_march / remaining_till_next;
interp_v3_v3v3(result, step_start, point, ratio);
return next_point_index;
return next_point_index == 0 ? gps->totpoints : next_point_index;
}
static int stroke_march_count(const bGPDstroke *gps, const float dist)
static int stroke_march_count(const bGPDstroke *gps, const float dist, const float sharp_threshold)
{
int point_count = 0;
float point[3];
@@ -315,8 +350,13 @@ static int stroke_march_count(const bGPDstroke *gps, const float dist)
copy_v3_v3(point, &pt->x);
point_count++;
/* Sharp points will be tagged by the stroke_march_next_point_no_interp() call below. */
for (int i = 0; i < gps->totpoints; i++) {
gps->points[i].flag &= (~GP_SPOINT_TEMP_TAG);
}
while ((next_point_index = stroke_march_next_point_no_interp(
gps, next_point_index, point, dist, point)) > -1) {
gps, next_point_index, point, dist, sharp_threshold, point)) > -1) {
point_count++;
if (next_point_index == 0) {
break; /* last point finished */
@@ -394,7 +434,11 @@ static void stroke_interpolate_deform_weights(
}
}
bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select)
bool BKE_gpencil_stroke_sample(bGPdata *gpd,
bGPDstroke *gps,
const float dist,
const bool select,
const float sharp_threshold)
{
bGPDspoint *pt = gps->points;
bGPDspoint *pt1 = nullptr;
@@ -406,7 +450,7 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
return false;
}
/* TODO: Implement feature point preservation. */
int count = stroke_march_count(gps, dist);
int count = stroke_march_count(gps, dist, sharp_threshold);
bGPDspoint *new_pt = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint) * count,
"gp_stroke_points_sampled");
@@ -491,6 +535,8 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
gps->totpoints = i;
gps->flag &= (~GP_STROKE_CYCLIC);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps);

View File

@@ -4356,6 +4356,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
const float length = RNA_float_get(op->ptr, "length");
const float sharp_threshold = RNA_float_get(op->ptr, "sharp_threshold");
/* sanity checks */
if (ELEM(NULL, gpd)) {
@@ -4365,7 +4366,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op)
/* Go through each editable + selected stroke */
GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) {
if (gps->flag & GP_STROKE_SELECT) {
BKE_gpencil_stroke_sample(gpd, gps, length, true);
BKE_gpencil_stroke_sample(gpd, gps, length, true, sharp_threshold);
}
}
GP_EDITABLE_STROKES_END(gpstroke_iter);

View File

@@ -314,7 +314,7 @@ void ED_gpencil_trace_data_to_strokes(Main *bmain,
if (sample > 0.0f) {
/* Resample stroke. Don't need to call to BKE_gpencil_stroke_geometry_update() because
* the sample function already call that. */
BKE_gpencil_stroke_sample(gpd, gps, sample, false);
BKE_gpencil_stroke_sample(gpd, gps, sample, false, 0);
}
else {
BKE_gpencil_stroke_geometry_update(gpd, gps);

View File

@@ -88,7 +88,7 @@ static void deformStroke(GpencilModifierData *md,
break;
}
case GP_SIMPLIFY_SAMPLE: {
BKE_gpencil_stroke_sample(gpd, gps, mmd->length, false);
BKE_gpencil_stroke_sample(gpd, gps, mmd->length, false, mmd->sharp_threshold);
break;
}
case GP_SIMPLIFY_MERGE: {
@@ -143,6 +143,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
}
else if (mode == GP_SIMPLIFY_SAMPLE) {
uiItemR(layout, ptr, "length", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "sharp_threshold", 0, NULL, ICON_NONE);
}
else if (mode == GP_SIMPLIFY_MERGE) {
uiItemR(layout, ptr, "distance", 0, NULL, ICON_NONE);

View File

@@ -196,7 +196,7 @@ void GpencilExporterPDF::export_gpencil_layers()
/* Sample stroke. */
if (params_.stroke_sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false, 0);
}
export_stroke_to_polyline(gpl, gps_perimeter, is_stroke, false, false);

View File

@@ -221,7 +221,7 @@ void GpencilExporterSVG::export_gpencil_layers()
/* Sample stroke. */
if (params_.stroke_sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false, 0);
}
export_stroke_to_path(gpl, gps_perimeter, node_gpl, false);

View File

@@ -649,9 +649,10 @@ typedef struct SimplifyGpencilModifierData {
int layer_pass;
/** Sample length */
float length;
/** Sample sharp threshold */
float sharp_threshold;
/** Merge distance */
float distance;
char _pad[4];
} SimplifyGpencilModifierData;
typedef enum eSimplifyGpencil_Flag {

View File

@@ -1223,6 +1223,14 @@ static void rna_def_modifier_gpencilsimplify(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Length", "Length of each segment");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "sharp_threshold", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_float_sdna(prop, NULL, "sharp_threshold");
RNA_def_property_range(prop, 0, M_PI);
RNA_def_property_ui_range(prop, 0, M_PI, 1.0, 1);
RNA_def_property_ui_text(
prop, "Sharp Threshold", "Preserve corners that have sharper angle than this threshold");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Merge */
prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "distance");