<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="myCanvas" width="1080" height="630" style="border: 1px solid;"></canvas>
</body>
</html>
<script>
const ctx = myCanvas.getContext('2d');
let p1 = [150, 300];
let p2 = [850, 300];
let c1 = [400, 150];
let c2 = [600, 400];
/**
* 在一段三次贝塞尔曲线上均匀取点, 不包括终点
* @param counts 一段贝塞尔曲线中取点的数量
*/
getPntsOf3Bezier = function (p0, p1, p2, p3, counts) {
let per = counts && counts != 0 ? 1 / counts : 0.02; //取点间隔
let points = [];
for (let t = 0; t <= 0.999999; t += per) {
points.push(getPntIn3Bezier(p0, p1, p2, p3, t));
}
return points;
};
/**
* 获取三次贝塞尔曲线上的一点,t∈[0,1]
* @param t 介于0 ~ 1, 表示点在曲线中的相对位置
*/
getPntIn3Bezier = function (p0, p1, p2, p3, t) {
let t_ = 1 - t;
let x = p0[0] * t_ * t_ * t_ + 3 * p1[0] * t * t_ * t_ + 3 * p2[0] * t * t * t_ + p3[0] * t * t * t,
y = p0[1] * t_ * t_ * t_ + 3 * p1[1] * t * t_ * t_ + 3 * p2[1] * t * t * t_ + p3[1] * t * t * t;
return [x, y];
};
function getVctLen([x, y]) { // 模长
return Math.sqrt(x * x + y * y);
};
/**
* 根据点在向量上的比例计算点坐标, [xO, yO]为起点,[xVct, yVct]为向量,k 为该点在向量方向上的长度
* 获取
*/
function getPntInVct([xO, yO], [xVct, yVct], k) {
let lenVct = getVctLen([xVct, yVct]); // 获取向量长度
let stdVct;
if (lenVct === 0) {
stdVct = [0, 0];
} else {
stdVct = [xVct / lenVct, yVct / lenVct]; // 单位向量
}
return [xO + k * stdVct[0], yO + k * stdVct[1]];
};
function getLenOfTwoPnts(p1, p2) {
return Math.sqrt(Math.pow((p1[0] - p2[0]), 2) + Math.pow((p1[1] - p2[1]), 2));
};
vct = function (start, end) {
let x = end[0] - start[0];
let y = end[1] - start[1];
return [x, y];
};
/**
* 在曲线(多点折线, 按点顺序)上取长度为 len 的一点坐标, 包括点真实坐标和曲线点数组中仅次于该点的下标, 0 < k < 1;
*/
calPntInCurveByLen = function (curvePnts, len) {
let lenNodes = curvePnts.length;
let P, index;
let templen = 0, temp = 0;
for (let j = 0; j < lenNodes - 1; j++) {
temp = getLenOfTwoPnts(curvePnts[j], curvePnts[j + 1]);
if (templen + temp > len) {
P = getPntInVct(curvePnts[j], vct(curvePnts[j], curvePnts[j + 1]), len - templen);
index = j;
break;
}
templen += temp;
}
if (!P) {
return null;
}
return [P, index];
};
// 获取两个向量之间的夹角
getRotateAng = function ([x1, y1], [x2, y2]) {
let EPSILON = 1.0e-8;
let dist, dot, cross, degree, angle;
dist = Math.sqrt(x1 * x1 + y1 * y1);
x1 /= dist;
y1 /= dist;
dist = Math.sqrt(x2 * x2 + y2 * y2);
x2 /= dist;
y2 /= dist;
dot = x1 * x2 + y1 * y2;
if (Math.abs(dot - 1.0) <= EPSILON) {
angle = 0;
} else if (Math.abs(dot + 1.0) <= EPSILON) {
angle = Math.PI;
} else {
angle = Math.acos(dot);
cross = x1 * y2 - x2 * y1;
if (cross < 0) {
angle = 2 * Math.PI - angle;
}
}
degree = angle * 180 / Math.PI;
return degree;
};
/**
* 创建向量 (从起点到终点的一个向量)
*/
function vct([x1, y1], [x2, y2]) {
let x = x2 - x1;
let y = y2 - y1;
return [x, y];
};
var len = 0;
ctx.textBaseline = 'middle' // 一定要设置,不然高度平移不一致
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
let points = getPntsOf3Bezier(p1, c1, c2, p2, 100);
points.forEach((p, i) => {
if (i == 0) {
ctx.moveTo(p[0], p[1]);
} else {
ctx.lineTo(p[0], p[1]);
}
})
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(c1[0], c1[1], 5, 0, 2 * Math.PI);
ctx.fill();
ctx.fillText("控制点1", c1[0], c1[1]);
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(c2[0], c2[1], 5, 0, 2 * Math.PI);
ctx.fill();
ctx.fillText("控制点2", c2[0], c2[1]);
ctx.beginPath();
let gpIndex = calPntInCurveByLen(points, len)[1];
let ni = gpIndex + 1;
let gp = calPntInCurveByLen(points, len)[0];
let vct1 = vct(gp, points[ni]);
let vct2 = vct(gp, [gp[0] + 1000, gp[1]]); // 或这直接写 [任意值, 0]也是与X轴平行的向量
let angle = getRotateAng(vct2, vct1);
ctx.save();
ctx.font = `30px Arial`
ctx.translate(gp[0], gp[1]); //设置画布上的(0,0)位置,也就是旋转的中心点
ctx.rotate(angle * Math.PI / 180);
ctx.fillStyle = "#006d75";
ctx.fillText("测", 0, -20);
ctx.restore();
}
// draw()
setInterval(draw, 0);
window.addEventListener("keydown", (e) => {
if (e.keyCode == 38) {
len += 5;
}
if (e.keyCode == 40) {
len -= 5;
}
console.log(len);
})
</script>
<style>
</style> 作者:__SomeBody https://www.bilibili.com/read/cv24251081 出处:bilibili
网友评论