diff options
author | Hans Goudey <h.goudey@me.com> | 2022-06-08 16:37:46 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2022-06-08 16:37:46 +0300 |
commit | 9e393fc2f12583d32dddf00bad8174d2bb06b61d (patch) | |
tree | b73ae3921fba6d76060ad484d2de87e010f91c9b /source/blender/nodes | |
parent | 520be607e8baf4dacfadc1897013c84e77b81b27 (diff) |
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
Diffstat (limited to 'source/blender/nodes')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc | 811 |
1 files changed, 568 insertions, 243 deletions
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)); |