Transform class (position, rotation and scale)

Introduction

The other day I was very interested again in how a Transform class was working behind the scenes.

Normally, on a 3D environment, for representing complex hierarchies of objects, we use a chain of matrix multiplications. The most basic example is the scenario of the Sun, Earth and Moon orbiting like they do in real life. In this case, if you want to know the final world position of the Moon, you will need to multiply the Moon matrix with the Earth matrix, and then the result with the Sun matrix.

Every matrix is represented by a 4x4 float array. This may cause some problems when constantly operating over it due to float multiplication error accumulation. Also, it can be difficult to interpolate between two matrices due to its representation. Its representation can be something like this:

Class Matrix4x4 {
    float mat[16];
};

Transform

Lets imagine that we want to make a Transform class, that will help us to mimic some of the features a matrix can give us. But in this case, we are going to use a position, rotation, and scale vectors.

Class Transform {
    point3 position;
    quaterion rotation;
    vec3 scale;   
};

We can see that the Transform class stores the position on a point, the rotation on a Quaternion, and finally the scale on a vector.

Multiply

If we want to multiply 2 Transforms together we will need to do something like this:

Transform Transform::multiply(const Transform& t) const {
    Transform ret;

    ret.position = t.position + (t.rotation * (t.scale * position));
    ret.rotation = t.rotation * rotation;
    ret.scale = scale * t.scale;

    return ret;
}

It is important to specify that with this Transform object we will not be able to represent any type of projection, skew transform or, in general, any matrix representation where the three axis are not orthogonal.

A common operation on the Transform class is a function that can return you the forward, right or up vector. And these vectors are computed like this:

vec3 Transform::getRight() const {
    return rotation * vec3(1,0,0);
};

vec3 Transform::getUp() const {
    return rotation * vec3(0,1,0);
};

vec3 Transform::getForward() const {
    return rotation * vec3(0,0,1);
};

As you can see, we use unit vectors multiplied with the rotation of the transform to get those directional concepts, but we will not be able to use non orthogonal vectors on the Transform::multiply(...) function, which will limit us from representing skew transforms in a chain of transforms.

Another caveat of this Transform class is about scale. It is highly recommended to not use non-uniform scales.

transform.scale = vec3(2,2,2); // uniform scale
transform.scale = vec3(1,2,3); // non-uniform scale

This is because a transform chain with these types of scales will not correctly apply the order of multiplications that a matrix multiplication do.

Invert

Another interesting property of this Transform class is how easy is to invert it.

Transform Transform::getInvert() const {
    Transform t;

    t.scale = 1.0 / scale;
    t.rotation = invert(rotation);
    tr.position = -1.0 * position;

    return t;
}

Conclusion

Mathematically speaking, the Transform class has a lot of limitations, but luckily there are a lot of places in game development where we can use this Transform concept.