Curves: Port set type node to new data-block

This commit ports the "Set Spline Type" node to the new curves type.
Performance should be improved in similar ways to the other refactors
from the conversion task (T95443). Converting to and from Catmull Rom
curves is now supported. There are a few cases where a lot of work can
be skipped: when the number of points doesn't change, and when the
types already match the goal type.

The refactor has a few other explicit goals as well:
 - Don't count on initialization of attribute arrays when they are
   first allocated.
 - Avoid copying the entire data-block when possible.
 - Make decisions about which attributes to copy when changing curves
   more obvious.
 - Use higher-level methods to copy data between curve points.
 - Optimize for the common cases of single types and full selections.
 - Process selected curves of the same types in the same loop.

The Bezier to NURBS conversion is written by Piotr Makal (@pmakal).

Differential Revision: https://developer.blender.org/D14769
This commit is contained in:
Hans Goudey 2022-06-08 15:37:46 +02:00
parent 520be607e8
commit 9e393fc2f1
Notes: blender-bot 2023-02-14 11:21:40 +01:00
Referenced by commit 7e55ff15b0, Fix: Incorrectly sized curves created in set conversion node
Referenced by issue #101972, Regression: GN: Crashes when setting Spline Type from NURBS to Bezier in a row.
Referenced by issue #95443, Refactor curve nodes to use new data structure
13 changed files with 856 additions and 367 deletions

View File

