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:
parent
7536abbe16
commit
ecf3435362
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
@ -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 ¤t_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]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BKE_pointcloud.h"
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue