admin 发表于 2024-3-16 09:12:46

凸包多边形最小外切矩形算法

暴力算法
遍历每一条边构造包围矩形比较面积大小。就是构造包围矩形,取到投影点到边以及垂直边上取距离最远两点距离即可

/* min value */
#define FLT采用MIN 1.175494351e-38F
/* max value */
#define FLT采用MAX 3.402823466e+38F

struct OBB {
    Point u; //x, y轴
    Point c; //中心点
    float e; //半长,半宽
};

float MinAreaRec(Point *pts, int ptsNum, OBB &obb) {

    float minArea = FLT采用MAX;

    for(int i = 0, j = ptsNum - 1; i < ptsNum; j = i, i++) {//遍历边
      Point u0 = pts - pts; //构造边
      u0 = u0/Length(u0);
      Point u1 = Point(0-u0.y, u0.x); //与u0垂直
      float min0 = 0.0f, max0 = 0.0f, min1 = 0.0f, max1 = 0.0f;

      for(int k = 0; k < ptsNum; k++) {//遍历点
            Point d = pts - pts;
            //投影在u0
            float dot = Dot(d, u0);
            if(dot < min0) min0 = dot;
            if(dot > max0) max0 = dot;
            //投影在u1
            dot = Dot(d, u1);
            if(dot < min1) min1 = dot;
            if(dot > max1) max1 = dot;
      }

      float area = (max0 - min0) * (max1 - min1);
      if( area < minArea ) {
            minArea = area;
            obb.c = pts + ( u0 * (max0 + min0) + u1 * (max1 + min1) )*0.5f;
            obb.u = u0;
            obb.u = u1;
            obb.e = (max0 - min0)*0.5f;
            obb.e = (max1 - min1)*0.5f;
      }
    }

    return minArea;

}

Point * GetOBBPoints(OBB obb) {//获取OBB四个顶点坐标
    Point *pts = new Point ;
    pts = obb.c + ( obb.u * obb.e + obb.u * obb.e );
    pts = obb.c + ( obb.u * obb.e - obb.u * obb.e );
    pts = obb.c - ( obb.u * obb.e + obb.u * obb.e );
    pts = obb.c + ( obb.u * obb.e - obb.u * obb.e );
    return pts;

}









旋转卡尺(旋转卡壳)算法
使用旋转卡尺算法可将计算凸多边形的最小包围矩形的时间消耗减少很多..

取坐标上两极值点构成平行线,旋转两线,当线与多边形一条边重合时,计算构成矩形面积。

继续旋转,直至旋转角度超过 90 度。取最小面积。

该算法仅对凸体有效(暴力法对凸体凹体均有效),因此需要先计算凸体,该算法的时间复杂度受限于凸体的计算过程

float Cos(Point v, Point p1) {
    float dot = Dot(v, p1);
    float cos = dot/(Length(v)*Length(p1));
    return cos;
}

void Setu0u1(Point e, Point &u0, Point &u1) {
    //以e方向为x轴方向,设定xy轴
    u0 = e / Length(e);
    u1 = Point( 0 - u0.y, u0.x);
}

int GetMinAngleIndex(int imin1, int imax0, int imax1, int imin0,
                     Point* e, Point u0, Point u1) {
    //返回旋转角度最小(cos值最大)的点的下标
    int imin采用angle采用index = 0;

    float cos = 0, maxCos = FLT采用MIN;
    cos = Cos(e, u0);
    if(cos > maxCos){maxCos = cos; imin采用angle采用index = imin1; }

    cos = Cos(e, u1);
    if(cos > maxCos){maxCos = cos; imin采用angle采用index = imax0; }
    cos = Cos(e, Point(0-u0.x, 0-u0.y));
    if(cos > maxCos){maxCos = cos; imin采用angle采用index = imax1; }
    cos = Cos(e, Point(0-u1.x, 0-u1.y));
    if(cos > maxCos){maxCos = cos; imin采用angle采用index = imin0; }
    return imin采用angle采用index;

}

