Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-sided offset with EndType::ButtLeft #905

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions CPP/Clipper2Lib/include/clipper2/clipper.offset.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ enum class JoinType { Square, Bevel, Round, Miter };
//Square : Joins are 'squared' at exactly the offset distance (more complex code)
//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster)

enum class EndType {Polygon, Joined, Butt, Square, Round};
//Butt : offsets both sides of a path, with square blunt ends
//Square : offsets both sides of a path, with square extended ends
//Round : offsets both sides of a path, with round extended ends
//Joined : offsets both sides of a path, with joined ends
//Polygon: offsets only one side of a closed path
enum class EndType {Polygon, Joined, Butt, Square, Round, ButtLeft, ButtRight};
//Butt : offsets both sides of a path, with square blunt ends
//Square : offsets both sides of a path, with square extended ends
//Round : offsets both sides of a path, with round extended ends
//Joined : offsets both sides of a path, with joined ends
//Polygon : offsets only one side of a closed path
//ButtLeft : offsets left side of a path with square blunt ends
//ButtRight: offsets right side of a path with square blunt ends

typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;

Expand Down
112 changes: 88 additions & 24 deletions CPP/Clipper2Lib/src/clipper.offset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,25 +319,53 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size

if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// to ensure that path reversals (ie over-shrunk paths) are removed.
// in case of EndType::ButtLeft the rectangles, formed by a one-sided offset,
// will cause artifacts if the angle is sharper then 90 degree.
// to avoid these artifacts calculate the intersection between between the segments
// of the one-sided offset.
const size_t l = k > j ? j - 1 : j + 1; // k is always j + 1 or j - 1, l is the opposite
const bool intersect =
end_type_ == EndType::ButtLeft // one-sided offset
&& cos_a < 0 // < 90 degre
&& !path_out.empty() // safety check, can this never happen?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think path_out can never by empty at this point, but i'm not 100% sure.

&& l < path.size(); // safety check, can this never happen?
Copy link
Contributor Author

@LarsSkiba LarsSkiba Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basicely j must never be the last or first point, depending on direction given by the relation between k and j.
It seems given to me, but again i'm not 100% sure.


if (!intersect)
{
// is concave
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// to ensure that path reversals (ie over-shrunk paths) are removed.
#ifdef USINGZ
path_out.push_back(Point64(GetPerpendic(path[j], norms[k], group_delta_), path[j].z));
path_out.push_back(Point64(GetPerpendic(path[j], norms[k], group_delta_), path[j].z));
#else
path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
#endif
// when the angle is almost flat (cos_a ~= 1), it's safe to skip this middle point
if (cos_a < 0.999) path_out.push_back(path[j]); // (#405, #873)

// when the angle is almost flat (cos_a ~= 1), it's safe to skip this middle point
if (cos_a < 0.999) path_out.push_back(path[j]); // (#405, #873)

#ifdef USINGZ
path_out.push_back(Point64(GetPerpendic(path[j], norms[j], group_delta_), path[j].z));
path_out.push_back(Point64(GetPerpendic(path[j], norms[j], group_delta_), path[j].z));
#else
path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
#endif
}
else // point of intersection for one-sided offset
{
// calculate intersection point between the two line segments
// shifted in the offset direction at current point.
const PointD pt1a(path_out.back());
const PointD pt1b(GetPerpendic(path[j], norms[k], group_delta_));
const PointD pt2a(GetPerpendic(path[j], norms[j], group_delta_));
const PointD pt2b(GetPerpendic(path[l], norms[j], group_delta_));

PointD pt;
GetSegmentIntersectPt(pt1a, pt1b, pt2a, pt2b, pt);

path_out.push_back(Point64(pt));
}
}
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
{
Expand Down Expand Up @@ -385,8 +413,10 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
{
// do the line start cap
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);

const double abs_group_delta = std::fabs(group_delta_);

if (std::fabs(group_delta_) <= floating_point_tolerance)
if (abs_group_delta <= floating_point_tolerance)
path_out.push_back(path[0]);
else
{
Expand All @@ -398,27 +428,39 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
case EndType::Round:
DoRound(path, 0, 0, PI);
break;
case EndType::ButtRight:
path_out.push_back(Point64(GetPerpendicD(path[0], norms[0], abs_group_delta)));
break;
case EndType::ButtLeft:
break;
default:
DoSquare(path, 0, 0);
break;
}
}

size_t highI = path.size() - 1;
// offset the left side going forward
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
OffsetPoint(group, path, j, k);

// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI];

if (end_type_ != EndType::ButtLeft)
{
// offset the left side going forward
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
OffsetPoint(group, path, j, k);
}

if (end_type_ != EndType::ButtRight)
{
// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI];
}

// do the line end cap
if (deltaCallback64_)
group_delta_ = deltaCallback64_(path, norms, highI, highI);

if (std::fabs(group_delta_) <= floating_point_tolerance)
if (abs_group_delta <= floating_point_tolerance)
path_out.push_back(path[highI]);
else
{
Expand All @@ -430,14 +472,36 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
case EndType::Round:
DoRound(path, highI, highI, PI);
break;
case EndType::ButtRight:
path_out.push_back(Point64(GetPerpendicD(path[highI], norms[1], abs_group_delta)));
break;
case EndType::ButtLeft:
path_out.push_back(Point64(GetPerpendicD(path[highI], norms[highI], abs_group_delta)));
break;
default:
DoSquare(path, highI, highI);
break;
}
}

if (end_type_ != EndType::ButtRight)
{
for (size_t j = highI - 1, k = highI; j > 0; k = j, --j)
OffsetPoint(group, path, j, k);
}

if (end_type_ == EndType::ButtLeft)
{
path_out.reserve(path_out.size() + 1 + path.size());
path_out.push_back(Point64(GetPerpendicD(path[0], norms[1], abs_group_delta)));
path_out.insert(path_out.begin(), path.begin(), path.end());
}

if(end_type_ == EndType::ButtRight)
{
path_out.insert(path_out.end(), path.rbegin(), path.rend());
}

for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
OffsetPoint(group, path, j, k);
solution->push_back(path_out);
}

Expand Down
Loading