Hi Wayne, In my testing there is no performance impact, but more testing is welcome. It shouldn't be doing the calculation on too many objects in general, since this is a "second pass" hit test that applies to modules that have a bounding box overlapping the mouse cursor. However, I did some more testing and discovered some weird behavior, so I have tweaked the algorithm in the attached new version of the patch.
-Jon On Sun, Feb 18, 2018 at 5:25 PM, Wayne Stambaugh <stambau...@gmail.com> wrote: > Hey Jon, > > Did you notice an performance hit with your patch? Obviously there is > going to be more overhead calculating a polygon versus a rectangle. I just > want to be sure we are not causing any usability issues due to the polygon > calculations. > > Thanks, > > Wayne > > > On 02/18/2018 12:10 PM, Jon Evans wrote: > >> Hi all, >> >> The attached patch adds some plumbing to calculate and make use of a >> polygonal bounding area for modules. It fixes the below issue and in >> general improves the accuracy of selection in my testing. >> >> This mechanism could be extended to other objects besides modules if it's >> useful. I figured I'd start by sending out this patch to get feedback, and >> if it gets merged, look for other areas where we could improve things by >> using polygons instead of bounding boxes. >> >> https://bugs.launchpad.net/kicad/+bug/1749077 >> >> -Jon >> >> >> _______________________________________________ >> Mailing list: https://launchpad.net/~kicad-developers >> Post to : kicad-developers@lists.launchpad.net >> Unsubscribe : https://launchpad.net/~kicad-developers >> More help : https://help.launchpad.net/ListHelp >> >> > _______________________________________________ > Mailing list: https://launchpad.net/~kicad-developers > Post to : kicad-developers@lists.launchpad.net > Unsubscribe : https://launchpad.net/~kicad-developers > More help : https://help.launchpad.net/ListHelp >
From caf208e46f5a0a78ce6c1da5a13882f28096f941 Mon Sep 17 00:00:00 2001 From: Jon Evans <j...@craftyjon.com> Date: Sun, 18 Feb 2018 19:00:29 -0500 Subject: [PATCH] Use polygonal hit testing for module selection --- common/geometry/shape_poly_set.cpp | 25 ++++++++++++---------- include/geometry/shape_poly_set.h | 15 +++++++++---- pcbnew/board_items_to_polygon_shape_transform.cpp | 13 ++++++++---- pcbnew/class_module.cpp | 26 +++++++++++++++++++++++ pcbnew/class_module.h | 24 ++++++++++++++++++--- pcbnew/collectors.cpp | 3 ++- 6 files changed, 83 insertions(+), 23 deletions(-) diff --git a/common/geometry/shape_poly_set.cpp b/common/geometry/shape_poly_set.cpp index c2821ff0b..49e3a66ed 100644 --- a/common/geometry/shape_poly_set.cpp +++ b/common/geometry/shape_poly_set.cpp @@ -1402,19 +1402,19 @@ bool SHAPE_POLY_SET::CollideEdge( const VECTOR2I& aPoint, } -bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex ) const +bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const { if( m_polys.size() == 0 ) // empty set? return false; // If there is a polygon specified, check the condition against that polygon if( aSubpolyIndex >= 0 ) - return containsSingle( aP, aSubpolyIndex ); + return containsSingle( aP, aSubpolyIndex, aIgnoreHoles ); // In any other case, check it against all polygons in the set for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ ) { - if( containsSingle( aP, polygonIdx ) ) + if( containsSingle( aP, polygonIdx, aIgnoreHoles ) ) return true; } @@ -1440,20 +1440,23 @@ void SHAPE_POLY_SET::RemoveVertex( VERTEX_INDEX aIndex ) } -bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex ) const +bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const { // Check that the point is inside the outline if( pointInPolygon( aP, m_polys[aSubpolyIndex][0] ) ) { - // Check that the point is not in any of the holes - for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ ) + if( !aIgnoreHoles ) { - const SHAPE_LINE_CHAIN hole = CHole( aSubpolyIndex, holeIdx ); + // Check that the point is not in any of the holes + for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ ) + { + const SHAPE_LINE_CHAIN hole = CHole( aSubpolyIndex, holeIdx ); - // If the point is inside a hole (and not on its edge), - // it is outside of the polygon - if( pointInPolygon( aP, hole ) && !hole.PointOnEdge( aP ) ) - return false; + // If the point is inside a hole (and not on its edge), + // it is outside of the polygon + if( pointInPolygon( aP, hole ) && !hole.PointOnEdge( aP ) ) + return false; + } } return true; diff --git a/include/geometry/shape_poly_set.h b/include/geometry/shape_poly_set.h index ee9144d60..cec587022 100644 --- a/include/geometry/shape_poly_set.h +++ b/include/geometry/shape_poly_set.h @@ -933,9 +933,15 @@ class SHAPE_POLY_SET : public SHAPE bool CollideEdge( const VECTOR2I& aPoint, VERTEX_INDEX& aClosestVertex, int aClearance = 0 ); - ///> Returns true if a given subpolygon contains the point aP. If aSubpolyIndex < 0 - ///> (default value), checks all polygons in the set - bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1 ) const; + /** + * Returns true if a given subpolygon contains the point aP + * + * @param aP is the point to check + * @param aSubpolyIndex is the subpolygon to check, or -1 to check all + * @param aIgnoreHoles controls whether or not internal holes are considered + * @return true if the polygon contains the point + */ + bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1, bool aIgnoreHoles = false ) const; ///> Returns true if the set is empty (no polygons at all) bool IsEmpty() const @@ -1112,10 +1118,11 @@ class SHAPE_POLY_SET : public SHAPE * the aSubpolyIndex-th polygon will be tested. * @param aSubpolyIndex is an integer specifying which polygon in the set has to be * checked. + * @param aIgnoreHoles can be set to true to ignore internal holes in the polygon * @return bool - true if aP is inside aSubpolyIndex-th polygon; false in any other * case. */ - bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex ) const; + bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles = false ) const; /** * Operations ChamferPolygon and FilletPolygon are computed under the private chamferFillet diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp index fd3e763b0..4d5200e61 100644 --- a/pcbnew/board_items_to_polygon_shape_transform.cpp +++ b/pcbnew/board_items_to_polygon_shape_transform.cpp @@ -138,7 +138,7 @@ void MODULE::TransformPadsShapesWithClearanceToPolygon( PCB_LAYER_ID aLayer, wxSize margin; for( ; pad != NULL; pad = pad->Next() ) { - if( !pad->IsOnLayer(aLayer) ) + if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) ) continue; // NPTH pads are not drawn on layers if the shape size and pos is the same @@ -206,7 +206,8 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( int aInflateValue, int aCircleToSegmentsCount, double aCorrectionFactor, - int aCircleToSegmentsCountForTexts ) const + int aCircleToSegmentsCountForTexts, + bool aIncludeText ) const { std::vector<TEXTE_MODULE *> texts; // List of TEXTE_MODULE to convert EDGE_MODULE* outline; @@ -219,7 +220,8 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( { TEXTE_MODULE* text = static_cast<TEXTE_MODULE*>( item ); - if( text->GetLayer() == aLayer && text->IsVisible() ) + if( ( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer ) + && text->IsVisible() ) texts.push_back( text ); break; @@ -228,7 +230,7 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( case PCB_MODULE_EDGE_T: outline = (EDGE_MODULE*) item; - if( outline->GetLayer() != aLayer ) + if( aLayer != UNDEFINED_LAYER && outline->GetLayer() != aLayer ) break; outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, 0, @@ -240,6 +242,9 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet( } } + if( !aIncludeText ) + return; + // Convert texts sur modules if( Reference().GetLayer() == aLayer && Reference().IsVisible() ) texts.push_back( &Reference() ); diff --git a/pcbnew/class_module.cpp b/pcbnew/class_module.cpp index c201a0f5d..46bb4538e 100644 --- a/pcbnew/class_module.cpp +++ b/pcbnew/class_module.cpp @@ -512,6 +512,25 @@ const EDA_RECT MODULE::GetBoundingBox() const } +SHAPE_POLY_SET MODULE::GetBoundingPoly() const +{ + const int segcountforcircle = 8; + double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); + SHAPE_POLY_SET poly; + + TransformPadsShapesWithClearanceToPolygon( UNDEFINED_LAYER, + poly, 0, segcountforcircle, correctionFactor ); + + TransformGraphicShapesWithClearanceToPolygonSet( UNDEFINED_LAYER, + poly, 0, segcountforcircle, correctionFactor, 0, false ); + + poly.NormalizeAreaOutlines(); + poly.Inflate( Millimeter2iu( 0.01 ), segcountforcircle ); + + return poly; +} + + void MODULE::GetMsgPanelInfo( std::vector< MSG_PANEL_ITEM >& aList ) { int nbpad; @@ -607,6 +626,13 @@ bool MODULE::HitTest( const wxPoint& aPosition ) const } +bool MODULE::HitTestAccurate( const wxPoint& aPosition ) const +{ + auto shape = GetBoundingPoly(); + return shape.Contains( aPosition, -1, true ); +} + + bool MODULE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const { EDA_RECT arect = aRect; diff --git a/pcbnew/class_module.h b/pcbnew/class_module.h index 3eca1a015..ac5d1c2ec 100644 --- a/pcbnew/class_module.h +++ b/pcbnew/class_module.h @@ -148,6 +148,12 @@ public: */ EDA_RECT GetFootprintRect() const; + /** + * Returns a bounding polygon for the shapes and pads in the module + * This operation is slower but more accurate than calculating a bounding box + */ + SHAPE_POLY_SET GetBoundingPoly() const; + // Virtual function const EDA_RECT GetBoundingBox() const override; @@ -336,7 +342,7 @@ public: * and adds these polygons to aCornerBuffer * Useful to generate a polygonal representation of a footprint * in 3D view and plot functions, when a full polygonal approach is needed - * @param aLayer = the current layer: pads on this layer are considered + * @param aLayer = the layer to consider, or UNDEFINED_LAYER to consider all * @param aCornerBuffer = the buffer to store polygons * @param aInflateValue = an additionnal size to add to pad shapes * aInflateValue = 0 to have the exact pad size @@ -366,7 +372,7 @@ public: * and adds these polygons to aCornerBuffer * Useful to generate a polygonal representation of a footprint * in 3D view and plot functions, when a full polygonal approach is needed - * @param aLayer = the current layer: items on this layer are considered + * @param aLayer = the layer to consider, or UNDEFINED_LAYER to consider all * @param aCornerBuffer = the buffer to store polygons * @param aInflateValue = a value to inflate shapes * aInflateValue = 0 to have the exact shape size @@ -385,7 +391,8 @@ public: int aInflateValue, int aCircleToSegmentsCount, double aCorrectionFactor, - int aCircleToSegmentsCountForTexts = 0 ) const; + int aCircleToSegmentsCountForTexts = 0, + bool aIncludeText = true ) const; /** * @brief TransformGraphicTextWithClearanceToPolygonSet @@ -430,6 +437,17 @@ public: bool HitTest( const wxPoint& aPosition ) const override; + /** + * Tests if a point is inside the bounding polygon of the module + * + * The other hit test methods are just checking the bounding box, which + * can be quite inaccurate for rotated or oddly-shaped footprints. + * + * @param aPosition is the point to test + * @return true if aPosition is inside the bounding polygon + */ + bool HitTestAccurate( const wxPoint& aPosition ) const; + bool HitTest( const EDA_RECT& aRect, bool aContained = true, int aAccuracy = 0 ) const override; /** diff --git a/pcbnew/collectors.cpp b/pcbnew/collectors.cpp index 8b3e7d37a..cd692adfc 100644 --- a/pcbnew/collectors.cpp +++ b/pcbnew/collectors.cpp @@ -399,7 +399,8 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData ) { if( item->HitTest( m_RefPos ) ) { - Append( item ); + if( !module || module->HitTestAccurate( m_RefPos ) ) + Append( item ); goto exit; } } -- 2.14.1
_______________________________________________ Mailing list: https://launchpad.net/~kicad-developers Post to : kicad-developers@lists.launchpad.net Unsubscribe : https://launchpad.net/~kicad-developers More help : https://help.launchpad.net/ListHelp