1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      Transformations.
12  *
13  *      By Pavel Sountsov.
14  *
15  *      See readme.txt for copyright information.
16  */
17 
18 #include "allegro5/allegro.h"
19 #include "allegro5/internal/aintern.h"
20 #include "allegro5/internal/aintern_bitmap.h"
21 #include "allegro5/internal/aintern_display.h"
22 #include "allegro5/internal/aintern_system.h"
23 #include "allegro5/internal/aintern_transform.h"
24 #include <math.h>
25 
26 /* ALLEGRO_DEBUG_CHANNEL("transformations") */
27 
28 /* Function: al_copy_transform
29  */
al_copy_transform(ALLEGRO_TRANSFORM * dest,const ALLEGRO_TRANSFORM * src)30 void al_copy_transform(ALLEGRO_TRANSFORM *dest, const ALLEGRO_TRANSFORM *src)
31 {
32    ASSERT(src);
33    ASSERT(dest);
34 
35    memcpy(dest, src, sizeof(ALLEGRO_TRANSFORM));
36 }
37 
38 /* Function: al_use_transform
39  */
al_use_transform(const ALLEGRO_TRANSFORM * trans)40 void al_use_transform(const ALLEGRO_TRANSFORM *trans)
41 {
42    ALLEGRO_BITMAP *target = al_get_target_bitmap();
43    ALLEGRO_DISPLAY *display;
44 
45    if (!target)
46       return;
47 
48    /* Changes to a back buffer should affect the front buffer, and vice versa.
49     * Currently we rely on the fact that in the OpenGL drivers the back buffer
50     * and front buffer bitmaps are exactly the same, and the DirectX driver
51     * doesn't support front buffer bitmaps.
52     */
53 
54    if (trans != &target->transform) {
55       al_copy_transform(&target->transform, trans);
56 
57       target->inverse_transform_dirty = true;
58    }
59 
60    /*
61     * When the drawing is held, we apply the transformations in software,
62     * so the hardware transformation has to be kept at identity.
63     */
64    if (!al_is_bitmap_drawing_held()) {
65       display = _al_get_bitmap_display(target);
66       if (display) {
67          display->vt->update_transformation(display, target);
68       }
69    }
70 }
71 
72 /* Function: al_use_projection_transform
73  */
al_use_projection_transform(const ALLEGRO_TRANSFORM * trans)74 void al_use_projection_transform(const ALLEGRO_TRANSFORM *trans)
75 {
76    ALLEGRO_BITMAP *target = al_get_target_bitmap();
77    ALLEGRO_DISPLAY *display;
78 
79    if (!target)
80       return;
81 
82    /* Memory bitmaps don't support custom projection transforms */
83    if (al_get_bitmap_flags(target) & ALLEGRO_MEMORY_BITMAP)
84       return;
85 
86    /* Changes to a back buffer should affect the front buffer, and vice versa.
87     * Currently we rely on the fact that in the OpenGL drivers the back buffer
88     * and front buffer bitmaps are exactly the same, and the DirectX driver
89     * doesn't support front buffer bitmaps.
90     */
91 
92    if (trans != &target->transform) {
93       al_copy_transform(&target->proj_transform, trans);
94    }
95 
96    display = _al_get_bitmap_display(target);
97    if (display) {
98       display->vt->update_transformation(display, target);
99    }
100 }
101 
102 /* Function: al_get_current_transform
103  */
al_get_current_transform(void)104 const ALLEGRO_TRANSFORM *al_get_current_transform(void)
105 {
106    ALLEGRO_BITMAP *target = al_get_target_bitmap();
107 
108    if (!target)
109       return NULL;
110 
111    return &target->transform;
112 }
113 
114 /* Function: al_get_current_projection_transform
115  */
al_get_current_projection_transform(void)116 const ALLEGRO_TRANSFORM *al_get_current_projection_transform(void)
117 {
118    ALLEGRO_BITMAP *target = al_get_target_bitmap();
119 
120    if (!target)
121       return NULL;
122 
123    return &target->proj_transform;
124 }
125 
126 /* Function: al_get_current_inverse_transform
127  */
al_get_current_inverse_transform(void)128 const ALLEGRO_TRANSFORM *al_get_current_inverse_transform(void)
129 {
130    ALLEGRO_BITMAP *target = al_get_target_bitmap();
131 
132    if (!target)
133       return NULL;
134 
135    if (target->inverse_transform_dirty) {
136       al_copy_transform(&target->inverse_transform, &target->transform);
137       al_invert_transform(&target->inverse_transform);
138    }
139 
140    return &target->inverse_transform;
141 }
142 
143 /* Function: al_identity_transform
144  */
al_identity_transform(ALLEGRO_TRANSFORM * trans)145 void al_identity_transform(ALLEGRO_TRANSFORM *trans)
146 {
147    ASSERT(trans);
148 
149    trans->m[0][0] = 1;
150    trans->m[0][1] = 0;
151    trans->m[0][2] = 0;
152    trans->m[0][3] = 0;
153 
154    trans->m[1][0] = 0;
155    trans->m[1][1] = 1;
156    trans->m[1][2] = 0;
157    trans->m[1][3] = 0;
158 
159    trans->m[2][0] = 0;
160    trans->m[2][1] = 0;
161    trans->m[2][2] = 1;
162    trans->m[2][3] = 0;
163 
164    trans->m[3][0] = 0;
165    trans->m[3][1] = 0;
166    trans->m[3][2] = 0;
167    trans->m[3][3] = 1;
168 }
169 
170 /* Function: al_build_transform
171  */
al_build_transform(ALLEGRO_TRANSFORM * trans,float x,float y,float sx,float sy,float theta)172 void al_build_transform(ALLEGRO_TRANSFORM *trans, float x, float y,
173    float sx, float sy, float theta)
174 {
175    float c, s;
176    ASSERT(trans);
177 
178    c = cosf(theta);
179    s = sinf(theta);
180 
181    trans->m[0][0] = sx * c;
182    trans->m[0][1] = sy * s;
183    trans->m[0][2] = 0;
184    trans->m[0][3] = 0;
185 
186    trans->m[1][0] = -sx * s;
187    trans->m[1][1] = sy * c;
188    trans->m[1][2] = 0;
189    trans->m[1][3] = 0;
190 
191    trans->m[2][0] = 0;
192    trans->m[2][1] = 0;
193    trans->m[2][2] = 1;
194    trans->m[2][3] = 0;
195 
196    trans->m[3][0] = x;
197    trans->m[3][1] = y;
198    trans->m[3][2] = 0;
199    trans->m[3][3] = 1;
200 }
201 
202 /* Function: al_build_camera_transform
203  */
al_build_camera_transform(ALLEGRO_TRANSFORM * trans,float position_x,float position_y,float position_z,float look_x,float look_y,float look_z,float up_x,float up_y,float up_z)204 void al_build_camera_transform(ALLEGRO_TRANSFORM *trans,
205    float position_x, float position_y, float position_z,
206    float look_x, float look_y, float look_z,
207    float up_x, float up_y, float up_z)
208 {
209    float x = position_x;
210    float y = position_y;
211    float z = position_z;
212    float xx, xy, xz, xnorm;
213    float yx, yy, yz;
214    float zx, zy, zz, znorm;
215 
216    al_identity_transform(trans);
217 
218    /* Get the z-axis (direction towards viewer) and normalize it.
219     */
220    zx = x - look_x;
221    zy = y - look_y;
222    zz = z - look_z;
223    znorm = sqrt(zx * zx + zy * zy + zz * zz);
224    if (znorm == 0)
225       return;
226    zx /= znorm;
227    zy /= znorm;
228    zz /= znorm;
229 
230    /* Get the x-axis (direction pointing to the right) as the cross product of
231     * the up-vector times the z-axis. We need to normalize it because we do
232     * neither require the up-vector to be normalized nor perpendicular.
233     */
234    xx = up_y * zz - zy * up_z;
235    xy = up_z * zx - zz * up_x;
236    xz = up_x * zy - zx * up_y;
237    xnorm = sqrt(xx * xx + xy * xy + xz * xz);
238    if (xnorm == 0)
239       return;
240    xx /= xnorm;
241    xy /= xnorm;
242    xz /= xnorm;
243 
244    /* Now use the cross product of z-axis and x-axis as our y-axis. This can
245     * have a different direction than the original up-vector but it will
246     * already be normalized.
247     */
248    yx = zy * xz - xy * zz;
249    yy = zz * xx - xz * zx;
250    yz = zx * xy - xx * zy;
251 
252    /* This is an inverse translation (subtract the camera position) followed by
253     * an inverse rotation (rotate in the opposite direction of the camera
254     * orientation).
255     */
256    trans->m[0][0] = xx;
257    trans->m[1][0] = xy;
258    trans->m[2][0] = xz;
259    trans->m[3][0] = xx * -x + xy * -y + xz * -z;
260    trans->m[0][1] = yx;
261    trans->m[1][1] = yy;
262    trans->m[2][1] = yz;
263    trans->m[3][1] = yx * -x + yy * -y + yz * -z;
264    trans->m[0][2] = zx;
265    trans->m[1][2] = zy;
266    trans->m[2][2] = zz;
267    trans->m[3][2] = zx * -x + zy * -y + zz * -z;
268 }
269 
270 /* Function: al_invert_transform
271  */
al_invert_transform(ALLEGRO_TRANSFORM * trans)272 void al_invert_transform(ALLEGRO_TRANSFORM *trans)
273 {
274    float det, t;
275    ASSERT(trans);
276 
277    det =  trans->m[0][0] *  trans->m[1][1] -  trans->m[1][0] *  trans->m[0][1];
278 
279    t =  trans->m[3][0];
280    trans->m[3][0] = ( trans->m[1][0] *  trans->m[3][1] - t *  trans->m[1][1]) / det;
281    trans->m[3][1] = (t *  trans->m[0][1] -  trans->m[0][0] *  trans->m[3][1]) / det;
282 
283    t =  trans->m[0][0];
284    trans->m[0][0] =  trans->m[1][1] / det;
285    trans->m[1][1] = t / det;
286 
287    trans->m[0][1] = - trans->m[0][1] / det;
288    trans->m[1][0] = - trans->m[1][0] / det;
289 }
290 
291 /* Function: al_transpose_transform
292  */
al_transpose_transform(ALLEGRO_TRANSFORM * trans)293 void al_transpose_transform(ALLEGRO_TRANSFORM *trans)
294 {
295    int i, j;
296    ASSERT(trans);
297 
298    ALLEGRO_TRANSFORM t = *trans;
299    for (i = 0; i < 4; i++) {
300       for (j = 0; j < 4; j++) {
301          trans->m[i][j] = t.m[j][i];
302       }
303    }
304 }
305 
306 /* Function: al_check_inverse
307  */
al_check_inverse(const ALLEGRO_TRANSFORM * trans,float tol)308 int al_check_inverse(const ALLEGRO_TRANSFORM *trans, float tol)
309 {
310    float det, norm, c0, c1, c3;
311    ASSERT(trans);
312 
313    det = fabsf(trans->m[0][0] *  trans->m[1][1] -  trans->m[1][0] *  trans->m[0][1]);
314    /*
315    We'll use the 1-norm, as it is the easiest to compute
316    */
317    c0 = fabsf(trans->m[0][0]) + fabsf(trans->m[0][1]);
318    c1 = fabsf(trans->m[1][0]) + fabsf(trans->m[1][1]);
319    c3 = fabsf(trans->m[3][0]) + fabsf(trans->m[3][1]) + 1;
320    norm = _ALLEGRO_MAX(_ALLEGRO_MAX(1, c0), _ALLEGRO_MAX(c1, c3));
321 
322    return det > tol * norm;
323 }
324 
325 /* Function: al_translate_transform
326  */
al_translate_transform(ALLEGRO_TRANSFORM * trans,float x,float y)327 void al_translate_transform(ALLEGRO_TRANSFORM *trans, float x, float y)
328 {
329    ASSERT(trans);
330 
331    trans->m[3][0] += x;
332    trans->m[3][1] += y;
333 }
334 
335 
336 /* Function: al_translate_transform_3d
337  */
al_translate_transform_3d(ALLEGRO_TRANSFORM * trans,float x,float y,float z)338 void al_translate_transform_3d(ALLEGRO_TRANSFORM *trans, float x, float y,
339     float z)
340 {
341    ASSERT(trans);
342 
343    trans->m[3][0] += x;
344    trans->m[3][1] += y;
345    trans->m[3][2] += z;
346 }
347 
348 
349 /* Function: al_rotate_transform
350  */
al_rotate_transform(ALLEGRO_TRANSFORM * trans,float theta)351 void al_rotate_transform(ALLEGRO_TRANSFORM *trans, float theta)
352 {
353    float c, s;
354    float t;
355    ASSERT(trans);
356 
357    c = cosf(theta);
358    s = sinf(theta);
359 
360    t = trans->m[0][0];
361    trans->m[0][0] = t * c - trans->m[0][1] * s;
362    trans->m[0][1] = t * s + trans->m[0][1] * c;
363 
364    t = trans->m[1][0];
365    trans->m[1][0] = t * c - trans->m[1][1] * s;
366    trans->m[1][1] = t * s + trans->m[1][1] * c;
367 
368    t = trans->m[3][0];
369    trans->m[3][0] = t * c - trans->m[3][1] * s;
370    trans->m[3][1] = t * s + trans->m[3][1] * c;
371 }
372 
373 /* Function: al_scale_transform
374  */
al_scale_transform(ALLEGRO_TRANSFORM * trans,float sx,float sy)375 void al_scale_transform(ALLEGRO_TRANSFORM *trans, float sx, float sy)
376 {
377    ASSERT(trans);
378 
379    trans->m[0][0] *= sx;
380    trans->m[0][1] *= sy;
381 
382    trans->m[1][0] *= sx;
383    trans->m[1][1] *= sy;
384 
385    trans->m[3][0] *= sx;
386    trans->m[3][1] *= sy;
387 }
388 
389 
390 /* Function: al_scale_transform_3d
391  */
al_scale_transform_3d(ALLEGRO_TRANSFORM * trans,float sx,float sy,float sz)392 void al_scale_transform_3d(ALLEGRO_TRANSFORM *trans, float sx, float sy,
393     float sz)
394 {
395    ASSERT(trans);
396 
397    trans->m[0][0] *= sx;
398    trans->m[0][1] *= sy;
399    trans->m[0][2] *= sz;
400 
401    trans->m[1][0] *= sx;
402    trans->m[1][1] *= sy;
403    trans->m[1][2] *= sz;
404 
405    trans->m[2][0] *= sx;
406    trans->m[2][1] *= sy;
407    trans->m[2][2] *= sz;
408 
409    trans->m[3][0] *= sx;
410    trans->m[3][1] *= sy;
411    trans->m[3][2] *= sz;
412 }
413 
414 /* Function: al_transform_coordinates
415  */
al_transform_coordinates(const ALLEGRO_TRANSFORM * trans,float * x,float * y)416 void al_transform_coordinates(const ALLEGRO_TRANSFORM *trans, float *x, float *y)
417 {
418    float t;
419    ASSERT(trans);
420    ASSERT(x);
421    ASSERT(y);
422 
423    t = *x;
424 
425    *x = t * trans->m[0][0] + *y * trans->m[1][0] + trans->m[3][0];
426    *y = t * trans->m[0][1] + *y * trans->m[1][1] + trans->m[3][1];
427 }
428 
429 /* Function: al_transform_coordinates_3d
430  */
al_transform_coordinates_3d(const ALLEGRO_TRANSFORM * trans,float * x,float * y,float * z)431 void al_transform_coordinates_3d(const ALLEGRO_TRANSFORM *trans,
432    float *x, float *y, float *z)
433 {
434    float rx, ry, rz;
435    ASSERT(trans);
436    ASSERT(x);
437    ASSERT(y);
438    ASSERT(z);
439 
440    #define M(i, j) trans->m[i][j]
441 
442    rx = M(0, 0) * *x + M(1, 0) * *y + M(2, 0) * *z + M(3, 0);
443    ry = M(0, 1) * *x + M(1, 1) * *y + M(2, 1) * *z + M(3, 1);
444    rz = M(0, 2) * *x + M(1, 2) * *y + M(2, 2) * *z + M(3, 2);
445 
446    #undef M
447 
448    *x = rx;
449    *y = ry;
450    *z = rz;
451 }
452 
453 /* Function: al_transform_coordinates_4d
454  */
al_transform_coordinates_4d(const ALLEGRO_TRANSFORM * trans,float * x,float * y,float * z,float * w)455 void al_transform_coordinates_4d(const ALLEGRO_TRANSFORM *trans,
456    float *x, float *y, float *z, float *w)
457 {
458    float rx, ry, rz, rw;
459    ASSERT(trans);
460    ASSERT(x);
461    ASSERT(y);
462    ASSERT(z);
463    ASSERT(w);
464 
465    #define M(i, j) trans->m[i][j]
466 
467    rx = M(0, 0) * *x + M(1, 0) * *y + M(2, 0) * *z + M(3, 0) * *w;
468    ry = M(0, 1) * *x + M(1, 1) * *y + M(2, 1) * *z + M(3, 1) * *w;
469    rz = M(0, 2) * *x + M(1, 2) * *y + M(2, 2) * *z + M(3, 2) * *w;
470    rw = M(0, 3) * *x + M(1, 3) * *y + M(2, 3) * *z + M(3, 3) * *w;
471 
472    #undef M
473 
474    *x = rx;
475    *y = ry;
476    *z = rz;
477    *w = rw;
478 }
479 
480 /* Function: al_transform_coordinates_3d_projective
481  */
al_transform_coordinates_3d_projective(const ALLEGRO_TRANSFORM * trans,float * x,float * y,float * z)482 void al_transform_coordinates_3d_projective(const ALLEGRO_TRANSFORM *trans,
483    float *x, float *y, float *z)
484 {
485    float w = 1;
486    al_transform_coordinates_4d(trans, x, y, z, &w);
487    *x /= w;
488    *y /= w;
489    *z /= w;
490 }
491 
492 /* Function: al_compose_transform
493  */
al_compose_transform(ALLEGRO_TRANSFORM * trans,const ALLEGRO_TRANSFORM * other)494 void al_compose_transform(ALLEGRO_TRANSFORM *trans, const ALLEGRO_TRANSFORM *other)
495 {
496    #define E(x, y)                        \
497       (other->m[0][y] * trans->m[x][0] +  \
498        other->m[1][y] * trans->m[x][1] +  \
499        other->m[2][y] * trans->m[x][2] +  \
500        other->m[3][y] * trans->m[x][3])   \
501 
502    const ALLEGRO_TRANSFORM tmp = {{
503       { E(0, 0), E(0, 1), E(0, 2), E(0, 3) },
504       { E(1, 0), E(1, 1), E(1, 2), E(1, 3) },
505       { E(2, 0), E(2, 1), E(2, 2), E(2, 3) },
506       { E(3, 0), E(3, 1), E(3, 2), E(3, 3) }
507    }};
508 
509    *trans = tmp;
510 
511    #undef E
512 }
513 
_al_transform_is_translation(const ALLEGRO_TRANSFORM * trans,float * dx,float * dy)514 bool _al_transform_is_translation(const ALLEGRO_TRANSFORM* trans,
515    float *dx, float *dy)
516 {
517    if (trans->m[0][0] == 1 &&
518           trans->m[1][0] == 0 &&
519           trans->m[2][0] == 0 &&
520           trans->m[0][1] == 0 &&
521           trans->m[1][1] == 1 &&
522           trans->m[2][1] == 0 &&
523           trans->m[0][2] == 0 &&
524           trans->m[1][2] == 0 &&
525           trans->m[2][2] == 1 &&
526           trans->m[3][2] == 0 &&
527           trans->m[0][3] == 0 &&
528           trans->m[1][3] == 0 &&
529           trans->m[2][3] == 0 &&
530           trans->m[3][3] == 1) {
531       *dx = trans->m[3][0];
532       *dy = trans->m[3][1];
533       return true;
534    }
535    return false;
536 }
537 
538 /* Function: al_orthographic_transform
539  */
al_orthographic_transform(ALLEGRO_TRANSFORM * trans,float left,float top,float n,float right,float bottom,float f)540 void al_orthographic_transform(ALLEGRO_TRANSFORM *trans,
541    float left, float top, float n,
542    float right, float bottom, float f)
543 {
544    float delta_x = right - left;
545    float delta_y = top - bottom;
546    float delta_z = f - n;
547    ALLEGRO_TRANSFORM tmp;
548 
549    al_identity_transform(&tmp);
550 
551    tmp.m[0][0] = 2.0f / delta_x;
552    tmp.m[1][1] = 2.0f / delta_y;
553    tmp.m[2][2] = 2.0f / delta_z;
554    tmp.m[3][0] = -(right + left) / delta_x;
555    tmp.m[3][1] = -(top + bottom) / delta_y;
556    tmp.m[3][2] = -(f + n) / delta_z;
557    tmp.m[3][3] = 1.0f;
558 
559    al_compose_transform(trans, &tmp);
560 }
561 
562 
563 /* Function: al_rotate_transform_3d
564  */
al_rotate_transform_3d(ALLEGRO_TRANSFORM * trans,float x,float y,float z,float angle)565 void al_rotate_transform_3d(ALLEGRO_TRANSFORM *trans,
566    float x, float y, float z, float angle)
567 {
568    double s = sin(angle);
569    double c = cos(angle);
570    double cc = 1 - c;
571    ALLEGRO_TRANSFORM tmp;
572 
573    al_identity_transform(&tmp);
574 
575    tmp.m[0][0] = (cc * x * x) + c;
576    tmp.m[0][1] = (cc * x * y) + (z * s);
577    tmp.m[0][2] = (cc * x * z) - (y * s);
578    tmp.m[0][3] = 0;
579 
580    tmp.m[1][0] = (cc * x * y) - (z * s);
581    tmp.m[1][1] = (cc * y * y) + c;
582    tmp.m[1][2] = (cc * z * y) + (x * s);
583    tmp.m[1][3] = 0;
584 
585    tmp.m[2][0] = (cc * x * z) + (y * s);
586    tmp.m[2][1] = (cc * y * z) - (x * s);
587    tmp.m[2][2] = (cc * z * z) + c;
588    tmp.m[2][3] = 0;
589 
590    tmp.m[3][0] = 0;
591    tmp.m[3][1] = 0;
592    tmp.m[3][2] = 0;
593    tmp.m[3][3] = 1;
594 
595    al_compose_transform(trans, &tmp);
596 }
597 
598 
599 /* Function: al_perspective_transform
600  */
al_perspective_transform(ALLEGRO_TRANSFORM * trans,float left,float top,float n,float right,float bottom,float f)601 void al_perspective_transform(ALLEGRO_TRANSFORM *trans,
602    float left, float top, float n,
603    float right, float bottom, float f)
604 {
605    float delta_x = right - left;
606    float delta_y = top - bottom;
607    float delta_z = f - n;
608    ALLEGRO_TRANSFORM tmp;
609 
610    al_identity_transform(&tmp);
611 
612    tmp.m[0][0] = 2.0f * n / delta_x;
613    tmp.m[1][1] = 2.0f * n / delta_y;
614    tmp.m[2][0] = (right + left) / delta_x;
615    tmp.m[2][1] = (top + bottom) / delta_y;
616    tmp.m[2][2] = -(f + n) / delta_z;
617    tmp.m[2][3] = -1.0f;
618    tmp.m[3][2] = -2.0f * f * n / delta_z;
619    tmp.m[3][3] = 0;
620 
621    al_compose_transform(trans, &tmp);
622 }
623 
624 /* Function: al_horizontal_shear_transform
625  */
al_horizontal_shear_transform(ALLEGRO_TRANSFORM * trans,float theta)626 void al_horizontal_shear_transform(ALLEGRO_TRANSFORM* trans, float theta)
627 {
628    float s;
629    ASSERT(trans);
630    s = -tanf(theta);
631 
632    trans->m[0][0] += trans->m[0][1] * s;
633    trans->m[1][0] += trans->m[1][1] * s;
634    trans->m[3][0] += trans->m[3][1] * s;
635 }
636 
637 
638 /* Function: al_vertical_shear_transform
639  */
al_vertical_shear_transform(ALLEGRO_TRANSFORM * trans,float theta)640 void al_vertical_shear_transform(ALLEGRO_TRANSFORM* trans, float theta)
641 {
642    float s;
643    ASSERT(trans);
644    s = tanf(theta);
645 
646    trans->m[0][1] += trans->m[0][0] * s;
647    trans->m[1][1] += trans->m[1][0] * s;
648    trans->m[3][1] += trans->m[3][0] * s;
649 }
650 
651 
652 /* vim: set sts=3 sw=3 et: */
653