Geometry Nodes: Port the trim curve node to the new data-block
The trim functionality is implemented in the geometry module, and generalized a bit to be potentially useful for bisecting in the future. The implementation is based on a helper type called `IndexRangeCyclic` which allows iteration over all control points between two points on a curve. Catmull Rom curves are now supported-- trimmed without resampling first. However, maintaining the exact shape is not possible. NURBS splines are still converted to polylines using the evaluated curve concept. Performance is equivalent or faster then a 3.1 build with regards to node timings. Compared to 3.3 and 3.2, it's easy to observe test cases where the node is at least 3 or 4 times faster. Differential Revision: https://developer.blender.org/D14481
This commit is contained in:
@@ -710,6 +710,19 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
|||||||
eAttrDomainMask domain_mask,
|
eAttrDomainMask domain_mask,
|
||||||
const Set<std::string> &skip = {});
|
const Set<std::string> &skip = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy attributes for the domain based on the elementwise mask.
|
||||||
|
*
|
||||||
|
* \param mask_indices: Indexed elements to copy from the source data-block.
|
||||||
|
* \param domain: Attribute domain to transfer.
|
||||||
|
* \param skip: Named attributes to ignore/skip.
|
||||||
|
*/
|
||||||
|
void copy_attribute_domain(AttributeAccessor src_attributes,
|
||||||
|
MutableAttributeAccessor dst_attributes,
|
||||||
|
IndexMask selection,
|
||||||
|
eAttrDomain domain,
|
||||||
|
const Set<std::string> &skip = {});
|
||||||
|
|
||||||
bool allow_procedural_attribute_access(StringRef attribute_name);
|
bool allow_procedural_attribute_access(StringRef attribute_name);
|
||||||
extern const char *no_procedural_access_message;
|
extern const char *no_procedural_access_message;
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "BLI_virtual_array.hh"
|
#include "BLI_virtual_array.hh"
|
||||||
|
|
||||||
#include "BKE_attribute.hh"
|
#include "BKE_attribute.hh"
|
||||||
|
#include "BKE_attribute_math.hh"
|
||||||
|
|
||||||
namespace blender::bke {
|
namespace blender::bke {
|
||||||
|
|
||||||
@@ -161,6 +162,11 @@ class CurvesGeometry : public ::CurvesGeometry {
|
|||||||
IndexRange points_range() const;
|
IndexRange points_range() const;
|
||||||
IndexRange curves_range() const;
|
IndexRange curves_range() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of control points in the indexed curve.
|
||||||
|
*/
|
||||||
|
int points_num_for_curve(const int index) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The index of the first point in every curve. The size of this span is one larger than the
|
* The index of the first point in every curve. The size of this span is one larger than the
|
||||||
* number of curves. Consider using #points_for_curve rather than using the offsets directly.
|
* number of curves. Consider using #points_for_curve rather than using the offsets directly.
|
||||||
@@ -531,6 +537,16 @@ bool segment_is_vector(Span<int8_t> handle_types_left,
|
|||||||
Span<int8_t> handle_types_right,
|
Span<int8_t> handle_types_right,
|
||||||
int segment_index);
|
int segment_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the Bezier curve contains polygonal segments of HandleType::BEZIER_HANDLE_VECTOR.
|
||||||
|
*
|
||||||
|
* \param num_curve_points: Number of points in the curve.
|
||||||
|
* \param evaluated_size: Number of evaluated points in the curve.
|
||||||
|
* \param cyclic: If curve is cyclic.
|
||||||
|
* \param resolution: Curve resolution.
|
||||||
|
*/
|
||||||
|
bool has_vector_handles(int num_curve_points, int64_t evaluated_size, bool cyclic, int resolution);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the curve's last cyclic segment has a vector type.
|
* Return true if the curve's last cyclic segment has a vector type.
|
||||||
* This only makes a difference in the shape of cyclic curves.
|
* This only makes a difference in the shape of cyclic curves.
|
||||||
@@ -693,6 +709,36 @@ void interpolate_to_evaluated(const GSpan src,
|
|||||||
const Span<int> evaluated_offsets,
|
const Span<int> evaluated_offsets,
|
||||||
GMutableSpan dst);
|
GMutableSpan dst);
|
||||||
|
|
||||||
|
void calculate_basis(const float parameter, float r_weights[4]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate the control point values for the given parameter on the piecewise segment.
|
||||||
|
* \param a: Value associated with the first control point influencing the segment.
|
||||||
|
* \param d: Value associated with the fourth control point.
|
||||||
|
* \param parameter: Parameter in range [0, 1] to compute the interpolation for.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
|
||||||
|
{
|
||||||
|
float n[4];
|
||||||
|
calculate_basis(parameter, n);
|
||||||
|
/* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify
|
||||||
|
* supporting more types. */
|
||||||
|
if constexpr (!is_same_any_v<T, float, float2, float3, float4, int8_t, int, int64_t>) {
|
||||||
|
T return_value;
|
||||||
|
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
||||||
|
mixer.mix_in(0, a, n[0] * 0.5f);
|
||||||
|
mixer.mix_in(0, b, n[1] * 0.5f);
|
||||||
|
mixer.mix_in(0, c, n[2] * 0.5f);
|
||||||
|
mixer.mix_in(0, d, n[3] * 0.5f);
|
||||||
|
mixer.finalize();
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0.5f * (a * n[0] + b * n[1] + c * n[2] + d * n[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace catmull_rom
|
} // namespace catmull_rom
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
@@ -807,6 +853,16 @@ inline IndexRange CurvesGeometry::curves_range() const
|
|||||||
return IndexRange(this->curves_num());
|
return IndexRange(this->curves_num());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int CurvesGeometry::points_num_for_curve(const int index) const
|
||||||
|
{
|
||||||
|
BLI_assert(this->curve_num > 0);
|
||||||
|
BLI_assert(this->curve_num > index);
|
||||||
|
BLI_assert(this->curve_offsets != nullptr);
|
||||||
|
const int offset = this->curve_offsets[index];
|
||||||
|
const int offset_next = this->curve_offsets[index + 1];
|
||||||
|
return offset_next - offset;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool CurvesGeometry::is_single_type(const CurveType type) const
|
inline bool CurvesGeometry::is_single_type(const CurveType type) const
|
||||||
{
|
{
|
||||||
return this->curve_type_counts()[type] == this->curves_num();
|
return this->curve_type_counts()[type] == this->curves_num();
|
||||||
@@ -833,6 +889,7 @@ inline IndexRange CurvesGeometry::points_for_curve(const int index) const
|
|||||||
{
|
{
|
||||||
/* Offsets are not allocated when there are no curves. */
|
/* Offsets are not allocated when there are no curves. */
|
||||||
BLI_assert(this->curve_num > 0);
|
BLI_assert(this->curve_num > 0);
|
||||||
|
BLI_assert(this->curve_num > index);
|
||||||
BLI_assert(this->curve_offsets != nullptr);
|
BLI_assert(this->curve_offsets != nullptr);
|
||||||
const int offset = this->curve_offsets[index];
|
const int offset = this->curve_offsets[index];
|
||||||
const int offset_next = this->curve_offsets[index + 1];
|
const int offset_next = this->curve_offsets[index + 1];
|
||||||
@@ -905,11 +962,13 @@ inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_in
|
|||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
|
namespace curves {
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/** \name Bezier Inline Methods
|
/** \name Bezier Inline Methods
|
||||||
* \{ */
|
* \{ */
|
||||||
|
|
||||||
namespace curves::bezier {
|
namespace bezier {
|
||||||
|
|
||||||
inline bool point_is_sharp(const Span<int8_t> handle_types_left,
|
inline bool point_is_sharp(const Span<int8_t> handle_types_left,
|
||||||
const Span<int8_t> handle_types_right,
|
const Span<int8_t> handle_types_right,
|
||||||
@@ -929,14 +988,24 @@ inline bool segment_is_vector(const int8_t left, const int8_t right)
|
|||||||
return segment_is_vector(HandleType(left), HandleType(right));
|
return segment_is_vector(HandleType(left), HandleType(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool has_vector_handles(const int num_curve_points,
|
||||||
|
const int64_t evaluated_size,
|
||||||
|
const bool cyclic,
|
||||||
|
const int resolution)
|
||||||
|
{
|
||||||
|
return evaluated_size - !cyclic != (int64_t)segments_num(num_curve_points, cyclic) * resolution;
|
||||||
|
}
|
||||||
|
|
||||||
inline float3 calculate_vector_handle(const float3 &point, const float3 &next_point)
|
inline float3 calculate_vector_handle(const float3 &point, const float3 &next_point)
|
||||||
{
|
{
|
||||||
return math::interpolate(point, next_point, 1.0f / 3.0f);
|
return math::interpolate(point, next_point, 1.0f / 3.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace bezier
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
} // namespace curves::bezier
|
} // namespace curves
|
||||||
|
|
||||||
struct CurvesSurfaceTransforms {
|
struct CurvesSurfaceTransforms {
|
||||||
float4x4 curves_to_world;
|
float4x4 curves_to_world;
|
||||||
|
|||||||
@@ -11,9 +11,301 @@
|
|||||||
|
|
||||||
#include "BLI_function_ref.hh"
|
#include "BLI_function_ref.hh"
|
||||||
#include "BLI_generic_pointer.hh"
|
#include "BLI_generic_pointer.hh"
|
||||||
|
#include "BLI_index_range.hh"
|
||||||
|
|
||||||
namespace blender::bke::curves {
|
namespace blender::bke::curves {
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------
|
||||||
|
* Utility structs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a piecewise segment on a spline curve.
|
||||||
|
*/
|
||||||
|
struct CurveSegment {
|
||||||
|
/**
|
||||||
|
* Index of the previous control/evaluated point on the curve. First point on the segment.
|
||||||
|
*/
|
||||||
|
int index;
|
||||||
|
/**
|
||||||
|
* Index of the next control/evaluated point on the curve. Last point on the curve segment.
|
||||||
|
* Should be 0 for looped segments.
|
||||||
|
*/
|
||||||
|
int next_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a point on a piecewise curve (spline).
|
||||||
|
*
|
||||||
|
* Tracks indices of the neighbouring control/evaluated point pair associated with the segment
|
||||||
|
* in which the point resides. Referenced point within the segment is defined by a
|
||||||
|
* normalized parameter in the range [0, 1].
|
||||||
|
*/
|
||||||
|
struct CurvePoint : public CurveSegment {
|
||||||
|
/**
|
||||||
|
* Normalized parameter in the range [0, 1] defining the point on the piecewise segment.
|
||||||
|
* Note that the curve point representation is not unique at segment endpoints.
|
||||||
|
*/
|
||||||
|
float parameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the parameter is an integer and references a control/evaluated point.
|
||||||
|
*/
|
||||||
|
inline bool is_controlpoint() const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare if the points are equal.
|
||||||
|
*/
|
||||||
|
inline bool operator==(const CurvePoint &other) const;
|
||||||
|
inline bool operator!=(const CurvePoint &other) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare if 'this' point comes before 'other'. Loop segment for cyclical curves counts
|
||||||
|
* as the first (least) segment.
|
||||||
|
*/
|
||||||
|
inline bool operator<(const CurvePoint &other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cyclical index range. Iterates the interval [start, end).
|
||||||
|
*/
|
||||||
|
class IndexRangeCyclic {
|
||||||
|
/* Index to the start and end of the iterated range.
|
||||||
|
*/
|
||||||
|
int64_t start_ = 0;
|
||||||
|
int64_t end_ = 0;
|
||||||
|
/* Index for the start and end of the entire iterable range which contains the iterated range
|
||||||
|
* (e.g. the point range for an indiviudal spline/curve within the entire Curves point domain).
|
||||||
|
*/
|
||||||
|
int64_t range_start_ = 0;
|
||||||
|
int64_t range_end_ = 0;
|
||||||
|
/* Number of times the range end is passed when the range is iterated.
|
||||||
|
*/
|
||||||
|
int64_t cycles_ = 0;
|
||||||
|
|
||||||
|
constexpr IndexRangeCyclic(int64_t begin,
|
||||||
|
int64_t end,
|
||||||
|
int64_t iterable_range_start,
|
||||||
|
int64_t iterable_range_end,
|
||||||
|
int64_t cycles)
|
||||||
|
: start_(begin),
|
||||||
|
end_(end),
|
||||||
|
range_start_(iterable_range_start),
|
||||||
|
range_end_(iterable_range_end),
|
||||||
|
cycles_(cycles)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr IndexRangeCyclic() = default;
|
||||||
|
~IndexRangeCyclic() = default;
|
||||||
|
|
||||||
|
constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range, int64_t cycles)
|
||||||
|
: start_(start),
|
||||||
|
end_(end),
|
||||||
|
range_start_(iterable_range.first()),
|
||||||
|
range_end_(iterable_range.one_after_last()),
|
||||||
|
cycles_(cycles)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an iterator over the cyclical interval [start_index, end_index).
|
||||||
|
*/
|
||||||
|
constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range)
|
||||||
|
: start_(start),
|
||||||
|
end_(end == iterable_range.one_after_last() ? iterable_range.first() : end),
|
||||||
|
range_start_(iterable_range.first()),
|
||||||
|
range_end_(iterable_range.one_after_last()),
|
||||||
|
cycles_(end < start)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the range by adding the given number of indices to the beginning of the range.
|
||||||
|
*/
|
||||||
|
constexpr IndexRangeCyclic push_forward(int n)
|
||||||
|
{
|
||||||
|
BLI_assert(n >= 0);
|
||||||
|
int64_t nstart = start_ - n;
|
||||||
|
int64_t cycles = cycles_;
|
||||||
|
if (nstart < range_start_) {
|
||||||
|
|
||||||
|
cycles += (int64_t)(n / (range_end_ - range_start_)) + (end_ < nstart) - (end_ < start_);
|
||||||
|
}
|
||||||
|
return {nstart, end_, range_start_, range_end_, cycles};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Increment the range by adding the given number of indices to the end of the range.
|
||||||
|
*/
|
||||||
|
constexpr IndexRangeCyclic push_backward(int n)
|
||||||
|
{
|
||||||
|
BLI_assert(n >= 0);
|
||||||
|
int64_t new_end = end_ + n;
|
||||||
|
int64_t cycles = cycles_;
|
||||||
|
if (range_end_ <= new_end) {
|
||||||
|
cycles += (int64_t)(n / (range_end_ - range_start_)) + (new_end < start_) - (end_ < start_);
|
||||||
|
}
|
||||||
|
return {start_, new_end, range_start_, range_end_, cycles};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index range for the curve buffer.
|
||||||
|
*/
|
||||||
|
constexpr IndexRange curve_range() const
|
||||||
|
{
|
||||||
|
return IndexRange(range_start_, total_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range between the first element up to the end of the range.
|
||||||
|
*/
|
||||||
|
constexpr IndexRange range_before_loop() const
|
||||||
|
{
|
||||||
|
return IndexRange(start_, size_before_loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range between the first element in the iterable range up to the last element in the range.
|
||||||
|
*/
|
||||||
|
constexpr IndexRange range_after_loop() const
|
||||||
|
{
|
||||||
|
return IndexRange(range_start_, size_after_loop());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the entire iterable range.
|
||||||
|
*/
|
||||||
|
constexpr int64_t total_size() const
|
||||||
|
{
|
||||||
|
return range_end_ - range_start_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of elements between the first element in the range up to the last element in the curve.
|
||||||
|
*/
|
||||||
|
constexpr int64_t size_before_loop() const
|
||||||
|
{
|
||||||
|
return range_end_ - start_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of elements between the first element in the iterable range up to the last element in
|
||||||
|
* the range.
|
||||||
|
*/
|
||||||
|
constexpr int64_t size_after_loop() const
|
||||||
|
{
|
||||||
|
return end_ - range_start_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of elements iterated by the cyclical index range.
|
||||||
|
*/
|
||||||
|
constexpr int64_t size() const
|
||||||
|
{
|
||||||
|
if (cycles_ > 0) {
|
||||||
|
return size_before_loop() + end_ + (cycles_ - 1) * (range_end_ - range_start_);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return end_ - start_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of times the iterator will cycle before ending.
|
||||||
|
*/
|
||||||
|
constexpr int64_t cycles() const
|
||||||
|
{
|
||||||
|
return cycles_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int64_t first() const
|
||||||
|
{
|
||||||
|
return start_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int64_t one_after_last() const
|
||||||
|
{
|
||||||
|
return end_;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CyclicIterator; /* Forward declaration */
|
||||||
|
|
||||||
|
constexpr CyclicIterator begin() const
|
||||||
|
{
|
||||||
|
return CyclicIterator(range_start_, range_end_, start_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr CyclicIterator end() const
|
||||||
|
{
|
||||||
|
return CyclicIterator(range_start_, range_end_, end_, cycles_);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CyclicIterator {
|
||||||
|
int64_t index_, begin_, end_, cycles_;
|
||||||
|
|
||||||
|
constexpr CyclicIterator(int64_t range_begin, int64_t range_end, int64_t index, int64_t cycles)
|
||||||
|
: index_(index), begin_(range_begin), end_(range_end), cycles_(cycles)
|
||||||
|
{
|
||||||
|
BLI_assert(range_begin <= index && index <= range_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr CyclicIterator(const CyclicIterator ©)
|
||||||
|
: index_(copy.index_), begin_(copy.begin_), end_(copy.end_), cycles_(copy.cycles_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~CyclicIterator() = default;
|
||||||
|
|
||||||
|
constexpr CyclicIterator &operator=(const CyclicIterator ©)
|
||||||
|
{
|
||||||
|
if (this == ©) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
index_ = copy.index_;
|
||||||
|
begin_ = copy.begin_;
|
||||||
|
end_ = copy.end_;
|
||||||
|
cycles_ = copy.cycles_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr CyclicIterator &operator++()
|
||||||
|
{
|
||||||
|
index_++;
|
||||||
|
if (index_ == end_) {
|
||||||
|
index_ = begin_;
|
||||||
|
cycles_++;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void increment(int64_t n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
++*this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const int64_t &operator*() const
|
||||||
|
{
|
||||||
|
return index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const CyclicIterator &other) const
|
||||||
|
{
|
||||||
|
return index_ == other.index_ && cycles_ == other.cycles_;
|
||||||
|
}
|
||||||
|
constexpr bool operator!=(const CyclicIterator &other) const
|
||||||
|
{
|
||||||
|
return !this->operator==(other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------
|
||||||
|
* Utility functions.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy the provided point attribute values between all curves in the #curve_ranges index
|
* Copy the provided point attribute values between all curves in the #curve_ranges index
|
||||||
* ranges, assuming that all curves have the same number of control points in #src_curves
|
* ranges, assuming that all curves have the same number of control points in #src_curves
|
||||||
@@ -88,4 +380,40 @@ void foreach_curve_by_type(const VArray<int8_t> &types,
|
|||||||
FunctionRef<void(IndexMask)> bezier_fn,
|
FunctionRef<void(IndexMask)> bezier_fn,
|
||||||
FunctionRef<void(IndexMask)> nurbs_fn);
|
FunctionRef<void(IndexMask)> nurbs_fn);
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/** \name #CurvePoint Inline Methods
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
inline bool CurvePoint::is_controlpoint() const
|
||||||
|
{
|
||||||
|
return parameter == 0.0 || parameter == 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool CurvePoint::operator==(const CurvePoint &other) const
|
||||||
|
{
|
||||||
|
return (parameter == other.parameter && index == other.index) ||
|
||||||
|
(parameter == 1.0 && other.parameter == 0.0 && next_index == other.index) ||
|
||||||
|
(parameter == 0.0 && other.parameter == 1.0 && index == other.next_index);
|
||||||
|
}
|
||||||
|
inline bool CurvePoint::operator!=(const CurvePoint &other) const
|
||||||
|
{
|
||||||
|
return !this->operator==(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool CurvePoint::operator<(const CurvePoint &other) const
|
||||||
|
{
|
||||||
|
if (index == other.index) {
|
||||||
|
return parameter < other.parameter;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Use next index for cyclic comparison due to loop segment < first segment. */
|
||||||
|
return next_index < other.next_index &&
|
||||||
|
!(next_index == other.index && parameter == 1.0 && other.parameter == 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
} // namespace blender::bke::curves
|
} // namespace blender::bke::curves
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "DNA_meshdata_types.h"
|
#include "DNA_meshdata_types.h"
|
||||||
#include "DNA_pointcloud_types.h"
|
#include "DNA_pointcloud_types.h"
|
||||||
|
|
||||||
|
#include "BLI_array_utils.hh"
|
||||||
#include "BLI_color.hh"
|
#include "BLI_color.hh"
|
||||||
#include "BLI_math_vec_types.hh"
|
#include "BLI_math_vec_types.hh"
|
||||||
#include "BLI_span.hh"
|
#include "BLI_span.hh"
|
||||||
@@ -974,6 +975,37 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
|||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void copy_attribute_domain(const AttributeAccessor src_attributes,
|
||||||
|
MutableAttributeAccessor dst_attributes,
|
||||||
|
const IndexMask selection,
|
||||||
|
const eAttrDomain domain,
|
||||||
|
const Set<std::string> &skip)
|
||||||
|
{
|
||||||
|
src_attributes.for_all(
|
||||||
|
[&](const bke::AttributeIDRef &id, const bke::AttributeMetaData &meta_data) {
|
||||||
|
if (meta_data.domain != domain) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (id.is_named() && skip.contains(id.name())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!id.should_be_kept()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GVArray src = src_attributes.lookup(id, meta_data.domain);
|
||||||
|
BLI_assert(src);
|
||||||
|
|
||||||
|
/* Copy attribute. */
|
||||||
|
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||||
|
id, domain, meta_data.data_type);
|
||||||
|
array_utils::copy(src, selection, dst.span);
|
||||||
|
dst.finish();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::bke
|
} // namespace blender::bke
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|||||||
@@ -17,16 +17,14 @@ int calculate_evaluated_num(const int points_num, const bool cyclic, const int r
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
||||||
template<typename T>
|
void calculate_basis(const float parameter, float r_weights[4])
|
||||||
static T calculate_basis(const T &a, const T &b, const T &c, const T &d, const float parameter)
|
|
||||||
{
|
{
|
||||||
const float t = parameter;
|
const float t = parameter;
|
||||||
const float s = 1.0f - parameter;
|
const float s = 1.0f - parameter;
|
||||||
const float n0 = -t * s * s;
|
r_weights[0] = -t * s * s;
|
||||||
const float n1 = 2.0f + t * t * (3.0f * t - 5.0f);
|
r_weights[1] = 2.0f + t * t * (3.0f * t - 5.0f);
|
||||||
const float n2 = 2.0f + s * s * (3.0f * s - 5.0f);
|
r_weights[2] = 2.0f + s * s * (3.0f * s - 5.0f);
|
||||||
const float n3 = -s * t * t;
|
r_weights[3] = -s * t * t;
|
||||||
return 0.5f * (a * n0 + b * n1 + c * n2 + d * n3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -35,7 +33,7 @@ static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, Mut
|
|||||||
const float step = 1.0f / dst.size();
|
const float step = 1.0f / dst.size();
|
||||||
dst.first() = b;
|
dst.first() = b;
|
||||||
for (const int i : dst.index_range().drop_front(1)) {
|
for (const int i : dst.index_range().drop_front(1)) {
|
||||||
dst[i] = calculate_basis<T>(a, b, c, d, i * step);
|
dst[i] = interpolate<T>(a, b, c, d, i * step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
source/blender/blenlib/BLI_array_utils.hh
Normal file
35
source/blender/blenlib/BLI_array_utils.hh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BLI_generic_span.hh"
|
||||||
|
#include "BLI_generic_virtual_array.hh"
|
||||||
|
#include "BLI_index_mask.hh"
|
||||||
|
#include "BLI_task.hh"
|
||||||
|
|
||||||
|
namespace blender::array_utils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the destination span by copying masked values from the src array. Threaded based on
|
||||||
|
* grainsize.
|
||||||
|
*/
|
||||||
|
void copy(const GVArray &src, IndexMask selection, GMutableSpan dst, int64_t grain_size = 4096);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the destination span by copying values from the src array. Threaded based on
|
||||||
|
* grainsize.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
inline void copy(const Span<T> src,
|
||||||
|
const IndexMask selection,
|
||||||
|
MutableSpan<T> dst,
|
||||||
|
const int64_t grain_size = 4096)
|
||||||
|
{
|
||||||
|
threading::parallel_for(selection.index_range(), grain_size, [&](const IndexRange range) {
|
||||||
|
for (const int64_t index : selection.slice(range)) {
|
||||||
|
dst[index] = src[index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::array_utils
|
||||||
@@ -47,6 +47,7 @@ set(SRC
|
|||||||
intern/array_store.c
|
intern/array_store.c
|
||||||
intern/array_store_utils.c
|
intern/array_store_utils.c
|
||||||
intern/array_utils.c
|
intern/array_utils.c
|
||||||
|
intern/array_utils.cc
|
||||||
intern/astar.c
|
intern/astar.c
|
||||||
intern/bitmap.c
|
intern/bitmap.c
|
||||||
intern/bitmap_draw_2d.c
|
intern/bitmap_draw_2d.c
|
||||||
@@ -164,6 +165,7 @@ set(SRC
|
|||||||
BLI_array_store.h
|
BLI_array_store.h
|
||||||
BLI_array_store_utils.h
|
BLI_array_store_utils.h
|
||||||
BLI_array_utils.h
|
BLI_array_utils.h
|
||||||
|
BLI_array_utils.hh
|
||||||
BLI_asan.h
|
BLI_asan.h
|
||||||
BLI_assert.h
|
BLI_assert.h
|
||||||
BLI_astar.h
|
BLI_astar.h
|
||||||
|
|||||||
18
source/blender/blenlib/intern/array_utils.cc
Normal file
18
source/blender/blenlib/intern/array_utils.cc
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "BLI_array_utils.hh"
|
||||||
|
#include "BLI_task.hh"
|
||||||
|
|
||||||
|
namespace blender::array_utils {
|
||||||
|
|
||||||
|
void copy(const GVArray &src,
|
||||||
|
const IndexMask selection,
|
||||||
|
GMutableSpan dst,
|
||||||
|
const int64_t grain_size)
|
||||||
|
{
|
||||||
|
BLI_assert(src.type() == dst.type());
|
||||||
|
BLI_assert(src.size() == dst.size());
|
||||||
|
threading::parallel_for(selection.index_range(), grain_size, [&](const IndexRange range) {
|
||||||
|
src.materialize_to_uninitialized(selection.slice(range), dst.data());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::array_utils
|
||||||
@@ -27,6 +27,7 @@ set(SRC
|
|||||||
intern/reverse_uv_sampler.cc
|
intern/reverse_uv_sampler.cc
|
||||||
intern/set_curve_type.cc
|
intern/set_curve_type.cc
|
||||||
intern/subdivide_curves.cc
|
intern/subdivide_curves.cc
|
||||||
|
intern/trim_curves.cc
|
||||||
intern/uv_parametrizer.cc
|
intern/uv_parametrizer.cc
|
||||||
|
|
||||||
GEO_add_curves_on_mesh.hh
|
GEO_add_curves_on_mesh.hh
|
||||||
@@ -41,6 +42,7 @@ set(SRC
|
|||||||
GEO_reverse_uv_sampler.hh
|
GEO_reverse_uv_sampler.hh
|
||||||
GEO_set_curve_type.hh
|
GEO_set_curve_type.hh
|
||||||
GEO_subdivide_curves.hh
|
GEO_subdivide_curves.hh
|
||||||
|
GEO_trim_curves.hh
|
||||||
GEO_uv_parametrizer.h
|
GEO_uv_parametrizer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
32
source/blender/geometry/GEO_trim_curves.hh
Normal file
32
source/blender/geometry/GEO_trim_curves.hh
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "BLI_span.hh"
|
||||||
|
|
||||||
|
#include "BKE_curves.hh"
|
||||||
|
#include "BKE_curves_utils.hh"
|
||||||
|
#include "BKE_geometry_set.hh"
|
||||||
|
|
||||||
|
namespace blender::geometry {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new Curves instance by trimming the input curves. Copying the selected splines
|
||||||
|
* between the start and end points.
|
||||||
|
*/
|
||||||
|
bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves,
|
||||||
|
IndexMask selection,
|
||||||
|
Span<bke::curves::CurvePoint> start_points,
|
||||||
|
Span<bke::curves::CurvePoint> end_points);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the point(s) and piecewise segment corresponding to the given distance along the length of
|
||||||
|
* the curve. Returns points on the evaluated curve for Catmull-Rom and NURBS splines.
|
||||||
|
*
|
||||||
|
* \param curves: Curve geometry to sample.
|
||||||
|
* \param lengths: Distance along the curve on form [0.0, length] to determine the point for.
|
||||||
|
* \param curve_indices: Curve index to lookup for each 'length', negative index are set to 0.
|
||||||
|
* \param is_normalized: If true, 'lengths' are normalized to the interval [0.0, 1.0].
|
||||||
|
*/
|
||||||
|
Array<bke::curves::CurvePoint, 12> lookup_curve_points(const bke::CurvesGeometry &curves,
|
||||||
|
Span<float> lengths,
|
||||||
|
Span<int64_t> curve_indices,
|
||||||
|
bool is_normalized);
|
||||||
|
|
||||||
|
} // namespace blender::geometry
|
||||||
1285
source/blender/geometry/intern/trim_curves.cc
Normal file
1285
source/blender/geometry/intern/trim_curves.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,12 +9,12 @@
|
|||||||
|
|
||||||
#include "NOD_socket_search_link.hh"
|
#include "NOD_socket_search_link.hh"
|
||||||
|
|
||||||
|
#include "GEO_trim_curves.hh"
|
||||||
|
|
||||||
#include "node_geometry_util.hh"
|
#include "node_geometry_util.hh"
|
||||||
|
|
||||||
namespace blender::nodes::node_geo_curve_trim_cc {
|
namespace blender::nodes::node_geo_curve_trim_cc {
|
||||||
|
|
||||||
using blender::attribute_math::mix2;
|
|
||||||
|
|
||||||
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
|
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
|
||||||
|
|
||||||
static void node_declare(NodeDeclarationBuilder &b)
|
static void node_declare(NodeDeclarationBuilder &b)
|
||||||
@@ -108,394 +108,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrimLocation {
|
|
||||||
/* Control point index at the start side of the trim location. */
|
|
||||||
int left_index;
|
|
||||||
/* Control point index at the end of the trim location's segment. */
|
|
||||||
int right_index;
|
|
||||||
/* The factor between the left and right indices. */
|
|
||||||
float factor;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
static void shift_slice_to_start(MutableSpan<T> data, const int start_index, const int num)
|
|
||||||
{
|
|
||||||
BLI_assert(start_index + num - 1 <= data.size());
|
|
||||||
memmove(data.data(), &data[start_index], sizeof(T) * num);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Shift slice to start of span and modifies start and end data. */
|
|
||||||
template<typename T>
|
|
||||||
static void linear_trim_data(const TrimLocation &start,
|
|
||||||
const TrimLocation &end,
|
|
||||||
MutableSpan<T> data)
|
|
||||||
{
|
|
||||||
const int num = end.right_index - start.left_index + 1;
|
|
||||||
|
|
||||||
if (start.left_index > 0) {
|
|
||||||
shift_slice_to_start<T>(data, start.left_index, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
const T start_data = mix2<T>(start.factor, data.first(), data[1]);
|
|
||||||
const T end_data = mix2<T>(end.factor, data[num - 2], data[num - 1]);
|
|
||||||
|
|
||||||
data.first() = start_data;
|
|
||||||
data[num - 1] = end_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identical operation as #linear_trim_data, but copy data to a new #MutableSpan rather than
|
|
||||||
* modifying the original data.
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
static void linear_trim_to_output_data(const TrimLocation &start,
|
|
||||||
const TrimLocation &end,
|
|
||||||
Span<T> src,
|
|
||||||
MutableSpan<T> dst)
|
|
||||||
{
|
|
||||||
const int num = end.right_index - start.left_index + 1;
|
|
||||||
|
|
||||||
const T start_data = mix2<T>(start.factor, src[start.left_index], src[start.right_index]);
|
|
||||||
const T end_data = mix2<T>(end.factor, src[end.left_index], src[end.right_index]);
|
|
||||||
|
|
||||||
dst.copy_from(src.slice(start.left_index, num));
|
|
||||||
dst.first() = start_data;
|
|
||||||
dst.last() = end_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Look up the control points to the left and right of factor, and get the factor between them. */
|
|
||||||
static TrimLocation lookup_control_point_position(const Spline::LookupResult &lookup,
|
|
||||||
const BezierSpline &spline)
|
|
||||||
{
|
|
||||||
Span<int> offsets = spline.control_point_offsets();
|
|
||||||
|
|
||||||
const int *offset = std::lower_bound(offsets.begin(), offsets.end(), lookup.evaluated_index);
|
|
||||||
const int index = offset - offsets.begin();
|
|
||||||
|
|
||||||
const int left = offsets[index] > lookup.evaluated_index ? index - 1 : index;
|
|
||||||
const int right = left == (spline.size() - 1) ? 0 : left + 1;
|
|
||||||
|
|
||||||
const float offset_in_segment = lookup.evaluated_index + lookup.factor - offsets[left];
|
|
||||||
const int segment_eval_num = offsets[left + 1] - offsets[left];
|
|
||||||
const float factor = std::clamp(offset_in_segment / segment_eval_num, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
return {left, right, factor};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void trim_poly_spline(Spline &spline,
|
|
||||||
const Spline::LookupResult &start_lookup,
|
|
||||||
const Spline::LookupResult &end_lookup)
|
|
||||||
{
|
|
||||||
/* Poly splines have a 1 to 1 mapping between control points and evaluated points. */
|
|
||||||
const TrimLocation start = {
|
|
||||||
start_lookup.evaluated_index, start_lookup.next_evaluated_index, start_lookup.factor};
|
|
||||||
const TrimLocation end = {
|
|
||||||
end_lookup.evaluated_index, end_lookup.next_evaluated_index, end_lookup.factor};
|
|
||||||
|
|
||||||
const int num = end.right_index - start.left_index + 1;
|
|
||||||
|
|
||||||
linear_trim_data<float3>(start, end, spline.positions());
|
|
||||||
linear_trim_data<float>(start, end, spline.radii());
|
|
||||||
linear_trim_data<float>(start, end, spline.tilts());
|
|
||||||
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
|
||||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
|
|
||||||
BLI_assert(src);
|
|
||||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
linear_trim_data<T>(start, end, src->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
|
|
||||||
spline.resize(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim NURB splines by converting to a poly spline.
|
|
||||||
*/
|
|
||||||
static PolySpline trim_nurbs_spline(const Spline &spline,
|
|
||||||
const Spline::LookupResult &start_lookup,
|
|
||||||
const Spline::LookupResult &end_lookup)
|
|
||||||
{
|
|
||||||
/* Since this outputs a poly spline, the evaluated indices are the control point indices. */
|
|
||||||
const TrimLocation start = {
|
|
||||||
start_lookup.evaluated_index, start_lookup.next_evaluated_index, start_lookup.factor};
|
|
||||||
const TrimLocation end = {
|
|
||||||
end_lookup.evaluated_index, end_lookup.next_evaluated_index, end_lookup.factor};
|
|
||||||
|
|
||||||
const int num = end.right_index - start.left_index + 1;
|
|
||||||
|
|
||||||
/* Create poly spline and copy trimmed data to it. */
|
|
||||||
PolySpline new_spline;
|
|
||||||
new_spline.resize(num);
|
|
||||||
|
|
||||||
/* Copy generic attribute data. */
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
|
||||||
std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id);
|
|
||||||
BLI_assert(src);
|
|
||||||
if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) {
|
|
||||||
BLI_assert_unreachable();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id);
|
|
||||||
BLI_assert(dst);
|
|
||||||
|
|
||||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
VArray<T> eval_data = spline.interpolate_to_evaluated<T>(src->typed<T>());
|
|
||||||
linear_trim_to_output_data<T>(
|
|
||||||
start, end, eval_data.get_internal_span(), dst->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
|
|
||||||
linear_trim_to_output_data<float3>(
|
|
||||||
start, end, spline.evaluated_positions(), new_spline.positions());
|
|
||||||
|
|
||||||
VArray<float> evaluated_radii = spline.interpolate_to_evaluated(spline.radii());
|
|
||||||
linear_trim_to_output_data<float>(
|
|
||||||
start, end, evaluated_radii.get_internal_span(), new_spline.radii());
|
|
||||||
|
|
||||||
VArray<float> evaluated_tilts = spline.interpolate_to_evaluated(spline.tilts());
|
|
||||||
linear_trim_to_output_data<float>(
|
|
||||||
start, end, evaluated_tilts.get_internal_span(), new_spline.tilts());
|
|
||||||
|
|
||||||
return new_spline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim Bezier splines by adjusting the first and last handles
|
|
||||||
* and control points to maintain the original shape.
|
|
||||||
*/
|
|
||||||
static void trim_bezier_spline(Spline &spline,
|
|
||||||
const Spline::LookupResult &start_lookup,
|
|
||||||
const Spline::LookupResult &end_lookup)
|
|
||||||
{
|
|
||||||
BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline);
|
|
||||||
|
|
||||||
const TrimLocation start = lookup_control_point_position(start_lookup, bezier_spline);
|
|
||||||
TrimLocation end = lookup_control_point_position(end_lookup, bezier_spline);
|
|
||||||
|
|
||||||
const Span<int> control_offsets = bezier_spline.control_point_offsets();
|
|
||||||
|
|
||||||
/* The number of control points in the resulting spline. */
|
|
||||||
const int num = end.right_index - start.left_index + 1;
|
|
||||||
|
|
||||||
/* Trim the spline attributes. Done before end.factor recalculation as it needs
|
|
||||||
* the original end.factor value. */
|
|
||||||
linear_trim_data<float>(start, end, bezier_spline.radii());
|
|
||||||
linear_trim_data<float>(start, end, bezier_spline.tilts());
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
|
||||||
std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
|
|
||||||
BLI_assert(src);
|
|
||||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
linear_trim_data<T>(start, end, src->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
|
|
||||||
/* Recalculate end.factor if the `num` is two, because the adjustment in the
|
|
||||||
* position of the control point of the spline to the left of the new end point will change the
|
|
||||||
* factor between them. */
|
|
||||||
if (num == 2) {
|
|
||||||
if (start_lookup.factor == 1.0f) {
|
|
||||||
end.factor = 0.0f;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
end.factor = (end_lookup.evaluated_index + end_lookup.factor -
|
|
||||||
(start_lookup.evaluated_index + start_lookup.factor)) /
|
|
||||||
(control_offsets[end.right_index] -
|
|
||||||
(start_lookup.evaluated_index + start_lookup.factor));
|
|
||||||
end.factor = std::clamp(end.factor, 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BezierSpline::InsertResult start_point = bezier_spline.calculate_segment_insertion(
|
|
||||||
start.left_index, start.right_index, start.factor);
|
|
||||||
|
|
||||||
/* Update the start control point parameters so they are used calculating the new end point. */
|
|
||||||
bezier_spline.positions()[start.left_index] = start_point.position;
|
|
||||||
bezier_spline.handle_positions_right()[start.left_index] = start_point.right_handle;
|
|
||||||
bezier_spline.handle_positions_left()[start.right_index] = start_point.handle_next;
|
|
||||||
|
|
||||||
const BezierSpline::InsertResult end_point = bezier_spline.calculate_segment_insertion(
|
|
||||||
end.left_index, end.right_index, end.factor);
|
|
||||||
|
|
||||||
/* If `num` is two, then the start point right handle needs to change to reflect the end point
|
|
||||||
* previous handle update. */
|
|
||||||
if (num == 2) {
|
|
||||||
start_point.right_handle = end_point.handle_prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Shift control point position data to start at beginning of array. */
|
|
||||||
if (start.left_index > 0) {
|
|
||||||
shift_slice_to_start(bezier_spline.positions(), start.left_index, num);
|
|
||||||
shift_slice_to_start(bezier_spline.handle_positions_left(), start.left_index, num);
|
|
||||||
shift_slice_to_start(bezier_spline.handle_positions_right(), start.left_index, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
bezier_spline.positions().first() = start_point.position;
|
|
||||||
bezier_spline.positions()[num - 1] = end_point.position;
|
|
||||||
|
|
||||||
bezier_spline.handle_positions_left().first() = start_point.left_handle;
|
|
||||||
bezier_spline.handle_positions_left()[num - 1] = end_point.left_handle;
|
|
||||||
|
|
||||||
bezier_spline.handle_positions_right().first() = start_point.right_handle;
|
|
||||||
bezier_spline.handle_positions_right()[num - 1] = end_point.right_handle;
|
|
||||||
|
|
||||||
/* If there is at least one control point between the endpoints, update the control
|
|
||||||
* point handle to the right of the start point and to the left of the end point. */
|
|
||||||
if (num > 2) {
|
|
||||||
bezier_spline.handle_positions_left()[start.right_index - start.left_index] =
|
|
||||||
start_point.handle_next;
|
|
||||||
bezier_spline.handle_positions_right()[end.left_index - start.left_index] =
|
|
||||||
end_point.handle_prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
bezier_spline.resize(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void trim_spline(SplinePtr &spline,
|
|
||||||
const Spline::LookupResult start,
|
|
||||||
const Spline::LookupResult end)
|
|
||||||
{
|
|
||||||
switch (spline->type()) {
|
|
||||||
case CURVE_TYPE_BEZIER:
|
|
||||||
trim_bezier_spline(*spline, start, end);
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_POLY:
|
|
||||||
trim_poly_spline(*spline, start, end);
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_NURBS:
|
|
||||||
spline = std::make_unique<PolySpline>(trim_nurbs_spline(*spline, start, end));
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_CATMULL_ROM:
|
|
||||||
BLI_assert_unreachable();
|
|
||||||
spline = {};
|
|
||||||
}
|
|
||||||
spline->mark_cache_invalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
static void to_single_point_data(const TrimLocation &trim, MutableSpan<T> data)
|
|
||||||
{
|
|
||||||
data.first() = mix2<T>(trim.factor, data[trim.left_index], data[trim.right_index]);
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
static void to_single_point_data(const TrimLocation &trim, Span<T> src, MutableSpan<T> dst)
|
|
||||||
{
|
|
||||||
dst.first() = mix2<T>(trim.factor, src[trim.left_index], src[trim.right_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void to_single_point_bezier(Spline &spline, const Spline::LookupResult &lookup)
|
|
||||||
{
|
|
||||||
BezierSpline &bezier = static_cast<BezierSpline &>(spline);
|
|
||||||
|
|
||||||
const TrimLocation trim = lookup_control_point_position(lookup, bezier);
|
|
||||||
|
|
||||||
const BezierSpline::InsertResult new_point = bezier.calculate_segment_insertion(
|
|
||||||
trim.left_index, trim.right_index, trim.factor);
|
|
||||||
bezier.positions().first() = new_point.position;
|
|
||||||
bezier.handle_types_left().first() = BEZIER_HANDLE_FREE;
|
|
||||||
bezier.handle_types_right().first() = BEZIER_HANDLE_FREE;
|
|
||||||
bezier.handle_positions_left().first() = new_point.left_handle;
|
|
||||||
bezier.handle_positions_right().first() = new_point.right_handle;
|
|
||||||
|
|
||||||
to_single_point_data<float>(trim, bezier.radii());
|
|
||||||
to_single_point_data<float>(trim, bezier.tilts());
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
|
||||||
std::optional<GMutableSpan> data = spline.attributes.get_for_write(attribute_id);
|
|
||||||
attribute_math::convert_to_static_type(data->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
to_single_point_data<T>(trim, data->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
spline.resize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void to_single_point_poly(Spline &spline, const Spline::LookupResult &lookup)
|
|
||||||
{
|
|
||||||
const TrimLocation trim{lookup.evaluated_index, lookup.next_evaluated_index, lookup.factor};
|
|
||||||
|
|
||||||
to_single_point_data<float3>(trim, spline.positions());
|
|
||||||
to_single_point_data<float>(trim, spline.radii());
|
|
||||||
to_single_point_data<float>(trim, spline.tilts());
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
|
|
||||||
std::optional<GMutableSpan> data = spline.attributes.get_for_write(attribute_id);
|
|
||||||
attribute_math::convert_to_static_type(data->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
to_single_point_data<T>(trim, data->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
spline.resize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PolySpline to_single_point_nurbs(const Spline &spline, const Spline::LookupResult &lookup)
|
|
||||||
{
|
|
||||||
/* Since this outputs a poly spline, the evaluated indices are the control point indices. */
|
|
||||||
const TrimLocation trim{lookup.evaluated_index, lookup.next_evaluated_index, lookup.factor};
|
|
||||||
|
|
||||||
/* Create poly spline and copy trimmed data to it. */
|
|
||||||
PolySpline new_spline;
|
|
||||||
new_spline.resize(1);
|
|
||||||
|
|
||||||
spline.attributes.foreach_attribute(
|
|
||||||
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
|
|
||||||
new_spline.attributes.create(attribute_id, meta_data.data_type);
|
|
||||||
std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id);
|
|
||||||
std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id);
|
|
||||||
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
|
|
||||||
using T = decltype(dummy);
|
|
||||||
VArray<T> eval_data = spline.interpolate_to_evaluated<T>(src->typed<T>());
|
|
||||||
to_single_point_data<T>(trim, eval_data.get_internal_span(), dst->typed<T>());
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ATTR_DOMAIN_POINT);
|
|
||||||
|
|
||||||
to_single_point_data<float3>(trim, spline.evaluated_positions(), new_spline.positions());
|
|
||||||
|
|
||||||
VArray<float> evaluated_radii = spline.interpolate_to_evaluated(spline.radii());
|
|
||||||
to_single_point_data<float>(trim, evaluated_radii.get_internal_span(), new_spline.radii());
|
|
||||||
|
|
||||||
VArray<float> evaluated_tilts = spline.interpolate_to_evaluated(spline.tilts());
|
|
||||||
to_single_point_data<float>(trim, evaluated_tilts.get_internal_span(), new_spline.tilts());
|
|
||||||
|
|
||||||
return new_spline;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void to_single_point_spline(SplinePtr &spline, const Spline::LookupResult &lookup)
|
|
||||||
{
|
|
||||||
switch (spline->type()) {
|
|
||||||
case CURVE_TYPE_BEZIER:
|
|
||||||
to_single_point_bezier(*spline, lookup);
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_POLY:
|
|
||||||
to_single_point_poly(*spline, lookup);
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_NURBS:
|
|
||||||
spline = std::make_unique<PolySpline>(to_single_point_nurbs(*spline, lookup));
|
|
||||||
break;
|
|
||||||
case CURVE_TYPE_CATMULL_ROM:
|
|
||||||
BLI_assert_unreachable();
|
|
||||||
spline = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void geometry_set_curve_trim(GeometrySet &geometry_set,
|
static void geometry_set_curve_trim(GeometrySet &geometry_set,
|
||||||
const GeometryNodeCurveSampleMode mode,
|
const GeometryNodeCurveSampleMode mode,
|
||||||
Field<float> &start_field,
|
Field<float> &start_field,
|
||||||
@@ -505,68 +117,49 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const Curves &src_curves_id = *geometry_set.get_curves_for_read();
|
const Curves &src_curves_id = *geometry_set.get_curves_for_read();
|
||||||
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(src_curves_id.geometry);
|
const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry);
|
||||||
|
if (src_curves.curves_num() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE};
|
bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE};
|
||||||
fn::FieldEvaluator evaluator{field_context, curves.curves_num()};
|
fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
|
||||||
evaluator.add(start_field);
|
evaluator.add(start_field);
|
||||||
evaluator.add(end_field);
|
evaluator.add(end_field);
|
||||||
evaluator.evaluate();
|
evaluator.evaluate();
|
||||||
const VArray<float> starts = evaluator.get_evaluated<float>(0);
|
const VArray<float> starts = evaluator.get_evaluated<float>(0);
|
||||||
const VArray<float> ends = evaluator.get_evaluated<float>(1);
|
const VArray<float> ends = evaluator.get_evaluated<float>(1);
|
||||||
|
|
||||||
std::unique_ptr<CurveEval> curve = curves_to_curve_eval(src_curves_id);
|
const VArray<bool> cyclic = src_curves.cyclic();
|
||||||
MutableSpan<SplinePtr> splines = curve->splines();
|
|
||||||
|
|
||||||
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
|
/* If node length input is on form [0, 1] instead of [0, length]*/
|
||||||
for (const int i : range) {
|
const bool normalized_length_lookup = mode == GEO_NODE_CURVE_SAMPLE_FACTOR;
|
||||||
SplinePtr &spline = splines[i];
|
|
||||||
|
|
||||||
/* Currently trimming cyclic splines is not supported. It could be in the future though. */
|
/* Stack start + end field. */
|
||||||
if (spline->is_cyclic()) {
|
Vector<float> length_factors(src_curves.curves_num() * 2);
|
||||||
continue;
|
Vector<int64_t> lookup_indices(src_curves.curves_num() * 2);
|
||||||
}
|
threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) {
|
||||||
|
for (const int64_t curve_i : curve_range) {
|
||||||
if (spline->evaluated_edges_num() == 0) {
|
const bool negative_trim = !cyclic[curve_i] && starts[curve_i] > ends[curve_i];
|
||||||
continue;
|
length_factors[curve_i] = starts[curve_i];
|
||||||
}
|
length_factors[curve_i + src_curves.curves_num()] = negative_trim ? starts[curve_i] :
|
||||||
|
ends[curve_i];
|
||||||
const float length = spline->length();
|
lookup_indices[curve_i] = curve_i;
|
||||||
if (length == 0.0f) {
|
lookup_indices[curve_i + src_curves.curves_num()] = curve_i;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float start = starts[i];
|
|
||||||
const float end = ends[i];
|
|
||||||
|
|
||||||
/* When the start and end samples are reversed, instead of implicitly reversing the spline
|
|
||||||
* or switching the parameters, create a single point spline with the end sample point. */
|
|
||||||
if (end <= start) {
|
|
||||||
if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
|
|
||||||
to_single_point_spline(spline,
|
|
||||||
spline->lookup_evaluated_length(std::clamp(start, 0.0f, length)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
to_single_point_spline(spline,
|
|
||||||
spline->lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f)));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
|
|
||||||
trim_spline(spline,
|
|
||||||
spline->lookup_evaluated_length(std::clamp(start, 0.0f, length)),
|
|
||||||
spline->lookup_evaluated_length(std::clamp(end, 0.0f, length)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
trim_spline(spline,
|
|
||||||
spline->lookup_evaluated_factor(std::clamp(start, 0.0f, 1.0f)),
|
|
||||||
spline->lookup_evaluated_factor(std::clamp(end, 0.0f, 1.0f)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Curves *dst_curves_id = curve_eval_to_curves(*curve);
|
/* Create curve trim lookup table. */
|
||||||
|
Array<bke::curves::CurvePoint, 12> point_lookups = geometry::lookup_curve_points(
|
||||||
|
src_curves, length_factors, lookup_indices, normalized_length_lookup);
|
||||||
|
|
||||||
|
bke::CurvesGeometry dst_curves = geometry::trim_curves(
|
||||||
|
src_curves,
|
||||||
|
src_curves.curves_range().as_span(),
|
||||||
|
point_lookups.as_span().slice(0, src_curves.curves_num()),
|
||||||
|
point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num()));
|
||||||
|
|
||||||
|
Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves));
|
||||||
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
|
bke::curves_copy_parameters(src_curves_id, *dst_curves_id);
|
||||||
geometry_set.replace_curves(dst_curves_id);
|
geometry_set.replace_curves(dst_curves_id);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user