Curves: Remove CurveEval and old Spline types

`CurveEval` was added for the first iteration of geometry nodes curve
support. Since then, it has been replaced by the new `Curves` type
which is designed to be much faster for many curves and better
integrated with the rest of Blender. Now that all curve nodes have
been moved to use `Curves` (T95443), the type can be removed,
along with the corresponding geometry component.
This commit is contained in:
Hans Goudey 2022-09-18 15:10:04 -05:00
parent 7536abbe16
commit ecf3435362
12 changed files with 1 additions and 4453 deletions

View File

@ -26,7 +26,6 @@
struct Curves;
struct Collection;
struct Curve;
struct CurveEval;
struct Mesh;
struct Object;
struct PointCloud;
@ -464,45 +463,6 @@ class PointCloudComponent : public GeometryComponent {
private:
};
/**
* Legacy runtime-only curves type.
* These curves are stored differently than other geometry components, because the data structure
* used here does not correspond exactly to the #Curve DNA data structure. A #CurveEval is stored
* here instead, though the component does give access to a #Curve for interfacing with render
* engines and other areas of Blender that expect to use a data-block with an #ID.
*/
class CurveComponentLegacy : public GeometryComponent {
private:
CurveEval *curve_ = nullptr;
GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
public:
CurveComponentLegacy();
~CurveComponentLegacy();
GeometryComponent *copy() const override;
void clear();
bool has_curve() const;
/**
* Clear the component and replace it with the new curve.
*/
void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
CurveEval *release();
const CurveEval *get_for_read() const;
CurveEval *get_for_write();
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
std::optional<blender::bke::AttributeAccessor> attributes() const final;
std::optional<blender::bke::MutableAttributeAccessor> attributes_for_write() final;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE;
};
/**
* A geometry component that stores a group of curves, corresponding the #Curves data-block type
* and the #CurvesGeometry type. Attributes are stored on the control point domain and the

View File

@ -1,688 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*/
#include <mutex>
#include "DNA_curves_types.h"
#include "BLI_float4x4.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
struct Curve;
struct Curves;
struct ListBase;
class Spline;
using SplinePtr = std::unique_ptr<Spline>;
/**
* A spline is an abstraction of a single branch-less curve section, its evaluation methods,
* and data. The spline data itself is just control points and a set of attributes by the set
* of "evaluated" data is often used instead. Conceptually, the derived vs. original data is
* an essential distinction. Derived data is usually calculated lazily and cached on the spline.
*
* Any derived class of Spline has to manage two things:
* 1. Interpolating arbitrary attribute data from the control points to evaluated points.
* 2. Evaluating the positions based on the stored control point data.
*
* Beyond that, everything is the base class's responsibility, with minor exceptions. Further
* evaluation happens in a layer on top of the evaluated points generated by the derived types.
*
* There are a few methods to evaluate a spline:
* 1. #evaluated_positions and #interpolate_to_evaluated give data for the initial
* evaluated points, depending on the resolution.
* 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups
* along the length of a curve.
* 3. #sample_uniform_index_factors returns an array that stores uniform-length samples
* along the spline which can be used to interpolate data from method 1.
*
* Commonly used evaluated data is stored in caches on the spline itself so that operations on
* splines don't need to worry about taking ownership of evaluated data when they don't need to.
*/
class Spline {
public:
NormalMode normal_mode = NORMAL_MODE_MINIMUM_TWIST;
blender::bke::CustomDataAttributes attributes;
protected:
CurveType type_;
bool is_cyclic_ = false;
/** Direction of the spline at each evaluated point. */
mutable blender::Vector<blender::float3> evaluated_tangents_cache_;
mutable std::mutex tangent_cache_mutex_;
mutable bool tangent_cache_dirty_ = true;
/** Normal direction vectors for each evaluated point. */
mutable blender::Vector<blender::float3> evaluated_normals_cache_;
mutable std::mutex normal_cache_mutex_;
mutable bool normal_cache_dirty_ = true;
/** Accumulated lengths along the evaluated points. */
mutable blender::Vector<float> evaluated_lengths_cache_;
mutable std::mutex length_cache_mutex_;
mutable bool length_cache_dirty_ = true;
public:
virtual ~Spline() = default;
Spline(const CurveType type) : type_(type)
{
}
Spline(Spline &other) : attributes(other.attributes), type_(other.type_)
{
copy_base_settings(other, *this);
}
/**
* Return a new spline with the same data, settings, and attributes.
*/
SplinePtr copy() const;
/**
* Return a new spline with the same type and settings like "cyclic", but without any data.
*/
SplinePtr copy_only_settings() const;
/**
* The same as #copy, but skips copying dynamic attributes to the new spline.
*/
SplinePtr copy_without_attributes() const;
static void copy_base_settings(const Spline &src, Spline &dst);
CurveType type() const;
/** Return the number of control points. */
virtual int size() const = 0;
int segments_num() const;
bool is_cyclic() const;
void set_cyclic(bool value);
virtual void resize(int size) = 0;
virtual blender::MutableSpan<blender::float3> positions() = 0;
virtual blender::Span<blender::float3> positions() const = 0;
virtual blender::MutableSpan<float> radii() = 0;
virtual blender::Span<float> radii() const = 0;
virtual blender::MutableSpan<float> tilts() = 0;
virtual blender::Span<float> tilts() const = 0;
virtual void translate(const blender::float3 &translation);
virtual void transform(const blender::float4x4 &matrix);
/**
* Change the direction of the spline (switch the start and end) without changing its shape.
*/
void reverse();
/**
* Mark all caches for re-computation. This must be called after any operation that would
* change the generated positions, tangents, normals, mapping, etc. of the evaluated points.
*/
virtual void mark_cache_invalid() = 0;
virtual int evaluated_points_num() const = 0;
int evaluated_edges_num() const;
float length() const;
virtual blender::Span<blender::float3> evaluated_positions() const = 0;
/**
* Return non-owning access to the cache of accumulated lengths along the spline. Each item is
* the length of the subsequent segment, i.e. the first value is the length of the first segment
* rather than 0. This calculation is rather trivial, and only depends on the evaluated
* positions. However, the results are used often, and it is necessarily single threaded, so it
* is cached.
*/
blender::Span<float> evaluated_lengths() const;
/**
* Return non-owning access to the direction of the curve at each evaluated point.
*/
blender::Span<blender::float3> evaluated_tangents() const;
/**
* Return non-owning access to the direction vectors perpendicular to the tangents at every
* evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode.
*/
blender::Span<blender::float3> evaluated_normals() const;
void bounds_min_max(blender::float3 &min, blender::float3 &max, bool use_evaluated) const;
struct LookupResult {
/**
* The index of the evaluated point before the result location. In other words, the index of
* the edge that the result lies on. If the sampled factor/length is the very end of the
* spline, this will be the second to last index, if it's the very beginning, this will be 0.
*/
int evaluated_index;
/**
* The index of the evaluated point after the result location, accounting for wrapping when
* the spline is cyclic. If the sampled factor/length is the very end of the spline, this will
* be the last index (#evaluated_points_num - 1).
*/
int next_evaluated_index;
/**
* The portion of the way from the evaluated point at #evaluated_index to the next point.
* If the sampled factor/length is the very end of the spline, this will be the 1.0f
*/
float factor;
};
/**
* Find the position on the evaluated spline at the given portion of the total length.
* The return value is the indices of the two neighboring points at that location and the
* factor between them, which can be used to look up any attribute on the evaluated points.
* \note This does not support extrapolation.
*/
LookupResult lookup_evaluated_factor(float factor) const;
/**
* The same as #lookup_evaluated_factor, but looks up a length directly instead of
* a portion of the total.
*/
LookupResult lookup_evaluated_length(float length) const;
/**
* Return an array of evenly spaced samples along the length of the spline. The samples are
* indices and factors to the next index encoded in floats. The logic for converting from the
* float values to interpolation data is in #lookup_data_from_index_factor.
*/
blender::Array<float> sample_uniform_index_factors(int samples_num) const;
LookupResult lookup_data_from_index_factor(float index_factor) const;
/**
* Sample any input data with a value for each evaluated point (already interpolated to evaluated
* points) to arbitrary parameters in between the evaluated points. The interpolation is quite
* simple, but this handles the cyclic and end point special cases.
*/
void sample_with_index_factors(const blender::GVArray &src,
blender::Span<float> index_factors,
blender::GMutableSpan dst) const;
template<typename T>
void sample_with_index_factors(const blender::VArray<T> &src,
blender::Span<float> index_factors,
blender::MutableSpan<T> dst) const
{
this->sample_with_index_factors(
blender::GVArray(src), index_factors, blender::GMutableSpan(dst));
}
template<typename T>
void sample_with_index_factors(blender::Span<T> src,
blender::Span<float> index_factors,
blender::MutableSpan<T> dst) const
{
this->sample_with_index_factors(blender::VArray<T>::ForSpan(src), index_factors, dst);
}
/**
* Interpolate a virtual array of data with the size of the number of control points to the
* evaluated points. For poly splines, the lifetime of the returned virtual array must not
* exceed the lifetime of the input data.
*/
virtual blender::GVArray interpolate_to_evaluated(const blender::GVArray &src) const = 0;
blender::GVArray interpolate_to_evaluated(blender::GSpan data) const;
template<typename T> blender::VArray<T> interpolate_to_evaluated(blender::Span<T> data) const
{
return this->interpolate_to_evaluated(blender::GSpan(data)).typed<T>();
}
protected:
virtual void correct_end_tangents() const = 0;
virtual void copy_settings(Spline &dst) const = 0;
virtual void copy_data(Spline &dst) const = 0;
virtual void reverse_impl() = 0;
};
/**
* A Bezier spline is made up of a many curve segments, possibly achieving continuity of curvature
* by constraining the alignment of curve handles. Evaluation stores the positions and a map of
* factors and indices in a list of floats, which is then used to interpolate any other data.
*/
class BezierSpline final : public Spline {
blender::Vector<blender::float3> positions_;
blender::Vector<float> radii_;
blender::Vector<float> tilts_;
int resolution_;
blender::Vector<int8_t> handle_types_left_;
blender::Vector<int8_t> handle_types_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_;
mutable std::mutex offset_cache_mutex_;
mutable bool offset_cache_dirty_ = true;
/** Cache of evaluated positions. */
mutable blender::Vector<blender::float3> evaluated_position_cache_;
mutable std::mutex position_cache_mutex_;
mutable bool position_cache_dirty_ = true;
/** Cache of "index factors" based calculated from the evaluated positions. */
mutable blender::Vector<float> evaluated_mapping_cache_;
mutable std::mutex mapping_cache_mutex_;
mutable bool mapping_cache_dirty_ = true;
public:
BezierSpline() : Spline(CURVE_TYPE_BEZIER)
{
}
BezierSpline(const BezierSpline &other)
: Spline((Spline &)other),
positions_(other.positions_),
radii_(other.radii_),
tilts_(other.tilts_),
resolution_(other.resolution_),
handle_types_left_(other.handle_types_left_),
handle_types_right_(other.handle_types_right_),
handle_positions_left_(other.handle_positions_left_),
handle_positions_right_(other.handle_positions_right_)
{
}
int size() const final;
int resolution() const;
void set_resolution(int value);
void resize(int size) final;
blender::MutableSpan<blender::float3> positions() final;
blender::Span<blender::float3> positions() const final;
blender::MutableSpan<float> radii() final;
blender::Span<float> radii() const final;
blender::MutableSpan<float> tilts() final;
blender::Span<float> tilts() const final;
blender::Span<int8_t> handle_types_left() const;
blender::MutableSpan<int8_t> handle_types_left();
blender::Span<blender::float3> handle_positions_left() const;
/**
* Get writable access to the handle position.
*
* \param write_only: pass true for an uninitialized spline, this prevents accessing
* uninitialized memory while auto-generating handles.
*/
blender::MutableSpan<blender::float3> handle_positions_left(bool write_only = false);
blender::Span<int8_t> handle_types_right() const;
blender::MutableSpan<int8_t> handle_types_right();
blender::Span<blender::float3> handle_positions_right() const;
/**
* Get writable access to the handle position.
*
* \param write_only: pass true for an uninitialized spline, this prevents accessing
* uninitialized memory while auto-generating handles.
*/
blender::MutableSpan<blender::float3> handle_positions_right(bool write_only = false);
/**
* Recalculate all #Auto and #Vector handles with positions automatically
* derived from the neighboring control points.
*/
void ensure_auto_handles() const;
void translate(const blender::float3 &translation) override;
void transform(const blender::float4x4 &matrix) override;
/**
* Set positions for the right handle of the control point, ensuring that
* aligned handles stay aligned. Has no effect for auto and vector type handles.
*/
void set_handle_position_right(int index, const blender::float3 &value);
/**
* Set positions for the left handle of the control point, ensuring that
* aligned handles stay aligned. Has no effect for auto and vector type handles.
*/
void set_handle_position_left(int index, const blender::float3 &value);
bool point_is_sharp(int index) const;
void mark_cache_invalid() final;
int evaluated_points_num() const final;
/**
* Returns access to a cache of offsets into the evaluated point array for each control point.
* While most control point edges generate the number of edges specified by the resolution,
* vector segments only generate one edge.
*
* \note The length of the result is one greater than the number of points, so that the last item
* is the total number of evaluated points. This is useful to avoid recalculating the size of the
* last segment everywhere.
*/
blender::Span<int> control_point_offsets() const;
/**
* Returns non-owning access to an array of values containing the information necessary to
* interpolate values from the original control points to evaluated points. The control point
* index is the integer part of each value, and the factor used for interpolating to the next
* control point is the remaining fractional part.
*/
blender::Span<float> evaluated_mappings() const;
blender::Span<blender::float3> evaluated_positions() const final;
struct InterpolationData {
int control_point_index;
int next_control_point_index;
/**
* Linear interpolation weight between the two indices, from 0 to 1.
* Higher means closer to next control point.
*/
float factor;
};
/**
* Convert the data encoded in #evaulated_mappings into its parts-- the information necessary
* to interpolate data from control points to evaluated points between them. The next control
* point index result will not overflow the size of the control point vectors.
*/
InterpolationData interpolation_data_from_index_factor(float index_factor) const;
virtual blender::GVArray interpolate_to_evaluated(const blender::GVArray &src) const override;
void evaluate_segment(int index,
int next_index,
blender::MutableSpan<blender::float3> positions) const;
/**
* \warning This functional assumes that the spline has more than one point.
*/
bool segment_is_vector(int start_index) const;
/** See comment and diagram for #calculate_segment_insertion. */
struct InsertResult {
blender::float3 handle_prev;
blender::float3 left_handle;
blender::float3 position;
blender::float3 right_handle;
blender::float3 handle_next;
};
/**
* De Casteljau Bezier subdivision.
* \param index: The index of the segment's start control point.
* \param next_index: The index of the control point at the end of the segment. Could be 0,
* if the spline is cyclic.
* \param parameter: The factor along the segment, between 0 and 1. Note that this is used
* directly by the calculation, it doesn't correspond to a portion of the evaluated length.
*
* <pre>
* handle_prev handle_next
* x----------------x
* / \
* / x---O---x \
* / result \
* / \
* O O
* point_prev point_next
* </pre>
*/
InsertResult calculate_segment_insertion(int index, int next_index, float parameter);
private:
/**
* If the spline is not cyclic, the direction for the first and last points is just the
* direction formed by the corresponding handles and control points. In the unlikely situation
* that the handles define a zero direction, fallback to using the direction defined by the
* first and last evaluated segments already calculated in #Spline::evaluated_tangents().
*/
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
protected:
void reverse_impl() override;
};
/**
* Data for Non-Uniform Rational B-Splines. The mapping from control points to evaluated points is
* influenced by a vector of knots, weights for each point, and the order of the spline. Every
* mapping of data to evaluated points is handled the same way, but the positions are cached in
* the spline.
*/
class NURBSpline final : public Spline {
public:
/** Method used to recalculate the knots vector when points are added or removed. */
KnotsMode knots_mode;
struct BasisCache {
/**
* For each evaluated point, the weight for all control points that influences it.
* The vector's size is the evaluated point count multiplied by the spline's order.
*/
blender::Vector<float> weights;
/**
* An offset for the start of #weights: the first control point index with a non-zero weight.
*/
blender::Vector<int> start_indices;
};
private:
blender::Vector<blender::float3> positions_;
blender::Vector<float> radii_;
blender::Vector<float> tilts_;
blender::Vector<float> weights_;
int resolution_;
/**
* Defines the number of nearby control points that influence a given evaluated point. Higher
* orders give smoother results. The number of control points must be greater than or equal to
* this value.
*/
uint8_t order_;
/**
* Determines where and how the control points affect the evaluated points. The length should
* always be the value returned by #knots_num(), and each value should be greater than or equal
* to the previous. Only invalidated when a point is added or removed.
*/
mutable blender::Vector<float> knots_;
mutable std::mutex knots_mutex_;
mutable bool knots_dirty_ = true;
/** Cache of control point influences on each evaluated point. */
mutable BasisCache basis_cache_;
mutable std::mutex basis_cache_mutex_;
mutable bool basis_cache_dirty_ = true;
/**
* Cache of position data calculated from the basis cache. Though it is interpolated
* in the same way as any other attribute, it is stored to save unnecessary recalculation.
*/
mutable blender::Vector<blender::float3> evaluated_position_cache_;
mutable std::mutex position_cache_mutex_;
mutable bool position_cache_dirty_ = true;
public:
NURBSpline() : Spline(CURVE_TYPE_NURBS)
{
}
NURBSpline(const NURBSpline &other)
: Spline((Spline &)other),
knots_mode(other.knots_mode),
positions_(other.positions_),
radii_(other.radii_),
tilts_(other.tilts_),
weights_(other.weights_),
resolution_(other.resolution_),
order_(other.order_)
{
}
int size() const final;
int resolution() const;
void set_resolution(int value);
uint8_t order() const;
void set_order(uint8_t value);
bool check_valid_num_and_order() const;
int knots_num() const;
void resize(int size) final;
blender::MutableSpan<blender::float3> positions() final;
blender::Span<blender::float3> positions() const final;
blender::MutableSpan<float> radii() final;
blender::Span<float> radii() const final;
blender::MutableSpan<float> tilts() final;
blender::Span<float> tilts() const final;
blender::Span<float> knots() const;
blender::MutableSpan<float> weights();
blender::Span<float> weights() const;
void mark_cache_invalid() final;
int evaluated_points_num() const final;
blender::Span<blender::float3> evaluated_positions() const final;
blender::GVArray interpolate_to_evaluated(const blender::GVArray &src) const final;
protected:
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
void reverse_impl() override;
void calculate_knots() const;
const BasisCache &calculate_basis_cache() const;
};
/**
* A Poly spline is like a Bezier spline with a resolution of one. The main reason to distinguish
* the two is for reduced complexity and increased performance, since interpolating data to control
* points does not change it.
*
* Poly spline code is very simple, since it doesn't do anything that the base #Spline doesn't
* handle. Mostly it just worries about storing the data used by the base class.
*/
class PolySpline final : public Spline {
blender::Vector<blender::float3> positions_;
blender::Vector<float> radii_;
blender::Vector<float> tilts_;
public:
PolySpline() : Spline(CURVE_TYPE_POLY)
{
}
PolySpline(const PolySpline &other)
: Spline((Spline &)other),
positions_(other.positions_),
radii_(other.radii_),
tilts_(other.tilts_)
{
}
int size() const final;
void resize(int size) final;
blender::MutableSpan<blender::float3> positions() final;
blender::Span<blender::float3> positions() const final;
blender::MutableSpan<float> radii() final;
blender::Span<float> radii() const final;
blender::MutableSpan<float> tilts() final;
blender::Span<float> tilts() const final;
void mark_cache_invalid() final;
int evaluated_points_num() const final;
blender::Span<blender::float3> evaluated_positions() const final;
/**
* Poly spline interpolation from control points to evaluated points is a special case, since
* the result data is the same as the input data. This function returns a #GVArray that points to
* the original data. Therefore the lifetime of the returned virtual array must not be longer
* than the source data.
*/
blender::GVArray interpolate_to_evaluated(const blender::GVArray &src) const final;
protected:
void correct_end_tangents() const final;
void copy_settings(Spline &dst) const final;
void copy_data(Spline &dst) const final;
void reverse_impl() override;
};
/**
* A collection of #Spline objects with the same attribute types and names. Most data and
* functionality is in splines, but this contains some helpers for working with them as a group.
*
* \note A #CurveEval corresponds to the #Curve object data. The name is different for clarity,
* since more of the data is stored in the splines, but also just to be different than the name in
* DNA.
*/
struct CurveEval {
private:
blender::Vector<SplinePtr> splines_;
public:
blender::bke::CustomDataAttributes attributes;
CurveEval() = default;
CurveEval(const CurveEval &other) : attributes(other.attributes)
{
for (const SplinePtr &spline : other.splines()) {
this->add_spline(spline->copy());
}
}
blender::Span<SplinePtr> splines() const;
blender::MutableSpan<SplinePtr> splines();
/**
* \return True if the curve contains a spline with the given type.
*
* \note If you are looping over all of the splines in the same scope anyway,
* it's better to avoid calling this function, in case there are many splines.
*/
bool has_spline_with_type(const CurveType type) const;
void resize(int size);
/**
* \warning Call #reallocate on the spline's attributes after adding all splines.
*/
void add_spline(SplinePtr spline);
void add_splines(blender::MutableSpan<SplinePtr> splines);
void remove_splines(blender::IndexMask mask);
void translate(const blender::float3 &translation);
void transform(const blender::float4x4 &matrix);
bool bounds_min_max(blender::float3 &min, blender::float3 &max, bool use_evaluated) const;
blender::bke::MutableAttributeAccessor attributes_for_write();
/**
* Return the start indices for each of the curve spline's control points, if they were part
* of a flattened array. This can be used to facilitate parallelism by avoiding the need to
* accumulate an offset while doing more complex calculations.
*
* \note The result is one longer than the spline count; the last element is the total size.
*/
blender::Array<int> control_point_offsets() const;
/**
* Exactly like #control_point_offsets, but uses the number of evaluated points instead.
*/
blender::Array<int> evaluated_point_offsets() const;
/**
* Return the accumulated length at the start of every spline in the curve.
* \note The result is one longer than the spline count; the last element is the total length.
*/
blender::Array<float> accumulated_spline_lengths() const;
float total_length() const;
int total_control_point_num() const;
void mark_cache_invalid();
/**
* Check the invariants that curve control point attributes should always uphold, necessary
* because attributes are stored on splines rather than in a flat array on the curve:
* - The same set of attributes exists on every spline.
* - Attributes with the same name have the same type on every spline.
* - Attributes are in the same order on every spline.
*/
void assert_valid_point_attributes() const;
};
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve,
const ListBase &nurbs_list);
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve);
std::unique_ptr<CurveEval> curves_to_curve_eval(const Curves &curves);
Curves *curve_eval_to_curves(const CurveEval &curve_eval);

