封闭曲线检测

封闭曲线检测

近期开发需求上有这么一个功能,用户自己通过画圈去查找物品。效果如下:

test.gif

那么问题来了,如何判断用户画的是一个封闭的曲线?

area.png

从图上可知

  1. 封闭曲线必然会存在至少一个交点,并且该交叉点是由尾部曲线回到起始点或穿过前面的曲线形成。
  2. 交叉点在X轴和y轴上与曲线上的点都应存在距离。(排除曲线由起点到终点,再由终点回到起点。这种情况存在交叉点但它是一条直线)
function getClosedCurvePoints(points){
  let intersectPArray = [];
  var isLine = true;
  for(var i=0; i<points.length; i++){
    let p = points[i];
    for(var j=points.length-1; j>=0; j--){
      if((j - i) < points.length / 2) continue;
      let eP = points[j];
      let xDis = p.x - eP.x;
      let yDis = p.y - eP.y;
      let dis = Math.sqrt(Math.pow(xDis,2) + Math.pow(yDis,2));

      if(Math.abs(xDis) >= 20 && Math.abs(yDis) >= 20){
        isLine = false;
      }

      if(dis <= this._maxDis){//dis=0才是真正有交叉点的情况,但是用户不一定每次都能画出完美存在交叉点的圈 这种情况下应给予一个合理的误差值
        // console.log("存在相交点",p);
        // console.log("i="+i+",j="+j);
        intersectPArray.push({
          p: p,
          dis: dis,
          leftIdx: i,
          rightIdx: j
        });
      }
    }
}

得到交叉点后,下一步则是获取封闭曲线上的点(存在多交叉点的情况不做分析)

area2.png

情况1: 可直接把所有的点都认为是封闭曲线上的点

情况2: 需去除交叉点下方的2条曲线的点

废话不多说,上代码i,j所在的区间就是封闭曲线上的点

function getClosedCurvePoints(points){
  let intersectPArray = [];
  var isLine = true;
  for(var i=0; i<points.length; i++){
    let p = points[i];
    for(var j=points.length-1; j>=0; j--){
      if((j - i) < points.length / 2) continue;
      let eP = points[j];
      let xDis = p.x - eP.x;
      let yDis = p.y - eP.y;
      let dis = Math.sqrt(Math.pow(xDis,2) + Math.pow(yDis,2));

      if(Math.abs(xDis) >= 20 && Math.abs(yDis) >= 20){
        isLine = false;
      }

      if(dis <= this._maxDis){//dis=0才是真正有交叉点的情况,但是用户不一定每次都能画出完美存在交叉点的圈 这种情况下应给予一个合理的误差值
        // console.log("存在相交点",p);
        // console.log("i="+i+",j="+j);
        intersectPArray.push({
          p: p,
          dis: dis,
          leftIdx: i,
          rightIdx: j
        });
      }
    }
  }

  if(isLine){
    console.log("绘制的是直线");
    return null;
  }
  if(intersectPArray.length == 0){
    console.log("不存在交叉点");
    return null;
  }

  intersectPArray.sort((a,b)=>{return a.dis - b.dis});
  let intersectP = intersectPArray[0];
  if(intersectP.leftIdx < intersectP.rightIdx){
    return points.slice(intersectP.leftIdx,intersectP.rightIdx + 1);
  }else{
    return points.slice(intersectP.rightIdx,intersectP.leftIdx + 1);
  }
}

获取到封闭曲线上的点后,我们可以通过这些点得到封闭曲线的外接矩形,通过判断物品是否在该矩形内判断用户是否圈中物品。

获取封闭曲线在X轴上最左、最右的点和Y轴上最下、最上的点所形成的矩形则为封闭曲线的外接矩形

QQ图片20220401164028.jpg
    function getBoundRect(){
      let minX = points[0].x;
      let maxX = points[0].x;
      let minY = points[0].y;
      let maxY = points[0].y;

      for(var i=1; i<points.length; i++){
        let p = points[i];
        if(p.x > maxX){
          maxX = p.x
        }
        if(p.x < minX){
          minX = p.x
        }
        if(p.y > maxY){
          maxY = p.y
        }
        if(p.y < minY){
          minY = p.y
        }
      }

      return {l:minX,t:minY,r:maxX,b:maxY};
    }

优化

由于判断物品是否在闭合曲线内是采用闭合曲线的外接矩形去判断的,那么如何减小框选区域误差值则可转换成如何计算闭合曲线的最小外接矩形,我们知道在旋转不同角度下得到的最大外接矩形的面积是不一样的,当闭合曲线与X轴或Y轴垂直时,此时的外接矩形应为最小外接矩形。如下图:

QQ截图20220402142727.png

由于闭合曲线是无规律的图形所以很难去计算它是否处于水平或垂直状态,但经过0-90度旋转后必然会出现水平或垂直状态,此时它的外接矩形面积最小。计算步骤如下:

  1. 计算重心(锚点)

  2. 以锚点为中心旋转0-90计算外接矩形面积,筛选出面积最小的外接矩形,并记录下当前旋转的角度θ

  3. 以锚点为中心反向旋转θ度,得到最小外接矩形

  function getBoundRect(){
    let anchorP = ShapeHepler.calcAnchorPoint(closedCurvePoints); //计算锚点, 锚点=(avgX,avgY)

    //计算0-90度下所有外接矩形,并获取面积最小的矩形,该矩形则为外接最小矩形
    let minBoundRectWrap = {}
    for(var i=0; i<90; i++){
      let rotatePS = [];
      let angle = i * Math.PI/180; //弧度制
      closedCurvePoints.forEach(p=>{
        var rP = ShapeHepler.rotatePoint(p, anchorP, angle);
        rotatePS.push(rP);
      });
      let boundRect = ShapeHepler.getBoundRect(rotatePS);
      let area = (boundRect.r - boundRect.l) * (boundRect.b - boundRect.t);

      if(!minBoundRectWrap.area || minBoundRectWrap.area > area){
        minBoundRectWrap = {
          rect: boundRect,
          angle: angle,
          anchorP: anchorP,
          area: (boundRect.r - boundRect.l) * (boundRect.b - boundRect.t)
        }
      }
    }

    //将外接最小矩形逆时针旋转回去
    let rotateMinRectPS = [];
    [
      {x: minBoundRectWrap.rect.l,y: minBoundRectWrap.rect.t},
      {x: minBoundRectWrap.rect.r,y: minBoundRectWrap.rect.b},
    ].forEach(p=>{
      let rP = ShapeHepler.rotatePoint(p, anchorP, -minBoundRectWrap.angle);
      rotateMinRectPS.push(rP);
    });

    return {
      l: rotateMinRectPS[0].x,
      t: rotateMinRectPS[0].y, 
      r: rotateMinRectPS[1].x, 
      b: rotateMinRectPS[1].y,
      angle: minBoundRectWrap.angle,
      anchorP: anchorP
    }
  }

注意:在判断点P是否在闭合曲线的外接矩形内时,要先判断是否有发生旋转,有的话需要把点P和外接矩形以锚点为中心旋转后再判断

测试效果如下:

QQ截图20220402110114.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容