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
25BOOST_AUTO_TEST_SUITE( TransformTrs )
26
27
28// 1 unit slack for float-to-int rounding.
29static constexpr int IUNIT_TOL = 1;
30
31
32static void CHECK_VEC_NEAR( const VECTOR2I& aActual, const VECTOR2I& aExpected, int aTol )
33{
34 BOOST_CHECK_MESSAGE( std::abs( aActual.x - aExpected.x ) <= aTol
35 && std::abs( aActual.y - aExpected.y ) <= aTol,
36 "actual ( " << aActual.x << ", " << aActual.y << " ) "
37 "expected ( " << aExpected.x << ", " << aExpected.y << " ) "
38 "tol " << aTol );
39}
40
41
42BOOST_AUTO_TEST_CASE( IdentityApply )
43{
45 BOOST_CHECK( t.IsIdentity() );
46 BOOST_CHECK( t.IsUniformScale() );
47
48 VECTOR2I p( 12345, -6789 );
49 CHECK_VEC_NEAR( t.Apply( p ), p, 0 );
50 CHECK_VEC_NEAR( t.InverseApply( p ), p, 0 );
51}
52
53
54BOOST_AUTO_TEST_CASE( TranslateOnly )
55{
56 TRANSFORM_TRS t( VECTOR2I( 100, 200 ), ANGLE_0, 1.0, 1.0 );
57 BOOST_CHECK( !t.IsIdentity() );
58
59 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 0, 0 ) ), VECTOR2I( 100, 200 ), 0 );
60 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 50, 50 ) ), VECTOR2I( 150, 250 ), 0 );
61
62 CHECK_VEC_NEAR( t.InverseApply( VECTOR2I( 100, 200 ) ), VECTOR2I( 0, 0 ), 0 );
63 CHECK_VEC_NEAR( t.InverseApply( VECTOR2I( 150, 250 ) ), VECTOR2I( 50, 50 ), 0 );
64}
65
66
68{
69 // Expected values match trigo.h::RotatePoint conventions.
70 struct CASE { EDA_ANGLE angle; VECTOR2I p; VECTOR2I expected; };
71
72 const std::vector<CASE> cases = {
73 { ANGLE_0, VECTOR2I( 100, 0 ), VECTOR2I( 100, 0 ) },
74 { ANGLE_90, VECTOR2I( 100, 0 ), VECTOR2I( 0, -100 ) },
75 { ANGLE_180, VECTOR2I( 100, 0 ), VECTOR2I( -100, 0 ) },
76 { ANGLE_270, VECTOR2I( 100, 0 ), VECTOR2I( 0, 100 ) },
77 };
78
79 for( const CASE& c : cases )
80 {
81 TRANSFORM_TRS t( VECTOR2I( 0, 0 ), c.angle, 1.0, 1.0 );
82 CHECK_VEC_NEAR( t.Apply( c.p ), c.expected, IUNIT_TOL );
83 CHECK_VEC_NEAR( t.InverseApply( c.expected ), c.p, IUNIT_TOL );
84 }
85}
86
87
89{
90 TRANSFORM_TRS uniform( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 2.0 );
91 BOOST_CHECK( uniform.IsUniformScale() );
92 CHECK_VEC_NEAR( uniform.Apply( VECTOR2I( 100, 50 ) ), VECTOR2I( 200, 100 ), 0 );
93 CHECK_VEC_NEAR( uniform.InverseApply( VECTOR2I( 200, 100 ) ), VECTOR2I( 100, 50 ), 0 );
94
95 TRANSFORM_TRS half( VECTOR2I( 0, 0 ), ANGLE_0, 0.5, 0.5 );
96 CHECK_VEC_NEAR( half.Apply( VECTOR2I( 100, 50 ) ), VECTOR2I( 50, 25 ), 0 );
97
98 TRANSFORM_TRS nonuniform( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 0.5 );
99 BOOST_CHECK( !nonuniform.IsUniformScale() );
100 CHECK_VEC_NEAR( nonuniform.Apply( VECTOR2I( 100, 100 ) ), VECTOR2I( 200, 50 ), 0 );
101 CHECK_VEC_NEAR( nonuniform.InverseApply( VECTOR2I( 200, 50 ) ), VECTOR2I( 100, 100 ), 0 );
102}
103
104
105BOOST_AUTO_TEST_CASE( CompoundSrtOracle )
106{
107 // p (10, 0): scale 2x to (20, 0); rotate 90 to (0, -20); translate to (1000, 480).
108 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_90, 2.0, 2.0 );
109
110 CHECK_VEC_NEAR( t.Apply( VECTOR2I( 10, 0 ) ), VECTOR2I( 1000, 480 ), IUNIT_TOL );
111 CHECK_VEC_NEAR( t.InverseApply( t.Apply( VECTOR2I( 10, 0 ) ) ), VECTOR2I( 10, 0 ), IUNIT_TOL );
112}
113
114
115BOOST_AUTO_TEST_CASE( InverseApplyRoundTrip )
116{
117 const std::vector<TRANSFORM_TRS> ts = {
118 TRANSFORM_TRS( VECTOR2I( 100, 200 ), EDA_ANGLE( 37.5, DEGREES_T ), 1.0, 1.0 ),
119 TRANSFORM_TRS( VECTOR2I( -50, 75 ), EDA_ANGLE( 123.4, DEGREES_T ), 1.5, 1.5 ),
120 TRANSFORM_TRS( VECTOR2I( 0, 0 ), EDA_ANGLE( 60.0, DEGREES_T ), 2.0, 0.5 ),
121 TRANSFORM_TRS( VECTOR2I( 1234, -567 ), EDA_ANGLE( 89.0, DEGREES_T ), 0.75, 1.25 ),
122 };
123
124 const std::vector<VECTOR2I> probes = {
125 { 0, 0 }, { 100, 0 }, { 0, 100 }, { 250, -125 }, { -300, 400 },
126 };
127
128 for( const TRANSFORM_TRS& t : ts )
129 {
130 for( const VECTOR2I& p : probes )
131 {
132 VECTOR2I round = t.InverseApply( t.Apply( p ) );
133 CHECK_VEC_NEAR( round, p, IUNIT_TOL );
134 }
135 }
136}
137
138
139BOOST_AUTO_TEST_CASE( InvertExactWhenUniformOrAxisAligned )
140{
141 // Invert is exact only for uniform scale or zero rotation.
142 const std::vector<TRANSFORM_TRS> exactlyInvertible = {
143 TRANSFORM_TRS( VECTOR2I( 100, 200 ), EDA_ANGLE( 30.0, DEGREES_T ), 2.0, 2.0 ),
144 TRANSFORM_TRS( VECTOR2I( -50, 75 ), ANGLE_0, 2.0, 0.5 ),
145 TRANSFORM_TRS( VECTOR2I( 0, 0 ), EDA_ANGLE( 45.0, DEGREES_T ), 1.0, 1.0 ),
146 };
147
148 const VECTOR2I probe( 250, -125 );
149
150 for( const TRANSFORM_TRS& t : exactlyInvertible )
151 {
152 TRANSFORM_TRS inv = t.Invert();
153 VECTOR2I round = inv.Apply( t.Apply( probe ) );
154 CHECK_VEC_NEAR( round, probe, IUNIT_TOL );
155 }
156}
157
158
159BOOST_AUTO_TEST_CASE( ComposeWithTranslateOnlyOuter )
160{
161 TRANSFORM_TRS inner( VECTOR2I( 100, 50 ), EDA_ANGLE( 30.0, DEGREES_T ), 2.0, 0.5 );
162 TRANSFORM_TRS outer( VECTOR2I( 1000, 1000 ), ANGLE_0, 1.0, 1.0 );
163
164 TRANSFORM_TRS composed = inner.Compose( outer );
165
166 const VECTOR2I probe( 25, 75 );
167 CHECK_VEC_NEAR( composed.Apply( probe ), outer.Apply( inner.Apply( probe ) ), IUNIT_TOL );
168}
169
170
171BOOST_AUTO_TEST_CASE( ComposeWithUniformScaleOuter )
172{
173 TRANSFORM_TRS inner( VECTOR2I( 100, 50 ), EDA_ANGLE( 30.0, DEGREES_T ), 1.0, 1.0 );
174 TRANSFORM_TRS outer( VECTOR2I( 200, -100 ), EDA_ANGLE( 45.0, DEGREES_T ), 2.0, 2.0 );
175
176 TRANSFORM_TRS composed = inner.Compose( outer );
177
178 const VECTOR2I probe( 30, 40 );
179 CHECK_VEC_NEAR( composed.Apply( probe ), outer.Apply( inner.Apply( probe ) ), 2 );
180}
181
182
183BOOST_AUTO_TEST_CASE( RescaleAroundFixedPointInvariant )
184{
185 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.0, 1.0 );
186 const VECTOR2I libPoint( 25, 25 );
187 const VECTOR2I fixedPoint = t.Apply( libPoint );
188
189 TRANSFORM_TRS rescaled = t.RescaleAround( fixedPoint, 3.0, 3.0 );
190
191 CHECK_VEC_NEAR( rescaled.Apply( libPoint ), fixedPoint, IUNIT_TOL );
192}
193
194
195BOOST_AUTO_TEST_CASE( RescaleAroundComposesScale )
196{
197 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.5, 1.5 );
198
199 TRANSFORM_TRS rescaled = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 2.0 );
200
201 BOOST_CHECK_CLOSE( rescaled.GetScaleX(), 3.0, 1e-6 );
202 BOOST_CHECK_CLOSE( rescaled.GetScaleY(), 3.0, 1e-6 );
203}
204
205
206BOOST_AUTO_TEST_CASE( RescaleAroundAnchorFollowsCenter )
207{
208 // 2x rescale around (0, 0): anchor (1000, 500) goes to (2000, 1000).
209 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), ANGLE_0, 1.0, 1.0 );
210 TRANSFORM_TRS r = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 2.0 );
211
212 CHECK_VEC_NEAR( r.GetTranslate(), VECTOR2I( 2000, 1000 ), 0 );
213}
214
215
216BOOST_AUTO_TEST_CASE( RescaleAroundRotatedNonUniformScalesLocalAxes )
217{
218 // The scale factors act in the footprint's own frame, so a non-uniform
219 // rescale must apply directly (Sx *= aSx, Sy *= aSy) and never swap axes
220 // with the rotation.
221 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), EDA_ANGLE( 90.0, DEGREES_T ), 1.0, 1.0 );
222
223 TRANSFORM_TRS r = t.RescaleAround( VECTOR2I( 0, 0 ), 2.0, 3.0 );
224
225 BOOST_CHECK_CLOSE( r.GetScaleX(), 2.0, 1e-6 );
226 BOOST_CHECK_CLOSE( r.GetScaleY(), 3.0, 1e-6 );
227}
228
229
230BOOST_AUTO_TEST_CASE( RescaleAroundRotatedFixedPointInvariant )
231{
232 // The fixed point stays put even with rotation and a non-uniform scale.
233 TRANSFORM_TRS t( VECTOR2I( 1000, 500 ), EDA_ANGLE( 90.0, DEGREES_T ), 1.0, 1.0 );
234 const VECTOR2I libPoint( 25, 40 );
235 const VECTOR2I fixedPoint = t.Apply( libPoint );
236
237 TRANSFORM_TRS r = t.RescaleAround( fixedPoint, 2.0, 3.0 );
238
239 CHECK_VEC_NEAR( r.Apply( libPoint ), fixedPoint, IUNIT_TOL );
240}
241
242
243BOOST_AUTO_TEST_CASE( ComposeAssociativity )
244{
245 // Translate-only outers keep the math exact.
246 TRANSFORM_TRS A( VECTOR2I( 10, 20 ), EDA_ANGLE( 30.0, DEGREES_T ), 1.0, 1.0 );
247 TRANSFORM_TRS B( VECTOR2I( 50, -50 ), ANGLE_0, 1.0, 1.0 );
248 TRANSFORM_TRS C( VECTOR2I( 100, 0 ), ANGLE_0, 1.0, 1.0 );
249
250 TRANSFORM_TRS leftAssoc = A.Compose( B ).Compose( C );
251 TRANSFORM_TRS rightAssoc = A.Compose( B.Compose( C ) );
252
253 const VECTOR2I probe( 7, 13 );
254 CHECK_VEC_NEAR( leftAssoc.Apply( probe ), rightAssoc.Apply( probe ), IUNIT_TOL );
255}
256
257
258BOOST_AUTO_TEST_CASE( EqualityAndInequality )
259{
260 TRANSFORM_TRS a( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.5 );
261 TRANSFORM_TRS b( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.5 );
262 TRANSFORM_TRS c( VECTOR2I( 100, 200 ), ANGLE_45, 1.5, 0.6 );
263
264 BOOST_CHECK( a == b );
265 BOOST_CHECK( !( a == c ) );
266 BOOST_CHECK( a != c );
267}
268
269
270BOOST_AUTO_TEST_CASE( ApplyLinearScaleArithmeticMean )
271{
272 TRANSFORM_TRS t( VECTOR2I( 0, 0 ), ANGLE_0, 2.0, 1.0 );
273 BOOST_CHECK_CLOSE( t.ApplyLinearScale( 100.0 ), 150.0, 1e-9 );
274
275 TRANSFORM_TRS uniform( VECTOR2I( 0, 0 ), ANGLE_0, 0.5, 0.5 );
276 BOOST_CHECK_CLOSE( uniform.ApplyLinearScale( 100.0 ), 50.0, 1e-9 );
277}
278
279
VECTOR2I InverseApply(const VECTOR2I &aPoint) const
TRANSFORM_TRS Compose(const TRANSFORM_TRS &aOuter) const
bool IsUniformScale() const
TRANSFORM_TRS Invert() const
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:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:416
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
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:683