View File

@ -110,7 +110,6 @@ set(SRC
intern/curve_convert.c
intern/curve_decimate.c
intern/curve_deform.c
intern/curve_eval.cc
intern/curve_legacy_convert.cc
intern/curve_nurbs.cc
intern/curve_poly.cc
@ -137,7 +136,6 @@ set(SRC
intern/fluid.c
intern/fmodifier.c
intern/freestyle.c
intern/geometry_component_curve.cc
intern/geometry_component_curves.cc
intern/geometry_component_edit_data.cc
intern/geometry_component_instances.cc
@ -266,10 +264,6 @@ set(SRC
intern/softbody.c
intern/sound.c
intern/speaker.c
intern/spline_base.cc
intern/spline_bezier.cc
intern/spline_nurbs.cc
intern/spline_poly.cc
intern/studiolight.c
intern/subdiv.c
intern/subdiv_ccg.c
@ -469,7 +463,6 @@ set(SRC
BKE_softbody.h
BKE_sound.h
BKE_speaker.h
BKE_spline.hh
BKE_studiolight.h
BKE_subdiv.h
BKE_subdiv_ccg.h

View File

@ -1,587 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_index_range.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "DNA_curve_types.h"
#include "BKE_anonymous_attribute.hh"
#include "BKE_curve.h"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_spline.hh"
using blender::Array;
using blender::float3;
using blender::float4x4;
using blender::GVArray;
using blender::GVArraySpan;
using blender::IndexRange;
using blender::Map;
using blender::MutableSpan;
using blender::Span;
using blender::StringRefNull;
using blender::VArray;
using blender::VArraySpan;
using blender::Vector;
using blender::bke::AttributeIDRef;
using blender::bke::AttributeMetaData;
blender::Span<SplinePtr> CurveEval::splines() const
{
return splines_;
}
blender::MutableSpan<SplinePtr> CurveEval::splines()
{
return splines_;
}
bool CurveEval::has_spline_with_type(const CurveType type) const
{
for (const SplinePtr &spline : this->splines()) {
if (spline->type() == type) {
return true;
}
}
return false;
}
void CurveEval::resize(const int size)
{
splines_.resize(size);
attributes.reallocate(size);
}
void CurveEval::add_spline(SplinePtr spline)
{
splines_.append(std::move(spline));
}
void CurveEval::add_splines(MutableSpan<SplinePtr> splines)
{
for (SplinePtr &spline : splines) {
this->add_spline(std::move(spline));
}
}
void CurveEval::remove_splines(blender::IndexMask mask)
{
for (int i = mask.size() - 1; i >= 0; i--) {
splines_.remove_and_reorder(mask.indices()[i]);
}
}
void CurveEval::translate(const float3 &translation)
{
for (SplinePtr &spline : this->splines()) {
spline->translate(translation);
spline->mark_cache_invalid();
}
}
void CurveEval::transform(const float4x4 &matrix)
{
for (SplinePtr &spline : this->splines()) {
spline->transform(matrix);
}
}
bool CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
{
bool have_minmax = false;
for (const SplinePtr &spline : this->splines()) {
if (spline->size()) {
spline->bounds_min_max(min, max, use_evaluated);
have_minmax = true;
}
}
return have_minmax;
}
float CurveEval::total_length() const
{
float length = 0.0f;
for (const SplinePtr &spline : this->splines()) {
length += spline->length();
}
return length;
}
int CurveEval::total_control_point_num() const
{
int count = 0;
for (const SplinePtr &spline : this->splines()) {
count += spline->size();
}
return count;
}
blender::Array<int> CurveEval::control_point_offsets() const
{
Array<int> offsets(splines_.size() + 1);
int offset = 0;
for (const int i : splines_.index_range()) {
offsets[i] = offset;
offset += splines_[i]->size();
}
offsets.last() = offset;
return offsets;
}
blender::Array<int> CurveEval::evaluated_point_offsets() const
{
Array<int> offsets(splines_.size() + 1);
int offset = 0;
for (const int i : splines_.index_range()) {
offsets[i] = offset;
offset += splines_[i]->evaluated_points_num();
}
offsets.last() = offset;
return offsets;
}
blender::Array<float> CurveEval::accumulated_spline_lengths() const
{
Array<float> spline_lengths(splines_.size() + 1);
float spline_length = 0.0f;
for (const int i : splines_.index_range()) {
spline_lengths[i] = spline_length;
spline_length += splines_[i]->length();
}
spline_lengths.last() = spline_length;
return spline_lengths;
}
void CurveEval::mark_cache_invalid()
{
for (SplinePtr &spline : splines_) {
spline->mark_cache_invalid();
}
}
static HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
{
switch (dna_handle_type) {
case HD_FREE:
return BEZIER_HANDLE_FREE;
case HD_AUTO:
return BEZIER_HANDLE_AUTO;
case HD_VECT:
return BEZIER_HANDLE_VECTOR;
case HD_ALIGN:
return BEZIER_HANDLE_ALIGN;
case HD_AUTO_ANIM:
return BEZIER_HANDLE_AUTO;
case HD_ALIGN_DOUBLESIDE:
return BEZIER_HANDLE_ALIGN;
}
BLI_assert_unreachable();
return BEZIER_HANDLE_AUTO;
}
static NormalMode normal_mode_from_dna_curve(const int twist_mode)
{
switch (twist_mode) {
case CU_TWIST_Z_UP:
case CU_TWIST_TANGENT:
return NORMAL_MODE_Z_UP;
case CU_TWIST_MINIMUM:
return NORMAL_MODE_MINIMUM_TWIST;
}
BLI_assert_unreachable();
return NORMAL_MODE_MINIMUM_TWIST;
}
static KnotsMode knots_mode_from_dna_nurb(const short flag)
{
switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) {
case CU_NURB_ENDPOINT:
return NURBS_KNOT_MODE_ENDPOINT;
case CU_NURB_BEZIER:
return NURBS_KNOT_MODE_BEZIER;
case CU_NURB_ENDPOINT | CU_NURB_BEZIER:
return NURBS_KNOT_MODE_ENDPOINT_BEZIER;
default:
return NURBS_KNOT_MODE_NORMAL;
}
BLI_assert_unreachable();
return NURBS_KNOT_MODE_NORMAL;
}
static SplinePtr spline_from_dna_bezier(const Nurb &nurb)
{
std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>();
spline->set_resolution(nurb.resolu);
spline->set_cyclic(nurb.flagu & CU_NURB_CYCLIC);
Span<const BezTriple> src_points{nurb.bezt, nurb.pntsu};
spline->resize(src_points.size());
MutableSpan<float3> positions = spline->positions();
MutableSpan<float3> handle_positions_left = spline->handle_positions_left(true);
MutableSpan<float3> handle_positions_right = spline->handle_positions_right(true);
MutableSpan<int8_t> handle_types_left = spline->handle_types_left();
MutableSpan<int8_t> handle_types_right = spline->handle_types_right();
MutableSpan<float> radii = spline->radii();
MutableSpan<float> tilts = spline->tilts();
blender::threading::parallel_for(src_points.index_range(), 2048, [&](IndexRange range) {
for (const int i : range) {
const BezTriple &bezt = src_points[i];
positions[i] = bezt.vec[1];
handle_positions_left[i] = bezt.vec[0];
handle_types_left[i] = handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h1);
handle_positions_right[i] = bezt.vec[2];
handle_types_right[i] = handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h2);
radii[i] = bezt.radius;
tilts[i] = bezt.tilt;
}
});
return spline;
}
static SplinePtr spline_from_dna_nurbs(const Nurb &nurb)
{
std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>();
spline->set_resolution(nurb.resolu);
spline->set_cyclic(nurb.flagu & CU_NURB_CYCLIC);
spline->set_order(nurb.orderu);
spline->knots_mode = knots_mode_from_dna_nurb(nurb.flagu);
Span<const BPoint> src_points{nurb.bp, nurb.pntsu};
spline->resize(src_points.size());
MutableSpan<float3> positions = spline->positions();
MutableSpan<float> weights = spline->weights();
MutableSpan<float> radii = spline->radii();
MutableSpan<float> tilts = spline->tilts();
blender::threading::parallel_for(src_points.index_range(), 2048, [&](IndexRange range) {
for (const int i : range) {
const BPoint &bp = src_points[i];
positions[i] = bp.vec;
weights[i] = bp.vec[3];
radii[i] = bp.radius;
tilts[i] = bp.tilt;
}
});
return spline;
}
static SplinePtr spline_from_dna_poly(const Nurb &nurb)
{
std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
spline->set_cyclic(nurb.flagu & CU_NURB_CYCLIC);
Span<const BPoint> src_points{nurb.bp, nurb.pntsu};
spline->resize(src_points.size());
MutableSpan<float3> positions = spline->positions();
MutableSpan<float> radii = spline->radii();
MutableSpan<float> tilts = spline->tilts();
blender::threading::parallel_for(src_points.index_range(), 2048, [&](IndexRange range) {
for (const int i : range) {
const BPoint &bp = src_points[i];
positions[i] = bp.vec;
radii[i] = bp.radius;
tilts[i] = bp.tilt;
}
});
return spline;
}
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve,
const ListBase &nurbs_list)
{
Vector<const Nurb *> nurbs(nurbs_list);
std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>();
curve->resize(nurbs.size());
MutableSpan<SplinePtr> splines = curve->splines();
blender::threading::parallel_for(nurbs.index_range(), 256, [&](IndexRange range) {
for (const int i : range) {
switch (nurbs[i]->type) {
case CU_BEZIER:
splines[i] = spline_from_dna_bezier(*nurbs[i]);
break;
case CU_NURBS:
splines[i] = spline_from_dna_nurbs(*nurbs[i]);
break;
case CU_POLY:
splines[i] = spline_from_dna_poly(*nurbs[i]);
break;
default:
BLI_assert_unreachable();
break;
}
}
});
/* Normal mode is stored separately in each spline to facilitate combining
* splines from multiple curve objects, where the value may be different. */
const NormalMode normal_mode = normal_mode_from_dna_curve(dna_curve.twist_mode);
for (SplinePtr &spline : curve->splines()) {
spline->normal_mode = normal_mode;
}
return curve;
}
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
{
return curve_eval_from_dna_curve(dna_curve, *BKE_curve_nurbs_get_for_read(&dna_curve));
}
static void copy_attributes_between_components(
const blender::bke::AttributeAccessor &src_attributes,
blender::bke::MutableAttributeAccessor &dst_attributes,
Span<std::string> skip)
{
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (id.is_named() && skip.contains(id.name())) {
return true;
}
GVArray src_attribute = src_attributes.lookup(id, meta_data.domain, meta_data.data_type);
if (!src_attribute) {
return true;
}
GVArraySpan src_attribute_data{src_attribute};
blender::bke::GAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write(
id, meta_data.domain, meta_data.data_type);
if (!dst_attribute) {
return true;
}
dst_attribute.varray.set_all(src_attribute_data.data());
dst_attribute.finish();
return true;
});
}
std::unique_ptr<CurveEval> curves_to_curve_eval(const Curves &curves_id)
{
CurveComponent src_component;
src_component.replace(&const_cast<Curves &>(curves_id), GeometryOwnershipType::ReadOnly);
const blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap(
curves_id.geometry);
const blender::bke::AttributeAccessor src_attributes = curves.attributes();
VArray<int> resolution = curves.resolution();
VArray<int8_t> normal_mode = curves.normal_mode();
VArraySpan<float> nurbs_weights{
src_attributes.lookup_or_default<float>("nurbs_weight", ATTR_DOMAIN_POINT, 1.0f)};
VArraySpan<int8_t> nurbs_orders{
src_attributes.lookup_or_default<int8_t>("nurbs_order", ATTR_DOMAIN_CURVE, 4)};
VArraySpan<int8_t> nurbs_knots_modes{
src_attributes.lookup_or_default<int8_t>("knots_mode", ATTR_DOMAIN_CURVE, 0)};
VArraySpan<int8_t> handle_types_right{
src_attributes.lookup_or_default<int8_t>("handle_type_right", ATTR_DOMAIN_POINT, 0)};
VArraySpan<int8_t> handle_types_left{
src_attributes.lookup_or_default<int8_t>("handle_type_left", ATTR_DOMAIN_POINT, 0)};
/* Create splines with the correct size and type. */
VArray<int8_t> curve_types = curves.curve_types();
std::unique_ptr<CurveEval> curve_eval = std::make_unique<CurveEval>();
for (const int curve_index : curve_types.index_range()) {
const IndexRange points = curves.points_for_curve(curve_index);
std::unique_ptr<Spline> spline;
/* #CurveEval does not support catmull rom curves, so convert those to poly splines. */
switch (std::max<int8_t>(1, curve_types[curve_index])) {
case CURVE_TYPE_POLY: {
spline = std::make_unique<PolySpline>();
spline->resize(points.size());
break;
}
case CURVE_TYPE_BEZIER: {
std::unique_ptr<BezierSpline> bezier_spline = std::make_unique<BezierSpline>();
bezier_spline->resize(points.size());
bezier_spline->set_resolution(resolution[curve_index]);
bezier_spline->handle_types_left().copy_from(handle_types_left.slice(points));
bezier_spline->handle_types_right().copy_from(handle_types_right.slice(points));
spline = std::move(bezier_spline);
break;
}
case CURVE_TYPE_NURBS: {
std::unique_ptr<NURBSpline> nurb_spline = std::make_unique<NURBSpline>();
nurb_spline->resize(points.size());
nurb_spline->set_resolution(resolution[curve_index]);
nurb_spline->weights().copy_from(nurbs_weights.slice(points));
nurb_spline->set_order(nurbs_orders[curve_index]);
nurb_spline->knots_mode = static_cast<KnotsMode>(nurbs_knots_modes[curve_index]);
spline = std::move(nurb_spline);
break;
}
case CURVE_TYPE_CATMULL_ROM:
/* Not supported yet. */
BLI_assert_unreachable();
continue;
}
spline->positions().fill(float3(0));
spline->tilts().fill(0.0f);
spline->radii().fill(1.0f);
spline->normal_mode = static_cast<NormalMode>(normal_mode[curve_index]);
curve_eval->add_spline(std::move(spline));
}
curve_eval->attributes.reallocate(curve_eval->splines().size());
CurveComponentLegacy dst_component;
dst_component.replace(curve_eval.get(), GeometryOwnershipType::Editable);
blender::bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write();
copy_attributes_between_components(src_attributes,
dst_attributes,
{"curve_type",
"resolution",
"normal_mode",
"nurbs_weight",
"nurbs_order",
"knots_mode",
"handle_type_right",
"handle_type_left"});
return curve_eval;
}
Curves *curve_eval_to_curves(const CurveEval &curve_eval)
{
Curves *curves_id = blender::bke::curves_new_nomain(curve_eval.total_control_point_num(),
curve_eval.splines().size());
CurveComponent dst_component;
dst_component.replace(curves_id, GeometryOwnershipType::Editable);
blender::bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write();
blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap(curves_id->geometry);
curves.offsets_for_write().copy_from(curve_eval.control_point_offsets());
MutableSpan<int8_t> curve_types = curves.curve_types_for_write();
blender::bke::SpanAttributeWriter<int8_t> normal_mode =
dst_attributes.lookup_or_add_for_write_only_span<int8_t>("normal_mode", ATTR_DOMAIN_CURVE);
blender::bke::SpanAttributeWriter<float> nurbs_weight;
blender::bke::SpanAttributeWriter<int8_t> nurbs_order;
blender::bke::SpanAttributeWriter<int8_t> nurbs_knots_mode;
if (curve_eval.has_spline_with_type(CURVE_TYPE_NURBS)) {
nurbs_weight = dst_attributes.lookup_or_add_for_write_only_span<float>("nurbs_weight",
ATTR_DOMAIN_POINT);
nurbs_order = dst_attributes.lookup_or_add_for_write_only_span<int8_t>("nurbs_order",
ATTR_DOMAIN_CURVE);
nurbs_knots_mode = dst_attributes.lookup_or_add_for_write_only_span<int8_t>("knots_mode",
ATTR_DOMAIN_CURVE);
}
blender::bke::SpanAttributeWriter<int8_t> handle_type_right;
blender::bke::SpanAttributeWriter<int8_t> handle_type_left;
if (curve_eval.has_spline_with_type(CURVE_TYPE_BEZIER)) {
handle_type_right = dst_attributes.lookup_or_add_for_write_only_span<int8_t>(
"handle_type_right", ATTR_DOMAIN_POINT);
handle_type_left = dst_attributes.lookup_or_add_for_write_only_span<int8_t>("handle_type_left",
ATTR_DOMAIN_POINT);
}
for (const int curve_index : curve_eval.splines().index_range()) {
const Spline &spline = *curve_eval.splines()[curve_index];
curve_types[curve_index] = curve_eval.splines()[curve_index]->type();
normal_mode.span[curve_index] = curve_eval.splines()[curve_index]->normal_mode;
const IndexRange points = curves.points_for_curve(curve_index);
switch (spline.type()) {
case CURVE_TYPE_POLY:
break;
case CURVE_TYPE_BEZIER: {
const BezierSpline &src = static_cast<const BezierSpline &>(spline);
handle_type_right.span.slice(points).copy_from(src.handle_types_right());
handle_type_left.span.slice(points).copy_from(src.handle_types_left());
break;
}
case CURVE_TYPE_NURBS: {
const NURBSpline &src = static_cast<const NURBSpline &>(spline);
nurbs_knots_mode.span[curve_index] = static_cast<int8_t>(src.knots_mode);
nurbs_order.span[curve_index] = src.order();
nurbs_weight.span.slice(points).copy_from(src.weights());
break;
}
case CURVE_TYPE_CATMULL_ROM: {
BLI_assert_unreachable();
break;
}
}
}
curves.update_curve_types();
normal_mode.finish();
nurbs_weight.finish();
nurbs_order.finish();
nurbs_knots_mode.finish();
handle_type_right.finish();
handle_type_left.finish();
CurveComponentLegacy src_component;
src_component.replace(&const_cast<CurveEval &>(curve_eval), GeometryOwnershipType::ReadOnly);
const blender::bke::AttributeAccessor src_attributes = *src_component.attributes();
copy_attributes_between_components(src_attributes, dst_attributes, {});
return curves_id;
}
void CurveEval::assert_valid_point_attributes() const
{
#ifdef DEBUG
if (splines_.size() == 0) {
return;
}
const int layer_len = splines_.first()->attributes.data.totlayer;
Array<AttributeIDRef> ids_in_order(layer_len);
Array<AttributeMetaData> meta_data_in_order(layer_len);
{
int i = 0;
splines_.first()->attributes.foreach_attribute(
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
ids_in_order[i] = attribute_id;
meta_data_in_order[i] = meta_data;
i++;
return true;
},
ATTR_DOMAIN_POINT);
}
for (const SplinePtr &spline : splines_) {
/* All splines should have the same number of attributes. */
BLI_assert(spline->attributes.data.totlayer == layer_len);
int i = 0;
spline->attributes.foreach_attribute(
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
/* Attribute names and IDs should have the same order and exist on all splines. */
BLI_assert(attribute_id == ids_in_order[i]);
/* Attributes with the same ID different splines should all have the same type. */
BLI_assert(meta_data == meta_data_in_order[i]);
i++;
return true;
},
ATTR_DOMAIN_POINT);
}
#endif
}

