Add Clipper2

Currently this lives behind the advanced config flag `UseClipper2`.
Enabling this flag will route all Clipper-based calls through the
Clipper2 library instead of the older Clipper.  The changes should be
mostly transparent.

Of note, Clipper2 does not utilize the `STRICTLY_SIMPLE` flag because
clipper1 did not actually guarantee a strictly simple polygon.
Currently we ignore this flag but we may decide to run strictly-simple
operations through a second NULL union to simplify the results as much
as possible.

Additionally, the inflation options are slightly different.  We cannot
choose the fallback miter.  The fallback miter is always square.  This
only affects the CHAMFER_ACUTE_CORNERS option in inflate, which does not
appear to be used.

Lastly, we currently utilize the 64-bit integer coordinates for
calculations.  This appears to still be faster than 32-bit calculations
in Clipper1 on a modern x86 system.  This may not be the case for older
systems, particularly 32-bit systems.
This commit is contained in:
Seth Hillbrand 2022-10-19 16:25:45 -07:00
parent 85805bdad7
commit 27add591ec
21 changed files with 7053 additions and 9 deletions

View File

@ -196,6 +196,8 @@ static const wxChar ShowPropertiesPanel[] = wxT( "ShowPropertiesPanel" );
static const wxChar V3DRT_BevelHeight_um[] = wxT( "V3DRT_BevelHeight_um" );
static const wxChar V3DRT_BevelExtentFactor[] = wxT( "V3DRT_BevelExtentFactor" );
static const wxChar UseClipper2[] = wxT( "UseClipper2" );
} // namespace KEYS
@ -323,6 +325,8 @@ ADVANCED_CFG::ADVANCED_CFG()
m_3DRT_BevelHeight_um = 30;
m_3DRT_BevelExtentFactor = 1.0 / 16.0;
m_UseClipper2 = false;
loadFromConfigFile();
}
@ -462,6 +466,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
0.0, 100.0,
AC_GROUPS::V3D_RayTracing ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::UseClipper2,
&m_UseClipper2, m_UseClipper2 ) );
// Special case for trace mask setting...we just grab them and set them immediately

View File

@ -289,6 +289,9 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
m_params.emplace_back( new PARAM<int>( "graphics.cairo_antialiasing_mode",
&m_Graphics.cairo_aa_mode, 0, 0, 2 ) );
m_params.emplace_back( new PARAM<int>( "graphics.use_clipper2",
&m_Graphics.cairo_aa_mode, 0, 0, 1 ) );
m_params.emplace_back( new PARAM<int>( "system.autosave_interval",
&m_System.autosave_interval, 600 ) );
@ -768,4 +771,4 @@ bool COMMON_SETTINGS::getLegacy3DHollerith( const std::string& aString, size_t&
aIndex = i2 + 1;
return true;
}
}

View File

@ -238,6 +238,12 @@ public:
*/
double m_3DRT_BevelExtentFactor;
/**
* User Clipper2 instead of Clipper1
*/
bool m_UseClipper2;
private:
ADVANCED_CFG();

View File

@ -32,6 +32,7 @@ add_library( kimath STATIC
target_link_libraries( kimath
clipper
clipper2
othermath
rtree
${wxWidgets_LIBRARIES} # wxLogDebug, wxASSERT

View File

@ -28,6 +28,7 @@
#include <clipper.hpp>
#include <clipper2/clipper.h>
#include <geometry/seg.h>
#include <geometry/shape.h>
#include <geometry/shape_arc.h>
@ -195,6 +196,10 @@ public:
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer );
SHAPE_LINE_CHAIN( const Clipper2Lib::Path64& aPath,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer );
virtual ~SHAPE_LINE_CHAIN()
{}
@ -913,6 +918,13 @@ protected:
std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
std::vector<SHAPE_ARC>& aArcBuffer ) const;
/**
* Create a new Clipper2 path from the SHAPE_LINE_CHAIN in a given orientation
*/
Clipper2Lib::Path64 convertToClipper2( bool aRequiredOrientation,
std::vector<CLIPPER_Z_VALUE> &aZValueBuffer,
std::vector<SHAPE_ARC> &aArcBuffer ) const;
/**
* Fix indices of this chain to ensure arcs are not split between the end and start indices
*/

View File

@ -39,6 +39,7 @@
#include <vector>
#include <clipper.hpp> // for ClipType, PolyTree (ptr only)
#include <clipper2/clipper.h>
#include <geometry/seg.h> // for SEG
#include <geometry/shape.h>
#include <geometry/shape_line_chain.h>
@ -1387,6 +1388,15 @@ private:
void importTree( ClipperLib::PolyTree* tree,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffe );
void importTree( Clipper2Lib::PolyTree64& tree,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffe );
void importTree( Clipper2Lib::Paths64& paths,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffe );
void inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy );
void inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy );
/**
* This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon
@ -1406,6 +1416,11 @@ private:
void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode );
void booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape );
void booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape );
/**
* Check whether the point \a aP is inside the \a aSubpolyIndex-th polygon of the polyset. If
* the points lies on an edge, the polygon is considered to contain it.

View File

@ -30,6 +30,7 @@
#include <string> // for basic_string
#include <clipper.hpp>
#include <clipper2/clipper.h>
#include <core/kicad_algo.h> // for alg::run_on_pair
#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
#include <geometry/shape_line_chain.h>
@ -52,6 +53,7 @@ SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const std::vector<int>& aV)
}
}
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const ClipperLib::Path& aPath,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer ) :
@ -95,6 +97,51 @@ SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const ClipperLib::Path& aPath,
fixIndicesRotation();
}
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const Clipper2Lib::Path64& aPath,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer ) :
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
m_closed( true ), m_width( 0 )
{
std::map<ssize_t, ssize_t> loadedArcs;
m_points.reserve( aPath.size() );
m_shapes.reserve( aPath.size() );
auto loadArc =
[&]( ssize_t aArcIndex ) -> ssize_t
{
if( aArcIndex == SHAPE_IS_PT )
{
return SHAPE_IS_PT;
}
else if( loadedArcs.count( aArcIndex ) == 0 )
{
loadedArcs.insert( { aArcIndex, m_arcs.size() } );
m_arcs.push_back( aArcBuffer.at( aArcIndex ) );
}
return loadedArcs.at( aArcIndex );
};
for( size_t ii = 0; ii < aPath.size(); ++ii )
{
Append( aPath[ii].x, aPath[ii].y );
m_shapes[ii].first = loadArc( aZValueBuffer[aPath[ii].z].m_FirstArcIdx );
m_shapes[ii].second = loadArc( aZValueBuffer[aPath[ii].z].m_SecondArcIdx );
}
// Clipper shouldn't return duplicate contiguous points. if it did, these would be
// removed during Append() and we would have different number of shapes to points
wxASSERT( m_shapes.size() == m_points.size() );
// Clipper might mess up the rotation of the indices such that an arc can be split between
// the end point and wrap around to the start point. Lets fix the indices up now
fixIndicesRotation();
}
ClipperLib::Path SHAPE_LINE_CHAIN::convertToClipper( bool aRequiredOrientation,
std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
std::vector<SHAPE_ARC>& aArcBuffer ) const
@ -129,6 +176,40 @@ ClipperLib::Path SHAPE_LINE_CHAIN::convertToClipper( bool aRequiredOrientation,
}
Clipper2Lib::Path64 SHAPE_LINE_CHAIN::convertToClipper2( bool aRequiredOrientation,
std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
std::vector<SHAPE_ARC>& aArcBuffer ) const
{
Clipper2Lib::Path64 c_path;
SHAPE_LINE_CHAIN input;
bool orientation = Area( false ) >= 0;
ssize_t shape_offset = aArcBuffer.size();
if( orientation != aRequiredOrientation )
input = Reverse();
else
input = *this;
int pointCount = input.PointCount();
c_path.reserve( pointCount );
for( int i = 0; i < pointCount; i++ )
{
const VECTOR2I& vertex = input.CPoint( i );
CLIPPER_Z_VALUE z_value( input.m_shapes[i], shape_offset );
size_t z_value_ptr = aZValueBuffer.size();
aZValueBuffer.push_back( z_value );
c_path.emplace_back( vertex.x, vertex.y, z_value_ptr );
}
aArcBuffer.insert( aArcBuffer.end(), input.m_arcs.begin(), input.m_arcs.end() );
return c_path;
}
void SHAPE_LINE_CHAIN::fixIndicesRotation()
{
wxCHECK( m_shapes.size() == m_points.size(), /*void*/ );

View File

