Fix slow selection time when calculating clearance

The clearance between two zones could be rather slow.  This was in part
to trying to do triangle-triangle collisions between zones when we only
need outline collision and in part to the shape_line_chain collision
routine.  The shape_line_chain collisions don't need to recreate
segments on each iteration and should instead create them once and using
this to check all collisions

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17327

(cherry picked from commit 3cc1617f5a)
This commit is contained in:
Seth Hillbrand 2024-03-11 16:28:51 -07:00
parent b1677d5f47
commit 9be4bf0d14
7 changed files with 262 additions and 59 deletions

View File

@ -57,8 +57,19 @@ int SHAPE::GetClearance( const SHAPE* aOther ) const
std::vector<const SHAPE*> a_shapes;
std::vector<const SHAPE*> 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<const SHAPE_POLY_SET*>( this )->COutline( 0 ) );
else
GetIndexableSubshapes( a_shapes );
if( aOther->Type() == SHAPE_TYPE::SH_POLY_SET )
b_shapes.push_back( &static_cast<const SHAPE_POLY_SET*>( aOther )->COutline( 0 ) );
else
aOther->GetIndexableSubshapes( b_shapes );
if( GetIndexableSubshapeCount() == 0 )
a_shapes.push_back( this );

View File

@ -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;
}

View File

@ -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<SEG> a_segs;
std::vector<SEG> 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<const SHAPE_LINE_CHAIN*>( &aA )->IsArcSegment( ii ) )
{
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 ) )
{
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<const SHAPE_LINE_CHAIN*>( &aB );
for( size_t i = 0; i < aB_line_chain->ArcCount(); i++ )
if( aB.Type() != SH_LINE_CHAIN
|| !static_cast<const SHAPE_LINE_CHAIN*>( &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<const SHAPE_LINE_CHAIN*> chains = {
static_cast<const SHAPE_LINE_CHAIN*>( &aA ),
static_cast<const SHAPE_LINE_CHAIN*>( &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;
}
}
}

View File

@ -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;

View File

@ -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<SHAPE> a_shape( a_conn->GetEffectiveShape( layer ) );
std::shared_ptr<SHAPE> b_shape( b_conn->GetEffectiveShape( layer ) );
int actual_clearance = a_shape->GetClearance( b_shape.get() );
if( actual_clearance > -1 && actual_clearance < std::numeric_limits<int>::max() )
{
std::shared_ptr<SHAPE> a_shape( a_conn->GetEffectiveShape( layer ) );
std::shared_ptr<SHAPE> b_shape( b_conn->GetEffectiveShape( layer ) );
int actual_clearance = a_shape->GetClearance( b_shape.get() );
if( actual_clearance > -1 && actual_clearance < std::numeric_limits<int>::max() )
{
msgItems.emplace_back( _( "Actual clearance" ),
m_frame->MessageTextFromValue( actual_clearance ) );
}
msgItems.emplace_back( _( "Actual clearance" ),
m_frame->MessageTextFromValue( actual_clearance ) );
}
}
}

View File

@ -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

View File

@ -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 <alejandro.garciamontoro@gmail.com>
*
* 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 <qa_utils/wx_utils/unit_test_utils.h>
#include <geometry/shape.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_line_chain.h>
#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<SHAPE*>( &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<SHAPE*>( &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<SHAPE*>( &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<SHAPE*>( &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<SHAPE*>( &lineA )->Collide( &lineB, 0, &actual, &location );
BOOST_CHECK( !collided );
BOOST_CHECK_MESSAGE( actual == 0, "Expected: " << 0 << " Actual: " << actual );
}
BOOST_AUTO_TEST_SUITE_END()