1 /*
2 Copyright (c) 2013-2021 Timur Gafarov, Martin Cejp
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * Matrix-based geometric transformations
31  * 
32  * Copyright: Timur Gafarov 2013-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov
35  */
36 module dlib.math.transformation;
37 
38 import std.math;
39 
40 import dlib.math.utils;
41 import dlib.math.vector;
42 import dlib.math.matrix;
43 import dlib.math.quaternion;
44 
45 /**
46  * Setup a rotation matrix, given Euler angles in radians
47  */
48 Matrix!(T,4) fromEuler(T) (Vector!(T,3) v)
49 {
50     auto res = Matrix!(T,4).identity;
51 
52     T cx = cos(v.x);
53     T sx = sin(v.x);
54     T cy = cos(v.y);
55     T sy = sin(v.y);
56     T cz = cos(v.z);
57     T sz = sin(v.z);
58 
59     T sxsy = sx * sy;
60     T cxsy = cx * sy;
61 
62     res.a11 =  (cy * cz);
63     res.a12 =  (sxsy * cz) + (cx * sz);
64     res.a13 = -(cxsy * cz) + (sx * sz);
65 
66     res.a21 = -(cy * sz);
67     res.a22 = -(sxsy * sz) + (cx * cz);
68     res.a23 =  (cxsy * sz) + (sx * cz);
69 
70     res.a31 =  (sy);
71     res.a32 = -(sx * cy);
72     res.a33 =  (cx * cy);
73 
74     return res;
75 }
76 
77 /**
78  * Setup the Euler angles in radians, given a rotation matrix
79  */
80 Vector!(T,3) toEuler(T) (Matrix!(T,4) m)
81 do
82 {
83     Vector!(T,3) v;
84 
85     v.y = asin(m.a31);
86 
87     T cy = cos(v.y);
88     T oneOverCosY = 1.0 / cy;
89 
90     if (fabs(cy) > 0.001)
91     {
92         v.x = atan2(-m.a32 * oneOverCosY, m.a33 * oneOverCosY);
93         v.z = atan2(-m.a21 * oneOverCosY, m.a11 * oneOverCosY);
94     }
95     else
96     {
97         v.x = 0.0;
98         v.z = atan2(m.a12, m.a22);
99     }
100 
101     return v;
102 }
103 
104 /**
105  * Right vector of the matrix
106  */
107 Vector!(T,3) right(T) (Matrix!(T,4) m)
108 do
109 {
110     return Vector!(T,3)(m.a11, m.a21, m.a31);
111 }
112 
113 /**
114  * Up vector of the matrix
115  */
116 Vector!(T,3) up(T) (Matrix!(T,4) m)
117 do
118 {
119     return Vector!(T,3)(m.a12, m.a22, m.a32);
120 }
121 
122 /**
123  * Forward vector of the matrix
124  */
125 Vector!(T,3) forward(T) (Matrix!(T,4) m)
126 do
127 {
128     return Vector!(T,3)(m.a13, m.a23, m.a33);
129 }
130 
131 /**
132  * Translation vector of the matrix
133  */
134 Vector!(T,3) translation(T) (Matrix!(T,4) m)
135 do
136 {
137     return Vector!(T,3)(m.a14, m.a24, m.a34);
138 }
139 
140 ///
141 unittest
142 {
143     Matrix4f tm = matrixf(
144         1.0f, 0.0f, 0.0f, 3.0f,
145         0.0f, 1.0f, 0.0f, 5.0f,
146         0.0f, 0.0f, 1.0f, 2.5f,
147         0.0f, 0.0f, 0.0f, 1.0f,
148     );
149     
150     Vector3f t = translation(tm);
151     
152     assert(t == Vector3f(3.0f, 5.0f, 2.5f));
153 }
154 
155 /**
156  * Scaling vector of the matrix
157  */
158 Vector!(T,3) scaling(T) (Matrix!(T,4) m)
159 do
160 {
161     T sx = Vector!(T,3)(m.a11, m.a12, m.a13).length;
162     T sy = Vector!(T,3)(m.a21, m.a22, m.a23).length;
163     T sz = Vector!(T,3)(m.a31, m.a32, m.a33).length;
164     return Vector!(T,3)(sx, sy, sz);
165 }
166 
167 /**
168  * Create a matrix to perform a rotation about a world axis
169  * (theta in radians)
170  */
171 Matrix!(T,4) rotationMatrix(T) (uint rotaxis, T theta)
172 do
173 {
174     auto res = Matrix!(T,4).identity;
175 
176     T s = sin(theta);
177     T c = cos(theta);
178 
179     switch (rotaxis)
180     {
181         case Axis.x:
182             res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0;
183             res.a21 = 0.0; res.a22 = c;   res.a23 =  s;
184             res.a31 = 0.0; res.a32 = -s;  res.a33 =  c;
185             break;
186 
187         case Axis.y:
188             res.a11 = c;   res.a12 = 0.0; res.a13 = -s;
189             res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0;
190             res.a31 = s;   res.a32 = 0.0; res.a33 = c;
191             break;
192 
193         case Axis.z:
194             res.a11 = c;   res.a12 =  s;  res.a13 = 0.0;
195             res.a21 = -s;  res.a22 =  c;  res.a23 = 0.0;
196             res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0;
197             break;
198 
199         default:
200             assert(0);
201     }
202 
203     return res;
204 }
205 
206 /**
207  * Create a translation matrix given a translation vector
208  */
209 Matrix!(T,4) translationMatrix(T) (Vector!(T,3) v)
210 do
211 {
212     auto res = Matrix!(T,4).identity;
213     res.a14 = v.x;
214     res.a24 = v.y;
215     res.a34 = v.z;
216     return res;
217 }
218 
219 ///
220 unittest
221 {
222     Matrix4f tm = translationMatrix(Vector3f(3.0f, 5.0f, 2.5f));
223     Vector3f t = translation(tm);
224     assert(t == Vector3f(3.0f, 5.0f, 2.5f));
225 }
226 
227 /**
228  * Create a matrix to perform scale on each axis
229  */
230 Matrix!(T,4) scaleMatrix(T) (Vector!(T,3) v)
231 do
232 {
233     auto res = Matrix!(T,4).identity;
234     res.a11 = v.x;
235     res.a22 = v.y;
236     res.a33 = v.z;
237     return res;
238 }
239 
240 /**
241  * Setup the matrix to perform scale along an arbitrary axis
242  */
243 Matrix!(T,4) scaleAlongAxisMatrix(T) (Vector!(T,3) scaleAxis, T k)
244 in
245 {
246     assert (fabs (dot(scaleAxis, scaleAxis) - 1.0) < 0.001);
247 }
248 do
249 {
250     auto res = Matrix!(T,4).identity;
251 
252     T a = k - 1.0;
253     T ax = a * scaleAxis.x;
254     T ay = a * scaleAxis.y;
255     T az = a * scaleAxis.z;
256 
257     res.a11 = (ax * scaleAxis.x) + 1.0;
258     res.a22 = (ay * scaleAxis.y) + 1.0;
259     res.a33 = (az * scaleAxis.z) + 1.0;
260 
261     res.a12 = res.a21 = (ax * scaleAxis.y);
262     res.a13 = res.a31 = (ax * scaleAxis.z);
263     res.a23 = res.a32 = (ay * scaleAxis.z);
264 
265     return res;
266 }
267 
268 /**
269  * Setup the matrix to perform a shear
270  */
271 Matrix!(T,4) shearMatrix(T) (uint shearAxis, T s, T t)
272 do
273 {
274     // NOTE: needs test
275     auto res = Matrix!(T,4).identity;
276 
277     switch (shearAxis)
278     {
279         case Axis.x:
280             res.a11 = 1.0; res.a12 = 0.0; res.a13 = 0.0;
281             res.a21 = s;   res.a22 = 1.0; res.a23 = 0.0;
282             res.a31 = t;   res.a32 = 0.0; res.a33 = 1.0;
283             break;
284 
285         case Axis.y:
286             res.a11 = 1.0; res.a12 = s;   res.a13 = 0.0;
287             res.a21 = 0.0; res.a22 = 1.0; res.a23 = 0.0;
288             res.a31 = 0.0; res.a32 = t;   res.a33 = 1.0;
289             break;
290 
291         case Axis.z:
292             res.a11 = 1.0; res.a12 = 0.0; res.a13 = s;
293             res.a21 = 0.0; res.a22 = 1.0; res.a23 = t;
294             res.a31 = 0.0; res.a32 = 0.0; res.a33 = 1.0;
295             break;
296 
297         default:
298             assert(0);
299     }
300 
301     return res;
302 }
303 
304 /**
305  * Setup the matrix to perform a projection onto a plane passing
306  * through the origin. The plane is perpendicular to the
307  * unit vector n.
308  */
309 Matrix!(T,4) projectionMatrix(T) (Vector!(T,3) n)
310 in
311 {
312     assert (fabs(dot(n, n) - 1.0) < 0.001);
313 }
314 do
315 {
316     // NOTE: needs test
317     auto res = Matrix!(T,4).identity;
318 
319     res.a11 = 1.0 - (n.x * n.x);
320     res.a22 = 1.0 - (n.y * n.y);
321     res.a33 = 1.0 - (n.z * n.z);
322 
323     res.a12 = res.a21 = -(n.x * n.y);
324     res.a13 = res.a31 = -(n.x * n.z);
325     res.a23 = res.a32 = -(n.y * n.z);
326 
327     return res;
328 }
329 
330 /**
331  * Setup the matrix to perform a reflection about a plane parallel
332  * to a cardinal plane.
333  */
334 Matrix!(T,4) reflectionMatrix(T) (Axis reflectionAxis, T k)
335 do
336 {
337     auto res = Matrix!(T,4).identity;
338 
339     switch (reflectionAxis)
340     {
341         case Axis.x:
342             res.a11 = -1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 2.0 * k;
343             res.a12 =  0.0; res.a22 =  1.0; res.a32 =  0.0; res.a42 = 0.0;
344             res.a13 =  0.0; res.a23 =  0.0; res.a33 =  1.0; res.a43 = 0.0;
345             break;
346 
347         case Axis.y:
348             res.a11 =  1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 0.0;
349             res.a12 =  0.0; res.a22 = -1.0; res.a32 =  0.0; res.a42 = 2.0 * k;
350             res.a13 =  0.0; res.a23 =  0.0; res.a33 =  1.0; res.a43 = 0.0;
351             break;
352 
353         case Axis.z:
354             res.a11 =  1.0; res.a21 =  0.0; res.a31 =  0.0; res.a41 = 0.0;
355             res.a12 =  0.0; res.a22 =  1.0; res.a32 =  0.0; res.a42 = 0.0;
356             res.a13 =  0.0; res.a23 =  0.0; res.a33 = -1.0; res.a43 = 2.0 * k;
357             break;
358 
359         default:
360             assert(0);
361     }
362 
363     return res;
364 }
365 
366 /**
367  * Setup the matrix to perform a reflection about an arbitrary plane
368  * through the origin. The unit vector n is perpendicular to the plane.
369  */
370 Matrix!(T,4) axisReflectionMatrix(T) (Vector!(T,3) n)
371 in
372 {
373     assert (fabs(dot(n, n) - 1.0) < 0.001);
374 }
375 do
376 {
377     auto res = Matrix!(T,4).identity;
378 
379     T ax = -2.0 * n.x;
380     T ay = -2.0 * n.y;
381     T az = -2.0 * n.z;
382 
383     res.a11 = 1.0 + (ax * n.x);
384     res.a22 = 1.0 + (ay * n.y);
385     res.a32 = 1.0 + (az * n.z);
386 
387     res.a12 = res.a21 = (ax * n.y);
388     res.a13 = res.a31 = (ax * n.z);
389     res.a23 = res.a32 = (ay * n.z);
390 
391     return res;
392 }
393 
394 /**
395  * Setup the matrix to perform a "Look At" transformation
396  * like a first person camera
397  */
398 Matrix!(T,4) lookAtMatrix(T) (Vector!(T,3) eye, Vector!(T,3) center, Vector!(T,3) up)
399 do
400 {
401     auto Result = Matrix!(T,4).identity;
402 
403     auto f = (center - eye).normalized;
404     auto u = (up).normalized;
405     auto s = cross(f, u).normalized;
406     u = cross(s, f);
407 
408     Result[0,0] = s.x;
409     Result[0,1] = s.y;
410     Result[0,2] = s.z;
411     Result[1,0] = u.x;
412     Result[1,1] = u.y;
413     Result[1,2] = u.z;
414     Result[2,0] =-f.x;
415     Result[2,1] =-f.y;
416     Result[2,2] =-f.z;
417     Result[0,3] =-dot(s, eye);
418     Result[1,3] =-dot(u, eye);
419     Result[2,3] = dot(f, eye);
420     return Result;
421 }
422 
423 /**
424  * Setup a frustum matrix given the left, right, bottom, top, near, and far
425  * values for the frustum boundaries.
426  */
427 Matrix!(T,4) frustumMatrix(T) (T l, T r, T b, T t, T n, T f)
428 in
429 {
430     assert (n >= 0.0);
431     assert (f >= 0.0);
432 }
433 do
434 {
435     auto res = Matrix!(T,4).identity;
436 
437     T width  = r - l;
438     T height = t - b;
439     T depth  = f - n;
440 
441     res.arrayof[0] = (2 * n) / width;
442     res.arrayof[1] = 0.0;
443     res.arrayof[2] = 0.0;
444     res.arrayof[3] = 0.0;
445 
446     res.arrayof[4] = 0.0;
447     res.arrayof[5] = (2 * n) / height;
448     res.arrayof[6] = 0.0;
449     res.arrayof[7] = 0.0;
450 
451     res.arrayof[8] = (r + l) / width;
452     res.arrayof[9] = (t + b) / height;
453     res.arrayof[10]= -(f + n) / depth;
454     res.arrayof[11]= -1.0;
455 
456     res.arrayof[12]= 0.0;
457     res.arrayof[13]= 0.0;
458     res.arrayof[14]= -(2 * f * n) / depth;
459     res.arrayof[15]= 0.0;
460 
461     return res;
462 }
463 
464 /**
465  * Setup a perspective matrix given the field-of-view in the Y direction
466  * in degrees, the aspect ratio of Y/X, and near and far plane distances
467  */
468 Matrix!(T,4) perspectiveMatrix(T) (T fovY, T aspect, T n, T f)
469 do
470 {
471     auto res = Matrix!(T,4).identity;
472 
473     T angle;
474     T cot;
475 
476     angle = fovY / 2.0;
477     angle = degtorad(angle);
478 
479     cot = cos(angle) / sin(angle);
480 
481     res.arrayof[0] = cot / aspect;
482     res.arrayof[1] = 0.0;
483     res.arrayof[2] = 0.0;
484     res.arrayof[3] = 0.0;
485 
486     res.arrayof[4] = 0.0;
487     res.arrayof[5] = cot;
488     res.arrayof[6] = 0.0;
489     res.arrayof[7] = 0.0;
490 
491     res.arrayof[8] = 0.0;
492     res.arrayof[9] = 0.0;
493     res.arrayof[10]= -(f + n) / (f - n);
494     res.arrayof[11]= -1.0f; //-(2 * f * n) / (f - n);
495 
496     res.arrayof[12]= 0.0;
497     res.arrayof[13]= 0.0;
498     res.arrayof[14]= -(2 * f * n) / (f - n); //-1.0;
499     res.arrayof[15]= 0.0;
500 
501     return res;
502 }
503 
504 /**
505  * Setup an orthographic Matrix4x4 given the left, right, bottom, top, near,
506  * and far values for the frustum boundaries.
507  */
508 Matrix!(T,4) orthoMatrix(T) (T l, T r, T b, T t, T n, T f)
509 do
510 {
511     auto res = Matrix!(T,4).identity;
512 
513     T width  = r - l;
514     T height = t - b;
515     T depth  = f - n;
516 
517     res.arrayof[0] =  2.0 / width;
518     res.arrayof[1] =  0.0;
519     res.arrayof[2] =  0.0;
520     res.arrayof[3] =  0.0;
521 
522     res.arrayof[4] =  0.0;
523     res.arrayof[5] =  2.0 / height;
524     res.arrayof[6] =  0.0;
525     res.arrayof[7] =  0.0;
526 
527     res.arrayof[8] =  0.0;
528     res.arrayof[9] =  0.0;
529     res.arrayof[10]= -2.0 / depth;
530     res.arrayof[11]=  0.0;
531 
532     res.arrayof[12]= -(r + l) / width;
533     res.arrayof[13]= -(t + b) / height;
534     res.arrayof[14]= -(f + n) / depth;
535     res.arrayof[15]=  1.0;
536 
537     return res;
538 }
539 
540 /**
541  * Setup an orientation matrix using 3 basis normalized vectors
542  */
543 Matrix!(T,4) orthoNormalMatrix(T) (Vector!(T,3) xdir, Vector!(T,3) ydir, Vector!(T,3) zdir)
544 do
545 {
546     auto res = Matrix!(T,4).identity;
547 
548     res.arrayof[0] = xdir.x; res.arrayof[4] = ydir.x; res.arrayof[8] = zdir.x; res.arrayof[12] = 0.0;
549     res.arrayof[1] = xdir.y; res.arrayof[5] = ydir.y; res.arrayof[9] = zdir.y; res.arrayof[13] = 0.0;
550     res.arrayof[2] = xdir.z; res.arrayof[6] = ydir.z; res.arrayof[10]= zdir.z; res.arrayof[14] = 0.0;
551     res.arrayof[3] = 0.0;    res.arrayof[7] = 0.0;    res.arrayof[11]= 0.0;    res.arrayof[15] = 1.0;
552 
553     return res;
554 }
555 
556 /**
557  * Setup a matrix that flattens geometry into a plane,
558  * as if it were casting a shadow from a light
559  */
560 Matrix!(T,4) shadowMatrix(T) (Vector!(T,4) groundplane, Vector!(T,4) lightpos)
561 {
562     T d = dot(groundplane, lightpos);
563 
564     auto res = Matrix!(T,4).identity;
565 
566     res.a11 = d-lightpos.x * groundplane.x;
567     res.a12 =  -lightpos.x * groundplane.y;
568     res.a13 =  -lightpos.x * groundplane.z;
569     res.a14 =  -lightpos.x * groundplane.w;
570 
571     res.a21 =  -lightpos.y * groundplane.x;
572     res.a22 = d-lightpos.y * groundplane.y;
573     res.a23 =  -lightpos.y * groundplane.z;
574     res.a24 =  -lightpos.y * groundplane.w;
575 
576     res.a31 =  -lightpos.z * groundplane.x;
577     res.a32 =  -lightpos.z * groundplane.y;
578     res.a33 = d-lightpos.z * groundplane.z;
579     res.a34 =  -lightpos.z * groundplane.w;
580 
581     res.a41 =  -lightpos.w * groundplane.x;
582     res.a42 =  -lightpos.w * groundplane.y;
583     res.a43 =  -lightpos.w * groundplane.z;
584     res.a44 = d-lightpos.w * groundplane.w;
585 
586     return res;
587 }
588 
589 /**
590  * Setup an orientation matrix using forward direction vector
591  */
592 Matrix!(T,4) directionToMatrix(T) (Vector!(T,3) zdir)
593 {
594     Vector!(T,3) xdir = Vector!(T,3)(0, 0, 1);
595     Vector!(T,3) ydir;
596     float d = zdir.z;
597 
598     if (d > -0.999999999 && d < 0.999999999)
599     {
600         xdir = xdir - zdir * d;
601         xdir.normalize();
602         ydir = cross(zdir, xdir);
603     }
604     else
605     {
606         xdir = Vector!(T,3)(zdir.z, 0, -zdir.x);
607         ydir = Vector!(T,3)(0, 1, 0);
608     }
609 
610     auto m = Matrix!(T,4).identity;
611 
612     m.a13 = zdir.x;
613     m.a23 = zdir.y;
614     m.a33 = zdir.z;
615 
616     m.a11 = xdir.x;
617     m.a21 = xdir.y;
618     m.a31 = xdir.z;
619 
620     m.a12 = ydir.x;
621     m.a22 = ydir.y;
622     m.a32 = ydir.z;
623 
624     return m;
625 }
626 
627 /**
628  * Setup an orientation matrix that performs rotation
629  * between two vectors
630  *
631  * Currently this is just a shortcut
632  * for dlib.math.quaternion.rotationBetween
633  */
634 Matrix!(T,4) rotationBetweenVectors(T) (Vector!(T,3) source, Vector!(T,3) target)
635 {
636     return rotationBetween(source, target).toMatrix4x4;
637 }
638 
639 ///
640 unittest
641 {
642     bool isAlmostZero4(Vector4f v)
643     {
644         float e = 0.002f;
645 
646         return abs(v.x) < e &&
647                abs(v.y) < e &&
648                abs(v.z) < e &&
649                abs(v.w) < e;
650     }
651 
652     // build ModelView (World to Camera)
653     vec3 center = vec3(0.0f, 0.0f, 0.0f);
654     vec3 eye = center + vec3(0.0f, 1.0f, 1.0f);
655     vec3 up = vec3(0.0f, -0.707f, 0.707f);
656 
657     Matrix4f modelView = lookAtMatrix(eye, center, up);
658 
659     // build Projection (Camera to Eye)
660     Matrix4f projection = perspectiveMatrix(45.0f, 16.0f / 9.0f, 1.0f, 100.0f);
661 
662     // compose into one transformation
663     Matrix4f projectionModelView = projection * modelView;
664 
665     vec4 positionInWorld = vec4(0.0f, 0.0f, 0.0f, 1.0f);
666 
667     vec4 transformed1 =
668         positionInWorld * projectionModelView;
669 
670     vec4 transformed2 =
671         (positionInWorld * modelView) * projection;
672 
673     assert(isAlmostZero4(transformed1 - transformed2));
674 }
675 
676 /**
677  * Affine transformations in 2D space
678  */
679  Vector!(T,2) affineTransform2D(T)(Vector!(T,2) v, Matrix!(T,3) m)
680 {
681     return Vector!(T,2)
682     (
683         (v.x * m.a11) + (v.y * m.a12) + m.a13,
684         (v.x * m.a21) + (v.y * m.a22) + m.a23
685     );
686 }
687 
688 /**
689  * Translation in 2D space
690  */
691 Matrix!(T,3) translationMatrix2D(T) (Vector!(T,2) t)
692 do
693 {
694     Matrix!(T,3) res;
695     res.a11 = 1; res.a12 = 0; res.a13 = t.x;
696     res.a21 = 0; res.a22 = 1; res.a23 = t.y;
697     res.a31 = 0; res.a32 = 0; res.a33 = 1;
698     return res;
699 }
700 
701 alias translation2 = translationMatrix2D;
702 
703 /**
704  * Rotation in 2D space
705  */
706 Matrix!(T,3) rotationMatrix2D(T) (T theta)
707 do
708 {
709     Matrix!(T,3) res;
710     T s = sin(theta);
711     T c = cos(theta);
712     res.a11 = c;  res.a12 = s; res.a13 = 0;
713     res.a21 = -s; res.a22 = c; res.a23 = 0;
714     res.a31 = 0;  res.a32 = 0; res.a33 = 1;
715     return res;
716 }
717 
718 alias rotation2 = rotationMatrix2D;
719 
720 /**
721  * Scale in 2D space
722  */
723 Matrix!(T,3) scaleMatrix2D(T) (Vector!(T,2) s)
724 do
725 {
726     Matrix!(T,3) res;
727     res.a11 = s.x; res.a12 = 0;   res.a13 = 0;
728     res.a21 = 0;   res.a22 = s.y; res.a23 = 0;
729     res.a31 = 0;   res.a32 = 0;   res.a33 = 1;
730     return res;
731 }
732 
733 alias scale2 = scaleMatrix2D;
734 
735 ///
736 unittest
737 {
738     bool isAlmostZero2(Vector2f v)
739     {
740         float e = 0.002f;
741 
742         return abs(v.x) < e &&
743                abs(v.y) < e;
744     }
745     
746     vec2 v = vec2(1, 0);
747     Matrix3f tm = translationMatrix2D(vec2(2, 3));
748     vec2 vt = affineTransform2D(v, tm);
749     assert(isAlmostZero2(vt - vec2(3, 3)));
750     
751     Matrix3f rm = rotationMatrix2D(cast(float)PI);
752     vt = affineTransform2D(v, rm);
753     assert(isAlmostZero2(vt - vec2(-1, 0)));
754     
755     Matrix3f sm = scaleMatrix2D(vec2(2, 2));
756     vt = affineTransform2D(v, sm);
757     assert(isAlmostZero2(vt - vec2(2, 0)));
758 }