点、线、面相关的算法(1)

转载原文:http://xuejx.blog.sohu.com/62019268.html


/* 需要包含的头文件 */

#i nclude <cmath > 

/* 常用的常量定义 */ 
const double INF = 1E200    
const double EP = 1E-10 
const int MAXV = 300 
const double PI = 3.14159265 

/* 基本几何结构 */ 
struct POINT 

double x; 
double y; POINT(double a=0, double b=0) { x=a; y=b;} //constructor 
}; 
struct LINESEG 

POINT s; 
POINT e; LINESEG(POINT a, POINT b) { s=a; e=b;} 
LINESEG() { } 
}; 
struct LINE               // 直线的解析方程 a*x+b*y+c=0      为统一表示,约定 a >= 0 

       double a; 
       double b; 
       double c; LINE(double d1=1, double d2=-1, double d3=0) {a=d1; b=d2; c=d3;}
}; 

/********************\ 
*                        * 
*       点的基本运算         * 
*                        * 
\********************/ 

double dist(POINT p1,POINT p2)                    // 返回两点之间欧氏距离 

return( sqrt( (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y) ) ); 

bool equal_point(POINT p1,POINT p2)               // 判断两个点是否重合  

return ( (abs(p1.x-p2.x)<EP)&&(abs(p1.y-p2.y)<EP) ); 


/****************************************************************************** 
r=multiply(sp,ep,op),得到(sp-op)*(ep-op)的叉积 
r>0:ep在矢量opsp的逆时针方向; 
r=0:opspep三点共线; 
r<0:ep在矢量opsp的顺时针方向 
*******************************************************************************/ 

double multiply(POINT sp,POINT ep,POINT op) 

return((sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y)); 


/******************************************************************************* 
r=dotmultiply(p1,p2,op),得到矢量(p1-op)和(p2-op)的点积,如果两个矢量都非零矢量 
r<0:两矢量夹角为锐角;r=0:两矢量夹角为直角;r>0:两矢量夹角为钝角 
*******************************************************************************/ 
double dotmultiply(POINT p1,POINT p2,POINT p0) 

return ((p1.x-p0.x)*(p2.x-p0.x)+(p1.y-p0.y)*(p2.y-p0.y)); 


/* 判断点p是否在线段l上,条件:(p在线段l所在的直线上)&& (点p在以线段l为对角线的矩形内) */
bool online(LINESEG l,POINT p) 

return((multiply(l.e,p,l.s)==0) 
&&( ( (p.x-l.s.x)*(p.x-l.e.x)<=0 )&&( (p.y-l.s.y)*(p.y-l.e.y)<=0 ) ) ); 


// 返回点p以点o为圆心逆时针旋转alpha(单位:弧度)后所在的位置 
POINT rotate(POINT o,double alpha,POINT p) 

POINT tp; 
p.x-=o.x; 
p.y-=o.y; 
tp.x=p.x*cos(alpha)-p.y*sin(alpha)+o.x; 
tp.y=p.y*cos(alpha)+p.x*sin(alpha)+o.y; 
return tp; 


/* 返回顶角在o点,起始边为os,终止边为oe的夹角(单位:弧度) 
角度小于pi,返回正值 
角度大于pi,返回负值 
可以用于求线段之间的夹角 
*/ 
double angle(POINT o,POINT s,POINT e) 

double cosfi,fi,norm; 
double dsx = s.x - o.x; 
double dsy = s.y - o.y; 
double dex = e.x - o.x; 
double dey = e.y - o.y; 

cosfi=dsx*dex+dsy*dey; 
norm=(dsx*dsx+dey*dey)*(dex*dex+dey*dey); 
cosfi /= sqrt( norm ); 

if (cosfi >=      1.0 ) return 0; 
      if (cosfi <= -1.0 ) return -3.1415926; 

fi=acos(cosfi); 
if (dsx*dey-dsy*dex>0) return fi;          // 说明矢量os 在矢量 oe的顺时针方向 
      return -fi; 



       /*****************************\ 
      *                                 * 
      *          线段及直线的基本运算       * 
      *                                 * 
      \*****************************/ 

