Curves: support transforming Bezier handles in edit mode

Allows user to transform Bezier handles.

Pull Request: https://projects.blender.org/blender/blender/pulls/120222
This commit is contained in:
Laurynas Duburas
2024-04-26 09:32:26 +02:00
committed by Jacques Lucke
parent c42eeb0c38
commit 12df5a68ba
3 changed files with 254 additions and 113 deletions

View File

@@ -90,7 +90,8 @@ struct CurvesTransformData {
/**
* The offsets of every grease pencil layer into `positions` array.
* For curves only one layer is used.
* For curves layers are used to store: positions, handle_positions_left and
* handle_positions_right.
*/
blender::Vector<int> layer_offsets;
@@ -171,15 +172,15 @@ void animrecord_check_state(TransInfo *t, ID *id);
/**
* Used for both curves and grease pencil objects.
*/
void curve_populate_trans_data_structs(TransDataContainer &tc,
blender::bke::CurvesGeometry &curves,
const blender::float4x4 &matrix,
std::optional<blender::MutableSpan<float>> value_attribute,
const blender::IndexMask &selected_indices,
bool use_proportional_edit,
const blender::IndexMask &affected_curves,
bool use_connected_only,
int trans_data_offset);
void curve_populate_trans_data_structs(
TransDataContainer &tc,
blender::bke::CurvesGeometry &curves,
const blender::float4x4 &transform,
std::optional<blender::MutableSpan<float>> value_attribute,
const blender::Span<blender::IndexMask> points_to_transform_indices,
const blender::IndexMask &affected_curves,
bool use_connected_only,
const blender::IndexMask &bezier_curves);
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data);

View File

