From c806f885d441ae36bcbbb2988b1cefb3249cbb7f Mon Sep 17 00:00:00 2001 From: Laurynas Duburas Date: Thu, 31 Oct 2024 11:25:41 +0100 Subject: [PATCH] Curves: Align handles in transform operator Aligns Bezier handles when both handles are of the`BEZIER_HANDLE_ALIGN` type. If the left handle is selected, then the right one is aligned with it. The left handle is aligned with the right handle only if the left handle is not selected. Rel #105038 Pull Request: https://projects.blender.org/blender/blender/pulls/128726 --- source/blender/blenkernel/BKE_curves.hh | 5 ++ .../blender/blenkernel/intern/curve_bezier.cc | 11 +++ .../editors/transform/transform_convert.hh | 3 + .../transform/transform_convert_curves.cc | 67 +++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 32475706068..f0cfa85b891 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -660,6 +660,11 @@ void calculate_auto_handles(bool cyclic, MutableSpan positions_left, MutableSpan positions_right); +void calculate_aligned_handles(const IndexMask &selection, + Span positions, + Span align_by, + MutableSpan align); + /** * Change the handles of a single control point, aligning any aligned (#BEZIER_HANDLE_ALIGN) * handles on the other side of the control point. diff --git a/source/blender/blenkernel/intern/curve_bezier.cc b/source/blender/blenkernel/intern/curve_bezier.cc index de5b80b67c7..eb0ecf18b35 100644 --- a/source/blender/blenkernel/intern/curve_bezier.cc +++ b/source/blender/blenkernel/intern/curve_bezier.cc @@ -181,6 +181,17 @@ void set_handle_position(const float3 &position, } } +void calculate_aligned_handles(const IndexMask &selection, + const Span positions, + const Span align_with, + MutableSpan align_handles) +{ + selection.foreach_index_optimized(GrainSize(4096), [&](const int point) { + align_handles[point] = calculate_aligned_handle( + positions[point], align_with[point], align_handles[point]); + }); +} + void calculate_auto_handles(const bool cyclic, const Span types_left, const Span types_right, diff --git a/source/blender/editors/transform/transform_convert.hh b/source/blender/editors/transform/transform_convert.hh index 9b086395b63..17c2b2545f2 100644 --- a/source/blender/editors/transform/transform_convert.hh +++ b/source/blender/editors/transform/transform_convert.hh @@ -88,6 +88,9 @@ struct TransDataVertSlideVert { struct CurvesTransformData { blender::IndexMaskMemory memory; blender::Vector selection_by_layer; + /* TODO: add support for grease pencil layers. */ + blender::IndexMask aligned_with_left; + blender::IndexMask aligned_with_right; /** * The offsets of every grease pencil layer into `positions` array. diff --git a/source/blender/editors/transform/transform_convert_curves.cc b/source/blender/editors/transform/transform_convert_curves.cc index bdec43bc6db..6caffaf28e6 100644 --- a/source/blender/editors/transform/transform_convert_curves.cc +++ b/source/blender/editors/transform/transform_convert_curves.cc @@ -32,6 +32,52 @@ namespace blender::ed::transform::curves { +static void create_aligned_handles_masks( + const bke::CurvesGeometry &curves, + const blender::Span points_to_transform_per_attr, + TransCustomData &custom_data) +{ + if (points_to_transform_per_attr.size() == 1) { + return; + } + const VArraySpan handle_types_left = curves.handle_types_left(); + const VArraySpan handle_types_right = curves.handle_types_right(); + CurvesTransformData &transform_data = *static_cast(custom_data.data); + + IndexMaskMemory memory; + /* When control point is selected both handles are treaded as selected and transformed together. + * So these will be excluded from alignment. */ + const IndexMask &selected_points = points_to_transform_per_attr[0]; + const IndexMask selected_left_handles = IndexMask::from_difference( + points_to_transform_per_attr[1], selected_points, memory); + index_mask::ExprBuilder builder; + /* Left are excluded here to align only one handle when both are selected. */ + const IndexMask selected_right_handles = evaluate_expression( + builder.subtract({&points_to_transform_per_attr[2]}, + {&selected_left_handles, &selected_points}), + memory); + + const IndexMask &affected_handles = IndexMask::from_union( + selected_left_handles, selected_right_handles, memory); + + auto aligned_handles_to_selection = [&](const VArraySpan &handle_types) { + return IndexMask::from_predicate( + affected_handles, GrainSize(4096), memory, [&](const int64_t i) { + return handle_types[i] == BEZIER_HANDLE_ALIGN; + }); + }; + + const IndexMask both_aligned = IndexMask::from_intersection( + aligned_handles_to_selection(handle_types_left), + aligned_handles_to_selection(handle_types_right), + memory); + + transform_data.aligned_with_left = IndexMask::from_intersection( + selected_left_handles, both_aligned, transform_data.memory); + transform_data.aligned_with_right = IndexMask::from_intersection( + selected_right_handles, both_aligned, transform_data.memory); +} + static void calculate_curve_point_distances_for_proportional_editing( const Span positions, MutableSpan r_distances) { @@ -276,12 +322,32 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t) curves.curves_range(), use_connected_only, bezier_curves[i]); + create_aligned_handles_masks(curves, points_to_transform_per_attribute[i], tc.custom.type); /* TODO: This is wrong. The attribute writer should live at least as long as the span. */ attribute_writer.finish(); } } +static void calculate_aligned_handles(const TransCustomData &custom_data, + bke::CurvesGeometry &curves) +{ + if (ed::curves::get_curves_selection_attribute_names(curves).size() == 1) { + return; + } + const CurvesTransformData &transform_data = *static_cast( + custom_data.data); + + const Span positions = curves.positions(); + MutableSpan handle_positions_left = curves.handle_positions_left_for_write(); + MutableSpan handle_positions_right = curves.handle_positions_right_for_write(); + + bke::curves::bezier::calculate_aligned_handles( + transform_data.aligned_with_left, positions, handle_positions_left, handle_positions_right); + bke::curves::bezier::calculate_aligned_handles( + transform_data.aligned_with_right, positions, handle_positions_right, handle_positions_left); +} + static void recalcData_curves(TransInfo *t) { const Span trans_data_contrainers(t->data_container, t->data_container_len); @@ -307,6 +373,7 @@ static void recalcData_curves(TransInfo *t) } curves.tag_positions_changed(); curves.calculate_bezier_auto_handles(); + calculate_aligned_handles(tc.custom.type, curves); } DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); }