WIP: GPencil: Improve simplify modifier #104521

Closed
Falk David wants to merge 1 commits from filedescriptor/blender:gpencil-simplify-modifier-improvements into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 123 additions and 67 deletions

View File

@ -82,7 +82,10 @@ void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]);
*/ */
void BKE_gpencil_stroke_simplify_adaptive(struct bGPdata *gpd, void BKE_gpencil_stroke_simplify_adaptive(struct bGPdata *gpd,
struct bGPDstroke *gps, struct bGPDstroke *gps,
float epsilon); float epsilon,
float fac_position,
float fac_thickness,
float fac_strength);
/** /**
* Simplify alternate vertex of stroke except extremes. * Simplify alternate vertex of stroke except extremes.
* \param gpd: Grease pencil data-block * \param gpd: Grease pencil data-block

View File

@ -15,6 +15,7 @@
#include "MEM_guardedalloc.h" #include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_array_utils.h" #include "BLI_array_utils.h"
#include "BLI_blenlib.h" #include "BLI_blenlib.h"
#include "BLI_ghash.h" #include "BLI_ghash.h"
@ -24,6 +25,7 @@
#include "BLI_math_vector_types.hh" #include "BLI_math_vector_types.hh"
#include "BLI_polyfill_2d.h" #include "BLI_polyfill_2d.h"
#include "BLI_span.hh" #include "BLI_span.hh"
#include "BLI_stack.hh"
#include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h" #include "DNA_gpencil_types.h"
@ -48,8 +50,12 @@
#include "DEG_depsgraph_query.h" #include "DEG_depsgraph_query.h"
using blender::Array;
using blender::float2;
using blender::float3; using blender::float3;
using blender::MutableSpan;
using blender::Span; using blender::Span;
using blender::Stack;
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Grease Pencil Object: Bound-box Support /** \name Grease Pencil Object: Bound-box Support
@ -2015,70 +2021,86 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3])
/** \} */ /** \} */
/**
* Iterative implementation of the RamerâDouglasâPeucker algorithm
* (https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm)
* Note: Assumes \param r_marked_keep has the same size as \param points.
*/
static void simplify_point_buffer(Span<bGPDspoint> points,
int start_idx,
int end_idx,
float epsilon,
float fac_position,
float fac_thickness,
float fac_strength,
MutableSpan<bool> r_marked_keep)
{
Stack<std::pair<int, int>> stack;
stack.push({start_idx, end_idx});
while (!stack.is_empty()) {
auto [start, end] = stack.pop();
float max_dist = 0.0f;
int max_index = -1;
for (int i = start + 1; i < end; i++) {
if (r_marked_keep[i - start_idx]) {
float3 point_on_line;
float lambda = closest_to_line_segment_v3(
point_on_line, &points[i].x, &points[start].x, &points[end].x);
float interp_thickness = interpf(points[end].pressure, points[start].pressure, lambda);
float interp_strength = interpf(points[end].strength, points[start].strength, lambda);
float dist_position = len_v3v3(point_on_line, &points[i].x) * fac_position;
float dist_thickness = fabsf(points[i].pressure - interp_thickness) * fac_thickness;
float dist_strength = fabsf(points[i].strength - interp_strength) * fac_strength;
float dist = std::max({dist_position, dist_thickness, dist_strength});
if (dist > max_dist) {
max_dist = dist;
max_index = i;
}
}
}
if (max_dist > epsilon) {
stack.push({start, max_index});
stack.push({max_index, end});
}
else {
for (int i = start + 1; i < end; i++) {
r_marked_keep[i - start_idx] = false;
}
}
}
}
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Stroke Simplify /** \name Stroke Simplify
* \{ */ * \{ */
void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float epsilon) void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd,
bGPDstroke *gps,
float epsilon,
float fac_position,
float fac_thickness,
float fac_strength)
{ {
bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points); bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
int totpoints = gps->totpoints; int totpoints = gps->totpoints;
char *marked = nullptr;
char work;
int start = 0; Span<bGPDspoint> points(old_points, totpoints);
int end = gps->totpoints - 1;
marked = (char *)MEM_callocN(totpoints, "GP marked array"); Array<bool> marked_as_keep(totpoints, true);
marked[start] = 1; simplify_point_buffer(points,
marked[end] = 1; 0,
totpoints - 1,
work = 1; epsilon,
int totmarked = 0; fac_position,
/* while still reducing */ fac_thickness,
while (work) { fac_strength,
int ls, le; marked_as_keep);
work = 0;
ls = start;
le = start + 1;
/* while not over interval */
while (ls < end) {
int max_i = 0;
/* divided to get more control */
float max_dist = epsilon / 10.0f;
/* find the next marked point */
while (marked[le] == 0) {
le++;
}
for (int i = ls + 1; i < le; i++) {
float point_on_line[3];
float dist;
closest_to_line_segment_v3(
point_on_line, &old_points[i].x, &old_points[ls].x, &old_points[le].x);
dist = len_v3v3(point_on_line, &old_points[i].x);
if (dist > max_dist) {
max_dist = dist;
max_i = i;
}
}
if (max_i != 0) {
work = 1;
marked[max_i] = 1;
totmarked++;
}
ls = le;
le = ls + 1;
}
}
/* adding points marked */ /* adding points marked */
MDeformVert *old_dvert = nullptr; MDeformVert *old_dvert = nullptr;
@ -2088,22 +2110,21 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e
old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert); old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
} }
/* resize gps */ /* resize gps */
int j = 0; int count = 0;
for (int i = 0; i < totpoints; i++) { for (int i = 0; i < totpoints; i++) {
bGPDspoint *pt_src = &old_points[i]; bGPDspoint *pt_src = &old_points[i];
bGPDspoint *pt = &gps->points[j]; bGPDspoint *pt = &gps->points[count];
if (marked_as_keep[i]) {
if ((marked[i]) || (i == 0) || (i == totpoints - 1)) {
*pt = blender::dna::shallow_copy(*pt_src); *pt = blender::dna::shallow_copy(*pt_src);
if (gps->dvert != nullptr) { if (gps->dvert != nullptr) {
dvert_src = &old_dvert[i]; dvert_src = &old_dvert[i];
MDeformVert *dvert = &gps->dvert[j]; MDeformVert *dvert = &gps->dvert[count];
memcpy(dvert, dvert_src, sizeof(MDeformVert)); memcpy(dvert, dvert_src, sizeof(MDeformVert));
if (dvert_src->dw) { if (dvert_src->dw) {
memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight)); memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
} }
} }
j++; count++;
} }
else { else {
if (gps->dvert != nullptr) { if (gps->dvert != nullptr) {
@ -2113,14 +2134,13 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e
} }
} }
gps->totpoints = j; gps->totpoints = count;
/* Calc geometry data. */ /* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps); BKE_gpencil_stroke_geometry_update(gpd, gps);
MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_points);
MEM_SAFE_FREE(old_dvert); MEM_SAFE_FREE(old_dvert);
MEM_SAFE_FREE(marked);
} }
void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps)

View File

@ -4748,7 +4748,7 @@ static int gpencil_stroke_simplify_exec(bContext *C, wmOperator *op)
GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) {
if (gps->flag & GP_STROKE_SELECT) { if (gps->flag & GP_STROKE_SELECT) {
/* simplify stroke using Ramer-Douglas-Peucker algorithm */ /* simplify stroke using Ramer-Douglas-Peucker algorithm */
BKE_gpencil_stroke_simplify_adaptive(gpd, gps, factor); BKE_gpencil_stroke_simplify_adaptive(gpd, gps, factor, 10, 1, 1);
changed = true; changed = true;
} }
} }

