Refactor: GPv3: Move core cutter function
This moves the core of the cutter tool to `ed::greasepencil::cutter::trim_curve_segments`. This is in preperation for the draw tool which also needs to be able to trim the stroke. No functional changes expected.
This commit is contained in:
parent
a2482ab450
commit
969efcad7b
@ -32,359 +32,56 @@
|
|||||||
|
|
||||||
namespace blender::ed::greasepencil {
|
namespace blender::ed::greasepencil {
|
||||||
|
|
||||||
enum Side : uint8_t { Start = 0, End = 1 };
|
|
||||||
enum Distance : uint8_t { Min = 0, Max = 1 };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure describing a curve segment (a point range in a curve) that needs to be removed from
|
|
||||||
* the curve.
|
|
||||||
*/
|
|
||||||
struct CutterSegment {
|
|
||||||
/* Curve index. */
|
|
||||||
int curve;
|
|
||||||
|
|
||||||
/* Point range of the segment: starting point and end point. Matches the point offsets
|
|
||||||
* in a CurvesGeometry. */
|
|
||||||
int point_range[2];
|
|
||||||
|
|
||||||
/* The normalized distance where the cutter segment is intersected by another curve.
|
|
||||||
* For the outer ends of the cutter segment the intersection distance is given between:
|
|
||||||
* - [start point - 1] and [start point]
|
|
||||||
* - [end point] and [end point + 1]
|
|
||||||
*/
|
|
||||||
float intersection_distance[2];
|
|
||||||
|
|
||||||
/* Intersection flag: true if the start/end point of the segment is the result of an
|
|
||||||
* intersection, false if the point is the outer end of a curve. */
|
|
||||||
bool is_intersected[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure describing:
|
|
||||||
* - A collection of cutter segments.
|
|
||||||
*/
|
|
||||||
struct CutterSegments {
|
|
||||||
/* Collection of cutter segments: parts of curves between other curves, to be removed from the
|
|
||||||
* geometry. */
|
|
||||||
Vector<CutterSegment> segments;
|
|
||||||
|
|
||||||
/* Create an initial cutter segment with a point range of one point. */
|
|
||||||
CutterSegment *create_segment(const int curve, const int point)
|
|
||||||
{
|
|
||||||
CutterSegment segment{};
|
|
||||||
segment.curve = curve;
|
|
||||||
segment.point_range[Side::Start] = point;
|
|
||||||
segment.point_range[Side::End] = point;
|
|
||||||
|
|
||||||
this->segments.append(std::move(segment));
|
|
||||||
|
|
||||||
return &this->segments.last();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merge cutter segments that are next to each other. */
|
|
||||||
void merge_adjacent_segments()
|
|
||||||
{
|
|
||||||
Vector<CutterSegment> merged_segments;
|
|
||||||
|
|
||||||
/* Note on performance: we deal with small numbers here, so we can afford the double loop. */
|
|
||||||
while (!this->segments.is_empty()) {
|
|
||||||
CutterSegment a = this->segments.pop_last();
|
|
||||||
|
|
||||||
bool merged = false;
|
|
||||||
for (CutterSegment &b : merged_segments) {
|
|
||||||
if (a.curve != b.curve) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* The segments overlap when the points ranges have overlap or are exactly adjacent. */
|
|
||||||
if ((a.point_range[Side::Start] <= b.point_range[Side::End] &&
|
|
||||||
a.point_range[Side::End] >= b.point_range[Side::Start]) ||
|
|
||||||
(a.point_range[Side::End] == b.point_range[Side::Start] - 1) ||
|
|
||||||
(b.point_range[Side::End] == a.point_range[Side::Start] - 1))
|
|
||||||
{
|
|
||||||
/* Merge the point ranges and related intersection data. */
|
|
||||||
const bool take_start_a = a.point_range[Side::Start] < b.point_range[Side::Start];
|
|
||||||
const bool take_end_a = a.point_range[Side::End] > b.point_range[Side::End];
|
|
||||||
b.point_range[Side::Start] = take_start_a ? a.point_range[Side::Start] :
|
|
||||||
b.point_range[Side::Start];
|
|
||||||
b.point_range[Side::End] = take_end_a ? a.point_range[Side::End] :
|
|
||||||
b.point_range[Side::End];
|
|
||||||
b.is_intersected[Side::Start] = take_start_a ? a.is_intersected[Side::Start] :
|
|
||||||
b.is_intersected[Side::Start];
|
|
||||||
b.is_intersected[Side::End] = take_end_a ? a.is_intersected[Side::End] :
|
|
||||||
b.is_intersected[Side::End];
|
|
||||||
b.intersection_distance[Side::Start] = take_start_a ?
|
|
||||||
a.intersection_distance[Side::Start] :
|
|
||||||
b.intersection_distance[Side::Start];
|
|
||||||
b.intersection_distance[Side::End] = take_end_a ? a.intersection_distance[Side::End] :
|
|
||||||
b.intersection_distance[Side::End];
|
|
||||||
merged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!merged) {
|
|
||||||
merged_segments.append(std::move(a));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->segments = merged_segments;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* When looking for intersections, we need a little padding, otherwise we could miss curves
|
|
||||||
* that intersect for the eye, but not in hard numbers. */
|
|
||||||
static constexpr int BBOX_PADDING = 2;
|
static constexpr int BBOX_PADDING = 2;
|
||||||
|
|
||||||
/* When creating new intersection points, we don't want them too close to their neighbor,
|
|
||||||
* because that clutters the geometry. This threshold defines what 'too close' is. */
|
|
||||||
static constexpr float DISTANCE_FACTOR_THRESHOLD = 0.01f;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the intersection distance of two line segments a-b and c-d.
|
* Apply the stroke cutter to a drawing.
|
||||||
* The intersection distance is defined as the normalized distance (0..1)
|
|
||||||
* from point a to the intersection point of a-b and c-d.
|
|
||||||
*/
|
*/
|
||||||
static float get_intersection_distance_of_segments(const float2 &co_a,
|
static bool execute_cutter_on_drawing(const int layer_index,
|
||||||
const float2 &co_b,
|
const int frame_number,
|
||||||
const float2 &co_c,
|
const Object &ob_eval,
|
||||||
const float2 &co_d)
|
const Object &obact,
|
||||||
{
|
const ARegion ®ion,
|
||||||
/* Get intersection point. */
|
const float4x4 &projection,
|
||||||
const float a1 = co_b[1] - co_a[1];
|
const Span<int2> mcoords,
|
||||||
const float b1 = co_a[0] - co_b[0];
|
const bool keep_caps,
|
||||||
const float c1 = a1 * co_a[0] + b1 * co_a[1];
|
bke::greasepencil::Drawing &drawing)
|
||||||
|
|
||||||
const float a2 = co_d[1] - co_c[1];
|
|
||||||
const float b2 = co_c[0] - co_d[0];
|
|
||||||
const float c2 = a2 * co_c[0] + b2 * co_c[1];
|
|
||||||
|
|
||||||
const float det = float(a1 * b2 - a2 * b1);
|
|
||||||
if (det == 0.0f) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float2 isect((b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det);
|
|
||||||
|
|
||||||
/* Get normalized distance from point a to intersection point. */
|
|
||||||
const float length_ab = math::length(co_b - co_a);
|
|
||||||
float distance = (length_ab == 0.0f ?
|
|
||||||
0.0f :
|
|
||||||
math::clamp(math::length(isect - co_a) / length_ab, 0.0f, 1.0f));
|
|
||||||
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For a curve, find all intersections with other curves.
|
|
||||||
*/
|
|
||||||
static void get_intersections_of_curve_with_curves(const int src_curve,
|
|
||||||
const bke::CurvesGeometry &src,
|
|
||||||
const Span<float2> screen_space_positions,
|
|
||||||
const Span<rcti> screen_space_bbox,
|
|
||||||
MutableSpan<bool> r_is_intersected_after_point,
|
|
||||||
MutableSpan<float2> r_intersection_distance)
|
|
||||||
{
|
|
||||||
const OffsetIndices<int> points_by_curve = src.points_by_curve();
|
|
||||||
const VArray<bool> is_cyclic = src.cyclic();
|
|
||||||
|
|
||||||
/* Edge case: skip curve with only one point. */
|
|
||||||
if (points_by_curve[src_curve].size() < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop all curve points and check for intersections between point a and point a + 1. */
|
|
||||||
const IndexRange src_curve_points = points_by_curve[src_curve].drop_back(
|
|
||||||
is_cyclic[src_curve] ? 0 : 1);
|
|
||||||
for (const int point_a : src_curve_points) {
|
|
||||||
const int point_b = (point_a == points_by_curve[src_curve].last()) ? src_curve_points.first() :
|
|
||||||
point_a + 1;
|
|
||||||
|
|
||||||
/* Get coordinates of segment a-b. */
|
|
||||||
const float2 co_a = screen_space_positions[point_a];
|
|
||||||
const float2 co_b = screen_space_positions[point_b];
|
|
||||||
rcti bbox_ab;
|
|
||||||
BLI_rcti_init_minmax(&bbox_ab);
|
|
||||||
BLI_rcti_do_minmax_v(&bbox_ab, int2(co_a));
|
|
||||||
BLI_rcti_do_minmax_v(&bbox_ab, int2(co_b));
|
|
||||||
BLI_rcti_pad(&bbox_ab, BBOX_PADDING, BBOX_PADDING);
|
|
||||||
|
|
||||||
float intersection_distance_min = FLT_MAX;
|
|
||||||
float intersection_distance_max = -FLT_MAX;
|
|
||||||
|
|
||||||
/* Loop all curves, looking for intersecting segments. */
|
|
||||||
for (const int curve : src.curves_range()) {
|
|
||||||
/* Only process curves with at least two points. */
|
|
||||||
if (points_by_curve[curve].size() < 2) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bounding box check: skip curves that don't overlap segment a-b. */
|
|
||||||
if (!BLI_rcti_isect(&bbox_ab, &screen_space_bbox[curve], nullptr)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find intersecting curve segments. */
|
|
||||||
const IndexRange points = points_by_curve[curve].drop_back(is_cyclic[curve] ? 0 : 1);
|
|
||||||
for (const int point_c : points) {
|
|
||||||
const int point_d = (point_c == points_by_curve[curve].last()) ? points.first() :
|
|
||||||
(point_c + 1);
|
|
||||||
|
|
||||||
/* Don't self check. */
|
|
||||||
if (curve == src_curve &&
|
|
||||||
(point_a == point_c || point_a == point_d || point_b == point_c || point_b == point_d))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip when bounding boxes of a-b and c-d don't overlap. */
|
|
||||||
const float2 co_c = screen_space_positions[point_c];
|
|
||||||
const float2 co_d = screen_space_positions[point_d];
|
|
||||||
rcti bbox_cd;
|
|
||||||
BLI_rcti_init_minmax(&bbox_cd);
|
|
||||||
BLI_rcti_do_minmax_v(&bbox_cd, int2(co_c));
|
|
||||||
BLI_rcti_do_minmax_v(&bbox_cd, int2(co_d));
|
|
||||||
BLI_rcti_pad(&bbox_cd, BBOX_PADDING, BBOX_PADDING);
|
|
||||||
if (!BLI_rcti_isect(&bbox_ab, &bbox_cd, nullptr)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add some padding to the line segment c-d, otherwise we could just miss an
|
|
||||||
* intersection. */
|
|
||||||
const float2 padding_cd = math::normalize(co_d - co_c);
|
|
||||||
const float2 padded_c = co_c - padding_cd;
|
|
||||||
const float2 padded_d = co_d + padding_cd;
|
|
||||||
|
|
||||||
/* Check for intersection. */
|
|
||||||
const auto isect = math::isect_seg_seg(co_a, co_b, padded_c, padded_d);
|
|
||||||
if (ELEM(isect.kind, isect.LINE_LINE_CROSS, isect.LINE_LINE_EXACT)) {
|
|
||||||
/* We found an intersection, set the intersection flag for segment a-b. */
|
|
||||||
r_is_intersected_after_point[point_a] = true;
|
|
||||||
|
|
||||||
/* Calculate the intersection factor. This is the normalized distance (0..1) of the
|
|
||||||
* intersection point on line segment a-b, measured from point a. */
|
|
||||||
const float normalized_distance = get_intersection_distance_of_segments(
|
|
||||||
co_a, co_b, co_c, co_d);
|
|
||||||
intersection_distance_min = math::min(normalized_distance, intersection_distance_min);
|
|
||||||
intersection_distance_max = math::max(normalized_distance, intersection_distance_max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r_is_intersected_after_point[point_a]) {
|
|
||||||
r_intersection_distance[point_a][Distance::Min] = intersection_distance_min;
|
|
||||||
r_intersection_distance[point_a][Distance::Max] = intersection_distance_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand a cutter segment by walking along the curve in forward or backward direction.
|
|
||||||
* A cutter segments ends at an intersection with another curve, or at the outer end of the curve.
|
|
||||||
*/
|
|
||||||
static void expand_cutter_segment_direction(CutterSegment &segment,
|
|
||||||
const int direction,
|
|
||||||
const bke::CurvesGeometry &src,
|
|
||||||
const Span<bool> is_intersected_after_point,
|
|
||||||
const Span<float2> intersection_distance,
|
|
||||||
MutableSpan<bool> point_is_in_segment)
|
|
||||||
{
|
|
||||||
const OffsetIndices<int> points_by_curve = src.points_by_curve();
|
|
||||||
const int point_first = points_by_curve[segment.curve].first();
|
|
||||||
const int point_last = points_by_curve[segment.curve].last();
|
|
||||||
|
|
||||||
const Side segment_side = (direction == 1) ? Side::End : Side::Start;
|
|
||||||
int point_a = segment.point_range[segment_side];
|
|
||||||
|
|
||||||
bool intersected = false;
|
|
||||||
segment.is_intersected[segment_side] = false;
|
|
||||||
|
|
||||||
/* Walk along the curve points. */
|
|
||||||
while ((direction == 1 && point_a < point_last) || (direction == -1 && point_a > point_first)) {
|
|
||||||
const int point_b = point_a + direction;
|
|
||||||
const bool at_end_of_curve = (direction == -1 && point_b == point_first) ||
|
|
||||||
(direction == 1 && point_b == point_last);
|
|
||||||
|
|
||||||
/* Expand segment point range. */
|
|
||||||
segment.point_range[segment_side] = point_a;
|
|
||||||
point_is_in_segment[point_a] = true;
|
|
||||||
|
|
||||||
/* Check for intersections with other curves. The intersections were established in ascending
|
|
||||||
* point order, so in forward direction we look at line segment a-b, in backward direction we
|
|
||||||
* look at line segment b-a. */
|
|
||||||
const int intersection_point = direction == 1 ? point_a : point_b;
|
|
||||||
intersected = is_intersected_after_point[intersection_point];
|
|
||||||
|
|
||||||
/* Avoid orphaned points at the end of a curve. */
|
|
||||||
if (at_end_of_curve &&
|
|
||||||
((direction == -1 &&
|
|
||||||
intersection_distance[intersection_point][Distance::Max] < DISTANCE_FACTOR_THRESHOLD) ||
|
|
||||||
(direction == 1 && intersection_distance[intersection_point][Distance::Min] >
|
|
||||||
(1.0f - DISTANCE_FACTOR_THRESHOLD))))
|
|
||||||
{
|
|
||||||
intersected = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* When we hit an intersection, store the intersection distance. Potentially, line segment
|
|
||||||
* a-b can be intersected by multiple curves, so we want to fetch the first intersection
|
|
||||||
* point we bumped into. In forward direction this is the minimum distance, in backward
|
|
||||||
* direction the maximum. */
|
|
||||||
if (intersected) {
|
|
||||||
segment.is_intersected[segment_side] = true;
|
|
||||||
segment.intersection_distance[segment_side] =
|
|
||||||
(direction == 1) ? intersection_distance[intersection_point][Distance::Min] :
|
|
||||||
intersection_distance[intersection_point][Distance::Max];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep walking along curve. */
|
|
||||||
point_a += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust point range at curve ends. */
|
|
||||||
if (!intersected) {
|
|
||||||
if (direction == -1) {
|
|
||||||
segment.point_range[Side::Start] = point_first;
|
|
||||||
point_is_in_segment[point_first] = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
segment.point_range[Side::End] = point_last;
|
|
||||||
point_is_in_segment[point_last] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand a cutter segment of one point by walking along the curve in both directions.
|
|
||||||
*/
|
|
||||||
static void expand_cutter_segment(CutterSegment &segment,
|
|
||||||
const bke::CurvesGeometry &src,
|
|
||||||
const Span<bool> is_intersected_after_point,
|
|
||||||
const Span<float2> intersection_distance,
|
|
||||||
MutableSpan<bool> point_is_in_segment)
|
|
||||||
{
|
|
||||||
const int8_t directions[2] = {-1, 1};
|
|
||||||
for (const int8_t direction : directions) {
|
|
||||||
expand_cutter_segment_direction(segment,
|
|
||||||
direction,
|
|
||||||
src,
|
|
||||||
is_intersected_after_point,
|
|
||||||
intersection_distance,
|
|
||||||
point_is_in_segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find curve points within the lasso area, expand them to segments between other curves and
|
|
||||||
* delete them from the geometry.
|
|
||||||
*/
|
|
||||||
static std::optional<bke::CurvesGeometry> stroke_cutter_find_and_remove_segments(
|
|
||||||
const bke::CurvesGeometry &src,
|
|
||||||
const Span<int2> mcoords,
|
|
||||||
const Span<float2> screen_space_positions,
|
|
||||||
const Span<rcti> screen_space_bbox,
|
|
||||||
const bool keep_caps)
|
|
||||||
{
|
{
|
||||||
|
const bke::CurvesGeometry &src = drawing.strokes();
|
||||||
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
|
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
|
||||||
|
|
||||||
|
/* Get evaluated geometry. */
|
||||||
|
bke::crazyspace::GeometryDeformation deformation =
|
||||||
|
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
|
||||||
|
&ob_eval, obact, layer_index, frame_number);
|
||||||
|
|
||||||
|
/* Compute screen space positions. */
|
||||||
|
Array<float2> screen_space_positions(src.points_num());
|
||||||
|
threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) {
|
||||||
|
for (const int src_point : src_points) {
|
||||||
|
screen_space_positions[src_point] = ED_view3d_project_float_v2_m4(
|
||||||
|
®ion, deformation.positions[src_point], projection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Compute bounding boxes of curves in screen space. The bounding boxes are used to speed
|
||||||
|
* up the search for intersecting curves. */
|
||||||
|
Array<rcti> screen_space_bbox(src.curves_num());
|
||||||
|
threading::parallel_for(src.curves_range(), 512, [&](const IndexRange src_curves) {
|
||||||
|
for (const int src_curve : src_curves) {
|
||||||
|
rcti *bbox = &screen_space_bbox[src_curve];
|
||||||
|
BLI_rcti_init_minmax(bbox);
|
||||||
|
|
||||||
|
const IndexRange src_points = src_points_by_curve[src_curve];
|
||||||
|
for (const int src_point : src_points) {
|
||||||
|
BLI_rcti_do_minmax_v(bbox, int2(screen_space_positions[src_point]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some padding, otherwise we could just miss intersections. */
|
||||||
|
BLI_rcti_pad(bbox, BBOX_PADDING, BBOX_PADDING);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
rcti bbox_lasso;
|
rcti bbox_lasso;
|
||||||
BLI_lasso_boundbox(&bbox_lasso, mcoords);
|
BLI_lasso_boundbox(&bbox_lasso, mcoords);
|
||||||
|
|
||||||
@ -418,225 +115,27 @@ static std::optional<bke::CurvesGeometry> stroke_cutter_find_and_remove_segments
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IndexMaskMemory memory;
|
||||||
|
const IndexMask curve_selection = IndexMask::from_indices(selected_curves.as_span(), memory);
|
||||||
/* Abort when the lasso area is empty. */
|
/* Abort when the lasso area is empty. */
|
||||||
if (selected_curves.is_empty()) {
|
if (curve_selection.is_empty()) {
|
||||||
return std::nullopt;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For the selected curves, find all the intersections with other curves. */
|
|
||||||
const int src_points_num = src.points_num();
|
|
||||||
Array<bool> is_intersected_after_point(src_points_num, false);
|
|
||||||
Array<float2> intersection_distance(src_points_num);
|
|
||||||
threading::parallel_for(selected_curves.index_range(), 1, [&](const IndexRange curve_range) {
|
|
||||||
for (const int selected_curve : curve_range) {
|
|
||||||
const int src_curve = selected_curves[selected_curve];
|
|
||||||
get_intersections_of_curve_with_curves(src_curve,
|
|
||||||
src,
|
|
||||||
screen_space_positions,
|
|
||||||
screen_space_bbox,
|
|
||||||
is_intersected_after_point,
|
|
||||||
intersection_distance);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Expand the selected curve points to cutter segments (the part of the curve between two
|
|
||||||
* intersections). */
|
|
||||||
const VArray<bool> is_cyclic = src.cyclic();
|
|
||||||
Array<bool> point_is_in_segment(src_points_num, false);
|
|
||||||
threading::EnumerableThreadSpecific<CutterSegments> cutter_segments_by_thread;
|
|
||||||
|
|
||||||
threading::parallel_for(selected_curves.index_range(), 1, [&](const IndexRange curve_range) {
|
|
||||||
for (const int selected_curve : curve_range) {
|
|
||||||
CutterSegments &thread_segments = cutter_segments_by_thread.local();
|
|
||||||
const int src_curve = selected_curves[selected_curve];
|
|
||||||
|
|
||||||
for (const int selected_point : selected_points_in_curves[selected_curve]) {
|
|
||||||
/* Skip point when it is already part of a cutter segment. */
|
|
||||||
if (point_is_in_segment[selected_point]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create new cutter segment. */
|
|
||||||
CutterSegment *segment = thread_segments.create_segment(src_curve, selected_point);
|
|
||||||
|
|
||||||
/* Expand the cutter segment in both directions until an intersection is found or the
|
|
||||||
* end of the curve is reached. */
|
|
||||||
expand_cutter_segment(
|
|
||||||
*segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
|
|
||||||
|
|
||||||
/* When the end of a curve is reached and the curve is cyclic, we add an extra cutter
|
|
||||||
* segment for the cyclic second part. */
|
|
||||||
if (is_cyclic[src_curve] &&
|
|
||||||
(!segment->is_intersected[Side::Start] || !segment->is_intersected[Side::End]) &&
|
|
||||||
!(!segment->is_intersected[Side::Start] && !segment->is_intersected[Side::End]))
|
|
||||||
{
|
|
||||||
const int cyclic_outer_point = !segment->is_intersected[Side::Start] ?
|
|
||||||
src_points_by_curve[src_curve].last() :
|
|
||||||
src_points_by_curve[src_curve].first();
|
|
||||||
segment = thread_segments.create_segment(src_curve, cyclic_outer_point);
|
|
||||||
|
|
||||||
/* Expand this second segment. */
|
|
||||||
expand_cutter_segment(*segment,
|
|
||||||
src,
|
|
||||||
is_intersected_after_point,
|
|
||||||
intersection_distance,
|
|
||||||
point_is_in_segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
CutterSegments cutter_segments;
|
|
||||||
for (CutterSegments &thread_segments : cutter_segments_by_thread) {
|
|
||||||
cutter_segments.segments.extend(thread_segments.segments);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Abort when no cutter segments are found in the lasso area. */
|
|
||||||
if (cutter_segments.segments.is_empty()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merge adjacent cutter segments. E.g. two point ranges of 0-10 and 11-20 will be merged
|
|
||||||
* to one range of 0-20. */
|
|
||||||
cutter_segments.merge_adjacent_segments();
|
|
||||||
|
|
||||||
/* Create the point transfer data, for converting the source geometry into the new geometry.
|
|
||||||
* First, add all curve points not affected by the cutter tool. */
|
|
||||||
Array<Vector<PointTransferData>> src_to_dst_points(src_points_num);
|
|
||||||
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) {
|
|
||||||
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
|
||||||
const int src_next_point = (src_point == src_points.last()) ? src_points.first() :
|
|
||||||
(src_point + 1);
|
|
||||||
|
|
||||||
/* Add the source point only if it does not lie inside a cutter segment. */
|
|
||||||
if (!point_is_in_segment[src_point]) {
|
|
||||||
dst_points.append({src_point, src_next_point, 0.0f, true, false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add new curve points at the intersection points of the cutter segments.
|
|
||||||
*
|
|
||||||
* a b
|
|
||||||
* source curve o--------o---*---o--------o----*---o--------o
|
|
||||||
* ^ ^
|
|
||||||
* cutter segment |-----------------|
|
|
||||||
*
|
|
||||||
* o = existing curve point
|
|
||||||
* * = newly created curve point
|
|
||||||
*
|
|
||||||
* The curve points between *a and *b will be deleted.
|
|
||||||
* The source curve will be cut in two:
|
|
||||||
* - the first curve ends at *a
|
|
||||||
* - the second curve starts at *b
|
|
||||||
*
|
|
||||||
* We avoid inserting a new point very close to the adjacent one, because that's just adding
|
|
||||||
* clutter to the geometry.
|
|
||||||
*/
|
|
||||||
for (const CutterSegment &cutter_segment : cutter_segments.segments) {
|
|
||||||
/* Intersection at cutter segment start. */
|
|
||||||
if (cutter_segment.is_intersected[Side::Start] &&
|
|
||||||
cutter_segment.intersection_distance[Side::Start] > DISTANCE_FACTOR_THRESHOLD)
|
|
||||||
{
|
|
||||||
const int src_point = cutter_segment.point_range[Side::Start] - 1;
|
|
||||||
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
|
||||||
dst_points.append({src_point,
|
|
||||||
src_point + 1,
|
|
||||||
cutter_segment.intersection_distance[Side::Start],
|
|
||||||
false,
|
|
||||||
false});
|
|
||||||
}
|
|
||||||
/* Intersection at cutter segment end. */
|
|
||||||
if (cutter_segment.is_intersected[Side::End]) {
|
|
||||||
const int src_point = cutter_segment.point_range[Side::End];
|
|
||||||
if (cutter_segment.intersection_distance[Side::End] < (1.0f - DISTANCE_FACTOR_THRESHOLD)) {
|
|
||||||
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
|
||||||
dst_points.append({src_point,
|
|
||||||
src_point + 1,
|
|
||||||
cutter_segment.intersection_distance[Side::End],
|
|
||||||
false,
|
|
||||||
true});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Mark the 'is_cut' flag on the next point, because a new curve is starting here after
|
|
||||||
* the removed cutter segment. */
|
|
||||||
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point + 1];
|
|
||||||
for (PointTransferData &dst_point : dst_points) {
|
|
||||||
if (dst_point.is_src_point) {
|
|
||||||
dst_point.is_cut = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create the new curves geometry. */
|
|
||||||
bke::CurvesGeometry dst;
|
|
||||||
compute_topology_change(src, dst, src_to_dst_points, keep_caps);
|
|
||||||
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply the stroke cutter to a drawing.
|
|
||||||
*/
|
|
||||||
static bool execute_cutter_on_drawing(const int layer_index,
|
|
||||||
const int frame_number,
|
|
||||||
const Object &ob_eval,
|
|
||||||
const Object &obact,
|
|
||||||
const ARegion ®ion,
|
|
||||||
const float4x4 &projection,
|
|
||||||
const Span<int2> mcoords,
|
|
||||||
const bool keep_caps,
|
|
||||||
bke::greasepencil::Drawing &drawing)
|
|
||||||
{
|
|
||||||
const bke::CurvesGeometry &src = drawing.strokes();
|
|
||||||
|
|
||||||
/* Get evaluated geometry. */
|
|
||||||
bke::crazyspace::GeometryDeformation deformation =
|
|
||||||
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
|
|
||||||
&ob_eval, obact, layer_index, frame_number);
|
|
||||||
|
|
||||||
/* Compute screen space positions. */
|
|
||||||
Array<float2> screen_space_positions(src.points_num());
|
|
||||||
threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) {
|
|
||||||
for (const int src_point : src_points) {
|
|
||||||
screen_space_positions[src_point] = ED_view3d_project_float_v2_m4(
|
|
||||||
®ion, deformation.positions[src_point], projection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Compute bounding boxes of curves in screen space. The bounding boxes are used to speed
|
|
||||||
* up the search for intersecting curves. */
|
|
||||||
Array<rcti> screen_space_bbox(src.curves_num());
|
|
||||||
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
|
|
||||||
threading::parallel_for(src.curves_range(), 512, [&](const IndexRange src_curves) {
|
|
||||||
for (const int src_curve : src_curves) {
|
|
||||||
rcti *bbox = &screen_space_bbox[src_curve];
|
|
||||||
BLI_rcti_init_minmax(bbox);
|
|
||||||
|
|
||||||
const IndexRange src_points = src_points_by_curve[src_curve];
|
|
||||||
for (const int src_point : src_points) {
|
|
||||||
BLI_rcti_do_minmax_v(bbox, int2(screen_space_positions[src_point]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add some padding, otherwise we could just miss intersections. */
|
|
||||||
BLI_rcti_pad(bbox, BBOX_PADDING, BBOX_PADDING);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Apply cutter. */
|
/* Apply cutter. */
|
||||||
std::optional<bke::CurvesGeometry> cut_strokes = stroke_cutter_find_and_remove_segments(
|
bke::CurvesGeometry cut_strokes = ed::greasepencil::cutter::trim_curve_segments(
|
||||||
src, mcoords, screen_space_positions, screen_space_bbox, keep_caps);
|
src,
|
||||||
|
screen_space_positions,
|
||||||
|
screen_space_bbox,
|
||||||
|
curve_selection,
|
||||||
|
selected_points_in_curves,
|
||||||
|
keep_caps);
|
||||||
|
|
||||||
if (cut_strokes.has_value()) {
|
/* Set the new geometry. */
|
||||||
/* Set the new geometry. */
|
drawing.strokes_for_write() = std::move(cut_strokes);
|
||||||
drawing.geometry.wrap() = std::move(cut_strokes.value());
|
drawing.tag_topology_changed();
|
||||||
drawing.tag_topology_changed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cut_strokes.has_value();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "BLI_enumerable_thread_specific.hh"
|
#include "BLI_enumerable_thread_specific.hh"
|
||||||
#include "BLI_kdtree.h"
|
#include "BLI_kdtree.h"
|
||||||
#include "BLI_math_vector.hh"
|
#include "BLI_math_vector.hh"
|
||||||
|
#include "BLI_rect.h"
|
||||||
#include "BLI_stack.hh"
|
#include "BLI_stack.hh"
|
||||||
|
|
||||||
#include "BKE_curves_utils.hh"
|
#include "BKE_curves_utils.hh"
|
||||||
@ -714,4 +715,502 @@ bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &draw
|
|||||||
return dst_curves;
|
return dst_curves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace cutter {
|
||||||
|
|
||||||
|
enum Side : uint8_t { Start = 0, End = 1 };
|
||||||
|
enum Distance : uint8_t { Min = 0, Max = 1 };
|
||||||
|
|
||||||
|
/* When looking for intersections, we need a little padding, otherwise we could miss curves
|
||||||
|
* that intersect for the eye, but not in hard numbers. */
|
||||||
|
static constexpr int BBOX_PADDING = 2;
|
||||||
|
|
||||||
|
/* When creating new intersection points, we don't want them too close to their neighbor,
|
||||||
|
* because that clutters the geometry. This threshold defines what 'too close' is. */
|
||||||
|
static constexpr float DISTANCE_FACTOR_THRESHOLD = 0.01f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure describing a curve segment (a point range in a curve) that needs to be removed from
|
||||||
|
* the curve.
|
||||||
|
*/
|
||||||
|
struct Segment {
|
||||||
|
/* Curve index. */
|
||||||
|
int curve;
|
||||||
|
|
||||||
|
/* Point range of the segment: starting point and end point. Matches the point offsets
|
||||||
|
* in a CurvesGeometry. */
|
||||||
|
int point_range[2];
|
||||||
|
|
||||||
|
/* The normalized distance where the cutter segment is intersected by another curve.
|
||||||
|
* For the outer ends of the cutter segment the intersection distance is given between:
|
||||||
|
* - [start point - 1] and [start point]
|
||||||
|
* - [end point] and [end point + 1]
|
||||||
|
*/
|
||||||
|
float intersection_distance[2];
|
||||||
|
|
||||||
|
/* Intersection flag: true if the start/end point of the segment is the result of an
|
||||||
|
* intersection, false if the point is the outer end of a curve. */
|
||||||
|
bool is_intersected[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure describing:
|
||||||
|
* - A collection of cutter segments.
|
||||||
|
*/
|
||||||
|
struct Segments {
|
||||||
|
/* Collection of cutter segments: parts of curves between other curves, to be removed from the
|
||||||
|
* geometry. */
|
||||||
|
Vector<Segment> segments;
|
||||||
|
|
||||||
|
/* Create an initial cutter segment with a point range of one point. */
|
||||||
|
Segment *create_segment(const int curve, const int point)
|
||||||
|
{
|
||||||
|
Segment segment{};
|
||||||
|
segment.curve = curve;
|
||||||
|
segment.point_range[Side::Start] = point;
|
||||||
|
segment.point_range[Side::End] = point;
|
||||||
|
|
||||||
|
this->segments.append(std::move(segment));
|
||||||
|
|
||||||
|
return &this->segments.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Merge cutter segments that are next to each other. */
|
||||||
|
void merge_adjacent_segments()
|
||||||
|
{
|
||||||
|
Vector<Segment> merged_segments;
|
||||||
|
|
||||||
|
/* Note on performance: we deal with small numbers here, so we can afford the double loop. */
|
||||||
|
while (!this->segments.is_empty()) {
|
||||||
|
Segment a = this->segments.pop_last();
|
||||||
|
|
||||||
|
bool merged = false;
|
||||||
|
for (Segment &b : merged_segments) {
|
||||||
|
if (a.curve != b.curve) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* The segments overlap when the points ranges have overlap or are exactly adjacent. */
|
||||||
|
if ((a.point_range[Side::Start] <= b.point_range[Side::End] &&
|
||||||
|
a.point_range[Side::End] >= b.point_range[Side::Start]) ||
|
||||||
|
(a.point_range[Side::End] == b.point_range[Side::Start] - 1) ||
|
||||||
|
(b.point_range[Side::End] == a.point_range[Side::Start] - 1))
|
||||||
|
{
|
||||||
|
/* Merge the point ranges and related intersection data. */
|
||||||
|
const bool take_start_a = a.point_range[Side::Start] < b.point_range[Side::Start];
|
||||||
|
const bool take_end_a = a.point_range[Side::End] > b.point_range[Side::End];
|
||||||
|
b.point_range[Side::Start] = take_start_a ? a.point_range[Side::Start] :
|
||||||
|
b.point_range[Side::Start];
|
||||||
|
b.point_range[Side::End] = take_end_a ? a.point_range[Side::End] :
|
||||||
|
b.point_range[Side::End];
|
||||||
|
b.is_intersected[Side::Start] = take_start_a ? a.is_intersected[Side::Start] :
|
||||||
|
b.is_intersected[Side::Start];
|
||||||
|
b.is_intersected[Side::End] = take_end_a ? a.is_intersected[Side::End] :
|
||||||
|
b.is_intersected[Side::End];
|
||||||
|
b.intersection_distance[Side::Start] = take_start_a ?
|
||||||
|
a.intersection_distance[Side::Start] :
|
||||||
|
b.intersection_distance[Side::Start];
|
||||||
|
b.intersection_distance[Side::End] = take_end_a ? a.intersection_distance[Side::End] :
|
||||||
|
b.intersection_distance[Side::End];
|
||||||
|
merged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!merged) {
|
||||||
|
merged_segments.append(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->segments = merged_segments;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the intersection distance of two line segments a-b and c-d.
|
||||||
|
* The intersection distance is defined as the normalized distance (0..1)
|
||||||
|
* from point a to the intersection point of a-b and c-d.
|
||||||
|
*/
|
||||||
|
static float get_intersection_distance_of_segments(const float2 &co_a,
|
||||||
|
const float2 &co_b,
|
||||||
|
const float2 &co_c,
|
||||||
|
const float2 &co_d)
|
||||||
|
{
|
||||||
|
/* Get intersection point. */
|
||||||
|
const float a1 = co_b[1] - co_a[1];
|
||||||
|
const float b1 = co_a[0] - co_b[0];
|
||||||
|
const float c1 = a1 * co_a[0] + b1 * co_a[1];
|
||||||
|
|
||||||
|
const float a2 = co_d[1] - co_c[1];
|
||||||
|
const float b2 = co_c[0] - co_d[0];
|
||||||
|
const float c2 = a2 * co_c[0] + b2 * co_c[1];
|
||||||
|
|
||||||
|
const float det = float(a1 * b2 - a2 * b1);
|
||||||
|
if (det == 0.0f) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 isect((b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det);
|
||||||
|
|
||||||
|
/* Get normalized distance from point a to intersection point. */
|
||||||
|
const float length_ab = math::length(co_b - co_a);
|
||||||
|
float distance = (length_ab == 0.0f ?
|
||||||
|
0.0f :
|
||||||
|
math::clamp(math::length(isect - co_a) / length_ab, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a curve, find all intersections with other curves.
|
||||||
|
*/
|
||||||
|
static void get_intersections_of_curve_with_curves(const int src_curve,
|
||||||
|
const bke::CurvesGeometry &src,
|
||||||
|
const Span<float2> screen_space_positions,
|
||||||
|
const Span<rcti> screen_space_curve_bounds,
|
||||||
|
MutableSpan<bool> r_is_intersected_after_point,
|
||||||
|
MutableSpan<float2> r_intersection_distance)
|
||||||
|
{
|
||||||
|
const OffsetIndices<int> points_by_curve = src.points_by_curve();
|
||||||
|
const VArray<bool> is_cyclic = src.cyclic();
|
||||||
|
|
||||||
|
/* Edge case: skip curve with only one point. */
|
||||||
|
if (points_by_curve[src_curve].size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop all curve points and check for intersections between point a and point a + 1. */
|
||||||
|
const IndexRange src_curve_points = points_by_curve[src_curve].drop_back(
|
||||||
|
is_cyclic[src_curve] ? 0 : 1);
|
||||||
|
for (const int point_a : src_curve_points) {
|
||||||
|
const int point_b = (point_a == points_by_curve[src_curve].last()) ? src_curve_points.first() :
|
||||||
|
point_a + 1;
|
||||||
|
|
||||||
|
/* Get coordinates of segment a-b. */
|
||||||
|
const float2 co_a = screen_space_positions[point_a];
|
||||||
|
const float2 co_b = screen_space_positions[point_b];
|
||||||
|
rcti bbox_ab;
|
||||||
|
BLI_rcti_init_minmax(&bbox_ab);
|
||||||
|
BLI_rcti_do_minmax_v(&bbox_ab, int2(co_a));
|
||||||
|
BLI_rcti_do_minmax_v(&bbox_ab, int2(co_b));
|
||||||
|
BLI_rcti_pad(&bbox_ab, BBOX_PADDING, BBOX_PADDING);
|
||||||
|
|
||||||
|
float intersection_distance_min = FLT_MAX;
|
||||||
|
float intersection_distance_max = -FLT_MAX;
|
||||||
|
|
||||||
|
/* Loop all curves, looking for intersecting segments. */
|
||||||
|
for (const int curve : src.curves_range()) {
|
||||||
|
/* Only process curves with at least two points. */
|
||||||
|
if (points_by_curve[curve].size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bounding box check: skip curves that don't overlap segment a-b. */
|
||||||
|
if (!BLI_rcti_isect(&bbox_ab, &screen_space_curve_bounds[curve], nullptr)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find intersecting curve segments. */
|
||||||
|
const IndexRange points = points_by_curve[curve].drop_back(is_cyclic[curve] ? 0 : 1);
|
||||||
|
for (const int point_c : points) {
|
||||||
|
const int point_d = (point_c == points_by_curve[curve].last()) ? points.first() :
|
||||||
|
(point_c + 1);
|
||||||
|
|
||||||
|
/* Don't self check. */
|
||||||
|
if (curve == src_curve &&
|
||||||
|
(point_a == point_c || point_a == point_d || point_b == point_c || point_b == point_d))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip when bounding boxes of a-b and c-d don't overlap. */
|
||||||
|
const float2 co_c = screen_space_positions[point_c];
|
||||||
|
const float2 co_d = screen_space_positions[point_d];
|
||||||
|
rcti bbox_cd;
|
||||||
|
BLI_rcti_init_minmax(&bbox_cd);
|
||||||
|
BLI_rcti_do_minmax_v(&bbox_cd, int2(co_c));
|
||||||
|
BLI_rcti_do_minmax_v(&bbox_cd, int2(co_d));
|
||||||
|
BLI_rcti_pad(&bbox_cd, BBOX_PADDING, BBOX_PADDING);
|
||||||
|
if (!BLI_rcti_isect(&bbox_ab, &bbox_cd, nullptr)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some padding to the line segment c-d, otherwise we could just miss an
|
||||||
|
* intersection. */
|
||||||
|
const float2 padding_cd = math::normalize(co_d - co_c);
|
||||||
|
const float2 padded_c = co_c - padding_cd;
|
||||||
|
const float2 padded_d = co_d + padding_cd;
|
||||||
|
|
||||||
|
/* Check for intersection. */
|
||||||
|
const auto isect = math::isect_seg_seg(co_a, co_b, padded_c, padded_d);
|
||||||
|
if (ELEM(isect.kind, isect.LINE_LINE_CROSS, isect.LINE_LINE_EXACT)) {
|
||||||
|
/* We found an intersection, set the intersection flag for segment a-b. */
|
||||||
|
r_is_intersected_after_point[point_a] = true;
|
||||||
|
|
||||||
|
/* Calculate the intersection factor. This is the normalized distance (0..1) of the
|
||||||
|
* intersection point on line segment a-b, measured from point a. */
|
||||||
|
const float normalized_distance = get_intersection_distance_of_segments(
|
||||||
|
co_a, co_b, co_c, co_d);
|
||||||
|
intersection_distance_min = math::min(normalized_distance, intersection_distance_min);
|
||||||
|
intersection_distance_max = math::max(normalized_distance, intersection_distance_max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r_is_intersected_after_point[point_a]) {
|
||||||
|
r_intersection_distance[point_a][Distance::Min] = intersection_distance_min;
|
||||||
|
r_intersection_distance[point_a][Distance::Max] = intersection_distance_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a cutter segment by walking along the curve in forward or backward direction.
|
||||||
|
* A cutter segments ends at an intersection with another curve, or at the outer end of the curve.
|
||||||
|
*/
|
||||||
|
static void expand_cutter_segment_direction(Segment &segment,
|
||||||
|
const int direction,
|
||||||
|
const bke::CurvesGeometry &src,
|
||||||
|
const Span<bool> is_intersected_after_point,
|
||||||
|
const Span<float2> intersection_distance,
|
||||||
|
MutableSpan<bool> point_is_in_segment)
|
||||||
|
{
|
||||||
|
const OffsetIndices<int> points_by_curve = src.points_by_curve();
|
||||||
|
const int point_first = points_by_curve[segment.curve].first();
|
||||||
|
const int point_last = points_by_curve[segment.curve].last();
|
||||||
|
|
||||||
|
const Side segment_side = (direction == 1) ? Side::End : Side::Start;
|
||||||
|
int point_a = segment.point_range[segment_side];
|
||||||
|
|
||||||
|
bool intersected = false;
|
||||||
|
segment.is_intersected[segment_side] = false;
|
||||||
|
|
||||||
|
/* Walk along the curve points. */
|
||||||
|
while ((direction == 1 && point_a < point_last) || (direction == -1 && point_a > point_first)) {
|
||||||
|
const int point_b = point_a + direction;
|
||||||
|
const bool at_end_of_curve = (direction == -1 && point_b == point_first) ||
|
||||||
|
(direction == 1 && point_b == point_last);
|
||||||
|
|
||||||
|
/* Expand segment point range. */
|
||||||
|
segment.point_range[segment_side] = point_a;
|
||||||
|
point_is_in_segment[point_a] = true;
|
||||||
|
|
||||||
|
/* Check for intersections with other curves. The intersections were established in ascending
|
||||||
|
* point order, so in forward direction we look at line segment a-b, in backward direction we
|
||||||
|
* look at line segment b-a. */
|
||||||
|
const int intersection_point = direction == 1 ? point_a : point_b;
|
||||||
|
intersected = is_intersected_after_point[intersection_point];
|
||||||
|
|
||||||
|
/* Avoid orphaned points at the end of a curve. */
|
||||||
|
if (at_end_of_curve &&
|
||||||
|
((direction == -1 &&
|
||||||
|
intersection_distance[intersection_point][Distance::Max] < DISTANCE_FACTOR_THRESHOLD) ||
|
||||||
|
(direction == 1 && intersection_distance[intersection_point][Distance::Min] >
|
||||||
|
(1.0f - DISTANCE_FACTOR_THRESHOLD))))
|
||||||
|
{
|
||||||
|
intersected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When we hit an intersection, store the intersection distance. Potentially, line segment
|
||||||
|
* a-b can be intersected by multiple curves, so we want to fetch the first intersection
|
||||||
|
* point we bumped into. In forward direction this is the minimum distance, in backward
|
||||||
|
* direction the maximum. */
|
||||||
|
if (intersected) {
|
||||||
|
segment.is_intersected[segment_side] = true;
|
||||||
|
segment.intersection_distance[segment_side] =
|
||||||
|
(direction == 1) ? intersection_distance[intersection_point][Distance::Min] :
|
||||||
|
intersection_distance[intersection_point][Distance::Max];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep walking along curve. */
|
||||||
|
point_a += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust point range at curve ends. */
|
||||||
|
if (!intersected) {
|
||||||
|
if (direction == -1) {
|
||||||
|
segment.point_range[Side::Start] = point_first;
|
||||||
|
point_is_in_segment[point_first] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
segment.point_range[Side::End] = point_last;
|
||||||
|
point_is_in_segment[point_last] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a cutter segment of one point by walking along the curve in both directions.
|
||||||
|
*/
|
||||||
|
static void expand_cutter_segment(Segment &segment,
|
||||||
|
const bke::CurvesGeometry &src,
|
||||||
|
const Span<bool> is_intersected_after_point,
|
||||||
|
const Span<float2> intersection_distance,
|
||||||
|
MutableSpan<bool> point_is_in_segment)
|
||||||
|
{
|
||||||
|
const int8_t directions[2] = {-1, 1};
|
||||||
|
for (const int8_t direction : directions) {
|
||||||
|
expand_cutter_segment_direction(segment,
|
||||||
|
direction,
|
||||||
|
src,
|
||||||
|
is_intersected_after_point,
|
||||||
|
intersection_distance,
|
||||||
|
point_is_in_segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bke::CurvesGeometry trim_curve_segments(const bke::CurvesGeometry &src,
|
||||||
|
const Span<float2> screen_space_positions,
|
||||||
|
const Span<rcti> screen_space_curve_bounds,
|
||||||
|
const IndexMask &curve_selection,
|
||||||
|
const Vector<Vector<int>> &selected_points_in_curves,
|
||||||
|
const bool keep_caps)
|
||||||
|
{
|
||||||
|
const OffsetIndices<int> src_points_by_curve = src.points_by_curve();
|
||||||
|
|
||||||
|
/* For the selected curves, find all the intersections with other curves. */
|
||||||
|
const int src_points_num = src.points_num();
|
||||||
|
Array<bool> is_intersected_after_point(src_points_num, false);
|
||||||
|
Array<float2> intersection_distance(src_points_num);
|
||||||
|
curve_selection.foreach_index(GrainSize(32), [&](const int curve_i) {
|
||||||
|
get_intersections_of_curve_with_curves(curve_i,
|
||||||
|
src,
|
||||||
|
screen_space_positions,
|
||||||
|
screen_space_curve_bounds,
|
||||||
|
is_intersected_after_point,
|
||||||
|
intersection_distance);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Expand the selected curve points to cutter segments (the part of the curve between two
|
||||||
|
* intersections). */
|
||||||
|
const VArray<bool> is_cyclic = src.cyclic();
|
||||||
|
Array<bool> point_is_in_segment(src_points_num, false);
|
||||||
|
threading::EnumerableThreadSpecific<Segments> cutter_segments_by_thread;
|
||||||
|
curve_selection.foreach_index(GrainSize(32), [&](const int curve_i, const int pos) {
|
||||||
|
Segments &thread_segments = cutter_segments_by_thread.local();
|
||||||
|
for (const int selected_point : selected_points_in_curves[pos]) {
|
||||||
|
/* Skip point when it is already part of a cutter segment. */
|
||||||
|
if (point_is_in_segment[selected_point]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create new cutter segment. */
|
||||||
|
Segment *segment = thread_segments.create_segment(curve_i, selected_point);
|
||||||
|
|
||||||
|
/* Expand the cutter segment in both directions until an intersection is found or the
|
||||||
|
* end of the curve is reached. */
|
||||||
|
expand_cutter_segment(
|
||||||
|
*segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
|
||||||
|
|
||||||
|
/* When the end of a curve is reached and the curve is cyclic, we add an extra cutter
|
||||||
|
* segment for the cyclic second part. */
|
||||||
|
if (is_cyclic[curve_i] &&
|
||||||
|
(!segment->is_intersected[Side::Start] || !segment->is_intersected[Side::End]) &&
|
||||||
|
!(!segment->is_intersected[Side::Start] && !segment->is_intersected[Side::End]))
|
||||||
|
{
|
||||||
|
const int cyclic_outer_point = !segment->is_intersected[Side::Start] ?
|
||||||
|
src_points_by_curve[curve_i].last() :
|
||||||
|
src_points_by_curve[curve_i].first();
|
||||||
|
segment = thread_segments.create_segment(curve_i, cyclic_outer_point);
|
||||||
|
|
||||||
|
/* Expand this second segment. */
|
||||||
|
expand_cutter_segment(
|
||||||
|
*segment, src, is_intersected_after_point, intersection_distance, point_is_in_segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Segments cutter_segments;
|
||||||
|
for (Segments &thread_segments : cutter_segments_by_thread) {
|
||||||
|
cutter_segments.segments.extend(thread_segments.segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Abort when no cutter segments are found in the lasso area. */
|
||||||
|
bke::CurvesGeometry dst;
|
||||||
|
if (cutter_segments.segments.is_empty()) {
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Merge adjacent cutter segments. E.g. two point ranges of 0-10 and 11-20 will be merged
|
||||||
|
* to one range of 0-20. */
|
||||||
|
cutter_segments.merge_adjacent_segments();
|
||||||
|
|
||||||
|
/* Create the point transfer data, for converting the source geometry into the new geometry.
|
||||||
|
* First, add all curve points not affected by the cutter tool. */
|
||||||
|
Array<Vector<PointTransferData>> src_to_dst_points(src_points_num);
|
||||||
|
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) {
|
||||||
|
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
||||||
|
const int src_next_point = (src_point == src_points.last()) ? src_points.first() :
|
||||||
|
(src_point + 1);
|
||||||
|
|
||||||
|
/* Add the source point only if it does not lie inside a cutter segment. */
|
||||||
|
if (!point_is_in_segment[src_point]) {
|
||||||
|
dst_points.append({src_point, src_next_point, 0.0f, true, false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add new curve points at the intersection points of the cutter segments.
|
||||||
|
*
|
||||||
|
* a b
|
||||||
|
* source curve o--------o---*---o--------o----*---o--------o
|
||||||
|
* ^ ^
|
||||||
|
* cutter segment |-----------------|
|
||||||
|
*
|
||||||
|
* o = existing curve point
|
||||||
|
* * = newly created curve point
|
||||||
|
*
|
||||||
|
* The curve points between *a and *b will be deleted.
|
||||||
|
* The source curve will be cut in two:
|
||||||
|
* - the first curve ends at *a
|
||||||
|
* - the second curve starts at *b
|
||||||
|
*
|
||||||
|
* We avoid inserting a new point very close to the adjacent one, because that's just adding
|
||||||
|
* clutter to the geometry.
|
||||||
|
*/
|
||||||
|
for (const Segment &cutter_segment : cutter_segments.segments) {
|
||||||
|
/* Intersection at cutter segment start. */
|
||||||
|
if (cutter_segment.is_intersected[Side::Start] &&
|
||||||
|
cutter_segment.intersection_distance[Side::Start] > DISTANCE_FACTOR_THRESHOLD)
|
||||||
|
{
|
||||||
|
const int src_point = cutter_segment.point_range[Side::Start] - 1;
|
||||||
|
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
||||||
|
dst_points.append({src_point,
|
||||||
|
src_point + 1,
|
||||||
|
cutter_segment.intersection_distance[Side::Start],
|
||||||
|
false,
|
||||||
|
false});
|
||||||
|
}
|
||||||
|
/* Intersection at cutter segment end. */
|
||||||
|
if (cutter_segment.is_intersected[Side::End]) {
|
||||||
|
const int src_point = cutter_segment.point_range[Side::End];
|
||||||
|
if (cutter_segment.intersection_distance[Side::End] < (1.0f - DISTANCE_FACTOR_THRESHOLD)) {
|
||||||
|
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point];
|
||||||
|
dst_points.append({src_point,
|
||||||
|
src_point + 1,
|
||||||
|
cutter_segment.intersection_distance[Side::End],
|
||||||
|
false,
|
||||||
|
true});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Mark the 'is_cut' flag on the next point, because a new curve is starting here after
|
||||||
|
* the removed cutter segment. */
|
||||||
|
Vector<PointTransferData> &dst_points = src_to_dst_points[src_point + 1];
|
||||||
|
for (PointTransferData &dst_point : dst_points) {
|
||||||
|
if (dst_point.is_src_point) {
|
||||||
|
dst_point.is_cut = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the new curves geometry. */
|
||||||
|
compute_topology_change(src, dst, src_to_dst_points, keep_caps);
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cutter
|
||||||
|
|
||||||
} // namespace blender::ed::greasepencil
|
} // namespace blender::ed::greasepencil
|
||||||
|
@ -591,4 +591,13 @@ bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &draw
|
|||||||
float outline_offset,
|
float outline_offset,
|
||||||
int material_index);
|
int material_index);
|
||||||
|
|
||||||
|
namespace cutter {
|
||||||
|
bke::CurvesGeometry trim_curve_segments(const bke::CurvesGeometry &src,
|
||||||
|
Span<float2> screen_space_positions,
|
||||||
|
Span<rcti> screen_space_curve_bounds,
|
||||||
|
const IndexMask &curve_selection,
|
||||||
|
const Vector<Vector<int>> &selected_points_in_curves,
|
||||||
|
bool keep_caps);
|
||||||
|
}; // namespace cutter
|
||||||
|
|
||||||
} // namespace blender::ed::greasepencil
|
} // namespace blender::ed::greasepencil
|
||||||
|
Loading…
Reference in New Issue
Block a user