Skip to content

Commit

Permalink
Changed MakePath to expect only ordinal/arithmetic values (C++) (#398)
Browse files Browse the repository at this point in the history
Fixed InflatePaths to return unchanged paths when delta == 0 (#389)
Minor tweak to detecting "touching" solution polygons
Fixed minor bug in ClipperOffset (#392,#393)
Tidied up error handling in C++
Added error_code param to ScalePath function (C++)
Tweaks to C# CI testing (#388)
Major rewrite of RectClip function (Delphi only so far) (#383)
  • Loading branch information
AngusJohnson committed Feb 6, 2023
1 parent 2f8d52e commit 1057798
Show file tree
Hide file tree
Showing 19 changed files with 870 additions and 458 deletions.
76 changes: 50 additions & 26 deletions CPP/Clipper2Lib/include/clipper2/clipper.core.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,45 @@ namespace Clipper2Lib
"Precision exceeds the permitted range";
static const char* range_error =
"Values exceed permitted range";
static const char* scale_error =
"Invalid scale (either 0 or too large)";
static const char* non_pair_error =
"There must be 2 values for each coordinate";
#endif

// error codes (2^n)
const int precision_error_i = 1; // non-fatal
const int range_error_i = 2;
const int precision_error_i = 1; // non-fatal
const int scale_error_i = 2; // non-fatal
const int non_pair_error_i = 4; // non-fatal
const int range_error_i = 64;

static const double PI = 3.141592653589793238;
static const int64_t MAX_COORD = INT64_MAX >> 2;
static const int64_t MIN_COORD = -MAX_COORD;
static const int64_t INVALID = INT64_MAX;
const double max_coord = static_cast<double>(MAX_COORD);
const double min_coord = static_cast<double>(MIN_COORD);

static const double MAX_DBL = (std::numeric_limits<double>::max)();
static const double MIN_DBL = (std::numeric_limits<double>::min)();

static void DoError(int error_code)
{
#if __cpp_exceptions
switch (error_code)
{
case precision_error_i:
throw Clipper2Exception(precision_error);
case scale_error_i:
throw Clipper2Exception(scale_error);
case non_pair_error_i:
throw Clipper2Exception(non_pair_error);
case range_error_i:
throw Clipper2Exception(range_error);
}
#endif
}

//By far the most widely used filling rules for polygons are EvenOdd
//and NonZero, sometimes called Alternate and Winding respectively.
//https://en.wikipedia.org/wiki/Nonzero-rule
Expand Down Expand Up @@ -384,9 +409,19 @@ namespace Clipper2Lib


template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y)
inline Path<T1> ScalePath(const Path<T2>& path,
double scale_x, double scale_y, int& error_code)
{
Path<T1> result;
if (scale_x == 0 || scale_y == 0)
{
error_code |= scale_error_i;
DoError(scale_error_i);
// if no exception, treat as non-fatal error
if (scale_x == 0) scale_x = 1.0;
if (scale_y == 0) scale_y = 1.0;
}

result.reserve(path.size());
#ifdef USINGZ
std::transform(path.begin(), path.end(), back_inserter(result),
Expand All @@ -401,9 +436,10 @@ namespace Clipper2Lib
}

template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale)
inline Path<T1> ScalePath(const Path<T2>& path,
double scale, int& error_code)
{
return ScalePath<T1, T2>(path, scale, scale);
return ScalePath<T1, T2>(path, scale, scale, error_code);
}

template <typename T1, typename T2>
Expand All @@ -416,27 +452,21 @@ namespace Clipper2Lib
!std::numeric_limits<T2>::is_integer)
{
RectD r = Bounds(paths);
const double max_coord_d = static_cast<double>(MAX_COORD);
const double min_coord_d = static_cast<double>(MIN_COORD);
if ((r.left * scale_x) < min_coord_d ||
(r.right * scale_x) > max_coord_d ||
(r.top * scale_y) < min_coord_d ||
(r.bottom * scale_y) > max_coord_d)
if ((r.left * scale_x) < min_coord ||
(r.right * scale_x) > max_coord ||
(r.top * scale_y) < min_coord ||
(r.bottom * scale_y) > max_coord)
{
error_code |= range_error_i;
#if __cpp_exceptions
throw Clipper2Exception(range_error);
#else
// error_code = range_error_i; // compiler complains if here
return result; // empty
#endif
DoError(range_error_i);
return result; // empty path
}
}

result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[scale_x, scale_y](const auto& path)
{ return ScalePath<T1, T2>(path, scale_x, scale_y); });
[=, &error_code](const auto& path)
{ return ScalePath<T1, T2>(path, scale_x, scale_y, error_code); });
return result;
}

