Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenkernel/BKE_spline.hh9
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc427
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc82
3 files changed, 515 insertions, 3 deletions
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 48b5dfb1623..35bbc23b21a 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -206,8 +206,12 @@ class BezierSpline final : public Spline {
blender::Vector<HandleType> handle_types_left_;
blender::Vector<HandleType> handle_types_right_;
- blender::Vector<blender::float3> handle_positions_left_;
- blender::Vector<blender::float3> handle_positions_right_;
+ /* These are mutable to allow lazy recalculation of #Auto and #Vector handle positions. */
+ mutable blender::Vector<blender::float3> handle_positions_left_;
+ mutable blender::Vector<blender::float3> handle_positions_right_;
+
+ mutable std::mutex auto_handle_mutex_;
+ mutable bool auto_handles_dirty_ = true;
/** Start index in evaluated points array for every control point. */
mutable blender::Vector<int> offset_cache_;
@@ -296,6 +300,7 @@ class BezierSpline final : public Spline {
const blender::fn::GVArray &source_data) const override;
private:
+ void ensure_auto_handles() const;
void correct_end_tangents() const final;
bool segment_is_vector(const int start_index) const;
void evaluate_bezier_segment(const int index,
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 7afecb66bcc..216f0930cf9 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -286,6 +286,415 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
/** \} */
/* -------------------------------------------------------------------- */
+/** \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.
+ * \{ */
+
+static Array<int> control_point_offsets(const CurveEval &curve)
+{
+ Array<int> offsets(curve.splines.size() + 1);
+
+ int size = 0;
+ for (const int spline_index : curve.splines.index_range()) {
+ offsets[spline_index] = size;
+ size += curve.splines[spline_index]->size();
+ }
+ offsets.last() = size;
+
+ return offsets;
+}
+
+namespace {
+struct PointIndices {
+ int spline_index;
+ int point_index;
+};
+} // namespace
+static PointIndices lookup_point_indices(Span<int> offsets, const int index)
+{
+ const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
+ offsets.begin() - 1;
+ const int index_in_spline = index - offsets[spline_index];
+ return {spline_index, index_in_spline};
+}
+
+template<typename T>
+static void point_attribute_materialize(Span<Span<T>> data,
+ Span<int> offsets,
+ const IndexMask mask,
+ MutableSpan<T> 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];
+ initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset);
+ }
+ }
+ else {
+ int spline_index = 0;
+ for (const int i : r_span.index_range()) {
+ const int dst_index = mask[i];
+
+ 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<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_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 i : r_span.index_range()) {
+ const int dst_index = mask[i];
+
+ 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<typename T> class VArray_For_SplinePoints : public VArray<T> {
+ private:
+ const Array<Span<T>> data_;
+ Array<int> offsets_;
+
+ public:
+ VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets)
+ : VArray<T>(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<T> r_span) const final
+ {
+ point_attribute_materialize(data_.as_span(), offsets_, mask, r_span);
+ }
+
+ void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> 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<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> {
+ private:
+ Array<MutableSpan<T>> data_;
+ Array<int> offsets_;
+
+ public:
+ VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets)
+ : VMutableArray<T>(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<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_impl(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_impl(const IndexMask mask, MutableSpan<T> r_span) const final
+ {
+ point_attribute_materialize_to_uninitialized(
+ {(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
+ }
+};
+
+/**
+ * Virtual array implementation specifically for control point positions. This is only needed for
+ * Bezier splines, where adjusting the position also needs to adjust handle positions depending on
+ * the 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<float3> {
+ private:
+ MutableSpan<SplinePtr> splines_;
+ Array<int> offsets_;
+
+ public:
+ VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets)
+ : VMutableArray<float3>(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<BezierSpline *>(&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<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];
+ if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) {
+ MutableSpan<float3> positions = bezier_spline->positions();
+ MutableSpan<float3> handle_positions_left = bezier_spline->handle_positions_left();
+ MutableSpan<float3> 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<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_impl(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_impl(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);
+ }
+};
+
+/**
+ * Provider for any builtin control point attribute that doesn't need
+ * special handling such as 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_;
+
+ 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<T>()),
+ 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
+ {
+ const CurveEval *curve = get_curve_from_component_for_read(component);
+ if (curve == nullptr) {
+ return {};
+ }
+
+ if (curve->splines.size() == 1) {
+ return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*curve->splines.first()));
+ }
+
+ Array<int> offsets = control_point_offsets(*curve);
+ Array<Span<T>> spans(curve->splines.size());
+ for (const int i : curve->splines.index_range()) {
+ spans[i] = get_span_(*curve->splines[i]);
+ }
+
+ return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>(
+ offsets.last(), std::move(spans), std::move(offsets));
+ }
+
+ GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const
+ {
+ CurveEval *curve = get_curve_from_component_for_write(component);
+ if (curve == nullptr) {
+ return {};
+ }
+
+ if (curve->splines.size() == 1) {
+ return std::make_unique<fn::GVMutableArray_For_GMutableSpan>(
+ get_mutable_span_(*curve->splines.first()));
+ }
+
+ Array<int> offsets = control_point_offsets(*curve);
+ Array<MutableSpan<T>> spans(curve->splines.size());
+ for (const int i : curve->splines.index_range()) {
+ spans[i] = get_mutable_span_(*curve->splines[i]);
+ if (update_on_write_) {
+ update_on_write_(*curve->splines[i]);
+ }
+ }
+
+ return std::make_unique<
+ fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>(
+ offsets.last(), std::move(spans), std::move(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. Having 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::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 {};
+ }
+
+ /* 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. */
+ bool curve_has_bezier_spline = false;
+ for (SplinePtr &spline : curve->splines) {
+ if (spline->type() == Spline::Type::Bezier) {
+ curve_has_bezier_spline = true;
+ break;
+ }
+ }
+
+ /* Use the regular position virtual array there are any bezier splines to potentially avoid
+ * using the special position virtual array when there are no Bezier splines anyway. */
+ if (!curve_has_bezier_spline) {
+ return BuiltinPointAttributeProvider<float3>::try_get_for_write(component);
+ }
+
+ for (SplinePtr &spline : curve->splines) {
+ spline->mark_cache_invalid();
+ }
+
+ Array<int> offsets = control_point_offsets(*curve);
+ return std::make_unique<
+ fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>(
+ offsets.last(), curve->splines, std::move(offsets));
+ }
+};
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Attribute Provider Declaration
* \{ */
@@ -307,7 +716,23 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
make_cyclic_read_attribute,
make_cyclic_write_attribute);
- return ComponentAttributeProviders({&resolution, &cyclic}, {});
+ static PositionAttributeProvider position;
+
+ static BuiltinPointAttributeProvider<float> radius(
+ "radius",
+ BuiltinAttributeProvider::Writable,
+ [](const Spline &spline) { return spline.radii(); },
+ [](Spline &spline) { return spline.radii(); },
+ nullptr);
+
+ static BuiltinPointAttributeProvider<float> tilt(
+ "tilt",
+ BuiltinAttributeProvider::Writable,
+ [](const Spline &spline) { return spline.tilts(); },
+ [](Spline &spline) { return spline.tilts(); },
+ [](Spline &spline) { spline.mark_cache_invalid(); });
+
+ return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, {});
}
} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index 20226dc3ed2..0bc6e71fa4d 100644
--- a/source/blender/blenkernel/intern/spline_bezier.cc
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -119,10 +119,12 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left()
}
Span<float3> BezierSpline::handle_positions_left() const
{
+ this->ensure_auto_handles();
return handle_positions_left_;
}
MutableSpan<float3> BezierSpline::handle_positions_left()
{
+ this->ensure_auto_handles();
return handle_positions_left_;
}
Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const
@@ -135,13 +137,90 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right()
}
Span<float3> BezierSpline::handle_positions_right() const
{
+ this->ensure_auto_handles();
return handle_positions_right_;
}
MutableSpan<float3> BezierSpline::handle_positions_right()
{
+ this->ensure_auto_handles();
return handle_positions_right_;
}
+static float3 previous_position(Span<float3> positions, const bool cyclic, const int i)
+{
+ if (i == 0) {
+ if (cyclic) {
+ return positions[positions.size() - 1];
+ }
+ return 2.0f * positions[i] - positions[i + 1];
+ }
+ return positions[i - 1];
+}
+
+static float3 next_position(Span<float3> positions, const bool cyclic, const int i)
+{
+ if (i == positions.size() - 1) {
+ if (cyclic) {
+ return positions[0];
+ }
+ return 2.0f * positions[i] - positions[i - 1];
+ }
+ return positions[i + 1];
+}
+
+void BezierSpline::ensure_auto_handles() const
+{
+ if (!auto_handles_dirty_) {
+ return;
+ }
+
+ std::lock_guard lock{auto_handle_mutex_};
+ if (!auto_handles_dirty_) {
+ return;
+ }
+
+ for (const int i : IndexRange(this->size())) {
+ if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
+ const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
+ const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i];
+ float prev_len = prev_diff.length();
+ float next_len = next_diff.length();
+ if (prev_len == 0.0f) {
+ prev_len = 1.0f;
+ }
+ if (next_len == 0.0f) {
+ next_len = 1.0f;
+ }
+ const float3 dir = next_diff / next_len + prev_diff / prev_len;
+
+ /* This magic number is unfortunate, but comes from elsewhere in Blender. */
+ const float len = dir.length() * 2.5614f;
+ if (len != 0.0f) {
+ if (handle_types_left_[i] == HandleType::Auto) {
+ const float prev_len_clamped = std::min(prev_len, next_len * 5.0f);
+ handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len);
+ }
+ if (handle_types_right_[i] == HandleType::Auto) {
+ const float next_len_clamped = std::min(next_len, prev_len * 5.0f);
+ handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len);
+ }
+ }
+ }
+
+ if (handle_types_left_[i] == HandleType::Vector) {
+ const float3 prev = previous_position(positions_, is_cyclic_, i);
+ handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f);
+ }
+
+ if (handle_types_right_[i] == HandleType::Vector) {
+ const float3 next = next_position(positions_, is_cyclic_, i);
+ handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f);
+ }
+ }
+
+ auto_handles_dirty_ = false;
+}
+
void BezierSpline::translate(const blender::float3 &translation)
{
for (float3 &position : this->positions()) {
@@ -195,6 +274,7 @@ void BezierSpline::mark_cache_invalid()
tangent_cache_dirty_ = true;
normal_cache_dirty_ = true;
length_cache_dirty_ = true;
+ auto_handles_dirty_ = true;
}
int BezierSpline::evaluated_points_size() const
@@ -389,6 +469,8 @@ Span<float3> BezierSpline::evaluated_positions() const
return evaluated_position_cache_;
}
+ this->ensure_auto_handles();
+
const int eval_size = this->evaluated_points_size();
evaluated_position_cache_.resize(eval_size);