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 67e2bed83c - Show all commits

View File

@ -676,35 +676,176 @@ struct EraseOperationExecutor {
/* Computes the new curve topology of a curves geometry after the removal of a set of points.
*/
struct CurvesTopologyTransferData {
int dst_curves_num;
Array<int> dst_to_src_curve;
Array<int> dst_curve_offsets;
int points_num;
int curves_num;
CurvesTopologyTransferData(const int size)
: dst_curves_num(size), dst_to_src_curve(size), dst_curve_offsets(size + 1)
Array<int> curve_map;
Array<int> curve_offsets;
Array<bool> curve_cyclic;
Array<std::pair<int, float>> points_map;
CurvesTopologyTransferData(const int points_num, const int curves_num)
: points_num(points_num),
curves_num(curves_num),
curve_map(curves_num),
curve_offsets(curves_num + 1),
curve_cyclic(curves_num),
points_map(points_num)
{
}
};
CurvesTopologyTransferData split_curves(const blender::bke::CurvesGeometry &src,
const IndexMask &src_points_to_remove) const
CurvesTopologyTransferData recompute_topology(const blender::bke::CurvesGeometry &src,
const Span<bool> src_remove_point,
const Span<int> src_nb_points_to_add,
const Span<float> src_new_points_parameters) const
{
const int dst_curves_num = src.curves_num();
CurvesTopologyTransferData dst_transfer(dst_curves_num);
using namespace blender::bke;
array_utils::copy(src.offsets(), dst_transfer.dst_curve_offsets.as_mutable_span(), 256);
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
const VArray<bool> src_cyclic = src.cyclic();
const int src_points_num = src.points_num();
const int src_curves_num = src.curves_num();
for (const int dst_curve : IndexRange(dst_curves_num)) {
dst_transfer.dst_to_src_curve[dst_curve] = dst_curve;
IndexMaskMemory srp_mem{};
const int total_points_removed = IndexMask::from_bools(src_remove_point, srp_mem).size();
const int total_points_added = src_new_points_parameters.size();
const int dst_points_num = src_points_num + total_points_added - total_points_removed;
/* 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<std::pair<int, float>> dst_points_parameters(dst_points_num);
Array<bool> dst_is_cut(dst_points_num, false);
Array<int> src_pivot_point(src_curves_num, -1);
Array<int> dst_interm_curves_offsets(src_curves_num + 1, 0);
int dst_point = -1;
int np_index = 0;
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) {
if (!src_remove_point[src_point]) {
/* Add a point from the source : the factor is only the index in the source. */
dst_points_parameters[++dst_point] = {src_point, 0.0};
}
IndexRange src_segment_new_points(np_index, src_nb_points_to_add[src_point]);
for (const float src_point_factor :
src_new_points_parameters.slice(src_segment_new_points)) {
dst_points_parameters[++dst_point] = {src_point, src_point_factor};
dst_is_cut[dst_point] = true;
}
if ((src_nb_points_to_add[src_point] == 0) && (src_point > src_points.first())) {
dst_is_cut[dst_point] = (!src_remove_point[src_point]) &&
(src_remove_point[src_point - 1]);
}
/* 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_is_cut[dst_point]) {
src_pivot_point[src_curve] = dst_point;
}
np_index += src_nb_points_to_add[src_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(), 256, [&](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_points_parameters.begin() + dst_interm_first,
dst_points_parameters.begin() + pivot_point,
dst_points_parameters.begin() + dst_interm_last);
std::rotate(dst_is_cut.begin() + dst_interm_first,
dst_is_cut.begin() + pivot_point,
dst_is_cut.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(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) {
if ((length_of_current > 0) && dst_is_cut[dst_point]) {
/* 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.one_after_last());
dst_to_src_curve.append(src_curve);
}
}
const int dst_curves_num = dst_curves_offset.size() - 1;
CurvesTopologyTransferData dst_transfer(dst_points_num, dst_curves_num);
array_utils::copy(dst_curves_offset.as_span(), dst_transfer.curve_offsets.as_mutable_span());
array_utils::copy(dst_to_src_curve.as_span(), dst_transfer.curve_map.as_mutable_span());
array_utils::copy(dst_points_parameters.as_span(), dst_transfer.points_map.as_mutable_span());
threading::parallel_for(IndexRange(dst_curves_num), 256, [&](const IndexRange dst_curves) {
for (const int dst_curve : dst_curves) {
const int src_curve = dst_transfer.curve_map[dst_curve];
dst_transfer.curve_cyclic[dst_curve] = src_now_cyclic[src_curve];
}
});
return dst_transfer;
}
CurvesTopologyTransferData recompute_topology(const blender::bke::CurvesGeometry &src,
const Span<bool> src_remove_point) const
{
return recompute_topology(
src, src_remove_point, Array<int>(src.points_num(), 0), Array<float>());
}
void soft_eraser(const blender::bke::CurvesGeometry &src,
const Array<float2> &screen_space_positions,
blender::bke::CurvesGeometry &dst,
const bool keep_caps)
blender::bke::CurvesGeometry &dst)
{
using namespace blender::bke;
@ -714,7 +855,6 @@ struct EraseOperationExecutor {
const std::string opacity_attr = "opacity";
const int src_points_num = src.points_num();
const int src_curves_num = src.curves_num();
const bke::AttributeAccessor src_attributes = src.attributes();
VArray<float> src_opacity = *(
@ -743,47 +883,71 @@ struct EraseOperationExecutor {
}
});
/* Mark points for removal. */
IndexMaskMemory memory;
IndexMask src_points_to_remove = IndexMask::from_predicate(
src.points_range(), GrainSize(256), memory, [&](const int64_t src_point) {
return (src_new_opacity[src_point] < opacity_threshold);
});
/* Compute destination points */
const auto is_point_transparent = [&](const int src_point) {
return (src_new_opacity[src_point] < opacity_threshold);
};
const int dst_points_num = src_points_num;
Array<bool> src_is_point_transparent(src_points_num);
threading::parallel_for(src.points_range(), 256, [&](const IndexRange src_points) {
for (const int src_point : src_points) {
src_is_point_transparent[src_point] = is_point_transparent(src_point);
}
});
bool any_transparent_point = false;
for (const bool is_transparent : src_is_point_transparent) {
if (is_transparent) {
any_transparent_point = true;
break;
}
}
/* Compute the destination's curve topology. */
CurvesTopologyTransferData dst_transfer = split_curves(src, src_points_to_remove);
/* If no points needs to be removed, then we can leave the topology as is.
*/
if (!any_transparent_point) {
dst = std::move(src);
const Array<int> &dst_curves_offset = dst_transfer.dst_curve_offsets;
const Array<int> &dst_to_src_curve = dst_transfer.dst_to_src_curve;
const int dst_curves_num = dst_transfer.dst_curves_num;
bke::MutableAttributeAccessor dst_attributes = dst.attributes_for_write();
/* Write the opacity attribute*/
SpanAttributeWriter<float> dst_opacity = dst_attributes.lookup_or_add_for_write_span<float>(
opacity_attr, ATTR_DOMAIN_POINT);
threading::parallel_for(dst.points_range(), 256, [&](const IndexRange dst_points) {
for (const int dst_point : dst_points) {
dst_opacity.span[dst_point] = src_new_opacity[dst_point];
}
});
dst_opacity.finish();
return;
}
/* Topology change. */
const CurvesTopologyTransferData dst_transfer = recompute_topology(src,
src_is_point_transparent);
const int dst_points_num = dst_transfer.points_num;
const int dst_curves_num = dst_transfer.curves_num;
/* Build destination curves geometry. */
dst.resize(dst_points_num, dst_curves_num);
array_utils::copy(dst_transfer.dst_curve_offsets.as_span(), dst.offsets_for_write(), 256);
/* For now, we don't remove points. */
Array<std::pair<int, float>> dst_points_parameters(dst_points_num);
for (const int dst_point : dst.points_range()) {
dst_points_parameters[dst_point] = {dst_point, 0.0f};
}
const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
array_utils::copy(dst_transfer.curve_offsets.as_span(), dst.offsets_for_write(), 256);
bke::MutableAttributeAccessor dst_attributes = dst.attributes_for_write();
const 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))
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_CURVE, propagation_info, {"cyclic"}))
{
bke::attribute_math::gather(attribute.src, dst_to_src_curve, attribute.dst.span);
bke::attribute_math::gather(attribute.src, dst_transfer.curve_map, attribute.dst.span);
attribute.dst.finish();
}
array_utils::copy(dst_transfer.curve_cyclic.as_span(), dst.cyclic_for_write());
/* Copy points attributes. */
const OffsetIndices<int> dst_points_by_curve = dst.points_by_curve();
for (bke::AttributeTransferData &attribute : bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info))
{
@ -798,7 +962,7 @@ struct EraseOperationExecutor {
threading::parallel_for(dst_curve_points, 256, [&](const IndexRange dst_points) {
for (const int dst_point : dst_points) {
const int src_point = dst_points_parameters[dst_point].first;
const int src_point = dst_transfer.points_map[dst_point].first;
dst_attr[dst_point] = src_attr[src_point];
}
});
@ -815,7 +979,8 @@ struct EraseOperationExecutor {
threading::parallel_for(dst.points_range(), 256, [&](const IndexRange dst_points) {
for (const int dst_point : dst_points) {
dst_opacity.span[dst_point] = src_new_opacity[dst_point];
const int src_point = dst_transfer.points_map[dst_point].first;
dst_opacity.span[dst_point] = src_new_opacity[src_point];
}
});
dst_opacity.finish();
@ -932,7 +1097,7 @@ struct EraseOperationExecutor {
erased = hard_eraser(src, screen_space_positions, dst, self.keep_caps);
break;
case GP_BRUSH_ERASER_SOFT:
soft_eraser(src, screen_space_positions, dst, self.keep_caps);
soft_eraser(src, screen_space_positions, dst);
break;
}