Use ZFillFunction to modify arcs post clipper (does not fully work yet)
Added a unit test to verify union and intersection of polygons result in desired results: subject + clip - intersect == union
This commit is contained in:
parent
437e2783fb
commit
260a9d0540
|
@ -39,6 +39,12 @@
|
|||
*/
|
||||
struct CLIPPER_Z_VALUE
|
||||
{
|
||||
CLIPPER_Z_VALUE()
|
||||
{
|
||||
m_FirstArcIdx = -1;
|
||||
m_SecondArcIdx = -1;
|
||||
}
|
||||
|
||||
CLIPPER_Z_VALUE( const std::pair<ssize_t, ssize_t> aShapeIndices, ssize_t aOffset = 0 )
|
||||
{
|
||||
m_FirstArcIdx = aShapeIndices.first;
|
||||
|
@ -836,7 +842,19 @@ protected:
|
|||
void splitArc( ssize_t aPtIndex, bool aCoincident = false );
|
||||
|
||||
|
||||
void ammendArc( size_t aArcIndex, VECTOR2I aNewStart, VECTOR2I aNewEnd );
|
||||
void ammendArc( size_t aArcIndex, const VECTOR2I& aNewStart, const VECTOR2I& aNewEnd );
|
||||
|
||||
|
||||
void ammendArcStart( size_t aArcIndex, const VECTOR2I& aNewStart )
|
||||
{
|
||||
ammendArc( aArcIndex, aNewStart, m_arcs[aArcIndex].GetP1() );
|
||||
}
|
||||
|
||||
|
||||
void ammendArcEnd( size_t aArcIndex, const VECTOR2I& aNewEnd )
|
||||
{
|
||||
ammendArc( aArcIndex, m_arcs[aArcIndex].GetP0(), aNewEnd );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation
|
||||
|
|
|
@ -151,7 +151,8 @@ void SHAPE_LINE_CHAIN::convertArc( ssize_t aArcIndex )
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_LINE_CHAIN::ammendArc( size_t aArcIndex, VECTOR2I aNewStart, VECTOR2I aNewEnd )
|
||||
void SHAPE_LINE_CHAIN::ammendArc( size_t aArcIndex, const VECTOR2I& aNewStart,
|
||||
const VECTOR2I& aNewEnd )
|
||||
{
|
||||
wxCHECK_MSG( aArcIndex < m_arcs.size(), /* void */,
|
||||
"Invalid arc index requested." );
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <cstdio>
|
||||
#include <istream> // for operator<<, operator>>
|
||||
#include <limits> // for numeric_limits
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string> // for char_traits, operator!=
|
||||
|
@ -518,6 +519,7 @@ void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET
|
|||
|
||||
std::vector<CLIPPER_Z_VALUE> zValues;
|
||||
std::vector<SHAPE_ARC> arcBuffer;
|
||||
std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
|
||||
|
||||
for( const POLYGON& poly : aShape.m_polys )
|
||||
{
|
||||
|
@ -540,12 +542,62 @@ void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET
|
|||
ClipperLib::PolyTree solution;
|
||||
|
||||
ClipperLib::ZFillCallback callback =
|
||||
[]( ClipperLib::IntPoint & e1bot, ClipperLib::IntPoint & e1top,
|
||||
[&]( ClipperLib::IntPoint & e1bot, ClipperLib::IntPoint & e1top,
|
||||
ClipperLib::IntPoint & e2bot, ClipperLib::IntPoint & e2top,
|
||||
ClipperLib::IntPoint & pt )
|
||||
{
|
||||
//@todo write callback to handle arc intersections
|
||||
int i = 0;
|
||||
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 ammend X,Y values to true intersection between arcs or arc and segment
|
||||
};
|
||||
|
||||
c.ZFillFunction( callback ); // register callback
|
||||
|
@ -553,6 +605,45 @@ void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET
|
|||
c.Execute( aType, solution, ClipperLib::pftNonZero, ClipperLib::pftNonZero );
|
||||
|
||||
importTree( &solution, zValues, arcBuffer );
|
||||
|
||||
// Ammend arcs for the intersection points
|
||||
for( auto& poly : m_polys )
|
||||
{
|
||||
for( size_t i = 0; i < poly.size(); i++ )
|
||||
{
|
||||
for( int j = 0; j < poly[i].PointCount(); j++ )
|
||||
{
|
||||
const VECTOR2I& pt = poly[i].CPoint( j );
|
||||
|
||||
if( newIntersectPoints.find( pt ) != newIntersectPoints.end() )
|
||||
{
|
||||
const std::pair<ssize_t, ssize_t>& shape = poly[i].CShapes()[j];
|
||||
CLIPPER_Z_VALUE zval = newIntersectPoints.at( pt );
|
||||
|
||||
// Fixup arc end points to match the new intersection points found in clipper
|
||||
//@todo consider editing the intersection point to be the "true" arc intersection
|
||||
if( poly[i].IsSharedPt( j ) )
|
||||
{
|
||||
poly[i].ammendArcEnd( shape.first, pt );
|
||||
poly[i].ammendArcStart( shape.second, pt );
|
||||
}
|
||||
else if( poly[i].IsArcStart( j ) )
|
||||
{
|
||||
poly[i].ammendArcStart( shape.first, pt );
|
||||
}
|
||||
else if( poly[i].IsArcEnd( j ) )
|
||||
{
|
||||
poly[i].ammendArcEnd( shape.first, pt );
|
||||
}
|
||||
else
|
||||
{
|
||||
poly[i].splitArc( j );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ struct CommonTestData
|
|||
///< Holes and outline contain arcs
|
||||
SHAPE_POLY_SET holeyCurvedPolyMulti; ///< Polygon with a multiple outlines + multiple holes.
|
||||
///< Holes and outlines contain arcs
|
||||
SHAPE_POLY_SET holeyCurvedPolyInter; ///< Polygon with a single outlines + multiple holes.
|
||||
///< Holes and outlines contain arcs. Intersects with above
|
||||
///< two polysets
|
||||
|
||||
// Vectors containing the information with which the polygons are populated.
|
||||
std::vector<VECTOR2I> uniquePoints;
|
||||
|
@ -191,6 +194,20 @@ struct CommonTestData
|
|||
o1h2.Append( SHAPE_ARC( { 760000, 258000 }, { 746000, 222000 }, { 686000, 260000 }, 0 ) );
|
||||
o1h2.SetClosed( true );
|
||||
holeyCurvedPolyMulti.AddHole( o1h2 );
|
||||
|
||||
// Intersecting shape:
|
||||
SHAPE_LINE_CHAIN oline2;
|
||||
oline2.Append( { 340000, 540000 } );
|
||||
oline2.Append( SHAPE_ARC( { 629000, 659000 }, { 790000, 436000 }, { 751000, 161000 }, 0 ) );
|
||||
oline2.Append( SHAPE_ARC( { 651000, 161000 }, { 300000, 272000 }, { 320000, 426000 }, 0 ) );
|
||||
oline2.SetClosed( true );
|
||||
holeyCurvedPolyInter.AddOutline( oline2 );
|
||||
|
||||
SHAPE_LINE_CHAIN o2h2;
|
||||
o2h2.Append( SHAPE_ARC( { 680000, 486000 }, { 728000, 506000 }, { 760000, 458000 }, 0 ) );
|
||||
o2h2.Append( SHAPE_ARC( { 760000, 458000 }, { 746000, 422000 }, { 686000, 460000 }, 0 ) );
|
||||
o2h2.SetClosed( true );
|
||||
holeyCurvedPolyInter.AddHole( o2h2 );
|
||||
}
|
||||
|
||||
~CommonTestData()
|
||||
|
|
|
@ -221,6 +221,81 @@ inline SHAPE_POLY_SET FilletPolySet( SHAPE_POLY_SET& aPolySet, int aRadius, int
|
|||
return filletedPolySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a SHAPE_LINE_CHAIN has been assembled correctly by ensuring that the
|
||||
* arc start and end points match points on the chain and that any points inside the arcs
|
||||
* actually collide with the arc segments (with an error margin of 5000 IU)
|
||||
*
|
||||
* @param aChain to test
|
||||
* @return true if outline is valid
|
||||
*/
|
||||
inline bool IsOutlineValid( const SHAPE_LINE_CHAIN& aChain )
|
||||
{
|
||||
bool areWeOnArc = false;
|
||||
SHAPE_ARC currentArc;
|
||||
|
||||
for( int i = 0; i < aChain.PointCount(); i++ )
|
||||
{
|
||||
if( !aChain.IsSharedPt( i ) && aChain.IsArcStart( i ) )
|
||||
{
|
||||
currentArc = aChain.Arc( aChain.ArcIndex( i ) );
|
||||
areWeOnArc = true;
|
||||
|
||||
if( currentArc.GetP0() != aChain.CPoint( i ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( areWeOnArc )
|
||||
{
|
||||
if( !currentArc.Collide( aChain.CPoint( i ), 5000 ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( aChain.IsArcEnd( i ) )
|
||||
{
|
||||
areWeOnArc = false;
|
||||
|
||||
if( currentArc.GetP1() != aChain.CPoint( i ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( aChain.IsSharedPt( i ) )
|
||||
{
|
||||
currentArc = aChain.Arc( aChain.ArcIndex( i ) );
|
||||
areWeOnArc = true;
|
||||
|
||||
if( currentArc.GetP0() != aChain.CPoint( i ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a SHAPE_POLY_SET has been assembled correctly by verifying each of the outlines
|
||||
* and holes contained within
|
||||
*
|
||||
* @param aSet to test
|
||||
* @return true if the poly set is valid
|
||||
*/
|
||||
inline bool IsPolySetValid( const SHAPE_POLY_SET& aSet )
|
||||
{
|
||||
for( int i = 0; i < aSet.OutlineCount(); i++ )
|
||||
{
|
||||
if( !IsOutlineValid( aSet.Outline( i ) ) )
|
||||
return false;
|
||||
|
||||
for( int j = 0; j < aSet.HoleCount( i ); j++ )
|
||||
{
|
||||
if( !IsOutlineValid( aSet.CHole( i, j ) ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace GEOM_TEST
|
||||
|
||||
namespace BOOST_TEST_PRINT_NAMESPACE_OPEN
|
||||
|
|
|
@ -20,12 +20,13 @@
|
|||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unit_test_utils/unit_test_utils.h>
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/shape_poly_set.h>
|
||||
|
||||
#include "fixtures_geometry.h"
|
||||
#include "geom_test_utils.h"
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( CurvedPolys )
|
||||
|
@ -80,4 +81,51 @@ BOOST_AUTO_TEST_CASE( TestSimplify )
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check intersection and union between two polygons
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( TestIntersectUnion )
|
||||
{
|
||||
KI_TEST::CommonTestData testData;
|
||||
|
||||
std::map<std::string, SHAPE_POLY_SET> polysToTest = {
|
||||
{ "Case 1: Single polygon", testData.holeyCurvedPolySingle },
|
||||
//{ "Case 2: Multi polygon", testData.holeyCurvedPolyMulti } // This test fails right now:
|
||||
// multiple polys unsupported
|
||||
};
|
||||
|
||||
for( std::pair<std::string, SHAPE_POLY_SET> testCase : polysToTest )
|
||||
{
|
||||
BOOST_TEST_CONTEXT( testCase.first )
|
||||
{
|
||||
SHAPE_POLY_SET testPoly = testCase.second;
|
||||
SHAPE_POLY_SET opPoly = testData.holeyCurvedPolyInter;
|
||||
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( testPoly ) );
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( opPoly ) );
|
||||
|
||||
double testPolyArea = testPoly.Area();
|
||||
double opPolyArea = opPoly.Area();
|
||||
|
||||
SHAPE_POLY_SET intersectionPoly = testPoly;
|
||||
intersectionPoly.BooleanIntersection( opPoly, SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE );
|
||||
double intersectArea = intersectionPoly.Area();
|
||||
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( intersectionPoly ) );
|
||||
|
||||
|
||||
SHAPE_POLY_SET unionPoly = testPoly;
|
||||
unionPoly.BooleanAdd( opPoly, SHAPE_POLY_SET::POLYGON_MODE::PM_STRICTLY_SIMPLE );
|
||||
double unionArea = unionPoly.Area();
|
||||
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( unionPoly ) );
|
||||
|
||||
// Acceptable error of 0.01% (fails at 0.001% for some - this is a Clipper limitation)
|
||||
BOOST_CHECK_CLOSE( testPolyArea + opPolyArea - intersectArea, unionArea, 0.01 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
Loading…
Reference in New Issue