View File

@ -634,7 +634,7 @@ void BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
/**
* \return True if the deformed curve control point data should be implicitly
* converted directly to a mesh, or false if it can be left as curve data via #CurveEval.
* converted directly to a mesh, or false if it can be left as curve data via the #Curves type.
*/
static bool do_curve_implicit_mesh_conversion(const Curve *curve,
ModifierData *first_modifier,

File diff suppressed because it is too large Load Diff

View File

@ -1,526 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "BKE_attribute_math.hh"
#include "BKE_spline.hh"
using blender::Array;
using blender::float3;
using blender::GMutableSpan;
using blender::GSpan;
using blender::GVArray;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
using blender::VArray;
using blender::attribute_math::convert_to_static_type;
using blender::bke::AttributeIDRef;
using blender::bke::AttributeMetaData;
CurveType Spline::type() const
{
return type_;
}
void Spline::copy_base_settings(const Spline &src, Spline &dst)
{
dst.normal_mode = src.normal_mode;
dst.is_cyclic_ = src.is_cyclic_;
}
static SplinePtr create_spline(const CurveType type)
{
switch (type) {
case CURVE_TYPE_POLY:
return std::make_unique<PolySpline>();
case CURVE_TYPE_BEZIER:
return std::make_unique<BezierSpline>();
case CURVE_TYPE_NURBS:
return std::make_unique<NURBSpline>();
case CURVE_TYPE_CATMULL_ROM:
BLI_assert_unreachable();
return {};
}
BLI_assert_unreachable();
return {};
}
SplinePtr Spline::copy() const
{
SplinePtr dst = this->copy_without_attributes();
dst->attributes = this->attributes;
return dst;
}
SplinePtr Spline::copy_only_settings() const
{
SplinePtr dst = create_spline(type_);
this->copy_base_settings(*this, *dst);
this->copy_settings(*dst);
return dst;
}
SplinePtr Spline::copy_without_attributes() const
{
SplinePtr dst = this->copy_only_settings();
this->copy_data(*dst);
/* Though the attributes storage is empty, it still needs to know the correct size. */
dst->attributes.reallocate(dst->size());
return dst;
}
void Spline::translate(const blender::float3 &translation)
{
for (float3 &position : this->positions()) {
position += translation;
}
this->mark_cache_invalid();
}
void Spline::transform(const blender::float4x4 &matrix)
{
for (float3 &position : this->positions()) {
position = matrix * position;
}
this->mark_cache_invalid();
}
void Spline::reverse()
{
this->positions().reverse();
this->radii().reverse();
this->tilts().reverse();
this->attributes.foreach_attribute(
[&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
std::optional<blender::GMutableSpan> attribute = this->attributes.get_for_write(id);
if (!attribute) {
BLI_assert_unreachable();
return false;
}
convert_to_static_type(meta_data.data_type, [&](auto dummy) {
using T = decltype(dummy);
attribute->typed<T>().reverse();
});
return true;
},
ATTR_DOMAIN_POINT);
this->reverse_impl();
this->mark_cache_invalid();
}
int Spline::evaluated_edges_num() const
{
const int eval_num = this->evaluated_points_num();
if (eval_num < 2) {
/* Two points are required for an edge. */
return 0;
}
return this->is_cyclic_ ? eval_num : eval_num - 1;
}
float Spline::length() const
{
Span<float> lengths = this->evaluated_lengths();
return lengths.is_empty() ? 0.0f : this->evaluated_lengths().last();
}
int Spline::segments_num() const
{
const int num = this->size();
return is_cyclic_ ? num : num - 1;
}
bool Spline::is_cyclic() const
{
return is_cyclic_;
}
void Spline::set_cyclic(const bool value)
{
is_cyclic_ = value;
}
static void accumulate_lengths(Span<float3> positions,
const bool is_cyclic,
MutableSpan<float> lengths)
{
using namespace blender::math;
float length = 0.0f;
for (const int i : IndexRange(positions.size() - 1)) {
length += distance(positions[i], positions[i + 1]);
lengths[i] = length;
}
if (is_cyclic) {
lengths.last() = length + distance(positions.last(), positions.first());
}
}
Span<float> Spline::evaluated_lengths() const
{
if (!length_cache_dirty_) {
return evaluated_lengths_cache_;
}
std::lock_guard lock{length_cache_mutex_};
if (!length_cache_dirty_) {
return evaluated_lengths_cache_;
}
const int total = evaluated_edges_num();
evaluated_lengths_cache_.resize(total);
if (total != 0) {
Span<float3> positions = this->evaluated_positions();
accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_);
}
length_cache_dirty_ = false;
return evaluated_lengths_cache_;
}
static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next)
{
using namespace blender::math;
const float3 dir_prev = normalize(middle - prev);
const float3 dir_next = normalize(next - middle);
const float3 result = normalize(dir_prev + dir_next);
if (UNLIKELY(is_zero(result))) {
return float3(0.0f, 0.0f, 1.0f);
}
return result;
}
static void calculate_tangents(Span<float3> positions,
const bool is_cyclic,
MutableSpan<float3> tangents)
{
using namespace blender::math;
if (positions.size() == 1) {
tangents.first() = float3(0.0f, 0.0f, 1.0f);
return;
}
for (const int i : IndexRange(1, positions.size() - 2)) {
tangents[i] = direction_bisect(positions[i - 1], positions[i], positions[i + 1]);
}
if (is_cyclic) {
const float3 &second_to_last = positions[positions.size() - 2];
const float3 &last = positions.last();
const float3 &first = positions.first();
const float3 &second = positions[1];
tangents.first() = direction_bisect(last, first, second);
tangents.last() = direction_bisect(second_to_last, last, first);
}
else {
tangents.first() = normalize(positions[1] - positions[0]);
tangents.last() = normalize(positions.last() - positions[positions.size() - 2]);
}
}
Span<float3> Spline::evaluated_tangents() const
{
if (!tangent_cache_dirty_) {
return evaluated_tangents_cache_;
}
std::lock_guard lock{tangent_cache_mutex_};
if (!tangent_cache_dirty_) {
return evaluated_tangents_cache_;
}
const int eval_num = this->evaluated_points_num();
evaluated_tangents_cache_.resize(eval_num);
Span<float3> positions = this->evaluated_positions();
calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_);
this->correct_end_tangents();
tangent_cache_dirty_ = false;
return evaluated_tangents_cache_;
}
static float3 rotate_direction_around_axis(const float3 &direction,
const float3 &axis,
const float angle)
{
using namespace blender::math;
BLI_ASSERT_UNIT_V3(direction);
BLI_ASSERT_UNIT_V3(axis);
const float3 axis_scaled = axis * dot(direction, axis);
const float3 diff = direction - axis_scaled;
const float3 cross = blender::math::cross(axis, diff);
return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle);
}
static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> r_normals)
{
using namespace blender::math;
BLI_assert(r_normals.size() == tangents.size());
/* Same as in `vec_to_quat`. */
const float epsilon = 1e-4f;
for (const int i : r_normals.index_range()) {
const float3 &tangent = tangents[i];
if (fabsf(tangent.x) + fabsf(tangent.y) < epsilon) {
r_normals[i] = {1.0f, 0.0f, 0.0f};
}
else {
r_normals[i] = normalize(float3(tangent.y, -tangent.x, 0.0f));
}
}
}
/**
* Rotate the last normal in the same way the tangent has been rotated.
*/
static float3 calculate_next_normal(const float3 &last_normal,
const float3 &last_tangent,
const float3 &current_tangent)
{
using namespace blender::math;
if (is_zero(last_tangent) || is_zero(current_tangent)) {
return last_normal;
}
const float angle = angle_normalized_v3v3(last_tangent, current_tangent);
if (angle != 0.0) {
const float3 axis = normalize(cross(last_tangent, current_tangent));
return rotate_direction_around_axis(last_normal, axis, angle);
}
return last_normal;
}
static void calculate_normals_minimum(Span<float3> tangents,
const bool cyclic,
MutableSpan<float3> r_normals)
{
using namespace blender::math;
BLI_assert(r_normals.size() == tangents.size());
if (r_normals.is_empty()) {
return;
}
const float epsilon = 1e-4f;
/* Set initial normal. */
const float3 &first_tangent = tangents[0];
if (fabs(first_tangent.x) + fabs(first_tangent.y) < epsilon) {
r_normals[0] = {1.0f, 0.0f, 0.0f};
}
else {
r_normals[0] = normalize(float3(first_tangent.y, -first_tangent.x, 0.0f));
}
/* Forward normal with minimum twist along the entire spline. */
for (const int i : IndexRange(1, r_normals.size() - 1)) {
r_normals[i] = calculate_next_normal(r_normals[i - 1], tangents[i - 1], tangents[i]);
}
if (!cyclic) {
return;
}
/* Compute how much the first normal deviates from the normal that has been forwarded along the
* entire cyclic spline. */
const float3 uncorrected_last_normal = calculate_next_normal(
r_normals.last(), tangents.last(), tangents[0]);
float correction_angle = angle_signed_on_axis_v3v3_v3(
r_normals[0], uncorrected_last_normal, tangents[0]);
if (correction_angle > M_PI) {
correction_angle = correction_angle - 2 * M_PI;
}
/* Gradually apply correction by rotating all normals slightly. */
const float angle_step = correction_angle / r_normals.size();
for (const int i : r_normals.index_range()) {
const float angle = angle_step * i;
r_normals[i] = rotate_direction_around_axis(r_normals[i], tangents[i], angle);
}
}
Span<float3> Spline::evaluated_normals() const
{
if (!normal_cache_dirty_) {
return evaluated_normals_cache_;
}
std::lock_guard lock{normal_cache_mutex_};
if (!normal_cache_dirty_) {
return evaluated_normals_cache_;
}
const int eval_num = this->evaluated_points_num();
evaluated_normals_cache_.resize(eval_num);
Span<float3> tangents = this->evaluated_tangents();
MutableSpan<float3> normals = evaluated_normals_cache_;
/* Only Z up normals are supported at the moment. */
switch (this->normal_mode) {
case NORMAL_MODE_Z_UP: {
calculate_normals_z_up(tangents, normals);
break;
}
case NORMAL_MODE_MINIMUM_TWIST: {
calculate_normals_minimum(tangents, is_cyclic_, normals);
break;
}
}
/* Rotate the generated normals with the interpolated tilt data. */
VArray<float> tilts = this->interpolate_to_evaluated(this->tilts());
for (const int i : normals.index_range()) {
normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]);
}
normal_cache_dirty_ = false;
return evaluated_normals_cache_;
}
Spline::LookupResult Spline::lookup_evaluated_factor(const float factor) const
{
return this->lookup_evaluated_length(this->length() * factor);
}
Spline::LookupResult Spline::lookup_evaluated_length(const float length) const
{
BLI_assert(length >= 0.0f && length <= this->length());
Span<float> lengths = this->evaluated_lengths();
const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length);
const int index = offset - lengths.begin();
const int next_index = (index == this->evaluated_points_num() - 1) ? 0 : index + 1;
const float previous_length = (index == 0) ? 0.0f : lengths[index - 1];
const float length_in_segment = length - previous_length;
const float segment_length = lengths[index] - previous_length;
const float factor = segment_length == 0.0f ? 0.0f : length_in_segment / segment_length;
return LookupResult{index, next_index, factor};
}
Array<float> Spline::sample_uniform_index_factors(const int samples_num) const
{
const Span<float> lengths = this->evaluated_lengths();
BLI_assert(samples_num > 0);
Array<float> samples(samples_num);
samples[0] = 0.0f;
if (samples_num == 1) {
return samples;
}
const float total_length = this->length();
const float sample_length = total_length / (samples_num - (is_cyclic_ ? 0 : 1));
/* Store the length at the previous evaluated point in a variable so it can
* start out at zero (the lengths array doesn't contain 0 for the first point). */
float prev_length = 0.0f;
int i_sample = 1;
for (const int i_evaluated : IndexRange(this->evaluated_edges_num())) {
const float length = lengths[i_evaluated];
/* Add every sample that fits in this evaluated edge. */
while ((sample_length * i_sample) < length && i_sample < samples_num) {
const float factor = (sample_length * i_sample - prev_length) / (length - prev_length);
samples[i_sample] = i_evaluated + factor;
i_sample++;
}
prev_length = length;
}
/* Zero lengths or float inaccuracies can cause invalid values, or simply
* skip some, so set the values that weren't completed in the main loop. */
for (const int i : IndexRange(i_sample, samples_num - i_sample)) {
samples[i] = float(samples_num);
}
if (!is_cyclic_) {
/* In rare cases this can prevent overflow of the stored index. */
samples.last() = lengths.size();
}
return samples;
}
Spline::LookupResult Spline::lookup_data_from_index_factor(const float index_factor) const
{
const int eval_num = this->evaluated_points_num();
if (is_cyclic_) {
if (index_factor < eval_num) {
const int index = std::floor(index_factor);
const int next_index = (index < eval_num - 1) ? index + 1 : 0;
return LookupResult{index, next_index, index_factor - index};
}
return LookupResult{eval_num - 1, 0, 1.0f};
}
if (index_factor < eval_num - 1) {
const int index = std::floor(index_factor);
const int next_index = index + 1;
return LookupResult{index, next_index, index_factor - index};
}
return LookupResult{eval_num - 2, eval_num - 1, 1.0f};
}
void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
{
Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions();
for (const float3 &position : positions) {
minmax_v3v3_v3(min, max, position);
}
}
GVArray Spline::interpolate_to_evaluated(GSpan data) const
{
return this->interpolate_to_evaluated(GVArray::ForSpan(data));
}
void Spline::sample_with_index_factors(const GVArray &src,
Span<float> index_factors,
GMutableSpan dst) const
{
BLI_assert(src.size() == this->evaluated_points_num());
blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
const VArray<T> src_typed = src.typed<T>();
MutableSpan<T> dst_typed = dst.typed<T>();
if (src.size() == 1) {
dst_typed.fill(src_typed[0]);
return;
}
blender::threading::parallel_for(dst_typed.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const LookupResult interp = this->lookup_data_from_index_factor(index_factors[i]);
dst_typed[i] = blender::attribute_math::mix2(interp.factor,
src_typed[interp.evaluated_index],
src_typed[interp.next_evaluated_index]);
}
});
});
}

