## 3D Scenes – manim Series: Part 10

The post is part of a series on learning how to use manim.  You can find the previous tutorial post in this series here and the overview of the entire series here.

Note: When extracting these scenes I’d recommend including the low quality command line argument l. These scenes can take several minutes to extract at higher qualities.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

# 10.0 3D Scenes

The 3D scenes really aren’t any different than 2D scenes except you can now move the camera around using self.move_camera(). Below is an example using the 2D vector field from my previous post.

class ExampleThreeD(ThreeDScene):
CONFIG = {
"plane_kwargs" : {
"color" : RED_B
},
"point_charge_loc" : 0.5*RIGHT-1.5*UP,
}
def construct(self):
self.set_camera_position(0, -np.pi/2)
plane = NumberPlane(**self.plane_kwargs)

field2D = VGroup(*[self.calc_field2D(x*RIGHT+y*UP)
for x in np.arange(-9,9,1)
for y in np.arange(-5,5,1)
])

self.play(ShowCreation(field2D))
self.wait()
self.move_camera(0.8*np.pi/2, -0.45*np.pi)
self.begin_ambient_camera_rotation()
self.wait(6)

def calc_field2D(self,point):
x,y = point[:2]
Rx,Ry = self.point_charge_loc[:2]
r = math.sqrt((x-Rx)**2 + (y-Ry)**2)
efield = (point - self.point_charge_loc)/r**3
return Vector(efield).shift(point)


The differences between this code and the example from my previous post is (1) the parent class is ThreeDScene rather than Scene, (2) the self.set_camera_position() command, (3) the self.move_camera() command, and (4) the self.begin_ambient_camera_rotation() command.

The ThreeDScene() adds the ability to change the orientation of the camera. set_camera_position() moves the camera to the specified location and orientation. The camera will abruptly jump to the given location. If we want a smooth camera transition (panning the camera) we’d use move_camera(). The keyword arguments for set_camera_position() are phi, theta, distance, center_x, center_y, and center_z. The point that the camera is pointing towards is given by center_x, center_y, center_z. The other three arguments correspond to $\phi$, $\theta$, and $r$ in the figure below. Notice that the center point corresponds to the origin of the graph, with the camera located at the ‘x’, looking back towards the origin. This is why $\phi = 0$ and $\theta = -\pi/2$ corresponds to the normal 2D orientation with the x-axis pointing right and the y-axis pointing up on the screen. If you set $\theta = \pi/2$ we flip the screen over.

By DmcqOwn work, CC BY-SA 3.0, Link

## 10.1 3D Vector Field

Drawing a three-dimensional vector field requires only a couple of tweaks to our original code. We will copy the code for field2D and the method calc_field2D to create field3D and calc_field3D. With field3D we need to only add for z in np.arange(-5,5,1) and then send the vector x*RIGHT + y*UP + z*OUT to calc_field3D. Next we add the z-coordinates to calc_field3D. This means we don’t slice point or self.point_charge since we need all three components. We also need to add (z-Rz)**2 to the equation for r.

class EFieldInThreeD(ThreeDScene):
CONFIG = {
"plane_kwargs" : {
"color" : RED_B
},
"point_charge_loc" : 0.5*RIGHT-1.5*UP,
}
def construct(self):
self.set_camera_position(0.1, -np.pi/2)
plane = NumberPlane(**self.plane_kwargs)

field2D = VGroup(*[self.calc_field2D(x*RIGHT+y*UP)
for x in np.arange(-9,9,1)
for y in np.arange(-5,5,1)
])

field3D = VGroup(*[self.calc_field3D(x*RIGHT+y*UP+z*OUT)
for x in np.arange(-9,9,1)
for y in np.arange(-5,5,1)
for z in np.arange(-5,5,1)])

self.play(ShowCreation(field3D))
self.wait()
self.move_camera(0.8*np.pi/2, -0.45*np.pi)
self.begin_ambient_camera_rotation()
self.wait(6)

def calc_field2D(self,point):
x,y = point[:2]
Rx,Ry = self.point_charge_loc[:2]
r = math.sqrt((x-Rx)**2 + (y-Ry)**2)
efield = (point - self.point_charge_loc)/r**3
return Vector(efield).shift(point)

def calc_field3D(self,point):
x,y,z = point
Rx,Ry,Rz = self.point_charge_loc
r = math.sqrt((x-Rx)**2 + (y-Ry)**2+(z-Rz)**2)
efield = (point - self.point_charge_loc)/r**3
return Vector(efield).shift(point)


We don’t need field2D or calc_field2D anymore but I left them in for comparison to the updated code.

Things to try
– Rotate the camera so it is below the axes and to the left (or any other point you choose)
– Try plotting a different vector field
– Plot a constant field
– Try efield = np.array((-y,x,z))/math.sqrt(x2+y2+z**2)
– Come up with your own 3D vector field

Check out the next post in this series where we look at how to animate the electric field of a moving charge.