Transformations
Transformations are a part of life. We are constantly being changed by things changing around us.
— S. Kassem
Transformations are indispensable mathematical tools for computer graphics. Transforms basically are bijective linear maps (or bijective affine linear maps) from the standard two-dimensional space into itself, describing how points and objects can move from one place to another, or even shrink and grow.
Mathematics teaches us that we can describe linear maps with matrices. The actual transformation is nothing more than a matrix-vector multiplication. While we won’t talk much about mathematics, please note that matrix multiplication is not commutative, thus changing the order of operations, whether you first translate and then rotate an object, or first rotate and then translate, does change the outcome.
Matrices are especially suitable for GPU parallelization, thus transforming geometries is often a lot faster than creating new geometries all the time. In this tutorial, we will see how each transformation that Direct2D has to offer works in the field.
Mathematical Basics
Each transformation can be written as an affine linear map (in the standard Euclidean space):
The vector
To work efficiently in the affine space, we write our two-dimensional vectors as follows:
An affine transformation matrix
Lost in Translation
A translation is, as the name indicates, a geometric transformation that moves every point of a figure or a space by the same distance in a given direction. Translations obviously do not change length or angles.
In affine space, a translation matrix
Direct2D provides a translation matrix, the Matrix3x2F::Translation matrix, the only input being the distance to translate along the x-axis and y-axis.
As an example, let us translate the entire scene from the previous tutorial:
Now let us try to only translate the sun up a bit, we do that by adding an identity translation matrix:
Scaling
A second type of affine maps in the plane is the so called scaling map, which, obviously, scales everything in a given direction, with respect to a line in another direction (not necessarily perpendicular), combined with a translation that is not purely in the direction of scaling. In a generalized sense, those maps include the cases that the scale factor is zero, which makes it a projection, or negative; the latter includes reflections, and combined with a translation it includes glide reflections.
Scaling around the centre point of a figure obviously scales that figure in both directions, if the point of scaling is not the centre of a figure, one of the other cases mentioned above occur.
For a scaling map with the fixed point being the centre of the object to scale, the corresponding matrix is always in the following form:
Direct2D offers the Matrix3x2F::Scale method to easily create scaling matrices:
The x and y floats define the scale factor for the x-axis and y-axis respectively. The centerPoint is the point around which the scaling is performed.
Let us learn by example again:
Rotations
A rotation is a circular movement of an object around a centre of rotation, which is fixed under the rotation map.
A rotation matrix around the origin has the following form:
To rotate about an arbitrary point
To create rotation matrices in Direct2D, we can use the Matrix3x2F::Rotation method:
The angle defines the rotation angle in degrees. A positive angle creates a clockwise rotation, and a negative angle creates a counterclockwise rotation. The centerPoint defines the point about which the rotation is performed.
Let us rotate the scene by 90 degrees around the centre of the screen:
Shearing
A shear mapping is an affine linear map that displaces each point in a fixed direction, by an amount proportional to its signed distance from a line that is parallel to that direction.
Applying a shear map to a set of points of the plane will change all angles between them, except for straight angles, and the length of any line segment that is not parallel to the direction of displacement. Therefore, it will usually distort the shape of a geometric figure, for example, turning squares into non-square parallelograms, and circles into ellipses.
However, shearing does preserve the area of geometric figures and the alignment and relative distances of collinear points.
To create a shear matrix, Direct2D offers the Matrix3x2F::Screw method:
The angle parameters define the x-axis and y-axis skew or shear angles. Both angles are measured in degrees; the x-axis angle counterclockwise from the y-axis and the y-axis angle is clockwise from the x-axis. The centerPoint is the point about which the shearing is done.
Thus, setting angleX to 30 degrees would shear the object counterclockwise away from the y-axis, which will appear as a distortion of the object toward the positive direction of the x-axis.
Let us try this out and shear the entire scene:
Transforming Geometries
I created a few standard transformations in the Direct2D class to have easy access to the most useful transformation maps.
So far, we have set the transformations to apply to the entire render target, now we are keen to learn how to transform unit geometries. Those unit geometries are enough to create any cube, rectangle, parallelogram, circle, or ellipse that we want, by simply transforming them as desired.
The problem is that geometries are immutable, and thus there is no SetTransform method available for them. We actually have to create a new, transformed geometry using the ID2D1TransformedGeometry interface.
To create an ID2D1TransformedGeometry, we call the ID2D1Factory::CreateTransformedGeometry method:
ID2D1Geometry *sourceGeometry
The source geometry to transform.
const D2D1_MATRIX_3X2_F *transform
The transformation to apply to the source geometry.
ID2D1TransformedGeometry **transformedGeometry
When this method returns, this parameter contains the address of the pointer to the new transformed geometry object.
Please note that, like other resources, a transformed geometry inherits the resource space and threading policy of the factory that created it. The new geometry is as immutable as the source geometry was. Also note that when stroking a transformed geometry with the DrawGeometry method, the stroke width is not affected by the transform applied to the geometry.
Let us learn by example once again. We will create a larger (scaled) version of the unit rectangle we created in a previous tutorial:
Now to scale and translate the rectangle, we concatenate two transformation matrices. Let
Direct2D however, thinks differently about matrices than most mathematicians. Direct2D does not read mathematics from right to left, but from left to right, which means that the matrices are transposed. The correct formula for the concatenated transformation matrix in Direct2D is thus:
This sounds complicated, but it actually isn’t, let’s see an example. We will translate the scaled rectangle from above to the centre of the screen:
Brushes
Direct2D also allows brushes to be transformed, and since brushes are mutable, they offer a SetTransform method, and thus everything works exactly as for transforming the entire render target.
Now we know the basics of a vital mathematical concept applied to 2D geometries. Transformations are a thing of beauty, and they will surely be helpful in later tutorials and projects.
You can download the source code from [here](https://filedn.eu/ltgnTcOBnsYpGSo6BiuFrPL/Game%20Programming/Flatland/Direct2D/transformations.7z.
In the next tutorial, we will learn how to create use bitmaps.
References
- Microsoft Developer Network (MSDN)
- Wikipedia