GPv3: Soft mode for the Eraser tool #110310

Merged
Falk David merged 61 commits from amelief/blender:gpv3-erase-operator-soft-mode into main 2024-08-20 17:24:21 +02:00
Showing only changes of commit 24462cd88b - Show all commits

View File

@ -397,7 +397,7 @@ struct EraseOperationExecutor {
bool is_cut;
/* Additional attributes changes that can be stored to be used after a call to
* recompute_topology.
* compute_topology_change.
* Note that they won't be automatically updated in the destination's attributes.
*/
float opacity;
@ -838,217 +838,6 @@ struct EraseOperationExecutor {
return total_intersections;
}
/**
* Computes a \a dst curves geometry by applying a change of topology from a \a src curves
* geometry.
* The change of topology is described by \a src_to_dst_points, which size should be
* equal to the number of points in the source.
* For each point in the source, the corresponding vector in \a src_to_dst_points contains a set
* of destination points (PointTransferData), which can correspond to points of the source, or
* linear combination of them. Note that this vector can be empty if we want to remove points for
* example. Curves can also be splitted if a destination point is marked as a cut.
*
* \returns an array containing the same elements as \a src_to_dst_points, but in the destination
* points domain.
*/
static Array<PointTransferData> recompute_topology(
const bke::CurvesGeometry &src,
bke::CurvesGeometry &dst,
const Span<Vector<PointTransferData>> src_to_dst_points,
const bool keep_caps)
{
const int src_curves_num = src.curves_num();
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
const VArray<bool> src_cyclic = src.cyclic();
int dst_points_num = 0;
for (const Vector<PointTransferData> &src_transfer_data : src_to_dst_points) {
dst_points_num += src_transfer_data.size();
}
if (dst_points_num == 0) {
dst.resize(0, 0);
return Array<PointTransferData>(0);
}
/* Set the intersection parameters in the destination domain : a pair of int and float
* numbers for which the integer is the index of the corresponding segment in the
* source curves, and the float part is the (0,1) factor representing its position in
* the segment.
*/
Array<PointTransferData> dst_transfer_data(dst_points_num);
Array<int> src_pivot_point(src_curves_num, -1);
Array<int> dst_interm_curves_offsets(src_curves_num + 1, 0);
int dst_point = -1;
for (const int src_curve : src.curves_range()) {
const IndexRange src_points = src_points_by_curve[src_curve];
for (const int src_point : src_points) {
for (const PointTransferData &dst_point_transfer : src_to_dst_points[src_point]) {
if (dst_point_transfer.is_src_point) {
dst_transfer_data[++dst_point] = dst_point_transfer;
continue;
}
/* Add an intersection with the eraser and mark it as a cut. */
dst_transfer_data[++dst_point] = dst_point_transfer;
/* For cyclic curves, mark the pivot point as the last intersection with the eraser
* that starts a new segment in the destination.
*/
if (src_cyclic[src_curve] && dst_point_transfer.is_cut) {
src_pivot_point[src_curve] = dst_point;
}
}
}
/* We store intermediate curve offsets represent an intermediate state of the
* destination curves before cutting the curves at eraser's intersection. Thus, it
* contains the same number of curves than in the source, but the offsets are
* different, because points may have been added or removed. */
dst_interm_curves_offsets[src_curve + 1] = dst_point + 1;
}
/* Cyclic curves. */
Array<bool> src_now_cyclic(src_curves_num);
threading::parallel_for(src.curves_range(), 4096, [&](const IndexRange src_curves) {
for (const int src_curve : src_curves) {
const int pivot_point = src_pivot_point[src_curve];
if (pivot_point == -1) {
/* Either the curve was not cyclic or it wasn't cut : no need to change it. */
src_now_cyclic[src_curve] = src_cyclic[src_curve];
continue;
}
/* A cyclic curve was cut :
* - this curve is not cyclic anymore,
* - and we have to shift points to keep the closing segment.
*/
src_now_cyclic[src_curve] = false;
const int dst_interm_first = dst_interm_curves_offsets[src_curve];
const int dst_interm_last = dst_interm_curves_offsets[src_curve + 1];
std::rotate(dst_transfer_data.begin() + dst_interm_first,
dst_transfer_data.begin() + pivot_point,
dst_transfer_data.begin() + dst_interm_last);
}
});
/* Compute the destination curve offsets. */
Vector<int> dst_curves_offset;
Vector<int> dst_to_src_curve;
dst_curves_offset.append(0);
for (int src_curve : src.curves_range()) {
const IndexRange dst_points_range(dst_interm_curves_offsets[src_curve],
dst_interm_curves_offsets[src_curve + 1] -
dst_interm_curves_offsets[src_curve]);
int length_of_current = 0;
for (int dst_point : dst_points_range) {
if ((length_of_current > 0) && dst_transfer_data[dst_point].is_cut) {
/* This is the new first point of a curve. */
dst_curves_offset.append(dst_point);
dst_to_src_curve.append(src_curve);
length_of_current = 0;
}
++length_of_current;
}
if (length_of_current != 0) {
/* End of a source curve. */
dst_curves_offset.append(dst_points_range.one_after_last());
dst_to_src_curve.append(src_curve);
}
}
const int dst_curves_num = dst_curves_offset.size() - 1;
if (dst_curves_num == 0) {
dst.resize(0, 0);
return dst_transfer_data;
}
/* Build destination curves geometry. */
dst.resize(dst_points_num, dst_curves_num);
array_utils::copy(dst_curves_offset.as_span(), dst.offsets_for_write());
const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
/* Attributes. */
const bke::AttributeAccessor src_attributes = src.attributes();
bke::MutableAttributeAccessor dst_attributes = dst.attributes_for_write();
const bke::AnonymousAttributePropagationInfo propagation_info{};
/* Copy curves attributes. */
for (bke::AttributeTransferData &attribute : bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_CURVE, propagation_info, {"cyclic"}))
{
bke::attribute_math::gather(attribute.src, dst_to_src_curve.as_span(), attribute.dst.span);
attribute.dst.finish();
}
array_utils::gather(
src_now_cyclic.as_span(), dst_to_src_curve.as_span(), dst.cyclic_for_write());
/* Display intersections with flat caps. */
if (!keep_caps) {
bke::SpanAttributeWriter<int8_t> dst_start_caps =
dst_attributes.lookup_or_add_for_write_span<int8_t>("start_cap", ATTR_DOMAIN_CURVE);
bke::SpanAttributeWriter<int8_t> dst_end_caps =
dst_attributes.lookup_or_add_for_write_span<int8_t>("end_cap", ATTR_DOMAIN_CURVE);
threading::parallel_for(dst.curves_range(), 4096, [&](const IndexRange dst_curves) {
for (const int dst_curve : dst_curves) {
const IndexRange dst_curve_points = dst_points_by_curve[dst_curve];
if (dst_transfer_data[dst_curve_points.first()].is_cut) {
dst_start_caps.span[dst_curve] = GP_STROKE_CAP_TYPE_FLAT;
}
if (dst_curve == dst_curves.last()) {
continue;
}
const PointTransferData &next_dst_point =
dst_transfer_data[dst_points_by_curve[dst_curve + 1].first()];
if (next_dst_point.is_cut) {
dst_end_caps.span[dst_curve] = GP_STROKE_CAP_TYPE_FLAT;
}
}
});
dst_start_caps.finish();
dst_end_caps.finish();
}
/* Copy/Interpolate point attributes. */
for (bke::AttributeTransferData &attribute : bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info))
{
bke::attribute_math::convert_to_static_type(attribute.dst.span.type(), [&](auto dummy) {
using T = decltype(dummy);
auto src_attr = attribute.src.typed<T>();
auto dst_attr = attribute.dst.span.typed<T>();
threading::parallel_for(dst.points_range(), 4096, [&](const IndexRange dst_points_range) {
for (const int dst_point : dst_points_range) {
const PointTransferData &point_transfer = dst_transfer_data[dst_point];
if (point_transfer.is_src_point) {
dst_attr[dst_point] = src_attr[point_transfer.src_point];
}
else {
dst_attr[dst_point] = bke::attribute_math::mix2<T>(
point_transfer.factor,
src_attr[point_transfer.src_point],
src_attr[point_transfer.src_next_point]);
}
}
});
attribute.dst.finish();
});
}
return dst_transfer_data;
}
Vector<FalloffSample> compute_piecewise_linear_falloff() const
{
/* The changes in opacity implied by the soft eraser are described by a falloff curve mapping.
@ -1256,7 +1045,7 @@ struct EraseOperationExecutor {
}
}
const Array<PointTransferData> dst_points = recompute_topology(
const Array<PointTransferData> dst_points = compute_topology_change(
src, dst, src_to_dst_points, keep_caps);
/* Set opacity. */