Transform: support properly transforming implicitly shared curve's positions #120824

Merged
Jacques Lucke merged 6 commits from laurynas/blender:transform-data-copy into main 2024-04-22 20:03:04 +02:00
3 changed files with 110 additions and 32 deletions

View File

@ -80,6 +80,26 @@ struct TransDataVertSlideVert {
}
};
/**
* Structure used for curves transform operation.
* Used for both curves and grease pencil objects.
*/
struct CurvesTransformData {
blender::IndexMaskMemory memory;
blender::Vector<blender::IndexMask> selection_by_layer;
laurynas marked this conversation as resolved Outdated

Add comments for what layer_offsets and positions contain.

Add comments for what `layer_offsets` and `positions` contain.
/**
* The offsets of every grease pencil layer into `positions` array.
* For curves only one layer is used.
*/
blender::Vector<int> layer_offsets;
/**
* Copy of all positions being transformed.
*/
blender::Array<blender::float3> positions;
};
/* `transform_convert.cc` */
/**
@ -161,6 +181,13 @@ void curve_populate_trans_data_structs(TransDataContainer &tc,
bool use_connected_only,
int trans_data_offset);
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data);
void copy_positions_from_curves_transform_custom_data(
const TransCustomData &custom_data,
const int layer,
blender::MutableSpan<blender::float3> positions_dst);
/* `transform_convert_action.cc` */
extern TransConvertTypeInfo TransConvertType_Action;

View File

