Transform: support properly transforming implicitly shared curve's positions #120824
|
@ -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
|
||||
/**
|
||||
* 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;
|
||||
|
|
|
@ -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
Jacques Lucke
commented
`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
Hans Goudey
commented
Could you make the customdata point to a struct like Could you make the customdata point to a struct like `TransformArrays` that contains a `Array` for each dhunk it needs to hold?
Laurynas Duburas
commented
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();
|
||||
Hans Goudey
commented
`IndexMaskMemory memory;` is unused above
|
||||
}
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Might it be simpler to use Might it be simpler to use `array_utils::gather` instead of using an intermediate step of building index ranges?
Laurynas Duburas
commented
If to go with 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.
Iliya Katushenock
commented
You need to do that:
And after this:
No need for 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`.
Hans Goudey
commented
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 I would suggest moving 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 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.
Laurynas Duburas
commented
I don't see to separate GP without two separate 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.
Laurynas Duburas
commented
Thanks for suggestion. > 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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Add comments for what
layer_offsets
andpositions
contain.