Revision d99cdaacb5cc329203fa64413f9e896119d0de89 (click the page title to view the current version)
Changes from d99cdaacb5cc329203fa64413f9e896119d0de89 to current
---
title: Session. 3D Objects in Python
categories: session
---
+ [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**
1. [Representations of 3D Motion]()
2. [Homogeneous Coordinates]()
## 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}
$$
```python
from numpy import *
R = array( [[ cos(pi/6), -sin(pi/6), 0], [ sin(pi/6), cos(pi/6), 0], [0,0,1]] )
```
This matrix is orthonormal, which we can check:
```python
matmul( R, transpose(R) )
```
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
```python
newvertices = vertices @ R.T
```
(Note, `@` is shorthand for the `np.matmul()` function.)
Translations are straight forward; adding a row vector to a matrix, python
makes the same addition to every line.
```python
newvertices = vertices + array([1,1,0])
```
(Note that if you want to make both the translation and the rotation, the
translation must be added to the result of the rotation and not to the original
`vertices` prior to rotation.)
Recall, to visualise,
```python
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:
```python
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
```python
a = array( [[1,1,0]] )
H1 = hstack( [R, transpose(a)] )
H = vstack( [ H1, array([0,0,0,1] ) ] )
```
Using this transform, we can transform the vertices
in homogeneous co-ordinates, and also retrieve an
inhomogeneous form.
```python
newhvertices = transpose( matmul( H, transpose(hvertices) ) )
newvertices = newhvertices[:,:3]
```
## STL files and the STL library
```sh
pip3 install python-utils
pip3 install numpy-stl
```
STL is a standard format for writing surface meshes.
The following example defines the same tetrahedron as we have
been working with, but it creates a mesh object using the
numpy-stl library.
```python
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.
```python
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
```python
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
```python
teapot = mesh.Mesh.from_file("teapot.stl")
```
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
+ [Documentation for numpy-stl](https://pypi.org/project/numpy-stl/)
+ [A simple tutorial](https://spltech.co.uk/how-to-create-a-3d-model-of-a-photo-using-python-numpy-and-google-colab-part-i/)
# 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
1. first translating by $\vec{v}$ and then rotating by $R$, and
1. 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:
1. Define the 3D object `ob1` by multiplying `ob` by $R_i$ and
translating by a vector $\vec{v}=[2,0,0]^T$.
2. Display `ob1` together and `ob` and describe in your own
words what the transformation has done.
Is it a rigid body motion?
3. Calculate the determinant $|R_i|$. You can use
`numpy.linalg.det` in python.
4. 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
1. rotate by $R_2$ and translate by $\vec{b}$
2. 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
1. $H_1$ which rotates by $30^\circ$
around the $z$-axis and translates by $(2,0,0)$.
1. $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](https://tiny-3d-engine.readthedocs.io/en/latest/)
allows you to experiment with Scene Graphs without any fluff.
+ [PyVista](https://docs.pyvista.org/) is able to visualise large and
complex surface meshes (STL files)
+ [plotly](https://plotly.com/python/getting-started/)
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.