KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_transform_trs.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21
23
24#include <cmath>
25
26
27BOOST_AUTO_TEST_SUITE( TransformTrs )
28
29
30// 1 unit slack for float-to-int rounding.
31static constexpr int IUNIT_TOL = 1;
32
33
34static void CHECK_VEC_NEAR( const VECTOR2I& aActual, const VECTOR2I& aExpected, int aTol )
35{
36 BOOST_CHECK_MESSAGE( std::abs( aActual.x - aExpected.x ) <= aTol
37 && std::abs( aActual.y - aExpected.y ) <= aTol,
38 "actual ( " << aActual.x << ", " << aActual.y << " ) "
39 "expected ( " << aExpected.x << ", " << aExpected.y << " ) "
40 "tol " << aTol );
41}
42
43
44BOOST_AUTO_TEST_CASE( IdentityApply )
45{
47 BOOST_CHECK( t.IsIdentity() );
48 BOOST_CHECK( t.IsUniformScale() );
49
50 VECTOR2I p( 12345, -6789 );
51 CHECK_VEC_NEAR( t.Apply( p ), p, 0 );
52 CHECK_VEC_NEAR( t.InverseApply( p ), p, 0 );
53}
54
55
56BOOST_AUTO_TEST_CASE( TranslateOnly )
57{
58 TRANSFORM_TRS t( VECTOR2I( 100, 200 ), ANGLE_0, 1.0, 1.0 );
59 BOOST_CHECK( !t.IsIdentity() );
60
61 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 0, 0 ) ), VECTOR2I( 100, 200 ), 0 );
62 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 50, 50 ) ), VECTOR2I( 150, 250 ), 0 );
63
64 CHECK_VEC_NEAR( t.InverseApply( VECTOR2I( 100, 200 ) ), VECTOR2I( 0, 0 ), 0 );
65 CHECK_VEC_NEAR( t.InverseApply( VECTOR2I( 150, 250 ) ), VECTOR2I( 50, 50 ), 0 );
66}
67
68
70{
71 // Expected values match trigo.h::RotatePoint conventions.
72 struct CASE { EDA_ANGLE angle; VECTOR2I p; VECTOR2I expected; };
73
74 const std::vector<CASE> cases = {
75 { ANGLE_0, VECTOR2I( 100, 0 ), VECTOR2I( 100, 0 ) },
76 { ANGLE_90, VECTOR2I( 100, 0 ), VECTOR2I( 0, -100 ) },
77 { ANGLE_180, VECTOR2I( 100, 0 ), VECTOR2I( -100, 0 ) },
78 { ANGLE_270, VECTOR2I( 100, 0 ), VECTOR2I( 0, 100 ) },
79 };
80
81 for( const CASE& c : cases )
82 {
83 TRANSFORM_TRS t( VECTOR2I( 0, 0 ), c.angle, 1.0, 1.0 );
84 CHECK_VEC_NEAR( t.Apply( c.p ), c.expected, IUNIT_TOL );
85 CHECK_VEC_NEAR( t.InverseApply( c.expected ), c.p, IUNIT_TOL );
86 }
87}
88
89
91{
92 TRANSFORM_TRS uniform( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 2.0 );
93 BOOST_CHECK( uniform.IsUniformScale() );
94 CHECK_VEC_NEAR( uniform.Apply( VECTOR2I( 100, 50 ) ), VECTOR2I( 200, 100 ), 0 );
95 CHECK_VEC_NEAR( uniform.InverseApply( VECTOR2I( 200, 100 ) ), VECTOR2I( 100, 50 ), 0 );
96
97 TRANSFORM_TRS half( VECTOR2I( 0, 0 ), ANGLE_0, 0.5, 0.5 );
98 CHECK_VEC_NEAR( half.Apply( VECTOR2I( 100, 50 ) ), VECTOR2I( 50, 25 ), 0 );
99
100 TRANSFORM_TRS nonuniform( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 0.5 );
101 BOOST_CHECK( !nonuniform.IsUniformScale() );
102 CHECK_VEC_NEAR( nonuniform.Apply( VECTOR2I( 100, 100 ) ), VECTOR2I( 200, 50 ), 0 );
103 CHECK_VEC_NEAR( nonuniform.InverseApply( VECTOR2I( 200, 50 ) ), VECTOR2I( 100, 100 ), 0 );
104}
105
106
107BOOST_AUTO_TEST_CASE( CompoundSrtOracle )
108{
109 // p (10, 0): scale 2x to (20, 0); rotate 90 to (0, -20); translate to (1000, 480).
110 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_90, 2.0, 2.0 );
111
112 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 10, 0 ) ), VECTOR2I( 1000, 480 ), IUNIT_TOL );
113 CHECK_VEC_NEAR( t.InverseApply( t.Apply( VECTOR2I( 10, 0 ) ) ), VECTOR2I( 10, 0 ), IUNIT_TOL );
114}
115
116
117BOOST_AUTO_TEST_CASE( InverseApplyRoundTrip )
118{
119 const std::vector<TRANSFORM_TRS> ts = {
120 TRANSFORM_TRS( VECTOR2I( 100, 200 ), EDA_ANGLE( 37.5, DEGREES_T ), 1.0, 1.0 ),
121 TRANSFORM_TRS( VECTOR2I( -50, 75 ), EDA_ANGLE( 123.4, DEGREES_T ), 1.5, 1.5 ),
122 TRANSFORM_TRS( VECTOR2I( 0, 0 ), EDA_ANGLE( 60.0, DEGREES_T ), 2.0, 0.5 ),
123 TRANSFORM_TRS( VECTOR2I( 1234, -567 ), EDA_ANGLE( 89.0, DEGREES_T ), 0.75, 1.25 ),
124 };
125
126 const std::vector<VECTOR2I> probes = {
127 { 0, 0 }, { 100, 0 }, { 0, 100 }, { 250, -125 }, { -300, 400 },
128 };
129
130 for( const TRANSFORM_TRS& t : ts )
131 {
132 for( const VECTOR2I& p : probes )
133 {
134 VECTOR2I round = t.InverseApply( t.Apply( p ) );
135 CHECK_VEC_NEAR( round, p, IUNIT_TOL );
136 }
137 }
138}
139
140
141BOOST_AUTO_TEST_CASE( RescaleAroundFixedPointInvariant )
142{
143 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.0, 1.0 );
144 const VECTOR2I libPoint( 25, 25 );
145 const VECTOR2I fixedPoint = t.Apply( libPoint );
146
147 TRANSFORM_TRS rescaled = t.RescaleAround( fixedPoint, 3.0, 3.0 );
148
149 CHECK_VEC_NEAR( rescaled.Apply( libPoint ), fixedPoint, IUNIT_TOL );
150}
151
152
153BOOST_AUTO_TEST_CASE( RescaleAroundComposesScale )
154{
155 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.5, 1.5 );
156
157 TRANSFORM_TRS rescaled = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 2.0 );
158
159 BOOST_CHECK_CLOSE( rescaled.GetScaleX(), 3.0, 1e-6 );
160 BOOST_CHECK_CLOSE( rescaled.GetScaleY(), 3.0, 1e-6 );
161}
162
163
164BOOST_AUTO_TEST_CASE( RescaleAroundAnchorFollowsCenter )
165{
166 // 2x rescale around (0, 0): anchor (1000, 500) goes to (2000, 1000).
167 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.0, 1.0 );
168 TRANSFORM_TRS r = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 2.0 );
169
170 CHECK_VEC_NEAR( r.GetTranslate(), VECTOR2I( 2000, 1000 ), 0 );
171}
172
173
174BOOST_AUTO_TEST_CASE( RescaleAroundRotatedNonUniformScalesLocalAxes )
175{
176 // The scale factors act in the footprint's own frame, so a non-uniform
177 // rescale must apply directly (Sx *= aSx, Sy *= aSy) and never swap axes
178 // with the rotation.
179 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), EDA_ANGLE( 90.0, DEGREES_T ), 1.0, 1.0 );
180
181 TRANSFORM_TRS r = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 3.0 );
182
183 BOOST_CHECK_CLOSE( r.GetScaleX(), 2.0, 1e-6 );
184 BOOST_CHECK_CLOSE( r.GetScaleY(), 3.0, 1e-6 );
185}
186
187
188BOOST_AUTO_TEST_CASE( RescaleAroundRotatedFixedPointInvariant )
189{
190 // The fixed point stays put even with rotation and a non-uniform scale.
191 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), EDA_ANGLE( 90.0, DEGREES_T ), 1.0, 1.0 );
192 const VECTOR2I libPoint( 25, 40 );
193 const VECTOR2I fixedPoint = t.Apply( libPoint );
194
195 TRANSFORM_TRS r = t.RescaleAround( fixedPoint, 2.0, 3.0 );
196
197 CHECK_VEC_NEAR( r.Apply( libPoint ), fixedPoint, IUNIT_TOL );
198}
199
200
201BOOST_AUTO_TEST_CASE( EqualityAndInequality )
202{
203 TRANSFORM_TRS a( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.5 );
204 TRANSFORM_TRS b( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.5 );
205 TRANSFORM_TRS c( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.6 );
206
207 BOOST_CHECK( a == b );
208 BOOST_CHECK( !( a == c ) );
209 BOOST_CHECK( a != c );
210}
211
212
213BOOST_AUTO_TEST_CASE( ApplyLinearScaleArithmeticMean )
214{
215 TRANSFORM_TRS t( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 1.0 );
216 BOOST_CHECK_CLOSE( t.ApplyLinearScale( 100.0 ), 150.0, 1e-9 );
217
218 TRANSFORM_TRS uniform( VECTOR2I( 0, 0 ), ANGLE_0, 0.5, 0.5 );
219 BOOST_CHECK_CLOSE( uniform.ApplyLinearScale( 100.0 ), 50.0, 1e-9 );
220}
221
222
223// A rotation composed from a matrix can leave one scale a unit in the last place off. That is
224// still a uniform, unit scale, or a rotated footprint's circles and arcs turn into ellipses.
225BOOST_AUTO_TEST_CASE( RoundingNoiseKeepsScaleUniform )
226{
227 const double noisy = std::nextafter( 1.0, 2.0 );
228 TRANSFORM_TRS t( VECTOR2I( 0, 0 ), EDA_ANGLE( 2.5, DEGREES_T ), 1.0, noisy );
229
230 BOOST_CHECK( t.IsUniformScale() );
231 BOOST_CHECK( t.HasUnitScale() );
232
233 TRANSFORM_TRS scaled( VECTOR2I( 0, 0 ), ANGLE_0, 1.0, 1.0 + 1e-6 );
234 BOOST_CHECK( !scaled.IsUniformScale() );
235 BOOST_CHECK( !scaled.HasUnitScale() );
236}
237
238
VECTOR2I InverseApply(const VECTOR2I &aPoint) const
bool HasUnitScale() const
True when both scales are 1, ignoring rounding.
bool IsUniformScale() const
Scales that differ only by rounding count as equal, so a pure rotation stays uniform.
double GetScaleX() const
VECTOR2I Apply(const VECTOR2I &aPoint) const
double GetScaleY() const
double ApplyLinearScale(double aLength) const
TRANSFORM_TRS RescaleAround(const VECTOR2I &aFixedPoint, double aSx, double aSy) const
bool IsIdentity() const
const VECTOR2I & GetTranslate() const
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_45
Definition eda_angle.h:449
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:453
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
static void CHECK_VEC_NEAR(const VECTOR2I &aActual, const VECTOR2I &aExpected, int aTol)
BOOST_AUTO_TEST_CASE(IdentityApply)
static constexpr int IUNIT_TOL
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708