计算几何(白书)

二维几何基础

#include
using namespace std;
const int MAXN = 1e5+5;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const double PI = acos(-1.0);
/**
atan2(y,x)返回反正切值,[-PI,PI]弧度,求极角
atan(x)返回反正切值,[-PI/2,PI/2]弧度
**/

struct Point{
    double x,y;
    Point(double _x=0,double _y=0):x(_x),y(_y){}
};

typedef Point Vector;

//向量+向量=向量,点+向量=点
Vector operator + (Vector A,Vector B){return Vector(A.x+B.x,A.y+B.y);}
//点-点=向量
Vector operator - (Point A,Point B){return Vector(A.x-B.x,A.y-B.y);}
//向量*数=向量
Vector operator * (Vector A,double p) {return Vector(A.x*p,A.y*p);}
//向量/数=向量
Vector operator / (Vector A,double p) {return Vector(A.x/p,A.y/p);}

bool operator < (const Point& a,const Point& b){
    return a.xint dcmp(double x){
    if(fabs(x)return 0;
    else return x<0?-1:1;
}

bool operator == (const Point& a,const Point& b){
    return dcmp(a.x-b.x)==0 && dcmp(a.y-b.y)==0;
}

int main(){

    return 0;
}

基本运算

//点积,v·w=|v||w|cos(sita),满足交换律
double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y; }
double Length(Vector A) { return sqrt(Dot(A,A)); }
double Angle(Vector A,Vector B) { return acos(Dot(A,B)/Length(A)/Length(B)); }

//叉积,组成三角形的有向面积的两倍,不满足交换律,cross(v,w)=-cross(w,v)
double Cross(Vector A,Vector B) { return A.x*B.y-A.y*B.x; }
double Area2(Point A,Point B,Point C) { return Cross(B-A,C-A); }

//两向量的位置关系
//假设第一个向量水平向右
//则第二个向量的情况为:(点积符号,叉积符号)
//x轴水平向右:(+,0)
//第一象限:(+,+)
//y轴竖直向上:(0,+)
//第二象限:(-,+)
//x轴水平向左:(-,0)
//第三象限:(-,-)
//y轴竖直向下:(0,-)
//第四象限:(+,-)

//向量旋转,绕起点
//x'=xcosa-ysina,
//y'=xsina+ycosa
//a为逆时针旋转的角
Vector Rotate(Vector A,double rad){
    return Vector(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}

//计算向量的单位法线,即左转90°,之后把长度归一化
//调用前确保A不是零向量
Vector Normal(Vector A){
    double L = Length(A);
    return Vector(-A.y/L,A.x/L);
}

点和直线

//有向直线。它的左边就是对应的平面
struct Line{
    Point p;
    Vector v;
    double ang;
    Line(){}
    Line(Point p,Vector v):p(p),v(v){ang=atan2(v.y,v.x);}
    Point point(double t) {return p+v*t;}
    bool operator < (const Line& L) const{//排序用的比较运算符
        return ang < L.ang;
    }
};

//直线交点
//使用前确保两条直线P+tv和Q+tw有唯一交点
//当且仅当Cross(v,w)非0

Point GetLineIntersection(Point P,Vector v,Point Q,Vector w){
    Vector u = P-Q;
    double t = Cross(w,u)/Cross(v,w);
    return P+v*t;
}

//点到线段的距离,判断投影点是否在线段上
double DistanceToSegment(Point P,Point A,Point B){
    if(A==B) return Length(P-A);
    Vector v1=B-A,v2=P-A,v3=P-B;
    if(dcmp(Dot(v1,v2))<0) return Length(v2);
    else if(dcmp(Dot(v1,v3))>0) return Length(v3);
    else return fabs(Cross(v1,v2))/Length(v1);
}

//点在直线上的投影
Point GetLineProjection(Point P,Point A,Point B){
    Vector v=B-A;
    return A+v*(Dot(v,P-A)/Dot(v,v));
}

// 线段相交判定
// 规范相交:两线段恰好有一个公共点,且不在任何一条线段的端点
bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2){
    double c1 = Cross(a2-a1,b1-a1),c2 = Cross(a2-a1,b2-a1),
           c3 = Cross(b2-b1,a1-b1),c4 = Cross(b2-b1,a2-b1);
    return dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0;
}

//判断一个点是否在一条线段上(不包含端点)
bool OnSegment(Point p,Point a1,Point a2){
    return dcmp(Cross(a1-p,a2-p))==0 && dcmp(Dot(a1-p,a2-p))<0 ;
}

多边形

//计算多边形的有向面积

double ConvexPolygonArea(Point *p,int n){
    double area = 0;
    for(int i=1;i1;++i){
        area += Cross(p[i]-p[0],p[i+1]-p[0]);
    }
    return area/2; 
}

//多边形的有向面积
double PolygonArea(Point *p,int n){
    double area = 0;
    for(int i=1;i1;++i){
        area+=Cross(p[i]-p[0],p[i+1]-p[0]);
    }
    return area/2;
}

与圆和球有关的计算问题

struct Circle{
    Point c;
    double r;
    Circle(Point c,double r):c(c),r(r){}
    Point point(double a){//通过圆心角求坐标
        return Point(c.x+cos(a)*r,c.y+sin(a)*r);
    }
};

//直线与圆的交点
//设交点P=A+t(B-A),代入圆方程整理的(at+b)^2+(ct+d)^2=r^2
//进一步整理得 et^2+ft+g=0,根据判别式的值判断
//函数返回的是交点的个数,参数sol存放的是交点本身。
//上述代码并没有清空sol,可以反复调用这个函数,把所有交点放在一个sol里

int getLineCircleIntersection(Line L,Circle C,double& t1,double& t2,vector& sol){
    double a = L.v.x, b = L.p.x - C.c.x, c = L.v.y, d = L.p.y - C.c.y;
    double e = a*a + c*c, f = 2*(a*b + c*d), g = b*b + d*d -C.r*C.r;
    double delta = f*f - 4*e*g; //判别式
    if(dcmp(delta) < 0) return 0; //相离
    if(dcmp(delta) == 0){ //相切
        t1 = t2 = -f /(2*e);
        sol.push_back(L.point(t1));
        return 1;
    }
    //相交
    t1 = (-f - sqrt(delta)) / (2*e);
    sol.push_back(L.point(t1));
    t2 = (-f + sqrt(delta)) / (2*e);
    sol.push_back(L.point(t2));
    return 2;
}

//计算向量极角
double angle(Vector v) {return atan2(v.y,v.x);}

//两圆相交

double getCircleCircleIntersection(Circle C1,Circle C2,vector& sol){
    double d = Length(C1.c - C2.c);
    if(dcmp(d) == 0){
        if(dcmp(C1.r-C2.r)==0) return -1;//两圆重合
        return 0;
    }
    if(dcmp(C1.r+C2.r-d)<0) return 0;
    if(dcmp(fabs(C1.r-C2.r)-d)>0) return 0;

    double a = angle(C2.c - C1.c); //向量C1C2的极角
    double da = acos((C1.r*C1.r+d*d-C2.r*C2.r)/(2*C1.r*d));
    //C1C2到C2P1的角
    Point p1 = C1.point(a-da), p2 = C1.point(a+da);

    sol.push_back(p1);
    if(p1 == p2) return 1;
    sol.push_back(p2);
    return 2;
}


//过定点作圆的切线
//过点p到圆C的切线,v[i]是第i条切线的向量,返回切线条数
int getTangents(Point p,Circle C,Vector* v){
    Vector u = C.c - p;
    double dist = Length(u);
    if(dist < C.r) return 0;
    else if(dcmp(dist - C.r) == 0){//p在圆上,只有一条切线
        v[0]=Rotate(u,PI/2);
        return 1;
    }else{
        double ang = asin(C.r / dist);
        v[0] = Rotate(u,-ang);
        v[1] = Rotate(u,+ang);
        return 2;
    }
}

/**
两圆的公切线。根据两圆的圆心距从小到大排列,
一共有6种情况:
情况一:两圆完全重合,有无数条公切线
情况二:两圆内含,没有公共点,没有公切线
情况三:两圆内切,有1条外公切线
情况四:两圆相交,有2条外公切线
情况五:两圆外切,有3条公切线,1条内公切线,2条外公切线
情况六:两圆相离,有4条公切线,其中内公切线2条,外公切线2条

情况三和五:连接圆心和切点,旋转90度
**/

//返回切线的条数。-1表示无穷条切线
//a[i]和b[i]分别是第i条切线在圆A和圆B上的切点
int getTangents(Circle A,Circle B,Point *a,Point *b){
    int cnt = 0;
    if(A.r < B.r) {swap(A,B); swap(a,b); }
    double d2 = (A.c.x-B.c.x)*(A.c.x-B.c.x) + (A.c.y-B.c.y)*(A.c.y-B.c.y);
    int rdiff = A.r-B.r;
    int rsum = A.r+B.r;
    if(d2 < rdiff*rdiff) return 0; //内含

    double base = atan2(B.c.y-A.c.y,B.c.x-A.c.x);
    if(d2 == 0 && A.r == B.r) return -1; //无限多条切线
    if(d2 == rdiff*rdiff){ //内切,1条切线
        a[cnt] = A.point(base);
        b[cnt] = B.point(base);
        cnt++;
        return 1;
    }

    //有外公切线
    double ang = acos((A.r-B.r) / sqrt(d2));
    a[cnt] = A.point(base+ang);
    b[cnt] = B.point(base+ang);
    cnt++;
    a[cnt] = A.point(base-ang);
    b[cnt] = B.point(base-ang);
    cnt++;
    if(d2 == rsum*rsum){ //一条公切线
        a[cnt] = A.point(base);
        b[cnt] = B.point(PI+base);
        cnt++;
    }else if(d2 > rsum*rsum){ //两条公切线
        double ang = acos((A.r+B.r) / sqrt(2));
        a[cnt] = A.point(base+ang);
        b[cnt] = B.point(PI+base+ang);
        cnt++;
        a[cnt] = A.point(base-ang);
        b[cnt] = B.point(PI+base-ang);
        cnt++;
    }
    return cnt;
}

//球面相关问题

//角度转换成弧度
double torad(double deg){
    return deg/180 * acos(-1); 
}

//经纬度(角度)转化为空间坐标
//经度 longitude 纬度 latitude
void get_coord(double R,double lat,double lng,double& x,double& y,double& z){
    lat = torad(lat);
    lng = torad(lng);
    x = R*cos(lat)*cos(lng);
    y = R*cos(lat)*sin(lng);
    z = R*sin(lat);
}

二维几何常用算法

typedef vector Polygon;

int isPointInPolygon(Point p,Polygon poly){
    int wn = 0;
    int n = poly.size();
    for(int i=0;iif(OnSegment(p,poly[i],poly[(i+1)%n])) return -1; //在边界上
        int k = dcmp(Cross(poly[(i+1)%n]-poly[i],p-poly[i]));
        int d1 = dcmp(poly[i].y - p.y);
        int d2 = dcmp(poly[(i+1)%n].y-p.y);
        if(k>0 && d1<=0 && d2>0) wn++; //绕数 winding number
        if(k<0 && d2<=0 && d1>0) wn--;
    }
    if(wn!=0) return 1; //内部
    return 0; //外部
}

//点在凸多边形内的判定更简单,
//只需判断是否在所有边的左边
//假设各点按照逆时针顺序排列

//凸包
//基于水平序的Andrew算法
//首先把所有点按照x从小到大排序
//(如果x相同,按照y从小到大排序)
//删除重复点后得到序列p1,p2,...
//时间复杂度O(nlogn)

//计算凸包,输入点数组p,个数为n,输出点数组ch.
//函数返回凸包顶点数
//输入不能有重复点。函数执行完之后输入点的顺序被破坏
//如果不希望在凸包的边上有输入点,把两个<=改成<
//在精度要求高时建议用dcmp比较

int ConvexHull(Point* p,int n,Point* ch){
    sort(p,p+n); //先比较x坐标,再比较y坐标
    int m = 0;
    for(int i=0;iwhile(m>i&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
        ch[m++] = p[i];
    }
    int k=m;
    for(int i=n-2;i>=0;i--){
        while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
        ch[m++]=p[i];
    }
    if(n>1) m--;
    return m;
}

//半平面交

//点p在有向直线L的左边(线上不算)
bool OnLeft(Line L,Point p){
    return Cross(L.v,p-L.p) > 0;
}

//两直线交点。假定交点唯一存在

Point GetIntersection(Line a,Line b){
    Vector u = a.p-b.p;
    double t = Cross(b.v,u) / Cross(a.v,b.v);
    return a.p+a.v*t;
}

//半平面交的主过程
int HalfplaneIntersection(Line* L,int n,Point* poly){
    sort(L,L+n); //按极角排序

    int first,last; //双端队列的第一个元素和最后一个元素的下标
    Point *p = new Point[n]; //p[i]为q[i]q[i+1]的交点
    Line *q = new Line[n]; //双端队列
    q[first=last=0]=L[0]; //双端队列初始化为只有一个半平面L[0]
    for(int i=1;iwhile(first<last&&!OnLeft(L[i],p[last-1])) last--;
        while(first<last&&!OnLeft(L[i],p[first])) first++;
        q[++last] = L[i];
        if(fabs(Cross(q[last].v,q[last-1].v))//两向量平行且同向,取内侧的一个
            last--;
            if(OnLeft(q[last],L[i].p)) q[last]=L[i];
        }
        if(first<last) p[last-1] = GetIntersection(q[last-1],q[last]);
    }
    while(first<last&&!OnLeft(q[first],p[last-1])) last--;
    //删除无用平面(*)
    if(last-first<=1) return 0; //空集(**)
    p[last] = GetIntersection(q[last],q[first]); //计算首尾两个半平面的交点

    //从deque复制到输出中
    int m = 0;
    for(int i=first;i<=last;i++) poly[m++] = p[i];
    return m;

}

你可能感兴趣的:(#,计算几何)