@ -43,6 +43,7 @@
#include <vector>
#include <clipper.hpp> // for Clipper, PolyNode, Clipp...
#include <clipper2/clipper.h>
#include <geometry/geometry_utils.h>
#include <geometry/polygon_triangulation.h>
#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
@ -57,6 +58,9 @@
#include <geometry/shape_segment.h>
#include <geometry/shape_circle.h>
// Do not keep this for release. Only for testing clipper
#include <advanced_config.h>
#include <wx/log.h>
@ -692,42 +696,173 @@ void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET
}
void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape )
{
booleanOp( aType, *this, aOtherShape );
}
void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape )
{
if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
&& ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
{
wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
"ClearArcs() before carrying out the boolean operation." ) );
}
Clipper2Lib::Clipper64 c;
std::vector<CLIPPER_Z_VALUE> zValues;
std::vector<SHAPE_ARC> arcBuffer;
std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
Clipper2Lib::Paths64 paths;
Clipper2Lib::Paths64 clips;
for( const POLYGON& poly : aShape.m_polys )
{
for( size_t i = 0; i < poly.size(); i++ )
{
paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
}
}
for( const POLYGON& poly : aOtherShape.m_polys )
{
for( size_t i = 0; i < poly.size(); i++ )
{
clips.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
}
}
c.AddSubject( paths );
c.AddClip( clips );
Clipper2Lib::PolyTree64 solution;
Clipper2Lib::ZCallback64 callback =
[&]( const Clipper2Lib::Point64 & e1bot, const Clipper2Lib::Point64 & e1top,
const Clipper2Lib::Point64 & e2bot, const Clipper2Lib::Point64 & e2top,
Clipper2Lib::Point64 & pt )
{
auto arcIndex =
[&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
{
ssize_t retval;
retval = zValues.at( aZvalue ).m_SecondArcIdx;
if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
retval = zValues.at( aZvalue ).m_FirstArcIdx;
return retval;
};
auto arcSegment =
[&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
{
ssize_t retval = arcIndex( aBottomZ );
if( retval != -1 )
{
if( retval != arcIndex( aTopZ, retval ) )
retval = -1; // Not an arc segment as the two indices do not match
}
return retval;
};
ssize_t e1ArcSegmentIndex = arcSegment( e1bot.z, e1top.z );
ssize_t e2ArcSegmentIndex = arcSegment( e2bot.z, e2top.z );
CLIPPER_Z_VALUE newZval;
if( e1ArcSegmentIndex != -1 )
{
newZval.m_FirstArcIdx = e1ArcSegmentIndex;
newZval.m_SecondArcIdx = e2ArcSegmentIndex;
}
else
{
newZval.m_FirstArcIdx = e2ArcSegmentIndex;
newZval.m_SecondArcIdx = -1;
}
size_t z_value_ptr = zValues.size();
zValues.push_back( newZval );
// Only worry about arc segments for later processing
if( newZval.m_FirstArcIdx != -1 )
newIntersectPoints.insert( { VECTOR2I( pt.x, pt.y ), newZval } );
pt.z = z_value_ptr;
//@todo amend X,Y values to true intersection between arcs or arc and segment
};
c.SetZCallback( callback ); // register callback
c.Execute( aType, Clipper2Lib::FillRule::NonZero, solution );
importTree( solution, zValues, arcBuffer );
}
void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctUnion, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Union, b );
else
booleanOp( ClipperLib::ctUnion, b, aFastMode );
}
void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctDifference, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Difference, b );
else
booleanOp( ClipperLib::ctDifference, b, aFastMode );
}
void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctIntersection, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Intersection, b );
else
booleanOp( ClipperLib::ctIntersection, b, aFastMode );
}
void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctUnion, a, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Union, a, b );
else
booleanOp( ClipperLib::ctUnion, a, b, aFastMode );
}
void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctDifference, a, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Difference, a, b );
else
booleanOp( ClipperLib::ctDifference, a, b, aFastMode );
}
void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
POLYGON_MODE aFastMode )
{
booleanOp( ClipperLib::ctIntersection, a, b, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Intersection, a, b );
else
booleanOp( ClipperLib::ctIntersection, a, b, aFastMode );
}
@ -740,7 +875,7 @@ void SHAPE_POLY_SET::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCou
}
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
void SHAPE_POLY_SET::inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
{
using namespace ClipperLib;
// A static table to avoid repetitive calculations of the coefficient
@ -831,6 +966,98 @@ void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY
}
void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
{
using namespace Clipper2Lib;
// A static table to avoid repetitive calculations of the coefficient
// 1.0 - cos( M_PI / aCircleSegCount )
// aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
#define SEG_CNT_MAX 64
static double arc_tolerance_factor[SEG_CNT_MAX + 1];
ClipperOffset c;
// N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
// and are not what you'd think they are.
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
JoinType joinType = JoinType::Round; // The way corners are offsetted
double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
JoinType miterFallback = JoinType::Square;
switch( aCornerStrategy )
{
case ALLOW_ACUTE_CORNERS:
joinType = JoinType::Miter;
miterLimit = 10; // Allows large spikes
break;
case CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
joinType = JoinType::Miter;
break;
case ROUND_ACUTE_CORNERS: // Acute angles are rounded
joinType = JoinType::Miter;
break;
case CHAMFER_ALL_CORNERS: // All angles are chamfered.
joinType = JoinType::Square;
break;
case ROUND_ALL_CORNERS: // All angles are rounded.
joinType = JoinType::Round;
break;
}
std::vector<CLIPPER_Z_VALUE> zValues;
std::vector<SHAPE_ARC> arcBuffer;
for( const POLYGON& poly : m_polys )
{
for( size_t i = 0; i < poly.size(); i++ )
{
c.AddPath( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ),
joinType, EndType::Polygon );
}
}
// Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
// nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
aCircleSegCount = 6;
double coeff;
if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
{
coeff = 1.0 - cos( M_PI / aCircleSegCount );
if( aCircleSegCount <= SEG_CNT_MAX )
arc_tolerance_factor[aCircleSegCount] = coeff;
}
else
{
coeff = arc_tolerance_factor[aCircleSegCount];
}
c.ArcTolerance( std::abs( aAmount ) * coeff );
c.MiterLimit( miterLimit );
Paths64 solution = c.Execute( aAmount );
importTree( solution, zValues, arcBuffer );
}
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
{
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
inflate2( aAmount, aCircleSegCount, aCornerStrategy );
else
inflate1( aAmount, aCircleSegCount, aCornerStrategy );
}
void SHAPE_POLY_SET::importTree( ClipperLib::PolyTree* tree,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer )
@ -855,6 +1082,59 @@ void SHAPE_POLY_SET::importTree( ClipperLib::PolyTree* tree,
}
void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer )
{
m_polys.clear();
for( Clipper2Lib::PolyPath64* n : tree )
{
if( !n->IsHole() )
{
POLYGON paths;
paths.reserve( n->Count() + 1 );
paths.emplace_back( n->Polygon(), aZValueBuffer, aArcBuffer );
for( Clipper2Lib::PolyPath64* child : *n)
paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer );
m_polys.push_back( paths );
}
}
}
void SHAPE_POLY_SET::importTree( Clipper2Lib::Paths64& tree,
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
const std::vector<SHAPE_ARC>& aArcBuffer )
{
m_polys.clear();
POLYGON path;
for( const Clipper2Lib::Path64& n : tree )
{
if( Clipper2Lib::Area( n ) > 0 )
{
if( !path.empty() )
m_polys.emplace_back( path );
path.clear();
}
else
{
wxCHECK2_MSG( !path.empty(), continue, wxT( "Cannot add a hole before an outline" ) );
}
path.emplace_back( n, aZValueBuffer, aArcBuffer );
}
if( !path.empty() )
m_polys.emplace_back( path );
}
struct FractureEdge
{
FractureEdge( int y = 0 ) :
@ -1281,7 +1561,10 @@ void SHAPE_POLY_SET::Simplify( POLYGON_MODE aFastMode )
{
SHAPE_POLY_SET empty;
booleanOp( ClipperLib::ctUnion, empty, aFastMode );
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
booleanOp( Clipper2Lib::ClipType::Union, empty );
else
booleanOp( ClipperLib::ctUnion, empty, aFastMode );
}

View File

@ -59,6 +59,9 @@ endif()
add_executable( qa_kimath
${QA_KIMATH_SRCS}
${QA_KIMATH_RESOURCES}
# Mock Pgm needed for advanced_config
${CMAKE_SOURCE_DIR}/qa/mocks/kicad/common_mocks.cpp
)
target_link_libraries( qa_kimath
@ -69,6 +72,7 @@ target_link_libraries( qa_kimath
target_include_directories( qa_kimath PRIVATE
${CMAKE_SOURCE_DIR}/include # Needed for core/optional.h
${CMAKE_SOURCE_DIR}/qa/mocks/include
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@ -25,6 +25,7 @@
add_subdirectory( argparse )
add_subdirectory( clipper )
add_subdirectory( clipper2 )
add_subdirectory( compoundfilereader )
add_subdirectory( delaunator )
add_subdirectory( dxflib_qcad )

33
thirdparty/clipper2/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,33 @@
include(GNUInstallDirs)
set(CLIPPER2_INC
Clipper2Lib/include/clipper2/clipper.h
Clipper2Lib/include/clipper2/clipper.core.h
Clipper2Lib/include/clipper2/clipper.engine.h
Clipper2Lib/include/clipper2/clipper.minkowski.h
Clipper2Lib/include/clipper2/clipper.offset.h
Clipper2Lib/include/clipper2/clipper.rectclip.h
)
set(CLIPPER2_SRC
Clipper2Lib/src/clipper.engine.cpp
Clipper2Lib/src/clipper.offset.cpp
Clipper2Lib/src/clipper.rectclip.cpp
)
add_library(clipper2 STATIC ${CLIPPER2_INC} ${CLIPPER2_SRC})
target_include_directories(clipper2
PUBLIC Clipper2Lib/include
)
target_compile_definitions(clipper2 PUBLIC USINGZ)
if (WIN32)
target_compile_options(clipper2 PRIVATE /W4 /WX)
else()
target_compile_options(clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_link_libraries(clipper2 PUBLIC -lm)
endif()

View File

@ -0,0 +1,632 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : Core Clipper Library structures and functions *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_CORE_H
#define CLIPPER_CORE_H
#include <cstdlib>
#include <cmath>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
namespace Clipper2Lib
{
static double const PI = 3.141592653589793238;
//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
enum class FillRule { EvenOdd, NonZero, Positive, Negative };
// Point ------------------------------------------------------------------------
template <typename T>
struct Point {
T x;
T y;
#ifdef USINGZ
int64_t z;
template <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
z = z_;
}
else
{
x = static_cast<T>(x_);
y = static_cast<T>(y_);
z = z_;
}
}
explicit Point() : x(0), y(0), z(0) {};
template <typename T2>
Point(const T2 x_, const T2 y_, const int64_t z_ = 0)
{
Init(x_, y_);
z = z_;
}
template <typename T2>
explicit Point<T>(const Point<T2>& p)
{
Init(p.x, p.y, p.z);
}
Point operator * (const double scale) const
{
return Point(x * scale, y * scale, z);
}
friend std::ostream& operator<<(std::ostream& os, const Point& point)
{
os << point.x << "," << point.y << "," << point.z;
return os;
}
#else
template <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
}
else
{
x = static_cast<T>(x_);
y = static_cast<T>(y_);
}
}
explicit Point() : x(0), y(0) {};
template <typename T2>
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
template <typename T2>
explicit Point<T>(const Point<T2>& p) { Init(p.x, p.y); }
Point operator * (const double scale) const
{
return Point(x * scale, y * scale);
}
friend std::ostream& operator<<(std::ostream& os, const Point& point)
{
os << point.x << "," << point.y;
return os;
}
#endif
friend bool operator==(const Point &a, const Point &b)
{
return a.x == b.x && a.y == b.y;
}
friend bool operator!=(const Point& a, const Point& b)
{
return !(a == b);
}
inline Point<T> operator-() const
{
return Point<T>(-x,-y);
}
inline Point operator+(const Point &b) const
{
return Point(x+b.x, y+b.y);
}
inline Point operator-(const Point &b) const
{
return Point(x-b.x, y-b.y);
}
inline void Negate() { x = -x; y = -y; }
};
//nb: using 'using' here (instead of typedef) as they can be used in templates
using Point64 = Point<int64_t>;
using PointD = Point<double>;
template <typename T>
using Path = std::vector<Point<T>>;
template <typename T>
using Paths = std::vector<Path<T>>;
using Path64 = Path<int64_t>;
using PathD = Path<double>;
using Paths64 = std::vector< Path64>;
using PathsD = std::vector< PathD>;
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
{
if (!path.empty())
{
auto pt = path.cbegin(), last = path.cend() - 1;
while (pt != last)
outstream << *pt++ << ", ";
outstream << *last << std::endl;
}
return outstream;
}
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Paths<T>& paths)
{
for (auto p : paths)
outstream << p;
return outstream;
}
template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale)
{
Path<T1> result;
result.reserve(path.size());
#ifdef USINGZ
for (const Point<T2>& pt : path)
result.push_back(Point<T1>(pt.x * scale, pt.y * scale, pt.z));
#else
for (const Point<T2>& pt : path)
result.push_back(Point<T1>(pt.x * scale, pt.y * scale));
#endif
return result;
}
template <typename T1, typename T2>
inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale)
{
Paths<T1> result;
result.reserve(paths.size());
for (const Path<T2>& path : paths)
result.push_back(ScalePath<T1, T2>(path, scale));
return result;
}
template <typename T1, typename T2>
inline Path<T1> TransformPath(const Path<T2>& path)
{
Path<T1> result;
result.reserve(path.size());
std::transform(path.cbegin(), path.cend(), std::back_inserter(result),
[](const Point<T2>& pt) {return Point<T1>(pt); });
return result;
}
template <typename T1, typename T2>
inline Paths<T1> TransformPaths(const Paths<T2>& paths)
{
Paths<T1> result;
std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result),
[](const Path<T2>& path) {return TransformPath<T1, T2>(path); });
return result;
}
inline PathD Path64ToPathD(const Path64& path)
{
return TransformPath<double, int64_t>(path);
}
inline PathsD Paths64ToPathsD(const Paths64& paths)
{
return TransformPaths<double, int64_t>(paths);
}
inline Path64 PathDToPath64(const PathD& path)
{
return TransformPath<int64_t, double>(path);
}
inline Paths64 PathsDToPaths64(const PathsD& paths)
{
return TransformPaths<int64_t, double>(paths);
}
template<typename T>
inline double Sqr(T val)
{
return static_cast<double>(val) * static_cast<double>(val);
}
template<typename T>
inline bool NearEqual(const Point<T>& p1,
const Point<T>& p2, double max_dist_sqrd)
{
return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd;
}
template<typename T>
inline Path<T> StripNearEqual(const Path<T>& path,
double max_dist_sqrd, bool is_closed_path)
{
if (path.size() == 0) return Path<T>();
Path<T> result;
result.reserve(path.size());
typename Path<T>::const_iterator path_iter = path.cbegin();
Point<T> first_pt = *path_iter++, last_pt = first_pt;
result.push_back(first_pt);
for (; path_iter != path.cend(); ++path_iter)
{
if (!NearEqual(*path_iter, last_pt, max_dist_sqrd))
{
last_pt = *path_iter;
result.push_back(last_pt);
}
}
if (!is_closed_path) return result;
while (result.size() > 1 &&
NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back();
return result;
}
template<typename T>
inline Paths<T> StripNearEqual(const Paths<T>& paths,
double max_dist_sqrd, bool is_closed_path)
{
Paths<T> result;
result.reserve(paths.size());
for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
paths_citer != paths.cend(); ++paths_citer)
{
result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path));
}
return result;
}
template<typename T>
inline Path<T> StripDuplicates(const Path<T>& path, bool is_closed_path)
{
if (path.size() == 0) return Path<T>();
Path<T> result;
result.reserve(path.size());
typename Path<T>::const_iterator path_iter = path.cbegin();
Point<T> first_pt = *path_iter++, last_pt = first_pt;
result.push_back(first_pt);
for (; path_iter != path.cend(); ++path_iter)
{
if (*path_iter != last_pt)
{
last_pt = *path_iter;
result.push_back(last_pt);
}
}
if (!is_closed_path) return result;
while (result.size() > 1 && result.back() == first_pt) result.pop_back();
return result;
}
template<typename T>
inline Paths<T> StripDuplicates(const Paths<T>& paths, bool is_closed_path)
{
Paths<T> result;
result.reserve(paths.size());
for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
paths_citer != paths.cend(); ++paths_citer)
{
result.push_back(StripDuplicates(*paths_citer, is_closed_path));
}
return result;
}
// Rect ------------------------------------------------------------------------
template <typename T>
struct Rect;
using Rect64 = Rect<int64_t>;
using RectD = Rect<double>;
template <typename T>
struct Rect {
T left;
T top;
T right;
T bottom;
Rect() :
left(0),
top(0),
right(0),
bottom(0) {}
Rect(T l, T t, T r, T b) :
left(l),
top(t),
right(r),
bottom(b) {}
T Width() const { return right - left; }
T Height() const { return bottom - top; }
void Width(T width) { right = left + width; }
void Height(T height) { bottom = top + height; }
Point<T> MidPoint() const
{
return Point<T>((left + right) / 2, (top + bottom) / 2);
}
Path<T> AsPath() const
{
Path<T> result;
result.reserve(4);
result.push_back(Point<T>(left, top));
result.push_back(Point<T>(right, top));
result.push_back(Point<T>(right, bottom));
result.push_back(Point<T>(left, bottom));
return result;
}
bool Contains(const Point<T>& pt) const
{
return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
}
bool Contains(const Rect<T>& rec) const
{
return rec.left >= left && rec.right <= right &&
rec.top >= top && rec.bottom <= bottom;
}
void Scale(double scale) {
left *= scale;
top *= scale;
right *= scale;
bottom *= scale;
}
bool IsEmpty() const { return bottom <= top || right <= left; };
bool Intersects(const Rect<T>& rec) const
{
return (std::max(left, rec.left) < std::min(right, rec.right)) &&
(std::max(top, rec.top) < std::min(bottom, rec.bottom));
};
friend std::ostream &operator<<(std::ostream &os, const Rect<T> &rect) {
os << "("
<< rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
<< ")";
return os;
}
};
template <typename T1, typename T2>
inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
{
Rect<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
result.left = static_cast<T1>(std::round(rect.left * scale));
result.top = static_cast<T1>(std::round(rect.top * scale));
result.right = static_cast<T1>(std::round(rect.right * scale));
result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
}
else
{
result.left = rect.left * scale;
result.top = rect.top * scale;
result.right = rect.right * scale;
result.bottom = rect.bottom * scale;
}
return result;
}
// clipper2Exception ---------------------------------------------------------
class Clipper2Exception : public std::exception {
public:
explicit Clipper2Exception(const char *description) :
m_descr(description) {}
virtual const char *what() const throw() override { return m_descr.c_str(); }
private:
std::string m_descr;
};
// Miscellaneous ------------------------------------------------------------
template <typename T>
inline double CrossProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.y -
pt2.y) - static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.x - pt2.x));
}
template <typename T>
inline double CrossProduct(const Point<T>& vec1, const Point<T>& vec2)
{
return static_cast<double>(vec1.y * vec2.x) - static_cast<double>(vec2.y * vec1.x);
}
template <typename T>
inline double DotProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.x - pt2.x) +
static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.y - pt2.y));
}
template <typename T>
inline double DotProduct(const Point<T>& vec1, const Point<T>& vec2)
{
return static_cast<double>(vec1.x * vec2.x) + static_cast<double>(vec1.y * vec2.y);
}
template <typename T>
inline double DistanceSqr(const Point<T> pt1, const Point<T> pt2)
{
return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y);
}
template <typename T>
inline double DistanceFromLineSqrd(const Point<T>& pt, const Point<T>& ln1, const Point<T>& ln2)
{
//perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
//see http://en.wikipedia.org/wiki/Perpendicular_distance
double A = static_cast<double>(ln1.y - ln2.y);
double B = static_cast<double>(ln2.x - ln1.x);
double C = A * ln1.x + B * ln1.y;
C = A * pt.x + B * pt.y - C;
return (C * C) / (A * A + B * B);
}
template <typename T>
inline double Area(const Path<T>& path)
{
size_t cnt = path.size();
if (cnt < 3) return 0.0;
double a = 0.0;
typename Path<T>::const_iterator it1, it2 = path.cend() - 1, stop = it2;
if (!(cnt & 1)) ++stop;
for (it1 = path.cbegin(); it1 != stop;)
{
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
it2 = it1 + 1;
a += static_cast<double>(it1->y + it2->y) * (it1->x - it2->x);
it1 += 2;
}
if (cnt & 1)
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
return a * 0.5;
}
template <typename T>
inline double Area(const Paths<T>& paths)
{
double a = 0.0;
for (typename Paths<T>::const_iterator paths_iter = paths.cbegin();
paths_iter != paths.cend(); ++paths_iter)
{
a += Area<T>(*paths_iter);
}
return a;
}
template <typename T>
inline bool IsPositive(const Path<T>& poly)
{
// A curve has positive orientation [and area] if a region 'R'
// is on the left when traveling around the outside of 'R'.
//https://mathworld.wolfram.com/CurveOrientation.html
//nb: This statement is premised on using Cartesian coordinates
return Area<T>(poly) >= 0;
}
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
{
if (inclusive)
{
double res1 = CrossProduct(seg1a, seg2a, seg2b);
double res2 = CrossProduct(seg1b, seg2a, seg2b);
if (res1 * res2 > 0) return false;
double res3 = CrossProduct(seg2a, seg1a, seg1b);
double res4 = CrossProduct(seg2b, seg1a, seg1b);
if (res3 * res4 > 0) return false;
return (res1 || res2 || res3 || res4); // ensures not collinear
}
else {
double dx1 = static_cast<double>(seg1a.x - seg1b.x);
double dy1 = static_cast<double>(seg1a.y - seg1b.y);
double dx2 = static_cast<double>(seg2a.x - seg2b.x);
double dy2 = static_cast<double>(seg2a.y - seg2b.y);
return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) *
(dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) &&
((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) *
(dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0));
}
}
enum class PointInPolygonResult { IsOn, IsInside, IsOutside };
template <typename T>
inline PointInPolygonResult PointInPolygon(const Point<T>& pt, const Path<T>& polygon)
{
if (polygon.size() < 3)
return PointInPolygonResult::IsOutside;
int val = 0;
typename Path<T>::const_iterator start = polygon.cbegin(), cit = start;
typename Path<T>::const_iterator cend = polygon.cend(), pit = cend - 1;
while (pit->y == pt.y)
{
if (pit == start) return PointInPolygonResult::IsOutside;
--pit;
}
bool is_above = pit->y < pt.y;
while (cit != cend)
{
if (is_above)
{
while (cit != cend && cit->y < pt.y) ++cit;
if (cit == cend) break;
}
else
{
while (cit != cend && cit->y > pt.y) ++cit;
if (cit == cend) break;
}
if (cit == start) pit = cend - 1;
else pit = cit - 1;
if (cit->y == pt.y)
{
if (cit->x == pt.x || (cit->y == pit->y &&
((pt.x < pit->x) != (pt.x < cit->x))))
return PointInPolygonResult::IsOn;
++cit;
continue;
}
if (pt.x < cit->x && pt.x < pit->x)
{
// we're only interested in edges crossing on the left
}
else if (pt.x > pit->x && pt.x > cit->x)
val = 1 - val; // toggle val
else
{
double d = CrossProduct(*pit, *cit, pt);
if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val;
}
is_above = !is_above;
++cit;
}
return (val == 0) ?
PointInPolygonResult::IsOutside :
PointInPolygonResult::IsInside;
}
} // namespace
#endif // CLIPPER_CORE_H

