diff --git a/scripts/startup/bl_ui/properties_grease_pencil_common.py b/scripts/startup/bl_ui/properties_grease_pencil_common.py index 95f3ffe6572..89faed1fd82 100644 --- a/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -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, ) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 7f18d1b83c4..0ae0c9da80d 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -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() diff --git a/source/blender/editors/grease_pencil/CMakeLists.txt b/source/blender/editors/grease_pencil/CMakeLists.txt index bee17981bb5..313a686ee25 100644 --- a/source/blender/editors/grease_pencil/CMakeLists.txt +++ b/source/blender/editors/grease_pencil/CMakeLists.txt @@ -15,6 +15,7 @@ set(INC ../../geometry ../sculpt_paint ../../modifiers/intern/lineart + ../../functions # RNA_prototypes.hh ${CMAKE_BINARY_DIR}/source/blender/makesrna ) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index c6dc509e516..9a91cd26eae 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -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 dist_function, - MutableSpan points_to_delete) -{ - int64_t total_points_to_delete = 0; - const Span 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 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 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(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 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 positions = curves.positions(); - const VArray 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 cyclic = curves.cyclic(); - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray selection = *curves.attributes().lookup_or_default( - ".selection", bke::AttrDomain::Point, true); - - /* Mark all points in the editable curves to be deleted. */ - Array points_to_delete(curves.points_num(), false); - bke::curves::fill_points(points_by_curve, strokes, true, points_to_delete.as_mutable_span()); - - std::atomic 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::ForSingle(resample_length, curves.curves_num()), {}); + info.drawing.tag_topology_changed(); + changed = true; + break; + } + case SimplifyMode::MERGE: { + const OffsetIndices points_by_curve = curves.points_by_curve(); + const Array 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); } /** \} */