记录几何计算常用工具函数,如两点之间的距离,直线的斜率倾角和方程,三角形内角等等
几何计算方法列表
-
判断两个浮点数是否相等
-
计算两个点的中点
-
计算两个点组成直线的斜率
-
弧度转化为角度
-
角度转化为弧度
-
计算两点组成的方程 y = kx + b
-
计算线相对于x正半轴的弧度
-
计算两点之间的距离
-
计算一个点相对另一个点旋转一定角度后的坐标
-
线段转化为起始点
-
起始点转化为线段
-
点到直线距离
-
点在线的哪一边
-
垂直平分线方程(y = kx + b)
-
求两直线交点
-
求两直线交点(根据方程计算)
-
求两线段交点
-
计算三角形内角
-
计算在一个角的两条边上距离角点固定距离的两个点。(可用来作为画角度圆弧、贝塞尔曲线画圆角的点)
-
计算一个角的角平分线上距离角点固定距离的点。(可用来作为显示角的角度数值的点)
-
判断弧线是否要翻转
-
计算一个角的角平分线方程 (y = kx + b)
-
射线法判断点是否在多边形内
-
把目标矩形区域内的点转换到距离矩形四边一定内边距的范围内(可用于svg绘图strokeWidth被截取、周边留出安全距离等)
-
获取固定长线段起始点坐标和角度弧度
-
用平滑曲线连接多个点
计算三角形内角
- 利用三角形三个点的坐标计算三个角的角度
- 演示
- 代码
/**
* 计算三角形内角
*
* @param A - 点A
* @param B - 点B
* @param C - 点C
* @returns a、b、c三个角度
*/
export const angleOfTriangle = (A: Point, B: Point, C: Point) => {
const angle = (radian: number) => (180 * radian) / Math.PI;
const a = distance(B, C);
const b = distance(A, C);
const c = distance(A, B);
const angleA = Math.acos((b * b + c * c - a * a) / (2 * b * c));
const angleB = Math.acos((a * a + c * c - b * b) / (2 * a * c));
const angleC = Math.acos((a * a + b * b - c * c) / (2 * a * b));
return {
a: Number.isNaN(angleA) ? 180 : angle(angleA),
b: Number.isNaN(angleB) ? 180 : angle(angleB),
c: Number.isNaN(angleC) ? 180 : angle(angleC),
};
};
射线法判断点是否在多边形内
- 射线法判断点是否在多边形内,可以用于显示多边形的角度数值、角度圆弧等等
- 参考:[射线法]判断一个点是否在多边形内部
- 演示
- 代码
/**
* 射线法判断点是否在多边形内
*
* @param points - 多边形的端点坐标数组
* @param p - 带测试是否在多边形内的一个点
* @returns 是否在多边形内
*/
export const pointInPolygon = (points: Point[], p: Point) => {
let intersectCount = 0;
for (let i = 0; i < points.length; i += 1) {
const p1 = points[i];
const p2 = points[i === points.length - 1 ? 0 : i + 1];
const k = (p2.y - p1.y) / (p2.x - p1.x);
const b = p1.y - k * p1.x;
const x_ok = Math.min(p1.x, p2.x) <= p.x && p.x <= Math.max(p1.x, p2.x);
const y_ok = k * p.x + b > p.y;
intersectCount += x_ok && y_ok ? 1 : 0;
}
return intersectCount % 2 === 1;
};
用平滑曲线连接多个点
- 利用三阶贝塞尔曲线拟合,平滑连接多个点。核心点在于计算贝塞尔曲线的控制点
- 参考:纯Javascript实现平滑曲线生成
- 演示
- 代码
/**
* 计算三点的贝塞尔曲线控制点
* @param param0
* @param rate 曲率
* @returns
*/
export const getControlPoints = ([start, center, end]: Point[], rate = 0.4) => {
const equ1 = angleBisectorEquation(start, center, end);
const k = -1 / equ1.k;
const b = center.y - k * center.x;
const equ2 = { k, b, x: 0 } as any;
const dis = distance(center, end) * rate;
const sita = angleToRadian(angleOfTriangle(start, center, end).b);
const d = dis * Math.sin(sita / 2);
const inside = true;
const control1 = disPointOnLineByEquation(equ2, center, d, inside);
const control2 = disPointOnLineByEquation(equ2, center, d, !inside);
if (pointOnLineByEquation(start, equ1) !== pointOnLineByEquation(control1, equ1)) {
return [{ ...control2, color: 'red' } as any, control1, [equ2]];
}
return [{ ...control1, color: 'red' } as any, control2, [equ2]];
};
/**
* 拟合曲线
* @param points
* @returns path
*/
export const fittingCurve = (points: Point[], rate = 0.4) => {
if (points.length < 3) {
return '';
}
let path = `M${points[0].x},${points[0].y} `;
const controlPoints: Point[] = [];
for (let i = 1; i < points.length - 1; i += 1) {
const [control1, control2] = getControlPoints([points[i - 1], points[i], points[i + 1]], rate);
controlPoints.push(control1, control2);
}
const ps = points.map((p: Point) => ({ x: p.x.toFixed(1), y: p.y.toFixed(1) }));
const c = controlPoints.map((p: Point) => ({ x: p.x.toFixed(1), y: p.y.toFixed(1) }));
for (let i = 0; i < ps.length - 1; i += 1) {
const n = i * 2 - 1;
if (i === 0) {
path += ` Q${c[i].x},${c[i].y}`;
} else if (i === ps.length - 2) {
path += ` Q${c[n].x},${c[n].y}`;
} else {
path += ` C${c[n].x},${c[n].y} ${c[n + 1].x},${c[n + 1].y}`;
}
path += ` ${ps[i + 1].x},${ps[i + 1].y}`;
}
return path;
};
一、工具方法
import type { Point, Line, Equation } from './interface';
export * from './interface';
/** ******常用数学几何相关的计算方法**************************************************** */
/**
* 判断两个浮点数是否相等
*
* @param a - 数字a
* @param b - 数字b
* @returns 是否相等
*/
export const floatEqual = (a: number, b: number) => Math.abs(a - b) < 1e-5;
/** ******点、线相关**************************************************** */
/**
* 计算两个点的中点
*
* @param p1 - 线端点的坐标
* @param p2 - 线端点的坐标
* @returns 中点的坐标
*/
export const midpoint = (p1: Point, p2: Point) => {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
} as Point;
};
/**
* 计算两个点组成直线的斜率
*
* @param p1 - 线起点的坐标
* @param p2 - 线终点的坐标
* @returns 斜率数值
*/
export const lineSlope = (p1: Point, p2: Point) => {
return Math.abs(p2.x - p1.x) === 0 ? Infinity : (p2.y - p1.y) / (p2.x - p1.x);
};
/**
* 弧度转化为角度
*
* @param radian - 弧度值
* @returns 角度值
*/
export const radianToAngle = (radian: number) => (180 * radian) / Math.PI;
/**
* 弧度转化为角度
*
* @param angle - 弧度值
* @returns 角度值
*/
export const angleToRadian = (angle: number) => (Math.PI * angle) / 180;
/**
* 计算两点组成的方程 y = kx + b
*
* @param p1 - 线上一点的坐标
* @param p2 - 线上另一点的坐标
* @returns 方程的参数 k 和 b
*/
export const lineEquation = (p1: Point, p2: Point) => {
const k = lineSlope(p1, p2);
return {
k,
b: p1.y - k * p1.x,
x: k === Infinity ? p1.x : NaN,
} as Equation;
};
/**
* 计算线相对于x正半轴的弧度
*
* @param start - 线上一点的坐标
* @param end - 线上另一点的坐标
* @returns 直线倾角
*/
export const lineRadian = (start: Point, end: Point, angle?: number) => {
const k = lineSlope(start, end);
let sita = Math.atan(k);
if (end.x - start.x < 0) {
sita += Math.PI;
}
if (angle) {
let degree = radianToAngle(sita);
// sita -= sita % radian < radian / 2 ? sita % radian : (sita % radian) - radian; // 角度吸附
degree = Math.round(degree / angle) * angle;
sita = angleToRadian(degree);
}
return sita;
};
/**
* 计算线相对于x正半轴的角度
*
* @param start - 线上一点的坐标
* @param end - 线上另一点的坐标
* @returns 直线倾角
*/
export const lineAngle = (start: Point, end: Point, angle?: number) => {
return radianToAngle(lineRadian(start, end, angle));
};
/**
* 计算两点之间的距离
*
* @param p1 - 线端点的坐标
* @param p2 - 线端点的坐标
* @returns 距离
*/
export const distance = (p1: Point, p2: Point) => {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
};
/**
* 计算一个点相对另一个点旋转一定角度后的坐标
*
* @param point - 目标点坐标
* @param deg - 角度
* @param o - 旋转中心点坐标
* @returns 旋转后的点坐标
*/
export const rotate = (point: Point, deg: number, o: Point = { x: 0, y: 0 }) => {
const dis = distance(point, o);
const k = (point.y - o.y) / (point.x - o.x);
let sitaA = Math.atan(k);
if (Math.abs(point.x - o.x) === 0) {
sitaA = (Math.PI / 2) * (point.y - o.y > 0 ? 1 : -1);
}
if (point.x - o.x < 0) {
sitaA -= Math.PI;
}
const sitaO = sitaA + (Math.PI * deg) / 180;
return {
x: o.x + dis * Math.cos(sitaO),
y: o.y + dis * Math.sin(sitaO),
} as Point;
};
/**
* 线段转化为起始点
*
* @param line - 线段
* @returns 起始点坐标
*/
export const lineToStartEndPoint = (line: Line) => {
const start: Point = { x: line.x1, y: line.y1 };
const end: Point = { x: line.x2, y: line.y2 };
return { start, end };
};
/**
* 起始点转化为线段
*
* @param start - 起点
* @param end - 终点
* @returns 起始点坐标
*/
export const startEndPointToLine = (start: Point, end: Point) => {
return {
x1: start.x,
y1: start.y,
x2: end.x,
y2: end.y,
} as Line;
};
/**
* 点在线的哪一边
*
* @param point - 点
* @param line - 线
* @returns 在直线上返回0,在直线下方返回-1,上方返回1
*/
export const pointOnLine = (point: Point, line: Line) => {
const { start, end } = lineToStartEndPoint(line);
const { k, b } = lineEquation(start, end);
if (k === Infinity) {
const diff = end.x - point.x;
// eslint-disable-next-line no-nested-ternary
return floatEqual(end.x, point.x) ? 0 : diff < 0 ? 1 : -1;
}
const dy = k * point.x + b;
const diff = dy - point.y;
// eslint-disable-next-line no-nested-ternary
return floatEqual(dy, point.y) ? 0 : diff > 0 ? 1 : -1;
};
/**
* 点在线的哪一边
*
* @param point - 点
* @param equation - 线方程
* @returns 在直线上返回0,在直线下方返回-1,上方返回1
*/
export const pointOnLineByEquation = (point: Point, equation: Equation) => {
const { k, b, x = 0 } = equation;
if (k === Infinity) {
const diff = x - point.x;
// eslint-disable-next-line no-nested-ternary
return floatEqual(x, point.x) ? 0 : diff < 0 ? 1 : -1;
}
const dy = k * point.x + b;
const diff = dy - point.y;
// eslint-disable-next-line no-nested-ternary
return floatEqual(dy, point.y) ? 0 : diff > 0 ? 1 : -1;
};
/**
* 求过一点垂直于一条直线的方程
* @param line 直线
* @param point
* @returns 垂涎方程
*/
export const verticalLineEquation = (line: Line, point: Point) => {
const { start, end } = lineToStartEndPoint(line);
const k1 = lineSlope(start, end);
const k = -1 / k1;
const b = point.y - k * point.x;
const verticalLine: Equation = { k, b };
if (Math.abs(k) === Infinity) {
verticalLine.x = point.x;
}
return verticalLine;
};
/**
* 求两直线交点
*
* @param l1 - 直线
* @param l2 - 直线
* @returns 两直线交点坐标,平行线返回null
*/
export const lineCrossPointByEquation = (l1: Equation, l2: Equation) => {
const { k: k1, b: b1 } = l1;
const { k: k2, b: b2 } = l2;
// 处理垂直于x轴的情况
if (typeof l1.x === 'number' && !Number.isNaN(l1.x)) {
return { x: l1.x, y: l2.k * l1.x + l2.b };
}
if (typeof l2.x === 'number' && !Number.isNaN(l2.x)) {
return { x: l2.x, y: l1.k * l2.x + l1.b };
}
if (Math.abs(k2 - k1) < 0.00001) {
return null;
}
const x = (b2 - b1) / (k1 - k2);
return {
x,
y: k1 * x + b1,
} as Point;
};
/**
* 求过一点垂直于一条直线的垂点
* @param line 直线
* @param point 点
* @returns 垂点坐标
*/
export const verticalLinePoint = (line: Line, point: Point) => {
const { start, end } = lineToStartEndPoint(line);
const lineEqua = lineEquation(start, end);
const verticalLineEqua = verticalLineEquation(line, point);
const verticalPoint = lineCrossPointByEquation(lineEqua, verticalLineEqua);
return verticalPoint;
};
/**
* 垂直平分线方程(y = kx + b)
*
* @param start - 起点
* @param end - 中点
* @returns 垂直平分线方程参数k b
*/
export const verticalBisectorEquation = (start: Point, end: Point) => {
const center = midpoint(start, end);
const k1 = lineSlope(start, end);
const k = k1 === Infinity ? 0 : -1 / k1;
const b = center.y - k * center.x;
return { k, b, x: k1 === Infinity ? start.x : NaN } as Equation;
};
/**
* 求两直线交点
*
* @param l1 - 直线
* @param l2 - 直线
* @returns 两直线交点坐标,平行线返回null
*/
export const lineCrossPoint = (l1: Line, l2: Line) => {
const { k: k1, b: b1 } = lineEquation({ x: l1.x1, y: l1.y1 }, { x: l1.x2, y: l1.y2 });
const { k: k2, b: b2 } = lineEquation({ x: l2.x1, y: l2.y1 }, { x: l2.x2, y: l2.y2 });
if (Math.abs(k2 - k1) < 0.00001) {
return null;
}
const x = (b2 - b1) / (k1 - k2);
return {
x,
y: k1 * x + b1,
} as Point;
};
/**
* 求两线段交点
*
* @param l1 - 线段
* @param l2 - 线段
* @returns 两线段交点坐标,平行线或线段不相交返回null
*/
export const lineSegmentCrossPoint = (l1: Line, l2: Line) => {
// 判断是否有一个线段的两个端点都在另一个线段的同一边
if (
pointOnLine({ x: l1.x1, y: l1.y1 }, l2) * pointOnLine({ x: l1.x2, y: l1.y2 }, l2) === 1 ||
pointOnLine({ x: l2.x1, y: l2.y1 }, l1) * pointOnLine({ x: l2.x2, y: l2.y2 }, l1) === 1
) {
return null;
}
return lineCrossPoint(l1, l2);
};
/**
* 求距离线段端点一定距离的点坐标
*
* @param line - 线段
* @param dis - 线段
* @param inside - 点在线段内
* @returns 两线段交点坐标,平行线或线段不相交返回null
*/
export const disPointOnLine = (line: Line, dis: number, inside = true) => {
const { start, end } = lineToStartEndPoint(line);
const lineLength = distance(start, end);
const rate = dis / lineLength;
const dx = end.x - start.x;
const dy = end.y - start.y;
const pos = inside ? -1 : 1;
const targetPoint = {
x: end.x + dx * rate * pos,
y: end.y + dy * rate * pos,
};
return targetPoint;
};
/**
* 求距离线段上一点一定距离的点坐标
*
* @param line - 线段
* @param dis - 线段
* @param inside - 点在线段内
* @returns 两线段交点坐标,平行线或线段不相交返回null
*/
export const disPointOnLineByEquation = (
equation: Equation,
point: Point,
dis: number,
inside = true,
) => {
const { k } = equation;
const sita = Math.atan(k);
const dx = dis * Math.cos(sita);
const dy = dis * Math.sin(sita);
const pos = inside ? -1 : 1;
const targetPoint = {
x: point.x + dx * pos,
y: point.y + dy * pos,
};
return targetPoint;
};
/** ********角度、三角形相关*********************************************** */
/**
* 计算三角形内角
*
* @param A - 点A
* @param B - 点B
* @param C - 点C
* @returns a、b、c三个角度
*/
export const angleOfTriangle = (A: Point, B: Point, C: Point) => {
const angle = (radian: number) => (180 * radian) / Math.PI;
const a = distance(B, C);
const b = distance(A, C);
const c = distance(A, B);
const angleA = Math.acos((b * b + c * c - a * a) / (2 * b * c));
const angleB = Math.acos((a * a + c * c - b * b) / (2 * a * c));
const angleC = Math.acos((a * a + b * b - c * c) / (2 * a * b));
return {
a: Number.isNaN(angleA) ? 180 : angle(angleA),
b: Number.isNaN(angleB) ? 180 : angle(angleB),
c: Number.isNaN(angleC) ? 180 : angle(angleC),
};
};
/**
* 点到直线距离
*
* @param point - 点
* @param line - 线
* @returns 距离
*/
export const disOfPointToLine = (point: Point, line: Line) => {
const { start, end } = lineToStartEndPoint(line);
const { b: angle } = angleOfTriangle(start, end, point);
const radian = angleToRadian(angle);
const len = distance(point, end);
const dis = len * Math.sin(radian);
return dis;
};
/**
* 计算在一个角的两条边上距离角点固定距离的两个点。(可用来作为画角度圆弧、贝塞尔曲线画圆角的点)
*
* @param A - 角一边的端点
* @param B - 角对应的点
* @param C - 角另一边的端点
* @param d - 距离角点固定距离
* @returns 两个点的坐标
*/
export const angleEquidistantPoints = (A: Point, B: Point, C: Point, d = 15) => {
const AB = distance(A, B);
const CB = distance(B, C);
return {
x1: B.x + (d / AB) * (A.x - B.x),
y1: B.y + (d / AB) * (A.y - B.y),
x2: B.x + (d / CB) * (C.x - B.x),
y2: B.y + (d / CB) * (C.y - B.y),
};
};
/**
* 计算一个角的角平分线上距离角点固定距离的点。(可用来作为显示角的角度数值的点)
*
* @param A - 角一边的端点
* @param B - 角对应的点
* @param C - 角另一边的端点
* @param d - 距离角点固定距离
* @returns 目标点的坐标
*/
export const angleBisectorPoint = (A: Point, B: Point, C: Point, d = 30) => {
// 先求角度等距点
const { x1, y1, x2, y2 } = angleEquidistantPoints(A, B, C);
const p1 = { x: x1, y: y1 };
const p2 = { x: x2, y: y2 };
// 连线中点
const lineCenter = midpoint(p1, p2);
const dis = distance(B, lineCenter);
if (dis === 0) {
const k = -1 / lineSlope(p1, p2);
const sita = Math.atan(k);
return {
x: B.x + d * Math.cos(sita),
y: B.y + d * Math.sin(sita),
};
}
const rate = d / dis;
return {
x: B.x + rate * (lineCenter.x - B.x),
y: B.y + rate * (lineCenter.y - B.y),
} as Point;
};
// 判断弧线是否要翻转
export const isArcReverse = (A: Point, B: Point, C: Point, d = 15) => {
const { start: startPoint, end: endPoint } = lineToStartEndPoint(
angleEquidistantPoints(A, B, C, d),
);
const p_a = startPoint;
const p_b = B;
const p_c = endPoint;
let sitaA = Math.asin((p_b.y - p_a.y) / d);
let sitaC = Math.asin((p_b.y - p_c.y) / d);
if (p_b.x > p_a.x) {
sitaA = Math.PI - sitaA;
}
if (p_b.x > p_c.x) {
sitaC = Math.PI - sitaC;
}
if (sitaA < 0) {
sitaA += 2 * Math.PI;
}
if (sitaC < 0) {
sitaC += 2 * Math.PI;
}
const reverse =
(sitaA > sitaC && sitaA - sitaC < Math.PI) || (sitaC > sitaA && sitaC - sitaA > Math.PI);
return reverse;
};
/**
* 计算一个角的角平分线方程 (y = kx + b)
*
* @param A - 角一边的端点
* @param B - 角对应的点
* @param C - 角另一边的端点
* @returns 角平分线方程的参数 k 和 b
*/
export const angleBisectorEquation = (A: Point, B: Point, C: Point) => {
const ka = lineSlope(A, B);
const kc = lineSlope(C, B);
let radianA = Math.atan(ka);
let radianC = Math.atan(kc);
if (Math.abs(A.x - B.x) === 0) {
radianA = (Math.PI / 2) * (A.y - B.y > 0 ? 1 : -1);
}
if (A.x - B.x < 0) {
radianA -= Math.PI;
}
if (Math.abs(C.x - B.x) === 0) {
radianC = (Math.PI / 2) * (C.y - B.y > 0 ? 1 : -1);
}
if (C.x - B.x < 0) {
radianC -= Math.PI;
}
const radianCenter = (radianA + radianC) / 2;
const k = Math.tan(radianCenter);
const b = B.y - k * B.x;
return { k, b, x: k === Infinity ? B.x : NaN } as Equation;
};
/** ********其他********************************************************* */
/**
* 射线法判断点是否在多边形内
*
* @param points - 多边形的端点坐标数组
* @param p - 带测试是否在多边形内的一个点
* @returns 是否在多边形内
*/
export const pointInPolygon = (points: Point[], p: Point) => {
let intersectCount = 0;
for (let i = 0; i < points.length; i += 1) {
const p1 = points[i];
const p2 = points[i === points.length - 1 ? 0 : i + 1];
const k = (p2.y - p1.y) / (p2.x - p1.x);
const b = p1.y - k * p1.x;
const x_ok = Math.min(p1.x, p2.x) <= p.x && p.x <= Math.max(p1.x, p2.x);
const y_ok = k * p.x + b > p.y;
intersectCount += x_ok && y_ok ? 1 : 0;
}
return intersectCount % 2 === 1;
};
/**
* 把目标矩形区域内的点转换到距离矩形四边一定内边距的范围内(可用于svg绘图strokeWidth被截取、周边留出安全距离等)
*
* @param point - 目标点坐标
* @param padding - 内边距
* @param width - 宽
* @param height - 高
* @returns 在内边距范围内的点
*/
export const handlePadding = (point: Point, padding: number, width: number, height: number) => {
let { x, y } = point;
x = Math.max(padding, x);
x = Math.min(width - padding, x);
y = Math.max(padding, y);
y = Math.min(height - padding, y);
return { x, y } as Point;
};
/**
* 获取固定长线段起始点坐标和角度弧度
*
* @param start - 开始点
* @param end - 结束点
* @param dis - 线段距离
* @returns
*/
export const getLineSegment = (start: Point, end: Point, dis = 20, angle?: number) => {
const sita = lineRadian(start, end, angle);
const x2 = start.x + dis * Math.cos(sita);
const y2 = start.y + dis * Math.sin(sita);
return {
start,
end: { x: x2, y: y2 },
radian: sita,
};
};
/**
* 计算多点组成的矩形的位置和大小信息
*
* @param points - 点
* @returns
*/
export const getPointsBoundingRect = (points: Point[]) => {
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
points.forEach((point: Point) => {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
});
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
};
/**
* 计算三点的贝塞尔曲线控制点
* @param param0
* @param rate 曲率
* @returns
*/
export const getControlPoints = ([start, center, end]: Point[], rate = 0.4) => {
const equ1 = angleBisectorEquation(start, center, end);
const k = -1 / equ1.k;
const b = center.y - k * center.x;
const equ2 = { k, b, x: 0 } as any;
const dis = distance(center, end) * rate;
const sita = angleToRadian(angleOfTriangle(start, center, end).b);
const d = dis * Math.sin(sita / 2);
const inside = true;
const control1 = disPointOnLineByEquation(equ2, center, d, inside);
const control2 = disPointOnLineByEquation(equ2, center, d, !inside);
if (pointOnLineByEquation(start, equ1) !== pointOnLineByEquation(control1, equ1)) {
return [{ ...control2, color: 'red' } as any, control1, [equ2]];
}
return [{ ...control1, color: 'red' } as any, control2, [equ2]];
};
/**
* 拟合曲线
* @param points
* @returns path
*/
export const fittingCurve = (points: Point[], rate = 0.4) => {
if (points.length < 3) {
return '';
}
let path = `M${points[0].x},${points[0].y} `;
const controlPoints: Point[] = [];
for (let i = 1; i < points.length - 1; i += 1) {
const [control1, control2] = getControlPoints([points[i - 1], points[i], points[i + 1]], rate);
controlPoints.push(control1, control2);
}
const ps = points.map((p: Point) => ({ x: p.x.toFixed(1), y: p.y.toFixed(1) }));
const c = controlPoints.map((p: Point) => ({ x: p.x.toFixed(1), y: p.y.toFixed(1) }));
for (let i = 0; i < ps.length - 1; i += 1) {
const n = i * 2 - 1;
if (i === 0) {
path += ` Q${c[i].x},${c[i].y}`;
} else if (i === ps.length - 2) {
path += ` Q${c[n].x},${c[n].y}`;
} else {
path += ` C${c[n].x},${c[n].y} ${c[n + 1].x},${c[n + 1].y}`;
}
path += ` ${ps[i + 1].x},${ps[i + 1].y}`;
}
return path;
};
二、interface接口
// 点
export interface Point {
x: number;
y: number;
}
// 线
export interface Line {
x1: number;
y1: number;
x2: number;
y2: number;
}
// 方程
export interface Equation {
k: number;
b: number;
x: number; // 这个x表示在斜率无限大,也即直线垂直x轴方程为x = n;时 n 的值,正常情况为NaN
}
export interface Triangle {
A: Point;
B: Point;
C: Point;
}