Geometry Nodes: Support for dynamic attributes on curve splines

With this patch you will be able to add and remove attributes from curve
data inside of geometry nodes. The following is currently implemented:
* Adding attributes with any data type to splines or spline points.
* Support for working with multiple splines at the same time.
* Interaction with the three builtin point attributes.
* Resampling attributes in the resample node.

The following is not implemented in this patch:
* Joining attributes when joining splines with the join geometry node.
* Domain interpolation between spline and point domains.
* More efficient ways to call attribute operations once per spline.

Differential Revision: https://developer.blender.org/D11251
This commit is contained in:
Hans Goudey 2021-05-19 13:22:09 -04:00
parent 192a3f1a05
commit 627f357127
Notes: blender-bot 2023-02-14 08:49:53 +01:00
Referenced by issue #87870, Support dynamic attributes on curve data
11 changed files with 549 additions and 11 deletions

View File

@ -37,6 +37,11 @@
struct AttributeMetaData {
AttributeDomain domain;
CustomDataType data_type;
constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b)
{
return (a.domain == b.domain) && (a.data_type == b.data_type);
}
};
/**
@ -305,4 +310,36 @@ template<typename T> class OutputAttribute_Typed {
}
};
/**
* A basic container around DNA CustomData so that its users
* don't have to implement special copy and move constructors.
*/
class CustomDataAttributes {
/**
* #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct
* itself, so keep track of the size here so this class can implement its own destructor.
* If the implementation of the attribute storage changes, this could be removed.
*/
int size_;
public:
CustomData data;
CustomDataAttributes();
~CustomDataAttributes();
CustomDataAttributes(const CustomDataAttributes &other);
CustomDataAttributes(CustomDataAttributes &&other);
void reallocate(const int size);
std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const;
std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name);
bool create(const blender::StringRef name, const CustomDataType data_type);
bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer);
bool remove(const blender::StringRef name);
bool foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const;
};
} // namespace blender::bke

View File

@ -28,6 +28,7 @@
#include "BLI_float4x4.hh"
#include "BLI_vector.hh"
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
struct Curve;
@ -74,6 +75,8 @@ class Spline {
/* Only #Zup is supported at the moment. */
NormalCalculationMode normal_mode;
blender::bke::CustomDataAttributes attributes;
protected:
Type type_;
bool is_cyclic_ = false;
@ -99,7 +102,10 @@ class Spline {
{
}
Spline(Spline &other)
: normal_mode(other.normal_mode), type_(other.type_), is_cyclic_(other.is_cyclic_)
: normal_mode(other.normal_mode),
attributes(other.attributes),
type_(other.type_),
is_cyclic_(other.is_cyclic_)
{
}
@ -482,8 +488,10 @@ class CurveEval {
blender::Vector<SplinePtr> splines_;
public:
blender::bke::CustomDataAttributes attributes;
CurveEval() = default;
CurveEval(const CurveEval &other)
CurveEval(const CurveEval &other) : attributes(other.attributes)
{
for (const SplinePtr &spline : other.splines()) {
this->add_spline(spline->copy());
@ -502,6 +510,8 @@ class CurveEval {
blender::Array<int> control_point_offsets() const;
blender::Array<int> evaluated_point_offsets() const;
void assert_valid_point_attributes() const;
};
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve);

View File

@ -45,6 +45,7 @@ using blender::Set;
using blender::StringRef;
using blender::StringRefNull;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
namespace blender::bke {
@ -590,6 +591,105 @@ void NamedLegacyCustomDataProvider::foreach_domain(
callback(domain_);
}
CustomDataAttributes::CustomDataAttributes()
{
CustomData_reset(&data);
size_ = 0;
}
CustomDataAttributes::~CustomDataAttributes()
{
CustomData_free(&data, size_);
}
CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other)
{
size_ = other.size_;
CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, size_);
}
CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other)
{
size_ = other.size_;
data = other.data;
CustomData_reset(&other.data);
}
std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const
{
BLI_assert(size_ != 0);
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
if (layer.name == name) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GSpan(*cpp_type, layer.data, size_);
}
}
return {};
}
std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
{
BLI_assert(size_ != 0);
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
if (layer.name == name) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GMutableSpan(*cpp_type, layer.data, size_);
}
}
return {};
}
bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type)
{
char name_c[MAX_NAME];
name.copy(name_c);
void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c);
return result != nullptr;
}
bool CustomDataAttributes::create_by_move(const blender::StringRef name,
const CustomDataType data_type,
void *buffer)
{
char name_c[MAX_NAME];
name.copy(name_c);
void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c);
return result != nullptr;
}
bool CustomDataAttributes::remove(const blender::StringRef name)
{
bool result = false;
for (const int i : IndexRange(data.totlayer)) {
const CustomDataLayer &layer = data.layers[i];
if (layer.name == name) {
CustomData_free_layer(&data, layer.type, size_, i);
result = true;
}
}
return result;
}
void CustomDataAttributes::reallocate(const int size)
{
size_ = size;
CustomData_realloc(&data, size);
}
bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const
{
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
AttributeMetaData meta_data{domain, (CustomDataType)layer.type};
if (!callback(layer.name, meta_data)) {
return false;
}
}
return true;
}
} // namespace blender::bke
/* -------------------------------------------------------------------- */

