interface Point { x: number; y: number; [proppName: string]: any; } /** * 创建一个2维度向量类 */ export class Vector { components: any; length: number; constructor(...components: any) { this.components = components; const pointO = components[0]; const pointA = components[1]; const diffX = pointA.x - pointO.x; const diffY = pointA.y - pointO.y; this.length = Math.sqrt(diffX * diffX + diffY * diffY); } // 加法 add(components: any) { const pointO = this.components[0]; const pointA = this.components[1]; const pointB = components; return { x: pointA.x + pointB.x - pointO.x, y: pointA.y + pointB.y - pointO.y, }; } // 减 subtract(components: any) { const pointO = this.components[0]; const pointA = this.components[1]; const pointC = components; return { x: pointA.x - pointC.x + pointO.x, y: pointA.y - pointC.y + pointO.y, }; } // 向量夹角 // 向量点乘:(内积) // // 数学公式 // a向量(x1,y1) b向量(x2,y2) // a向量 点乘 b向量 = x1*x2 + y1*y2 // = a向量的模 * b向量的模 * cos(夹角) // https://zhuanlan.zhihu.com/p/359975221 dotProduct(components: any) { const pointO = this.components[0]; const pointA = this.components[1]; const pointB = components; const _pointA = { x: pointA.x - pointO.x, y: pointA.y - pointO.y }; const _pointB = { x: pointB.x - pointO.x, y: pointB.y - pointO.y }; // 求向量OA与OB的夹角 // AB向量 = OB向量 - OA向量 // const ab_x = _pointB.x - _pointA.x; // const ab_y = _pointB.y - _pointA.y; // OA向量 * OB向量 > 0,夹角在0~90度之间 // OA向量 * OB向量 = 0,正交,相互垂直 // OA向量 * OB向量 < 0,夹角在90度~180度之间 // // OA向量 * OB向量 = x1*x2 + y1*y2; const abMultiplication = _pointA.x * _pointB.x + _pointA.y * _pointB.y; // OA向量的模 const abs_OA = new Vector({ x: 0, y: 0 }, _pointA).length; // OB向量的模 const abs_OB = new Vector({ x: 0, y: 0 }, _pointB).length; // 得到弧度值 let result = Math.acos(abMultiplication / (abs_OA * abs_OB)); // 转为角度值 result = toAngle(result); const arr = []; arr.push(_pointA); arr.push({ x: 0, y: 0 }); arr.push(_pointB); const is_clockwise = isClockwise(arr); if (is_clockwise) { // 顺时针方向 大于180度 result = 360 - result; } else { } return result; } // 向量叉乘 // a向量(x1,y1) b向量(x2,y2) // a向量 叉乘 b向量 = c向量 // c向量的模 = x1*y2 - x2*y1 // = a向量的模 * b向量的模 * sin(夹角) // 如果以向量a和向量b边构成一个平行四边形,那么这两个向量外积的模长与这个平行四边形的面积相等。 // 向量单位化 // 单位化后的向量以(0,0)点为原点 unitization() { const pointO = this.components[0]; const pointA = this.components[1]; const norm = (x: number, y: number) => Math.sqrt(x * x + y * y); const vectorX1 = pointA.x - pointO.x; // 向量OA 横坐标 const vectorY1 = pointA.y - pointO.y; // 向量OA 纵坐标 const n1 = norm(vectorX1, vectorY1); // 向量的平方根 为了对向量OA做单位化 const vectorUnitX1 = vectorX1 / n1; // 向量单位化 横坐标 const vectorUnitY1 = vectorY1 / n1; // 向量单位化 纵坐标 return { point: { x: vectorUnitX1, y: vectorUnitY1 }, n: n1, }; } } /** * 判断坐标数组是否顺时针(默认为false) * @param {Point[]} points 点坐标数组 [{x:0,y:0}...] * @returns {boolean} 是否顺时针 */ export function isClockwise(points: Point[]) { // 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向 // 因此需要取多边形中绝对是凸边的点来判断, // 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。 if (!Array.isArray(points) || points.length < 3) { console.error('多边形坐标集合不能少于3个'); return false; } let coords = JSON.parse(JSON.stringify(points)); if (coords[0] === coords[coords.length - 1]) { coords = coords.slice(0, coords.length - 1); } coords = coords.reverse(); let maxXIndex = 0; let maxX = parseFloat(coords[maxXIndex].x); let c1; let c2; let c3; for (let i = 0; i < coords.length; i++) { if (parseFloat(coords[i].x) > maxX) { maxX = parseFloat(coords[i].x); maxXIndex = i; } } if (maxXIndex === 0) { c1 = coords[coords.length - 1]; c2 = coords[maxXIndex]; c3 = coords[maxXIndex + 1]; } else if (maxXIndex === coords.length - 1) { c1 = coords[maxXIndex - 1]; c2 = coords[maxXIndex]; c3 = coords[0]; } else { c1 = coords[maxXIndex - 1]; c2 = coords[maxXIndex]; c3 = coords[maxXIndex + 1]; } const x1 = parseFloat(c1.x); const y1 = parseFloat(c1.y); const x2 = parseFloat(c2.x); const y2 = parseFloat(c2.y); const x3 = parseFloat(c3.x); const y3 = parseFloat(c3.y); const s = (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3); return s < 0; } /** * 转为弧度值 */ export function toRadian(val: number) { return (val * Math.PI) / 180; } /** * 转角度值 */ export function toAngle(val: number) { return (val * 180) / Math.PI; }