/* 判断点与线段的关系,用途很广泛 
本函数是根据下面的公式写的,P是点C到线段AB所在直线的垂足 

                    AC dot AB 
            r =         --------- 
                     ||AB||^2 
                 (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay) 
              = ------------------------------- 
                              L^2 

        r has the following meaning: 

            r=0          P = A 
            r=1          P = B 
            r<0 P is on the backward extension of AB 
r>1          P is on the forward extension of AB 
            0<r<1 P is interior to AB 
*/ 
double relation(POINT p,LINESEG l) 

LINESEG tl; 
tl.s=l.s; 
tl.e=p; 
return dotmultiply(tl.e,l.e,l.s)/(dist(l.s,l.e)*dist(l.s,l.e)); 


// 求点C到线段AB所在直线的垂足 P 
POINT perpendicular(POINT p,LINESEG l) 

double r=relation(p,l); 
POINT tp; 
tp.x=l.s.x+r*(l.e.x-l.s.x); 
tp.y=l.s.y+r*(l.e.y-l.s.y); 
return tp; 

/* 求点p到线段l的最短距离,并返回线段上距该点最近的点np 
注意:np是线段l上到点p最近的点,不一定是垂足 */ 
double ptolinesegdist(POINT p,LINESEG l,POINT &np) 

double r=relation(p,l); 
if(r<0) 

np=l.s; 
return dist(p,l.s); 

if(r>1) 

np=l.e; 
return dist(p,l.e); 

np=perpendicular(p,l); 
return dist(p,np); 


// 求点p到线段l所在直线的距离,请注意本函数与上个函数的区别  
double ptoldist(POINT p,LINESEG l) 

return abs(multiply(p,l.e,l.s))/dist(l.s,l.e); 


/* 计算点到折线集的最近距离,并返回最近点. 
注意:调用的是ptolineseg()函数 */ 
double ptopointset(int vcount,POINT pointset[],POINT p,POINT &q) 

int i; 
double cd=double(INF),td; 
LINESEG l; 
POINT tq,cq; 

for(i=0;i<vcount-1;i++) 

l.s=pointset[i]; 

l.e=pointset[i+1]; 
td=ptolinesegdist(p,l,tq); 
if(td<cd) 

cd=td; 
cq=tq; 


q=cq; 
return cd; 

/* 判断圆是否在多边形内.ptolineseg()函数的应用2 */ 
bool CircleInsidePolygon(int vcount,POINT center,double radius,POINT polygon[]) 

POINT q; 
double d; 
q.x=0; 
q.y=0; 
d=ptopointset(vcount,polygon,center,q); 
if(d<radius||fabs(d-radius)<EP) 
return true; 
else 
return false; 


/* 返回两个矢量l1和l2的夹角的余弦(-1 --- 1)注意:如果想从余弦求夹角的话,注意反余弦函数的定义域是从 0到pi */ 
double cosine(LINESEG l1,LINESEG l2) 

return (((l1.e.x-l1.s.x)*(l2.e.x-l2.s.x) + 
(l1.e.y-l1.s.y)*(l2.e.y-l2.s.y))/(dist(l1.e,l1.s)*dist(l2.e,l2.s))) ); 

// 返回线段l1与l2之间的夹角 单位:弧度 范围(-pi,pi) 
double lsangle(LINESEG l1,LINESEG l2) 

POINT o,s,e; 
o.x=o.y=0; 
s.x=l1.e.x-l1.s.x; 
s.y=l1.e.y-l1.s.y; 
e.x=l2.e.x-l2.s.x; 
e.y=l2.e.y-l2.s.y; 
return angle(o,s,e); 

// 如果线段u和v相交(包括相交在端点处)时,返回true 
bool intersect(LINESEG u,LINESEG v) 