@ -9,6 +9,7 @@
#include <optional>
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_inplace_priority_queue.hh"
#include "BLI_math_matrix.h"
#include "BLI_span.hh"
@ -62,10 +63,23 @@ 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)
{
CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(custom_data.data);
transform_data.selection_by_layer.append(selection);
const int data_offset = transform_data.layer_offsets.last();
transform_data.layer_offsets.append(data_offset + selection.size());
array_utils::gather(
positions,
selection,
laurynas marked this conversation as resolved Outdated

selection

`selection`
transform_data.positions.as_mutable_span().slice(data_offset, selection.size()));
}
static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
{
MutableSpan<TransDataContainer> trans_data_contrainers(t->data_container, t->data_container_len);
IndexMaskMemory memory;
Array<IndexMask> selection_per_object(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;
@ -76,16 +90,22 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
Curves *curves_id = static_cast<Curves *>(tc.obedit->data);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
laurynas marked this conversation as resolved Outdated

Could you make the customdata point to a struct like TransformArrays that contains a Array for each dhunk it needs to hold?

Could you make the customdata point to a struct like `TransformArrays` that contains a `Array` for each dhunk it needs to hold?

I was trying to avoid new struct, it will change a lot.

I was trying to avoid new struct, it will change a lot.
CurvesTransformData *curves_transform_data = create_curves_transform_custom_data(
tc.custom.type);
if (use_proportional_edit) {
selection_per_object[i] = curves.points_range();
tc.data_len = curves.point_num;
}
else {
selection_per_object[i] = ed::curves::retrieve_selected_points(curves, memory);
selection_per_object[i] = ed::curves::retrieve_selected_points(
curves, curves_transform_data->memory);
tc.data_len = selection_per_object[i].size();

IndexMaskMemory memory; is unused above

`IndexMaskMemory memory;` is unused above
}
laurynas marked this conversation as resolved Outdated

Might it be simpler to use array_utils::gather instead of using an intermediate step of building index ranges?

Might it be simpler to use `array_utils::gather` instead of using an intermediate step of building index ranges?

If to go with array_utils::gather selection as IndexMask needs to be stored in tc.custom.type.data for latter use by array_utils::scatter. I didn't find how to make deep copy of IndexMask and that forces in function createTransCurvesVerts to use IndexMaskMemory instance from tc.custom.type.data instead of local variable.
And it is doable, but curve_populate_trans_data_structs is also called from grease pencil code. Selections there also has to be attached to tc.custom.type.data and it's data type (currentlyTransformArrays) exposed outside from transform_convert_curves.cc. Here things get messier.

Also with ranges I like fact that copying is done in chunks, though this advantage is selection dependent.

Can you check current version? It would transfer to grease pencil a lot easier.

If to go with `array_utils::gather` selection as `IndexMask` needs to be stored in `tc.custom.type.data` for latter use by `array_utils::scatter`. I didn't find how to make deep copy of `IndexMask` and that forces in function `createTransCurvesVerts` to use `IndexMaskMemory` instance from `tc.custom.type.data` instead of local variable. And it is doable, but `curve_populate_trans_data_structs` is also called from grease pencil code. Selections there also has to be attached to `tc.custom.type.data` and it's data type (currently`TransformArrays`) exposed outside from `transform_convert_curves.cc`. Here things get messier. Also with ranges I like fact that copying is done in chunks, though this advantage is selection dependent. Can you check current version? It would transfer to grease pencil a lot easier.

You need to do that:

Array<int> mapping(mask.size());
mask.to_indices(mapping);
array_utils::gather(src_positions, mapping.as_span(), dst_positions);

And after this:

for (const int i : mapping.index_range()) {
  dst_positions[mapping[i]] = src_positions[i];
}

No need for to_ranges and Array<int> offsets.

You need to do that: ```Cpp Array<int> mapping(mask.size()); mask.to_indices(mapping); array_utils::gather(src_positions, mapping.as_span(), dst_positions); ``` And after this: ```Cpp for (const int i : mapping.index_range()) { dst_positions[mapping[i]] = src_positions[i]; } ``` No need for `to_ranges` and `Array<int> offsets`.

Storing the selection for later would really just be an optimization anyway, it can always be recomputed from the current attributes. Anyway, I'm not convinced that storing Array<IndexRange> is faster than IndexMask general. Especially since there's a lot of single threaded work going on computing the offsets, etc, it doesn't seem worth the complexity.

I would suggest moving IndexMaskMemory and IndexMask into TransformArrays. Since TransformArrays is becoming more general that way, maybe it should be renamed CurvesTransformData or something.

You're right about that being more different for grease pencil, but to me it seems better to find the optimal solution separately for each case. GP is doing a different thing by combining the data from multiple CurvesGeometry into one container.

Storing the selection for later would really just be an optimization anyway, it can always be recomputed from the current attributes. Anyway, I'm not convinced that storing `Array<IndexRange>` is faster than `IndexMask` general. Especially since there's a lot of single threaded work going on computing the offsets, etc, it doesn't seem worth the complexity. I would suggest moving `IndexMaskMemory` and `IndexMask` into `TransformArrays`. Since `TransformArrays` is becoming more general that way, maybe it should be renamed `CurvesTransformData` or something. You're right about that being more different for grease pencil, but to me it seems better to find the optimal solution separately for each case. GP is doing a different thing by combining the data from multiple `CurvesGeometry` into one container.

I don't see to separate GP without two separate curve_populate_trans_data_structs functions.
Current version works for both, also I think it'll be handy for Bezier handles also.

I don't see to separate GP without two separate `curve_populate_trans_data_structs` functions. Current version works for both, also I think it'll be handy for Bezier handles also.

No need for to_ranges and Array<int> offsets.

Thanks for suggestion.
I wanted ranges to copy in chunks. I was frightened by fact that many curves with all vertices selected will be copied point by point.
Anyway I think I solved it with IndexMasks.

> No need for `to_ranges` and `Array<int> offsets`. Thanks for suggestion. I wanted ranges to copy in chunks. I was frightened by fact that many curves with all vertices selected will be copied point by point. Anyway I think I solved it with `IndexMask`s.
if (tc.data_len > 0) {
tc.data = MEM_cnew_array<TransData>(tc.data_len, __func__);
curves_transform_data->positions.reinitialize(tc.data_len);
}
}
@ -144,6 +164,8 @@ 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());
curves.tag_positions_changed();
curves.calculate_bezier_auto_handles();
}
@ -153,6 +175,34 @@ static void recalcData_curves(TransInfo *t)
} // namespace blender::ed::transform::curves
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data)
{
CurvesTransformData *transform_data = MEM_new<CurvesTransformData>(__func__);
transform_data->layer_offsets.append(0);
custom_data.data = transform_data;
custom_data.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) {
CurvesTransformData *data = static_cast<CurvesTransformData *>(custom_data->data);
MEM_delete(data);
custom_data->data = nullptr;
};
return transform_data;
}
void copy_positions_from_curves_transform_custom_data(
const TransCustomData &custom_data,
const int layer,
blender::MutableSpan<blender::float3> positions_dst)
{
using namespace blender;
const CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(
custom_data.data);
const IndexMask &selection = transform_data.selection_by_layer[layer];
OffsetIndices<int> offsets{transform_data.layer_offsets};
Span<float3> positions = transform_data.positions.as_span().slice(offsets[layer]);
array_utils::scatter(positions, selection, positions_dst);
}
void curve_populate_trans_data_structs(TransDataContainer &tc,
blender::bke::CurvesGeometry &curves,
const blender::float4x4 &transform,
@ -169,7 +219,12 @@ void curve_populate_trans_data_structs(TransDataContainer &tc,
copy_m3_m4(mtx, transform.ptr());
pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
MutableSpan<float3> positions = curves.positions_for_write();
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());
if (use_proportional_edit) {
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
@ -233,7 +288,7 @@ void curve_populate_trans_data_structs(TransDataContainer &tc,
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[point_i];
float3 *elem = &positions[selection_i];
copy_v3_v3(td->iloc, *elem);
copy_v3_v3(td->center, td->iloc);

View File

@ -44,23 +44,25 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
}
int layer_offset = 0;
IndexMaskMemory memory;
Array<IndexMask> points_per_layer_per_object(total_number_of_drawings);
/* Count selected elements per layer per object and create TransData structs. */
for (const int i : trans_data_contrainers.index_range()) {
TransDataContainer &tc = trans_data_contrainers[i];
CurvesTransformData *curves_transform_data = create_curves_transform_custom_data(
tc.custom.type);
const Vector<ed::greasepencil::MutableDrawingInfo> drawings = all_drawings[i];
for (ed::greasepencil::MutableDrawingInfo info : drawings) {
if (use_proportional_edit) {
points_per_layer_per_object[layer_offset] = ed::greasepencil::retrieve_editable_points(
*object, info.drawing, memory);
*object, info.drawing, curves_transform_data->memory);
tc.data_len += points_per_layer_per_object[layer_offset].size();
}
else {
points_per_layer_per_object[layer_offset] =
ed::greasepencil::retrieve_editable_and_selected_points(*object, info.drawing, memory);
ed::greasepencil::retrieve_editable_and_selected_points(
*object, info.drawing, curves_transform_data->memory);
tc.data_len += points_per_layer_per_object[layer_offset].size();
}
@ -69,11 +71,13 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
if (tc.data_len > 0) {
tc.data = MEM_cnew_array<TransData>(tc.data_len, __func__);
curves_transform_data->positions.reinitialize(tc.data_len);
}
}
/* Reuse the variable `layer_offset`. */
layer_offset = 0;
IndexMaskMemory memory;
/* Populate TransData structs. */
for (const int i : trans_data_contrainers.index_range()) {
@ -103,30 +107,19 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
value_attribute = opacities;
}
if (use_proportional_edit) {
const IndexMask affected_strokes = ed::greasepencil::retrieve_editable_strokes(
*object, info.drawing, memory);
curve_populate_trans_data_structs(tc,
curves,
layer_space_to_world_space,
value_attribute,
points,
true,
affected_strokes,
use_connected_only,
layer_points_offset);
}
else {
curve_populate_trans_data_structs(tc,
curves,
layer_space_to_world_space,
value_attribute,
points,
false,
{},
use_connected_only,
layer_points_offset);
}
const IndexMask affected_strokes = use_proportional_edit ?
ed::greasepencil::retrieve_editable_strokes(
*object, info.drawing, memory) :
IndexMask();
curve_populate_trans_data_structs(tc,
curves,
layer_space_to_world_space,
value_attribute,
points,
use_proportional_edit,
affected_strokes,
use_connected_only,
layer_points_offset);
layer_points_offset += points.size();
layer_offset++;
@ -145,8 +138,11 @@ static void recalcData_grease_pencil(TransInfo *t)
const Vector<ed::greasepencil::MutableDrawingInfo> drawings =
ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
for (ed::greasepencil::MutableDrawingInfo info : drawings) {
for (const int64_t i : drawings.index_range()) {
ed::greasepencil::MutableDrawingInfo info = drawings[i];
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
copy_positions_from_curves_transform_custom_data(
tc.custom.type, i, curves.positions_for_write());
curves.calculate_bezier_auto_handles();
curves.tag_positions_changed();