Implement true arc collisions for arcs inside a SHAPE_LINE_CHAIN

This commit is contained in:
Roberto Fernandez Bautista 2021-11-05 18:06:22 +00:00
parent ad3b4f25c2
commit 0c3da0f072
3 changed files with 267 additions and 11 deletions

View File

@ -66,7 +66,11 @@ struct CLIPPER_Z_VALUE
/**
* Represent a polyline (an zero-thickness chain of connected line segments).
* 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.
@ -187,7 +191,8 @@ public:
{
m_points = aArc.ConvertToPolyline().CPoints();
m_arcs.emplace_back( aArc );
m_shapes = std::vector<std::pair<ssize_t, ssize_t>>( m_points.size(), SHAPES_ARE_PT );
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,
@ -197,6 +202,36 @@ public:
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;

View File

@ -308,6 +308,15 @@ static inline bool Collide( const SHAPE_LINE_CHAIN_BASE& aA, const SHAPE_LINE_CH
int collision_dist = 0;
VECTOR2I pn;
if( aB.Type() == SH_LINE_CHAIN )
{
const SHAPE_LINE_CHAIN* aB_line_chain = static_cast<const SHAPE_LINE_CHAIN*>( &aB );
// ignore arcs - we will collide these separately
if( aB_line_chain->IsArcSegment( i ) )
continue;
}
if( aA.Collide( aB.GetSegment( i ), aClearance,
aActual || aLocation ? &collision_dist : nullptr,
aLocation ? &pn : nullptr ) )
@ -326,6 +335,22 @@ static inline bool Collide( const SHAPE_LINE_CHAIN_BASE& aA, const SHAPE_LINE_CH
break;
}
}
if( aB.Type() == SH_LINE_CHAIN )
{
const SHAPE_LINE_CHAIN* aB_line_chain = static_cast<const SHAPE_LINE_CHAIN*>( &aB );
for( size_t i = 0; i < aB_line_chain->ArcCount(); i++ )
{
const SHAPE_ARC& arc = aB_line_chain->Arc( i );
// The arcs in the chain should have zero width
wxASSERT_MSG( arc.GetWidth() == 0, "Invalid arc width - should be zero" );
if( arc.Collide( &aA, aClearance, aActual, aLocation ) )
return true;
}
}
}
if( closest_dist == 0 || closest_dist < aClearance )
@ -462,7 +487,7 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_RECT& aB, int aClea
aA.Type(),
aB.Type() ) );
const SHAPE_LINE_CHAIN lc = aA.ConvertToPolyline();
const SHAPE_LINE_CHAIN lc( aA );
bool rv = Collide( lc, aB.Outline(), aClearance + aA.GetWidth() / 2, aActual, aLocation, aMTV );
@ -480,7 +505,7 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_CIRCLE& aB, int aCl
aA.Type(),
aB.Type() ) );
const SHAPE_LINE_CHAIN lc = aA.ConvertToPolyline();
const SHAPE_LINE_CHAIN lc( aA );
bool rv = Collide( aB, lc, aClearance + aA.GetWidth() / 2, aActual, aLocation, aMTV );
@ -498,14 +523,68 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_LINE_CHAIN& aB, int
aA.Type(),
aB.Type() ) );
const SHAPE_LINE_CHAIN lc = aA.ConvertToPolyline();
int closest_dist = INT_MAX;
VECTOR2I nearest;
bool rv = Collide( lc, aB, aClearance + aA.GetWidth() / 2, aActual, aLocation, aMTV );
if( aB.IsClosed() && aB.PointInside( aA.GetP0() ) )
{
closest_dist = 0;
nearest = aA.GetP0();
}
else
{
for( size_t i = 0; i < aB.GetSegmentCount(); i++ )
{
int collision_dist = 0;
VECTOR2I pn;
if( rv && aActual )
*aActual = std::max( 0, *aActual - aA.GetWidth() / 2 );
// ignore arcs - we will collide these separately
if( aB.IsArcSegment( i ) )
continue;
return rv;
if( aA.Collide( aB.GetSegment( i ), aClearance,
aActual || aLocation ? &collision_dist : nullptr,
aLocation ? &pn : nullptr ) )
{
if( collision_dist < closest_dist )
{
nearest = pn;
closest_dist = collision_dist;
}
if( closest_dist == 0 )
break;
// If we're not looking for aActual then any collision will do
if( !aActual )
break;
}
}
for( size_t i = 0; i < aB.ArcCount(); i++ )
{
const SHAPE_ARC& arc = aB.Arc( i );
// The arcs in the chain should have zero width
wxASSERT_MSG( arc.GetWidth() == 0, "Invalid arc width - should be zero" );
if( arc.Collide( &aA, aClearance, aActual, aLocation ) )
return true;
}
}
if( closest_dist == 0 || closest_dist < aClearance )
{
if( aLocation )
*aLocation = nearest;
if( aActual )
*aActual = closest_dist;
return true;
}
return false;
}
@ -516,7 +595,7 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_SEGMENT& aB, int aC
aA.Type(),
aB.Type() ) );
const SHAPE_LINE_CHAIN lc = aA.ConvertToPolyline();
const SHAPE_LINE_CHAIN lc( aA );
bool rv = Collide( lc, aB, aClearance + aA.GetWidth() / 2, aActual, aLocation, aMTV );

View File

@ -300,6 +300,75 @@ bool SHAPE_LINE_CHAIN_BASE::Collide( const VECTOR2I& aP, int aClearance, int* aA
}
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, "Invalid arc width - should be zero" );
if( arc.Collide( aP, aClearance, aActual, aLocation ) )
return true;
}
return false;
}
void SHAPE_LINE_CHAIN::Rotate( double aAngle, const VECTOR2I& aCenter )
{
for( auto& pt : m_points )
@ -368,6 +437,76 @@ bool SHAPE_LINE_CHAIN_BASE::Collide( const SEG& aSeg, int aClearance, int* aActu
}
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, "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 );
@ -1066,6 +1205,7 @@ void SHAPE_LINE_CHAIN::Append( const SHAPE_ARC& aArc )
// @todo should the below 4 LOC be moved to SHAPE_ARC::ConvertToPolyline ?
chain.m_arcs.push_back( aArc );
chain.m_arcs.back().SetWidth( 0 );
for( auto& sh : chain.m_shapes )
sh.first = 0;
@ -1130,7 +1270,9 @@ void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const SHAPE_ARC& aArc )
} );
}
m_arcs.insert( m_arcs.begin() + arc_pos, aArc );
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