View File

@ -16,7 +16,9 @@
#include "BLI_array.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "DNA_curve_types.h"
@ -26,7 +28,9 @@
using blender::Array;
using blender::float3;
using blender::float4x4;
using blender::Map;
using blender::Span;
using blender::StringRefNull;
blender::Span<SplinePtr> CurveEval::splines() const
{
@ -38,6 +42,9 @@ blender::MutableSpan<SplinePtr> CurveEval::splines()
return splines_;
}
/**
* \warning Call #reallocate on the spline's attributes after adding all splines.
*/
void CurveEval::add_spline(SplinePtr spline)
{
splines_.append(std::move(spline));
@ -178,7 +185,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
bezt.radius,
bezt.tilt);
}
spline->attributes.reallocate(spline->size());
curve->add_spline(std::move(spline));
break;
}
@ -192,7 +199,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) {
spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]);
}
spline->attributes.reallocate(spline->size());
curve->add_spline(std::move(spline));
break;
}
@ -203,7 +210,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) {
spline->add_point(bp.vec, bp.radius, bp.tilt);
}
spline->attributes.reallocate(spline->size());
curve->add_spline(std::move(spline));
break;
}
@ -214,6 +221,9 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
}
}
/* Though the curve has no attributes, this is necessary to properly set the custom data size. */
curve->attributes.reallocate(curve->splines().size());
/* Note: Normal mode is stored separately in each spline to facilitate combining splines
* from multiple curve objects, where the value may be different. */
const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve(
@ -224,3 +234,39 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
return curve;
}
/**
* Check the invariants that curve control point attributes should always uphold, necessary
* because attributes are stored on splines rather than in a flat array on the curve:
* - The same set of attributes exists on every spline.
* - Attributes with the same name have the same type on every spline.
*/
void CurveEval::assert_valid_point_attributes() const
{
#ifdef DEBUG
if (splines_.size() == 0) {
return;
}
const int layer_len = splines_.first()->attributes.data.totlayer;
Map<StringRefNull, AttributeMetaData> map;
for (const SplinePtr &spline : splines_) {
BLI_assert(spline->attributes.data.totlayer == layer_len);
spline->attributes.foreach_attribute(
[&](StringRefNull name, const AttributeMetaData &meta_data) {
map.add_or_modify(
name,
[&](AttributeMetaData *map_data) {
/* All unique attribute names should be added on the first spline. */
BLI_assert(spline == splines_.first());
*map_data = meta_data;
},
[&](AttributeMetaData *map_data) {
/* Attributes on different splines should all have the same type. */
BLI_assert(meta_data == *map_data);
});
return true;
},
ATTR_DOMAIN_POINT);
}
#endif
}

View File

