2525
2626using namespace yup ;
2727
28- TEST (AffineTransformOpsTests, TransformPointsMatchesScalarReference)
28+ class AffineTransformOpsTests : public ::testing::Test
29+ {
30+ protected:
31+ static void transformScalar (const float * srcXs, const float * srcYs,
32+ float * dstXs, float * dstYs, int numPoints,
33+ float sx, float shx, float tx, float shy, float sy, float ty) noexcept
34+ {
35+ for (int i = 0 ; i < numPoints; ++i)
36+ {
37+ const float x = srcXs[i];
38+ const float y = srcYs[i];
39+ dstXs[i] = sx * x + shx * y + tx;
40+ dstYs[i] = shy * x + sy * y + ty;
41+ }
42+ }
43+ };
44+
45+ TEST_F (AffineTransformOpsTests, TransformPointsMatchesScalarReference)
2946{
3047 constexpr int numPoints = 6 ;
3148 const float srcXs[numPoints] = { -2 .0f , -1 .0f , 0 .0f , 1 .0f , 2 .0f , 3 .0f };
@@ -48,3 +65,162 @@ TEST (AffineTransformOpsTests, TransformPointsMatchesScalarReference)
4865 EXPECT_NEAR (dstYs[i], shy * srcXs[i] + sy * srcYs[i] + ty, 1 .0e-5f );
4966 }
5067}
68+
69+ TEST_F (AffineTransformOpsTests, TransformPointsInPlace)
70+ {
71+ constexpr int numPoints = 5 ;
72+ float xs[numPoints] = { 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f };
73+ float ys[numPoints] = { 5 .0f , 4 .0f , 3 .0f , 2 .0f , 1 .0f };
74+
75+ const float origXs[numPoints] = { 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f };
76+ const float origYs[numPoints] = { 5 .0f , 4 .0f , 3 .0f , 2 .0f , 1 .0f };
77+
78+ constexpr float sx = 2 .0f , shx = 0 .5f , tx = 1 .0f ;
79+ constexpr float shy = -0 .5f , sy = 3 .0f , ty = -1 .0f ;
80+
81+ AffineTransformOperations::transformPoints (xs, ys, numPoints, sx, shx, tx, shy, sy, ty);
82+
83+ for (int i = 0 ; i < numPoints; ++i)
84+ {
85+ EXPECT_NEAR (xs[i], sx * origXs[i] + shx * origYs[i] + tx, 1 .0e-5f );
86+ EXPECT_NEAR (ys[i], shy * origXs[i] + sy * origYs[i] + ty, 1 .0e-5f );
87+ }
88+ }
89+
90+ TEST_F (AffineTransformOpsTests, IdentityTransformLeavesPointsUnchanged)
91+ {
92+ constexpr int numPoints = 8 ;
93+ const float srcXs[numPoints] = { 1 .0f , -2 .0f , 3 .0f , -4 .0f , 5 .0f , -6 .0f , 7 .0f , -8 .0f };
94+ const float srcYs[numPoints] = { 8 .0f , -7 .0f , 6 .0f , -5 .0f , 4 .0f , -3 .0f , 2 .0f , -1 .0f };
95+ float dstXs[numPoints] = {};
96+ float dstYs[numPoints] = {};
97+
98+ // Identity: sx=1, shx=0, tx=0, shy=0, sy=1, ty=0
99+ AffineTransformOperations::transformPoints (srcXs, srcYs, dstXs, dstYs, numPoints, 1 .0f , 0 .0f , 0 .0f , 0 .0f , 1 .0f , 0 .0f );
100+
101+ for (int i = 0 ; i < numPoints; ++i)
102+ {
103+ EXPECT_FLOAT_EQ (dstXs[i], srcXs[i]);
104+ EXPECT_FLOAT_EQ (dstYs[i], srcYs[i]);
105+ }
106+ }
107+
108+ TEST_F (AffineTransformOpsTests, PureTranslation)
109+ {
110+ constexpr int numPoints = 6 ;
111+ const float srcXs[numPoints] = { 0 .0f , 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f };
112+ const float srcYs[numPoints] = { 0 .0f , 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f };
113+ float dstXs[numPoints] = {};
114+ float dstYs[numPoints] = {};
115+
116+ constexpr float tx = 100 .0f , ty = 200 .0f ;
117+
118+ // Pure translation: sx=1, shx=0, shy=0, sy=1
119+ AffineTransformOperations::transformPoints (srcXs, srcYs, dstXs, dstYs, numPoints, 1 .0f , 0 .0f , tx, 0 .0f , 1 .0f , ty);
120+
121+ for (int i = 0 ; i < numPoints; ++i)
122+ {
123+ EXPECT_FLOAT_EQ (dstXs[i], srcXs[i] + tx);
124+ EXPECT_FLOAT_EQ (dstYs[i], srcYs[i] + ty);
125+ }
126+ }
127+
128+ TEST_F (AffineTransformOpsTests, PureScale)
129+ {
130+ constexpr int numPoints = 8 ;
131+ const float srcXs[numPoints] = { 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f , 6 .0f , 7 .0f , 8 .0f };
132+ const float srcYs[numPoints] = { 1 .0f , 2 .0f , 3 .0f , 4 .0f , 5 .0f , 6 .0f , 7 .0f , 8 .0f };
133+ float dstXs[numPoints] = {};
134+ float dstYs[numPoints] = {};
135+
136+ constexpr float sx = 2 .0f , sy = -0 .5f ;
137+
138+ AffineTransformOperations::transformPoints (srcXs, srcYs, dstXs, dstYs, numPoints, sx, 0 .0f , 0 .0f , 0 .0f , sy, 0 .0f );
139+
140+ for (int i = 0 ; i < numPoints; ++i)
141+ {
142+ EXPECT_FLOAT_EQ (dstXs[i], sx * srcXs[i]);
143+ EXPECT_FLOAT_EQ (dstYs[i], sy * srcYs[i]);
144+ }
145+ }
146+
147+ TEST_F (AffineTransformOpsTests, SinglePointTransform)
148+ {
149+ const float srcX = 3 .0f ;
150+ const float srcY = 4 .0f ;
151+ float dstX = 0 .0f ;
152+ float dstY = 0 .0f ;
153+
154+ constexpr float sx = 2 .0f , shx = 1 .0f , tx = -1 .0f ;
155+ constexpr float shy = 0 .5f , sy = -1 .0f , ty = 3 .0f ;
156+
157+ AffineTransformOperations::transformPoints (&srcX, &srcY, &dstX, &dstY, 1 , sx, shx, tx, shy, sy, ty);
158+
159+ EXPECT_NEAR (dstX, sx * srcX + shx * srcY + tx, 1 .0e-5f );
160+ EXPECT_NEAR (dstY, shy * srcX + sy * srcY + ty, 1 .0e-5f );
161+ }
162+
163+ TEST_F (AffineTransformOpsTests, ZeroPointsDoesNothing)
164+ {
165+ float dstXs[4 ] = { 99 .0f , 99 .0f , 99 .0f , 99 .0f };
166+ float dstYs[4 ] = { 99 .0f , 99 .0f , 99 .0f , 99 .0f };
167+
168+ // numPoints=0 should not write to output
169+ AffineTransformOperations::transformPoints (nullptr , nullptr , dstXs, dstYs, 0 , 1 .0f , 0 .0f , 0 .0f , 0 .0f , 1 .0f , 0 .0f );
170+
171+ for (int i = 0 ; i < 4 ; ++i)
172+ {
173+ EXPECT_FLOAT_EQ (dstXs[i], 99 .0f );
174+ EXPECT_FLOAT_EQ (dstYs[i], 99 .0f );
175+ }
176+ }
177+
178+ TEST_F (AffineTransformOpsTests, LargeBufferMatchesScalarReference)
179+ {
180+ constexpr int numPoints = 100 ;
181+ float srcXs[numPoints], srcYs[numPoints];
182+ float dstXs[numPoints], dstYs[numPoints];
183+ float refXs[numPoints], refYs[numPoints];
184+
185+ for (int i = 0 ; i < numPoints; ++i)
186+ {
187+ srcXs[i] = (float ) (i - 50 );
188+ srcYs[i] = (float ) (i * 2 - 100 );
189+ }
190+
191+ constexpr float sx = 1 .5f , shx = 0 .3f , tx = 7 .0f ;
192+ constexpr float shy = -0 .2f , sy = 2 .0f , ty = -3 .0f ;
193+
194+ AffineTransformOperations::transformPoints (srcXs, srcYs, dstXs, dstYs, numPoints, sx, shx, tx, shy, sy, ty);
195+ transformScalar (srcXs, srcYs, refXs, refYs, numPoints, sx, shx, tx, shy, sy, ty);
196+
197+ for (int i = 0 ; i < numPoints; ++i)
198+ {
199+ EXPECT_NEAR (dstXs[i], refXs[i], 1 .0e-4f );
200+ EXPECT_NEAR (dstYs[i], refYs[i], 1 .0e-4f );
201+ }
202+ }
203+
204+ TEST_F (AffineTransformOpsTests, ThreePointsUsesScalarTail)
205+ {
206+ // 3 points: the SIMD path handles 4 at a time, so all 3 go through scalar tail
207+ constexpr int numPoints = 3 ;
208+ const float srcXs[numPoints] = { 1 .0f , 2 .0f , 3 .0f };
209+ const float srcYs[numPoints] = { 4 .0f , 5 .0f , 6 .0f };
210+ float dstXs[numPoints] = {};
211+ float dstYs[numPoints] = {};
212+ float refXs[numPoints] = {};
213+ float refYs[numPoints] = {};
214+
215+ constexpr float sx = 2 .0f , shx = 0 .5f , tx = 1 .0f ;
216+ constexpr float shy = -0 .5f , sy = 3 .0f , ty = -1 .0f ;
217+
218+ AffineTransformOperations::transformPoints (srcXs, srcYs, dstXs, dstYs, numPoints, sx, shx, tx, shy, sy, ty);
219+ transformScalar (srcXs, srcYs, refXs, refYs, numPoints, sx, shx, tx, shy, sy, ty);
220+
221+ for (int i = 0 ; i < numPoints; ++i)
222+ {
223+ EXPECT_NEAR (dstXs[i], refXs[i], 1 .0e-5f );
224+ EXPECT_NEAR (dstYs[i], refYs[i], 1 .0e-5f );
225+ }
226+ }
0 commit comments