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
This commit is contained in:
Laurynas Duburas
2024-10-31 11:25:41 +01:00
committed by Hans Goudey
parent 229e0a8cae
commit c806f885d4
4 changed files with 86 additions and 0 deletions

View File

@@ -660,6 +660,11 @@ void calculate_auto_handles(bool cyclic,
MutableSpan<float3> positions_left,
MutableSpan<float3> positions_right);
void calculate_aligned_handles(const IndexMask &selection,
Span<float3> positions,
Span<float3> align_by,
MutableSpan<float3> align);
/**
* Change the handles of a single control point, aligning any aligned (#BEZIER_HANDLE_ALIGN)
* handles on the other side of the control point.

View File

@@ -181,6 +181,17 @@ void set_handle_position(const float3 &position,
}
}
void calculate_aligned_handles(const IndexMask &selection,
const Span<float3> positions,
const Span<float3> align_with,
MutableSpan<float3> align_handles)
{
selection.foreach_index_optimized<int>(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<int8_t> types_left,
const Span<int8_t> types_right,

View File

@@ -88,6 +88,9 @@ struct TransDataVertSlideVert {
struct CurvesTransformData {
blender::IndexMaskMemory memory;
blender::Vector<blender::IndexMask> 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.

View File

@@ -32,6 +32,52 @@
namespace blender::ed::transform::curves {
static void create_aligned_handles_masks(
const bke::CurvesGeometry &curves,
const blender::Span<blender::IndexMask> points_to_transform_per_attr,
TransCustomData &custom_data)
{
if (points_to_transform_per_attr.size() == 1) {
return;
}
const VArraySpan<int8_t> handle_types_left = curves.handle_types_left();
const VArraySpan<int8_t> handle_types_right = curves.handle_types_right();
CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(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<int8_t> &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<float3> positions, MutableSpan<float> 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<const CurvesTransformData *>(
custom_data.data);
const Span<float3> positions = curves.positions();
MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
MutableSpan<float3> 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<TransDataContainer> 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);
}