diff --git a/libs/kimath/src/geometry/shape.cpp b/libs/kimath/src/geometry/shape.cpp index e39c8e1bdb..7d9902d63e 100644 --- a/libs/kimath/src/geometry/shape.cpp +++ b/libs/kimath/src/geometry/shape.cpp @@ -57,8 +57,19 @@ int SHAPE::GetClearance( const SHAPE* aOther ) const std::vector a_shapes; std::vector b_shapes; - GetIndexableSubshapes( a_shapes ); - aOther->GetIndexableSubshapes( b_shapes ); + /// POLY_SETs contain a bunch of polygons that are triangulated. + /// But there are way more triangles than necessary for collision detection. + /// Triangles check three vertices each but for the outline, we only need one. + /// These are also fractured, so we don't need to worry about holes + if( Type() == SHAPE_TYPE::SH_POLY_SET ) + a_shapes.push_back( &static_cast( this )->COutline( 0 ) ); + else + GetIndexableSubshapes( a_shapes ); + + if( aOther->Type() == SHAPE_TYPE::SH_POLY_SET ) + b_shapes.push_back( &static_cast( aOther )->COutline( 0 ) ); + else + aOther->GetIndexableSubshapes( b_shapes ); if( GetIndexableSubshapeCount() == 0 ) a_shapes.push_back( this ); diff --git a/libs/kimath/src/geometry/shape_arc.cpp b/libs/kimath/src/geometry/shape_arc.cpp index 7b812fd451..c1c9815516 100644 --- a/libs/kimath/src/geometry/shape_arc.cpp +++ b/libs/kimath/src/geometry/shape_arc.cpp @@ -261,13 +261,18 @@ bool SHAPE_ARC::Collide( const SEG& aSeg, int aClearance, int* aActual, VECTOR2I candidatePts.push_back( aSeg.A ); candidatePts.push_back( aSeg.B ); + bool any_collides = false; + for( const VECTOR2I& candidate : candidatePts ) { - if( Collide( candidate, aClearance, aActual, aLocation ) ) + bool collides = Collide( candidate, aClearance, aActual, aLocation ); + any_collides |= collides; + + if( collides && ( !aActual || *aActual == 0 ) ) return true; } - return false; + return any_collides; } diff --git a/libs/kimath/src/geometry/shape_collisions.cpp b/libs/kimath/src/geometry/shape_collisions.cpp index 82a12abc92..d487812341 100644 --- a/libs/kimath/src/geometry/shape_collisions.cpp +++ b/libs/kimath/src/geometry/shape_collisions.cpp @@ -304,52 +304,78 @@ static inline bool Collide( const SHAPE_LINE_CHAIN_BASE& aA, const SHAPE_LINE_CH } else { - for( size_t i = 0; i < aB.GetSegmentCount(); i++ ) + std::vector a_segs; + std::vector b_segs; + + for( size_t ii = 0; ii < aA.GetSegmentCount(); ii++ ) { - int collision_dist = 0; - VECTOR2I pn; - - if( aB.Type() == SH_LINE_CHAIN ) + if( aA.Type() != SH_LINE_CHAIN + || !static_cast( &aA )->IsArcSegment( ii ) ) { - const SHAPE_LINE_CHAIN* aB_line_chain = static_cast( &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 ) ) - { - 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; + a_segs.push_back( aA.GetSegment( ii ) ); } } - if( aB.Type() == SH_LINE_CHAIN ) + for( size_t ii = 0; ii < aB.GetSegmentCount(); ii++ ) { - const SHAPE_LINE_CHAIN* aB_line_chain = static_cast( &aB ); - - for( size_t i = 0; i < aB_line_chain->ArcCount(); i++ ) + if( aB.Type() != SH_LINE_CHAIN + || !static_cast( &aB )->IsArcSegment( ii ) ) { - const SHAPE_ARC& arc = aB_line_chain->Arc( i ); + b_segs.push_back( aB.GetSegment( ii ) ); + } + } - // The arcs in the chain should have zero width - wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) ); + auto seg_sort = []( const SEG& a, const SEG& b ) + { + return a.A.x < b.A.x || ( a.A.x == b.A.x && a.A.y < b.A.y ); + }; - if( arc.Collide( &aA, aClearance, aActual, aLocation ) ) - return true; + std::sort( a_segs.begin(), a_segs.end(), seg_sort ); + std::sort( b_segs.begin(), b_segs.end(), seg_sort ); + + for( const SEG& a_seg : a_segs ) + { + for( const SEG& b_seg : b_segs ) + { + int dist = 0; + + if( a_seg.Collide( b_seg, aClearance, aActual || aLocation ? &dist : nullptr ) ) + { + if( dist < closest_dist ) + { + nearest = a_seg.NearestPoint( b_seg ); + closest_dist = dist; + } + + if( closest_dist == 0 ) + break; + + // If we're not looking for aActual then any collision will do + if( !aActual ) + break; + } + } + } + + if( closest_dist > 0 && aActual ) + { + std::vector chains = { + static_cast( &aA ), + static_cast( &aB ) + }; + + for( int ii = 0; ii < 2; ii++ ) + { + const SHAPE_LINE_CHAIN* chain = chains[ii]; + const SHAPE_LINE_CHAIN* other = chains[( ii + 1 ) % 2]; + + for( size_t jj = 0; jj < chain->ArcCount(); jj++ ) + { + const SHAPE_ARC& arc = chain->Arc( jj ); + + if( arc.Collide( other, aClearance, aActual, aLocation ) ) + return true; + } } } } @@ -540,11 +566,11 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_LINE_CHAIN& aB, int } else { + int collision_dist = 0; + VECTOR2I pn; + for( size_t i = 0; i < aB.GetSegmentCount(); i++ ) { - int collision_dist = 0; - VECTOR2I pn; - // ignore arcs - we will collide these separately if( aB.IsArcSegment( i ) ) continue; @@ -575,8 +601,21 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_LINE_CHAIN& aB, int // The arcs in the chain should have zero width wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) ); - if( arc.Collide( &aA, aClearance, aActual, aLocation ) ) - return true; + if( aA.Collide( &arc, 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( !aActual ) + break; + } } } diff --git a/libs/kimath/src/geometry/shape_line_chain.cpp b/libs/kimath/src/geometry/shape_line_chain.cpp index a1dfd345de..92c92e0e63 100644 --- a/libs/kimath/src/geometry/shape_line_chain.cpp +++ b/libs/kimath/src/geometry/shape_line_chain.cpp @@ -655,6 +655,9 @@ bool SHAPE_LINE_CHAIN::Collide( const SEG& aSeg, int aClearance, int* aActual, return true; } + int dist = 0; + int closest_dist = sqrt( closest_dist_sq ); + // Collide arc segments for( size_t i = 0; i < ArcCount(); i++ ) { @@ -663,8 +666,28 @@ bool SHAPE_LINE_CHAIN::Collide( const SEG& aSeg, int aClearance, int* aActual, // 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; + if( arc.Collide( aSeg, aClearance, aActual || aLocation ? &dist : nullptr, + aLocation ? &nearest : nullptr ) ) + { + if( !aActual ) + return true; + + if( dist < closest_dist ) + { + closest_dist = dist; + } + } + } + + 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; diff --git a/pcbnew/tools/pcb_control.cpp b/pcbnew/tools/pcb_control.cpp index 14fb7b7143..b459614622 100644 --- a/pcbnew/tools/pcb_control.cpp +++ b/pcbnew/tools/pcb_control.cpp @@ -1591,18 +1591,15 @@ int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent ) msgItems.emplace_back( _( "Resolved clearance" ), m_frame->MessageTextFromValue( constraint.m_Value.Min() ) ); - if( a->Type() != PCB_ZONE_T || b->Type() != PCB_ZONE_T ) + std::shared_ptr a_shape( a_conn->GetEffectiveShape( layer ) ); + std::shared_ptr b_shape( b_conn->GetEffectiveShape( layer ) ); + + int actual_clearance = a_shape->GetClearance( b_shape.get() ); + + if( actual_clearance > -1 && actual_clearance < std::numeric_limits::max() ) { - std::shared_ptr a_shape( a_conn->GetEffectiveShape( layer ) ); - std::shared_ptr b_shape( b_conn->GetEffectiveShape( layer ) ); - - int actual_clearance = a_shape->GetClearance( b_shape.get() ); - - if( actual_clearance > -1 && actual_clearance < std::numeric_limits::max() ) - { - msgItems.emplace_back( _( "Actual clearance" ), - m_frame->MessageTextFromValue( actual_clearance ) ); - } + msgItems.emplace_back( _( "Actual clearance" ), + m_frame->MessageTextFromValue( actual_clearance ) ); } } } diff --git a/qa/tests/libs/kimath/CMakeLists.txt b/qa/tests/libs/kimath/CMakeLists.txt index 5b5a822dc3..8b5f39c113 100644 --- a/qa/tests/libs/kimath/CMakeLists.txt +++ b/qa/tests/libs/kimath/CMakeLists.txt @@ -42,6 +42,7 @@ set( QA_KIMATH_SRCS geometry/test_shape_poly_set_distance.cpp geometry/test_shape_poly_set_iterator.cpp geometry/test_shape_line_chain.cpp + geometry/test_shape_line_chain_collision.cpp math/test_box2.cpp math/test_matrix3x3.cpp diff --git a/qa/tests/libs/kimath/geometry/test_shape_line_chain_collision.cpp b/qa/tests/libs/kimath/geometry/test_shape_line_chain_collision.cpp new file mode 100644 index 0000000000..085838231b --- /dev/null +++ b/qa/tests/libs/kimath/geometry/test_shape_line_chain_collision.cpp @@ -0,0 +1,127 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 CERN + * @author Alejandro GarcĂ­a Montoro + * + * 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 + +#include +#include +#include + +#include "fixtures_geometry.h" + + +BOOST_AUTO_TEST_SUITE( SHAPE_LINE_CHAIN_COLLIDE_TEST ) + +BOOST_AUTO_TEST_CASE( Collide_LineToLine ) +{ + SHAPE_LINE_CHAIN lineA; + lineA.Append( VECTOR2I( 0, 0 ) ); + lineA.Append( VECTOR2I( 10, 0 ) ); + + SHAPE_LINE_CHAIN lineB; + lineB.Append( VECTOR2I( 5, 5 ) ); + lineB.Append( VECTOR2I( 5, -5 ) ); + + VECTOR2I location; + int actual = 0; + bool collided = static_cast( &lineA )->Collide( &lineB, 0, &actual, &location ); + + BOOST_CHECK( collided ); + BOOST_CHECK( actual == 0 ); + BOOST_CHECK_MESSAGE( location == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << location ); +} + +BOOST_AUTO_TEST_CASE( Collide_LineToArc ) +{ + SHAPE_LINE_CHAIN lineA; + lineA.Append( VECTOR2I( 0, 0 ) ); + lineA.Append( VECTOR2I( 10, 0 ) ); + + SHAPE_LINE_CHAIN arcB; + arcB.Append( SHAPE_ARC( VECTOR2I( 5, 5 ), VECTOR2I( 6, 4 ), VECTOR2I( 7, 0 ), 0 ) ); + + VECTOR2I location; + int actual = 0; + bool collided = static_cast( &lineA )->Collide( &arcB, 0, &actual, &location ); + + BOOST_CHECK( collided ); + BOOST_CHECK( actual == 0 ); + BOOST_CHECK_MESSAGE( location == VECTOR2I( 7, 0 ), "Expected: " << VECTOR2I( 7, 0 ) << " Actual: " << location ); +} + +BOOST_AUTO_TEST_CASE( Collide_ArcToArc ) +{ + SHAPE_LINE_CHAIN arcA; + arcA.Append( SHAPE_ARC( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ), VECTOR2I( 5, 5 ), 0 ) ); + + SHAPE_LINE_CHAIN arcB; + arcB.Append( SHAPE_ARC( VECTOR2I( 5, 5 ), VECTOR2I( 5, -5 ), VECTOR2I( 10, 0 ), 0 ) ); + + VECTOR2I location; + int actual = 0; + bool collided = static_cast( &arcA )->Collide( &arcB, 0, &actual, &location ); + + BOOST_CHECK( collided ); + BOOST_CHECK( actual == 0 ); + BOOST_CHECK_MESSAGE( location == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << location ); +} + +BOOST_AUTO_TEST_CASE( Collide_WithClearance ) +{ + SHAPE_LINE_CHAIN lineA; + lineA.Append( VECTOR2I( 0, 0 ) ); + lineA.Append( VECTOR2I( 10, 0 ) ); + + SHAPE_LINE_CHAIN lineB; + lineB.Append( VECTOR2I( 5, 6 ) ); + lineB.Append( VECTOR2I( -5, 6 ) ); + + VECTOR2I location; + int actual = 0; + bool collided = static_cast( &lineA )->Collide( &lineB, 7, &actual, &location ); + + BOOST_CHECK( collided ); + BOOST_CHECK_MESSAGE( actual == 6, "Expected: " << 6 << " Actual: " << actual ); + BOOST_CHECK_MESSAGE( location == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << location ); +} + +BOOST_AUTO_TEST_CASE( Collide_NoClearance ) +{ + SHAPE_LINE_CHAIN lineA; + lineA.Append( VECTOR2I( 0, 0 ) ); + lineA.Append( VECTOR2I( 10, 0 ) ); + + SHAPE_LINE_CHAIN lineB; + lineB.Append( VECTOR2I( 5, 6 ) ); + lineB.Append( VECTOR2I( -5, 6 ) ); + + VECTOR2I location; + int actual = 0; + bool collided = static_cast( &lineA )->Collide( &lineB, 0, &actual, &location ); + + BOOST_CHECK( !collided ); + BOOST_CHECK_MESSAGE( actual == 0, "Expected: " << 0 << " Actual: " << actual ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file