Splines: Add API functions for interpolating data

First, expand on the interpolation to evaluated points with a templated
helper function, and a function that takes a GSPan. Next, add a set of
functions to `Spline` for interpolating at arbitrary intervals between
the evaluated points. The code for doing that isn't that complicated
anyway, but it's nice to avoid repeating, and it might make it easier
to unroll the special cases for the first and last points if we require
the index factors to be sorted.
This commit is contained in:
Hans Goudey 2021-06-09 14:53:39 -05:00
parent df2a19eac7
commit 675677ec67
5 changed files with 94 additions and 61 deletions

View File

@ -172,6 +172,25 @@ class Spline {
blender::Array<float> sample_uniform_index_factors(const int samples_size) const;
LookupResult lookup_data_from_index_factor(const float index_factor) const;
void sample_based_on_index_factors(const blender::fn::GVArray &src,
blender::Span<float> index_factors,
blender::fn::GMutableSpan dst) const;
template<typename T>
void sample_based_on_index_factors(const blender::VArray<T> &src,
blender::Span<float> index_factors,
blender::MutableSpan<T> dst) const
{
this->sample_based_on_index_factors(
blender::fn::GVArray_For_VArray(src), index_factors, blender::fn::GMutableSpan(dst));
}
template<typename T>
void sample_based_on_index_factors(blender::Span<T> src,
blender::Span<float> index_factors,
blender::MutableSpan<T> dst) const
{
this->sample_based_on_index_factors(blender::VArray_For_Span(src), index_factors, dst);
}
/**
* Interpolate a virtual array of data with the size of the number of control points to the
* evaluated points. For poly splines, the lifetime of the returned virtual array must not
@ -179,6 +198,13 @@ class Spline {
*/
virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points(
const blender::fn::GVArray &source_data) const = 0;
blender::fn::GVArrayPtr interpolate_to_evaluated_points(blender::fn::GSpan data) const;
template<typename T>
blender::fn::GVArray_Typed<T> interpolate_to_evaluated_points(blender::Span<T> data) const
{
return blender::fn::GVArray_Typed<T>(
this->interpolate_to_evaluated_points(blender::fn::GSpan(data)));
}
protected:
virtual void correct_end_tangents() const = 0;

View File

@ -16,6 +16,7 @@
#include "BLI_array.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "BKE_spline.hh"
@ -27,6 +28,12 @@ using blender::float3;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray;
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_Typed;
using blender::fn::GVArrayPtr;
Spline::Type Spline::type() const
{
@ -234,8 +241,7 @@ Span<float3> Spline::evaluated_normals() const
calculate_normals_z_up(tangents, normals);
/* Rotate the generated normals with the interpolated tilt data. */
blender::fn::GVArray_Typed<float> tilts{
this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(this->tilts()))};
GVArray_Typed<float> tilts = this->interpolate_to_evaluated_points(this->tilts());
for (const int i : normals.index_range()) {
normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]);
}
@ -341,3 +347,34 @@ void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated)
minmax_v3v3_v3(min, max, position);
}
}
GVArrayPtr Spline::interpolate_to_evaluated_points(GSpan data) const
{
return this->interpolate_to_evaluated_points(GVArray_For_GSpan(data));
}
/**
* Sample any input data with a value for each evaluated point (already interpolated to evaluated
* points) to arbitrary parameters in betwen the evaluated points. The interpolation is quite
* simple, but this handles the cyclic and end point special cases.
*/
void Spline::sample_based_on_index_factors(const GVArray &src,
Span<float> index_factors,
GMutableSpan dst) const
{
BLI_assert(src.size() == this->evaluated_points_size());
blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
const GVArray_Typed<T> src_typed = src.typed<T>();
MutableSpan<T> dst_typed = dst.typed<T>();
blender::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const LookupResult interp = this->lookup_data_from_index_factor(index_factors[i]);
dst_typed[i] = blender::attribute_math::mix2(interp.factor,
src_typed[interp.evaluated_index],
src_typed[interp.next_evaluated_index]);
}
});
});
}

View File

@ -26,6 +26,7 @@ using blender::float3;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
using blender::fn::GVArray_Typed;
SplinePtr NURBSpline::copy() const
{
@ -434,10 +435,9 @@ Span<float3> NURBSpline::evaluated_positions() const
const int eval_size = this->evaluated_points_size();
evaluated_position_cache_.resize(eval_size);
blender::fn::GVArray_Typed<float3> evaluated_positions{
this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span<float3>(positions_))};
evaluated_positions->materialize(evaluated_position_cache_);
/* TODO: Avoid copying the evaluated data from the temporary array. */
GVArray_Typed<float3> evaluated = Spline::interpolate_to_evaluated_points(positions_.as_span());
evaluated->materialize(evaluated_position_cache_);
position_cache_dirty_ = false;
return evaluated_position_cache_;

