Curves: Port reverse curves node to the new data-block

Create a function on CurvesGeometry that can also be used for an edit
mode operator in the future. Dealing with CustomData directly means the
code is a bit more verbose than would be ideal, but this would be a
simple thing to clean up in the future if we get an attribute API here.

Also change the reverse node to first work on a read-only geometry
component, and only get write access if there is a curve selected.

Differential Revision: https://developer.blender.org/D14375
This commit is contained in:
Hans Goudey 2022-03-18 12:24:05 -05:00
parent d2726e4626
commit 298d8a7b4a
Notes: blender-bot 2023-02-13 22:37:44 +01:00
Referenced by issue #100768, Regression: Reverse curve sometimes change the shape of input curve
Referenced by issue #95443, Refactor curve nodes to use new data structure
3 changed files with 114 additions and 11 deletions

View File

@ -277,6 +277,12 @@ class CurvesGeometry : public ::CurvesGeometry {
void remove_curves(IndexMask curves_to_delete);
/**
* Change the direction of selected curves (switch the start and end) without changing their
* shape.
*/
void reverse_curves(IndexMask curves_to_reverse);
/* --------------------------------------------------------------------
* Attributes.
*/

View File

@ -860,6 +860,106 @@ void CurvesGeometry::remove_curves(const IndexMask curves_to_delete)
*this = copy_with_removed_curves(*this, curves_to_delete);
}
template<typename T>
static void reverse_curve_point_data(const CurvesGeometry &curves,
const IndexMask curve_selection,
MutableSpan<T> data)
{
threading::parallel_for(curve_selection.index_range(), 256, [&](IndexRange range) {
for (const int curve_i : curve_selection.slice(range)) {
data.slice(curves.range_for_curve(curve_i)).reverse();
}
});
}
template<typename T>
static void reverse_swap_curve_point_data(const CurvesGeometry &curves,
const IndexMask curve_selection,
MutableSpan<T> data_a,
MutableSpan<T> data_b)
{
threading::parallel_for(curve_selection.index_range(), 256, [&](IndexRange range) {
for (const int curve_i : curve_selection.slice(range)) {
const IndexRange points = curves.range_for_curve(curve_i);
MutableSpan<T> a = data_a.slice(points);
MutableSpan<T> b = data_b.slice(points);
for (const int i : IndexRange(points.size() / 2)) {
const int end_index = points.size() - 1 - i;
std::swap(a[end_index], b[i]);
std::swap(b[end_index], a[i]);
}
}
});
}
static bool layer_matches_name_and_type(const CustomDataLayer &layer,
const StringRef name,
const CustomDataType type)
{
if (layer.type != type) {
return false;
}
return layer.name == name;
}
void CurvesGeometry::reverse_curves(const IndexMask curves_to_reverse)
{
CustomData_duplicate_referenced_layers(&this->point_data, this->points_size());
/* Collect the Bezier handle attributes while iterating through the point custom data layers;
* they need special treatment later. */
MutableSpan<float3> positions_left;
MutableSpan<float3> positions_right;
MutableSpan<int8_t> types_left;
MutableSpan<int8_t> types_right;
for (const int layer_i : IndexRange(this->point_data.totlayer)) {
CustomDataLayer &layer = this->point_data.layers[layer_i];
if (positions_left.is_empty() &&
layer_matches_name_and_type(layer, ATTR_HANDLE_POSITION_LEFT, CD_PROP_FLOAT3)) {
positions_left = {static_cast<float3 *>(layer.data), this->points_size()};
continue;
}
if (positions_right.is_empty() &&
layer_matches_name_and_type(layer, ATTR_HANDLE_POSITION_RIGHT, CD_PROP_FLOAT3)) {
positions_right = {static_cast<float3 *>(layer.data), this->points_size()};
continue;
}
if (types_left.is_empty() &&
layer_matches_name_and_type(layer, ATTR_HANDLE_TYPE_LEFT, CD_PROP_INT8)) {
types_left = {static_cast<int8_t *>(layer.data), this->points_size()};
continue;
}
if (types_right.is_empty() &&
layer_matches_name_and_type(layer, ATTR_HANDLE_TYPE_RIGHT, CD_PROP_INT8)) {
types_right = {static_cast<int8_t *>(layer.data), this->points_size()};
continue;
}
const CustomDataType data_type = static_cast<CustomDataType>(layer.type);
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
using T = decltype(dummy);
reverse_curve_point_data<T>(
*this, curves_to_reverse, {static_cast<T *>(layer.data), this->points_size()});
});
}
/* In order to maintain the shape of Bezier curves, handle attributes must reverse, but also the
* values for the left and right must swap. Use a utility to swap and reverse at the same time,
* to avoid loading the attribute twice. Generally we can expect the right layer to exist when
* the left does, but there's no need to count on it, so check for both attributes. */
if (!positions_left.is_empty() && !positions_right.is_empty()) {
reverse_swap_curve_point_data(*this, curves_to_reverse, positions_left, positions_right);
}
if (!types_left.is_empty() && !types_right.is_empty()) {
reverse_swap_curve_point_data(*this, curves_to_reverse, types_left, types_right);
}
this->tag_topology_changed();
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -2,7 +2,7 @@
#include "BLI_task.hh"
#include "BKE_spline.hh"
#include "BKE_curves.hh"
#include "node_geometry_util.hh"
@ -25,7 +25,7 @@ static void node_geo_exec(GeoNodeExecParams params)
}
Field<bool> selection_field = params.get_input<Field<bool>>("Selection");
CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>();
GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE};
const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE);
@ -33,16 +33,13 @@ static void node_geo_exec(GeoNodeExecParams params)
selection_evaluator.add(selection_field);
selection_evaluator.evaluate();
const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
if (selection.is_empty()) {
return;
}
std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*component.get_for_write());
MutableSpan<SplinePtr> splines = curve->splines();
threading::parallel_for(selection.index_range(), 128, [&](IndexRange range) {
for (const int i : range) {
splines[selection[i]]->reverse();
}
});
component.replace(curve_eval_to_curves(*curve));
Curves &curves_id = *geometry_set.get_curves_for_write();
bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
curves.reverse_curves(selection);
});
params.set_output("Curve", std::move(geometry_set));