Blender V5.0
curve_catmull_rom.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_task.hh"
10
11#include "BKE_attribute_math.hh"
12#include "BKE_curves.hh"
13
15
16int calculate_evaluated_num(const int points_num, const bool cyclic, const int resolution)
17{
18 const int points_per_segment = std::max(1, resolution);
19 const int eval_num = points_per_segment * segments_num(points_num, cyclic);
20 if (cyclic) {
21 /* Make sure there is a single evaluated point for the single-point curve case. */
22 return std::max(eval_num, 1);
23 }
24 /* If the curve isn't cyclic, one last point is added to the final point. */
25 return eval_num + 1;
26}
27
28float4 calculate_basis(const float parameter)
29{
30 /* Adapted from Cycles #catmull_rom_basis_eval function. */
31 const float t = parameter;
32 const float s = 1.0f - parameter;
33 return {
34 -t * s * s,
35 2.0f + t * t * (3.0f * t - 5.0f),
36 2.0f + s * s * (3.0f * s - 5.0f),
37 -s * t * t,
38 };
39}
40
41template<typename T>
42static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, MutableSpan<T> dst)
43{
44 const float step = 1.0f / dst.size();
45 dst.first() = b;
46 for (const int i : dst.index_range().drop_front(1)) {
47 dst[i] = interpolate<T>(a, b, c, d, i * step);
48 }
49}
50
56template<typename T, typename RangeForSegmentFn>
57static void interpolate_to_evaluated(const Span<T> src,
58 const bool cyclic,
59 const RangeForSegmentFn &range_fn,
61
62{
63 /* - First deal with one and two point curves need special attention.
64 * - Then evaluate the first and last segment(s) whose control points need to wrap around
65 * to the other side of the source array.
66 * - Finally evaluate all of the segments in the middle in parallel. */
67
68 if (src.size() == 1) {
69 dst.first() = src.first();
70 return;
71 }
72
73 const IndexRange first = range_fn(0);
74
75 if (src.size() == 2) {
76 evaluate_segment(src.first(), src.first(), src.last(), src.last(), dst.slice(first));
77 if (cyclic) {
78 const IndexRange last = range_fn(1);
79 evaluate_segment(src.last(), src.last(), src.first(), src.first(), dst.slice(last));
80 }
81 else {
82 dst.last() = src.last();
83 }
84 return;
85 }
86
87 const IndexRange second_to_last = range_fn(src.index_range().last(1));
88 const IndexRange last = range_fn(src.index_range().last());
89 if (cyclic) {
90 evaluate_segment(src.last(), src[0], src[1], src[2], dst.slice(first));
91 evaluate_segment(src.last(2), src.last(1), src.last(), src.first(), dst.slice(second_to_last));
92 evaluate_segment(src.last(1), src.last(), src[0], src[1], dst.slice(last));
93 }
94 else {
95 evaluate_segment(src[0], src[0], src[1], src[2], dst.slice(first));
96 evaluate_segment(src.last(2), src.last(1), src.last(), src.last(), dst.slice(second_to_last));
97 /* For non-cyclic curves, the last segment should always just have a single point. We could
98 * assert that the size of the provided range is 1 here, but that would require specializing
99 * the #range_fn implementation for the last point, which may have a performance cost. */
100 dst.last() = src.last();
101 }
102
103 /* Evaluate every segment that isn't the first or last. */
104 const IndexRange inner_range = src.index_range().drop_back(2).drop_front(1);
105 threading::parallel_for(inner_range, 512, [&](IndexRange range) {
106 for (const int i : range) {
107 const IndexRange segment = range_fn(i);
108 evaluate_segment(src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment));
109 }
110 });
111}
112
113template<typename T>
114static void interpolate_to_evaluated(const Span<T> src,
115 const bool cyclic,
116 const int resolution,
117 MutableSpan<T> dst)
118
119{
120 BLI_assert(dst.size() == calculate_evaluated_num(src.size(), cyclic, resolution));
122 src,
123 cyclic,
124 [resolution](const int segment_i) -> IndexRange {
125 const int points_per_segment = std::max(1, resolution);
126 return {segment_i * points_per_segment, points_per_segment};
127 },
128 dst);
129}
130
131template<typename T>
132static void interpolate_to_evaluated(const Span<T> src,
133 const bool cyclic,
134 const OffsetIndices<int> evaluated_offsets,
135 MutableSpan<T> dst)
136
137{
139 src,
140 cyclic,
141 [evaluated_offsets](const int segment_i) -> IndexRange {
142 return evaluated_offsets[segment_i];
143 },
144 dst);
145}
146
148 const bool cyclic,
149 const int resolution,
150 GMutableSpan dst)
151{
152 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
153 using T = decltype(dummy);
154 interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
155 });
156}
157
159 const bool cyclic,
160 const OffsetIndices<int> evaluated_offsets,
161 GMutableSpan dst)
162{
163 attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
164 using T = decltype(dummy);
165 interpolate_to_evaluated(src.typed<T>(), cyclic, evaluated_offsets, dst.typed<T>());
166 });
167}
168
169} // namespace blender::bke::curves::catmull_rom
Low-level operations for curves.
#define BLI_assert(a)
Definition BLI_assert.h:46
const CPPType & type() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr T & first() const
Definition BLI_span.hh:679
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
#define T
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, MutableSpan< T > dst)
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
int calculate_evaluated_num(int points_num, bool cyclic, int resolution)
float4 calculate_basis(const float parameter)
void interpolate_to_evaluated(GSpan src, bool cyclic, int resolution, GMutableSpan dst)
int segments_num(const int points_num, const bool cyclic)
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, 4 > float4
i
Definition text_draw.cc:230