import { misc, Rect, Vec2, Vec3 } from "cc"; /** 用于计算的临时工 */ const tempVec3 = new Vec3(); /** 用于弧度转角度 */ const rad2Deg = 180 / Math.PI; /** 用于角度转弧度 */ const deg2Rad = Math.PI / 180; export class MathUtil { static readonly zore: Vec2 = new Vec2(1, 0); /** * 用于弧度转角度 */ public static get rad2Deg() { return rad2Deg; } /** * 用于角度转弧度 */ public static get deg2Rad() { return deg2Rad; } /** * 弧度转角度 * @param radians */ public static radiansToDegrees(radians: number) { return radians * rad2Deg; } /** * 角度转弧度 * @param degree */ public static degreesToRadians(degree: number) { return degree * deg2Rad; } /**限制数大小 */ static Clamp(value: number, min: number, max: number): number { if (value < min) { value = min; } else if (value > max) { value = max; } return value; } static Clamp01(value: number): number { return this.Clamp(value, 0, 1); } static PingPong(t: number, length: number): number { t = this.Repeat(t, length * 2) return length - Math.abs(t - length) } static Repeat(t: number, length: number): number { return t - (Math.floor(t / length) * length) } static Round(num: number): number { return Math.floor(num + 0.5) } static Sign(num: number): number { if (num > 0) { num = 1 } else if (num < 0) { num = -1 } else { num = 0; } return num; } static InverseLerp(from: number, to: number, value: number): number { if (from < to) { if (value < from) return 0; if (value > to) return 1; value = value - from value = value / (to - from) return value; } if (from <= to) return 0; if (value < to) return 1; if (value > from) return 0; return 1 - ((value - to) / (from - to)); } static Lerp(from: number, to: number, t: number): number { return from + (to - from) * this.Clamp01(t); } static LerpUnclamped(a: number, b: number, t: number): number { return a + (b - a) * t; } static DeltaAngle(current: number, target: number): number { let num = this.Repeat(target - current, 360); if (num > 180) num = num - 360; return num; } static LerpAngle(a: number, b: number, t: number): number { let num = this.Repeat(b - a, 360) if (num > 180) num = num - 360; return a + num * this.Clamp01(t) } /** * 在最小值和最大值之间进行插值,并在极限处进行平滑处理 * @param from * @param to * @param t */ public static smoothStep(from: number, to: number, t: number) { t = this.Clamp01(t); t = (-2.0 * t * t * t + 3.0 * t * t); return (to * t + from * (1.0 - t)); } /** * 平滑控制 * @param current 当前值 * @param target 目标值 * @param currentVelocity 当前速度 * @param smoothTime 平滑时间 * @param maxSpeed 最大速度 * @param deltaTime 时间增量 */ public static smoothDamp(current: number, target: number, currentVelocity: number, smoothTime: number, deltaTime: number, maxSpeed: number = Number.POSITIVE_INFINITY) { smoothTime = Math.max(0.0001, smoothTime); const num1 = 2 / smoothTime; const num2 = num1 * deltaTime; const num3 = (1 / (1 + num2 + 0.47999998927116394 * num2 * num2 + 0.23499999940395355 * num2 * num2 * num2)); const num4 = current - target; const num5 = target; const max = maxSpeed * smoothTime; const num6 = this.Clamp(num4, -max, max); target = current - num6; const num7 = (currentVelocity + num1 * num6) * deltaTime; let velocity = (currentVelocity - num1 * num7) * num3; let num8 = target + (num6 + num7) * num3; if ((num5 - current > 0) === (num8 > num5)) { num8 = num5; velocity = (num8 - num5) / deltaTime; } return { value: num8, velocity: velocity, }; } /** * 垂直于原向量V的单位向量 * @param v 一个向量 * @returns */ static getPerpendicular(v: Vec2): Vec2 { const perpendicular = new Vec2(-v.y, v.x); //计算垂直向量 const normal = perpendicular.normalize();//归一化 return normal; } //角度转向量 static angleToVector(angle: number): Vec2 { return this.zore.clone().rotate(-misc.degreesToRadians(angle)); } // 向量转角度 static vectorToAngle(dir: Vec2): number { return -misc.radiansToDegrees(dir.signAngle(this.zore)); } // 角度转向量 static angle_to_vector(angle: number): Vec2 { // tan = sin / cos // 将传入的角度转为弧度 let radian = this.degreesToRadians(angle); // 算出cos,sin和tan let cos = Math.cos(radian);// 邻边 / 斜边 let sin = Math.sin(radian);// 对边 / 斜边 let tan = sin / cos;// 对边 / 邻边 // 结合在一起并归一化 let vec = new Vec2(cos, sin).normalize(); // 返回向量 return (vec); } // !!!!!!!!其实使用Math.atan2求出弧度再转角度一样的效果 // 向量转角度 static vector_to_angle(dir: Vec2): number { // 将传入的向量归一化 一般已经归一化了 //dir.normalize(); // 计算出目标角度的弧度 let radian = dir.signAngle(this.zore); // 把弧度计算成角度 let angle = -this.radiansToDegrees(radian); // 返回角度 return (angle); } //向量转弧度 static vector_to_radian(dir: Vec2): number { return dir.signAngle(this.zore); } /// /// 旋转向量,使其方向改变,大小不变 /// /// 需要旋转的向量 /// 旋转的角度 /// 旋转后的向量 static RotationMatrix(x: number, y: number, angle: number): Vec2 { let radian = this.degreesToRadians(angle); let sin = Math.sin(radian); var cos = Math.cos(radian); var newX = x * cos + y * sin; var newY = x * -sin + y * cos; //var newX = x * cos - y * sin; //var newY = x * sin + y * cos; return new Vec2(newX, newY); } static RotationDir(dir: Vec2, angle: number): Vec2 { let radian = this.radiansToDegrees(angle); let sin = Math.sin(radian); var cos = Math.cos(radian); var newX = dir.x * cos + dir.y * sin; var newY = dir.x * -sin + dir.y * cos; dir.x = newX; dir.y = newY; return dir; } //扇形范围 public static CheckInView(mPos: Vec2, faceDir: Vec2, tPos: Vec2, dis: number, angle: number): boolean { //let t = tPos.subtract(mPos); //let distance = Vec2.lengthSqr(t); //if(distance= 360) return true; let dir = tPos.subtract(mPos).normalize(); let ang = this.radiansToDegrees(Vec2.angle(faceDir, dir)); if (ang <= angle * 0.5) { return true; } } return false; } //检测点是否在一个凸四边形内 public static InConvexQuad(point: Vec2, pointA: Vec2, pointB: Vec2, pointC: Vec2, pointD: Vec2): boolean { let vec1: Vec2, vec2: Vec2; vec1.x = pointB.x - pointA.x; vec1.y = pointB.y - pointA.y; vec2.x = point.x - pointA.x; vec2.y = point.y - pointA.y; if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false; vec1.x = pointC.x - pointB.x; vec1.y = pointC.y - pointB.y; vec2.x = point.x - pointB.x; vec2.y = point.y - pointB.y; if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false; vec1.x = pointD.x - pointC.x; vec1.y = pointD.y - pointC.y; vec2.x = point.x - pointC.x; vec2.y = point.y - pointC.y; if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false; vec1.x = pointA.x - pointD.x; vec1.y = pointA.y - pointD.y; vec2.x = point.x - pointD.x; vec2.y = point.y - pointD.y; if (this.cross2DPoint(vec2.x, vec2.y, vec1.x, vec1.y)) return false; return true; } /**等同于Vec2.cross */ public static cross2DPoint(x1: number, y1: number, x2: number, y2: number): number { return x1 * y2 - x2 * y1; } /**检测两个2D包围盒(xy为中心点 wh分别为x轴半径宽 y轴半径宽) 是否相交*/ public static AABBAABB2D(pointX1: number, pointY1: number, w1: number, h1: number, pointX2: number, pointY2: number, w2: number, h2: number): boolean { if (Math.abs(pointX1 - pointX2) > (w1 + w2)) return false; if (Math.abs(pointY1 - pointY2) > (h1 + h2)) return false; return true; } /**检测两个3D包围盒 是否相交*/ public static AABBAABB3D(pointX1: number, pointY1: number, pointZ1: number, w1: number, h1: number, t1: number, pointX2: number, pointY2: number, pointZ2: number, w2: number, h2: number, t2: number): boolean { if (Math.abs(pointX1 - pointX2) > (w1 + w2)) return false; if (Math.abs(pointY1 - pointY2) > (h1 + h2)) return false; if (Math.abs(pointZ1 - pointZ2) > (t1 + t2)) return false; return true; } /**点与点*/ public static pointPoint(point1: Vec2, point2: Vec2): boolean { if (point1.x == point2.x && point1.y == point2.y) { return true; } return false; } //判断点是否在线上,在返回1,不在返回0 public static onSegement(Q: Vec2, p1: Vec2, p2: Vec2): boolean { let maxx: number, minx: number, maxy: number, miny: number; maxx = p1.x > p2.x ? p1.x : p2.x; //矩形的右边长 minx = p1.x > p2.x ? p2.x : p1.x; //矩形的左边长 maxy = p1.y > p2.y ? p1.y : p2.y; //矩形的上边长 miny = p1.y > p2.y ? p2.y : p1.y; //矩形的下边长 if (((Q.x - p1.x) * (p2.y - p1.y) == (p2.x - p1.x) * (Q.y - p1.y)) && (Q.x >= minx && Q.x <= maxx) && (Q.y >= miny && Q.y <= maxy)) { return true; } return false; } //点与圆的碰撞 //通过计算点到圆心的距离与半径比较判断circle(由中心点和半径构成) public static pointInCircle(point: Vec2, center: Vec2, radius: number): boolean { return Vec2.squaredDistance(point, center) <= radius * radius; } //点与矩形的碰撞 //通过判断点坐标是否在矩形四个顶点围成的坐标区域内 public static pointInRect(point: Vec2, rect: Rect): boolean { if (point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height) return true; return false } //线与矩形的碰撞 public static lineInRect(p1: Vec2, p2: Vec2, rect: Rect): boolean { let height = p1.y - p2.y; let width = p2.x - p1.x; let c = p1.x * p2.y - p2.x - p1.y;//计算叉乘 if ((height * rect.xMin + width * rect.yMin + c >= 0 && height * rect.xMax + width * rect.yMax + c <= 0) || (height * rect.xMin + width * rect.yMin + c <= 0 && height * rect.xMax + width * rect.yMax + c >= 0) || (height * rect.xMin + width * rect.yMax + c >= 0 && height * rect.xMax + width * rect.yMin + c <= 0) || (height * rect.xMin + width * rect.yMax + c <= 0 && height * rect.xMax + width * rect.yMin + c >= 0) ) { if ((p1.x < rect.xMin && p2.x < rect.xMin) || (p1.x > rect.xMax && p2.x > rect.xMax) || (p1.y > rect.yMin && p2.y > rect.yMin) || (p1.y < rect.yMax && p2.y < rect.yMax) ) { return false; } else { return true; } } else { return false } } //两个rect是否相交 public static collisionRectWithRect(rect1: Rect, rect2: Rect): boolean { //计算相交部分的矩形 //左下角坐标:( lx , ly ) //右上角坐标:( rx , ry ) let lx = Math.max(rect1.xMin, rect2.xMin); let ly = Math.max(rect1.yMin, rect2.yMin); let rx = Math.min(rect1.xMax, rect2.xMax); let ry = Math.min(rect1.yMax, rect2.yMax); //判断是否能构成小矩形 if (lx > rx || ly > ry) return false; //矩形不相交 return true; //发生碰撞 } //圆与矩形是否相交 public static collisionRectWithCircle(rect: Rect, p: Vec2, r: number): boolean { //获取矩形信息 //左下角坐标:( lx , ly ) //右上角坐标:( rx , ry ) let lx = rect.xMin; let ly = rect.yMin; let rx = rect.xMax; let ry = rect.yMax; //计算圆心到四个顶点的距离 let d1 = Vec2.distance(p, new Vec2(lx, ly)); let d2 = Vec2.distance(p, new Vec2(lx, ry)); let d3 = Vec2.distance(p, new Vec2(rx, ly)); let d4 = Vec2.distance(p, new Vec2(rx, ry)); //判断是否碰撞//判断距离是否小于半径 if (d1 < r || d2 < r || d3 < r || d4 < r) return true; //是否在圆角矩形的,横向矩形内 if (p.x > (lx - r) && p.x < (rx + r) && p.y > ly && p.y < ry) return true; //是否在圆角矩形的,纵向矩形内 if (p.x > lx && p.x < rx && p.y > (ly - r) && p.y < (ry + r)) return true; //不发生碰撞 return false; } /** * 计算向量在指定平面上的投影 * @param vector 被投影的向量 * @param planeNormal 平面法线 */ public static projectOnPlane(vector: Vec3, planeNormal: Vec3) { // 也可以直接用 Vec3 自带的平面投影函数 // return Vec3.projectOnPlane(new Vec3, targetDir, planeNormal); // 使用点乘计算方向矢量在平面法线上的投影长度 const projectionLength = Vec3.dot(vector, planeNormal); // 平面法线与长度相乘得到方向矢量在平面法线上的投影矢量 const vectorOnPlane = tempVec3.set(planeNormal).multiplyScalar(projectionLength); // 方向矢量减去其在平面法线上的投影矢量即是其在平面上的投影矢量 return Vec3.subtract(new Vec3, vector, vectorOnPlane); } /** * 计算两个向量基于指定轴的夹角(逆时针方向为正方向,值范围 -180 ~ 180) * @param a 向量 a * @param b 向量 b * @param axis 参照轴向量(请确保是归一化的) */ public static signedAngle(a: Vec3, b: Vec3, axis: Vec3) { // 将向量 a 和 b 分别投影到以 axis 为法线的平面上 const aOnAxisPlane = this.projectOnPlane(a, axis); const bOnAxisPlane = this.projectOnPlane(b, axis); // 归一化处理 const aNormalized = aOnAxisPlane.normalize(); const bNormalized = bOnAxisPlane.normalize(); // 求出同时垂直于 a 和 b 的法向量 const abNormal = Vec3.cross(new Vec3, aNormalized, bNormalized).normalize(); // 将法向量到 axis 上的投影长度 // 若投影长度为正值(+1)则表示法向量与 axis 同向(向量叉乘的右手法则) const sign = Vec3.dot(abNormal, axis); // 求出向量 a 和 b 的夹角 const radian = Math.acos(Vec3.dot(aNormalized, bNormalized)); // 混合在一起! return radian * sign * this.rad2Deg; } //获取某点到某点的Y轴方向 static GetYDir(from: Vec3, to: Vec3) { let dir = to.subtract(from); dir.y = from.y return dir.normalize(); } //向量绕Y轴转一个角度 static RotateAroundAxisY(dir: Vec3, angle: number): Vec3 { dir = dir.clone(); let rad = this.degreesToRadians(angle); let cos = Math.cos(rad); let sin = Math.sin(rad); dir.x = dir.x * cos + dir.z * sin; dir.z = dir.x * (-sin) + dir.z * cos; return dir; } //将一个向量围绕X轴旋转angle个角度 static RotateAroundAxisX(dir: Vec3, angle: number): Vec3 { dir = dir.clone(); let rad = this.degreesToRadians(angle); let cos = Math.cos(rad); let sin = Math.sin(rad); dir.y = dir.y * cos - dir.z * sin; dir.z = dir.y * sin + dir.z * cos; return dir; } //将一个向量围绕Z轴旋转angle个角度 static RotateAroundAxisZ(dir: Vec3, angle: number): Vec3 { dir = dir.clone(); let rad = this.degreesToRadians(angle); let cos = Math.cos(rad); let sin = Math.sin(rad); dir.x = dir.x * cos - dir.y * sin; dir.y = dir.x * sin + dir.y * cos; return dir; } //线段是否与矩形相交 static LineRectIntersection(lineStartPoint: Vec2, lineEndPoint: Vec2, rectMinX: number, rectMaxX: number, rectMinY: number, rectMaxY: number): boolean {//针对四种不同的情况,进行碰撞检测 let minXLinePoint = lineEndPoint; if (lineStartPoint.x <= lineEndPoint.x) { minXLinePoint = lineStartPoint; } let maxXLinePoint = lineStartPoint; if (lineStartPoint.x <= lineEndPoint.x) { maxXLinePoint = lineEndPoint; } let minYLinePoint = lineEndPoint; if (lineStartPoint.y <= lineEndPoint.y) { minYLinePoint = lineStartPoint; } let maxYLinePoint = lineStartPoint; if (lineStartPoint.y <= lineEndPoint.y) { maxYLinePoint = lineEndPoint; } if (minXLinePoint.x <= rectMinX && rectMinX <= maxXLinePoint.x) { let m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x); let intersectionY = ((rectMinX - minXLinePoint.x) * m) + minXLinePoint.y; if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y && rectMinY <= intersectionY && intersectionY <= rectMaxY) { return true;//new Vec2(rectMinX, intersectionY) } } if (minXLinePoint.x <= rectMaxX && rectMaxX <= maxXLinePoint.x) { let m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x); let intersectionY = ((rectMaxX - minXLinePoint.x) * m) + minXLinePoint.y; if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y && rectMinY <= intersectionY && intersectionY <= rectMaxY) { return true;//new Vec2(rectMaxX, intersectionY) } } if (minYLinePoint.y <= rectMaxY && rectMaxY <= maxYLinePoint.y) { let rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y); let intersectionX = ((rectMaxY - minYLinePoint.y) * rm) + minYLinePoint.x; if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x && rectMinX <= intersectionX && intersectionX <= rectMaxX) { return true;//new Vec2(intersectionX, rectMaxY) } } if (minYLinePoint.y <= rectMinY && rectMinY <= maxYLinePoint.y) { let rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y); let intersectionX = ((rectMinY - minYLinePoint.y) * rm) + minYLinePoint.x; if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x && rectMinX <= intersectionX && intersectionX <= rectMaxX) { return true;//new Vec2(intersectionX, rectMinY) } } return false } }