为了解决您当前的问题,您当前的代码存在两个问题:
- 不像
Sphere
,ParametricEllipsoid
由于 VTK 构造它的方式,有很多杂散点,所以最终结果在技术上并不是一个封闭的表面。这是我计划在 pyvista 中修复的一个已知问题。现在你可以通过调用clean(tolerance=1e-5)
你的椭球来解决这个问题。这应该阻止这些vtkMath
错误弹出。
- 从坑洼中减去街道将在每次调用 后反转飞机的法线
boolean_difference
,这将使您在飞机的交替两侧产生颠簸,这可能不是您想要的。street_mesh.flip_normals()
您可以通过在循环结束时调用来解决此问题。
通过这两个更改,您的代码将运行,尽管速度很慢(我真的不知道为什么,但我也不知道布尔运算如何在幕后工作):
from random import choice
import numpy as np
import pyvista as pv
def add_pothole():
street_mesh = pv.Plane(i_size=100, j_size=100, i_resolution=100, j_resolution=100)
pothole_template = pv.ParametricEllipsoid(10, 5, 3).clean(tolerance=1e-5).triangulate(
points = street_mesh.points
for i in range(4):
pothole_mesh = pothole_template.copy()
pothole_mesh.translate(choice(points))
# "add" the pothole to the street
street_mesh = pothole_mesh.boolean_difference(street_mesh.triangulate())
street_mesh.flip_normals()
street_mesh.plot()
add_pothole()
z(x, y)
但是要解决您的根本问题,如果您的街道一开始就是一个表面,那么您不必首先这样做。考虑到 Perlin 噪声,您可能从一个平面开始,其中标量从 Perlin 噪声中采样,然后调用warp_by_scalar()
以实现噪声调制。好吧,您可以将椭圆形的凸块添加到相同的标量,然后在最后一步进行变形(与叠加多个 Perlin 噪声样本的方式完全相同)。
为此,您必须计算z(x, y)
椭球的参数化。这不是微不足道的,但也不是很困难。可能更广为人知的是,R
以原点为半径的球体方程为
x^2 + y^2 + z^2= R^2.
当除以时,这是更好的(和规范的)R^2
:
(x/R)^2 + (y/R)^2 + (z/R)^2 = 1.
你得到一个椭球的方法是线性变换每个轴,改变它们各自的半径(所以它们不再是 all R
)。这是椭圆体的隐式方程:
(x/a)^2 + (y/b)^2 + (z/c)^2 = 1.
如果你想为椭球有一个重要的中心,你必须翻译每个坐标:
(x - x0)^2/a^2 + (y - y0)^2/b^2 + (z - z0)^2/c^2 = 1.
这是 3 维椭球的一般形式,假设它的轴与笛卡尔坐标系的轴定向。
嗯,这很棒,因为我们可以重新排列它以获得
(z - z0)^2/c^2 = 1 - (x - x0)^2/a^2 - (y - y0)^2/b^2
(z - z0)^2 = c^2 - (c/a)^2 (x - x0)^2 - (c/b)^2 (y - y0)^2
z = z0 +- sqrt(c^2 - (c/a)^2 (x - x0)^2 - (c/b)^2 (y - y0)^2)
加减部分对应于椭圆体不是z(x, y)
函数的事实,因为对于每个(x, y)
点,您都有两个可能的值。但是你可以从两个函数构造一个椭球:一个顶面和一个底面。
最后回到你的问题,你可以选择一个随机(x0, y0, z0)
点,然后选择上面椭圆体的底面(那个z = z0 - sqrt(...)
)。你唯一需要担心的是,对于(x, y)
没有椭球的点,你会得到一个假想的平方根。所以你必须首先过滤掉椭球内的点。最简单的方法是计算平方根,然后丢弃 NaN:
import numpy as np
import pyvista as pv
# denser plane for sharper ellipsoid
street_mesh = pv.Plane(i_size=100, j_size=100, i_resolution=1000, j_resolution=1000)
street_mesh['Normals'] *= -1 # make normals point up for warping later
# add Perlin noise for example
freq = [0.03, 0.03, 0.03]
noise = pv.perlin_noise(5, freq, [0, 0, 0])
street_mesh['height'] = [noise.EvaluateFunction(point) for point in street_mesh.points]
# the relevant part: ellipsoid function
x0, y0, z0 = street_mesh.points[street_mesh.points.size//6, :] # or whatever random point you want
x, y, _ = street_mesh.points.T # two 1d arrays of coordinates
a, b, c = 10, 5, 3 # semi-axes
ellipsoid_fun = z0 - np.sqrt(c**2 - (c/a)**2 * (x - x0)**2 - (c/b)**2 *(y - y0)**2) # RuntimeWarning
keep_inds = ~np.isnan(ellipsoid_fun)
street_mesh['height'][keep_inds] += ellipsoid_fun[keep_inds]
street_mesh.set_active_scalars('height')
# warp by 'height' and do a quick plot
street_mesh.warp_by_scalar().plot()
以上将发出关于虚平方根的警告(导致数据中的 NaN)。如果这让您感到困扰,您可以明确地将其静音。或者,我们可以按照 LBYL 并在计算平方根之前自己检查值:
arg = c**2 - (c/a)**2 * (x - x0)**2 - (c/b)**2 *(y - y0)**2
keep_inds = arg >= 0
ellipsoid = z0 - np.sqrt(arg[keep_inds])
street_mesh['height'][keep_inds] = ellipsoid
这是结果,使用增加的平面密度以获得更清晰的椭圆体:
(我最初建议您可以使用 numpy 广播计算一次坐着的每个坑洼,但这实际上并不容易,甚至可能是不可能的,因为 NaN 过滤打破了这一点。)