View File

@ -1,646 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BKE_spline.hh"
using blender::Array;
using blender::float3;
using blender::GVArray;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
using blender::VArray;
void BezierSpline::copy_settings(Spline &dst) const
{
BezierSpline &bezier = static_cast<BezierSpline &>(dst);
bezier.resolution_ = resolution_;
}
void BezierSpline::copy_data(Spline &dst) const
{
BezierSpline &bezier = static_cast<BezierSpline &>(dst);
bezier.positions_ = positions_;
bezier.handle_types_left_ = handle_types_left_;
bezier.handle_positions_left_ = handle_positions_left_;
bezier.handle_types_right_ = handle_types_right_;
bezier.handle_positions_right_ = handle_positions_right_;
bezier.radii_ = radii_;
bezier.tilts_ = tilts_;
}
int BezierSpline::size() const
{
const int size = positions_.size();
BLI_assert(size == handle_types_left_.size());
BLI_assert(size == handle_positions_left_.size());
BLI_assert(size == handle_types_right_.size());
BLI_assert(size == handle_positions_right_.size());
BLI_assert(size == radii_.size());
BLI_assert(size == tilts_.size());
return size;
}
int BezierSpline::resolution() const
{
return resolution_;
}
void BezierSpline::set_resolution(const int value)
{
BLI_assert(value > 0);
resolution_ = value;
this->mark_cache_invalid();
}
void BezierSpline::resize(const int size)
{
handle_types_left_.resize(size);
handle_positions_left_.resize(size);
positions_.resize(size);
handle_types_right_.resize(size);
handle_positions_right_.resize(size);
radii_.resize(size);
tilts_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> BezierSpline::positions()
{
return positions_;
}
Span<float3> BezierSpline::positions() const
{
return positions_;
}
MutableSpan<float> BezierSpline::radii()
{
return radii_;
}
Span<float> BezierSpline::radii() const
{
return radii_;
}
MutableSpan<float> BezierSpline::tilts()
{
return tilts_;
}
Span<float> BezierSpline::tilts() const
{
return tilts_;
}
Span<int8_t> BezierSpline::handle_types_left() const
{
return handle_types_left_;
}
MutableSpan<int8_t> BezierSpline::handle_types_left()
{
return handle_types_left_;
}
Span<float3> BezierSpline::handle_positions_left() const
{
this->ensure_auto_handles();
return handle_positions_left_;
}
MutableSpan<float3> BezierSpline::handle_positions_left(const bool write_only)
{
if (!write_only) {
this->ensure_auto_handles();
}
return handle_positions_left_;
}
Span<int8_t> BezierSpline::handle_types_right() const
{
return handle_types_right_;
}
MutableSpan<int8_t> BezierSpline::handle_types_right()
{
return handle_types_right_;
}
Span<float3> BezierSpline::handle_positions_right() const
{
this->ensure_auto_handles();
return handle_positions_right_;
}
MutableSpan<float3> BezierSpline::handle_positions_right(const bool write_only)
{
if (!write_only) {
this->ensure_auto_handles();
}
return handle_positions_right_;
}
void BezierSpline::reverse_impl()
{
this->handle_positions_left().reverse();
this->handle_positions_right().reverse();
std::swap(this->handle_positions_left_, this->handle_positions_right_);
this->handle_types_left().reverse();
this->handle_types_right().reverse();
std::swap(this->handle_types_left_, this->handle_types_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;
}
if (this->size() == 1) {
auto_handles_dirty_ = false;
return;
}
for (const int i : IndexRange(this->size())) {
using namespace blender;
if (ELEM(BEZIER_HANDLE_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 = math::length(prev_diff);
float next_len = math::length(next_diff);
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 = math::length(dir) * 2.5614f;
if (len != 0.0f) {
if (handle_types_left_[i] == BEZIER_HANDLE_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] == BEZIER_HANDLE_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] == BEZIER_HANDLE_VECTOR) {
const float3 prev = previous_position(positions_, is_cyclic_, i);
handle_positions_left_[i] = math::interpolate(positions_[i], prev, 1.0f / 3.0f);
}
if (handle_types_right_[i] == BEZIER_HANDLE_VECTOR) {
const float3 next = next_position(positions_, is_cyclic_, i);
handle_positions_right_[i] = math::interpolate(positions_[i], next, 1.0f / 3.0f);
}
}
auto_handles_dirty_ = false;
}
void BezierSpline::translate(const blender::float3 &translation)
{
for (float3 &position : this->positions()) {
position += translation;
}
for (float3 &handle_position : this->handle_positions_left()) {
handle_position += translation;
}
for (float3 &handle_position : this->handle_positions_right()) {
handle_position += translation;
}
this->mark_cache_invalid();
}
void BezierSpline::transform(const blender::float4x4 &matrix)
{
for (float3 &position : this->positions()) {
position = matrix * position;
}
for (float3 &handle_position : this->handle_positions_left()) {
handle_position = matrix * handle_position;
}
for (float3 &handle_position : this->handle_positions_right()) {
handle_position = matrix * handle_position;
}
this->mark_cache_invalid();
}
static void set_handle_position(const float3 &position,
const HandleType type,
const HandleType type_other,
const float3 &new_value,
float3 &handle,
float3 &handle_other)
{
using namespace blender::math;
/* Don't bother when the handle positions are calculated automatically anyway. */
if (ELEM(type, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_VECTOR)) {
return;
}
handle = new_value;
if (type_other == BEZIER_HANDLE_ALIGN) {
/* Keep track of the old length of the opposite handle. */
const float length = distance(handle_other, position);
/* Set the other handle to directly opposite from the current handle. */
const float3 dir = normalize(handle - position);
handle_other = position - dir * length;
}
}
void BezierSpline::set_handle_position_right(const int index, const blender::float3 &value)
{
set_handle_position(positions_[index],
static_cast<HandleType>(handle_types_right_[index]),
static_cast<HandleType>(handle_types_left_[index]),
value,
handle_positions_right_[index],
handle_positions_left_[index]);
}
void BezierSpline::set_handle_position_left(const int index, const blender::float3 &value)
{
set_handle_position(positions_[index],
static_cast<HandleType>(handle_types_right_[index]),
static_cast<HandleType>(handle_types_left_[index]),
value,
handle_positions_left_[index],
handle_positions_right_[index]);
}
bool BezierSpline::point_is_sharp(const int index) const
{
return ELEM(handle_types_left_[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) ||
ELEM(handle_types_right_[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE);
}
bool BezierSpline::segment_is_vector(const int index) const
{
/* Two control points are necessary to form a segment, that should be checked by the caller. */
BLI_assert(this->size() > 1);
if (index == this->size() - 1) {
if (is_cyclic_) {
return handle_types_right_.last() == BEZIER_HANDLE_VECTOR &&
handle_types_left_.first() == BEZIER_HANDLE_VECTOR;
}
/* There is actually no segment in this case, but it's nice to avoid
* having a special case for the last segment in calling code. */
return true;
}
return handle_types_right_[index] == BEZIER_HANDLE_VECTOR &&
handle_types_left_[index + 1] == BEZIER_HANDLE_VECTOR;
}
void BezierSpline::mark_cache_invalid()
{
offset_cache_dirty_ = true;
position_cache_dirty_ = true;
mapping_cache_dirty_ = true;
tangent_cache_dirty_ = true;
normal_cache_dirty_ = true;
length_cache_dirty_ = true;
auto_handles_dirty_ = true;
}
int BezierSpline::evaluated_points_num() const
{
BLI_assert(this->size() > 0);
return this->control_point_offsets().last();
}
void BezierSpline::correct_end_tangents() const
{
using namespace blender::math;
if (is_cyclic_) {
return;
}
MutableSpan<float3> tangents(evaluated_tangents_cache_);
if (handle_positions_right_.first() != positions_.first()) {
tangents.first() = normalize(handle_positions_right_.first() - positions_.first());
}
if (handle_positions_left_.last() != positions_.last()) {
tangents.last() = normalize(positions_.last() - handle_positions_left_.last());
}
}
BezierSpline::InsertResult BezierSpline::calculate_segment_insertion(const int index,
const int next_index,
const float parameter)
{
using namespace blender::math;
BLI_assert(parameter <= 1.0f && parameter >= 0.0f);
BLI_assert(ELEM(next_index, 0, index + 1));
const float3 &point_prev = positions_[index];
const float3 &handle_prev = handle_positions_right_[index];
const float3 &handle_next = handle_positions_left_[next_index];
const float3 &point_next = positions_[next_index];
const float3 center_point = interpolate(handle_prev, handle_next, parameter);
BezierSpline::InsertResult result;
result.handle_prev = interpolate(point_prev, handle_prev, parameter);
result.handle_next = interpolate(handle_next, point_next, parameter);
result.left_handle = interpolate(result.handle_prev, center_point, parameter);
result.right_handle = interpolate(center_point, result.handle_next, parameter);
result.position = interpolate(result.left_handle, result.right_handle, parameter);
return result;
}
static void bezier_forward_difference_3d(const float3 &point_0,
const float3 &point_1,
const float3 &point_2,
const float3 &point_3,
MutableSpan<float3> result)
{
BLI_assert(result.size() > 0);
const float inv_len = 1.0f / static_cast<float>(result.size());
const float inv_len_squared = inv_len * inv_len;
const float inv_len_cubed = inv_len_squared * inv_len;
const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
float3 q0 = point_0;
float3 q1 = rt1 + rt2 + rt3;
float3 q2 = 2.0f * rt2 + 6.0f * rt3;
float3 q3 = 6.0f * rt3;
for (const int i : result.index_range()) {
result[i] = q0;
q0 += q1;
q1 += q2;
q2 += q3;
}
}
void BezierSpline::evaluate_segment(const int index,
const int next_index,
MutableSpan<float3> positions) const
{
if (this->segment_is_vector(index)) {
BLI_assert(positions.size() == 1);
positions.first() = positions_[index];
}
else {
bezier_forward_difference_3d(positions_[index],
handle_positions_right_[index],
handle_positions_left_[next_index],
positions_[next_index],
positions);
}
}
Span<int> BezierSpline::control_point_offsets() const
{
if (!offset_cache_dirty_) {
return offset_cache_;
}
std::lock_guard lock{offset_cache_mutex_};
if (!offset_cache_dirty_) {
return offset_cache_;
}
const int size = this->size();
offset_cache_.resize(size + 1);
MutableSpan<int> offsets = offset_cache_;
if (size == 1) {
offsets.first() = 0;
offsets.last() = 1;
}
else {
int offset = 0;
for (const int i : IndexRange(size)) {
offsets[i] = offset;
offset += this->segment_is_vector(i) ? 1 : resolution_;
}
offsets.last() = offset;
}
offset_cache_dirty_ = false;
return offsets;
}
static void calculate_mappings_linear_resolution(Span<int> offsets,
const int size,
const int resolution,
const bool is_cyclic,
MutableSpan<float> r_mappings)
{
const float first_segment_len_inv = 1.0f / offsets[1];
for (const int i : IndexRange(0, offsets[1])) {
r_mappings[i] = i * first_segment_len_inv;
}
const int grain_size = std::max(2048 / resolution, 1);
blender::threading::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
for (const int i_control_point : range) {
const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point];
const float segment_len_inv = 1.0f / segment_len;
for (const int i : IndexRange(segment_len)) {
r_mappings[offsets[i_control_point] + i] = i_control_point + i * segment_len_inv;
}
}
});
if (is_cyclic) {
const int last_segment_len = offsets[size] - offsets[size - 1];
const float last_segment_len_inv = 1.0f / last_segment_len;
for (const int i : IndexRange(last_segment_len)) {
r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv;
}
}
else {
r_mappings.last() = size - 1;
}
}
Span<float> BezierSpline::evaluated_mappings() const
{
if (!mapping_cache_dirty_) {
return evaluated_mapping_cache_;
}
std::lock_guard lock{mapping_cache_mutex_};
if (!mapping_cache_dirty_) {
return evaluated_mapping_cache_;
}
const int num = this->size();
const int eval_num = this->evaluated_points_num();
evaluated_mapping_cache_.resize(eval_num);
MutableSpan<float> mappings = evaluated_mapping_cache_;
if (eval_num == 1) {
mappings.first() = 0.0f;
mapping_cache_dirty_ = false;
return mappings;
}
Span<int> offsets = this->control_point_offsets();
blender::threading::isolate_task([&]() {
/* Isolate the task, since this is function is multi-threaded and holds a lock. */
calculate_mappings_linear_resolution(offsets, num, resolution_, is_cyclic_, mappings);
});
mapping_cache_dirty_ = false;
return mappings;
}
Span<float3> BezierSpline::evaluated_positions() const
{
if (!position_cache_dirty_) {
return evaluated_position_cache_;
}
std::lock_guard lock{position_cache_mutex_};
if (!position_cache_dirty_) {
return evaluated_position_cache_;
}
const int num = this->size();
const int eval_num = this->evaluated_points_num();
evaluated_position_cache_.resize(eval_num);
MutableSpan<float3> positions = evaluated_position_cache_;
if (num == 1) {
/* Use a special case for single point splines to avoid checking in #evaluate_segment. */
BLI_assert(eval_num == 1);
positions.first() = positions_.first();
position_cache_dirty_ = false;
return positions;
}
this->ensure_auto_handles();
Span<int> offsets = this->control_point_offsets();
const int grain_size = std::max(512 / resolution_, 1);
blender::threading::isolate_task([&]() {
/* Isolate the task, since this is function is multi-threaded and holds a lock. */
blender::threading::parallel_for(IndexRange(num - 1), grain_size, [&](IndexRange range) {
for (const int i : range) {
this->evaluate_segment(i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i]));
}
});
});
if (is_cyclic_) {
this->evaluate_segment(
num - 1, 0, positions.slice(offsets[num - 1], offsets[num] - offsets[num - 1]));
}
else {
/* Since evaluating the bezier segment doesn't add the final point,
* it must be added manually in the non-cyclic case. */
positions.last() = positions_.last();
}
position_cache_dirty_ = false;
return positions;
}
BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor(
const float index_factor) const
{
const int num = this->size();
if (is_cyclic_) {
if (index_factor < num) {
const int index = std::floor(index_factor);
const int next_index = (index < num - 1) ? index + 1 : 0;
return InterpolationData{index, next_index, index_factor - index};
}
return InterpolationData{num - 1, 0, 1.0f};
}
if (index_factor < num - 1) {
const int index = std::floor(index_factor);
const int next_index = index + 1;
return InterpolationData{index, next_index, index_factor - index};
}
return InterpolationData{num - 2, num - 1, 1.0f};
}
/* Use a spline argument to avoid adding this to the header. */
template<typename T>
static void interpolate_to_evaluated_impl(const BezierSpline &spline,
const blender::VArray<T> &src,
MutableSpan<T> dst)
{
BLI_assert(src.size() == spline.size());
BLI_assert(dst.size() == spline.evaluated_points_num());
Span<float> mappings = spline.evaluated_mappings();
for (const int i : dst.index_range()) {
BezierSpline::InterpolationData interp = spline.interpolation_data_from_index_factor(
mappings[i]);
const T &value = src[interp.control_point_index];
const T &next_value = src[interp.next_control_point_index];
dst[i] = blender::attribute_math::mix2(interp.factor, value, next_value);
}
}
GVArray BezierSpline::interpolate_to_evaluated(const GVArray &src) const
{
BLI_assert(src.size() == this->size());
if (src.is_single()) {
return src;
}
const int eval_num = this->evaluated_points_num();
if (eval_num == 1) {
return src;
}
GVArray new_varray;
blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) {
Array<T> values(eval_num);
interpolate_to_evaluated_impl<T>(*this, src.typed<T>(), values);
new_varray = VArray<T>::ForContainer(std::move(values));
}
});
return new_varray;
}