return( (max(u.s.x,u.e.x)>=min(v.s.x,v.e.x))&&                         //排斥实验 
            (max(v.s.x,v.e.x)>=min(u.s.x,u.e.x))&& 
            (max(u.s.y,u.e.y)>=min(v.s.y,v.e.y))&& 
            (max(v.s.y,v.e.y)>=min(u.s.y,u.e.y))&& 
            (multiply(v.s,u.e,u.s)*multiply(u.e,v.e,u.s)>=0)&&             //跨立实验
            (multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0)); 



//      (线段u和v相交)&&(交点不是双方的端点) 时返回true    
bool intersect_A(LINESEG u,LINESEG v) 

return((intersect(u,v))&& 
           (!online(u,v.s))&& 
           (!online(u,v.e))&& 
           (!online(v,u.e))&& 
           (!online(v,u.s))); 



// 线段v所在直线与线段u相交时返回true;方法:判断线段u是否跨立线段v  
bool intersect_l(LINESEG u,LINESEG v) 

return multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0; 



// 根据已知两点坐标,求过这两点的直线解析方程: a*x+b*y+c = 0      (a >= 0)  
LINE makeline(POINT p1,POINT p2) 

       LINE tl; 
       int sign = 1; 
       tl.a=p2.y-p1.y; 
       if(tl.a<0) 

sign = -1; 
tl.a=sign*tl.a; 

tl.b=sign*(p1.x-p2.x); 
tl.c=sign*(p1.y*p2.x-p1.x*p2.y); 
return tl; 


// 根据直线解析方程返回直线的斜率k,水平线返回 0,竖直线返回 1e200 
double slope(LINE l) 

if(abs(l.a) < 1e-20)return 0; 
if(abs(l.b) < 1e-20)return INF; 
return -(l.a/l.b); 


// 返回直线的倾斜角alpha ( 0 - pi) 
double alpha(LINE l) 

if(abs(l.a)< EP)return 0; 
if(abs(l.b)< EP)return PI/2; 
double k=slope(l); 
if(k>0) 
          return atan(k); 
       else 
          return PI+atan(k); 


// 求点p关于直线l的对称点  
POINT symmetry(LINE l,POINT p) 

       POINT tp; 
       tp.x=((l.b*l.b-l.a*l.a)*p.x-2*l.a*l.b*p.y-2*l.a*l.c)/(l.a*l.a+l.b*l.b); 
       tp.y=((l.a*l.a-l.b*l.b)*p.y-2*l.a*l.b*p.x-2*l.b*l.c)/(l.a*l.a+l.b*l.b); 
       return tp; 


// 如果两条直线 l1(a1*x+b1*y+c1 = 0), l2(a2*x+b2*y+c2 = 0)相交,返回true,且返回交点p  
bool lineintersect(LINE l1,LINE l2,POINT &p) // 是 L1,L2 

       double d=l1.a*l2.b-l2.a*l1.b; 
       if(abs(d)<EP) // 不相交 
return false; 
p.x = (l2.c*l1.b-l1.c*l2.b)/d; 
p.y = (l2.a*l1.c-l1.a*l2.c)/d; 
return true; 


// 如果线段l1和l2相交,返回true且交点由(inter)返回,否则返回false 
bool intersection(LINESEG l1,LINESEG l2,POINT &inter) 

LINE ll1,ll2; 
ll1=makeline(l1.s,l1.e); 
ll2=makeline(l2.s,l2.e); 
if(lineintersect(ll1,ll2,inter)) 

return online(l1,inter); 

else 
return false; 





/******************************\ 
* * 
* 多边形常用算法模块 * 
* * 
\******************************/ 

// 如果无特别说明,输入多边形顶点要求按逆时针排列 

/* 
返回值:输入的多边形是简单多边形,返回true 
要 求:输入顶点序列按逆时针排序 
说 明:简单多边形定义: 
1:循环排序中相邻线段对的交是他们之间共有的单个点 
2:不相邻的线段不相交 
本程序默认第一个条件已经满足 
*/ 
bool issimple(int vcount,POINT polygon[]) 

