Grease Pencil: Add all supported modes to simplify operator

The stroke simplify operator was implemented at the beginning of GPv3
migration, which means it lacked a few other modes. This fix reused the
same code as in the simplify modifier to provide more simplify modes
back to the modifier so it can work just like GPv2.

Resolves #130616.

Pull Request: https://projects.blender.org/blender/blender/pulls/131076
This commit is contained in:
YimingWu
2024-11-28 17:00:05 +01:00
committed by Falk David
parent d8182421fa
commit 8fb12839f2
4 changed files with 170 additions and 104 deletions

View File

@@ -782,6 +782,17 @@ class GREASE_PENCIL_MT_draw_delete(Menu):
).type = 'ALL_FRAMES'
class GREASE_PENCIL_MT_stroke_simplify(Menu):
bl_label = "Simplify Stroke"
def draw(self, context):
layout = self.layout
layout.operator("grease_pencil.stroke_simplify", text="Fixed").mode = 'FIXED'
layout.operator("grease_pencil.stroke_simplify", text="Adaptive").mode = 'ADAPTIVE'
layout.operator("grease_pencil.stroke_simplify", text="Sample").mode = 'SAMPLE'
layout.operator("grease_pencil.stroke_simplify", text="Merge").mode = 'MERGE'
classes = (
GPENCIL_UL_annotation_layer,
GPENCIL_UL_layer,
@@ -795,6 +806,8 @@ classes = (
GREASE_PENCIL_MT_draw_delete,
GREASE_PENCIL_MT_stroke_simplify,
GreasePencilFlipTintColors,
)

View File

@@ -5716,7 +5716,7 @@ class VIEW3D_MT_edit_greasepencil_stroke(Menu):
layout.operator("grease_pencil.stroke_subdivide", text="Subdivide")
layout.operator("grease_pencil.stroke_subdivide_smooth", text="Subdivide and Smooth")
layout.operator("grease_pencil.stroke_simplify", text="Simplify")
layout.menu("GREASE_PENCIL_MT_stroke_simplify")
layout.separator()

View File

@@ -15,6 +15,7 @@ set(INC
../../geometry
../sculpt_paint
../../modifiers/intern/lineart
../../functions
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)

View File

@@ -65,7 +65,9 @@
#include "GEO_join_geometries.hh"
#include "GEO_realize_instances.hh"
#include "GEO_reorder.hh"
#include "GEO_resample_curves.hh"
#include "GEO_set_curve_type.hh"
#include "GEO_simplify_curves.hh"
#include "GEO_smooth_curves.hh"
#include "GEO_subdivide_curves.hh"
@@ -203,52 +205,53 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot)
/** \name Simplify Stroke Operator
* \{ */
static float dist_to_interpolated(
float3 pos, float3 posA, float3 posB, float val, float valA, float valB)
enum class SimplifyMode {
FIXED = 0,
ADAPTIVE = 1,
SAMPLE = 2,
MERGE = 3,
};
static const EnumPropertyItem prop_simplify_modes[] = {
{int(SimplifyMode::FIXED),
"FIXED",
0,
"Fixed",
"Delete alternating vertices in the stroke, except extremes"},
{int(SimplifyMode::ADAPTIVE),
"ADAPTIVE",
0,
"Adaptive",
"Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"},
{int(SimplifyMode::SAMPLE),
"SAMPLE",
0,
"Sample",
"Re-sample the stroke with segments of the specified length"},
{int(SimplifyMode::MERGE),
"MERGE",
0,
"Merge",
"Simplify the stroke by merging vertices closer than a given distance"},
{0, nullptr, 0, nullptr, nullptr},
};
static IndexMask simplify_fixed(const bke::CurvesGeometry &curves,
const int step,
IndexMaskMemory &memory)
{
float dist1 = math::distance_squared(posA, pos);
float dist2 = math::distance_squared(posB, pos);
if (dist1 + dist2 > 0) {
float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2));
return math::distance(interpolated_val, val);
}
return 0.0f;
}
static int64_t stroke_simplify(const IndexRange points,
const bool cyclic,
const float epsilon,
const FunctionRef<float(int64_t, int64_t, int64_t)> dist_function,
MutableSpan<bool> points_to_delete)
{
int64_t total_points_to_delete = 0;
const Span<bool> curve_selection = points_to_delete.slice(points);
if (!curve_selection.contains(true)) {
return total_points_to_delete;
}
const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last());
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(curve_selection, true);
threading::parallel_for(
selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) {
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) {
total_points_to_delete += ramer_douglas_peucker_simplify(
range.shift(points.start()), epsilon, dist_function, points_to_delete);
const OffsetIndices points_by_curve = curves.points_by_curve();
const Array<int> point_to_curve_map = curves.point_to_curve_map();
return IndexMask::from_predicate(
curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) {
const int curve_i = point_to_curve_map[i];
const IndexRange points = points_by_curve[curve_i];
if (points.size() <= 2) {
return true;
}
const int local_i = i - points.start();
return (local_i % int(math::pow(2.0f, float(step))) == 0) || points.last() == i;
});
/* For cyclic curves, simplify the last segment. */
if (cyclic && points.size() > 2 && is_last_segment_selected) {
const float dist = dist_function(points.last(1), points.first(), points.last());
if (dist <= epsilon) {
points_to_delete[points.last()] = true;
total_points_to_delete++;
}
}
return total_points_to_delete;
}
static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
@@ -257,7 +260,7 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const float epsilon = RNA_float_get(op->ptr, "factor");
const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
bool changed = false;
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
@@ -274,65 +277,67 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
return;
}
const Span<float3> positions = curves.positions();
const VArray<float> radii = info.drawing.radii();
/* Distance functions for `ramer_douglas_peucker_simplify`. */
const auto dist_function_positions =
[positions](int64_t first_index, int64_t last_index, int64_t index) {
const float dist_position = dist_to_line_v3(
positions[index], positions[first_index], positions[last_index]);
return dist_position;
};
const auto dist_function_positions_and_radii =
[positions, radii](int64_t first_index, int64_t last_index, int64_t index) {
const float dist_position = dist_to_line_v3(
positions[index], positions[first_index], positions[last_index]);
const float dist_radii = dist_to_interpolated(positions[index],
positions[first_index],
positions[last_index],
radii[index],
radii[first_index],
radii[last_index]);
return math::max(dist_position, dist_radii);
};
const VArray<bool> cyclic = curves.cyclic();
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
/* Mark all points in the editable curves to be deleted. */
Array<bool> points_to_delete(curves.points_num(), false);
bke::curves::fill_points(points_by_curve, strokes, true, points_to_delete.as_mutable_span());
std::atomic<int64_t> total_points_to_delete = 0;
if (radii.is_single()) {
strokes.foreach_index([&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
total_points_to_delete += stroke_simplify(points,
cyclic[curve_i],
epsilon,
dist_function_positions,
points_to_delete.as_mutable_span());
});
}
else if (radii.is_span()) {
strokes.foreach_index([&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
total_points_to_delete += stroke_simplify(points,
cyclic[curve_i],
epsilon,
dist_function_positions_and_radii,
points_to_delete.as_mutable_span());
});
}
if (total_points_to_delete > 0) {
IndexMaskMemory memory;
curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {});
info.drawing.tag_topology_changed();
changed = true;
switch (mode) {
case SimplifyMode::FIXED: {
const int steps = RNA_int_get(op->ptr, "steps");
const IndexMask points_to_keep = simplify_fixed(curves, steps, memory);
if (points_to_keep.is_empty()) {
info.drawing.strokes_for_write() = {};
break;
}
if (points_to_keep.size() == curves.points_num()) {
break;
}
info.drawing.strokes_for_write() = bke::curves_copy_point_selection(
curves, points_to_keep, {});
info.drawing.tag_topology_changed();
changed = true;
break;
}
case SimplifyMode::ADAPTIVE: {
const float simplify_factor = RNA_float_get(op->ptr, "factor");
const IndexMask points_to_delete = geometry::simplify_curve_attribute(
curves.positions(),
strokes,
curves.points_by_curve(),
curves.cyclic(),
simplify_factor,
curves.positions(),
memory);
info.drawing.strokes_for_write().remove_points(points_to_delete, {});
info.drawing.tag_topology_changed();
changed = true;
break;
}
case SimplifyMode::SAMPLE: {
const float resample_length = RNA_float_get(op->ptr, "length");
info.drawing.strokes_for_write() = geometry::resample_to_length(
curves, strokes, VArray<float>::ForSingle(resample_length, curves.curves_num()), {});
info.drawing.tag_topology_changed();
changed = true;
break;
}
case SimplifyMode::MERGE: {
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const Array<int> point_to_curve_map = curves.point_to_curve_map();
const float merge_distance = RNA_float_get(op->ptr, "distance");
const IndexMask points = IndexMask::from_predicate(
curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) {
const int curve_i = point_to_curve_map[i];
const IndexRange points = points_by_curve[curve_i];
if (points.drop_front(1).drop_back(1).contains(i)) {
return true;
}
return false;
});
info.drawing.strokes_for_write() = ed::greasepencil::curves_merge_by_distance(
curves, merge_distance, points, {});
info.drawing.tag_topology_changed();
changed = true;
break;
}
default:
break;
}
});
@@ -343,6 +348,38 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static void grease_pencil_simplify_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
wmWindowManager *wm = CTX_wm_manager(C);
PointerRNA ptr = RNA_pointer_create(&wm->id, op->type->srna, op->properties);
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, &ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE);
const SimplifyMode mode = SimplifyMode(RNA_enum_get(op->ptr, "mode"));
switch (mode) {
case SimplifyMode::FIXED:
uiItemR(layout, &ptr, "steps", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
case SimplifyMode::ADAPTIVE:
uiItemR(layout, &ptr, "factor", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
case SimplifyMode::SAMPLE:
uiItemR(layout, &ptr, "length", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
case SimplifyMode::MERGE:
uiItemR(layout, &ptr, "distance", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
default:
break;
}
}
static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
{
PropertyRNA *prop;
@@ -356,8 +393,23 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->ui = grease_pencil_simplify_ui;
prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float(ot->srna, "length", 0.05f, 0.01f, 100.0f, "Length", "", 0.01f, 1.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float(ot->srna, "distance", 0.01f, 0.0f, 100.0f, "Distance", "", 0.0f, 1.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "steps", 1, 0, 50, "Steps", "", 0.0f, 10);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_enum(ot->srna,
"mode",
prop_simplify_modes,
0,
"Mode",
"Method used for simplifying stroke points");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */