/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DNA_ID_enums.h" #include "DNA_curve_types.h" #include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_curve.h" #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_spline.hh" #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::GVArrayPtr; using blender::fn::GVMutableArray_For_GMutableSpan; /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation * \{ */ CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE) { } CurveComponent::~CurveComponent() { this->clear(); } GeometryComponent *CurveComponent::copy() const { CurveComponent *new_component = new CurveComponent(); if (curve_ != nullptr) { new_component->curve_ = new CurveEval(*curve_); new_component->ownership_ = GeometryOwnershipType::Owned; } return new_component; } void CurveComponent::clear() { BLI_assert(this->is_mutable()); if (curve_ != nullptr) { if (ownership_ == GeometryOwnershipType::Owned) { delete curve_; } if (curve_for_render_ != nullptr) { /* The curve created by this component should not have any edit mode data. */ BLI_assert(curve_for_render_->editfont == nullptr && curve_for_render_->editnurb == nullptr); BKE_id_free(nullptr, curve_for_render_); curve_for_render_ = nullptr; } curve_ = nullptr; } } bool CurveComponent::has_curve() const { return curve_ != nullptr; } /* Clear the component and replace it with the new curve. */ void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); this->clear(); curve_ = curve; ownership_ = ownership; } CurveEval *CurveComponent::release() { BLI_assert(this->is_mutable()); CurveEval *curve = curve_; curve_ = nullptr; return curve; } const CurveEval *CurveComponent::get_for_read() const { return curve_; } CurveEval *CurveComponent::get_for_write() { BLI_assert(this->is_mutable()); if (ownership_ == GeometryOwnershipType::ReadOnly) { curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } return curve_; } bool CurveComponent::is_empty() const { return curve_ == nullptr; } bool CurveComponent::owns_direct_data() const { return ownership_ == GeometryOwnershipType::Owned; } void CurveComponent::ensure_owns_direct_data() { BLI_assert(this->is_mutable()); if (ownership_ != GeometryOwnershipType::Owned) { curve_ = new CurveEval(*curve_); ownership_ = GeometryOwnershipType::Owned; } } /** * Create empty curve data used for rendering the spline's wire edges. * \note See comment on #curve_for_render_ for further explanation. */ const Curve *CurveComponent::get_curve_for_render() const { if (curve_ == nullptr) { return nullptr; } if (curve_for_render_ != nullptr) { return curve_for_render_; } std::lock_guard lock{curve_for_render_mutex_}; if (curve_for_render_ != nullptr) { return curve_for_render_; } curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU, nullptr); curve_for_render_->curve_eval = curve_; return curve_for_render_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Attribute Access Helper Functions * \{ */ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const { if (curve_ == nullptr) { return 0; } if (domain == ATTR_DOMAIN_POINT) { int total = 0; for (const SplinePtr &spline : curve_->splines()) { total += spline->size(); } return total; } if (domain == ATTR_DOMAIN_CURVE) { return curve_->splines().size(); } return 0; } namespace blender::bke { namespace { struct PointIndices { int spline_index; int point_index; }; } // namespace static PointIndices lookup_point_indices(Span 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 static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, const VArray &old_values, MutableSpan r_values) { const int splines_len = curve.splines().size(); Array offsets = curve.control_point_offsets(); BLI_assert(r_values.size() == splines_len); attribute_math::DefaultMixer 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>) { Array values(curve.splines().size()); adapt_curve_domain_point_to_spline_impl(curve, varray->typed(), values); new_varray = std::make_unique>>(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 class VArray_For_SplineToPoint final : public VArray { GVArrayPtr original_varray_; /* 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. */ fn::GVArray_Span original_data_; Array offsets_; public: VArray_For_SplineToPoint(GVArrayPtr original_varray, Array offsets) : VArray(offsets.last()), original_varray_(std::move(original_varray)), 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 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 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 offsets = curve.control_point_offsets(); new_varray = std::make_unique>>( offsets.last(), std::move(varray), 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); CurveComponent &curve_component = static_cast(component); return curve_component.get_for_write(); } static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component) { BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); const CurveComponent &curve_component = static_cast(component); return curve_component.get_for_read(); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Builtin Spline Attributes * * Attributes with a value for every spline, stored contiguously or in every spline separately. * \{ */ namespace blender::bke { class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; public: BuiltinSplineAttributeProvider(std::string attribute_name, const CustomDataType attribute_type, const WritableEnum writable, const AsReadAttribute as_read_attribute, const AsWriteAttribute as_write_attribute) : BuiltinAttributeProvider(std::move(attribute_name), ATTR_DOMAIN_CURVE, attribute_type, BuiltinAttributeProvider::NonCreatable, writable, BuiltinAttributeProvider::NonDeletable), as_read_attribute_(as_read_attribute), as_write_attribute_(as_write_attribute) { } GVArrayPtr try_get_for_read(const GeometryComponent &component) const final { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { return {}; } return as_read_attribute_(*curve); } GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final { if (writable_ != Writable) { return {}; } CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return {}; } return as_write_attribute_(*curve); } bool try_delete(GeometryComponent &UNUSED(component)) const final { return false; } bool try_create(GeometryComponent &UNUSED(component), const AttributeInit &UNUSED(initializer)) const final { return false; } bool exists(const GeometryComponent &component) const final { return component.attribute_domain_size(ATTR_DOMAIN_CURVE) != 0; } }; static int get_spline_resolution(const SplinePtr &spline) { if (const BezierSpline *bezier_spline = dynamic_cast(spline.get())) { return bezier_spline->resolution(); } if (const NURBSpline *nurb_spline = dynamic_cast(spline.get())) { return nurb_spline->resolution(); } return 1; } static void set_spline_resolution(SplinePtr &spline, const int resolution) { if (BezierSpline *bezier_spline = dynamic_cast(spline.get())) { bezier_spline->set_resolution(std::max(resolution, 1)); } if (NURBSpline *nurb_spline = dynamic_cast(spline.get())) { nurb_spline->set_resolution(std::max(resolution, 1)); } } static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) { return std::make_unique>( curve.splines()); } static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) { return std::make_unique>( curve.splines()); } static bool get_cyclic_value(const SplinePtr &spline) { return spline->is_cyclic(); } static void set_cyclic_value(SplinePtr &spline, const bool value) { if (spline->is_cyclic() != value) { spline->set_cyclic(value); spline->mark_cache_invalid(); } } static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) { return std::make_unique>( curve.splines()); } static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) { return std::make_unique< fn::GVMutableArray_For_DerivedSpan>( curve.splines()); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Builtin Control Point Attributes * * Attributes with a value for every control point. Most of the complexity here is due to the fact * that we must provide access to the attribute data as if it was a contiguous array when it is * really stored separately on each spline. That will be inherently rather slow, but these virtual * array implementations try to make it workable in common situations. * \{ */ template static void point_attribute_materialize(Span> data, Span offsets, const IndexMask mask, MutableSpan r_span) { const int total_size = offsets.last(); if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { for (const int spline_index : data.index_range()) { const int offset = offsets[spline_index]; const int next_offset = offsets[spline_index + 1]; r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]); } } else { int spline_index = 0; for (const int dst_index : mask) { while (offsets[spline_index] < dst_index) { spline_index++; } const int index_in_spline = dst_index - offsets[spline_index]; r_span[dst_index] = data[spline_index][index_in_spline]; } } } template static void point_attribute_materialize_to_uninitialized(Span> data, Span offsets, const IndexMask mask, MutableSpan r_span) { 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 : data.index_range()) { const int offset = offsets[spline_index]; const int next_offset = offsets[spline_index + 1]; uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset); } } else { int spline_index = 0; for (const int dst_index : mask) { while (offsets[spline_index] < dst_index) { spline_index++; } const int index_in_spline = dst_index - offsets[spline_index]; new (dst + dst_index) T(data[spline_index][index_in_spline]); } } } /** * Virtual array for any control point data accessed with spans and an offset array. */ template class VArray_For_SplinePoints : public VArray { private: const Array> data_; Array offsets_; public: VArray_For_SplinePoints(Array> data, Array offsets) : VArray(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) { } T get_impl(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return data_[indices.spline_index][indices.point_index]; } void materialize_impl(const IndexMask mask, MutableSpan r_span) const final { point_attribute_materialize(data_.as_span(), offsets_, mask, r_span); } void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan r_span) const final { point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span); } }; /** * Mutable virtual array for any control point data accessed with spans and an offset array. */ template class VMutableArray_For_SplinePoints final : public VMutableArray { private: Array> data_; Array offsets_; public: VMutableArray_For_SplinePoints(Array> data, Array offsets) : VMutableArray(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) { } T get_impl(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return data_[indices.spline_index][indices.point_index]; } void set_impl(const int64_t index, T value) final { const PointIndices indices = lookup_point_indices(offsets_, index); data_[indices.spline_index][indices.point_index] = value; } void set_all_impl(Span src) final { for (const int spline_index : data_.index_range()) { const int offset = offsets_[spline_index]; const int next_offsets = offsets_[spline_index + 1]; data_[spline_index].copy_from(src.slice(offset, next_offsets - offset)); } } void materialize_impl(const IndexMask mask, MutableSpan r_span) const final { point_attribute_materialize({(Span *)data_.data(), data_.size()}, offsets_, mask, r_span); } void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan r_span) const final { point_attribute_materialize_to_uninitialized( {(Span *)data_.data(), data_.size()}, offsets_, mask, r_span); } }; 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 * on handle types. We pay a small price for this when other spline types are mixed with Bezier. * * \note There is no need to check the handle type to avoid changing auto handles, since * retrieving write access to the position data will mark them for recomputation anyway. */ class VMutableArray_For_SplinePosition final : public VMutableArray { private: MutableSpan splines_; Array offsets_; public: VMutableArray_For_SplinePosition(MutableSpan splines, Array offsets) : VMutableArray(offsets.last()), splines_(splines), offsets_(std::move(offsets)) { } float3 get_impl(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return splines_[indices.spline_index]->positions()[indices.point_index]; } void set_impl(const int64_t index, float3 value) final { const PointIndices indices = lookup_point_indices(offsets_, index); Spline &spline = *splines_[indices.spline_index]; if (BezierSpline *bezier_spline = dynamic_cast(&spline)) { const float3 delta = value - bezier_spline->positions()[indices.point_index]; bezier_spline->handle_positions_left()[indices.point_index] += delta; bezier_spline->handle_positions_right()[indices.point_index] += delta; bezier_spline->positions()[indices.point_index] = value; } else { spline.positions()[indices.point_index] = value; } } void set_all_impl(Span src) final { for (const int spline_index : splines_.index_range()) { Spline &spline = *splines_[spline_index]; const int offset = offsets_[spline_index]; const int next_offset = offsets_[spline_index + 1]; if (BezierSpline *bezier_spline = dynamic_cast(&spline)) { MutableSpan positions = bezier_spline->positions(); MutableSpan handle_positions_left = bezier_spline->handle_positions_left(); MutableSpan handle_positions_right = bezier_spline->handle_positions_right(); for (const int i : IndexRange(next_offset - offset)) { const float3 delta = src[offset + i] - positions[i]; handle_positions_left[i] += delta; handle_positions_right[i] += delta; positions[i] = src[offset + i]; } } else { spline.positions().copy_from(src.slice(offset, next_offset - offset)); } } } /** Utility so we can pass positions to the materialize functions above. */ Array> get_position_spans() const { Array> spans(splines_.size()); for (const int i : spans.index_range()) { spans[i] = splines_[i]->positions(); } return spans; } void materialize_impl(const IndexMask mask, MutableSpan r_span) const final { Array> spans = this->get_position_spans(); point_attribute_materialize(spans.as_span(), offsets_, mask, r_span); } void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan r_span) const final { Array> spans = this->get_position_spans(); point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span); } }; /** * Provider for any builtin control point attribute that doesn't need * special handling like access to other arrays in the spline. */ template class BuiltinPointAttributeProvider : public BuiltinAttributeProvider { protected: using GetSpan = Span (*)(const Spline &spline); using GetMutableSpan = MutableSpan (*)(Spline &spline); using UpdateOnWrite = void (*)(Spline &spline); const GetSpan get_span_; const GetMutableSpan get_mutable_span_; const UpdateOnWrite update_on_write_; public: BuiltinPointAttributeProvider(std::string attribute_name, const WritableEnum writable, const GetSpan get_span, const GetMutableSpan get_mutable_span, const UpdateOnWrite update_on_write) : BuiltinAttributeProvider(std::move(attribute_name), ATTR_DOMAIN_POINT, bke::cpp_type_to_custom_data_type(CPPType::get()), BuiltinAttributeProvider::NonCreatable, writable, BuiltinAttributeProvider::NonDeletable), get_span_(get_span), get_mutable_span_(get_mutable_span), update_on_write_(update_on_write) { } GVArrayPtr try_get_for_read(const GeometryComponent &component) const override { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { return {}; } Span splines = curve->splines(); if (splines.size() == 1) { return std::make_unique(get_span_(*splines.first())); } Array offsets = curve->control_point_offsets(); Array> spans(splines.size()); for (const int i : splines.index_range()) { spans[i] = get_span_(*splines[i]); } return point_data_gvarray(spans, offsets); } GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return {}; } MutableSpan splines = curve->splines(); if (splines.size() == 1) { return std::make_unique( get_mutable_span_(*splines.first())); } Array offsets = curve->control_point_offsets(); Array> spans(splines.size()); for (const int i : splines.index_range()) { spans[i] = get_mutable_span_(*splines[i]); if (update_on_write_) { update_on_write_(*splines[i]); } } return point_data_gvarray(spans, offsets); } bool try_delete(GeometryComponent &UNUSED(component)) const final { return false; } bool try_create(GeometryComponent &UNUSED(component), const AttributeInit &UNUSED(initializer)) const final { return false; } bool exists(const GeometryComponent &component) const final { return component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0; } }; /** * Special attribute provider for the position attribute. Keeping this separate means we don't * need to make #BuiltinPointAttributeProvider overly generic, and the special handling for the * positions is more clear. */ class PositionAttributeProvider final : public BuiltinPointAttributeProvider { public: PositionAttributeProvider() : BuiltinPointAttributeProvider( "position", BuiltinAttributeProvider::Writable, [](const Spline &spline) { return spline.positions(); }, [](Spline &spline) { return spline.positions(); }, [](Spline &spline) { spline.mark_cache_invalid(); }) { } GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return {}; } /* Use the regular position virtual array when there aren't any Bezier splines * to avoid the overhead of checking the spline type for every point. */ if (!curve->has_spline_with_type(Spline::Type::Bezier)) { return BuiltinPointAttributeProvider::try_get_for_write(component); } /* Changing the positions requires recalculation of cached evaluated data in many cases. * This could set more specific flags in the future to avoid unnecessary recomputation. */ for (SplinePtr &spline : curve->splines()) { spline->mark_cache_invalid(); } Array offsets = curve->control_point_offsets(); return std::make_unique< fn::GVMutableArray_For_EmbeddedVMutableArray>( offsets.last(), curve->splines(), std::move(offsets)); } }; /** \} */ /* -------------------------------------------------------------------- */ /** \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 AttributeIDRef &attribute_id) const final { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr || curve->splines().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_id); 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_id); 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 AttributeIDRef &attribute_id) 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_id); 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_id); 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 AttributeIDRef &attribute_id) const final { CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return false; } /* Reuse the boolean for all splines; we expect all splines to have the same attributes. */ bool layer_freed = false; for (SplinePtr &spline : curve->splines()) { layer_freed = spline->attributes.remove(attribute_id); } 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 AttributeIDRef &attribute_id, 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_id, 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_id, 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_id); /* 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 * \{ */ /** * In this function all the attribute providers for a curve component are created. * Most data in this function is statically allocated, because it does not change over time. */ static ComponentAttributeProviders create_attribute_providers_for_curve() { static BuiltinSplineAttributeProvider resolution("resolution", CD_PROP_INT32, BuiltinAttributeProvider::Writable, make_resolution_read_attribute, make_resolution_write_attribute); static BuiltinSplineAttributeProvider cyclic("cyclic", CD_PROP_BOOL, BuiltinAttributeProvider::Writable, 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( "radius", BuiltinAttributeProvider::Writable, [](const Spline &spline) { return spline.radii(); }, [](Spline &spline) { return spline.radii(); }, nullptr); static BuiltinPointAttributeProvider tilt( "tilt", BuiltinAttributeProvider::Writable, [](const Spline &spline) { return spline.tilts(); }, [](Spline &spline) { return spline.tilts(); }, [](Spline &spline) { spline.mark_cache_invalid(); }); static DynamicPointAttributeProvider point_custom_data; return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, {&spline_custom_data, &point_custom_data}); } } // namespace blender::bke const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const { static blender::bke::ComponentAttributeProviders providers = blender::bke::create_attribute_providers_for_curve(); return &providers; } /** \} */