NumPy 與環面(一)


既然 Matplotlib 可以畫立體圖,我就有了一個想法,可以用來 3D 建模嗎?例如,簡單畫個環面 3D 模型?

NumPy 與環面(一)

上圖其實是用 plot_surface 畫出來的,就像一塊布包在圓環,看來就像個 3D 模型了,你可以試著用純 Python 與 Matplot 來畫畫看,不過,這邊直接用 NumPy 來實現。

首先,試著用 NumPy 建立 plot_surface 需要的 x、y、z 軸資料,先來個平面的資料就可以了:

import numpy as np
import matplotlib.pyplot as plt

width = 96

x = np.arange(width)
y = np.arange(width)

X, Y = np.meshgrid(x, y) 
Z = np.zeros((width, width))

ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z) 
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

這邊看到了 NumPy 的 zeros 函式,你可以指定陣列的形狀,該函式產生的陣列元素,全部都會是 0,也就是畫出來的平面是在 xy 平面上:

NumPy 與環面(一)

下一步是,試著讓這個平面變成一個圓筒面,這需要指定圓筒半徑,然後讓面繞著 z 軸轉一圈,為此,可以將 x 值當成是圓筒的每個步進,原本的 y 就當成是圓筒的高:

import numpy as np
import matplotlib.pyplot as plt

fn = 96
radius = 150

# 因為要接合起來,因此必須是 fn + 1
x = np.arange(fn + 1)
y = np.arange(fn + 1)

X, Y = np.meshgrid(x, y) 
theta = X * (np.pi * 2 / fn)

TX = radius * np.cos(theta)
TY = radius * np.sin(theta)
TZ = Y

ax = plt.axes(projection='3d')
ax.plot_surface(TX, TY, TZ) 
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

因為圓周現在是由 radius 決定了,在這邊將 width 改成了 fn,表示一個圓可以切為幾段直線來表示,會比較符合這個參數的意義,結果可以畫出底下的圓筒:

NumPy 與環面(一)

下一步要將圓筒面上每個與 z 軸平行的線,也繞成一個圓,這需要指定另一個半徑,而 y 值當成是圓的每個步進。

在計算的順序上,這邊採取先繞成一個圓,再推至圓筒半徑處繞一個環,在先繞圓時,圓上每個點的 x 座標會是 radius2 * np.cos(theta2)theta2 旋轉的角度,而推至圓筒半徑處時,就會是 radius1 + radius2 * np.cos(theta2),至於圓的 z 座標會是 radius2 * np.sin(theta2),推至圓筒半徑處再旋轉後不會改變。

結果就是,以下的程式可以畫出環面:

import numpy as np
import matplotlib.pyplot as plt

fn = 6
radius1 = 150
radius2 = 50

x = np.arange(fn + 1)
y = np.arange(fn + 1)

theta_step = np.pi * 2 / fn
X, Y = np.meshgrid(x, y) 

theta1 = X * theta_step
theta2 = Y * theta_step
r = radius1 + radius2 * np.cos(theta2)

TX = r * np.cos(theta1)
TY = r * np.sin(theta1)
TZ = radius2 * np.sin(theta2)

ax = plt.axes(projection='3d')
ax.plot_surface(TX, TY, TZ) 
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_box_aspect((1, 1, radius2 / radius1)) # 調整比例,不然 1:1:1 看來會很怪
plt.show()

畫出來的圖案會是:

NumPy 與環面(一)

嗯?不是環面?因為 fn 故意設成 6,就繪圖來說,圓從來就不完美,只能是正多邊形罷了,這篇文章一開始的圓,是 fn 設為 96 的結果,在人眼看來就覺得很圓了!