diff options
-rw-r--r-- | source/blender/blenkernel/BKE_blender_version.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_curves.hh | 12 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_curves_utils.hh | 58 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curves_geometry.cc | 34 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curves_utils.cc | 84 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_index_mask.hh | 2 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_300.c | 50 | ||||
-rw-r--r-- | source/blender/geometry/intern/resample_curves.cc | 62 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 6 | ||||
-rw-r--r-- | source/blender/makesrna/RNA_enum_items.h | 2 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_curves.c | 8 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 10 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc | 811 |
13 files changed, 815 insertions, 326 deletions
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index ab13a2e85d0..2cd753da9d3 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -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 diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index dc67f1e7403..28d03f96db1 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -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())); diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index 62b060093e9..f223e173ea9 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -9,9 +9,53 @@ * \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. */ void fill_curve_counts(const bke::CurvesGeometry &curves, @@ -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 diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index eb9b201c5b5..3c060bd1b8f 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -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(); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index 78c2382b62f..802469399ab 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -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 diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh index b87ab0afc98..22bdf090181 100644 --- a/source/blender/blenlib/BLI_index_mask.hh +++ b/source/blender/blenlib/BLI_index_mask.hh @@ -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 diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 76d12cc1109..57240b93ab1 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -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. */ } } diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index 9eaab327e01..36525e1bdf0 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -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(); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 9dc2971b322..25c8a1f1514 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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 diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 7c6a434a97a..61c5c1a6c72 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -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 diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c index b50082056bf..bc3e5203ed0 100644 --- a/source/blender/makesrna/intern/rna_curves.c +++ b/source/blender/makesrna/intern/rna_curves.c @@ -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" diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 841c250df4c..53f207328c9 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -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"); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 500804e41f0..6c95ab3bdc9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -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,22 +194,8 @@ 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) { - for (const int i : IndexRange(nurbs_positions_num)) { - if (i % 3 == 1) { - continue; - } - handle_positions.append(nurbs_positions[i]); - } - if (nurbs_positions_num % 3 == 1) { - handle_positions.pop_last(); - } - else if (nurbs_positions_num % 3 == 2) { - const int last_index = nurbs_positions_num - 1; - handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); - } - } - else { + + 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] + @@ -134,11 +205,15 @@ static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_po 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)); @@ -151,6 +226,7 @@ static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_po } } } + const int last_index = nurbs_positions_num - 1; if (is_periodic) { handle_positions.append( @@ -162,199 +238,435 @@ 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 { + for (const int i : IndexRange(nurbs_positions_num)) { + if (i % 3 == 1) { + continue; + } + handle_positions.append(nurbs_positions[i]); + } + if (nurbs_positions_num % 3 == 1) { + handle_positions.pop_last(); + } + else if (nurbs_positions_num % 3 == 2) { + const int last_index = nurbs_positions_num - 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; + 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); + } } - - 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); + else { + /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */ + scale_input_assign(nurbs_positions, 3, 1, bezier_positions); } - return bezier_positions; } -static SplinePtr convert_to_poly_spline(const Spline &input) +static int to_bezier_size(const CurveType src_type, + const bool cyclic, + const KnotsMode knots_mode, + const int src_size) { - 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; + 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 SplinePtr poly_to_nurbs(const Spline &input) +static int to_nurbs_size(const CurveType src_type, const int src_size) { - 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; + switch (src_type) { + case CURVE_TYPE_BEZIER: + case CURVE_TYPE_CATMULL_ROM: + return src_size * 3; + default: + return src_size; + } } -static SplinePtr bezier_to_nurbs(const Spline &input) +static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan<int> sizes) { - 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>()); - }); + threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) { + for (const int i : range) { + sizes[i] = curves.points_for_curve(i).size(); + } }); - return output; } -static SplinePtr poly_to_bezier(const Spline &input) +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) { - 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; + 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 SplinePtr nurbs_to_bezier(const Spline &input) +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 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; + 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]); } - else { - nurbs_positions = nurbs_spline.positions(); - knots_mode = nurbs_spline.knots_mode; + + for (OutputAttribute &attribute : attributes.attributes) { + attribute.save(); } - 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) +static void convert_to_nurbs(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + CurveComponent &dst_component, + bke::CurvesGeometry &dst_curves) { - 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(); + 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)); } - return nurbs_to_bezier(input); - case CURVE_TYPE_CATMULL_ROM: { - BLI_assert_unreachable(); - return {}; + }); + + 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]); } - BLI_assert_unreachable(); - return {}; -} -static SplinePtr convert_to_nurbs(const Spline &input) -{ - 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 {}; + 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); - - Span<SplinePtr> src_splines = curve->splines(); - - 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); - - std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); - new_curve->resize(src_splines.size()); - - 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)); + GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE}; + const int domain_size = src_component.attribute_domain_num(ATTR_DOMAIN_CURVE); + + 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; + } + + 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()); + + dst_curves.fill_curve_types(selection, dst_type); + + 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)); |