int i,cn; 
LINESEG l1,l2; 
for(i=0;i<vcount;i++) 

l1.s=polygon[i]; 
l1.e=polygon[(i+1)%vcount]; 
cn=vcount-3; 
while(cn) 

l2.s=polygon[(i+2)%vcount]; 
l2.e=polygon[(i+3)%vcount]; 
if(intersect(l1,l2)) 
break; 
cn--; 

if(cn) 
return false; 

return true; 


// 返回值:按输入顺序返回多边形顶点的凸凹性判断,bc[i]=1,iff:第i个顶点是凸顶点 
void checkconvex(int vcount,POINT polygon[],bool bc[]) 

int i,index=0; 
POINT tp=polygon[0]; 
for(i=1;i<vcount;i++) // 寻找第一个凸顶点 

if(polygon[i].y<tp.y||(polygon[i].y == tp.y&&polygon[i].x<tp.x)) 

tp=polygon[i]; 
index=i; 


int count=vcount-1; 
bc[index]=1; 
while(count) // 判断凸凹性 

if(multiply(polygon[(index+1)%vcount],polygon[(index+2)%vcount],polygon[index])>=0 )
                bc[(index+1)%vcount]=1; 
             else 
                bc[(index+1)%vcount]=0; 
             index++; 
             count--; 
          } 


// 返回值:多边形polygon是凸多边形时,返回true  
bool isconvex(int vcount,POINT polygon[]) 

bool bc[MAXV]; 
checkconvex(vcount,polygon,bc); 
for(int i=0;i<vcount;i++) // 逐一检查顶点,是否全部是凸顶点 
if(!bc[i]) 
return false; 
return true; 


// 返回多边形面积(signed);输入顶点按逆时针排列时,返回正值;否则返回负值 
double area_of_polygon(int vcount,POINT polygon[]) 

int i; 
double s; 
if (vcount<3) return 0; 
s=polygon[0].y*(polygon[vcount-1].x-polygon[1].x); 
for (i=1;i<vcount;i++) 
s+=polygon[i].y*(polygon[(i-1)].x-polygon[(i+1)%vcount].x); 
return s/2; 


// 如果输入顶点按逆时针排列,返回true 
bool isconterclock(int vcount,POINT polygon[]) 

return area_of_polygon(vcount,polygon)>0; 

// 另一种判断多边形顶点排列方向的方法  
bool isccwize(int vcount,POINT polygon[]) 

       int i,index; 
       POINT a,b,v; 
       v=polygon[0]; 
       index=0; 
       for(i=1;i<vcount;i++) // 找到最低且最左顶点,肯定是凸顶点 

if(polygon[i].y<v.y||polygon[i].y == v.y && polygon[i].x<v.x) 

index=i; 


a=polygon[(index-1+vcount)%vcount]; // 顶点v的前一顶点 
b=polygon[(index+1)%vcount]; // 顶点v的后一顶点 
return multiply(v,b,a)>0; 


/* 射线法判断点q与多边形polygon的位置关系,要求polygon为简单多边形,顶点逆时针排列 
       如果点在多边形内:       返回0 
       如果点在多边形边上: 返回1 
       如果点在多边形外: 返回2 
*/ 
int insidepolygon(int vcount,POINT Polygon[],POINT q) 

int c=0,i,n; 
LINESEG l1,l2; 
bool bintersect_a,bonline1,bonline2,bonline3; 
double r1,r2; 

l1.s=q; 
l1.e=q; 
l1.e.x=double(INF); 
n=vcount; 
for (i=0;i<vcount;i++) 

