Curves: Bezier and general interpolate to evaluated utility

This commit implements generic evaluation for Bezier curves (which is
really just linear interpolation, since attributes are not stored on
Bezier handles). For complete parity with the old curve type, we would
have to add options for this (RNA: `Spline.radius_interpolation`),
but it's not clear that we want to do that.

This also adds a generic `interpolate_to_evaluate` utility on curves
that hides the implementation details. Though there is theoretically
a performance cost to that, without some abstraction calling code
would usually be too complex.

Differential Revision: https://developer.blender.org/D14447
This commit is contained in:
Hans Goudey 2022-03-25 09:03:35 -05:00
parent c0016d85b2
commit cea51c1bb5
4 changed files with 164 additions and 0 deletions

View File

@ -266,6 +266,15 @@ class CurvesGeometry : public ::CurvesGeometry {
Span<float3> evaluated_positions() const;
/**
* Evaluate a generic data to the standard evaluated points of a specific curve,
* defined by the resolution attribute or other factors, depending on the curve type.
*
* \warning This function expects offsets to the evaluated points for each curve to be
* calculated. That can be ensured with #ensure_evaluated_offsets.
*/
void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const;
private:
/**
* Make sure the basis weights for NURBS curve's evaluated points are calculated.
@ -381,6 +390,13 @@ void calculate_evaluated_positions(Span<float3> positions,
Span<int> evaluated_offsets,
MutableSpan<float3> evaluated_positions);
/**
* Evaluate generic data to the evaluated points, with counts for each segment described by
* #evaluated_offsets. Unlike other curve types, for Bezier curves generic data and positions
* are treated separately, since attribute values aren't stored for the handle control points.
*/
void interpolate_to_evaluated(GSpan src, Span<int> evaluated_offsets, GMutableSpan dst);
} // namespace bezier
namespace catmull_rom {

View File

@ -134,6 +134,46 @@ void calculate_evaluated_positions(const Span<float3> positions,
}
}
template<typename T>
static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
{
dst.first() = a;
const float step = 1.0f / dst.size();
for (const int i : dst.index_range().drop_front(1)) {
dst[i] = attribute_math::mix2(i * step, a, b);
}
}
template<typename T>
static void interpolate_to_evaluated(const Span<T> src,
const Span<int> evaluated_offsets,
MutableSpan<T> dst)
{
linear_interpolation(src.first(), src[1], dst.take_front(evaluated_offsets.first()));
threading::parallel_for(
src.index_range().drop_back(1).drop_front(1), 512, [&](IndexRange range) {
for (const int i : range) {
const IndexRange segment_points = offsets_to_range(evaluated_offsets, i - 1);
linear_interpolation(src[i], src[i + 1], dst.slice(segment_points));
}
});
const IndexRange last_segment_points(evaluated_offsets.last(1),
evaluated_offsets.last() - evaluated_offsets.last(1));
linear_interpolation(src.last(), src.first(), dst.slice(last_segment_points));
}
void interpolate_to_evaluated(const GSpan src, const Span<int> evaluated_offsets, GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
interpolate_to_evaluated(src.typed<T>(), evaluated_offsets, dst.typed<T>());
}
});
}
/** \} */
} // namespace blender::bke::curves::bezier

View File

@ -689,6 +689,41 @@ Span<float3> CurvesGeometry::evaluated_positions() const
return this->runtime->evaluated_position_cache;
}
void CurvesGeometry::interpolate_to_evaluated(const int curve_index,
const GSpan src,
GMutableSpan dst) const
{
BLI_assert(!this->runtime->offsets_cache_dirty);
BLI_assert(!this->runtime->nurbs_basis_cache_dirty);
const IndexRange points = this->points_for_curve(curve_index);
BLI_assert(src.size() == points.size());
BLI_assert(dst.size() == this->evaluated_points_for_curve(curve_index).size());
switch (this->curve_types()[curve_index]) {
case CURVE_TYPE_CATMULL_ROM:
curves::catmull_rom::interpolate_to_evaluated(
src, this->cyclic()[curve_index], this->resolution()[curve_index], dst);
break;
case CURVE_TYPE_POLY:
dst.type().copy_assign_n(src.data(), dst.data(), src.size());
break;
case CURVE_TYPE_BEZIER:
curves::bezier::interpolate_to_evaluated(
src, this->runtime->bezier_evaluated_offsets.as_span().slice(points), dst);
break;
case CURVE_TYPE_NURBS:
curves::nurbs::interpolate_to_evaluated(this->runtime->nurbs_basis_cache[curve_index],
this->nurbs_orders()[curve_index],
this->nurbs_weights().slice(points),
src,
dst);
break;
default:
BLI_assert_unreachable();
break;
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -405,4 +405,77 @@ TEST(curves_geometry, NURBSEvaluation)
}
}
TEST(curves_geometry, BezierGenericEvaluation)
{
CurvesGeometry curves(3, 1);
curves.curve_types().fill(CURVE_TYPE_BEZIER);
curves.resolution().fill(8);
curves.offsets().last() = 3;
MutableSpan<float3> handles_left = curves.handle_positions_left();
MutableSpan<float3> handles_right = curves.handle_positions_right();
MutableSpan<float3> positions = curves.positions();
positions.first() = {-1, 0, 0};
handles_right.first() = {-1, 1, 0};
handles_left[1] = {0, 0, 0};
positions[1] = {1, 0, 0};
handles_right[1] = {2, 0, 0};
handles_left.last() = {1, 1, 0};
positions.last() = {2, 1, 0};
/* Dangling handles shouldn't be used in a non-cyclic curve. */
handles_left.first() = {100, 100, 100};
handles_right.last() = {100, 100, 100};
Span<float3> evaluated_positions = curves.evaluated_positions();
static const Array<float3> result_1{{
{-1.0f, 0.0f, 0.0f},
{-0.955078f, 0.287109f, 0.0f},
{-0.828125f, 0.421875f, 0.0f},
{-0.630859f, 0.439453f, 0.0f},
{-0.375f, 0.375f, 0.0f},
{-0.0722656f, 0.263672f, 0.0f},
{0.265625f, 0.140625f, 0.0f},
{0.626953f, 0.0410156f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.28906f, 0.0429688f, 0.0f},
{1.4375f, 0.15625f, 0.0f},
{1.49219f, 0.316406f, 0.0f},
{1.5f, 0.5f, 0.0f},
{1.50781f, 0.683594f, 0.0f},
{1.5625f, 0.84375f, 0.0f},
{1.71094f, 0.957031f, 0.0f},
{2.0f, 1.0f, 0.0f},
}};
for (const int i : evaluated_positions.index_range()) {
EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f);
}
Array<float> radii{{0.0f, 1.0f, 2.0f}};
Array<float> evaluated_radii(17);
curves.interpolate_to_evaluated(0, radii.as_span(), evaluated_radii.as_mutable_span());
static const Array<float> result_2{{
0.0f,
0.125f,
0.25f,
0.375f,
0.5f,
0.625f,
0.75f,
0.875f,
1.0f,
1.125f,
1.25f,
1.375f,
1.5f,
1.625f,
1.75f,
1.875f,
2.0f,
}};
for (const int i : evaluated_radii.index_range()) {
EXPECT_NEAR(evaluated_radii[i], result_2[i], 1e-6f);
}
}
} // namespace blender::bke::tests