Expand Down Expand Up @@ -578,11 +608,8 @@ namespace Clipper2Lib
{
if (precision >= -8 && precision <= 8) return;
error_code |= precision_error_i; // non-fatal error
#if __cpp_exceptions
throw Clipper2Exception(precision_error);
#else
DoError(precision_error_i); // unless exceptions enabled
precision = precision > 8 ? 8 : -8;
#endif
}

inline void CheckPrecision(int& precision)
Expand Down Expand Up @@ -675,9 +702,6 @@ namespace Clipper2Lib
return Area<T>(poly) >= 0;
}

static const double max_coord = static_cast<double>(MAX_COORD);
static const double min_coord = static_cast<double>(MIN_COORD);

inline int64_t CheckCastInt64(double val)
{
if ((val >= max_coord) || (val <= min_coord)) return INVALID;
Expand Down
156 changes: 38 additions & 118 deletions CPP/Clipper2Lib/include/clipper2/clipper.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define CLIPPER_H

#include <cstdlib>
#include <type_traits>
#include <vector>

#include "clipper.core.h"
Expand Down Expand Up @@ -134,6 +135,7 @@ namespace Clipper2Lib {
inline Paths64 InflatePaths(const Paths64& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0)
{
if (!delta) return paths;
ClipperOffset clip_offset(miter_limit);
clip_offset.AddPaths(paths, jt, et);
return clip_offset.Execute(delta);
Expand All @@ -144,6 +146,7 @@ namespace Clipper2Lib {
{
int error_code = 0;
CheckPrecision(precision, error_code);
if (!delta) return paths;
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit);
Expand Down Expand Up @@ -401,94 +404,6 @@ namespace Clipper2Lib {
return true;
}

inline bool GetInt(std::string::const_iterator& iter, const
std::string::const_iterator& end_iter, int64_t& val)
{
val = 0;
bool is_neg = *iter == '-';
if (is_neg) ++iter;
std::string::const_iterator start_iter = iter;
while (iter != end_iter &&
((*iter >= '0') && (*iter <= '9')))
{
val = val * 10 + (static_cast<int64_t>(*iter++) - '0');
}
if (is_neg) val = -val;
return (iter != start_iter);
}

inline bool GetFloat(std::string::const_iterator& iter, const
std::string::const_iterator& end_iter, double& val)
{
val = 0;
bool is_neg = *iter == '-';
if (is_neg) ++iter;
int dec_pos = 1;
const std::string::const_iterator start_iter = iter;
while (iter != end_iter && (*iter == '.' ||
((*iter >= '0') && (*iter <= '9'))))
{
if (*iter == '.')
{
if (dec_pos != 1) break;
dec_pos = 0;
++iter;
continue;
}
if (dec_pos != 1) --dec_pos;
val = val * 10 + ((int64_t)(*iter++) - '0');
}
if (iter == start_iter || dec_pos == 0) return false;
if (dec_pos < 0)
val *= std::pow(10, dec_pos);
if (is_neg)
val *= -1;
return true;
}

inline void SkipWhiteSpace(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter)
{
while (iter != end_iter && *iter <= ' ') ++iter;
}

inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter)
{
bool comma_seen = false;
while (iter != end_iter)
{
if (*iter == ' ') ++iter;
else if (*iter == ',')
{
if (comma_seen) return; // don't skip 2 commas!
comma_seen = true;
++iter;
}
else return;
}
}

inline bool has_one_match(const char c, char* chrs)
{
while (*chrs > 0 && c != *chrs) ++chrs;
if (!*chrs) return false;
*chrs = ' '; // only match once per char
return true;
}


inline void SkipUserDefinedChars(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter, const std::string& skip_chars)
{
const size_t MAX_CHARS = 16;
char buff[MAX_CHARS] = {0};
std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]);
while (iter != end_iter &&
(*iter <= ' ' || has_one_match(*iter, buff))) ++iter;
return;
}

static void OutlinePolyPath(std::ostream& os,
bool isHole, size_t count, const std::string& preamble)
{
Expand Down Expand Up @@ -588,39 +503,44 @@ namespace Clipper2Lib {
return true;
}

inline Path64 MakePath(const std::string& s)
{
const std::string skip_chars = " ,(){}[]";
Path64 result;
std::string::const_iterator s_iter = s.cbegin();
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
while (s_iter != s.cend())
{
int64_t y = 0, x = 0;
if (!details::GetInt(s_iter, s.cend(), x)) break;
details::SkipSpacesWithOptionalComma(s_iter, s.cend());
if (!details::GetInt(s_iter, s.cend(), y)) break;
result.push_back(Point64(x, y));
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
}
template<typename T, std::size_t N,
typename std::enable_if<
std::is_integral<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline Path64 MakePath(const T(&list)[N])
{
if (N % 2 != 0)
DoError(non_pair_error_i); // non-fatal without exception handling
Path64 result; // else ignores unpaired value
result.reserve(N / 2);
#ifdef USINGZ
for (size_t i = 0; i < N; ++i)
result.emplace_back(Point64{ list[i], list[++i], 0 });
#else
for (size_t i = 0; i < N; ++i)
result.emplace_back(Point64{ list[i], list[++i] });
#endif
return result;
}

inline PathD MakePathD(const std::string& s)
{
const std::string skip_chars = " ,(){}[]";
PathD result;
std::string::const_iterator s_iter = s.cbegin();
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
while (s_iter != s.cend())
{
double y = 0, x = 0;
if (!details::GetFloat(s_iter, s.cend(), x)) break;
details::SkipSpacesWithOptionalComma(s_iter, s.cend());
if (!details::GetFloat(s_iter, s.cend(), y)) break;
result.push_back(PointD(x, y));
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
}
template<typename T, std::size_t N,
typename std::enable_if<std::is_arithmetic<T>::value &&
!std::is_same<char, T>::value, bool
>::type = true>
inline PathD MakePathD(const T(&list)[N])
{
if (N % 2 != 0)
DoError(non_pair_error_i); // non-fatal without exception handling
PathD result; // else ignores unpaired value
result.reserve(N / 2);
#ifdef USINGZ
for (size_t i = 0; i < N; ++i)
result.emplace_back(PointD{ list[i], list[++i], 0 });
#else
for (size_t i = 0; i < N; ++i)
result.emplace_back(PointD{ list[i], list[++i] });
#endif
return result;
}

Expand Down
22 changes: 14 additions & 8 deletions CPP/Clipper2Lib/src/clipper.engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1828,7 +1828,6 @@ namespace Clipper2Lib {
return resultOp;
}


inline void ClipperBase::DeleteFromAEL(Active& e)
{
Active* prev = e.prev_in_ael;
Expand Down Expand Up @@ -2654,9 +2653,13 @@ namespace Clipper2Lib {
IsOpen(*prev) || !IsHotEdge(*prev) ||
pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins
return;
if (check_curr_x) prev->curr_x = TopX(*prev, pt.y);
if (e.curr_x != prev->curr_x || CrossProduct(e.top, pt, prev->top))
return;

if (check_curr_x)
{
if (DistanceFromLineSqrd(pt, prev->bot, prev->top) > 0.35) return;
}
else if (e.curr_x != prev->curr_x) return;
if (CrossProduct(e.top, pt, prev->top)) return;

if (e.outrec->idx == prev->outrec->idx)
AddLocalMaxPoly(*prev, e, pt);
Expand All @@ -2677,10 +2680,13 @@ namespace Clipper2Lib {
pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins
return;

if (check_curr_x) next->curr_x = TopX(*next, pt.y);
if (e.curr_x != next->curr_x ||
CrossProduct(e.top, pt, next->top)) return;

if (check_curr_x)
{
if (DistanceFromLineSqrd(pt, next->bot, next->top) > 0.35) return;
}
else if (e.curr_x != next->curr_x) return;
if (CrossProduct(e.top, pt, next->top)) return;

if (e.outrec->idx == next->outrec->idx)
AddLocalMaxPoly(e, *next, pt);
else if (e.outrec->idx < next->outrec->idx)
Expand Down
Loading

0 comments on commit 1057798

Please sign in to comment.