View File

@ -0,0 +1,539 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : This is the main polygon clipping module *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef clipper_engine_h
#define clipper_engine_h
#define CLIPPER2_VERSION "1.0.5"
#include <cstdlib>
#include <queue>
#include <stdexcept>
#include <vector>
#include <functional>
#include "clipper.core.h"
namespace Clipper2Lib {
struct Scanline;
struct IntersectNode;
struct Active;
struct Vertex;
struct LocalMinima;
struct OutRec;
struct Joiner;
//Note: all clipping operations except for Difference are commutative.
enum class ClipType { None, Intersection, Union, Difference, Xor };
enum class PathType { Subject, Clip };
enum class VertexFlags : uint32_t {
None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
};
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
}
constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
}
struct Vertex {
Point64 pt;
Vertex* next = nullptr;
Vertex* prev = nullptr;
VertexFlags flags = VertexFlags::None;
};
struct OutPt {
Point64 pt;
OutPt* next = nullptr;
OutPt* prev = nullptr;
OutRec* outrec;
Joiner* joiner = nullptr;
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
next = this;
prev = this;
}
};
template <typename T>
class PolyPath;
using PolyPath64 = PolyPath<int64_t>;
using PolyPathD = PolyPath<double>;
template <typename T>
using PolyTree = PolyPath<T>;
using PolyTree64 = PolyTree<int64_t>;
using PolyTreeD = PolyTree<double>;
struct OutRec;
typedef std::vector<OutRec*> OutRecList;
//OutRec: contains a path in the clipping solution. Edges in the AEL will
//have OutRec pointers assigned when they form part of the clipping solution.
struct OutRec {
size_t idx = 0;
OutRec* owner = nullptr;
OutRecList* splits = nullptr;
Active* front_edge = nullptr;
Active* back_edge = nullptr;
OutPt* pts = nullptr;
PolyPath64* polypath = nullptr;
Rect64 bounds = {};
Path64 path;
bool is_open = false;
~OutRec() { if (splits) delete splits; };
};
///////////////////////////////////////////////////////////////////
//Important: UP and DOWN here are premised on Y-axis positive down
//displays, which is the orientation used in Clipper's development.
///////////////////////////////////////////////////////////////////
struct Active {
Point64 bot;
Point64 top;
int64_t curr_x = 0; //current (updated at every new scanline)
double dx = 0.0;
int wind_dx = 1; //1 or -1 depending on winding direction
int wind_cnt = 0;
int wind_cnt2 = 0; //winding count of the opposite polytype
OutRec* outrec = nullptr;
//AEL: 'active edge list' (Vatti's AET - active edge table)
// a linked list of all edges (from left to right) that are present
// (or 'active') within the current scanbeam (a horizontal 'beam' that
// sweeps from bottom to top over the paths in the clipping operation).
Active* prev_in_ael = nullptr;
Active* next_in_ael = nullptr;
//SEL: 'sorted edge list' (Vatti's ST - sorted table)
// linked list used when sorting edges into their new positions at the
// top of scanbeams, but also (re)used to process horizontals.
Active* prev_in_sel = nullptr;
Active* next_in_sel = nullptr;
Active* jump = nullptr;
Vertex* vertex_top = nullptr;
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
bool is_left_bound = false;
};
struct LocalMinima {
Vertex* vertex;
PathType polytype;
bool is_open;
LocalMinima(Vertex* v, PathType pt, bool open) :
vertex(v), polytype(pt), is_open(open){}
};
struct IntersectNode {
Point64 pt;
Active* edge1;
Active* edge2;
IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {}
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
pt(pt_), edge1(e1), edge2(e2)
{
}
};
#ifdef USINGZ
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
#endif
// ClipperBase -------------------------------------------------------------
class ClipperBase {
private:
ClipType cliptype_ = ClipType::None;
FillRule fillrule_ = FillRule::EvenOdd;
FillRule fillpos = FillRule::Positive;
int64_t bot_y_ = 0;
bool has_open_paths_ = false;
bool minima_list_sorted_ = false;
bool using_polytree_ = false;
bool succeeded_ = true;
Active* actives_ = nullptr;
Active *sel_ = nullptr;
Joiner *horz_joiners_ = nullptr;
std::vector<LocalMinima*> minima_list_; //pointers in case of memory reallocs
std::vector<LocalMinima*>::iterator current_locmin_iter_;
std::vector<Vertex*> vertex_lists_;
std::priority_queue<int64_t> scanline_list_;
std::vector<IntersectNode> intersect_nodes_;
std::vector<OutRec*> outrec_list_; //pointers in case of memory reallocs
std::vector<Joiner*> joiner_list_; //pointers in case of memory reallocs
void Reset();
void InsertScanline(int64_t y);
bool PopScanline(int64_t &y);
bool PopLocalMinima(int64_t y, LocalMinima *&local_minima);
void DisposeAllOutRecs();
void DisposeVerticesAndLocalMinima();
void DeleteEdges(Active*& e);
void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
bool IsContributingClosed(const Active &e) const;
inline bool IsContributingOpen(const Active &e) const;
void SetWindCountForClosedPathEdge(Active &edge);
void SetWindCountForOpenPathEdge(Active &e);
void InsertLocalMinimaIntoAEL(int64_t bot_y);
void InsertLeftEdge(Active &e);
inline void PushHorz(Active &e);
inline bool PopHorz(Active *&e);
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
inline void UpdateEdgeIntoAEL(Active *e);
OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt);
inline void DeleteFromAEL(Active &e);
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
void DoIntersections(const int64_t top_y);
void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
bool BuildIntersectList(const int64_t top_y);
void ProcessIntersectList();
void SwapPositionsInAEL(Active& edge1, Active& edge2);
OutPt* AddOutPt(const Active &e, const Point64& pt);
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
const Point64& pt, bool is_new = false);
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
void DoHorizontal(Active &horz);
bool ResetHorzDirection(const Active &horz, const Active *max_pair,
int64_t &horz_left, int64_t &horz_right);
void DoTopOfScanbeam(const int64_t top_y);
Active *DoMaxima(Active &e);
void JoinOutrecPaths(Active &e1, Active &e2);
void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
bool ValidateClosedPathEx(OutPt*& outrec);
void CleanCollinear(OutRec* outrec);
void FixSelfIntersects(OutRec* outrec);
OutPt* DoSplitOp(OutPt* outRecOp, OutPt* splitOp);
Joiner* GetHorzTrialParent(const OutPt* op);
bool OutPtInTrialHorzList(OutPt* op);
void SafeDisposeOutPts(OutPt*& op);
void SafeDeleteOutPtJoiners(OutPt* op);
void AddTrialHorzJoin(OutPt* op);
void DeleteTrialHorzJoin(OutPt* op);
void ConvertHorzTrialsToJoins();
void AddJoin(OutPt* op1, OutPt* op2);
void DeleteJoin(Joiner* joiner);
void ProcessJoinerList();
OutRec* ProcessJoin(Joiner* joiner);
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
bool DeepCheckOwner(OutRec* outrec, OutRec* owner);
void BuildPaths(Paths64& solutionClosed, Paths64* solutionOpen);
void BuildTree(PolyPath64& polytree, Paths64& open_paths);
protected:
#ifdef USINGZ
ZCallback64 zCallback_ = nullptr;
void SetZ(const Active& e1, const Active& e2, Point64& pt);
#endif
void CleanUp(); // unlike Clear, CleanUp preserves added paths
void AddPath(const Path64& path, PathType polytype, bool is_open);
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& solution_closed);
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& solution_closed, Paths64& solution_open);
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree);
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths);
public:
virtual ~ClipperBase();
bool PreserveCollinear = true;
bool ReverseSolution = false;
void Clear();
};
// PolyPath / PolyTree --------------------------------------------------------
//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
//by clipping operations. While this structure is more complex than the
//alternative Paths structure, it does preserve path 'ownership' - ie those
//paths that contain (or own) other paths. This will be useful to some users.
template <typename T>
class PolyPath final {
private:
double scale_;
Path<T> polygon_;
std::vector<PolyPath*> childs_;
protected:
const PolyPath<T>* parent_;
PolyPath(const PolyPath<T>* parent,
const Path<T>& path) :
scale_(parent->scale_), polygon_(path), parent_(parent){}
public:
explicit PolyPath(int precision = 0) // NB only for root node
{
scale_ = std::pow(10, precision);
parent_ = nullptr;
}
~PolyPath() { Clear(); };
//https://en.cppreference.com/w/cpp/language/rule_of_three
PolyPath(const PolyPath&) = delete;
PolyPath& operator=(const PolyPath&) = delete;
PolyPath<T>* operator [] (size_t index) { return childs_[index]; }
typename std::vector<PolyPath*>::const_iterator begin() const { return childs_.cbegin(); }
typename std::vector<PolyPath*>::const_iterator end() const { return childs_.cend(); }
void Clear() {
for (PolyPath<T>* child : childs_) delete child;
childs_.resize(0);
}
unsigned Level() const
{
unsigned result = 0;
const PolyPath<T>* p = parent_;
while (p) { ++result; p = p->parent_; }
return result;
}
void reserve(size_t size)
{
if (size > childs_.size()) childs_.reserve(size);
}
PolyPath<T>* AddChild(const Path<T>& path)
{
childs_.push_back(new PolyPath<T>(this, path));
return childs_.back();
}
size_t Count() const { return childs_.size(); }
const PolyPath<T>* parent() const { return parent_; }
bool IsHole() const
{
const PolyPath* pp = parent_;
bool is_hole = pp;
while (pp) {
is_hole = !is_hole;
pp = pp->parent_;
}
return is_hole;
}
const Path<T>& Polygon() const { return polygon_; }
double Area() const
{
double result = Clipper2Lib::Area<T>(polygon_);
for (const PolyPath<T>* child : childs_)
result += child->Area();
return result;
}
friend std::ostream& operator << (std::ostream& outstream, const PolyPath& polypath)
{
const unsigned level_indent = 4;
const unsigned coords_per_line = 4;
unsigned level = polypath.Level();
if (level > 0)
{
std::string level_padding;
level_padding.insert(0, (level -1) * level_indent, ' ');
std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon ";
std::string childs = polypath.Count() == 1 ? " child" : " children";
outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl;
int last_on_line = coords_per_line - 1;
outstream << level_padding;
int i = 0, highI = polypath.Polygon().size() - 1;
for (; i < highI; ++i)
{
outstream << polypath.Polygon()[i] << ' ';
if ((i % coords_per_line) == last_on_line)
outstream << std::endl << level_padding;
}
if (highI >= 0)
outstream << polypath.Polygon()[i];
outstream << std::endl;
}
for (auto child : polypath)
outstream << *child;
return outstream;
}
};
void Polytree64ToPolytreeD(const PolyPath64& polytree, PolyPathD& result);
class Clipper64 : public ClipperBase
{
public:
#ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
#endif
void AddSubject(const Paths64& subjects)
{
AddPaths(subjects, PathType::Subject, false);
}
void AddOpenSubject(const Paths64& open_subjects)
{
AddPaths(open_subjects, PathType::Subject, true);
}
void AddClip(const Paths64& clips)
{
AddPaths(clips, PathType::Clip, false);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& closed_paths)
{
return ClipperBase::Execute(clip_type, fill_rule, closed_paths);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& closed_paths, Paths64& open_paths)
{
return ClipperBase::Execute(clip_type, fill_rule, closed_paths, open_paths);
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
{
return ClipperBase::Execute(clip_type, fill_rule, polytree);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
{
return ClipperBase::Execute(clip_type, fill_rule, polytree, open_paths);
}
};
class ClipperD : public ClipperBase {
private:
double scale_ = 1.0, invScale_ = 1.0;
#ifdef USINGZ
ZCallbackD zCallback_ = nullptr;
#endif
public:
explicit ClipperD(int precision = 2) : ClipperBase()
{
scale_ = std::pow(10, precision);
invScale_ = 1 / scale_;
}
#ifdef USINGZ
void SetZCallback(ZCallbackD cb) { zCallback_ = cb; };
void ZCB(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)
{
// de-scale (x & y)
// temporarily convert integers to their initial float values
// this will slow clipping marginally but will make it much easier
// to understand the coordinates passed to the callback function
PointD tmp = PointD(pt) * invScale_;
PointD e1b = PointD(e1bot) * invScale_;
PointD e1t = PointD(e1top) * invScale_;
PointD e2b = PointD(e2bot) * invScale_;
PointD e2t = PointD(e2top) * invScale_;
zCallback_(e1b,e1t, e2b, e2t, tmp);
pt.z = tmp.z; // only update 'z'
};
void CheckCallback()
{
if(zCallback_)
// if the user defined float point callback has been assigned
// then assign the proxy callback function
ClipperBase::zCallback_ =
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
else
ClipperBase::zCallback_ = nullptr;
}
#endif
void AddSubject(const PathsD& subjects)
{
AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false);
}
void AddOpenSubject(const PathsD& open_subjects)
{
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true);
}
void AddClip(const PathsD& clips)
{
AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false);
}
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
Paths64 closed_paths64;
if (!ClipperBase::Execute(clip_type, fill_rule, closed_paths64)) return false;
closed_paths = ScalePaths<double, int64_t>(closed_paths64, invScale_);
return true;
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
Paths64 closed_paths64;
Paths64 open_paths64;
if (!ClipperBase::Execute(clip_type,
fill_rule, closed_paths64, open_paths64)) return false;
closed_paths = ScalePaths<double, int64_t>(closed_paths64, invScale_);
open_paths = ScalePaths<double, int64_t>(open_paths64, invScale_);
return true;
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
{
#ifdef USINGZ
CheckCallback();
#endif
PolyTree64 tree_result;
if (!ClipperBase::Execute(clip_type, fill_rule, tree_result)) return false;;
Polytree64ToPolytreeD(tree_result, polytree);
return true;
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTreeD& polytree, Paths64& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
PolyTree64 tree_result;
if (!ClipperBase::Execute(clip_type, fill_rule, tree_result, open_paths)) return false;;
Polytree64ToPolytreeD(tree_result, polytree);
return true;
}
};
} // namespace
#endif // clipper_engine_h

