From 78a58a075f58d2c13da6ccdf7cb3509e5e7a3341 Mon Sep 17 00:00:00 2001 From: Falk David Date: Wed, 21 Feb 2024 12:41:39 +0100 Subject: [PATCH 01/14] Curves: Add resample adaptive algorithm The Ramer-Douglas-Peucker algorithm finds a set of points in a polyline to remove so that the overall shape of the polyline is similar. How similar can be controlled by the distance `epsilon`. The name "resample adaptive" comes from grease pencil. Other suggestions are welcome. --- .../blender/geometry/GEO_resample_curves.hh | 8 ++ .../geometry/intern/resample_curves.cc | 121 ++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/source/blender/geometry/GEO_resample_curves.hh b/source/blender/geometry/GEO_resample_curves.hh index dab10c75a06..a6a3aae3a79 100644 --- a/source/blender/geometry/GEO_resample_curves.hh +++ b/source/blender/geometry/GEO_resample_curves.hh @@ -50,4 +50,12 @@ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, const fn::Field &selection_field, const ResampleCurvesOutputAttributeIDs &output_ids = {}); +/** + * Resamples the selected curves using the Ramer-Douglas-Peucker algorithm by removing points that + * don't change the shape of the curve within distance #epsilon. + */ +CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, + const IndexMask &selection, + const VArray epsilons); + } // namespace blender::geometry diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index 6c530fbab87..b9ee1e173af 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -4,9 +4,11 @@ #include "BLI_array_utils.hh" #include "BLI_math_color.hh" +#include "BLI_math_geom.h" #include "BLI_math_quaternion.hh" #include "BLI_length_parameterize.hh" +#include "BLI_stack.hh" #include "BLI_task.hh" #include "FN_field.hh" @@ -511,4 +513,123 @@ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, return dst_curves; } +/** + * An implementation of the Ramer-Douglas-Peucker algorithm. + * + * \param range: The range to simplify. + * \param epsilon: The threshold distance from the coord between two points for when a point + * in-between needs to be kept. + * \param dist_function: A function that computes the distance to a point at an index in the range. + * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. + * \param points_to_delete: Writes true to the indices for which the points should be removed. + */ +static void ramer_douglas_peucker( + const IndexRange range, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + /* Mark all points to be kept. */ + points_to_delete.slice(range).fill(false); + + Stack stack; + stack.push(range); + while (!stack.is_empty()) { + const IndexRange sub_range = stack.pop(); + /* Skip ranges with less than 3 points. All points are kept. */ + if (sub_range.size() < 3) { + continue; + } + const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); + /* Compute the maximum distance and the corresponding index. */ + float max_dist = -1.0f; + int max_index = -1; + for (const int64_t index : inside_range) { + const float dist = dist_function(sub_range.first(), sub_range.last(), index); + if (dist > max_dist) { + max_dist = dist; + max_index = index - sub_range.first(); + } + } + + if (max_dist > epsilon) { + /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat + * the search on the left & right side. */ + stack.push(sub_range.slice(0, max_index + 1)); + stack.push(sub_range.slice(max_index, sub_range.size() - max_index)); + } + else { + /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ + points_to_delete.slice(inside_range).fill(true); + } + } +} + +static void curve_resample_adaptive( + const IndexRange points, + const bool cyclic, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + const Span curve_selection = points_to_delete.slice(points); + if (!curve_selection.contains(true)) { + return; + } + + 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)) { + ramer_douglas_peucker( + range.shift(points.start()), epsilon, dist_function, points_to_delete); + } + }); + + /* For cyclic curves, handle 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; + } + } +} + +CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, + const IndexMask &selection, + const VArray epsilons) +{ + CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + + const Span positions = src_curves.positions(); + const VArray cyclic = src_curves.cyclic(); + const OffsetIndices points_by_curve = src_curves.points_by_curve(); + + /* 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; + }; + + Array points_to_delete(src_curves.points_num(), false); + bke::curves::fill_points(points_by_curve, selection, true, points_to_delete.as_mutable_span()); + + selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + curve_resample_adaptive(points, + cyclic[curve_i], + epsilons[curve_i], + dist_function_positions, + points_to_delete.as_mutable_span()); + }); + + IndexMaskMemory memory; + dst_curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {}); + return dst_curves; +} + } // namespace blender::geometry -- 2.30.2 From 91f98810841f5b11ebe5d08e7bdd189d233801a4 Mon Sep 17 00:00:00 2001 From: Falk David Date: Wed, 21 Feb 2024 14:15:43 +0100 Subject: [PATCH 02/14] Fixes --- .../blender/geometry/intern/resample_curves.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index b9ee1e173af..b58b2adad85 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -601,7 +601,7 @@ CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, const IndexMask &selection, const VArray epsilons) { - CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + CurvesGeometry dst_curves(src_curves); const Span positions = src_curves.positions(); const VArray cyclic = src_curves.cyclic(); @@ -620,11 +620,16 @@ CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { const IndexRange points = points_by_curve[curve_i]; - curve_resample_adaptive(points, - cyclic[curve_i], - epsilons[curve_i], - dist_function_positions, - points_to_delete.as_mutable_span()); + if (epsilons[curve_i] > 0.0f) { + curve_resample_adaptive(points, + cyclic[curve_i], + epsilons[curve_i], + dist_function_positions, + points_to_delete.as_mutable_span()); + } + else { + points_to_delete.as_mutable_span().slice(points).fill(false); + } }); IndexMaskMemory memory; -- 2.30.2 From 67d3caec8fc9175d9b7b7378b58ca2526b22e01f Mon Sep 17 00:00:00 2001 From: Falk David Date: Thu, 29 Feb 2024 14:11:01 +0100 Subject: [PATCH 03/14] Move to another file --- source/blender/geometry/CMakeLists.txt | 2 + .../blender/geometry/GEO_resample_curves.hh | 8 - .../blender/geometry/GEO_simplify_curves.hh | 21 +++ .../geometry/intern/resample_curves.cc | 126 ---------------- .../geometry/intern/simplify_curves.cc | 139 ++++++++++++++++++ 5 files changed, 162 insertions(+), 134 deletions(-) create mode 100644 source/blender/geometry/GEO_simplify_curves.hh create mode 100644 source/blender/geometry/intern/simplify_curves.cc diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index 596fead5be0..685301107ea 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -41,6 +41,7 @@ set(SRC intern/reverse_uv_sampler.cc intern/separate_geometry.cc intern/set_curve_type.cc + intern/simplify_curves.cc intern/smooth_curves.cc intern/subdivide_curves.cc intern/transform.cc @@ -74,6 +75,7 @@ set(SRC GEO_reverse_uv_sampler.hh GEO_separate_geometry.hh GEO_set_curve_type.hh + GEO_simplify_curves.hh GEO_smooth_curves.hh GEO_subdivide_curves.hh GEO_transform.hh diff --git a/source/blender/geometry/GEO_resample_curves.hh b/source/blender/geometry/GEO_resample_curves.hh index a6a3aae3a79..dab10c75a06 100644 --- a/source/blender/geometry/GEO_resample_curves.hh +++ b/source/blender/geometry/GEO_resample_curves.hh @@ -50,12 +50,4 @@ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, const fn::Field &selection_field, const ResampleCurvesOutputAttributeIDs &output_ids = {}); -/** - * Resamples the selected curves using the Ramer-Douglas-Peucker algorithm by removing points that - * don't change the shape of the curve within distance #epsilon. - */ -CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, - const IndexMask &selection, - const VArray epsilons); - } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_simplify_curves.hh b/source/blender/geometry/GEO_simplify_curves.hh new file mode 100644 index 00000000000..3712986b15a --- /dev/null +++ b/source/blender/geometry/GEO_simplify_curves.hh @@ -0,0 +1,21 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_curves.hh" + +namespace blender::geometry { + +using bke::CurvesGeometry; + +/** + * Simplifies the selected curves using the Ramer-Douglas-Peucker algorithm by removing points that + * don't change the shape of the curve within distance #epsilon. + */ +CurvesGeometry curves_simplify(const CurvesGeometry &src_curves, + const IndexMask &selection, + float epsilon); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index b58b2adad85..6c530fbab87 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -4,11 +4,9 @@ #include "BLI_array_utils.hh" #include "BLI_math_color.hh" -#include "BLI_math_geom.h" #include "BLI_math_quaternion.hh" #include "BLI_length_parameterize.hh" -#include "BLI_stack.hh" #include "BLI_task.hh" #include "FN_field.hh" @@ -513,128 +511,4 @@ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, return dst_curves; } -/** - * An implementation of the Ramer-Douglas-Peucker algorithm. - * - * \param range: The range to simplify. - * \param epsilon: The threshold distance from the coord between two points for when a point - * in-between needs to be kept. - * \param dist_function: A function that computes the distance to a point at an index in the range. - * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. - * \param points_to_delete: Writes true to the indices for which the points should be removed. - */ -static void ramer_douglas_peucker( - const IndexRange range, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) -{ - /* Mark all points to be kept. */ - points_to_delete.slice(range).fill(false); - - Stack stack; - stack.push(range); - while (!stack.is_empty()) { - const IndexRange sub_range = stack.pop(); - /* Skip ranges with less than 3 points. All points are kept. */ - if (sub_range.size() < 3) { - continue; - } - const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); - /* Compute the maximum distance and the corresponding index. */ - float max_dist = -1.0f; - int max_index = -1; - for (const int64_t index : inside_range) { - const float dist = dist_function(sub_range.first(), sub_range.last(), index); - if (dist > max_dist) { - max_dist = dist; - max_index = index - sub_range.first(); - } - } - - if (max_dist > epsilon) { - /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat - * the search on the left & right side. */ - stack.push(sub_range.slice(0, max_index + 1)); - stack.push(sub_range.slice(max_index, sub_range.size() - max_index)); - } - else { - /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ - points_to_delete.slice(inside_range).fill(true); - } - } -} - -static void curve_resample_adaptive( - const IndexRange points, - const bool cyclic, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) -{ - const Span curve_selection = points_to_delete.slice(points); - if (!curve_selection.contains(true)) { - return; - } - - 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)) { - ramer_douglas_peucker( - range.shift(points.start()), epsilon, dist_function, points_to_delete); - } - }); - - /* For cyclic curves, handle 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; - } - } -} - -CurvesGeometry resample_adaptive(const CurvesGeometry &src_curves, - const IndexMask &selection, - const VArray epsilons) -{ - CurvesGeometry dst_curves(src_curves); - - const Span positions = src_curves.positions(); - const VArray cyclic = src_curves.cyclic(); - const OffsetIndices points_by_curve = src_curves.points_by_curve(); - - /* 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; - }; - - Array points_to_delete(src_curves.points_num(), false); - bke::curves::fill_points(points_by_curve, selection, true, points_to_delete.as_mutable_span()); - - selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { - const IndexRange points = points_by_curve[curve_i]; - if (epsilons[curve_i] > 0.0f) { - curve_resample_adaptive(points, - cyclic[curve_i], - epsilons[curve_i], - dist_function_positions, - points_to_delete.as_mutable_span()); - } - else { - points_to_delete.as_mutable_span().slice(points).fill(false); - } - }); - - IndexMaskMemory memory; - dst_curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {}); - return dst_curves; -} - } // namespace blender::geometry diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc new file mode 100644 index 00000000000..217d22d965f --- /dev/null +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -0,0 +1,139 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_array_utils.hh" +#include "BLI_math_geom.h" +#include "BLI_set.hh" +#include "BLI_stack.hh" + +#include "BKE_curves_utils.hh" + +#include "GEO_simplify_curves.hh" + +namespace blender::geometry { + +/** + * An implementation of the Ramer-Douglas-Peucker algorithm. + * + * \param range: The range to simplify. + * \param epsilon: The threshold distance from the coord between two points for when a point + * in-between needs to be kept. + * \param dist_function: A function that computes the distance to a point at an index in the range. + * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. + * \param points_to_delete: Writes true to the indices for which the points should be removed. + */ +static void ramer_douglas_peucker( + const IndexRange range, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + /* Mark all points to be kept. */ + points_to_delete.slice(range).fill(false); + + Stack stack; + stack.push(range); + while (!stack.is_empty()) { + const IndexRange sub_range = stack.pop(); + /* Skip ranges with less than 3 points. All points are kept. */ + if (sub_range.size() < 3) { + continue; + } + const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); + /* Compute the maximum distance and the corresponding index. */ + float max_dist = -1.0f; + int max_index = -1; + for (const int64_t index : inside_range) { + const float dist = dist_function(sub_range.first(), sub_range.last(), index); + if (dist > max_dist) { + max_dist = dist; + max_index = index - sub_range.first(); + } + } + + if (max_dist > epsilon) { + /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat + * the search on the left & right side. */ + stack.push(sub_range.slice(0, max_index + 1)); + stack.push(sub_range.slice(max_index, sub_range.size() - max_index)); + } + else { + /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ + points_to_delete.slice(inside_range).fill(true); + } + } +} + +static void curve_simplifiy(const IndexRange points, + const bool cyclic, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) +{ + const Span curve_selection = points_to_delete.slice(points); + if (!curve_selection.contains(true)) { + return; + } + + 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)) { + ramer_douglas_peucker( + range.shift(points.start()), epsilon, dist_function, points_to_delete); + } + }); + + /* For cyclic curves, handle 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; + } + } +} + +CurvesGeometry curves_simplify(const CurvesGeometry &src_curves, + const IndexMask &selection, + const float epsilon) +{ + CurvesGeometry dst_curves(src_curves); + + const Span positions = src_curves.positions(); + const VArray cyclic = src_curves.cyclic(); + const OffsetIndices points_by_curve = src_curves.points_by_curve(); + + /* 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; + }; + + Array points_to_delete(src_curves.points_num(), false); + bke::curves::fill_points(points_by_curve, selection, true, points_to_delete.as_mutable_span()); + + selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + if (epsilon > 0.0f) { + curve_simplifiy(points, + cyclic[curve_i], + epsilon, + dist_function_positions, + points_to_delete.as_mutable_span()); + } + else { + points_to_delete.as_mutable_span().slice(points).fill(false); + } + }); + + IndexMaskMemory memory; + dst_curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {}); + return dst_curves; +} + +} // namespace blender::geometry -- 2.30.2 From 68bff9cbf3bc6e26ee16c9eb28cc6894fd304782 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 13:09:55 +0100 Subject: [PATCH 04/14] Implement a generic version for the simplifiy algorithm --- .../blender/geometry/GEO_simplify_curves.hh | 16 ++- .../geometry/intern/simplify_curves.cc | 120 +++++++++++------- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/source/blender/geometry/GEO_simplify_curves.hh b/source/blender/geometry/GEO_simplify_curves.hh index 3712986b15a..25091b4ab07 100644 --- a/source/blender/geometry/GEO_simplify_curves.hh +++ b/source/blender/geometry/GEO_simplify_curves.hh @@ -8,14 +8,16 @@ namespace blender::geometry { -using bke::CurvesGeometry; - /** - * Simplifies the selected curves using the Ramer-Douglas-Peucker algorithm by removing points that - * don't change the shape of the curve within distance #epsilon. + * Compute an index masks of points to remove to simplify the curve attribute using the + * Ramer-Douglas-Peucker algorithm. */ -CurvesGeometry curves_simplify(const CurvesGeometry &src_curves, - const IndexMask &selection, - float epsilon); +IndexMask simplify_curve_attribute(const Span positions, + const IndexMask &curves_selection, + const OffsetIndices points_by_curve, + const VArray cyclic, + float epsilon, + GSpan attribute_data, + IndexMaskMemory memory); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 217d22d965f..b8566afdb71 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -13,21 +13,46 @@ namespace blender::geometry { +/* Computes a "perpendicular distance" value for the generic attribute data based on the + * positions of the curve. + * + * First, we compute a lambda value that represents a factor from the first point to the last + * point of the current range. This is the projection of the point of interest onto the vector + * from the first to the last point. + * + * Then this lambda value is used to compute an interpolated value of the first and last point + * and finally we compute the distance from the interpolated value to the actual value. + * This is the "perpendicular distance". + */ +template +float perpendicular_distance(const Span positions, + const Span attribute_data, + const int64_t first_index, + const int64_t last_index, + const int64_t index) +{ + const float3 ray_dir = positions[last_index] - positions[first_index]; + float lambda = 0.0f; + if (!math::is_zero(ray_dir)) { + lambda = math::dot(ray_dir, positions[index] - positions[first_index]) / + math::dot(ray_dir, ray_dir); + } + const T &from = attribute_data[first_index]; + const T &to = attribute_data[last_index]; + const T &value = attribute_data[index]; + const T &interpolated = math::interpolate(from, to, lambda); + return math::distance(value, interpolated); +} + /** * An implementation of the Ramer-Douglas-Peucker algorithm. - * - * \param range: The range to simplify. - * \param epsilon: The threshold distance from the coord between two points for when a point - * in-between needs to be kept. - * \param dist_function: A function that computes the distance to a point at an index in the range. - * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. - * \param points_to_delete: Writes true to the indices for which the points should be removed. */ -static void ramer_douglas_peucker( - const IndexRange range, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) +template +static void ramer_douglas_peucker(const IndexRange range, + const Span positions, + const float epsilon, + const Span attribute_data, + MutableSpan points_to_delete) { /* Mark all points to be kept. */ points_to_delete.slice(range).fill(false); @@ -45,7 +70,8 @@ static void ramer_douglas_peucker( float max_dist = -1.0f; int max_index = -1; for (const int64_t index : inside_range) { - const float dist = dist_function(sub_range.first(), sub_range.last(), index); + const float dist = perpendicular_distance( + positions, attribute_data, sub_range.first(), sub_range.last(), index); if (dist > max_dist) { max_dist = dist; max_index = index - sub_range.first(); @@ -65,10 +91,12 @@ static void ramer_douglas_peucker( } } +template static void curve_simplifiy(const IndexRange points, + const Span positions, const bool cyclic, const float epsilon, - const FunctionRef dist_function, + const Span attribute_data, MutableSpan points_to_delete) { const Span curve_selection = points_to_delete.slice(points); @@ -80,60 +108,56 @@ static void curve_simplifiy(const IndexRange points, 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) { + selection_ranges.index_range(), 512, [&](const IndexRange range_of_ranges) { for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) { ramer_douglas_peucker( - range.shift(points.start()), epsilon, dist_function, points_to_delete); + range.shift(points.start()), positions, epsilon, attribute_data, points_to_delete); } }); - /* For cyclic curves, handle the last segment. */ + /* For cyclic curves, handle the last segment separately. */ if (cyclic && points.size() > 2 && is_last_segment_selected) { - const float dist = dist_function(points.last(1), points.first(), points.last()); + const float dist = perpendicular_distance( + positions, attribute_data, points.last(1), points.first(), points.last()); if (dist <= epsilon) { points_to_delete[points.last()] = true; } } } -CurvesGeometry curves_simplify(const CurvesGeometry &src_curves, - const IndexMask &selection, - const float epsilon) +IndexMask simplify_curve_attribute(const Span positions, + const IndexMask &curves_selection, + const OffsetIndices points_by_curve, + const VArray cyclic, + const float epsilon, + GSpan attribute_data, + IndexMaskMemory memory) { - CurvesGeometry dst_curves(src_curves); - - const Span positions = src_curves.positions(); - const VArray cyclic = src_curves.cyclic(); - const OffsetIndices points_by_curve = src_curves.points_by_curve(); - - /* 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; - }; - - Array points_to_delete(src_curves.points_num(), false); - bke::curves::fill_points(points_by_curve, selection, true, points_to_delete.as_mutable_span()); - - selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { + Array points_to_delete(positions.size(), false); + bke::curves::fill_points( + points_by_curve, curves_selection, true, points_to_delete.as_mutable_span()); + curves_selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { const IndexRange points = points_by_curve[curve_i]; if (epsilon > 0.0f) { - curve_simplifiy(points, - cyclic[curve_i], - epsilon, - dist_function_positions, - points_to_delete.as_mutable_span()); + bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) + { + curve_simplifiy(points, + positions.slice(points), + cyclic[curve_i], + epsilon, + attribute_data.typed(), + points_to_delete.as_mutable_span()); + } + }); } else { points_to_delete.as_mutable_span().slice(points).fill(false); } }); - - IndexMaskMemory memory; - dst_curves.remove_points(IndexMask::from_bools(points_to_delete, memory), {}); - return dst_curves; + return IndexMask::from_bools(points_to_delete, memory); } } // namespace blender::geometry -- 2.30.2 From 77e7f302b3b71746344cba517305ba5247f5db35 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 13:12:09 +0100 Subject: [PATCH 05/14] Pass cyclic VArray as const reference --- source/blender/geometry/GEO_simplify_curves.hh | 2 +- source/blender/geometry/intern/simplify_curves.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/GEO_simplify_curves.hh b/source/blender/geometry/GEO_simplify_curves.hh index 25091b4ab07..d024ed95bea 100644 --- a/source/blender/geometry/GEO_simplify_curves.hh +++ b/source/blender/geometry/GEO_simplify_curves.hh @@ -15,7 +15,7 @@ namespace blender::geometry { IndexMask simplify_curve_attribute(const Span positions, const IndexMask &curves_selection, const OffsetIndices points_by_curve, - const VArray cyclic, + const VArray &cyclic, float epsilon, GSpan attribute_data, IndexMaskMemory memory); diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index b8566afdb71..506a3272fbb 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -128,7 +128,7 @@ static void curve_simplifiy(const IndexRange points, IndexMask simplify_curve_attribute(const Span positions, const IndexMask &curves_selection, const OffsetIndices points_by_curve, - const VArray cyclic, + const VArray &cyclic, const float epsilon, GSpan attribute_data, IndexMaskMemory memory) -- 2.30.2 From c17885b19a7e72ba9fe1c08713d5585636ddf89f Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 13:57:22 +0100 Subject: [PATCH 06/14] Pass memory by reference --- source/blender/geometry/GEO_simplify_curves.hh | 2 +- source/blender/geometry/intern/simplify_curves.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/GEO_simplify_curves.hh b/source/blender/geometry/GEO_simplify_curves.hh index d024ed95bea..649770708c7 100644 --- a/source/blender/geometry/GEO_simplify_curves.hh +++ b/source/blender/geometry/GEO_simplify_curves.hh @@ -18,6 +18,6 @@ IndexMask simplify_curve_attribute(const Span positions, const VArray &cyclic, float epsilon, GSpan attribute_data, - IndexMaskMemory memory); + IndexMaskMemory &memory); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 506a3272fbb..e736ae3fdfb 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -131,7 +131,7 @@ IndexMask simplify_curve_attribute(const Span positions, const VArray &cyclic, const float epsilon, GSpan attribute_data, - IndexMaskMemory memory) + IndexMaskMemory &memory) { Array points_to_delete(positions.size(), false); bke::curves::fill_points( -- 2.30.2 From ba0261deb22485226eb58ae0f3f54293f90451cc Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 13:58:07 +0100 Subject: [PATCH 07/14] Don't slice positions span, inidices are global --- source/blender/geometry/intern/simplify_curves.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index e736ae3fdfb..253042d34c4 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -145,7 +145,7 @@ IndexMask simplify_curve_attribute(const Span positions, std::is_same_v) { curve_simplifiy(points, - positions.slice(points), + positions, cyclic[curve_i], epsilon, attribute_data.typed(), -- 2.30.2 From 7c8e84befa85328108ef32e3123771640c65d47a Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:15:30 +0100 Subject: [PATCH 08/14] Fix comment --- source/blender/geometry/intern/simplify_curves.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 253042d34c4..d1768ec260c 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -13,7 +13,8 @@ namespace blender::geometry { -/* Computes a "perpendicular distance" value for the generic attribute data based on the +/** + * Computes a "perpendicular distance" value for the generic attribute data based on the * positions of the curve. * * First, we compute a lambda value that represents a factor from the first point to the last -- 2.30.2 From ad5ea59f478ec9e141201ce8d317c9390528df07 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:16:46 +0100 Subject: [PATCH 09/14] Use stack inline capacity of 32 --- source/blender/geometry/intern/simplify_curves.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index d1768ec260c..1d52cb51b6c 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -58,7 +58,7 @@ static void ramer_douglas_peucker(const IndexRange range, /* Mark all points to be kept. */ points_to_delete.slice(range).fill(false); - Stack stack; + Stack stack; stack.push(range); while (!stack.is_empty()) { const IndexRange sub_range = stack.pop(); -- 2.30.2 From 3b131cb5b4f02bf0887706248b2c32044acd4355 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:18:30 +0100 Subject: [PATCH 10/14] Return early if epsilon is less than or equal to zero --- .../geometry/intern/simplify_curves.cc | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 1d52cb51b6c..12a430c0d68 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -131,32 +131,30 @@ IndexMask simplify_curve_attribute(const Span positions, const OffsetIndices points_by_curve, const VArray &cyclic, const float epsilon, - GSpan attribute_data, + const GSpan attribute_data, IndexMaskMemory &memory) { Array points_to_delete(positions.size(), false); + if (epsilon <= 0.0f) { + return IndexMask::from_bools(points_to_delete, memory); + } bke::curves::fill_points( points_by_curve, curves_selection, true, points_to_delete.as_mutable_span()); curves_selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) { const IndexRange points = points_by_curve[curve_i]; - if (epsilon > 0.0f) { - bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { - using T = decltype(dummy); - if constexpr (std::is_same_v || std::is_same_v || - std::is_same_v) - { - curve_simplifiy(points, - positions, - cyclic[curve_i], - epsilon, - attribute_data.typed(), - points_to_delete.as_mutable_span()); - } - }); - } - else { - points_to_delete.as_mutable_span().slice(points).fill(false); - } + bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) + { + curve_simplifiy(points, + positions, + cyclic[curve_i], + epsilon, + attribute_data.typed(), + points_to_delete.as_mutable_span()); + } + }); }); return IndexMask::from_bools(points_to_delete, memory); } -- 2.30.2 From 608ec346cd0dc9977293ae8c7117d027d64fb04d Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:21:17 +0100 Subject: [PATCH 11/14] Remove unused imports --- source/blender/geometry/intern/simplify_curves.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 12a430c0d68..406127c5364 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -3,8 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_array_utils.hh" -#include "BLI_math_geom.h" -#include "BLI_set.hh" #include "BLI_stack.hh" #include "BKE_curves_utils.hh" -- 2.30.2 From ae3d08594cbf2d2f47475fb4d67140e14f24fe68 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:33:52 +0100 Subject: [PATCH 12/14] Remove redundant check --- source/blender/geometry/intern/simplify_curves.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 406127c5364..79be485f2ea 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -99,10 +99,6 @@ static void curve_simplifiy(const IndexRange points, MutableSpan points_to_delete) { const Span curve_selection = points_to_delete.slice(points); - if (!curve_selection.contains(true)) { - return; - } - const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last()); const Vector selection_ranges = array_utils::find_all_ranges(curve_selection, true); -- 2.30.2 From 8f5148dc56b0f79791db5b577ca6d7a588c8c9ca Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:53:25 +0100 Subject: [PATCH 13/14] Make indices local to curve --- .../geometry/intern/simplify_curves.cc | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index 79be485f2ea..a6fc871e43e 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -91,31 +91,28 @@ static void ramer_douglas_peucker(const IndexRange range, } template -static void curve_simplifiy(const IndexRange points, - const Span positions, +static void curve_simplifiy(const Span positions, const bool cyclic, const float epsilon, const Span attribute_data, MutableSpan points_to_delete) { - const Span curve_selection = points_to_delete.slice(points); - const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last()); - - const Vector selection_ranges = array_utils::find_all_ranges(curve_selection, true); + const Vector selection_ranges = array_utils::find_all_ranges( + points_to_delete.as_span(), true); threading::parallel_for( selection_ranges.index_range(), 512, [&](const IndexRange range_of_ranges) { for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) { - ramer_douglas_peucker( - range.shift(points.start()), positions, epsilon, attribute_data, points_to_delete); + ramer_douglas_peucker(range, positions, epsilon, attribute_data, points_to_delete); } }); /* For cyclic curves, handle the last segment separately. */ - if (cyclic && points.size() > 2 && is_last_segment_selected) { + const int points_num = positions.size(); + if (cyclic && points_num > 2) { const float dist = perpendicular_distance( - positions, attribute_data, points.last(1), points.first(), points.last()); + positions, attribute_data, points_num - 2, 0, points_num - 1); if (dist <= epsilon) { - points_to_delete[points.last()] = true; + points_to_delete[points_num - 1] = true; } } } @@ -141,12 +138,11 @@ IndexMask simplify_curve_attribute(const Span positions, if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - curve_simplifiy(points, - positions, + curve_simplifiy(positions.slice(points), cyclic[curve_i], epsilon, - attribute_data.typed(), - points_to_delete.as_mutable_span()); + attribute_data.typed().slice(points), + points_to_delete.as_mutable_span().slice(points)); } }); }); -- 2.30.2 From 0fbf054734294b576368861f500dcf2e404ceb68 Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 26 Mar 2024 14:58:31 +0100 Subject: [PATCH 14/14] Fix spelling --- .../geometry/intern/simplify_curves.cc | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/blender/geometry/intern/simplify_curves.cc b/source/blender/geometry/intern/simplify_curves.cc index a6fc871e43e..9aa161ddb66 100644 --- a/source/blender/geometry/intern/simplify_curves.cc +++ b/source/blender/geometry/intern/simplify_curves.cc @@ -91,11 +91,11 @@ static void ramer_douglas_peucker(const IndexRange range, } template -static void curve_simplifiy(const Span positions, - const bool cyclic, - const float epsilon, - const Span attribute_data, - MutableSpan points_to_delete) +static void curve_simplify(const Span positions, + const bool cyclic, + const float epsilon, + const Span attribute_data, + MutableSpan points_to_delete) { const Vector selection_ranges = array_utils::find_all_ranges( points_to_delete.as_span(), true); @@ -138,11 +138,11 @@ IndexMask simplify_curve_attribute(const Span positions, if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - curve_simplifiy(positions.slice(points), - cyclic[curve_i], - epsilon, - attribute_data.typed().slice(points), - points_to_delete.as_mutable_span().slice(points)); + curve_simplify(positions.slice(points), + cyclic[curve_i], + epsilon, + attribute_data.typed().slice(points), + points_to_delete.as_mutable_span().slice(points)); } }); }); -- 2.30.2