-
1.
判断点在多边形内外的简单算法
--
改进弧长法
< br>今天学图形学的时候发现了一个求多边形内外的超简单算法,
当时觉得非常惊喜,
后来
晚上上完选修的时候在走廊遇到
b
ug
,
bug
也是很惊喜地感慨道:居
然有甘简单既办法
都捻唔到!遂将其写下,供大家分享,希望不会太火星。
这个算法是源自《计算机图形学基础教程
》(孙家广,清华大学出版社),在该书
的
48-49
页,
名字可称为“改进的弧长法”。
该算法只需
O(1)
的附加空间,
时间复杂度
p>
为
O(n)
,
但系
数很小;
最大的优点是具有很高的精度,
只需做乘法和减法,<
/p>
若针对整数
坐标则完全没有精度问题。
而
且实现起来也非常简单,
比转角法和射线法都要好写且不
易出错
。
首先从该收中摘抄一段弧
长法的介绍:
“弧长法要求多边形是有向多边形,
一般规
定沿多边形的正向,边的左侧为多边形的内侧域。以被测点为圆心作单位圆,将全部有
向边向单位圆作径向投影,并计算其中单位圆上弧长的代数和。若代数和为
0
,则点在
多边形外部;
若代数和为
2
π
则点在多边形内部;
若代数和为
π
,
则点在多边
形上。
”
< br>按书上的这个介绍,其实弧长法就是转角法。但它的改进方法比较厉害:将坐标原
点平移到被测点
P
,这个新坐标系将平面划分为
4
个象限,对每个多边形顶点
P
,只考
虑其所在的象限,然后按邻接顺序访问多边形的各个顶点
P
,分析
P
和
P[i+1]
,有下列
三种情况:
(
1
)
P[i
+1]
在
P
的下一象限。此时弧长和加
π
/2
;
<
/p>
(
2
)
P[i+
1]
在
P
的上一象限。此时弧长和减<
/p>
π
/2
;
p>
(
3
)
P[i+1
]
在
Pi
的相对象限。首先计算
f=y[i+1]*x-x[i+1]*y
(叉积),若
f=0
,则
点在多边形上;若
f<0
,弧长和减
π
;若
f>0
,弧长和加
π
。<
/p>
最后对算出的代数和和上述的情况一样判断即可。
实现的时候还有两点要注意,第一个是若
P
的某个坐标为
0
时,一律
当正号处理;
第二点是若被测点和多边形的顶点重合时要特殊处理。
以上就是书上讲解的内容,
其实还存在一个问题。
那就是当多边形的某条边在坐标
轴上
而且两个顶点分别在原点的两侧时会出错。如边
(3,0)-(-3,0)
,按以上的处理,象
限分别是第一和第二,
这样会使
代数和加
π
/2
,
有可能导致最后结果是被测点在多边形
外。而实际上被测点是在多边形上(该边穿过
该点)。
对于这点,
我的处理办法是:
每次算
P
< br>和
P[i+1]
时,
就计算叉积
和点积,
判断该点
是否在该边上,
是则
判断结束,
否则继续上述过程。
这样牺牲了时间,
但保证了正确性。
具体实现的时候,由于只需知道当前点和上一点的象限位置,所以附加空间只需
O(1
)
。实现的时候可以把上述的“
π
/2
”改成
1
,“
π
”改成
2
,这样便可以完全使用
整数
进行计算。不必考虑顶点的顺序,逆时针和顺时针都可以处理,只是最后的代数和
符号不
同而已。整个算法编写起来非常容易。
针对以上算法,我写了一个代码,拿
ZOJ 1081
Points Within
进行测试,顺利
Accepted
。这证明该算法的正确性还是可以保障的。
以下附上我的代码:
// ZOJ
1081 ,
改进弧长法判点在形内形外
#include
#include
const int MAX = 101
struct point { int x , y } p[MAX]
int main()
{
int n , m , i , sum , t1 , t2 , f , prob = 0
point t
while (
scanf(
{
if(
prob ++ ) printf (
printf (
scanf (
for ( i = 0 i < n i ++ ) scanf (
p[n] = p[0]
while ( m --
)
{
scanf (
for ( i =
0 i <= n i ++ ) p.x -= t.x , p.y -= t.y
//
坐标平移
t1 = p[0].x>=0 ?
( p[0].y>=0?0:3 ) :
( p[0].y>=0?1:2 );
//
计算象限
for ( sum = 0 , i = 1 i <= n i ++ )
{
if ( !p.x && !p.y ) break //
被测点为多边形顶点
f = p.y * p[i-1].x - p.x * p[i-1].y //
计算
叉积
if ( !f && p[i-1].x*p.x <= 0 && p[i-1].y*p[i].y <=
0 ) break //
点在边上
t2 = p.x>=0 ? ( p.y>=0?0:3 ) : ( p.y>=0?1:2 ) //
计算象限
if ( t2 == ( t1 + 1 ) % 4 ) sum += 1 //
情况
1
else
if
(
t2
==
(
t1
+
3
)
%
4
)
sum
-=
1
; //
情况
2
else if ( t2
== ( t1 + 2 ) % 4 ) //
情况
3
{
if ( f > 0 ) sum += 2 else sum -= 2;
}
t1 = t2
}
if ( i<=n || sum ) printf(
printf(
for
(
i
=
0
;
i
<=
n
;
i
++
)
p.x
+=
t.x
,
p.y
+=
t.y
; //
恢复坐标
}
}
return 0;
}
2.
本文是采用射线法判断点是否
在多边形内的
C
语言程序。多年前,我自己实现了这样一个
p>
算法。但是随着时间的推移,我决定重写这个代码。参考周培德的《计算几何》一书,结合<
/p>
我的实践和经验,我相信,在这个算法的实现上,这是你迄今为止遇到的最优的代码。
p>
这是个
C
语
言的小算法的实现程序,本来不想放到这里。可是,当我自己要实现这样
一个算法的时候
,
想在网上找个现成的,
考察下来竟然一个符合需要的也没有。
我对自己大
学读书时写的代码没有信心,所以,决定重新写一个
,并把它放到这里,以飨读者。也增加
一下
BLOG
的点击量。
首先定义点结构如下:
以下是引用片段:
Copy code
/* Vertex structure */
typedef struct
{
double x, y;
} vertex_t;
p>
本算法里所指的多边形,
是指由一系列点序列组成的封闭简单多边形
。
它的首尾点可以
是或不是同一个点
(
不强制要求首尾点是同一个点
)
。这样
的多边形可以是任意形状的,包括
C
语
多条边在一条绝对直线上。因此,定义多边形结构如下:
实现
以下是引用片段:
点在
Copy code
/* Vertex list structure
–
polygon */
多边
typedef struct
形内
{
算法
int
num_vertices; /* Number of vertices in list */
言中
vertex_t *vertex; /* Vertex array
pointer */
} vertexlist_t;
p>
为加快判别速度,
首先计算多边形的外包矩形
(rect_t)
,
判断点是否落在外包矩形内,
只
有满足落在外包矩形内的条件的点,才进入下一步的计算。为此,引入外
包矩形结构
rect_t
和求点集合的外包矩形内的方法
vertices_get_extent
,代码如下:
以下是引用片段:
Copy code
/* bounding rectangle type */
typedef struct
{
double min_x, min_y, max_x,
max_y;
} rect_t;
/* gets extent of vertices
*/
void vertices_get_extent (const
vertex_t* vl, int np, /* in vertices */
rect_t* rc /* out extent*/ )
{
int
i;
if (np > 0){
rc->min_x = rc->max_x =
vl[0].x; rc->min_y = rc->max_y = vl[0].y;
}else{
rc->min_x = rc->min_y =
rc->max_x = rc->max_y = 0; /* =0 ? no vertices at
all */
}
for(i=1; i
{
if(vl[i].x < rc->min_x)
rc->min_x = vl[i].x;
if(vl[i].y < rc->min_y)
rc->min_y = vl[i].y;
if(vl[i].x > rc->max_x)
rc->max_x = vl[i].x;
if(vl[i].y > rc->max_y)
rc->max_y = vl[i].y;
}
}
当点满
足落在多边形外包矩形内的条件,要进一步判断点
(v)
是否在
多边形
(vl
:
np)
内。
本程序采用射线法,由待测试点
(v)
水平引出一条射线
B(v
,
w)
,计算
B
与
vl
边线的交点数
目,记为
c
p>
,根据奇内偶外原则
(c
为奇数说明
v
在
vl
内,否则<
/p>
v
不在
vl
内<
/p>
)
判断点是否在多
边形内。
具体原理就不多说。为计算线段间是否存在交点,引入下面的
函数:
(1)is_same
判断
2(p
、
q)
个点是<
/p>
(1)
否
(0)
在直线
l(l_start
,
l_en
d)
的同侧
;
(2)
is_intersect
用来判断
2
条线段
(
不是直线
)s1
、
s2
是
(1)
否
(0)
相交
;
以下是引用片段:
Copy code
/* p, q is on the same of line l */
static int is_same(const vertex_t*
l_start, const vertex_t* l_end, /* line l */
const vertex_t* p,
const vertex_t* q)
{
double dx =
l_end->x - l_start->x;
double dy = l_end->y - l_start->y;
double dx1= p->x - l_start->x;
double dy1= p->y - l_start->y;
double dx2= q->x - l_end->x;
double dy2= q->y - l_end->y;
return ((dx*dy1-dy*dx1)*(dx*dy2-dy*dx2)
> 0? 1 : 0);
}
/* 2 line segments (s1, s2) are
intersect? */
static int is_intersect(const vertex_t*
s1_start, const vertex_t* s1_end,
const vertex_t*
s2_start, const vertex_t* s2_end)
{
return
(is_same(s1_start, s1_end, s2_start, s2_end)==0 &&
is_same(s2_start, s2_end, s1_start,
s1_end)==0)? 1: 0;
}
下面的函数
pt_in_poly<
/p>
就是判断点
(v)
是
(1)
否
(0)
在多边形
(vl
:
np)
内的程
序:
以下是引用片段:
Copy code
int pt_in_poly ( const vertex_t* vl,
int np, /* polygon vl with np vertices */
const vertex_t* v)
{
int i, j, k1,
k2, c;
rect_t rc;
vertex_t w;
if (np < 3)
return 0;
vertices_get_extent(vl, np, &rc);
if (v->x < _x || v->x > _x
|| v->y < _y || v->y > _y)
return 0;
/* Set a horizontal beam
l(*v, w) from v to the ultra right */
w.x = _x +
DBL_EPSILON;
w.y = v->y;
c = 0; /* Intersection
points counter */
for(i=0; i
{
j = (i+1) % np;
if(is_intersect(vl+i, vl+j, v, &w))
{
c++;
}
else if(vl[i].y==w.y)
{
k1
= (np+i-1)%np;
while(k1!=i &&
vl[k1].y==w.y)
k1 = (np+k1-1)%np;
k2 = (i+1)%np;
while(k2!=i && vl[k2].y==w.y)
k2 = (k2+1)%np;
if(k1 != k2 && is_same(v,
&w, vl+k1, vl+k2)==0)
c++;
if(k2 <= i)
break;
i = k2;
}
}
return c%2;
}
本想配些插图说明问题,但是,
CS
DN
的文章里放图片我还没用过。以后再试吧
!
实践
证明,
本程序算法的适应性极强。
但是,
对于点正好落在多边形边上的极端情形,
有可能
得
出
2
种不同的结果。
3.
判断一个几何点在多边形内的例子:
#define
X
0
#define
Y
1
typedef
enum
{
FALSE,
TRUE
}
bool;
#define
DIM
2
/*
Dimension
of
points
*/
typedef
int
tPointi[DIM];
/*
type
integer
point
*/
typedef
double
tPointd[DIM];
/*
type
double
point
*/
#define
PMAX
10000
/*
Max
#
of
pts
in
polygon
*/
typedef
tPointi
tPolygoni[PMAX];/*
type
integer
polygon
*/
char
InPoly(
tPointi
q,
tPolygoni
P
,
int
n
);
void
PrintPoly(
int
n,
tPolygoni
P
);
void
PrintPoint(
tPointi
p
);
void
Copy
(
tPolygoni
a,
tPolygoni
b
,
int
n
);
main()
{
int
n;
tPolygoni
P
,
Porig;
tPointi
q;
n
=
ReadPoly(
P
);
Copy(
P
,
Porig,
n
);
while(
scanf(
%d
&q[X],
&q[Y])
!=
EOF
)
{
printf(
=
%cn
InPoly(
q,
P
,
n
)
);
/*
Refill
the
destroyed
polygon
with
original.
*/
Copy(
Porig,
P
,
n
);
}
}
/*
InPoly
returns
a
char
in
{i,o,v,e}.
See
above
for
definitions.
*/
char
InPoly(
tPointi
q,
tPolygoni
P
,
int
n
)
{
int
i,
i1;
/*
point
index;
i1
=
i-1
mod
n
*/
int
d;
/*
dimension
index
*/
double
x;
/*
x
intersection
of
e
with
ray
*/
int
Rcross
=
0;
/*
number
of
right
edge/ray
crossings
*/
int
Lcross
=
0;
/*
number
of
left
edge/ray
crossings
*/
printf(
q
=
PrintPoint(q);
putchar('n');
/*
Shift
so
that
q
is
the
origin.
Note
this
destroys
the
polygon.
This
is
done
for
pedogical
clarity.
*/
for(
i
=
0;
i
<
n;
i++
)
{
for(
d
=
0;
d
<
DIM;
d++
)
P[i][d]
=
P[i][d]
-
q[d];
}
/*
For
each
edge
e=(i-1,i),
see
if
crosses
ray.
*/
for(
i
=
0;
i
<
n;
i++
)
{
/*
First
see
if
q=(0,0)
is
a
vertex.
*/
if
(
P[i][X]==0
&&
P[i][Y]==0
)
return
'v';
i1
=
(
i
+
n
-
1
)
%
n;
/*
printf(
i1,
i);
*/
/*
if
e
the
x-axis...
*/
/*
The
commented-out
statement
is
logically
equivalent
to
the
one
following.
*/
/*
if(
(
(
P[i][Y]
>
0
)
&&
(
P[i1][Y]
<=
0
)
)
||
(
(
P[i1][Y]
>
0
)
&&
(
P[i]
[Y]
<=
0
)
)
)
{
*/
if(
(
P[i][Y]
>
0
)
!=
(
P[i1][Y]
>
0
)
)
{
/*
e
straddles
ray
,
so
compute
intersection
with
ray
.
*/
x
=
(P[i][X]
*
(double)P[i1][Y]
-
P[i1][X]
*
(double)P[i][Y])
/
(double)(P[i1][Y]
-
P[i][Y]);
/*
printf(
x
=
%gt
x);
*/
/*
crosses
ray
if
strictly
positive
intersection.
*/
if
(x
>
0)
Rcross++;
}
/*
printf(
cross=%dt
Rcross);
*/
/*
if
e
straddles
the
x-axis
when
reversed...
*/
/*
if(
(
(
P[i]
[Y]
<
0
)
&&
(
P[i1][Y]
>=
0
)
)
||
(
(
P[i1][Y]
<
0
)
&&
(
P[i]
[Y]
>=
0
)
)
)
{
*/
if
(
(
P[i][Y]
<
0
)
!=
(
P[i1][Y]
<
0
)
)
{
/*
e
straddles
ray
,
so
compute
intersection
with
ray
.
*/
x
=
(P[i][X]
*
P[i1][Y]
-
P[i1][X]
*
P[i][Y])
/
(double)(P[i1][Y]
-
P[i][Y]);
/*
printf(
x
=
%gt
x);
*/
/*
crosses
ray
if
strictly
positive
intersection.
*/
if
(x
<
0)
Lcross++;
}
/*
printf(
cross=%dn
Lcross);
*/
}
/*
q
on
the
edge
if
left
and
right
cross
are
not
the
same
parity.
*/
if(
(
Rcross
%
2
)
!=
(Lcross
%
2
)
)
return
'e';
/*
q
inside
iff
an
odd
number
of
crossings.
*/
if(
(Rcross
%
2)
==
1
)
return
'i';
else
return
'o';
}
void
PrintPoint(
tPointi
p
)
{
int
i;
putchar('(');
for
(
i
=
0;
i
<
DIM;
i++
)
{
printf(
p[i]);
if
(
i
!=
DIM-1
)
putchar(',');
}
putchar(')');
}
/*
Reads
in
the
coordinates
of
the
vertices
of
a
polygon
from
puts
them
into
P
,
and
returns
n,
the
number
of
vertices.
Formatting
conventions:
etc.
*/
int
ReadPoly(
tPolygoni
P
)
{
int
i,
n;
do
{
printf(
the
number
of
vertices:n
scanf(
&n
);
if
(
n
<=
PMAX
)
break;
printf(
in
read_poly:
too
many
points;
max
is
%dn
}
while
(
1
);
printf(
);
printf(
i
x
yn
for
(
i
=
0;
i
<
n;
i++
)
{
stdin,
PMAX);
scanf(
%d
&P[i][0],
&P[i][1]
);
printf(
i,
P[i][0],
P[i][1]);
}
printf(
=
%3d
vertices
readn
putchar('n');
return
n;
}
void
PrintPoly(
int
n,
tPolygoni
P
)
{
int
i;
printf(
printf(
i
x
yn
for(
i
=
0;
i
<
n;
i++
)
printf(
i,
P[i][0],
P[i][1]);
}
/*
Copy
polygon
a
to
b
(overwriting
b).
*/
void
Copy(
tPolygoni
a,
tPolygoni
b,
int
n
)
{
int
i,
j;
for
(
i=0;
i
<
n;
i++)
for
(
j
=
0;
j
<
DIM;
j++
)
b[i][j]
=
a[i][j];
}
bool
EqPoint(
tPointi
a,
tPointi
b
)
{
int
i;
for
(
i
=
0;
i
<
DIM;
i++
)
if
(
a[i]
!=
b[i])
return
FALSE;
return
TRUE;
}
1.
已知点
p>
point(x,y)
和多边形
Polyg
on
(
x1,y1;x2,y2;….xn,yn;
);
2.
< br>以
point
为起点,以无穷远为终点作平行于
X
轴的直线
line(
x,y;
-
∞
,y)
;
3.
循环取得
line
(f
or(i=0;i
多边形的每一条边
side
(xi,yi;xi+1,yi+1)
,
且判断是否平行于
X
轴,
如果平行<
/p>
continue
,
否则,
i++
;
4.
同时判断
point(x,y)
是否在
side
上,如果是,则返回
1(
p>
点在多边形
上
)
,否则继续下面的判断;
5.
判断线
side
与
是否有交点,如果有则
count++
,否则,
i++
。
6.
判断交点的总数,如果为奇数则返回<
/p>
0
(点在多边形内),偶数则返回
2
p>
(点在多边形外)。
代码:
/*
射线法判断点
q
与多边形
polyg
on
的位置关系,要求
polygon
为简单多边形,顶点逆时针排列
如果点在多边形内:
返回
0
如果点在多边形边上:
返回
1
如果点在多边形外:
返回
2
*/
const double
INFINITY = 1e10;
const
double ESP = 1e-5;
const int
MAX_N = 1000;
-
-
-
-
-
-
-
-
-
上一篇:英语考试来不及怎么办。
下一篇:高中数学词汇英文