@ -25,7 +25,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 0
#define BLENDER_FILE_SUBVERSION 1
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -182,6 +182,7 @@ class CurvesGeometry : public ::CurvesGeometry {
void update_curve_types();
bool has_curve_with_type(CurveType type) const;
bool has_curve_with_type(Span<CurveType> types) const;
/** Return true if all of the curves have the provided type. */
bool is_single_type(CurveType type) const;
/** Return the number of curves with each type. */
@ -394,6 +395,11 @@ class CurvesGeometry : public ::CurvesGeometry {
*/
void reverse_curves(IndexMask curves_to_reverse);
/**
* Remove any attributes that are unused based on the types in the curves.
*/
void remove_attributes_based_on_types();
/* --------------------------------------------------------------------
* Attributes.
*/
@ -710,6 +716,12 @@ inline bool CurvesGeometry::has_curve_with_type(const CurveType type) const
return this->curve_type_counts()[type] > 0;
}
inline bool CurvesGeometry::has_curve_with_type(const Span<CurveType> types) const
{
return std::any_of(
types.begin(), types.end(), [&](CurveType type) { return this->has_curve_with_type(type); });
}
inline const std::array<int, CURVE_TYPES_NUM> &CurvesGeometry::curve_type_counts() const
{
BLI_assert(this->runtime->type_counts == calculate_type_counts(this->curve_types()));

View File

@ -9,8 +9,52 @@
* \brief Low-level operations for curves.
*/
#include "BLI_function_ref.hh"
#include "BLI_generic_pointer.hh"
namespace blender::bke::curves {
/**
* 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
* and #dst_curves.
*/
void copy_point_data(const CurvesGeometry &src_curves,
const CurvesGeometry &dst_curves,
Span<IndexRange> curve_ranges,
GSpan src,
GMutableSpan dst);
void copy_point_data(const CurvesGeometry &src_curves,
const CurvesGeometry &dst_curves,
IndexMask src_curve_selection,
GSpan src,
GMutableSpan dst);
template<typename T>
void copy_point_data(const CurvesGeometry &src_curves,
const CurvesGeometry &dst_curves,
const IndexMask src_curve_selection,
const Span<T> src,
MutableSpan<T> dst)
{
copy_point_data(src_curves, dst_curves, src_curve_selection, GSpan(src), GMutableSpan(dst));
}
void fill_points(const CurvesGeometry &curves,
IndexMask curve_selection,
GPointer value,
GMutableSpan dst);
template<typename T>
void fill_points(const CurvesGeometry &curves,
const IndexMask curve_selection,
const T &value,
MutableSpan<T> dst)
{
fill_points(curves, curve_selection, &value, dst);
}
/**
* Copy the size of every curve in #curve_ranges to the corresponding index in #counts.
*/
@ -23,4 +67,18 @@ void fill_curve_counts(const bke::CurvesGeometry &curves,
*/
void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets, int start_offset = 0);
IndexMask indices_for_type(const VArray<int8_t> &types,
const std::array<int, CURVE_TYPES_NUM> &type_counts,
const CurveType type,
const IndexMask selection,
Vector<int64_t> &r_indices);
void foreach_curve_by_type(const VArray<int8_t> &types,
const std::array<int, CURVE_TYPES_NUM> &type_counts,
IndexMask selection,
FunctionRef<void(IndexMask)> catmull_rom_fn,
FunctionRef<void(IndexMask)> poly_fn,
FunctionRef<void(IndexMask)> bezier_fn,
FunctionRef<void(IndexMask)> nurbs_fn);
} // namespace blender::bke::curves

View File

@ -18,6 +18,7 @@
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
namespace blender::bke {
@ -550,16 +551,8 @@ IndexMask CurvesGeometry::indices_for_curve_type(const CurveType type,
const IndexMask selection,
Vector<int64_t> &r_indices) const
{
if (this->curve_type_counts()[type] == this->curves_num()) {
return selection;
}
const VArray<int8_t> types = this->curve_types();
if (types.is_single()) {
return types.get_internal_single() == type ? IndexMask(this->curves_num()) : IndexMask(0);
}
Span<int8_t> types_span = types.get_internal_span();
return index_mask_ops::find_indices_based_on_predicate(
selection, 1024, r_indices, [&](const int index) { return types_span[index] == type; });
return curves::indices_for_type(
this->curve_types(), this->curve_type_counts(), type, selection, r_indices);
}
void CurvesGeometry::ensure_nurbs_basis_cache() const
@ -1322,6 +1315,27 @@ void CurvesGeometry::reverse_curves(const IndexMask curves_to_reverse)
this->tag_topology_changed();
}
void CurvesGeometry::remove_attributes_based_on_types()
{
const int points_num = this->points_num();
const int curves_num = this->curves_num();
if (!this->has_curve_with_type(CURVE_TYPE_BEZIER)) {
CustomData_free_layer_named(&this->point_data, ATTR_HANDLE_TYPE_LEFT.c_str(), points_num);
CustomData_free_layer_named(&this->point_data, ATTR_HANDLE_TYPE_RIGHT.c_str(), points_num);
CustomData_free_layer_named(&this->point_data, ATTR_HANDLE_POSITION_LEFT.c_str(), points_num);
CustomData_free_layer_named(&this->point_data, ATTR_HANDLE_POSITION_RIGHT.c_str(), points_num);
}
if (!this->has_curve_with_type(CURVE_TYPE_NURBS)) {
CustomData_free_layer_named(&this->point_data, ATTR_NURBS_WEIGHT.c_str(), points_num);
CustomData_free_layer_named(&this->curve_data, ATTR_NURBS_ORDER.c_str(), curves_num);
CustomData_free_layer_named(&this->curve_data, ATTR_NURBS_KNOTS_MODE.c_str(), curves_num);
}
if (!this->has_curve_with_type({CURVE_TYPE_BEZIER, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_NURBS})) {
CustomData_free_layer_named(&this->curve_data, ATTR_RESOLUTION.c_str(), curves_num);
}
this->update_customdata_pointers();
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -4,6 +4,8 @@
* \ingroup bke
*/
#include "BLI_index_mask_ops.hh"
#include "BKE_curves_utils.hh"
namespace blender::bke::curves {
@ -35,4 +37,86 @@ void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets, const int
counts_to_offsets.last() = offset;
}
void copy_point_data(const CurvesGeometry &src_curves,
const CurvesGeometry &dst_curves,
const Span<IndexRange> curve_ranges,
const GSpan src,
GMutableSpan dst)
{
threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange range) {
for (const IndexRange range : curve_ranges.slice(range)) {
const IndexRange src_points = src_curves.points_for_curves(range);
const IndexRange dst_points = dst_curves.points_for_curves(range);
/* The arrays might be large, so a threaded copy might make sense here too. */
dst.slice(dst_points).copy_from(src.slice(src_points));
}
});
}
void copy_point_data(const CurvesGeometry &src_curves,
const CurvesGeometry &dst_curves,
const IndexMask src_curve_selection,
const GSpan src,
GMutableSpan dst)
{
threading::parallel_for(src_curve_selection.index_range(), 512, [&](IndexRange range) {
for (const int i : src_curve_selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
/* The arrays might be large, so a threaded copy might make sense here too. */
dst.slice(dst_points).copy_from(src.slice(src_points));
}
});
}
void fill_points(const CurvesGeometry &curves,
const IndexMask curve_selection,
const GPointer value,
GMutableSpan dst)
{
BLI_assert(*value.type() == dst.type());
const CPPType &type = dst.type();
threading::parallel_for(curve_selection.index_range(), 512, [&](IndexRange range) {
for (const int i : curve_selection.slice(range)) {
const IndexRange points = curves.points_for_curve(i);
type.fill_assign_n(value.get(), dst.slice(curves.points_for_curve(i)).data(), points.size());
}
});
}
IndexMask indices_for_type(const VArray<int8_t> &types,
const std::array<int, CURVE_TYPES_NUM> &type_counts,
const CurveType type,
const IndexMask selection,
Vector<int64_t> &r_indices)
{
if (type_counts[type] == types.size()) {
return selection;
}
if (types.is_single()) {
return types.get_internal_single() == type ? IndexMask(types.size()) : IndexMask(0);
}
Span<int8_t> types_span = types.get_internal_span();
return index_mask_ops::find_indices_based_on_predicate(
selection, 4096, r_indices, [&](const int index) { return types_span[index] == type; });
}
void foreach_curve_by_type(const VArray<int8_t> &types,
const std::array<int, CURVE_TYPES_NUM> &counts,
const IndexMask selection,
FunctionRef<void(IndexMask)> catmull_rom_fn,
FunctionRef<void(IndexMask)> poly_fn,
FunctionRef<void(IndexMask)> bezier_fn,
FunctionRef<void(IndexMask)> nurbs_fn)
{
Vector<int64_t> catmull_rom;
Vector<int64_t> poly;
Vector<int64_t> bezier;
Vector<int64_t> nurbs;
catmull_rom_fn(indices_for_type(types, counts, CURVE_TYPE_CATMULL_ROM, selection, catmull_rom));
poly_fn(indices_for_type(types, counts, CURVE_TYPE_POLY, selection, poly));
bezier_fn(indices_for_type(types, counts, CURVE_TYPE_BEZIER, selection, bezier));
nurbs_fn(indices_for_type(types, counts, CURVE_TYPE_NURBS, selection, nurbs));
}
} // namespace blender::bke::curves

View File

@ -278,7 +278,7 @@ class IndexMask {
* before each range in the return value starts.
*/
Vector<IndexRange> extract_ranges_invert(const IndexRange full_range,
Vector<int64_t> *r_skip_amounts) const;
Vector<int64_t> *r_skip_amounts = nullptr) const;
};
} // namespace blender

View File

@ -25,6 +25,7 @@
#include "DNA_collection_types.h"
#include "DNA_constraint_types.h"
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "DNA_genfile.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_lineart_types.h"
@ -3021,18 +3022,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - "versioning_userdef.c", #blo_do_versions_userdef
* - "versioning_userdef.c", #do_versions_theme
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
if (!MAIN_VERSION_ATLEAST(bmain, 303, 1)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
versioning_replace_legacy_combined_and_separate_color_nodes(ntree);
}
@ -3066,5 +3056,41 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
SEQ_for_each_callback(&ed->seqbase, version_merge_still_offsets, NULL);
}
}
/* Use the curves type enum for the set spline type node, instead of a special one. */
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == GEO_NODE_CURVE_SPLINE_TYPE) {
NodeGeometryCurveSplineType *storage = (NodeGeometryCurveSplineType *)node->storage;
switch (storage->spline_type) {
case 0: /* GEO_NODE_SPLINE_TYPE_BEZIER */
storage->spline_type = CURVE_TYPE_BEZIER;
break;
case 1: /* GEO_NODE_SPLINE_TYPE_NURBS */
storage->spline_type = CURVE_TYPE_NURBS;
break;
case 2: /* GEO_NODE_SPLINE_TYPE_POLY */
storage->spline_type = CURVE_TYPE_POLY;
break;
}
}
}
}
}
FOREACH_NODETREE_END;
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - "versioning_userdef.c", #blo_do_versions_userdef
* - "versioning_userdef.c", #do_versions_theme
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}

