Blender V5.0
simplify_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_array_utils.hh"
6#include "BLI_stack.hh"
7
8#include "BKE_curves_utils.hh"
9
11
12namespace blender::geometry {
13
26template<typename T>
28 const Span<T> attribute_data,
29 const int64_t first_index,
30 const int64_t last_index,
31 const int64_t index)
32{
33 const float3 ray_dir = positions[last_index] - positions[first_index];
34 float lambda = 0.0f;
35 if (!math::is_zero(ray_dir)) {
36 lambda = math::dot(ray_dir, positions[index] - positions[first_index]) /
37 math::dot(ray_dir, ray_dir);
38 }
39 const T &from = attribute_data[first_index];
40 const T &to = attribute_data[last_index];
41 const T &value = attribute_data[index];
42 const T &interpolated = math::interpolate(from, to, lambda);
43 return math::distance(value, interpolated);
44}
45
49template<typename T>
50static void ramer_douglas_peucker(const IndexRange range,
51 const Span<float3> positions,
52 const float epsilon,
53 const Span<T> attribute_data,
54 MutableSpan<bool> points_to_delete)
55{
56 /* Mark all points to be kept. */
57 points_to_delete.slice(range).fill(false);
58
60 stack.push(range);
61 while (!stack.is_empty()) {
62 const IndexRange sub_range = stack.pop();
63 /* Skip ranges with less than 3 points. All points are kept. */
64 if (sub_range.size() < 3) {
65 continue;
66 }
67 const IndexRange inside_range = sub_range.drop_front(1).drop_back(1);
68 /* Compute the maximum distance and the corresponding index. */
69 float max_dist = -1.0f;
70 int max_index = -1;
71 for (const int64_t index : inside_range) {
72 const float dist = perpendicular_distance(
73 positions, attribute_data, sub_range.first(), sub_range.last(), index);
74 if (dist > max_dist) {
75 max_dist = dist;
76 max_index = index - sub_range.first();
77 }
78 }
79
80 if (max_dist > epsilon) {
81 /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat
82 * the search on the left & right side. */
83 stack.push(sub_range.slice(0, max_index + 1));
84 stack.push(sub_range.slice(max_index, sub_range.size() - max_index));
85 }
86 else {
87 /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */
88 points_to_delete.slice(inside_range).fill(true);
89 }
90 }
91}
92
93template<typename T>
94static void curve_simplify(const Span<float3> positions,
95 const bool cyclic,
96 const float epsilon,
97 const Span<T> attribute_data,
98 MutableSpan<bool> points_to_delete)
99{
100 const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(
101 points_to_delete.as_span(), true);
103 selection_ranges.index_range(), 512, [&](const IndexRange range_of_ranges) {
104 for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) {
105 ramer_douglas_peucker(range, positions, epsilon, attribute_data, points_to_delete);
106 }
107 });
108
109 /* For cyclic curves, handle the last segment separately. */
110 const int points_num = positions.size();
111 if (cyclic && points_num > 2) {
112 const float dist = perpendicular_distance(
113 positions, attribute_data, points_num - 2, 0, points_num - 1);
114 if (dist <= epsilon) {
115 points_to_delete[points_num - 1] = true;
116 }
117 }
118}
119
120void curve_simplify(const Span<float3> positions,
121 const bool cyclic,
122 const float epsilon,
123 const GSpan attribute_data,
124 MutableSpan<bool> points_to_delete)
125
126{
127 bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) {
128 using T = decltype(dummy);
129 if constexpr (is_same_any_v<T, float, float2, float3>) {
130 curve_simplify(positions, cyclic, epsilon, attribute_data.typed<T>(), points_to_delete);
131 }
132 });
133}
134
136 const IndexMask &curves_selection,
137 const OffsetIndices<int> points_by_curve,
138 const VArray<bool> &cyclic,
139 const float epsilon,
140 const GSpan attribute_data,
141 IndexMaskMemory &memory)
142{
143 Array<bool> points_to_delete(positions.size(), false);
144 if (epsilon <= 0.0f) {
145 return IndexMask::from_bools(points_to_delete, memory);
146 }
148 points_by_curve, curves_selection, true, points_to_delete.as_mutable_span());
149 curves_selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
150 const IndexRange points = points_by_curve[curve_i];
151 bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) {
152 using T = decltype(dummy);
153 if constexpr (is_same_any_v<T, float, float2, float3>) {
154 curve_simplify(positions.slice(points),
155 cyclic[curve_i],
156 epsilon,
157 attribute_data.typed<T>().slice(points),
158 points_to_delete.as_mutable_span().slice(points));
159 }
160 });
161 });
162 return IndexMask::from_bools(points_to_delete, memory);
163}
164
165} // namespace blender::geometry
Low-level operations for curves.
long long int int64_t
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:248
const CPPType & type() const
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr IndexRange slice(int64_t start, int64_t size) const
constexpr IndexRange drop_front(int64_t n) const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr Span< T > as_span() const
Definition BLI_span.hh:661
constexpr int64_t size() const
Definition BLI_span.hh:252
bool is_empty() const
Definition BLI_stack.hh:308
void push(const T &value)
Definition BLI_stack.hh:213
IndexRange index_range() const
void foreach_index(Fn &&fn) const
#define T
Vector< IndexRange > find_all_ranges(const Span< T > span, const T &value)
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
void fill_points(OffsetIndices< int > points_by_curve, const IndexMask &curve_selection, GPointer value, GMutableSpan dst)
float perpendicular_distance(const Span< float3 > positions, const Span< T > attribute_data, const int64_t first_index, const int64_t last_index, const int64_t index)
static void ramer_douglas_peucker(const IndexRange range, const Span< float3 > positions, const float epsilon, const Span< T > attribute_data, MutableSpan< bool > points_to_delete)
IndexMask simplify_curve_attribute(const Span< float3 > positions, const IndexMask &curves_selection, const OffsetIndices< int > points_by_curve, const VArray< bool > &cyclic, float epsilon, GSpan attribute_data, IndexMaskMemory &memory)
void curve_simplify(const Span< float3 > positions, const bool cyclic, const float epsilon, const GSpan attribute_data, MutableSpan< bool > points_to_delete)
T distance(const T &a, const T &b)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
bool is_zero(const T &a)
T interpolate(const T &a, const T &b, const FactorT &t)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< float, 3 > float3