KiCad PCB EDA Suite
Loading...
Searching...
No Matches
arc_construction.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <cmath>
23#include <limits>
24
25#include <math/util.h>
26
27
28namespace KIGEOM
29{
30
31namespace
32{
33
35double crossZ( double aX, double aY, double bX, double bY )
36{
37 const double p = aY * bX;
38 const double e = std::fma( aY, bX, -p );
39 return std::fma( aX, bY, -p ) - e;
40}
41
42
44bool toIU( const VECTOR2D& aPt, VECTOR2I& aOut )
45{
46 constexpr double limit = std::numeric_limits<int>::max() - 1;
47
48 if( !std::isfinite( aPt.x ) || !std::isfinite( aPt.y ) || std::abs( aPt.x ) > limit
49 || std::abs( aPt.y ) > limit )
50 {
51 return false;
52 }
53
54 aOut = VECTOR2I( KiROUND( aPt.x ), KiROUND( aPt.y ) );
55 return true;
56}
57
58
59// aSide is +1 for an arc on the (-dy, dx) side of start to end, and aCenterT is the center offset along that normal
60ARC_SOLUTION buildOnChord( const VECTOR2I& aStart, const VECTOR2I& aEnd, double aSide, double aCenterT,
61 double aRadius )
62{
63 ARC_SOLUTION sol;
64 sol.start = aStart;
65 sol.end = aEnd;
66
67 const VECTOR2D chord( static_cast<double>( aEnd.x ) - aStart.x, static_cast<double>( aEnd.y ) - aStart.y );
68 const double len = chord.EuclideanNorm();
69
70 if( len <= 0.0 || !std::isfinite( aRadius ) || aRadius <= 0.0 )
71 return sol;
72
73 const VECTOR2D normal( -chord.y / len, chord.x / len );
74 const VECTOR2D chordMid( ( static_cast<double>( aStart.x ) + aEnd.x ) * 0.5,
75 ( static_cast<double>( aStart.y ) + aEnd.y ) * 0.5 );
76 const double half = len * 0.5;
77 const double offset = std::abs( aCenterT );
78
79 // radius - offset cancels for shallow arcs, so use the equivalent half^2 / ( radius + offset )
80 const double height = aSide * aCenterT <= 0.0 ? half * half / ( aRadius + offset ) : aRadius + offset;
81
82 if( !toIU( chordMid + normal * ( aSide * height ), sol.mid ) )
83 return sol;
84
85 // An apex within rounding of the chord cannot be stored as an arc
86 if( crossZ( chord.x, chord.y, static_cast<double>( sol.mid.x ) - aStart.x,
87 static_cast<double>( sol.mid.y ) - aStart.y ) == 0.0 )
88 {
89 return sol;
90 }
91
92 sol.center = chordMid + normal * aCenterT;
93 sol.radius = aRadius;
94 sol.valid = true;
95 return sol;
96}
97
98} // namespace
99
100
101VECTOR2I ProjectToChordBisector( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCursor )
102{
103 const VECTOR2D chord( static_cast<double>( aEnd.x ) - aStart.x, static_cast<double>( aEnd.y ) - aStart.y );
104 const double len = chord.EuclideanNorm();
105
106 if( len <= 0.0 )
107 return aCursor;
108
109 const VECTOR2D normal( -chord.y / len, chord.x / len );
110 const VECTOR2D chordMid( ( static_cast<double>( aStart.x ) + aEnd.x ) * 0.5,
111 ( static_cast<double>( aStart.y ) + aEnd.y ) * 0.5 );
112 const VECTOR2D rel( aCursor.x - chordMid.x, aCursor.y - chordMid.y );
113
114 VECTOR2I projected;
115
116 if( !toIU( chordMid + normal * rel.Dot( normal ), projected ) )
117 return aCursor;
118
119 return projected;
120}
121
122
123ARC_SOLUTION ArcThroughPoints( const VECTOR2I& aStart, const VECTOR2I& aOnArc, const VECTOR2I& aEnd )
124{
125 const VECTOR2D b( static_cast<double>( aEnd.x ) - aStart.x, static_cast<double>( aEnd.y ) - aStart.y );
126 const VECTOR2D c( static_cast<double>( aOnArc.x ) - aStart.x, static_cast<double>( aOnArc.y ) - aStart.y );
127 const double d = crossZ( b.x, b.y, c.x, c.y );
128
129 if( d == 0.0 )
130 return ARC_SOLUTION();
131
132 const double bb = b.SquaredEuclideanNorm();
133 const double cc = c.SquaredEuclideanNorm();
134
135 // Circumcenter relative to the start point
136 const VECTOR2D rel( ( c.y * bb - b.y * cc ) / ( 2.0 * d ), ( b.x * cc - c.x * bb ) / ( 2.0 * d ) );
137 const double len = b.EuclideanNorm();
138
139 // The signed distance of the center along the chord normal, measured from the chord midpoint
140 const double centerT = ( -rel.x * b.y + rel.y * b.x ) / len;
141
142 return buildOnChord( aStart, aEnd, d > 0.0 ? 1.0 : -1.0, centerT, rel.EuclideanNorm() );
143}
144
145
146ARC_SOLUTION ArcFromStartEndMidDrag( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCursor )
147{
148 const VECTOR2I projected = ProjectToChordBisector( aStart, aEnd, aCursor );
149 ARC_SOLUTION sol = ArcThroughPoints( aStart, projected, aEnd );
150
151 // The circle passes through the projected point exactly, so keep it as the midpoint
152 if( sol.valid )
153 sol.mid = projected;
154
155 return sol;
156}
157
158
159ARC_SOLUTION ArcFromStartEndCenterDrag( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCursor,
160 bool aMajor )
161{
162 const VECTOR2D chord( static_cast<double>( aEnd.x ) - aStart.x, static_cast<double>( aEnd.y ) - aStart.y );
163 const double len = chord.EuclideanNorm();
164
165 if( len <= 0.0 )
166 return ARC_SOLUTION();
167
168 const VECTOR2D normal( -chord.y / len, chord.x / len );
169 const VECTOR2D chordMid( ( static_cast<double>( aStart.x ) + aEnd.x ) * 0.5,
170 ( static_cast<double>( aStart.y ) + aEnd.y ) * 0.5 );
171 const double centerT = VECTOR2D( aCursor.x - chordMid.x, aCursor.y - chordMid.y ).Dot( normal );
172
173 // The minor arc bulges away from the center, the major arc toward it
174 double side = centerT < 0.0 ? -1.0 : 1.0;
175
176 if( !aMajor )
177 side = -side;
178
179 return buildOnChord( aStart, aEnd, side, centerT, std::hypot( len * 0.5, centerT ) );
180}
181
182
183ARC_SOLUTION ArcFromStartTangentEnd( const VECTOR2I& aStart, const VECTOR2D& aTangent, const VECTOR2I& aEnd )
184{
185 const double tangentLen = aTangent.EuclideanNorm();
186
187 if( tangentLen <= 0.0 || !std::isfinite( tangentLen ) )
188 return ARC_SOLUTION();
189
190 const VECTOR2D t( aTangent.x / tangentLen, aTangent.y / tangentLen );
191 const VECTOR2D d( static_cast<double>( aEnd.x ) - aStart.x, static_cast<double>( aEnd.y ) - aStart.y );
192 const double dd = d.SquaredEuclideanNorm();
193 const double cross = crossZ( d.x, d.y, t.x, t.y );
194
195 if( dd <= 0.0 || cross == 0.0 )
196 return ARC_SOLUTION();
197
198 // The center sits on the normal to the tangent at the start, equidistant from start and end
199 const double k = -dd / ( 2.0 * cross );
200 const VECTOR2D n( -t.y, t.x );
201 const double len = std::sqrt( dd );
202 const VECTOR2D chordNormal( -d.y / len, d.x / len );
203
204 return buildOnChord( aStart, aEnd, cross > 0.0 ? 1.0 : -1.0, k * n.Dot( chordNormal ), std::abs( k ) );
205}
206
207} // namespace KIGEOM
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:312
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:567
Construction helpers for the interactive arc drawing modes.
ARC_SOLUTION ArcFromStartEndCenterDrag(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor, bool aMajor)
Arc from aStart to aEnd centered on aCursor projected onto the chord bisector.
VECTOR2I ProjectToChordBisector(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor)
Project aCursor onto the perpendicular bisector of the chord aStart - aEnd.
ARC_SOLUTION ArcFromStartTangentEnd(const VECTOR2I &aStart, const VECTOR2D &aTangent, const VECTOR2I &aEnd)
Arc leaving aStart along aTangent and ending at aEnd.
ARC_SOLUTION ArcFromStartEndMidDrag(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor)
Arc from aStart to aEnd whose midpoint is aCursor projected onto the chord bisector.
ARC_SOLUTION ArcThroughPoints(const VECTOR2I &aStart, const VECTOR2I &aOnArc, const VECTOR2I &aEnd)
Arc from aStart to aEnd that passes through aOnArc.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
bool valid
False for degenerate input (collinear points, zero chord, etc)
VECTOR2I mid
The point on the arc halfway along the sweep.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707