View File

@ -165,38 +165,6 @@ static void gather_point_attributes_to_interpolate(const CurveComponent &src_com
dst_curves.update_customdata_pointers();
}
/**
* Copy the provided point attribute values between all curves in the #curve_ranges index
* ranges, assuming that all curves are the same size in #src_curves and #dst_curves.
*/
template<typename T>
static void copy_between_curves(const bke::CurvesGeometry &src_curves,
const bke::CurvesGeometry &dst_curves,
const Span<IndexRange> curve_ranges,
const Span<T> src,
const MutableSpan<T> dst)
{
threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange range) {
for (const IndexRange range : curve_ranges.slice(range)) {
const IndexRange src_points = src_curves.points_for_curves(range);
const IndexRange dst_points = dst_curves.points_for_curves(range);
/* The arrays might be large, so a threaded copy might make sense here too. */
dst.slice(dst_points).copy_from(src.slice(src_points));
}
});
}
static void copy_between_curves(const bke::CurvesGeometry &src_curves,
const bke::CurvesGeometry &dst_curves,
const Span<IndexRange> unselected_ranges,
const GSpan src,
const GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
copy_between_curves(src_curves, dst_curves, unselected_ranges, src.typed<T>(), dst.typed<T>());
});
}
static Curves *resample_to_uniform(const CurveComponent &src_component,
const fn::Field<bool> &selection_field,
const fn::Field<int> &count_field)
@ -328,20 +296,21 @@ static Curves *resample_to_uniform(const CurveComponent &src_component,
/* Any attribute data from unselected curve points can be directly copied. */
for (const int i : attributes.src.index_range()) {
copy_between_curves(
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
}
for (const int i : attributes.src_no_interpolation.index_range()) {
copy_between_curves(src_curves,
dst_curves,
unselected_ranges,
attributes.src_no_interpolation[i],
attributes.dst_no_interpolation[i]);
bke::curves::copy_point_data(src_curves,
dst_curves,
unselected_ranges,
attributes.src_no_interpolation[i],
attributes.dst_no_interpolation[i]);
}
/* Copy positions for unselected curves. */
Span<float3> src_positions = src_curves.positions();
copy_between_curves(src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
for (bke::OutputAttribute &attribute : attributes.dst_attributes) {
attribute.save();
@ -449,20 +418,21 @@ Curves *resample_to_evaluated(const CurveComponent &src_component,
/* Any attribute data from unselected curve points can be directly copied. */
for (const int i : attributes.src.index_range()) {
copy_between_curves(
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
}
for (const int i : attributes.src_no_interpolation.index_range()) {
copy_between_curves(src_curves,
dst_curves,
unselected_ranges,
attributes.src_no_interpolation[i],
attributes.dst_no_interpolation[i]);
bke::curves::copy_point_data(src_curves,
dst_curves,
unselected_ranges,
attributes.src_no_interpolation[i],
attributes.dst_no_interpolation[i]);
}
/* Copy positions for unselected curves. */
Span<float3> src_positions = src_curves.positions();
copy_between_curves(src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, src_positions, dst_positions);
for (bke::OutputAttribute &attribute : attributes.dst_attributes) {
attribute.save();

View File

@ -1915,12 +1915,6 @@ typedef enum GeometryNodeBooleanOperation {
GEO_NODE_BOOLEAN_DIFFERENCE = 2,
} GeometryNodeBooleanOperation;
typedef enum GeometryNodeSplineType {
GEO_NODE_SPLINE_TYPE_BEZIER = 0,
GEO_NODE_SPLINE_TYPE_NURBS = 1,
GEO_NODE_SPLINE_TYPE_POLY = 2,
} GeometryNodeSplineType;
typedef enum GeometryNodeCurvePrimitiveCircleMode {
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS = 0,
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS = 1

View File

@ -228,6 +228,8 @@ DEF_ENUM(rna_enum_transform_orientation_items)
DEF_ENUM(rna_enum_velocity_unit_items)
DEF_ENUM(rna_enum_curves_types)
/* Not available to RNA pre-processing (`makesrna`).
* Defined in editors for example. */
#ifndef RNA_MAKESRNA

View File

@ -18,6 +18,14 @@
#include "WM_types.h"
const EnumPropertyItem rna_enum_curves_types[] = {
{CURVE_TYPE_CATMULL_ROM, "CATMULL_ROM", 0, "Catmull Rom", ""},
{CURVE_TYPE_POLY, "POLY", 0, "Poly", ""},
{CURVE_TYPE_BEZIER, "BEZIER", 0, "Bezier", ""},
{CURVE_TYPE_NURBS, "NURBS", 0, "NURBS", ""},
{0, NULL, 0, NULL, NULL},
};
#ifdef RNA_RUNTIME
# include "BLI_math_vector.h"

View File

@ -9575,18 +9575,14 @@ static void def_geo_distribute_points_on_faces(StructRNA *srna)
static void def_geo_curve_spline_type(StructRNA *srna)
{
static const EnumPropertyItem type_items[] = {
{GEO_NODE_SPLINE_TYPE_BEZIER, "BEZIER", ICON_NONE, "Bezier", "Set the splines to Bezier"},
{GEO_NODE_SPLINE_TYPE_NURBS, "NURBS", ICON_NONE, "NURBS", "Set the splines to NURBS"},
{GEO_NODE_SPLINE_TYPE_POLY, "POLY", ICON_NONE, "Poly", "Set the splines to Poly"},
{0, NULL, 0, NULL, NULL}};
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSplineType", "storage");
prop = RNA_def_property(srna, "spline_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "spline_type");
RNA_def_property_enum_items(prop, type_items);
RNA_def_property_enum_items(prop, rna_enum_curves_types);
RNA_def_property_ui_text(prop, "Type", "The curve type to change the selected curves to");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}

View File

@ -1,6 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_spline.hh"
#include <numeric>
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BLI_task.hh"
@ -29,10 +33,39 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
{
NodeGeometryCurveSplineType *data = MEM_cnew<NodeGeometryCurveSplineType>(__func__);
data->spline_type = GEO_NODE_SPLINE_TYPE_POLY;
data->spline_type = CURVE_TYPE_POLY;
node->storage = data;
}
/**
* This function answers the question about possible conversion method for NURBS-to-Bezier. In
* general for 3rd degree NURBS curves there is one-to-one relation with 3rd degree Bezier curves
* that can be exploit for conversion - Bezier handles sit on NURBS hull segments and in the middle
* between those handles are Bezier anchor points.
*/
static bool is_nurbs_to_bezier_one_to_one(const KnotsMode knots_mode)
{
if (ELEM(knots_mode, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT)) {
return true;
}
return false;
}
/**
* As an optimization, just change the types on a mutable curves data-block when the conversion is
* simple. This could be expanded to more cases where the number of points doesn't change in the
* future, though that might require properly initializing some attributes, or removing others.
*/
static bool conversion_can_change_point_num(const CurveType dst_type)
{
if (ELEM(dst_type, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_POLY)) {
/* The conversion to Catmull Rom or Poly should never change the number of points, no matter
* the source type (Bezier to Catmull Rom conversion cannot maintain the same shape anyway). */
return false;
}
return true;
}
template<typename T>
static void scale_input_assign(const Span<T> src,
const int scale,
@ -44,32 +77,97 @@ static void scale_input_assign(const Span<T> src,
}
}
template<typename T>
static void scale_output_assign(const Span<T> src,
const int scale,
const int offset,
MutableSpan<T> dst)
/**
* The Bezier control point and its handles become three control points on the NURBS curve,
* so each attribute value is duplicated three times.
*/
template<typename T> static void bezier_generic_to_nurbs(const Span<T> src, MutableSpan<T> dst)
{
for (const int i : src.index_range()) {
dst[i * scale + offset] = src[i];
dst[i * 3] = src[i];
dst[i * 3 + 1] = src[i];
dst[i * 3 + 2] = src[i];
}
}
static void bezier_generic_to_nurbs(const GSpan src, GMutableSpan dst)
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
bezier_generic_to_nurbs(src.typed<T>(), dst.typed<T>());
});
}
static void bezier_positions_to_nurbs(const Span<float3> src_positions,
const Span<float3> src_handles_l,
const Span<float3> src_handles_r,
MutableSpan<float3> dst_positions)
{
for (const int i : src_positions.index_range()) {
dst_positions[i * 3] = src_handles_l[i];
dst_positions[i * 3 + 1] = src_positions[i];
dst_positions[i * 3 + 2] = src_handles_r[i];
}
}
static void catmull_rom_to_bezier_handles(const Span<float3> src_positions,
const bool cyclic,
MutableSpan<float3> dst_handles_l,
MutableSpan<float3> dst_handles_r)
{
/* Catmull Rom curves are the same as Bezier curves with automatically defined handle positions.
* This constant defines the portion of the distance between the next/previous points to use for
* the length of the handles. */
constexpr float handle_scale = 1.0f / 6.0f;
if (src_positions.size() == 1) {
dst_handles_l.first() = src_positions.first();
dst_handles_r.first() = src_positions.first();
return;
}
const float3 first_offset = cyclic ? src_positions[1] - src_positions.last() :
src_positions[1] - src_positions[0];
dst_handles_r.first() = src_positions.first() + first_offset * handle_scale;
dst_handles_l.first() = src_positions.first() - first_offset * handle_scale;
const float3 last_offset = cyclic ? src_positions.first() - src_positions.last(1) :
src_positions.last() - src_positions.last(1);
dst_handles_l.last() = src_positions.last() - last_offset * handle_scale;
dst_handles_r.last() = src_positions.last() + last_offset * handle_scale;
for (const int i : src_positions.index_range().drop_front(1).drop_back(1)) {
const float3 left_offset = src_positions[i - 1] - src_positions[i + 1];
dst_handles_l[i] = src_positions[i] + left_offset * handle_scale;
const float3 right_offset = src_positions[i + 1] - src_positions[i - 1];
dst_handles_r[i] = src_positions[i] + right_offset * handle_scale;
}
}
static void catmull_rom_to_nurbs_positions(const Span<float3> src_positions,
const bool cyclic,
MutableSpan<float3> dst_positions)
{
/* Convert the Catmull Rom position data to Bezier handles in order to reuse the Bezier to
* NURBS positions assignment. If this becomes a bottleneck, this step could be avoided. */
Array<float3, 32> bezier_handles_l(src_positions.size());
Array<float3, 32> bezier_handles_r(src_positions.size());
catmull_rom_to_bezier_handles(src_positions, cyclic, bezier_handles_l, bezier_handles_r);
bezier_positions_to_nurbs(src_positions, bezier_handles_l, bezier_handles_r, dst_positions);
}
template<typename T>
static void nurbs_to_bezier_assign(const Span<T> src,
const MutableSpan<T> dst,
const KnotsMode knots_mode)
{
switch (knots_mode) {
case NURBS_KNOT_MODE_BEZIER:
scale_input_assign<T>(src, 3, 1, dst);
break;
case NURBS_KNOT_MODE_NORMAL:
for (const int i : dst.index_range()) {
dst[i] = src[(i + 1) % src.size()];
}
break;
case NURBS_KNOT_MODE_ENDPOINT_BEZIER:
case NURBS_KNOT_MODE_ENDPOINT:
for (const int i : dst.index_range().drop_back(1).drop_front(1)) {
dst[i] = src[i + 1];
@ -77,31 +175,18 @@ static void nurbs_to_bezier_assign(const Span<T> src,
dst.first() = src.first();
dst.last() = src.last();
break;
default:
/* Every 3rd NURBS position (starting from index 1) should have its attributes transfered. */
scale_input_assign<T>(src, 3, 1, dst);
}
}
template<typename CopyFn>
static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn)
static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, GMutableSpan dst)
{
input_spline.attributes.foreach_attribute(
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id);
if (!dst) {
BLI_assert_unreachable();
return false;
}
copy_fn(*src, *dst);
return true;
},
ATTR_DOMAIN_POINT);
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
nurbs_to_bezier_assign(src.typed<T>(), dst.typed<T>(), knots_mode);
});
}
static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_positions,
@ -109,7 +194,51 @@ static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_po
{
const int nurbs_positions_num = nurbs_positions.size();
Vector<float3> handle_positions;
if (knots_mode == NURBS_KNOT_MODE_BEZIER) {
if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL;
if (is_periodic) {
handle_positions.append(nurbs_positions[1] +
((nurbs_positions[0] - nurbs_positions[1]) / 3));
}
else {
handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]);
handle_positions.append(nurbs_positions[1]);
}
/* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on
* endpoints, midpoints or 1/3 of the distance of a hull segment. */
const int segments_num = nurbs_positions_num - 1;
const bool ignore_interior_segment = segments_num == 3 && is_periodic == false;
if (ignore_interior_segment == false) {
const float mid_offset = (float)(segments_num - 1) / 2.0f;
for (const int i : IndexRange(1, segments_num - 2)) {
/* Divisor can have values: 1, 2 or 3. */
const int divisor = is_periodic ?
3 :
std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f));
const float3 &p1 = nurbs_positions[i];
const float3 &p2 = nurbs_positions[i + 1];
const float3 displacement = (p2 - p1) / divisor;
const int num_handles_on_segment = divisor < 3 ? 1 : 2;
for (int j : IndexRange(1, num_handles_on_segment)) {
handle_positions.append(p1 + (displacement * j));
}
}
}
const int last_index = nurbs_positions_num - 1;
if (is_periodic) {
handle_positions.append(
nurbs_positions[last_index - 1] +
((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3));
}
else {
handle_positions.append(nurbs_positions[last_index - 1]);
handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
}
}
else {
for (const int i : IndexRange(nurbs_positions_num)) {
if (i % 3 == 1) {
continue;
@ -124,237 +253,420 @@ static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_po
handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
}
}
else {
const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL;
if (is_periodic) {
handle_positions.append(nurbs_positions[1] +
((nurbs_positions[0] - nurbs_positions[1]) / 3));
}
else {
handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]);
handle_positions.append(nurbs_positions[1]);
}
const int segments_num = nurbs_positions_num - 1;
const bool ignore_interior_segment = segments_num == 3 && is_periodic == false;
if (ignore_interior_segment == false) {
const float mid_offset = (float)(segments_num - 1) / 2.0f;
for (const int i : IndexRange(1, segments_num - 2)) {
const int divisor = is_periodic ?
3 :
std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f));
const float3 &p1 = nurbs_positions[i];
const float3 &p2 = nurbs_positions[i + 1];
const float3 displacement = (p2 - p1) / divisor;
const int num_handles_on_segment = divisor < 3 ? 1 : 2;
for (int j : IndexRange(1, num_handles_on_segment)) {
handle_positions.append(p1 + (displacement * j));
}
}
}
const int last_index = nurbs_positions_num - 1;
if (is_periodic) {
handle_positions.append(
nurbs_positions[last_index - 1] +
((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3));
}
else {
handle_positions.append(nurbs_positions[last_index - 1]);
handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
}
}
return handle_positions;
}
static Array<float3> create_nurbs_to_bezier_positions(const Span<float3> nurbs_positions,
const Span<float3> handle_positions,
const KnotsMode knots_mode)
static void create_nurbs_to_bezier_positions(const Span<float3> nurbs_positions,
const Span<float3> handle_positions,
const KnotsMode knots_mode,
MutableSpan<float3> bezier_positions)
{
if (knots_mode == NURBS_KNOT_MODE_BEZIER) {
/* Every third NURBS position (starting from index 1) should be converted to Bezier position */
const int scale = 3;
const int offset = 1;
Array<float3> bezier_positions((nurbs_positions.size() + offset) / scale);
scale_input_assign(nurbs_positions, scale, offset, bezier_positions.as_mutable_span());
return bezier_positions;
}
Array<float3> bezier_positions(handle_positions.size() / 2);
for (const int i : IndexRange(bezier_positions.size())) {
bezier_positions[i] = math::interpolate(
handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f);
}
return bezier_positions;
}
static SplinePtr convert_to_poly_spline(const Spline &input)
{
std::unique_ptr<PolySpline> output = std::make_unique<PolySpline>();
output->resize(input.positions().size());
output->positions().copy_from(input.positions());
output->radii().copy_from(input.radii());
output->tilts().copy_from(input.tilts());
Spline::copy_base_settings(input, *output);
output->attributes = input.attributes;
return output;
}
static SplinePtr poly_to_nurbs(const Spline &input)
{
std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>();
output->resize(input.positions().size());
output->positions().copy_from(input.positions());
output->radii().copy_from(input.radii());
output->tilts().copy_from(input.tilts());
output->weights().fill(1.0f);
output->set_resolution(12);
output->set_order(4);
Spline::copy_base_settings(input, *output);
output->knots_mode = NURBS_KNOT_MODE_BEZIER;
output->attributes = input.attributes;
return output;
}
static SplinePtr bezier_to_nurbs(const Spline &input)
{
const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(input);
std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>();
output->resize(input.size() * 3);
scale_output_assign(bezier_spline.handle_positions_left(), 3, 0, output->positions());
scale_output_assign(input.radii(), 3, 0, output->radii());
scale_output_assign(input.tilts(), 3, 0, output->tilts());
scale_output_assign(bezier_spline.positions(), 3, 1, output->positions());
scale_output_assign(input.radii(), 3, 1, output->radii());
scale_output_assign(input.tilts(), 3, 1, output->tilts());
scale_output_assign(bezier_spline.handle_positions_right(), 3, 2, output->positions());
scale_output_assign(input.radii(), 3, 2, output->radii());
scale_output_assign(input.tilts(), 3, 2, output->tilts());
Spline::copy_base_settings(input, *output);
output->weights().fill(1.0f);
output->set_resolution(12);
output->set_order(4);
output->set_cyclic(input.is_cyclic());
output->knots_mode = NURBS_KNOT_MODE_BEZIER;
output->attributes.reallocate(output->size());
copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) {
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
scale_output_assign<T>(src.typed<T>(), 3, 0, dst.typed<T>());
scale_output_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>());
scale_output_assign<T>(src.typed<T>(), 3, 2, dst.typed<T>());
});
});
return output;
}
static SplinePtr poly_to_bezier(const Spline &input)
{
std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>();
output->resize(input.size());
output->positions().copy_from(input.positions());
output->radii().copy_from(input.radii());
output->tilts().copy_from(input.tilts());
output->handle_types_left().fill(BEZIER_HANDLE_VECTOR);
output->handle_types_right().fill(BEZIER_HANDLE_VECTOR);
output->set_resolution(12);
Spline::copy_base_settings(input, *output);
output->attributes = input.attributes;
return output;
}
static SplinePtr nurbs_to_bezier(const Spline &input)
{
const NURBSpline &nurbs_spline = static_cast<const NURBSpline &>(input);
Span<float3> nurbs_positions;
Vector<float3> nurbs_positions_vector;
KnotsMode knots_mode;
if (nurbs_spline.is_cyclic()) {
nurbs_positions_vector = nurbs_spline.positions();
nurbs_positions_vector.append(nurbs_spline.positions()[0]);
nurbs_positions_vector.append(nurbs_spline.positions()[1]);
nurbs_positions = nurbs_positions_vector;
knots_mode = NURBS_KNOT_MODE_NORMAL;
}
else {
nurbs_positions = nurbs_spline.positions();
knots_mode = nurbs_spline.knots_mode;
}
const Vector<float3> handle_positions = create_nurbs_to_bezier_handles(nurbs_positions,
knots_mode);
BLI_assert(handle_positions.size() % 2 == 0);
const Array<float3> bezier_positions = create_nurbs_to_bezier_positions(
nurbs_positions, handle_positions.as_span(), knots_mode);
BLI_assert(handle_positions.size() == bezier_positions.size() * 2);
std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>();
output->resize(bezier_positions.size());
output->positions().copy_from(bezier_positions);
nurbs_to_bezier_assign(nurbs_spline.radii(), output->radii(), knots_mode);
nurbs_to_bezier_assign(nurbs_spline.tilts(), output->tilts(), knots_mode);
scale_input_assign(handle_positions.as_span(), 2, 0, output->handle_positions_left());
scale_input_assign(handle_positions.as_span(), 2, 1, output->handle_positions_right());
output->handle_types_left().fill(BEZIER_HANDLE_ALIGN);
output->handle_types_right().fill(BEZIER_HANDLE_ALIGN);
output->set_resolution(nurbs_spline.resolution());
Spline::copy_base_settings(nurbs_spline, *output);
output->attributes.reallocate(output->size());
copy_attributes(nurbs_spline, *output, [knots_mode](GSpan src, GMutableSpan dst) {
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
nurbs_to_bezier_assign(src.typed<T>(), dst.typed<T>(), knots_mode);
});
});
return output;
}
static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params)
{
switch (input.type()) {
case CURVE_TYPE_BEZIER:
return input.copy();
case CURVE_TYPE_POLY:
return poly_to_bezier(input);
case CURVE_TYPE_NURBS:
if (input.size() < 4) {
params.error_message_add(
NodeWarningType::Info,
TIP_("NURBS must have minimum of 4 points for Bezier Conversion"));
return input.copy();
}
return nurbs_to_bezier(input);
case CURVE_TYPE_CATMULL_ROM: {
BLI_assert_unreachable();
return {};
if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
for (const int i : bezier_positions.index_range()) {
bezier_positions[i] = math::interpolate(
handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f);
}
}
BLI_assert_unreachable();
return {};
else {
/* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */
scale_input_assign(nurbs_positions, 3, 1, bezier_positions);
}
}
static SplinePtr convert_to_nurbs(const Spline &input)
static int to_bezier_size(const CurveType src_type,
const bool cyclic,
const KnotsMode knots_mode,
const int src_size)
{
switch (input.type()) {
case CURVE_TYPE_NURBS:
return input.copy();
case CURVE_TYPE_BEZIER:
return bezier_to_nurbs(input);
case CURVE_TYPE_POLY:
return poly_to_nurbs(input);
case CURVE_TYPE_CATMULL_ROM:
BLI_assert_unreachable();
return {};
switch (src_type) {
case CURVE_TYPE_NURBS: {
if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
return cyclic ? src_size : src_size - 2;
}
return (src_size + 1) / 3;
}
default:
return src_size;
}
}
static int to_nurbs_size(const CurveType src_type, const int src_size)
{
switch (src_type) {
case CURVE_TYPE_BEZIER:
case CURVE_TYPE_CATMULL_ROM:
return src_size * 3;
default:
return src_size;
}
}
static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan<int> sizes)
{
threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
sizes[i] = curves.points_for_curve(i).size();
}
});
}
struct GenericAttributes : NonCopyable, NonMovable {
Vector<GSpan> src;
Vector<GMutableSpan> dst;
Vector<OutputAttribute> attributes;
};
static void retrieve_generic_point_attributes(const CurveComponent &src_component,
CurveComponent &dst_component,
GenericAttributes &attributes)
{
src_component.attribute_foreach(
[&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (meta_data.domain != ATTR_DOMAIN_POINT) {
/* Curve domain attributes are all copied directly to the result in one step. */
return true;
}
if (src_component.attribute_is_builtin(id)) {
if (!(id.is_named() && ELEM(id, "tilt", "radius"))) {
return true;
}
}
GVArray src_attribute = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT);
BLI_assert(src_attribute);
attributes.src.append(src_attribute.get_internal_span());
OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
id, ATTR_DOMAIN_POINT, meta_data.data_type);
attributes.dst.append(dst_attribute.as_span());
attributes.attributes.append(std::move(dst_attribute));
return true;
});
}
static void convert_to_bezier(const CurveComponent &src_component,
const bke::CurvesGeometry &src_curves,
const IndexMask selection,
CurveComponent &dst_component,
bke::CurvesGeometry &dst_curves)
{
const VArray<int8_t> src_knot_modes = src_curves.nurbs_knots_modes();
const VArray<int8_t> src_types = src_curves.curve_types();
const VArray<bool> src_cyclic = src_curves.cyclic();
const Span<float3> src_positions = src_curves.positions();
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
dst_offsets[i] = to_bezier_size(
CurveType(src_types[i]), src_cyclic[i], KnotsMode(src_knot_modes[i]), dst_offsets[i]);
}
});
bke::curves::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
GenericAttributes attributes;
retrieve_generic_point_attributes(src_component, dst_component, attributes);
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
MutableSpan<float3> dst_handles_l = dst_curves.handle_positions_left_for_write();
MutableSpan<float3> dst_handles_r = dst_curves.handle_positions_right_for_write();
MutableSpan<int8_t> dst_types_l = dst_curves.handle_types_left_for_write();
MutableSpan<int8_t> dst_types_r = dst_curves.handle_types_right_for_write();
MutableSpan<float> dst_weights = dst_curves.nurbs_weights_for_write();
auto catmull_rom_to_bezier = [&](IndexMask selection) {
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
catmull_rom_to_bezier_handles(src_positions.slice(src_points),
src_cyclic[i],
dst_handles_l.slice(dst_points),
dst_handles_r.slice(dst_points));
}
});
for (const int i : attributes.src.index_range()) {
bke::curves::copy_point_data(
src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
}
};
auto poly_to_bezier = [&](IndexMask selection) {
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_l);
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_r);
dst_curves.calculate_bezier_auto_handles();
for (const int i : attributes.src.index_range()) {
bke::curves::copy_point_data(
src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
}
};
auto bezier_to_bezier = [&](IndexMask selection) {
const VArray_Span<int8_t> src_types_l = src_curves.handle_types_left();
const VArray_Span<int8_t> src_types_r = src_curves.handle_types_right();
const Span<float3> src_handles_l = src_curves.handle_positions_left();
const Span<float3> src_handles_r = src_curves.handle_positions_right();
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_l, dst_handles_l);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_r, dst_handles_r);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_l, dst_types_l);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_r, dst_types_r);
dst_curves.calculate_bezier_auto_handles();
for (const int i : attributes.src.index_range()) {
bke::curves::copy_point_data(
src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
}
};
auto nurbs_to_bezier = [&](IndexMask selection) {
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
bke::curves::fill_points<float>(dst_curves, selection, 0.0f, dst_weights);
threading::parallel_for(selection.index_range(), 64, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
const Span<float3> src_curve_positions = src_positions.slice(src_points);
KnotsMode knots_mode = KnotsMode(src_knot_modes[i]);
Span<float3> nurbs_positions = src_curve_positions;
Vector<float3> nurbs_positions_vector;
if (src_cyclic[i] && is_nurbs_to_bezier_one_to_one(knots_mode)) {
/* For conversion treat this as periodic closed curve. Extend NURBS hull to first and
* second point which will act as a sceleton for placing Bezier handles. */
nurbs_positions_vector.extend(src_curve_positions);
nurbs_positions_vector.append(src_curve_positions[0]);
nurbs_positions_vector.append(src_curve_positions[1]);
nurbs_positions = nurbs_positions_vector;
knots_mode = NURBS_KNOT_MODE_NORMAL;
}
const Vector<float3> handle_positions = create_nurbs_to_bezier_handles(nurbs_positions,
knots_mode);
scale_input_assign(handle_positions.as_span(), 2, 0, dst_handles_l.slice(dst_points));
scale_input_assign(handle_positions.as_span(), 2, 1, dst_handles_r.slice(dst_points));
create_nurbs_to_bezier_positions(
nurbs_positions, handle_positions, knots_mode, dst_positions.slice(dst_points));
}
});
for (const int i_attribute : attributes.src.index_range()) {
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
nurbs_to_bezier_assign(attributes.src[i_attribute].slice(src_points),
KnotsMode(src_knot_modes[i]),
attributes.dst[i_attribute].slice(dst_points));
}
});
}
};
bke::curves::foreach_curve_by_type(src_curves.curve_types(),
src_curves.curve_type_counts(),
selection,
catmull_rom_to_bezier,
poly_to_bezier,
bezier_to_bezier,
nurbs_to_bezier);
const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
src_curves.curves_range());
for (const int i : attributes.src.index_range()) {
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
}
for (OutputAttribute &attribute : attributes.attributes) {
attribute.save();
}
}
static void convert_to_nurbs(const CurveComponent &src_component,
const bke::CurvesGeometry &src_curves,
const IndexMask selection,
CurveComponent &dst_component,
bke::CurvesGeometry &dst_curves)
{
const VArray<int8_t> src_types = src_curves.curve_types();
const VArray<bool> src_cyclic = src_curves.cyclic();
const Span<float3> src_positions = src_curves.positions();
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), dst_offsets[i]);
}
});
bke::curves::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
GenericAttributes attributes;
retrieve_generic_point_attributes(src_component, dst_component, attributes);
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
auto fill_weights_if_necessary = [&](const IndexMask selection) {
if (!src_curves.nurbs_weights().is_empty()) {
bke::curves::fill_points(dst_curves, selection, 1.0f, dst_curves.nurbs_weights_for_write());
}
};
auto catmull_rom_to_nurbs = [&](IndexMask selection) {
dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
fill_weights_if_necessary(selection);
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
catmull_rom_to_nurbs_positions(
src_positions.slice(src_points), src_cyclic[i], dst_positions.slice(dst_points));
}
});
for (const int i_attribute : attributes.src.index_range()) {
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
attributes.dst[i_attribute].slice(dst_points));
}
});
}
};
auto poly_to_nurbs = [&](IndexMask selection) {
dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
fill_weights_if_necessary(selection);
/* Avoid using "Endpoint" knots modes for cyclic curves, since it adds a sharp point at the
* start/end. */
if (src_cyclic.is_single()) {
bke::curves::fill_points<int8_t>(dst_curves,
selection,
src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL :
NURBS_KNOT_MODE_ENDPOINT,
dst_curves.nurbs_knots_modes_for_write());
}
else {
VArray_Span<bool> cyclic{src_cyclic};
MutableSpan<int8_t> knots_modes = dst_curves.nurbs_knots_modes_for_write();
threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT;
}
});
}
for (const int i_attribute : attributes.src.index_range()) {
bke::curves::copy_point_data(src_curves,
dst_curves,
selection,
attributes.src[i_attribute],
attributes.dst[i_attribute]);
}
};
auto bezier_to_nurbs = [&](IndexMask selection) {
const Span<float3> src_handles_l = src_curves.handle_positions_left();
const Span<float3> src_handles_r = src_curves.handle_positions_right();
dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
fill_weights_if_necessary(selection);
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
bezier_positions_to_nurbs(src_positions.slice(src_points),
src_handles_l.slice(src_points),
src_handles_r.slice(src_points),
dst_positions.slice(dst_points));
}
});
for (const int i_attribute : attributes.src.index_range()) {
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange src_points = src_curves.points_for_curve(i);
const IndexRange dst_points = dst_curves.points_for_curve(i);
bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
attributes.dst[i_attribute].slice(dst_points));
}
});
}
};
auto nurbs_to_nurbs = [&](IndexMask selection) {
bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
if (!src_curves.nurbs_weights().is_empty()) {
bke::curves::copy_point_data(src_curves,
dst_curves,
selection,
src_curves.nurbs_weights(),
dst_curves.nurbs_weights_for_write());
}
for (const int i_attribute : attributes.src.index_range()) {
bke::curves::copy_point_data(src_curves,
dst_curves,
selection,
attributes.src[i_attribute],
attributes.dst[i_attribute]);
}
};
bke::curves::foreach_curve_by_type(src_curves.curve_types(),
src_curves.curve_type_counts(),
selection,
catmull_rom_to_nurbs,
poly_to_nurbs,
bezier_to_nurbs,
nurbs_to_nurbs);
const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert(
src_curves.curves_range());
for (const int i : attributes.src.index_range()) {
bke::curves::copy_point_data(
src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
}
for (OutputAttribute &attribute : attributes.attributes) {
attribute.save();
}
BLI_assert_unreachable();
return {};
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryCurveSplineType &storage = node_storage(params.node());
const GeometryNodeSplineType dst_type = (const GeometryNodeSplineType)storage.spline_type;
const CurveType dst_type = CurveType(storage.spline_type);
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
@ -363,45 +675,58 @@ static void node_geo_exec(GeoNodeExecParams params)
if (!geometry_set.has_curves()) {
return;
}
const CurveComponent &src_component = *geometry_set.get_component_for_read<CurveComponent>();
const Curves &src_curves_id = *src_component.get_for_read();
const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry);
if (src_curves.is_single_type(dst_type)) {
return;
}
const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>();
const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(
*curve_component->get_for_read());
GeometryComponentFieldContext field_context{*curve_component, ATTR_DOMAIN_CURVE};
const int domain_num = curve_component->attribute_domain_num(ATTR_DOMAIN_CURVE);
GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
const int domain_size = src_component.attribute_domain_num(ATTR_DOMAIN_CURVE);
Span<SplinePtr> src_splines = curve->splines();
fn::FieldEvaluator evaluator{field_context, domain_size};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (!conversion_can_change_point_num(dst_type)) {
CurveComponent &dst_component = geometry_set.get_component_for_write<CurveComponent>();
Curves &curves_id = *dst_component.get_for_write();
bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
curves.fill_curve_types(selection, dst_type);
curves.remove_attributes_based_on_types();
return;
}
fn::FieldEvaluator selection_evaluator{field_context, domain_num};
selection_evaluator.add(selection_field);
selection_evaluator.evaluate();
const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0);
Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num());
bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
CurveComponent dst_component;
dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
/* Directly copy curve attributes, since they stay the same (except for curve types). */
CustomData_copy(&src_curves.curve_data,
&dst_curves.curve_data,
CD_MASK_ALL,
CD_DUPLICATE,
src_curves.curves_num());
std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
new_curve->resize(src_splines.size());
dst_curves.fill_curve_types(selection, dst_type);
threading::parallel_for(src_splines.index_range(), 512, [&](IndexRange range) {
for (const int i : range) {
if (selection[i]) {
switch (dst_type) {
case GEO_NODE_SPLINE_TYPE_POLY:
new_curve->splines()[i] = convert_to_poly_spline(*src_splines[i]);
break;
case GEO_NODE_SPLINE_TYPE_BEZIER:
new_curve->splines()[i] = convert_to_bezier(*src_splines[i], params);
break;
case GEO_NODE_SPLINE_TYPE_NURBS:
new_curve->splines()[i] = convert_to_nurbs(*src_splines[i]);
break;
}
}
else {
new_curve->splines()[i] = src_splines[i]->copy();
}
}
});
new_curve->attributes = curve->attributes;
geometry_set.replace_curves(curve_eval_to_curves(*new_curve));
switch (dst_type) {
case CURVE_TYPE_CATMULL_ROM:
case CURVE_TYPE_POLY:
/* Converting to Catmull Rom curves or poly curves should be handled
* above by the optimization to avoid changing the point count. */
BLI_assert_unreachable();
break;
case CURVE_TYPE_BEZIER:
convert_to_bezier(src_component, src_curves, selection, dst_component, dst_curves);
break;
case CURVE_TYPE_NURBS:
convert_to_nurbs(src_component, src_curves, selection, dst_component, dst_curves);
break;
}
geometry_set.replace_curves(dst_curves_id);
});
params.set_output("Curve", std::move(geometry_set));