• 向量:简单地说,向量就是有大小和方向的量,如速度、位移等物理量都是向量。
    • 向量加法:满足平行四边形法则
    • 点-点=向量,向量+向量=向量,点+向量=点
    • 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){//重载'<',先排x后排y,亦可以看作以极角排序 
          return a.x < b.x || (a.x==b.x && a.y<b.y);
      }  
      
      const double eps = 1e-10;
      int dcmp(double x){
          if(fabs(x) < eps) 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;
      }
      
    • 上面相等中用了“三态函数”dcmp,减少了精度问题
    • 另外,向量中有一个所谓的极角,即从x轴正半轴旋转到改向量方向所需要的角度,C语言中的atan2函数就是用来求极角的
    • 如向量(x,y)的极角就是atan2(y,x)(单位:弧度)
  • 基本运算
    • 点积:a * b = | a | * | b | * cos(a,b) //cos是偶函数,所以角度的方向关系不大(逆时针转角)
      1. 两向量垂直(cos = 0),a * b = 0
      2. 平面坐标系下,点积等于XaXb + YaYb
      3. 代码
        double Dot(Vector A, Vector B) { return A.x*B.x + A.y*B.y; }//平面直角坐标系上向量点积 
        double Length(Vector A) {return sqrt(Dot(A,A));} //x方加y方的和的算术平方根为向量的模长 
        double Angle(Vector A, Vector B){ return acos(Dot(A,B) / Length(A) / Length(B)); } //点积除以模长的积等于cos(a,b); 
    • 叉积:两个向量v和w的叉积等于v和w组成的三角形的有向面积的两倍
      1. b在a左边时,叉积大于0;在右边时小于0(absin(a,b) 逆时针)
      2. 平面直角坐标系上,叉积等于XaYb – XbYa
      3. 代码
        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’ = xcosa – ysina, y’ = xsina + ycosa;
      a的单位为rad,2Πrad = 360°

      • 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度后长度归一化
      • Vector Normal(Vector A){ //确保不是零向量方可使用 
            double L = Length(A);
            return Vector(-A.y/L, A.x/L);
        }
  • 点和直线
    • 直线可用直线上一点P。与方向向量v表示,直线上的所有点可以表示为P=P。+tv,其中t称为参数。
    • 已知直线上两个不同的点A,B,则方向向量表示为B-A,参数方程表示为A+(B-A)t。(直线的t无范围限制,射线t>0,线段t属于[0,1])
    • 设直线分别为P+tv与Q+tw,交点在第一条直线的参数为t1,第二条上为t2,则可得
      • t1=cross(w,u)/cross(v,w)
      • t2=cross(v,u)/cross(v,w)
    • 两直线的交点:使用前保证两条直线有唯一交点(当且仅当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;
        }
        
    • 点到直线的距离:点到直线的距离即为平行四边形面积除以底的商(外积除以AB模长的积的绝对值)
    • double Distancetoline(Point P, Point A, Point B){
          Vector v1 = B - A, v2 = P - A; //得向量AB,向量PA
          return fabs(Cross(v1, v2)) / Length(v1); 
      }
    • 点到线段的距离:从程序设计的角度来说,点到线段分为三种情况(其中第一二种实际上属于同一种投影不在线段AB上的情况)
      1. 如图,向量AB与AP的点积小于0时,距离为AP的模
      2. 如图,向量AB与BP的点积大于0时,距离为BP的模
      3. 如图,向量AB在线段正上方时,不满足以上两个条件,距离即为点到直线的距离
      4. 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));
        }
    • 点在直线AB上的投影:
      • 将AB写成参数式A+tv(v为向量AB),设Q的参数为t。, 那么Q=A+vt。,由点积为0可得Dot(v,p-(A+vt。)) = 0,这样就可以解出t。
      • Point getlineprojection(Point P, Point A, Point B){
            Vector v = B-A;
            return A+v*(Dot(v,P-A) / Dot(v,v));
        }
    • 线段相交判定:
      • 规范相交:两线段恰好有一个公共点,且不在任何一条线段的端点
      • 规范相交的充要条件是:每条线段的两个端点都在另一条线段的两侧(这里的“两侧”是指叉积的符号不同)
      • 可以用线段的两点式求出线性规划,代入另外两点
        但鉴于代码风格的统一性,这里还是使用书上使用向量外积进行判断的代码,两种方法本质上是一样的,都是分别以一条边为标准(所以需要判断两次),判断另外两个点是否在该边的两侧。
      • bool segmentpropersection(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;
        }
        
      • 如果允许出现在端点处呢?那就需要再进一步的讨论:
        如果c1,c2都等于0,则两线段共线,有可能部分重叠;若不都为0,则必然只有一种相交情况,即某个端点再另一条线段上,用如下代码进行判断(不含端点,因为端点重合的话可以直接看出来…):
      • bool onsegment(Point P, Point a1, Point a2){
            return dcmp(Cross(a1-P, a2-P)) == 0 && dcmp(Dot(a1-P, a2-P)) < 0;
            //第一个条件用来判断点在直线上,第二个条件用来判断点在被两条垂足为AB端点的直线围成的区域内
            //合在一起用来判断点端在线段上 
        }
        
    • 多边形

发表评论

邮箱地址不会被公开。 必填项已用*标注