文章目录
- 前情提要
- 补点
- 球形膨胀
前情提要
足球是正五边形和正六边形拼接而成,由此形成的骨架结构,可通过切割正二十面体获得,所以画足球的第一步是画正二十面体:Python绘制正二十面体
在学会绘制正二十面体之后,就可以通过切割顶点从而得到足球的骨架:用Python画一个足球
补点
尽管通过对正二十面体削角得到了足球,但这个足球有个非常明显的问题,就是踢不动。接下来,就要想办法将这个足球"吹"圆。
好在最初创建正二十面体的时候,便以原点为中心,所以一个显而易见的方案就是,将足球上的点向外膨胀到球的半径长度。
然而这个时候问题又来了,这个足球总共有60个顶点,每个顶点到圆心的距离都是恒定的,换言之,就算将其膨胀一下,也无非是等比例地将这个足球放大而已,所以问题的第一步,就是对足球补点。
在补点之前,先把正六边形和正五边形的点提取出来。
# 此为五边形面的顶点
penPts = getPtEdges(pts, edges)
# 此为六边形面的顶点
hexPts = [getHexEdges(f) for f in faces]
然后进行补点操作,其方法非常简单,以六边形为例,任选六边形的两个顶点,二者连线后,对线段进行五等分,然后将五等分处的四个点提取出来,就算是点被提取了。
from itertools import combinations
# N表示等分点数
def getMorePts(pts, N=5):
morePts = []
for p1, p2 in combinations(pts, 2):
morePts += [(i*p1+(N-i)*p2)/N for i in range(N)]
return np.unique(morePts,axis=0)
penMorePts = [getMorePts(pt) for pt in penPts]
hexMorePts = [getMorePts(pt) for pt in hexPts]
接下来绘制一下,
ax = plt.subplot(projection='3d')
for pt in hexMorePts:
pt = Rx(1)@Ry(1)@pt.T
ax.plot_trisurf(*pt, color="white")
for pt in penMorePts:
pt = Rx(1)@Ry(1)@pt.T
ax.plot_trisurf(*pt, color="black")
ax.axis('off')
plt.show()
效果为
球形膨胀
补点操作当然不足以让图形变圆,接下来才是见证奇迹的时刻。
其实方法也非常简单,无非对足球上的点进行归一化而已,将所有点到圆心的距离变为某个定值,简单起见,这里暂且设为1。
ptNorm = lambda pts : np.array([p/np.linalg.norm(p) for p in pts])
hexBallPts = [ptNorm(pts) for pts in hexMorePts]
penBallPts = [ptNorm(pts) for pts in penMorePts]
接下来,就是激动人心的画图环节,但这里又涉及到plot_trisurf
绘图逻辑引入的Bug,即对于
z
>
0
z>0
z>0和
z
<
0
z<0
z<0的两个区域,plot_trisurf
优先连接
x
,
y
x,y
x,y相近的位置。
def splitPtByZ(pts):
zs = pts[:,2]
if min(zs) > 0 or max(zs) < 0:
return [pts]
indPlus = np.where(zs>-0.1)[0]
indMinus = np.where(zs<0.1)[0]
indMid = np.where((zs>-0.1) & (zs<0.1))[0]
return [pts[indPlus], pts[indMid], pts[indMinus]]
ax = plt.subplot(projection='3d')
for pt in hexBallPts:
ptDraw = splitPtByZ(pt)
for p in ptDraw:
ax.plot_trisurf(*p.T, color="white")
for pt in penBallPts:
ptDraw = splitPtByZ(pt)
for p in ptDraw:
ax.plot_trisurf(*p.T, color="black")
ax.axis('off')
plt.show()
效果如图所示