2018-02-04 13:09:30 +01:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 CERN
|
2024-04-23 19:19:49 +03:00
|
|
|
* Copyright (C) 2019-2024 KiCad Developers, see AUTHORS.txt for contributors.
|
2018-02-04 13:09:30 +01:00
|
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2021-07-25 20:46:00 +01:00
|
|
|
#include <core/kicad_algo.h>
|
2018-04-24 16:28:13 -07:00
|
|
|
#include <geometry/geometry_utils.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <geometry/seg.h> // for SEG
|
2018-02-04 13:09:30 +01:00
|
|
|
#include <geometry/shape_arc.h>
|
2024-05-20 17:22:07 -07:00
|
|
|
#include <geometry/shape_circle.h>
|
2018-02-04 13:09:30 +01:00
|
|
|
#include <geometry/shape_line_chain.h>
|
2023-08-15 23:49:22 -04:00
|
|
|
#include <convert_basic_shapes_to_polygon.h>
|
2020-07-03 16:12:28 +01:00
|
|
|
#include <trigo.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2021-03-14 20:28:49 +00:00
|
|
|
std::ostream& operator<<( std::ostream& aStream, const SHAPE_ARC& aArc )
|
|
|
|
{
|
|
|
|
aStream << "Arc( P0=" << aArc.GetP0() << " P1=" << aArc.GetP1() << " Mid=" << aArc.GetArcMid()
|
|
|
|
<< " Width=" << aArc.GetWidth() << " )";
|
|
|
|
return aStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
SHAPE_ARC::SHAPE_ARC( const VECTOR2I& aArcCenter, const VECTOR2I& aArcStartPoint,
|
2022-01-14 15:34:41 +00:00
|
|
|
const EDA_ANGLE& aCenterAngle, int aWidth ) :
|
|
|
|
SHAPE( SH_ARC ),
|
|
|
|
m_width( aWidth )
|
2020-06-12 14:58:24 -07:00
|
|
|
{
|
|
|
|
m_start = aArcStartPoint;
|
|
|
|
|
2022-03-04 13:14:19 -08:00
|
|
|
VECTOR2D mid = aArcStartPoint;
|
|
|
|
VECTOR2D end = aArcStartPoint;
|
|
|
|
VECTOR2D center = aArcCenter;
|
|
|
|
|
|
|
|
RotatePoint( mid, center, -aCenterAngle / 2.0 );
|
|
|
|
RotatePoint( end, center, -aCenterAngle );
|
|
|
|
|
|
|
|
m_mid = VECTOR2I( KiROUND( mid.x ), KiROUND( mid.y ) );
|
|
|
|
m_end = VECTOR2I( KiROUND( end.x ), KiROUND( end.y ) );
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_ARC::SHAPE_ARC( const VECTOR2I& aArcStart, const VECTOR2I& aArcMid,
|
2020-07-03 16:12:28 +01:00
|
|
|
const VECTOR2I& aArcEnd, int aWidth ) :
|
2022-01-14 15:34:41 +00:00
|
|
|
SHAPE( SH_ARC ),
|
|
|
|
m_start( aArcStart ),
|
|
|
|
m_mid( aArcMid ),
|
|
|
|
m_end( aArcEnd ),
|
|
|
|
m_width( aWidth )
|
2020-06-12 14:58:24 -07:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
SHAPE_ARC::SHAPE_ARC( const SEG& aSegmentA, const SEG& aSegmentB, int aRadius, int aWidth ) :
|
|
|
|
SHAPE( SH_ARC )
|
2020-10-20 21:23:05 +00:00
|
|
|
{
|
2020-10-25 10:02:07 +01:00
|
|
|
m_width = aWidth;
|
|
|
|
|
2020-10-20 21:23:05 +00:00
|
|
|
/*
|
|
|
|
* Construct an arc that is tangent to two segments with a given radius.
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2020-10-20 21:23:05 +00:00
|
|
|
* p
|
|
|
|
* A
|
|
|
|
* A \
|
|
|
|
* / \
|
2021-02-03 23:33:36 +00:00
|
|
|
* / . . \ segB
|
|
|
|
* /. .\
|
2020-10-20 21:23:05 +00:00
|
|
|
* segA / c \
|
|
|
|
* / B
|
|
|
|
* /
|
|
|
|
* /
|
|
|
|
* B
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2020-10-20 21:23:05 +00:00
|
|
|
*
|
|
|
|
* segA is the fist segment (with its points A and B)
|
|
|
|
* segB is the second segment (with its points A and B)
|
|
|
|
* p is the point at which segA and segB would intersect if they were projected
|
|
|
|
* c is the centre of the arc to be constructed
|
|
|
|
* rad is the radius of the arc to be constructed
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2021-06-09 19:32:58 +00:00
|
|
|
* We can create two vectors, between point p and segA /segB
|
2020-10-20 21:23:05 +00:00
|
|
|
* pToA = p - segA.B //< note that segA.A would also be valid as it is colinear
|
|
|
|
* pToB = p - segB.B //< note that segB.A would also be valid as it is colinear
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
|
|
|
* Let the angle formed by segA and segB be called 'alpha':
|
2020-10-20 21:23:05 +00:00
|
|
|
* alpha = angle( pToA ) - angle( pToB )
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2020-10-20 21:23:05 +00:00
|
|
|
* The distance PC can be computed as
|
|
|
|
* distPC = rad / abs( sin( alpha / 2 ) )
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2020-10-20 21:23:05 +00:00
|
|
|
* The polar angle of the vector PC can be computed as:
|
|
|
|
* anglePC = angle( pToA ) + alpha / 2
|
2020-10-25 10:02:07 +01:00
|
|
|
*
|
2020-10-20 21:23:05 +00:00
|
|
|
* Therefore:
|
|
|
|
* C.x = P.x + distPC*cos( anglePC )
|
|
|
|
* C.y = P.y + distPC*sin( anglePC )
|
|
|
|
*/
|
|
|
|
|
|
|
|
OPT_VECTOR2I p = aSegmentA.Intersect( aSegmentB, true, true );
|
|
|
|
|
|
|
|
if( !p || aSegmentA.Length() == 0 || aSegmentB.Length() == 0 )
|
|
|
|
{
|
|
|
|
// Catch bugs in debug
|
|
|
|
wxASSERT_MSG( false, "The input segments do not intersect or one is zero length." );
|
|
|
|
|
|
|
|
// Make a 180 degree arc around aSegmentA in case we end up here in release
|
|
|
|
m_start = aSegmentA.A;
|
|
|
|
m_end = aSegmentA.B;
|
|
|
|
m_mid = m_start;
|
|
|
|
|
|
|
|
VECTOR2I arcCenter = aSegmentA.Center();
|
2022-01-16 21:15:20 +00:00
|
|
|
RotatePoint( m_mid, arcCenter, ANGLE_90 ); // mid point at 90 degrees
|
2020-10-20 21:23:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-30 13:58:43 -07:00
|
|
|
VECTOR2I pToA = aSegmentA.B - *p;
|
|
|
|
VECTOR2I pToB = aSegmentB.B - *p;
|
2020-10-20 21:23:05 +00:00
|
|
|
|
|
|
|
if( pToA.EuclideanNorm() == 0 )
|
2022-08-30 13:58:43 -07:00
|
|
|
pToA = aSegmentA.A - *p;
|
2020-10-25 10:02:07 +01:00
|
|
|
|
2020-10-20 21:23:05 +00:00
|
|
|
if( pToB.EuclideanNorm() == 0 )
|
2022-08-30 13:58:43 -07:00
|
|
|
pToB = aSegmentB.A - *p;
|
2020-10-20 21:23:05 +00:00
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
EDA_ANGLE pToAangle( pToA );
|
|
|
|
EDA_ANGLE pToBangle( pToB );
|
2020-10-20 21:23:05 +00:00
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
EDA_ANGLE alpha = ( pToAangle - pToBangle ).Normalize180();
|
2020-10-20 21:23:05 +00:00
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
double distPC = (double) aRadius / abs( sin( alpha.AsRadians() / 2 ) );
|
|
|
|
EDA_ANGLE angPC = pToAangle - alpha / 2;
|
|
|
|
VECTOR2I arcCenter;
|
2020-10-20 21:23:05 +00:00
|
|
|
|
2022-08-30 13:58:43 -07:00
|
|
|
arcCenter.x = p->x + KiROUND( distPC * angPC.Cos() );
|
|
|
|
arcCenter.y = p->y + KiROUND( distPC * angPC.Sin() );
|
2020-10-20 21:23:05 +00:00
|
|
|
|
|
|
|
// The end points of the arc are the orthogonal projected lines from the line segments
|
|
|
|
// to the center of the arc
|
|
|
|
m_start = aSegmentA.LineProject( arcCenter );
|
|
|
|
m_end = aSegmentB.LineProject( arcCenter );
|
|
|
|
|
|
|
|
//The mid point is rotated start point around center, half the angle of the arc.
|
|
|
|
VECTOR2I startVector = m_start - arcCenter;
|
|
|
|
VECTOR2I endVector = m_end - arcCenter;
|
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
EDA_ANGLE startAngle( startVector );
|
|
|
|
EDA_ANGLE endAngle( endVector );
|
|
|
|
EDA_ANGLE midPointRotAngle = ( startAngle - endAngle ).Normalize180() / 2;
|
2020-10-20 21:23:05 +00:00
|
|
|
|
|
|
|
m_mid = m_start;
|
|
|
|
RotatePoint( m_mid, arcCenter, midPointRotAngle );
|
|
|
|
}
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-10-20 21:23:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
SHAPE_ARC::SHAPE_ARC( const SHAPE_ARC& aOther )
|
|
|
|
: SHAPE( SH_ARC )
|
|
|
|
{
|
|
|
|
m_start = aOther.m_start;
|
|
|
|
m_end = aOther.m_end;
|
|
|
|
m_mid = aOther.m_mid;
|
|
|
|
m_width = aOther.m_width;
|
|
|
|
m_bbox = aOther.m_bbox;
|
2024-04-23 19:19:49 +03:00
|
|
|
m_center = aOther.m_center;
|
|
|
|
m_radius = aOther.m_radius;
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-31 11:56:49 -05:00
|
|
|
SHAPE_ARC& SHAPE_ARC::ConstructFromStartEndAngle( const VECTOR2I& aStart, const VECTOR2I& aEnd,
|
2022-01-16 21:15:20 +00:00
|
|
|
const EDA_ANGLE& aAngle, double aWidth )
|
2020-12-31 11:56:49 -05:00
|
|
|
{
|
|
|
|
m_start = aStart;
|
|
|
|
m_mid = aStart;
|
|
|
|
m_end = aEnd;
|
|
|
|
m_width = aWidth;
|
|
|
|
|
2021-07-17 20:56:18 +01:00
|
|
|
VECTOR2I center( CalcArcCenter( aStart, aEnd, aAngle ) );
|
2020-12-31 11:56:49 -05:00
|
|
|
|
2022-01-16 21:15:20 +00:00
|
|
|
RotatePoint( m_mid, center, -aAngle / 2.0 );
|
2020-12-31 11:56:49 -05:00
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-12-31 11:56:49 -05:00
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-09 23:27:02 +01:00
|
|
|
SHAPE_ARC& SHAPE_ARC::ConstructFromStartEndCenter( const VECTOR2I& aStart, const VECTOR2I& aEnd,
|
|
|
|
const VECTOR2I& aCenter, bool aClockwise,
|
|
|
|
double aWidth )
|
|
|
|
{
|
|
|
|
VECTOR2I startLine = aStart - aCenter;
|
|
|
|
VECTOR2I endLine = aEnd - aCenter;
|
|
|
|
|
2022-01-16 16:15:07 +00:00
|
|
|
EDA_ANGLE startAngle( startLine );
|
|
|
|
EDA_ANGLE endAngle( endLine );
|
|
|
|
|
|
|
|
startAngle.Normalize();
|
|
|
|
endAngle.Normalize();
|
|
|
|
|
|
|
|
EDA_ANGLE angle = endAngle - startAngle;
|
2021-05-09 23:27:02 +01:00
|
|
|
|
|
|
|
if( aClockwise )
|
2022-01-16 16:15:07 +00:00
|
|
|
angle = angle.Normalize() - ANGLE_360;
|
2021-05-09 23:27:02 +01:00
|
|
|
else
|
2022-01-16 16:15:07 +00:00
|
|
|
angle = angle.Normalize();
|
2021-05-09 23:27:02 +01:00
|
|
|
|
|
|
|
m_start = aStart;
|
|
|
|
m_end = aEnd;
|
|
|
|
m_mid = aStart;
|
|
|
|
|
|
|
|
RotatePoint( m_mid, aCenter, -angle / 2.0 );
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2021-05-09 23:27:02 +01:00
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
bool SHAPE_ARC::Collide( const SEG& aSeg, int aClearance, int* aActual, VECTOR2I* aLocation ) const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2020-07-02 17:06:09 +01:00
|
|
|
VECTOR2I center = GetCenter();
|
2024-05-20 17:22:07 -07:00
|
|
|
double radius = ( center - m_start ).EuclideanNorm();
|
|
|
|
SHAPE_CIRCLE circle( center, radius );
|
|
|
|
ecoord clearance_sq = SEG::Square( aClearance );
|
|
|
|
|
|
|
|
// Circle or at least an arc with less space remaining than the clearance
|
|
|
|
if( GetCentralAngle().AsDegrees() > 180.0
|
|
|
|
&& ( m_start - m_end ).SquaredEuclideanNorm() < clearance_sq )
|
|
|
|
{
|
|
|
|
ecoord a_dist_sq = ( aSeg.A - center ).SquaredEuclideanNorm();
|
|
|
|
ecoord b_dist_sq = ( aSeg.B - center ).SquaredEuclideanNorm();
|
|
|
|
ecoord radius_sq = SEG::Square( radius - aClearance );
|
|
|
|
|
|
|
|
if( a_dist_sq < radius_sq && b_dist_sq < radius_sq )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
return circle.Collide( aSeg, aClearance, aActual, aLocation );
|
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2021-11-12 15:40:22 +00:00
|
|
|
// Possible points of the collision are:
|
|
|
|
// 1. Intersetion of the segment with the full circle
|
|
|
|
// 2. Closest point on the segment to the center of the circle
|
2021-12-13 18:06:11 +00:00
|
|
|
// 3. Closest point on the segment to the end points of the arc
|
2021-12-14 16:14:15 +00:00
|
|
|
// 4. End points of the segment
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2024-05-20 17:22:07 -07:00
|
|
|
std::vector<VECTOR2I> candidatePts = circle.GetCircle().Intersect( aSeg );
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2021-11-12 15:40:22 +00:00
|
|
|
candidatePts.push_back( aSeg.NearestPoint( center ) );
|
2021-12-09 00:35:33 +01:00
|
|
|
candidatePts.push_back( aSeg.NearestPoint( m_start ) );
|
|
|
|
candidatePts.push_back( aSeg.NearestPoint( m_end ) );
|
2021-12-14 16:14:15 +00:00
|
|
|
candidatePts.push_back( aSeg.A );
|
|
|
|
candidatePts.push_back( aSeg.B );
|
2020-09-28 23:27:33 +01:00
|
|
|
|
2024-03-11 16:28:51 -07:00
|
|
|
bool any_collides = false;
|
|
|
|
|
2021-11-12 15:40:22 +00:00
|
|
|
for( const VECTOR2I& candidate : candidatePts )
|
2020-09-28 23:27:33 +01:00
|
|
|
{
|
2024-03-11 16:28:51 -07:00
|
|
|
bool collides = Collide( candidate, aClearance, aActual, aLocation );
|
|
|
|
any_collides |= collides;
|
|
|
|
|
|
|
|
if( collides && ( !aActual || *aActual == 0 ) )
|
2021-11-12 15:40:22 +00:00
|
|
|
return true;
|
2020-07-02 17:06:09 +01:00
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2024-03-11 16:28:51 -07:00
|
|
|
return any_collides;
|
2018-02-04 13:09:30 +01:00
|
|
|
}
|
|
|
|
|
2019-05-16 17:13:21 -07:00
|
|
|
|
2021-07-25 20:46:00 +01:00
|
|
|
int SHAPE_ARC::IntersectLine( const SEG& aSeg, std::vector<VECTOR2I>* aIpsBuffer ) const
|
|
|
|
{
|
2022-10-09 23:03:29 +01:00
|
|
|
if( aSeg.A == aSeg.B ) // One point does not define a line....
|
|
|
|
return 0;
|
|
|
|
|
2021-07-25 20:46:00 +01:00
|
|
|
CIRCLE circ( GetCenter(), GetRadius() );
|
|
|
|
|
|
|
|
std::vector<VECTOR2I> intersections = circ.IntersectLine( aSeg );
|
|
|
|
|
|
|
|
size_t originalSize = aIpsBuffer->size();
|
|
|
|
|
|
|
|
for( const VECTOR2I& intersection : intersections )
|
|
|
|
{
|
|
|
|
if( sliceContainsPoint( intersection ) )
|
|
|
|
aIpsBuffer->push_back( intersection );
|
|
|
|
}
|
|
|
|
|
|
|
|
return aIpsBuffer->size() - originalSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SHAPE_ARC::Intersect( const SHAPE_ARC& aArc, std::vector<VECTOR2I>* aIpsBuffer ) const
|
|
|
|
{
|
|
|
|
CIRCLE thiscirc( GetCenter(), GetRadius() );
|
|
|
|
CIRCLE othercirc( aArc.GetCenter(), aArc.GetRadius() );
|
|
|
|
|
|
|
|
std::vector<VECTOR2I> intersections = thiscirc.Intersect( othercirc );
|
|
|
|
|
|
|
|
size_t originalSize = aIpsBuffer->size();
|
|
|
|
|
|
|
|
for( const VECTOR2I& intersection : intersections )
|
|
|
|
{
|
|
|
|
if( sliceContainsPoint( intersection ) && aArc.sliceContainsPoint( intersection ) )
|
|
|
|
aIpsBuffer->push_back( intersection );
|
|
|
|
}
|
|
|
|
|
|
|
|
return aIpsBuffer->size() - originalSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
void SHAPE_ARC::update_values()
|
2018-04-19 13:20:25 -07:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
m_center = CalcArcCenter( m_start, m_mid, m_end );
|
|
|
|
m_radius = std::sqrt( ( VECTOR2D( m_start ) - m_center ).SquaredEuclideanNorm() );
|
|
|
|
|
2018-04-19 13:20:25 -07:00
|
|
|
std::vector<VECTOR2I> points;
|
2020-01-03 14:27:00 +01:00
|
|
|
// Put start and end points in the point list
|
2020-06-12 14:58:24 -07:00
|
|
|
points.push_back( m_start );
|
|
|
|
points.push_back( m_end );
|
2018-04-19 13:20:25 -07:00
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE start_angle = GetStartAngle();
|
|
|
|
EDA_ANGLE end_angle = start_angle + GetCentralAngle();
|
2019-01-09 12:01:00 +00:00
|
|
|
|
|
|
|
// we always count quadrants clockwise (increasing angle)
|
|
|
|
if( start_angle > end_angle )
|
|
|
|
std::swap( start_angle, end_angle );
|
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
int quad_angle_start = std::ceil( start_angle.AsDegrees() / 90.0 );
|
|
|
|
int quad_angle_end = std::floor( end_angle.AsDegrees() / 90.0 );
|
2019-01-09 12:01:00 +00:00
|
|
|
|
2024-02-29 18:01:13 +01:00
|
|
|
// very large radius means the arc is similar to a segment
|
|
|
|
// so do not try to add more points, center cannot be handled
|
|
|
|
// Very large is here > INT_MAX/2
|
2024-04-23 19:19:49 +03:00
|
|
|
if( m_radius < (double)INT_MAX/2.0 )
|
2019-01-09 12:01:00 +00:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
const int radius = KiROUND( m_radius );
|
2019-01-09 12:01:00 +00:00
|
|
|
|
2024-02-29 18:01:13 +01:00
|
|
|
// count through quadrants included in arc
|
|
|
|
for( int quad_angle = quad_angle_start; quad_angle <= quad_angle_end; ++quad_angle )
|
2019-01-09 12:01:00 +00:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
VECTOR2I quad_pt = m_center;
|
2024-02-29 18:01:13 +01:00
|
|
|
|
|
|
|
switch( quad_angle % 4 )
|
|
|
|
{
|
|
|
|
case 0: quad_pt += { radius, 0 }; break;
|
|
|
|
case 1: case -3: quad_pt += { 0, radius }; break;
|
|
|
|
case 2: case -2: quad_pt += { -radius, 0 }; break;
|
|
|
|
case 3: case -1: quad_pt += { 0, -radius }; break;
|
|
|
|
default:
|
|
|
|
assert( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
points.push_back( quad_pt );
|
2019-01-09 12:01:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-16 17:13:21 -07:00
|
|
|
m_bbox.Compute( points );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const BOX2I SHAPE_ARC::BBox( int aClearance ) const
|
|
|
|
{
|
|
|
|
BOX2I bbox( m_bbox );
|
2018-04-19 13:20:25 -07:00
|
|
|
|
2024-02-12 16:59:28 +00:00
|
|
|
if( m_width != 0 )
|
|
|
|
bbox.Inflate( KiROUND( m_width / 2.0 ) + 1 );
|
|
|
|
|
2018-04-19 13:20:25 -07:00
|
|
|
if( aClearance != 0 )
|
|
|
|
bbox.Inflate( aClearance );
|
|
|
|
|
|
|
|
return bbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-09 23:27:02 +01:00
|
|
|
bool SHAPE_ARC::IsClockwise() const
|
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
return GetCentralAngle() < ANGLE_0;
|
2021-05-09 23:27:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-31 23:38:05 +03:00
|
|
|
VECTOR2I SHAPE_ARC::NearestPoint( const VECTOR2I& aP ) const
|
|
|
|
{
|
|
|
|
const static int s_epsilon = 8;
|
|
|
|
|
|
|
|
CIRCLE fullCircle( GetCenter(), GetRadius() );
|
|
|
|
VECTOR2I nearestPt = fullCircle.NearestPoint( aP );
|
|
|
|
|
|
|
|
if( ( nearestPt - m_start ).SquaredEuclideanNorm() <= s_epsilon )
|
|
|
|
return m_start;
|
|
|
|
|
|
|
|
if( ( nearestPt - m_end ).SquaredEuclideanNorm() <= s_epsilon )
|
|
|
|
return m_end;
|
|
|
|
|
|
|
|
if( sliceContainsPoint( nearestPt ) )
|
|
|
|
return nearestPt;
|
|
|
|
|
|
|
|
if( ( aP - m_start ).SquaredEuclideanNorm() <= ( aP - m_end ).SquaredEuclideanNorm() )
|
|
|
|
return m_start;
|
|
|
|
else
|
|
|
|
return m_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
bool SHAPE_ARC::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
|
|
|
|
VECTOR2I* aLocation ) const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2022-04-11 13:38:57 +01:00
|
|
|
int minDist = aClearance + m_width / 2;
|
2019-05-16 17:13:21 -07:00
|
|
|
auto bbox = BBox( minDist );
|
|
|
|
|
2021-03-14 20:26:35 +00:00
|
|
|
// Fast check using bounding box:
|
2019-05-16 17:13:21 -07:00
|
|
|
if( !bbox.Contains( aP ) )
|
|
|
|
return false;
|
|
|
|
|
2024-05-20 17:22:07 -07:00
|
|
|
VECTOR2I center = GetCenter();
|
|
|
|
double radius = ( center - m_start ).EuclideanNorm();
|
|
|
|
CIRCLE fullCircle( center, radius );
|
|
|
|
VECTOR2I nearestPt = fullCircle.NearestPoint( aP );
|
|
|
|
int dist = ( nearestPt - aP ).EuclideanNorm();
|
|
|
|
EDA_ANGLE angleToPt( aP - fullCircle.Center ); // Angle from center to the point
|
2021-03-14 20:26:35 +00:00
|
|
|
|
2024-05-20 17:22:07 -07:00
|
|
|
if( !dist )
|
|
|
|
{
|
|
|
|
dist = radius - ( aP - center ).EuclideanNorm();
|
|
|
|
nearestPt = center + VECTOR2I( radius, 0 );
|
|
|
|
RotatePoint( nearestPt, center, angleToPt );
|
|
|
|
}
|
2021-03-14 20:26:35 +00:00
|
|
|
|
|
|
|
// If not a 360 degree arc, need to use arc angles to decide if point collides
|
|
|
|
if( m_start != m_end )
|
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
bool ccw = GetCentralAngle() > ANGLE_0;
|
2022-04-11 13:38:57 +01:00
|
|
|
EDA_ANGLE rotatedPtAngle = ( angleToPt.Normalize() - GetStartAngle() ).Normalize();
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE rotatedEndAngle = ( GetEndAngle() - GetStartAngle() ).Normalize();
|
2020-07-02 17:06:09 +01:00
|
|
|
|
2022-04-11 13:38:57 +01:00
|
|
|
if( ( ccw && rotatedPtAngle > rotatedEndAngle )
|
|
|
|
|| ( !ccw && rotatedPtAngle < rotatedEndAngle ) )
|
2021-03-14 20:26:35 +00:00
|
|
|
{
|
|
|
|
int distStartpt = ( aP - m_start ).EuclideanNorm();
|
|
|
|
int distEndpt = ( aP - m_end ).EuclideanNorm();
|
2024-05-20 17:22:07 -07:00
|
|
|
|
|
|
|
if( distStartpt < distEndpt )
|
|
|
|
{
|
|
|
|
dist = distStartpt;
|
|
|
|
nearestPt = m_start;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dist = distEndpt;
|
|
|
|
nearestPt = m_end;
|
|
|
|
}
|
2021-03-14 20:26:35 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-02 17:06:09 +01:00
|
|
|
|
2021-03-14 20:26:35 +00:00
|
|
|
if( dist <= minDist )
|
2020-07-02 17:06:09 +01:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
if( aLocation )
|
2022-12-18 15:40:15 +01:00
|
|
|
*aLocation = nearestPt;
|
2020-09-28 23:27:33 +01:00
|
|
|
|
2020-07-02 17:06:09 +01:00
|
|
|
if( aActual )
|
2021-03-14 20:26:35 +00:00
|
|
|
*aActual = std::max( 0, dist - m_width / 2 );
|
2020-07-02 17:06:09 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-05-16 17:13:21 -07:00
|
|
|
|
2020-07-02 17:06:09 +01:00
|
|
|
return false;
|
2018-02-04 13:09:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE SHAPE_ARC::GetStartAngle() const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE angle( m_start - GetCenter() );
|
|
|
|
return angle.Normalize();
|
2018-02-16 17:34:57 +01:00
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE SHAPE_ARC::GetEndAngle() const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE angle( m_end - GetCenter() );
|
|
|
|
return angle.Normalize();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
const VECTOR2I& SHAPE_ARC::GetCenter() const
|
2020-06-12 14:58:24 -07:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
return m_center;
|
2018-02-16 17:34:57 +01:00
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2021-05-10 21:22:51 -04:00
|
|
|
double SHAPE_ARC::GetLength() const
|
|
|
|
{
|
|
|
|
double radius = GetRadius();
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE includedAngle = GetCentralAngle();
|
2021-05-10 21:22:51 -04:00
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
return std::abs( radius * includedAngle.AsRadians() );
|
2021-05-10 21:22:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE SHAPE_ARC::GetCentralAngle() const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2022-12-17 14:44:53 +01:00
|
|
|
// Arcs with same start and end points can be 0 deg or 360 deg arcs.
|
|
|
|
// However, they are expected to be circles.
|
|
|
|
// So return 360 degrees as central arc:
|
|
|
|
if( m_start == m_end )
|
|
|
|
return ANGLE_360;
|
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
VECTOR2I center = GetCenter();
|
|
|
|
EDA_ANGLE angle1 = EDA_ANGLE( m_mid - center ) - EDA_ANGLE( m_start - center );
|
|
|
|
EDA_ANGLE angle2 = EDA_ANGLE( m_end - center ) - EDA_ANGLE( m_mid - center );
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
return angle1.Normalize180() + angle2.Normalize180();
|
2018-02-04 13:09:30 +01:00
|
|
|
}
|
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2020-06-13 11:21:14 -07:00
|
|
|
double SHAPE_ARC::GetRadius() const
|
2018-02-16 17:34:57 +01:00
|
|
|
{
|
2024-04-23 19:19:49 +03:00
|
|
|
return m_radius;
|
2018-02-16 17:34:57 +01:00
|
|
|
}
|
2018-02-15 10:22:47 +01:00
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2021-06-30 13:33:49 +02:00
|
|
|
const SHAPE_LINE_CHAIN SHAPE_ARC::ConvertToPolyline( double aAccuracy,
|
|
|
|
double* aEffectiveAccuracy ) const
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
|
|
|
SHAPE_LINE_CHAIN rv;
|
2022-01-15 12:52:20 +00:00
|
|
|
double r = GetRadius();
|
|
|
|
EDA_ANGLE sa = GetStartAngle();
|
|
|
|
VECTOR2I c = GetCenter();
|
|
|
|
EDA_ANGLE ca = GetCentralAngle();
|
2020-06-12 14:58:24 -07:00
|
|
|
|
2024-01-07 10:47:47 +01:00
|
|
|
SEG startToEnd( GetP0(), GetP1() );
|
|
|
|
double halfAccuracy = std::max( 1.0, aAccuracy / 2 );
|
|
|
|
|
2018-02-04 13:09:30 +01:00
|
|
|
int n;
|
|
|
|
|
2021-06-23 18:28:16 +02:00
|
|
|
// To calculate the arc to segment count, use the external radius instead of the radius.
|
|
|
|
// for a arc with small radius and large width, the difference can be significant
|
|
|
|
double external_radius = r+(m_width/2);
|
2021-06-30 13:33:49 +02:00
|
|
|
double effectiveAccuracy;
|
2021-06-23 18:28:16 +02:00
|
|
|
|
2024-01-07 10:47:47 +01:00
|
|
|
if( external_radius < halfAccuracy
|
|
|
|
|| startToEnd.Distance( GetArcMid() ) < halfAccuracy ) // Should be a very rare case
|
2021-06-30 13:33:49 +02:00
|
|
|
{
|
|
|
|
// In this case, the arc is approximated by one segment, with a effective error
|
|
|
|
// between -aAccuracy/2 and +aAccuracy/2, as expected.
|
2018-02-16 17:34:57 +01:00
|
|
|
n = 0;
|
2021-06-30 13:33:49 +02:00
|
|
|
effectiveAccuracy = external_radius;
|
|
|
|
}
|
2018-02-16 17:34:57 +01:00
|
|
|
else
|
2021-06-30 13:33:49 +02:00
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
n = GetArcToSegmentCount( external_radius, aAccuracy, ca );
|
2021-06-30 13:33:49 +02:00
|
|
|
|
|
|
|
// Recalculate the effective error of approximation, that can be < aAccuracy
|
2022-01-19 14:51:36 +00:00
|
|
|
int seg360 = n * 360.0 / fabs( ca.AsDegrees() );
|
2021-06-30 13:33:49 +02:00
|
|
|
effectiveAccuracy = CircleToEndSegmentDeltaRadius( external_radius, seg360 );
|
|
|
|
}
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2020-10-23 22:37:34 +01:00
|
|
|
// Split the error on either side of the arc. Since we want the start and end points
|
|
|
|
// to be exactly on the arc, the first and last segments need to be shorter to stay within
|
|
|
|
// the error band (since segments normally start 1/2 the error band outside the arc).
|
2021-06-30 13:33:49 +02:00
|
|
|
r += effectiveAccuracy / 2;
|
2020-10-23 22:37:34 +01:00
|
|
|
n = n * 2;
|
|
|
|
|
|
|
|
rv.Append( m_start );
|
|
|
|
|
|
|
|
for( int i = 1; i < n ; i += 2 )
|
2018-02-04 13:09:30 +01:00
|
|
|
{
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE a = sa;
|
2019-04-16 19:23:18 +01:00
|
|
|
|
|
|
|
if( n != 0 )
|
2020-06-12 14:58:24 -07:00
|
|
|
a += ( ca * i ) / n;
|
2019-04-16 19:23:18 +01:00
|
|
|
|
2022-01-16 21:15:20 +00:00
|
|
|
double x = c.x + r * a.Cos();
|
|
|
|
double y = c.y + r * a.Sin();
|
2018-02-04 13:09:30 +01:00
|
|
|
|
2020-06-12 14:58:24 -07:00
|
|
|
rv.Append( KiROUND( x ), KiROUND( y ) );
|
2018-02-04 13:09:30 +01:00
|
|
|
}
|
|
|
|
|
2020-10-23 22:37:34 +01:00
|
|
|
rv.Append( m_end );
|
|
|
|
|
2021-06-30 13:33:49 +02:00
|
|
|
if( aEffectiveAccuracy )
|
|
|
|
*aEffectiveAccuracy = effectiveAccuracy;
|
|
|
|
|
2018-02-04 13:09:30 +01:00
|
|
|
return rv;
|
|
|
|
}
|
2020-06-12 14:58:24 -07:00
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_ARC::Move( const VECTOR2I& aVector )
|
|
|
|
{
|
|
|
|
m_start += aVector;
|
|
|
|
m_end += aVector;
|
|
|
|
m_mid += aVector;
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-20 20:54:22 +00:00
|
|
|
void SHAPE_ARC::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
|
2020-06-12 14:58:24 -07:00
|
|
|
{
|
2022-01-20 20:54:22 +00:00
|
|
|
RotatePoint( m_start, aCenter, aAngle );
|
|
|
|
RotatePoint( m_end, aCenter, aAngle );
|
|
|
|
RotatePoint( m_mid, aCenter, aAngle );
|
2020-07-06 13:50:55 +02:00
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_ARC::Mirror( bool aX, bool aY, const VECTOR2I& aVector )
|
|
|
|
{
|
|
|
|
if( aX )
|
|
|
|
{
|
|
|
|
m_start.x = -m_start.x + 2 * aVector.x;
|
|
|
|
m_end.x = -m_end.x + 2 * aVector.x;
|
|
|
|
m_mid.x = -m_mid.x + 2 * aVector.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( aY )
|
|
|
|
{
|
|
|
|
m_start.y = -m_start.y + 2 * aVector.y;
|
|
|
|
m_end.y = -m_end.y + 2 * aVector.y;
|
|
|
|
m_mid.y = -m_mid.y + 2 * aVector.y;
|
|
|
|
}
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2020-06-12 14:58:24 -07:00
|
|
|
}
|
2021-01-04 08:54:29 -05:00
|
|
|
|
|
|
|
|
2021-04-11 14:28:32 +02:00
|
|
|
void SHAPE_ARC::Mirror( const SEG& axis )
|
|
|
|
{
|
|
|
|
m_start = axis.ReflectPoint( m_start );
|
|
|
|
m_end = axis.ReflectPoint( m_end );
|
|
|
|
m_mid = axis.ReflectPoint( m_mid );
|
|
|
|
|
2024-04-23 19:19:49 +03:00
|
|
|
update_values();
|
2021-04-11 14:28:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-04 08:54:29 -05:00
|
|
|
void SHAPE_ARC::Reverse()
|
|
|
|
{
|
|
|
|
std::swap( m_start, m_end );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_ARC SHAPE_ARC::Reversed() const
|
|
|
|
{
|
|
|
|
return SHAPE_ARC( m_end, m_mid, m_start, m_width );
|
|
|
|
}
|
2021-07-25 20:46:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
bool SHAPE_ARC::sliceContainsPoint( const VECTOR2I& p ) const
|
|
|
|
{
|
2023-10-17 10:27:59 +03:00
|
|
|
EDA_ANGLE sa = GetStartAngle().Normalize();
|
2022-01-15 12:52:20 +00:00
|
|
|
EDA_ANGLE ca = GetCentralAngle();
|
2023-10-17 10:27:59 +03:00
|
|
|
EDA_ANGLE ea = sa + ca;
|
|
|
|
|
|
|
|
EDA_ANGLE phi( p - GetCenter() ); // Angle from center to the point
|
|
|
|
phi.Normalize();
|
2021-07-25 20:46:00 +01:00
|
|
|
|
2022-01-15 12:52:20 +00:00
|
|
|
if( ca >= ANGLE_0 )
|
2021-07-25 20:46:00 +01:00
|
|
|
{
|
2023-10-17 10:27:59 +03:00
|
|
|
while( phi < sa )
|
|
|
|
phi += ANGLE_360;
|
|
|
|
|
|
|
|
return phi >= sa && phi <= ea;
|
2021-07-25 20:46:00 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-17 10:27:59 +03:00
|
|
|
while( phi > sa )
|
|
|
|
phi -= ANGLE_360;
|
2021-07-25 20:46:00 +01:00
|
|
|
|
2023-10-17 10:27:59 +03:00
|
|
|
return phi <= sa && phi >= ea;
|
|
|
|
}
|
2021-07-25 20:46:00 +01:00
|
|
|
}
|
2023-08-15 23:49:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_ARC::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, ERROR_LOC aErrorLoc ) const
|
|
|
|
{
|
|
|
|
TransformArcToPolygon( aBuffer, m_start, m_mid, m_end, m_width, aError, aErrorLoc );
|
|
|
|
}
|