View File

@ -0,0 +1,672 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : This module provides a simple interface to the Clipper Library *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_H
#define CLIPPER_H
#include <cstdlib>
#include <vector>
#include "clipper.core.h"
#include "clipper.engine.h"
#include "clipper.offset.h"
#include "clipper.minkowski.h"
#include "clipper.rectclip.h"
namespace Clipper2Lib {
static const char* precision_error =
"Precision exceeds the permitted range";
static const Rect64 MaxInvalidRect64 = Rect64(
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::lowest)(),
(std::numeric_limits<int64_t>::lowest)());
static const RectD MaxInvalidRectD = RectD(
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::lowest)(),
(std::numeric_limits<double>::lowest)());
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
{
Paths64 sol_open;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, solution, sol_open);
}
inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips, int decimal_prec = 2)
{
if (decimal_prec > 8 || decimal_prec < -8)
throw Clipper2Exception(precision_error);
PathsD result;
ClipperD clipper(decimal_prec);
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
}
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips);
}
inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2)
{
if (decimal_prec > 8 || decimal_prec < -8)
throw Clipper2Exception(precision_error);
PathsD result;
ClipperD clipper(decimal_prec);
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
}
inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
}
inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
}
inline bool IsFullOpenEndType(EndType et)
{
return (et != EndType::Polygon) && (et != EndType::Joined);
}
inline Paths64 InflatePaths(const Paths64& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0)
{
ClipperOffset clip_offset(miter_limit);
clip_offset.AddPaths(paths, jt, et);
return clip_offset.Execute(delta);
}
inline PathsD InflatePaths(const PathsD& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0, double precision = 2)
{
if (precision < -8 || precision > 8)
throw new Clipper2Exception(precision_error);
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit);
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale), jt, et);
Paths64 tmp = clip_offset.Execute(delta * scale);
return ScalePaths<double, int64_t>(tmp, 1 / scale);
}
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
{
Path64 result;
result.reserve(path.size());
for (const Point64& pt : path)
result.push_back(Point64(pt.x + dx, pt.y + dy));
return result;
}
inline PathD TranslatePath(const PathD& path, double dx, double dy)
{
PathD result;
result.reserve(path.size());
for (const PointD& pt : path)
result.push_back(PointD(pt.x + dx, pt.y + dy));
return result;
}
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
{
Paths64 result;
result.reserve(paths.size());
for (const Path64& path : paths)
result.push_back(TranslatePath(path, dx, dy));
return result;
}
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
{
PathsD result;
result.reserve(paths.size());
for (const PathD& path : paths)
result.push_back(TranslatePath(path, dx, dy));
return result;
}
inline Rect64 Bounds(const Path64& path)
{
Rect64 rec = MaxInvalidRect64;
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline Rect64 Bounds(const Paths64& paths)
{
Rect64 rec = MaxInvalidRect64;
for (const Path64& path : paths)
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline RectD Bounds(const PathD& path)
{
RectD rec = MaxInvalidRectD;
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline RectD Bounds(const PathsD& paths)
{
RectD rec = MaxInvalidRectD;
for (const PathD& path : paths)
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline Path64 RectClip(const Rect64& rect, const Path64& path)
{
if (rect.IsEmpty() || path.empty()) return Path64();
Rect64 pathRec = Bounds(path);
if (!rect.Intersects(pathRec)) return Path64();
if (rect.Contains(pathRec)) return path;
RectClip64 rc(rect);
return rc.Execute(path);
}
inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
{
if (rect.IsEmpty() || paths.empty()) return Paths64();
RectClip64 rc(rect);
Paths64 result;
result.reserve(paths.size());
for (const Path64& p : paths)
{
Rect64 pathRec = Bounds(p);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(p);
else
{
Path64 p2 = rc.Execute(p);
if (!p2.empty()) result.push_back(std::move(p2));
}
}
return result;
}
inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2)
{
if (rect.IsEmpty() || path.empty() ||
!rect.Contains(Bounds(path))) return PathD();
if (precision < -8 || precision > 8)
throw new Clipper2Exception(precision_error);
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClip64 rc(r);
Path64 p = ScalePath<int64_t, double>(path, scale);
return ScalePath<double, int64_t>(rc.Execute(p), 1 / scale);
}
inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
{
if (rect.IsEmpty() || paths.empty()) return PathsD();
if (precision < -8 || precision > 8)
throw new Clipper2Exception(precision_error);
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClip64 rc(r);
PathsD result;
result.reserve(paths.size());
for (const PathD& path : paths)
{
RectD pathRec = Bounds(path);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(path);
else
{
Path64 p = ScalePath<int64_t, double>(path, scale);
p = rc.Execute(p);
if (!p.empty())
result.push_back(ScalePath<double, int64_t>(p, 1 / scale));
}
}
return result;
}
namespace details
{
template <typename T>
inline void InternalPolyNodeToPaths(const PolyPath<T>& polypath, Paths<T>& paths)
{
paths.push_back(polypath.Polygon());
for (auto child : polypath)
InternalPolyNodeToPaths(*child, paths);
}
inline bool InternalPolyPathContainsChildren(const PolyPath64& pp)
{
for (auto child : pp)
{
for (const Point64& pt : child->Polygon())
if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside)
return false;
if (child->Count() > 0 && !InternalPolyPathContainsChildren(*child))
return false;
}
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;
}
} // end details namespace
template <typename T>
inline Paths<T> PolyTreeToPaths(const PolyTree<T>& polytree)
{
Paths<T> result;
for (auto child : polytree)
details::InternalPolyNodeToPaths(*child, result);
return result;
}
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
{
for (auto child : polytree)
if (child->Count() > 0 && !details::InternalPolyPathContainsChildren(*child))
return false;
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);
}
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);
}
return result;
}
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
{
size_t len = p.size();
if (len < 3)
{
if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
else return p;
}
Path64 dst;
dst.reserve(len);
Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
if (!is_open_path)
{
while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1)))
++srcIt;
while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt))
--stop;
if (srcIt == stop) return Path64();
}
prevIt = srcIt++;
dst.push_back(*prevIt);
for (; srcIt != stop; ++srcIt)
{
if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1)))
{
prevIt = srcIt;
dst.push_back(*prevIt);
}
}
if (is_open_path)
dst.push_back(*srcIt);
else if (CrossProduct(*prevIt, *stop, dst[0]))
dst.push_back(*stop);
else
{
while (dst.size() > 2 &&
!CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
dst.pop_back();
if (dst.size() < 3) return Path64();
}
return dst;
}
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
{
if (precision > 8 || precision < -8)
throw new Clipper2Exception(precision_error);
const double scale = std::pow(10, precision);
Path64 p = ScalePath<int64_t, double>(path, scale);
p = TrimCollinear(p, is_open_path);
return ScalePath<double, int64_t>(p, 1/scale);
}
template <typename T>
inline double Distance(const Point<T> pt1, const Point<T> pt2)
{
return std::sqrt(DistanceSqr(pt1, pt2));
}
template <typename T>
inline double Length(const Path<T>& path, bool is_closed_path = false)
{
double result = 0.0;
if (path.size() < 2) return result;
auto it = path.cbegin(), stop = path.end() - 1;
for (; it != stop; ++it)
result += Distance(*it, *(it + 1));
if (is_closed_path)
result += Distance(*stop, *path.cbegin());
return result;
}
template <typename T>
inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
{
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
}
template <typename T>
inline Path<T> Ellipse(const Rect<T>& rect, int steps = 0)
{
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
static_cast<double>(rect.Height()) * 0.5, steps);
}
template <typename T>
inline Path<T> Ellipse(const Point<T>& center,
double radiusX, double radiusY = 0, int steps = 0)
{
if (radiusX <= 0) return Path<T>();
if (radiusY <= 0) radiusY = radiusX;
if (steps <= 2)
steps = static_cast<int>(PI * sqrt((radiusX + radiusY) / 2));
double si = std::sin(2 * PI / steps);
double co = std::cos(2 * PI / steps);
double dx = co, dy = si;
Path<T> result;
result.reserve(steps);
result.push_back(Point<T>(center.x + radiusX, static_cast<double>(center.y)));
for (int i = 1; i < steps; ++i)
{
result.push_back(Point<T>(center.x + radiusX * dx, center.y + radiusY * dy));
double x = dx * co - dy * si;
dy = dy * co + dx * si;
dx = x;
}
return result;
}
template <typename T>
inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
const Point<T>& line1, const Point<T>& line2)
{
double a = static_cast<double>(pt.x - line1.x);
double b = static_cast<double>(pt.y - line1.y);
double c = static_cast<double>(line2.x - line1.x);
double d = static_cast<double>(line2.y - line1.y);
if (c == 0 && d == 0) return 0;
return Sqr(a * d - c * b) / (c * c + d * d);
}
template <typename T>
inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags)
{
typename Path<T>::size_type idx = 0;
double max_d = 0;
while (end > begin && path[begin] == path[end]) flags[end--] = false;
for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
{
// PerpendicDistFromLineSqrd - avoids expensive Sqrt()
double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
if (d <= max_d) continue;
max_d = d;
idx = i;
}
if (max_d <= epsSqrd) return;
flags[idx] = true;
if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
}
template <typename T>
inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
{
const typename Path<T>::size_type len = path.size();
if (len < 5) return Path<T>(path);
std::vector<bool> flags(len);
flags[0] = true;
flags[len - 1] = true;
RDP(path, 0, len - 1, Sqr(epsilon), flags);
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (flags[i])
result.push_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
{
Paths<T> result;
result.reserve(paths.size());
for (const Path<T>& path : paths)
result.push_back(RamerDouglasPeucker<T>(path, epsilon));
return result;
}
} // end Clipper2Lib namespace
#endif // CLIPPER_H

