Add a method to offset line chains.
This commit is contained in:
parent
8a22a8fa69
commit
de0c1218c3
|
@ -32,6 +32,7 @@
|
|||
#include <geometry/seg.h>
|
||||
#include <geometry/shape.h>
|
||||
#include <geometry/shape_arc.h>
|
||||
#include <geometry/corner_strategy.h>
|
||||
#include <math/vector2d.h>
|
||||
|
||||
/**
|
||||
|
@ -786,6 +787,20 @@ public:
|
|||
*/
|
||||
double Area( bool aAbsolute = true ) const;
|
||||
|
||||
/**
|
||||
* Creates line chains \a aLeft and \a aRight offset to this line chain.
|
||||
*
|
||||
* @param aAmount is the amount to offset.
|
||||
* @param aCornerStrategy is the corner rounding strategy.
|
||||
* @param aMaxError is the max error used for rounding.
|
||||
* @param aLeft left line chain output.
|
||||
* @param aRight right line chain output.
|
||||
* @param aSimplify set to run Simplify on the inflated polygon.
|
||||
*/
|
||||
bool OffsetLine( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
|
||||
SHAPE_LINE_CHAIN& aLeft, SHAPE_LINE_CHAIN& aRight,
|
||||
bool aSimplify = false ) const;
|
||||
|
||||
size_t ArcCount() const
|
||||
{
|
||||
return m_arcs.size();
|
||||
|
|
|
@ -1032,6 +1032,22 @@ public:
|
|||
Inflate( -aAmount, aCornerStrategy, aMaxError );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform offsetting of a line chain. Replaces this polygon set with the result.
|
||||
*
|
||||
* @param aLine is the line to perform offsetting on.
|
||||
* @param aAmount is the number of units to offset the line chain.
|
||||
* @param aCornerStrategy #ALLOW_ACUTE_CORNERS to preserve all angles,
|
||||
* #CHAMFER_ACUTE_CORNERS to chop angles less than 90°,
|
||||
* #ROUND_ACUTE_CORNERS to round off angles less than 90°,
|
||||
* #ROUND_ALL_CORNERS to round regardless of angles
|
||||
* @param aMaxError is the allowable deviation when rounding corners with an approximated
|
||||
* polygon
|
||||
* @param aSimplify set to simplify the output polygon.
|
||||
*/
|
||||
void OffsetLineChain( const SHAPE_LINE_CHAIN& aLine, int aAmount,
|
||||
CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify );
|
||||
|
||||
/**
|
||||
* Perform outline inflation/deflation, using round corners.
|
||||
*
|
||||
|
@ -1435,6 +1451,9 @@ private:
|
|||
void inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy );
|
||||
void inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify = false );
|
||||
|
||||
void inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
|
||||
CORNER_STRATEGY aCornerStrategy, bool aSimplify = false );
|
||||
|
||||
/**
|
||||
* This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon
|
||||
* simplification (merging overlapping polygons).
|
||||
|
|
|
@ -2131,6 +2131,123 @@ double SHAPE_LINE_CHAIN::Area( bool aAbsolute ) const
|
|||
}
|
||||
|
||||
|
||||
bool SHAPE_LINE_CHAIN::OffsetLine( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
|
||||
SHAPE_LINE_CHAIN& aLeft, SHAPE_LINE_CHAIN& aRight,
|
||||
bool aSimplify ) const
|
||||
{
|
||||
if( PointCount() < 2 )
|
||||
return false;
|
||||
|
||||
SHAPE_POLY_SET poly;
|
||||
poly.OffsetLineChain( *this, aAmount * 2, aCornerStrategy, aMaxError, aSimplify );
|
||||
|
||||
if( poly.OutlineCount() != 1 )
|
||||
return false;
|
||||
|
||||
if( poly.COutline( 0 ).PointCount() < 3 )
|
||||
return false;
|
||||
|
||||
if( poly.HasHoles() )
|
||||
return false;
|
||||
|
||||
SHAPE_LINE_CHAIN outline = poly.COutline( 0 );
|
||||
|
||||
wxASSERT( outline.IsClosed() );
|
||||
|
||||
const VECTOR2I& start = CPoint( 0 );
|
||||
const VECTOR2I& end = CPoint( -1 );
|
||||
|
||||
outline.Split( start, true );
|
||||
outline.Split( end, true );
|
||||
|
||||
const int idA = outline.Find( start );
|
||||
const int idB = outline.Find( end );
|
||||
|
||||
if( idA == -1 || idB == -1 )
|
||||
return false;
|
||||
|
||||
aLeft.Clear();
|
||||
aRight.Clear();
|
||||
|
||||
for( int i = idA;; )
|
||||
{
|
||||
aLeft.Append( outline.CPoint( i ) );
|
||||
|
||||
i = ( i + 1 ) % outline.PointCount();
|
||||
|
||||
if( i == idB )
|
||||
{
|
||||
aLeft.Append( outline.CPoint( i ) );
|
||||
break;
|
||||
}
|
||||
|
||||
if( i == idA )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( aLeft.PointCount() < 2 )
|
||||
return false;
|
||||
|
||||
for( int i = idB;; )
|
||||
{
|
||||
aRight.Append( outline.CPoint( i ) );
|
||||
|
||||
i = ( i + 1 ) % outline.PointCount();
|
||||
|
||||
if( i == idA )
|
||||
{
|
||||
aRight.Append( outline.CPoint( i ) );
|
||||
break;
|
||||
}
|
||||
|
||||
if( i == idB )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( aRight.PointCount() < 2 )
|
||||
return false;
|
||||
|
||||
if( aLeft.CPoint( 0 ) != start )
|
||||
{
|
||||
aLeft = aLeft.Reverse();
|
||||
wxASSERT( aLeft.CPoint( 0 ) == start );
|
||||
}
|
||||
|
||||
if( aRight.CPoint( 0 ) != start )
|
||||
{
|
||||
aRight = aRight.Reverse();
|
||||
wxASSERT( aRight.CPoint( 0 ) == start );
|
||||
}
|
||||
|
||||
SEG base( CPoint( 0 ), CPoint( 1 ) );
|
||||
int sideLeft = base.Side( aLeft.CPoint( 1 ) );
|
||||
int sideRight = base.Side( aRight.CPoint( 1 ) );
|
||||
|
||||
if( sideLeft == 0 || sideRight == 0 )
|
||||
return false;
|
||||
|
||||
if( sideLeft == sideRight )
|
||||
return false;
|
||||
|
||||
if( sideLeft > 0 && sideRight < 0 )
|
||||
std::swap( aLeft, aRight );
|
||||
|
||||
if( aLeft.PointCount() < 4 )
|
||||
return false;
|
||||
|
||||
if( aRight.PointCount() < 4 )
|
||||
return false;
|
||||
|
||||
aLeft.Remove( 0 );
|
||||
aLeft.Remove( aLeft.PointCount() - 1 );
|
||||
|
||||
aRight.Remove( 0 );
|
||||
aRight.Remove( aRight.PointCount() - 1 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_LINE_CHAIN::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError,
|
||||
ERROR_LOC aErrorLoc ) const
|
||||
{
|
||||
|
|
|
@ -1235,6 +1235,103 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
|
||||
CORNER_STRATEGY aCornerStrategy, bool aSimplify )
|
||||
{
|
||||
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
|
||||
|
||||
switch( aCornerStrategy )
|
||||
{
|
||||
case CORNER_STRATEGY::ALLOW_ACUTE_CORNERS:
|
||||
joinType = JoinType::Miter;
|
||||
miterLimit = 10; // Allows large spikes
|
||||
break;
|
||||
|
||||
case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
|
||||
joinType = JoinType::Miter;
|
||||
break;
|
||||
|
||||
case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
|
||||
joinType = JoinType::Miter;
|
||||
break;
|
||||
|
||||
case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
|
||||
joinType = JoinType::Square;
|
||||
break;
|
||||
|
||||
case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
|
||||
joinType = JoinType::Round;
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<CLIPPER_Z_VALUE> zValues;
|
||||
std::vector<SHAPE_ARC> arcBuffer;
|
||||
|
||||
Path64 path = aLine.convertToClipper2( true, zValues, arcBuffer );
|
||||
c.AddPath( path, joinType, EndType::Butt );
|
||||
|
||||
// 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 );
|
||||
|
||||
PolyTree64 tree;
|
||||
|
||||
if( aSimplify )
|
||||
{
|
||||
Paths64 paths2;
|
||||
c.Execute( aAmount, paths2 );
|
||||
|
||||
Clipper2Lib::SimplifyPaths( paths2, std::abs( aAmount ) * coeff, false );
|
||||
|
||||
Clipper64 c2;
|
||||
c2.PreserveCollinear = false;
|
||||
c2.ReverseSolution = false;
|
||||
c2.AddSubject( paths2 );
|
||||
c2.Execute( ClipType::Union, FillRule::Positive, tree );
|
||||
}
|
||||
else
|
||||
{
|
||||
c.Execute( aAmount, tree );
|
||||
}
|
||||
|
||||
importTree( tree, zValues, arcBuffer );
|
||||
tree.Clear();
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
|
||||
bool aSimplify )
|
||||
{
|
||||
|
@ -1247,6 +1344,15 @@ void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::OffsetLineChain( const SHAPE_LINE_CHAIN& aLine, int aAmount,
|
||||
CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify )
|
||||
{
|
||||
int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
|
||||
|
||||
inflateLine2( aLine, aAmount, segCount, aCornerStrategy, aSimplify );
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::importTree( ClipperLib::PolyTree* tree,
|
||||
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
|
||||
const std::vector<SHAPE_ARC>& aArcBuffer )
|
||||
|
|
Loading…
Reference in New Issue