1022 lines
32 KiB
C++
1022 lines
32 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2013 CERN
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
* Copyright (C) 2013-2022 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#ifndef __SHAPE_LINE_CHAIN
|
|
#define __SHAPE_LINE_CHAIN
|
|
|
|
|
|
#include <clipper.hpp>
|
|
#include <clipper2/clipper.h>
|
|
#include <geometry/seg.h>
|
|
#include <geometry/shape.h>
|
|
#include <geometry/shape_arc.h>
|
|
#include <geometry/corner_strategy.h>
|
|
#include <math/vector2d.h>
|
|
|
|
/**
|
|
* Holds information on each point of a SHAPE_LINE_CHAIN that is retrievable
|
|
* after an operation with ClipperLib
|
|
*/
|
|
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;
|
|
m_SecondArcIdx = aShapeIndices.second;
|
|
|
|
auto offsetVal = [&]( ssize_t& aVal )
|
|
{
|
|
if( aVal >= 0 )
|
|
aVal += aOffset;
|
|
};
|
|
|
|
offsetVal( m_FirstArcIdx );
|
|
offsetVal( m_SecondArcIdx );
|
|
}
|
|
|
|
ssize_t m_FirstArcIdx;
|
|
ssize_t m_SecondArcIdx;
|
|
};
|
|
|
|
|
|
/**
|
|
* Represent a polyline containing arcs as well as line segments: A chain of connected line and/or
|
|
* arc segments.
|
|
*
|
|
* The arc shapes are piecewise approximated for the purpose of boolean operations but are used as
|
|
* arcs when doing collision checks.
|
|
*
|
|
* It is purposely not named "polyline" to avoid confusion with the existing CPolyLine
|
|
* in Pcbnew.
|
|
*
|
|
* @note The SHAPE_LINE_CHAIN class shall not be used for polygons!
|
|
*/
|
|
class SHAPE_LINE_CHAIN : public SHAPE_LINE_CHAIN_BASE
|
|
{
|
|
private:
|
|
typedef std::vector<VECTOR2I>::iterator point_iter;
|
|
typedef std::vector<VECTOR2I>::const_iterator point_citer;
|
|
|
|
public:
|
|
/**
|
|
* Represent an intersection between two line segments
|
|
*/
|
|
struct INTERSECTION
|
|
{
|
|
/// Point of intersection between our and their.
|
|
VECTOR2I p;
|
|
|
|
/// Index of the intersecting corner/segment in the 'our' (== this) line.
|
|
int index_our;
|
|
|
|
/// index of the intersecting corner/segment in the 'their' (Intersect() method
|
|
/// parameter) line.
|
|
int index_their;
|
|
|
|
/// When true, the corner [index_our] of the 'our' line lies exactly on 'their' line.
|
|
bool is_corner_our;
|
|
|
|
/// When true, the corner [index_their] of the 'their' line lies exactly on 'our' line.
|
|
/// Note that when both is_corner_our and is_corner_their are set, the line chains touch
|
|
/// with with corners.
|
|
bool is_corner_their;
|
|
|
|
/// Auxiliary flag to avoid copying intersection info to intersection refining code,
|
|
/// used by the refining code (e.g. hull handling stuff in the P&S) to reject false
|
|
/// intersection points.
|
|
bool valid;
|
|
|
|
INTERSECTION() :
|
|
index_our( -1 ),
|
|
index_their( -1 ),
|
|
is_corner_our( false ),
|
|
is_corner_their( false ),
|
|
valid( false )
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* A dynamic state checking if a point lies within polygon with a dynamically built outline (
|
|
* with each piece of the outline added by AddPolyline ()
|
|
*/
|
|
class POINT_INSIDE_TRACKER
|
|
{
|
|
public:
|
|
POINT_INSIDE_TRACKER( const VECTOR2I& aPoint );
|
|
|
|
void AddPolyline( const SHAPE_LINE_CHAIN& aPolyline );
|
|
bool IsInside();
|
|
|
|
private:
|
|
|
|
bool processVertex ( const VECTOR2I& ip, const VECTOR2I& ipNext );
|
|
|
|
VECTOR2I m_point;
|
|
VECTOR2I m_lastPoint;
|
|
VECTOR2I m_firstPoint;
|
|
bool m_finished;
|
|
int m_state;
|
|
int m_count;
|
|
};
|
|
|
|
typedef std::vector<INTERSECTION> INTERSECTIONS;
|
|
|
|
|
|
/**
|
|
* Initialize an empty line chain.
|
|
*/
|
|
SHAPE_LINE_CHAIN() :
|
|
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
|
|
m_closed( false ),
|
|
m_width( 0 )
|
|
{}
|
|
|
|
SHAPE_LINE_CHAIN( const SHAPE_LINE_CHAIN& aShape ) :
|
|
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
|
|
m_points( aShape.m_points ),
|
|
m_shapes( aShape.m_shapes ),
|
|
m_arcs( aShape.m_arcs ),
|
|
m_closed( aShape.m_closed ),
|
|
m_width( aShape.m_width ),
|
|
m_bbox( aShape.m_bbox )
|
|
{}
|
|
|
|
SHAPE_LINE_CHAIN( const std::vector<int>& aV );
|
|
|
|
SHAPE_LINE_CHAIN( const std::vector<VECTOR2I>& aV, bool aClosed = false ) :
|
|
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
|
|
m_closed( aClosed ),
|
|
m_width( 0 )
|
|
{
|
|
m_points = aV;
|
|
m_shapes = std::vector<std::pair<ssize_t, ssize_t>>( aV.size(), SHAPES_ARE_PT );
|
|
}
|
|
|
|
SHAPE_LINE_CHAIN( const SHAPE_ARC& aArc, bool aClosed = false ) :
|
|
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
|
|
m_closed( aClosed ),
|
|
m_width( 0 )
|
|
{
|
|
m_points = aArc.ConvertToPolyline().CPoints();
|
|
m_arcs.emplace_back( aArc );
|
|
m_arcs.back().SetWidth( 0 );
|
|
m_shapes = std::vector<std::pair<ssize_t, ssize_t>>( m_points.size(), { 0, SHAPE_IS_PT } );
|
|
}
|
|
|
|
SHAPE_LINE_CHAIN( const ClipperLib::Path& aPath,
|
|
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()
|
|
{}
|
|
|
|
/**
|
|
* Check if point \a aP lies closer to us than \a aClearance.
|
|
*
|
|
* Note: This is overridden as we want to ensure we test collisions with the arcs in this chain
|
|
* as true arcs rather than segment approximations.
|
|
*
|
|
* @param aP the point to check for collisions with
|
|
* @param aClearance minimum distance that does not qualify as a collision.
|
|
* @param aActual an optional pointer to an int to store the actual distance in the event
|
|
* of a collision.
|
|
* @return true, when a collision has been found
|
|
*/
|
|
virtual bool Collide( const VECTOR2I& aP, int aClearance = 0, int* aActual = nullptr,
|
|
VECTOR2I* aLocation = nullptr ) const override;
|
|
|
|
/**
|
|
* Check if segment \a aSeg lies closer to us than \a aClearance.
|
|
*
|
|
* Note: This is overridden as we want to ensure we test collisions with the arcs in this chain
|
|
* as true arcs rather than segment approximations.
|
|
*
|
|
* @param aSeg the segment to check for collisions with
|
|
* @param aClearance minimum distance that does not qualify as a collision.
|
|
* @param aActual an optional pointer to an int to store the actual distance in the event
|
|
* of a collision.
|
|
* @return true, when a collision has been found
|
|
*/
|
|
virtual bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr,
|
|
VECTOR2I* aLocation = nullptr ) const override;
|
|
|
|
SHAPE_LINE_CHAIN& operator=( const SHAPE_LINE_CHAIN& ) = default;
|
|
|
|
SHAPE* Clone() const override;
|
|
|
|
/**
|
|
* Remove all points from the line chain.
|
|
*/
|
|
void Clear()
|
|
{
|
|
m_points.clear();
|
|
m_arcs.clear();
|
|
m_shapes.clear();
|
|
m_closed = false;
|
|
}
|
|
|
|
/**
|
|
* Mark the line chain as closed (i.e. with a segment connecting the last point with
|
|
* the first point).
|
|
*
|
|
* @param aClosed: whether the line chain is to be closed or not.
|
|
*/
|
|
void SetClosed( bool aClosed )
|
|
{
|
|
m_closed = aClosed;
|
|
mergeFirstLastPointIfNeeded();
|
|
}
|
|
|
|
/**
|
|
* @return true when our line is closed.
|
|
*/
|
|
bool IsClosed() const override
|
|
{
|
|
return m_closed;
|
|
}
|
|
|
|
/**
|
|
* Set the width of all segments in the chain.
|
|
*
|
|
* @param aWidth is the width in internal units.
|
|
*/
|
|
void SetWidth( int aWidth )
|
|
{
|
|
m_width = aWidth;
|
|
}
|
|
|
|
/**
|
|
* Get the current width of the segments in the chain.
|
|
*
|
|
* @return the width in internal units.
|
|
*/
|
|
int Width() const
|
|
{
|
|
return m_width;
|
|
}
|
|
|
|
/**
|
|
* Return the number of segments in this line chain.
|
|
*
|
|
* @return the number of segments.
|
|
*/
|
|
int SegmentCount() const
|
|
{
|
|
int c = m_points.size() - 1;
|
|
|
|
if( m_closed )
|
|
c++;
|
|
|
|
return std::max( 0, c );
|
|
}
|
|
|
|
/**
|
|
* Return the number of shapes (line segments or arcs) in this line chain.
|
|
*
|
|
* This is kind of like SegmentCount() but will only count arcs as 1 segment.
|
|
*
|
|
* @return ArcCount() + the number of non-arc segments.
|
|
*/
|
|
int ShapeCount() const;
|
|
|
|
/**
|
|
* Return the number of points (vertices) in this line chain.
|
|
*
|
|
* @return the number of points.
|
|
*/
|
|
int PointCount() const
|
|
{
|
|
return m_points.size();
|
|
}
|
|
|
|
/**
|
|
* Return a copy of the aIndex-th segment in the line chain.
|
|
*
|
|
* @param aIndex is the index of the segment in the line chain. Negative values are counted
|
|
* from the end (i.e. -1 means the last segment in the line chain).
|
|
* @return a segment at the \a aIndex in the line chain.
|
|
*/
|
|
SEG Segment( int aIndex )
|
|
{
|
|
if( aIndex < 0 )
|
|
aIndex += SegmentCount();
|
|
|
|
if( aIndex == (int)( m_points.size() - 1 ) && m_closed )
|
|
return SEG( m_points[aIndex], m_points[0], aIndex );
|
|
else
|
|
return SEG( m_points[aIndex], m_points[aIndex + 1], aIndex );
|
|
}
|
|
|
|
/**
|
|
* Return a constant copy of the \a aIndex segment in the line chain.
|
|
*
|
|
* @param aIndex is the index of the segment in the line chain. Negative values are counted
|
|
* from the end (i.e. -1 means the last segment in the line chain).
|
|
* @return a segment at \a aIndex in the line chain.
|
|
*/
|
|
const SEG CSegment( int aIndex ) const
|
|
{
|
|
if( aIndex < 0 )
|
|
aIndex += SegmentCount();
|
|
|
|
if( aIndex == (int)( m_points.size() - 1 ) && m_closed )
|
|
return SEG( m_points[aIndex], m_points[0], aIndex );
|
|
else
|
|
return SEG( m_points[aIndex], m_points[aIndex + 1], aIndex );
|
|
}
|
|
|
|
/**
|
|
* Return the vertex index of the next shape in the chain, or -1 if \a aPointIndex is the
|
|
* last shape.
|
|
*
|
|
* If \a aPointIndex is the start of a segment, this will be ( aPointIndex + 1 ). If
|
|
* \a aPointIndex is part of an arc, this will be the index of the start of the next shape after
|
|
* the arc, in other words, the last point of the arc.
|
|
*
|
|
* @param aPointIndex is a vertex in the chain.
|
|
* @param aForwards is true if the next shape is desired, false for previous shape.
|
|
* @return the vertex index of the start of the next shape after aPoint's shape or -1 if
|
|
* the end was reached.
|
|
*/
|
|
int NextShape( int aPointIndex, bool aForwards = true ) const;
|
|
|
|
int PrevShape( int aPointIndex ) const
|
|
{
|
|
return NextShape( aPointIndex, false );
|
|
}
|
|
|
|
/**
|
|
* Move a point to a specific location.
|
|
*
|
|
* @param aIndex is the index of the point to move. Negative indexes are from the back.
|
|
* @param aPos is the new absolute location of the point.
|
|
*/
|
|
void SetPoint( int aIndex, const VECTOR2I& aPos );
|
|
|
|
/**
|
|
* Return a reference to a given point in the line chain.
|
|
*
|
|
* @param aIndex is the index of the point. Negative indexes are from the back.
|
|
* @return a const reference to the point.
|
|
*/
|
|
const VECTOR2I& CPoint( int aIndex ) const
|
|
{
|
|
if( aIndex < 0 )
|
|
aIndex += PointCount();
|
|
else if( aIndex >= PointCount() )
|
|
aIndex -= PointCount();
|
|
|
|
return m_points[aIndex];
|
|
}
|
|
|
|
const std::vector<VECTOR2I>& CPoints() const
|
|
{
|
|
return m_points;
|
|
}
|
|
|
|
/**
|
|
* Return the last point in the line chain.
|
|
*/
|
|
const VECTOR2I& CLastPoint() const
|
|
{
|
|
return m_points[static_cast<size_t>( PointCount() ) - 1];
|
|
}
|
|
|
|
/**
|
|
* @return the vector of stored arcs.
|
|
*/
|
|
const std::vector<SHAPE_ARC>& CArcs() const
|
|
{
|
|
return m_arcs;
|
|
}
|
|
|
|
/**
|
|
* @return the vector of values indicating shape type and location.
|
|
*/
|
|
const std::vector<std::pair<ssize_t, ssize_t>>& CShapes() const
|
|
{
|
|
return m_shapes;
|
|
}
|
|
|
|
/// @copydoc SHAPE::BBox()
|
|
const BOX2I BBox( int aClearance = 0 ) const override
|
|
{
|
|
BOX2I bbox;
|
|
bbox.Compute( m_points );
|
|
|
|
if( aClearance != 0 || m_width != 0 )
|
|
bbox.Inflate( aClearance + m_width );
|
|
|
|
return bbox;
|
|
}
|
|
|
|
void GenerateBBoxCache() const
|
|
{
|
|
m_bbox.Compute( m_points );
|
|
|
|
if( m_width != 0 )
|
|
m_bbox.Inflate( m_width );
|
|
}
|
|
|
|
BOX2I* GetCachedBBox() const override
|
|
{
|
|
return &m_bbox;
|
|
}
|
|
|
|
/**
|
|
* Reverse point order in the line chain.
|
|
*
|
|
* @return line chain with reversed point order (original A-B-C-D: returned D-C-B-A).
|
|
*/
|
|
const SHAPE_LINE_CHAIN Reverse() const;
|
|
|
|
/**
|
|
* Remove all arc references in the line chain, resulting in a chain formed
|
|
* only of straight segments. Any arcs in the chain are removed and only the
|
|
* piecewise linear approximation remains.
|
|
*/
|
|
void ClearArcs();
|
|
|
|
/**
|
|
* Return length of the line chain in Euclidean metric.
|
|
*
|
|
* @return the length of the line chain.
|
|
*/
|
|
long long int Length() const;
|
|
|
|
/**
|
|
* Allocate a number of points all at once (for performance).
|
|
*/
|
|
void ReservePoints( size_t aSize )
|
|
{
|
|
m_points.reserve( aSize );
|
|
}
|
|
|
|
/**
|
|
* Append a new point at the end of the line chain.
|
|
*
|
|
* @param aX is X coordinate of the new point.
|
|
* @param aY is Y coordinate of the new point.
|
|
* @param aAllowDuplication set to true to append the new point even if it is the same as
|
|
* the last entered point, false (default) to skip it if it is the same as the last
|
|
* entered point.
|
|
*/
|
|
void Append( int aX, int aY, bool aAllowDuplication = false )
|
|
{
|
|
VECTOR2I v( aX, aY );
|
|
Append( v, aAllowDuplication );
|
|
}
|
|
|
|
/**
|
|
* Append a new point at the end of the line chain.
|
|
*
|
|
* @param aP is the new point.
|
|
* @param aAllowDuplication set to true to append the new point even it is the same as the
|
|
* last entered point or false (default) to skip it if it is the same as the last
|
|
* entered point.
|
|
*/
|
|
void Append( const VECTOR2I& aP, bool aAllowDuplication = false )
|
|
{
|
|
if( m_points.size() == 0 )
|
|
m_bbox = BOX2I( aP, VECTOR2I( 0, 0 ) );
|
|
|
|
if( m_points.size() == 0 || aAllowDuplication || CPoint( -1 ) != aP )
|
|
{
|
|
m_points.push_back( aP );
|
|
m_shapes.push_back( SHAPES_ARE_PT );
|
|
m_bbox.Merge( aP );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Append another line chain at the end.
|
|
*
|
|
* @param aOtherLine is the line chain to be appended.
|
|
*/
|
|
void Append( const SHAPE_LINE_CHAIN& aOtherLine );
|
|
|
|
void Append( const SHAPE_ARC& aArc );
|
|
void Append( const SHAPE_ARC& aArc, double aAccuracy );
|
|
|
|
void Insert( size_t aVertex, const VECTOR2I& aP );
|
|
|
|
void Insert( size_t aVertex, const SHAPE_ARC& aArc );
|
|
|
|
/**
|
|
* Replace points with indices in range [start_index, end_index] with a single point \a aP.
|
|
*
|
|
* @param aStartIndex is the start of the point range to be replaced (inclusive).
|
|
* @param aEndIndex is the end of the point range to be replaced (inclusive).
|
|
* @param aP is the replacement point.
|
|
*/
|
|
void Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP );
|
|
|
|
/**
|
|
* Replace points with indices in range [start_index, end_index] with the points from line
|
|
* chain \a aLine.
|
|
*
|
|
* @param aStartIndex is the start of the point range to be replaced (inclusive).
|
|
* @param aEndIndex is the end of the point range to be replaced (inclusive).
|
|
* @param aLine is the replacement line chain.
|
|
*/
|
|
void Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE_CHAIN& aLine );
|
|
|
|
/**
|
|
* Remove the range of points [start_index, end_index] from the line chain.
|
|
*
|
|
* @param aStartIndex is the start of the point range to be replaced (inclusive).
|
|
* @param aEndIndex is the end of the point range to be replaced (inclusive).
|
|
*/
|
|
void Remove( int aStartIndex, int aEndIndex );
|
|
|
|
/**
|
|
* Remove the aIndex-th point from the line chain.
|
|
*
|
|
* @param aIndex is the index of the point to be removed.
|
|
*/
|
|
void Remove( int aIndex )
|
|
{
|
|
Remove( aIndex, aIndex );
|
|
}
|
|
|
|
/**
|
|
* Remove the shape at the given index from the line chain.
|
|
*
|
|
* If the given index is inside an arc, the entire arc will be removed.
|
|
* Otherwise this is equivalent to Remove( aPointIndex ).
|
|
*
|
|
* @param aPointIndex is the index of the point to remove.
|
|
*/
|
|
void RemoveShape( int aPointIndex );
|
|
|
|
/**
|
|
* Insert the point aP belonging to one of the our segments, splitting the adjacent segment
|
|
* in two.
|
|
* @param aP is the point to be inserted.
|
|
* @param aExact set to skip the split logic when an exact point match exists.
|
|
* @return index of the newly inserted point (or a negative value if aP does not lie on
|
|
* our line).
|
|
*/
|
|
int Split( const VECTOR2I& aP, bool aExact = false );
|
|
|
|
/**
|
|
* Search for point \a aP.
|
|
*
|
|
* @param aP is the point to be looked for.
|
|
* @return the index of the corresponding point in the line chain or negative when not found.
|
|
*/
|
|
int Find( const VECTOR2I& aP, int aThreshold = 0 ) const;
|
|
|
|
/**
|
|
* Search for segment containing point \a aP.
|
|
*
|
|
* @param aP is the point to be looked for.
|
|
* @return index of the corresponding segment in the line chain or negative when not found.
|
|
*/
|
|
int FindSegment( const VECTOR2I& aP, int aThreshold = 1 ) const;
|
|
|
|
/**
|
|
* Return a subset of this line chain containing the [start_index, end_index] range of points.
|
|
*
|
|
* @param aStartIndex is the start of the point range to be returned (inclusive).
|
|
* @param aEndIndex is the end of the point range to be returned (inclusive).
|
|
* @return the cut line chain.
|
|
*/
|
|
const SHAPE_LINE_CHAIN Slice( int aStartIndex, int aEndIndex = -1 ) const;
|
|
|
|
struct compareOriginDistance
|
|
{
|
|
compareOriginDistance( const VECTOR2I& aOrigin ):
|
|
m_origin( aOrigin )
|
|
{}
|
|
|
|
bool operator()( const INTERSECTION& aA, const INTERSECTION& aB )
|
|
{
|
|
return ( m_origin - aA.p ).EuclideanNorm() < ( m_origin - aB.p ).EuclideanNorm();
|
|
}
|
|
|
|
VECTOR2I m_origin;
|
|
};
|
|
|
|
bool Intersects( const SHAPE_LINE_CHAIN& aChain ) const;
|
|
|
|
/**
|
|
* Find all intersection points between our line chain and the segment \a aSeg.
|
|
*
|
|
* @param aSeg is the segment chain to find intersections with.
|
|
* @param aIp is the reference to a vector to store found intersections. Intersection points
|
|
* are sorted with increasing distances from point aSeg.a.
|
|
* @return the number of intersections found.
|
|
*/
|
|
int Intersect( const SEG& aSeg, INTERSECTIONS& aIp ) const;
|
|
|
|
/**
|
|
* Find all intersection points between our line chain and the line chain \a aChain.
|
|
*
|
|
* @param aChain is the line chain to find intersections with.
|
|
* @param aIp is reference to a vector to store found intersections. Intersection points are
|
|
* sorted with increasing path lengths from the starting point of \a aChain.
|
|
* @return the number of intersections found.
|
|
*/
|
|
int Intersect( const SHAPE_LINE_CHAIN& aChain, INTERSECTIONS& aIp,
|
|
bool aExcludeColinearAndTouching = false,
|
|
BOX2I* aChainBBox = nullptr ) const;
|
|
|
|
/**
|
|
* Compute the walk path length from the beginning of the line chain and the point \a aP
|
|
* belonging to our line.
|
|
*
|
|
* @return the path length in Euclidean metric or -1 if aP does not belong to the line chain.
|
|
*/
|
|
int PathLength( const VECTOR2I& aP, int aIndex = -1 ) const;
|
|
|
|
/**
|
|
* Check if point \a aP is closer to (or on) an edge or vertex of the line chain.
|
|
*
|
|
* @param aP is the point to check.
|
|
* @param aDist is the distance in internal units.
|
|
* @return true if the point is equal to or closer than \a aDist to the line chain.
|
|
*/
|
|
bool CheckClearance( const VECTOR2I& aP, const int aDist) const;
|
|
|
|
/**
|
|
* Check if the line chain is self-intersecting.
|
|
*
|
|
* @return (optional) first found self-intersection point.
|
|
*/
|
|
const std::optional<INTERSECTION> SelfIntersecting() const;
|
|
|
|
/**
|
|
* Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
|
|
*
|
|
* @param aRemoveColinear controls the removal of colinear adjacent segments.
|
|
* @return reference to this line chain.
|
|
*/
|
|
SHAPE_LINE_CHAIN& Simplify( bool aRemoveColinear = true );
|
|
|
|
/**
|
|
* Find the segment nearest the given point.
|
|
*
|
|
* @param aP is the point to compare with.
|
|
* @return the index of the segment closest to the point.
|
|
*/
|
|
int NearestSegment( const VECTOR2I& aP ) const;
|
|
|
|
/**
|
|
* Find a point on the line chain that is closest to point \a aP.
|
|
*
|
|
* @param aP is the point to find.
|
|
* @param aAllowInternalShapePoints if false will not return points internal to an arc (i.e.
|
|
* only the arc endpoints are possible candidates)
|
|
* @return the nearest point.
|
|
*/
|
|
const VECTOR2I NearestPoint( const VECTOR2I& aP, bool aAllowInternalShapePoints = true ) const;
|
|
|
|
/**
|
|
* Find a point on the line chain that is closest to the line defined by the points of
|
|
* segment \a aSeg, also returns the distance.
|
|
*
|
|
* @param aSeg is the segment defining the line.
|
|
* @param dist is the reference receiving the distance to the nearest point.
|
|
* @return the nearest point.
|
|
*/
|
|
const VECTOR2I NearestPoint( const SEG& aSeg, int& dist ) const;
|
|
|
|
/// @copydoc SHAPE::Format()
|
|
const std::string Format( bool aCplusPlus = true ) const override;
|
|
|
|
/// @copydoc SHAPE::Parse()
|
|
bool Parse( std::stringstream& aStream ) override;
|
|
|
|
bool operator!=( const SHAPE_LINE_CHAIN& aRhs ) const
|
|
{
|
|
if( PointCount() != aRhs.PointCount() )
|
|
return true;
|
|
|
|
for( int i = 0; i < PointCount(); i++ )
|
|
{
|
|
if( CPoint( i ) != aRhs.CPoint( i ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CompareGeometry( const SHAPE_LINE_CHAIN& aOther ) const;
|
|
|
|
void Move( const VECTOR2I& aVector ) override
|
|
{
|
|
for( auto& pt : m_points )
|
|
pt += aVector;
|
|
|
|
for( auto& arc : m_arcs )
|
|
arc.Move( aVector );
|
|
|
|
m_bbox.Move( aVector );
|
|
}
|
|
|
|
/**
|
|
* Mirror the line points about y or x (or both).
|
|
*
|
|
* @param aX If true, mirror about the y axis (flip X coordinate).
|
|
* @param aY If true, mirror about the x axis (flip Y coordinate).
|
|
* @param aRef sets the reference point about which to mirror.
|
|
*/
|
|
void Mirror( bool aX = true, bool aY = false, const VECTOR2I& aRef = { 0, 0 } );
|
|
|
|
/**
|
|
* Mirror the line points using an given axis.
|
|
*
|
|
* @param axis is the axis on which to mirror.
|
|
*/
|
|
void Mirror( const SEG& axis );
|
|
|
|
/**
|
|
* Rotate all vertices by a given angle.
|
|
*
|
|
* @param aCenter is the rotation center.
|
|
* @param aAngle is the rotation angle.
|
|
*/
|
|
void Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override;
|
|
|
|
bool IsSolid() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const VECTOR2I PointAlong( int aPathLength ) const;
|
|
|
|
/**
|
|
* Return the area of this chain
|
|
* @param aAbsolute If true, returns a positive value. Otherwise the value depends on the
|
|
* orientation of the chain
|
|
*/
|
|
double Area( bool aAbsolute = true ) const;
|
|
|
|
/**
|
|
* Extract parts of this line chain, depending on the starting and ending points.
|
|
*
|
|
* @param aStart first split point.
|
|
* @param aEnd second split point.
|
|
* @param aPre part before the aStart point.
|
|
* @param aMid part between aStart and aEnd.
|
|
* @param aPost part after the aEnd point.
|
|
*/
|
|
void Split( const VECTOR2I& aStart, const VECTOR2I& aEnd, SHAPE_LINE_CHAIN& aPre,
|
|
SHAPE_LINE_CHAIN& aMid, SHAPE_LINE_CHAIN& aPost ) 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();
|
|
}
|
|
|
|
/**
|
|
* Return the arc index for the given segment index.
|
|
*/
|
|
ssize_t ArcIndex( size_t aSegment ) const
|
|
{
|
|
if( IsSharedPt( aSegment ) )
|
|
return m_shapes[aSegment].second;
|
|
else
|
|
return m_shapes[aSegment].first;
|
|
}
|
|
|
|
const SHAPE_ARC& Arc( size_t aArc ) const
|
|
{
|
|
return m_arcs[aArc];
|
|
}
|
|
|
|
/**
|
|
* Test if a point is shared between multiple shapes
|
|
* @param aIndex
|
|
* @return
|
|
*/
|
|
bool IsSharedPt( size_t aIndex ) const
|
|
{
|
|
return aIndex < m_shapes.size()
|
|
&& m_shapes[aIndex].first != SHAPE_IS_PT
|
|
&& m_shapes[aIndex].second != SHAPE_IS_PT;
|
|
}
|
|
|
|
|
|
bool IsPtOnArc( size_t aPtIndex ) const
|
|
{
|
|
return aPtIndex < m_shapes.size() && m_shapes[aPtIndex] != SHAPES_ARE_PT;
|
|
}
|
|
|
|
|
|
bool IsArcSegment( size_t aSegment ) const
|
|
{
|
|
/*
|
|
* A segment is part of an arc except in the special case of two arcs next to each other
|
|
* but without a shared vertex. Here there is a segment between the end of the first arc
|
|
* and the start of the second arc.
|
|
*/
|
|
size_t nextIdx = aSegment + 1;
|
|
|
|
if( nextIdx > m_shapes.size() - 1 )
|
|
{
|
|
if( nextIdx == m_shapes.size() && m_closed )
|
|
nextIdx = 0; // segment between end point and first point
|
|
else
|
|
return false;
|
|
}
|
|
|
|
return ( IsPtOnArc( aSegment )
|
|
&& ( IsSharedPt( aSegment )
|
|
|| m_shapes[aSegment].first == m_shapes[nextIdx].first ) );
|
|
}
|
|
|
|
|
|
bool IsArcStart( size_t aIndex ) const
|
|
{
|
|
if( aIndex == 0 )
|
|
return IsPtOnArc( aIndex );
|
|
|
|
return ( IsSharedPt( aIndex ) || ( IsPtOnArc( aIndex ) && !IsArcSegment( aIndex - 1 ) ) );
|
|
}
|
|
|
|
|
|
bool IsArcEnd( size_t aIndex ) const
|
|
{
|
|
return ( IsSharedPt( aIndex ) || ( IsPtOnArc( aIndex ) && !IsArcSegment( aIndex ) ) );
|
|
}
|
|
|
|
using SHAPE::Distance;
|
|
|
|
int Distance( const VECTOR2I& aP, bool aOutlineOnly ) const
|
|
{
|
|
return sqrt( SquaredDistance( aP, aOutlineOnly ) );
|
|
}
|
|
|
|
virtual const VECTOR2I GetPoint( int aIndex ) const override { return CPoint(aIndex); }
|
|
virtual const SEG GetSegment( int aIndex ) const override { return CSegment(aIndex); }
|
|
virtual size_t GetPointCount() const override { return PointCount(); }
|
|
virtual size_t GetSegmentCount() const override { return SegmentCount(); }
|
|
|
|
void TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError,
|
|
ERROR_LOC aErrorLoc ) const override;
|
|
|
|
protected:
|
|
friend class SHAPE_POLY_SET;
|
|
|
|
/**
|
|
* Convert an arc to only a point chain by removing the arc and references
|
|
*
|
|
* @param aArcIndex index of the arc to convert to points
|
|
*/
|
|
void convertArc( ssize_t aArcIndex );
|
|
|
|
/**
|
|
* Splits an arc into two arcs at aPtIndex. Parameter \p aCoincident controls whether the two
|
|
* arcs are to be coincident at aPtIndex or whether a short straight segment should be created
|
|
* instead
|
|
*
|
|
* @param aPtIndex index of the point in the chain in which to split the arc
|
|
* @param aCoincident If true, the end point of the first arc will be coincident with the start
|
|
point of the second arc at aPtIndex.
|
|
If false, the end point of the first arc will be at aPtIndex-1 and the
|
|
start point of the second arc will be at aPtIndex, resulting in a short
|
|
straight line segment between aPtIndex-1 and aPtIndex.
|
|
*/
|
|
void splitArc( ssize_t aPtIndex, bool aCoincident = false );
|
|
|
|
void amendArc( size_t aArcIndex, const VECTOR2I& aNewStart, const VECTOR2I& aNewEnd );
|
|
|
|
void amendArcStart( size_t aArcIndex, const VECTOR2I& aNewStart )
|
|
{
|
|
amendArc( aArcIndex, aNewStart, m_arcs[aArcIndex].GetP1() );
|
|
}
|
|
|
|
void amendArcEnd( size_t aArcIndex, const VECTOR2I& aNewEnd )
|
|
{
|
|
amendArc( aArcIndex, m_arcs[aArcIndex].GetP0(), aNewEnd );
|
|
}
|
|
|
|
/**
|
|
* Return the arc index for the given segment index, looking backwards
|
|
*/
|
|
ssize_t reversedArcIndex( size_t aSegment ) const
|
|
{
|
|
if( IsSharedPt( aSegment ) )
|
|
return m_shapes[aSegment].first;
|
|
else
|
|
return m_shapes[aSegment].second;
|
|
}
|
|
|
|
/**
|
|
* Create a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation
|
|
*/
|
|
ClipperLib::Path convertToClipper( bool aRequiredOrientation,
|
|
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
|
|
*/
|
|
void fixIndicesRotation();
|
|
|
|
/**
|
|
* Merge the first and last point if they are the same and this chain is closed.
|
|
*/
|
|
void mergeFirstLastPointIfNeeded();
|
|
|
|
private:
|
|
|
|
static const ssize_t SHAPE_IS_PT;
|
|
|
|
static const std::pair<ssize_t, ssize_t> SHAPES_ARE_PT;
|
|
|
|
/// array of vertices
|
|
std::vector<VECTOR2I> m_points;
|
|
|
|
/**
|
|
* Array of indices that refer to the index of the shape if the point is part of a larger
|
|
* shape, e.g. arc or spline.
|
|
* If the value is -1, the point is just a point.
|
|
*
|
|
* There can be up to two shapes associated with a single point (e.g. the end point of
|
|
* one arc might be the start point of another).
|
|
*
|
|
* Generally speaking only the first element of the pair will be populated (i.e. with a value
|
|
* not equal to SHAPE_IS_PT), unless the point is shared between two arc shapes. If the point
|
|
* is shared, then both the first and second element of the pair should be populated.
|
|
*
|
|
* The second element must always be SHAPE_IS_PT if the first element is SHAPE_IS_PT.
|
|
*/
|
|
std::vector<std::pair<ssize_t, ssize_t>> m_shapes;
|
|
|
|
std::vector<SHAPE_ARC> m_arcs;
|
|
|
|
/// is the line chain closed?
|
|
bool m_closed;
|
|
|
|
/// Width of the segments (for BBox calculations in RTree)
|
|
/// TODO Adjust usage of SHAPE_LINE_CHAIN to account for where we need a width and where not
|
|
/// Alternatively, we could split the class into a LINE_CHAIN (no width) and SHAPE_LINE_CHAIN
|
|
/// that derives from SHAPE as well that does have a width. Not sure yet on the correct path.
|
|
/// TODO Note that we also have SHAPE_SIMPLE which is a closed, filled SHAPE_LINE_CHAIN.
|
|
int m_width;
|
|
|
|
/// cached bounding box
|
|
mutable BOX2I m_bbox;
|
|
};
|
|
|
|
|
|
#endif // __SHAPE_LINE_CHAIN
|