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); }