import Constant from "../Constant"; import bezierUtil from "./bezierUtil.js"; export default class MathUtil { constructor() {} getFixed(num, decimal) { if (!decimal) { decimal = 5; } // return Math.floor(num * 10000) / 10000; return parseFloat(num.toFixed(decimal)); } // 求两个点的距离 getDistance(p1, p2) { const x1 = p1.x; const y1 = p1.y; const x2 = p2.x; const y2 = p2.y; const num = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); return this.getFixed(num); } createLine1(point1, point2) { if (point1.x == point2.x && point1.y == point2.y) { return null; } else if (this.getFixed(Math.abs(point1.x - point2.x)) == 0) { return { x: point1.x }; } else if (this.getFixed(Math.abs(point1.y - point2.y)) == 0) { return { y: point1.y }; } const parametera = (point1.y - point2.y) / (point1.x - point2.x); const parameterb = (point1.x * point2.y - point2.x * point1.y) / (point1.x - point2.x); if (this.getFixed(parametera) == 0) { return { y: this.getFixed(parameterb) }; } const parameter = { a: this.getFixed(parametera), b: this.getFixed(parameterb), }; return parameter; } createLine2(point, angle) { if (angle == 90 || angle == 270) { return { x: point.x }; } let a = Math.tan((angle / 180) * Math.PI); let b = point.y - a * point.x; if (a != 0) { return { a: a, b: b }; } else { return { y: point.y }; } } // 与lineA平行并且point在线上 createLine3(lineA, point) { const parameter = {}; if (typeof lineA.a === "undefined") { if (typeof lineA.x !== "undefined") { parameter.x = point.x; } else if (typeof lineA.y !== "undefined") { parameter.y = point.y; } } else { parameter.a = lineA.a; parameter.b = point.y - point.x * lineA.a; } return parameter; } create2AngleLine(point, angle, driftAngle) { let line1 = this.createLine2(point, angle - driftAngle / 2); let line2 = this.createLine2(point, angle + driftAngle / 2); return { line1: line1, line2: line2 }; } distanceForPoints(point1, point2) { return Math.sqrt( Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2) ); } //与line平行且两条线直接的距离是distance的两条线 getParallelLineForDistance(line, distance) { let line1 = {}; let line2 = {}; if (!line.hasOwnProperty("a")) { if (line.hasOwnProperty("x")) { let x = line.x; line1.x = x + distance; line2.x = x - distance; } else if (line.hasOwnProperty("y")) { let y = line.y; line1.y = y + distance; line2.y = y - distance; } } else { line1.a = line.a; line1.b = line.b; line2.a = line.a; line2.b = line.b; let angle = Math.atan(line.a); let db = Math.abs(distance / Math.cos(angle)); let b = line.b; line1.b = b + db; line2.b = b - db; } return { line1: line1, line2: line2 }; } //获取扇形的两个端点 getEndpoint(point, angle, sectorAngle) { const distance = 15; //line1是减,line2是加 let lines1 = this.create2AngleLine(point, angle, sectorAngle); let line = this.createLine2(point, angle); line = this.getLineForPoint(line, point); let lines2 = this.getParallelLineForDistance(line, distance); let point1 = this.getIntersectionPoint(lines1.line1, lines2.line1); let point2 = this.getIntersectionPoint(lines1.line1, lines2.line2); let point3 = this.getIntersectionPoint(lines1.line2, lines2.line1); let point4 = this.getIntersectionPoint(lines1.line2, lines2.line2); let angle1 = this.Angle(point, point1, { x: point.x + 1, y: point.y }); let angle2 = this.Angle(point, point2, { x: point.x + 1, y: point.y }); let angle3 = this.Angle(point, point3, { x: point.x + 1, y: point.y }); let angle4 = this.Angle(point, point4, { x: point.x + 1, y: point.y }); if (angle > 180) { angle = 360 - angle; } if ( Math.abs((angle1 + angle3) / 2 - angle) < Math.abs((angle2 + angle4) / 2 - angle) ) { return { p1: point1, p2: point3 }; } else { return { p1: point2, p2: point4 }; } } // true表示逆时针,false表示顺时针 isClockwise(vertices) { let area = 0; for (let i = 0; i < vertices.length; i++) { const j = (i + 1) % vertices.length; area += vertices[i].x * vertices[j].y; area -= vertices[j].x * vertices[i].y; } const sub = area / 2; if (sub > 0) { // 逆时针 return true; } else { // 顺时针 return false; } } reverse(points) { const _points = []; for (let i = points.length - 1; i > -1; --i) { _points.push(points[i]); } return _points; } //两条线的交点 getIntersectionPoint(parameter1, parameter2) { if (this.isParallel(parameter1, parameter2)) { return null; } if ( typeof parameter1.a == "undefined" && typeof parameter2.a != "undefined" ) { if (parameter1.x) { return { x: parameter1.x, y: parameter2.a * parameter1.x + parameter2.b, }; } else if (parameter1.y) { return { x: (parameter1.y - parameter2.b) / parameter2.a, y: parameter1.y, }; } } else if ( typeof parameter2.a == "undefined" && typeof parameter1.a != "undefined" ) { if (parameter2.x) { return { x: parameter2.x, y: parameter1.a * parameter2.x + parameter1.b, }; } else if (parameter2.y) { return { x: (parameter2.y - parameter1.b) / parameter1.a, y: parameter2.y, }; } } else if ( typeof parameter2.a == "undefined" && typeof parameter1.a == "undefined" ) { if (parameter1.hasOwnProperty("x") && parameter2.hasOwnProperty("y")) { return { x: parameter1.x, y: parameter2.y }; } else if ( parameter1.hasOwnProperty("y") && parameter2.hasOwnProperty("x") ) { return { x: parameter2.x, y: parameter1.y }; } else { return null; } } if (parameter1.a == parameter2.a) { return null; } let joinpointx = (parameter2.b - parameter1.b) / (parameter1.a - parameter2.a); let joinpointy = (parameter1.a * parameter2.b - parameter2.a * parameter1.b) / (parameter1.a - parameter2.a); let point = { x: joinpointx, y: joinpointy }; return point; } // 直线的交点 getIntersectionPoint2(a, b, c, d) { /** 1 解线性方程组, 求线段交点. **/ // 如果分母为0 则平行或共线, 不相交 const denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y); if (denominator == 0) { return null; } // 线段所在直线的交点坐标 (x , y) const x = ((b.x - a.x) * (d.x - c.x) * (c.y - a.y) + (b.y - a.y) * (d.x - c.x) * a.x - (d.y - c.y) * (b.x - a.x) * c.x) / denominator; const y = -( (b.y - a.y) * (d.y - c.y) * (c.x - a.x) + (b.x - a.x) * (d.y - c.y) * a.y - (d.x - c.x) * (b.y - a.y) * c.y ) / denominator; return { x: x, y: y }; } //两条线段交点 getIntersectionPoint3(a, b, c, d) { const join = this.getIntersectionPoint2(a, b, c, d); if (join) { const x = join.x; const y = join.y; // 交点在线段1上 且交点也在线段2上 /** 2 判断交点是否在两条线段上 **/ if ( (x - a.x) * (x - b.x) <= 0.001 && (y - a.y) * (y - b.y) <= 0.001 && (x - c.x) * (x - d.x) <= 0.001 && (y - c.y) * (y - d.y) <= 0.001 ) { // 返回交点p return { x: x, y: y, }; } return null; } return null; } // 线段和直线是否相交 getIntersectionPoint4(point1, point2, line) { const line1 = this.createLine1(point1, point2); const join = this.getIntersectionPoint(line1, line); if (join == null) { return null; } if (this.PointInSegment(join, point1, point2)) { return join; // 相交 } else { return null; } } //返回true表示平行 isParallel(line1, line2) { if (typeof line1.a == "undefined" && typeof line2.a == "undefined") { if (line1.hasOwnProperty("x") && line2.hasOwnProperty("x")) { return true; } else if (line1.hasOwnProperty("y") && line2.hasOwnProperty("y")) { return true; } else { return false; } } else if (typeof line1.a == "undefined" || typeof line2.a == "undefined") { return false; } else if (this.getFixed(line1.a) == this.getFixed(line2.a)) { return true; } else { return false; } } //两条相交的线段的夹角,永远小于180度 Angle(o, s, e) { let cosfi = 0, fi = 0, norm = 0; let dsx = s.x - o.x; let dsy = s.y - o.y; let dex = e.x - o.x; let dey = e.y - o.y; cosfi = dsx * dex + dsy * dey; norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey); cosfi /= Math.sqrt(norm); if (cosfi >= 1.0) return 0; //if (cosfi <= -1.0) return Math.PI; if (cosfi <= -1.0) return 180; fi = Math.acos(cosfi); if ((180 * fi) / Math.PI < 180) { //return 180 * fi / Math.PI; return (fi * 180) / Math.PI; } else { //return 360 - 180 * fi / Math.PI; return ((2 * Math.PI - fi) * 180) / Math.PI; } } Angle1(o, s, e) { let cosfi = 0, fi = 0, norm = 0; let dsx = s.x - o.x; let dsy = s.y - o.y; let dex = e.x - o.x; let dey = e.y - o.y; cosfi = dsx * dex + dsy * dey; norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey); cosfi /= Math.sqrt(norm); if (cosfi >= 1.0) return 0; //if (cosfi <= -1.0) return Math.PI; if (cosfi <= -1.0) return 180; fi = Math.acos(cosfi); return (fi * 180) / Math.PI; } getArrow(start, end, ange = 30, L = 20) { let a = Math.atan2(end.y - start.y, end.x - start.x); let xC = end.x - L * Math.cos(a + (ange * Math.PI) / 180); // θ=30 let yC = end.y - L * Math.sin(a + (ange * Math.PI) / 180); let xD = end.x - L * Math.cos(a - (ange * Math.PI) / 180); let yD = end.y - L * Math.sin(a - (ange * Math.PI) / 180); return [{ x: xC, y: yC }, end, { x: xD, y: yD }]; } //经过point且与line垂直的线 getLineForPoint(line, point) { let parameter = {}; if (line.a == 0 || typeof line.a == "undefined") { if (line.hasOwnProperty("x")) { parameter.x = line.x; parameter.y = point.y; } else if (line.hasOwnProperty("y")) { parameter.x = point.x; parameter.y = line.y; } } else { parameter.a = -1 / line.a; parameter.b = point.y - point.x * parameter.a; } return parameter; } // 经过point且与line垂直的直线,该直线与line的交点 getJoinLinePoint(point, line) { const verticalLine = this.getVerticalLine(line, point); const join = this.getIntersectionPoint(line, verticalLine); return join; } // 点到直线的距离 getDisForPoinLine(point, line) { const join = this.getJoinLinePoint(point, line); return this.getDistance(point, join); } // 垂直线 getVerticalLine(line, point) { if (typeof line.a === "undefined") { if (line.hasOwnProperty("x")) { return { y: point.y }; } else if (line.hasOwnProperty("y")) { return { x: point.x }; } else { return null; } } else if (line.a == 0) { return { x: point.x }; } else { const tl = {}; tl.a = -1 / line.a; const result = this.createLine3(tl, point); return result; } } //point在直线上,只是不确定是否在线段上 //方法:point到startPoint和endPoint的距离之和与startPoint和endPoint之间的距离对比 isContainForSegment(point, startPoint, endPoint, minDis) { if (!minDis) { minDis = Constant.minLen; } let dis1 = this.getDistance(startPoint, point) + this.getDistance(endPoint, point); let dis2 = this.getDistance(startPoint, endPoint); if (Math.abs(dis1 - dis2) < minDis) { return true; } else { return false; } } /* //minDis isPointInPoly(point, points, minDis) { if (!minDis) { minDis = Constant.minRealDis } const x = point.x const y = point.y let inside = false // 是否在顶点附近 for (let i = 0; i < points.length; ++i) { let distance = this.getDistance(point, points[i]) if (distance < minDis) { return true } } // 是否在边沿 for (let i = 0, j = points.length - 1; i < points.length; j = i++) { let pt1 = points[i] let pt2 = points[j] const flag = this.isContainForSegment(point, pt1, pt2, minDis) if (flag) { return true } } for (let i = 0, j = points.length - 1; i < points.length; j = i++) { let pt1 = points[i] let pt2 = points[j] const xi = pt1.x const yi = pt1.y const xj = pt2.x const yj = pt2.y const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi if (intersect) inside = !inside } return inside } */ isPointInPoly(point, points) { const x = point.x; const y = point.y; let inside = false; for (let i = 0, j = points.length - 1; i < points.length; j = i++) { let pt1 = points[i]; let pt2 = points[j]; const xi = pt1.x; const yi = pt1.y; const xj = pt2.x; const yj = pt2.y; const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; } //a表示横轴,b表示竖轴 isPointInElliptic(point, center, a, b) { let r = Math.pow((point.x - center.x) / a, 2) + Math.pow((point.y - center.y) / b, 2); if (r <= 1) { return true; } else { return false; } } // 点到线段的距离 // 在minDistance范围内,会吸附到point1/point2上 // 返回值:type是1表示吸附在point1,是2表示吸附在point2,是0表示在线段point1-point2上; getDisForPoinSegment(point, point1, point2, minDistance) { const line = this.createLine1(point1, point2); const join = this.getJoinLinePoint(point, line); const dis = this.getDistance(point1, point2); const dis1 = this.getDistance(join, point1); const dis2 = this.getDistance(join, point2); if ( this.getDistance(join, point1) > dis || this.getDistance(join, point2) > dis ) { // 在线段外 if (dis1 < dis2 && dis1 < minDistance) { return { type: 1, join: point1 }; } else if (dis2 < dis1 && dis2 < minDistance) { return { type: 2, join: point2 }; } else { return null; } } else { if (dis1 < minDistance) { return { type: 1, join: point1 }; } else if (dis2 < minDistance) { return { type: 2, join: point2 }; } else if (this.getDistance(point, join) < minDistance) { return { type: 0, join: join }; } } } PointInSegment(Q, pi, pj, minDis) { if ( this.getDistance(Q, pi) < Constant.minAdsorbPix || this.getDistance(Q, pj) < Constant.minAdsorbPix ) { return true; } if (!minDis) { minDis = 0.1; } minDis = minDis / 2; const offset1 = (Q.x - pi.x) * (pj.y - pi.y) - (pj.x - pi.x) * (Q.y - pi.y); const offset2 = Math.min(pi.x, pj.x) - Q.x; const offset3 = Q.x - Math.max(pi.x, pj.x); const offset4 = Math.min(pi.y, pj.y) - Q.y; const offset5 = Q.y - Math.max(pi.y, pj.y); if ( Math.abs(offset1) < minDis && (offset2 <= 0 || Math.abs(offset2) < minDis) && (offset3 <= 0 || Math.abs(offset3) < minDis) && (offset4 <= 0 || Math.abs(offset4) < minDis) && (offset5 <= 0 || Math.abs(offset5) < minDis) ) { return true; } else { return false; } } //点p是否在线段AB上 isPointOnSegment(p, A, B) { // 计算向量 AP 和 BP const AP = { x: p.x - A.x, y: p.y - A.y, }; const BP = { x: p.x - B.x, y: p.y - B.y, }; // 计算向量 AB 的长度和方向 const AB = { x: B.x - A.x, y: B.y - A.y, }; const AB_length = this.getDistance(A, B); const AB_direction = { x: AB.x / AB_length, y: AB.y / AB_length, }; // 检查 AP 和 BP 的方向是否与 AB 相同,并检查它们的长度是否小于等于 AB 的长度 const dot_product_AP = AP.x * AB_direction.x + AP.y * AB_direction.y; const dot_product_BP = BP.x * AB_direction.x + BP.y * AB_direction.y; //return dot_product_AP >= 0 && dot_product_BP <= 0 && Math.abs(AP.x * BP.y - AP.y * BP.x) <= AB_length * Number.EPSILON; return ( dot_product_AP >= 0 && dot_product_BP <= 0 && Math.abs(AP.x * BP.y - AP.y * BP.x) <= 0.01 ); } clonePoint(p1, p2) { p1.x = p2.x; p1.y = p2.y; } clonePoints(points1, points2) { for (let i = 0; i < points1.length; ++i) { this.clonePoint(points1[i], points2[i]); } } equalPoint(p1, p2) { if (p1.x == p2.x && p1.y == p2.y) { return true; } else { return false; } } equalPoints(points1, points2) { if (points1.length != points2.length) { return false; } for (let i = 0; i < points1.length; ++i) { let flag = this.equalPoint(points1[i], points2[i]); if (!flag) { return false; } } return true; } equalJSON(json1, json2) { for (let key in json1) { if (json2.hasOwnProperty(key) && json1[key] == json2[key]) { continue; } else { return false; } } for (let key in json2) { if (json1.hasOwnProperty(key) && json1[key] == json2[key]) { continue; } else { return false; } } return true; } crossTwoLines(point1, point2, point3, point4, dis) { if (typeof dis == "undefined") { dis = Constant.minAdsorbPix; } const join = this.getIntersectionPoint2(point1, point2, point3, point4); if (join != null) { if ( this.getDistance(point1, join) > dis && this.getDistance(point2, join) > dis && this.getDistance(point3, join) > dis && this.getDistance(point4, join) > dis ) { if ( this.getDistance(point1, join) < this.getDistance(point1, point2) && this.getDistance(point2, join) < this.getDistance(point1, point2) && this.getDistance(point3, join) < this.getDistance(point3, point4) && this.getDistance(point4, join) < this.getDistance(point3, point4) ) { return true; } else { return false; } } } else { if ( this.PointInSegment(point1, point3, point4) || this.PointInSegment(point2, point3, point4) ) { return true; } } return false; } getDisPointsLine(line, point, distance1, distance2) { const newpoint1 = {}; const newpoint2 = {}; const result = {}; if (line.hasOwnProperty("x")) { newpoint1.x = line.x; newpoint1.y = point.y - distance1; newpoint2.x = line.x; newpoint2.y = point.y + distance2; } else if (line.hasOwnProperty("y")) { newpoint1.y = line.y; newpoint1.x = point.x - distance1; newpoint2.y = line.y; newpoint2.x = point.x + distance2; } else { const a = Math.atan(line.a); const t_line = { a: -1 / line.a }; const line_ab2 = this.createLine3(t_line, point); const join = this.getIntersectionPoint(line, line_ab2); newpoint1.x = join.x - distance1 * Math.cos(a); newpoint1.y = join.y - distance1 * Math.sin(a); newpoint2.x = join.x + distance2 * Math.cos(a); newpoint2.y = join.y + distance2 * Math.sin(a); } result.newpoint1 = newpoint1; result.newpoint2 = newpoint2; return result; } getBoundingBox(points) { let minX = points[0].x; let maxX = points[0].x; let minY = points[0].y; let maxY = points[0].y; for (let i = 1; i < points.length; ++i) { const point = points[i]; if (minX > point.x) { minX = point.x; } if (minY > point.y) { minY = point.y; } if (maxX < point.x) { maxX = point.x; } if (maxY < point.y) { maxY = point.y; } } const box = {}; box.minX = minX; box.minY = minY; box.maxX = maxX; box.maxY = maxY; return box; } getBoundingBox2(points) { let minX = null; let maxX = null; let minY = null; let maxY = null; for (let key in points) { const point = points[key]; if (minX == null || minX > point.x) { minX = point.x; } if (minY == null || minY > point.y) { minY = point.y; } if (maxX == null || maxX < point.x) { maxX = point.x; } if (maxY == null || maxY < point.y) { maxY = point.y; } } const box = {}; box.minX = minX; box.minY = minY; box.maxX = maxX; box.maxY = maxY; return box; } ComputePolygonArea(points) { const point_num = points.length; if (point_num < 3) { return 0; } let s = points[0].y * (points[point_num - 1].x - points[1].x); for (let i = 1; i < point_num; ++i) s += points[i].y * (points[i - 1].x - points[(i + 1) % point_num].x); return Math.abs(s / 2.0); } // 获取多边形重心 getPolygonCore(points) { function Area(p0, p1, p2) { let area = 0.0; area = p0.x * p1.y + p1.x * p2.y + p2.x * p0.y - p1.x * p0.y - p2.x * p1.y - p0.x * p2.y; return area / 2; } let sum_x = 0; let sum_y = 0; let sum_area = 0; let p1 = points[1]; for (let i = 2; i < points.length; i++) { const p2 = points[i]; const area = Area(points[0], p1, p2); sum_area += area; sum_x += (points[0].x + p1.x + p2.x) * area; sum_y += (points[0].y + p1.y + p2.y) * area; p1 = p2; } const xx = sum_x / sum_area / 3; const yy = sum_y / sum_area / 3; return { x: xx, y: yy, }; } // points1是否在points2里 isPolyInPoly(points1, points2, minDis) { for (let i = 0; i < points1.length; ++i) { let flag = false; for (let j = 0; j < points2.length; ++j) { if (this.equalPoint(points1[i], points2[j])) { flag = true; break; } } if (!flag) { if (!this.isPointInPoly(points1[i], points2, minDis)) { return false; } } else { const nextIndex = i == points1.length - 1 ? 0 : i + 1; const mid = { x: (points1[i].x + points1[nextIndex].x) / 2, y: (points1[i].y + points1[nextIndex].y) / 2, }; if (!this.isPointInPoly(mid, points2, minDis)) { return false; } } } return true; } dotPoints(pt1, pt2, point1, point2) { let vt1 = {}; let vt2 = {}; vt1.start = {}; vt1.end = {}; vt1.start.x = 0; vt1.start.y = 0; vt1.end.x = pt2.x - pt1.x; vt1.end.y = pt2.y - pt1.y; vt2.start = {}; vt2.end = {}; vt2.start.x = 0; vt2.start.y = 0; vt2.end.x = point2.x - point1.x; vt2.end.y = point2.y - point1.y; let result = vt1.end.x * vt2.end.x + vt1.end.y * vt2.end.y; return result; } //start是起点,target是朝着目标移动,distance是移动的距离 translate(start, target, point, distance) { let dx = target.x - start.x; let dy = target.y - start.y; let dis = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); let result = { x: point.x + (dx * distance) / dis, y: point.y + (dy * distance) / dis, }; return result; } //射线与线段相交 // intersection(rayStart, rayEnd, segmentStart, segmentEnd) { // // 计算射线和线段的方向向量 // const rayDirection = { // x:rayEnd.x - rayStart.x, // y:rayEnd.y - rayStart.y, // }; // const segmentDirection = { // x:segmentEnd.x - segmentStart.x, // y:segmentEnd.y - segmentStart.y, // }; // // 计算射线和线段的起点之间的向量 // const startPointVector = { // x:rayStart.x - segmentStart.x, // y:rayStart.y - segmentStart.y, // }; // // 计算射线和线段的叉积 // const crossProduct = rayDirection.x * segmentDirection.y - rayDirection.y * segmentDirection.x; // // 如果叉积为0,则表示射线和线段平行 // if (crossProduct === 0) { // return null; // } // // 计算线段起点到射线的交点的向量 // const t = (startPointVector.x * segmentDirection.y - startPointVector.y * segmentDirection.x) / crossProduct; // // 如果t的值小于0,则交点在射线的起点之后 // if (t < 0) { // return null; // } // // 计算交点的坐标 // const intersectionX = rayStart.x + t * rayDirection.x; // const intersectionY = rayStart.y + t * rayDirection.y; // // 如果交点在线段的范围内,则返回交点坐标 // if ((intersectionX >= Math.min(segmentStart.x, segmentEnd.x)) && // (intersectionX <= Math.max(segmentStart.x, segmentEnd.x)) && // (intersectionY >= Math.min(segmentStart.y, segmentEnd.y)) && // (intersectionY <= Math.max(segmentStart.y, segmentEnd.y))) { // //return [intersectionX, intersectionY]; // return { // x:intersectionX, // y:intersectionY, // }; // } // // 否则返回null // return null; // } // raySegmentIntersection(rayOrigin, rayDirection, segmentStart, segmentEnd) { // // 计算射线和线段的交点 // const x1 = rayOrigin.x // const y1 = rayOrigin.y // const x2 = segmentStart.x // const y2 = segmentStart.y // const dx1 = rayDirection.x // const dy1 = rayDirection.y // const dx2 = segmentEnd.x - x2 // const dy2 = segmentEnd.y - y2 // const crossProduct = dx1 * dy2 - dx2 * dy1 // if (Math.abs(crossProduct) < 1e-8) { // // 射线和线段平行或共线 // return null // } // const t1 = (dx2 * (y1 - y2) - dy2 * (x1 - x2)) / crossProduct // const t2 = (dx1 * (y1 - y2) - dy1 * (x1 - x2)) / crossProduct // if (t1 >= 0 && t2 >= 0 && t2 <= 1) { // // 有交点,计算交点坐标 // const intersectionX = x1 + t1 * dx1 // const intersectionY = y1 + t1 * dy1 // return { // x: intersectionX, // y: intersectionY, // } // } else { // // 没有交点 // return null // } // } raySegmentIntersection(rayOrigin, rayDirection, segmentStart, segmentEnd) { const end = { x: rayOrigin.x + rayDirection.x, y: rayOrigin.y - rayDirection.z, }; const line = this.createLine1(rayOrigin, end); const join = this.getIntersectionPoint4(segmentStart, segmentEnd, line); if (join == null) { return null; } else { const dis = this.getDistance(end, join); const dis1 = this.getDistance(rayOrigin, join); const dis2 = this.getDistance(rayOrigin, end); if (dis - (dis1 + dis2) + 0.01 > 0) { return null; } else { return join; } } } RectangleVertex(startPoint, endPoint, width) { let line = this.createLine1(startPoint, endPoint); let lines = this.getParallelLineForDistance(line, width / 2); let leftEdgeStart, rightEdgeStart, rightEdgeEnd, leftEdgeEnd; let point = null; let points = []; //先计算start部分 point = startPoint; points.push(endPoint); points.push(startPoint); let point1 = this.getJoinLinePoint(point, lines.line1); let point2 = this.getJoinLinePoint(point, lines.line2); points[2] = point1; if (this.isClockwise(points)) { rightEdgeStart = point1; leftEdgeStart = point2; } else { rightEdgeStart = point2; leftEdgeStart = point1; } //再计算end部分 points = []; point = endPoint; points.push(startPoint); points.push(endPoint); point1 = this.getJoinLinePoint(point, lines.line1); point2 = this.getJoinLinePoint(point, lines.line2); points[2] = point1; if (this.isClockwise(points)) { rightEdgeEnd = point2; leftEdgeEnd = point1; } else { rightEdgeEnd = point1; leftEdgeEnd = point2; } return { leftEdgeStart: leftEdgeStart, rightEdgeStart: rightEdgeStart, rightEdgeEnd: rightEdgeEnd, leftEdgeEnd: leftEdgeEnd, }; } //start到end的射线中取一点point,start-end和end-point的距离相同 getPositionForExtendedLine(start, end) { const dx = end.x - start.x; const dy = end.y - start.y; const point = { x: end.x + dx, y: end.y + dy, }; return point; } isOnRay(start, dir, position) { const v1 = { x: dir.x - start.x, y: dir.y - start.y }; const v2 = { x: position.x - start.x, y: position.y - start.y }; return v1.x * v2.y - v1.y * v2.x; } //向量是否同样的方向 isSameDirForVector(point1, point2, p1, p2) { const v1 = { x: point2.x - point1.x, y: point2.y - point1.y, }; const v2 = { x: p2.x - p1.x, y: p2.y - p1.y, }; const value = this.dot(v1, v2); if (value > 0) { return true; } { return false; } } //生成五角星 createFivePointedStar(position, r) { let deg = Math.PI / 180; //角度 let points = []; points[0] = { x: position.x - r * Math.cos(54 * deg), y: position.y + r * Math.sin(54 * deg), }; points[1] = { x: position.x, y: position.y - r, }; points[2] = { x: position.x + r * Math.cos(54 * deg), y: position.y + r * Math.sin(54 * deg), }; points[3] = { x: position.x - r * Math.cos(18 * deg), y: position.y - r * Math.sin(18 * deg), }; points[4] = { x: position.x + r * Math.cos(18 * deg), y: position.y - r * Math.sin(18 * deg), }; return points; } createSixPoint(position, r) { let deg = Math.PI / 180; //角度 let points = []; points[0] = { x: position.x, y: position.y + r, }; points[1] = { x: position.x + r * Math.sin(60 * deg), y: position.y + r * Math.cos(60 * deg), }; points[2] = { x: position.x + r * Math.cos(30 * deg), y: position.y - r * Math.sin(30 * deg), }; points[3] = { x: position.x, y: position.y - r, }; points[4] = { x: position.x - r * Math.cos(30 * deg), y: position.y - r * Math.sin(30 * deg), }; points[5] = { x: position.x - r * Math.sin(60 * deg), y: position.y + r * Math.cos(60 * deg), }; return points; } //求圆和直线之间的交点 /** * 求圆和直线之间的交点 * 直线方程:y = kx + b * 圆的方程:(x - m)² + (x - n)² = r² * x1, y1 = 线坐标1, x2, y2 = 线坐标2, m, n = 圆坐标, r = 半径 */ getInsertPointBetweenCircleAndLine(x1, y1, x2, y2, m, n, radius) { let insertPoints = []; if (Math.abs(x1 - x2) < 0.5) { insertPoints[0] = { x: x1, y: n - Math.sqrt(radius * radius - Math.pow(x1 - m, 2)), }; insertPoints[1] = { x: x1, y: n + Math.sqrt(radius * radius - Math.pow(x1 - m, 2)), }; return insertPoints; } // console.log(x1, y1, x2, y2, m, n, radius) let kbArr = this.binaryEquationGetKB(x1, y1, x2, y2); let k = kbArr[0]; let b = kbArr[1]; let aX = 1 + k * k; let bX = 2 * k * (b - n) - 2 * m; let cX = m * m + (b - n) * (b - n) - radius * radius; let xArr = this.quadEquationGetX(aX, bX, cX); xArr.forEach((x) => { let y = k * x + b; insertPoints.push({ x: x, y: y }); }); return insertPoints; } /** * 求二元一次方程的系数 * y1 = k * x1 + b => k = (y1 - b) / x1 * y2 = k * x2 + b => y2 = ((y1 - b) / x1) * x2 + b */ binaryEquationGetKB(x1, y1, x2, y2) { let k = (y1 - y2) / (x1 - x2); let b = (x1 * y2 - x2 * y1) / (x1 - x2); return [k, b]; } /** * 一元二次方程求根 * ax² + bx + c = 0 */ quadEquationGetX(a, b, c) { let xArr = []; let result = Math.pow(b, 2) - 4 * a * c; if (result > 0) { xArr.push((-b + Math.sqrt(result)) / (2 * a)); xArr.push((-b - Math.sqrt(result)) / (2 * a)); } //else if (result == 0) { else { xArr.push(-b / (2 * a)); } return xArr; } angleTo(v1, v2) { const denominator = Math.sqrt(this.lengthSq(v1) * this.lengthSq(v2)); if (denominator === 0) return 90; const theta = this.dot(v1, v2) / denominator; //return Math.acos(this.clamp(theta, -1, 1)); return (Math.acos(this.clamp(theta, -1, 1)) / Math.PI) * 180; } //点乘 dot(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } //叉乘 cross(v1, v2) { return v1.x * v2.y - v1.y * v2.x; } // 两点相减 pointMinus(v1, v2) { return { x: v1.x - v2.x, y: v1.y - v2.y, }; } // 两点相加 pointPlus(v1, v2) { return { x: v1.x + v2.x, y: v1.y + v2.y, }; } // 中心点 lineCenter(v1, v2) { const point = this.pointPlus(v1, v2); return { x: point.x / 2, y: point.y / 2, }; } // 点放大 pointScale(v, a) { return { x: v.x * a, y: v.y * a, }; } clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } lengthSq(v) { return v.x * v.x + v.y * v.y; } // 当前点 下一个点 下下个点 getCurvesControls(p1, pt, p2, scale = 0.3) { const vec1T = this.pointMinus(p1, pt); const vecT2 = this.pointMinus(p1, pt); const len1 = Math.hypot(vec1T.x, vec1T.y); const len2 = Math.hypot(vecT2.x, vecT2.y); const v = len1 / len2; let delta; if (v > 1) { delta = this.pointMinus( p1, this.pointPlus(pt, this.pointScale(this.pointMinus(p2, pt), 1 / v)) ); } else { delta = this.pointMinus( this.pointPlus(pt, this.pointScale(this.pointMinus(p1, pt), v)), p2 ); } delta = this.pointScale(delta, scale); const control1 = { x: this.pointPlus(pt, delta).x, y: this.pointPlus(pt, delta).y, }; const control2 = { x: this.pointMinus(pt, delta).x, y: this.pointMinus(pt, delta).y, }; return { control1, control2 }; } getCurvesByPoints(points, scale = 0.2) { const curves = []; let preControl1, preControl2; for (let i = 0; i < points.length - 2; i++) { const { control1, control2 } = this.getCurvesControls( points[i], points[i + 1], points[i + 2], scale ); curves.push({ start: points[i], end: points[i + 1], controls: i === 0 ? [control1] : [preControl2, control1], }); preControl1 = control1; preControl2 = control2; } curves.push({ start: points[points.length - 2], controls: [preControl2], end: points[points.length - 1], }); return curves; } /** * 已知四个控制点,及曲线中的某一个点的 x/y,反推求 t * @param {number} x1 起点 x/y * @param {number} x2 控制点1 x/y * @param {number} x3 控制点2 x/y * @param {number} x4 终点 x/y * @param {number} X 曲线中的某个点 x/y * @returns {number[]} t[] */ getThreeBezierT(x1, x2, x3, x4, X) { const a = -x1 + 3 * x2 - 3 * x3 + x4; const b = 3 * x1 - 6 * x2 + 3 * x3; const c = -3 * x1 + 3 * x2; const d = x1 - X; // 盛金公式, 预先需满足, a !== 0 // 判别式 const A = Math.pow(b, 2) - 3 * a * c; const B = b * c - 9 * a * d; const C = Math.pow(c, 2) - 3 * b * d; const delta = Math.pow(B, 2) - 4 * A * C; let t1 = -100, t2 = -100, t3 = -100; // 3个相同实数根 if (A === B && A === 0) { t1 = -b / (3 * a); t2 = -c / b; t3 = (-3 * d) / c; return [t1, t2, t3]; } // 1个实数根和1对共轭复数根 if (delta > 0) { const v = Math.pow(B, 2) - 4 * A * C; const xsv = v < 0 ? -1 : 1; const m1 = A * b + (3 * a * (-B + (v * xsv) ** (1 / 2) * xsv)) / 2; const m2 = A * b + (3 * a * (-B - (v * xsv) ** (1 / 2) * xsv)) / 2; const xs1 = m1 < 0 ? -1 : 1; const xs2 = m2 < 0 ? -1 : 1; t1 = (-b - (m1 * xs1) ** (1 / 3) * xs1 - (m2 * xs2) ** (1 / 3) * xs2) / (3 * a); // 涉及虚数,可不考虑。i ** 2 = -1 } // 3个实数根 if (delta === 0) { const K = B / A; t1 = -b / a + K; t2 = t3 = -K / 2; } // 3个不相等实数根 if (delta < 0) { const xsA = A < 0 ? -1 : 1; const T = (2 * A * b - 3 * a * B) / (2 * (A * xsA) ** (3 / 2) * xsA); const theta = Math.acos(T); if (A > 0 && T < 1 && T > -1) { t1 = (-b - 2 * A ** (1 / 2) * Math.cos(theta / 3)) / (3 * a); t2 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) + 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a); t3 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) - 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a); } } return [t1, t2, t3]; } /** * @desc 获取三阶贝塞尔曲线的线上坐标 * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1] * @param {number} t 当前百分比 * @param {Array} p1 起点坐标 * @param {Array} p2 终点坐标 * @param {Array} cp1 控制点1 * @param {Array} cp2 控制点2 */ getThreeBezierPoint(t, p1, cp1, cp2, p2) { const { x: x1, y: y1 } = p1; const { x: x2, y: y2 } = p2; const { x: cx1, y: cy1 } = cp1; const { x: cx2, y: cy2 } = cp2; const x = x1 * (1 - t) * (1 - t) * (1 - t) + 3 * cx1 * t * (1 - t) * (1 - t) + 3 * cx2 * t * t * (1 - t) + x2 * t * t * t; const y = y1 * (1 - t) * (1 - t) * (1 - t) + 3 * cy1 * t * (1 - t) * (1 - t) + 3 * cy2 * t * t * (1 - t) + y2 * t * t * t; return { x, y }; } getHitInfoForThreeBezier(position, curve, rang = 3) { // 定义三次贝塞尔曲线的控制点和目标点 var p0 = curve.start; var p1 = curve.controls[0]; var p2 = curve.controls[1]; var p3 = curve.end; var target = position; // 参数化方式在曲线上取一系列的点 var pointsOnCurve = []; for (var t = 0; t <= 1; t += 0.01) { var x = Math.pow(1 - t, 3) * p0.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * Math.pow(t, 2) * p2.x + Math.pow(t, 3) * p3.x; var y = Math.pow(1 - t, 3) * p0.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * Math.pow(t, 2) * p2.y + Math.pow(t, 3) * p3.y; pointsOnCurve.push({ x: x, y: y }); } // 计算每个点与目标点的距离 var shortestDistance = Number.MAX_VALUE; var closestPoint; for (var i = 0; i < pointsOnCurve.length; i++) { var distance = Math.sqrt( Math.pow(pointsOnCurve[i].x - target.x, 2) + Math.pow(pointsOnCurve[i].y - target.y, 2) ); if (distance < shortestDistance) { shortestDistance = distance; closestPoint = pointsOnCurve[i]; } } return { position: closestPoint, distance: shortestDistance, }; console.log("最短距离:", shortestDistance); console.log("最近点:", closestPoint); const { x: offsetX, y: offsetY } = position; let results = []; // 用 x 求出对应的 t,用 t 求相应位置的 y,再比较得出的 y 与 offsetY 之间的差值 const tsx = this.getThreeBezierT( curve.start.x, curve.controls[0].x, curve.controls[1].x, curve.end.x, offsetX ); console.log(tsx); for (let x = 0; x < 3; x++) { if (tsx[x] <= 1 && tsx[x] >= 0) { const point = this.getThreeBezierPoint( tsx[x], curve.start, curve.controls[0], curve.controls[1], curve.end ); // if (Math.abs(point.y - offsetY) < rang) { results.push({ position: point, distance: this.getDistance(point, position), }); // } } } // 如果上述没有结果,则用 y 求出对应的 t,再用 t 求出对应的 x,与 offsetX 进行匹配 const tsy = this.getThreeBezierT( curve.start.y, curve.controls[0].y, curve.controls[1].y, curve.end.y, offsetY ); for (let y = 0; y < 3; y++) { if (tsy[y] <= 1 && tsy[y] >= 0) { const point = this.getThreeBezierPoint( tsy[y], curve.start, curve.controls[0], curve.controls[1], curve.end ); // if (Math.abs(point.x - offsetX) < rang) { results.push({ position: point, distance: this.getDistance(point, position), }); // } } } console.log(results); return results.sort((a, b) => a.distance - b.distance)[0]; } // 二次曲线 getHitInfoForTwoBezier(position, curve) { let bezierData = []; bezierData.push(curve.start.x); bezierData.push(curve.start.y); bezierData.push(curve.controls[0].x); bezierData.push(curve.controls[0].y); bezierData.push(curve.end.x); bezierData.push(curve.end.y); const { isHit, getInfo } = bezierUtil.measureBezier(...bezierData); const { point } = getInfo(position); return { position: { x: point[0], y: point[1], }, distance: this.getDistance(position, { x: point[0], y: point[1], }), }; } getHitInfoForCurves(pos, curves, roadWidth) { let joinInfo; for (const curve of curves) { const tempJoinInfo = curve.controls.length === 2 ? this.getHitInfoForThreeBezier(pos, curve, roadWidth / 2) : this.getHitInfoForTwoBezier(pos, curve); if ( !joinInfo || (tempJoinInfo && tempJoinInfo.distance < joinInfo.distance) ) { joinInfo = tempJoinInfo; } } return joinInfo; } getHitInfoForCurve(pos, curve, roadWidth) { let joinInfo; const tempJoinInfo = curve.controls.length === 2 ? this.getHitInfoForThreeBezier(pos, curve, roadWidth / 2) : this.getHitInfoForTwoBezier(pos, curve); if ( !joinInfo || (tempJoinInfo && tempJoinInfo.distance < joinInfo.distance) ) { joinInfo = tempJoinInfo; } return joinInfo; } getIndexForCurvesPoints(position, points) { let minDis = null; let minDisToPoint = null; let minPointIndex = -1; let index = -1; for (let i = 0; i < points.length - 1; ++i) { const line = this.createLine1(points[i], points[i + 1]); const join = this.getJoinLinePoint(position, line); const dis = this.getDistance(position, join); if (this.isContainForSegment(join, points[i], points[i + 1])) { if (minDis == null || minDis > dis) { minDis = dis; index = i + 1; } } if (minDisToPoint == null) { minDisToPoint = mathUtil.getDistance(position, points[i]); minPointIndex = i; } else if (minDisToPoint > mathUtil.getDistance(position, points[i])) { minDisToPoint = mathUtil.getDistance(position, points[i]); minPointIndex = i; } } if (index == -1) { if ( minDisToPoint > mathUtil.getDistance(position, points[points.length - 1]) ) { return points.length; } else { return minPointIndex; } } else { return index; } } getCurvesIndexForCurvesPoints(position, points) { let minDis = null; let minDisToPoint = null; let minPointIndex = -1; let index = -1; for (let i = 0; i < points.length - 1; ++i) { const line = this.createLine1(points[i], points[i + 1]); const join = this.getJoinLinePoint(position, line); const dis = this.getDistance(position, join); if (this.isContainForSegment(join, points[i], points[i + 1])) { if (minDis == null || minDis > dis) { minDis = dis; index = i; } } if (minDisToPoint == null) { minDisToPoint = mathUtil.getDistance(position, points[i]); minPointIndex = i; } else if (minDisToPoint > mathUtil.getDistance(position, points[i])) { minDisToPoint = mathUtil.getDistance(position, points[i]); minPointIndex = i; } } if ((index = -1)) { if ( minDisToPoint > mathUtil.getDistance(position, points[points.length - 1]) ) { return points.length - 2; } else { return minPointIndex; } } else { return index; } } // //获取一组点的偏移 // getOffset(points, leftWidth, rightWidth, dir) { // //斜边长度d已知,角度angle已知 // //对边长度就是y的偏移量 就是 d * sin(angle) ==> d * Math.sin(angle * Math.PI / 180) // //邻边长度就是x的偏移量 就是 d * cos(angle) ==> d * Math.cos(angle * Math.PI / 180) // let result = {}; // if (dir == "left" || !dir) { // let angle = 90; // let d = leftWidth; // result.leftEdgePoints = points.map((coords) => { // let ox = d * Math.cos((angle * Math.PI) / 180); // let oy = d * Math.sin((angle * Math.PI) / 180); // return { // x: coords.x + ox, // y: coords.y + oy, // }; // }); // } // if (dir == "right" || !dir) { // let angle = -90; // let d = rightWidth; // result.rightEdgePoints = points.map((coords) => { // let ox = d * Math.cos((angle * Math.PI) / 180); // let oy = d * Math.sin((angle * Math.PI) / 180); // return { // x: coords.x + ox, // y: coords.y + oy, // }; // }); // } // return result; // } getOffset(points, leftWidth, rightWidth, dir) { let leftEdgePoints = []; let rightEdgePoints = []; for (let i = 0; i < points.length - 1; ++i) { if (dir == "left" || !dir) { let leftEdgePoins1 = this.RectangleVertex( points[i], points[i + 1], leftWidth * 2 ); let leftLine1 = mathUtil.createLine1( leftEdgePoins1.leftEdgeStart, leftEdgePoins1.leftEdgeEnd ); if (i != points.length - 2) { let leftEdgePoins2 = this.RectangleVertex( points[i + 1], points[i + 2], leftWidth * 2 ); let leftLine2 = mathUtil.createLine1( leftEdgePoins2.leftEdgeStart, leftEdgePoins2.leftEdgeEnd ); let join = mathUtil.getIntersectionPoint(leftLine1, leftLine2); if (join != null) { leftEdgePoints[i + 1] = join; } else { leftEdgePoints[i + 1] = mathUtil.getJoinLinePoint( points[i + 1], leftLine1 ); } } else { leftEdgePoints[i + 1] = mathUtil.getJoinLinePoint( points[i + 1], leftLine1 ); } if (!leftEdgePoints[0]) { leftEdgePoints[0] = mathUtil.getJoinLinePoint(points[0], leftLine1); } } if (dir == "right" || !dir) { let rightEdgePoins1 = this.RectangleVertex( points[i], points[i + 1], rightWidth * 2 ); let rightLine1 = mathUtil.createLine1( rightEdgePoins1.rightEdgeStart, rightEdgePoins1.rightEdgeEnd ); if (i != points.length - 2) { let rightEdgePoins2 = this.RectangleVertex( points[i + 1], points[i + 2], rightWidth * 2 ); let rightLine2 = mathUtil.createLine1( rightEdgePoins2.rightEdgeStart, rightEdgePoins2.rightEdgeEnd ); let join = mathUtil.getIntersectionPoint(rightLine1, rightLine2); if (join != null) { rightEdgePoints[i + 1] = join; } else { rightEdgePoints[i + 1] = mathUtil.getJoinLinePoint( points[i + 1], rightLine1 ); } } else { rightEdgePoints[i + 1] = mathUtil.getJoinLinePoint( points[i + 1], rightLine1 ); } if (!rightEdgePoints[0]) { rightEdgePoints[0] = mathUtil.getJoinLinePoint(points[0], rightLine1); } } } return { leftEdgePoints: leftEdgePoints, rightEdgePoints: rightEdgePoints, }; } twoOrderBezier(t, p1, cp, p2) { //参数分别是t,起始点,控制点和终点 var x1 = p1.x; var y1 = p1.y; var cx = cp.x; var cy = cp.y; var x2 = p2.x; var y2 = p2.y; // var [x1, y1] = p1, // [cx, cy] = cp, // [x2, y2] = p2; var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2, y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2; return { x: x, y: y, }; } //t是0.5,求cp。p是曲线上的点 twoOrderBezier2(t, p1, p, p2) { var x1 = p1.x; var y1 = p1.y; var x2 = p2.x; var y2 = p2.y; let cx = (p.x - t * t * x2 - (1 - t) * (1 - t) * x1) / (2 * t * (1 - t)); let cy = (p.y - t * t * y2 - (1 - t) * (1 - t) * y1) / (2 * t * (1 - t)); return { x: cx, y: cy, }; } rgb() { //rgb颜色随机 const r = Math.floor(Math.random() * 256); const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); return `rgb(${r},${g},${b})`; } } const mathUtil = new MathUtil(); export { mathUtil };