Blender V5.0
node_geo_curve_spline_parameter.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
5#include "BLI_array_utils.hh"
6#include "BLI_task.hh"
7
8#include "BKE_curves.hh"
9
10#include "node_geometry_util.hh"
11
13
15{
16 b.add_output<decl::Float>("Factor").field_source().description(
17 "For points, the portion of the spline's total length at the control point. For "
18 "Splines, the factor of that spline within the entire curve");
19 b.add_output<decl::Float>("Length").field_source().description(
20 "For points, the distance along the control point's spline, For splines, the "
21 "distance along the entire curve");
22 b.add_output<decl::Int>("Index").field_source().description(
23 "Each control point's index on its spline");
24}
25
32{
34
35 Array<float> lengths(curves.curves_num());
36 const VArray<bool> cyclic = curves.cyclic();
37 float length = 0.0f;
38 for (const int i : curves.curves_range()) {
39 lengths[i] = length;
40 length += curves.evaluated_length_total_for_curve(i, cyclic[i]);
41 }
42
43 return lengths;
44}
45
47{
48 const VArray<bool> cyclic = curves.cyclic();
50
51 const int last_index = curves.curves_num() - 1;
52 const float total_length = lengths.last() + curves.evaluated_length_total_for_curve(
53 last_index, cyclic[last_index]);
54 if (total_length > 0.0f) {
55 const float factor = 1.0f / total_length;
56 for (float &value : lengths) {
57 value *= factor;
58 }
59 }
60 else {
61 /* It is arbitrary what to do in those rare cases when all the points are
62 * in the same position. In this case we are just arbitrarily giving a valid
63 * value in the range based on the curve index. */
64 for (const int i : lengths.index_range()) {
65 lengths[i] = i / (lengths.size() - 1.0f);
66 }
67 }
68 return lengths;
69}
70
82 const bke::CurvesGeometry &curves,
83 const FunctionRef<void(MutableSpan<float>, float)> postprocess_lengths_for_curve)
84{
86 const OffsetIndices points_by_curve = curves.points_by_curve();
87 const VArray<int8_t> types = curves.curve_types();
88 const VArray<int> resolutions = curves.resolution();
89 const VArray<bool> cyclic = curves.cyclic();
90
92
93 threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) {
94 for (const int i_curve : range) {
95 const IndexRange points = points_by_curve[i_curve];
96 const bool is_cyclic = cyclic[i_curve];
97 const Span<float> evaluated_lengths = curves.evaluated_lengths_for_curve(i_curve, is_cyclic);
98 MutableSpan<float> lengths = result.as_mutable_span().slice(points);
99 lengths.first() = 0.0f;
100 const float last_evaluated_length = evaluated_lengths.is_empty() ? 0.0f :
101 evaluated_lengths.last();
102
103 float total;
104 switch (types[i_curve]) {
105 case CURVE_TYPE_CATMULL_ROM: {
106 const int resolution = resolutions[i_curve];
107 for (const int i : IndexRange(points.size()).drop_back(1)) {
108 lengths[i + 1] = evaluated_lengths[resolution * (i + 1) - 1];
109 }
110 total = last_evaluated_length;
111 break;
112 }
113 case CURVE_TYPE_POLY:
114 lengths.drop_front(1).copy_from(evaluated_lengths.take_front(lengths.size() - 1));
115 total = last_evaluated_length;
116 break;
117 case CURVE_TYPE_BEZIER: {
118 const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(i_curve);
119 for (const int i : IndexRange(points.size()).drop_back(1)) {
120 lengths[i + 1] = evaluated_lengths[offsets[i + 1] - 1];
121 }
122 total = last_evaluated_length;
123 break;
124 }
125 case CURVE_TYPE_NURBS: {
126 const Span<float3> positions = curves.positions().slice(points);
127 float length = 0.0f;
128 for (const int i : positions.index_range().drop_back(1)) {
129 lengths[i] = length;
130 length += math::distance(positions[i], positions[i + 1]);
131 }
132 lengths.last() = length;
133 if (is_cyclic) {
134 length += math::distance(positions.first(), positions.last());
135 }
136 total = length;
137 break;
138 }
139 }
140 postprocess_lengths_for_curve(lengths, total);
141 }
142 });
143 return result;
144}
145
146static void convert_lengths_to_factors(MutableSpan<float> lengths, const float total_curve_length)
147{
148 if (total_curve_length > 0.0f) {
149 const float factor = 1.0f / total_curve_length;
150 for (float &value : lengths.drop_front(1)) {
151 value *= factor;
152 }
153 }
154 else if (lengths.size() == 1) {
155 /* The curve is a single point. */
156 lengths[0] = 0.0f;
157 }
158 else {
159 /* It is arbitrary what to do in those rare cases when all the
160 * points are in the same position. In this case we are just
161 * arbitrarily giving a valid
162 * value in the range based on the point index. */
163 for (const int i : lengths.index_range()) {
164 lengths[i] = i / (lengths.size() - 1.0f);
165 }
166 }
167}
172
174 public:
176 : bke::CurvesFieldInput(CPPType::get<float>(), "Spline Parameter node")
177 {
179 }
180
182 const AttrDomain domain,
183 const IndexMask & /*mask*/) const final
184 {
185 switch (domain) {
186 case AttrDomain::Point:
188 case AttrDomain::Curve:
190 default:
192 return {};
193 }
194 }
195
196 uint64_t hash() const override
197 {
198 return 29837456298;
199 }
200
201 bool is_equal_to(const fn::FieldNode &other) const override
202 {
203 return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr;
204 }
205};
206
208 public:
210 : bke::CurvesFieldInput(CPPType::get<float>(), "Curve Length node")
211 {
213 }
214
216 const AttrDomain domain,
217 const IndexMask & /*mask*/) const final
218 {
219 switch (domain) {
220 case AttrDomain::Point:
222 curves, [](MutableSpan<float> /*lengths*/, const float /*total*/) {}));
223 case AttrDomain::Curve:
225 default:
227 return {};
228 }
229 }
230
231 uint64_t hash() const override
232 {
233 return 345634563454;
234 }
235
236 bool is_equal_to(const fn::FieldNode &other) const override
237 {
238 return dynamic_cast<const CurveLengthParameterFieldInput *>(&other) != nullptr;
239 }
240};
241
243 public:
244 IndexOnSplineFieldInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Spline Index")
245 {
247 }
248
250 const AttrDomain domain,
251 const IndexMask & /*mask*/) const final
252 {
253 if (domain != AttrDomain::Point) {
254 return {};
255 }
256 Array<int> result(curves.points_num());
257 const OffsetIndices points_by_curve = curves.points_by_curve();
258 threading::parallel_for(curves.curves_range(), 1024, [&](IndexRange range) {
259 for (const int i_curve : range) {
260 MutableSpan<int> indices = result.as_mutable_span().slice(points_by_curve[i_curve]);
261 array_utils::fill_index_range(indices);
262 }
263 });
264 return VArray<int>::from_container(std::move(result));
265 }
266
268 {
269 return 4536246522;
270 }
271
272 bool is_equal_to(const fn::FieldNode &other) const final
273 {
274 return dynamic_cast<const IndexOnSplineFieldInput *>(&other) != nullptr;
275 }
276
277 std::optional<AttrDomain> preferred_domain(const bke::CurvesGeometry & /*curves*/) const final
278 {
279 return AttrDomain::Point;
280 }
281};
282
284{
285 Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()};
286 Field<float> length_field{std::make_shared<CurveLengthParameterFieldInput>()};
287 Field<int> index_on_spline_field{std::make_shared<IndexOnSplineFieldInput>()};
288 params.set_output("Factor", std::move(parameter_field));
289 params.set_output("Length", std::move(length_field));
290 params.set_output("Index", std::move(index_on_spline_field));
291}
292
293static void node_register()
294{
295 static blender::bke::bNodeType ntype;
296 geo_node_type_base(&ntype, "GeometryNodeSplineParameter", GEO_NODE_CURVE_SPLINE_PARAMETER);
297 ntype.ui_name = "Spline Parameter";
298 ntype.ui_description = "Retrieve how far along each spline a control point is";
299 ntype.enum_name_legacy = "SPLINE_PARAMETER";
300 ntype.nclass = NODE_CLASS_INPUT;
302 ntype.declare = node_declare;
304}
305NOD_REGISTER_NODE(node_register)
306
307} // namespace blender::nodes::node_geo_curve_spline_parameter_cc
Low-level operations for curves.
#define NODE_CLASS_INPUT
Definition BKE_node.hh:447
#define GEO_NODE_CURVE_SPLINE_PARAMETER
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define final(a, b, c)
Definition BLI_hash.h:19
#define NOD_REGISTER_NODE(REGISTER_FUNC)
unsigned long long int uint64_t
static VArray from_container(ContainerT container)
int64_t size() const
Definition BLI_array.hh:256
const T & last(const int64_t n=0) const
Definition BLI_array.hh:296
IndexRange index_range() const
Definition BLI_array.hh:360
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan drop_front(const int64_t n) const
Definition BLI_span.hh:607
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
VArray< int > resolution() const
float evaluated_length_total_for_curve(int curve_index, bool cyclic) const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
GVArray get_varray_for_context(const bke::CurvesGeometry &curves, const AttrDomain domain, const IndexMask &) const final
GVArray get_varray_for_context(const bke::CurvesGeometry &curves, const AttrDomain domain, const IndexMask &) const final
GVArray get_varray_for_context(const bke::CurvesGeometry &curves, const AttrDomain domain, const IndexMask &) const final
std::optional< AttrDomain > preferred_domain(const bke::CurvesGeometry &) const final
nullptr float
float length(VecOp< float, D >) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static char ** types
Definition makesdna.cc:71
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
static Array< float > calculate_point_lengths(const bke::CurvesGeometry &curves, const FunctionRef< void(MutableSpan< float >, float)> postprocess_lengths_for_curve)
static Array< float > calculate_point_parameters(const bke::CurvesGeometry &curves)
static Array< float > accumulated_lengths_curve_domain(const bke::CurvesGeometry &curves)
static void convert_lengths_to_factors(MutableSpan< float > lengths, const float total_curve_length)
static Array< float > calculate_curve_parameters(const bke::CurvesGeometry &curves)
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
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230