View File

@ -0,0 +1,118 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : Minkowski Sum and Difference *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_MINKOWSKI_H
#define CLIPPER_MINKOWSKI_H
#include <cstdlib>
#include <vector>
#include <string>
#include "clipper.core.h"
namespace Clipper2Lib
{
namespace detail
{
inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed)
{
size_t delta = isClosed ? 0 : 1;
size_t patLen = pattern.size(), pathLen = path.size();
if (patLen == 0 || pathLen == 0) return Paths64();
Paths64 tmp;
tmp.reserve(pathLen);
if (isSum)
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p + pt2; });
tmp.push_back(path2);
}
}
else
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p - pt2; });
tmp.push_back(path2);
}
}
Paths64 result;
result.reserve((pathLen - delta) * patLen);
size_t g = isClosed ? pathLen - 1 : 0;
for (size_t h = patLen - 1, i = delta; i < pathLen; ++i)
{
for (size_t j = 0; j < patLen; j++)
{
Path64 quad;
quad.reserve(4);
{
quad.push_back(tmp[g][h]);
quad.push_back(tmp[i][h]);
quad.push_back(tmp[i][j]);
quad.push_back(tmp[g][j]);
};
if (!IsPositive(quad))
std::reverse(quad.begin(), quad.end());
result.push_back(quad);
h = j;
}
g = i;
}
return result;
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
} // namespace internal
inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
Path64 path64 = ScalePath<int64_t, double>(path, scale);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale);
}
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale);
Path64 path64 = ScalePath<int64_t, double>(path, scale);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale);
}
} // Clipper2Lib namespace
#endif // CLIPPER_MINKOWSKI_H

