Remove Creepage generation from reporting

Recalculating creepeage every time we reported a new clearance error was
unneccesarily complex.  Since the clearance errors are always straight
lines, we just need to calculate the closest approach and present that
segment
This commit is contained in:
Seth Hillbrand 2025-07-21 13:00:28 -07:00
parent 8c7dca7532
commit 2aeecec9bd
6 changed files with 1534 additions and 26 deletions

View File

@ -32,6 +32,7 @@ set( KIMATH_SRCS
src/geometry/shape_compound.cpp
src/geometry/shape_file_io.cpp
src/geometry/shape_line_chain.cpp
src/geometry/shape_nearest_points.cpp
src/geometry/shape_poly_set.cpp
src/geometry/shape_rect.cpp
src/geometry/shape_segment.cpp

View File

@ -248,6 +248,17 @@ public:
*/
virtual SEG::ecoord SquaredDistance( const VECTOR2I& aP, bool aOutlineOnly = false ) const;
/**
* Return the two points that mark the closest distance between this shape and \a aOther.
* If the shapes are overlapping, the points will be the same.
*
* @param aOther the other shape to compare with
* @param aPtThis [out] the point on this shape closest to \a aOther
* @param aPtOther [out] the point on \a aOther closest to this shape
* @return true if the points were found
*/
bool NearestPoints( const SHAPE* aOther, VECTOR2I& aPtThis, VECTOR2I& aPtOther ) const;
/**
* Check if point \a aP lies inside a closed shape. Always returns false if this shape is not closed.
*

View File

@ -0,0 +1,914 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3
* 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:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the https://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <cmath>
#include <limits>
#include <geometry/seg.h>
#include <geometry/shape.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_circle.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_compound.h>
#include <geometry/shape_poly_set.h>
#include <geometry/shape_simple.h>
#include <math/vector2d.h>
typedef VECTOR2I::extended_type ecoord;
/**
* Find the nearest points between two circles.
*
* @param aA first circle
* @param aB second circle
* @param aPtA [out] nearest point on first circle
* @param aPtB [out] nearest point on second circle
* @return true (circles always have nearest points)
*/
static bool NearestPoints( const SHAPE_CIRCLE& aA, const SHAPE_CIRCLE& aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
const VECTOR2I delta = aB.GetCenter() - aA.GetCenter();
const int dist = delta.EuclideanNorm();
if( dist == 0 )
{
// Circles are concentric - pick arbitrary points
aPtA = aA.GetCenter() + VECTOR2I( aA.GetRadius(), 0 );
aPtB = aB.GetCenter() + VECTOR2I( aB.GetRadius(), 0 );
}
else
{
// Points lie on line between centers
VECTOR2I dir = delta.Resize( 1 );
aPtA = aA.GetCenter() + dir.Resize( aA.GetRadius() );
aPtB = aB.GetCenter() - dir.Resize( aB.GetRadius() );
}
return true;
}
/**
* Find the nearest points between a circle and a rectangle.
*
* @param aCircle the circle
* @param aRect the rectangle
* @param aPtA [out] nearest point on circle
* @param aPtB [out] nearest point on rectangle
* @return true (always succeeds)
*/
static bool NearestPoints( const SHAPE_CIRCLE& aCircle, const SHAPE_RECT& aRect,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
const VECTOR2I c = aCircle.GetCenter();
const VECTOR2I p0 = aRect.GetPosition();
const VECTOR2I size = aRect.GetSize();
// Clamp circle center to rectangle bounds to find nearest point on rect
aPtB.x = std::max( p0.x, std::min( c.x, p0.x + size.x ) );
aPtB.y = std::max( p0.y, std::min( c.y, p0.y + size.y ) );
// Find nearest point on circle
if( aPtB == c )
{
// Center is inside rectangle - find nearest edge
int distToLeft = c.x - p0.x;
int distToRight = p0.x + size.x - c.x;
int distToTop = c.y - p0.y;
int distToBottom = p0.y + size.y - c.y;
int minDist = std::min( { distToLeft, distToRight, distToTop, distToBottom } );
if( minDist == distToLeft )
{
aPtB = VECTOR2I( p0.x, c.y );
aPtA = c - VECTOR2I( aCircle.GetRadius(), 0 );
}
else if( minDist == distToRight )
{
aPtB = VECTOR2I( p0.x + size.x, c.y );
aPtA = c + VECTOR2I( aCircle.GetRadius(), 0 );
}
else if( minDist == distToTop )
{
aPtB = VECTOR2I( c.x, p0.y );
aPtA = c - VECTOR2I( 0, aCircle.GetRadius() );
}
else
{
aPtB = VECTOR2I( c.x, p0.y + size.y );
aPtA = c + VECTOR2I( 0, aCircle.GetRadius() );
}
}
else
{
VECTOR2I dir = ( aPtB - c ).Resize( aCircle.GetRadius() );
aPtA = c + dir;
}
return true;
}
/**
* Find the nearest points between a circle and a line segment.
*
* @param aCircle the circle
* @param aSeg the segment
* @param aPtA [out] nearest point on circle
* @param aPtB [out] nearest point on segment
* @return true (always succeeds)
*/
static bool NearestPoints( const SHAPE_CIRCLE& aCircle, const SEG& aSeg,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
aPtB = aSeg.NearestPoint( aCircle.GetCenter() );
if( aPtB == aCircle.GetCenter() )
{
// Center is on segment - pick perpendicular direction
VECTOR2I dir = ( aSeg.B - aSeg.A ).Perpendicular().Resize( aCircle.GetRadius() );
aPtA = aCircle.GetCenter() + dir;
}
else
{
VECTOR2I dir = ( aPtB - aCircle.GetCenter() ).Resize( aCircle.GetRadius() );
aPtA = aCircle.GetCenter() + dir;
}
return true;
}
/**
* Find the nearest points between a circle and a line chain.
*/
static bool NearestPoints( const SHAPE_CIRCLE& aCircle, const SHAPE_LINE_CHAIN_BASE& aChain,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( size_t i = 0; i < aChain.GetSegmentCount(); i++ )
{
VECTOR2I ptA, ptB;
if( NearestPoints( aCircle, aChain.GetSegment( i ), ptA, ptB ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
/**
* Find the nearest points between two rectangles.
*/
static bool NearestPoints( const SHAPE_RECT& aA, const SHAPE_RECT& aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
// Convert rectangles to line chains and use that algorithm
const SHAPE_LINE_CHAIN outlineA = aA.Outline();
const SHAPE_LINE_CHAIN outlineB = aB.Outline();
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( int i = 0; i < outlineA.SegmentCount(); i++ )
{
for( int j = 0; j < outlineB.SegmentCount(); j++ )
{
SEG segA = outlineA.CSegment( i );
SEG segB = outlineB.CSegment( j );
VECTOR2I ptA = segA.NearestPoint( segB );
VECTOR2I ptB = segB.NearestPoint( segA );
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return true;
}
/**
* Find the nearest points between a rectangle and a segment.
*/
static bool NearestPoints( const SHAPE_RECT& aRect, const SEG& aSeg, VECTOR2I& aPtA, VECTOR2I& aPtB )
{
const SHAPE_LINE_CHAIN outline = aRect.Outline();
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( int i = 0; i < outline.SegmentCount(); i++ )
{
SEG rectSeg = outline.CSegment( i );
VECTOR2I ptA = rectSeg.NearestPoint( aSeg );
VECTOR2I ptB = aSeg.NearestPoint( ptA );
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
return true;
}
/**
* Find the nearest points between a rectangle and a line chain.
*/
static bool NearestPoints( const SHAPE_RECT& aRect, const SHAPE_LINE_CHAIN_BASE& aChain,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
const SHAPE_LINE_CHAIN outline = aRect.Outline();
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( int i = 0; i < outline.SegmentCount(); i++ )
{
for( size_t j = 0; j < aChain.GetSegmentCount(); j++ )
{
SEG rectSeg = outline.CSegment( i );
SEG chainSeg = aChain.GetSegment( j );
VECTOR2I ptA = rectSeg.NearestPoint( chainSeg );
VECTOR2I ptB = chainSeg.NearestPoint( ptA );
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return true;
}
/**
* Find the nearest points between two segments.
*/
static bool NearestPoints( const SEG& aA, const SEG& aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
aPtA = aA.NearestPoint( aB );
aPtB = aB.NearestPoint( aPtA );
return true;
}
/**
* Find the nearest points between a segment and a line chain.
*/
static bool NearestPoints( const SEG& aSeg, const SHAPE_LINE_CHAIN_BASE& aChain,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( size_t i = 0; i < aChain.GetSegmentCount(); i++ )
{
VECTOR2I ptA, ptB;
// Reverse the output points to match the segment's order
if( NearestPoints( aSeg, aChain.GetSegment( i ), ptB, ptA ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
/**
* Find the nearest points between two line chains.
*/
static bool NearestPoints( const SHAPE_LINE_CHAIN_BASE& aA, const SHAPE_LINE_CHAIN_BASE& aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t minDistSq = std::numeric_limits<int64_t>::max();
// Check all segment pairs
for( size_t i = 0; i < aA.GetSegmentCount(); i++ )
{
for( size_t j = 0; j < aB.GetSegmentCount(); j++ )
{
VECTOR2I ptA, ptB;
if( NearestPoints( aA.GetSegment( i ), aB.GetSegment( j ), ptA, ptB ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
}
// Also handle arcs if this is a SHAPE_LINE_CHAIN
const SHAPE_LINE_CHAIN* chainA = dynamic_cast<const SHAPE_LINE_CHAIN*>( &aA );
const SHAPE_LINE_CHAIN* chainB = dynamic_cast<const SHAPE_LINE_CHAIN*>( &aB );
if( chainA )
{
for( size_t i = 0; i < chainA->ArcCount(); i++ )
{
const SHAPE_ARC& arcA = chainA->Arc( i );
if( chainB )
{
// Arc to arc
for( size_t j = 0; j < chainB->ArcCount(); j++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( arcA.NearestPoints( chainB->Arc( j ), ptA, ptB, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
}
// Arc to segments
for( size_t j = 0; j < aB.GetSegmentCount(); j++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( arcA.NearestPoints( aB.GetSegment( j ), ptA, ptB, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
}
}
if( chainB && !chainA )
{
// Handle arcs in chainB vs segments in aA
for( size_t j = 0; j < chainB->ArcCount(); j++ )
{
const SHAPE_ARC& arcB = chainB->Arc( j );
for( size_t i = 0; i < aA.GetSegmentCount(); i++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( arcB.NearestPoints( aA.GetSegment( i ), ptB, ptA, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
/**
* Find the nearest points between an arc and other shapes.
* Uses the arc's built-in NearestPoints methods.
*/
static bool NearestPoints( const SHAPE_ARC& aArc, const SHAPE_CIRCLE& aCircle,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t distSq;
return aArc.NearestPoints( aCircle, aPtA, aPtB, distSq );
}
static bool NearestPoints( const SHAPE_ARC& aArc, const SHAPE_RECT& aRect,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t distSq;
return aArc.NearestPoints( aRect, aPtA, aPtB, distSq );
}
static bool NearestPoints( const SHAPE_ARC& aArc, const SHAPE_SEGMENT& aSeg,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t distSq;
return aArc.NearestPoints( aSeg.GetSeg(), aPtA, aPtB, distSq );
}
static bool NearestPoints( const SHAPE_ARC& aArcA, const SHAPE_ARC& aArcB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t distSq;
return aArcA.NearestPoints( aArcB, aPtA, aPtB, distSq );
}
static bool NearestPoints( const SHAPE_ARC& aArc, const SHAPE_LINE_CHAIN_BASE& aChain,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t distSq;
int64_t minDistSq = std::numeric_limits<int64_t>::max();
VECTOR2I tmp_ptA, tmp_ptB;
for( size_t i = 0; i < aChain.GetSegmentCount(); i++ )
{
if( aArc.NearestPoints( aChain.GetSegment( i ), tmp_ptA, tmp_ptB, distSq ) )
{
if( distSq < minDistSq )
{
aPtA = tmp_ptA;
aPtB = tmp_ptB;
minDistSq = distSq;
}
}
}
return true;
}
/**
* Find nearest points between SHAPE_SEGMENT and other shapes
*/
static bool NearestPoints( const SHAPE_SEGMENT& aSeg, const SHAPE_CIRCLE& aCircle,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
// If we get nearest points, then we need to move the aPtA half of the width of the segment
// toward aPtB
if( NearestPoints( aCircle, aSeg.GetSeg(), aPtB, aPtA ) )
{
VECTOR2I dir = ( aPtB - aPtA ).Resize( aSeg.GetWidth() / 2 );
aPtA += dir;
return true;
}
return false;
}
static bool NearestPoints( const SHAPE_SEGMENT& aSeg, const SHAPE_RECT& aRect,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
if( NearestPoints( aRect, aSeg.GetSeg(), aPtB, aPtA ) )
{
// Adjust point A by half the segment width towards point B
VECTOR2I dir = ( aPtB - aPtA ).Resize( aSeg.GetWidth() / 2 );
aPtA += dir;
return true;
}
return false;
}
static bool NearestPoints( const SHAPE_SEGMENT& aSegA, const SHAPE_SEGMENT& aSegB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
// Find nearest points between two segments
if( NearestPoints( aSegA.GetSeg(), aSegB.GetSeg(), aPtA, aPtB ) )
{
// Adjust point A by half the segment width towards point B
VECTOR2I dir = ( aPtB - aPtA ).Resize( aSegA.GetWidth() / 2 );
aPtA += dir;
// Adjust point B by half the segment width towards point A
dir = ( aPtA - aPtB ).Resize( aSegB.GetWidth() / 2 );
aPtB += dir;
return true;
}
return false;
}
static bool NearestPoints( const SHAPE_SEGMENT& aSeg, const SHAPE_LINE_CHAIN_BASE& aChain,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
if( NearestPoints( aSeg.GetSeg(), aChain, aPtB, aPtA ) )
{
// Adjust point A by half the segment width towards point B
VECTOR2I dir = ( aPtB - aPtA ).Resize( aSeg.GetWidth() / 2 );
aPtA += dir;
return true;
}
return false;
}
/**
* Template functions to handle shape conversions and reversals
*/
template<class T_a, class T_b>
inline bool NearestPointsCase( const SHAPE* aA, const SHAPE* aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
return NearestPoints( *static_cast<const T_a*>( aA ),
*static_cast<const T_b*>( aB ),
aPtA, aPtB );
}
template<class T_a, class T_b>
inline bool NearestPointsCaseReversed( const SHAPE* aA, const SHAPE* aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
return NearestPoints( *static_cast<const T_b*>( aB ),
*static_cast<const T_a*>( aA ),
aPtB, aPtA );
}
/**
* Main dispatcher for finding nearest points between arbitrary shapes
*/
static bool nearestPointsSingleShapes( const SHAPE* aA, const SHAPE* aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
switch( aA->Type() )
{
case SH_RECT:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCase<SHAPE_RECT, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCaseReversed<SHAPE_RECT, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCase<SHAPE_RECT, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCaseReversed<SHAPE_RECT, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_RECT, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
return NearestPointsCaseReversed<SHAPE_RECT, SHAPE_ARC>( aA, aB, aPtA, aPtB );
default:
break;
}
break;
case SH_CIRCLE:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCase<SHAPE_CIRCLE, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCase<SHAPE_CIRCLE, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCase<SHAPE_CIRCLE, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCaseReversed<SHAPE_CIRCLE, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_CIRCLE, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
return NearestPointsCaseReversed<SHAPE_CIRCLE, SHAPE_ARC>( aA, aB, aPtA, aPtB );
default:
break;
}
break;
case SH_LINE_CHAIN:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCase<SHAPE_LINE_CHAIN, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_LINE_CHAIN, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
// Special handling for arc
{
const SHAPE_LINE_CHAIN* chain = static_cast<const SHAPE_LINE_CHAIN*>( aA );
const SHAPE_ARC* arc = static_cast<const SHAPE_ARC*>( aB );
int64_t minDistSq = std::numeric_limits<int64_t>::max();
// Check segments
for( int i = 0; i < chain->SegmentCount(); i++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( arc->NearestPoints( chain->CSegment( i ), ptB, ptA, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
// Check arcs
for( size_t i = 0; i < chain->ArcCount(); i++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( chain->Arc( i ).NearestPoints( *arc, ptA, ptB, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
default:
break;
}
break;
case SH_SEGMENT:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCase<SHAPE_SEGMENT, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCase<SHAPE_SEGMENT, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCase<SHAPE_SEGMENT, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCase<SHAPE_SEGMENT, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_SEGMENT, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
return NearestPointsCaseReversed<SHAPE_SEGMENT, SHAPE_ARC>( aA, aB, aPtA, aPtB );
default:
break;
}
break;
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN_BASE, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN_BASE, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN_BASE, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCaseReversed<SHAPE_LINE_CHAIN_BASE, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_LINE_CHAIN_BASE, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
// Handle arc specially
{
const SHAPE_LINE_CHAIN_BASE* chain = static_cast<const SHAPE_LINE_CHAIN_BASE*>( aA );
const SHAPE_ARC* arc = static_cast<const SHAPE_ARC*>( aB );
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( size_t i = 0; i < chain->GetSegmentCount(); i++ )
{
VECTOR2I ptA, ptB;
int64_t distSq;
if( arc->NearestPoints( chain->GetSegment( i ), ptB, ptA, distSq ) )
{
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
default:
break;
}
break;
case SH_ARC:
switch( aB->Type() )
{
case SH_RECT:
return NearestPointsCase<SHAPE_ARC, SHAPE_RECT>( aA, aB, aPtA, aPtB );
case SH_CIRCLE:
return NearestPointsCase<SHAPE_ARC, SHAPE_CIRCLE>( aA, aB, aPtA, aPtB );
case SH_LINE_CHAIN:
return NearestPointsCase<SHAPE_ARC, SHAPE_LINE_CHAIN>( aA, aB, aPtA, aPtB );
case SH_SEGMENT:
return NearestPointsCase<SHAPE_ARC, SHAPE_SEGMENT>( aA, aB, aPtA, aPtB );
case SH_SIMPLE:
case SH_POLY_SET_TRIANGLE:
return NearestPointsCase<SHAPE_ARC, SHAPE_LINE_CHAIN_BASE>( aA, aB, aPtA, aPtB );
case SH_ARC:
return NearestPointsCase<SHAPE_ARC, SHAPE_ARC>( aA, aB, aPtA, aPtB );
default:
break;
}
break;
case SH_POLY_SET:
// For polygon sets, find nearest points to all edges
{
const SHAPE_POLY_SET* polySet = static_cast<const SHAPE_POLY_SET*>( aA );
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( auto it = polySet->CIterateSegments(); it; ++it )
{
SHAPE_SEGMENT seg( *it );
VECTOR2I ptA, ptB;
if( nearestPointsSingleShapes( &seg, aB, ptA, ptB ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
break;
default:
break;
}
// Handle SHAPE_POLY_SET as second shape
if( aB->Type() == SH_POLY_SET )
{
const SHAPE_POLY_SET* polySet = static_cast<const SHAPE_POLY_SET*>( aB );
int64_t minDistSq = std::numeric_limits<int64_t>::max();
for( auto it = polySet->CIterateSegments(); it; ++it )
{
SHAPE_SEGMENT seg( *it );
VECTOR2I ptA, ptB;
if( nearestPointsSingleShapes( aA, &seg, ptA, ptB ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
}
}
}
return minDistSq < std::numeric_limits<int64_t>::max();
}
return false;
}
/**
* Handle compound shapes by finding nearest points between all sub-shape pairs
*/
static bool nearestPoints( const SHAPE* aA, const SHAPE* aB,
VECTOR2I& aPtA, VECTOR2I& aPtB )
{
int64_t minDistSq = std::numeric_limits<int64_t>::max();
bool found = false;
auto checkNearestPoints = [&]( const SHAPE* shapeA, const SHAPE* shapeB ) -> bool
{
VECTOR2I ptA, ptB;
if( nearestPointsSingleShapes( shapeA, shapeB, ptA, ptB ) )
{
int64_t distSq = ( ptB - ptA ).SquaredEuclideanNorm();
if( distSq < minDistSq )
{
minDistSq = distSq;
aPtA = ptA;
aPtB = ptB;
found = true;
}
return true;
}
return false;
};
if( aA->Type() == SH_COMPOUND && aB->Type() == SH_COMPOUND )
{
const SHAPE_COMPOUND* cmpA = static_cast<const SHAPE_COMPOUND*>( aA );
const SHAPE_COMPOUND* cmpB = static_cast<const SHAPE_COMPOUND*>( aB );
for( const SHAPE* elemA : cmpA->Shapes() )
{
for( const SHAPE* elemB : cmpB->Shapes() )
{
checkNearestPoints( elemA, elemB );
}
}
}
else if( aA->Type() == SH_COMPOUND )
{
const SHAPE_COMPOUND* cmpA = static_cast<const SHAPE_COMPOUND*>( aA );
for( const SHAPE* elemA : cmpA->Shapes() )
{
checkNearestPoints( elemA, aB );
}
}
else if( aB->Type() == SH_COMPOUND )
{
const SHAPE_COMPOUND* cmpB = static_cast<const SHAPE_COMPOUND*>( aB );
for( const SHAPE* elemB : cmpB->Shapes() )
{
checkNearestPoints( aA, elemB );
}
}
else
{
return nearestPointsSingleShapes( aA, aB, aPtA, aPtB );
}
return found;
}
/**
* Public interface for finding nearest points between two shapes.
*
* @param aA first shape
* @param aB second shape
* @param aPtA [out] nearest point on first shape
* @param aPtB [out] nearest point on second shape
* @return true if nearest points were found, false otherwise
*/
bool SHAPE::NearestPoints( const SHAPE* aOther, VECTOR2I& aPtThis, VECTOR2I& aPtOther ) const
{
return nearestPoints( this, aOther, aPtThis, aPtOther );
}

View File

@ -101,35 +101,17 @@ void DRC_TEST_PROVIDER_CLEARANCE_BASE::ReportAndShowPathCuToCu( std::shared_ptr<
const BOARD_ITEM* aItem2,
PCB_LAYER_ID layer, int aDistance )
{
CREEPAGE_GRAPH graph( *m_board );
std::shared_ptr<GRAPH_NODE> NetA = graph.AddNodeVirtual();
std::shared_ptr<GRAPH_NODE> NetB = graph.AddNodeVirtual();
std::shared_ptr<SHAPE> aShape1 = aItem1->GetEffectiveShape( layer );
std::shared_ptr<SHAPE> aShape2 = aItem2->GetEffectiveShape( layer );
// They need to be different or the algorithm won't compute the path.
NetA->m_net = 1;
NetB->m_net = 2;
VECTOR2I ptA, ptB;
graph.Addshape( *( aItem1->GetEffectiveShape( layer ) ), NetA, nullptr );
graph.Addshape( *( aItem2->GetEffectiveShape( layer ) ), NetB, nullptr );
graph.GeneratePaths( aDistance * 2, layer );
double minValue = aDistance * 2;
GRAPH_CONNECTION* minGc = nullptr;
for( std::shared_ptr<GRAPH_CONNECTION> gc : graph.m_connections )
if( aShape1->NearestPoints( aShape2.get(), ptA, ptB ) )
{
if( ( gc->m_path.weight < minValue ) && ( gc->m_path.weight > 0 ) )
{
minValue = gc->m_path.weight;
minGc = gc.get();
}
}
if( minGc )
{
PATH_CONNECTION pc = minGc->m_path;
DRC_CUSTOM_MARKER_HANDLER handler = GetGraphicsHandler( minGc->GetShapes(), pc.a1, pc.a2, aDistance );
PCB_SHAPE ptAShape( nullptr, SHAPE_T::SEGMENT );
ptAShape.SetStart( ptA );
ptAShape.SetEnd( ptB );
DRC_CUSTOM_MARKER_HANDLER handler = GetGraphicsHandler( { ptAShape }, ptA, ptB, aDistance );
reportViolation( aDrce, aMarkerPos, aMarkerLayer, &handler );
}
else

View File

@ -40,6 +40,7 @@ set( QA_KIMATH_SRCS
geometry/test_segment.cpp
geometry/test_shape_compound_collision.cpp
geometry/test_shape_arc.cpp
geometry/test_shape_nearest_points.cpp
geometry/test_shape_poly_set.cpp
geometry/test_shape_poly_set_arcs.cpp
geometry/test_shape_poly_set_collision.cpp

View File

@ -0,0 +1,599 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3
* 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:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the https://www.gnu.org website for the version 3 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_circle.h>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_compound.h>
#include <geometry/shape_poly_set.h>
#include "fixtures_geometry.h"
BOOST_AUTO_TEST_SUITE( SHAPE_NEAREST_POINTS_TEST )
// Circle to Circle tests
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Separate )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 );
SHAPE_CIRCLE circleB( VECTOR2I( 20, 0 ), 5 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 0 ), "Expected: " << VECTOR2I( 15, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Concentric )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 );
SHAPE_CIRCLE circleB( VECTOR2I( 0, 0 ), 10 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
// For concentric circles, points should be on arbitrary direction (positive X by convention)
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Overlapping )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 );
SHAPE_CIRCLE circleB( VECTOR2I( 6, 0 ), 5 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 1, 0 ), "Expected: " << VECTOR2I( 1, 0 ) << " Actual: " << ptB );
}
// Circle to Rectangle tests
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToRect_Outside )
{
SHAPE_CIRCLE circle( VECTOR2I( -10, 5 ), 3 );
SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &rect, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( -7, 5 ), "Expected: " << VECTOR2I( -7, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToRect_Inside )
{
SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 );
SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &rect, ptA, ptB );
BOOST_CHECK( result );
// Circle center is inside rectangle, should find nearest edge
// Center at (5,5) is equidistant from left/right edges (5 units) and top/bottom edges (5 units)
// Implementation should choose one consistently - likely left edge based on min comparison
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 3, 5 ), "Expected: " << VECTOR2I( 3, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptB );
}
// Circle to Segment tests
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToSegment )
{
SHAPE_CIRCLE circle( VECTOR2I( 0, 5 ), 2 );
SHAPE_SEGMENT segment( VECTOR2I( -5, 0 ), VECTOR2I( 5, 0 ) );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &segment, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 3 ), "Expected: " << VECTOR2I( 0, 3 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToSegment_CenterOnSegment )
{
SHAPE_CIRCLE circle( VECTOR2I( 0, 0 ), 3 );
SHAPE_SEGMENT segment( VECTOR2I( -5, 0 ), VECTOR2I( 5, 0 ) );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &segment, ptA, ptB );
BOOST_CHECK( result );
// When center is on segment, should pick perpendicular direction
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 3 ), "Expected: " << VECTOR2I( 0, 3 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptB );
}
// Rectangle to Rectangle tests
BOOST_AUTO_TEST_CASE( NearestPoints_RectToRect_Separate )
{
SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) );
SHAPE_RECT rectB( VECTOR2I( 10, 0 ), VECTOR2I( 25, 25 ) );
VECTOR2I ptA, ptB;
bool result = rectA.NearestPoints( &rectB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_RectToRect_Corner )
{
SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) );
SHAPE_RECT rectB( VECTOR2I( 7, 7 ), VECTOR2I( 25, 25 ) );
VECTOR2I ptA, ptB;
bool result = rectA.NearestPoints( &rectB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 7, 7 ), "Expected: " << VECTOR2I( 7, 7 ) << " Actual: " << ptB );
}
// Line Chain tests
BOOST_AUTO_TEST_CASE( NearestPoints_LineChainToLineChain )
{
SHAPE_LINE_CHAIN chainA;
chainA.Append( VECTOR2I( 0, 0 ) );
chainA.Append( VECTOR2I( 10, 0 ) );
chainA.Append( VECTOR2I( 10, 10 ) );
SHAPE_LINE_CHAIN chainB;
chainB.Append( VECTOR2I( 5, 5 ) );
chainB.Append( VECTOR2I( 15, 5 ) );
VECTOR2I ptA, ptB;
bool result = chainA.NearestPoints( &chainB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_LineChainWithArc )
{
SHAPE_LINE_CHAIN chainA;
chainA.Append( VECTOR2I( 0, 0 ) );
chainA.Append( VECTOR2I( 10, 0 ) );
SHAPE_LINE_CHAIN chainB;
chainB.Append( SHAPE_ARC( VECTOR2I( 5, 10 ), VECTOR2I( 10, 5 ), VECTOR2I( 15, 10 ), 0 ) );
VECTOR2I ptA, ptB;
bool result = chainA.NearestPoints( &chainB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 10 ), "Expected: " << VECTOR2I( 10, 10 ) << " Actual: " << ptB );
}
// Arc tests
BOOST_AUTO_TEST_CASE( NearestPoints_ArcToArc )
{
SHAPE_ARC arcA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 );
SHAPE_ARC arcB( VECTOR2I( 15, 0 ), VECTOR2I( 20, 5 ), VECTOR2I( 25, 0 ), 0 );
VECTOR2I ptA, ptB;
int64_t distSq;
bool result = arcA.NearestPoints( arcB, ptA, ptB, distSq );
BOOST_CHECK( result );
// Points should be on the arcs closest to each other
BOOST_CHECK( ptA.x <= 10 && ptA.x >= 0 );
BOOST_CHECK( ptB.x >= 15 && ptB.x <= 25 );
}
BOOST_AUTO_TEST_CASE( NearestPoints_ArcToCircle )
{
SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 );
SHAPE_CIRCLE circle( VECTOR2I( 5, 10 ), 3 );
VECTOR2I ptA, ptB;
int64_t distSq;
bool result = arc.NearestPoints( circle, ptA, ptB, distSq );
BOOST_CHECK( result );
// Arc point should be at or near the top of the arc
BOOST_CHECK( ptA.y >= 0 );
// Circle point should be at bottom of circle
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 7 ), "Expected: " << VECTOR2I( 5, 7 ) << " Actual: " << ptB );
}
// Segment tests
BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToSegment )
{
SHAPE_SEGMENT segA( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 2 );
SHAPE_SEGMENT segB( SEG( VECTOR2I( 5, 5 ), VECTOR2I( 5, 15 ) ), 2 );
VECTOR2I ptA, ptB;
bool result = segA.NearestPoints( &segB, ptA, ptB );
BOOST_CHECK( result );
// Points should account for segment width
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 1 ), "Expected: " << VECTOR2I( 5, 1 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 4 ), "Expected: " << VECTOR2I( 5, 4 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToCircle )
{
SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 4 );
SHAPE_CIRCLE circle( VECTOR2I( 5, 10 ), 3 );
VECTOR2I ptA, ptB;
bool result = segment.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 2 ), "Expected: " << VECTOR2I( 5, 2 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 7 ), "Expected: " << VECTOR2I( 5, 7 ) << " Actual: " << ptB );
}
// Compound Shape tests
BOOST_AUTO_TEST_CASE( NearestPoints_CompoundShapes )
{
SHAPE_COMPOUND compoundA;
compoundA.AddShape( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 5 ) );
compoundA.AddShape( new SHAPE_RECT( VECTOR2I( 10, 0 ), VECTOR2I( 5, 5 ) ) );
SHAPE_COMPOUND compoundB;
compoundB.AddShape( new SHAPE_CIRCLE( VECTOR2I( 20, 0 ), 3 ) );
VECTOR2I ptA, ptB;
bool result = compoundA.NearestPoints( &compoundB, ptA, ptB );
BOOST_CHECK( result );
// Should find nearest points between rectangle in A and circle in B
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 17, 0 ), "Expected: " << VECTOR2I( 17, 0 ) << " Actual: " << ptB );
}
// Polygon Set tests
BOOST_AUTO_TEST_CASE( NearestPoints_PolySetToCircle )
{
SHAPE_POLY_SET polySet;
polySet.NewOutline();
polySet.Append( VECTOR2I( 0, 0 ) );
polySet.Append( VECTOR2I( 10, 0 ) );
polySet.Append( VECTOR2I( 10, 10 ) );
polySet.Append( VECTOR2I( 0, 10 ) );
SHAPE_CIRCLE circle( VECTOR2I( 15, 5 ), 3 );
VECTOR2I ptA, ptB;
bool result = polySet.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 12, 5 ), "Expected: " << VECTOR2I( 12, 5 ) << " Actual: " << ptB );
}
// Missing Basic Shape Combinations
BOOST_AUTO_TEST_CASE( NearestPoints_RectToSegment )
{
SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) );
SHAPE_SEGMENT segment( VECTOR2I( 15, 5 ), VECTOR2I( 25, 5 ) );
VECTOR2I ptA, ptB;
bool result = rect.NearestPoints( &segment, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 5 ), "Expected: " << VECTOR2I( 15, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_RectToLineChain )
{
SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) );
SHAPE_LINE_CHAIN chain;
chain.Append( VECTOR2I( 10, 0 ) );
chain.Append( VECTOR2I( 15, 5 ) );
chain.Append( VECTOR2I( 10, 10 ) );
VECTOR2I ptA, ptB;
bool result = rect.NearestPoints( &chain, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToLineChain )
{
SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 2 );
SHAPE_LINE_CHAIN chain;
chain.Append( VECTOR2I( 5, 10 ) );
chain.Append( VECTOR2I( 15, 10 ) );
chain.Append( VECTOR2I( 15, 5 ) );
VECTOR2I ptA, ptB;
bool result = segment.NearestPoints( &chain, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 11, 1 ), "Expected: " << VECTOR2I( 11, 1 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 5 ), "Expected: " << VECTOR2I( 15, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_CircleToLineChain )
{
SHAPE_CIRCLE circle( VECTOR2I( 0, 0 ), 3 );
SHAPE_LINE_CHAIN chain;
chain.Append( VECTOR2I( 10, -5 ) );
chain.Append( VECTOR2I( 10, 0 ) );
chain.Append( VECTOR2I( 10, 5 ) );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &chain, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 3, 0 ), "Expected: " << VECTOR2I( 3, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_ArcToRect )
{
SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 );
SHAPE_RECT rect( VECTOR2I( 150, 2 ), VECTOR2I( 50, 6 ) );
VECTOR2I ptA, ptB;
int64_t distSq;
bool result = arc.NearestPoints( rect, ptA, ptB, distSq );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 50, 2 ), "Expected: " << VECTOR2I( 50, 2 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_ArcToSegment )
{
SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 0, 10 ), VECTOR2I( 0, 20 ), 0 ); // Semicircle
SEG segment( VECTOR2I( 15, 10 ), VECTOR2I( 25, 10 ) );
VECTOR2I ptA, ptB;
int64_t distSq;
bool result = arc.NearestPoints( segment, ptA, ptB, distSq );
BOOST_CHECK( result );
// Arc point should be on the rightmost part of the arc
BOOST_CHECK( ptA.x >= 0 );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 10 ), "Expected: " << VECTOR2I( 15, 10 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_ArcToLineChain )
{
SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( VECTOR2I( 5, 15 ) );
chain.Append( VECTOR2I( 5, 10 ) );
chain.Append( VECTOR2I( 15, 10 ) );
VECTOR2I ptA, ptB;
int64_t distSq;
bool result = chain.NearestPoints( &arc, ptB, ptA );
BOOST_CHECK( result );
// Arc point should be near the top of the arc
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 10 ), "Expected: " << VECTOR2I( 5, 10 ) << " Actual: " << ptB );
}
// Distance and Symmetry Validation Tests
BOOST_AUTO_TEST_CASE( NearestPoints_DistanceValidation )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 );
SHAPE_CIRCLE circleB( VECTOR2I( 20, 0 ), 3 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
// Calculate expected distance: center distance - both radii
int expectedDistance = 20 - 5 - 3; // 12
int actualDistance = (ptB - ptA).EuclideanNorm();
BOOST_CHECK_MESSAGE( actualDistance == expectedDistance,
"Expected distance: " << expectedDistance << " Actual: " << actualDistance );
}
BOOST_AUTO_TEST_CASE( NearestPoints_SymmetryTest )
{
SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) );
SHAPE_CIRCLE circleB( VECTOR2I( 10, 2 ), 2 );
// Test A->B
VECTOR2I ptA1, ptB1;
bool result1 = rectA.NearestPoints( &circleB, ptA1, ptB1 );
// Test B->A
VECTOR2I ptA2, ptB2;
bool result2 = circleB.NearestPoints( &rectA, ptA2, ptB2 );
BOOST_CHECK( result1 && result2 );
// Distance should be the same both ways
int dist1 = (ptB1 - ptA1).EuclideanNorm();
int dist2 = (ptB2 - ptA2).EuclideanNorm();
BOOST_CHECK_MESSAGE( dist1 == dist2, "Distance A->B: " << dist1 << " Distance B->A: " << dist2 );
// Points should be swapped (ptA1 should equal ptB2, ptB1 should equal ptA2)
BOOST_CHECK_MESSAGE( ptA1 == ptB2, "Expected ptA1 == ptB2. ptA1: " << ptA1 << " ptB2: " << ptB2 );
BOOST_CHECK_MESSAGE( ptB1 == ptA2, "Expected ptB1 == ptA2. ptB1: " << ptB1 << " ptA2: " << ptA2 );
}
BOOST_AUTO_TEST_CASE( NearestPoints_MinimumDistanceValidation )
{
SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 0 );
SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 );
VECTOR2I ptA, ptB;
bool result = segment.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( result );
// Verify that this is indeed the minimum distance by checking nearby points
int minDist = (ptB - ptA).EuclideanNorm();
// Check a few other points on the segment
for( int x = 0; x <= 10; x += 2 )
{
VECTOR2I testPt( x, 0 );
VECTOR2I circleClosest = circle.GetCenter() +
(testPt - circle.GetCenter()).Resize( circle.GetRadius() );
int testDist = (circleClosest - testPt).EuclideanNorm();
BOOST_CHECK_MESSAGE( minDist <= testDist,
"Found shorter distance at x=" << x << ": " << testDist << " vs " << minDist );
}
}
// Complex Line Chain Tests
BOOST_AUTO_TEST_CASE( NearestPoints_LineChainWithMixedArcs )
{
SHAPE_LINE_CHAIN chainA;
chainA.Append( VECTOR2I( 0, 0 ) );
chainA.Append( VECTOR2I( 10, 0 ) );
chainA.Append( SHAPE_ARC( VECTOR2I( 10, 0 ), VECTOR2I( 15, 5 ), VECTOR2I( 20, 0 ), 0 ) );
chainA.Append( VECTOR2I( 30, 0 ) );
SHAPE_CIRCLE circle( VECTOR2I( 15, 15 ), 3 );
VECTOR2I ptA, ptB;
bool result = chainA.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 12 ), "Expected: " << VECTOR2I( 15, 12 ) << " Actual: " << ptB );
}
// Degenerate Shape Tests
BOOST_AUTO_TEST_CASE( NearestPoints_DegenerateShapes_ZeroWidthRect )
{
SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 0, 10 ) ); // Zero width
SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 );
VECTOR2I ptA, ptB;
bool result = rectA.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 3, 5 ), "Expected: " << VECTOR2I( 3, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_DegenerateShapes_ZeroLengthSegment )
{
SHAPE_SEGMENT zeroSeg( VECTOR2I( 5, 5 ), VECTOR2I( 5, 5 ) ); // Point segment
SHAPE_CIRCLE circle( VECTOR2I( 10, 5 ), 3 );
VECTOR2I ptA, ptB;
bool result = circle.NearestPoints( &zeroSeg, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 7, 5 ), "Expected: " << VECTOR2I( 7, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_SinglePointLineChain )
{
SHAPE_LINE_CHAIN singlePoint;
singlePoint.Append( VECTOR2I( 0, 0 ) );
SHAPE_CIRCLE circle( VECTOR2I( 5, 0 ), 2 );
VECTOR2I ptA, ptB;
bool result = singlePoint.NearestPoints( &circle, ptA, ptB );
BOOST_CHECK( !result );
}
// Negative Coordinate Tests
BOOST_AUTO_TEST_CASE( NearestPoints_NegativeCoordinates )
{
SHAPE_CIRCLE circleA( VECTOR2I( -10, -5 ), 3 );
SHAPE_RECT rect( VECTOR2I( 0, -2 ), VECTOR2I( 5, 4 ) );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &rect, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( -7, -4 ), "Expected: " << VECTOR2I( -7, -4 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, -2 ), "Expected: " << VECTOR2I( 0, -2 ) << " Actual: " << ptB );
}
// Edge case tests
BOOST_AUTO_TEST_CASE( NearestPoints_IdenticalShapes )
{
SHAPE_CIRCLE circleA( VECTOR2I( 5, 5 ), 3 );
SHAPE_CIRCLE circleB( VECTOR2I( 5, 5 ), 3 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
// Identical concentric circles - should return arbitrary points
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 8, 5 ), "Expected: " << VECTOR2I( 8, 5 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 8, 5 ), "Expected: " << VECTOR2I( 8, 5 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_ZeroSizeShapes )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 0 );
SHAPE_CIRCLE circleB( VECTOR2I( 10, 0 ), 5 );
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_CASE( NearestPoints_VeryCloseShapes )
{
SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 );
SHAPE_CIRCLE circleB( VECTOR2I( 10, 0 ), 5 ); // Just touching
VECTOR2I ptA, ptB;
bool result = circleA.NearestPoints( &circleB, ptA, ptB );
BOOST_CHECK( result );
BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA );
BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptB );
}
BOOST_AUTO_TEST_SUITE_END()