确定一个点是否在多面体内部
Determining if a point is inside a polyhedron
我正试图确定一个特定的点是否位于多面体内。在我目前的实现中,我正在研究的方法取了我们正在寻找多面体的面数组的点(在这种情况下是三角形,但以后可能是其他多边形)。我一直在尝试根据以下信息进行工作:http://softsurfer.com/Archive/algorithm_0111/algorithm_0111.htm
下面,您将看到我的"内部"方法。我知道正常的事情有点奇怪。。这是旧代码的结果。当我运行这个程序时,无论我给它什么输入,它似乎总是返回true。
bool Container::inside(Point* point, float* polyhedron[3], int faces) {
Vector* dS = Vector::fromPoints(point->X, point->Y, point->Z,
100, 100, 100);
int T_e = 0;
int T_l = 1;
for (int i = 0; i < faces; i++) {
float* polygon = polyhedron[i];
float* nrml = normal(&polygon[0], &polygon[1], &polygon[2]);
Vector* normal = new Vector(nrml[0], nrml[1], nrml[2]);
delete nrml;
float N = -((point->X-polygon[0][0])*normal->X +
(point->Y-polygon[0][1])*normal->Y +
(point->Z-polygon[0][2])*normal->Z);
float D = dS->dot(*normal);
if (D == 0) {
if (N < 0) {
return false;
}
continue;
}
float t = N/D;
if (D < 0) {
T_e = (t > T_e) ? t : T_e;
if (T_e > T_l) {
return false;
}
} else {
T_l = (t < T_l) ? t : T_l;
if (T_l < T_e) {
return false;
}
}
}
return true;
}
这是用C++编写的,但正如评论中所提到的,它实际上是与语言无关的。
您问题中的链接已过期,我无法理解您代码中的算法。假设你有一个凸多面体,其逆时针方向的面(从外面看),那么检查你的点是否在所有面的后面就足够了。要做到这一点,可以从点到每个面的向量,并检查标量乘积与面的法线的符号。如果是正的,则该点位于面部后面;如果为零,则该点在面上;如果是负数,则该点位于面部前方。
以下是一些完整的C++11代码,适用于3点面或更多点面(只考虑前3点)。可以很容易地更改bound
以排除边界。
#include <vector>
#include <cassert>
#include <iostream>
#include <cmath>
struct Vector {
double x, y, z;
Vector operator-(Vector p) const {
return Vector{x - p.x, y - p.y, z - p.z};
}
Vector cross(Vector p) const {
return Vector{
y * p.z - p.y * z,
z * p.x - p.z * x,
x * p.y - p.x * y
};
}
double dot(Vector p) const {
return x * p.x + y * p.y + z * p.z;
}
double norm() const {
return std::sqrt(x*x + y*y + z*z);
}
};
using Point = Vector;
struct Face {
std::vector<Point> v;
Vector normal() const {
assert(v.size() > 2);
Vector dir1 = v[1] - v[0];
Vector dir2 = v[2] - v[0];
Vector n = dir1.cross(dir2);
double d = n.norm();
return Vector{n.x / d, n.y / d, n.z / d};
}
};
bool isInConvexPoly(Point const& p, std::vector<Face> const& fs) {
for (Face const& f : fs) {
Vector p2f = f.v[0] - p; // f.v[0] is an arbitrary point on f
double d = p2f.dot(f.normal());
d /= p2f.norm(); // for numeric stability
constexpr double bound = -1e-15; // use 1e15 to exclude boundaries
if (d < bound)
return false;
}
return true;
}
int main(int argc, char* argv[]) {
assert(argc == 3+1);
char* end;
Point p;
p.x = std::strtod(argv[1], &end);
p.y = std::strtod(argv[2], &end);
p.z = std::strtod(argv[3], &end);
std::vector<Face> cube{ // faces with 4 points, last point is ignored
Face{{Point{0,0,0}, Point{1,0,0}, Point{1,0,1}, Point{0,0,1}}}, // front
Face{{Point{0,1,0}, Point{0,1,1}, Point{1,1,1}, Point{1,1,0}}}, // back
Face{{Point{0,0,0}, Point{0,0,1}, Point{0,1,1}, Point{0,1,0}}}, // left
Face{{Point{1,0,0}, Point{1,1,0}, Point{1,1,1}, Point{1,0,1}}}, // right
Face{{Point{0,0,1}, Point{1,0,1}, Point{1,1,1}, Point{0,1,1}}}, // top
Face{{Point{0,0,0}, Point{0,1,0}, Point{1,1,0}, Point{1,0,0}}}, // bottom
};
std::cout << (isInConvexPoly(p, cube) ? "inside" : "outside") << std::endl;
return 0;
}
使用您最喜欢的编译器进行编译
clang++ -Wall -std=c++11 code.cpp -o inpoly
并像一样进行测试
$ ./inpoly 0.5 0.5 0.5
inside
$ ./inpoly 1 1 1
inside
$ ./inpoly 2 2 2
outside
如果网格是凹面的,并且不一定是水密的,那么很难实现。
作为第一步,在网格的曲面上找到最靠近该点的点。需要跟踪位置和特定特征:最近的点是在面的中间、网格的边缘还是网格的某个顶点。
如果特征是人脸,你很幸运,可以使用绕组来找到它是内部还是外部。计算面法线(甚至不需要对其进行归一化,非单位长度即可),然后计算dot( normal, pt - tri[0] )
,其中pt是点,tri[0]是面的任何顶点。如果这些面有一致的弯曲,点积的符号会告诉你它是内侧还是外侧。
如果特征是边,则计算两个面的法线(通过标准化叉积),将它们相加,用作网格的法线,然后计算相同的点积。
最困难的情况是顶点是最接近的特征。若要计算该顶点处的网格法线,需要计算共享该顶点的面的法线之和,由该顶点处该面的2D角度加权。例如,对于具有3个相邻三角形的立方体的顶点,权重将为Pi/2。对于具有6个相邻三角形的立方体的顶点,权重将为Pi/4。对于现实生活中的网格,每个面的权重都不同,在[0..+Pi]的范围内。这意味着在这种情况下,你需要一些反三角代码来计算角度,可能是acos()
。
如果你想知道为什么这样做,请参阅J.Andreas Bærentzen和Henrik Aanæs的"从三角形网格生成有符号距离场"。
我几年前就已经回答了这个问题。但从那时起,我发现了更好的算法。它发明于2018年,链接如下。
这个想法相当简单。给定该特定点,计算从该点观察多面体所有面的有符号立体角之和。如果点在外面,那个和一定是零。如果点在里面,这个和必须是±4πsteradians,+或-取决于多面体面的缠绕顺序。
这种特殊的算法将多面体打包成一棵树,当您需要对同一多面体进行多个内部/外部查询时,这将显著提高性能。该算法仅在人脸非常靠近查询点时计算各个人脸的立体角。对于远离查询点的大型面集,该算法使用这些集的近似值,使用它们在从源网格构建的BVH树的节点中保留的一些数字。
在FP数学精度有限的情况下,如果使用近似的BVH树,则该角度将永远不会精确为0或±4·π。但是,2π阈值在实践中仍然相当有效,至少在我的经验中是这样。如果立体角之和的绝对值小于2·π,则认为该点在外面。
事实证明,问题是我阅读了上面链接中引用的算法。我在读:
N = - dot product of (P0-Vi) and ni;
作为
N = - dot product of S and ni;
在更改了这一点之后,上面的代码现在似乎可以正常工作。(我也在更新问题中的代码,以反映正确的解决方案)。
- 在提升multi_index容器中,是否定义了"default index"?
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 检查输入是否不是整数或数字
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 是否可以通过C++扩展强制多个python进程共享同一内存
- 此代码是否违反一个定义规则
- 是否需要删除包含对象的"pair"?
- 是否可以从int转换为enum类类型
- 无论条件是否为true,if总是在c++中执行
- 如何找到大小'x'数组是否完全填充,在C++?
- 检查值是否在集合p1和p2中,但不在p3中
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 检查 std::shared_ptr<> 的当前底层类型是否为 T
- 在c++中检查长方体是否尽可能快地重叠(无迭代)
- GL_SHADERSTORAGE_BUFFER位置是否与其他着色器位置冲突
- 子目录是否继承属性,例如add_definitions,include_directories和父Cmakelist.t
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- C/C++预处理器是否可以检测一些编译器选项
- 确定一个点是否在多面体内部