View File

@ -0,0 +1,107 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_OFFSET_H_
#define CLIPPER_OFFSET_H_
#include "clipper.core.h"
namespace Clipper2Lib {
enum class JoinType { Square, Round, Miter };
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
class ClipperOffset {
private:
class Group {
public:
Paths64 paths_in_;
Paths64 paths_out_;
Path64 path_;
bool is_reversed_ = false;
JoinType join_type_;
EndType end_type_;
Group(const Paths64& paths, JoinType join_type, EndType end_type) :
paths_in_(paths), join_type_(join_type), end_type_(end_type) {}
};
double group_delta_ = 0.0;
double abs_group_delta_ = 0.0;
double temp_lim_ = 0.0;
double steps_per_rad_ = 0.0;
PathD norms;
Paths64 solution;
std::vector<Group> groups_;
JoinType join_type_ = JoinType::Square;
double miter_limit_ = 0.0;
double arc_tolerance_ = 0.0;
bool merge_groups_ = true;
bool preserve_collinear_ = false;
bool reverse_solution_ = false;
void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
void BuildNormals(const Path64& path);
void OffsetPolygon(Group& group, Path64& path);
void OffsetOpenJoined(Group& group, Path64& path);
void OffsetOpenPath(Group& group, Path64& path, EndType endType);
void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k);
void DoGroupOffset(Group &group, double delta);
public:
ClipperOffset(double miter_limit = 2.0,
double arc_tolerance = 0.0,
bool preserve_collinear = false,
bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear),
reverse_solution_(reverse_solution) { };
~ClipperOffset() { Clear(); };
void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
void AddPath(const PathD &p, JoinType jt_, EndType et_);
void AddPaths(const PathsD &p, JoinType jt_, EndType et_);
void Clear() { groups_.clear(); norms.clear(); };
Paths64 Execute(double delta);
double MiterLimit() const { return miter_limit_; }
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
double ArcTolerance() const { return arc_tolerance_; }
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
//MergeGroups: A path group is one or more paths added via the AddPath or
//AddPaths methods. By default these path groups will be offset
//independently of other groups and this may cause overlaps (intersections).
//However, when MergeGroups is enabled, any overlapping offsets will be
//merged (via a clipping union operation) to remove overlaps.
bool MergeGroups() const { return merge_groups_; }
void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; }
bool PreserveCollinear() const { return preserve_collinear_; }
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
bool ReverseSolution() const { return reverse_solution_; }
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
};
}
#endif /* CLIPPER_OFFSET_H_ */

View File