@ -22,6 +22,12 @@
#include "attribute_access_intern.hh"
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_GSpan;
using blender::fn::GVMutableArray_For_GMutableSpan;
/* -------------------------------------------------------------------- */
/** \name Geometry Component Implementation
* \{ */
@ -445,6 +451,20 @@ template<typename T> class VMutableArray_For_SplinePoints final : public VMutabl
}
};
template<typename T> GVArrayPtr point_data_gvarray(Array<Span<T>> spans, Array<int> offsets)
{
return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
}
template<typename T>
GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> offsets)
{
return std::make_unique<
fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
}
/**
* Virtual array implementation specifically for control point positions. This is only needed for
* Bezier splines, where adjusting the position also requires adjusting handle positions depending
@ -581,8 +601,7 @@ template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttribu
spans[i] = get_span_(*splines[i]);
}
return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
return point_data_gvarray(spans, offsets);
}
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override
@ -607,9 +626,7 @@ template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttribu
}
}
return std::make_unique<
fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>(
offsets.last(), std::move(spans), std::move(offsets));
return point_data_gvarray(spans, offsets);
}
bool try_delete(GeometryComponent &UNUSED(component)) const final
@ -682,6 +699,256 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider<flo
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dynamic Control Point Attributes
*
* The dynamic control point attribute implementation is very similar to the builtin attribute
* implementation-- it uses the same virtual array types. In order to work, this code depends on
* the fact that all a curve's splines will have the same attributes and they all have the same
* type.
* \{ */
class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
private:
static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 |
CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 |
CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL;
public:
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
const StringRef attribute_name) const final
{
const CurveEval *curve = get_curve_from_component_for_read(component);
if (curve == nullptr || curve->splines().size() == 0) {
return {};
}
Span<SplinePtr> splines = curve->splines();
Vector<GSpan> spans; /* GSpan has no default constructor. */
spans.reserve(splines.size());
std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
* situation we don't even expect to encounter. */
BLI_assert_unreachable();
return {};
}
if (span->type() != spans.last().type()) {
/* Data layer types on separate splines do not match. */
BLI_assert_unreachable();
return {};
}
spans.append(*span);
}
/* First check for the simpler situation when we can return a simpler span virtual array. */
if (spans.size() == 1) {
return {std::make_unique<GVArray_For_GSpan>(spans.first()), ATTR_DOMAIN_POINT};
}
ReadAttributeLookup attribute = {};
Array<int> offsets = curve->control_point_offsets();
attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) {
using T = decltype(dummy);
Array<Span<T>> data(splines.size());
for (const int i : splines.index_range()) {
data[i] = spans[i].typed<T>();
BLI_assert(data[i].data() != nullptr);
}
attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT};
});
return attribute;
}
/* This function is almost the same as #try_get_for_read, but without const. */
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
const StringRef attribute_name) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr || curve->splines().size() == 0) {
return {};
}
MutableSpan<SplinePtr> splines = curve->splines();
Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */
spans.reserve(splines.size());
std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
* situation we don't even expect to encounter. */
BLI_assert_unreachable();
return {};
}
if (span->type() != spans.last().type()) {
/* Data layer types on separate splines do not match. */
BLI_assert_unreachable();
return {};
}
spans.append(*span);
}
/* First check for the simpler situation when we can return a simpler span virtual array. */
if (spans.size() == 1) {
return {std::make_unique<GVMutableArray_For_GMutableSpan>(spans.first()), ATTR_DOMAIN_POINT};
}
WriteAttributeLookup attribute = {};
Array<int> offsets = curve->control_point_offsets();
attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) {
using T = decltype(dummy);
Array<MutableSpan<T>> data(splines.size());
for (const int i : splines.index_range()) {
data[i] = spans[i].typed<T>();
BLI_assert(data[i].data() != nullptr);
}
attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT};
});
return attribute;
}
bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
return false;
}
bool layer_freed = false;
for (SplinePtr &spline : curve->splines()) {
spline->attributes.remove(attribute_name);
}
return layer_freed;
}
static GVArrayPtr varray_from_initializer(const AttributeInit &initializer,
const CustomDataType data_type,
const int total_size)
{
switch (initializer.type) {
case AttributeInit::Type::Default:
/* This function shouldn't be called in this case, since there
* is no need to copy anything to the new custom data array. */
BLI_assert_unreachable();
return {};
case AttributeInit::Type::VArray:
return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy();
case AttributeInit::Type::MoveArray:
return std::make_unique<fn::GVArray_For_GSpan>(
GSpan(*bke::custom_data_type_to_cpp_type(data_type),
static_cast<const AttributeInitMove &>(initializer).data,
total_size));
}
BLI_assert_unreachable();
return {};
}
bool try_create(GeometryComponent &component,
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const final
{
BLI_assert(this->type_is_supported(data_type));
if (domain != ATTR_DOMAIN_POINT) {
return false;
}
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr || curve->splines().size() == 0) {
return false;
}
MutableSpan<SplinePtr> splines = curve->splines();
/* First check the one case that allows us to avoid copying the input data. */
if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) {
MEM_freeN(source_data);
return false;
}
return true;
}
/* Otherwise just create a custom data layer on each of the splines. */
for (const int i : splines.index_range()) {
if (!splines[i]->attributes.create(attribute_name, data_type)) {
/* If attribute creation fails on one of the splines, we cannot leave the custom data
* layers in the previous splines around, so delete them before returning. However,
* this is not an expected case. */
BLI_assert_unreachable();
return false;
}
}
/* With a default initializer type, we can keep the values at their initial values. */
if (initializer.type == AttributeInit::Type::Default) {
return true;
}
WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name);
/* We just created the attribute, it should exist. */
BLI_assert(write_attribute);
const int total_size = curve->control_point_offsets().last();
GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, total_size);
/* TODO: When we can call a variant of #set_all with a virtual array argument,
* this theoretically unnecessary materialize step could be removed. */
GVArray_GSpan source_varray_span{*source_varray};
write_attribute.varray->set_all(source_varray_span.data());
if (initializer.type == AttributeInit::Type::MoveArray) {
MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data);
}
return true;
}
bool foreach_attribute(const GeometryComponent &component,
const AttributeForeachCallback callback) const final
{
const CurveEval *curve = get_curve_from_component_for_read(component);
if (curve == nullptr || curve->splines().size() == 0) {
return false;
}
Span<SplinePtr> splines = curve->splines();
/* In a debug build, check that all corresponding custom data layers have the same type. */
curve->assert_valid_point_attributes();
/* Use the first spline as a representative for all the others. */
splines.first()->attributes.foreach_attribute(callback, ATTR_DOMAIN_POINT);
return true;
}
void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final
{
callback(ATTR_DOMAIN_POINT);
}
bool type_is_supported(CustomDataType data_type) const
{
return ((1ULL << data_type) & supported_types_mask) != 0;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attribute Provider Declaration
* \{ */
@ -704,6 +971,20 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
make_cyclic_read_attribute,
make_cyclic_write_attribute);
static CustomDataAccessInfo spline_custom_data_access = {
[](GeometryComponent &component) -> CustomData * {
CurveEval *curve = get_curve_from_component_for_write(component);
return curve ? &curve->attributes.data : nullptr;
},
[](const GeometryComponent &component) -> const CustomData * {
const CurveEval *curve = get_curve_from_component_for_read(component);
return curve ? &curve->attributes.data : nullptr;
},
nullptr};
static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE,
spline_custom_data_access);
static PositionAttributeProvider position;
static BuiltinPointAttributeProvider<float> radius(
@ -720,7 +1001,10 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
[](Spline &spline) { return spline.tilts(); },
[](Spline &spline) { spline.mark_cache_invalid(); });
return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, {});
static DynamicPointAttributeProvider point_custom_data;
return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic},
{&spline_custom_data, &point_custom_data});
}
} // namespace blender::bke

