diff options
author | Hans Goudey <h.goudey@me.com> | 2022-07-05 23:51:12 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2022-07-05 23:51:12 +0300 |
commit | 7688f0ace7a6c45aaa8304d2a26a760be0056aa6 (patch) | |
tree | d101d9e6321d5e4acf9a4f4f3c23ffd00ea32658 | |
parent | c52a18abf84b29ca19aa79ef1ce580e67a437779 (diff) |
Curves: Move type conversion to the geometry module
This helps to separate concerns, and makes the functionality
available for edit mode.
-rw-r--r-- | source/blender/geometry/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/geometry/GEO_set_curve_type.hh | 40 | ||||
-rw-r--r-- | source/blender/geometry/intern/set_curve_type.cc | 711 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc | 672 |
4 files changed, 764 insertions, 661 deletions
diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index f0fb5c5c9af..21b2071d0e6 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -24,6 +24,7 @@ set(SRC intern/realize_instances.cc intern/resample_curves.cc intern/reverse_uv_sampler.cc + intern/set_curve_type.cc intern/uv_parametrizer.c GEO_add_curves_on_mesh.hh @@ -35,6 +36,7 @@ set(SRC GEO_realize_instances.hh GEO_resample_curves.hh GEO_reverse_uv_sampler.hh + GEO_set_curve_type.hh GEO_uv_parametrizer.h ) diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh new file mode 100644 index 00000000000..f7ac8be5889 --- /dev/null +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_curves_types.h" + +#include "BLI_function_ref.hh" +#include "BLI_index_mask.hh" + +struct Curves; +struct CurveComponent; + +namespace blender::bke { +class CurvesGeometry; +} + +namespace blender::geometry { + +/** + * Try the conversion to the #dst_type-- avoiding the majority of the work done in + * #convert_curves by modifying an existing object in place rather than creating a new one. + * + * \note This function is necessary because attributes do not have proper support for CoW. + * + * \param get_writable_curves_fn: Should return the write-able curves to change directly if + * possible. This is a function in order to avoid the cost of retrieval when unnecessary. + */ +bool try_curves_conversion_in_place(IndexMask selection, + CurveType dst_type, + FunctionRef<Curves &()> get_writable_curves_fn); + +/** + * Change the types of the selected curves, potentially changing the total point count. + */ +Curves *convert_curves(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + IndexMask selection, + CurveType dst_type); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc new file mode 100644 index 00000000000..d7a5bc9b27d --- /dev/null +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -0,0 +1,711 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_curves_utils.hh" +#include "BKE_geometry_set.hh" + +#include "BLI_task.hh" + +#include "GEO_set_curve_type.hh" + +namespace blender::geometry { + +/** + * 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, + const int offset, + MutableSpan<T> dst) +{ + for (const int i : dst.index_range()) { + dst[i] = src[i * scale + offset]; + } +} + +/** + * 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 * 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_NORMAL: + for (const int i : dst.index_range()) { + dst[i] = src[(i + 1) % src.size()]; + } + break; + case NURBS_KNOT_MODE_ENDPOINT: + for (const int i : dst.index_range().drop_back(1).drop_front(1)) { + dst[i] = src[i + 1]; + } + dst.first() = src.first(); + dst.last() = src.last(); + break; + default: + /* Every 3rd NURBS position (starting from index 1) should have its attributes transferred. + */ + scale_input_assign<T>(src, 3, 1, dst); + } +} + +static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, 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); + }); +} + +static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_positions, + const KnotsMode knots_mode) +{ + const int nurbs_positions_num = nurbs_positions.size(); + Vector<float3> handle_positions; + + if (is_nurbs_to_bezier_one_to_one(knots_mode)) { + const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL; + if (is_periodic) { + handle_positions.append(nurbs_positions[1] + + ((nurbs_positions[0] - nurbs_positions[1]) / 3)); + } + else { + handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]); + handle_positions.append(nurbs_positions[1]); + } + + /* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on + * endpoints, midpoints or 1/3 of the distance of a hull segment. */ + const int segments_num = nurbs_positions_num - 1; + const bool ignore_interior_segment = segments_num == 3 && is_periodic == false; + if (ignore_interior_segment == false) { + const float mid_offset = (float)(segments_num - 1) / 2.0f; + for (const int i : IndexRange(1, segments_num - 2)) { + /* Divisor can have values: 1, 2 or 3. */ + const int divisor = is_periodic ? + 3 : + std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f)); + const float3 &p1 = nurbs_positions[i]; + const float3 &p2 = nurbs_positions[i + 1]; + const float3 displacement = (p2 - p1) / divisor; + const int num_handles_on_segment = divisor < 3 ? 1 : 2; + for (int j : IndexRange(1, num_handles_on_segment)) { + handle_positions.append(p1 + (displacement * j)); + } + } + } + + const int last_index = nurbs_positions_num - 1; + if (is_periodic) { + handle_positions.append( + nurbs_positions[last_index - 1] + + ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3)); + } + else { + handle_positions.append(nurbs_positions[last_index - 1]); + handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); + } + } + else { + for (const int i : IndexRange(nurbs_positions_num)) { + if (i % 3 == 1) { + continue; + } + 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 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 (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); + } + } + else { + /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */ + scale_input_assign(nurbs_positions, 3, 1, bezier_positions); + } +} + +static int to_bezier_size(const CurveType src_type, + const bool cyclic, + const KnotsMode knots_mode, + const int src_size) +{ + switch (src_type) { + case CURVE_TYPE_NURBS: { + if (is_nurbs_to_bezier_one_to_one(knots_mode)) { + return cyclic ? src_size : src_size - 2; + } + return (src_size + 1) / 3; + } + default: + return src_size; + } +} + +static int to_nurbs_size(const CurveType src_type, const int src_size) +{ + switch (src_type) { + case CURVE_TYPE_BEZIER: + case CURVE_TYPE_CATMULL_ROM: + return src_size * 3; + default: + return src_size; + } +} + +static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan<int> sizes) +{ + threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) { + for (const int i : range) { + sizes[i] = curves.points_for_curve(i).size(); + } + }); +} + +struct GenericAttributes : NonCopyable, NonMovable { + Vector<GSpan> src; + Vector<GMutableSpan> dst; + + Vector<bke::OutputAttribute> attributes; +}; + +static void retrieve_generic_point_attributes(const CurveComponent &src_component, + CurveComponent &dst_component, + GenericAttributes &attributes) +{ + src_component.attribute_foreach( + [&](const bke::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()); + + bke::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 Curves *create_result_curves(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) +{ + 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); + + return dst_curves_id; +} + +static Curves *convert_curves_to_bezier(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + const IndexMask selection) +{ + 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(); + + Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_BEZIER); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + + 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 VArraySpan<int8_t> src_types_l = src_curves.handle_types_left(); + const VArraySpan<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 skeleton for placing Bezier handles. */ + nurbs_positions_vector.extend(src_curve_positions); + nurbs_positions_vector.append(src_curve_positions[0]); + nurbs_positions_vector.append(src_curve_positions[1]); + nurbs_positions = nurbs_positions_vector; + knots_mode = NURBS_KNOT_MODE_NORMAL; + } + + const Vector<float3> handle_positions = create_nurbs_to_bezier_handles(nurbs_positions, + knots_mode); + + scale_input_assign(handle_positions.as_span(), 2, 0, dst_handles_l.slice(dst_points)); + scale_input_assign(handle_positions.as_span(), 2, 1, dst_handles_r.slice(dst_points)); + + create_nurbs_to_bezier_positions( + nurbs_positions, handle_positions, knots_mode, dst_positions.slice(dst_points)); + } + }); + + for (const int i_attribute : attributes.src.index_range()) { + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + nurbs_to_bezier_assign(attributes.src[i_attribute].slice(src_points), + KnotsMode(src_knot_modes[i]), + attributes.dst[i_attribute].slice(dst_points)); + } + }); + } + }; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + catmull_rom_to_bezier, + poly_to_bezier, + bezier_to_bezier, + nurbs_to_bezier); + + const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range()); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + + for (bke::OutputAttribute &attribute : attributes.attributes) { + attribute.save(); + } + + return dst_curves_id; +} + +static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + const IndexMask selection) +{ + 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(); + + Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_NURBS); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + + 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()) { + dst_curves.nurbs_knots_modes_for_write().fill_indices( + selection, + src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT); + } + else { + VArraySpan<bool> cyclic{src_cyclic}; + MutableSpan<int8_t> knots_modes = dst_curves.nurbs_knots_modes_for_write(); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT; + } + }); + } + + for (const int i_attribute : attributes.src.index_range()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + attributes.src[i_attribute], + attributes.dst[i_attribute]); + } + }; + + auto bezier_to_nurbs = [&](IndexMask selection) { + const Span<float3> src_handles_l = src_curves.handle_positions_left(); + const Span<float3> src_handles_r = src_curves.handle_positions_right(); + + dst_curves.nurbs_orders_for_write().fill_indices(selection, 4); + dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER); + fill_weights_if_necessary(selection); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + bezier_positions_to_nurbs(src_positions.slice(src_points), + src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + dst_positions.slice(dst_points)); + } + }); + + for (const int i_attribute : attributes.src.index_range()) { + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points), + attributes.dst[i_attribute].slice(dst_points)); + } + }); + } + }; + + auto nurbs_to_nurbs = [&](IndexMask selection) { + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + + if (!src_curves.nurbs_weights().is_empty()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + src_curves.nurbs_weights(), + dst_curves.nurbs_weights_for_write()); + } + + for (const int i_attribute : attributes.src.index_range()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + attributes.src[i_attribute], + attributes.dst[i_attribute]); + } + }; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + catmull_rom_to_nurbs, + poly_to_nurbs, + bezier_to_nurbs, + nurbs_to_nurbs); + + const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range()); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + + for (bke::OutputAttribute &attribute : attributes.attributes) { + attribute.save(); + } + + return dst_curves_id; +} + +static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) +{ + bke::CurvesGeometry dst_curves(src_curves); + dst_curves.fill_curve_types(selection, dst_type); + dst_curves.remove_attributes_based_on_types(); + return dst_curves; +} + +Curves *convert_curves(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) +{ + switch (dst_type) { + case CURVE_TYPE_CATMULL_ROM: + case CURVE_TYPE_POLY: + return bke::curves_new_nomain(convert_curves_trivial(src_curves, selection, dst_type)); + case CURVE_TYPE_BEZIER: + return convert_curves_to_bezier(src_component, src_curves, selection); + case CURVE_TYPE_NURBS: + return convert_curves_to_nurbs(src_component, src_curves, selection); + } + BLI_assert_unreachable(); + return nullptr; +} + +bool try_curves_conversion_in_place(const IndexMask selection, + const CurveType dst_type, + FunctionRef<Curves &()> get_writable_curves_fn) +{ + if (conversion_can_change_point_num(dst_type)) { + return false; + } + Curves &curves_id = get_writable_curves_fn(); + bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + curves.fill_curve_types(selection, dst_type); + curves.remove_attributes_based_on_types(); + return true; +} + +} // namespace blender::geometry 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 5c836391abe..183c98e9c9f 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,16 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include <numeric> - -#include "BKE_attribute_math.hh" #include "BKE_curves.hh" -#include "BKE_curves_utils.hh" - -#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" +#include "GEO_set_curve_type.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_curve_spline_type_cc { @@ -37,625 +33,6 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node) 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, - const int offset, - MutableSpan<T> dst) -{ - for (const int i : dst.index_range()) { - dst[i] = src[i * scale + offset]; - } -} - -/** - * 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 * 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_NORMAL: - for (const int i : dst.index_range()) { - dst[i] = src[(i + 1) % src.size()]; - } - break; - case NURBS_KNOT_MODE_ENDPOINT: - for (const int i : dst.index_range().drop_back(1).drop_front(1)) { - dst[i] = src[i + 1]; - } - dst.first() = src.first(); - dst.last() = src.last(); - break; - default: - /* Every 3rd NURBS position (starting from index 1) should have its attributes transferred. - */ - scale_input_assign<T>(src, 3, 1, dst); - } -} - -static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, 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); - }); -} - -static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_positions, - const KnotsMode knots_mode) -{ - const int nurbs_positions_num = nurbs_positions.size(); - Vector<float3> handle_positions; - - if (is_nurbs_to_bezier_one_to_one(knots_mode)) { - const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL; - if (is_periodic) { - handle_positions.append(nurbs_positions[1] + - ((nurbs_positions[0] - nurbs_positions[1]) / 3)); - } - else { - handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]); - handle_positions.append(nurbs_positions[1]); - } - - /* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on - * endpoints, midpoints or 1/3 of the distance of a hull segment. */ - const int segments_num = nurbs_positions_num - 1; - const bool ignore_interior_segment = segments_num == 3 && is_periodic == false; - if (ignore_interior_segment == false) { - const float mid_offset = (float)(segments_num - 1) / 2.0f; - for (const int i : IndexRange(1, segments_num - 2)) { - /* Divisor can have values: 1, 2 or 3. */ - const int divisor = is_periodic ? - 3 : - std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f)); - const float3 &p1 = nurbs_positions[i]; - const float3 &p2 = nurbs_positions[i + 1]; - const float3 displacement = (p2 - p1) / divisor; - const int num_handles_on_segment = divisor < 3 ? 1 : 2; - for (int j : IndexRange(1, num_handles_on_segment)) { - handle_positions.append(p1 + (displacement * j)); - } - } - } - - const int last_index = nurbs_positions_num - 1; - if (is_periodic) { - handle_positions.append( - nurbs_positions[last_index - 1] + - ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3)); - } - else { - handle_positions.append(nurbs_positions[last_index - 1]); - handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); - } - } - else { - for (const int i : IndexRange(nurbs_positions_num)) { - if (i % 3 == 1) { - continue; - } - 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 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 (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); - } - } - else { - /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */ - scale_input_assign(nurbs_positions, 3, 1, bezier_positions); - } -} - -static int to_bezier_size(const CurveType src_type, - const bool cyclic, - const KnotsMode knots_mode, - const int src_size) -{ - switch (src_type) { - case CURVE_TYPE_NURBS: { - if (is_nurbs_to_bezier_one_to_one(knots_mode)) { - return cyclic ? src_size : src_size - 2; - } - return (src_size + 1) / 3; - } - default: - return src_size; - } -} - -static int to_nurbs_size(const CurveType src_type, const int src_size) -{ - switch (src_type) { - case CURVE_TYPE_BEZIER: - case CURVE_TYPE_CATMULL_ROM: - return src_size * 3; - default: - return src_size; - } -} - -struct GenericAttributes : NonCopyable, NonMovable { - Vector<GSpan> src; - Vector<GMutableSpan> dst; - - Vector<OutputAttribute> attributes; -}; - -static void retrieve_generic_point_attributes(const CurveComponent &src_component, - CurveComponent &dst_component, - GenericAttributes &attributes) -{ - src_component.attribute_foreach( - [&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.domain != ATTR_DOMAIN_POINT) { - /* Curve domain attributes are all copied directly to the result in one step. */ - return true; - } - if (src_component.attribute_is_builtin(id)) { - if (!(id.is_named() && ELEM(id, "tilt", "radius"))) { - return true; - } - } - - GVArray src_attribute = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT); - BLI_assert(src_attribute); - attributes.src.append(src_attribute.get_internal_span()); - - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - id, ATTR_DOMAIN_POINT, meta_data.data_type); - attributes.dst.append(dst_attribute.as_span()); - attributes.attributes.append(std::move(dst_attribute)); - - return true; - }); -} - -static void convert_to_bezier(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - CurveComponent &dst_component, - bke::CurvesGeometry &dst_curves) -{ - const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( - src_curves.curves_range()); - - 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(); - bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write()); - threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { - for (const int i : selection.slice(range)) { - const CurveType type = CurveType(src_types[i]); - const KnotsMode knots_mode = KnotsMode(src_knot_modes[i]); - const IndexRange points = src_curves.points_for_curve(i); - dst_offsets[i] = to_bezier_size(type, src_cyclic[i], knots_mode, points.size()); - } - }); - 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 VArraySpan<int8_t> src_types_l = src_curves.handle_types_left(); - const VArraySpan<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 skeleton 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); - - for (const int i : attributes.src.index_range()) { - bke::curves::copy_point_data( - src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); - } - - for (OutputAttribute &attribute : attributes.attributes) { - attribute.save(); - } -} - -static void convert_to_nurbs(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - CurveComponent &dst_component, - bke::CurvesGeometry &dst_curves) -{ - const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( - src_curves.curves_range()); - - 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(); - bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write()); - threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { - for (const int i : selection.slice(range)) { - const IndexRange points = src_curves.points_for_curve(i); - dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), points.size()); - } - }); - 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()) { - dst_curves.nurbs_knots_modes_for_write().fill_indices( - selection, - src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT); - } - else { - VArraySpan<bool> cyclic{src_cyclic}; - MutableSpan<int8_t> knots_modes = dst_curves.nurbs_knots_modes_for_write(); - threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { - for (const int i : selection.slice(range)) { - knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT; - } - }); - } - - for (const int i_attribute : attributes.src.index_range()) { - bke::curves::copy_point_data(src_curves, - dst_curves, - selection, - attributes.src[i_attribute], - attributes.dst[i_attribute]); - } - }; - - auto bezier_to_nurbs = [&](IndexMask selection) { - const Span<float3> src_handles_l = src_curves.handle_positions_left(); - const Span<float3> src_handles_r = src_curves.handle_positions_right(); - - dst_curves.nurbs_orders_for_write().fill_indices(selection, 4); - dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER); - fill_weights_if_necessary(selection); - - threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { - for (const int i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(i); - const IndexRange dst_points = dst_curves.points_for_curve(i); - bezier_positions_to_nurbs(src_positions.slice(src_points), - src_handles_l.slice(src_points), - src_handles_r.slice(src_points), - dst_positions.slice(dst_points)); - } - }); - - for (const int i_attribute : attributes.src.index_range()) { - threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { - for (const int i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(i); - const IndexRange dst_points = dst_curves.points_for_curve(i); - bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points), - attributes.dst[i_attribute].slice(dst_points)); - } - }); - } - }; - - auto nurbs_to_nurbs = [&](IndexMask selection) { - bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); - - if (!src_curves.nurbs_weights().is_empty()) { - bke::curves::copy_point_data(src_curves, - dst_curves, - selection, - src_curves.nurbs_weights(), - dst_curves.nurbs_weights_for_write()); - } - - for (const int i_attribute : attributes.src.index_range()) { - bke::curves::copy_point_data(src_curves, - dst_curves, - selection, - attributes.src[i_attribute], - attributes.dst[i_attribute]); - } - }; - - bke::curves::foreach_curve_by_type(src_curves.curve_types(), - src_curves.curve_type_counts(), - selection, - catmull_rom_to_nurbs, - poly_to_nurbs, - bezier_to_nurbs, - nurbs_to_nurbs); - - for (const int i : attributes.src.index_range()) { - bke::curves::copy_point_data( - src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); - } - - for (OutputAttribute &attribute : attributes.attributes) { - attribute.save(); - } -} - static void node_geo_exec(GeoNodeExecParams params) { const NodeGeometryCurveSplineType &storage = node_storage(params.node()); @@ -676,50 +53,23 @@ static void node_geo_exec(GeoNodeExecParams params) } 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}; + fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; 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(); + if (selection.is_empty()) { 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; + if (geometry::try_curves_conversion_in_place(selection, dst_type, [&]() -> Curves & { + return *geometry_set.get_curves_for_write(); + })) { + return; } - geometry_set.replace_curves(dst_curves_id); + Curves *dst_curves = geometry::convert_curves(src_component, src_curves, selection, dst_type); + + geometry_set.replace_curves(dst_curves); }); params.set_output("Curve", std::move(geometry_set)); |