KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_eda_angle.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
20#include <cmath>
21#include <iomanip>
22
24
25#include <geometry/eda_angle.h>
26
27
28BOOST_AUTO_TEST_SUITE( EdaAngle )
29
30
42
43
44static const std::vector<EDA_ANGLE_NORMALISE_CASE> normalize_cases =
45{
46 //@todo: should we unify the ranges of Normalize180, Normalize720 to be the same
47 // as Normalize90 (i.e. inclusive of both sides of the range)?
48
49 // [0,360) (-360,0] [-90,90] (-180,180] [-360,360)
50 // Original Normalized NormNeg Norm90 Norm180 Norm720
51 { 90.0, 90.0, -270.0, 90.0, 90.0, 90.0 },
52 { -90.0, 270.0, -90.0, -90.0, -90.0, -90.0 },
53 { 135.0, 135.0, -225.0, -45.0, 135.0, 135.0 },
54 { -135.0, 225.0, -135.0, 45.0, -135.0, -135.0 },
55 { 180.0, 180.0, -180.0, 0.0, 180.0, 180.0 },
56 { -180.0, 180.0, -180.0, 0.0, 180.0, -180.0 },
57 { 360.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
58 { -360.0, 0.0, 0.0, 0.0, 0.0, -360.0 },
59 { 390.0, 30.0, -330.0, 30.0, 30.0, 30.0 },
60 { -390.0, 330.0, -30.0, -30.0, -30.0, -30.0 },
61 { 720.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
62 { -720.0, 0.0, 0.0, 0.0, 0.0, -360.0 },
63};
64
65
67{
68 for( const auto& c : normalize_cases )
69 {
70 BOOST_TEST_INFO_SCOPE( "Original angle: " << c.m_Angle << " degrees" );
71
72 EDA_ANGLE normalized( c.m_Angle, DEGREES_T );
73 normalized.Normalize();
74
75 EDA_ANGLE normalizedNegative( c.m_Angle, DEGREES_T );
76 normalizedNegative.NormalizeNegative();
77
78 EDA_ANGLE normalized90( c.m_Angle, DEGREES_T );
79 normalized90.Normalize90();
80
81 EDA_ANGLE normalized180( c.m_Angle, DEGREES_T );
82 normalized180.Normalize180();
83
84 EDA_ANGLE normalized720( c.m_Angle, DEGREES_T );
85 normalized720.Normalize720();
86
87 BOOST_CHECK_EQUAL( normalized.AsDegrees(), c.m_ExpNormalized );
88 BOOST_CHECK_EQUAL( normalizedNegative.AsDegrees(), c.m_ExpNormalizedNegative );
89 BOOST_CHECK_EQUAL( normalized90.AsDegrees(), c.m_ExpNormalized90 );
90 BOOST_CHECK_EQUAL( normalized180.AsDegrees(), c.m_ExpNormalized180 );
91 BOOST_CHECK_EQUAL( normalized720.AsDegrees(), c.m_ExpNormalized720 );
92 }
93}
94
95
96BOOST_AUTO_TEST_CASE( NormalizeHugeAndNonFinite )
97{
98 // The old loops leave NaN alone and never finish on inf or 1e19, so fail here before reaching those
99 BOOST_REQUIRE_EQUAL( EDA_ANGLE( NAN, DEGREES_T ).Normalize180().AsDegrees(), 0.0 );
100
101 // 1e19 is exact and 1e19 mod 360 = 280
102 // [0,360) (-360,0] [-90,90] (-180,180] [-360,360)
103 const std::vector<EDA_ANGLE_NORMALISE_CASE> cases = {
104 { 1e19, 280.0, -80.0, -80.0, -80.0, 280.0 },
105 { -1e19, 80.0, -280.0, 80.0, 80.0, -280.0 },
106 { INFINITY, 0.0, 0.0, 0.0, 0.0, 0.0 },
107 { -INFINITY, 0.0, 0.0, 0.0, 0.0, 0.0 },
108 { NAN, 0.0, 0.0, 0.0, 0.0, 0.0 },
109 };
110
111 for( const auto& c : cases )
112 {
113 BOOST_TEST_INFO_SCOPE( "Original angle: " << c.m_Angle << " degrees" );
114
115 BOOST_CHECK_EQUAL( EDA_ANGLE( c.m_Angle, DEGREES_T ).Normalize().AsDegrees(), c.m_ExpNormalized );
117 c.m_ExpNormalizedNegative );
118 BOOST_CHECK_EQUAL( EDA_ANGLE( c.m_Angle, DEGREES_T ).Normalize90().AsDegrees(), c.m_ExpNormalized90 );
119 BOOST_CHECK_EQUAL( EDA_ANGLE( c.m_Angle, DEGREES_T ).Normalize180().AsDegrees(), c.m_ExpNormalized180 );
120 BOOST_CHECK_EQUAL( EDA_ANGLE( c.m_Angle, DEGREES_T ).Normalize720().AsDegrees(), c.m_ExpNormalized720 );
121 }
122
123 // 9e18 is an exact multiple of 180
124 BOOST_CHECK( EDA_ANGLE( 9e18, DEGREES_T ).IsCardinal() );
125 BOOST_CHECK( !EDA_ANGLE( 9e18, DEGREES_T ).IsCardinal90() );
126
127 for( double bad : { INFINITY, -INFINITY } )
128 {
129 BOOST_CHECK( !EDA_ANGLE( bad, DEGREES_T ).IsCardinal() );
130 BOOST_CHECK( !EDA_ANGLE( bad, DEGREES_T ).IsCardinal90() );
131 }
132}
133
134
140static double refStep( double aValue, double aLow, bool aLowIncl, double aHigh, bool aHighIncl, double aPeriod )
141{
142 while( aLowIncl ? aValue < aLow : aValue <= aLow )
143 aValue += aPeriod;
144
145 while( aHighIncl ? aValue > aHigh : aValue >= aHigh )
146 aValue -= aPeriod;
147
148 return aValue;
149}
150
151
152static bool refIsCardinal( double aValue )
153{
154 while( aValue < 0.0 )
155 aValue += 90.0;
156
157 while( aValue >= 90.0 )
158 aValue -= 90.0;
159
160 return aValue == 0.0;
161}
162
163
164static bool refIsCardinal90( double aValue )
165{
166 aValue = std::abs( aValue );
167
168 while( aValue >= 180.0 )
169 aValue -= 180.0;
170
171 return aValue == 90.0;
172}
173
174
175BOOST_AUTO_TEST_CASE( NormalizeMatchesSteppingLoops )
176{
177 std::vector<double> inputs;
178
179 for( int i = -40; i <= 40; ++i )
180 {
181 double base = 45.0 * i;
182 inputs.insert( inputs.end(), { base, std::nextafter( base, -INFINITY ), std::nextafter( base, INFINITY ),
183 base - 1e-20, base + 1e-20 } );
184 }
185
186 for( int i = -5000; i <= 5000; ++i )
187 inputs.push_back( i * 199.87654321 );
188
189 inputs.insert( inputs.end(), { 0.0, -0.0, 1e-300, -1e-300 } );
190
191 auto sameBits = []( double a, double b )
192 {
193 return a == b && std::signbit( a ) == std::signbit( b );
194 };
195
196 for( double x : inputs )
197 {
198 BOOST_TEST_INFO_SCOPE( "Original angle: " << std::setprecision( 17 ) << x << " degrees" );
199
200 BOOST_CHECK( sameBits( EDA_ANGLE( x, DEGREES_T ).Normalize().AsDegrees(),
201 refStep( x, 0.0, true, 360.0, false, 360.0 ) ) );
202 BOOST_CHECK( sameBits( EDA_ANGLE( x, DEGREES_T ).NormalizeNegative().AsDegrees(),
203 refStep( x, -360.0, false, 0.0, true, 360.0 ) ) );
204 BOOST_CHECK( sameBits( EDA_ANGLE( x, DEGREES_T ).Normalize90().AsDegrees(),
205 refStep( x, -90.0, true, 90.0, true, 180.0 ) ) );
206 BOOST_CHECK( sameBits( EDA_ANGLE( x, DEGREES_T ).Normalize180().AsDegrees(),
207 refStep( x, -180.0, false, 180.0, true, 360.0 ) ) );
208 BOOST_CHECK( sameBits( EDA_ANGLE( x, DEGREES_T ).Normalize720().AsDegrees(),
209 refStep( x, -360.0, true, 360.0, false, 360.0 ) ) );
210 BOOST_CHECK_EQUAL( EDA_ANGLE( x, DEGREES_T ).IsCardinal(), refIsCardinal( x ) );
211 BOOST_CHECK_EQUAL( EDA_ANGLE( x, DEGREES_T ).IsCardinal90(), refIsCardinal90( x ) );
212 }
213}
214
215
216BOOST_AUTO_TEST_CASE( ConstantAngles )
217{
218 BOOST_CHECK_EQUAL( ANGLE_0.AsDegrees(), 0.0 );
219 BOOST_CHECK_EQUAL( ANGLE_45.AsDegrees(), 45.0 );
220 BOOST_CHECK_EQUAL( ANGLE_90.AsDegrees(), 90.0 );
221 BOOST_CHECK_EQUAL( ANGLE_135.AsDegrees(), 135.0 );
222 BOOST_CHECK_EQUAL( ANGLE_180.AsDegrees(), 180.0 );
223 BOOST_CHECK_EQUAL( ANGLE_270.AsDegrees(), 270.0 );
224 BOOST_CHECK_EQUAL( ANGLE_360.AsDegrees(), 360.0 );
225
226 BOOST_CHECK_EQUAL( ANGLE_HORIZONTAL.AsDegrees(), 0.0 );
227 BOOST_CHECK_EQUAL( ANGLE_VERTICAL.AsDegrees(), 90.0 );
228 BOOST_CHECK_EQUAL( FULL_CIRCLE.AsDegrees(), 360.0 );
229}
230
231
233{
234 auto deg = []( double d ) { return EDA_ANGLE( d, DEGREES_T ); };
235
236 BOOST_CHECK_EQUAL( deg( 80 ).Snapped( ANGLE_90 ).AsDegrees(), 90.0 );
237 BOOST_CHECK_EQUAL( deg( 30 ).Snapped( ANGLE_90 ).AsDegrees(), 0.0 );
238 BOOST_CHECK_EQUAL( deg( 135 ).Snapped( ANGLE_90 ).AsDegrees(), 180.0 );
239 BOOST_CHECK_EQUAL( deg( 45 ).Snapped( ANGLE_90 ).AsDegrees(), 90.0 );
240 BOOST_CHECK_EQUAL( deg( -10 ).Snapped( ANGLE_90 ).AsDegrees(), 0.0 );
241 BOOST_CHECK_EQUAL( deg( 80 ).Snapped( ANGLE_45 ).AsDegrees(), 90.0 );
242 BOOST_CHECK_EQUAL( deg( 5 ).Snapped( ANGLE_0 ).AsDegrees(), 5.0 ); // step <= 0 guard
243
244 // Minimal equivalent rotation used by grid-aware placement: raw - raw.Snapped(period).
245 auto residual = [&]( double raw, const EDA_ANGLE& period )
246 {
247 EDA_ANGLE a = deg( raw );
248 return ( a - a.Snapped( period ) ).AsDegrees();
249 };
250
251 BOOST_CHECK_EQUAL( residual( 80, ANGLE_90 ), -10.0 );
252 BOOST_CHECK_EQUAL( residual( 30, ANGLE_90 ), 30.0 );
253 BOOST_CHECK_EQUAL( residual( 135, ANGLE_90 ), -45.0 );
254 BOOST_CHECK_EQUAL( residual( 0, ANGLE_90 ), 0.0 );
255}
256
257
259{
260 auto deg = []( double d ) { return EDA_ANGLE( d, DEGREES_T ); };
261 auto orientation = [&]( double d ) { return EDA_ORIENTATION( deg( d ) ); };
262
263 // Construction normalizes into [0, 360), and equality compares canonical values.
264 BOOST_CHECK_EQUAL( orientation( 370 ).GetAngle().AsDegrees(), 10.0 );
265 BOOST_CHECK_EQUAL( orientation( -90 ).GetAngle().AsDegrees(), 270.0 );
266 BOOST_CHECK( orientation( 370 ) == orientation( 10 ) );
267
268 // Addition and subtraction wrap in every form.
269 EDA_ORIENTATION turn = orientation( 350 );
270 BOOST_CHECK_EQUAL( ( turn + deg( 30 ) ).GetAngle().AsDegrees(), 20.0 );
271 BOOST_CHECK_EQUAL( ( deg( 30 ) + turn ).GetAngle().AsDegrees(), 20.0 );
272 BOOST_CHECK_EQUAL( ( turn += deg( 30 ) ).GetAngle().AsDegrees(), 20.0 );
273 BOOST_CHECK_EQUAL( ( turn -= deg( 30 ) ).GetAngle().AsDegrees(), 350.0 );
274
275 // Negation and subtraction
276 BOOST_CHECK_EQUAL( ( -orientation( 45 ) ).GetAngle().AsDegrees(), 315.0 );
277 // Wrapping subtraction
278 BOOST_CHECK_EQUAL( ( ANGLE_180 - orientation( 270 ) ).GetAngle().AsDegrees(), 270.0 );
279 // Orientation minus angle, with wrapping
280 BOOST_CHECK_EQUAL( ( orientation( 10 ) - deg( 350 ) ).GetAngle().AsDegrees(), 20.0 );
281
282 // A difference of orientations -> signed rotation amount (EDA_ANGLE, not EDA_ORIENTATION)
283 EDA_ANGLE diff = orientation( 10 ) - orientation( 350 );
284 BOOST_CHECK_EQUAL( diff.AsDegrees(), -340.0 );
285
286 // -0 becomes 0
287 BOOST_CHECK( !std::signbit( ( -orientation( 0 ) ).GetAngle().AsDegrees() ) );
288}
289
290
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
EDA_ANGLE Normalize()
Definition eda_angle.h:228
EDA_ANGLE NormalizeNegative()
Definition eda_angle.h:247
EDA_ANGLE Normalize90()
Definition eda_angle.h:260
double AsDegrees() const
Definition eda_angle.h:115
EDA_ANGLE Snapped(const EDA_ANGLE &aStep) const
Snap to the nearest integer multiple of aStep (returns *this if aStep <= 0).
Definition eda_angle.h:300
EDA_ANGLE Normalize180()
Definition eda_angle.h:273
EDA_ANGLE Normalize720()
Definition eda_angle.h:286
An "orientation", held as an angle constrained to the range [0, 360) degrees.
Definition eda_angle.h:473
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ DEGREES_T
Definition eda_angle.h:30
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:445
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:444
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:449
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:453
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:446
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
static constexpr EDA_ANGLE ANGLE_135
Definition eda_angle.h:451
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static double refStep(double aValue, double aLow, bool aLowIncl, double aHigh, bool aHighIncl, double aPeriod)
Reference copy of the stepping loops that normalization used before exact folding.
static bool refIsCardinal(double aValue)
BOOST_AUTO_TEST_CASE(Normalize)
static const std::vector< EDA_ANGLE_NORMALISE_CASE > normalize_cases
static bool refIsCardinal90(double aValue)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")