Blender  V3.3
resample_curves.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
4 #include "BLI_task.hh"
5 
6 #include "FN_field.hh"
8 
9 #include "BKE_attribute_math.hh"
10 #include "BKE_curves.hh"
11 #include "BKE_curves_utils.hh"
12 #include "BKE_geometry_fields.hh"
13 
14 #include "GEO_resample_curves.hh"
15 
16 namespace blender::geometry {
17 
19 {
20  static fn::CustomMF_SI_SO<int, int> max_one_fn(
21  "Clamp Above One",
22  [](int value) { return std::max(1, value); },
24  auto clamp_op = std::make_shared<fn::FieldOperation>(
25  fn::FieldOperation(max_one_fn, {count_field}));
26 
27  return fn::Field<int>(std::move(clamp_op));
28 }
29 
31 {
32  static fn::CustomMF_SI_SI_SO<float, float, int> get_count_fn(
33  "Length Input to Count",
34  [](const float curve_length, const float sample_length) {
35  /* Find the number of sampled segments by dividing the total length by
36  * the sample length. Then there is one more sampled point than segment. */
37  const int count = int(curve_length / sample_length) + 1;
38  return std::max(1, count);
39  },
41 
42  auto get_count_op = std::make_shared<fn::FieldOperation>(fn::FieldOperation(
43  get_count_fn,
44  {fn::Field<float>(std::make_shared<bke::CurveLengthFieldInput>()), length_field}));
45 
46  return fn::Field<int>(std::move(get_count_op));
47 }
48 
53 static bool interpolate_attribute_to_curves(const bke::AttributeIDRef &attribute_id,
54  const std::array<int, CURVE_TYPES_NUM> &type_counts)
55 {
56  if (!attribute_id.is_named()) {
57  return true;
58  }
59  if (ELEM(attribute_id.name(),
60  "handle_type_left",
61  "handle_type_right",
62  "handle_left",
63  "handle_right")) {
64  return type_counts[CURVE_TYPE_BEZIER] != 0;
65  }
66  if (ELEM(attribute_id.name(), "nurbs_weight")) {
67  return type_counts[CURVE_TYPE_NURBS] != 0;
68  }
69  return true;
70 }
71 
76 {
77  static const Set<StringRef> no_interpolation{{
78  "handle_type_left",
79  "handle_type_right",
80  "handle_right",
81  "handle_left",
82  "nurbs_weight",
83  }};
84  return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name()));
85 }
86 
91  const CurveComponent &src_component,
92  CurveComponent &dst_component,
95  Vector<bke::GSpanAttributeWriter> &dst_attributes)
96 {
97  for (const int i : ids.index_range()) {
98  GVArray src_attribute = src_component.attributes()->lookup(ids[i], ATTR_DOMAIN_POINT);
99  BLI_assert(src_attribute);
100  src.append(src_attribute.get_internal_span());
101 
102  const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(src_attribute.type());
103  bke::GSpanAttributeWriter dst_attribute =
104  dst_component.attributes_for_write()->lookup_or_add_for_write_only_span(
105  ids[i], ATTR_DOMAIN_POINT, data_type);
106  dst.append(dst_attribute.span);
107  dst_attributes.append(std::move(dst_attribute));
108  }
109 }
110 
114 
116 
119 };
120 
125  CurveComponent &dst_component,
127 {
129  dst_component.get_for_write()->geometry);
130 
132  VectorSet<bke::AttributeIDRef> ids_no_interpolation;
133  src_component.attributes()->for_all(
134  [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
135  if (meta_data.domain != ATTR_DOMAIN_POINT) {
136  return true;
137  }
139  return true;
140  }
142  ids.add_new(id);
143  }
144  else {
145  ids_no_interpolation.add_new(id);
146  }
147  return true;
148  });
149 
150  /* Position is handled differently since it has non-generic interpolation for Bezier
151  * curves and because the evaluated positions are cached for each evaluated point. */
152  ids.remove_contained("position");
153 
155  ids, src_component, dst_component, result.src, result.dst, result.dst_attributes);
156 
157  /* Attributes that aren't interpolated like Bezier handles still have to be be copied
158  * to the result when there are any unselected curves of the corresponding type. */
159  retrieve_attribute_spans(ids_no_interpolation,
160  src_component,
161  dst_component,
162  result.src_no_interpolation,
163  result.dst_no_interpolation,
164  result.dst_attributes);
165 }
166 
167 static Curves *resample_to_uniform(const CurveComponent &src_component,
168  const fn::Field<bool> &selection_field,
169  const fn::Field<int> &count_field)
170 {
172  src_component.get_for_read()->geometry);
173 
174  /* Create the new curves without any points and evaluate the final count directly
175  * into the offsets array, in order to be accumulated into offsets later. */
176  Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num());
177  bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
178 
179  /* Directly copy curve attributes, since they stay the same (except for curve types). */
180  CustomData_copy(&src_curves.curve_data,
181  &dst_curves.curve_data,
182  CD_MASK_ALL,
183  CD_DUPLICATE,
184  src_curves.curves_num());
185  MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
186 
187  bke::GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
188  fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
189  evaluator.set_selection(selection_field);
190  evaluator.add_with_destination(count_field, dst_offsets);
191  evaluator.evaluate();
192  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
193  const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
194  src_curves.curves_range(), nullptr);
195 
196  /* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */
197  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
199  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
200 
201  /* All resampled curves are poly curves. */
202  dst_curves.fill_curve_types(selection, CURVE_TYPE_POLY);
203 
204  VArray<bool> curves_cyclic = src_curves.cyclic();
205  VArray<int8_t> curve_types = src_curves.curve_types();
206  Span<float3> evaluated_positions = src_curves.evaluated_positions();
207  MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
208 
209  AttributesForInterpolation attributes;
210  CurveComponent dst_component;
211  dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
212  gather_point_attributes_to_interpolate(src_component, dst_component, attributes);
213 
214  src_curves.ensure_evaluated_lengths();
215 
216  /* Sampling arbitrary attributes works by first interpolating them to the curve's standard
217  * "evaluated points" and then interpolating that result with the uniform samples. This is
218  * potentially wasteful when down-sampling a curve to many fewer points. There are two possible
219  * solutions: only sample the necessary points for interpolation, or first sample curve
220  * parameter/segment indices and evaluate the curve directly. */
221  Array<int> sample_indices(dst_curves.points_num());
222  Array<float> sample_factors(dst_curves.points_num());
223 
224  /* Use a "for each group of curves: for each attribute: for each curve" pattern to work on
225  * smaller sections of data that ideally fit into CPU cache better than simply one attribute at a
226  * time or one curve at a time. */
227  threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) {
228  const IndexMask sliced_selection = selection.slice(selection_range);
229 
230  Vector<std::byte> evaluated_buffer;
231 
232  /* Gather uniform samples based on the accumulated lengths of the original curve. */
233  for (const int i_curve : sliced_selection) {
234  const bool cyclic = curves_cyclic[i_curve];
235  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
236  const Span<float> lengths = src_curves.evaluated_lengths_for_curve(i_curve, cyclic);
237  if (lengths.is_empty()) {
238  /* Handle curves with only one evaluated point. */
239  sample_indices.as_mutable_span().slice(dst_points).fill(0);
240  sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
241  }
242  else {
243  length_parameterize::sample_uniform(lengths,
244  !curves_cyclic[i_curve],
245  sample_indices.as_mutable_span().slice(dst_points),
246  sample_factors.as_mutable_span().slice(dst_points));
247  }
248  }
249 
250  /* For every attribute, evaluate attributes from every curve in the range in the original
251  * curve's "evaluated points", then use linear interpolation to sample to the result. */
252  for (const int i_attribute : attributes.dst.index_range()) {
253  attribute_math::convert_to_static_type(attributes.src[i_attribute].type(), [&](auto dummy) {
254  using T = decltype(dummy);
255  Span<T> src = attributes.src[i_attribute].typed<T>();
256  MutableSpan<T> dst = attributes.dst[i_attribute].typed<T>();
257 
258  for (const int i_curve : sliced_selection) {
259  const IndexRange src_points = src_curves.points_for_curve(i_curve);
260  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
261 
262  if (curve_types[i_curve] == CURVE_TYPE_POLY) {
263  length_parameterize::interpolate(src.slice(src_points),
264  sample_indices.as_span().slice(dst_points),
265  sample_factors.as_span().slice(dst_points),
266  dst.slice(dst_points));
267  }
268  else {
269  const int evaluated_size = src_curves.evaluated_points_for_curve(i_curve).size();
270  evaluated_buffer.clear();
271  evaluated_buffer.resize(sizeof(T) * evaluated_size);
272  MutableSpan<T> evaluated = evaluated_buffer.as_mutable_span().cast<T>();
273  src_curves.interpolate_to_evaluated(i_curve, src.slice(src_points), evaluated);
274 
275  length_parameterize::interpolate(evaluated.as_span(),
276  sample_indices.as_span().slice(dst_points),
277  sample_factors.as_span().slice(dst_points),
278  dst.slice(dst_points));
279  }
280  }
281  });
282  }
283 
284  /* Interpolate the evaluated positions to the resampled curves. */
285  for (const int i_curve : sliced_selection) {
286  const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve);
287  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
288  length_parameterize::interpolate(evaluated_positions.slice(src_points),
289  sample_indices.as_span().slice(dst_points),
290  sample_factors.as_span().slice(dst_points),
291  dst_positions.slice(dst_points));
292  }
293 
294  /* Fill the default value for non-interpolating attributes that still must be copied. */
295  for (GMutableSpan dst : attributes.dst_no_interpolation) {
296  for (const int i_curve : sliced_selection) {
297  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
298  dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size());
299  }
300  }
301  });
302 
303  /* Any attribute data from unselected curve points can be directly copied. */
304  for (const int i : attributes.src.index_range()) {
306  src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
307  }
308  for (const int i : attributes.src_no_interpolation.index_range()) {
309  bke::curves::copy_point_data(src_curves,
310  dst_curves,
311  unselected_ranges,
312  attributes.src_no_interpolation[i],
313  attributes.dst_no_interpolation[i]);
314  }
315 
316  /* Copy positions for unselected curves. */
317  Span<float3> src_positions = src_curves.positions();
319  src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
320 
321  for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) {
322  attribute.finish();
323  }
324 
325  return dst_curves_id;
326 }
327 
329  const fn::Field<bool> &selection_field,
330  const fn::Field<int> &count_field)
331 {
332  return resample_to_uniform(src_component, selection_field, get_count_input_max_one(count_field));
333 }
334 
336  const fn::Field<bool> &selection_field,
337  const fn::Field<float> &segment_length_field)
338 {
339  return resample_to_uniform(
340  src_component, selection_field, get_count_input_from_length(segment_length_field));
341 }
342 
344  const fn::Field<bool> &selection_field)
345 {
347  src_component.get_for_read()->geometry);
348  src_curves.ensure_evaluated_offsets();
349 
350  bke::GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
351  fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
352  evaluator.set_selection(selection_field);
353  evaluator.evaluate();
354  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
355  const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
356  src_curves.curves_range(), nullptr);
357 
358  Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num());
359  bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
360 
361  /* Directly copy curve attributes, since they stay the same (except for curve types). */
362  CustomData_copy(&src_curves.curve_data,
363  &dst_curves.curve_data,
364  CD_MASK_ALL,
365  CD_DUPLICATE,
366  src_curves.curves_num());
367  /* All resampled curves are poly curves. */
368  dst_curves.fill_curve_types(selection, CURVE_TYPE_POLY);
369  MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
370 
372  threading::parallel_for(selection.index_range(), 4096, [&](IndexRange range) {
373  for (const int i : selection.slice(range)) {
374  dst_offsets[i] = src_curves.evaluated_points_for_curve(i).size();
375  }
376  });
377  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
379 
380  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
381 
382  /* Create the correct number of uniform-length samples for every selected curve. */
383  Span<float3> evaluated_positions = src_curves.evaluated_positions();
384  MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
385 
386  AttributesForInterpolation attributes;
387  CurveComponent dst_component;
388  dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
389  gather_point_attributes_to_interpolate(src_component, dst_component, attributes);
390 
391  threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) {
392  const IndexMask sliced_selection = selection.slice(selection_range);
393 
394  /* Evaluate generic point attributes directly to the result attributes. */
395  for (const int i_attribute : attributes.dst.index_range()) {
396  attribute_math::convert_to_static_type(attributes.src[i_attribute].type(), [&](auto dummy) {
397  using T = decltype(dummy);
398  Span<T> src = attributes.src[i_attribute].typed<T>();
399  MutableSpan<T> dst = attributes.dst[i_attribute].typed<T>();
400 
401  for (const int i_curve : sliced_selection) {
402  const IndexRange src_points = src_curves.points_for_curve(i_curve);
403  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
404  src_curves.interpolate_to_evaluated(
405  i_curve, src.slice(src_points), dst.slice(dst_points));
406  }
407  });
408  }
409 
410  /* Copy the evaluated positions to the selected curves. */
411  for (const int i_curve : sliced_selection) {
412  const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve);
413  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
414  dst_positions.slice(dst_points).copy_from(evaluated_positions.slice(src_points));
415  }
416 
417  /* Fill the default value for non-interpolating attributes that still must be copied. */
418  for (GMutableSpan dst : attributes.dst_no_interpolation) {
419  for (const int i_curve : sliced_selection) {
420  const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
421  dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size());
422  }
423  }
424  });
425 
426  /* Any attribute data from unselected curve points can be directly copied. */
427  for (const int i : attributes.src.index_range()) {
429  src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
430  }
431  for (const int i : attributes.src_no_interpolation.index_range()) {
432  bke::curves::copy_point_data(src_curves,
433  dst_curves,
434  unselected_ranges,
435  attributes.src_no_interpolation[i],
436  attributes.dst_no_interpolation[i]);
437  }
438 
439  /* Copy positions for unselected curves. */
440  Span<float3> src_positions = src_curves.positions();
442  src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
443 
444  for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) {
445  attribute.finish();
446  }
447 
448  return dst_curves_id;
449 }
450 
451 } // namespace blender::geometry
@ ATTR_DOMAIN_CURVE
Definition: BKE_attribute.h:31
@ ATTR_DOMAIN_POINT
Definition: BKE_attribute.h:27
Low-level operations for curves.
Low-level operations for curves.
@ CD_DUPLICATE
void CustomData_copy(const struct CustomData *source, struct CustomData *dest, eCustomDataMask mask, eCDAllocType alloctype, int totelem)
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define ELEM(...)
@ CURVE_TYPE_BEZIER
@ CURVE_TYPE_NURBS
@ CURVE_TYPE_POLY
#define CD_MASK_ALL
eCustomDataType
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Sky Generate a procedural sky texture Noise Generate fractal Perlin noise Wave Generate procedural bands or rings with noise Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between and object coordinate space Combine Create a color from its and value channels Color Retrieve a color attribute
const Curves * get_for_read() const
void replace(Curves *curve, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
std::optional< blender::bke::AttributeAccessor > attributes() const final
std::optional< blender::bke::MutableAttributeAccessor > attributes_for_write() final
const CPPType & type() const
Vector< IndexRange > extract_ranges_invert(const IndexRange full_range, Vector< int64_t > *r_skip_amounts=nullptr) const
Definition: index_mask.cc:100
IndexRange index_range() const
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition: BLI_span.hh:581
constexpr T & last(const int64_t n=0) const
Definition: BLI_span.hh:680
constexpr Span slice(int64_t start, int64_t size) const
Definition: BLI_span.hh:142
constexpr IndexRange index_range() const
Definition: BLI_span.hh:401
void add_new(const Key &key)
void remove_contained(const Key &key)
void append(const T &value)
Definition: BLI_vector.hh:433
MutableSpan< float3 > positions_for_write()
void ensure_can_interpolate_to_evaluated() const
IndexRange curves_range() const
Definition: BKE_curves.hh:795
const std::array< int, CURVE_TYPES_NUM > & curve_type_counts() const
Definition: BKE_curves.hh:816
static CurvesGeometry & wrap(::CurvesGeometry &dna_struct)
Definition: BKE_curves.hh:138
void resize(int points_num, int curves_num)
void fill_curve_types(CurveType type)
MutableSpan< int > offsets_for_write()
Span< float3 > evaluated_positions() const
VArray< int8_t > curve_types() const
VArray< bool > cyclic() const
SyclQueue void void * src
int count
void fill_curve_counts(const bke::CurvesGeometry &curves, Span< IndexRange > curve_ranges, MutableSpan< int > counts)
Definition: curves_utils.cc:13
void accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
Definition: curves_utils.cc:28
void copy_point_data(const CurvesGeometry &src_curves, const CurvesGeometry &dst_curves, Span< IndexRange > curve_ranges, GSpan src, GMutableSpan dst)
Definition: curves_utils.cc:40
static struct PartialUpdateUser * wrap(PartialUpdateUserImpl *user)
eCustomDataType cpp_type_to_custom_data_type(const blender::CPPType &type)
Definition: customdata.cc:5337
Curves * curves_new_nomain(int points_num, int curves_num)
Definition: curves.cc:367
Curves * resample_to_count(const CurveComponent &src_component, const fn::Field< bool > &selection_field, const fn::Field< int > &count_field)
static void gather_point_attributes_to_interpolate(const CurveComponent &src_component, CurveComponent &dst_component, AttributesForInterpolation &result)
static fn::Field< int > get_count_input_from_length(const fn::Field< float > &length_field)
static void retrieve_attribute_spans(const Span< bke::AttributeIDRef > ids, const CurveComponent &src_component, CurveComponent &dst_component, Vector< GSpan > &src, Vector< GMutableSpan > &dst, Vector< bke::GSpanAttributeWriter > &dst_attributes)
static Curves * resample_to_uniform(const CurveComponent &src_component, const fn::Field< bool > &selection_field, const fn::Field< int > &count_field)
static fn::Field< int > get_count_input_max_one(const fn::Field< int > &count_field)
Curves * resample_to_evaluated(const CurveComponent &src_component, const fn::Field< bool > &selection_field)
Curves * resample_to_length(const CurveComponent &src_component, const fn::Field< bool > &selection_field, const fn::Field< float > &segment_length_field)
static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attribute_id)
static bool interpolate_attribute_to_curves(const bke::AttributeIDRef &attribute_id, const std::array< int, CURVE_TYPES_NUM > &type_counts)
void interpolate(const Span< T > src, const Span< int > indices, const Span< float > factors, MutableSpan< T > dst)
void parallel_for(IndexRange range, int64_t grain_size, const Function &function)
Definition: BLI_task.hh:51
CustomData curve_data
CurvesGeometry geometry
Vector< bke::GSpanAttributeWriter > dst_attributes
float max