This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/geometry_component_curve.cc
Hans Goudey 25237d2625 Attributes: Improve custom data initialization options
When allocating new `CustomData` layers, often we do redundant
initialization of arrays. For example, it's common that values are
allocated, set to their default value, and then set to some other
value. This is wasteful, and it negates the benefits of optimizations
to the allocator like D15082. There are two reasons for this. The
first is array-of-structs storage that makes it annoying to initialize
values manually, and the second is confusing options in the Custom Data
API. This patch addresses the latter.

The `CustomData` "alloc type" options are rearranged. Now, besides
the options that use existing layers, there are two remaining:
* `CD_SET_DEFAULT` sets the default value.
  * Usually zeroes, but for colors this is white (how it was before).
  * Should be used when you add the layer but don't set all values.
* `CD_CONSTRUCT` refers to the "default construct" C++ term.
  * Only necessary or defined for non-trivial types like vertex groups.
  * Doesn't do anything for trivial types like `int` or `float3`.
  * Should be used every other time, when all values will be set.

The attribute API's `AttributeInit` types are updated as well.
To update code, replace `CD_CALLOC` with `CD_SET_DEFAULT` and
`CD_DEFAULT` with `CD_CONSTRUCT`. This doesn't cause any functional
changes yet. Follow-up commits will change to avoid initializing
new layers where the correctness is clear.

Differential Revision: https://developer.blender.org/D15617
2022-08-30 14:56:05 -05:00

