Meshes / Surfaces

Meshes (or surfaces) in ipyvolume consist of triangles, and are defined by their coordinate (vertices) and faces/triangles, which refer to three vertices.

In [1]:
import ipyvolume as ipv
import numpy as np

Triangle meshes

Lets first construct a ‘solid’, a tetrahedron, consisting out of 4 vertices, and 4 faces (triangles) using plot_trisurf

In [2]:
s = 1/2**0.5
# 4 vertices for the tetrahedron
x = np.array([1.,  -1, 0,  0])
y = np.array([0,   0, 1., -1])
z = np.array([-s, -s, s,  s])
# and 4 surfaces (triangles), where the number refer to the vertex index
triangles = [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1,3,2)]
In [3]:
ipv.figure()
# we draw the tetrahedron
mesh = ipv.plot_trisurf(x, y, z, triangles=triangles, color='orange')
# and also mark the vertices
ipv.scatter(x, y, z, marker='sphere', color='blue')
ipv.xyzlim(-2, 2)
ipv.show()

Surfaces

To draw parametric surfaces, which go from \(\Bbb{R}^2 \rightarrow \Bbb{R}^3\), it’s convenient to use plot_surface, which takes 2d numpy arrays as arguments, assuming they form a regular grid (meaning you do not need to provide the triangles, since they can be inferred from the shape of the arrays). Note that plot_wireframe has a similar api, as does plot_mesh which can do both the surface and wireframe at the same time.

In [4]:
# f(u, v) -> (u, v, u*v**2)
a = np.arange(-5, 5)
U, V = np.meshgrid(a, a)
X = U
Y = V
Z = X*Y**2

ipv.figure()
ipv.plot_surface(X, Z, Y, color="orange")
ipv.plot_wireframe(X, Z, Y, color="red")
ipv.show()

Colors

Vertices can take colors as well, as the example below (adapted from matplotlib) shows.

In [5]:
X = np.arange(-5, 5, 0.25*1)
Y = np.arange(-5, 5, 0.25*1)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
In [6]:
from matplotlib import cm
colormap = cm.coolwarm
znorm = Z - Z.min()
znorm /= znorm.ptp()
znorm.min(), znorm.max()
color = colormap(znorm)
In [7]:
ipv.figure()
mesh = ipv.plot_surface(X, Z, Y, color=color[...,:3])
ipv.show()

Texture mapping

Texture mapping can be done by providing a PIL image, and UV coordiante (texture coordinates, between 0 and 1). Note that like almost anything in ipyvolume, these u & v coordinates can be animated, as well as the textures.

In [8]:
# import PIL.Image
# image = PIL.Image.open('data/jupyter.png')
In [9]:
# fig = p3.figure()
# p3.style.use('dark')
# # we create a sequence of 8 u v coordinates so that the texture moves across the surface.
# u = np.array([X/5 +np.sin(k/8*np.pi)*4. for k in range(8)])
# v = np.array([-Y/5*(1-k/7.) + Z*(k/7.) for k in range(8)])
# mesh = p3.plot_mesh(X, Z, Y, u=u, v=v, texture=image, wireframe=False)
# p3.animation_control(mesh, interval=800, sequence_length=8)
# p3.show()

We now make a small movie / animated gif of 30 frames.

In [10]:
# frames = 30
# p3.movie('movie.gif', frames=frames)

And play that movie on a square

In [11]:
# p3.figure()
# x = np.array([-1.,  1,  1,  -1])
# y = np.array([-1,  -1, 1., 1])
# z = np.array([0., 0, 0., 0])
# u = x / 2 + 0.5
# v = y / 2 + 0.5
# # square
# triangles = [(0, 1, 2), (0, 2, 3)]
# m = p3.plot_trisurf(x, y, z, triangles=triangles, u=u, v=v, texture=PIL.Image.open('movie.gif'))
# p3.animation_control(m, sequence_length=frames)
# p3.show()
In [ ]: