From 3f2c4db95105a1abbbcdcfdd6d5895c22afaddb0 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 25 Apr 2024 10:56:43 +0200 Subject: [PATCH] Curves: support more curve type conversion options in edit mode This adds a new `Handles` checkbox to the conversion operator that affects how the conversion works in the following cases: `Bezier -> Catmull Rom / Poly / Nurbs` and `Catmull Rom -> Nurbs`. If enabled, three control points are added for each original control point, otherwise only one. ----- The images show the effect of the toggle. The top result is always the one with handles and the bottom one without. * `Bezier -> Poly` ![image](/attachments/c4833568-fb8a-415e-b4fc-a8af2002ded8) * `Bezier -> Catmull Rom` ![image](/attachments/df62e4c0-1a88-4f04-aa82-506bc40765a2) * `Bezier -> Nurbs` ![image](/attachments/3b78d49d-c840-4c15-a342-050fb1f5c3f5) * `Catmull Rom -> Nurbs` ![image](/attachments/de9a4c08-d442-4f97-a0e0-6393b0f0e6de) Pull Request: https://projects.blender.org/blender/blender/pulls/120423 --- .../editors/curves/intern/curves_ops.cc | 15 +- source/blender/geometry/GEO_set_curve_type.hh | 18 +- .../blender/geometry/intern/set_curve_type.cc | 162 +++++++++++++++++- 3 files changed, 190 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 1f5dc41fe0f..5e1beda9abb 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -1394,6 +1394,7 @@ namespace curve_type_set { static int exec(bContext *C, wmOperator *op) { const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type")); + const bool use_handles = RNA_boolean_get(op->ptr, "use_handles"); for (Curves *curves_id : get_unique_editable_curves(*C)) { bke::CurvesGeometry &curves = curves_id->geometry.wrap(); @@ -1403,7 +1404,13 @@ static int exec(bContext *C, wmOperator *op) continue; } - curves = geometry::convert_curves(curves, selection, dst_type, {}); + geometry::ConvertCurvesOptions options; + options.convert_bezier_handles_to_poly_points = use_handles; + options.convert_bezier_handles_to_catmull_rom_points = use_handles; + options.keep_bezier_shape_as_nurbs = use_handles; + options.keep_catmull_rom_shape_as_nurbs = use_handles; + + curves = geometry::convert_curves(curves, selection, dst_type, {}, options); DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); @@ -1426,6 +1433,12 @@ static void CURVES_OT_curve_type_set(wmOperatorType *ot) ot->prop = RNA_def_enum( ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type"); + + RNA_def_boolean(ot->srna, + "use_handles", + false, + "Handles", + "Take handle information into account in the conversion"); } namespace switch_direction { diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh index eec5c584d3d..31a1e471b0d 100644 --- a/source/blender/geometry/GEO_set_curve_type.hh +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -11,12 +11,28 @@ namespace blender::geometry { +struct ConvertCurvesOptions { + bool convert_bezier_handles_to_poly_points = false; + bool convert_bezier_handles_to_catmull_rom_points = false; + /** + * Make the nurb curve behave like a bezier curve and also keep the handle positions as control + * points. + */ + bool keep_bezier_shape_as_nurbs = true; + /** + * Keep the exact shape of the catmull rom curve by inserting extra handle control points in the + * nurbs curve. + */ + bool keep_catmull_rom_shape_as_nurbs = true; +}; + /** * Change the types of the selected curves, potentially changing the total point count. */ bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, const IndexMask &selection, CurveType dst_type, - const bke::AnonymousAttributePropagationInfo &propagation_info); + const bke::AnonymousAttributePropagationInfo &propagation_info, + const ConvertCurvesOptions &options = {}); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index 0ff551af276..bbcf1c2701d 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -629,19 +629,175 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src return dst_curves; } +static bke::CurvesGeometry convert_curves_to_catmull_rom_or_poly( + const bke::CurvesGeometry &src_curves, + const IndexMask &selection, + const CurveType dst_type, + const bke::AnonymousAttributePropagationInfo &propagation_info, + const ConvertCurvesOptions &options) +{ + const bool use_bezier_handles = (dst_type == CURVE_TYPE_CATMULL_ROM) ? + options.convert_bezier_handles_to_catmull_rom_points : + options.convert_bezier_handles_to_poly_points; + if (!use_bezier_handles || !src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + return convert_curves_trivial(src_curves, selection, dst_type); + } + + const OffsetIndices src_points_by_curve = src_curves.points_by_curve(); + const VArray src_types = src_curves.curve_types(); + const VArray src_cyclic = src_curves.cyclic(); + const Span src_positions = src_curves.positions(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + IndexMaskMemory memory; + const IndexMask unselected = selection.complement(src_curves.curves_range(), memory); + + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + dst_curves.fill_curve_types(selection, dst_type); + + MutableSpan dst_offsets = dst_curves.offsets_for_write(); + offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets); + selection.foreach_index(GrainSize(1024), [&](const int i) { + const IndexRange src_points = src_points_by_curve[i]; + const CurveType src_curve_type = CurveType(src_types[i]); + int &size = dst_offsets[i]; + if (src_curve_type == CURVE_TYPE_BEZIER) { + size = src_points.size() * 3; + } + else { + size = src_points.size(); + } + }); + offset_indices::accumulate_counts_to_offsets(dst_offsets); + dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve(); + + MutableSpan dst_positions = dst_curves.positions_for_write(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + Vector generic_attributes = bke::retrieve_attributes_for_transfer( + src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + propagation_info, + {"position", + "handle_type_left", + "handle_type_right", + "handle_right", + "handle_left", + "nurbs_weight"}); + + auto convert_from_catmull_rom_or_poly_or_nurbs = [&](const IndexMask &selection) { + array_utils::copy_group_to_group( + src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); + for (bke::AttributeTransferData &attribute : generic_attributes) { + array_utils::copy_group_to_group( + src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); + } + }; + + auto convert_from_bezier = [&](const IndexMask &selection) { + const Span src_left_handles = src_curves.handle_positions_left(); + const Span src_right_handles = src_curves.handle_positions_right(); + + /* Transfer positions. */ + selection.foreach_index([&](const int curve_i) { + const IndexRange src_points = src_points_by_curve[curve_i]; + const IndexRange dst_points = dst_points_by_curve[curve_i]; + for (const int i : src_points.index_range()) { + const int src_point_i = src_points[i]; + const int dst_points_start = dst_points.start() + 3 * i; + dst_positions[dst_points_start + 0] = src_left_handles[src_point_i]; + dst_positions[dst_points_start + 1] = src_positions[src_point_i]; + dst_positions[dst_points_start + 2] = src_right_handles[src_point_i]; + } + }); + /* Transfer attributes. The handles the same attribute values as their corresponding control + * point. */ + for (bke::AttributeTransferData &attribute : generic_attributes) { + const CPPType &cpp_type = attribute.src.type(); + selection.foreach_index([&](const int curve_i) { + const IndexRange src_points = src_points_by_curve[curve_i]; + const IndexRange dst_points = dst_points_by_curve[curve_i]; + for (const int i : src_points.index_range()) { + const int src_point_i = src_points[i]; + const int dst_points_start = dst_points.start() + 3 * i; + const void *src_value = attribute.src[src_point_i]; + cpp_type.fill_assign_n(src_value, attribute.dst.span[dst_points_start], 3); + } + }); + } + }; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + convert_from_catmull_rom_or_poly_or_nurbs, + convert_from_catmull_rom_or_poly_or_nurbs, + convert_from_bezier, + convert_from_catmull_rom_or_poly_or_nurbs); + + for (bke::AttributeTransferData &attribute : generic_attributes) { + attribute.dst.finish(); + } + + bke::copy_attributes_group_to_group(src_attributes, + bke::AttrDomain::Point, + propagation_info, + {}, + src_points_by_curve, + dst_points_by_curve, + unselected, + dst_attributes); + + return dst_curves; +} + +/** + * Converts some curves to poly curves before they are converted to nurbs. This is useful because + * it discards the bezier/catmull-rom shape which is sometimes the desired behavior. + */ +static bke::CurvesGeometry convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs( + const bke::CurvesGeometry &src_curves, + const IndexMask &selection, + const ConvertCurvesOptions &options) +{ + const VArray src_curve_types = src_curves.curve_types(); + IndexMaskMemory memory; + const IndexMask mask = IndexMask::from_predicate( + selection, GrainSize(4096), memory, [&](const int curve_i) { + const CurveType type = CurveType(src_curve_types[curve_i]); + if (!options.keep_bezier_shape_as_nurbs && type == CURVE_TYPE_BEZIER) { + return true; + } + if (!options.keep_catmull_rom_shape_as_nurbs && type == CURVE_TYPE_CATMULL_ROM) { + return true; + } + return false; + }); + return convert_curves_trivial(src_curves, mask, CURVE_TYPE_POLY); +} + bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, const IndexMask &selection, const CurveType dst_type, - const bke::AnonymousAttributePropagationInfo &propagation_info) + const bke::AnonymousAttributePropagationInfo &propagation_info, + const ConvertCurvesOptions &options) { switch (dst_type) { case CURVE_TYPE_CATMULL_ROM: case CURVE_TYPE_POLY: - return convert_curves_trivial(src_curves, selection, dst_type); + return convert_curves_to_catmull_rom_or_poly( + src_curves, selection, dst_type, propagation_info, options); case CURVE_TYPE_BEZIER: return convert_curves_to_bezier(src_curves, selection, propagation_info); - case CURVE_TYPE_NURBS: + case CURVE_TYPE_NURBS: { + if (!options.keep_bezier_shape_as_nurbs || !options.keep_catmull_rom_shape_as_nurbs) { + const bke::CurvesGeometry tmp_src_curves = + convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs( + src_curves, selection, options); + return convert_curves_to_nurbs(tmp_src_curves, selection, propagation_info); + } return convert_curves_to_nurbs(src_curves, selection, propagation_info); + } } BLI_assert_unreachable(); return {};