-
Notifications
You must be signed in to change notification settings - Fork 30
Transformation and Bounds
Transformation, that means translation, rotation and scaling of objects, is the key to position them in the scene.
The Transformable interface is implemented by those types of objects you will mainly deal with in visual applications: shapes, sprites and texts. Transformables can be positioned on the screen by translating (= moving), rotating and scaling them.
For these operations, there are always two variants:
- absolute transformation, that will override the current transformation and
- relative transformation, that will be applied to the current transformation to result in a new one.
Translation, in a geometric sense, means moving something to a different place in the scene. Simply put, it means changing an object's position on the screen.
The origin of the scene is the point (0, 0)
, which is - by default - the top left corner of the screen (this statement will be revisited once you learn about views). The translation, or position of an object is determined by the vector that points from the scene's origin to the object.
The unit in which distances are measured in JSFML is pixels. The vector (100, 200)
means 100 pixels to the right and 200 pixels to the bottom. Note that a positive y
component means moving "down" on the screen, unlike in geometry, where it is the other way round.
Using [setPosition](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#setPosition(org.jsfml.system.Vector2f\)), the absolute position of an object can be set, using [move](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#move(org.jsfml.system.Vector2f\)), the position can be changed by adding another vector.
The following example creates a rectangle and places it at the center of the screen.
//create a new 200x150 rectangle
RectangleShape rect = new RectangleShape(new Vector2f(200, 150));
//first, place the object to the bottom-right corner of the screen (assuming 640x480)
rect.setPosition(640, 480);
//then, move the rectangle back by half the screen size
rect.move(-320, -240);
//the rectangle is now located at (320, 240)
Rotating an object is always done around its origin, which will be explained below. Rotation occurs clockwise (ie a positive angle means a rotation to the right) and is measured in degrees.
Use [setRotation](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#setRotation(float\)) to set an object's rotation and rotate to change its rotation by a certain angle.
Let us rotate the rectangle we created above:
// ...
//Set the rotation to 60 degrees clockwise
rect.setRotation(60);
//Rotate the rectangle by 30 degrees counter-clockwise
rect.rotate(-30);
//The rectangle's rotation is now 30 degrees clockwise
The scale of an object consists of an x
scale and a y
scale and can therefore be represented by a vector. It is a multiplier determining how much the object's size changes. A scale of (x, y)
means that the object will become x
times as wide and y
times as high. The following rules apply to x
and y
respectively:
- If it is 1, the object's size stays normal (this is the default).
- If it is greater than 1, then the object becomes larger.
- If it is less than 1 (but greater than zero), then the object becomes smaller.
- A negative value will mirror the object at the respective axis, the same rules as above apply for the value's magnitude (ie -2 will mirror the object and make it 2 times as large).
Objects are scaled relative to their origin, which is explained below.
[setScale](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#setScale(org.jsfml.system.Vector2f\)) is used to set an object's scale, [scale](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#scale(org.jsfml.system.Vector2f\)) multiplies the current scale by the given scaling vector.
The following code scales our rectangle:
// ...
//Set the scale to half the width, but unchanged height
rect.setScale(0.5f, 1);
//Make the rectangle 1.5 times as high
rect.scale(1, 1.5f);
//The rectangle is now half its original width, but 1.5 times its original height
You might have noticed how it was always the top left corner of the rectangle that was really in the center of the screen, not the rectangle itself. That is because we did not set its origin to what we really want.
The origin of an object defines how translation, rotation and scaling will be applied to it:
- Translation will be off by the origin.
- Rotation occurs around its origin.
- Scaling occurs with the origin as the center.
The origin of an object is always relative to its top left corner. By default, it is (0, 0)
, which means it is precisely the top left corner. That said, in the code above, we first moved the top left corner of the rectangle to the center of the screen, then rotated around the top left corner and then scaled the rectangle using its top left corner as the center.
What we really wanted to achieve is move the center of the rectangle, rotate around the center of the rectangle and then scale using the rectangle's center as the scaling center. To achieve this, we must set the rectangle's origin to its center:
//Correcting the code above...
RectangleShape rect = new RectangleShape(new Vector2f(200, 150));
//Set the rectangle's origin to its center (half width and height)
rect.setOrigin(100, 75);
//Move the rectangle's center to the center of the screen and then rotate and scale it
rect.setPosition(320, 240);
rect.setRotation(30);
rect.setScale(0.5f, 1.5f);
//The rectangle's center stays at the center of the screen
As a conclusion, the origin of an object can be used to align it.
For instance, if we wanted the rectangle to be in the bottom right corner of the screen without doing too much calculation, we can set its origin to its bottom right corner (width and height) and then move it to the bottom right corner of the screen:
rect.setOrigin(200, 150)
rect.setPosition(640, 480)
Sometimes, you might find yourself in a situation where it is useful to implement the Transformable interface. JSFML provides the BasicTransformable class, which provides an implementation of the interface without any extra features. It can be extended or used as a delegation target for custom transformables.
All affine 2D transformations (translations, rotations, scaling and even more) can be mathematically expressed in a transformation matrix. In JSFML, the Transform class represents such a transformation matrix.
You can retrieve the current Transform of any Transformable object using the [getTransform](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#getTransform(\)) method. The [getInverseTransform](http://jsfml.org/javadoc/org/jsfml/graphics/Transformable.html#getInverseTransform(\)) gets the inverted transformation, which can also be seen as an "undo matrix".
The Transform class has methods to simulate transformation chains in a matrix. There are two common use cases for these matrices. One is related to render states and will be discussed in a future chapter, the other is transforming vectors.
Let us assume that we want to rotate the vector (150, 30)
by 25 degrees counter-clockwise. This is the code to achieve this:
//Create the vector we want to rotate
Vector2f v = new Vector2f(150, 30);
//Create the transformation matrix
Transform matrix = new Transform();
//By default, the transformation is the identity transformation (ie it does nothing)
//Add a 25 degrees counter-clockwise rotation to the transformation
matrix.rotate(-25);
//Transform (rotate) the vector
v = matrix.transformPoint(v);
//v is now rotated 25 degrees counter-clockwise
In JSFML, the bounds of an object describe its AABB, the axis-aligned bounding box. Picture a triangle in the scene that was rotated and scaled in an arbitrary manner. The axis-aligned bounding box is the smallest rectangle you can draw around the object, with the sides of the rectangle being parallel to the x
and y
axes (→ axis-aligned).
This means that the bounds of an object practically tell us where an object is in the scene and how large it is - independently of its origin.
Even though it is not defined by the Transformable class, all transformable objects (Shapes, Sprites and Texts) provide two methods to retrieve their bounds,
- [getGlobalBounds](http://jsfml.org/javadoc/org/jsfml/graphics/Shape.html#getGlobalBounds(\)) retrieves the object's global AABB, this means as viewn by the scene (in scene coordinates). This works pretty much like explained above: the global bounds are the smallest axis-aligned rectangle that can be drawn around the translated, rotated and scaled object.
- [getLocalBounds](http://jsfml.org/javadoc/org/jsfml/graphics/Shape.html#getLocalBounds(\)) retrieves the object's local AAB, this means as viewn from itself. The local bounds, simply put, describe the size of the raw object, not considering its translation, rotation and scaling.
Picture a 80x60
rectangle that was moved to (100, 100)
, rotated by 90
degrees clockwise and scaled by (2, 2)
.
The global bounds are (100, 100, 120x160)
(moved, rotated, scaled), whereas the local bounds are (0, 0, 80x60)
(not moved, rotated or scaled).