@ -0,0 +1,46 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_RECTCLIP_H
#define CLIPPER_RECTCLIP_H
#include <cstdlib>
#include <vector>
#include "clipper.h"
#include "clipper.core.h"
namespace Clipper2Lib
{
enum class Location { Left, Top, Right, Bottom, Inside };
class RectClip64 {
private:
const Rect64 rect_;
const Point64 mp_;
const Path64 rectPath_;
Path64 result_;
std::vector<Location> start_locs_;
void Reset();
void GetNextLocation(const Path64& path,
Location& loc, int& i, int highI);
void AddCorner(Location prev, Location curr);
void AddCorner(Location& loc, bool isClockwise);
public:
RectClip64(const Rect64& rect) :
rect_(rect),
mp_(rect.MidPoint()),
rectPath_(rect.AsPath()) {}
Path64 Execute(const Path64& path);
};
} // Clipper2Lib namespace
#endif // CLIPPER_RECTCLIP_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,485 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#include <cmath>
#include "clipper2/clipper.h"
#include "clipper2/clipper.offset.h"
namespace Clipper2Lib {
const double default_arc_tolerance = 0.25;
const double floating_point_tolerance = 1e-12;
//------------------------------------------------------------------------------
// Miscellaneous methods
//------------------------------------------------------------------------------
Paths64::size_type GetLowestPolygonIdx(const Paths64& paths)
{
Paths64::size_type result = 0;
Point64 lp = Point64(static_cast<int64_t>(0),
std::numeric_limits<int64_t>::min());
for (Paths64::size_type i = 0 ; i < paths.size(); ++i)
for (const Point64& p : paths[i])
{
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue;
result = i;
lp = p;
}
return result;
}
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
{
double dx, dy, inverse_hypot;
if (pt1 == pt2) return PointD(0.0, 0.0);
dx = static_cast<double>(pt2.x - pt1.x);
dy = static_cast<double>(pt2.y - pt1.y);
inverse_hypot = 1.0 / hypot(dx, dy);
dx *= inverse_hypot;
dy *= inverse_hypot;
return PointD(dy, -dx);
}
inline bool AlmostZero(double value, double epsilon = 0.001)
{
return std::fabs(value) < epsilon;
}
inline double Hypot(double x, double y)
{
//see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
}
inline PointD NormalizeVector(const PointD& vec)
{
double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h;
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
}
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
{
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
}
inline bool IsClosedPath(EndType et)
{
return et == EndType::Polygon || et == EndType::Joined;
}
inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
{
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
}
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
{
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
}
//------------------------------------------------------------------------------
// ClipperOffset methods
//------------------------------------------------------------------------------
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
{
Paths64 paths;
paths.push_back(path);
AddPaths(paths, jt_, et_);
}
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
{
if (paths.size() == 0) return;
groups_.push_back(Group(paths, jt_, et_));
}
void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_)
{
PathsD paths;
paths.push_back(path);
AddPaths(paths, jt_, et_);
}
void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_)
{
if (paths.size() == 0) return;
groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_));
}
void ClipperOffset::BuildNormals(const Path64& path)
{
norms.clear();
norms.reserve(path.size());
if (path.size() == 0) return;
Path64::const_iterator path_iter, path_last_iter = --path.cend();
for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
}
inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
{
return PointD(pt.x + dx, pt.y + dy);
}
inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
{
return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
}
PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
const PointD& pt2a, const PointD& pt2b)
{
if (pt1a.x == pt1b.x) //vertical
{
if (pt2a.x == pt2b.x) return PointD(0, 0);
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
return PointD(pt1a.x, m2 * pt1a.x + b2);
}
else if (pt2a.x == pt2b.x) //vertical
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
return PointD(pt2a.x, m1 * pt2a.x + b1);
}
else
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
if (m1 == m2) return PointD(0, 0);
double x = (b2 - b1) / (m1 - m2);
return PointD(x, m1 * x + b1);
}
}
void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
{
PointD vec;
if (j == k)
vec = PointD(norms[0].y, -norms[0].x);
else
vec = GetAvgUnitVector(
PointD(-norms[k].y, norms[k].x),
PointD(norms[j].y, -norms[j].x));
// now offset the original vertex delta units along unit vector
PointD ptQ = PointD(path[j]);
ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y);
// get perpendicular vertices
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
// get 2 vertices along one edge offset
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
if (j == k)
{
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
//get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
group.path_.push_back(Point64(pt));
}
else
{
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
group.path_.push_back(Point64(pt));
//get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
}
}
void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
{
double q = group_delta_ / (cos_a + 1);
group.path_.push_back(Point64(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q));
}
void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
{
//even though angle may be negative this is a convex join
Point64 pt = path[j];
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle)));
double step_sin = std::sin(angle / steps);
double step_cos = std::cos(angle / steps);
PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
if (j == k) pt2.Negate();
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
for (int i = 0; i < steps; i++)
{
pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y,
pt2.x * step_sin + pt2.y * step_cos);
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
}
group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
}
void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
{
// Let A = change in angle where edges join
// A == 0: ie no change in angle (flat join)
// A == PI: edges 'spike'
// sin(A) < 0: right turning
// cos(A) < 0: change in angle is more than 90 degree
if (path[j] == path[k]) { k = j; return; }
double sin_a = CrossProduct(norms[j], norms[k]);
double cos_a = DotProduct(norms[j], norms[k]);
if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0;
bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0;
// when there's almost no angle of deviation or it's concave
if (almostNoAngle || (sin_a * group_delta_ < 0))
{
Point64 p1 = Point64(
path[j].x + norms[k].x * group_delta_,
path[j].y + norms[k].y * group_delta_);
Point64 p2 = Point64(
path[j].x + norms[j].x * group_delta_,
path[j].y + norms[j].y * group_delta_);
group.path_.push_back(p1);
if (p1 != p2)
{
// when concave add an extra vertex to ensure neat clipping
if (!almostNoAngle) group.path_.push_back(path[j]);
group.path_.push_back(p2);
}
}
else // it's convex
{
if (join_type_ == JoinType::Round)
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
else if (join_type_ == JoinType::Miter)
{
// miter unless the angle is so acute the miter would exceeds ML
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
else DoSquare(group, path, j, k);
}
// don't bother squaring angles that deviate < ~20 degrees because
// squaring will be indistinguishable from mitering and just be a lot slower
else if (cos_a > 0.9)
DoMiter(group, path, j, k, cos_a);
else
DoSquare(group, path, j, k);
}
k = j;
}
void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
{
group.path_.clear();
for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
OffsetPoint(group, path, i, j);
group.paths_out_.push_back(group.path_);
}
void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
{
OffsetPolygon(group, path);
std::reverse(path.begin(), path.end());
BuildNormals(path);
OffsetPolygon(group, path);
}
void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
{
group.path_.clear();
// do the line start cap
switch (end_type)
{
case EndType::Butt:
group.path_.push_back(Point64(
path[0].x - norms[0].x * group_delta_,
path[0].y - norms[0].y * group_delta_));
group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_));
break;
case EndType::Round:
DoRound(group, path, 0,0, PI);
break;
default:
DoSquare(group, path, 0, 0);
break;
}
size_t highI = path.size() - 1;
// offset the left side going forward
for (Path64::size_type i = 1, k = 0; i < highI; ++i)
OffsetPoint(group, path, i, 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];
// do the line end cap
switch (end_type)
{
case EndType::Butt:
group.path_.push_back(Point64(
path[highI].x - norms[highI].x * group_delta_,
path[highI].y - norms[highI].y * group_delta_));
group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
break;
case EndType::Round:
DoRound(group, path, highI, highI, PI);
break;
default:
DoSquare(group, path, highI, highI);
break;
}
for (size_t i = highI, k = 0; i > 0; --i)
OffsetPoint(group, path, i, k);
group.paths_out_.push_back(group.path_);
}
void ClipperOffset::DoGroupOffset(Group& group, double delta)
{
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
bool isClosedPaths = IsClosedPath(group.end_type_);
if (isClosedPaths)
{
//the lowermost polygon must be an outer polygon. So we can use that as the
//designated orientation for outer polygons (needed for tidy-up clipping)
Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_);
// nb: don't use the default orientation here ...
double area = Area(group.paths_in_[lowestIdx]);
if (area == 0) return;
group.is_reversed_ = (area < 0);
if (group.is_reversed_) delta = -delta;
}
else
group.is_reversed_ = false;
group_delta_ = delta;
abs_group_delta_ = std::abs(group_delta_);
join_type_ = group.join_type_;
double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_
: std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived
//calculate a sensible number of steps (for 360 deg for the given offset
if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round)
{
steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2);
}
bool is_closed_path = IsClosedPath(group.end_type_);
Paths64::const_iterator path_iter;
for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter)
{
Path64 path = StripDuplicates(*path_iter, is_closed_path);
Path64::size_type cnt = path.size();
if (cnt == 0) continue;
if (cnt == 1) // single point - only valid with open paths
{
group.path_ = Path64();
//single vertex so build a circle or square ...
if (group.join_type_ == JoinType::Round)
{
double radius = abs_group_delta_;
group.path_ = Ellipse(path[0], radius, radius);
}
else
{
int d = (int)std::ceil(abs_group_delta_);
Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
group.path_ = r.AsPath();
}
group.paths_out_.push_back(group.path_);
}
else
{
BuildNormals(path);
if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path);
else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
else OffsetOpenPath(group, path, group.end_type_);
}
}
if (!merge_groups_)
{
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != group.is_reversed_;
c.AddSubject(group.paths_out_);
if (group.is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
else
c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
}
solution.reserve(solution.size() + group.paths_out_.size());
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
group.paths_out_.clear();
}
Paths64 ClipperOffset::Execute(double delta)
{
solution.clear();
if (std::abs(delta) < default_arc_tolerance)
{
for (const Group& group : groups_)
{
solution.reserve(solution.size() + group.paths_in_.size());
copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution));
}
return solution;
}
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
std::vector<Group>::iterator groups_iter;
for (groups_iter = groups_.begin();
groups_iter != groups_.end(); ++groups_iter)
{
DoGroupOffset(*groups_iter, delta);
}
if (merge_groups_ && groups_.size() > 0)
{
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
c.AddSubject(solution);
if (groups_[0].is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, solution);
else
c.Execute(ClipType::Union, FillRule::Positive, solution);
}
return solution;
}
} // namespace

View File