View File

@ -1280,7 +1280,8 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
/* Simplify adaptive */ /* Simplify adaptive */
if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) &&
(brush->gpencil_settings->simplify_f > 0.0f)) { (brush->gpencil_settings->simplify_f > 0.0f)) {
BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); BKE_gpencil_stroke_simplify_adaptive(
gpd, gps, brush->gpencil_settings->simplify_f, 10, 0, 0);
} }
/* Set material index. */ /* Set material index. */

View File

@ -85,7 +85,8 @@ static void deformStroke(GpencilModifierData *md,
} }
case GP_SIMPLIFY_ADAPTIVE: { case GP_SIMPLIFY_ADAPTIVE: {
/* simplify stroke using Ramer-Douglas-Peucker algorithm */ /* simplify stroke using Ramer-Douglas-Peucker algorithm */
BKE_gpencil_stroke_simplify_adaptive(gpd, gps, mmd->factor); BKE_gpencil_stroke_simplify_adaptive(
gpd, gps, mmd->factor, mmd->fac_position, mmd->fac_thickness, mmd->fac_strength);
break; break;
} }
case GP_SIMPLIFY_SAMPLE: { case GP_SIMPLIFY_SAMPLE: {
@ -133,6 +134,9 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
} }
else if (mode == GP_SIMPLIFY_ADAPTIVE) { else if (mode == GP_SIMPLIFY_ADAPTIVE) {
uiItemR(layout, ptr, "factor", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "factor", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "fac_position", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "fac_thickness", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "fac_strength", 0, NULL, ICON_NONE);
} }
else if (mode == GP_SIMPLIFY_SAMPLE) { else if (mode == GP_SIMPLIFY_SAMPLE) {
uiItemR(layout, ptr, "length", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "length", 0, NULL, ICON_NONE);

View File

@ -198,6 +198,9 @@
.pass_index = 0, \ .pass_index = 0, \
.flag = 0, \ .flag = 0, \
.factor = 0.0f, \ .factor = 0.0f, \
.fac_position = 10.0f, \
.fac_thickness = 0.0f, \
.fac_strength = 0.0f, \
.mode = 0, \ .mode = 0, \
.step = 1, \ .step = 1, \
.layer_pass = 0, \ .layer_pass = 0, \

View File

@ -742,6 +742,9 @@ typedef struct SimplifyGpencilModifierData {
int flag; int flag;
/** Factor of simplify. */ /** Factor of simplify. */
float factor; float factor;
float fac_position;
float fac_thickness;
float fac_strength;
/** Type of simplify. */ /** Type of simplify. */
short mode; short mode;
/** Every n vertex to keep. */ /** Every n vertex to keep. */
@ -754,6 +757,7 @@ typedef struct SimplifyGpencilModifierData {
float sharp_threshold; float sharp_threshold;
/** Merge distance */ /** Merge distance */
float distance; float distance;
char _pad[4];
} SimplifyGpencilModifierData; } SimplifyGpencilModifierData;
typedef enum eSimplifyGpencil_Flag { typedef enum eSimplifyGpencil_Flag {

View File

@ -1378,6 +1378,27 @@ static void rna_def_modifier_gpencilsimplify(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Factor", "Factor of Simplify"); RNA_def_property_ui_text(prop, "Factor", "Factor of Simplify");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fac_position", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "fac_position");
RNA_def_property_range(prop, 0, 100.0);
RNA_def_property_ui_range(prop, 0, 20.0f, 1.0f, 1);
RNA_def_property_ui_text(prop, "Factor Position", "Factor of Position");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fac_thickness", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "fac_thickness");
RNA_def_property_range(prop, 0, 100.0);
RNA_def_property_ui_range(prop, 0, 20.0f, 1.0f, 1);
RNA_def_property_ui_text(prop, "Factor Thickness", "Factor of Thickness");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fac_strength", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "fac_strength");
RNA_def_property_range(prop, 0, 100.0);
RNA_def_property_ui_range(prop, 0, 20.0f, 1.0f, 1);
RNA_def_property_ui_text(prop, "Factor Strength", "Factor of Strength");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "pass_index"); RNA_def_property_int_sdna(prop, NULL, "pass_index");
RNA_def_property_range(prop, 0, 100); RNA_def_property_range(prop, 0, 100);