绘制3D多边形

73 投票
2 回答
72317 浏览
提问于 2025-04-16 09:32

我在网上找解决方案时遇到了一些困难,想问一个简单的问题:

如何用顶点的值来画一个3D多边形(比如一个填充的矩形或三角形)?我尝试了很多方法,但都没有成功,见:

from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes3D(fig)
x = [0,1,1,0]
y = [0,0,1,1]
z = [0,1,0,1]
verts = [zip(x, y,z)]
ax.add_collection3d(PolyCollection(verts),zs=z)
plt.show()

提前感谢任何想法或评论。

根据被接受的答案的更新:

import mpl_toolkits.mplot3d as a3
import matplotlib.colors as colors
import pylab as pl
import numpy as np

ax = a3.Axes3D(pl.figure())
for i in range(10000):
    vtx = np.random.rand(3,3)
    tri = a3.art3d.Poly3DCollection([vtx])
    tri.set_color(colors.rgb2hex(np.random.rand(3)))
    tri.set_edgecolor('k')
    ax.add_collection3d(tri)
pl.show()

这是结果:

在这里输入图片描述

2 个回答

1

很遗憾,下面的答案和更新代码都导致了可视化结果是错误的,但原因不同。下面会解释这些原因。

一个解决方案

首先考虑一个“正确”的答案。Matplotlib可以正确绘制填充的多边形。多边形是平面的。问题中提到的四个顶点并不在同一个平面上。但这些顶点可以用来定义两个三角形。在art3d.Poly3DCollection中的verts参数是一个(N, 3)的数组,表示多边形的顶点。通过使用四个顶点的列表v和两个三角形面的顶点索引列表f,可以简单地解决这个问题:

import pylab as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

v = [ [0,0,0], [1,0,1], [1,1,0], [0,1,1] ]   # 4 vertices
f = [ [0,1,3], [1,2,3] ]                     # 2 faces
verts =  [ [ v[i] for i in p] for p in f]    # list comprehension
c = ['C0','C3']                              # 2 colors

fig = plt.figure()
ax = plt.axes(projection='3d')
ax.set(xlim=(0,1), ylim=(0,1), zlim=(0,1))
ax.add_collection3d(Poly3DCollection(verts,color=c))
plt.show()

这样就能生成两个不同颜色的多边形图形,如下所示:

two surface solution

这甚至适用于边数不同的多边形列表。例如,一个包含正方形和三角形面的集合:

closed surface

生成这个图形的代码是:

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

z = 2**(1/2)
v =  [ [1,1, 1], [-1,1, 1], [-1,-1, 1], [1,-1, 1], 
       [z,0,-1], [ 0,z,-1], [-z, 0,-1], [0,-z,-1] ]      # 8 vertices
f4 = [ [0,1,2,3], [7,6,5,4] ]
fa = [ [0,3,4], [1,0,5], [2,1,6], [3,2,7] ]
fb = [ [4,5,0], [5,6,1], [6,7,2], [7,4,3] ]
f = f4 + fa + fb                                         # 10 faces
verts =  [ [ v[i] for i in p] for p in f]                # list comprehension
colors = ['teal']*2 + ['indianred']*4 + ['olivedrab']*4  # 10 colors

fig = plt.figure(figsize=plt.figaspect(1))
ax = plt.axes(projection='3d')
ax.set(xlim=(-1.5,1.5), ylim=(-1.5,1.5), zlim=(-1.5,1.5))
ax.add_collection3d(Poly3DCollection(verts,color=colors))

fig.tight_layout()
plt.show()

注意colors列表中的颜色顺序与f列表中的面顺序是相同的。此外,如果所有多边形都是同一种类型,比如全是三角形或全是四边形,使用Numpy数组来表示顶点v和面f会更方便。这样verts可以简单地定义为:

verts = v[f]

而不是使用Python的列表推导式。此外,如果使用了阴影,所有多边形必须根据右手或左手的顶点顺序,使用每个面的顶点索引列表来定义。

问题1. 非平面面

通过以下方式定义verts列表:

x = [0,1,1,0]
y = [0,0,1,1]
z = [0,1,0,1]
verts = [list(zip(x,y,z))]

结果是定义了一个四边形面。对于Matplotlib默认的坐标轴视图(azim=-60),可视化“看起来”是正确的。然而,从不同的视角来看,视觉效果是不一致的。下面通过比较三个视角来展示这一点。这不是Matplotlib的问题,而是因为用四个不在同一平面上的顶点错误地定义了一个四边形。

incorrect views

使用两个三角形面的解决方案,这个3D集合的可视化在任何观察方向上都是一致的:

correct views

可以从这四个顶点构建一个扭曲的表面,但这需要将表面细分为多个多边形。使用Matplotlib的第三方包S3Dlib,可以从512个三角形构建出以下的规则表面。

ruled surface

在这个例子中,表面的顶部和底部分别被涂成蓝色和红色。然后应用阴影以增强表面的可视化效果。这样就不再简单,但却是正确的。

Matplotlib的3D可视化可以通过正确地沿视图方向堆叠多边形来处理多个多边形的集合。然而,当多边形相交时,Matplotlib无法直接处理可视化。可视化结果将是错误的,并且依赖于视角。

例如,考虑以下三个三角形:

v = [ [0,0,0], [1,0,0], [1,1,0], [0,1,0],
      [0,0,1], [1,0,1], [1,1,1], [0,1,1] ]    # 8 vertices
f = [ [1,6,4], [1,3,5], [0,2,7] ]             # 3 triangles
c = [ 'C0', 'C1', 'C2' ]

这些是三个相交的三角形,但从不同的方向观察时,可视化效果是不一致的。

incorrect intersecting triangles

通过将这三个三角形细分为更小的三角形,可以在不依赖视角的情况下一致地可视化它们之间的交集:

correct intersecting triangles

上面的相交三角形图形是使用Matplotlib和S3Dlib创建的。

问题更新生成了一组随机的三角形多边形。然而,如上面的简单例子所示,这么多多边形会相互交叉。因此,实际的集合在没有考虑交集的情况下是无法正确显示的。

通过从不同的方向仔细观察类似的三角形集合,可以发现可视化中的异常。这在下面的图中展示。

random triangles

一个可能的解决方案是将众多三角形细分为更小的多边形,以考虑交集。然而,这种方法会导致显著的计算成本。问题的解决方案不再是“简单”的。

总结

通过从不同的视角观察3D集合,可以识别出这两个问题。这样,在开发过程中,可能会发现解决方法存在缺陷。

另外需要注意的是:在采样之前,给Numpy随机生成器设置种子,例如使用:

np.random.seed(seed)

这样可以在代码开发和验证过程中使用相同的一组随机数。

74

我觉得你差不多明白了。这个是你想要的吗?

from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(ax)
x = [0,1,1,0]
y = [0,0,1,1]
z = [0,1,0,1]
verts = [list(zip(x,y,z))]
ax.add_collection3d(Poly3DCollection(verts))
plt.show()

alt text你可能还会对art3d.pathpatch_2d_to_3d感兴趣。

撰写回答