KiCad PCB EDA Suite
Loading...
Searching...
No Matches
arc_geom_manager.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 <math/util.h> // for KiROUND
23#include <geometry/eda_angle.h>
24#include <trigo.h>
25
26using namespace KIGFX::PREVIEW;
27
28
30static EDA_ANGLE snapAngle( const EDA_ANGLE& aAngle )
31{
32 return ANGLE_45 * KiROUND( aAngle / ANGLE_45 );
33}
34
35
37{
39 return acceptExactPoint( aPt );
40
41 switch( getStep() )
42 {
43 case FIRST_POINT: return setOrigin( aPt );
44 case SECOND_POINT: return setStart( aPt );
45 case THIRD_POINT: return setEnd( aPt );
46 case COMPLETE: return false;
47 }
48
49 return false;
50}
51
52
54{
55 if( IsComplete() )
56 return false;
57
58 const int index = GetPointIndex();
59
60 m_pts[index] = aPt;
61
62 // The points before the last only draw guides
63 if( index < 2 && !HasTangentSeed() )
64 {
66
67 if( index == 0 )
68 return true;
69
70 return UsesDirectionClick() ? setDirection( aPt ) : aPt != m_pts[0];
71 }
72
74 {
75 // The nearer of the two directions along the axis gives the sweep up to a half turn
76 const VECTOR2D chord = VECTOR2D( aPt ) - VECTOR2D( m_pts[0] );
77 const double sign = ( chord.Dot( m_axis ) >= 0.0 ) != m_axisFlipped ? 1.0 : -1.0;
78
79 m_dir = m_axis * sign;
80 }
81
82 KIGEOM::ARC_SOLUTION solution;
83
84 switch( m_mode )
85 {
87 solution = KIGEOM::ArcFromStartEndMidDrag( m_pts[0], m_pts[1], aPt );
88 break;
89
91 solution = KIGEOM::ArcFromStartEndCenterDrag( m_pts[0], m_pts[1], aPt, m_major );
92 break;
93
96 solution = KIGEOM::ArcFromStartTangentEnd( m_pts[0], m_dir, aPt );
97 break;
98
100 break;
101 }
102
103 if( solution.valid )
104 applySolution( solution );
105
106 return solution.valid;
107}
108
109
111{
112 m_solution = aSolution;
113
114 const VECTOR2D start = VECTOR2D( aSolution.start ) - aSolution.center;
115 const VECTOR2D end = VECTOR2D( aSolution.end ) - aSolution.center;
116 const VECTOR2D mid = VECTOR2D( aSolution.mid ) - aSolution.center;
117
118 m_origin = VECTOR2I( KiROUND( aSolution.center.x, true ), KiROUND( aSolution.center.y, true ) );
119 m_radius = aSolution.radius;
120 m_startAngle = EDA_ANGLE( start ).Normalize();
122
123 // Raw angles grow toward the start to mid side unless the cross product is negative
124 m_clockwise = start.Cross( mid ) < 0.0;
125}
126
127
137
138
140{
141 const VECTOR2D vec = VECTOR2D( aPt ) - VECTOR2D( m_pts[0] );
142 const double length = vec.EuclideanNorm();
143
144 if( length == 0.0 )
145 return false;
146
147 m_dir = vec;
148
149 if( m_angleSnap )
150 {
151 // Table lookup keeps snapped axis directions free of trigonometric rounding
152 static const double d = M_SQRT1_2;
153 static const VECTOR2D units[8] = { { 1, 0 }, { d, d }, { 0, 1 }, { -d, d },
154 { -1, 0 }, { -d, -d }, { 0, -1 }, { d, -d } };
155
156 const int octant = ( KiROUND( EDA_ANGLE( vec ) / ANGLE_45 ) % 8 + 8 ) % 8;
157
158 m_dir = units[octant] * length;
159 }
160
161 return true;
162}
163
164
166{
168
169 // The manager is reused for every arc in a chain, so the next arc must not inherit the
170 // previous arc's direction, its locked-in direction choice, or its geometry
171 m_clockwise = true;
172 m_directionLocked = false;
173 m_angleSnap = false;
174 m_origin = VECTOR2I();
175 m_radius = 0.0;
178 m_pts = {};
179 m_dir = VECTOR2D();
180 m_hasSeed = false;
181 m_seedIsAxis = false;
182 m_axisFlipped = false;
183 m_axis = VECTOR2D();
184 m_major = false;
186}
187
188
190{
191 if( aMode == m_mode )
192 return;
193
194 if( IsReset() )
195 {
196 // Nothing is locked, so a seed set beforehand stays available
197 m_mode = aMode;
200 return;
201 }
202
203 // Only the first point of a start-first mode carries over, and only into another start-first mode
204 const bool keepStart = m_mode != ARC_DRAW_MODE::CENTER_START_END && aMode != ARC_DRAW_MODE::CENTER_START_END;
205
206 const VECTOR2I start = m_pts[0];
207 const VECTOR2I cursor = GetLastPoint();
208 const bool snap = m_angleSnap;
209
210 m_mode = aMode;
211 Reset();
212
213 if( !keepStart )
214 return;
215
216 m_angleSnap = snap;
217 AddPoint( start, true );
218 AddPoint( cursor, false );
219}
220
221
222void ARC_GEOM_MANAGER::SetTangentSeed( const VECTOR2I& aStart, const VECTOR2D& aDirection, bool aDirectionIsAxis )
223{
224 if( !IsReset() || ( aDirection.x == 0.0 && aDirection.y == 0.0 ) )
225 return;
226
227 m_pts[0] = aStart;
228 m_dir = aDirection;
229 m_axis = aDirection;
230 m_seedIsAxis = aDirectionIsAxis;
231 m_axisFlipped = false;
232 m_hasSeed = true;
235}
236
237
239{
240 switch( m_mode )
241 {
244 m_directionLocked = true;
246 break;
247
249 m_major = !m_major;
250
251 // Rebuild the arc under the cursor with the other posture
252 if( getStep() == THIRD_POINT )
254
256 break;
257
260 {
264 }
265
266 break;
267
268 default:
269 break;
270 }
271}
272
273
275{
276 if( UsesExactEndpoints() && !m_solution.valid )
277 return m_pts[0];
278
279 return m_origin;
280}
281
282
284{
285 if( UsesExactEndpoints() )
286 return m_pts[0];
287
288 VECTOR2I vec( static_cast<int>( m_radius ), 0 );
289 RotatePoint( vec, -m_startAngle );
290 return m_origin +vec;
291}
292
293
295{
296 if( UsesExactEndpoints() )
297 return m_solution.valid ? m_solution.end : m_pts[0];
298
299 VECTOR2I vec( static_cast<int>( m_radius ), 0 );
300 RotatePoint( vec, -m_endAngle );
301 return m_origin + vec;
302}
303
304
306{
307 return m_radius;
308}
309
310
312{
313 EDA_ANGLE angle = m_startAngle;
314
315 if( m_clockwise )
316 angle -= ANGLE_360;
317
318 return -angle;
319}
320
321
323{
325
326 if( m_endAngle <= m_startAngle )
327 angle += ANGLE_360;
328
329 if( m_clockwise )
330 angle -= ANGLE_360;
331
332 return -angle;
333}
334
335
337{
338 m_origin = aOrigin;
341
342 return true;
343}
344
345
347{
348 const VECTOR2I radVec = aEnd - m_origin;
349
350 m_radius = radVec.EuclideanNorm();
351 m_startAngle = EDA_ANGLE( radVec );
352
353 if( m_angleSnap )
355
356 // normalise to 0..360
357 while( m_startAngle < ANGLE_0 )
359
361
362 return m_radius != 0.0;
363}
364
365
366bool ARC_GEOM_MANAGER::setEnd( const VECTOR2I& aCursor )
367{
368 const VECTOR2I radVec = aCursor - m_origin;
369
370 m_endAngle = EDA_ANGLE( radVec );
371
372 if( m_angleSnap )
374
375 // normalise to 0..360
376 while( m_endAngle < ANGLE_0 )
378
379 if( !m_directionLocked )
380 {
381 EDA_ANGLE ccwAngle = m_endAngle - m_startAngle;
382
383 if( m_endAngle <= m_startAngle )
384 ccwAngle += ANGLE_360;
385
386 EDA_ANGLE cwAngle = std::abs( ccwAngle - ANGLE_360 );
387
388 if( std::min( ccwAngle, cwAngle ) >= ANGLE_90 )
389 m_directionLocked = true;
390 else
391 m_clockwise = cwAngle < ccwAngle;
392 }
393 else if( std::abs( GetSubtended() ) < ANGLE_90 )
394 {
395 m_directionLocked = false;
396 }
397
398 // if the end is the same as the start, this is a bad point
399 return m_endAngle != m_startAngle;
400}
int index
ARC_DRAW_MODE
Construction sequence used when interactively drawing a circular arc.
@ TANGENT
End only; start and departure direction come from a tangent seed.
@ START_END_MID
Start, end, then the arc midpoint dragged along the chord bisector.
@ START_DIR_END
Start, a point giving the departure direction, then the end.
@ START_END_CENTER
Start, end, then the center dragged along the chord bisector.
@ CENTER_START_END
Center, then start (sets radius), then sweep to the end.
static EDA_ANGLE snapAngle(const EDA_ANGLE &aAngle)
Snap an angle to the nearest 45 degrees.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
EDA_ANGLE Normalize()
Definition eda_angle.h:228
VECTOR2I GetOrigin() const
Get the center point of the arc (valid when state > FIRST_POINT in center mode)
bool acceptExactPoint(const VECTOR2I &aPt)
Point acceptor for every mode that keeps the clicked endpoints.
void Reset() override
Reset to the initial state, ready for the next arc.
bool HasTangentSeed() const
True when the TANGENT mode is running from a seed, so its first click already sets the end.
void ToggleClockwise()
Reverse the current arc direction in center mode, or switch between the minor and major arc in START_...
EDA_ANGLE GetStartAngle() const
Get the angle of the vector leading to the start point (valid if step >= SECOND_POINT in center mode)
bool setStart(const VECTOR2I &aEnd)
Set the end of the first radius line (arc start)
VECTOR2I GetStartRadiusEnd() const
Get the coordinates of the arc start.
bool acceptPoint(const VECTOR2I &aPt) override
Function that accepts a point for a stage, or rejects it to return to the previous stage.
bool setDirection(const VECTOR2I &aPt)
Set the departure direction from the second click, snapped if requested.
void SetTangentSeed(const VECTOR2I &aStart, const VECTOR2D &aDirection, bool aDirectionIsAxis=false)
Preload the start point and departure direction for ARC_DRAW_MODE::TANGENT.
int GetPointIndex() const
Index of the point the next click sets, counting a tangent seed as point 0.
bool UsesDirectionClick() const
True when a direction is part of the construction, that is START_DIR_END or an unseeded TANGENT.
VECTOR2I GetEndRadiusEnd() const
Get the coordinates of the arc end point.
bool UsesExactEndpoints() const
True when the committed endpoints are the clicked points, described by GetSolution()
void applySolution(const KIGEOM::ARC_SOLUTION &aSolution)
Take over a valid solution as the canonical arc state.
bool setEnd(const VECTOR2I &aCursor)
Set a point of the second radius line (collinear with arc end)
void clearSolution()
Forget the solution, leaving nothing drawable.
double GetRadius() const
Get the radius of the arc (valid if step >= SECOND_POINT in center mode)
EDA_ANGLE GetSubtended() const
Get the angle of the vector leading to the end point (valid if step >= THIRD_POINT in center mode)
std::array< VECTOR2I, 3 > m_pts
The points set so far for the modes that keep the clicked endpoints, the last following the cursor.
bool setOrigin(const VECTOR2I &aOrigin)
Set the center point of the arc.
void SetMode(ARC_DRAW_MODE aMode)
Select the construction sequence.
@ FIRST_POINT
Waiting to lock in the first point.
@ THIRD_POINT
Waiting to lock in the third point.
@ SECOND_POINT
Waiting to lock in the second point.
VECTOR2I GetLastPoint() const
Get the last point added (locked in or not).
int getStep() const
Get the current stage of the manager.
void AddPoint(const VECTOR2I &aPt, bool aLockIn)
Add a point to the construction manager.
void setGeometryChanged()
Mark the geometry as changed for clients to notice.
virtual void Reset()
Reset the manager to the initial state.
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:559
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
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:449
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
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.
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.
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.
VECTOR2I end
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
constexpr int sign(T val)
Definition util.h:166
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707