void SetMinMax(Point*pts, int i, int iu, Point u0, Point u1,
               float &max0, float &min0, float &max1,
               int & new采用imax0, int &new采用imin0, int &new采用imax1)

{
    //找到x轴投影最大最小,y轴投影最大的长度(y轴最小则是重合边上点,长度为0)
    //以及极值点在pts中的下标
    Point d =pts - pts;
    float dist0 = Dot( d, u0);
    if(dist0 > max0){ max0 = dist0; new采用imax0 = i; }
    if(dist0 < min0){ min0 = dist0; new采用imin0 = i; }

    float dist1 = Dot( d, u1);
    if(dist1 > max1){ max1 = dist1; new采用imax1 = i; }
}

float MinAreaRec2(Point *pts, int ptsNum, OBB &obb) {//旋转卡壳算法
    //必须是凸包
    float minArea = FLT采用MAX;
    //初始化边e
    Point *e = new Point[ ptsNum ];
    for(int i = 0; i < ptsNum; i++) {
      e = pts[(i+1)%ptsNum] - pts;
    }
    int iu = 0; //以e为重合边
    //初始化u0 u1
    Point u0, u1;
    Setu0u1(e, u0, u1);

    int imax0 = 0, imax1 = 0, imin0 = 0, imin1 = 0;
    float min0 = FLT采用MAX, max0 = FLT采用MIN,
          max1 = FLT采用MIN, min1 = 0; //min1其实可以不需要设定的,始终为0
                                    //只是为了理解方便加上
                                    //要去掉则需要把下方用到的min1都改为0

    //求三个极值坐标
    for( int i = 0; i < ptsNum; i++) {
      SetMinMax(pts, i, iu, u0, u1,
               max0, min0, max1,
               imax0, imin0, imax1) ;
    }

    for(int i = 0; i < ptsNum ; i++) {
      int iminangle = 0;
      iminangle = GetMinAngleIndex((iu+1)%ptsNum, imax0, imax1, imin0, e, u0, u1);
      if(iminangle == 0)break; //旋转回了初始点 没必要继续

      if(iminangle == imax0){imax0 = (iu + 1)%ptsNum; iu = iminangle; }
      else if(iminangle == imax1){imax1 = (iu + 1)%ptsNum; iu = iminangle; }
      else if(iminangle == imin0){imin0 = (iu + 1)%ptsNum; iu = iminangle; }
      else if(iminangle == (iu+1)%ptsNum){iu = (iu+1)%ptsNum; }

      Setu0u1(e, u0, u1); //重设u0u1

      //维护三个极值点
      int new采用imax0 = imax0, new采用imax1 = imax1, new采用imin0 = imin0;
      min0 =FLT采用MAX, max0 = FLT采用MIN, max1 = FLT采用MIN;

      //确定原先imax0在新坐标系中是什么极值
      SetMinMax(pts, imax0, iu, u0, u1,
               max0, min0, max1,
               new采用imax0, new采用imin0, new采用imax1) ;
      //确定原先imax1在新坐标系中是什么极值
      SetMinMax(pts, imax1, iu, u0, u1,
               max0, min0, max1,
               new采用imax0, new采用imin0, new采用imax1) ;
      //确定原先imin0在新坐标系中是什么极值
      SetMinMax(pts, imin0, iu, u0, u1,
               max0, min0, max1,
               new采用imax0, new采用imin0, new采用imax1) ;

      imax0 = new采用imax0;
      imax1 = new采用imax1;
      imin0 = new采用imin0;
      //维护完毕

      //求面积 设置obb
      float area = (max0 - min0)*(max1 - min1);
      if(area < minArea) {
            minArea = area;

            obb.e = (max0 - min0)*0.5f;
            obb.e = (max1 - min1)*0.5f;
            obb.u = u0;
            obb.u = u1;
            obb.c = pts + ( u0 * (max0 + min0) + u1 * (max1 + min1) )*0.5f;
      }
    }
    return minArea;
}
复制
拿到了四个点, 其他的就好说了, 从主体上裁切 出矩形, 然后平行到 90° 就 OK 了.
页: [1]
查看完整版本: 凸包多边形最小外切矩形算法