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°时 则在内,反之在外。
射线检测适用较广。 第二、三种方法遇到重叠或相交多边形判断不准如图:
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;
}
可以使用点与多边形判断,也可以用以下方法判断,比较简单
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;
}