I'm plotting a 3D mesh using mayavi's triangular_mesh
method. The data describes a human silhouette laying face-down in 3D space (so the cmap
can be used to denote distance from the camera).
Here's the code used to generate the plot (the faces and vertices come from an external object, and there are far too many to show here):
from mayavi import mlab
import math
import numpy as np
import sys
import os
fig = mlab.figure(fgcolor=(0, 0, 0), bgcolor=(1, 1, 1), size=(1920, 980))
a = np.array(this_mesh.vertices - refC.conj().transpose()) # this_mesh is an object created from external data files
this_mesh.faces = this_mesh.faces.astype(int) -1 # mesh data is generated by matlab, which is 1-indexed
m = mlab.triangular_mesh(x, y, z, this_mesh.faces, opacity=0.75)
mlab.axes(extent=[-1100, 1100, -1100, 1100, -1100, 1100])
Without moving the camera, the silhouette lays face-down. In order to view the model face-on, I'm altering the azimuth and elevation of the camera to look at the graph top-down. This shows the silhouette as intended.
mlab.view(azimuth=0, elevation=180)
mlab.show()
My next task is to create a series of images where the camera pans around the plot, starting with the silhouette facing to the right, and finishing with it facing to the left.
The complication is that in order to get the colormap for the depth information, I'm already moving the view's azimuth and elevation (as shown in the code above). Mayavi has more options for moving the camera than matplotlib, but it doesn't seem to have a way to rotate around the Y axis, so I'm guessing I'll need to perform some complex calculations on the azimuth and elevation to achieve the same result - but I'm at a loss as to where to start (I'm new to working in 3D space, and my brain doesn't think that way yet).
Can anyone point me in the right direction?
It turns out there's a bit of a workaround for this.
You can rotate the actors on their axes independently of the camera. (This throws the visualization out of step with the data labeling, but as I'm actually hiding the axes of the figure it doesn't matter in this case.)
All you need to do is:
m.actor.actor.rotate_y(desired_angle)
...and you're good to go.
You need some math here. Ok, here is how to do it in terms of code, its not the best code but I wanted to make it self-explanatory. I use the Rodrigues formula for rotation in 3d to achieve this, az_new and el_new are your new viewing angles. Change the value of theta to get a different viewing angle in your frame of reference, i have used 45 degrees in the code below:
import numpy as np
import math
def rotation_matrix(axis, theta):
"""
Return the rotation matrix associated with counterclockwise rotation about
the given axis by theta radians.
"""
axis = np.asarray(axis)
theta = np.asarray(theta)
axis = axis/math.sqrt(np.dot(axis, axis))
a = math.cos(theta/2.0)
b, c, d = -axis*math.sin(theta/2.0)
aa, bb, cc, dd = a*a, b*b, c*c, d*d
bc, ad, ac, ab, bd, cd = b*c, a*d, a*c, a*b, b*d, c*d
return np.array([[aa+bb-cc-dd, 2*(bc+ad), 2*(bd-ac)],
[2*(bc-ad), aa+cc-bb-dd, 2*(cd+ab)],
[2*(bd+ac), 2*(cd-ab), aa+dd-bb-cc]])
az = 90
el = -75
x = np.cos(np.deg2rad(el))*np.cos(np.deg2rad(az))
y = np.cos(np.deg2rad(el))*np.sin(np.deg2rad(az))
z = np.sin(np.deg2rad(el))
# So your viewing vector in x,y coordinates on unit sphere
v = [x,y,z]
# Since you want to rotate about the y axis from this viewing angle, we just increase the
# elevation angle by 90 degrees to obtain our axis of rotation
az2 = az
el2 = el+90
x = np.cos(np.deg2rad(el2))*np.cos(np.deg2rad(az2))
y = np.cos(np.deg2rad(el2))*np.sin(np.deg2rad(az2))
z = np.sin(np.deg2rad(el2))
axis = [x,y,z]
# Now to rotate about the y axis from this viewing angle we use the rodrigues formula
# We compute our new viewing vector, lets say we rotate by 45 degrees
theta = 45
newv = np.dot(rotation_matrix(axis,np.deg2rad(theta)), v)
#Get azimuth and elevation for new viewing vector
az_new = np.rad2deg(np.arctan(newv[1]/newv[0]))
el_new = np.rad2deg(np.arcsin(newv[2]))