Session. 3D Objects in Python
- [Previous: 3D Modelling]-[Up: Overview]-[Next: 3D Modelling Part II]
Learning Objectives
Key learning outcome Improve the understanding of the mathematical descriptions of 3D Motion, by testing implementations in Python.
Secondary outcomes if you have time, it is worth browsing different libraries and frameworks to build, visualise, and animate scenes using 3D objects.
Reading
- Ma (2004) Chapter 2.4-2.5
- (Szeliski Chapter 2 until Section 2.1.3 inclusive)
Briefing
New Material
Recap: a simple object
Remember, last week we worked with this data structure in Python:
In [1]: print(vertices)
[[-1. 0.5 0.5]
[ 1. 0.5 0.5]
[ 0. -0.5 0.5]
[-1. 0.5 0.5]
[ 1. 0.5 0.5]
[ 0. 0.5 -0.5]
[-1. 0.5 0.5]
[ 0. -0.5 0.5]
[ 0. 0.5 -0.5]
[ 1. 0.5 0.5]
[ 0. -0.5 0.5]
[ 0. 0.5 -0.5]]
The rows are points in 3D. Note that there are only four distinct points. If we divide the matrix into sets of three rows, each triplet defines a triangle. These four triangles form the faces of an irregular tetrahedron.
This is a standard way to define a 3D object. More complex objects need more triangles.
Note that Ma (2004) have vertices as column vectors, while we here use row vectors. This means that we need to transpose matrices when we translate textbook formulæ to python formulæ.
Consider, for instance a rotation matrix
\[ \begin{align} R = \begin{bmatrix} \cos(\pi/6) & -\sin(\pi/6) & 0 \\ \sin(\pi/6) & \cos(\pi/6) & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{align} \]
This matrix is orthonormal, which we can check:
To rotate a vector \(\vec{v}\), we would calculate \(\vec{u}= R\cdot \vec{v}\), where \(\vec{u}\) and \(\vec{v}\) are column vectors. If you have a shape \(V\) where the columns are points, it could be rotated as \(U=R\cdot V\). In python this has to become
Translation is a little simpler, as we can do
Recall, to visualise,
ob1 = Poly3DCollection(vertices, linewidths=1, alpha=0.2)
ob2 = Poly3DCollection(newvertices, linewidths=1, alpha=0.2)
ob1.set_facecolor( [1, 0.5, 0.5] )
ob2.set_facecolor( [0.5, 1, 0.5] )
ob1.set_edgecolor([0,0,0])
ob2.set_edgecolor([0,0,0])
ax.add_collection3d(ob1)
ax.add_collection3d(ob2)
plt.show()
Homogeneous co-ordinates
As we know, a lot of operations are simpler in homogeneous co-ordinates.
We can add a one to the vertices as follows:
In [2]: vertices.shape
Out[2]: (12, 3)
In [3]: hvertices = hstack( [vertices, ones([12,1])] )
In [4]: print( hvertices)
[[-1. 0.5 0.5 1. ]
[ 1. 0.5 0.5 1. ]
[ 0. -0.5 0.5 1. ]
[-1. 0.5 0.5 1. ]
[ 1. 0.5 0.5 1. ]
[ 0. 0.5 -0.5 1. ]
[-1. 0.5 0.5 1. ]
[ 0. -0.5 0.5 1. ]
[ 0. 0.5 -0.5 1. ]
[ 1. 0.5 0.5 1. ]
[ 0. -0.5 0.5 1. ]
[ 0. 0.5 -0.5 1. ]]
In [5]:
Suppose vi have the rotational matrix \(R\) as above, and want to translate by a vector \(\vec{a} = [1,1,0]^T\). We can build the transform matrix as
Using this transform, we can transform the vertices in homogeneous co-ordinates, and also retrieve an inhomogeneous form.
STL files and the STL library
STL is a standard format for writing surface meshes. The following example defines the same tetrahedron as we have been working with, but the creates a mesh object using the numpy-stl library.
import numpy as np
from stl import mesh
corners = [ [-1,0.5,0.5], [+1,0.5,0.5], [0,-0.5,0.5], [0,0.5,-0.5] ]
vertices = np.array( corners )
face1 = [ 0, 1, 2 ]
face2 = [ 0, 1, 3 ]
face3 = [ 0, 2, 3 ]
face4 = [ 1, 2, 3 ]
faces = np.array( [ face1, face2, face3, face4 ] )
th = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces):
for j in range(3):
print( (i,j), vertices[f[j],:])
th.vectors[i][j] = vertices[f[j]]
th.save('tetrahedron.stl')
Here, we save it to file. We can also show it.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
ob = Poly3DCollection(th.vectors, linewidths=1, alpha=0.2)
ob.set_facecolor( [0.5, 0.5, 1] )
ob.set_edgecolor([0,0,0])
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plt.show()
ax.add_collection3d(ob)
This is essentially the same code as we have used before, just note how we access the vectors from the th
object. Note that the th.vectors
array is three-dimensional:
In [2]: cube.vectors
Out[2]:
array([[[-1. , 0.5, 0.5],
[ 1. , 0.5, 0.5],
[ 0. , -0.5, 0.5]],
[[-1. , 0.5, 0.5],
[ 1. , 0.5, 0.5],
[ 0. , 0.5, -0.5]],
[[-1. , 0.5, 0.5],
[ 0. , -0.5, 0.5],
[ 0. , 0.5, -0.5]],
[[ 1. , 0.5, 0.5],
[ 0. , -0.5, 0.5],
[ 0. , 0.5, -0.5]]], dtype=float32)
This makes it a lot more difficult to use low-level matrix operations to rotate and translate the object.
Reshaping
In [25]: th.vectors.shape
Out[25]: (4, 3, 3)
In [26]: th.vectors.reshape(12,3)
Out[26]:
array([[-1. , 0.5, 0.5],
[ 1. , 0.5, 0.5],
[ 0. , -0.5, 0.5],
[-1. , 0.5, 0.5],
[ 1. , 0.5, 0.5],
[ 0. , 0.5, -0.5],
[-1. , 0.5, 0.5],
[ 0. , -0.5, 0.5],
[ 0. , 0.5, -0.5],
[ 1. , 0.5, 0.5],
[ 0. , -0.5, 0.5],
[ 0. , 0.5, -0.5]], dtype=float32)
In [27]:
To be certain of what actually happens, it may be easiest to reshape the matrix before rotation and translation, and then reshape it again (.reshape(4,3,3)
in this case)
Loading STL files
Note that large meshes are computationally demanding and one is likely to need a visualisation engine which is designed for meshes, rather than matplotlib
.
More reading
Exercise
Rotations and translations
Exercise 1.
Consider the translation vector \(\vec{v}=[0,0,5]\) and the rotation matrix \[R = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix} \] which rotates 180⁰ around the \(y\)-axis.
What is the difference between
- first translating by \(\vec{v}\) and then rotating by \(R\), and
- first rotating by \(R\) and then translating by \(\vec{v}\)?
Try to reason first, and then check it in python.
Exercise 2.
Make a 3D object ob
and display it.
For each of the following matrices \(R_i\), do the following:
- Define the 3D object
ob1
by multiplyingob
by \(R_i\) and translating by a vector \(\vec{v}=[2,0,0]^T\). - Display
ob1
together andob
and describe in your own words what the transformation has done. Is it a rigid body motion? - Calculate the determinant \(|R_i|\). You can use
numpy.linalg.det
in python. - Check if \(R_i\) is orthonormal, i.e. whether \(R_iR_i^T=I\).
When you have done a couple of example:
- reflect on what the determinant tells you about the transformation, and
- identify which matrices \(R_i\) are rotations.
\[R_1 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
\[R_2 = \begin{bmatrix} \frac{\sqrt2}2 & -\frac{\sqrt2}2 & 0 \\ \frac{\sqrt2}2 & \frac{\sqrt2}2 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
\[R_3 = \begin{bmatrix} \frac{\sqrt2}2 & \frac{\sqrt2}2 & 0 \\ \frac{\sqrt2}2 & -\frac{\sqrt2}2 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
\[R_4 = \begin{bmatrix} \frac{\sqrt2}2 & 0 & -\frac{\sqrt2}2 \\ 0 & 1 & 0 \\ \frac{\sqrt2}2 & 0 & \frac{\sqrt2}2 & 0 \end{bmatrix} \]
\[R_5 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 1 \\ 0 & 1 & 0 \\ \end{bmatrix} \]
\[R_6 = \begin{bmatrix} 0 & 1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix} \]
\[R_7 = \begin{bmatrix} 2 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 2 \\ \end{bmatrix} \]
\[R_8 = R_6\cdot R_7 = \begin{bmatrix} 0 & 2 & 0 \\ 2 & 0 & 0 \\ 0 & 0 & 2 \\ \end{bmatrix} \]
\[R_9 = \begin{bmatrix} \frac13 & 0 & 0 \\ 0 & \frac13 & 0 \\ 0 & 0 & \frac13 \\ \end{bmatrix} \]
\[R_{10}=R_2\cdot R_4\]
Homogenous Coordinates
Exercise 3.
Let \(R_i\) be defined as in Exercise 1. And let \(\vec{b}=[1,1,0]^T\) and \(\vec{c}=[0,1,1]^T\).
Define the homogeneous matrices \(H_1\) and \(H_2\) to represent the following operations
- rotate by \(R_2\) and translate by \(\vec{b}\)
- rotate by \(R_4\) and translate by \(\vec{c}\)
Visualise again the object ob
as in Exercise 1, as well as ob1
and ob2
resulting from the transforms \(H_1\) and \(H_2\) respectively.
What happens if you transform ob
by the matrix \(H=H_1H_2\)?
Try to imagine the transformation first. Then run it in python and see if you are correct or not.
Finally, try the transformation \(H'=H_2H_1\). What do you think will happen? What actually happens?
Exercise 4.
Define the homogeneous matrices
- \(H_1\) which rotates by \(30^\circ\) around the \(z\)-axis and translates by \((2,0,0)\).
- \(H_2\) which rotates by \(60^\circ\) around the \(x\)-axis and translates by \((-2,0,0)\).
Using the existing 3D object ob
, test the two transformations \(H_1H_2\) and \(H_2H_1\). Do they give the same result, or different results? Why?
Additional Libraries and Documentation
- Tiny 3D Engine allows you to experiment with Scene Graphs without any fluff.
- PyVista is able to visualise large and complex surface meshes (STL files)
- plotly is a popular alternative to
matplotlib
. It is more modern and efficient, but a little harder to get started with.
Debrief
- We use experiments in python to check our reasoning.
- If you are stuck, please show us the experiment, and we discuss what sense we can make thereof.