1466 lines
49 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "DNA_ID_enums.h"
#include "DNA_curve_types.h"
#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::GMutableSpan;
using blender::GSpan;
using blender::GVArray;
using blender::GVArraySpan;
/* -------------------------------------------------------------------- */
/** \name Geometry Component Implementation
* \{ */
CurveComponentLegacy::CurveComponentLegacy() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
{
}
CurveComponentLegacy::~CurveComponentLegacy()
{
this->clear();
}
GeometryComponent *CurveComponentLegacy::copy() const
{
CurveComponentLegacy *new_component = new CurveComponentLegacy();
if (curve_ != nullptr) {
new_component->curve_ = new CurveEval(*curve_);
new_component->ownership_ = GeometryOwnershipType::Owned;
}
return new_component;
}
void CurveComponentLegacy::clear()
{
BLI_assert(this->is_mutable());
if (curve_ != nullptr) {
if (ownership_ == GeometryOwnershipType::Owned) {
delete curve_;
}
curve_ = nullptr;
}
}
bool CurveComponentLegacy::has_curve() const
{
return curve_ != nullptr;
}
void CurveComponentLegacy::replace(CurveEval *curve, GeometryOwnershipType ownership)
{
BLI_assert(this->is_mutable());
this->clear();
curve_ = curve;
ownership_ = ownership;
}
CurveEval *CurveComponentLegacy::release()
{
BLI_assert(this->is_mutable());
CurveEval *curve = curve_;
curve_ = nullptr;
return curve;
}
const CurveEval *CurveComponentLegacy::get_for_read() const
{
return curve_;
}
CurveEval *CurveComponentLegacy::get_for_write()
{
BLI_assert(this->is_mutable());
if (ownership_ == GeometryOwnershipType::ReadOnly) {
curve_ = new CurveEval(*curve_);
ownership_ = GeometryOwnershipType::Owned;
}
return curve_;
}
bool CurveComponentLegacy::is_empty() const
{
return curve_ == nullptr;
}
bool CurveComponentLegacy::owns_direct_data() const
{
return ownership_ == GeometryOwnershipType::Owned;
}
void CurveComponentLegacy::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
if (ownership_ != GeometryOwnershipType::Owned) {
curve_ = new CurveEval(*curve_);
ownership_ = GeometryOwnershipType::Owned;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attribute Access Helper Functions
* \{ */
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();
}
/**
* A spline is selected if all of its control points were selected.
*
* \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<>
void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve,
const VArray<bool> &old_values,
MutableSpan<bool> r_values)
{
const int splines_len = curve.splines().size();
Array<int> offsets = curve.control_point_offsets();
BLI_assert(r_values.size() == splines_len);
r_values.fill(true);
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)) {
if (!old_values[spline_offset + i_point]) {
r_values[i_spline] = false;
break;
}
}
}
}
static GVArray adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArray varray)
{
GVArray 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 = VArray<T>::ForContainer(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 VArrayImpl<T> {
GVArray 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. */
VArraySpan<T> original_data_;
Array<int> offsets_;
public:
VArray_For_SplineToPoint(GVArray original_varray, Array<int> offsets)
: VArrayImpl<T>(offsets.last()),
original_varray_(std::move(original_varray)),
original_data_(original_varray_.typed<T>()),
offsets_(std::move(offsets))
{
}
T get(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return original_data_[indices.spline_index];
}
void materialize(const IndexMask mask, MutableSpan<T> r_span) const final
{
const int total_num = offsets_.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_num)) {
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(const IndexMask mask, MutableSpan<T> r_span) const final
{
T *dst = r_span.data();
const int total_num = offsets_.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_num)) {
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 GVArray adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArray varray)
{
GVArray 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 = VArray<T>::template For<VArray_For_SplineToPoint<T>>(std::move(varray),
std::move(offsets));
});
return new_varray;
}
} // namespace blender::bke
static GVArray adapt_curve_attribute_domain(const CurveEval &curve,
const GVArray &varray,
const eAttrDomain from_domain,
const eAttrDomain to_domain)
{
if (!varray) {
return {};
}
if (varray.is_empty()) {
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 {};
}
/** \} */
namespace blender::bke {
/* -------------------------------------------------------------------- */
/** \name Builtin Spline Attributes
*
* Attributes with a value for every spline, stored contiguously or in every spline separately.
* \{ */
class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider {
using AsReadAttribute = GVArray (*)(const CurveEval &data);
using AsWriteAttribute = GVMutableArray (*)(CurveEval &data);
const AsReadAttribute as_read_attribute_;
const AsWriteAttribute as_write_attribute_;
public:
BuiltinSplineAttributeProvider(std::string attribute_name,
const eCustomDataType 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)
{
}
GVArray try_get_for_read(const void *owner) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
return as_read_attribute_(*curve);
}
GAttributeWriter try_get_for_write(void *owner) const final
{
if (writable_ != Writable) {
return {};
}
CurveEval *curve = static_cast<CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
return {as_write_attribute_(*curve), domain_};
}
bool try_delete(void *UNUSED(owner)) const final
{
return false;
}
bool try_create(void *UNUSED(owner), const AttributeInit &UNUSED(initializer)) const final
{
return false;
}
bool exists(const void *owner) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
return !curve->splines().is_empty();
}
};
static int get_spline_resolution(const SplinePtr &spline)
{
if (const BezierSpline *bezier_spline = dynamic_cast<const BezierSpline *>(spline.get())) {
return bezier_spline->resolution();
}
if (const NURBSpline *nurb_spline = dynamic_cast<const NURBSpline *>(spline.get())) {
return nurb_spline->resolution();
}
return 1;
}
static void set_spline_resolution(SplinePtr &spline, const int resolution)
{
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
bezier_spline->set_resolution(std::max(resolution, 1));
}
if (NURBSpline *nurb_spline = dynamic_cast<NURBSpline *>(spline.get())) {
nurb_spline->set_resolution(std::max(resolution, 1));
}
}
static GVArray make_resolution_read_attribute(const CurveEval &curve)
{
return VArray<int>::ForDerivedSpan<SplinePtr, get_spline_resolution>(curve.splines());
}
static GVMutableArray make_resolution_write_attribute(CurveEval &curve)
{
return VMutableArray<int>::
ForDerivedSpan<SplinePtr, get_spline_resolution, set_spline_resolution>(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 GVArray make_cyclic_read_attribute(const CurveEval &curve)
{
return VArray<bool>::ForDerivedSpan<SplinePtr, get_cyclic_value>(curve.splines());
}
static GVMutableArray make_cyclic_write_attribute(CurveEval &curve)
{
return VMutableArray<bool>::ForDerivedSpan<SplinePtr, get_cyclic_value, set_cyclic_value>(
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.
* \{ */
/**
* Individual spans in \a data may be empty if that spline contains no data for the attribute.
*/
template<typename T>
static void point_attribute_materialize(Span<Span<T>> data,
Span<int> offsets,
const IndexMask mask,
MutableSpan<T> r_span)
{
const int total_num = offsets.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_num)) {
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
Span<T> src = data[spline_index];
MutableSpan<T> dst = r_span.slice(offset, next_offset - offset);
if (src.is_empty()) {
dst.fill(T());
}
else {
dst.copy_from(src);
}
}
}
else {
int spline_index = 0;
for (const int dst_index : mask) {
/* Skip splines that don't have any control points in the mask. */
while (dst_index >= offsets[spline_index + 1]) {
spline_index++;
}
const int index_in_spline = dst_index - offsets[spline_index];
Span<T> src = data[spline_index];
if (src.is_empty()) {
r_span[dst_index] = T();
}
else {
r_span[dst_index] = src[index_in_spline];
}
}
}
}
/**
* Individual spans in \a data may be empty if that spline contains no data for the attribute.
*/
template<typename T>
static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
Span<int> offsets,
const IndexMask mask,
MutableSpan<T> r_span)
{
T *dst = r_span.data();
const int total_num = offsets.last();
if (mask.is_range() && mask.as_range() == IndexRange(total_num)) {
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
Span<T> src = data[spline_index];
if (src.is_empty()) {
uninitialized_fill_n(dst + offset, next_offset - offset, T());
}
else {
uninitialized_copy_n(src.data(), next_offset - offset, dst + offset);
}
}
}
else {
int spline_index = 0;
for (const int dst_index : mask) {
/* Skip splines that don't have any control points in the mask. */
while (dst_index >= offsets[spline_index + 1]) {
spline_index++;
}
const int index_in_spline = dst_index - offsets[spline_index];
Span<T> src = data[spline_index];
if (src.is_empty()) {
new (dst + dst_index) T();
}
else {
new (dst + dst_index) T(src[index_in_spline]);
}
}
}
}
static GVArray varray_from_initializer(const AttributeInit &initializer,
const eCustomDataType data_type,
const Span<SplinePtr> splines)
{
switch (initializer.type) {
case AttributeInit::Type::Construct:
case AttributeInit::Type::DefaultValue:
/* 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;
case AttributeInit::Type::MoveArray:
int total_num = 0;
for (const SplinePtr &spline : splines) {
total_num += spline->size();
}
return GVArray::ForSpan(GSpan(*bke::custom_data_type_to_cpp_type(data_type),
static_cast<const AttributeInitMoveArray &>(initializer).data,
total_num));
}
BLI_assert_unreachable();
return {};
}
static bool create_point_attribute(CurveEval *curve,
const AttributeIDRef &attribute_id,
const AttributeInit &initializer,
const eCustomDataType data_type)
{
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 AttributeInitMoveArray &>(initializer).data;
if (!splines.first()->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 (ELEM(initializer.type, AttributeInit::Type::DefaultValue, AttributeInit::Type::Construct)) {
return true;
}
GAttributeWriter write_attribute = curve->attributes_for_write().lookup_for_write(attribute_id);
/* We just created the attribute, it should exist. */
BLI_assert(write_attribute);
GVArray source_varray = varray_from_initializer(initializer, data_type, splines);
/* TODO: When we can call a variant of #set_all with a virtual array argument,
* this theoretically unnecessary materialize step could be removed. */
GVArraySpan source_VArraySpan{source_varray};
write_attribute.varray.set_all(source_VArraySpan.data());
write_attribute.finish();
if (initializer.type == AttributeInit::Type::MoveArray) {
MEM_freeN(static_cast<const AttributeInitMoveArray &>(initializer).data);
}
return true;
}
static bool remove_point_attribute(CurveEval *curve, const AttributeIDRef &attribute_id)
{
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;
}
/**
* Mutable virtual array for any control point data accessed with spans and an offset array.
*/
template<typename T> class VArrayImpl_For_SplinePoints final : public VMutableArrayImpl<T> {
private:
Array<MutableSpan<T>> data_;
Array<int> offsets_;
public:
VArrayImpl_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets)
: VMutableArrayImpl<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets))
{
}
T get(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
return data_[indices.spline_index][indices.point_index];
}
void set(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(Span<T> 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(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
}
void materialize_to_uninitialized(const IndexMask mask, MutableSpan<T> r_span) const final
{
point_attribute_materialize_to_uninitialized(
{(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
}
};
template<typename T> VArray<T> point_data_varray(Array<MutableSpan<T>> spans, Array<int> offsets)
{
return VArray<T>::template For<VArrayImpl_For_SplinePoints<T>>(std::move(spans),
std::move(offsets));
}
template<typename T>
VMutableArray<T> point_data_varray_mutable(Array<MutableSpan<T>> spans, Array<int> offsets)
{
return VMutableArray<T>::template For<VArrayImpl_For_SplinePoints<T>>(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 VArrayImpl_For_SplinePosition final : public VMutableArrayImpl<float3> {
private:
MutableSpan<SplinePtr> splines_;
Array<int> offsets_;
public:
VArrayImpl_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets)
: VMutableArrayImpl<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets))
{
}
float3 get(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(const int64_t index, float3 value) final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
Spline &spline = *splines_[indices.spline_index];
spline.positions()[indices.point_index] = value;
}
void set_all(Span<float3> 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];
spline.positions().copy_from(src.slice(offset, next_offset - offset));
}
}
/** Utility so we can pass positions to the materialize functions above. */
Array<Span<float3>> get_position_spans() const
{
Array<Span<float3>> spans(splines_.size());
for (const int i : spans.index_range()) {
spans[i] = splines_[i]->positions();
}
return spans;
}
void materialize(const IndexMask mask, MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = this->get_position_spans();
point_attribute_materialize(spans.as_span(), offsets_, mask, r_span);
}
void materialize_to_uninitialized(const IndexMask mask, MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = this->get_position_spans();
point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span);
}
};
class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl<float3> {
private:
MutableSpan<SplinePtr> splines_;
Array<int> offsets_;
bool is_right_;
public:
VArrayImpl_For_BezierHandles(MutableSpan<SplinePtr> splines,
Array<int> offsets,
const bool is_right)
: VMutableArrayImpl<float3>(offsets.last()),
splines_(splines),
offsets_(std::move(offsets)),
is_right_(is_right)
{
}
float3 get(const int64_t index) const final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
const Spline &spline = *splines_[indices.spline_index];
if (spline.type() == CURVE_TYPE_BEZIER) {
const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(spline);
return is_right_ ? bezier_spline.handle_positions_right()[indices.point_index] :
bezier_spline.handle_positions_left()[indices.point_index];
}
return float3(0);
}
void set(const int64_t index, float3 value) final
{
const PointIndices indices = lookup_point_indices(offsets_, index);
Spline &spline = *splines_[indices.spline_index];
if (spline.type() == CURVE_TYPE_BEZIER) {
BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline);
if (is_right_) {
bezier_spline.handle_positions_right()[indices.point_index] = value;
}
else {
bezier_spline.handle_positions_left()[indices.point_index] = value;
}
bezier_spline.mark_cache_invalid();
}
}
void set_all(Span<float3> src) final
{
for (const int spline_index : splines_.index_range()) {
Spline &spline = *splines_[spline_index];
if (spline.type() == CURVE_TYPE_BEZIER) {
const int offset = offsets_[spline_index];
BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline);
if (is_right_) {
for (const int i : IndexRange(bezier_spline.size())) {
bezier_spline.handle_positions_right()[i] = src[offset + i];
}
}
else {
for (const int i : IndexRange(bezier_spline.size())) {
bezier_spline.handle_positions_left()[i] = src[offset + i];
}
}
bezier_spline.mark_cache_invalid();
}
}
}
void materialize(const IndexMask mask, MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = get_handle_spans(splines_, is_right_);
point_attribute_materialize(spans.as_span(), offsets_, mask, r_span);
}
void materialize_to_uninitialized(const IndexMask mask, MutableSpan<float3> r_span) const final
{
Array<Span<float3>> spans = get_handle_spans(splines_, is_right_);
point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span);
}
/**
* Utility so we can pass handle positions to the materialize functions above.
*
* \note This relies on the ability of the materialize implementations to
* handle empty spans, since only Bezier splines have handles.
*/
static Array<Span<float3>> get_handle_spans(Span<SplinePtr> splines, const bool is_right)
{
Array<Span<float3>> spans(splines.size());
for (const int i : spans.index_range()) {
if (splines[i]->type() == CURVE_TYPE_BEZIER) {
BezierSpline &bezier_spline = static_cast<BezierSpline &>(*splines[i]);
spans[i] = is_right ? bezier_spline.handle_positions_right() :
bezier_spline.handle_positions_left();
}
else {
spans[i] = {};
}
}
return spans;
}
};
/**
* Provider for any builtin control point attribute that doesn't need
* special handling like access to other arrays in the spline.
*/
template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttributeProvider {
protected:
using GetSpan = Span<T> (*)(const Spline &spline);
using GetMutableSpan = MutableSpan<T> (*)(Spline &spline);
using UpdateOnWrite = void (*)(Spline &spline);
const GetSpan get_span_;
const GetMutableSpan get_mutable_span_;
const UpdateOnWrite update_on_write_;
bool stored_in_custom_data_;
public:
BuiltinPointAttributeProvider(std::string attribute_name,
const CreatableEnum creatable,
const DeletableEnum deletable,
const GetSpan get_span,
const GetMutableSpan get_mutable_span,
const UpdateOnWrite update_on_write,
const bool stored_in_custom_data)
: BuiltinAttributeProvider(std::move(attribute_name),
ATTR_DOMAIN_POINT,
bke::cpp_type_to_custom_data_type(CPPType::get<T>()),
creatable,
WritableEnum::Writable,
deletable),
get_span_(get_span),
get_mutable_span_(get_mutable_span),
update_on_write_(update_on_write),
stored_in_custom_data_(stored_in_custom_data)
{
}
GVArray try_get_for_read(const void *owner) const override
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
if (!this->exists(owner)) {
return {};
}
Span<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
return GVArray::ForSpan(get_span_(*splines.first()));
}
Array<int> offsets = curve->control_point_offsets();
Array<MutableSpan<T>> spans(splines.size());
for (const int i : splines.index_range()) {
Span<T> span = get_span_(*splines[i]);
/* Use const-cast because the underlying virtual array implementation is shared between const
* and non const data. */
spans[i] = MutableSpan<T>(const_cast<T *>(span.data()), span.size());
}
return point_data_varray(spans, offsets);
}
GAttributeWriter try_get_for_write(void *owner) const override
{
CurveEval *curve = static_cast<CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
if (!this->exists(owner)) {
return {};
}
std::function<void()> tag_modified_fn;
if (update_on_write_ != nullptr) {
tag_modified_fn = [curve, update = update_on_write_]() {
for (SplinePtr &spline : curve->splines()) {
update(*spline);
}
};
}
MutableSpan<SplinePtr> splines = curve->splines();
if (splines.size() == 1) {
return {GVMutableArray::ForSpan(get_mutable_span_(*splines.first())),
domain_,
std::move(tag_modified_fn)};
}
Array<int> offsets = curve->control_point_offsets();
Array<MutableSpan<T>> spans(splines.size());
for (const int i : splines.index_range()) {
spans[i] = get_mutable_span_(*splines[i]);
}
return {point_data_varray_mutable(spans, offsets), domain_, tag_modified_fn};
}
bool try_delete(void *owner) const final
{
if (deletable_ == DeletableEnum::NonDeletable) {
return false;
}
CurveEval *curve = static_cast<CurveEval *>(owner);
return remove_point_attribute(curve, name_);
}
bool try_create(void *owner, const AttributeInit &initializer) const final
{
if (createable_ == CreatableEnum::NonCreatable) {
return false;
}
CurveEval *curve = static_cast<CurveEval *>(owner);
return create_point_attribute(curve, name_, initializer, CD_PROP_INT32);
}
bool exists(const void *owner) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
if (curve == nullptr) {
return false;
}
Span<SplinePtr> splines = curve->splines();
if (splines.size() == 0) {
return false;
}
if (stored_in_custom_data_) {
if (!curve->splines().first()->attributes.get_for_read(name_)) {
return false;
}
}
bool has_point = false;
for (const SplinePtr &spline : curve->splines()) {
if (spline->size() != 0) {
has_point = true;
break;
}
}
if (!has_point) {
return false;
}
return true;
}
};
/**
* 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<float3> {
public:
PositionAttributeProvider()
: BuiltinPointAttributeProvider(
"position",
BuiltinAttributeProvider::NonCreatable,
BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.positions(); },
[](Spline &spline) { return spline.positions(); },
[](Spline &spline) { spline.mark_cache_invalid(); },
false)
{
}
GAttributeWriter try_get_for_write(void *owner) const final
{
CurveEval *curve = static_cast<CurveEval *>(owner);
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(CURVE_TYPE_BEZIER)) {
return BuiltinPointAttributeProvider<float3>::try_get_for_write(owner);
}
auto tag_modified_fn = [curve]() {
/* 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. */
curve->mark_cache_invalid();
};
Array<int> offsets = curve->control_point_offsets();
return {VMutableArray<float3>::For<VArrayImpl_For_SplinePosition>(curve->splines(),
std::move(offsets)),
domain_,
tag_modified_fn};
}
};
class BezierHandleAttributeProvider : public BuiltinAttributeProvider {
private:
bool is_right_;
public:
BezierHandleAttributeProvider(const bool is_right)
: BuiltinAttributeProvider(is_right ? "handle_right" : "handle_left",
ATTR_DOMAIN_POINT,
CD_PROP_FLOAT3,
BuiltinAttributeProvider::NonCreatable,
BuiltinAttributeProvider::Writable,
BuiltinAttributeProvider::NonDeletable),
is_right_(is_right)
{
}
GVArray try_get_for_read(const void *owner) const override
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
if (!curve->has_spline_with_type(CURVE_TYPE_BEZIER)) {
return {};
}
Array<int> offsets = curve->control_point_offsets();
/* Use const-cast because the underlying virtual array implementation is shared between const
* and non const data. */
return VArray<float3>::For<VArrayImpl_For_BezierHandles>(
const_cast<CurveEval *>(curve)->splines(), std::move(offsets), is_right_);
}
GAttributeWriter try_get_for_write(void *owner) const override
{
CurveEval *curve = static_cast<CurveEval *>(owner);
if (curve == nullptr) {
return {};
}
if (!curve->has_spline_with_type(CURVE_TYPE_BEZIER)) {
return {};
}
auto tag_modified_fn = [curve]() { curve->mark_cache_invalid(); };
Array<int> offsets = curve->control_point_offsets();
return {VMutableArray<float3>::For<VArrayImpl_For_BezierHandles>(
curve->splines(), std::move(offsets), is_right_),
domain_,
tag_modified_fn};
}
bool try_delete(void *UNUSED(owner)) const final
{
return false;
}
bool try_create(void *UNUSED(owner), const AttributeInit &UNUSED(initializer)) const final
{
return false;
}
bool exists(const void *owner) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
if (curve == nullptr) {
return false;
}
CurveComponentLegacy component;
component.replace(const_cast<CurveEval *>(curve), GeometryOwnershipType::ReadOnly);
return curve->has_spline_with_type(CURVE_TYPE_BEZIER) && !curve->splines().is_empty();
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \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 |
CD_MASK_PROP_INT8;
public:
GAttributeReader try_get_for_read(const void *owner,
const AttributeIDRef &attribute_id) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
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_id);
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_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 {GVArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT};
}
GAttributeReader 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()) {
Span<T> span = spans[i].typed<T>();
/* Use const-cast because the underlying virtual array implementation is shared between
* const and non const data. */
data[i] = MutableSpan<T>(const_cast<T *>(span.data()), span.size());
BLI_assert(data[i].data() != nullptr);
}
attribute = {point_data_varray(data, offsets), ATTR_DOMAIN_POINT};
});
return attribute;
}
/* This function is almost the same as #try_get_for_read, but without const. */
GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = static_cast<CurveEval *>(owner);
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_id);
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_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 {GVMutableArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT};
}
GAttributeWriter 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_varray_mutable(data, offsets), ATTR_DOMAIN_POINT};
});
return attribute;
}
bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = static_cast<CurveEval *>(owner);
return remove_point_attribute(curve, attribute_id);
}
bool try_create(void *owner,
const AttributeIDRef &attribute_id,
const eAttrDomain domain,
const eCustomDataType data_type,
const AttributeInit &initializer) const final
{
BLI_assert(this->type_is_supported(data_type));
if (domain != ATTR_DOMAIN_POINT) {
return false;
}
CurveEval *curve = static_cast<CurveEval *>(owner);
return create_point_attribute(curve, attribute_id, initializer, data_type);
}
bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const final
{
const CurveEval *curve = static_cast<const CurveEval *>(owner);
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(eAttrDomain)> callback) const final
{
callback(ATTR_DOMAIN_POINT);
}
bool type_is_supported(eCustomDataType 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 = {
[](void *owner) -> CustomData * {
CurveEval *curve = static_cast<CurveEval *>(owner);
return curve ? &curve->attributes.data : nullptr;
},
[](const void *owner) -> const CustomData * {
const CurveEval *curve = static_cast<const CurveEval *>(owner);
return curve ? &curve->attributes.data : nullptr;
},
[](const void *owner) -> int {
const CurveEval *curve = static_cast<const CurveEval *>(owner);
return curve->splines().size();
},
nullptr};
static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE,
spline_custom_data_access);
static PositionAttributeProvider position;
static BezierHandleAttributeProvider handles_start(false);
static BezierHandleAttributeProvider handles_end(true);
static BuiltinPointAttributeProvider<int> id(
"id",
BuiltinAttributeProvider::Creatable,
BuiltinAttributeProvider::Deletable,
[](const Spline &spline) {
std::optional<GSpan> span = spline.attributes.get_for_read("id");
return span ? span->typed<int>() : Span<int>();
},
[](Spline &spline) {
std::optional<GMutableSpan> span = spline.attributes.get_for_write("id");
return span ? span->typed<int>() : MutableSpan<int>();
},
{},
true);
static BuiltinPointAttributeProvider<float> radius(
"radius",
BuiltinAttributeProvider::NonCreatable,
BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.radii(); },
[](Spline &spline) { return spline.radii(); },
nullptr,
false);
static BuiltinPointAttributeProvider<float> tilt(
"tilt",
BuiltinAttributeProvider::NonCreatable,
BuiltinAttributeProvider::NonDeletable,
[](const Spline &spline) { return spline.tilts(); },
[](Spline &spline) { return spline.tilts(); },
[](Spline &spline) { spline.mark_cache_invalid(); },
false);
static DynamicPointAttributeProvider point_custom_data;
return ComponentAttributeProviders(
{&position, &id, &radius, &tilt, &handles_start, &handles_end, &resolution, &cyclic},
{&spline_custom_data, &point_custom_data});
}
/** \} */
static AttributeAccessorFunctions get_curve_accessor_functions()
{
static const ComponentAttributeProviders providers = create_attribute_providers_for_curve();
AttributeAccessorFunctions fn =
attribute_accessor_functions::accessor_functions_for_providers<providers>();
fn.domain_size = [](const void *owner, const eAttrDomain domain) -> int {
if (owner == nullptr) {
return 0;
}
const CurveEval &curve_eval = *static_cast<const CurveEval *>(owner);
switch (domain) {
case ATTR_DOMAIN_POINT:
return curve_eval.total_control_point_num();
case ATTR_DOMAIN_CURVE:
return curve_eval.splines().size();
default:
return 0;
}
};
fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) {
return ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
};
fn.adapt_domain = [](const void *owner,
const blender::GVArray &varray,
const eAttrDomain from_domain,
const eAttrDomain to_domain) -> GVArray {
if (owner == nullptr) {
return {};
}
const CurveEval &curve_eval = *static_cast<const CurveEval *>(owner);
return adapt_curve_attribute_domain(curve_eval, varray, from_domain, to_domain);
};
return fn;
}
static const AttributeAccessorFunctions &get_curve_accessor_functions_ref()
{
static const AttributeAccessorFunctions fn = get_curve_accessor_functions();
return fn;
}
} // namespace blender::bke
std::optional<blender::bke::AttributeAccessor> CurveComponentLegacy::attributes() const
{
return blender::bke::AttributeAccessor(curve_, blender::bke::get_curve_accessor_functions_ref());
}
std::optional<blender::bke::MutableAttributeAccessor> CurveComponentLegacy::attributes_for_write()
{
CurveEval *curve = this->get_for_write();
return blender::bke::MutableAttributeAccessor(curve,
blender::bke::get_curve_accessor_functions_ref());
}
blender::bke::MutableAttributeAccessor CurveEval::attributes_for_write()
{
return blender::bke::MutableAttributeAccessor(this,
blender::bke::get_curve_accessor_functions_ref());
}