View File

@ -1,395 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_span.hh"
#include "BLI_virtual_array.hh"
#include "BKE_attribute_math.hh"
#include "BKE_spline.hh"
using blender::Array;
using blender::float3;
using blender::GVArray;
using blender::IndexRange;
using blender::MutableSpan;
using blender::Span;
using blender::VArray;
void NURBSpline::copy_settings(Spline &dst) const
{
NURBSpline &nurbs = static_cast<NURBSpline &>(dst);
nurbs.knots_mode = knots_mode;
nurbs.resolution_ = resolution_;
nurbs.order_ = order_;
}
void NURBSpline::copy_data(Spline &dst) const
{
NURBSpline &nurbs = static_cast<NURBSpline &>(dst);
nurbs.positions_ = positions_;
nurbs.weights_ = weights_;
nurbs.knots_ = knots_;
nurbs.knots_dirty_ = knots_dirty_;
nurbs.radii_ = radii_;
nurbs.tilts_ = tilts_;
}
int NURBSpline::size() const
{
const int size = positions_.size();
BLI_assert(size == radii_.size());
BLI_assert(size == tilts_.size());
BLI_assert(size == weights_.size());
return size;
}
int NURBSpline::resolution() const
{
return resolution_;
}
void NURBSpline::set_resolution(const int value)
{
BLI_assert(value > 0);
resolution_ = value;
this->mark_cache_invalid();
}
uint8_t NURBSpline::order() const
{
return order_;
}
void NURBSpline::set_order(const uint8_t value)
{
BLI_assert(value >= 2 && value <= 6);
order_ = value;
this->mark_cache_invalid();
}
void NURBSpline::resize(const int size)
{
positions_.resize(size);
radii_.resize(size);
tilts_.resize(size);
weights_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> NURBSpline::positions()
{
return positions_;
}
Span<float3> NURBSpline::positions() const
{
return positions_;
}
MutableSpan<float> NURBSpline::radii()
{
return radii_;
}
Span<float> NURBSpline::radii() const
{
return radii_;
}
MutableSpan<float> NURBSpline::tilts()
{
return tilts_;
}
Span<float> NURBSpline::tilts() const
{
return tilts_;
}
MutableSpan<float> NURBSpline::weights()
{
return weights_;
}
Span<float> NURBSpline::weights() const
{
return weights_;
}
void NURBSpline::reverse_impl()
{
this->weights().reverse();
}
void NURBSpline::mark_cache_invalid()
{
basis_cache_dirty_ = true;
position_cache_dirty_ = true;
tangent_cache_dirty_ = true;
normal_cache_dirty_ = true;
length_cache_dirty_ = true;
}
int NURBSpline::evaluated_points_num() const
{
if (!this->check_valid_num_and_order()) {
return 0;
}
return resolution_ * this->segments_num();
}
void NURBSpline::correct_end_tangents() const
{
}
bool NURBSpline::check_valid_num_and_order() const
{
if (this->size() < order_) {
return false;
}
if (ELEM(this->knots_mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER)) {
if (this->knots_mode == NURBS_KNOT_MODE_BEZIER && this->size() <= order_) {
return false;
}
return (!is_cyclic_ || this->size() % (order_ - 1) == 0);
}
return true;
}
int NURBSpline::knots_num() const
{
const int num = this->size() + order_;
return is_cyclic_ ? num + order_ - 1 : num;
}
void NURBSpline::calculate_knots() const
{
const KnotsMode mode = this->knots_mode;
const int order = order_;
const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
/* Inner knots are always repeated once except on Bezier case. */
const int repeat_inner = is_bezier ? order - 1 : 1;
/* How many times to repeat 0.0 at the beginning of knot. */
const int head = is_end_point ? (order - (is_cyclic_ ? 1 : 0)) :
(is_bezier ? min_ii(2, repeat_inner) : 1);
/* Number of knots replicating widths of the starting knots.
* Covers both Cyclic and EndPoint cases. */
const int tail = is_cyclic_ ? 2 * order - 1 : (is_end_point ? order : 0);
knots_.resize(this->knots_num());
MutableSpan<float> knots = knots_;
int r = head;
float current = 0.0f;
const int offset = is_end_point && is_cyclic_ ? 1 : 0;
if (offset) {
knots[0] = current;
current += 1.0f;
}
for (const int i : IndexRange(offset, knots.size() - offset - tail)) {
knots[i] = current;
r--;
if (r == 0) {
current += 1.0;
r = repeat_inner;
}
}
const int tail_index = knots.size() - tail;
for (const int i : IndexRange(tail)) {
knots[tail_index + i] = current + (knots[i] - knots[0]);
}
}
Span<float> NURBSpline::knots() const
{
if (!knots_dirty_) {
BLI_assert(knots_.size() == this->knots_num());
return knots_;
}
std::lock_guard lock{knots_mutex_};
if (!knots_dirty_) {
BLI_assert(knots_.size() == this->knots_num());
return knots_;
}
this->calculate_knots();
knots_dirty_ = false;
return knots_;
}
static void calculate_basis_for_point(const float parameter,
const int num,
const int degree,
const Span<float> knots,
MutableSpan<float> r_weights,
int &r_start_index)
{
const int order = degree + 1;
int start = 0;
int end = 0;
for (const int i : IndexRange(num + degree)) {
const bool knots_equal = knots[i] == knots[i + 1];
if (knots_equal || parameter < knots[i] || parameter > knots[i + 1]) {
continue;
}
start = std::max(i - degree, 0);
end = i;
break;
}
Array<float, 12> buffer(order * 2, 0.0f);
buffer[end - start] = 1.0f;
for (const int i_order : IndexRange(2, degree)) {
if (end + i_order >= knots.size()) {
end = num + degree - i_order;
}
for (const int i : IndexRange(end - start + 1)) {
const int knot_index = start + i;
float new_basis = 0.0f;
if (buffer[i] != 0.0f) {
new_basis += ((parameter - knots[knot_index]) * buffer[i]) /
(knots[knot_index + i_order - 1] - knots[knot_index]);
}
if (buffer[i + 1] != 0.0f) {
new_basis += ((knots[knot_index + i_order] - parameter) * buffer[i + 1]) /
(knots[knot_index + i_order] - knots[knot_index + 1]);
}
buffer[i] = new_basis;
}
}
buffer.as_mutable_span().drop_front(end - start + 1).fill(0.0f);
r_weights.copy_from(buffer.as_span().take_front(order));
r_start_index = start;
}
const NURBSpline::BasisCache &NURBSpline::calculate_basis_cache() const
{
if (!basis_cache_dirty_) {
return basis_cache_;
}
std::lock_guard lock{basis_cache_mutex_};
if (!basis_cache_dirty_) {
return basis_cache_;
}
const int num = this->size();
const int eval_num = this->evaluated_points_num();
const int order = this->order();
const int degree = order - 1;
basis_cache_.weights.resize(eval_num * order);
basis_cache_.start_indices.resize(eval_num);
if (eval_num == 0) {
return basis_cache_;
}
MutableSpan<float> basis_weights(basis_cache_.weights);
MutableSpan<int> basis_start_indices(basis_cache_.start_indices);
const Span<float> control_weights = this->weights();
const Span<float> knots = this->knots();
const int last_control_point_index = is_cyclic_ ? num + degree : num;
const float start = knots[degree];
const float end = knots[last_control_point_index];
const float step = (end - start) / this->evaluated_edges_num();
for (const int i : IndexRange(eval_num)) {
/* Clamp parameter due to floating point inaccuracy. */
const float parameter = std::clamp(start + step * i, knots[0], knots[num + degree]);
MutableSpan<float> point_weights = basis_weights.slice(i * order, order);
calculate_basis_for_point(
parameter, last_control_point_index, degree, knots, point_weights, basis_start_indices[i]);
for (const int j : point_weights.index_range()) {
const int point_index = (basis_start_indices[i] + j) % num;
point_weights[j] *= control_weights[point_index];
}
}
basis_cache_dirty_ = false;
return basis_cache_;
}
template<typename T>
void interpolate_to_evaluated_impl(const NURBSpline::BasisCache &basis_cache,
const int order,
const blender::VArray<T> &src,
MutableSpan<T> dst)
{
const int num = src.size();
blender::attribute_math::DefaultMixer<T> mixer(dst);
for (const int i : dst.index_range()) {
Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
const int start_index = basis_cache.start_indices[i];
for (const int j : point_weights.index_range()) {
const int point_index = (start_index + j) % num;
mixer.mix_in(i, src[point_index], point_weights[j]);
}
}
mixer.finalize();
}
GVArray NURBSpline::interpolate_to_evaluated(const GVArray &src) const
{
BLI_assert(src.size() == this->size());
if (src.is_single()) {
return src;
}
const BasisCache &basis_cache = this->calculate_basis_cache();
GVArray new_varray;
blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) {
Array<T> values(this->evaluated_points_num());
interpolate_to_evaluated_impl<T>(basis_cache, this->order(), src.typed<T>(), values);
new_varray = VArray<T>::ForContainer(std::move(values));
}
});
return new_varray;
}
Span<float3> NURBSpline::evaluated_positions() const
{
if (!position_cache_dirty_) {
return evaluated_position_cache_;
}
std::lock_guard lock{position_cache_mutex_};
if (!position_cache_dirty_) {
return evaluated_position_cache_;
}
const int eval_num = this->evaluated_points_num();
evaluated_position_cache_.resize(eval_num);
/* TODO: Avoid copying the evaluated data from the temporary array. */
VArray<float3> evaluated = Spline::interpolate_to_evaluated(positions_.as_span());
evaluated.materialize(evaluated_position_cache_);
position_cache_dirty_ = false;
return evaluated_position_cache_;
}