View File

@ -76,34 +76,17 @@ struct SampleModeParam {
std::optional<int> count;
};
template<typename T>
static void sample_span_to_output_spline(const Spline &input_spline,
Span<float> index_factors,
const VArray<T> &input_data,
MutableSpan<T> output_data)
{
BLI_assert(input_data.size() == input_spline.evaluated_points_size());
parallel_for(output_data.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const Spline::LookupResult interp = input_spline.lookup_data_from_index_factor(
index_factors[i]);
output_data[i] = blender::attribute_math::mix2(interp.factor,
input_data[interp.evaluated_index],
input_data[interp.next_evaluated_index]);
}
});
}
static SplinePtr resample_spline(const Spline &input_spline, const int count)
{
std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>();
output_spline->set_cyclic(input_spline.is_cyclic());
output_spline->normal_mode = input_spline.normal_mode;
if (input_spline.evaluated_edges_size() < 1) {
output_spline->resize(1);
output_spline->positions().first() = input_spline.positions().first();
if (input_spline.evaluated_edges_size() < 1 || count == 1) {
output_spline->add_point(input_spline.positions().first(),
input_spline.tilts().first(),
input_spline.radii().first());
output_spline->attributes.reallocate(1);
return output_spline;
}
@ -111,26 +94,18 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count);
{
GVArray_For_Span positions(input_spline.evaluated_positions());
GVArray_Typed<float3> positions_typed(positions);
sample_span_to_output_spline<float3>(
input_spline, uniform_samples, positions_typed, output_spline->positions());
}
{
GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points(
GVArray_For_Span(input_spline.radii()));
GVArray_Typed<float> interpolated_data_typed{*interpolated_data};
sample_span_to_output_spline<float>(
input_spline, uniform_samples, interpolated_data_typed, output_spline->radii());
}
{
GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points(
GVArray_For_Span(input_spline.tilts()));
GVArray_Typed<float> interpolated_data_typed{*interpolated_data};
sample_span_to_output_spline<float>(
input_spline, uniform_samples, interpolated_data_typed, output_spline->tilts());
}
input_spline.sample_based_on_index_factors<float3>(
input_spline.evaluated_positions(), uniform_samples, output_spline->positions());
input_spline.sample_based_on_index_factors<float>(
input_spline.interpolate_to_evaluated_points(input_spline.radii()),
uniform_samples,
output_spline->radii());
input_spline.sample_based_on_index_factors<float>(
input_spline.interpolate_to_evaluated_points(input_spline.tilts()),
uniform_samples,
output_spline->tilts());
output_spline->attributes.reallocate(count);
input_spline.attributes.foreach_attribute(
@ -147,16 +122,12 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
BLI_assert_unreachable();
return false;
}
GVArrayPtr interpolated_attribute = input_spline.interpolate_to_evaluated_points(
GVArray_For_GSpan(*input_attribute));
attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
using T = decltype(dummy);
GVArray_Typed<T> interpolated_attribute_typed{*interpolated_attribute};
sample_span_to_output_spline<T>(input_spline,
uniform_samples,
interpolated_attribute_typed,
(*output_attribute).typed<T>());
});
input_spline.sample_based_on_index_factors(
*input_spline.interpolate_to_evaluated_points(*input_attribute),
uniform_samples,
*output_attribute);
return true;
},
ATTR_DOMAIN_POINT);
@ -177,7 +148,7 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve,
else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
BLI_assert(mode_param.length);
const float length = spline->length();
const int count = length / *mode_param.length;
const int count = std::max(int(length / *mode_param.length), 1);
output_curve->add_spline(resample_spline(*spline, count));
}
}

View File

@ -181,8 +181,7 @@ static void spline_extrude_to_mesh_data(const Spline &spline,
Span<float3> normals = spline.evaluated_normals();
Span<float3> profile_positions = profile_spline.evaluated_positions();
GVArray_Typed<float> radii{
spline.interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(spline.radii()))};
GVArray_Typed<float> radii = spline.interpolate_to_evaluated_points(spline.radii());
for (const int i_ring : IndexRange(spline_vert_len)) {
float4x4 point_matrix = float4x4::from_normalized_axis_data(
positions[i_ring], normals[i_ring], tangents[i_ring]);