Geometry Nodes: Support interpolation between curve domains

This commit adds interpolation from the point domain to the spline
domain and the other way around. Before this, spline domain attributes
were basically useless, but now they are quite helpful as a way to use
a shared value in a contiguous group of points.

I implementented a special virtual array for the spline to points
conversion, so that conversion should be close to the ideal performance
level, but there are a few ways we could optimize the point to spline
conversion in the future:
 - Use a function virtual array to mix the point values for each spline
   on demand.
 - Implement a special case for when the input virtual array is one of
   the virtual arrays from the spline point attributes. In other words,
   decrease curve attribute access overhead.

Differential Revision: https://developer.blender.org/D11376
This commit is contained in:
Hans Goudey 2021-05-26 22:14:59 -04:00
parent f3944cf503
commit c97b6215a3
2 changed files with 171 additions and 16 deletions

View File

@ -420,6 +420,10 @@ class CurveComponent : public GeometryComponent {
CurveEval *get_for_write();
int attribute_domain_size(const AttributeDomain domain) const final;
std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain(
std::unique_ptr<blender::fn::GVArray> varray,
const AttributeDomain from_domain,
const AttributeDomain to_domain) const final;
bool is_empty() const final;

View File

@ -14,11 +14,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BKE_spline.hh"
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
#include "BKE_geometry_set.hh"
#include "BKE_spline.hh"
#include "attribute_access_intern.hh"
@ -26,6 +25,7 @@ using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_GSpan;
using blender::fn::GVArrayPtr;
using blender::fn::GVMutableArray_For_GMutableSpan;
/* -------------------------------------------------------------------- */
@ -142,6 +142,171 @@ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
return 0;
}
namespace blender::bke {
namespace {
struct PointIndices {
int spline_index;
int point_index;
};
} // namespace
static PointIndices lookup_point_indices(Span<int> offsets, const int index)
{
const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
offsets.begin() - 1;
const int index_in_spline = index - offsets[spline_index];
return {spline_index, index_in_spline};
}
/**
* Mix together all of a spline's control point values.
*
* \note Theoretically this interpolation does not need to compute all values at once.
* However, doing that makes the implementation simpler, and this can be optimized in the future if
* only some values are required.
*/
template<typename T>
static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve,
const VArray<T> &old_values,
MutableSpan<T> r_values)
{
const int splines_len = curve.splines().size();
Array<int> offsets = curve.control_point_offsets();
BLI_assert(r_values.size() == splines_len);
attribute_math::DefaultMixer<T> mixer(r_values);
for (const int i_spline : IndexRange(splines_len)) {
const int spline_offset = offsets[i_spline];
const int spline_point_len = offsets[i_spline + 1] - spline_offset;
for (const int i_point : IndexRange(spline_point_len)) {
const T value = old_values[spline_offset + i_point];
mixer.mix_in(i_spline, value);
}
}
mixer.finalize();
}
static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray)
{
GVArrayPtr new_varray;
attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(curve.splines().size());
adapt_curve_domain_point_to_spline_impl<T>(curve, varray->typed<T>(), values);
new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values));
}
});
return new_varray;
}
/**
* A virtual array implementation for the conversion of spline attributes to control point
* attributes. The goal is to avoid copying the spline value for every one of its control points
* unless it is necessary (in that case the materialize functions will be called).
*/
template<typename T> class VArray_For_SplineToPoint final : public VArray<T> {
/* Store existing data materialized if it was not already a span. This is expected
* to be worth it because a single spline's value will likely be accessed many times. */
VArray_Span<T> original_data_;
Array<int> offsets_;
public:
VArray_For_SplineToPoint(const VArray<T> &original_varray, Array<int> offsets)
: VArray<T>(offsets.last()), original_data_(original_varray), offsets_(std::move(offsets))
{
}
T get_impl(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return original_data_[indices.spline_index];
}
void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
const int total_size = offsets_.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
for (const int spline_index : original_data_.index_range()) {
const int offset = offsets_[spline_index];
const int next_offset = offsets_[spline_index + 1];
r_span.slice(offset, next_offset - offset).fill(original_data_[spline_index]);
}
}
else {
int spline_index = 0;
for (const int dst_index : mask) {
while (offsets_[spline_index] < dst_index) {
spline_index++;
}
r_span[dst_index] = original_data_[spline_index];
}
}
}
void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
{
T *dst = r_span.data();
const int total_size = offsets_.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
for (const int spline_index : original_data_.index_range()) {
const int offset = offsets_[spline_index];
const int next_offset = offsets_[spline_index + 1];
uninitialized_fill_n(dst + offset, next_offset - offset, original_data_[spline_index]);
}
}
else {
int spline_index = 0;
for (const int dst_index : mask) {
while (offsets_[spline_index] < dst_index) {
spline_index++;
}
new (dst + dst_index) T(original_data_[spline_index]);
}
}
}
};
static GVArrayPtr adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArrayPtr varray)
{
GVArrayPtr new_varray;
attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
Array<int> offsets = curve.control_point_offsets();
new_varray = std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplineToPoint<T>>>(
offsets.last(), *varray->typed<T>(), std::move(offsets));
});
return new_varray;
}
} // namespace blender::bke
GVArrayPtr CurveComponent::attribute_try_adapt_domain(GVArrayPtr varray,
const AttributeDomain from_domain,
const AttributeDomain to_domain) const
{
if (!varray) {
return {};
}
if (varray->size() == 0) {
return {};
}
if (from_domain == to_domain) {
return varray;
}
if (from_domain == ATTR_DOMAIN_POINT && to_domain == ATTR_DOMAIN_CURVE) {
return blender::bke::adapt_curve_domain_point_to_spline(*curve_, std::move(varray));
}
if (from_domain == ATTR_DOMAIN_CURVE && to_domain == ATTR_DOMAIN_POINT) {
return blender::bke::adapt_curve_domain_spline_to_point(*curve_, std::move(varray));
}
return {};
}
static CurveEval *get_curve_from_component_for_write(GeometryComponent &component)
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
@ -300,20 +465,6 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
* array implementations try to make it workable in common situations.
* \{ */
namespace {
struct PointIndices {
int spline_index;
int point_index;
};
} // namespace
static PointIndices lookup_point_indices(Span<int> offsets, const int index)
{
const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
offsets.begin() - 1;
const int index_in_spline = index - offsets[spline_index];
return {spline_index, index_in_spline};
}
template<typename T>
static void point_attribute_materialize(Span<Span<T>> data,
Span<int> offsets,