View File

@ -1,97 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_span.hh"
#include "BLI_virtual_array.hh"
#include "BKE_spline.hh"
using blender::float3;
using blender::GVArray;
using blender::MutableSpan;
using blender::Span;
void PolySpline::copy_settings(Spline &UNUSED(dst)) const
{
/* Poly splines have no settings not covered by the base class. */
}
void PolySpline::copy_data(Spline &dst) const
{
PolySpline &poly = static_cast<PolySpline &>(dst);
poly.positions_ = positions_;
poly.radii_ = radii_;
poly.tilts_ = tilts_;
}
int PolySpline::size() const
{
const int size = positions_.size();
BLI_assert(size == radii_.size());
BLI_assert(size == tilts_.size());
return size;
}
void PolySpline::resize(const int size)
{
positions_.resize(size);
radii_.resize(size);
tilts_.resize(size);
this->mark_cache_invalid();
attributes.reallocate(size);
}
MutableSpan<float3> PolySpline::positions()
{
return positions_;
}
Span<float3> PolySpline::positions() const
{
return positions_;
}
MutableSpan<float> PolySpline::radii()
{
return radii_;
}
Span<float> PolySpline::radii() const
{
return radii_;
}
MutableSpan<float> PolySpline::tilts()
{
return tilts_;
}
Span<float> PolySpline::tilts() const
{
return tilts_;
}
void PolySpline::reverse_impl()
{
}
void PolySpline::mark_cache_invalid()
{
tangent_cache_dirty_ = true;
normal_cache_dirty_ = true;
length_cache_dirty_ = true;
}
int PolySpline::evaluated_points_num() const
{
return this->size();
}
void PolySpline::correct_end_tangents() const
{
}
Span<float3> PolySpline::evaluated_positions() const
{
return this->positions();
}
GVArray PolySpline::interpolate_to_evaluated(const GVArray &src) const
{
BLI_assert(src.size() == this->size());
return src;
}

View File

@ -7,7 +7,6 @@
#include "DNA_pointcloud_types.h"
#include "BKE_pointcloud.h"
#include "BKE_spline.hh"
#include "GEO_resample_curves.hh"

View File

@ -1,7 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_spline.hh"
#include "BLI_task.hh"
#include "UI_interface.h"