l2.s=Polygon[i]; 
l2.e=Polygon[(i+1)%n]; 
if(online(l2,q))return 1; // 如果点在边上,返回1 
if ( (bintersect_a=intersect_A(l1,l2))|| // 相交且不在端点 

(bonline1=online(l1,Polygon[(i+1)%n]))&& // 第二个端点在射线上 

(!(bonline2=online(l1,Polygon[(i+2)%n])))&& /* 前一个端点和后一个 
端点在射线两侧 */ 
((r1=multiply(Polygon[i],Polygon[(i+1)%n],l1.s)*multiply(Polygon[(i+1)%n],Polygon[(i+2)%n],l1.s))>0) ||    
(bonline3=online(l1,Polygon[(i+2)%n]))&&         /* 下一条边是水平线, 
前一个端点和后一个端点在射线两侧      */ 
          ((r2=multiply(Polygon[i],Polygon[(i+2)%n],l1.s)*multiply(Polygon[(i+2)%n],
Polygon[(i+3)%n],l1.s))>0) 
            ) 
          ) 
       ) c++; 

         if(c%2 == 1) 
            return 0; 
         else 
            return 2; 

// 点q是凸多边形polygon内时,返回true;注意:多边形polygon一定要是凸多边形  
bool InsideConvexPolygon(int vcount,POINT polygon[],POINT q) // 可用于三角形! 

POINT p; 
LINESEG l; 
int i; 
p.x=0;p.y=0; 
for(i=0;i<vcount;i++) // 寻找一个肯定在多边形polygon内的点p:多边形顶点平均值 

p.x+=polygon[i].x; 
p.y+=polygon[i].y; 

p.x /= vcount; 
p.y /= vcount; 

for(i=0;i<vcount;i++) 

l.s=polygon[i];l.e=polygon[(i+1)%vcount]; 
if(multiply(p,l.e,l.s)*multiply(q,l.e,l.s)<0) /* 点p和点q在边l的两侧,说明 
点q肯定在多边形外 */ 
break; 

return (i==vcount); 


/********************************************** 
寻找凸包的graham 扫描法 
PointSet为输入的点集; 
ch为输出的凸包上的点集,按照逆时针方向排列; 
n为PointSet中的点的数目 
len为输出的凸包上的点的个数 
**********************************************/ 

void Graham_scan(POINT PointSet[],POINT ch[],int n,int &len) 

int i,j,k=0,top=2; 
POINT tmp; 
// 选取PointSet中y坐标最小的点PointSet[k],如果这样的点有多个,则取最左边的一个 
for(i=1;i<n;i++) 
if ( PointSet[i].y<PointSet[k].y || 
(PointSet[i].y==PointSet[k].y) && (PointSet[i].x<PointSet[k].x) ) 
k=i; 
tmp=PointSet[0]; 
PointSet[0]=PointSet[k]; 
PointSet[k]=tmp; // 现在PointSet中y坐标最小的点在PointSet[0] 
for (i=1;i<n-1;i++) /* 对顶点按照相对PointSet[0]的极角从小到大进行排序,极角相同 
的按照距离PointSet[0]从近到远进行排序 */ 

k=i; 
for (j=i+1;j<n;j++) 
if ( multiply(PointSet[j],PointSet[k],PointSet[0])>0 ||      // 极角更小    
          (multiply(PointSet[j],PointSet[k],PointSet[0])==0) && /* 极角相等, 
距离更短 */             dist(PointSet[0],PointSet[j])<dist(PointSet[0],PointSet[k]) )
k=j; 
tmp=PointSet[i]; 
PointSet[i]=PointSet[k]; 
PointSet[k]=tmp; 

ch[0]=PointSet[0]; 
ch[1]=PointSet[1]; 
ch[2]=PointSet[2]; 
for (i=3;i<n;i++) 

while (multiply(PointSet[i],ch[top],ch[top-1])>=0) top--; 
ch[++top]=PointSet[i]; 

len=top+1; 


// 卷包裹法求点集凸壳,参数说明同graham算法    
void ConvexClosure(POINT PointSet[],POINT ch[],int n,int &len) 

int top=0,i,index,first; 
double curmax,curcos,curdis; 
POINT tmp; 
LINESEG l1,l2; 
bool use[MAXV]; 
tmp=PointSet[0]; 
index=0; 
// 选取y最小点,如果多于一个,则选取最左点 
for(i=1;i<n;i++) 

