Photo by MARIOLA GROBELSKA on Unsplash
In computer graphics, affine transformation is the most general transformations model. Any combination of translation, rotations, scalings/reflections and shears can be combined in a single 4 by 4 affine transformation matrix.
In this article, we are going to explore common 3d affine transformation matrices and implement it with NumPy.
3D Affine Transformation Matrices
Here is a affine transformation matrix that transforms point (or vector) x to point (or vector) y.
The upper-left 3 × 3 sub-matrix of the matrix represents a rotation transform (include scales and shears). The last column of the matrix represents a translation. When used as a coordinate system, the upper-left 3 x 3 sub-matrix represents an orientation in space while the last column vector represents a position in space. The transformation of point x to point y is obtained by performing the matrix-vector multiplication Mx:
The transformation matrix uses homogeneous coordinates, which allow to distinguish between points and vectors. Vectors have a direction and magnitude whereas points are positions. Points and vectors are both represented as mathematical column vectors in homogeneous coordinates. The only difference is points have a 1 in the fourth position whereas vectors have a zero at this position, which removes translation operations (4th column) for vectors.
The transformation of point x to point y using homogeneous matrix is written as:
Implements matrix with NumPy
Matrix is easy to be Implemented with NumPy. Let’s see how to construct a matrix with NumPy:
import numpy as np
elements = [
[1,2,3,4],
[4,6,7,8],
[9,10,11,12],
[13,14,15,16]
]
matrix = np.array(elements)
print(matrix)
# [[ 1 2 3 4]
# [ 4 6 7 8]
# [ 9 10 11 12]
# [13 14 15 16]]
Since affine transformation matrix is used to represent object’s rotaion and translation in 3D models, serialization and deserialization is common scenario. Here is code snippets for constructing matrix from list and converting it to a list:
import numpy as np
# constructs from list
elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
matrix = np.array(elements)
matrix = matrix.reshape(4,4)
print(matrix)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]
# [13 14 15 16]]
# converts to list
print(matrix.reshape(-1).tolist())
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
Translation
A translation operation will translate a point(or an object) from an initial position to a new position based on a linear shift. Here equation of translate a point (x, y, z)
to point (x', y', z')
And the transformation matrix form:
Implements translation matrix with NumPy
def translation_matrix(tx, ty, tz):
matrix = [
[1,0,0,tx],
[0,1,0,ty],
[0,0,1,tz],
[0,0,0,1]
]
return np.array(matrix)
np.random.seed(202403)
tx, ty, tz = np.random.randint(10, size=3)
print(tx, ty, tz) #5 5 2
matrix = translation_matrix(tx, ty, tz)
print(matrix)
# [[1 0 0 5]
# [0 1 0 5]
# [0 0 1 2]
# [0 0 0 1]]
# translates a point x, y, z
x, y, z = np.random.randint(10, size = 3)
print(x, y, z) #3 6 8
translated_point = matrix @ [x,y,z,1]
print(translated_point[:3]) #[ 8 11 10]
#decompose translation from translation_matrix
tx, ty, tz = matrix[:3,3]
print(tx, ty, tz) #5 5 2
Scaling
A scale operation will shift a point(or an object) from an initial position to a new position based on a scaling. Here is the equation of scaling point:
And the transformation matrix form:
Implements scaling matrix with NumPy
def scaling_matrix(sx, sy, sz):
matrix = [
[sx,0,0,0],
[0,sy,0,0],
[0,0,sz,0],
[0,0,0,1]
]
return np.array(matrix)
np.random.seed(202403)
sx, sy, sz = np.random.rand(3)
print(sx, sy, sz) #0.5243255319030659 0.4804928539608385 0.07838680854264224
matrix = scaling_matrix(sx, sy, sz)
print(matrix)
# [[0.52432553 0. 0. 0. ]
# [0. 0.48049285 0. 0. ]
# [0. 0. 0.07838681 0. ]
# [0. 0. 0. 1. ]]
# scales a point x, y, z
x, y, z = np.random.randint(10, size = 3)
print(x, y, z) #6 8 8
scaled_point = matrix @ [x,y,z,1]
print(scaled_point[:3]) #[3.14595319 3.84394283 0.62709447]
Rotation
A rotation operation will shift a point(or an object) from an initial position to a new position based on a rotation about a given axis or any arbitrary vector.
The rotation matrix is more complex than the scaling and translation matrix since the whole 3x3 upper-left matrix is needed to express complex rotations. It is common to specify arbitrary rotations with a sequence of simpler ones each along one of the three axes. In each case, the rotation is through an angle, about the given axis. Let’s explore rotation matrix around single axis one by one. Notice that the signs of rotation angles are defined using a right-hand rule convention in the following sections.
Rotation around the x-axis
Here is the matrix for rotating a point through the angle alpha around the x-axis
Implements x-axis rotation matrix with NumPy
def rotation_matrix_x(alpha_degree):
alpha_radian = np.deg2rad(alpha_degree)
rotation_alpha = [
[1, 0, 0, 0],
[0, np.cos(alpha_radian), -np.sin(alpha_radian), 0],
[0, np.sin(alpha_radian), np.cos(alpha_radian), 0],
[0, 0, 0, 1]
]
return np.array(rotation_alpha)
rotated_point = rotation_matrix_x(90) @ [0, 1, 0, 1]
print(rotated_point[:3]) #[0.000000e+00 6.123234e-17 1.000000e+00]
Rotation around the y-axis
Here is the matrix for rotating a point through the angle beta around the y-axis
Implements y-axis rotation matrix with NumPy
def rotation_matrix_y(beta_degree):
beta_radian = np.deg2rad(beta_degree)
rotation_beta = [
[np.cos(beta_radian), 0, np.sin(beta_radian), 0],
[0, 1, 0, 0],
[-np.sin(beta_radian), 0, np.cos(beta_radian), 0],
[0, 0, 0, 1]
]
return np.array(rotation_beta)
rotated_point = rotation_matrix_y(90) @ [1, 0, 0, 1]
print(rotated_point[:3]) #[ 6.123234e-17 0.000000e+00 -1.000000e+00]
Rotation around the z-axis
Here is the matrix for rotating a point through the angle gamma around the z-axis
Implements z-axis rotation matrix with NumPy
def rotation_matrix_z(gamma_degree):
gamma_radian = np.deg2rad(gamma_degree)
rotation_gamma = [
[np.cos(gamma_radian), -np.sin(gamma_radian), 0, 0],
[np.sin(gamma_radian), np.cos(gamma_radian), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]
return np.array(rotation_gamma)
rotated_point = rotation_matrix_z(90) @ [1, 0, 0, 1]
print(rotated_point[:3]) #[6.123234e-17 1.000000e+00 0.000000e+00]
General rotation
General rotation matrix around 3 axes can be composed by concatenating matrices around each axis using matrix multiplication. Here is a general rotation matrix:
Compose general rotaton matrix with NumPy
def rotation_matrix(alpha, beta, gamma):
return rotation_matrix_x(alpha) @ rotation_matrix_y(beta) @ rotation_matrix_z(gamma)
np.random.seed(202403)
alpha, beta, gamma = np.random.randint(0, 360, size = 3)
print(alpha, beta, gamma) #165 77 117
x, y, z = np.random.randint(10, size = 3)
print(x, y, z) #2 3 6
rotated_point = rotation_matrix(alpha, beta, gamma) @ [x,y,z,1]
print(rotated_point[:3]) #[ 5.04067053 -1.65813521 -4.56532892]
Jason 🚀
Thank you for reading until the end. Before you go:
👏 Please Clap and follow me
📬 Subscribe to my Medium newsletter for email updates!
☕ or just buy me a coffee