主题:【翻译】计算机几何基础算法(一) -- 东方射日
凸包
对于一组点,能将所有点包含在内的最小的凸多边形就是所谓的凸包。这个凸包是有该组点中的若干点组成的。可以想象这些点是一块板上钉的钉子,用一个弹性很好的橡皮筋箍住所有的点,那么这条橡皮筋所形成的多边形就是凸包。有很多种不同的算法可以计算凸包,本文中,我们将讨论其中一种算法,该算法在大多数情况下的性能足够好,但是比起其他一些算法还是相当慢的。
首先,我们可以遍历所有点,找到最左边的点(x最小),如果有两个以上的点的x相同,那么我们取最高的那个点(y最大)。显然该点必然是凸包上的一个点,我们就设该点P为起点。现在我们以顺时针方向遍历所有的点。这里我们要使用叉乘算子。如何确定哪一个点是下一个点呢?我们可以任选一个未用的点N作为下一个点,然后遍历所有其他未用的点,假设是X。如果(X-P) x (N-P) 是负的,我们将X设为新的下一个点N。在我们遍历所有点后,得到的点N就是凸包上的下一个点。下面的图示表明了该算法的步骤:
接着同样的方法我们可以找到凸包的下一个点,直到我们返回起点。
这里我们是使用叉乘寻找顺时针的下一个点是很容易理解的。不过在有几点共线的时候,事情稍稍有点复杂。假设不考虑共线的情况,代码很简单:
convexHull(point[] X){
int N = lengthof(X);
int p = 0;
//First find the leftmost point
for(int i = 1; i<N; i++){
if(X[i] < X[p])
p = i;
}
int start = p;
do{
int n = -1;
for(int i = 0; i<N; i++){
//Don't go back to the same point you came from
if(i == p)continue;
//If there is no N yet, set it to i
if(n == -1)n = i;
int cross = (X[i] - X[p]) x (X[n] - X[p]);
if(cross < 0){
//As described above, set N=X
n = i;
}
}
p = n;
}while(start!=p);
}
如果考虑共线的情况,代码稍微复杂一些。首先我们添加一个bool型的参数,该参数指定线上的点是否记为凸包上的点。
//If onEdge is true, use as many points as possible for
//the convex hull, otherwise as few as possible.
convexHull(point[] X, boolean onEdge){
int N = lengthof(X);
int p = 0;
boolean[] used = new boolean[N];
//First find the leftmost point
for(int i = 1; i<N; i++){
if(X[i] < X[p])
p = i;
}
int start = p;
do{
int n = -1;
int dist = onEdge?INF:0;
for(int i = 0; i<N; i++){
//X[i] is the X in the discussion
//Don't go back to the same point you came from
if(i==p)continue;
//Don't go to a visited point
if(used[i])continue;
//If there is no N yet, set it to X
if(n == -1)n = i;
int cross = (X[i] - X[p]) x (X[n] - X[p]);
//d is the distance from P to X
int d = (X[i] - X[p]) (X[i] - X[p]);
if(cross < 0){
//As described above, set N=X
n = i;
dist = d;
}else if(cross == 0){
//In this case, both N and X are in the
//same direction. If onEdge is true, pick the
//closest one, otherwise pick the farthest one.
if(onEdge && d < dist){
dist = d;
n = i;
}else if(!onEdge && d > dist){
dist = d;
n = i;
}
}
}
p = n;
used[p] = true;
}while(start!=p);
}
(译注:该算法复杂度为O(N^2),有更好的算法,复杂度为O(NLogN),参见维基:
http://zh.wikipedia.org/w/index.php?title=%E5%87%B8%E5%8C%85&variant=zh-cn
http://en.wikipedia.org/wiki/Convex_hull_algorithms
多边形内点
给出一个任意多边形的顶点和一个点,测试该点是否在多边形内、外或边上。
这个问题可以简单使用线-线交点和点-线距离的算法。
首先我们可以使用点-线距离来测试该点是否在边上。如果该点到任意一条边的距离为0,那么显然是在边上。
然后,我们要检测该点是否在多边形内部。想象一下以该点为起点,向任意方向画一条射线,每一次该射线和边相交,该射线就由多边形内部穿越到外部或相反。这样,如果该点在多变形内部,该射线必然和边相交奇数次。技术上我们不需要真的画一条无限长的射线,我们只要有一足够长的线段即可。如果另一端点没有选好,你有时会陷入困境,例如线段两点均在多边形内部或和某一条边重合。简单但是龌龊的做法是用一个足够大随机数作为线段的另一个端点,这个方法虽然不优美且不安全,但是在实践中它管用。这线段和某一条边重合的概率是如此之小以至于你可以拍胸脯说可以得到正确的解答。如果你还是不放心,你可以选几个随机点,然后返回最普遍的答案。
String testPoint(verts, x, y){
int N = lengthof(verts);
int cnt = 0;
double x2 = random()*1000+1000;
double y2 = random()*1000+1000;
for(int i = 0; i<N; i++){
if(distPointToSegment(verts[i],verts[(i+1)%N],x,y) == 0)
return "BOUNDARY";
if(segmentsIntersect((verts[i],verts[(i+1)%N],{x,y},{x2,y2}))
cnt++;
}
if(cnt%2 == 0)return "EXTERIOR";
else return "INTERIOR";
}
本帖一共被 1 帖 引用 (帖内工具实现)
- 相关回复 上下关系8
🙂【翻译】计算机几何基础算法(二) 16 东方射日 字4265 2009-01-11 21:05:34
🙂这一炮是白打了…… 响马 字127 2009-01-15 07:14:20
🙂科普技术贴阿,送花 木秀于林 字0 2009-01-13 03:34:54
🙂【翻译】计算机几何基础算法(三)
🙂不知你是否考虑到计算精度的问题 奔波儿 字230 2009-01-18 16:21:27
🙂老兄看来是有经验的 东方射日 字239 2009-01-19 00:43:44
🙂我是小半年没见过宝了, ajie1a 字159 2009-01-17 23:18:21
🙂赞一个,我最近在做3d开发 雨后的假牙 字38 2009-01-16 03:44:45