In order to successfully complete this assignment, you must follow all the instructions in this notebook and upload your edited ipynb file to D2L with your answers on or before 11:59pm on Friday October 9th.
BIG HINT: Read the entire homework before starting.
In this homework we are going to work with some 3D data in the obj format which is a common file type which mostly consists of a list of 3d points (vertices) and a list of point indexes which form polygons (faces). Some example objects in the obj format can be found at the on this Website with obj files.
The following code will open the website and download a list names for the available obj files.
from urllib.request import urlopen
import re
url = 'https://people.sc.fsu.edu/~jburkardt/data/obj/'
fid = urlopen(url)
html = fid.readlines()
all_names = []
index = 0
for line in html:
matches = re.findall('\"\w*\.obj\"',str(line))
for i in matches:
if i:
name = i[1:-1]
print(f"{index} {name}")
all_names.append(name)
index += 1
You can use the following code to download individual files to your current directory:
from urllib.request import urlretrieve, urlopen
filename = all_names[25]
print(filename)
urlretrieve(url+filename, filename);
The following function (written with the help of Claudia Chen) reads in the 3D object and stores it as a set of 3D points (vertices) and how the points are connected (faces). NOTE Not all of the files in the above list have been tested with this code, it is possible some of them do not work correctly.
import numpy as np
%matplotlib inline
import matplotlib.pylab as plt
def readobjfile(filename):
"""Simple function to read standard object (obj) files and returns a list
of verticies and polygon faces (typically triangles)"""
render_thing = open(filename)
vertices = []
faces = []
for i in render_thing.readlines():
row = i.strip().split()
if row:
if(row[0] == "v"):
vertices.append(row[1:4])
elif(row[0] == "f"):
face = np.array(row[1:], dtype=np.int16)
face -=1 #adjust to match python index (start counting from zero)
faces.append(list(face))
vertices = np.array(vertices, dtype=np.float32)
return vertices, faces
def plotobjdata(vertices, faces):
'''Plot the obj surface. Note this function does not scale the axis equally so there
be some visual skew in the data'''
polys = []
for i in faces:
poly = []
for index in i:
poly.append(vertices[index])
polys.append(poly)
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
srf = Poly3DCollection(polys, alpha=0.5, facecolor='#800000')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter3D(vertices[:,0], vertices[:,1], vertices[:,2]);
ax.autoscale()
ax.add_collection3d(srf)
#Read in the data
vertices, faces = readobjfile(filename)
#Plot the object with it's faces
plotobjdata(vertices, faces)
✅ **Do This:** (10 pts) Modify the above code to download the teapot example and store the values in verticies and faces.
#either modify the code above or add your code here.
from answercheck import checkanswer
checkanswer.matrix(vertices, '25bcaa0c8991d0d80b7d8ec89baa38c3')
from answercheck import checkanswer
checkanswer(faces,'33a415ce4e4ce818822df4f8c97f60b3')
Now lets apply a few simple affine transforms as matrix multiplications in the following form:
$$P_2 = MP$$Where $M$ is the transformation matrix, $P$ are the 3D points before the transformation and $P_2$ are the resulting points. Remember from class that all 3D transfroms can be represented as a $4 \times 4$ matrix of the following form:
$$ M = \left[ \begin{matrix} R_{11} & R_{12} & R_{13} & T_x \\ R_{21} & R_{22} & R_{23} & T_y \\ R_{31} & R_{32} & R_{33} & T_z \\ 0 & 0 & 0 & 1 \end{matrix} \right] $$Where the $3x3$ submatrix $R$ is affine part and $T$ is the translation vector. The bottom row is always zeros and a 1 just to make the math work out. In order to use this $4 \times 4$ matrix we need to put our points in a $4 \times n$ matrix where the first row is the $x$ coordinate, the second row is the $y$ coordinate, the third row is the $z$ coordinate and the fourth row are just ones; as follows: $$ P = \left[ \begin{matrix} p_{x1} & p_{x2} & p_{x3} &\dots & p_{xn} \\ p_{y1} & p_{y2} & p_{y3} &\dots & p_{yn} \\ p_{z1} & p_{z2} & p_{z3} &\dots & p_{zn} \\ 1 & 1 & 1 & \dots & 1 \end{matrix} \right] $$
✅ **Do This:** (10 pts) Create a ($4 \times n$) point matrix $P$ from the teapot vertices.
# Put your answer to the above question here.
from answercheck import checkanswer
checkanswer.matrix(P,'96770f757e8013453560d4b58bd623e1')
The following will plot the x and y rows in $P$ on the xy-axis:
plt.scatter(np.array(P[0,:]),np.array(P[1,:]));
plt.axis('equal');
Now lets apply a simple ($4 \times 4$) transformation T
that translate the teapot by 50 units in the y direction and plots the teapot.
T = np.matrix([[1, 0, 0, 0],
[0, 1, 0, 50],
[0, 0, 1, 0],
[0,0,0,1]])
P2 = T*P
#NOTE: to get the plotting to work we convert the Matrices to np.arrays
plt.scatter(np.array(P2[0,:]),np.array(P2[1,:]));
plt.axis('equal');
✅ **Question:** (10 pts) Construct 3D affine transform using a matrix called R
to rotate the coordinate system down by 60 degrees around the z-axis. Apply the transform to the original teapot points (P
) and plot the rotated points (P2
) in the xy-plane (as above). It should look like the pot is tilted in a direction to poor the tea out of the pot.
Hint: many implementations for sin
and cos
assume the imput is in Radians instead of Degrees, you may need to make a conversion.
#Put your answer to the above question here
from answercheck import checkanswer
checkanswer.matrix(R,'f971b9c9876afc881d2fb2b361cc6761')
✅ **Question:** (10 pts) Construct another transform S
that scales the x and z axes by a factor of 2 while leaving the y axes the same. Apply the transform to the original teapot points (P
) and plot the results (P2
) in the xy-plane (as above). It should look like a flatter teapot.
#Put your answer to the above question here
from answercheck import checkanswer
checkanswer.matrix(S,'98688524afa007f8042263e3d9f9ffc6')
✅ **Question**: (10 pts) Create a fourth affine transform called G
that is a combination of the previous transforms. First skew the original points (P
) with S
and then rotate the results by R
and translate the by T
. Plot the results (P2
). Picture should look like a flat rotated teapot that is picked up and poring out.
#Put your answer to the above question here
from answercheck import checkanswer
checkanswer.matrix(G,'9fb98fe93d326a6b7da6526ccd227533')
Just for fun, we can use the following code to try and plot our new rotated teapot in "3D". Note again that this function isn't that great since it scales the axis weirdly.
plotobjdata(np.array(P2[0:3]).T, faces)
✅ **Question**: (10 pts) The affine transforms we used are all invertible. Calculate the inverse of G
and apply it to the P2
points from the previous question and plot the results (P3
).
# YOUR CODE HERE
raise NotImplementedError()
✅ **Question**: (5 pts) Assuming the above calculations are correct the results from the previous question (P3
) should be exactly the same as the original points P
. However, the following code does not produce only zeros. Explain why?
P3-P
YOUR ANSWER HERE
Lets say we want to create an animation of the teapot as it is rotating the full 60 degrees. To get this to work we need to create a "flip-book" effect. Rotate the teapot a little - show the results - rotate the teapot a little more - show the results - until we hit our 60 degree target.
The following function can be used to "animate" figures inside of jupyter notebooks:
%matplotlib inline
import matplotlib.pylab as plt
from IPython.display import display, clear_output
import time
imageindex=0
def show_animation(delay=0.01):
global imageindex
fig = plt.gcf()
#fig.savefig(f"./images/image_{imageindex:03d}.jpg")
#imageindex+=1
time.sleep(delay) # Sleep for half a second to slow down the animation
clear_output(wait=True) # Clear output for dynamic display
display(fig) # Reset display
fig.clear() # Prevent overlapping and layered plots
We can use the above function to create an animation. This one applies a simple translation at each step:
Pa = P
T = np.matrix([[1, 0, 0, 15],
[0, 1, 0, 5],
[0, 0, 1, 0],
[0,0,0,1]])
for i in range(20):
Pa = T*Pa
plt.scatter(np.array(Pa[0,:]),np.array(Pa[1,:]));
plt.axis('equal')
plt.axis('off')
plt.axis([-150,150,-150,150])
show_animation(delay=0.01)
✅ **DO THIS**: (15 pts) Modify the above code to rotate instead of translate. The final pot should be at a 60 degree angle (like we did above) but this time you wnat to make about 20 steps between zero rotation and 60 so that the animation looks smooth. (HINT: you are trying to recreate the animation shown at the top of this assignment).
# YOUR CODE HERE
raise NotImplementedError()
✅ **Do This**: (20 pts) Using the objects from make your own "flip-book" video. Experiment and be creative. Try including more than one type of transformation, multiple transforms at the same time, more than one 3D object etc. Be creative and show us you what you can do:
# YOUR CODE HERE
raise NotImplementedError()
✅ **Question**: In your own words, describe what the above animation is doing. (sometimes it can be hard for instructors to tell what is going on so this description will help us evaluate your animation).
Put your description here.
Turn in your assignment using D2L no later than 11:59pm on the day of class. See links at the end of this document for access to the class timeline for your section.
Written by Dirk Colbry, Michigan State University
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.