if(PointSet[i].y<tmp.y||PointSet[i].y == tmp.y&&PointSet[i].x<tmp.x) 

index=i; 

use[i]=false; 

tmp=PointSet[index]; 
first=index; 
use[index]=true; 

index=-1; 
ch[top++]=tmp; 
tmp.x-=100; 
l1.s=tmp; 
l1.e=ch[0]; 
l2.s=ch[0]; 

while(index!=first) 

curmax=-100; 
curdis=0; 
// 选取与最后一条确定边夹角最小的点,即余弦值最大者 
for(i=0;i<n;i++) 

if(use[i])continue; 
l2.e=PointSet[i]; 
curcos=cosine(l1,l2); // 根据cos值求夹角余弦,范围在 (-1 -- 1 ) 
if(curcos>curmax || fabs(curcos-curmax)<1e-6 && dist(l2.s,l2.e)>curdis) 

curmax=curcos; 
index=i; 
curdis=dist(l2.s,l2.e); 



use[first]=false;                //清空第first个顶点标志,使最后能形成封闭的hull 
use[index]=true; 
ch[top++]=PointSet[index]; 
l1.s=ch[top-2]; 
l1.e=ch[top-1]; 
l2.s=ch[top-1]; 

len=top-1; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
扫描线算法是一种常用的计算机图形学算法,可以用于处理各种形状的图形。它的基本思想是将图形划分为一系列水平线段,然后按照线段的位置进行排序,依次处理每条线段,从而得到图形的相关信息。 在 Python 中实现扫描线算法,通常需要用到一些图形学库,比如 PyOpenGL、Pygame 等。下是一个简单的示例代码,用 Pygame 实现了一个简单的矩形扫描线填充算法: ```python import pygame # 定义一个矩形类 class Rectangle: def __init__(self, x, y, w, h): self.x = x self.y = y self.w = w self.h = h # 定义一个扫描线填充函数 def scanline_fill(screen, rect, color): # 将矩形的四个顶按 y 坐标排序 vertices = sorted([(rect.x, rect.y), (rect.x + rect.w, rect.y), (rect.x + rect.w, rect.y + rect.h), (rect.x, rect.y + rect.h)], key=lambda v: v[1]) # 初始化活化边表和扫描线 aet = [] y = vertices[0][1] # 从上到下扫描每条线段 for i in range(len(vertices) - 1): # 如果当前扫描线线段相交,则将线段添加到活化边表中 if vertices[i][1] == y: aet.append((vertices[i][0], vertices[i + 1][0])) # 如果当前扫描线线段不相交,则将线段从活化边表中删除 if vertices[i + 1][1] == y: aet = [edge for edge in aet if edge != (vertices[i][0], vertices[i + 1][0])] # 对活化边表进行排序 aet = sorted(aet) # 从左到右扫描每个像素 for x in range(aet[0][0], aet[-1][1] + 1): screen.set_at((x, y), color) # 更新扫描线的位置 y = vertices[i + 1][1] # 初始化 Pygame pygame.init() # 创建一个 640x480 的窗口 screen = pygame.display.set_mode((640, 480)) # 填充窗口背景为白色 screen.fill((255, 255, 255)) # 定义一个红色矩形 rect = Rectangle(100, 100, 200, 150) # 使用扫描线算法填充矩形 scanline_fill(screen, rect, (255, 0, 0)) # 更新窗口显示 pygame.display.flip() # 等待用户关闭窗口 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() ``` 上的代码中,我们先定义了一个矩形类,然后实现了一个扫描线填充函数。在函数中,我们将矩形的四个顶按 y 坐标排序,然后从上到下扫描每条线段,将线段添加到活化边表中,从左到右扫描每个像素,并将颜色设置为指定颜色。最后,我们使用 Pygame 创建了一个窗口,填充了一个红色矩形,然后等待用户关闭窗口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值