|
|
|
|
@@ -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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|