Fixed issue in a 32bit clipper, where IntersectPoint() checked for

the Y coordinate of the calculated intersection point for validity,
but the Y coordinate was already rounded to 32bits, thus an overflow
may have in rare cases masked invalidity of the result.
This commit is contained in:
Vojtech Bubnik 2023-09-01 09:42:05 +02:00
parent 7f57ba6b12
commit b39c33414f

View File

@ -84,15 +84,24 @@ inline IntPoint IntPoint2d(cInt x, cInt y)
); );
} }
inline cInt Round(double val) // Fast rounding upwards.
inline double FRound(double a)
{ {
double v = val < 0 ? val - 0.5 : val + 0.5; // Why does Java Math.round(0.49999999999999994) return 1?
// https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1
return a == 0.49999999999999994 ? 0 : floor(a + 0.5);
}
template<typename IType>
inline IType Round(double val)
{
double v = FRound(val);
#if defined(CLIPPERLIB_INT32) && ! defined(NDEBUG) #if defined(CLIPPERLIB_INT32) && ! defined(NDEBUG)
static constexpr const double hi = 65536 * 16383; static constexpr const double hi = 65536 * 16383;
if (v > hi || -v > hi) if (v > hi || -v > hi)
throw clipperException("Coordinate outside allowed range"); throw clipperException("Coordinate outside allowed range");
#endif #endif
return static_cast<cInt>(v); return static_cast<IType>(v);
} }
// Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional. // Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional.
@ -340,7 +349,7 @@ inline cInt TopX(TEdge &edge, const cInt currentY)
{ {
return (currentY == edge.Top.y()) ? return (currentY == edge.Top.y()) ?
edge.Top.x() : edge.Top.x() :
edge.Bot.x() + Round(edge.Dx *(currentY - edge.Bot.y())); edge.Bot.x() + Round<cInt>(edge.Dx *(currentY - edge.Bot.y()));
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -350,65 +359,53 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
ip.z() = 0; ip.z() = 0;
#endif #endif
double b1, b2;
if (Edge1.Dx == Edge2.Dx) if (Edge1.Dx == Edge2.Dx)
{ {
ip.y() = Edge1.Curr.y(); ip.y() = Edge1.Curr.y();
ip.x() = TopX(Edge1, ip.y()); ip.x() = TopX(Edge1, ip.y());
return; return;
} }
else if (Edge1.Delta.x() == 0)
int64_t y;
if (Edge1.Delta.x() == 0)
{ {
ip.x() = Edge1.Bot.x(); ip.x() = Edge1.Bot.x();
if (IsHorizontal(Edge2)) y = IsHorizontal(Edge2) ?
ip.y() = Edge2.Bot.y(); Edge2.Bot.y() :
else Round<int64_t>(ip.x() / Edge2.Dx + Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx));
{
b2 = Edge2.Bot.y() - (Edge2.Bot.x() / Edge2.Dx);
ip.y() = Round(ip.x() / Edge2.Dx + b2);
}
} }
else if (Edge2.Delta.x() == 0) else if (Edge2.Delta.x() == 0)
{ {
ip.x() = Edge2.Bot.x(); ip.x() = Edge2.Bot.x();
if (IsHorizontal(Edge1)) y = IsHorizontal(Edge1) ?
ip.y() = Edge1.Bot.y(); Edge1.Bot.y() :
else Round<int64_t>(ip.x() / Edge1.Dx + Edge1.Bot.y() - (Edge1.Bot.x() / Edge1.Dx));
{ }
b1 = Edge1.Bot.y() - (Edge1.Bot.x() / Edge1.Dx); else
ip.y() = Round(ip.x() / Edge1.Dx + b1);
}
}
else
{ {
b1 = double(Edge1.Bot.x()) - double(Edge1.Bot.y()) * Edge1.Dx; double b1 = double(Edge1.Bot.x()) - double(Edge1.Bot.y()) * Edge1.Dx;
b2 = double(Edge2.Bot.x()) - double(Edge2.Bot.y()) * Edge2.Dx; double b2 = double(Edge2.Bot.x()) - double(Edge2.Bot.y()) * Edge2.Dx;
double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); double q = (b2-b1) / (Edge1.Dx - Edge2.Dx);
ip.y() = Round(q); y = Round<int64_t>(q);
ip.x() = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? ip.x() = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ?
Round(Edge1.Dx * q + b1) : Round<cInt>(Edge1.Dx * q + b1) :
Round(Edge2.Dx * q + b2); Round<cInt>(Edge2.Dx * q + b2);
} }
if (ip.y() < Edge1.Top.y() || ip.y() < Edge2.Top.y()) ip.y() = cInt(y);
if (y < Edge1.Top.y() || y < Edge2.Top.y())
{ {
if (Edge1.Top.y() > Edge2.Top.y()) ip.y() = (Edge1.Top.y() > Edge2.Top.y() ? Edge1 : Edge2).Top.y();
ip.y() = Edge1.Top.y(); y = ip.y();
else ip.x() = TopX(std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx) ? Edge1 : Edge2, ip.y());
ip.y() = Edge2.Top.y(); }
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.x() = TopX(Edge1, ip.y());
else
ip.x() = TopX(Edge2, ip.y());
}
//finally, don't allow 'ip' to be BELOW curr.y() (ie bottom of scanbeam) ... //finally, don't allow 'ip' to be BELOW curr.y() (ie bottom of scanbeam) ...
if (ip.y() > Edge1.Curr.y()) if (y > Edge1.Curr.y())
{ {
ip.y() = Edge1.Curr.y(); ip.y() = Edge1.Curr.y();
//use the more vertical edge to derive X ... //use the more vertical edge to derive X ...
if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) ip.x() = TopX(std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx) ? Edge2 : Edge1, ip.y());
ip.x() = TopX(Edge2, ip.y()); else
ip.x() = TopX(Edge1, ip.y());
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -3539,8 +3536,8 @@ void ClipperOffset::DoOffset(double delta)
for (cInt j = 1; j <= steps; j++) for (cInt j = 1; j <= steps; j++)
{ {
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[0].x() + X * delta), Round<cInt>(m_srcPoly[0].x() + X * delta),
Round(m_srcPoly[0].y() + Y * delta))); Round<cInt>(m_srcPoly[0].y() + Y * delta)));
double X2 = X; double X2 = X;
X = X * m_cos - m_sin * Y; X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos; Y = X2 * m_sin + Y * m_cos;
@ -3552,8 +3549,8 @@ void ClipperOffset::DoOffset(double delta)
for (int j = 0; j < 4; ++j) for (int j = 0; j < 4; ++j)
{ {
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[0].x() + X * delta), Round<cInt>(m_srcPoly[0].x() + X * delta),
Round(m_srcPoly[0].y() + Y * delta))); Round<cInt>(m_srcPoly[0].y() + Y * delta)));
if (X < 0) X = 1; if (X < 0) X = 1;
else if (Y < 0) Y = 1; else if (Y < 0) Y = 1;
else X = -1; else X = -1;
@ -3606,9 +3603,9 @@ void ClipperOffset::DoOffset(double delta)
if (node.m_endtype == etOpenButt) if (node.m_endtype == etOpenButt)
{ {
int j = len - 1; int j = len - 1;
pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); pt1 = IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * delta), Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * delta));
m_destPoly.emplace_back(pt1); m_destPoly.emplace_back(pt1);
pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); pt1 = IntPoint2d(Round<cInt>(m_srcPoly[j].x() - m_normals[j].x() * delta), Round<cInt>(m_srcPoly[j].y() - m_normals[j].y() * delta));
m_destPoly.emplace_back(pt1); m_destPoly.emplace_back(pt1);
} }
else else
@ -3633,9 +3630,9 @@ void ClipperOffset::DoOffset(double delta)
if (node.m_endtype == etOpenButt) if (node.m_endtype == etOpenButt)
{ {
pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); pt1 = IntPoint2d(Round<cInt>(m_srcPoly[0].x() - m_normals[0].x() * delta), Round<cInt>(m_srcPoly[0].y() - m_normals[0].y() * delta));
m_destPoly.emplace_back(pt1); m_destPoly.emplace_back(pt1);
pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); pt1 = IntPoint2d(Round<cInt>(m_srcPoly[0].x() + m_normals[0].x() * delta), Round<cInt>(m_srcPoly[0].y() + m_normals[0].y() * delta));
m_destPoly.emplace_back(pt1); m_destPoly.emplace_back(pt1);
} }
else else
@ -3663,8 +3660,8 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() ); double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() );
if (cosA > 0) // angle => 0 degrees if (cosA > 0) // angle => 0 degrees
{ {
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); Round<cInt>(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
return; return;
} }
//else angle => 180 degrees //else angle => 180 degrees
@ -3674,11 +3671,11 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
if (m_sinA * m_delta < 0) if (m_sinA * m_delta < 0)
{ {
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[k].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); Round<cInt>(m_srcPoly[j].y() + m_normals[k].y() * m_delta)));
m_destPoly.emplace_back(m_srcPoly[j]); m_destPoly.emplace_back(m_srcPoly[j]);
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
} }
else else
switch (jointype) switch (jointype)
@ -3702,19 +3699,19 @@ void ClipperOffset::DoSquare(int j, int k)
double dx = std::tan(std::atan2(m_sinA, double dx = std::tan(std::atan2(m_sinA,
m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4); m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4);
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)), Round<cInt>(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)),
Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx)))); Round<cInt>(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx))));
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)), Round<cInt>(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)),
Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx)))); Round<cInt>(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx))));
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void ClipperOffset::DoMiter(int j, int k, double r) void ClipperOffset::DoMiter(int j, int k, double r)
{ {
double q = m_delta / r; double q = m_delta / r;
m_destPoly.emplace_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), m_destPoly.emplace_back(IntPoint2d(Round<cInt>(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q),
Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q))); Round<cInt>(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q)));
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -3722,21 +3719,21 @@ void ClipperOffset::DoRound(int j, int k)
{ {
double a = std::atan2(m_sinA, double a = std::atan2(m_sinA,
m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()); m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y());
auto steps = std::max<int>(Round(m_StepsPerRad * std::fabs(a)), 1); auto steps = std::max<int>(Round<cInt>(m_StepsPerRad * std::fabs(a)), 1);
double X = m_normals[k].x(), Y = m_normals[k].y(), X2; double X = m_normals[k].x(), Y = m_normals[k].y(), X2;
for (int i = 0; i < steps; ++i) for (int i = 0; i < steps; ++i)
{ {
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + X * m_delta), Round<cInt>(m_srcPoly[j].x() + X * m_delta),
Round(m_srcPoly[j].y() + Y * m_delta))); Round<cInt>(m_srcPoly[j].y() + Y * m_delta)));
X2 = X; X2 = X;
X = X * m_cos - m_sin * Y; X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos; Y = X2 * m_sin + Y * m_cos;
} }
m_destPoly.emplace_back(IntPoint2d( m_destPoly.emplace_back(IntPoint2d(
Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round<cInt>(m_srcPoly[j].x() + m_normals[j].x() * m_delta),
Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); Round<cInt>(m_srcPoly[j].y() + m_normals[j].y() * m_delta)));
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------