@@ -16,6 +16,7 @@
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "ED_curves.hh"
@@ -63,9 +64,9 @@ static void calculate_curve_point_distances_for_proportional_editing(
}
}
static void append_positions_to_custom_data(const IndexMask selection,
Span<float3> positions,
TransCustomData &custom_data)
static MutableSpan<float3> append_positions_to_custom_data(const IndexMask selection,
Span<float3> positions,
TransCustomData &custom_data)
{
CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(custom_data.data);
transform_data.selection_by_layer.append(selection);
@@ -75,32 +76,101 @@ static void append_positions_to_custom_data(const IndexMask selection,
positions,
selection,
transform_data.positions.as_mutable_span().slice(data_offset, selection.size()));
return transform_data.positions.as_mutable_span().slice(transform_data.layer_offsets.last(1),
selection.size());
}
static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
{
MutableSpan<TransDataContainer> trans_data_contrainers(t->data_container, t->data_container_len);
Array<IndexMask> selection_per_object(t->data_container_len);
Array<Vector<IndexMask>> points_to_transform_per_attribute(t->data_container_len);
Array<IndexMask> bezier_curves(t->data_container_len);
const bool use_proportional_edit = (t->flag & T_PROP_EDIT_ALL) != 0;
const bool use_connected_only = (t->flag & T_PROP_CONNECTED) != 0;
Vector<int> must_be_selected;
/* Count selected elements per object and create TransData structs. */
for (const int i : trans_data_contrainers.index_range()) {
TransDataContainer &tc = trans_data_contrainers[i];
Curves *curves_id = static_cast<Curves *>(tc.obedit->data);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
CurvesTransformData *curves_transform_data = create_curves_transform_custom_data(
tc.custom.type);
Span<StringRef> selection_attribute_names = ed::curves::get_curves_selection_attribute_names(
curves);
std::array<IndexMask, 3> selection_per_attribute;
for (const int attribute_i : selection_attribute_names.index_range()) {
const StringRef &selection_name = selection_attribute_names[attribute_i];
selection_per_attribute[attribute_i] = ed::curves::retrieve_selected_points(
curves, selection_name, curves_transform_data->memory);
}
bezier_curves[i] = bke::curves::indices_for_type(curves.curve_types(),
curves.curve_type_counts(),
CURVE_TYPE_BEZIER,
curves.curves_range(),
curves_transform_data->memory);
/* Alter selection as in legacy curves bezt_select_to_transform_triple_flag(). */
if (!bezier_curves[i].is_empty()) {
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<int8_t> handle_types_left = curves.handle_types_left();
const VArray<int8_t> handle_types_right = curves.handle_types_right();
must_be_selected.clear();
bezier_curves[i].foreach_index([&](const int bezier_index) {
for (const int point_i : points_by_curve[bezier_index]) {
if (selection_per_attribute[0].contains(point_i)) {
const HandleType type_left = HandleType(handle_types_left[point_i]);
const HandleType type_right = HandleType(handle_types_right[point_i]);
if (ELEM(type_left, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_ALIGN) &&
ELEM(type_right, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_ALIGN))
{
must_be_selected.append(point_i);
}
}
}
});
/* Select bezier handles that must be transformed if the main control point is selected. */
IndexMask must_be_selected_mask = IndexMask::from_indices(must_be_selected.as_span(),
curves_transform_data->memory);
if (must_be_selected.size()) {
selection_per_attribute[1] = IndexMask::from_union(
selection_per_attribute[1], must_be_selected_mask, curves_transform_data->memory);
selection_per_attribute[2] = IndexMask::from_union(
selection_per_attribute[2], must_be_selected_mask, curves_transform_data->memory);
}
}
if (use_proportional_edit) {
selection_per_object[i] = curves.points_range();
tc.data_len = curves.point_num;
Array<int> bezier_point_offset_data(bezier_curves[i].size() + 1);
OffsetIndices<int> bezier_offsets = offset_indices::gather_selected_offsets(
curves.points_by_curve(), bezier_curves[i], bezier_point_offset_data);
const int bezier_point_count = bezier_offsets.total_size();
tc.data_len = curves.points_num() + 2 * bezier_point_count;
points_to_transform_per_attribute[i].append(curves.points_range());
if (bezier_point_count > 0) {
Vector<index_mask::IndexMask::Initializer> bezier_point_ranges;
OffsetIndices<int> points_by_curve = curves.points_by_curve();
bezier_curves[i].foreach_index(GrainSize(512), [&](const int bezier_curve_i) {
bezier_point_ranges.append(points_by_curve[bezier_curve_i]);
});
IndexMask bezier_points = IndexMask::from_initializers(bezier_point_ranges,
curves_transform_data->memory);
points_to_transform_per_attribute[i].append(bezier_points);
points_to_transform_per_attribute[i].append(bezier_points);
}
}
else {
selection_per_object[i] = ed::curves::retrieve_selected_points(
curves, curves_transform_data->memory);
tc.data_len = selection_per_object[i].size();
tc.data_len = 0;
for (const int selection_i : selection_attribute_names.index_range()) {
points_to_transform_per_attribute[i].append(selection_per_attribute[selection_i]);
tc.data_len += points_to_transform_per_attribute[i][selection_i].size();
}
}
if (tc.data_len > 0) {
@@ -140,11 +210,10 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
curves,
object->object_to_world(),
value_attribute,
selection_per_object[i],
use_proportional_edit,
points_to_transform_per_attribute[i],
curves.curves_range(),
use_connected_only,
0 /* No data offset for curves. */);
bezier_curves[i]);
/* TODO: This is wrong. The attribute writer should live at least as long as the span. */
attribute_writer.finish();
@@ -164,8 +233,16 @@ static void recalcData_curves(TransInfo *t)
curves.tag_normals_changed();
}
else {
copy_positions_from_curves_transform_custom_data(
tc.custom.type, 0, curves.positions_for_write());
const std::array<MutableSpan<float3>, 3> positions_per_selection_attr = {
curves.positions_for_write(),
curves.handle_positions_left_for_write(),
curves.handle_positions_right_for_write()};
for (const int selection_i :
ed::curves::get_curves_selection_attribute_names(curves).index_range())
{
copy_positions_from_curves_transform_custom_data(
tc.custom.type, selection_i, positions_per_selection_attr[selection_i]);
}
curves.tag_positions_changed();
curves.calculate_bezier_auto_handles();
}
@@ -173,6 +250,45 @@ static void recalcData_curves(TransInfo *t)
}
}
static OffsetIndices<int> recent_position_offsets(TransCustomData &custom_data, int num)
{
const CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(
custom_data.data);
return OffsetIndices(transform_data.layer_offsets.as_span().slice(
transform_data.layer_offsets.size() - num - 1, num + 1));
}
/**
* Creates map of indices to `tc.data` representing curve in layout
* [L0, P0, R0, L1, P1, R1, L2,P2, R2], where [P0, P1, P2], [L0, L1, L2] and [R0, R1, R2] are
* positions, left handles and right handles respectively.
*/
static void fill_map(const CurveType curve_type,
const IndexRange curve_points,
const OffsetIndices<int> position_offsets_in_td,
const int handles_offset,
MutableSpan<int> map)
{
const int attr_num = (curve_type == CURVE_TYPE_BEZIER) ? 3 : 1;
const int left_handle_index = handles_offset + position_offsets_in_td[1].start();
const int position_index = curve_points.start() + position_offsets_in_td[0].start();
const int right_handle_index = handles_offset + position_offsets_in_td[2].start();
std::array<int, 3> first_per_attr = {curve_type == CURVE_TYPE_BEZIER ? left_handle_index :
position_index,
/* Next two unused for non Bezier curves. */
position_index,
right_handle_index};
threading::parallel_for(curve_points.index_range(), 4096, [&](const IndexRange range) {
for (const int i : range) {
for (const int attr : IndexRange(attr_num)) {
map[i * attr_num + attr] = first_per_attr[attr] + i;
}
}
});
}
} // namespace blender::ed::transform::curves
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data)
@@ -203,108 +319,136 @@ void copy_positions_from_curves_transform_custom_data(
array_utils::scatter(positions, selection, positions_dst);
}
void curve_populate_trans_data_structs(TransDataContainer &tc,
blender::bke::CurvesGeometry &curves,
const blender::float4x4 &transform,
std::optional<blender::MutableSpan<float>> value_attribute,
const blender::IndexMask &selected_indices,
const bool use_proportional_edit,
const blender::IndexMask &affected_curves,
bool use_connected_only,
int trans_data_offset)
void curve_populate_trans_data_structs(
TransDataContainer &tc,
blender::bke::CurvesGeometry &curves,
const blender::float4x4 &transform,
std::optional<blender::MutableSpan<float>> value_attribute,
const blender::Span<blender::IndexMask> points_to_transform_per_attr,
const blender::IndexMask &affected_curves,
bool use_connected_only,
const blender::IndexMask &bezier_curves)
{
using namespace blender;
const std::array<Span<float3>, 3> src_positions_per_selection_attr = {
curves.positions(), curves.handle_positions_left(), curves.handle_positions_right()};
std::array<MutableSpan<float3>, 3> positions_per_selection_attr;
for (const int selection_i : points_to_transform_per_attr.index_range()) {
positions_per_selection_attr[selection_i] =
ed::transform::curves::append_positions_to_custom_data(
points_to_transform_per_attr[selection_i],
src_positions_per_selection_attr[selection_i],
tc.custom.type);
}
float mtx[3][3], smtx[3][3];
copy_m3_m4(mtx, transform.ptr());
pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
ed::transform::curves::append_positions_to_custom_data(
selected_indices, curves.positions(), tc.custom.type);
MutableSpan<float3> positions = static_cast<CurvesTransformData *>(tc.custom.type.data)
->positions.as_mutable_span()
.slice(trans_data_offset, selected_indices.size());
MutableSpan<TransData> all_tc_data = MutableSpan(tc.data, tc.data_len);
OffsetIndices<int> position_offsets_in_td = ed::transform::curves::recent_position_offsets(
tc.custom.type, points_to_transform_per_attr.size());
if (use_proportional_edit) {
Vector<VArray<bool>> selection_attrs;
Span<StringRef> selection_attribute_names = ed::curves::get_curves_selection_attribute_names(
curves);
for (const StringRef selection_name : selection_attribute_names) {
const VArray<bool> selection_attr = *curves.attributes().lookup_or_default<bool>(
selection_name, bke::AttrDomain::Point, true);
selection_attrs.append(selection_attr);
}
for (const int selection_i : position_offsets_in_td.index_range()) {
if (position_offsets_in_td[selection_i].is_empty()) {
continue;
}
MutableSpan<TransData> tc_data = all_tc_data.slice(position_offsets_in_td[selection_i]);
MutableSpan<float3> positions = positions_per_selection_attr[selection_i];
IndexMask points_to_transform = points_to_transform_per_attr[selection_i];
VArray<bool> selection = selection_attrs[selection_i];
threading::parallel_for(points_to_transform.index_range(), 1024, [&](const IndexRange range) {
for (const int tranform_point_i : range) {
const int point_in_domain_i = points_to_transform[tranform_point_i];
TransData &td = tc_data[tranform_point_i];
float3 *elem = &positions[tranform_point_i];
copy_v3_v3(td.iloc, *elem);
copy_v3_v3(td.center, td.iloc);
td.loc = *elem;
td.flag = 0;
if (selection[point_in_domain_i]) {
td.flag = TD_SELECTED;
}
if (value_attribute) {
float *value = &((*value_attribute)[point_in_domain_i]);
td.val = value;
td.ival = *value;
}
td.ext = nullptr;
copy_m3_m3(td.smtx, smtx);
copy_m3_m3(td.mtx, mtx);
}
});
}
if (use_connected_only) {
const VArray<int8_t> curve_types = curves.curve_types();
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
Array<int> bezier_offsets_in_td(curves.curves_num() + 1, 0);
offset_indices::copy_group_sizes(points_by_curve, bezier_curves, bezier_offsets_in_td);
offset_indices::accumulate_counts_to_offsets(bezier_offsets_in_td);
affected_curves.foreach_segment(GrainSize(512), [&](const IndexMaskSegment segment) {
Vector<float> closest_distances;
Array<int> map;
Array<float> closest_distances;
Array<float3> mapped_curve_positions;
for (const int curve_i : segment) {
const IndexRange points = points_by_curve[curve_i];
const bool has_any_selected = ed::curves::has_anything_selected(selection, points);
if (!has_any_selected && use_connected_only) {
for (const int point_i : points) {
TransData &td = tc.data[point_i + trans_data_offset];
const int selection_attrs_num = curve_types[curve_i] == CURVE_TYPE_BEZIER ? 3 : 1;
const IndexRange curve_points = points_by_curve[curve_i];
const int total_curve_points = selection_attrs_num * curve_points.size();
map.reinitialize(total_curve_points);
closest_distances.reinitialize(total_curve_points);
closest_distances.fill(std::numeric_limits<float>::max());
mapped_curve_positions.reinitialize(total_curve_points);
ed::transform::curves::fill_map(CurveType(curve_types[curve_i]),
curve_points,
position_offsets_in_td,
bezier_offsets_in_td[curve_i],
map);
bool has_any_selected = false;
for (const int selection_attr_i : IndexRange(selection_attrs_num)) {
has_any_selected = has_any_selected ||
ed::curves::has_anything_selected(selection_attrs[selection_attr_i],
curve_points);
}
if (!has_any_selected) {
for (const int i : map) {
TransData &td = all_tc_data[i];
td.flag |= TD_SKIP;
}
continue;
}
closest_distances.reinitialize(points.size());
closest_distances.fill(std::numeric_limits<float>::max());
for (const int i : IndexRange(points.size())) {
const int point_i = points[i];
TransData &td = tc.data[point_i + trans_data_offset];
float3 *elem = &positions[point_i];
copy_v3_v3(td.iloc, *elem);
copy_v3_v3(td.center, td.iloc);
td.loc = *elem;
td.flag = 0;
if (selection[point_i]) {
for (const int i : closest_distances.index_range()) {
TransData &td = all_tc_data[map[i]];
mapped_curve_positions[i] = td.loc;
if (td.flag & TD_SELECTED) {
closest_distances[i] = 0.0f;
td.flag = TD_SELECTED;
}
if (value_attribute) {
float *value = &((*value_attribute)[point_i]);
td.val = value;
td.ival = *value;
}
td.ext = nullptr;
copy_m3_m3(td.smtx, smtx);
copy_m3_m3(td.mtx, mtx);
}
if (use_connected_only) {
blender::ed::transform::curves::calculate_curve_point_distances_for_proportional_editing(
positions.slice(points), closest_distances.as_mutable_span());
for (const int i : IndexRange(points.size())) {
TransData &td = tc.data[points[i] + trans_data_offset];
td.dist = closest_distances[i];
}
}
}
});
}
else {
threading::parallel_for(selected_indices.index_range(), 1024, [&](const IndexRange range) {
for (const int selection_i : range) {
TransData *td = &tc.data[selection_i + trans_data_offset];
const int point_i = selected_indices[selection_i];
float3 *elem = &positions[selection_i];
copy_v3_v3(td->iloc, *elem);
copy_v3_v3(td->center, td->iloc);
td->loc = *elem;
if (value_attribute) {
float *value = &((*value_attribute)[point_i]);
td->val = value;
td->ival = *value;
blender::ed::transform::curves::calculate_curve_point_distances_for_proportional_editing(
mapped_curve_positions.as_span(), closest_distances.as_mutable_span());
for (const int i : closest_distances.index_range()) {
TransData &td = all_tc_data[map[i]];
td.dist = closest_distances[i];
}
td->flag = TD_SELECTED;
td->ext = nullptr;
copy_m3_m3(td->smtx, smtx);
copy_m3_m3(td->mtx, mtx);
}
});
}

View File

@@ -89,7 +89,6 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(tc.obedit->data);
Span<const bke::greasepencil::Layer *> layers = grease_pencil.layers();
int layer_points_offset = 0;
const Vector<ed::greasepencil::MutableDrawingInfo> drawings = all_drawings[i];
for (ed::greasepencil::MutableDrawingInfo info : drawings) {
const bke::greasepencil::Layer &layer = *layers[info.layer_index];
@@ -115,13 +114,10 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
curves,
layer_space_to_world_space,
value_attribute,
points,
use_proportional_edit,
{points},
affected_strokes,
use_connected_only,
layer_points_offset);
layer_points_offset += points.size();
IndexMask());
layer_offset++;
}
}