@ -0,0 +1,480 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 15 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#include <cmath>
#include "clipper2/clipper.h"
#include "clipper2/clipper.rectclip.h"
namespace Clipper2Lib {
//------------------------------------------------------------------------------
// Miscellaneous methods
//------------------------------------------------------------------------------
inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
{
PointInPolygonResult result = PointInPolygonResult::IsOn;
for(const Point64& pt : path2)
{
result = PointInPolygon(pt, path1);
if (result != PointInPolygonResult::IsOn) break;
}
return result;
}
inline bool GetLocation(const Rect64& rec,
const Point64& pt, Location& loc)
{
if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom)
{
loc = Location::Left;
return false;
}
else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom)
{
loc = Location::Right;
return false;
}
else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right)
{
loc = Location::Top;
return false;
}
else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right)
{
loc = Location::Bottom;
return false;
}
else if (pt.x < rec.left) loc = Location::Left;
else if (pt.x > rec.right) loc = Location::Right;
else if (pt.y < rec.top) loc = Location::Top;
else if (pt.y > rec.bottom) loc = Location::Bottom;
else loc = Location::Inside;
return true;
}
Point64 GetIntersectPoint64(const Point64& ln1a, const Point64& ln1b,
const Point64& ln2a, const Point64& ln2b)
{
// see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/
if (ln1b.x == ln1a.x)
{
if (ln2b.x == ln2a.x) return Point64(); // parallel lines
double m2 = static_cast<double>(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x);
double b2 = ln2a.y - m2 * ln2a.x;
return Point64(ln1a.x, static_cast<int64_t>(std::round(m2 * ln1a.x + b2)));
}
else if (ln2b.x == ln2a.x)
{
double m1 = static_cast<double>(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x);
double b1 = ln1a.y - m1 * ln1a.x;
return Point64(ln2a.x, static_cast<int64_t>(std::round(m1 * ln2a.x + b1)));
}
else
{
double m1 = static_cast<double>(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x);
double b1 = ln1a.y - m1 * ln1a.x;
double m2 = static_cast<double>(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x);
double b2 = ln2a.y - m2 * ln2a.x;
if (std::fabs(m1 - m2) > 1.0E-15)
{
double x = (b2 - b1) / (m1 - m2);
return Point64(x, m1 * x + b1);
}
else
return Point64((ln1a.x + ln1b.x) * 0.5, (ln1a.y + ln1b.y) * 0.5);
}
}
inline bool GetIntersection(const Path64& rectPath,
const Point64& p, const Point64& p2, Location& loc, Point64& ip)
{
// gets the intersection closest to 'p'
// when Result = false, loc will remain unchanged
switch (loc)
{
case Location::Left:
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
else if (p.y < rectPath[0].y &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
loc = Location::Bottom;
}
else return false;
break;
case Location::Top:
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
else if (p.x < rectPath[0].x &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
loc = Location::Left;
}
else if (p.x > rectPath[1].x &&
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
loc = Location::Right;
}
else return false;
break;
case Location::Right:
if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
else if (p.y < rectPath[0].y &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
loc = Location::Bottom;
}
else return false;
break;
case Location::Bottom:
if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
else if (p.x < rectPath[3].x &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
loc = Location::Left;
}
else if (p.x > rectPath[2].x &&
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
loc = Location::Right;
}
else return false;
break;
default: // loc == rInside
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
loc = Location::Left;
}
else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
loc = Location::Right;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
loc = Location::Bottom;
}
else return false;
break;
}
return true;
}
inline Location GetAdjacentLocation(Location loc, bool isClockwise)
{
int delta = (isClockwise) ? 1 : 3;
return static_cast<Location>((static_cast<int>(loc) + delta) % 4);
}
inline bool HeadingClockwise(Location prev, Location curr)
{
return (static_cast<int>(prev) + 1) % 4 == static_cast<int>(curr);
}
inline bool AreOpposites(Location prev, Location curr)
{
return abs(static_cast<int>(prev) - static_cast<int>(curr)) == 2;
}
inline bool IsClockwise(Location prev, Location curr,
Point64 prev_pt, Point64 curr_pt, Point64 rect_mp)
{
if (AreOpposites(prev, curr))
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
else
return HeadingClockwise(prev, curr);
}
//----------------------------------------------------------------------------
// RectClip64
//----------------------------------------------------------------------------
inline void RectClip64::Reset()
{
result_.clear();
start_locs_.clear();
}
void RectClip64::AddCorner(Location prev, Location curr)
{
if (HeadingClockwise(prev, curr))
result_.push_back(rectPath_[static_cast<int>(prev)]);
else
result_.push_back(rectPath_[static_cast<int>(curr)]);
}
void RectClip64::AddCorner(Location& loc, bool isClockwise)
{
if (isClockwise)
{
result_.push_back(rectPath_[static_cast<int>(loc)]);
loc = GetAdjacentLocation(loc, true);
}
else
{
loc = GetAdjacentLocation(loc, false);
result_.push_back(rectPath_[static_cast<int>(loc)]);
}
}
void RectClip64::GetNextLocation(const Path64& path,
Location& loc, int& i, int highI)
{
switch (loc)
{
case Location::Left:
while (i <= highI && path[i].x <= rect_.left) ++i;
if (i > highI) break;
else if (path[i].x >= rect_.right) loc = Location::Right;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else loc = Location::Inside;
break;
case Location::Top:
while (i <= highI && path[i].y <= rect_.top) ++i;
if (i > highI) break;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].x >= rect_.right) loc = Location::Right;
else loc = Location::Inside;
break;
case Location::Right:
while (i <= highI && path[i].x >= rect_.right) ++i;
if (i > highI) break;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else loc = Location::Inside;
break;
case Location::Bottom:
while (i <= highI && path[i].y >= rect_.bottom) ++i;
if (i > highI) break;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].x >= rect_.right) loc = Location::Right;
else loc = Location::Inside;
break;
case Location::Inside:
while (i <= highI)
{
if (path[i].x < rect_.left) loc = Location::Left;
else if (path[i].x > rect_.right) loc = Location::Right;
else if (path[i].y > rect_.bottom) loc = Location::Bottom;
else if (path[i].y < rect_.top) loc = Location::Top;
else { result_.push_back(path[i]); ++i; continue; }
break; //inner loop
}
break;
} //switch
}
Path64 RectClip64::Execute(const Path64& path)
{
if (rect_.IsEmpty() || path.size() < 3) return Path64();
Reset();
int i = 0, highI = static_cast<int>(path.size()) - 1;
Location prev = Location::Inside, loc;
Location crossing_loc = Location::Inside;
Location first_cross_ = Location::Inside;
if (!GetLocation(rect_, path[highI], loc))
{
i = highI - 1;
while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
if (i < 0) return path;
if (prev == Location::Inside) loc = Location::Inside;
i = 0;
}
Location starting_loc = loc;
///////////////////////////////////////////////////
while (i <= highI)
{
prev = loc;
Location crossing_prev = crossing_loc;
GetNextLocation(path, loc, i, highI);
if (i > highI) break;
Point64 ip, ip2;
Point64 prev_pt = (i) ? path[static_cast<size_t>(i - 1)] : path[highI];
crossing_loc = loc;
if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
{
// ie remaining outside
if (crossing_prev == Location::Inside)
{
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
do {
start_locs_.push_back(prev);
prev = GetAdjacentLocation(prev, isClockw);
} while (prev != loc);
crossing_loc = crossing_prev; // still not crossed
}
else if (prev != Location::Inside && prev != loc)
{
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
do {
AddCorner(prev, isClockw);
} while (prev != loc);
}
++i;
continue;
}
////////////////////////////////////////////////////
// we must be crossing the rect boundary to get here
////////////////////////////////////////////////////
if (loc == Location::Inside) // path must be entering rect
{
if (first_cross_ == Location::Inside)
{
first_cross_ = crossing_loc;
start_locs_.push_back(prev);
}
else if (prev != crossing_loc)
{
bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_);
do {
AddCorner(prev, isClockw);
} while (prev != crossing_loc);
}
}
else if (prev != Location::Inside)
{
// passing right through rect. 'ip' here will be the second
// intersect pt but we'll also need the first intersect pt (ip2)
loc = prev;
GetIntersection(rectPath_, prev_pt, path[i], loc, ip2);
if (crossing_prev != Location::Inside)
AddCorner(crossing_prev, loc);
if (first_cross_ == Location::Inside)
{
first_cross_ = loc;
start_locs_.push_back(prev);
}
loc = crossing_loc;
result_.push_back(ip2);
if (ip == ip2)
{
// it's very likely that path[i] is on rect
GetLocation(rect_, path[i], loc);
AddCorner(crossing_loc, loc);
crossing_loc = loc;
continue;
}
}
else // path must be exiting rect
{
loc = crossing_loc;
if (first_cross_ == Location::Inside)
first_cross_ = crossing_loc;
}
result_.push_back(ip);
} //while i <= highI
///////////////////////////////////////////////////
if (first_cross_ == Location::Inside)
{
if (starting_loc == Location::Inside) return path;
Rect64 tmp_rect = Bounds(path);
if (tmp_rect.Contains(rect_) &&
Path1ContainsPath2(path, rectPath_) !=
PointInPolygonResult::IsOutside) return rectPath_;
else
return Path64();
}
if (loc != Location::Inside &&
(loc != first_cross_ || start_locs_.size() > 2))
{
if (start_locs_.size() > 0)
{
prev = loc;
for (auto loc2 : start_locs_)
{
if (prev == loc2) continue;
AddCorner(prev, HeadingClockwise(prev, loc2));
prev = loc2;
}
loc = prev;
}
if (loc != first_cross_)
AddCorner(loc, HeadingClockwise(loc, first_cross_));
}
if (result_.size() < 3) return Path64();
// tidy up duplicates and collinear segments
Path64 res;
res.reserve(result_.size());
size_t k = 0; highI = static_cast<int>(result_.size()) - 1;
Point64 prev_pt = result_[highI];
res.push_back(result_[0]);
Path64::const_iterator cit;
for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit)
{
if (CrossProduct(prev_pt, res[k], *cit))
{
prev_pt = res[k++];
res.push_back(*cit);
}
else
res[k] = *cit;
}
if (k < 2) return Path64();
// and a final check for collinearity
else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back();
return res;
}
} // namespace

23
thirdparty/clipper2/LICENSE vendored Normal file
View File

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.