View File

@ -563,6 +563,17 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp
}
}
for (SplinePtr &spline : new_curve->splines()) {
/* Spline instances should have no custom attributes, since they always come
* from original objects which currenty do not support custom attributes.
*
* This is only true as long as a GeometrySet cannot be instanced directly. */
BLI_assert(spline->attributes.data.totlayer == 0);
UNUSED_VARS_NDEBUG(spline);
}
new_curve->attributes.reallocate(new_curve->splines().size());
result.replace(new_curve);
}

View File

@ -55,6 +55,9 @@ void BezierSpline::set_resolution(const int value)
this->mark_cache_invalid();
}
/**
* \warning Call #reallocate on the spline's attributes after adding all points.
*/
void BezierSpline::add_point(const float3 position,
const HandleType handle_type_start,
const float3 handle_position_start,
@ -83,6 +86,7 @@ void BezierSpline::resize(const int size)
radii_.resize(size);
tilts_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> BezierSpline::positions()

View File

@ -65,6 +65,9 @@ void NURBSpline::set_order(const uint8_t value)
this->mark_cache_invalid();
}
/**
* \warning Call #reallocate on the spline's attributes after adding all points.
*/
void NURBSpline::add_point(const float3 position,
const float radius,
const float tilt,
@ -85,6 +88,7 @@ void NURBSpline::resize(const int size)
tilts_.resize(size);
weights_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> NURBSpline::positions()

View File

@ -36,6 +36,9 @@ int PolySpline::size() const
return size;
}
/**
* \warning Call #reallocate on the spline's attributes after adding all points.
*/
void PolySpline::add_point(const float3 position, const float radius, const float tilt)
{
positions_.append(position);
@ -50,6 +53,7 @@ void PolySpline::resize(const int size)
radii_.resize(size);
tilts_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> PolySpline::positions()

View File

@ -26,6 +26,7 @@
#include "node_geometry_util.hh"
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_For_Span;
using blender::fn::GVArray_Typed;
@ -131,6 +132,35 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
input_spline, uniform_samples, interpolated_data_typed, output_spline->tilts());
}
output_spline->attributes.reallocate(count);
input_spline.attributes.foreach_attribute(
[&](StringRefNull name, const AttributeMetaData &meta_data) {
std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name);
BLI_assert(input_attribute);
if (!output_spline->attributes.create(name, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write(
name);
if (!output_attribute) {
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>());
});
return true;
},
ATTR_DOMAIN_POINT);
return output_spline;
}

View File

@ -337,6 +337,14 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge
}
}
/* For now, remove all custom attributes, since they might have different types,
* or an attribute might not exist on all splines. */
dst_curve->attributes.reallocate(dst_curve->splines().size());
CustomData_reset(&dst_curve->attributes.data);
for (SplinePtr &spline : dst_curve->splines()) {
CustomData_reset(&spline->attributes.data);
}
dst_component.replace(dst_curve);
}