点与几何图形的算法

常用的向量方法

interface Point {
    x: number;
    y: number;
}
export  function  sub(p1:IPoint,p2:IPoint):IPoint{
    const x = p1.x - p2.x;
    const y = p1.y - p2.y;
    return {x,y};
}
export  function  add(p1:IPoint,p2:IPoint):IPoint{
    const x = p1.x + p2.x;
    const y = p1.y + p2.y;
    return {x,y};
}

export  function  dot(p1:IPoint,p2:IPoint):number{
    return p1.x * p2.x + p1.y * p2.y;
}

export  function  cross(p1:IPoint,p2:IPoint):number{
    return p1.x * p2.y - p2.x * p1.y;
}

export function mag({x,y}:IPoint):number{
    return Math.sqrt(x*x+y*y);
}

export function clone(cloneP:IPoint,out:IPoint):IPoint{
    out.x = cloneP.x;
    out.y = cloneP.y;
    return out;
}
export function distance(p1:IPoint,p2:IPoint):number{
    return Math.sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
}

点到直线的距离

/**
 * 点到直线的距离
 * 1,叉乘得到平行四边形面积
 * 2,叉乘结果再除以另一边长的模等于四边形的高   即等于点到底边的距离
 */
export function pointToLineDis(p1:IPoint,p2:IPoint,p:IPoint):number{
    const s = cross(sub(p2,p1),sub(p,p1));
    const len = distance(p1,p2);
    return s/len;
 }

点到线段的距离

/** 点到线段的距离
 * 1,计算得出点到线段的投影距离(点乘)|a||b|cosAngle
 * 2,投影距离与线段长度的比值  t   1,小于0,夹角大于90°    2,大于1  投影长度大于线段长度  3,在线段内
 * 3,相似三角形三边比相等 p1.x +=  t * x;  p1.y += t*y;   投影点向量
 * 4,点到投影点的向量   再求向量模
 **/ 
export function pointToSegmentDis(p1:IPoint,p2:IPoint,p:IPoint):number{
  //线段的长度
  const segmentDis = distance(p1,p2);
  //点p到线段p1p2的投影 点乘的几何公式 |a||b|cosAngle  
  //1,angle > PI/2        =》 < 0  
  //2,0<= angle < PI/2    =》 > 0
  const dis = dot(sub(p,p1),sub(p2,p1));
  //投影与线段长度的比值
  const t = dis/segmentDis;
  const sp:IPoint = {x:0,y:0}
  if(t <0 ){//投影在线段向量p1p2 的反向 
    clone(p1,sp);
  }else if(t>1){//投影在线段向量p1p2 的同向 投影长度大于线段长度 
    clone(p2,sp);
  }else{//在线段上 相似三角形三边比相等
    sp.x = p1.x + t *(p2.x - p1.x);
    sp.y = p1.y + t * (p2.y - p1.y);
  }
  //获取垂直于线段的向量并求其模
  return mag(sub(sp,p));
}

点与多边形关系

第一种计算 射线检测

/* 点与多边形
 * 第一种计算 射线检测
 * 用点的y轴值 向x轴正方向做一条射线,与多边形边的交点奇数在多边形内,偶数在多边形外
 * 需特殊处理:
 * 1,过顶点 (会同时记录两次)
 * 2,共线 
 */
//奇数则在多边形内。否则在多边形外
export function pointPolygon(points:IPoint[],p:IPoint):boolean{
  let intersect = 0;
  for (let i = 0; i < points.length; i++) {
    const p1 = points[i];
    const p2 = points[(i+1) % points.length];
    //点在多边形的线段中间  使用大于号 目的是为了避开射线过顶点时 记算两次 以及共线问题
    //p.y < p1.y && p.y >= p2.y  ||  p.y < p2.y && p.y >= p1.y   =》 p1.y > p.y !== p2.y > p.y
    const bool1 = p1.y > p.y !== p2.y > p.y;
    //取p点y值向x轴正方向发射一条射线  计算与线段的交点
    const k = (p2.x - p1.x)/(p2.y - p1.y); //斜率
    const x1 = k * (p.y - p1.y) + p1.x; //得到与线段交点的x值
    const bool2 = x1 > p.x; 
    if(bool1 && bool2) intersect++; //只记录点p 右侧的交点数
  }
  //交点数是奇数则在多边形内。否则在外
  return intersect % 2 === 1;
}

第二种方法 向量叉乘

该方法主要是利用在点在线段左侧进行计数,如果是奇数则在多边形内。否则在多边形外部。
(对以重叠或相交的多边形,判断不准)

/**
 * 第二种计算方式
 * 点在线段左边与右边 进行计数,叉乘值大于0 在左侧(右手定则 逆时针)。 值小于0 在右侧(右手定则 顺时针)
 * 遍历完所有的边后,
 * 1,如果左侧计数器是奇数 则在多边形内
 * 3,否则,点P在多边形的外。
 */
export function pointPolygon2(points:IPoint[],p:IPoint):boolean{
  let isInside = false;
  for (let i = 0; i < points.length; i++) {
    const p1 = points[i];
    const p2 = points[(i+1) % points.length];
    const cor = cross(sub(p2,p1),sub(p,p1));
    if(cor > 0){
      isInside =!isInside;
    }
  }
  return isInside;
}

第三种方法 内角合等于 360°

实现方式同第二种,通过每条边与点的夹角和等于 360°时 则在内,反之在外。

总结

射线检测适用较广。 第二、三种方法遇到重叠或相交多边形判断不准如图:

点与几何图形的算法_第1张图片

点与圆的关系

interface ICircle{
  x:number,
  y:number,
  r:number
}
export function pointInCircle(p:IPoint,{x,y,r}:ICircle):boolean{
  const dis = distance(p,{x,y});
  return dis <= r;
}

点与矩形的关系

可以使用点与多边形判断,也可以用以下方法判断,比较简单

  1. 点的x值在最小值与最大值的之间
  2. 点的y值也在最小值与最大值的之间
    两者同时满足即可
interface IRect{
  x:number,
  y:number,
  width:number,
  height:number
}
export function pointInRectangle(p:IPoint,{x,y,width,height}:IRect): boolean {
  return p.x >= x && p.x <= x + width && p.y >= y && p.y <= y + height;
}

你可能感兴趣的:(算法)