Matplotlib 三角曲面


環面或球之類的曲面,可以透過 Matplotlib 的 plot_surface 來繪製,那麼其他立方體之類的呢?像是一個盒子?基本上,是可以透過多次呼叫 plot_surface 來達到目的,例如:

import numpy as np
import matplotlib.pyplot as plt

width = 30
depth = 40
height = 50

def box(width, depth, height):
    x = np.arange(width + 1)
    y = np.arange(depth + 1)
    z = np.arange(height + 1)

    X0, Y0 = np.meshgrid(x, y) 
    Z0 = np.zeros((depth + 1, width + 1))

    X1 = X0
    Y1 = Y0
    Z1 = Z0 + height

    X2, Z2 = np.meshgrid(x, z)
    Y2 = np.zeros((height + 1, width + 1))

    X3 = X2
    Y3 = Y2 + depth
    Z3 = Z2

    Y4, Z4 = np.meshgrid(y, z)
    X4 = np.zeros((height + 1, depth + 1))

    X5 = X4 + width
    Y5 = Y4
    Z5 = Z4

    surfaces = [
        [X0, Y0, Z0], 
        [X1, Y1, Z1], 
        [X2, Y2, Z2], 
        [X3, Y3, Z3],
        [X4, Y4, Z4], 
        [X5, Y5, Z5]
    ]

    ax = plt.axes(projection='3d')
    for X, Y, Z in surfaces:
        ax.plot_surface(X, Y, Z) 

    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    leng = width + depth + height
    ax.set_box_aspect((width / leng, depth / leng, height / leng)) 
    plt.show()

box(width, depth, height)

畫出的圖如下:

Matplotlib 三角曲面

然而,這種方式並不能套用至各種立方體,例如正四面體就沒辦法用這種方式繪製,怎麼辦呢?Matplotlib 有個 plot_trisurf 方法,可以用來以三角形為基礎來繪製曲面,最基本的使用方式是,提供每個點的座標,它會自動以 x 與 y 進行〈Delaunay 三角分割〉,例如隨機產生一些點:

import numpy as np
import matplotlib.pyplot as plt

n = 20

points = np.random.rand(n, 3)
xs = points[:,0]
ys = points[:,1]
zs = points[:,2]

ax = plt.axes(projection='3d')
ax.plot_trisurf(xs, ys, zs)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

plot_trisurf 需要的是一維陣列,分別代表所有座標的 x、y 與 z,這會繪製出以下的圖案:

Matplotlib 三角曲面

當然,隨機產生的點沒有連續性,來個可以用連續函式描述的曲面吧!

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

def function_grapher(f, x, y):
    X, Y = np.meshgrid(x, y) 
    Z = f(X, Y)

    ax = plt.axes(projection='3d')
    # plot_trisurf 需要一維陣列,因此透過 flatten 將二維陣列打平
    ax.plot_trisurf(X.flatten(), Y.flatten(), Z.flatten(), cmap = cm.coolwarm) 
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    ax.set_box_aspect((1, 1, 0.5))
    plt.title('Axes3D Plot Surface')
    plt.show()

def f(x, y):
    n = np.sqrt(np.power(x, 2) + np.power(y, 2)) / 180 * np.pi
    return np.cos(n) + np.cos(3 * n)

width = 200
step = 10

x = np.arange(-width, width, step)
y = np.arange(-width, width, step)

function_grapher(f, x, y)

這可以繪製出以下的圖案,可以跟〈Matplotlib 立體圖〉中相對應的函式曲面圖比較看看:

Matplotlib 三角曲面

那麼這跟繪製正四面體有什麼關係呢?plot_trisurf 可以透過 triangles 參數指定每個三角形使用的座標索引,這就可以用來構造 3D 物件的 mesh 了。

對於正四面體,一個簡單的方式是連接正立方體的四個頂點來繪製:

Matplotlib 三角曲面

因為正四面體可以有共用邊,將之展開的話就可以清楚看出,只要依序且循環地走訪頂點四次就可以了:

Matplotlib 三角曲面

使用 NumPy 及 Matplotlib 來實作的話:

import numpy as np
import matplotlib.pyplot as plt

def tetrahedron(width):
    n = width / (2 ** 0.5) * 0.5;

    xs = np.array([n, -n,  n, -n]) 
    ys = np.array([n,  n, -n, -n]) 
    zs = np.array([n, -n, -n, n]) 

    ax = plt.axes(projection='3d')
    ax.plot_trisurf(xs, ys, zs, triangles = [[0, 1, 2], [1, 2, 3], [2, 3, 0], [3, 0, 1]])
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    plt.show()

width = 30
tetrahedron(width)

就可以繪製出以下的圖案:

Matplotlib 三角曲面