From 627f3571271e5f1a416314fb73f8e3c613271db3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 19 May 2021 13:22:09 -0400 Subject: 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 --- source/blender/blenkernel/BKE_attribute_access.hh | 37 +++ source/blender/blenkernel/BKE_spline.hh | 14 +- .../blender/blenkernel/intern/attribute_access.cc | 100 +++++++ source/blender/blenkernel/intern/curve_eval.cc | 52 +++- .../blenkernel/intern/geometry_component_curve.cc | 296 ++++++++++++++++++++- .../blenkernel/intern/geometry_set_instances.cc | 11 + source/blender/blenkernel/intern/spline_bezier.cc | 4 + source/blender/blenkernel/intern/spline_nurbs.cc | 4 + source/blender/blenkernel/intern/spline_poly.cc | 4 + .../geometry/nodes/node_geo_curve_resample.cc | 30 +++ .../nodes/geometry/nodes/node_geo_join_geometry.cc | 8 + 11 files changed, 549 insertions(+), 11 deletions(-) (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index 358daa40723..d81d66951cc 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -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 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 get_for_read(const blender::StringRef name) const; + std::optional 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 diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index c3386d12b02..9e5552082af 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -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 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 control_point_offsets() const; blender::Array evaluated_point_offsets() const; + + void assert_valid_point_attributes() const; }; std::unique_ptr curve_eval_from_dna_curve(const Curve &curve); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index c24630c5666..62833e10438 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -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 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 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 /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 1679f21516a..9cafe1124b1 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -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 CurveEval::splines() const { @@ -38,6 +42,9 @@ blender::MutableSpan 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 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 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 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 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 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 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 +} \ No newline at end of file diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index d6c7cae2727..73c9dae92bc 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -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 class VMutableArray_For_SplinePoints final : public VMutabl } }; +template GVArrayPtr point_data_gvarray(Array> spans, Array offsets) +{ + return std::make_unique>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +template +GVMutableArrayPtr point_data_gvarray(Array> spans, Array offsets) +{ + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray>>( + 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 class BuiltinPointAttributeProvider : public BuiltinAttribu spans[i] = get_span_(*splines[i]); } - return std::make_unique>>( - 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 class BuiltinPointAttributeProvider : public BuiltinAttribu } } - return std::make_unique< - fn::GVMutableArray_For_EmbeddedVMutableArray>>( - 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 BuiltinPointAttributeProvidersplines().size() == 0) { + return {}; + } + + Span splines = curve->splines(); + Vector spans; /* GSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional 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 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(spans.first()), ATTR_DOMAIN_POINT}; + } + + ReadAttributeLookup attribute = {}; + Array offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed(); + 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 splines = curve->splines(); + Vector spans; /* GMutableSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional 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 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(spans.first()), ATTR_DOMAIN_POINT}; + } + + WriteAttributeLookup attribute = {}; + Array offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed(); + 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(initializer).varray->shallow_copy(); + case AttributeInit::Type::MoveArray: + return std::make_unique( + GSpan(*bke::custom_data_type_to_cpp_type(data_type), + static_cast(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 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(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(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 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 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 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 diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 97892075eca..81984321b70 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -563,6 +563,17 @@ static void join_curve_splines(Span 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); } diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index 58a8f46730a..0fd3efce033 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -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 BezierSpline::positions() diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 7816f303e2e..cd3ebe9e680 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -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 NURBSpline::positions() diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index ab6f4704a88..5c33b0052fc 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -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 PolySpline::positions() diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index d7d31a4ef92..1c42b9341a0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -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 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 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 interpolated_attribute_typed{*interpolated_attribute}; + sample_span_to_output_spline(input_spline, + uniform_samples, + interpolated_attribute_typed, + (*output_attribute).typed()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + return output_spline; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 915ebc1e1f2..adfd924f185 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -337,6 +337,14 @@ static void join_curve_components(MutableSpan 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); } -- cgit v1.2.3