You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2421 lines
64 KiB

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 CERN
* Copyright (C) 2013-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* 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
*/
#include <limits>
#include <math.h> // for hypot
#include <map>
#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>
#include <geometry/shape_poly_set.h>
#include <math/box2.h> // for BOX2I
#include <math/util.h> // for rescale
#include <math/vector2d.h> // for VECTOR2, VECTOR2I
#include <trigo.h> // for RotatePoint
class SHAPE;
const ssize_t SHAPE_LINE_CHAIN::SHAPE_IS_PT = -1;
const std::pair<ssize_t, ssize_t> SHAPE_LINE_CHAIN::SHAPES_ARE_PT = { SHAPE_IS_PT, SHAPE_IS_PT };
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const std::vector<int>& aV)
: SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ), m_closed( false ), m_width( 0 )
{
for(size_t i = 0; i < aV.size(); i+= 2 )
{
Append( aV[i], aV[i+1] );
}
}
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const std::vector<VECTOR2I>& aV, bool aClosed ) :
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
m_closed( false ),
m_width( 0 )
{
m_points = aV;
m_shapes = std::vector<std::pair<ssize_t, ssize_t>>( aV.size(), SHAPES_ARE_PT );
SetClosed( aClosed );
}
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const SHAPE_ARC& aArc, bool aClosed ) :
SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ),
m_closed( false ),
m_width( aArc.GetWidth() )
{
Append( aArc );
SetClosed( aClosed );
}
SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const ClipperLib::Path& 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();
}
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
{
ClipperLib::Path 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;
}
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*/ );
if( m_shapes.size() <= 1 )
return;
size_t rotations = 0;
size_t numPoints = m_points.size();
while( ArcIndex( 0 ) != SHAPE_IS_PT
&& ArcIndex( 0 ) == ArcIndex( numPoints - 1 )
&& !IsSharedPt( 0 ) )
{
// Rotate right
std::rotate( m_points.rbegin(), m_points.rbegin() + 1, m_points.rend() );
std::rotate( m_shapes.rbegin(), m_shapes.rbegin() + 1, m_shapes.rend() );
// Sanity check - avoid infinite loops (NB: wxCHECK is not thread-safe)
if( rotations++ > m_shapes.size() )
return;
}
}
void SHAPE_LINE_CHAIN::mergeFirstLastPointIfNeeded()
{
if( m_closed )
{
if( m_points.size() > 1 && m_points.front() == m_points.back() )
{
if( m_shapes.back() != SHAPES_ARE_PT )
{
m_shapes.front().second = m_shapes.front().first;
m_shapes.front().first = m_shapes.back().first;
}
m_points.pop_back();
m_shapes.pop_back();
fixIndicesRotation();
}
}
else
{
if( m_points.size() > 1 && IsSharedPt( 0 ) )
{
// Create a duplicate point at the end
m_points.push_back( m_points.front() );
m_shapes.push_back( { m_shapes.front().first, SHAPE_IS_PT });
m_shapes.front().first = m_shapes.front().second;
m_shapes.front().second = SHAPE_IS_PT;
}
}
}
void SHAPE_LINE_CHAIN::convertArc( ssize_t aArcIndex )
{
if( aArcIndex < 0 )
aArcIndex += m_arcs.size();
if( aArcIndex >= static_cast<ssize_t>( m_arcs.size() ) )
return;
// Clear the shapes references
for( auto& sh : m_shapes )
{
alg::run_on_pair( sh,
[&]( ssize_t& aShapeIndex )
{
if( aShapeIndex == aArcIndex )
aShapeIndex = SHAPE_IS_PT;
if( aShapeIndex > aArcIndex )
--aShapeIndex;
} );
if( sh.second != SHAPE_IS_PT && sh.first == SHAPE_IS_PT )
std::swap( sh.first, sh.second );
}
m_arcs.erase( m_arcs.begin() + aArcIndex );
}
void SHAPE_LINE_CHAIN::amendArc( size_t aArcIndex, const VECTOR2I& aNewStart,
const VECTOR2I& aNewEnd )
{
wxCHECK_MSG( aArcIndex < m_arcs.size(), /* void */,
wxT( "Invalid arc index requested." ) );
SHAPE_ARC& theArc = m_arcs[aArcIndex];
// Try to preseve the centre of the original arc
SHAPE_ARC newArc;
newArc.ConstructFromStartEndCenter( aNewStart, aNewEnd, theArc.GetCenter(),
theArc.IsClockwise() );
m_arcs[aArcIndex] = newArc;
}
void SHAPE_LINE_CHAIN::splitArc( ssize_t aPtIndex, bool aCoincident )
{
if( aPtIndex < 0 )
aPtIndex += m_shapes.size();
if( !IsSharedPt( aPtIndex ) && IsArcStart( aPtIndex ) )
return; // Nothing to do
if( !IsPtOnArc( aPtIndex ) )
return; // Nothing to do
wxCHECK_MSG( aPtIndex < static_cast<ssize_t>( m_shapes.size() ), /* void */,
wxT( "Invalid point index requested." ) );
if( IsSharedPt( aPtIndex ) || IsArcEnd( aPtIndex ) )
{
if( aCoincident || aPtIndex == 0 )
return; // nothing to do
ssize_t firstArcIndex = m_shapes[aPtIndex].first;
const VECTOR2I& newStart = m_arcs[firstArcIndex].GetP0(); // don't amend the start
const VECTOR2I& newEnd = m_points[aPtIndex - 1];
amendArc( firstArcIndex, newStart, newEnd );
if( IsSharedPt( aPtIndex ) )
{
m_shapes[aPtIndex].first = m_shapes[aPtIndex].second;
m_shapes[aPtIndex].second = SHAPE_IS_PT;
}
else
{
m_shapes[aPtIndex] = SHAPES_ARE_PT;
}
return;
}
ssize_t currArcIdx = ArcIndex( aPtIndex );
SHAPE_ARC& currentArc = m_arcs[currArcIdx];
SHAPE_ARC newArc1;
SHAPE_ARC newArc2;
VECTOR2I arc1End = ( aCoincident ) ? m_points[aPtIndex] : m_points[aPtIndex - 1];
VECTOR2I arc2Start = m_points[aPtIndex];
newArc1.ConstructFromStartEndCenter( currentArc.GetP0(), arc1End, currentArc.GetCenter(),
currentArc.IsClockwise() );
newArc2.ConstructFromStartEndCenter( arc2Start, currentArc.GetP1(), currentArc.GetCenter(),
currentArc.IsClockwise() );
if( !aCoincident && ArcIndex( aPtIndex - 1 ) != currArcIdx )
{
//Ignore newArc1 as it has zero points
m_arcs[currArcIdx] = newArc2;
}
else
{
m_arcs[currArcIdx] = newArc1;
m_arcs.insert( m_arcs.begin() + currArcIdx + 1, newArc2 );
if( aCoincident )
{
m_shapes[aPtIndex].second = currArcIdx + 1;
aPtIndex++;
}
// Only change the arc indices for the second half of the point range
for( int i = aPtIndex; i < PointCount(); i++ )
{
alg::run_on_pair( m_shapes[i], [&]( ssize_t& aIndex ) {
if( aIndex != SHAPE_IS_PT )
aIndex++;
} );
}
}
}
bool SHAPE_LINE_CHAIN_BASE::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
VECTOR2I* aLocation ) const
{
if( IsClosed() && PointInside( aP, aClearance ) )
{
if( aLocation )
*aLocation = aP;
if( aActual )
*aActual = 0;
return true;
}
SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
SEG::ecoord clearance_sq = SEG::Square( aClearance );
VECTOR2I nearest;
for( size_t i = 0; i < GetSegmentCount(); i++ )
{
const SEG& s = GetSegment( i );
VECTOR2I pn = s.NearestPoint( aP );
SEG::ecoord dist_sq = ( pn - aP ).SquaredEuclideanNorm();
if( dist_sq < closest_dist_sq )
{
nearest = pn;
closest_dist_sq = dist_sq;
if( closest_dist_sq == 0 )
break;
// If we're not looking for aActual then any collision will do
if( closest_dist_sq < clearance_sq && !aActual )
break;
}
}
if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
{
if( aLocation )
*aLocation = nearest;
if( aActual )
*aActual = sqrt( closest_dist_sq );
return true;
}
return false;
}
bool SHAPE_LINE_CHAIN::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
VECTOR2I* aLocation ) const
{
if( IsClosed() && PointInside( aP, aClearance ) )
{
if( aLocation )
*aLocation = aP;
if( aActual )
*aActual = 0;
return true;
}
SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
SEG::ecoord clearance_sq = SEG::Square( aClearance );
VECTOR2I nearest;
// Collide line segments
for( size_t i = 0; i < GetSegmentCount(); i++ )
{
if( IsArcSegment( i ) )
continue;
const SEG& s = GetSegment( i );
VECTOR2I pn = s.NearestPoint( aP );
SEG::ecoord dist_sq = ( pn - aP ).SquaredEuclideanNorm();
if( dist_sq < closest_dist_sq )
{
nearest = pn;
closest_dist_sq = dist_sq;
if( closest_dist_sq == 0 )
break;
// If we're not looking for aActual then any collision will do
if( closest_dist_sq < clearance_sq && !aActual )
break;
}
}
if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
{
if( aLocation )
*aLocation = nearest;
if( aActual )
*aActual = sqrt( closest_dist_sq );
return true;
}
// Collide arc segments
for( size_t i = 0; i < ArcCount(); i++ )
{
const SHAPE_ARC& arc = Arc( i );
// The arcs in the chain should have zero width
wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) );
if( arc.Collide( aP, aClearance, aActual, aLocation ) )
return true;
}
return false;
}
void SHAPE_LINE_CHAIN::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
{
for( VECTOR2I& pt : m_points )
RotatePoint( pt, aCenter, aAngle );
for( SHAPE_ARC& arc : m_arcs )
arc.Rotate( aAngle, aCenter );
}
bool SHAPE_LINE_CHAIN_BASE::Collide( const SEG& aSeg, int aClearance, int* aActual,
VECTOR2I* aLocation ) const
{
if( IsClosed() && PointInside( aSeg.A ) )
{
if( aLocation )
*aLocation = aSeg.A;
if( aActual )
*aActual = 0;
return true;
}
SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
SEG::ecoord clearance_sq = SEG::Square( aClearance );
VECTOR2I nearest;
for( size_t i = 0; i < GetSegmentCount(); i++ )
{
const SEG& s = GetSegment( i );
SEG::ecoord dist_sq = s.SquaredDistance( aSeg );
if( dist_sq < closest_dist_sq )
{
if( aLocation )
nearest = s.NearestPoint( aSeg );
closest_dist_sq = dist_sq;
if( closest_dist_sq == 0)
break;
// If we're not looking for aActual then any collision will do
if( closest_dist_sq < clearance_sq && !aActual )
break;
}
}
if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
{
if( aLocation )
*aLocation = nearest;
if( aActual )
*aActual = sqrt( closest_dist_sq );
return true;
}
return false;
}
bool SHAPE_LINE_CHAIN::Collide( const SEG& aSeg, int aClearance, int* aActual,
VECTOR2I* aLocation ) const
{
if( IsClosed() && PointInside( aSeg.A ) )
{
if( aLocation )
*aLocation = aSeg.A;
if( aActual )
*aActual = 0;
return true;
}
SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
SEG::ecoord clearance_sq = SEG::Square( aClearance );
VECTOR2I nearest;
// Collide line segments
for( size_t i = 0; i < GetSegmentCount(); i++ )
{
if( IsArcSegment( i ) )
continue;
const SEG& s = GetSegment( i );
SEG::ecoord dist_sq = s.SquaredDistance( aSeg );
if( dist_sq < closest_dist_sq )
{
if( aLocation )
nearest = s.NearestPoint( aSeg );
closest_dist_sq = dist_sq;
if( closest_dist_sq == 0 )
break;
// If we're not looking for aActual then any collision will do
if( closest_dist_sq < clearance_sq && !aActual )
break;
}
}
if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
{
if( aLocation )
*aLocation = nearest;
if( aActual )
*aActual = sqrt( closest_dist_sq );
return true;
}
// Collide arc segments
for( size_t i = 0; i < ArcCount(); i++ )
{
const SHAPE_ARC& arc = Arc( i );
// The arcs in the chain should have zero width
wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) );
if( arc.Collide( aSeg, aClearance, aActual, aLocation ) )
return true;
}
return false;
}
const SHAPE_LINE_CHAIN SHAPE_LINE_CHAIN::Reverse() const
{
SHAPE_LINE_CHAIN a( *this );
reverse( a.m_points.begin(), a.m_points.end() );
reverse( a.m_shapes.begin(), a.m_shapes.end() );
reverse( a.m_arcs.begin(), a.m_arcs.end() );
for( auto& sh : a.m_shapes )
{
if( sh != SHAPES_ARE_PT )
{
alg::run_on_pair( sh,
[&]( ssize_t& aShapeIndex )
{
if( aShapeIndex != SHAPE_IS_PT )
aShapeIndex = a.m_arcs.size() - aShapeIndex - 1;
} );
if( sh.second != SHAPE_IS_PT )
{
// If the second element is populated, the first one should be too!
assert( sh.first != SHAPE_IS_PT );
// Switch round first and second in shared points, as part of reversing the chain
std::swap( sh.first, sh.second );
}
}
}
for( SHAPE_ARC& arc : a.m_arcs )
arc.Reverse();
a.m_closed = m_closed;
return a;
}
void SHAPE_LINE_CHAIN::ClearArcs()
{
for( ssize_t arcIndex = m_arcs.size() - 1; arcIndex >= 0; --arcIndex )
convertArc( arcIndex );
}
long long int SHAPE_LINE_CHAIN::Length() const
{
long long int l = 0;
for( int i = 0; i < SegmentCount(); i++ )
{
// Only include segments that aren't part of arc shapes
if( !IsArcSegment(i) )
l += CSegment( i ).Length();
}
for( size_t i = 0; i < ArcCount(); i++ )
l += CArcs()[i].GetLength();
return l;
}
void SHAPE_LINE_CHAIN::Mirror( bool aX, bool aY, const VECTOR2I& aRef )
{
for( auto& pt : m_points )
{
if( aX )
pt.x = -pt.x + 2 * aRef.x;
if( aY )
pt.y = -pt.y + 2 * aRef.y;
}
for( auto& arc : m_arcs )
arc.Mirror( aX, aY, aRef );
}
void SHAPE_LINE_CHAIN::Mirror( const SEG& axis )
{
for( auto& pt : m_points )
pt = axis.ReflectPoint( pt );
for( auto& arc : m_arcs )
arc.Mirror( axis );
}
void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP )
{
Remove( aStartIndex, aEndIndex );
Insert( aStartIndex, aP );
assert( m_shapes.size() == m_points.size() );
}
void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE_CHAIN& aLine )
{
if( aEndIndex < 0 )
aEndIndex += PointCount();
if( aStartIndex < 0 )
aStartIndex += PointCount();
// We only process lines in order in this house
wxASSERT( aStartIndex <= aEndIndex );
wxASSERT( aEndIndex < static_cast<int>( m_points.size() ) );
SHAPE_LINE_CHAIN newLine = aLine;
// Zero points to add?
if( newLine.PointCount() == 0 )
{
Remove( aStartIndex, aEndIndex );
return;
}
// Remove coincident points in the new line
if( newLine.m_points.front() == m_points[aStartIndex] )
{
aStartIndex++;
newLine.Remove( 0 );
// Zero points to add?
if( newLine.PointCount() == 0 )
{
Remove( aStartIndex, aEndIndex );
return;
}
}
if( newLine.m_points.back() == m_points[aEndIndex] && aEndIndex > 0 )
{
aEndIndex--;
newLine.Remove( -1 );
}
Remove( aStartIndex, aEndIndex );
// Zero points to add?
if( newLine.PointCount() == 0 )
return;
// The total new arcs index is added to the new arc indices
size_t prev_arc_count = m_arcs.size();
std::vector<std::pair<ssize_t, ssize_t>> new_shapes = newLine.m_shapes;
for( std::pair<ssize_t, ssize_t>& shape_pair : new_shapes )
{
alg::run_on_pair( shape_pair,
[&]( ssize_t& aShape )
{
if( aShape != SHAPE_IS_PT )
aShape += prev_arc_count;
} );
}
m_shapes.insert( m_shapes.begin() + aStartIndex, new_shapes.begin(), new_shapes.end() );
m_points.insert( m_points.begin() + aStartIndex, newLine.m_points.begin(),
newLine.m_points.end() );
m_arcs.insert( m_arcs.end(), newLine.m_arcs.begin(), newLine.m_arcs.end() );
assert( m_shapes.size() == m_points.size() );
}
void SHAPE_LINE_CHAIN::Remove( int aStartIndex, int aEndIndex )
{
wxCHECK( m_shapes.size() == m_points.size(), /*void*/ );
// Unwrap the chain first (correctly handling removing arc at
// end of chain coincident with start)
bool closedState = IsClosed();
SetClosed( false );
if( aEndIndex < 0 )
aEndIndex += PointCount();
if( aStartIndex < 0 )
aStartIndex += PointCount();
if( aStartIndex >= PointCount() || aEndIndex >= PointCount() || aStartIndex > aEndIndex)
{
SetClosed( closedState );
return;
}
// Split arcs, making arcs coincident
if( !IsArcStart( aStartIndex ) && IsPtOnArc( aStartIndex ) )
splitArc( aStartIndex, false );
if( IsSharedPt( aStartIndex ) ) // Don't delete the shared point
aStartIndex += 1;
if( !IsArcEnd( aEndIndex ) && IsPtOnArc( aEndIndex ) && aEndIndex < PointCount() - 1 )
splitArc( aEndIndex + 1, true );
if( IsSharedPt( aEndIndex ) ) // Don't delete the shared point
aEndIndex -= 1;
if( aStartIndex > aEndIndex )
{
SetClosed( closedState );
return;
}
std::set<size_t> extra_arcs;
auto logArcIdxRemoval = [&]( ssize_t& aShapeIndex )
{
if( aShapeIndex != SHAPE_IS_PT )
extra_arcs.insert( aShapeIndex );
};
// Remove any overlapping arcs in the point range
for( int i = aStartIndex; i <= aEndIndex; i++ )
{
if( IsSharedPt( i ) )
{
if( i == aStartIndex )
{
logArcIdxRemoval( m_shapes[i].second ); // Only remove the arc on the second index
// Ensure that m_shapes has been built correctly.
assert( i < aEndIndex || m_shapes[i + 1].first == m_shapes[i].second );
continue;
}
else if( i == aEndIndex )
{
logArcIdxRemoval( m_shapes[i].first ); // Only remove the arc on the first index
// Ensure that m_shapes has been built correctly.
assert( i > aStartIndex || ( IsSharedPt( i - 1 )
? m_shapes[i - 1].second == m_shapes[i].first
: m_shapes[i - 1].first == m_shapes[i].first ) );
continue;
}
}
else
{
alg::run_on_pair( m_shapes[i], logArcIdxRemoval );
}
}
for( auto arc : extra_arcs )
convertArc( arc );
m_shapes.erase( m_shapes.begin() + aStartIndex, m_shapes.begin() + aEndIndex + 1 );
m_points.erase( m_points.begin() + aStartIndex, m_points.begin() + aEndIndex + 1 );
assert( m_shapes.size() == m_points.size() );
SetClosed( closedState );
}
SEG::ecoord SHAPE_LINE_CHAIN_BASE::SquaredDistance( const VECTOR2I& aP, bool aOutlineOnly ) const
{
ecoord d = VECTOR2I::ECOORD_MAX;
if( IsClosed() && PointInside( aP ) && !aOutlineOnly )
return 0;
for( size_t s = 0; s < GetSegmentCount(); s++ )
d = std::min( d, GetSegment( s ).SquaredDistance( aP ) );
return d;
}
int SHAPE_LINE_CHAIN::Split( const VECTOR2I& aP, bool aExact )
{
int ii = -1;
int min_dist = 2;
int found_index = Find( aP );
if( found_index >= 0 && aExact )
return found_index;
for( int s = 0; s < SegmentCount(); s++ )
{
const SEG seg = CSegment( s );
int dist = seg.Distance( aP );
// make sure we are not producing a 'slightly concave' primitive. This might happen
// if aP lies very close to one of already existing points.
if( dist < min_dist && seg.A != aP && seg.B != aP )
{
min_dist = dist;
if( found_index < 0 )
ii = s;
else if( s < found_index )
ii = s;
}
}
if( ii < 0 )
ii = found_index;
if( ii >= 0 )
{
// Don't create duplicate points
if( GetPoint( ii ) == aP )
return ii;
size_t newIndex = static_cast<size_t>( ii ) + 1;
if( IsArcSegment( ii ) )
{
m_points.insert( m_points.begin() + newIndex, aP );
m_shapes.insert( m_shapes.begin() + newIndex, { ArcIndex( ii ), SHAPE_IS_PT } );
splitArc( newIndex, true ); // Make the inserted point a shared point
}
else
{
Insert( newIndex, aP );
}
return newIndex;
}
return -1;
}
int SHAPE_LINE_CHAIN::Find( const VECTOR2I& aP, int aThreshold ) const
{
for( int s = 0; s < PointCount(); s++ )
{
if( aThreshold == 0 )
{
if( CPoint( s ) == aP )
return s;
}
else
{
if( (CPoint( s ) - aP).EuclideanNorm() <= aThreshold )
return s;
}
}
return -1;
}
int SHAPE_LINE_CHAIN::FindSegment( const VECTOR2I& aP, int aThreshold ) const
{
for( int s = 0; s < SegmentCount(); s++ )
{
if( CSegment( s ).Distance( aP ) <= aThreshold )
return s;
}
return -1;
}
int SHAPE_LINE_CHAIN::ShapeCount() const
{
wxCHECK2_MSG( m_points.size() == m_shapes.size(), return 0, "Invalid chain!" );
if( m_points.size() < 2 )
return 0;
int numShapes = 1;
for( int i = NextShape( 0 ); i != -1; i = NextShape( i ) )
numShapes++;
return numShapes;
}
SEG SHAPE_LINE_CHAIN::Segment( int aIndex ) const
{
if( aIndex < 0 )
aIndex += SegmentCount();
wxCHECK( aIndex < SegmentCount() && aIndex >= 0,
m_points.size() > 0 ? SEG( m_points.back(), m_points.back() ) : SEG( 0, 0, 0, 0 ) );
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 );
}
int SHAPE_LINE_CHAIN::NextShape( int aPointIndex ) const
{
if( aPointIndex < 0 )
aPointIndex += PointCount();
if( aPointIndex < 0 )
return -1;
int lastIndex = PointCount() - 1;
// Last point?
if( aPointIndex >= lastIndex )
return -1; // we don't want to wrap around
if( m_shapes[aPointIndex] == SHAPES_ARE_PT )
{
if( aPointIndex == lastIndex - 1 )
{
if( m_closed )
return lastIndex;
else
return -1;
}
else
{
return aPointIndex + 1;
}
}
int arcStart = aPointIndex;
// The second element should only get populated when the point is shared between two shapes.
// If not a shared point, then the index should always go on the first element.
wxCHECK2_MSG( m_shapes[aPointIndex].first != SHAPE_IS_PT, return -1, "malformed chain!" );
ssize_t currentArcIdx = ArcIndex( aPointIndex );
// Now skip the rest of the arc
while( aPointIndex < lastIndex && ArcIndex( aPointIndex ) == currentArcIdx )
aPointIndex += 1;
bool indexStillOnArc = alg::pair_contains( m_shapes[aPointIndex], currentArcIdx );
// We want the last vertex of the arc if the initial point was the start of one
// Well-formed arcs should generate more than one point to travel above
if( aPointIndex - arcStart > 1 && !indexStillOnArc )
aPointIndex -= 1;
if( aPointIndex == lastIndex )
{
if( !m_closed || IsArcSegment( aPointIndex ) )
return -1; //no shape
else
return lastIndex; // Segment between last point and the start of the chain
}
return aPointIndex;
}
void SHAPE_LINE_CHAIN::SetPoint( int aIndex, const VECTOR2I& aPos )
{
if( aIndex < 0 )
aIndex += PointCount();
else if( aIndex >= PointCount() )
aIndex -= PointCount();
m_points[aIndex] = aPos;
alg::run_on_pair( m_shapes[aIndex],
[&]( ssize_t& aIdx )
{
if( aIdx != SHAPE_IS_PT )
convertArc( aIdx );
} );
}
void SHAPE_LINE_CHAIN::RemoveShape( int aPointIndex )
{
if( aPointIndex < 0 )
aPointIndex += PointCount();
if( aPointIndex >= PointCount() || aPointIndex < 0 )
return; // Invalid index, fail gracefully
if( m_shapes[aPointIndex] == SHAPES_ARE_PT )
{
Remove( aPointIndex );
return;
}
int start = aPointIndex;
int end = aPointIndex;
int arcIdx = ArcIndex( aPointIndex );
if( !IsArcStart( start ) )
{
// aPointIndex is not a shared point, so iterate backwards to find the start of the arc
while( start > 0 && ArcIndex( static_cast<ssize_t>( start ) - 1 ) == arcIdx )
start--;
}
if( !IsArcEnd( end ) || start == end )
end = NextShape( end ); // can be -1 to indicate end of chain
Remove( start, end );
}
const SHAPE_LINE_CHAIN SHAPE_LINE_CHAIN::Slice( int aStartIndex, int aEndIndex ) const
{
SHAPE_LINE_CHAIN rv;
if( aEndIndex < 0 )
aEndIndex += PointCount();
if( aStartIndex < 0 )
aStartIndex += PointCount();
// Bad programmer checks
wxCHECK( aStartIndex >= 0, SHAPE_LINE_CHAIN() );
wxCHECK( aEndIndex >= 0, SHAPE_LINE_CHAIN() );
wxCHECK( aStartIndex < PointCount(), SHAPE_LINE_CHAIN() );
wxCHECK( aEndIndex < PointCount(), SHAPE_LINE_CHAIN() );
wxCHECK( aEndIndex >= aStartIndex, SHAPE_LINE_CHAIN() );
int numPoints = static_cast<int>( m_points.size() );
if( IsArcSegment( aStartIndex ) && !IsArcStart( aStartIndex ) )
{
// Cutting in middle of an arc, lets split it
ssize_t arcToSplitIndex = ArcIndex( aStartIndex );
const SHAPE_ARC& arcToSplit = Arc( arcToSplitIndex );
// Copy the points as arc points
for( size_t i = aStartIndex; arcToSplitIndex == ArcIndex( i ); i++ )
{
rv.m_points.push_back( m_points[i] );
rv.m_shapes.push_back( { rv.m_arcs.size(), SHAPE_IS_PT } );
rv.m_bbox.Merge( m_points[i] );
}
// Create a new arc from the existing one, with different start point.
SHAPE_ARC newArc;
VECTOR2I newArcStart = m_points[aStartIndex];
newArc.ConstructFromStartEndCenter( newArcStart, arcToSplit.GetP1(),
arcToSplit.GetCenter(),
arcToSplit.IsClockwise() );
rv.m_arcs.push_back( newArc );
aStartIndex += rv.PointCount();
}
for( int i = aStartIndex; i <= aEndIndex && i < numPoints; i = NextShape( i ) )
{
if( i == -1 )
return rv; // NextShape reached the end
int nextShape = NextShape( i );
bool isLastShape = nextShape < 0;
if( IsArcStart( i ) )
{
if( ( isLastShape && aEndIndex != ( numPoints - 1 ) )
|| ( nextShape > aEndIndex ) )
{
if( i == aEndIndex )
{
// Single point on an arc, just append the single point
rv.Append( m_points[i] );
return rv;
}
// Cutting in middle of an arc, lets split it
ssize_t arcIndex = ArcIndex( i );
const SHAPE_ARC& currentArc = Arc( arcIndex );
// Copy the points as arc points
for( ; i <= aEndIndex && i < numPoints; i++ )
{
if( arcIndex != ArcIndex( i ) )
break;
rv.m_points.push_back( m_points[i] );
rv.m_shapes.push_back( { rv.m_arcs.size(), SHAPE_IS_PT } );
rv.m_bbox.Merge( m_points[i] );
}
// Create a new arc from the existing one, with different end point.
SHAPE_ARC newArc;
VECTOR2I newArcEnd = m_points[aEndIndex];
newArc.ConstructFromStartEndCenter( currentArc.GetP0(), newArcEnd,
currentArc.GetCenter(),
currentArc.IsClockwise() );
rv.m_arcs.push_back( newArc );
return rv;
}
else
{
// append the whole arc
const SHAPE_ARC& currentArc = Arc( ArcIndex( i ) );
rv.Append( currentArc );
}
if( isLastShape )
return rv;
}
else
{
wxASSERT_MSG( !IsArcSegment( i ),
wxT( "Still on an arc segment, we missed something..." ) );
if( i == aStartIndex )
rv.Append( m_points[i] );
bool nextPointIsArc = isLastShape ? false : IsArcSegment( nextShape );
if( !nextPointIsArc && i < SegmentCount() && i < aEndIndex )
rv.Append( GetSegment( i ).B );
}
}
return rv;
}
void SHAPE_LINE_CHAIN::Append( const SHAPE_LINE_CHAIN& aOtherLine )
{
assert( m_shapes.size() == m_points.size() );
if( aOtherLine.PointCount() == 0 )
{
return;
}
size_t num_arcs = m_arcs.size();
m_arcs.insert( m_arcs.end(), aOtherLine.m_arcs.begin(), aOtherLine.m_arcs.end() );
auto fixShapeIndices =
[&]( const std::pair<ssize_t, ssize_t>& aShapeIndices ) -> std::pair<ssize_t, ssize_t>
{
std::pair<ssize_t, ssize_t> retval = aShapeIndices;
alg::run_on_pair( retval, [&]( ssize_t& aIndex )
{
if( aIndex != SHAPE_IS_PT )
aIndex = aIndex + num_arcs;
} );
return retval;
};
if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) )
{
const VECTOR2I p = aOtherLine.CPoint( 0 );
m_points.push_back( p );
m_shapes.push_back( fixShapeIndices( aOtherLine.CShapes()[0] ) );
m_bbox.Merge( p );
}
else if( aOtherLine.IsArcSegment( 0 ) )
{
// Associate the new arc shape with the last point of this chain
if( m_shapes.back() == SHAPES_ARE_PT )
m_shapes.back().first = aOtherLine.CShapes()[0].first + num_arcs;
else
m_shapes.back().second = aOtherLine.CShapes()[0].first + num_arcs;
}
for( int i = 1; i < aOtherLine.PointCount(); i++ )
{
const VECTOR2I p = aOtherLine.CPoint( i );
m_points.push_back( p );
ssize_t arcIndex = aOtherLine.ArcIndex( i );
if( arcIndex != ssize_t( SHAPE_IS_PT ) )
{
m_shapes.push_back( fixShapeIndices( aOtherLine.m_shapes[i] ) );
}
else
m_shapes.push_back( SHAPES_ARE_PT );
m_bbox.Merge( p );
}
mergeFirstLastPointIfNeeded();
assert( m_shapes.size() == m_points.size() );
}
void SHAPE_LINE_CHAIN::Append( const SHAPE_ARC& aArc )
{
Append( aArc, SHAPE_ARC::DefaultAccuracyForPCB() );
}
void SHAPE_LINE_CHAIN::Append( const SHAPE_ARC& aArc, double aAccuracy )
{
SHAPE_LINE_CHAIN chain = aArc.ConvertToPolyline( aAccuracy );
if( chain.PointCount() > 2 )
{
chain.m_arcs.push_back( aArc );
chain.m_arcs.back().SetWidth( 0 );
for( auto& sh : chain.m_shapes )
sh.first = 0;
}
Append( chain );
assert( m_shapes.size() == m_points.size() );
}
void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const VECTOR2I& aP )
{
if( aVertex == m_points.size() )
{
Append( aP );
return;
}
wxCHECK( aVertex < m_points.size(), /* void */ );
if( aVertex > 0 && IsPtOnArc( aVertex ) )
splitArc( aVertex );
//@todo need to check we aren't creating duplicate points
m_points.insert( m_points.begin() + aVertex, aP );
m_shapes.insert( m_shapes.begin() + aVertex, SHAPES_ARE_PT );
assert( m_shapes.size() == m_points.size() );
}
void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const SHAPE_ARC& aArc )
{
wxCHECK( aVertex < m_points.size(), /* void */ );
if( aVertex > 0 && IsPtOnArc( aVertex ) )
splitArc( aVertex );
/// Step 1: Find the position for the new arc in the existing arc vector
ssize_t arc_pos = m_arcs.size();
for( auto arc_it = m_shapes.rbegin() ;
arc_it != m_shapes.rend() + aVertex;
arc_it++ )
{
if( *arc_it != SHAPES_ARE_PT )
{
arc_pos = std::max( ( *arc_it ).first, ( *arc_it ).second );
arc_pos++;
}
}
//Increment all arc indices before inserting the new arc
for( auto& sh : m_shapes )
{
alg::run_on_pair( sh,
[&]( ssize_t& aIndex )
{
if( aIndex >= arc_pos )
aIndex++;
} );
}
SHAPE_ARC arcCopy( aArc );
arcCopy.SetWidth( 0 );
m_arcs.insert( m_arcs.begin() + arc_pos, arcCopy );
/// Step 2: Add the arc polyline points to the chain
//@todo need to check we aren't creating duplicate points at start or end
auto& chain = aArc.ConvertToPolyline();
m_points.insert( m_points.begin() + aVertex, chain.CPoints().begin(), chain.CPoints().end() );
/// Step 3: Add the vector of indices to the shape vector
//@todo need to check we aren't creating duplicate points at start or end
std::vector<std::pair<ssize_t, ssize_t>> new_points( chain.PointCount(),
{ arc_pos, SHAPE_IS_PT } );
m_shapes.insert( m_shapes.begin() + aVertex, new_points.begin(), new_points.end() );
assert( m_shapes.size() == m_points.size() );
}
struct compareOriginDistance
{
compareOriginDistance( const VECTOR2I& aOrigin ) :
m_origin( aOrigin ) {};
bool operator()( const SHAPE_LINE_CHAIN::INTERSECTION& aA,
const SHAPE_LINE_CHAIN::INTERSECTION& aB ) const
{
return ( m_origin - aA.p ).EuclideanNorm() < ( m_origin - aB.p ).EuclideanNorm();
}
VECTOR2I m_origin;
};
int SHAPE_LINE_CHAIN::Intersect( const SEG& aSeg, INTERSECTIONS& aIp ) const
{
for( int s = 0; s < SegmentCount(); s++ )
{
OPT_VECTOR2I p = CSegment( s ).Intersect( aSeg );
if( p )
{
INTERSECTION is;
is.valid = true;
is.index_our = s;
is.index_their = -1;
is.is_corner_our = is.is_corner_their = false;
is.p = *p;
aIp.push_back( is );
}
}
compareOriginDistance comp( aSeg.A );
sort( aIp.begin(), aIp.end(), comp );
return aIp.size();
}
static inline void addIntersection( SHAPE_LINE_CHAIN::INTERSECTIONS& aIps, int aPc,
const SHAPE_LINE_CHAIN::INTERSECTION& aP )
{
if( aIps.size() == 0 )
{
aIps.push_back( aP );
return;
}
aIps.push_back( aP );
}
int SHAPE_LINE_CHAIN::Intersect( const SHAPE_LINE_CHAIN& aChain, INTERSECTIONS& aIp,
bool aExcludeColinearAndTouching, BOX2I* aChainBBox ) const
{
BOX2I bb_other = aChainBBox ? *aChainBBox : aChain.BBox();
for( int s1 = 0; s1 < SegmentCount(); s1++ )
{
const SEG& a = CSegment( s1 );
const BOX2I bb_cur( a.A, a.B - a.A );
if( !bb_other.Intersects( bb_cur ) )
continue;
for( int s2 = 0; s2 < aChain.SegmentCount(); s2++ )
{
const SEG& b = aChain.CSegment( s2 );
INTERSECTION is;
is.index_our = s1;
is.index_their = s2;
is.is_corner_our = false;
is.is_corner_their = false;
is.valid = true;
OPT_VECTOR2I p = a.Intersect( b );
bool coll = a.Collinear( b );
if( coll && ! aExcludeColinearAndTouching )
{
if( a.Contains( b.A ) )
{
is.p = b.A;
is.is_corner_their = true;
addIntersection(aIp, PointCount(), is);
}
if( a.Contains( b.B ) )
{
is.p = b.B;
is.index_their++;
is.is_corner_their = true;
addIntersection( aIp, PointCount(), is );
}
if( b.Contains( a.A ) )
{
is.p = a.A;
is.is_corner_our = true;
addIntersection( aIp, PointCount(), is );
}
if( b.Contains( a.B ) )
{
is.p = a.B;
is.index_our++;
is.is_corner_our = true;
addIntersection( aIp, PointCount(), is );
}
}
else if( p )
{
is.p = *p;
is.is_corner_our = false;
is.is_corner_their = false;
if( p == a.A )
{
is.is_corner_our = true;
}
if( p == a.B )
{
is.is_corner_our = true;
is.index_our++;
}
if( p == b.A )
{
is.is_corner_their = true;
}
if( p == b.B )
{
is.is_corner_their = true;
is.index_their++;
}
addIntersection( aIp, PointCount(), is );
}
}
}
return aIp.size();
}
int SHAPE_LINE_CHAIN::PathLength( const VECTOR2I& aP, int aIndex ) const
{
int sum = 0;
for( int i = 0; i < SegmentCount(); i++ )
{
const SEG seg = CSegment( i );
bool indexMatch = true;
if( aIndex >= 0 )
{
if( aIndex == SegmentCount() )
{
indexMatch = ( i == SegmentCount() - 1 );
}
else
{
indexMatch = ( i == aIndex );
}
}
if( indexMatch )
{
sum += ( aP - seg.A ).EuclideanNorm();
return sum;
}
else
sum += seg.Length();
}
return -1;
}
bool SHAPE_LINE_CHAIN_BASE::PointInside( const VECTOR2I& aPt, int aAccuracy,
bool aUseBBoxCache ) const
{
/*
* Don't check the bounding box unless it's cached. Building it is about the same speed as
* the rigorous test below and so just slows things down by doing potentially two tests.
*/
if( aUseBBoxCache && GetCachedBBox() && !GetCachedBBox()->Contains( aPt ) )
return false;
if( !IsClosed() || GetPointCount() < 3 )
return false;
bool inside = false;
/*
* To check for interior points, we draw a line in the positive x direction from
* the point. If it intersects an even number of segments, the point is outside the
* line chain (it had to first enter and then exit). Otherwise, it is inside the chain.
*
* Note: slope might be denormal here in the case of a horizontal line but we require our
* y to move from above to below the point (or vice versa)
*
* Note: we open-code CPoint() here so that we don't end up calculating the size of the
* vector number-of-points times. This has a non-trivial impact on zone fill times.
*/
int pointCount = GetPointCount();
for( int i = 0; i < pointCount; )
{
const auto p1 = GetPoint( i++ );
const auto p2 = GetPoint( i == pointCount ? 0 : i );
const auto diff = p2 - p1;
if( diff.y != 0 )
{
const int d = rescale( diff.x, ( aPt.y - p1.y ), diff.y );
if( ( ( p1.y > aPt.y ) != ( p2.y > aPt.y ) ) && ( aPt.x - p1.x < d ) )
inside = !inside;
}
}
// If accuracy is <= 1 (nm) then we skip the accuracy test for performance. Otherwise
// we use "OnEdge(accuracy)" as a proxy for "Inside(accuracy)".
if( aAccuracy <= 1 )
return inside;
else
return inside || PointOnEdge( aPt, aAccuracy );
}
bool SHAPE_LINE_CHAIN_BASE::PointOnEdge( const VECTOR2I& aPt, int aAccuracy ) const
{
return EdgeContainingPoint( aPt, aAccuracy ) >= 0;
}
int SHAPE_LINE_CHAIN_BASE::EdgeContainingPoint( const VECTOR2I& aPt, int aAccuracy ) const
{
if( !GetPointCount() )
{
return -1;
}
else if( GetPointCount() == 1 )
{
VECTOR2I dist = GetPoint(0) - aPt;
return ( hypot( dist.x, dist.y ) <= aAccuracy + 1 ) ? 0 : -1;
}
for( size_t i = 0; i < GetSegmentCount(); i++ )
{
const SEG s = GetSegment( i );
if( s.A == aPt || s.B == aPt )
return i;
if( s.Distance( aPt ) <= aAccuracy + 1 )
return i;
}
return -1;
}
bool SHAPE_LINE_CHAIN::CheckClearance( const VECTOR2I& aP, const int aDist ) const
{
if( !PointCount() )
return false;
else if( PointCount() == 1 )
return m_points[0] == aP;
for( int i = 0; i < SegmentCount(); i++ )
{
const SEG s = CSegment( i );
if( s.A == aP || s.B == aP )
return true;
if( s.Distance( aP ) <= aDist )
return true;
}
return false;
}
const std::optional<SHAPE_LINE_CHAIN::INTERSECTION> SHAPE_LINE_CHAIN::SelfIntersecting() const
{
for( int s1 = 0; s1 < SegmentCount(); s1++ )
{
for( int s2 = s1 + 1; s2 < SegmentCount(); s2++ )
{
const VECTOR2I s2a = CSegment( s2 ).A, s2b = CSegment( s2 ).B;
if( s1 + 1 != s2 && CSegment( s1 ).Contains( s2a ) )
{
INTERSECTION is;
is.index_our = s1;
is.index_their = s2;
is.p = s2a;
return is;
}
else if( CSegment( s1 ).Contains( s2b ) &&
// for closed polylines, the ending point of the
// last segment == starting point of the first segment
// this is a normal case, not self intersecting case
!( IsClosed() && s1 == 0 && s2 == SegmentCount()-1 ) )
{
INTERSECTION is;
is.index_our = s1;
is.index_their = s2;
is.p = s2b;
return is;
}
else
{
OPT_VECTOR2I p = CSegment( s1 ).Intersect( CSegment( s2 ), true );
if( p )
{
INTERSECTION is;
is.index_our = s1;
is.index_their = s2;
is.p = *p;
return is;
}
}
}
}
return std::optional<SHAPE_LINE_CHAIN::INTERSECTION>();
}
SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify( bool aRemoveColinear )
{
std::vector<VECTOR2I> pts_unique;
std::vector<std::pair<ssize_t, ssize_t>> shapes_unique;
// Always try to keep at least 2 points otherwise, we're not really a line
if( PointCount() < 3 )
{
return *this;
}
else if( PointCount() == 3 )
{
if( m_points[0] == m_points[1] )
Remove( 1 );
return *this;
}
int i = 0;
int np = PointCount();
// stage 1: eliminate duplicate vertices
while( i < np )
{
int j = i + 1;
// We can eliminate duplicate vertices as long as they are part of the same shape, OR if
// one of them is part of a shape and one is not.
while( j < np && m_points[i] == m_points[j] &&
( m_shapes[i] == m_shapes[j] ||
m_shapes[i] == SHAPES_ARE_PT ||
m_shapes[j] == SHAPES_ARE_PT ) )
{
j++;
}
std::pair<ssize_t,ssize_t> shapeToKeep = m_shapes[i];
if( shapeToKeep == SHAPES_ARE_PT )
shapeToKeep = m_shapes[j - 1];
assert( shapeToKeep.first < static_cast<int>( m_arcs.size() ) );
assert( shapeToKeep.second < static_cast<int>( m_arcs.size() ) );
pts_unique.push_back( CPoint( i ) );
shapes_unique.push_back( shapeToKeep );
i = j;
}
m_points.clear();
m_shapes.clear();
np = pts_unique.size();
i = 0;
// stage 2: eliminate colinear segments
while( i < np - 2 )
{
const VECTOR2I p0 = pts_unique[i];
int n = i;
if( aRemoveColinear && shapes_unique[i] == SHAPES_ARE_PT
&& shapes_unique[i + 1] == SHAPES_ARE_PT )
{
while( n < np - 2
&& ( SEG( p0, pts_unique[n + 2] ).LineDistance( pts_unique[n + 1] ) <= 1
|| SEG( p0, pts_unique[n + 2] ).Collinear( SEG( p0, pts_unique[n + 1] ) ) ) )
n++;
}
m_points.push_back( p0 );
m_shapes.push_back( shapes_unique[i] );
if( n > i )
i = n;
if( n == np - 2 )
{
m_points.push_back( pts_unique[np - 1] );
m_shapes.push_back( shapes_unique[np - 1] );
return *this;
}
i++;
}
if( np > 1 )
{
m_points.push_back( pts_unique[np - 2] );
m_shapes.push_back( shapes_unique[np - 2] );
}
m_points.push_back( pts_unique[np - 1] );
m_shapes.push_back( shapes_unique[np - 1] );
assert( m_points.size() == m_shapes.size() );
return *this;
}
const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const VECTOR2I& aP,
bool aAllowInternalShapePoints ) const
{
if( PointCount() == 0 )
{
// The only right answer here is "don't crash".
return { 0, 0 };
}
int min_d = std::numeric_limits<int>::max();
int nearest = 0;
for( int i = 0; i < SegmentCount(); i++ )
{
int d = CSegment( i ).Distance( aP );
if( d < min_d )
{
min_d = d;
nearest = i;
}
}
if( !aAllowInternalShapePoints )
{
//Snap to arc end points if the closest found segment is part of an arc segment
if( nearest > 0 && nearest < PointCount() && IsArcSegment( nearest ) )
{
VECTOR2I ptToSegStart = CSegment( nearest ).A - aP;
VECTOR2I ptToSegEnd = CSegment( nearest ).B - aP;
if( ptToSegStart.EuclideanNorm() > ptToSegEnd.EuclideanNorm() )
nearest++;
// Is this the start or end of an arc? If so, return it directly
if( IsArcStart( nearest ) || IsArcEnd( nearest ) )
{
return m_points[nearest];
}
else
{
const SHAPE_ARC& nearestArc = Arc( ArcIndex( nearest ) );
VECTOR2I ptToArcStart = nearestArc.GetP0() - aP;
VECTOR2I ptToArcEnd = nearestArc.GetP1() - aP;
if( ptToArcStart.EuclideanNorm() > ptToArcEnd.EuclideanNorm() )
return nearestArc.GetP1();
else
return nearestArc.GetP0();
}
}
}
return CSegment( nearest ).NearestPoint( aP );
}
const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const SEG& aSeg, int& dist ) const
{
if( PointCount() == 0 )
{
// The only right answer here is "don't crash".
return { 0, 0 };
}
int nearest = 0;
dist = std::numeric_limits<int>::max();
for( int i = 0; i < PointCount(); i++ )
{
int d = aSeg.LineDistance( CPoint( i ) );
if( d < dist )
{
dist = d;
nearest = i;
}
}
return CPoint( nearest );
}
int SHAPE_LINE_CHAIN::NearestSegment( const VECTOR2I& aP ) const
{
int min_d = std::numeric_limits<int>::max();
int nearest = 0;
for( int i = 0; i < SegmentCount(); i++ )
{
int d = CSegment( i ).Distance( aP );
if( d < min_d )
{
min_d = d;
nearest = i;
}
}
return nearest;
}
const std::string SHAPE_LINE_CHAIN::Format( bool aCplusPlus ) const
{
std::stringstream ss;
ss << "SHAPE_LINE_CHAIN( { ";
for( int i = 0; i < PointCount(); i++ )
{
ss << "VECTOR2I( " << m_points[i].x << ", " << m_points[i].y << ")";
if( i != PointCount() -1 )
ss << ", ";
}
ss << "}, " << ( m_closed ? "true" : "false" );
ss << " );";
return ss.str();
/* fixme: arcs
for( size_t i = 0; i < m_arcs.size(); i++ )
ss << m_arcs[i].GetCenter().x << " " << m_arcs[i].GetCenter().y << " "
<< m_arcs[i].GetP0().x << " " << m_arcs[i].GetP0().y << " "
<< m_arcs[i].GetCentralAngle().AsDegrees();
return ss.str();*/
}
bool SHAPE_LINE_CHAIN::CompareGeometry( const SHAPE_LINE_CHAIN & aOther ) const
{
SHAPE_LINE_CHAIN a(*this), b( aOther );
a.Simplify();
b.Simplify();
if( a.m_points.size() != b.m_points.size() )
return false;
for( int i = 0; i < a.PointCount(); i++ )
{
if( a.CPoint( i ) != b.CPoint( i ) )
return false;
}
return true;
}
bool SHAPE_LINE_CHAIN::Intersects( const SHAPE_LINE_CHAIN& aChain ) const
{
INTERSECTIONS dummy;
return Intersect( aChain, dummy ) != 0;
}
SHAPE* SHAPE_LINE_CHAIN::Clone() const
{
return new SHAPE_LINE_CHAIN( *this );
}
bool SHAPE_LINE_CHAIN::Parse( std::stringstream& aStream )
{
size_t n_pts;
size_t n_arcs;
m_points.clear();
aStream >> n_pts;
// Rough sanity check, just make sure the loop bounds aren't absolutely outlandish
if( n_pts > aStream.str().size() )
return false;
aStream >> m_closed;
aStream >> n_arcs;
if( n_arcs > aStream.str().size() )
return false;
for( size_t i = 0; i < n_pts; i++ )
{
int x, y;
ssize_t ind;
aStream >> x;
aStream >> y;
m_points.emplace_back( x, y );
aStream >> ind;
m_shapes.emplace_back( std::make_pair( ind, SHAPE_IS_PT ) );
}
for( size_t i = 0; i < n_arcs; i++ )
{
VECTOR2I p0, pc;
double angle;
aStream >> pc.x;
aStream >> pc.y;
aStream >> p0.x;
aStream >> p0.y;
aStream >> angle;
m_arcs.emplace_back( pc, p0, EDA_ANGLE( angle, DEGREES_T ) );
}
return true;
}
const VECTOR2I SHAPE_LINE_CHAIN::PointAlong( int aPathLength ) const
{
int total = 0;
if( aPathLength == 0 )
return CPoint( 0 );
for( int i = 0; i < SegmentCount(); i++ )
{
const SEG& s = CSegment( i );
int l = s.Length();
if( total + l >= aPathLength )
{
VECTOR2I d( s.B - s.A );
return s.A + d.Resize( aPathLength - total );
}
total += l;
}
return CPoint( -1 );
}
double SHAPE_LINE_CHAIN::Area( bool aAbsolute ) const
{
// see https://www.mathopenref.com/coordpolygonarea2.html
if( !m_closed )
return 0.0;
double area = 0.0;
int size = m_points.size();
for( int i = 0, j = size - 1; i < size; ++i )
{
area += ( (double) m_points[j].x + m_points[i].x ) *
( (double) m_points[j].y - m_points[i].y );
j = i;
}
if( aAbsolute )
return std::fabs( area * 0.5 ); // The result would be negative if points are anti-clockwise
else
return -area * 0.5; // The result would be negative if points are anti-clockwise
}
void SHAPE_LINE_CHAIN::Split( const VECTOR2I& aStart, const VECTOR2I& aEnd, SHAPE_LINE_CHAIN& aPre,
SHAPE_LINE_CHAIN& aMid, SHAPE_LINE_CHAIN& aPost ) const
{
VECTOR2I cp( aEnd );
VECTOR2I n = NearestPoint( cp, false );
VECTOR2I m = NearestPoint( aStart, false );
SHAPE_LINE_CHAIN l( *this );
l.Split( n, true );
l.Split( m, true );
int i_start = l.Find( m );
int i_end = l.Find( n );
if( i_start > i_end )
{
l = l.Reverse();
i_start = l.Find( m );
i_end = l.Find( n );
}
aPre = l.Slice( 0, i_start );
aPost = l.Slice( i_end, -1 );
aMid = l.Slice( i_start, i_end );
}
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, 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
{
aBuffer.AddOutline( *this );
}
SHAPE_LINE_CHAIN::POINT_INSIDE_TRACKER::POINT_INSIDE_TRACKER( const VECTOR2I& aPoint ) :
m_point( aPoint ),
m_finished( false ),
m_state( 0 ),
m_count( 0 )
{
}
bool SHAPE_LINE_CHAIN::POINT_INSIDE_TRACKER::processVertex(
const VECTOR2I& ip, const VECTOR2I& ipNext )
{
if( ipNext.y == m_point.y )
{
if( ( ipNext.x == m_point.x )
|| ( ip.y == m_point.y && ( ( ipNext.x > m_point.x ) == ( ip.x < m_point.x ) ) ) )
{
m_finished = true;
m_state = -1;
return false;
}
}
if( ( ip.y < m_point.y ) != ( ipNext.y < m_point.y ) )
{
if( ip.x >= m_point.x )
{
if( ipNext.x > m_point.x )
{
m_state = 1 - m_state;
}
else
{
double d = (double) ( ip.x - m_point.x ) * ( ipNext.y - m_point.y )
- (double) ( ipNext.x - m_point.x ) * ( ip.y - m_point.y );
if( !d )
{
m_finished = true;
m_state = -1;
return false;
}
if( ( d > 0 ) == ( ipNext.y > ip.y ) )
m_state = 1 - m_state;
}
}
else
{
if( ipNext.x > m_point.x )
{
double d = (double) ( ip.x - m_point.x ) * ( ipNext.y - m_point.y )
- (double) ( ipNext.x - m_point.x ) * ( ip.y - m_point.y );
if( !d )
{
m_finished = true;
m_state = -1;
return false;
}
if( ( d > 0 ) == ( ipNext.y > ip.y ) )
m_state = 1 - m_state;
}
}
}
return true;
}
void SHAPE_LINE_CHAIN::POINT_INSIDE_TRACKER::AddPolyline( const SHAPE_LINE_CHAIN& aPolyline )
{
if( !m_count )
{
m_lastPoint = aPolyline.CPoint( 0 );
m_firstPoint = aPolyline.CPoint( 0 );
}
m_count += aPolyline.PointCount();
for( int i = 1; i < aPolyline.PointCount(); i++ )
{
auto p = aPolyline.CPoint( i );
if( !processVertex( m_lastPoint, p ) )
return;
m_lastPoint = p;
}
}
bool SHAPE_LINE_CHAIN::POINT_INSIDE_TRACKER::IsInside()
{
processVertex( m_lastPoint, m_firstPoint );
return m_state > 0;
}