布尔联合两个网格仅适用于椭圆体
Boolean union two meshes only works for ellipsoid
我想在街道(平面)中创建一个坑洞(椭圆体)。经过一些测试,我意识到布尔联合可能是一个可以使用的(因为我想结合两个网格但不希望街道掩盖坑洼)。
当我使用 Sphere 尝试这个时,它工作得很好。
但是,我使用的是 ParameticEllipsoid。在那里我经常收到错误“vtkMath::Jacobi:提取特征函数时出错”,并且想要的效果仅适用于布尔差值而不是并集。我想它必须与 vtk/py 及其几何对象创建有关。
虽然采用布尔差值会带来想要的结果,但它会花费很多时间(我不会介意太多)并且很少起作用(并且仅当椭圆体未与其他对象啮合时)。
有没有办法避免这种情况?我的想法是提取网格的点(NumPy 数组)并计算它们的并集,但是我做不到。
def add_pothole():
street_mesh = pv.Plane(i_size=100, j_size=100, i_resolution=100, j_resolution=100)
points = street_mesh.points
for i in range(4):
pothole_mesh = pv.ParametricEllipsoid(10,5,3)
# alternatively to get the half ellipsoid: (but boolean difference seemed to only work with the closed one
# pothole_mesh = pv.ParametricEllipsoid(10,5,3, max_v=pi /2)
pothole_mesh.translate(choice(points))
# "add" the pothole to the street
street_mesh = pothole_mesh.boolean_difference(street_mesh.triangulate())
street_mesh.plot()
或者,我考虑直接定义椭圆形凹陷,使用高度作为平面内位置的函数。是否可以在不使用布尔运算的情况下执行此操作?
为了解决您当前的问题,您当前的代码存在两个问题:
- 与
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()
来实现噪声调制。好吧,你可以只在相同的标量上添加椭圆形的凸起,然后在最后一步进行扭曲(与 完全相同的方式)。
为此,您必须计算椭圆体的 z(x, y)
参数化。这不是微不足道的,但也不是很难。更广为人知的是,围绕原点半径为 R
的球体的方程是
x^2 + y^2 + z^2= R^2.
除以 R^2
:
更好(并且规范)
(x/R)^2 + (y/R)^2 + (z/R)^2 = 1.
获得椭圆体的方法是线性变换每个轴,改变它们各自的半径(因此它们不再都是 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 过滤打破了这一点。)
我想在街道(平面)中创建一个坑洞(椭圆体)。经过一些测试,我意识到布尔联合可能是一个可以使用的(因为我想结合两个网格但不希望街道掩盖坑洼)。
当我使用 Sphere 尝试这个时,它工作得很好。
但是,我使用的是 ParameticEllipsoid。在那里我经常收到错误“vtkMath::Jacobi:提取特征函数时出错”,并且想要的效果仅适用于布尔差值而不是并集。我想它必须与 vtk/py 及其几何对象创建有关。
虽然采用布尔差值会带来想要的结果,但它会花费很多时间(我不会介意太多)并且很少起作用(并且仅当椭圆体未与其他对象啮合时)。
有没有办法避免这种情况?我的想法是提取网格的点(NumPy 数组)并计算它们的并集,但是我做不到。
def add_pothole():
street_mesh = pv.Plane(i_size=100, j_size=100, i_resolution=100, j_resolution=100)
points = street_mesh.points
for i in range(4):
pothole_mesh = pv.ParametricEllipsoid(10,5,3)
# alternatively to get the half ellipsoid: (but boolean difference seemed to only work with the closed one
# pothole_mesh = pv.ParametricEllipsoid(10,5,3, max_v=pi /2)
pothole_mesh.translate(choice(points))
# "add" the pothole to the street
street_mesh = pothole_mesh.boolean_difference(street_mesh.triangulate())
street_mesh.plot()
或者,我考虑直接定义椭圆形凹陷,使用高度作为平面内位置的函数。是否可以在不使用布尔运算的情况下执行此操作?
为了解决您当前的问题,您当前的代码存在两个问题:
- 与
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()
来实现噪声调制。好吧,你可以只在相同的标量上添加椭圆形的凸起,然后在最后一步进行扭曲(与
为此,您必须计算椭圆体的 z(x, y)
参数化。这不是微不足道的,但也不是很难。更广为人知的是,围绕原点半径为 R
的球体的方程是
x^2 + y^2 + z^2= R^2.
除以 R^2
:
(x/R)^2 + (y/R)^2 + (z/R)^2 = 1.
获得椭圆体的方法是线性变换每个轴,改变它们各自的半径(因此它们不再都是 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 过滤打破了这一点。)